Link to the split @dossier/docs surface dev-origin-aware (same-origin in prod, docs dev origin in dev) via an SSOT $lib/docs module — NOT a Vite dev proxy, which two Vite dev servers cannot share one origin for

0048-dev-aware-docs-origin-linking-no-dev-proxy

decision read as Explain confidence verified status active 2026-06-17 owner sveltekit-engineer
Reversibility
two-way door

DEC-0048 — Dev-aware linking to the split @dossier/docs surface (no dev proxy)

Reversibility: two-way door — additive and revertible: $lib/docs is a thin SSOT helper, and reverting to bare /knowledge/ literals (or, if Vite ever supports isolated proxy module namespaces, a dev proxy) is a small change. The durable commitments are (a) NO dev proxy between two Vite servers (the module-namespace collision is structural, not a config bug) and (b) ONE source of truth for the docs origin ($lib/docs), never a duplicated /knowledge/ literal.

Decided + built this turn by the Principal SvelteKit Engineer, a direct follow-on to Migrate chrome-light app surfaces to SvelteKit; docs stay on Astro/Starlight (two apps, one origin) (the split that created two apps on one origin). Recorded automatically in the same turn (the standing rule). confidence: verified — built, type-checked, and the dev links were exercised live against the docs dev server (both 200).

Context

