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
- 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.proxyforwarding/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.
- (a) Inline an
import.meta.env.DEVcheck at each link site. Rejected: the docs base would be duplicated acrossSiteHeader,SiteFooter, the graph builder, and the graph empty state — the same drift the shared-chrome consolidation (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) and the SSOT bar (Dossier — The Knowledge Model (v0)) exist to prevent. (The "Docs" link was already a duplicated literal'/knowledge/'in bothSiteHeaderandSiteFooter.) - (b) One SSOT module
$lib/docs(CHOSEN).packages/app/src/lib/docs.tsexportsdocsBaseanddocsUrl(path); every link site imports from it. One place decides the dev-vs-prod base; the previously-duplicated/knowledge/literal collapses to one source.
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 module —
packages/app/src/lib/docs.tsexports: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_ORIGINoverrides 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.tsbuildGraphView— atom deep-links (the nodepathand the no-JS fallbackroute) resolved via adocsRoute()helper that prefixesdocsBase. Prod output is byte-identical (docsBase=''); the prerendered/graphis 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 proxy —
packages/app/vite.config.tsis reverted to no proxy, carrying a NOTE comment explaining the two-dev-servers-one-origin problem and pointing at$lib/docs.
Rationale
- It fixes exactly what the operator hit. A bare same-origin
/knowledge/404'd on the app dev server because the prod rewrite doesn't exist in dev; linking the docs origin directly in dev makes the link resolve against the server that actually serves it. - The proxy was the obvious fix and it was genuinely tried — and it cannot work between two Vite servers. Proxying the docs through the app origin pulls Astro's dev module graph onto the app origin, where the app's oxc/Vite pipeline tries (and fails) to transform
.astromodules. The collision is between two Vite dev servers each owning the same virtual-module namespace on one origin — structural, not tunable. Recording the rejected option (with the exact[plugin:vite:oxc] … TableOfContents.astrofailure) so no future agent re-attempts the proxy and re-hits the same wall. - SSOT over scattered conditionals (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 / Dossier — The Knowledge Model (v0)). One module owns the docs base; the "Docs" link that was already duplicated across
SiteHeaderandSiteFooternow has one source, and the graph builder and empty state share it too — a nav or origin change lands in one place. - Fully-open holds, just dev-origin-aware (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 Docs link stays open and visible on every chrome-light surface; this changes only how the href resolves per environment, not whether the link shows.
- Production is provably untouched.
import.meta.env.DEVfolds tofalsein the prod build, so the'http://localhost:4321'dev-origin string is dead-code-eliminated and never ships;docsBaseis''in prod, so every link stays same-origin (the vercel rewrite serves it) and the prerendered/graphis byte-identical to before — the durable Adopt OKF as Dossier's canonical knowledge format sovereignty property (a derived, replaceable surface) is unchanged.
Consequences
- Local dev now requires BOTH servers running. Run root
pnpm dev(parallel —pnpm -r --parallel dev) orpnpm --filter @dossier/docs dev(Astro on4321) alongside the app. If the docs server is down, the docs links simply fail to load againstlocalhost: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.
/graphnode 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.tsNOTE 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/;/graphnodepath=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 → falsein the prod build dead-code-eliminates the dev origin;docsBase=''keeps prod same-origin and/graphprerender byte-identical.
- App landing "Docs" href =
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.