Skip to contentWolf-Rayet

Decision records

ADR-0131 Wiring a component to its tier is a record question

Accepted2026-09-24Phase 7

Opens the tier-wiring job ADR-0122 and ADR-0125 both pointed at. Wires the first component and reports what the second cost.

#Context

ADR-0122 found the docs shell drawing from wolf-rayet.css's flat default surface, which answers to data-theme and has two modes, while the semantic tier answers to data-wr-theme and has four. Its comment named the wider case in passing: *the grouped sheets read none of it.* ADR-0125 measured the same boundary in the motion channel and found thirteen grants issued and never opened. ADR-0130 found lozenge on the flat surface too, and could not write it a record because of it.

Measured this session across every component holding a token record, counting the custom properties each one's rules read:

The operational twenty read their tier and nothing else. navigation-item reads 15 of its 29 tier tokens and 0 foreign names, popover 10 of 14 and 0, live-feed 10 of 16 and 0. Twenty components, zero foreign reads between them.

The eleven in grouped stylesheets read none of their tier. meter 0 of 29, tooltip 0 of 14, button 0 of 32, checkbox 0 of 29, modal 0 of 22, accordion 0 of 35, page-header 0 of 17, button-group 0 of 15, empty-state 0 of 17, pagination 0 of 21, link 0 of 13. Between them they read the flat surface directly: --wr-space-100, --wr-text, --wr-font, --wr-neutral-boldest and the rest.

§9 is blunt about what that is: *never let a component reference a primitive directly, that single discipline is what makes theming work.* The generator writes 249 tokens for these eleven and no stylesheet opens one of them.

#Decision

A component holding a token record reads its own tier and no primitive. The rule is §9's and it is not new; what is new is that the eleven are now known by name and the job is opened rather than referred to.

button-group is wired, and it is the worked example of the cheap case. Its rule read --wr-space-050, a flat 4px at any density. It now reads --wr-button-group-gap, which is the space-inline-tight role: 4px at comfortable, and an answer to the density switch at compact. Same pixel today and a different one the moment a reader asks for it. 69 baseline comparisons identical to six decimals, nothing moved.

A tier has to be imported to resolve, so apps/docs/app/ds/styles.ts gains one line per wired component. A component reading a tier that is not on the page draws nothing at all rather than something wrong, which is the failure mode ADR-0122 spent a pass on, so the list carries a comment saying so.

#What the second component cost, and why the rest are sequenced

page-header was wired and reverted. Its rules read --wr-space-100, --wr-space-200, --wr-text and a hard-coded 24px; its tier offers gap, pad, title-color and title-size. The wiring was mechanical and the result was not: four baselines moved, worst Δ 1.31e-3, and the title dropped from 24px to 18.29px in the browser.

That is not a wiring error. It is a disagreement the wiring exposed, and it is three-way:

  • the component draws 24px, which is the text-title role
  • the record declares title-size: text-lead, which is 1.143rem
  • the record's own note says the title *takes the primary role at body size because it is what the route is*, which is text-body, smaller still

Three sources, three answers, none of them consulted by the other two, because nothing read the tier. Deciding it means deciding how loud a page title is, which is an attention question about a component rather than a substitution.

Every one of the remaining nine will raise its own version. empty-state's tier puts its pad at space-stack where the component draws space-300, 20px against 24px, and its reason text at text-secondary where the component draws text-subtle. button reads 38 foreign names against a 32-token tier. So the job is a series of record decisions with a component's appearance attached to each, not a sweep, and it is sequenced one component per unit on that basis.

#Consequences

The boundary is now a list rather than an observation. Eleven components, named, with the count of what each reads and what its tier offers, measured rather than estimated.

button-group is the first grouped component to read its own tier since tabs, and it cost nothing, which is worth knowing: not every one of the eleven is a design decision. The ones whose tier and flat values coincide are free, and they can be taken whenever.

No check lands here. The rule is stated and the obvious enforcement, that a record-holding component reads no name its tier does not define, would fail on ten components today. That is the same wall ADR-0125 and ADR-0128 hit, and the same answer: the check lands with the last component it would fail on. Until then this record is the list, and the list is what stops the job being referred to again.

#Rejected

Wire all eleven in one pass and re-record the baselines. The sweep. Every component's appearance changes, each change is a judgement about what its record should say, and a single commit re-recording the darwin and linux baselines over eleven undiscussed visual changes is the trade ADR-0032 refuses for exactly this reason.

Fit each record to what its component already draws, so nothing moves. It would wire all eleven today at zero visual cost and it is ADR-0124's rejected option in a new place: a number fitted to the output describes it rather than constrains it. page-header is the argument against it, because the value that would be written down is the one the record's own note says is wrong.

Leave the eleven on the flat surface and narrow the tier to the twenty. ADR-0121 rejected this and its reason holds: a design system where two thirds of the components are outside the budget is a design system with a budget for the older third.

Delete the 249 unread tokens until someone needs them. They are the record's claims in resolvable form, and deleting them would make the gap invisible instead of unread. The tokens are not the problem; nothing opening them is.