Skip to contentWolf-Rayet

Decision records

ADR-0047 A file must name the component it is for

Accepted2026-09-05Phase 4

#Context

shell-left-panel shipped in 15074d7 with twenty-one lines, across seven files, that described shell-header.

Not comparisons. The left panel's battery names the header fifteen further times on purpose, and every one of those is correct and worth keeping — a reader learns what a panel is partly by learning how it differs from the band above it. These twenty-one were different: they were lines where the word "header" named *the component the file was for*.

Four of them were not merely wording.

  • packages/react/src/shell-left-panel.css opened /* ShellHeader — the styled layer. The file announced itself as a different component, in the first line, which is the one place a reader looks to find out what a file is.
  • The left panel's $stackingNote in component.config.json said *"First consumer of the raised context in the set"*, copied verbatim from the header's block. Its own $surfaceNote, eleven lines above, correctly said *"The second consumer."* One block, two answers.
  • Scene 45's NOTE was scene 44's note with a single filename swapped. It titled itself SHELL HEADER, CALM, said the View held *two* things where the generator's own console line prints three, called its subject *"the twenty-second component and the third Scope"*, and claimed the raised perimeter had appeared in no prior receipt — false; the header put one there in scene 44. That note was baked into four committed fixture files, which is to say it shipped as an artifact rather than as a comment.
  • The specimen caption read Its {records.length} parts are both governed chrome. The panel has one part.

Every suite in the repository was green throughout. The types held. The tokens resolved in four themes and two densities. The budget matched to six decimals on both platforms. All eight registries were full. build:check reproduced 140 files byte for byte. Nothing was wrong with the system; what was wrong was what the system *said about itself*, and no check read that.

This is not a proofreading problem, and the reason is mechanical. A battery is produced by copying the previous component's — and that is the right way to build the twenty-fifth component out of the twenty-fourth, because the shape genuinely repeats and re-deriving it each time would produce twenty-five subtly different shapes. What the copy carries with it is the original's identity, in the exact positions where identity is stated. Both halves of this defect were found by widening a grep after the first half had already been fixed and declared clean.

Twenty-three components remain on ADR-0021's roster. Left alone, this recurs twenty-three more times.

There is a second shape of the same failure, found while writing the check for the first. Prose that counts is data wearing prose's clothes. "The twenty-fourth component", "the fifth Scope", "the second consumer of the raised context", "one of the fourteen that do not" — each is a claim with a truth value the repository already holds, and each goes stale the moment the set changes, silently, because no reader recomputes a number word on a set of twenty-four. Two blocks said *"one of the twelve that do not"* when the answer was fourteen; corrected to fourteen, they were stale again one component later, and the check caught them at fifteen.

#Decision

A file must name the component it is for, in the positions that exist to say so; and prose that counts the set is checked against the set.

Two halves, because the failure has two shapes.

#Identity: the own name must lead

Certain positions in a component's files exist for no purpose other than to say which component the file is for: the opening comment of the component, the contract, the stylesheet, the must-fail fixture and the budget generator; and the story's title. In such a position the component's own name must appear, and must appear first.

Leading, rather than appearing alone. The rest of an opening line is frequently and correctly a comparison — TextArea — the seventeenth component, and InputField's shape rotated is exactly right, and a rule banning the foreign name outright would have demanded that line be made worse. What may not happen is the foreign name arriving *before* the file names itself, because that is the file claiming to be something else.

Below the opening line the rule says nothing. Another component's name in ordinary prose is ordinary, and usually load-bearing.

#Arithmetic: a counted claim is checked against the count

A claim in component.config.json that counts the set is verified against the set. Five patterns are read: the ordinal a component gives itself, the ordinal it gives itself within its layer, the ordinal it claims among a stacking context's consumers, and the two cardinalities of the interactive split. A number word that disagrees with the set is a failure, not a nit — it is the record asserting something false about the system, in the file the generator reads.

The vocabulary is restricted to actual ordinals and cardinals, and that restriction is the rule rather than an implementation detail. Unrestricted, the ([a-z]+) component matches "the one component", "the only component", and "the first component in the set to declare ADR-0013's raised context" — the last of which is a true claim about something else entirely and must not be read as arithmetic.

#Rejected options

Proofread it. The defect was found by a human read, twice, and the second half only after the first was fixed and the battery declared clean. A class of defect that survives being looked for is not addressed by looking harder.

Generate the prose. The opening comments and the config notes are the most valuable prose in the repository — they carry the reasoning a reader needs and a diff cannot express. Generating them would trade a defect that misnames a file for a system with nothing worth reading in it.

Ban a foreign name in the opening line outright. Simpler to state and worse to live with: it would have required TextArea's opening line, which is correct, to be rewritten to satisfy a check. Position, not presence, is what distinguishes a wrong claim from a comparison.

Check every line rather than the identity positions. Unworkable and wrong in principle. The left panel's fifteen correct mentions of the header would all report, and a check whose output is mostly false is a check nobody reads.

#Consequences

Every component's opening comments, story title and generator heading now carry a machine-checked claim about which component they are for. A battery copied from the previous component fails at the moment of copying rather than shipping and being found later by grep.

The counted claims in component.config.json become maintained data rather than decaying prose. Adding a component that shifts an ordinal fails the pass that adds it.

A cost worth naming. The identity check reads five positions per component and one more where a generator exists — 138 positions at twenty-four components — and it reads them by pattern. A position that changes shape stops being read, which is the failure mode the coverage counts printed on every run exist to expose: a set that quietly emptied prints a smaller number rather than a green line. The arithmetic half carries the same guard in stronger form — a pattern matching zero claims fails outright, because a pattern that reads nothing cannot be green about anything.

#Measured

tools/check-self-description.js, run on the twenty-four-component set at the commit that introduces it:

  • 138 identity positions, each naming its own component and none naming another before itself.
  • 5 counted claims, each matching the set.

It found four real defects on its first run against a repository whose every other suite was green: two fixture generators whose opening lines named no component at all — *"The Phase 4 mixed component scenes"*, *"The Phase 4 instrumentation scene"* — and the two interactive counts, stale again at fourteen one component after being corrected to fourteen.

It also found three false-positive classes of its own, all three now closed and all three worth recording because each is a way this check could have been wrong in the quiet direction: an unanchored title: that read a story's own arg as an identity claim; an ordinal pattern that matched any word and reported "the one component"; and a first-draft rule that banned foreign names rather than ordering them, which reported TextArea, Select and NumberInput for lines that were correct.

Seen failing before being trusted (ADR-0041). Both halves were broken deliberately and observed to fire on the exact breakage: ShellHeader — the styled layer. ShellRightPanel follows. reported *"names ShellHeader before it names itself"*, and The twenty-third component and the sixth Scope reported both *"the set says twenty-fourth"* and *"the set says fifth"*. Restored, the check returns green.

#What this record does not cover

The check reads a component's own files. It does not read docs/STATUS.md, docs/PLAYBOOK.md or the ADRs, which carry far more counted prose than component.config.json does and drift the same way — two figures in STATUS's own system table were stale by 116 and 13 respectively when this was written, and both were marked *"carried forward, unverified"* rather than being wrong in silence. Extending arithmetic checking to the prose record is a larger question: STATUS is a narrative of passes, and a pass narrative is *supposed* to preserve what was true when it was written. Deciding which of its numbers are claims about now and which are claims about then is its own record.