Hold'em: every seat on the ring is the same seat, and the ring is measured before it is believed #122
Open
jheaps
wants to merge 27 commits from
polish/holdem-seats-one-size into master
pull from: polish/holdem-seats-one-size
merge into: :master
:master
:feature/socialism
:polish/holdem-seats-one-size
:fix/holdem-dealer-chip-eats-its-seat
:polish/holdem-game-night-ready
:polish/holdem-round-next
:polish/holdem-seat-collisions
:polish/holdem-offline-and-render
:fix/naming-a-blind-deals-the-hand
:release/casino-holdem-polish
:fix/casino-holdem-phone-look
:fix/casino-holdem-sidebar-crowding
:fix/casino-holdem-contribution-rule
:fix/casino-holdem-one-stack-rule
:fix/casino-holdem-default-stack-buyin
:feat/casino-holdem-waiting-list
:feat/casino-holdem-misdeal
:feat/casino-holdem-cash-out
:feat/casino-holdem-clock
:feat/casino-holdem-rake
:fix/casino-holdem-stadium-ring
:fix/casino-holdem-phone-felt-shape
:fix/casino-holdem-audit-runs-everything
:feat/casino-holdem-straddle
:fix/casino-holdem-landscape-ring
:feat/casino-holdem-table-file
:fix/casino-holdem-touch-targets
:fix/casino-holdem-audit-modal-blindspot
:fix/casino-holdem-quicksplit-chop
:fix/casino-holdem-buildable-bet-sizes
:fix/casino-holdem-pot-sized-raise
:fix/casino-holdem-chips-to-count-out
:fix/casino-holdem-first-buy-in
:fix/casino-holdem-seating-a-table
:fix/casino-holdem-showdown-order
:fix/casino-holdem-hand-log-filter
:fix/casino-holdem-posting-in
:fix/casino-holdem-dead-button
:fix/casino-holdem-discarded-pot
:fix/casino-holdem-honest-tank-clock
:fix/casino-holdem-export-the-notes
:fix/casino-holdem-stacks-in-chips
:fix/casino-holdem-close-audit-blind-spots
:fix/casino-holdem-readable-dim-states
:fix/casino-holdem-drop-broken-watermark
:fix/casino-holdem-two-tabs
:fix/casino-holdem-unreadable-save
:fix/casino-holdem-say-when-not-saving
:fix/casino-holdem-shortcut-discovery
:fix/casino-holdem-rulings-index-compact
:fix/casino-holdem-rulings-index
:fix/casino-holdem-payout-in-chips
:fix/casino-holdem-move-seat-on-touch
:fix/casino-holdem-amount-fields-select
:fix/casino-holdem-touch-targets-by-pointer
:fix/casino-holdem-button-box-model
:fix/casino-holdem-blind-badge-placement
:fix/casino-holdem-buyin-deals-in
:fix/casino-holdem-bet-vs-raise
:fix/casino-holdem-deal-around-broke
:fix/casino-holdem-settle-up
:fix/casino-holdem-allin-and-bar
:fix/casino-holdem-one-dealer-button
:fix/casino-holdem-manual-deal
:fix/casino-holdem-less-guessing
:feature/casino-holdem-seat-editor
:fix/casino-holdem-landscape-table
:feature/casino-holdem-acting-seat
:feature/casino-holdem-raise-guard
:feature/casino-holdem-first-run
:feature/casino-holdem-contrast-audit
:feature/casino-holdem-layout-audit
:feature/casino-holdem-blind-fairness
:feature/casino-holdem-rulings-depth
:feature/casino-holdem-bank-audit
:feature/casino-holdem-fewer-taps
:feature/casino-holdem-render-perf
:feature/casino-holdem-keyboard-focus
:feature/casino-holdem-resilience
:feature/casino-holdem-messy-tables
:feature/casino-holdem-touch-mobile
:chore/casino-holdem-tech-debt
:feature/casino-holdem-dealer-batch
:feature/casino-holdem-seat-count-safety
:feature/casino-holdem-odd-chip
:feature/casino-holdem-button-moves
:feature/casino-holdem-allin-runout
:feature/casino-holdem-heads-up
:feature/casino-holdem-contrast
:feature/casino-holdem-touch-audit
:feature/casino-holdem-wizard-polish
:feature/casino-quicksplit-side-pots
:feature/casino-holdem-rebuys
:feature/casino-holdem-rulings
:feature/casino-holdem-sidebar-polish
:feature/casino-holdem-keyboard
:feature/casino-holdem-action-undo
:feature/casino-holdem-pace
:feature/casino-holdem-side-pot-breakdown
:feature/casino-holdem-gestures
:feature/casino-holdem-card-picker
:feature/casino-holdem-live-betting
:feature/casino-holdem-bet-chips
:feature/casino-holdem-seat-legibility
:feature/casino-holdem-undo-toasts
:feature/casino-holdem-quick-split
:feature/casino-holdem-mobile-table
:feature/casino-holdem-hand-history
:feature/casino-texas-holdem
:feature/casino-holdem-dealer-mode-polish
:feature/fractional-learned-weights
:fix/stockfish-dispose-hang
:fix/blog-cache-staleness
:fix/workflows
:gitea/workflows
:echo-acoustic-ranging
:chess-ui-overhaul
:feature/customChessEngine
:feature/blog
:AddProject
:feature/generalUpdate
:tech-debt/chess-cleanup
:tests/CreatePlaywrightTests
:UpdateParticleSimulatorButton
Reference in New Issue
Block a user
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.
Nine chairs on the ring, and until this no two of them were the same size.
Measured at 1280x900 with five players seated: 64px for Bob, 70 for Christopher because his name wrapped, 71 for Alexandra because she had chips out, 89 for "Mike from work" who had both. Twenty-five pixels of difference around one ring.
The ragged edge was the smaller half of it. The larger half is that the sizes moved: a puck grew 17px the moment its player put money in and shrank again when the street closed, so the felt reflowed twice a hand under the thumb of someone trying to tap it — and because the ring is sized against the tallest puck on it, the whole table could resize with it.
One puck, one size
Three rows are held open instead — name, amount, stack — at a height that does not depend on what is written in them. The name gets one line whatever it says, the amount pill keeps its row when it is empty rather than collapsing, and each row is a token rather than an em, so an amount stepping its type down to fit shortens its digits and not its row.
It costs nothing.
measuredSeatBoxalready took the tallest puck on the ring, on purpose, so the room was reserved at every seat and the short ones simply weren't drawn into it. What pays for the third row is the second line of name going away: an earlier round measured uniform-with-two-lines at 88px and rightly refused it. Uniform-with-one is 71.A name with no word left to drop steps its type down instead of ellipsising.
fitSeatNamesturns "Mike from work" into "Mike", which is what the table calls him, and could do nothing at all with "Christopher" — so that one was cut to "Christo…" on a felt that may also have a Christine at it. The amounts on the same puck have never taken that trade. One step, floored at 9px, then the ellipsis.The float that kept the button seat's name off the dealer chip goes with the second line. Left in place with a one-line clamp it drew "Christopher" as an empty label. Three rules become one, and the padding is widened from 22px to 25 so it actually covers the chip and the gap it is inset by.
The ring was sized from pucks with nothing in them
renderSeatsbuilds nine empty pucks and fills them afterwards, so the layout search ran against blank boxes and cached the answer under a key naming a table it had never measured. The comment where it was cached said "let the resize that follows do this properly".Nothing resizes a phone. Turning the phone sideways and back was the only thing that ever fixed it — and it fixed it into a different layout:
The finished pucks are now measured against the box the search settled on, and if they disagree the search is asked again with the real thing in front of it. Once; the second answer is cached. Fresh load and rotate-and-back agree exactly.
That also lets the cache key shrink to almost nothing. It used to name every player's name, the dealer's seat, whether anybody had cards and whether anybody had chips out — four things, three of which flip during an ordinary hand, each flip a ten-candidate re-search. None of them can change a puck's height any more. Measured over three calls in a row: no re-search, and not one of the nine seats moved a pixel.
The crowded ring pays its own way
.seatsTightgives up the 1.4 leading on its money rows for the row it now holds open — 10px of figures is 12px of ink and the rest was blank. Without it the Galaxy S8 twelve-handed, where the pucks are already at the 44px floor, put neighbouring seats' blind badges down onto each other's "ALL IN". Hanging the badges further out was tried first and is the wrong direction: the badge landing on a stack figure belongs to the seat next door, so every pixel further out is a pixel further onto a neighbour.Checks
chip $1,300 is behind seat 11that master already carries. Measured on master first to be sure.The acting line names whoever the table is waiting on, and on a phone it had stopped doing it. The name was set up as the half that gives way — right rule, no floor under it. Flex shrinks the only item that can shrink until the line fits, and with the clock button on the line there was no fitting to be had: measured with five players nine-handed, mid-street, 360x640 "Christopher" wanted 62px, drawn 0. "Alexandra" 54 -> 0. 390x844 "Christopher" wanted 62px, drawn 0. "Alexandra" 54 -> 0. 430x932 "Christopher" 62 -> 31. What the bar read on the first two was "· $1,200 behind" — a dangling separator and a figure, at the moment the dealer is looking at it to say whose turn it is out loud. The ellipsis the rule was written around never appeared; there were no characters left to put one after. The rule stays: a number read wrong is worse than a name read short, so the stack is never truncated. It comes off the line whole instead, and it is not lost — the same figure is on that player's own seat puck. The stopwatch pays for the rest. It was on the line from the first second of every decision, reading "0:07" in grey: 50px of the narrowest row in the tool spent on a number nobody acts on. It is a prompt, not a readout, and it now arrives when the button that acts on it arrives — the same thirty-second threshold, asked once. 360x640 "Christopher" 62 -> 44 ("Christo…"), "Alexandra" 54 -> 44 390x844 both whole, where both were nothing 430x932 both whole and "Mike from work" whole at 360 where it was cut to 68 with the bar 151px and the felt 309x463 either side of the thirty-second mark, and 0px of seat movement across it. Also: the space before the separator. These are two flex items and a flex item's own leading whitespace is collapsed at its edge, so the bar has read "Alexandra· $1,500 behind" since the sentence was split in two. The gap is in the stylesheet now, where it can survive that. 1018 unit tests; 320 layout audits across 13 viewports, clean. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgbThe action bar learned two rounds ago that a landscape phone is short of height and swimming in width: it stops being a bar across the bottom and becomes a rail down the right. The seat menu was never told. Measured on the fullest menu the tool can build, twelve-handed with somebody on the waiting list: 640x360 545px of content in a 306px sheet. Cash out…, Move to…, Edit seat…, Small blind, Big blind and Cancel below the fold — Cancel by 224px. 667x375 545 in 319, Cancel 211px under. 844x390 529 in 332, Cancel 183px under. And on all three the sheet was 420px wide inside a window 640 to 844 wide. Two hundred and forty-seven pixels of width held empty on the one screen shape whose whole problem is height, while the dealer scrolled a menu to find the way out of it. The two tiers this menu has been sorted into since round 7 — what you press with cards in the air, and housekeeping between hands — stop being the top and bottom of a column and become the right and left of a row. Fold and All-in land under the same thumb the rail puts Fold, Call and Raise under, with Cancel beneath them. The housekeeping grid takes as many columns as the width holds instead of the two a 360px phone has room for. After: 297px in a 298px sheet at 640x360 and at 667x375, 281 in 282 at 844x390. Nothing below the fold on any of them, every button still at least 44x44 and still hit-testing as itself. The move picker's twelve chairs and its Back went from four rows and a scroll to three rows and none. Upright nothing moved at all — 545px before, 545px after, the same two columns. auto-fill rather than auto-fit: auto-fit throws away the tracks nothing sits in, so a group of two stretched to half the sheet each while the group above it was a third, and the two grids lined up with nothing. 1253 tests pass; the layout sweep reports clean. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgbThe picker put the whole deck on the screen at once — four suits across, thirteen ranks down — and that grid is 646px tall. Measured with the board tracked and seven cards already out: 640x360 816px of content in a 317px sheet, 40 of the 52 below the fold 667x375 816 in 330, 40 below 844x390 774 in 343, 36 below 375x667 816 in 587, 20 below 1280x900 774 in 775, none So on every phone the tool is actually used on, entering the flop began with a scroll, and a dealer looking for the nine of diamonds was reading down a column to find it. No arrangement fixes that: fifty-two targets at the 44px this sweep holds everything else to need 572px of width before gaps if they are thirteen across, which is wider than any phone in portrait, and thirteen rows deep if they are four. A card is not one choice out of fifty-two. It is two — a suit and a rank — and that is how a dealer says it out loud. So the sheet asks them in that order: four suits on a row, then the thirteen ranks of the one chosen. Measured afterwards at eight viewports from 360x640 to 1280x900: nothing below the fold, nothing scrolling, every button still at least 44x44 and still hit-testing as itself. Portrait content went 816 -> 372, landscape 816 -> 322. The second tap is not a second cost in the usual case. The suit stays where it was put, so a second card of the same suit is one tap — and one tap is what picking a card used to cost *plus* a scroll through thirteen rows. Opening on a slot that already holds a card shows that card's suit with it under the cursor, which is the case where the dealer came to change something rather than add it. Three things that had to move with it: - the cursor is a rank now, 0-12, and changing suit deliberately leaves it alone: the same rank in another suit is the card beside the one you were on. - the arrows read the column count off the layout instead of assuming SUITS.length. The rank row is auto-fill — five across on a 360px phone, seven at 460 — and the last time this number disagreed with the grid it slid the cursor into a column nobody asked for. - the suits are a radiogroup, not a tablist: the grid below is not another panel of content, it is the same thirteen controls filtered, and the unchosen red suits stay red, because reading four shapes to find the hearts is the search this sheet exists to remove. deck() still returns all fifty-two and the duplicate check still reads all of it, so splitting the grid by suit cannot let a card be dealt twice. 1276 tests pass; the layout sweep reports clean. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgbTwo findings in the seat editor, and the second is why the first was hard to notice. A seat is sitting out, in the hand, or folded, and "folded" means nothing while the seat is out. The form asked that as two checkboxes, which offers a fourth corner that has no meaning, and the code that stopped a dealer reaching it disabled the Fold box whenever In hand was clear. Measured: that left the label at opacity 1 and colour rgb(170,170,170) — identical to a live one — with a title of "Folded" on it. Pressing Fold on a seat that was sitting out gave no movement and no reason, which is the exact failure round 16 went through the tool to remove; it hid from the sweep rule that finds those because the title sat on the <label> rather than on the control the rule inspects. Three named states can't contradict each other and none of them is ever unavailable, so there is nothing left to dim. Sitting out · In the hand · Folded, as a radiogroup — the same shape the card picker's suits use — with the chosen Folded segment in the red the seat menu's own Fold already wears. And the form itself. The phone block's comment has always said why the editor becomes a bottom sheet — "the seat editor lives far enough down the page that tapping a seat looks like it did nothing" — but the sheet was scoped to max-width 700px while #holdemLayout goes to one column at 900. The two tiers in between had exactly the fault the sheet was written to fix. Measured, tapping a seat: 844x390 the editor drew in the flow at y=369 in a 390px window — 21px of 401 on screen, 5% of it, at scrollY 0 with nothing scrolling it into view. The seat lit up green and the form it opened was under the bottom edge. 768x1024 31% visible, the rest below the fold, again no scroll. An iPhone held sideways and an iPad held upright are not obscure. The query is the layout's own breakpoint now, and the seat-sheet rules moved out of the phone block into their own so it could be asked separately. Afterwards every viewport from 360x640 to 900x500 shows 97-99% of the sheet, and above 900 the editor is a static block at the top of a sidebar beside the table — 100% visible there too. 1307 tests pass; the sweep is clean at 428 audits across 13 viewports. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgbView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.