ADR-0094 Focus is drawn by the system, and the fixtures are build output
#Context
The design pass began by asking what a keyboard user sees. Nine components accept input. Tabbed through their specimens on the docs site:
input-fieldandnumber-inputremoved focus. Both stylesheets set
outline: none on :focus-visible, on the reasoning that a focused field is told apart by its ground. It is — when the caller passes state="focused". These components are presentational and never set that state themselves, so real keyboard focus drew nothing.
checkboxandradio-buttondrew focus where nobody could see it. Their
native input is transparent over the drawn box, so the browser's ring was painted on an invisible element.
- The other five fell back to the browser's own ring, which no theme here
was designed against — in field-night that is a hue the theme exists to avoid.
No check read any of this. Every check in the repository reads declarations, tokens, source and receipts, and the site check reads layout; none moved focus.
#Decision
One ring, drawn by the system, on every component that accepts input. Each interactive component's tier gains focus-ring-color — text-primary, the role furthest from every ground in every theme — and focus-ring-width and focus-ring-offset, both border-heavy. The ring is an outline outside the box, so it moves no layout. Where the native control is transparent, the ring goes on the shape the reader sees. The two suppressions are removed.
A site check moves focus. docs:check-site tabs through each interactive component's specimens as a keyboard user would, and every stop must draw an outline on the focused element or on the shape beside it — and an outline on a transparent element does not count. It was run against the old stylesheets first and reported all five failing stops across the four components above, after a first draft that counted the transparent inputs as drawn was caught missing two of them. 62 focus stops pass on the new stylesheets.
No measured value moves, and that was checked rather than assumed: no scene holds focus, and all 63 unthemed and 197 themed comparisons are identical.
#Found while regenerating: the fixtures had drifted
The fixtures inline the built stylesheets, so they were regenerated — and thirty-odd of them changed before the focus rules did: they carried stylesheet comments their components had rewritten since. The measured values were identical, because comments render nothing, but committed output that its generator no longer produces is the failure build:check exists to refuse, and it never looked at fixtures.
The fixtures are now the fourth committed output. fixtures:all reruns every generator the budget package declares, then the themed set, and build:check asserts the tree still matches HEAD. One generator is excluded by name with its reason printed: fixture:proof captures the proof product from a running server and is not a build step.
#Rejected
- Draw focus with the ground, as
state="focused"does. The component does
not know it is focused unless the product tells it, and a product that forgot would ship an invisible focus.
- A focus role in the semantic tier.
text-primaryalready clears every
ground by the widest margin in every theme; a new role would be a second name for it.
- An inset ring. It would draw inside the governed box and change emission.
- Leave the fixtures out of
build:checkbecause their values were right.
A committed file is a claim about what its source produces, and these were not.