Design: Docs browser enhancements (layout, hash routing, chunks)
Date: 2026-05-05 Status: Approved (implementation plan: 2026-05-05-docs-browser-enhancements.md)
Relationship to prior spec
2026-05-04-docs-browser-design.md remains the record of the initial /docs/ SPA shape. This document supersedes it for the items below (routing model, sidebar side, bundle shape, and related UI). Implementation should treat this spec as authoritative for those areas once approved.
Goals
- Layout: Documentation outline on the left, article on the right (LTR). Independent vertical scrolling for outline vs article.
- Navigation chrome: Icon-only close control to collapse the sidebar; hamburger (or equivalent) top-left, always available when the sidebar is collapsed so the tree can be reopened on desktop as well as mobile.
- Tree UX: Collapsible directory nodes; folder icons with open/closed state; documents as a list with document-style markers (not plain uppercase folder labels only).
- Visual consistency: Color and surface treatment aligned with the admin SPA (shared tokens or a small shared stylesheet; avoid duplicating large unrelated admin UI).
- Mermaid: Lazy-loaded library (
dynamic import()), invoked only when the current page contains Mermaid blocks. - Bundles: Option 1 — Vite/Rollup code-split per document (lazy virtual modules or equivalent static import graph) so production ships many hashed
assets/*.jschunks plus a small shell; no runtime Markdown compilation and no new Worker routes for docs. - Front matter: Not rendered in the article HTML; parsed at build time and exposed as structured data per page (e.g. alongside title/HTML in the lazy module, and optionally mirrored on the
<article>asdata-*for future UI). - Links: Relative links between docs resolve to in-app hash URLs; behavior matches contributor expectations (including extensionless and
../cases where feasible). - Deep links / refresh: Hash-based app state so a cold load of
/docs/with#/…always boots the docs shell and opens the correct doc without relying on pathname deep routes or multi-SPA pathname fallback.
Non-goals
- Search and authenticated docs (unchanged from v1).
- SSR for docs (still client-rendered HTML from build pipeline).
Routing and URL shape
- Shell URL:
/docs/(trailing-slash policy unchanged from repo conventions). - Document identity in the browser:
location.hash, notpathname. - Canonical fragment:
- Base:
#/<routeKey>where<routeKey>is the same logical key as today (repository path underdocs/without thedocs/prefix and without.md, POSIX/segments). Example:#/guides/admin/installation. - In-document target (optional):
#/<routeKey>::<slug>where<slug>matches the generated headingidin the rendered HTML (or an agreed slug algorithm). Only the first::in the fragment splits route vs slug; the route segment must not contain::(route keys are file paths and do not use::).
- Base:
- Heading
idgeneration: Any pipeline step that assignsidattributes from headings (or from link targets) must strip or replace::so slugs never contain the delimiter, avoiding ambiguity with the fragment parser. - Same-page links that are pure fragment references (
#some-id) on the current document continue to scroll within the article as usual; cross-doc links use the#/<routeKey>::<slug>form when a heading is targeted. - Events: Sync route state from
hashchangeandpopstate(and initial load).navigateToRouteKeyupdateslocation.hash(and replaces history as appropriate) so copy-paste preserves state. - Legacy path URLs (optional implementation nicety): if a user hits an old pathname-style
/docs/guides/..., the app mayreplaceto/docs/#/guides/...once; not required for correctness of new links.
Markdown pipeline
- Front matter: Parse (e.g.
remark-frontmatteror equivalent), remove from the tree before HTML serialization so it never appears in prose output; attach parsed object (or YAML string + parsed JSON) to the per-page payload for future use. - Internal links: Rewrite relative
.md/ extensionless intra-repo links to#/<resolvedRouteKey>or#/<resolvedRouteKey>::<slug>per rules above; align withweb/docs/build/markdown.tsresolver behavior and add tests for../,./, and README-style paths. - Mermaid: Unchanged marker in HTML (e.g.
.mermaid-doc); client loads Mermaid only when needed.
Option 1 — Production bundle shape (no new server code)
- Build: The Vite docs plugin (or companion codegen) emits modules such that the client uses lazy
import()perrouteKey(or a generated static map of such imports) so Rollup emits one chunk per page (or per explicitly grouped pages if later optimized). - Deploy: Same as today:
npm run buildproducesdist/docs/index.html,dist/assets/<hash>.jsfor the shell and additionaldist/assets/<hash>.jsper lazy chunk. CI copiesdist/assets/,dist/docs/,dist/admin/into_bundle/public/as today. - Worker: No new routes or logic required for docs: static
ASSETS.fetchcontinues to serve files. Chunks are plain static JS under/assets/.
UI shell (layout and scroll)
- Flex/grid: Ensure outline column is physically left and article right in LTR (DOM order or
order/ explicit template areas). - Scroll: Shell is a column: fixed top bar; body row
flex: 1; min-height: 0; outline column and main column eachoverflow-y: autoso scrolling the tree does not scroll the document and vice versa.
Testing
- Unit: Hash parse/serialize (
routeKey, optional slug), link rewriter output, front-matter stripping, slug sanitization (::), manifest/tree helpers as applicable. - Manual / optional E2E: Cold load with hash, collapse/expand sidebar, follow relative and hash links, Mermaid on a page that includes a diagram.
Open questions (implementation)
- Exact shared theme extraction from admin (which variables or partial CSS to share without pulling admin-only components).
- Whether folder expand state persists in
sessionStoragevslocalStorage(default recommendation: session).
References
- Prior: 2026-05-04-docs-browser-design.md
- Plan to update after approval: 2026-05-04-docs-browser-spa.md