Migrate chrome-light app surfaces to SvelteKit; docs stay on Astro/Starlight (two apps, one origin) split the surface into two apps on one Vercel origin: the SvelteKit @dossier/app owns /, /board, /graph, the preview, and /api/subscribe; the Astro/Starlight @dossier/docs serves the reading surface/knowledge/* plus the top-level KB atom routes (/decisions/*, /tasks/*, /roles/*, /model/*, /references/*, /log/, /mission/). In production the unifier is the guarded catch-all rewrite in packages/app/vercel.json (proxies every path except the app's own namespace to the docs deploy — DEC-0043's corrected mechanism), so a same-origin /knowledge/ link resolves.

But that rewrite is a Vercel production construct. In dev the two apps run as two separate dev servers on two origins (the app, and Astro on localhost:4321); there is no rewrite. So a same-origin /knowledge/ link 404s on the app dev server. The operator hit exactly this: "local docs arent working. i get 404 going to /knowledge." Every place the app links into the docs surface — the shared SiteHeader/SiteFooter "Docs" nav link (fully open by Site chrome unified into shared SiteHeader + SiteFooter + ThemeToggle components, and the docs/KB surface made fully open — DEC-0022's DOCS_ENABLED landing-only gate is removed so /knowledge ships publicly alongside the landing/board/graph), the /graph node deep-links, the graph empty-state CTA — was emitting a bare /knowledge/… (or /decisions/…) path that only resolves in prod.

Options considered

1. How dev resolves the cross-app docs links — a Vite dev proxy vs. linking the docs origin directly.

  • (a) A Vite dev server.proxy forwarding /knowledge (+ the atom prefixes) to the docs dev origin (localhost:4321). This mimics the prod rewrite in dev so links could stay bare same-origin paths. Tried and REJECTED: it served the Astro HTML, but the proxied page's Astro dev module graph (/@fs/.../*.astro?astro&type=script, /@id, the Starlight component scripts) was then requested from the APP origin and failed the app's oxc transform — [plugin:vite:oxc] Transform failed … TableOfContents.astro. Two Vite-based dev servers cannot share one origin: their dev module namespaces collide (each expects to own /@fs, /@id, ?astro&type=script, etc. on its own origin). This is structural, not a config bug. The proxy was reverted.
  • (b) Link to the docs ORIGIN directly in dev, same-origin in prod (CHOSEN). No proxy. The app emits docs links prefixed with the docs origin in dev and bare same-origin in prod, so each dev server owns its own origin and module namespace; nothing is proxied through the app server.

2. How the dev-vs-prod base is expressed — scattered conditionals vs. one SSOT module.

Decision

Link to the split @dossier/docs surface dev-origin-aware — the docs dev origin in dev, same-origin in prod — through a single SSOT module $lib/docs; do NOT run a Vite dev proxy between the two apps (two Vite dev servers cannot share one origin).

  • SSOT modulepackages/app/src/lib/docs.ts exports:
    • docsBase: string = import.meta.env.DEV ? (import.meta.env.VITE_DOCS_ORIGIN ?? 'http://localhost:4321') : '' — the docs dev origin in dev, empty (same-origin) in prod. VITE_DOCS_ORIGIN overrides the default port.
    • docsUrl(path = '/knowledge/')${docsBase}${path}, a link to any docs path defaulting to the KB root.
  • Wiring (every app→docs link goes through $lib/docs):
    • SiteHeader.svelte + SiteFooter.svelte — the "Docs" nav link → docsUrl('/knowledge/') (was a duplicated literal '/knowledge/' in both; now one source).
    • $lib/server/graph-view.ts buildGraphView — atom deep-links (the node path and the no-JS fallback route) resolved via a docsRoute() helper that prefixes docsBase. Prod output is byte-identical (docsBase=''); the prerendered /graph is unchanged. In dev, graph node links point at the docs dev origin and resolve.
    • GraphExplorer.svelte — the empty-state "Browse the knowledge base" CTA → docsUrl(docsHref).
  • No dev proxypackages/app/vite.config.ts is reverted to no proxy, carrying a NOTE comment explaining the two-dev-servers-one-origin problem and pointing at $lib/docs.

Rationale

Consequences

  • Local dev now requires BOTH servers running. Run root pnpm dev (parallel — pnpm -r --parallel dev) or pnpm --filter @dossier/docs dev (Astro on 4321) alongside the app. If the docs server is down, the docs links simply fail to load against localhost:4321 — which is honest (the surface genuinely isn't running) and strictly better than the prior silent app-origin 404. This operational requirement is not yet documented in a root dev script note or README → filed as Document that local dev needs BOTH servers running (app + @dossier/docs on :4321) — root dev-script note or README.
  • One source of truth for the docs origin. Any future change to the docs dev origin/port, or the prod base, is a one-line edit in $lib/docs; the previously-duplicated /knowledge/ literal is gone.
  • The graph surface's deep-links resolve in dev too. /graph node links + the no-JS fallback index now point at the docs dev origin in dev (and stay same-origin in prod), so clicking a node in dev reaches the docs reading surface instead of 404ing.
  • No dev proxy is the standing answer for app→docs in dev. The vite.config.ts NOTE records why, so the proxy isn't re-attempted; if Vite ever isolates per-proxy module namespaces this could be revisited (two-way door).
  • Verification (reproduced this turn, real values):
    • App landing "Docs" href = http://localhost:4321/knowledge/; /graph node path = http://localhost:4321/decisions/0001-adopt-okf/both 200 on the docs dev server; no Vite error overlay (the rejected proxy's [plugin:vite:oxc] failure is gone).
    • pnpm check (svelte-check) clean; full vitest suite 389 passed.
    • Production safety confirmed by construction: import.meta.env.DEV → false in the prod build dead-code-eliminates the dev origin; docsBase='' keeps prod same-origin and /graph prerender byte-identical.

Review

confidence: verified: built and exercised this turn — the app→docs links resolve against the docs dev server (Docs → :4321/knowledge/, graph node → :4321/decisions/0001-adopt-okf/, both 200), the rejected proxy's oxc-transform failure is gone, pnpm check is clean, and the suite is 389 passed. Production same-origin behavior is guaranteed by construction (import.meta.env.DEV dead-code-elimination + docsBase='') and is identical to the DEC-0043 prod rewrite path already verified live; no separate prod gate is open on this record. The one open follow-up — documenting the both-servers-in-dev requirement — is tracked by Document that local dev needs BOTH servers running (app + @dossier/docs on :4321) — root dev-script note or README and is not a gate on this decision. See Migrate chrome-light app surfaces to SvelteKit; docs stay on Astro/Starlight (two apps, one origin) and Site chrome unified into shared SiteHeader + SiteFooter + ThemeToggle components, and the docs/KB surface made fully open — DEC-0022's DOCS_ENABLED landing-only gate is removed so /knowledge ships publicly alongside the landing/board/graph.