46. Documentation site (Docusaurus v3 — TypeDoc + generated RPC Markdown)
Date: 2026-06-23
Status
Accepted (implemented, with revision) — website/ is scaffolded, committed, and built/deployed.
Revision (as-built, 2026-06-24)
The OpenAPI approach below was superseded during execution. The
docusaurus-plugin-openapi-docs / docusaurus-theme-openapi-docs plugin + theme (and its
gen-api-docs / @theme/ApiItem flow) were dropped in favour of a dependency-free Node
generator, website/scripts/gen-rpc-docs.mjs, which renders
one Markdown page per Supabase RPC from the committed website/specs/supabase.json. The OpenAPI
theme's transitive postman-code-generators postinstall breaks npm ci on CI/Vercel runners
(see commit 05fe36d). Trade-off: no interactive "try-it" explorer, but a reliable build.
As shipped: the site builds and deploys to docs.nightingalepm.com via a separate Vercel project
(Root Directory = website/); .github/workflows/docs.yml only build-verifies it (no Supabase key in
CI — the build regenerates the REST pages from the committed spec via the prebuild hook). The
generator emits 15 RPC pages, not 13. Sections §1, §3 and checklist steps 2/3/4/6/7 below describe
the abandoned OpenAPI plan and are kept as the historical record; the corrected facts are noted inline.
Context
We want auto-generated, always-current API references with minimal hand-maintenance:
- a code reference for the logic/data layers, from the TSDoc comments already in
src/, and - a REST reference for the Supabase/PostgREST surface — valuable here mainly for our 15 RPCs
(
send_invoices,void_invoice,backfill_invoice,weekly_billing_run,recompute_draft_invoice,create_facility_invoice, the payroll-batch RPCs,hire_candidate, …).
The source runbook assumes a "React/TS app" pointing TypeDoc at all of ../src. This is Next.js
(App Router): src/ also holds 'use client' components, 'use server' actions, and default-export
pages/layouts, which TypeDoc renders noisily. The plan below adapts the runbook to that reality.
Decision (proposed)
-
Tooling, as in the runbook. Docusaurus v3 in a new
website/folder;docusaurus-plugin-typedoc(+typedoc-plugin-markdown) for code;docusaurus-plugin-openapi-docsdocusaurus-theme-openapi-docsfor REST. Both regenerate on build.
Superseded: the OpenAPI plugin/theme were not used — REST regenerates via
website/scripts/gen-rpc-docs.mjs(generated Markdown), run by theprebuild/prestartnpm scripts. See the Revision note above. -
Scope TypeDoc to the logic/data layers, not the UI.
entryPoints: ['../src/lib', '../src/server', '../src/db', '../src/types']withentryPointStrategy: 'expand', and exclude the generatedsrc/db/types.ts(≈5k lines of Supabase row types — pure noise). This is the runbook's "narrow entryPoints" escape hatch. Components/pages are excluded (default exports + JSX document poorly and aren't an API surface). Covers ~200 export-bearing files. -
REST reference kept, scoped to signal. Pull the PostgREST spec to
website/specs/supabase.json(committed). The raw table CRUD is low-value, but the RPCs make it worth it. Pull the URL + service-role key (SUPABASE_SERVICE_KEY) via the Supabase MCP / env — never commit the key. (The service-role key is required because the anon key only exposes the RLS-visible surface, not the full RPC spec.) Nosupabase/functions/exists, so the runbook's "Edge Functions aren't in the spec" caveat is moot.Superseded: the
groupPathsBy: 'tag'mechanics belonged to the OpenAPI plugin. The shipped generator (gen-rpc-docs.mjs) filters the spec to/rpc/*paths only — table CRUD is never rendered — so the "scoped viagroupPathsBy" framing no longer applies. -
Node 24 everywhere. Repo is pinned to Node 24.16.0 (
.nvmrc, ADR-0041); the §5 CI workflow uses node 24, not the runbook's 20. -
website/is an isolated npm project. It keeps its ownpackage.json/lockfile (Docusaurus scaffolds with npm); the app stays on pnpm. The docs build never touches the app's deps. -
Build coupling is acceptable. TypeDoc reruns inside the docs build, so a
src/type error fails the docs build — a feature (stale docs can't ship); the app's own CI already keepstscgreen.
Execution checklist (after approval)
As-built corrections (2026-06-24): steps 2/3/6/7 below describe the abandoned OpenAPI flow. What actually shipped: no
docusaurus-plugin-openapi-docs/docusaurus-theme-openapi-docsdep (step 2); the config registers the TypeDoc plugin (withexclude: ['**/*.test.ts', '**/*.spec.ts', '../src/db/types.ts']andskipErrorChecking: true) plus a second@docusaurus/plugin-content-docsinstance for the hand-written guides — no OpenAPI block and nodocItemComponent: '@theme/ApiItem'(step 3); the sidebar's REST category is an autogenerated category overdirName: 'rest'with agenerated-indexlanding page — it does not import./docs/rest/sidebar.js(step 4); REST pages are generated bynode scripts/gen-rpc-docs.mjs(run via theprebuild/prestarthooks), notnpx docusaurus gen-api-docs supabase(step 6);.github/workflows/docs.ymlneeds no Supabase secret — node 24,npm ci --legacy-peer-deps,npm run build(the build regenerates the REST pages from the committed spec) — and deploy is handled by the docs Vercel project's Git integration, not a CI step (step 7).
npx create-docusaurus@latest website classic; set title/url; commit the scaffold.- Add dev deps:
typedoc typedoc-plugin-markdown docusaurus-plugin-typedocdocusaurus-plugin-openapi-docs docusaurus-theme-openapi-docs. - Merge the consolidated
docusaurus.config.js(runbook §3): TypeDoc block with the scopedentryPoints+exclude: ['../src/db/types.ts']; OpenAPI block;docItemComponent: '@theme/ApiItem'; registerdocusaurus-theme-openapi-docs. website/sidebars.js:intro+ "Code reference" (autogeneratedcode/) + "REST API" (./docs/rest/sidebar.js).- Pull the spec:
SUPABASE_URL/SUPABASE_SERVICE_KEYfrom env (values via Supabase MCP) →specs/supabase.json; addspec:pull/spec:prep/gen:restscripts (runbook §4). There is noclean:restscript — the old REST output is wiped bygen:restitself (rmSync('docs/rest', …)insidescripts/gen-rpc-docs.mjs). npx docusaurus gen-api-docs supabasethennpm run build; fix any TypeDoc scope/excludes.- Add
.github/workflows/docs.yml(runbook §5, node 24;SUPABASE_URL/SUPABASE_ANON_KEYas repo secrets) — build only at first; add the deploy step once the target is chosen (see Open decisions). .gitignorethe generatedwebsite/docs/code/andwebsite/docs/rest/(rebuilt each run); keepspecs/supabase.jsoncommitted.
Open decisions (need your call before/around execution)
- Deploy target — RESOLVED (option a). A separate Vercel project (
npm-helper-docs) withRoot Directory = website/and "include files outside root" enabled serves the custom domaindocs.nightingalepm.com— as wired inwebsite/docusaurus.config.js(url) and noted in.github/workflows/docs.yml(Vercel Git integration handles deploy; CI only build-verifies). Options (b) GitHub Pages and (c) build-only were not taken. - Surface the hand-written
docs/? We already have ADRs + runbooks (go-live,cron-setup,deploy-app-subdomain,ux-ui-guidelines). Option to include them as Docusaurus pages for a single home, or leave them as repo markdown and only publish the generated references. - TSDoc backfill is a separate task. The site renders whatever comments exist; our code already has
good prose
/** */headers but few@param/@returnstags. Backfilling tags acrosssrc/is its own follow-up (potentially a workflow), not part of standing up the site.
Consequences
- A new
website/workspace + a docs CI workflow; a small ongoing "pull spec after schema changes" step (scripted in §4). - The code reference is only as good as the comments — uncommented exports render as bare signatures.
- Excluding components means the UI layer isn't documented here; acceptable (it's not an API surface).