Skip to contentWolf-Rayet

Decision records

ADR-0059 Disclosure is a property, not a component, and for one of them it is the resting state

Accepted2026-09-05Phase 4, reopened

#Context

ADR-0040 decided that a collapsed panel's children are absent from the frame rather than exempted from the budget, and that because absence is free, a demand may not be the thing that is hidden. It is one of the load-bearing records in the system: it produced a check kind, a must-fail scene, a rule about a stylesheet, and the finding that hidden is a presentational hint any class rule outranks.

It states that rule about an accordion. The record names that component seventeen times, and the sentence carrying the decision reads *"an accordion may not collapse a panel that holds a Demanded element."*

The enforcement was never about the accordion. The check, in packages/budget/src/engine.js, is this:

const hiddenDemands = dom.nodes.filter((n) => !n.rendered && n.level === DEMAND_LEVEL && !n.inExemptSlot);

No component name, no class, no panel. It reads any node in the frame. So the rule has been general since the day it was written and the record has not — which is the exact shape ADR-0046 avoided when it stated its own rule on *persistence* rather than on the three shell components that happened to be persistent, and which ADR-0053 needed two records later when something finally stood in the complement.

tabs is the second component that discloses, and it is where the gap costs something.

#Decision

ADR-0040's rule is restated on the property: a component that removes an element from the frame may not remove a Demanded one. The record is amended, not the engine — nothing in engine.js changes, because nothing in it was ever specific.

Three things follow, and the third is the one that had to be decided rather than transcribed.

#The react suite's disclosure checks were reading a narrower set than their subject

The three checks ADR-0040 produced — the panel is not clipped, the component writes the attribute, and the [hidden] rule beats the panel's own display — ran over this set:

const DISCLOSING = COMPONENTS.filter((c) => (c.ownShapes ?? {}).disclosure !== undefined
  && read(path.join(PKG, 'src', c.tsx)).includes('hidden={'));

ownShapes.disclosure is the accordion's chevron. A component that removes a panel and draws its state as an edge rather than as a chevron wrote the attribute, disclosed exactly as the accordion does, and sat outside all three checks. tabs is that component. This is the defect ADR-0056 generalises, and this session's fifth instance of it: *a check reading a narrower set than its subject. Widen the set; never patch the instance.*

Widening it found that the set has two shapes and that the widening was owed twice over.

removesADR-0040's panel checksthe cascade check
accordiona panel it holdsapplyapplies
tabsa panel it holdsapplyapplies
toastitselfno objectapplies, and did not run
tooltipitselfno objectapplies, and did not run

toast and tooltip write hidden={!visible} on their own root — ADR-0026's transience rather than ADR-0040's disclosure — and .wr-toast sets display: flex while .wr-tooltip sets display: inline-flex. Both are removed by an attribute a class rule outranks. Both carry the [hidden] rule that saves them. Neither was checked. Two components had the exact bug the check exists to catch, did not have it, and nothing in the repository knew which. The set is now every component that writes the attribute, the removed element is read out of the source rather than assumed to be a panel, and the cascade check runs against whatever that element is.

#A tab strip renders what it hides, so that hiding it can be refused

This is the decision rather than a transcription, and the alternative is the tempting one.

A tab strip that rendered only the selected panel would be smaller, simpler and faster. It would also put ADR-0040's illegal state permanently out of reach: Salience — hidden reads !n.rendered && n.level === DEMAND_LEVEL over the nodes in the frame, and a panel that was never rendered contributes no node at all. A demand in it would not be a hidden demand. It would be nothing, and nothing is what no check can see.

So the component is required to render every panel and remove the unselected ones with hidden — the accordion's mechanism adopted whole, including the explicit [hidden] { display: none } rule, because .wr-tabs__panel sets a display of its own and a class selector outranks the UA hint.

This is the same test ADR-0040 used to distinguish collapse from the three lauderings it refuses. There, the question was whether the check and the reader disagree. Here the reader sees nothing either way; what separates the two implementations is whether *the system can still be asked*. An implementation that makes an illegal state unrepresentable is good. An implementation that makes it unobservable is the same screen with the alarm removed from the record.

#For a tab strip, disclosure is the resting state, and that changes what the remedy can say

A collapsed accordion panel is a state a reader chose and can undo by choosing again. A tab strip of n panels has n−1 absent at every moment it exists, and no act of the reader's put them there.

ADR-0040's remedy, as the engine printed it until this record, was: *"Lift the demand to something on the screen — a disclosure header carries a summary for exactly this — or leave the panel open."* For a tab strip the second half names something a reader cannot do. The panel was never open. The remedy is restated: put the demand in the selected panel, in a route header's summary region (which ADR-0053 finally built), or outside the disclosing component — and "leave it open" is named as the remedy for the case where somebody closed something.

And the strip may not carry the demand on the panel's behalf. Every Arbiter's ceiling is Directed, which is ordinarily the queue item's argument — a container that could reach the region maximum would compete with the things it exists to rank. Here that ceiling does a second job: a tab drawing a demand for a panel the reader cannot see is precisely the screen ADR-0040 refuses, arriving through the component that removed the panel rather than through the reader who closed it.

#The component

tabs is section B's own first example — *"Each ranks one member of a set above its siblings — a selected tab, …"* — so the layer question was answered before the row existed. What needed deciding is above.

