Skip to content

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 in apps/docs/ with its content collection loaded cross-directory through Astro’s glob() 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-*.md and docs/decisions/NNN-*.md (not the 000 template). case-study-source/ and strategy/ stay in the repo but are not published.
  • Every published page needs title: frontmatter (Starlight’s schema). The body keeps its # H1 for raw and GitHub readers. apps/docs/docs-markdown.mjs drops that H1 at render time and rewrites relative .md links 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.md becomes the site root through the loader’s generateId, not a rename, because docs-check globs docs/NN-*.md.
  • The sidebar is derived from filenames: NN-*.md for reference pages, decisions/NNN-*.md for ADRs. Nothing is hand-maintained. Starlight’s own autogenerate can’t be used, because it matches entries by path under src/content/docs/ and finds nothing in a cross-directory collection.
  • docs-build.yml builds the site on PRs touching docs/** or apps/docs/**. A missing title: fails there, not on main. deploy-showcase.yml builds it and copies apps/docs/dist into 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 declared sources: 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.

  • generateId maps 00-start-here to start-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 same docs-check reason as before.
  • The splash lives in apps/docs/, not docs/, because it is site chrome, not reference content: it declares no sources: and is outside docs-check. Keep it short and pointer-only so it has nothing to go stale.