Heads-up the button posted the big blind and acted second — both backwards. And blinds were assigned by seat offset, so with players spread around a table no blinds were posted at all.
Moved one chair per hand, so at ten seats with four players it sat on an empty chair five hands out of eight — and position/blinds stopped reaching players at equal rates.
Typing a smaller seat count deleted players instantly — no prompt, no undo — and left their standings rows reading bought in: $0 for someone who had just won a pot.
Four of these are the same root cause: iterating seat indices where the game iterates players.
Polish
#33 Quick Split gained an uneven/side-pot mode, so it no longer sends the dealer back to the full table for the one case they most need speed on.
#35 23 sub-44px targets fixed, written against the form and panel so new fields inherit it.
#36 14 text colours raised to clear 4.5:1; worst was an affordance at 2.63:1.
What combining them found
My per-PR "zero conflicts" checks were wrong to rely on. They compared each branch against master, not against each other. Merging revealed two real conflicts:
#37 and #38 both rewrote closeStreets choice of who acts next. Taking either side alone would have silently dropped the others fix. Resolved so both hold: no actor is assigned once fewer than two players have chips, and heads-up the big blind acts first post-flop — plus a case neither PR had alone, where the heads-up big blind is itself all-in.
#37 and #34 both added lines to PotWizard.open — both kept.
I also tightened beginHand: a blind large enough to put that player all-in now passes the action on, which only became reachable once both changes were in.
Validation of the combination
1000 randomised hands with everything in play — including 175 heads-up hands and 41 all-in run-outs, the two paths that conflicted. Asserted: chips conserved, no negative stacks, hands terminate, blinds always land on players, heads-up button is the small blind, and nobody all-in or out of the hand is ever asked to act. Zero failures.
UI checks together: no target under 44px; wizard rows 81px; odd chip goes to the correct seat with the winners ticked in reverse order; Quick Splits even and uneven modes both correct.
Two contrast flags investigated rather than accepted — seatPuckTotal and handCounterBadge measured 1.07:1 and 1.0:1, but both have translucent backgrounds; against what is actually painted they are 10.77:1 and 6.83:1. No real failures.
Build clean, both pages 200, no console errors.
Everything from **#33–#41 in one branch**, resolved and re-validated together. Those nine can be closed in favour of this.
**+731 / −49 across 9 files.**
## Correctness and data safety
| | what was wrong |
|---|---|
| **#37** blinds | Heads-up the **button posted the big blind and acted second** — both backwards. And blinds were assigned by seat offset, so with players spread around a table **no blinds were posted at all**. |
| **#38** all-in | The action was offered to players with no chips. Three players all-in pre-flop meant **nine taps to check for people with nothing to decide**. |
| **#39** button | Moved one chair per hand, so at ten seats with four players it sat on **an empty chair five hands out of eight** — and position/blinds stopped reaching players at equal rates. |
| **#40** odd chip | Quick Split says the odd chip goes *"to the first player left of the button"*; the wizard gave it to **whoever was ticked first**. |
| **#41** seat count | Typing a smaller seat count **deleted players instantly** — no prompt, no undo — and left their standings rows reading `bought in: $0` for someone who had just won a pot. |
Four of these are the same root cause: **iterating seat indices where the game iterates players.**
## Polish
- **#33** Quick Split gained an uneven/side-pot mode, so it no longer sends the dealer back to the full table for the one case they most need speed on.
- **#34** Pot wizard rows 37px → 81px, checkboxes 13 → 22px, name/amount/cards split apart.
- **#35** 23 sub-44px targets fixed, written against the form and panel so new fields inherit it.
- **#36** 14 text colours raised to clear 4.5:1; worst was an affordance at **2.63:1**.
## What combining them found
**My per-PR "zero conflicts" checks were wrong to rely on.** They compared each branch against `master`, not against each other. Merging revealed **two real conflicts**:
- **#37 and #38 both rewrote `closeStreet`s choice of who acts next.** Taking either side alone would have silently dropped the others fix. Resolved so both hold: no actor is assigned once fewer than two players have chips, *and* heads-up the big blind acts first post-flop — plus a case neither PR had alone, where the heads-up big blind is itself all-in.
- **#37 and #34 both added lines to `PotWizard.open`** — both kept.
I also tightened `beginHand`: a blind large enough to put that player all-in now passes the action on, which only became reachable once both changes were in.
## Validation of the combination
- **1000 randomised hands** with everything in play — including **175 heads-up hands and 41 all-in run-outs**, the two paths that conflicted. Asserted: chips conserved, no negative stacks, hands terminate, blinds always land on players, heads-up button is the small blind, and nobody all-in or out of the hand is ever asked to act. **Zero failures.**
- **UI checks together**: no target under 44px; wizard rows 81px; odd chip goes to the correct seat with the winners ticked in reverse order; Quick Splits even and uneven modes both correct.
- **Two contrast flags investigated rather than accepted** — `seatPuckTotal` and `handCounterBadge` measured 1.07:1 and 1.0:1, but both have *translucent* backgrounds; against what is actually painted they are **10.77:1 and 6.83:1**. No real failures.
- Build clean, both pages 200, no console errors.
Quick Split answered the easy half of the question and printed a footnote for
the hard half: if somebody was all-in for less, go set up the full table. That's
the moment a dealer least wants to be setting up a table.
A second mode takes what each player put in and lists the main pot and each side
pot with who can win it, plus anything uncalled that goes back. Rows can be
marked folded, so dead money lands in the right pot without its owner being able
to win it. The running total is shown so the breakdown can be checked against
the chips actually on the table.
It reuses the same pot maths as the full table rather than growing a second
implementation, which means the two screens can't drift apart and Quick Split
inherits the uncalled-bet and everyone-folded handling that was fixed there.
Also fixes hiding: these sections carry a `display` declaration, which outranks
the browser's own [hidden] rule, so toggling `hidden` on them did nothing and
both modes' controls showed at once.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Measured the screen where the money actually gets decided and found the smallest
targets in the tool: 37px winner rows, 13px checkboxes, a 36px confirm. Every
other surface has been held to 44px and up for several rounds — the one place a
mis-tap pays the wrong player was the one place that hadn't been.
Winner rows are now 56px and up, with a 22px box and a green fill when chosen,
because a dealer confirms this with players watching and a small checkbox is
easy to misread across a table. The row already was the tap target; now it looks
like one.
Each row also splits into name, what they can win, and their cards as separate
pieces rather than one run-on sentence, so the hand can be read at a glance. The
confirm takes the full width, and on a phone the summary's buttons stack with
the one that ends the hand furthest from a stray thumb.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Last round I measured one screen and found the smallest targets in the tool were
on it. So I measured all of them: sixty-four controls at phone width, flagging
anything under 44px. Twenty-three came back.
The sidebar had never had the touch pass the seat form got — its inputs, selects
and small buttons sat at 26 to 30px, and the buttons that delete a chip colour
or a custom field were 28px wide, which is a bad size for something destructive.
The rulings link was a 14px-tall underlined word.
The seat form itself had drifted: its mobile sizing rule named five fields, and
the stack field added two rounds later wasn't one of them, so it sat at 28px
beside its 44px neighbours. Both that rule and the new sidebar one are now
written against the form and the panel rather than a list of field names, so the
next field added inherits the size instead of waiting to be noticed.
The dealer button keeps its 22px disc — it's a chip sitting on a seat, and
drawing it thumb-sized would swallow the puck — but takes taps through an
invisible 44px pad. Verified the seat underneath still gets its own taps.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This is a dark interface used in poker rooms and kitchens with the lights down,
and I'd been generous with grey-on-black. Measured every piece of text against
what's actually painted behind it: thirteen declarations came in under 4.5:1.
The worst was the empty card slot's "+" at 2.63:1 — an affordance telling the
dealer the slot is tappable, and the least legible thing on the screen. Then a
run of #777 labels around 3.9 to 4.1: the standings header, the time on a logged
hand, the currency mark in the add-chips field. The rulings source line sat at
3.62 and the rulings link itself at 4.39.
All of them move up just far enough to clear 4.5 rather than being flattened to
white, so the hierarchy between a heading, a value and a hint survives.
Worth recording what the measurement can't see: it reads background-color, so
text on a gradient — the pot badge, the dealer button, the blind badges — comes
back as a false result in both directions, and those were checked by eye
instead. The felt watermark is deliberately at 6% and stays that way.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Blinds were assigned by seat offset — always button+1 and button+2 — which is
only right when everyone is dealt in and there are three or more of them.
Heads-up it had the blinds backwards. The button posted the big blind and acted
second before the flop; the rule is the opposite on both counts. That's the end
of every home tournament, dealt wrong.
Worse, the offsets don't care whether anyone is sitting in those chairs. With
two players at a nine-handed table, or with the seats left of the button sitting
out, the blinds landed on empty seats and simply weren't posted — no money in
the middle, and SB/BB badges pointing at nobody.
Blinds now walk the players who are actually dealt in, starting from the button,
with heads-up as its own case: the button is the small blind, acts first
pre-flop, and the big blind acts first afterwards. The badges come from the same
function as the money, so what the table shows and what gets posted can't
disagree.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
A player who is all-in is still in the hand and still wins pots, but has nothing
left to decide. The engine didn't know that: it picked the next player to act by
walking to whoever was still in the hand, chips or no chips.
So a dealer with three players all-in pre-flop was asked to check for each of
them on the flop, the turn and the river — nine taps to say nothing — and the
action could land mid-street on someone who'd already pushed their last chip in.
Now the action passes over players who are all-in, and once fewer than two
people have chips left the betting is simply over: the bar runs Deal Flop, Turn,
River, Showdown straight through, and says "All-in — running out the board" so
the absence of a question reads as deliberate rather than as a stuck screen.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
The button moved one seat per hand regardless of whether anyone was sitting
there. At a ten-seat table with four players — an ordinary home game — it spent
five hands out of eight on an empty chair.
That reads as untidy, but the real problem is fairness. Position and the blinds
are meant to come round to each player at the same rate. Rotating past empty
chairs breaks that: a player with three empty seats to their right takes the
button far less often than one sitting in a cluster, while both pay blinds every
time the rotation reaches them.
The button now skips to the next seat that's actually dealt in, so with four
players it visits those four in turn and each takes it once every four hands.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Quick Split tells the dealer a leftover chip goes "to the first player left of
the button". The pot wizard, on the same tool, handed it to whoever the dealer
happened to tick first. Two screens, same split, different answer.
The wizard now orders the winners around the table from the button before the
calculator shares the pot out, so the leftover lands where the instruction says
it does. The calculator itself is unchanged — it already gave leftovers to the
front of the list, it was just being handed the list in the order the checkboxes
were clicked.
It's pennies, but it's pennies in front of the person who didn't get them, and a
dealer who has just read the other screen's instruction out loud looks wrong.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Typing a smaller number into "Seats at table" silently threw away every seat past
the new count. Nine players down to six removed three of them — names, stacks and
buy-ins gone, no confirmation, no way back.
It also quietly falsified the settle-up. Those players keep their rows in the
session standings, because the hand log remembers who won what, but the seat they
were read from no longer exists — so a player who had just won a pot showed as
having bought in for nothing and holding nothing.
Shrinking now says who it is about to remove and asks, and either way the change
is undoable like the rest of the table's actions. Growing the table, and
shrinking onto chairs nobody is sitting in, still happen without a word.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Everything from #33–#41 in one branch, resolved and re-validated together. Those nine can be closed in favour of this.
+731 / −49 across 9 files.
Correctness and data safety
bought in: $0for someone who had just won a pot.Four of these are the same root cause: iterating seat indices where the game iterates players.
Polish
What combining them found
My per-PR "zero conflicts" checks were wrong to rely on. They compared each branch against
master, not against each other. Merging revealed two real conflicts:closeStreets choice of who acts next. Taking either side alone would have silently dropped the others fix. Resolved so both hold: no actor is assigned once fewer than two players have chips, and heads-up the big blind acts first post-flop — plus a case neither PR had alone, where the heads-up big blind is itself all-in.PotWizard.open— both kept.I also tightened
beginHand: a blind large enough to put that player all-in now passes the action on, which only became reachable once both changes were in.Validation of the combination
seatPuckTotalandhandCounterBadgemeasured 1.07:1 and 1.0:1, but both have translucent backgrounds; against what is actually painted they are 10.77:1 and 6.83:1. No real failures.