Ch 38 deliverable A: Query state lives in a same-name folder note (Layout B′)
Challenge 38 Deliverable — Query State Lives in a Same-Name Folder Note (Layout B′)
Section titled “Challenge 38 Deliverable — Query State Lives in a Same-Name Folder Note (Layout B′)”- Recommend Layout B′ — a same-name folder-note variant where
_crosswalker/queries/<slug>--<id8>/<slug>--<id8>.mdis the canonical query note,view.baseis its generated sibling, and the folder is the durable envelope for all per-query derivative artifacts. Reject the user’s literalindex.mdproposal: per Obsidian Help (help.obsidian.md/links), Obsidian resolves wikilinks by filename only —[[csf-coverage]]does not resolve tocsf-coverage/index.mdin vanilla Obsidian, so theindex.mdpattern requires the LostPaul Folder Notes community plugin and breaks Commitment #3 (mobile parity, no extra plugin dependency). - Slug-collision policy: opaque
query_idis canonical identity; the slug is a renamable display alias; the folder is named<slug>--<id8>. This makes silent rename-on-duplicate structurally impossible, survives folder renames (the--<id8>suffix is the durable handle), and works with Obsidian’s default “Shortest path when possible” link resolution. - Migrate before Phase 5 starts via a one-shot
Crosswalker: Migrate queries to folder layoutcommand. The current Phase 4.5 flat layout (3 queries in test-vault) is fully regenerable fromcrosswalker_query:frontmatter, so migration is mechanical and idempotent. Layout B′ also pre-positions Phase 5 materialization, v0.1.7 exports, and v0.1.8 audit snapshots as siblings — the per-query state-growth problem the challenge exists to solve dissolves into “add new sibling writers.”
Key Findings
Section titled “Key Findings”-
The challenge is a genuine inflection point, not a cosmetic refactor. Phase 5 (materialization + sparse-pivot guard), v0.1.7 (exporters), and v0.1.8 (audit trail) each generate per-query derivative artifacts. Today’s flat
_crosswalker/views/q-<id>.basedump has no answer for “where does the snapshot/export/audit go?” — every new artifact type would force a new flat folder and a new opaque-id naming scheme. Folder-note layout gives each query a stable home that scales orthogonally to artifact types. -
Vanilla Obsidian forces the folder-note convention to be same-name, not index.md. Per Obsidian Help (“Internal links”,
help.obsidian.md/links):"Wikilink: [[Three laws of motion]] or [[Three laws of motion.md]]"— no mention of index.md or folder resolution. Obsidian resolves internal links by filename without requiring file extensions or full paths. So[[csf-coverage]]resolves to anycsf-coverage.mdanywhere in the vault, but does NOT resolve tocsf-coverage/index.md. The kepanoobsidian-basesSKILL.md documents the canonical embed as bare-basename![[MyBase.base]]. As of May 2026 the forum feature request “Make folder notes a core plugin” (forum.obsidian.md/t/make-folder-notes-a-core-plugin/108760, posted Dec 6 2025) has 21 likes and no staff response or implementation tag — so this is not changing in the v0.1 window. Same-name (<slug>/<slug>.md) is the only plugin-free folder-note pattern that works in vanilla Obsidian Mobile (Capacitor). -
recipe + paramsIS sufficient to regenerate every durable artifact — but five adjacent artifact types are NOT regenerable, and Layout A has nowhere to put them. Non-regenerable items: (a) user prose annotations on a query, (b) frozen audit snapshots (the entire v0.1.8 commitment), (c) hand-edits to.basefilters/views/formulas the user wants to keep acrossRefresh query views, (d) LLM-authored explanations / TODOs, (e) external export artifacts (OSCAL/SSSOM/STRM) at the path the user opens them from. Layout B′ has a sibling location for each; Layout A forces them either into the host note (conflating concerns) or nowhere. -
The host-note frontmatter (current Phase 4.5 model) is not canonical query state — it is an embed pointer with cached metadata. The conceptual shift: a query is not “a note with
crosswalker_query:frontmatter”; a query is “an addressable object in_crosswalker/queries/that a host note may embed.” This unblocks the N-host-notes-embed-the-same-query case the user surfaced (“an embed is just!+ wikilink — it shouldn’t be something that gets checked”). The intuition is correct; B′ is its right implementation. -
The “no separate folder” steelman loses three things Crosswalker has already committed to in the v0.1 architectural-commitments table: (a) Commitment #5, runtime-agnostic recipe schema — the JSON-Schema-validated
recipe + paramsis the portable contract, not the rendered.base; (b) Commitment #2’s single coupling pointrender(Recipe, ConceptIdentity) → Address— needs a typed recipe input, not a YAML output; (c) the upgrade path when Bases YAML evolves — regenerating fromrecipe + paramsis one command; reverse-engineering recipes from hand-edited.basefiles is not. Reject the steelman, but adopt its UX intuition (embeds are just!-wikilinks) into B′.
Details
Section titled “Details”Restatement of the challenge
Section titled “Restatement of the challenge”The Phase 4.5 architecture (shipped 2026-05-15) places canonical query state in crosswalker_query: frontmatter on a user-authored “host note,” generates a .base file at _crosswalker/views/q-<id>.base, and inserts an ![[...]] embed at cursor. Challenge 38 stress-tests this against a folder-note alternative — _crosswalker/queries/<slug>/index.md as canonical, view.base as sibling, room for snapshots/, exports/, materialized/ siblings — and asks whether to migrate before Phase 5 (which generates the first wave of per-query derivative artifacts) starts. The success criteria require resolving (1) regenerability of every state-shaped artifact, (2) UX scoring across the three layouts, (3) the slug-collision policy specifically, (4) Phase 5 / v0.1.7 / v0.1.8 state-growth mapping, and (5) a migration plan from the current flat state.
Regenerability audit
Section titled “Regenerability audit”The challenge brief’s regenerability table is correct as far as it goes, but it omits five practically-important artifact types that the v0.1 commitments imply. Audited list:
| Artifact | Regenerable from recipe + params + data? | Layout-A home today | Layout B′ home |
|---|---|---|---|
.base YAML body | ✅ Yes (deterministic via renderRecipeTemplate()) | _crosswalker/views/q-<id>.base | <slug>--<id8>/view.base |
| Closure cache / Tier 2 mappings table | ✅ Yes (rebuildable cache) | Tier 2 sidecar | Tier 2 sidecar (unchanged) |
| Phase 5 materialized pivot result | ✅ Yes (derivative) | nowhere defined | <slug>--<id8>/materialized/<ts>.json |
| Audit run-log (when run / against what snapshot) | ❌ No — historical event | nowhere | <slug>--<id8>/audit.ndjson |
Bases user hand-edits to .base | ❌ No — Refresh query views overwrites | overwritten silently | <slug>/overrides.base sibling (never overwritten) |
| Per-query prose / TODOs / annotations | ❌ No | conflated with host-note body | <slug>/<slug>--<id8>.md body below frontmatter |
| Snapshots for audit retention (v0.1.8) | ❌ No — point-in-time evidence | nowhere | <slug>/snapshots/<period>/ (frozen md + base + OTS) |
| External exports (OSCAL / SSSOM / STRM) v0.1.7 | ✅ Yes from data, ❌ as named artifacts | nowhere | <slug>/exports/<format>.<ext> |
| Bases view-time state (column widths, sort) | ❌ No (Bases stores in .obsidian/workspace.json) | unchanged | unchanged (out of scope) |
| Multiple shape renderings of same query | ✅ Yes | requires N host notes | <slug>/view-pivot.base + <slug>/view-list.base siblings |
Conclusion: the original challenge-brief claim (“recipe + params regenerates everything durable”) is true of rendered artifacts but understates the durable envelope. The non-regenerable items must live somewhere; the flat layout has no answer; B′ has a sibling-folder answer for every one.
Three-layout UX scorecard
Section titled “Three-layout UX scorecard”| Scenario | A — Flat (current) | B — index.md (user proposal) | B′ — same-name folder note (recommended) | C — Hybrid back-pointer |
|---|---|---|---|---|
| Create new query | ✅ picker writes 2 files | ✅ writes folder + 2 files | ✅ writes folder + 2 files | ⚠️ 3 writes + back-pointer |
| Embed in N other notes | ⚠️ N frontmatter blocks drift | ✅ N ![[slug]] embeds | ✅ N ![[slug--id8]] embeds | ✅ but back-pointer drifts |
Bare [[slug]] works in vanilla Obsidian (mobile) | n/a | ❌ fails — needs Folder Notes plugin | ✅ same-name resolves natively | ✅ |
| Browse all queries | ⚠️ Bases scan for frontmatter | ✅ single folder | ✅ single folder | ⚠️ split |
| Rename a query | ⚠️ touches host frontmatter + .base | ⚠️ folder renames; index.md doesn’t | ✅ folder + <slug>.md rename atomically; Obsidian auto-updates links | ❌ drift |
| Delete a query | ⚠️ orphans .base + frontmatter | ✅ rm folder | ✅ rm folder | ❌ orphans back-pointers |
| Copy a query to another vault | ⚠️ two files, different paths | ✅ folder copy | ✅ folder copy | ❌ multi-location |
| Per-query annotations / TODOs | ❌ conflated with host note | ✅ index.md body | ✅ <slug>.md body below frontmatter | ⚠️ split |
| Backlinks “where embedded?” | ⚠️ via Bases on frontmatter | ✅ Obsidian backlinks pane | ✅ Obsidian backlinks pane | ⚠️ split |
| Re-run / UPDATE picker | ✅ rewrites frontmatter in-place | ✅ rewrites index.md | ✅ rewrites <slug>.md | ⚠️ which source-of-truth? |
| Diff two queries (params over time) | ⚠️ host-note git history pollutes diff | ✅ folder git diff | ✅ folder git diff | ⚠️ split |
| Phase 5 materialization home | ❌ undefined | ✅ sibling | ✅ sibling | ⚠️ split |
| v0.1.7 exports home | ❌ undefined | ✅ sibling | ✅ sibling | ⚠️ split |
| v0.1.8 audit-snapshot home | ❌ undefined | ✅ sibling | ✅ sibling | ⚠️ split |
| Mobile parity (Obsidian Mobile Capacitor) | ✅ | ❌ plugin-dependent | ✅ vanilla Obsidian | ✅ |
B′ wins on every row that A loses, ties B on the rows where B wins, and avoids B’s plugin dependency. C adds drift risk for no UX benefit and is rejected.
Slug-collision policy
Section titled “Slug-collision policy”Recommendation: opaque query_id as canonical identity; slug as renamable display alias; folder name = <slug>--<id8>.
Alternative policies, scored:
- Auto-suffix (
coverage,coverage-2,coverage-3) — surfaces the exact concern the user flagged in the challenge brief (“user creates ‘Coverage’ twice, second silently becomes ‘Coverage-2’, user can’t find it”). Rejected. - Refuse-and-prompt — friction-heavy; the common case (user re-running the picker against an existing query) hits the prompt every time. Rejected.
- Opaque ID with display slug (recommended) — folder is
csf-coverage--a1b2c3d4. Slug is a rename-safe nickname;<id8>is the durable identity. The picker, on duplicate slug, detects the collision via thequery_idindex and shows “Open existing” or “Create new (new id)”. Embeds use![[csf-coverage--a1b2c3d4]]; users typing the bare slug get Obsidian autocomplete to the qualified form. Phase 4.5 already generatesquery_idasq-<date>-<8hex>; B′ just promotes that ID to the folder name suffix.
This policy also resolves the rename case: renaming the folder from csf-coverage--a1b2c3d4 to csf-to-800-53--a1b2c3d4 preserves identity (the --id8 is invariant) and Obsidian’s “automatically update internal links” feature (per help.obsidian.md/links) rewrites every ![[csf-coverage--a1b2c3d4]] in the vault to the new path.
Anti-pattern coverage
Section titled “Anti-pattern coverage”| Anti-pattern from §5 | B′ resolution |
|---|---|
| ”Smart” folder auto-creation when not asked | Picker writes folder only on explicit recipe selection; no daemon |
| Slug collisions silently renamed | Opaque --<id8> suffix; collisions surface as “Open existing?” prompt |
| Folder embeds broken on mobile / Publish | Not relied on — embed targets <slug>--<id8>/view.base directly; folder-name embed ![[<slug>--<id8>]] resolves to the same-name .md, which works in vanilla Obsidian + Mobile + Publish |
| Renames orphan embeds | Obsidian native link-update handles this; folder rename triggers <slug>--<id8>.md rename atomically |
| Hybrid back-pointer drift | B′ has no back-pointers |
| Vault pollution | 10 queries × ~5 mature artifacts = 50 files under _crosswalker/queries/; same count as the flat layout, but organized into per-query folders. Net wash on count; large win on legibility. |
Engaging the “no separate folder” steelman seriously
Section titled “Engaging the “no separate folder” steelman seriously”The steelman: .base file at user-chosen path. No frontmatter, no folder, no _crosswalker/queries/, no recipe storage. Edit = open .base, hand-edit, save.
What it loses:
- Commitment #5 (runtime-agnostic recipe schema) — the recipe is the durable, AJV-validated portable contract; the rendered
.baseis one of N renderings. Throwing away the recipe means throwing away the multi-runtime story (Python producer v0.5+, MCP server, agent emitters). - The
render(Recipe, ConceptIdentity) → Addresssingle coupling point (Commitment #2) — requires a typed recipe input. Without it, every Bases YAML schema change requires manual.baseedits across the vault. - Phase 5 / v0.1.7 / v0.1.8 derivatives have nowhere per-query to live. They’d be flat-bagged in
_crosswalker/materialized/,_crosswalker/exports/,_crosswalker/audit/— exactly the failure mode Layout A already has. - Re-render after upstream change is impossible. When the
crosswalker-pivotview evolves (newcellOpvalues, newemptymodes), the plugin needs to regenerate YAML bodies. Without a recipe + params source-of-truth, the only path is “ask the user to re-author.” - The user’s “an embed is just
!+ wikilink” intuition is fully served by B′, which is why B′ is the right interpretation of that intuition, not the steelman.
The steelman is correct that the current Layout A is over-engineered for what it delivers (host-note frontmatter conflates roles, no answer for derivatives). The fix is to move canonical state into a proper home (B′), not to strip the recipe layer.
Phase 5+ state-growth map onto Layout B′
Section titled “Phase 5+ state-growth map onto Layout B′”Every Phase 5+ artifact has a stable home that didn’t exist in Layout A. Future v0.3 (multi-vault federation), v1.0 (companion plugins, marketplace) needs all fit as additional siblings without architectural change.
Migration plan (Phase 4.6 sub-phase, ~1 day before Phase 5)
Section titled “Migration plan (Phase 4.6 sub-phase, ~1 day before Phase 5)”The current Phase 4.5 state is fully regenerable from crosswalker_query: frontmatter — the entire design point of Phase 4.5. Migration is therefore mechanical:
- Add command
Crosswalker: Migrate queries to folder layout. - Scan the vault for notes with
crosswalker_query:frontmatter (reuseregenerate-query-views.ts’s walker; 359 existing tests guard this code path). - For each: derive
<slug>(kebab-case, ASCII, max 48 chars, from the recipenamefield with a fallback toquery-<id8>); create_crosswalker/queries/<slug>--<id8>/; write<slug>--<id8>.mdwith the existing frontmatter copied verbatim (preserve any user prose body if the host note was Crosswalker-authored, else create a fresh.mdwith a transclusion line![[<slug>--<id8>/view.base]]); regenerateview.baseat the new sibling. - Rewrite embeds: scan each source host note for the matching
![[_crosswalker/views/q-<id>.base]]embed and replace with![[<slug>--<id8>]](resolves to the canonical.md, which in turn transcludesview.base). - Backward-compat read for one minor version: the picker’s UPDATE flow checks the new location first, falls back to the old host-note frontmatter, and auto-migrates that query on next save. Old
.basefiles in_crosswalker/views/stay during transition; a v0.2 cleanup command removes them after explicit confirmation. - Schema bump:
crosswalker_query.schema_version: 1 → 2records the canonical-location change.
Test-vault impact: 3 queries already exist. Migration is one command, idempotent, runs in <1s. No user-facing scripts.
Acceptance criteria (reviewer sign-off):
- After migration,
Crosswalker: Refresh query viewsis a no-op on second run (idempotent). ![[<slug>--<id8>]]renders the folder note, which transcludesview.base;![[<slug>--<id8>/view.base]]renders the base directly. Both work on Obsidian Mobile (Capacitor) with no Folder Notes plugin installed (this is the critical mobile-parity check).- Slug collision: creating two queries that would slug to
coverageproduces two folderscoverage--<id-a>andcoverage--<id-b>. Picker on the second attempt offers “Open existing” with a preview. - All 359 existing Phase 4.5 tests pass with the new write target; new tests added for slug derivation, collision handling, and migration idempotency (target: +30 tests).
- Phase 3.5c trace-IDs thread through the migration command, so a failed migration is diagnosable from
crosswalker-debug.logper the observability commitment.
Recommendations
Section titled “Recommendations”Stage 1 — Before Phase 5 starts (Phase 4.6, ~1 day work)
- Implement Layout B′ + migration command per the plan above.
- Add to
query-frontmatter-schema.ts:query_idformatter (q-<YYYY-MM-DD>-<8hex>, already exists), slug derivation rule (kebab-case ASCII ≤48 chars), folder-name composer (<slug>--<id8>). - Update
apply-query-to-note.tsto write_crosswalker/queries/<slug>--<id8>/<slug>--<id8>.mdinstead of host-note frontmatter; insert![[<slug>--<id8>]]embed at cursor. - Update
regenerate-query-views.tsto walk_crosswalker/queries/**/*.mdfor canonical sources. - Update SKILL.md to teach the convention: “create a query → embed the resulting
![[<slug>--<id8>]]link wherever you want it shown.” - Add CSS hook
.crosswalker-generatedtoview.base(not to the canonical.md— users can freely edit its body).
Stage 2 — During Phase 5
- Define
<slug>/materialized/write target in the materialize command (per Ch 32a’s materialization-folder spec, scoped down to per-query). - Add
crosswalker.staleSinceto the canonical<slug>--<id8>.mdfrontmatter (computed fromsourceHashof the input junction set).
Stage 3 — v0.1.7 / v0.1.8
- Wire
<slug>/exports/(v0.1.7) and<slug>/snapshots/+<slug>/audit.ndjson(v0.1.8) into Layout B′ — new sibling writers, no architectural change.
Benchmarks that would change these recommendations
- If Obsidian ships built-in folder-note semantics (currently the forum feature request
forum.obsidian.md/t/make-folder-notes-a-core-plugin/108760, posted Dec 6 2025, 21 likes, no staff response as of May 2026): switch to the cleaner literal<slug>/index.mdsince[[<slug>]]would resolve natively; the--<id8>suffix on the folder becomes optional aesthetic. Low-priority watch item. - If user testing surfaces that the
<slug>--<id8>suffix is too ugly in the file explorer: ship a CSS snippet that hides everything after--in.nav-file-title-content. Identity stays in the filesystem; display is cosmetic. ~5 lines of CSS. - If project later relaxes Commitment #3 to allow a community-plugin dependency, the LostPaul Folder Notes plugin (325,963 total downloads, 677 GitHub stars, v1.8.19, per
obsidianstats.com/plugins/folder-notes, retrieved May 2026) becomes the path of least resistance and lets us switch toindex.md. Don’t relax #3 just for this. - If Phase 5 materialization volume per query exceeds ~100 snapshots: introduce
<slug>/snapshots/YYYY/year-grouping. Not needed at v0.1.
Caveats
Section titled “Caveats”- The same-name convention’s correctness depends entirely on Obsidian’s wikilink resolver. Per Obsidian Help “Internal links” (
help.obsidian.md/links), Obsidian resolves wikilinks by filename without requiring extensions or full paths:"Wikilink: [[Three laws of motion]] or [[Three laws of motion.md]]". The--<id8>suffix on every Crosswalker-generated note makes duplicate-basename collisions structurally impossible; if a user does manually create a collision, Obsidian’s default “Shortest path when possible” setting handles disambiguation, and the plugin’s recipe-picker uses the path-qualified form in autocomplete. - Bases folder-embed semantics are filename-based, not folder-based.
![[<slug>--<id8>]]does not embed the folder; it embeds the same-name.mdfile. The generated.mdtemplate therefore must contain a transclusion line![[<slug>--<id8>/view.base]]if the user wants the rendered Bases view inline when they embed the folder note. This is one extra line in the template. - Phase 4.5 was “the right design” per the project’s commitment table. This deliverable argues that interpretation was correct on the frontmatter-canonical axis but wrong on the host-note-is-the-home axis. The frontmatter stays canonical; it just lives on a query-owned note (
<slug>--<id8>.md), not a user-authored host note. Phase 4.5 is not reverted; it is re-homed. - Bases view-time state (column widths, per-view sort/filter) is stored by Obsidian Bases in
.obsidian/workspace.json, not in the.basefile. This is out of Crosswalker’s control and out of scope for this deliverable; it does not affect the recommendation. - The challenge brief flags Phase 4.5 as “current state being stress-tested, not locked-in.” This deliverable explicitly recommends re-homing it. The recommendation should be re-confirmed with the user before the Phase 4.6 migration ships, per the brief’s own “How to use the deliverable” workflow (read → decide → synthesis log → refactor before Phase 5).
- The user’s literal
index.mdproposal is not wrong — it is plugin-dependent. If the project later relaxes Commitment #3 to allow a community-plugin dependency (LostPaul Folder Notes is stable and widely-adopted at 325,963 downloads per Obsidian Stats),index.mdbecomes viable and slightly cleaner. Until then, same-name (<slug>.md) is the only mobile-safe path. - One concession: B′ adds a per-query folder where Layout A had two flat files. For users with very few queries (≤3), the folder structure feels heavier. Mitigation: the v0.1.6 default folder location is
_crosswalker/queries/(already gated behind the_crosswalker/excluded-files prompt), so the perceived footprint is the same as today.
Completion table
Section titled “Completion table”| Plan item | Covered |
|---|---|
| Fetch primary challenge page | ✅ |
| Project root / agent-context section | ✅ |
| Adjacent challenges & research (Ch 32a) | ✅ |
| Phase 4.5 briefing as current state | ✅ |
| “Query state” definition (crosswalker_query frontmatter + .base) | ✅ |
| “Folder note pattern” — vanilla Obsidian resolver limits (subagent) | ✅ |
| Architectural commitments (mobile parity, runtime-agnostic recipe) | ✅ |
| Regenerability audit with 5 added artifact types | ✅ |
| 3-layout scorecard + B′ refinement + steelman engagement | ✅ |
| Slug-collision policy (3 options scored, recommendation made) | ✅ |
| Phase 5 / v0.1.7 / v0.1.8 state-growth mapping | ✅ |
| Migration plan with acceptance criteria | ✅ |
| Staged recommendations + benchmarks to revisit | ✅ |
| Caveats including Phase 4.5 lock-in status | ✅ |