Docs freshness
Every page has a review cadence and a reviewer discipline. An expired page is reported by CI, not discovered by a reader.
Why a documentation site needs an expiry date
Guidance rots more quietly than code. A component that no longer compiles is found in minutes; a paragraph that stopped being true two releases ago reads exactly as authoritative as it did when it was written, and in a health system it is read by somebody making a decision about how to present a number to a patient.
So every page here carries three fields in its frontmatter, and a script checks them:
| Field | What it means |
|---|---|
reviewed | The ISO date somebody last re-read the page against the system |
reviewer | Which discipline owes the next read-through: design, engineering, clinical or content |
reviewEvery | How long the last read-through stands before it expires: 3m, 6m, 12m or never |
<Reviewed /> prints the date and the cadence at the foot of the page, so a
reader can see the age of what they are reading without opening a file.
It does not print reviewer, and it must not be made to. That field is an
assignment and not an attestation. Twenty pages name clinical, twelve of them
safety patterns including the alert escalation ladder and the result disclosure
rules, and no clinician has read any of them. A footer saying "last reviewed by
clinical" would be the single most dangerous sentence this corpus could print.
When a real sign-off exists it needs a field that records that it happened,
because this one records who is next.
There were two more fields here until recently, owner and a11yDate. Both
are gone: owner asked the same question as reviewer without a closed list
of answers and collected six spellings for four disciplines, and a11yDate
held the same stamped date on seventy-seven pages, which records a bulk edit
and not a review.
The cadence, by page kind
reviewEvery is optional in frontmatter. When it is absent, the default for the
page's kind applies. Those defaults live in one place, DEFAULT_CADENCE in
scripts/check-freshness.mts, and that is the authority: if the table below ever
disagrees with it, the script is right and this page is the thing to correct.
| Kind | Default cadence | Usual reviewer | Why |
|---|---|---|---|
health | 6 months | clinical | Doctrine that shapes what a patient is told |
accessibility | 6 months | engineering | Claims that are tested and can regress |
project | 6 months | engineering | Policy and status pages, which describe a system that is still moving |
content | 12 months | content | Language guidance changes slowly |
component | 12 months | design | The implementation moves, but a component page moves with it in the same commit |
foundation | 12 months | design | Token changes arrive through the generated tables, which the drift gate checks every build |
pattern | 12 months | design | Patterns outlive the components implementing them |
recipe, screen | 12 months | design | Compose components, so they rot when components change |
handbook | 12 months | engineering | Mechanics, tied to releases rather than the calendar |
guide | 12 months | engineering | Installation instructions age fastest of all, which is why the ones that matter override this down to 3 months rather than relying on the default |
reference | never | engineering | Bodies are generated and are checked by the drift gate on every build, so a calendar review would report an age the gate has already disproved |
The reference exemption is the one that surprises people. It is not a claim
that those pages cannot be wrong; it is that a date is the wrong instrument for
them. pnpm run check:generated regenerates every one of them and fails on a
single changed byte, which is a stronger guarantee than a human re-reading a
table of two hundred rows twice a year. The hand-written half above each
generated marker is not covered by that, and a reference page whose prose needs
a cadence sets reviewEvery explicitly. Both
contrast and
catalogue do this.
Pages that override the default downwards to 3 months are the ones where
being wrong is most expensive. They include the emergency and crisis health
pages, the accessibility statement and the two contrast pages, the six start/
guides a newcomer follows literally, the measured
contrast table, the
catalogue, the roadmap
and state of the system. The six guides are quick
start, installation, the Next guide, components.json, the first component and
troubleshooting.
reviewEvery: never exists for pages whose content is fixed by history, such as
a dated changelog entry or a decision record that has been superseded. Use it
rarely; it is the one setting that cannot be reported as stale.
What the check actually does
scripts/check-freshness.mts reads every page, adds either the page's
reviewEvery or its kind's default to its reviewed date, and sorts the corpus
into three states. It prints one summary line and then up to three tables,
headed OVERDUE, DUE WITHIN 30 DAYS and NO REVIEW DATE; a table is omitted
when it would be empty. Each row is one page: its slug, its kind, its
reviewer, its reviewed date and its due date, with the overdue rows ordered
by how far past due they are so the worst is at the top. --json prints the
same three sets plus the totals, and --strict exits non-zero when anything is
overdue.
The tables group by state and by nothing else, so do not expect a per-team
digest from this script. State is what somebody clearing a backlog needs. If you
want the digest, group the --json output by reviewer with jq.
It runs both places, differently. On a pull request it is advisory and
continue-on-error, because a page expiring is not a reason to block somebody
else's change. In the nightly job it runs with --strict and blocks, which is
the only thing that makes reviewed: and reviewEvery: mean anything.
A page with kind: health and no reviewed date is a different matter: that
fails assert-ia.mts on every build. Health doctrine without a review date is
not a stale page, it is an unsourced claim.
What a review is, and is not
A review is re-reading the page against the system as it is today and
changing what has stopped being true. If a page needed no change, the reviewer
still updates reviewed. That is a real signal, and it is the whole point of
separating "when it was written" from "when it was last checked".
Bumping the date without re-reading the page is the one prohibited operation here. It converts the only honest freshness signal on the site into decoration. A page that is out of date and says so is more useful than a page that is out of date and claims to have been checked last week.
This repository has done it, twice. Of the 343 pages that carry a reviewed
date, 255 share one date and another 81 share a second, so two bulk edits
account for 336 of them. The a11yDate field deleted alongside owner was the
same story on a smaller scale, one stamped date across seventy-seven pages. So
read a reviewed date here as the weaker claim it is: somebody passed over the
page on that day. When the next one comes round, re-read the page rather than
repeating the edit that made this paragraph necessary.
If you are reading a stale page
The review date at the foot of the page is the truth. Cross-check against the
changelog: if a release landed after the review date and
touched what you are reading, believe the changelog. Right now the changelog is
itself behind, because the component layer landed without an entry.
State of the system is therefore the more current of
the two and is the one to check first. Then tell us: report it as a docs issue
naming the page path, and Community says where to send it.
There is no report-a-problem control at the foot of the page; a <Feedback>
component is written and nothing renders it, which Community
also records. A stale page is worth reporting anyway, and it is the report this
section exists to ask for.
Last read through against the system on 2026-09-02. Due for review every 12 months; expiry is reported by pnpm run check:freshness.