offplan · online
Workstream · repo-as-canonical-store

Repo as canonical store — vault polish

Obsidian vault polish: .obsidian/ baseline, INDEX.md MOCs, wikilink-to-MD normalisation, CLAUDE.md split into docs/conventions/.

Activeworkstreamrepo-as-canonical-storepriority P1
Owner
roman
Created
2026-05-11
Updated
2026-05-11
Plan
repo-as-canonical-store
Priority
P1
Tags
obsidian, docs, polish

Goal

Ship the Obsidian vault layer agreed in plans/repo-as-canonical-store.md: committed .obsidian/ workspace baseline, per-folder INDEX.md MOCs, link normalisation audit (zero [[wikilink]] in source files), and the CLAUDE.md split into docs/conventions/* so the directive file stays lean. Blocked until the flip workstream ships and proves the new architecture works daily.

Tasks

What's Next

P10 + P11b shipped 2026-05-11 on branch vault/p10-p11b. P11a shipped 2026-05-11 on vault/p11a-regen-indexes. P12 shipped 2026-05-11 on vault/p12-audit-links-and-conventions (closes F7 of forge-vault-setup). P11e shipped 2026-05-11 on vault/p11e-about-pages (62 about-pages, directory-style URLs at <slug>/, index card hrefs flipped).

Workstream remains active. Remaining tasks:

Key Context

About-page brief (P11e, surfaced 2026-05-11)

Roman: "Each item, when you click it on the index, should drop into a page dedicated to that item — explaining what it is, the history of how it evolved, where it is today, and the sessions that contributed. So we don't try to put all that information into the index card; we just open into a richer landing first." The repo is a graph of cross-referenced artefacts; the about-page surfaces the graph without bloating either the index or the source render.

Page placement

Page sections (in order)

  1. Hero — back-to-index breadcrumb, status pill + kind pill + group pill + LATEST chip if applicable, title (h1), one-line summary (the <meta name="summary"> text in lede style).
  2. At a glance — small dl block: status (+ ratified date/score if known), owner, source file path (e.g. plans/repo-as-canonical-store.md), tags, supersedes (if any). Quiet, monospace-y, dense.
  3. Primary CTA[ Open the full document → ] button linking to the render itself. Above the fold on every viewport.
  4. How it evolved — list of version bumps. Source: the ## Changelog block in the underlying .md (the convention introduced in plan v1.2). Each entry shows version + date + change_summary line + (optionally) commit link. If no ## Changelog, fall back to "Created <date>".
  5. Sessions that contributed — reverse-scan docs/sessions/*.md frontmatter for entries whose plans_touched / workstreams_touched / adrs_touched array contains this artefact's slug. Each row: session id · date · session.summary (truncated). Legacy CONV-* sessions surface here only if their content mentions the slug (relaxed match — relies on free-text, accept some false positives) or if backfill ever lands. Order: most-recent first.
  6. Earlier iterations in this group — list of sibling files in the same <meta name="group">, sorted latest-first. Already-superseded ones get the dashed-border treatment. Single-file groups omit this section.
  7. Related — outbound references from this artefact: supersedes, plan→workstream(s), workstream.blocked_by, plan.parent or plan.successors, etc. Surface what's in the frontmatter; don't invent linkages.
  8. Footer — generation timestamp + source-of-truth note ("Generated from <filename>. Edit the source file to update this page.").

Data sources (no Notion; all local)

Visual register

Out of scope for P11e (defer)

Search brief (P11f, surfaced 2026-05-11)

Roman: "We need a really good memory and we share it with search. The simplified version without taking too much time." Memory shipped today (.claude/memory/ + docs/conventions/memory.md); search is the remaining piece. The intent is a fast, infra-free way to find any artefact across the repo from preview.offplan.online — keyword-only is fine at our corpus scale (~200 artefacts at saturation); semantic / vectorised search is overkill and adds infra dependencies we don't want.

Before /build: run /plan

This brief is interview material, not an implementation spec. Roman's explicit ask: "remind to actually plan it properly and not just to do it." Search seems simple but has more design surface than the matcher choice suggests — the result-row UX, the scoring heuristics, and the keyboard ergonomics are where this either delights or feels generic. Run /plan plans/preview-search.md (or similar slug) first; interview against the open questions below; ratify; then /build. Don't skip the interview just because the brief reads close to a spec — the open questions below are real design calls, not boilerplate.

Why this shape (rationale)

Three distinctions matter for design intent. Hold them in mind during the /plan interview:

1. Search vs memory vs navigation are different needs.

Need "What did we decide that's still true?" "Show me everything in this group" "I remember a phrase but not where"
Solved by .claude/memory/ + ADRs + conventions index page + about-pages (P11e) search (P11f)

Don't let search creep into solving the other two — it becomes worse at all three. Search is for finding something you half-remember.

2. Keyword-only isn't a compromise — it's the right answer at this scale. At ~200 artefacts saturation, keyword returns in <100ms on a phone and handles ~95% of "find that thing about Y" queries. Embeddings would buy: marginally better matches on conceptual queries you can't keyword. Cost: vector store, embedding API, latency, cold-start, infra to maintain. If we were at GUPPI's scale (thousands of sessions, same idea restated session after session), the calculus inverts — at offplan's scale, it doesn't.

3. The real UX work is the result rows, not the matcher. Two implementations of the matcher (homegrown ~50-line scoring vs vendored fuzzysort) are roughly indistinguishable from a user perspective. What actually makes search feel great:

Spend ~30% of build time on the matcher, ~70% on result-row UX. That ratio is the design call; the rest is mechanics.

Output

  1. scripts/build_search_index.py — emitted at build time alongside build-rendered-index.py. Scans every .md in plans/, workstreams/, docs/sessions/, docs/decisions/, docs/conventions/, docs/learnings/ + every .html in docs/rendered/. Outputs docs/rendered/search/index.json (one entry per artefact).
  2. docs/rendered/search/index.html — static HTML page with vanilla-JS keyword matcher. Lives at preview.offplan.online/search/; loads the JSON manifest at page boot.

JSON manifest fields (per entry)

Search UI

Matcher

Two acceptable implementations — author's choice:

  1. Homegrown ~50-line substring matcher — splits query into tokens, scans title + summary + headings + body_excerpt + tags, scores by token count + field weight (title 3×, summary 2×, body 1×). Cheapest; no dependency.
  2. fuzzysort (single-file vendored library) — fuzzy matching with score-based ranking. Slightly more polish; ~5KB minified vendored into the page.

Either is fine at offplan corpus scale (<1MB JSON manifest, <100ms search across 200 entries on a phone).

Visual register

Same as the index page and the about-pages: Skeleton White surface, Helvetica Neue 200-300 display, Inter body, JetBrains Mono mono, oxidised pills. Search-box styled like an input on the brand surface — no rounded-bubble Spotlight clone, more like a clean text field with a thin border.

Composition with P11e

Search results link to the about-page when P11e exists (the about-page is the canonical landing); fall back to the direct render until then. The href field in the manifest is computed at build time based on whether <slug>/index.html exists.

Composition with P11b

build_search_index.py reads the same HTML output that render_md.py (P11b) produces — the <meta> tags are the contract. If P11b's meta-emit changes, P11f's indexer follows.

Open questions for the /plan interview

These are real design calls, not boilerplate. The /plan author should interview Roman through each before writing the spec:

Out of scope for P11f (defer)

Session Log