Hold'em: give the seat holding the dealer button its own middle back #121

Merged
jheaps merged 1 commits from fix/holdem-dealer-chip-eats-its-seat into master 2026-09-09 15:18:47 -06:00
Owner

Tapping seat 1 on a fresh table did nothing, and seat 1 is where the first player goes. The dealer button starts there, moves every hand, and took the middle of whichever seat it was on with it.

What was happening

The dealer chip is a 22px disc with an invisible pad around it to reach the 44px a fingertip needs. The pad had been moved twice looking for room: outward first, where it hung over the seat next door and handed that seat the button; then inward, over "its own seat, which is a large target that can spare the corner".

The seat could not spare it. Measured on a 390px phone, a 72x46 puck:

share of its own puck its exact centre
seat holding the button 52% (130 of 276 sampled points to the chip) the chip
every other seat 97% the seat

The chip's handler calls stopPropagation, so those taps reached nothing at all — and the chip they reached had nothing to say: its only answer is "give the button to this seat", on the seat that has it. The seat menu already hid "Give the button" there. Mid-hand the same no-op would have taken the blinds off the felt and re-posted the same chips to announce that nothing happened.

The fix

  • The pad is gone, not moved again. A fixed pad cannot be safe at every puck size, and there is no finger left to serve it: ghost chips are pointer-events: none without hover, so on touch the only live chip was this one.
  • The chip that has the button is inert to the pointer and still there to read — its label is the only place on the page that says who has the button.
  • makeDealer returns early on the seat that already has it, so the keyboard can't re-post the blinds to move the button nowhere.
  • Giving the button is unchanged: long-press menu on touch, revealed ghost chip on a mouse. Both verified end to end.

The sweep was blind to it

uiAudit.js has a check named "a seat's centre belongs to that seat" — written when this same pad was reaching outward. It ran clean through all of this, because seat.contains(hit) waves through anything inside the seat and the chip is inside the seat. The check written for this bug could not see the second half of it. A control inside a seat counts as a rival for the tap now; against the old stylesheet it fails seven scenarios.

Verified

  • 982 unit tests, 6 new in tests/holdem/seatTapTargets.test.js (5 of the 6 fail against the pre-fix code).
  • 320 layout audits across 13 viewports, mouse and thumb: the one pre-existing failure (a $1,300 chip behind seat 11 on a twelve-handed 360x640 all-in) and nothing else.
  • Real taps at 390x844 and 1440x900: tapping the button's own seat opens its editor and leaves the button alone; the long-press menu and the hover path still move it.

🤖 Generated with Claude Code

https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb

**Tapping seat 1 on a fresh table did nothing**, and seat 1 is where the first player goes. The dealer button starts there, moves every hand, and took the middle of whichever seat it was on with it. ### What was happening The dealer chip is a 22px disc with an invisible pad around it to reach the 44px a fingertip needs. The pad had been moved twice looking for room: outward first, where it hung over the seat next door and handed *that* seat the button; then inward, over "its own seat, which is a large target that can spare the corner". The seat could not spare it. Measured on a 390px phone, a 72x46 puck: | | share of its own puck | its exact centre | |---|---|---| | seat holding the button | **52%** (130 of 276 sampled points to the chip) | the chip | | every other seat | 97% | the seat | The chip's handler calls `stopPropagation`, so those taps reached nothing at all — and the chip they reached had nothing to say: its only answer is "give the button to this seat", on the seat that has it. The seat menu already hid "Give the button" there. Mid-hand the same no-op would have taken the blinds off the felt and re-posted the same chips to announce that nothing happened. ### The fix - **The pad is gone**, not moved again. A fixed pad cannot be safe at every puck size, and there is no finger left to serve it: ghost chips are `pointer-events: none` without hover, so on touch the only live chip was this one. - **The chip that has the button is inert to the pointer** and still there to read — its label is the only place on the page that says who has the button. - **`makeDealer` returns early** on the seat that already has it, so the keyboard can't re-post the blinds to move the button nowhere. - Giving the button is unchanged: long-press menu on touch, revealed ghost chip on a mouse. Both verified end to end. ### The sweep was blind to it `uiAudit.js` has a check named *"a seat's centre belongs to that seat"* — written when this same pad was reaching *outward*. It ran clean through all of this, because `seat.contains(hit)` waves through anything inside the seat and the chip is inside the seat. The check written for this bug could not see the second half of it. A control inside a seat counts as a rival for the tap now; against the old stylesheet it fails seven scenarios. ### Verified - 982 unit tests, 6 new in `tests/holdem/seatTapTargets.test.js` (5 of the 6 fail against the pre-fix code). - 320 layout audits across 13 viewports, mouse and thumb: the one pre-existing failure (a $1,300 chip behind seat 11 on a twelve-handed 360x640 all-in) and nothing else. - Real taps at 390x844 and 1440x900: tapping the button's own seat opens its editor and leaves the button alone; the long-press menu and the hover path still move it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
jheaps added 1 commit 2026-09-09 15:11:22 -06:00
On a fresh table the button is on seat 1, and seat 1 is where the first player
goes. Tapping it did nothing.

The dealer chip is a 22px disc, and a 22px disc is half the 44px a fingertip
needs, so it carried an invisible pad to catch the tap. The pad was moved twice
looking for somewhere to grow that wasn't already spoken for: outward first,
where it hung over the seat next door and gave that seat the button; then
inward, over "its own seat, which is a large target that can spare the corner".

The seat could not spare it. Measured on a 390px phone, a 72x46 puck: the pad
ran from the puck's own centre to its right edge and from its top to 3px off its
bottom, and the chip's handler calls stopPropagation. The seat holding the
button kept 52% of its face — 130 of 276 sampled points went to the chip, its
exact middle among them. Every other seat kept 97%. The button moves every hand,
so the dead puck went round the table with it.

And the chip it went to had nothing to say. Its only answer is "give the button
to this seat", and this is the seat that has it. The seat menu already knew that
and hides "Give the button" there; mid-hand the same no-op would have taken the
blinds off the felt and re-posted the same chips to announce that nothing
happened.

So the pad is gone rather than moved again — a fixed pad cannot be safe at every
puck size, and no finger reaches any chip now: the ghosts are pointer-events:
none without hover, and the one live chip is this one, which is now inert to the
pointer and still there to read. Giving the button is the long-press menu on
touch and the revealed ghost on a mouse, both unchanged. makeDealer returns
early on the seat that already has it, so the keyboard can't re-post blinds to
move the button nowhere either.

The sweep had a check for exactly this — "a seat's centre belongs to that seat",
written when the pad was reaching outward — and it ran clean through all of it,
because `seat.contains(hit)` waves through anything inside the seat and the chip
is inside the seat. The check written for this bug could not see the second half
of it. A control inside a seat is a rival for the tap now, and against the old
stylesheet it fails seven scenarios.

982 unit tests, 6 of them new; the layout sweep is 320 audits with the one
pre-existing failure (a $1,300 chip behind seat 11 on a twelve-handed 360x640
all-in).

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
jheaps merged commit 3326557043 into master 2026-09-09 15:18:47 -06:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jheaps/JoshHeaps.Net#121