Skip to main content

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 changedUpdate
A screen, a flow, anything a user seesdocs/product/ — and mockups.mdx if layout moved
A table, a column, a relationshipdocs/architecture/data-model.mdx
An endpoint or a payload shapedocs/architecture/api.mdx
Sync or offline behaviourdocs/architecture/offline-first.mdx
A workflow, a secret, an environmentdocs/engineering/ci-cd.mdx, environments.mdx
A shipped roadmap itemdocs/product/roadmap.mdx — move the row, do not delete it
An idea nobody has committed todocs/product/wishlist.mdx
A climbing termdocs/wiki/glossary.mdx
Anything hard to reverse or cross-cuttingA 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​

  1. Create the MDX under docs/.
  2. Add frontmatter: title, sidebar_position, description.
  3. 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.
  4. Link to it from somewhere a reader will actually be.
  5. pnpm docs:build to 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.