StatsHub Docs
General

Conventions

Where a doc lives, what shape a page takes, and the rules this app enforces at build time rather than in review.

The house style is Writing docs, and it applies to whoever is writing, in any app. What follows is the part specific to maintaining this one.

Every doc in the repo lives here

There is no docs/ at the repository root and none inside an app. A README may introduce a directory in a paragraph; anything longer belongs under content/docs, with the README linking to it.

Two copies of an instruction means one of them is wrong within a month.

The generated pages are not yours

content/docs/api/{v1,v2}/reference is gitignored and rewritten on every dev, build and lint. content/docs/api/go-port.md is generated by make porting-status and parsed by statshub-api-contract — do not hand-edit its tables, and do not move the file without updating packages/tests/api-contract/src/cli.ts, which pins the path.

Failures belong at build time, not in review

Two rules here are enforced by code rather than by a reviewer, and both exist because dev renders the broken version happily:

  • resolveIcon throws on an unknown icon name. A silently empty icon is a typo nobody notices until a reader sees a gap in the sidebar.
  • scripts/check-links.ts resolves every internal /docs link, in markdown and in JSX hrefs.

If you add a mechanism that can be got wrong invisibly, add its gate in the same change.

One decision, one place

src/lib/source.tsx holds every decision about how content is loaded — the icon resolver, the OpenAPI badge plugin, the status badges, the remark set. Read it before changing any of them, and put new ones there rather than beside the feature that needed them.

src/app/docs/layout.tsx is the same for navigation: which sections exist and in what order.

Narrow before you serialise

src/lib/openapi-subset.ts cuts the OpenAPI document down to the operations a page renders before handing it to the client. Without it, each of 298 pages would ship the whole 500 KB document; with it, about three kilobytes.

The general rule it stands for: a client component gets the slice it renders, not the corpus it was drawn from.

On this page