ADR-0107 Nothing sustains motion
Amends playbook §2. Supersedes the sustained clause of ADR-0018.
#Context
Playbook §2 grants sustained motion at level 4 and nowhere else, and only while a demand is live. Four components took the grant: alert, modal, staleness and status indicator. Each animated an edge on an infinite alternating loop for as long as the condition lasted.
The mechanism was chosen carefully. It moves the box edge rather than the colour, because colour is the channel a demand has already spent to the region maximum and pulsing it would either dip below the level's emission or exceed it. An edge that breathes rings the same bell without moving the budget's floor or ceiling.
The maintainer looked at it on /components/alert and said it can never be used in a product.
They are right, and the argument against it is the system's own. A thing that never stops moving is not a signal, it is a condition of the room. Someone working a shift beside a pulsing alert stops seeing it within minutes while it goes on costing them for hours: peripheral motion is expensive to ignore and the cost does not fall off the way the salience does. The budget prices the pixels an animation paints and cannot price that. A demand is supposed to be the one thing that needs a person now; a demand that is still breathing twenty minutes later has become furniture, and the ladder has no rung above it to escalate to.
#Decision
- No component sustains motion. The four
@keyframesblocks, the four
rules that ran them, and the reduced-motion rules that existed only to stop them are removed. Each of the four contracts now declares motion.sustained: null, which 33 of the 37 already did.
- Motion stays one of the three channels. It is still spent, on the
transition: a component moves when it arrives, when it leaves, and when its level changes. That is motion reporting a change, which is what the channel is good at, rather than motion asserting a state, which is what it is bad at.
- The grant in
coreis left in place.MOTION_GRANTS[4]still lists
sustained, so the type still refuses it at level 3. Removing the grant would make the rule unstatable rather than unused, and a future record wanting a bounded sustained behaviour (three cycles on arrival, say) would have to rebuild the machinery to say so.
- The React discipline test asserts the absence for every component, not
just the four. The four are where it was removed; the check is what stops it reappearing somewhere else.
- A live demand no longer animates a docs specimen.
demandLiveis opt in
on the docs site. That change stands on its own: a gallery renders every legal cell of every component at once, and a page that breaks the one-demand-per-region rule while explaining it is arguing against itself.
#Consequences
No budget value moves. The engine captures under reduced motion, where these animations were already switched off, so the scenes measure what they measured before. Confirmed: the same 32 intentionally red fixtures before and after, diffed rather than eyeballed.
The stylesheets of three of the four now carry no --demanded selector at all, because the animation rule was the only thing naming the class. The level is carried by the mark token the render selects, which is where it always was.
#Alternatives
Bound the loop. Three cycles on arrival, then still. It answers the "furniture" objection and not the one underneath it: a demand that has been acknowledged descends a rung (ADR-0050), and one that has not is still on the screen, so the thing that would stop the motion is the thing that already changes the level. The motion adds nothing the level change does not.
Keep it for field-night only. Where hue carries nothing, motion is a channel with more work to do. Rejected because that theme is for night operations, which is exactly where a moving thing in the periphery is most expensive.
Remove sustained from MOTION_GRANTS. Makes the rule unstatable rather than unused. See the decision above.