Documentation standards
The rule
Every change that affects product behaviour, the data model, the API surface, or how we deploy updates the documentation in the same pull request.
Not afterwards, not in a follow-up ticket. It is in
CLAUDE.md, and the
docs-check workflow comments on any PR that changes code without touching docs/.
The workflow comments rather than fails, because the author knows better than a path glob whether a change is genuinely documentation-neutral. If it is — a refactor, a dependency bump, a test — say so in the PR and carry on.
Where things go
| You changed | Update |
|---|---|
| A screen, a flow, anything a user sees | docs/product/ — and mockups.mdx if layout moved |
| A table, a column, a relationship | docs/architecture/data-model.mdx |
| An endpoint or a payload shape | docs/architecture/api.mdx |
| Sync or offline behaviour | docs/architecture/offline-first.mdx |
| A workflow, a secret, an environment | docs/engineering/ci-cd.mdx, environments.mdx |
| A shipped roadmap item | docs/product/roadmap.mdx — move the row, do not delete it |
| An idea nobody has committed to | docs/product/wishlist.mdx |
| A climbing term | docs/wiki/glossary.mdx |
| Anything hard to reverse or cross-cutting | A new ADR — ask first |
Write the why
The code says what it does. A documentation page earns its place by recording what the code cannot:
- What forced the decision. The constraint, not the conclusion.
- What was considered and rejected, so it does not get re-proposed in six months.
- What it costs. Every decision closes doors; say which.
- What is missing. A "Known gaps" section is worth more than three paragraphs of description.
A page that only restates the code will be wrong within a month and nobody will notice.
Be honest about what does not exist
Several pages here have a Known gaps or Not implemented section. That is deliberate.
The worst documentation failure is not omission — it is confident description of something that does not work. Where the code is a placeholder, say so, in a callout, with a link to the decision that will replace it. See API → Authentication for the pattern.
Diagrams
Mermaid renders natively — no plugin, no headless browser:
```mermaid
graph LR
A["Screen"] --> B[("SQLite")]
```
Use a diagram when the structure is the point: entity relationships (erDiagram), a protocol
exchange (sequenceDiagram), a pipeline (graph). Do not use one to decorate a list — a table
is clearer and diffs better.
Quote node labels that contain punctuation: A["Worker — Hono"], not A[Worker — Hono].
Mockups are SVG components, not inline SVG
Hand-drawn SVG, as in Mockups. They live in git, they diff on review, and they cannot drift out of the repository the way a link to a design file does.
Put them in apps/docs/src/components/, not inline in the MDX, and import them:
import { MockupGrid, Mockup, CragListMockup } from '@site/src/components/mockups';
<MockupGrid>
<Mockup caption="Crags — local-first list">
<CragListMockup />
</Mockup>
</MockupGrid>
The reason is mechanical and easy to get caught by: MDX parses text inside a multi-line JSX
element as markdown. A <text>9:41</text> that Prettier splits across three lines becomes a
<p> inside the SVG, and the mockup silently degrades into a list of stray words — the page still
builds. In a .tsx file it is just JSX.
Use JSX attribute names: strokeWidth, textAnchor, fontSize, className, stopColor.
SVG exported from a design tool needs converting first.
The mockup-grid styling is in apps/docs/src/css/custom.css.
Status pills
For tables of decisions or roadmap items:
<span class="pill pill--accepted">Shipped</span>
<span class="pill pill--proposed">In progress</span>
<span class="pill pill--superseded">Not started</span>
<span class="pill pill--rejected">Dropped</span>
Cross-linking
Link liberally. onBrokenLinks: 'throw' and onBrokenAnchors: 'throw' mean a stale link fails
CI, which is what makes the links trustworthy.
Use absolute doc paths: /architecture/data-model, not ../architecture/data-model.mdx.
Docusaurus resolves them against the doc root and they survive a page moving directory.
Moving a page means updating every inbound link. The build tells you which.
Adding a page
- Create the MDX under
docs/. - Add frontmatter:
title,sidebar_position,description. - Add it to
apps/docs/sidebars.ts. Sidebars are hand-written on purpose — if a page is not worth placing in a reading order, it is not worth writing. - Link to it from somewhere a reader will actually be.
pnpm docs:buildto check the links.
Callouts
:::note background a reader can skip
:::tip a shortcut worth knowing
:::warning a sharp edge — this will bite you
:::danger do not do this / this is not implemented
:::caution a limit that will be hit eventually
Reserve danger for genuine hazards — unimplemented auth, data loss. Overusing it makes all of
them invisible.
Style
- British English, matching the domain. "Metres", not "meters".
- Present tense for what the system does. Past tense only for what was decided.
- Short paragraphs. These pages are read on a laptop between other tasks.
- Tables for comparisons, prose for reasoning. Neither for the other.
- No changelog sections in a page. Git has the history; a hand-maintained changelog rots.
Related
- ADR-0005 — Documentation as MDX in the repository
- Decision log — including the ask-first rule