It is the second Arbiter to use §3's grant. ADR-0054 built the first and found that four Arbiters had been built without any of them assigning a level to anything: a queue item's rank, a table row's order and an accordion's disclosure are resolutions by position, which is real and is not a grant. tabsGrant is buttonGroupGrant's shape deliberately — §3 gives the tier one power and a second spelling of it would be a second thing to keep in agreement.

The panels are data and not children, which is ADR-0054's finding applied a second time: a strip that accepted <Panel level={3}/> could not grant anything, because the caller would already have decided. "Exactly one selected" is unrepresentable rather than validated, and it takes three fields rather than button-group's two — before, selected, after — because a tab strip's members have an order and a button set's do not.

One variant, and its name is the rule of the set rather than a state. ADR-0028: a checkbox is square because several of its set may be picked, a radio button is a circle because exactly one may be. A tab strip is that exclusivity one tier up, at the Arbiter, where the container makes the choice rather than the control reporting it. So the variant is exclusive.

Carbon's three variants are not three variants here. Line against contained is emphasis, and ADR-0027 settled what emphasis is: it is the level, which the ladder already expresses. Vertical is orientation, which is where a strip sits rather than how loud it is — typed, like a table row's alignments, and off the variant axis.

#Measured: two scenes with the same receipt

Scene 55 is a tab strip at rest. Scene 56 is scene 55 with one element changed — #press-head2 declared at level 4 instead of level 2, inside a panel the strip removed from the frame. It is declared must-fail and fails on Salience — hidden, naming the element, its Scope and the View.

Scope55 (passes)56 (must fail)
#shell0.0583070.058307
#scope-views0.0396640.039664
#scope-line0.0469670.046967

Identical on every Scope, to six decimals. That is ADR-0040's premise as a measurement rather than an argument: absence is free, so a demand in a removed panel costs exactly nothing, and the legal screen and the illegal one are indistinguishable by emission. Nothing a pixel-reading check could do would tell them apart. Only the declaration separates them, which is why the check that refuses this reads declarations and no pixels — and why an implementation that removed the declaration from the frame entirely would have removed the last thing that can.

Scene 56 is also the first scene in the set whose illegal state is a component's default rather than something done to it. Scene 41 is a reader collapsing a panel over a demand; here nobody did anything.

Both scenes hold their Scopes as <Region>, which makes them the first two composed after ADR-0058 and the first two whose containers are a component rather than a hand-written section.

#Rejected options

Render only the selected panel. Smaller, faster, and what most implementations do. Rejected above at length: it does not make the illegal state impossible, it makes it invisible, and the record that exists to refuse it would have no object. The system's own habit is the argument — ADR-0048 refused a screen that replaces a lie with a blank, ADR-0055 refused an absence that renders identically whatever the reason, and this is the same refusal at the level of what a check can reach.

Let the strip carry the maximum of its panels. Genuinely attractive: it is ADR-0040's own remedy — lift the demand somewhere the reader is looking — arriving inside the component instead of in a page header, and ADR-0054 had just established that an Arbiter may assign a level. Rejected because it does not work and would look as though it did. The element in the hidden panel is still declared at level 4 and still absent, so Salience — hidden fails whatever the tab draws; the lift would produce a strip shouting about a panel while the check refuses the screen anyway. And if the check were relaxed to permit it, the screen would be one where a reader is asked to act on something that is not on the screen — which is what ADR-0040 refuses in the first place.

Give tabs its own check kind rather than restating ADR-0040. It would have been the smaller edit and it would have doubled a rule. Salience — hidden already reads every node and names no component; a second check for the same illegal state would be a fact with two derivations, which is what tools/roster.js exists to stop one tier down and what ADR-0053 found had already gone wrong once.

Leave the react suite's filter alone and add tabs to it by giving it a disclosure shape. Patching the instance. It would have put a polygon in a contract to satisfy a test, which is the tail wagging the component, and the next discloser would be outside the set again. ADR-0056's sentence is the answer: widen the set, never patch the instance.

#Consequences

Nothing in the engine changes, which is the point. engine.js is untouched; report.js's prose is amended, because the message a reader actually meets said "collapsed panel" and told them to leave it open.

The react suite's disclosure section grows from 1 component and 3 checks to 4 components and 7, and two of the four are ones ADR-0040 never contemplated. The widened cascade check was observed red on toast — its [hidden] rule removed, the attribute still written, the class rule still setting display — before being trusted green (ADR-0041).

Roster 54, built 29 → 30. Section B is ten rows and eight remain.

Scene 56 is the eleventh declared must-fail scene and the second to fail on Salience — hidden. It has no themed sibling; the RETHEMED list ends at 47 and seven scenes now sit past it, which is the gap ADR-0058 named and this record does not close.

Playbook §10 stands at 26. No new check kind: the whole of this record is that one already existed and was more general than its own account of itself.

linux-x64 is short by exactly the two new scenes, six Scope values.

#Measured

  • ADR-0040 names accordion 17 times and states its rule about that component; engine.js filters !n.rendered && n.level === DEMAND_LEVEL and names none — *disk, this pass*.
  • The react suite's DISCLOSING set before this pass: 1 component, 3 checks. After: 4 components, 7 checks, of which toast and tooltip were never reached by the check whose bug they could have carried.
  • Scenes 55 and 56: identical emission on all three Scopes to six decimals; 56 fails on Salience — hidden naming #press-head2 at level 4.
  • tabs: 23 component tier tokens, 36 APCA floor checks all met, worst |Lc| 16.23 against an interior-light Ambient floor of 15.
  • Roster 54, built 29 → 30. pnpm battery — the verdict this pass is measured by.