ADR-0125 The cascade is read, and the grouped set consumes the tier generated for it
Builds on ADR-0107 and ADR-0120. Amends neither.
#Context
Two records already settled the things this one could have spent itself re-deciding.
ADR-0107 decided that no component sustains motion, removed the four loops, and asserted the absence in packages/react/test/discipline.test.js. ADR-0120 recorded that the package holds two populations: COMPONENT_LAYERS, the operational twenty with one module and one stylesheet and one token record each, and the Atlassian-baseline grouped set the docs roster documents, whose files are listed by name in GROUPED_SET_ARTIFACTS with the reason attached to each.
What neither of them has is a reader. Every motion rule in the system examines a declaration, and the declarations are all correct.
verify-output.js §6 verifies the shipped grant files across five levels and two resolutions: every non-static cap an alias into the duration scale, sustained below level 4 a failure, the reduced resolution never exceeding level 1, a prefers-reduced-motion block present in css/motion.css. Further down, each component's declared motion block is resolved against its level's grant, so an easing outside the permitted set or a duration that is not that level's cap fails. All of it passes. None of it opens a stylesheet.
Measured this session, in packages/react/src, which ships whole: the package exports "./css/*": "./src/*".
Four of the 33 components that declare a motion block actually move, and three of them move by a number. button, accordion and meter each carry a transition driven by a literal while the tier generates a grant for them: --wr-button-transition-duration is emitted, resolves to 110ms on the enter curve, and button.css:21 writes transition: background-color 100ms ease-out. The fourth, data-table, writes transition: none in a reduced-motion block, which is a grant switched off rather than ignored.
All three are grouped set, and that is the shape of the defect. The grouped set is a second population, not a second system. It gets token records generated for it, motion grants included. Its stylesheets were authored in the Atlassian shape and never consumed them. ADR-0120's boundary says these components are documented by the roster rather than by COMPONENT_LAYERS. It does not say they are outside the token tier, and nothing has been checking the difference.
The other 29 hold a motion record and never move. That is not the same defect and it is not one: a grant is a ceiling a component may decline, and a rule requiring every declared grant to be exercised would be a rule requiring motion.
11 declarations carry a literal duration, across button.css, content.css, feedback.css and forms.css, all four of them grouped set. §7 calls a raw ms value a lint error. There has been no lint. ease-out is not a member of the standard easing set either, and four declarations name it.
Three of those literals loop forever: the spinner at 0.86s, the indeterminate progress bar at 1.4s, the skeleton shimmer at 1.4s. These three are grouped set with no token record at all, so ADR-0107's rule was never asked about them: its test walks a roster of 37 names and these are not among them.
The indeterminate bar honours no reduced-motion behaviour. The spinner slows to 3s, the shimmer stops, the bar runs at full rate under prefers-reduced-motion: reduce. §10 carries a row for exactly this and it has never looked at a stylesheet.
#Decision
#The cascade is read
A check reads every stylesheet in packages/react/src. It is packages/react/test/cascade.test.js, a file of its own, run by pnpm react:cascade from the budget workflow and classified in tools/battery.js like every other check. Three rules.
Every stylesheet: no literal duration and no literal easing in a transition-* or animation-* declaration. The value comes from the emitted vocabulary. Both populations, because the value vocabulary is not the attention budget: the grouped set is outside COMPONENT_LAYERS, not outside the units the system generates.
A moving component that holds a token record moves by its own grant, and does not loop. none and the CSS-wide keywords are exempt from the first half: they switch motion off rather than measure it, so there is no grant for them to disagree with. The second half is ADR-0107, moved off a hand-maintained roster of 37 names and onto the property itself, so it now covers whatever ships rather than whatever someone remembered to list.
Every stylesheet: a looping animation carries reduced-motion handling. Stop it or slow it. Neither is optional and the boundary does not apply, because prefers-reduced-motion is an accessibility floor rather than an allocation.
#The primitives gain a period scale
A loop's rate is a quantity the emitted vocabulary does not contain. The duration scale runs 80 to 430ms and a spinner turns at 860, so rule one is unsatisfiable for the three loops and the only exit is a literal.
motion.period joins the primitives on the duration scale's terms, the same 5ms quantum and the same rounding-collision failure: base 800ms, ratio 1.4, five steps, 800, 1120, 1570, 2195, 3075. The spinner takes step 0, the shimmer and the indeterminate bar take step 2, the spinner's reduced rate takes step 4.
Vocabulary only. No grant, no motion class, no level. It gives a loop a step to name and gives it nothing else. Whether a loop should be granted at all is ADR-0126's question, and this record does not answer it.
#Consequences
The three channels can be described the same way for the first time. Light is measured in rendered pixels by the budget engine, language is scanned in shipped strings by content-lint, motion is now read in the shipped cascade rather than in the config that describes it. The homepage's claim about light, language and motion was carrying the third on trust.
ADR-0107's rule stops depending on a roster someone has to remember to extend. That file's own comment records the last time it fell behind: four entries were missing until ADR-0051 added them. The three loops are the same failure, still open, found from the other side.
No fixture and no baseline moves. measure.js screenshots with animations: 'disabled', so a settled capture cannot see a transition duration or a loop period. Assert it by running the suites, not by this sentence.
Three defects are fixed in passing: the indeterminate bar gains reduced-motion handling, button, accordion and meter start moving by the grants generated for them, and seven declarations stop easing on ease-out, a curve the standard set does not contain and no grant has ever permitted.
The check reports 23 of the 33 components holding a motion record as moving by their own grant, which is the operational twenty plus the three repaired here. That figure is printed by the suite rather than written down anywhere, so nothing has to be kept in step with it.
#Rejected
Give the loops an operative grant class, off the ladder, floored by period rather than capped by duration. The strongest form of the other answer, and a real design: motion bounded by an operation the system is running is not motion bounded by a condition a person must clear, and ADR-0107's furniture argument addresses the second kind only. Rejected on sequencing, not on merit. ADR-0107 removed sustained motion deliberately and left the ground for a later record that wanted a bounded form back. Taking it inside a record about reading stylesheets would carry the larger decision in under the smaller one. ADR-0126 asks it on its own.
Exempt the three loops by name. ADR-0120 already listed their files as grouped set, and a reader might call that permission. It is not: that record classifies which battery a file answers to, and says in its own comment that a widened skip pattern was rejected because it would swallow the next genuine orphan. Reusing it as a motion exemption would turn a classification into a licence.
Remove the three loops, because ADR-0107 removed the other four. That record's roster is the operational twenty and its argument is about demands. A spinner that does not spin is not a stricter system, it is a broken affordance.
Put the check in discipline.test.js, where ADR-0107's rule lives. The first plan, and it was abandoned on discovering that the file does not run. pnpm react:test runs test/scope.test.js; test:discipline is a script no workflow invokes, and the CI step that runs react:test carries a comment describing discipline.test.js rather than the file it actually runs. The test itself exits at meter on a missing meter.css: 16 of its 37 entries name stylesheets the grouped-modules refactor deleted. So ADR-0107's enforcement has not executed since that refactor, which is the same failure this record is about, one layer up, and it is why the three loops were reachable in the first place.
That repair is its own unit. Putting a new check inside a file that does not run would have been a check that reports nothing, which tools/battery.js exists to prevent.
Put the check in verify-output.js beside the other motion rules. It would make the tokens package read packages/react/src, which is the dependency backwards. Tokens emits the vocabulary; react consumes it; the consumer is where consumption is checked.
Put it in the ESLint plugin beside no-raw-motion-values. That rule parses JSX and these values are in stylesheets it never sees.