---
title: "ADR 0006. Generated, never authored"
description: "Source code, tables and every measured number are produced by scripts, committed as MDX, and checked for drift in CI."
url: "https://opsinjs.pensievelabs.org/project/decisions/0006-generated-not-authored"
source: "https://opsinjs.pensievelabs.org/project/decisions/0006-generated-not-authored.md"
section: "Project"
kind: "project"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["generated docs", "why is this table empty", "drift check"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="project" />

## Status [#status]

**Accepted.** 2026-09-02.

## Context [#context]

Documentation rots unevenly. Prose about intent ages slowly and can be reviewed on
a cadence. Facts about a system age at the speed of the system, and the facts that
age fastest are the ones that look most authoritative: prop tables, token lists,
data attributes, CSS variables, keyboard tables, contrast ratios, bundle sizes.

A stale prop table is not a small problem. It is a table of things that do not
exist, indistinguishable from a table of things that do, sitting on a page that a
program is about to read and generate code from.

There is a sharper version of the problem in this project. Nothing is built, so
every measured number would have to be either omitted or invented. **A plausible
fake row in a reference table is indistinguishable from a real one**, and it
poisons exactly the audience the site is designed for.

**Amendment, after the component layer shipped.** One clause above is stale and
is left standing rather than rewritten: "Nothing is built" was true on the day
this record was accepted and is not true now. Sixty components are implemented
and every catalogue row reads `shipped`, and the first consequence is that the
generators finally have something to read. `lib/generated/props.ts`
carries a prop table extracted from the exported interfaces under
`registry/bases/` for every component and every exported part, and the pages that
show them still carry no prop row of their own, because they render
`<PropsTable>` and nothing else. The
argument does not weaken with the fact; it moves. The sharper version of the
problem is no longer "every number would have to be invented", it is that **an
empty table on a system that visibly has components is far more tempting to fill
in by hand than an empty table on a system that has none**. Two tables are in
exactly that position today:
[Keyboard](../../reference/generated/keyboard.mdx) and
[Data attributes](../../reference/generated/data-attributes.mdx) both still
render an honest empty state naming `scripts/build-reference.mts`. The reason is
that the extraction is unwritten, not that the components emit nothing. Rule 3
below is what holds that line, and it is load-bearing now in a way it could not
have been while nothing existed.

## Decision [#decision]

**Anything that can be derived from the system is generated by a script,
committed as MDX, and verified in CI.** That covers component source, props
tables, token tables, data-attribute tables, CSS-variable tables, keyboard tables,
APCA and WCAG measurements, bundle sizes, catalogue rows, the glossary and eval
scores.

Three rules make it work:

1. **Generated artefacts are committed**, as real MDX under
   [`reference/generated`](../../reference/generated/tokens.mdx). They are
   therefore indexed by the site's search exactly like written pages, which is the
   only change needed to make search cover generated content.
2. **CI regenerates and diffs.** `pnpm run check:generated` runs the generators and
   fails on any change, so a committed table cannot drift from its source.
3. **An empty source renders an honest empty state**, naming the script that will
   fill it. Never a sample row, never an ellipsis, never a plausible example.

## Consequences [#consequences]

* **You cannot fix a wrong number by editing the page.** The next generate run
  erases it and CI fails the pull request that contained it. You fix the source.
  This is the intended friction.
* **Every generated page needs a committed placeholder** so that a clean clone
  typechecks and builds before anything has been generated. Whoever owns a
  directory writes that placeholder.
* **A hand-written header survives on every generated page.** Above the marker
  comment sits a short block naming the source and the script. It is there
  because "generated from `tokens/color.json` by `build-tokens.mts`" is itself a
  fact a reader needs, and no generator can be trusted to explain its own
  provenance well.
* **Several tables are empty today**, and say so. That is the decision working
  correctly, not a gap in the documentation.
* **Generated directories are excluded from the formatter**, so that a formatting
  pass cannot create a diff that the drift check then reports as a source change.
* **Numbers become checkable claims.** The contrast figures on this site were
  computed from shipped token values on a dated run, and a regression fails the
  build rather than being noticed by a reader.

## Alternatives considered [#alternatives-considered]

**Generate at runtime, from an API route.** Rejected: the search index would not
cover it, the `.md` twins would not contain it, and the offline bundle would be
missing exactly the machine-readable facts it exists to carry.

**Hand-written tables with a review cadence.** Rejected. This is the industry
default and it is the failure mode being avoided; a cadence catches a stale table
months after somebody has already generated code from it.

**Generate but do not commit.** Rejected: a clean clone would not typecheck, CI
could not diff for drift, and there would be no way to review a change to a
generated artefact in a pull request. That review is often exactly where a token
mistake becomes visible.

## Revisiting this [#revisiting-this]

Revisit only if committed generated output becomes unmanageable in review. That
is a plausible future once there are hundreds of components. The mitigation would
be narrower generated files, not hand-authored tables.

<LastUpdated />

<Reviewed />
