ADR-022 — Starlight replaces Docsify; markdown stays in docs/
Date: 2026-09-23
Amended: 2026-09-23
Status: accepted
Context
The reference site (Phase 12) was a Docsify shell: a CDN-loaded docs/index.html rendering docs/*.md client-side, a hand-maintained docs/_sidebar.md (every new ADR had to be added by hand), and a deploy step that copied the whole docs/ folder into the showcase Pages artifact. Copying the folder also published everything in it, including docs/case-study-source/ and docs/strategy/, which were never meant to be public reference pages.
Starlight (Astro) gives static per-page HTML, built-in search, and an autogenerated sidebar. Its default layout expects content in src/content/docs/. But docs/ paths are a contract several other things depend on: every page’s sources: frontmatter and scripts/docs-check.js, the doc/ADR links in apps/showcase/src/pipeline/model.ts, ADR references in CLAUDE.md, commands, handoffs, and the docs-scribe agent.
Options:
- (a) Move the markdown into
apps/docs/src/content/docs/. This is Starlight’s default, but it breaks every path above (~40 references), and the docs-check staleness history would restart at new paths. - (b) Keep the markdown in
docs/, and put the Starlight app inapps/docs/with its content collection loaded cross-directory through Astro’sglob()loader.
Decision
Option (b). The markdown files stay in docs/. apps/docs/ (@upskill/docs) only holds config.
- What is published is set by the loader pattern in
apps/docs/src/content.config.ts:docs/NN-*.mdanddocs/decisions/NNN-*.md(not the000template).case-study-source/andstrategy/stay in the repo but are not published. - Every published page needs
title:frontmatter (Starlight’s schema). The body keeps its# H1for raw and GitHub readers.apps/docs/docs-markdown.mjsdrops that H1 at render time and rewrites relative.mdlinks to site routes, so source links stay GitHub-valid. Links to unpublished files (case-study source,CLAUDE.md) are sent to the GitHub blob view. 00-start-here.mdbecomes the site root through the loader’sgenerateId, not a rename, becausedocs-checkglobsdocs/NN-*.md.- The sidebar is derived from filenames:
NN-*.mdfor reference pages,decisions/NNN-*.mdfor ADRs. Nothing is hand-maintained. Starlight’s ownautogeneratecan’t be used, because it matches entries by path undersrc/content/docs/and finds nothing in a cross-directory collection. docs-build.ymlbuilds the site on PRs touchingdocs/**orapps/docs/**. A missingtitle:fails there, not onmain.deploy-showcase.ymlbuilds it and copiesapps/docs/distinto the Pages artifact at the same/upskill-design-system/docs/URL.
Consequences
- No change to the git workflow,
docs-check,/docs-sync, or any path other code references. - New ADRs and pages need
title:frontmatter (the template carries it). The PR build enforces this. - Deep links change shape, from Docsify hash routes (
#/01-token-pipeline) to real paths (/01-token-pipeline/). Old hash URLs land on the site root. - Adding
title:touches every ADR. Because ADRs are declaredsources:of reference pages, ADR and page edits like this must land in the same commit (docs-check compares with strict>). - Trade-off: the docs site is now a build step with npm dependencies (Astro, Starlight, astro-mermaid) instead of a zero-build CDN shell.
Amendment (2026-09-23) — Splash page owns the site root
The site root is now a Starlight splash page (apps/docs/src/pages/index.astro, template: "splash") rather than 00-start-here.md. The first page a visitor lands on should say what the system is in a few sentences and route them onward; “Start here” is a long orientation page and reads better as the first click than as the landing.
generateIdmaps00-start-heretostart-here, so it serves at/start-here/; the markdown link rewriter and the sidebar’s “Start here” entry follow the same route. The file is not renamed, for the samedocs-checkreason as before.- The splash lives in
apps/docs/, notdocs/, because it is site chrome, not reference content: it declares nosources:and is outsidedocs-check. Keep it short and pointer-only so it has nothing to go stale.