Planet4.Me docs
Open the app

#The docs site

docs.planet4.me publishes the documents this repository already keeps, and nothing else. scripts/build-docs.mjs reads README, ARCHITECTURE, DECISIONS, BACKLOG and CLAUDE, plus the assistant's capabilities document out of functions/src/capabilities.ts, and writes a static site to docs-dist/: one page per document, ARCHITECTURE cut into a page per ## section with a contents page in front, and the capabilities document as the household's own guide — verbatim, under a note saying so, because it is the claim the app makes to the household and CLAUDE.md is what keeps it current. The home page's opening is the README's, lifted rather than rewritten. Nothing on the site is prose written for it, so there is no fifth copy of the product's story to drift. See DECISIONS, "The household's guide is the capabilities document, verbatim".

Three files under scripts/docs/. markdown.mjs is the renderer: the subset of Markdown these documents use — headings, paragraphs, fenced code, pipe tables, nested lists with a fence inside an item, blockquotes, rules; inline code, bold, *italic*, links — pure, and tested by markdown.test.mjs, which npm test runs alongside layer 1. Its last block renders every source document and fails if anything comes out still wearing its markup, which is how a document that starts using syntax the renderer does not know is caught before it ships as a page with ** printed on it; CI runs the build on every push for the same reason. site.mjs is the plan and the shell — which files, how each is cut, the nav, the on-page contents, the search index, the page frame — and takes the sources as strings, so the whole site is buildable in a test. assets/ holds the site's own stylesheet and script, copied out verbatim. See DECISIONS, "The docs site is a script in the repository, not a framework".

The palette is the app's: src/styles/tokens.css is copied into the output at build time, and the docs stylesheet defines only what the app has no use for (a reading measure, a three-column shell, prose rhythm), so the site is emerald on sage in light and dark for the same reason the app is, and a palette change reaches it on the next build.

Search is client-side over one index — a record per heading with the plain text under it, ids assigned by the same walk that assigns them to the page, so a hit lands on its section's anchor — written as assets/search-index.js and loaded by a script tag on the first keystroke, never up front: it is the largest thing the site has (~430 kB) and most visits never type. A script tag rather than fetch() because the site also has to work opened straight off the disk, where a fetch of a local file is blocked; every link on the site is a real .html file for the same reason, and Hosting's cleanUrls stays off so those links are served rather than redirected. Results rank a title hit above a body hit and cap at thirty. The on-page contents ships open and is folded by the script on narrow screens; the document list is a checkbox, not a script; nothing on the site needs JavaScript to be read.

Hosting is a second Firebase Hosting site — target docs in firebase.json and .firebaserc, site smart-list-app-docs, custom domain docs.planet4.me — rather than a /docs path under the app. The app's service worker owns every navigation on its origin (navigateFallback to the shell), so a path under it would be served the app by any phone that had ever opened it; and Vite copies public/ into the bundle the worker precaches, so a site emitted there would ride into every install. Its own origin costs one console step and buys a site the app cannot break and that cannot break the app. npm run docs:build is the target's predeploy hook. The site id and the domain are set up once by hand — see README, "The docs site" — and until the site exists firebase deploy fails on the docs target; --only hosting:app ships the app alone. See DECISIONS, "The docs site is its own Hosting site, not a path under the app".