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
0038-shared-site-chrome-and-fully-open-kb
- Reversibility
- two-way door
DEC-0038 — Shared site chrome (SiteHeader + SiteFooter + ThemeToggle) and a fully-open KB surface
Reversibility: two-way door — re-adding the docs gate = restore DOCS_ENABLED=false in vercel.json + the integrations: docsEnabled ? [...] : [] ternary; the shared-chrome components are a pure consolidation, revertible by inlining the header/footer markup back into each page. The durable parts are the standing rule (surfaces fully open by default, chrome single-sourced) and one-place header/footer.
Context
While iterating on the site, the operator gave a standing directive: "keep it all fully open. remove the gate. wire in the same header to graph. yes unify the footer. this should always be a rule unless there is a good reason otherwise. remember, atomic." This establishes a STANDING RULE for the surface layer: surfaces are fully open by default (no conditional hiding) and built atomically (one shared single-source component, never per-surface copies).
It follows directly from earlier same-session work: a shared SiteHeader.astro + ThemeToggle.astro were already extracted (landing + board). This decision completes the consolidation — it adds the graph to the shared header, adds a shared SiteFooter.astro, and removes the docs gate everywhere — and records the standing rule so future chrome-light pages inherit it by default.
The gate being removed is the DOCS_ENABLED landing-only gate from Ship the landing publicly behind a docs-gate flag, capture demand through two honest doors, into a list we own (which gated the Starlight docs/KB surface out of the public production build). Web ingestion — a keyless HttpConnector by default, Firecrawl wired as the premium path, and a first-class CLI web-ingest mode is unrelated web ingestion — the gate's real record is DEC-0022.
Options considered
1. Site chrome — per-page topbar/footer copies vs. one shared single-source component.
- (a) Keep each surface's own
.landing-topbar/.board-topbar/.graph-topbar(+ matching footers). Rejected: three copies of the same header/footer means every brand-bar fix, nav change, or theme-toggle tweak must be re-applied N times and can drift between surfaces — the opposite of single-source-of-truth, and against the operator's explicit "remember, atomic." - (b) Extract one shared
SiteHeader.astro+SiteFooter.astro+ThemeToggle.astro; every surface renders them (chosen).SiteHeader.astrocarries the wordmark + nav (Docs·Board·Graph·GitHub) + GitHub link +ThemeToggle, with optionalcta/badgeprops for per-surface affordances;SiteFooter.astrocarries a<slot>note + the same nav. The three chrome-light surfaces —LandingPage.astro,pages/board.astro,pages/graph.astro— render<SiteHeader/>+<SiteFooter/>; their per-page topbar/footer markup + dead consts were removed. Self-contained scoped styles,--ds-*tokens only, no hex.
2. The docs/KB surface — keep DEC-0022's landing-only gate vs. remove it (fully open).
- (a) Keep
DOCS_ENABLED=falsein production so only the landing (+ capture endpoint) builds. Rejected per the standing directive — and because the board (Decouple the agentic board from DOCS_ENABLED — ship /board publicly in production while the /knowledge reading surface stays gated dark) and graph already expose KB-derived data publicly, so holding the reading surface back is now incongruent: the KB the board/graph project from is hidden while their projections are public. - (b) Remove the gate everywhere — fully open (chosen).
astro.config.mjsALWAYS includes the Starlight docs integration (wasdocsEnabled ? [...] : []);vercel.json'sDOCS_ENABLED=falsebuild env was deleted; every{docsEnabled && …}link/CTA conditional and theprocess.env.DOCS_ENABLEDconsts were removed across the three pages. Atom deep-links and the "Browse the knowledge base" CTA now always resolve.
3. The one exception — leak client demos publicly vs. keep a single justified gate.
- (a) Drop every gate, including
/preview/*. Rejected: client landing demos are not Dossier's to publish on its own public site (sovereignty — the demo's canonical home is the client's own repo, The marketing landing becomes a tailorable per-client template — a typed LandingContent model rendered by LandingPage.astro; the Dossier render stays byte-for-byte identical; client instances are values of the same type, canonical in the client's own repo, generated by the generate-landing skill/Adopt OKF as Dossier's canonical knowledge format). - (b) Keep exactly one gate, with a good reason (chosen).
pages/preview/[client].astrostill emits ZERO pages in production — switched fromDOCS_ENABLED==='false'to!import.meta.env.DEV(getStaticPathsreturns[]outside dev). This is the explicit "deviate only with a good reason" exception the standing rule sanctions.
Decision
Unify the site chrome into shared SiteHeader + SiteFooter + ThemeToggle components rendered by every chrome-light surface, and make the docs/KB surface fully open by removing DEC-0022's DOCS_ENABLED landing-only gate — keeping exactly one justified gate so client demos never ship publicly. This encodes the operator's standing rule: surfaces fully open by default, chrome single-sourced (atomic), deviation only with a good reason.
- Shared chrome (atomic SSOT). New
packages/site/src/components/SiteHeader.astro(wordmark + nav Docs·Board·Graph·GitHub + GitHub +ThemeToggle, optionalcta/badgeprops),SiteFooter.astro(slotted note + the same nav), andThemeToggle.astro.LandingPage.astro,pages/board.astro, andpages/graph.astronow render<SiteHeader/>+<SiteFooter/>; their per-page topbar/footer markup + dead consts were removed. Scoped styles,--ds-*tokens only, no hex. - Fully open (reversing DEC-0022).
astro.config.mjsALWAYS includes Starlight (thedocsEnabledternary is gone);vercel.json'sDOCS_ENABLED=falsebuild env is deleted (the file is now bare{ "$schema": … }); every{docsEnabled && …}conditional andprocess.env.DOCS_ENABLEDconst is removed across the three pages. The full/knowledgereading surface ships. - The one gate kept (good reason).
pages/preview/[client].astroemits zero pages in production viaif (!import.meta.env.DEV) return [](wasDOCS_ENABLED==='false'), so client landing demos stay dev-only. - A real bug fixed en route.
pages/graph.astrohad two literal NUL bytes as edge-dedup separators (~line 117) that broke the Astro compiler's AST parse ("Bad control character in JSON"); replaced with a space (matchingboard.astro). The graph page now compiles and is wired into the shared chrome.
Rationale
- One shared component > three copies, because chrome is identical by intent. The header/footer are meant to be the same everywhere; encoding that as a single component makes the sameness true by construction and means a nav or theme-toggle change lands in one place — directly satisfying "remember, atomic" and single-source-of-truth.
- Fully open is congruent with what already ships. The board (Decouple the agentic board from DOCS_ENABLED — ship /board publicly in production while the /knowledge reading surface stays gated dark) and graph already publish KB-derived data; making the underlying OKF reading surface public is "work in the open" applied consistently, not a new exposure of anything qualitatively private. Sovereignty holds — the OKF git repo stays the source of truth; the rendered site is a derived, replaceable view (Adopt OKF as Dossier's canonical knowledge format, KB-agnostic @dossier/site (renders any tenant's OKF KB) + runtime-driven site rendering + the Node-26 Windows build fix).
- One justified gate, not zero, is the disciplined reading of the rule. "Fully open unless there is a good reason" — and not leaking a client's demo onto Dossier's public site is exactly such a reason (The marketing landing becomes a tailorable per-client template — a typed LandingContent model rendered by LandingPage.astro; the Dossier render stays byte-for-byte identical; client instances are values of the same type, canonical in the client's own repo, generated by the generate-landing skill). Re-pointing it from the now-removed
DOCS_ENABLEDto!import.meta.env.DEVkeeps the gate meaningful after its old flag is gone. - The NUL-byte fix is a correctness prerequisite, not scope creep. The graph page literally could not compile until the control characters were removed; fixing it was the cost of wiring graph into the shared chrome at all.
verified— but scoped precisely (no fabricated status). Verified this session by the FDE:astro checkis clean on every file touched (the only 4 remaining errors are pre-existing and unrelated —src/lib/graph-island.tsmissing ad3-transitionimport; see Consequences / Review). All three chrome-light surfaces render the shared header + footer, confirmed via Playwright againstastro dev:/(landing),/board,/grapheach show.site-header(wordmark + Docs·Board·Graph·GitHub + theme toggle) and.site-footer(note + same nav); the old per-page.landing-topbar/.board-topbar/.graph-topbar(+*-footer) chrome is gone (graph screenshot atscreenshots/graph-shared-chrome-light.png). NOT yet verified: a real PRODUCTION build (DOCS_ENABLEDnow unset → Starlight always builds). In local dev the docs surface was already on, so removing the gate is verified for dev; the prod build that now publishes/knowledgeshould be confirmed on the next deploy.
Consequences
- On the next Vercel deploy, the full
/knowledgeOKF reading surface becomes PUBLIC alongside the landing/board/graph — Dossier's institutional memory is now "work in the open," consistent with the board/graph already exposing KB-derived data. Reversible (restoreDOCS_ENABLED=false+ the ternary). - Header/footer are now edited in one place. Every surface — and future chrome-light pages — inherits the shared
SiteHeader/SiteFooter/ThemeToggleby default, so the standing rule is self-enforcing for new surfaces. - The standing rule is now recorded, not just spoken. "Surfaces fully open by default, chrome single-sourced, deviate only with a good reason" is durable and citable for the next surface decision.
- Open follow-ups (recorded honestly; NOT fixed in this turn):
src/lib/graph-island.tshas 4ts(2339) Property 'transition' does not existerrors becaused3-transitionis imported by neither the file norpackages/site/package.json(only d3-force/selection/zoom/drag are deps). The graph renders, but zoom-button transitions won't animate at runtime. Fix = addd3-transitionas a dep +import 'd3-transition'. This belongs to the graph work-stream (adding a dependency was out of scope this turn). Filed as Add the missing d3-transition dependency + import so graph-island's zoom transitions animate (and astro check reaches zero).- Dead CSS now unused:
.landing-topbar*/.landing-footer*/.landing-wordmark(landing.css),.board-topbar*/.board-wordmark/.board-readonly/.board-footer(board.css),.graph-topbar*/.graph-wordmark/.graph-readonly/.graph-footer(graph.css) — safe to delete in a cleanup pass. (.landing-externalis still used by the landing hero — keep it.) Filed as Prune the now-dead per-page topbar/footer CSS left behind after the shared-chrome consolidation (keep .landing-external). - A prod build should be run to confirm the now-public docs surface builds cleanly before/at deploy. Folded into the verification gate in Review (deploy-time confirmation), not a separate task.
- Two-way vs. durable. Re-adding the gate = restore
DOCS_ENABLED=falseinvercel.json+ theintegrations: docsEnabled ? [...] : []ternary; the shared-chrome components are a pure consolidation, revertible by inlining. The durable commitments are the standing rule and one-place header/footer.
Review
Promote toward a fuller verified once a real production Vercel build has confirmed the now-always-on Starlight docs surface builds cleanly and /knowledge publishes as expected on the next deploy (the one piece verified for dev but not yet for prod). Close the d3-transition follow-up (Add the missing d3-transition dependency + import so graph-island's zoom transitions animate (and astro check reaches zero)) so the graph's zoom transitions animate at runtime and astro check reaches zero errors, and run the dead-CSS prune (Prune the now-dead per-page topbar/footer CSS left behind after the shared-chrome consolidation (keep .landing-external)) so the removed per-page chrome leaves no orphan styles. Re-examine what of the public knowledge/ repo should stay open if any atom is ever judged not-for-public (the standing rule's "good reason" escape hatch, the same discipline as the kept /preview/* gate).