The card picker greys out a card that is already dealt and hangs the reason
off a `title`. Measured on an iPhone with three hands out: seven of the
fifty-two were dead, none of them could take focus, and tapping one changed
nothing at all — no toast, no text, not a byte of DOM. The tool's answer to
"who has the ace of spades?" existed and was reachable only with a mouse.
The header's two buttons were the same, on a new dealer's first screen, and
Export CSV was greyed out with no explanation written anywhere.
`disabled` is the wrong attribute whenever there is something to say: it
drops the control out of the tab order, so a screen reader never lands on it
to hear the description, and it eats the tap on a touch screen, where the
hover that would have shown the `title` does not exist. `aria-disabled`
announces the same state and leaves the control reachable, so one capture
listener can turn the press into a sentence — in a toast, or inside a sheet
that is standing on top of the toast.
It paid for itself in the picker's keyboard too. The arrows hunted past
dealt cards because a `disabled` button cannot hold focus, and the hunting
did the exact thing the grid's no-wrap rule was written to prevent: Right
from A-spades over a dealt A-hearts landed on A-diamonds, so the column
shifted and the next press of Down walked a different suit. A cell that can
hold focus needs no hunting, and the APG asks for that inside a composite
widget anyway.
The line that answers is one line by measurement, not by taste. Its box is
318px on a Galaxy S8 at 20px of root text; the first wording drew 332px and
wrapped, shoving fifty-two targets 21px down mid-tap, and twenty-four capital
Ws draw 434px whatever the wording, so the box is what holds.
Two things the sweep found on the way. It opened the card picker by
un-hiding an overlay whose grid had never been painted, so for its whole
life it audited an empty sheet; it now opens it the way a dealer does, with
cards out and the answer showing. And the slot buttons above the grid were
named to a screen reader by the em dash they draw when empty.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Modal answers one question for all six overlays — where focus goes and how
Tab is held — and this round it was measured as wrong in three directions.
Its list of Tab stops could not see a <summary>. The rulings sheet is twelve
of them and one Done button, so the trap decided Done was both ends of the
ring: eighty Tab presses, one element ever reached, twelve rulings that could
not be opened without a mouse. Adding `details > summary` gives the sheet its
thirteen stops back. `:not([tabindex="-1"])` moves onto every line while it is
open, because a control taken out of the Tab order still matches `button`.
"The first control" was the card picker's Clear: the sheet a dealer opens to
write a card down opened on the button that rubs one out. A sheet names its
own landing spot now, and on five of six it is the heading — which says which
seat, which card, which pot. The confirm dialog keeps a control, because a
short question with one irreversible answer should land on the answer that
leaves the dealer no worse off.
And a sheet that redraws itself was deleting the control that held focus.
Choosing a buy-in, picking a card, switching slots, confirming a side pot —
all four left focus on the document body. Rebuilt controls carry a name now
and are handed back to whoever had them; where there is no coming back, the
caller says where to go instead.
The card grid was fifty-two Tab stops of the fifty-six in that sheet. It is
one now, with the arrows moving inside it, stepping over cards already dealt
and stopping at the edges rather than wrapping onto a different rank.
1136 tests, 418 audits clean.