ADR-0119 A list answers for what its surface renders, not for the whole roster
#Context
tools/check-registries.js exists because a list that has to name every component fails silently when it is short. A stylesheet import that was never added renders the component unstyled, which reads as a design nobody liked rather than a list nobody updated. That reasoning is unchanged and this record does not reopen it.
What it got wrong is the population. The check took COMPONENT_LAYERS — twenty components, the one list in the repository proved complete, against the contracts in packages/react/test/discipline.test.js — and measured all eight of its lists against all twenty of them.
That was right while one set of components was rendered everywhere. It stopped being right when the docs site and the workbench were rebuilt around a different and larger set. Measured on the tree this record is committed against, the check reported 0/20 on six of its eight lists, and pnpm registries:check exited 1 in the budget workflow's lint job.
Nought out of twenty is not a list that has fallen short. A list that had drifted would report nineteen, or seventeen. Zero is the signature of a question asked of the wrong surface, and the measurements say so plainly:
apps/docs/app/components/roster.tsholds 67 entries, and none of the twenty is among them.apps/docs/app/ds/styles.tsandapps/storybook/.storybook/preview.tsxeach import the same nine grouped stylesheets andwolf-rayet.css, and nocomponent-*.cssat all.preview.tsxholds zero?rawimports, so the list checking the reduced-motion toggle's sources was checking a mechanism that no longer exists.apps/proofrenders 18 of the twenty and imports exactly those eighteen. It was not checked by any list.
The twenty in COMPONENT_LAYERS are the operational components — Status indicator, Queue item, Shell header, SLA countdown and the rest of the supervisor's console. Today only apps/proof renders them. The docs site documents a different set, whose components live in grouped modules: Checkbox, Radio, Tabs, Modal and DropdownMenu are real exports of Form.tsx, Navigation.tsx and Overlays.tsx, styled by forms.css, navigation.css and overlays.css.
So the failing lists were not describing a gap in the codebase. They were asserting that the docs site ought to render the supervisor's console, which it was never rebuilt to do.
#Decision
A list declares the population it answers for, and that population is derived from the surface itself: the components that surface renders.
A component counts as rendered when the surface writes its element. That is the same question a stylesheet import exists to answer — if it is on the page, its rules have to be loaded — so both are derived from one fact rather than maintained as two lists that can disagree.
COMPONENT_LAYERS remains the reference, and remains the only list proved complete. What changes is that it is filtered by the surface before it is used as an expectation.
A surface that renders none of the reference is not short. It is uninvolved, and it says so rather than reporting a deficit it has no way to clear.
Four lists survive, and one of them is new:
| List | Population | Result |
|---|---|---|
apps/proof/app/layout.tsx — component tier stylesheets | renders 18 | 18/18 |
apps/proof/app/layout.tsx — component stylesheets | renders 18 | 18/18 |
apps/docs/app/ds/styles.ts — component stylesheets | renders 0 | nothing owed |
apps/storybook/.storybook/preview.tsx — component stylesheets | renders 0 | nothing owed |
apps/proof had no list before this record and now has two. It is the surface that renders the operational components, so it is the surface that owed the imports all along, and the check that was failing on six lists was silent about the one place the rule applied.
Loading and Popover are in COMPONENT_LAYERS, are not rendered by apps/proof, and are not imported by it. Under the old population those were two of twenty absences to explain. Under this one they are simply not that surface's business.
#Rejected options
Add the twenty imports to the docs site and the workbench until the lists are long enough. One edit per list, six lists, and the job goes green. Rejected because it would make both surfaces load stylesheets for components neither renders, to satisfy a check, and the first person to ask why the docs site imports component-sla-countdown.css would find no answer except that a tool wanted it. It also inverts the rule: the imports exist so that what is rendered is styled, not so that a list reaches a length.
Hand-write an exclusion per list with skip. The mechanism already existed and was built for exactly this shape — "an exclusion on the record rather than a hole in the check". Rejected on arithmetic rather than principle: six lists times twenty components is a hundred and twenty exclusions, each of which would have to be revisited every time a surface changed what it renders. An exclusion list that large is the hole it was meant to prevent, and it would encode today's rendering decisions as prose that nothing keeps true.
Retire the check. Defensible, since six of eight lists were measuring nothing. Rejected because the two that were not measuring nothing were measuring nothing *only because apps/proof was never a list*. The rule is sound and the failure was in its aim. A check that has found a real gap once it is pointed correctly is not a check to delete.
Derive the population from the roster instead of from rendering. apps/docs/app/components/roster.ts is a real declaration and would give the docs site a real population of 67. Rejected for this record because the roster answers a different question — which components the site builds a *page* for — and a page can exist for a component the site never renders in a specimen. Whether every roster entry resolves to a page is worth checking and is not this check. Rendering is the fact the stylesheet rule depends on, so rendering is what this one reads.
#Consequences
pnpm registries:check exits 0, and the budget workflow's lint job stops failing on it.
apps/proof gains coverage it never had, which is the substantive change. The removal of a single import from its layout now fails the check by name — verified against component-toast.css on this tree, which reported Toast — expected to find "@wolf-rayet/tokens/css/component-toast.css" and exited 1.
Four lists are retired, and what each was protecting is recorded here rather than left to be rediscovered:
- the reduced-motion
?rawlist, becausepreview.tsxholds no?rawimports and ADR-0107 removed the sustained motion those rules lifted; - the docs roster list, because the roster's 67 entries are a different population and its completeness is a different question;
- the
scan-real-copy.jslist, because that tool names 38 per-component story files of which 36 do not exist, and it is broken in its own right rather than short; - the Storybook specimen list, because the stories were grouped into nine files the way the stylesheets were, so presence in a file named for a component no longer means anything.
The docs site's own completeness is now unchecked, and this record does not pretend otherwise. Nothing here asserts that every one of the 67 roster entries has a page, a stylesheet, or a specimen. That is a real gap, it is larger than the one this record closes, and it wants a check written against the roster rather than against COMPONENT_LAYERS.
The check can now pass while a surface renders nothing. A surface that stopped rendering every component would report "renders none, nothing owed" and stay green. That is the cost of deriving the population from the surface, it is accepted, and it is bounded: the surfaces that render nothing here render nothing *of the operational twenty*, which the roster and the route count already speak for.