sparkles:docs SSG surface — Feature Requirements
Status: shipped (extraction) + two open rows · Date: 2026-08-21 · Scope: the library surface extracted from hue's gallery — one module per concern under libs/docs/src/sparkles/docs/.
NOTE
Most rows here absorb by reference: the normative text lives in the shipped gallery.md / hue feature-requirements rows each cites, and this table records which sparkles:docs module owns it now. New normative content appears only in DOC8 and DOC10.
Design & rationale
The extraction (milestone D0) moved code, not behavior: hue gallery output is byte-identical before and after, which is the property that let the move happen as one reviewable refactor. What the library adds over the app-internal version is shareability — ci reads the sidebar schema from it, hue renders with it, and the discovery and apidoc efforts build on it — plus a real package, which finally gives the shared helpers (escapeInto, the options vocabulary) a home that isn't "public because root-level modules have no package visibility".
The module split follows the import graph: options at the bottom (plain data
- pure builders),
fragment/breadcrumbs/site_treeabove it,page_shellon top,source_setandsidebarbeside them,assetsfor the stylesheet files.
SSG surface (DOC)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| DOC1 | Content fragments. One document renders to a <style> + <pre class="syn-root"><code> fragment (plain or twoslash), with the physical-line gutter available fragment-side for shell-less pages. Owns HTM1's fragment half, HTM4, GAL4. | full | fragment.plainFragment/twoslashFragment/relayoutGutter/withLineNumbers |
| DOC2 | Page shell. The full page around a fragment: nav header (prev/next disabled-not-omitted, index link), one scroll container, selection domains. Owns GAL3, GAL6, GAL7, HTM7, HTM8. | full | page_shell.pageShell |
| DOC3 | Dual themes + appearance toggle. Light and dark from one rendering, the toggle writing VitePress's own localStorage['vitepress-theme-appearance'] key, a no-flash <head> script, everything gated on a two-theme run. Owns HTM10. | full | page_shell.pageShell (toggle + script); assets.themeStylesheet |
| DOC4 | Chrome palette. The whole surround (page, header, rule, nav, gutter) derives from the rendering theme's default bg/fg — themeChrome — so pane and chrome are one surface by construction; the palette is a parameter (GalleryOptions.chrome), so a site can substitute its own (--chrome=site, DSC5). Owns GAL6's theme-sourcing half. | full | options.ChromePalette/themeChrome/themeBackground |
| DOC5 | Mirrored site tree. A recursive set renders <out>/<rel-path>.html plus an index.html in every directory, page-relative hrefs throughout, depth-rebased asset hrefs; a flat set keeps the flat layout byte for byte. Owns GAL12, HTM6's mirrored half. | full | site_tree.buildSiteTree/directoryIndex; page_shell.writeGallery/pageHref/depthAdjustedHref |
| DOC6 | Breadcrumbs. Per-segment directory-index links that work from file://, copy-path controls, forge links under --repo-url/--repo-prefix, VitePress Breadcrumbs.vue markup/class parity. Owns GAL14. | full | breadcrumbs.breadcrumbsFor/renderBreadcrumbs/breadcrumbCss |
| DOC7 | Stylesheet assets. A page set leaves theme rules to one shared stylesheet — self-hosted assets/hue.css, --stylesheet link, --emit-stylesheet write — carrying both theme scopes and the chrome/twoslash/markdown blocks. Owns GAL11, HTM1's shared-sheet half. | full | assets.themeStylesheet/writeStylesheetAsset/writeStylesheetFile |
| DOC8 | Sidebar. The docs-site sidebar/srcExclude data (docs/.vitepress/sidebar.json, docs-config.json) has one D schema, in this library, consumed by the site config, by ci --check-docs-sidebar / --audit-fences, and by generated pages: a listing page must be able to render the same sidebar tree the docs site shows, its links resolved against the site base URL, so a reader crossing between a VitePress page and a listing keeps the same navigation. hue gallery --sidebar <sidebar.json> --site-base <url> supplies the tree and the base the routes resolve against; on hue site pages the docs nav renders inside the explorer's docs/ node (DOC11) rather than as the whole aside; --chrome=site (DSC5) will later bundle the base with the site palette. | full (100a10b1) | sidebar.SidebarItem/loadSidebar/loadSidebarFile/sidebarLinks; sidebar.sidebarNav/sidebarCss; GalleryOptions.sidebarHtml |
| DOC9 | Document set. The ordered, filtered, summarized file list every mode consumes, with the recursive .gitignore-aware descent delegated to sparkles:build-primitives' glob walk. Owns GAL1/GAL8 and the library half of SRC5/SRC6/SRC9. | full | source_set.SourceSet/collectSources/isRenderable; sparkles.build_primitives.glob_walk |
| DOC11 | Explorer sidebar + unified directory pages. On a generated site, the sidebar is the site's own file explorer, built per page: directories as collapsibles open along the current page's path, each name a link to its own index inside the summary, pages as page-relative links (the file:// discipline) with the current one highlighted, and the docs nav rendered inline as the docs/ node's children — no wrapper, no parallel copy of the raw docs/ file subtree, and a listing-route nav entry rendered as that directory's real file subtree with relative hrefs. A directory page is the same shell as a file page — title, header, appearance toggle, breadcrumbs in directory mode — around an intentionally empty pane: the explorer is the listing, so moving between a file and a directory keeps the tree and clears the pane (the VSCode "no file open" state). The explorer is built from what was actually written, so it never links a hole. Born from PR #371 UAT: bare list indexes and a docs sidebar redundant with them read as two different sites. | full | page_shell.explorerNav/directoryPage/writeExplorerGallery; GalleryOptions.explorerSidebar/siteBase; sidebar.sidebarItemsHtml; breadcrumbs.breadcrumbsFor(isDirectory) |
| DOC10 | Escaping unification. options.escapeInto (4 entities) must fold into sparkles.base.text.html.writeHtmlEscaped (5 entities — it also escapes '), so the repository has one HTML-escaping implementation. This is a deliberate byte change for any name/summary containing an apostrophe: it lands with a golden update, after the D0 byte-identity guarantee has served its purpose — never silently. | full (36e7225b) | options.escapeInto (a forwarder); sparkles.base.text.html.writeHtmlEscaped |
Non-goals
- Interactive navigation (
GAL5,GNV*) stays hue's: the library renders static pages; sessions are the app's concern. - Serving. The library writes files. Dev servers, deployment, and CI wiring are the consumer's (discovery.md
DSC4and the docs-site build scripts).