ADR-0121 The component tier describes components the rewrite replaced
#Context
ADR-0119 and ADR-0120 each found a check aimed at a surface that had moved, and each was fixed by deriving the question from the artifact rather than from a filename. Fixing them let steps run that had never run, and what those steps report is not a third instance of the same tooling mistake. It is the thing the tooling mistakes were hiding.
packages/tokens/component.config.json declares 37 components. Twenty are the operational set — COMPONENT_LAYERS, the supervisor's console, proved complete against the package's contracts. Seventeen are not, and for sixteen of those seventeen, not one declared variant exists as a class in any shipped stylesheet:
meter exported 3/3 classed ok|attention|critical tag exported 0/4 classed neutral|positive|caution|critical tooltip exported 0/1 classed descriptive button exported 0/2 classed neutral|destructive checkbox exported 0/3 classed unselected|selected|indeterminate radio-button NOT-EXPORTED 0/2 classed unselected|selected modal exported 0/2 classed blocked|pending accordion exported 0/2 classed collapsed|expanded page-header exported 0/1 classed route button-group exported 0/1 classed ranked empty-state exported 0/2 classed clear|filtered tabs exported 0/1 classed exclusive progress-indicator exported 0/2 classed advancing|blocked pagination exported 0/2 classed counted|uncounted code-snippet NOT-EXPORTED 0/1 classed snippet presence NOT-EXPORTED 0/2 classed alone|accompanied link exported 0/1 classed destination
meter reads 3/3 because it was reconciled in the pass that produced this record, and it is the only one of the seventeen where the record's variants mapped onto states the component actually has.
The mismatch is not a spelling drift. The records declare an attention vocabulary and the components ship a presentation vocabulary:
| record declares | component ships |
|---|---|
tag: neutral, positive, caution, critical | rounded, blue, green, red, orange, purple, teal, magenta, lime, yellow |
button: neutral, destructive | compact, default, subtle, primary, warning, danger, discovery, link, selected, full-width, icon, circle |
modal: blocked, pending | small, medium, large, xlarge |
link: destination | subtle |
tooltip: descriptive | top |
progress-indicator: advancing, blocked | *(no variant classes)* |
checkbox, accordion, pagination, tabs, empty-state, page-header, button-group | *(no variant classes)* |
Three are not exported at all: radio-button (the package exports Radio), code-snippet (Code and CodeBlock), presence (a type, consumed as Avatar's presence prop).
Two further measurements say how far the separation goes. None of the seventeen references a single component-tier token in its stylesheet — number-input makes 33 such references and input-field 28, which is what a component wired to its tier looks like. And the generator writes 17 files, 43,028 bytes, of packages/tokens/css/component-*.css for them, which no app imports.
verify-output.js reports 37 problems. Twenty-nine of them name one of these seventeen, including all eight remaining "told apart by hue alone" pairs.
#Decision
A component tier entry is a claim about a component this system ships. Where no such component ships, the entry is retired rather than carried.
Concretely, for each of the sixteen unreconciled entries, one of three outcomes, decided per component and recorded in a table this record carries:
- Reconcile — the record describes states the component really has, under other names. The record and the component are brought to one vocabulary, as
meterwas: the prop keeps the reference's words, the class carries the system's, and any attention rule ADR-0093 requires is drawn. This is the outcome wherever a component has genuine adverse states.
- Retire — the record describes a component the rewrite replaced. The entry, its generated
component-<slug>.css, and its generated token record go. Nothing imports them today.
- Restore — the component the record describes is one this system wants back, and the rewrite dropped it. The record stays and the component is rebuilt to it.
No entry is kept in its current state, because an entry whose variants no component carries cannot be checked by anything and cannot fail in the direction that matters. That is the property this repository has spent ADR-0119 and ADR-0120 removing from three other lists.
#The table
One row per entry, decided with the component and its stylesheet open. Rows are added as they are decided; an entry with no row is undecided and its record still stands.
Every entry has a row. The last eleven were decided under ADR-0128, which supplies the rule this table needed and which corrects the measurement above it: an unclassed variant is not evidence, because none of the operational twenty classes its variants either. Six of the eleven turned out correct as they stand.
| entry | outcome | why |
|---|---|---|
meter | reconciled | Its three declared variants map 1:1 onto three states the component has, and ADR-0093 had already specified the marks. The prop keeps the reference's words, the class carries the system's, and the triangle and octagon are drawn again. |
tag | retired | The record describes a budgeted classification Emitter, levels 1 to 3, no level 4 "because a classification has no imperative in it". fa5a969a replaced that component. What ships under the name Tag is a decorative label — "labels UI objects for quick recognition and navigation" — with ten colours, no levels, and an optional remove button, which is why the record's interactive: false is also wrong against it. Ten of verify-output's failures were this one record. |
button | reconciled | Its own fixture generator still says what it lost: "the difference between their shares is the cost of the leading octagon and nothing else". The pre-rewrite button drew the vocabulary's critical mark beside a destructive label, and b251635a dropped the mark and the variant together — the fixture has been passing variant: 'destructive' to a component that ignores it and silently rendering a default button ever since. The octagon is drawn again, keyed to a destructive class carried beside the reference's danger appearance, because an appearance is a fill and a variant is an attention claim. |
code-snippet | retired | The package exports Code and CodeBlock; nothing is named CodeSnippet. No fixture, no scene, no generator to take with it. |
presence | retired | It became Avatar's presence prop — a capability that is a property rather than a component, which is ADR-0059's pattern. |
progress-indicator | retired | The record describes advancing and blocked; the component is the reference's dot stepper and has no states at all. Its scene, 57-progress-indicator-blocked, renders an empty <div class="wr-progress-indicator"> — a must-pass scene measuring nothing, which is why retiring it costs no evidence. |
checkbox | reconciled | The question this row had to answer first is answered by the two records themselves: checkbox and radio declare the same selector, .wr-choice, and the same 36 fixture elements answer to both. .wr-choice is one component. checkbox is the survivor because its three variants are the three a choice can be in, and because DataTable's selection prop still speaks them. Correct as it stands under ADR-0128: an Emitter's level list is a capability, and 1 to 3 is what a choice may hold. |
radio | retired | Into checkbox, above. The two records declare one selector, .wr-choice, and the same 36 fixture elements answer to both. A radio is a choice whose box is round and whose selection is exclusive: the shape is the encoding, which is §4's field-first principle, and the exclusivity is a property of the group rather than of the control. Executed by ADR-0129 at build position 14, so every ordinal after it stays what it was. Scene 28 stays and its generator inlines the surviving record's tier, because the round box is a glyph ADR-0028's shape argument turns on and scene 27 does not draw it. The docs roster keeps two entries: the reference documents two components, which is a presentation fact and not an attention one. |
modal | reconciled | Narrowed from [3, 4] to [4]. Every other Scope in the tier declares exactly one rung, and the eight fixture elements carrying .wr-modal declare 4 and never 3. A Scope owns a budget rather than holding an allocation, so a range here was never the capability list an Emitter's is (ADR-0128). Its two variants, blocked and pending, are left: they name what a modal is for, and nothing in the system reads them. |
tooltip | reconciled | Correct as it stands. An Emitter recording [1, 2], which under ADR-0128 is what it may hold rather than what a fixture caught it holding. Its one variant names its kind, as region's set and popover's anchored do. ADR-0121's measurement had it queued for a change it does not need. |
page-header | reconciled | Correct as it stands, and the only one of the eleven that was already exact on both columns: a Scope at [1], which is what its twelve fixture elements declare, and one variant naming its kind. |
button-group | reconciled | Correct as it stands, and the sharpest misreading of the eleven. It writes no level anywhere, in the React render or in its five fixture elements, which reads as a component declaring nothing and is in fact an Arbiter behaving exactly as queue-item, table-row and navigation-item do: §3 gives an Arbiter the grant and gives the allocation to the Emitter (ADR-0128). |
empty-state | reconciled | Correct as it stands. An Emitter recording [1, 2] and rendering 2. Its two variants, clear and filtered, name a distinction the copy carries rather than the attention model, which is a reason to leave them rather than to class them: a .wr-empty-state--filtered would style a state the component cannot be in, which is the trade this record already refuses. |
pagination | reconciled | Correct as it stands. An Emitter recording [1, 2] and rendering 1. uncounted reads as unreachable against the React component, whose pages prop is required, and scene 58 is named for it, so the variant names a case the scene set still exercises even where the reference component does not. |
link | reconciled | Correct as it stands, exact on both columns: an Emitter at [2], which is what its five fixture elements declare. Its subtle appearance is a fill and its destination variant is an attention claim, which is the distinction this record already drew for button. |
accordion | reconciled | A layer contradiction rather than a level error. .wr-accordion writes data-wr-level="1" on the Arbiter's own root, and data-wr-level is what declares an Emitter, so the markup and the record disagree about what the component is. The three operational Arbiters never write it; wr-queue-item carries its rung as a class and reads its tier through inline properties. Executed by ADR-0129, which gave both Arbiters a slot-only declaration and moved no measured value at all: the roots painted nothing attributable, so the declarations were noise in the model rather than weight in it. Scene 41 still fails on its own reason. |
tabs | reconciled | The same contradiction, at level 2, and the component otherwise furthest along: it is the one entry whose tier an app imports and whose stylesheet reads it, 22 references in navigation.css. Executed by ADR-0129 with the accordion, and everything except the declaration was already where it should be. |
#What tag's retirement does not settle
The capability did not disappear with the component. Lozenge ships the classification role the record described — "a visual indicator used to highlight an item's status" — and its appearances carry the same distinctions the record named: default, success, removed and moved render as Draft, Completed, Blocked and At risk against the record's neutral, positive, critical and caution.
So the honest reading is that the role moved rather than ended, and lozenge wants a record of its own. That is not written here, for two reasons. The first is scope: a record for lozenge is an attention decision about a component this system has never budgeted, not a transcription of the one being retired. The second is a real question it has to answer first — Lozenge also ships four decorative appearances (lime, yellow, teal, magenta) alongside its four status ones, and a budgeted Emitter either holds an allocation or does not. Whether those four are classifications the record simply has not named, or a second component wearing one name, decides what lozenge's record can say.
Until that record exists, the classification label is outside the budget. That is a gap, it is named here, and it is the first thing this table's next row should answer.
#Rejected options
Add the missing classes so the checks pass. Sixteen components, thirty-odd classes, each existing so a check can find a rule inside it. Rejected because it is the inverse of what a class is for: .wr-progress-indicator--blocked would style a state the component cannot be in, and .wr-button--destructive would duplicate .wr-button--danger under a name nothing renders. It would also convert twenty-nine honest failures into twenty-nine green ticks over fiction, which is the exact trade ADR-0032 refuses for baselines and ADR-0120 refuses for the copy corpus.
Delete component.config.json's seventeen entries in one sweep, without deciding each. Fast, and it would clear twenty-nine failures immediately. Rejected because the seventeen are not one kind of thing. meter was worth reconciling and proved it. presence became a property, which is a decision ADR-0059 already has a pattern for. radio-button is a rename. Sweeping them together would retire a real capability on the same line as a spelling change, with no record of which was which.
Leave the tier alone and narrow the checks to the operational twenty. Defensible: the twenty are the set with contracts, fixtures and a proved-complete list, and the checks were written for them. Rejected because it makes the Atlassian-baseline set — the set the docs site documents, sixty-seven entries — permanently ungoverned. The attention budget is the thesis of this system; a component library where two thirds of the components are outside it is not a design system with a budget, it is a design system with a budget for the older third.
Treat this as ADR-0093 work and design the eight missing distinctions now. This is what the pass that found it set out to do, and it completed two of them — meter and number-input — before the measurements above made it clear the rest are not the same job. Rejected as sequencing: six of the eight are tag, and what tag should look like cannot be decided before deciding whether tag is a status component or a decorative one.
#Consequences
Twenty-nine of verify-output's thirty-seven failures are in scope of this record, and the eight that are not — two raw family names in navigation.css, two type roles below the 1rem floor, and four others — are ordinary defects to fix on their own.
roster:check's last failure resolves here too. presence is marked built and the package exports it as a type; that is outcome 1 or 2 for presence, and the roster row follows whichever is chosen. The built count moves off 37 and docs/STATUS.md moves with it.
The generated tier shrinks by whatever is retired, at about 2.5KB per component, none of it currently imported by any app.
What this record does not settle. It does not say which outcome each of the sixteen takes. That table is the work this record authorises and the next pass writes, one component at a time, with the component and its stylesheet open. Writing the table from the measurements alone would be the sweep this record rejects.