Skip to main content

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)

  1. Tooling, as in the runbook. Docusaurus v3 in a new website/ folder; docusaurus-plugin-typedoc (+ typedoc-plugin-markdown) for code; docusaurus-plugin-openapi-docs

    • docusaurus-theme-openapi-docs for 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 the prebuild/prestart npm scripts. See the Revision note above.

  2. Scope TypeDoc to the logic/data layers, not the UI. entryPoints: ['../src/lib', '../src/server', '../src/db', '../src/types'] with entryPointStrategy: 'expand', and exclude the generated src/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.

  3. 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.) No supabase/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 via groupPathsBy" framing no longer applies.

  4. 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.

  5. website/ is an isolated npm project. It keeps its own package.json/lockfile (Docusaurus scaffolds with npm); the app stays on pnpm. The docs build never touches the app's deps.

  6. 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 keeps tsc green.

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-docs dep (step 2); the config registers the TypeDoc plugin (with exclude: ['**/*.test.ts', '**/*.spec.ts', '../src/db/types.ts'] and skipErrorChecking: true) plus a second @docusaurus/plugin-content-docs instance for the hand-written guides — no OpenAPI block and no docItemComponent: '@theme/ApiItem' (step 3); the sidebar's REST category is an autogenerated category over dirName: 'rest' with a generated-index landing page — it does not import ./docs/rest/sidebar.js (step 4); REST pages are generated by node scripts/gen-rpc-docs.mjs (run via the prebuild/prestart hooks), not npx docusaurus gen-api-docs supabase (step 6); .github/workflows/docs.yml needs 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).

  1. npx create-docusaurus@latest website classic; set title/url; commit the scaffold.
  2. Add dev deps: typedoc typedoc-plugin-markdown docusaurus-plugin-typedoc docusaurus-plugin-openapi-docs docusaurus-theme-openapi-docs.
  3. Merge the consolidated docusaurus.config.js (runbook §3): TypeDoc block with the scoped entryPoints + exclude: ['../src/db/types.ts']; OpenAPI block; docItemComponent: '@theme/ApiItem'; register docusaurus-theme-openapi-docs.
  4. website/sidebars.js: intro + "Code reference" (autogenerated code/) + "REST API" (./docs/rest/sidebar.js).
  5. Pull the spec: SUPABASE_URL/SUPABASE_SERVICE_KEY from env (values via Supabase MCP) → specs/supabase.json; add spec:pull / spec:prep / gen:rest scripts (runbook §4). There is no clean:rest script — the old REST output is wiped by gen:rest itself (rmSync('docs/rest', …) inside scripts/gen-rpc-docs.mjs).
  6. npx docusaurus gen-api-docs supabase then npm run build; fix any TypeDoc scope/excludes.
  7. Add .github/workflows/docs.yml (runbook §5, node 24; SUPABASE_URL/SUPABASE_ANON_KEY as repo secrets) — build only at first; add the deploy step once the target is chosen (see Open decisions).
  8. .gitignore the generated website/docs/code/ and website/docs/rest/ (rebuilt each run); keep specs/supabase.json committed.

Open decisions (need your call before/around execution)

  • Deploy target — RESOLVED (option a). A separate Vercel project (npm-helper-docs) with Root Directory = website/ and "include files outside root" enabled serves the custom domain docs.nightingalepm.com — as wired in website/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/@returns tags. Backfilling tags across src/ 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).