3ffce9d7c11c914ecd728d40f329c15c0b661b7c
292
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3ffce9d7c1 |
Stop the empty table shouting, and point at the next step
The first screen a dealer ever sees was the one nobody had looked at. Opened
fresh, with nothing saved, the tool showed:
* the word EMPTY, in capitals, ten times across the felt
* Resolve Pot as the accent-green primary — on a table with no players and no
pot, so the loudest control on screen was the one that could do least
* Next Hand, with no button to move
* no indication anywhere of what to do first, except a grey caption under the
felt saying "tap a seat to edit"
An empty state is the one screen a new user is guaranteed to meet, and this one
described itself ten times without once saying what to do.
**The felt stops repeating itself.** An open chair is already dashed instead of
solid, faded, and has no stack under it — that reads at a glance, which is how a
dealer looks at a felt, and it reads the same on the one open chair at a full
table, which is the case the label was actually for. The word is gone from all
ten, and the seat numbers underneath it are now the thing you see. What the word
carried for anyone reading by ear moves to an aria-label on the seat, which now
says more than the sighted version ever did: "Seat 3, empty", "Seat 1, Ann".
**The bar says what the table is.** It used to hide itself when nobody was
seated — silent at exactly the moment a dealer has never used this before. It
now says "Nobody's at the table yet." with one full-width button, Seat a player,
which opens the seat editor on the first free chair. That is the same panel
tapping a seat gives them, so the button teaches the gesture rather than
replacing it.
**Two dead controls stop pretending.** Resolve Pot and Next Hand are disabled
until somebody is at the table, which takes the accent green off the button that
cannot work and leaves the only green on screen on the one that can. Disabled
rather than hidden: hiding them would move the header the moment the first
player is named, and a control that vanishes teaches nothing about what it was
for. Next Hand's own explanatory title is kept and handed back when it
re-enables.
One bug of my own, found by walking the flow rather than by reading it: after
typing a name both header buttons stayed greyed out, still captioned "nobody is
at the table yet". commitSeatChange refreshes narrowly on purpose — a full
render on every keystroke takes the name field out from under the dealer
mid-word — and the comment directly above it already warns about this exact trap
for the action bar. The header needed the same line.
609 tests, 0 failures. 65 layout audits across 13 viewports x 5 table sizes,
0 failures — the empty table is now one of those sizes, and had never been
audited before. Re-verified with the network cut: the first-run screen, the CTA
and seating a player all work offline.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
5d5093f3cc |
Make the dealer tool work with no signal, and stop it shouting
Three findings this round, all measured on the running page.
**1. The tool needed the internet to exist.**
The night already survived anything — it lives in localStorage — but the page
did not. Reload in somebody's back room with one bar of signal and a dealer
holding a phone in one hand and a deck in the other got a browser error page
with nine players waiting. Verified: with no worker, an offline reload is
net::ERR_INTERNET_DISCONNECTED.
There is now a service worker (wwwroot/casino/sw.js) and a manifest, so the
tool installs to a home screen and runs with the network switched off. Measured
end to end: 19 assets cached from one ordinary visit, then with the context
offline the page loads, draws six seats, applies its CSS, and plays a whole
hand — blinds posted, four calls, $800 still on the table.
Three decisions worth writing down:
* The precache list is read off the document rather than kept in the worker.
Every stylesheet and script is already in the DOM; a hand-written file list
stays right until somebody adds a script tag, and then the tool is quietly
offline-broken until a dealer finds out at a table.
* No skipWaiting. A new worker taking over a live page can hand it assets
from a different release than the ones it started with, and this page loads
seventeen scripts that expect to agree with each other.
* Stale-while-revalidate for assets, not cache-first — see finding 3.
Scope is /casino/ rather than /casino/texasholdem/, because a scope has to be a
prefix of the page it controls and "/casino/texasholdem" does not start with
"/casino/texasholdem/". Anything in scope the worker wasn't told about goes
straight to the network.
Program.cs needed .webmanifest added to the MIME map: an unknown extension is
not served with the wrong type, it is not served at all, and a 404 manifest is
exactly the silent failure that stops a browser ever offering "Add to Home
Screen".
**2. A screen reader read out the whole action bar after every tap.**
The bar carried aria-live on the element whose entire contents render() rebuilds
each action. Measured, after one tap:
"PRE-FLOP Pot $15 9 players Bob · $250 behind 0:00 ↶ dealing the hand U
Fold Call $10 Raise"
Nothing in it is wrong; it is unusable at the speed a hand is dealt, and it is
the documented anti-pattern — a live region has to be a stable element whose
text changes, not a container that gets replaced. There is now a small
role="status" element that exists from page load and says one sentence:
"Dee to act, $10 to call. Pot $15." Guarded on the sentence rather than on the
state, because render() runs for many reasons that don't change whose turn it
is, and a live region handed the same words twice reads them twice.
**3. Every tap rebuilt ninety DOM nodes.**
renderSeats did `innerHTML = ""` and then cloned a template, attached three sets
of listeners and set a dozen attributes per chair — on every render, and
renderAll runs after every action. Profiled on a nine-handed table:
renderSeats 7.3ms -> 4.0ms
renderAll 12.4ms -> 9.5ms
The pucks are built once and updated in place, which is what the rest of that
function was already doing to their contents. A seat's node belongs to its index
for as long as the table is that size, so the listeners still close over the
index; only a change in the number of chairs rebuilds the row. Verified that
listeners don't stack: one click after six renders fires the hook exactly once,
and resizing 9 -> 4 -> 10 renumbers correctly.
Found along the way and deliberately left alone: ViewData["cssVersion"] renders
empty on every page of the site, so the ?v= cache-busting has never worked. It
is inert — Program.cs sends Cache-Control: no-store on all static files — and
the service worker was the only reason to care, which is why it uses
stale-while-revalidate instead. Noted in TexasHoldem.cshtml.
608 tests, 0 failures, including a new announcing.test.js. 52 layout audits
across 13 viewports x 4 table sizes: 0 failures, and the action bar still
doesn't change height mid-hand. Gestures re-verified after the seat refactor:
double-tap fold, long-press menu, give-the-button, and the active dealer chip.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
aaf96543a8 |
Merge pull request 'Hold'em dealer: blind fix, design tokens, and the decrowd' (#116) from fix/naming-a-blind-deals-the-hand into master
Build and Deploy / build-deploy (push) Successful in 1m36s
Reviewed-on: #116 |
||
|
|
2e9c55af21 |
Give the felt back the screen the chrome was using
Measured on a 390x844 phone, six-handed, same table before and after:
before after
header 193px 120px
felt starts at 225px 152px
controls on screen 27 18
bordered elements 37 22
text elements 61 49
A quarter of the screen went on chrome before the dealer saw the table.
**The header.** Six equally weighted buttons in two rows, of which two are
per-hand controls. The other four have gone where they belong rather than into
a "more" menu — that relocates crowding instead of removing it. Reset Table is
in the Session panel now, beside Clear history and the save/load pair: it is
the control that throws the night away and it was sitting in the same weight as
Resolve Pot, the most-pressed button in a session. Quick Split is down with
Common rulings, where the other "open something else" link already lived. The
way back to the site is a quiet link at the top, where a back link goes.
The subtitle is gone. It was one sentence of orientation on a tool used
nightly, and the landscape and short-phone rules already hid it — the layout
had decided it was expendable on every screen this is actually used on.
**Teaching copy, once.** Table Setup shipped open, so the first screen was the
most crowded one; it now opens only on a table nobody has set up yet, decided
once at load so it cannot reopen itself under a dealer who just shut it. The
gesture hint under the felt goes after the first deal of the session — keyed to
the hand number, not to bettingOpen, which was the obvious test and the wrong
one: it would have appeared and disappeared twice a hand, shunting the panel
stack up and down all night.
**Borders.** Panels had a border, a lighter background and a shadow — three
ways of drawing one edge. The standings had a rule under every row, which is
what makes a list read as ruled paper. Both drop the line and keep the
grouping; the standings header keeps its rule, because that one marks a change
of kind. Fields and buttons keep theirs: those say "interactive", which is not
decoration.
**The felt itself.** Names wrap to two lines instead of ellipsising, so
"Christopher" and "Mike from work" are readable rather than "Chri..." and "Mike
fro..." — the truncation was eating exactly the half a dealer typed to tell two
Mikes apart. And a stack of $1,234,567 rendered at 7px; it now drops the
thousands separators before it drops the type size, so the same number is eight
characters at 10px with every digit intact. The comment above fitAmount was
right that abbreviating loses precision — but precision the dealer cannot read
is not precision.
Two things tried and reverted, both measured:
* Reserving the second name line made every puck the height of the tallest
(69-73px became a uniform 88). Fifteen pixels on all nine seats is felt
spent on chrome, which is what this round is trying to give back.
* Dropping the name a type step to fit more on one line still did not fit
"Christopher" on the button seat, where the dealer chip takes 22px of the
row — so it cost legibility and bought nothing.
Known limit: a single word too long for a puck breaks across the two lines, and
gets a hyphen only where the browser has hyphenation dictionaries. Real phones
do; headless Chromium does not, so audit screenshots show "Christo / pher". The
whole name is visible either way, which it was not before.
599 tests, 0 failures. 52 layout audits across 13 viewports x 4 table sizes
(2, 6, 9, 12 seats) and 20 across the five overlays: 0 failures, and the action
bar still does not change height mid-hand at any of them. The back link was
caught by the audit at 56x13 when it stopped being a button; its hit area is
44px now, paid for with negative margins so the header keeps the 73px.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
8b43614b1c |
Give the stylesheet a vocabulary, and stop the bar moving
The tool read as crowded on screens where nothing overlapped, and the reason turned out to be arithmetic rather than taste: holdem.css used 42 different spacing values, 31 font sizes, 14 border-radii and 139 colours, and defined three custom properties. Every number had been picked by hand at the moment it was needed. A 0.42rem gap beside a 0.45rem gap beside a 0.5rem gap gives the eye no rhythm to lock onto, so it reads the whole surface as noise. So: constrained scales, and everything snapped to them. Seven spacing steps on an 8px rhythm, eight type sizes, four corners, and a four-step surface ramp that stays off pure black — shadows are invisible on #000 and white-on-black smears on the OLED phone this is used on. 661 values rewritten; colour mentions halved, 381 to 174. Nothing here changes what the tool does. Two things were measurably moving under the dealer's thumb, and both are fixed by reserving space rather than by animating anything: * The undo pill named the last action, so it resized on every single tap. Measured across one hand: 148, 110, 109, 108, 109, 110, 120px. It is now a fixed 10rem, with the label in its own element so it can ellipsize without truncating through the keycap beside it. * The action bar is fixed to the bottom, so anything that changes its height moves its top edge. The status row wraps, and it wrapped differently as the street and pot changed. It now holds two lines' worth of room whenever a hand is running. Across 13 viewports x 3 table sizes, the bar no longer changes height mid-hand at all. Numbers that change while the dealer watches — pot, stacks, bets, standings — are now set in tabular figures, so a pot ticking $18 to $21 stops shuffling its digits sideways. Two regressions the sweeps caught on the way, both from the snapping: * The Add Chips field cleared 44px by way of its font size plus its padding, so moving either dropped it to 42. It states its own min-height now; a touch target shouldn't be an accident of two other numbers. * The bar came out 2px shorter in landscape, which grew the felt, which lifted the top seat's dealer button 2px into the header. That clearance was structurally 1-7px, so the top seat inset became a floor rather than a ceiling instead of chasing the 2px. tests/holdem/designTokens.test.js keeps the scales honest — a new value has to be a step or an exception with a reason. Fault-injected four ways. 599 tests, 0 failures. 39 layout audits across 13 viewports and 3 table sizes, plus 35 across the five overlays: 0 failures. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
42b9e83e8f |
Don't deal the hand when the dealer names a blind
The seat menu's "Make small blind" / "Make big blind" / "Blinds follow the button again" all re-posted the blinds whenever `Betting.onlyBlindsIn(state)` was true. That condition means "nothing has happened in this hand except the blinds" — which is true of a hand one second old and equally true of a table sitting in setup with no cards out and nothing committed. So naming a blind before the deal ran repostBlinds -> beginHand: betting opened, two stacks docked, the action bar up and waiting on the seat after the big blind. The dealer had said where the blinds go and the tool heard "deal". Betting.blindsArePosted() is the question these three actually wanted — the blinds are down only once a hand is open — and it's the guard makeDealer was already using by hand. Before the deal, naming a blind now moves a badge and nothing else; the toast says "Dee has the small blind next hand." rather than claiming she posted it. Mid-hand behaviour is unchanged: chips come back and go in again from the new positions, and the chip count stays straight. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
419c1de2a0 |
Merge pull request 'Hold'em dealer: 55 polish rounds, one merge' (#101) from release/casino-holdem-polish into master
Build and Deploy / build-deploy (push) Successful in 1m32s
Reviewed-on: #101 |
||
|
|
470fbc0d26 | Add the seat-name round to the release | ||
|
|
c4e842f3e3 |
Stop the button's chip sitting on top of the name underneath it
A phone screenshot, mid-hand, eight-handed: the seat with the button reads "Christop" and then a white D. The name is centred across the whole puck and the chip sits in its top-right corner on a layer above, so a long name runs underneath and stops — measured, "Christopher" lost 20px of its 64px box. The bad part is the silence. The text believed it had the room it was being painted out of, so no ellipsis appeared: nothing on screen said the name went on. On the one seat at the table a dealer looks at most. The name now keeps out of the chip's corner on the seat that has the button, so a long one ends in an ellipsis before it gets there. Only that seat — every other puck has its full width, because the chip on those is invisible. And the audit has a check for it now. Nothing it had would have caught this: the occlusion check is about controls a thumb has to reach, and this is a label a dealer has to read. The new one measures every seat's name against its own chip and badges, and the fix was undone by hand to confirm it fires — "20px under button.dealerChip.active". 182 audits across 13 viewports, both input kinds, plain and straddled tables, every overlay: clean. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
1d9353c94e | Add the hidden-means-hidden round to the release | ||
|
|
8616636212 |
Make hidden mean hidden, and check that it does
Three rounds inside the money, so this one was a look at the thing. On a desktop
screenshot of a table mid-session there is a small empty box under the bank
check: 21 pixels wide, 44 tall, no text, no purpose. It is the "Take $600 from 3
players" button from three rounds ago, which sets `hidden` when there is nothing
to record — and `hidden` was losing.
The attribute carries its `display: none` from the browser's own stylesheet,
which is the weakest rule in the cascade, so any class that sets a display beats
it. Every button class here sets `inline-flex`. This is not a new discovery in
this file either: seven overlays already carry their own `[hidden] { display:
none }`, each one presumably found the same way. One global rule now covers all
of them, so there is no ninth.
The audit had been telling me. Its touch check reported the box as "could not be
probed (drawn 21x44)" — in the warnings, which my sweeps printed only sometimes
and never failed on. So the check now says the plainer thing: an element that
claims to be hidden and is drawn anyway is a fault, not a note. It is in runAll
with the rest, and the bug was put back by hand to confirm it fires.
The same warning stream had a second thing in it: the waiting list's Add button
was 43px wide. Three letters, one pixel under the rule, and a thumb doesn't know
the word is short.
182 audits across 13 viewports, both input kinds, every overlay and a table with
a rake, a cash-out, a waiting list and six logged hands on it: clean, and the
only note left is the one that says the 44px rule stood itself down on a mouse.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
6174981cdc | Add the assumed-contributions round to the release | ||
|
|
4db6f6e4d3 |
Take the chips the pot was priced with, not just pay them out
Two rounds of asking where the tool disagrees with itself, and here is the last of it — the one that was actually creating money. There are two ways a contribution gets in. Tapping the action moves chips: the bet leaves the stack, sits in front of the player, and is swept into their hand total. The other is the "without live betting" panel — a base pot amount, or chips counted out on a seat — for a dealer recording a hand they are watching rather than driving. Nothing leaves anybody's stack there. The pot wizard prices both the same way, and awarding it credits the winner. So in the second mode the table simply grew. Measured at a three-handed table with a $50 base pot: stacks 200, 200, 200 before, pot priced at $150, and afterwards 350, 200, 200 — six hundred dollars of chips became seven hundred and fifty, and the bank check, newly able to see it, blamed a rebuy nobody had made. So before the pot is pushed, whatever was assumed rather than taken comes off the stacks that assumed it, and is recorded where a swept bet lives. Chips already committed or swept are left alone — they have been paid once, and taking them twice is the same bug the other way round. A base pot on top of a live bet collects only the difference. Nobody is charged more than they are holding, and an empty chair or a seat sitting out is charged nothing at all. The same hand now ends 300, 150, 150: the table has the six hundred it started with, and the bank check is balanced before, during and after. 13 new tests, with the already-paid test disabled on purpose to confirm five of them catch the double charge. Walked through the UI in base-pot mode: pot badge $150, wizard pricing three seats at $50 each, and afterwards the felt holding exactly what it held before. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
0ad5592687 | Add the one-stack-rule round to the release | ||
|
|
1c2e716508 |
Ask the same two questions the same way everywhere
The round before this found the bank check and the standings reading a seat's own
stack where the felt reads the table's starting stack — two parts of the tool
disagreeing about how much money was on the table. That was worth a sweep, and
the sweep found more.
The colour-up panel had the same fault. It is the panel that says "count out
2×$100 for this player", and on a table opened the ordinary way — a starting
stack and some names — it showed nothing at all. The one table where every stack
is a round number made of the largest chips there are was the table it had
nothing to say about.
The other question, "is anybody in this chair", existed in four spellings: in the
seat count, in the seat menu twice, in the waiting list, and twice more in the
state module. Three of them looked at name, chips and cash; one also looked at a
hand being dealt. A chair that counts as occupied for one and empty for another
is how somebody's chips get given away — measured, a seat marked in hand with no
name and no stack yet was on offer to the next person in the waiting queue, on
top of whoever was already playing it.
Both questions now have one implementation and one name:
"What is this seat holding?" HoldemState.chipsHeldBy
"Is anybody in this chair?" HoldemState.seatInPlay
10 new tests, including one that pins the bank check, the standings and the
settle-up plan to the same total, and one that pins the seat count and the open
seats to the same set of empty chairs. The occupancy test was cut back to its old
three-part form on purpose to confirm the waiting-list case fails.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
236c52567e | Add the starting-stack accounting round to the release | ||
|
|
1689ecb4f0 |
Count the chips the table is actually playing with
"Starting stack" is how a night opens. The setup panel offers it and says what it means — "seats without their own stack start here" — and everything a dealer looks at reads it: the puck, the action bar, what a player is allowed to bet. The money side didn't. The bank check and the standings read `seat.stack` directly, which is null for exactly those seats. Measured on a three-handed table with a $200 starting stack and nobody's own stack typed in: the felt played for $600, the bank check reported no chips in play and pronounced itself balanced, and the standings — the list a dealer settles the night from — listed nobody at all. Three players, six hundred dollars, and the tool's own accounting said the table was empty. Both now ask the same question the felt asks. The check reports $600 in play against nothing taken, which is the truth: those chips have not been paid for. Empty chairs are excluded, or the starting stack would make every one of them look like somebody holding two hundred dollars — a ten-seat table would have opened with $2,000 of imaginary chips. Being told is only half of it. The verdict names the seats rather than saying "somebody's rebuy probably wasn't recorded", because the dealer's next move is to go to a particular chair and the tool knows which. And the seats holding the table's starting stack — the dealer's own instruction, said once in the setup panel — can be written down in one tap instead of six trips through Add chips. Only those: what somebody's forgotten rebuy was is a question only the dealer can answer, and guessing it would be the tool inventing money, which is the fault this round is about. 14 new tests, with the stack source and the empty-chair test broken on purpose to confirm they fail. Walked through in the browser from a cleared table: set the starting stack, name three seats, and the panel goes from "balanced, no chips" to "$600 more in chips than cash taken. Ann, Ben and Cal are holding chips with no buy-in written down" — then to balanced, with the buy-ins recorded and undoable. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
b8655be258 | Add the waiting-list round to the release | ||
|
|
55eb569d3b |
Keep the list of who's next, instead of leaving it on a napkin
A full home game always has two or three people standing behind the chairs, and the list of who's next lives on a napkin or in the dealer's head. That is where the argument an hour later comes from. The tool had just learned to open a chair — somebody cashes out and the seat is free — and had nothing to say about who should sit in it. The Session panel keeps the list in order of arrival, which is the only ordering anybody has ever agreed to. Whoever is at the front is offered the first open chair by number, and everybody else waits their turn: a list that isn't kept in order is a pile of names, and jumping the queue is the thing a dealer gets shouted at for. With the table full it says so rather than offering a seat that doesn't exist. The same offer appears on the empty chair's own menu — "Seat Priya" — because tapping the chair somebody is about to sit in is what a dealer's hand does anyway. Sitting somebody down puts their name on the chair and nothing else. No stack, no buy-in, not dealt in: they haven't paid yet, and inventing chips for them would put money on the table nobody has handed over, which is the one thing the bank check exists to catch. Add chips is the next thing the dealer does, and it is one tap away in the same menu. Two people called Mike are two people — the list is keyed by id, not by name. Names are trimmed, blanks are refused, and a saved list from a table that never had one comes back empty rather than undefined. 15 new tests, with the occupied-chair guard removed and a stack handed to the new player on purpose to confirm they fail. Walked through the browser: two names added to a full table, a player cashed out to open seat 2, the front of the queue seated there with nothing in front of them, the bank still balanced, and the second name taken off the list. 156 audits with the list populated, including a name long enough to test the row. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
7e420ce747 | Add the misdeal round to the release | ||
|
|
86886ac126 |
Take a hand back, which the rulings sheet has been asking for all along
This tool has told dealers to redeal on a misdeal since the rulings sheet was written — first card to the wrong seat, a seat entitled to a hand dealt out, one of the first two cards exposed — and it had no way to do it. The only control that ended a live hand was Next Hand, which says out loud that it takes the money off the table without paying anybody. That is the right answer for an abandoned pot and the wrong one for a misdeal, where the blinds and the calls belong to the people who put them in. Undo covers the first second of it: "↶ dealing the hand" is one tap. But the action history lives in memory, so a dealer who is three calls in, or who has refreshed the page mid-hand, has nothing — and a refresh mid-hand is exactly the situation this tool has spent rounds making survivable everywhere else. Misdeal appears in the header while a hand is out and goes away when it isn't: before the deal there is nothing to take back, and afterwards it would be offering to unwind a hand that has already been paid. It says what it will do to the money — "$25 goes back to the players who put it in, cards are cleared, and the same button deals again" — and adds a note when two or more players have already acted, because most rooms let a deal stand at that point. It doesn't refuse: which conditions make a misdeal is the dealer's call and the sheet is right there. What comes back is everything, including chips already swept into hand totals on an earlier street — the first version returned only the current street's and would have quietly kept a flop's betting. What stays is the button, the blinds, the straddle and anyone posting in: a misdeal is the same hand dealt again, not the next one, so the hand number doesn't move and nothing is logged. 13 new tests, with the sweep and the straddle broken on purpose to confirm they catch it. Measured through the UI: pot $25 with three players in, take it back, every stack is exactly $200 again, the bank still balances, and dealing again posts the same blinds on the same players from the same button. 156 audits with the button live, including a landscape header that stays one row at 46px. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
c36d7f60bd | Add the cash-out round to the release | ||
|
|
f966d8c340 |
Pay somebody out and keep the chair, the cash and the record straight
The most ordinary thing that happens at a home game after midnight — somebody counts up and goes home — and the tool had nothing for it. Freeing the chair meant wiping the seat by hand, and the bank check then declared their whole stack missing: "someone has paid for chips they haven't been given", for the rest of the night. Measured on a balanced table, cashing a player out with $350 in front of them left the check $350 short. Wiping their buy-in as well silenced it, at the price of losing their entire night from the standings and from the file the dealer hands over at the end. Cashing out is two facts, and both have to survive the empty chair: the box took their cash, and the box handed their chips back. Both are kept now, so the chair opens and the money still adds up. Cash out… is in the seat menu. It says what to count — "$350 — 3×$100, 2×$25" — what they bought in for, and whether they leave up or down, because that is the sentence the dealer says while their hands are moving. The bank check gains a Paid out line and reads "balanced ... once the $350 paid out to players who have left is counted", alongside the discards and the house's cut. The standings keep them with a stripe and the words "· paid out", so nobody counts money out to them twice at 2am, and the export marks them the same way. It refuses to pay anybody with money still in the middle: taking chips out of a pot other people can win, or handing the same chips over twice, are both worse than waiting for the hand to finish. The chair gets a fresh id, because the person who had it has gone and the next player to sit down is not them. Two things this turned up. The first draft worked out what a seat was holding by itself, read `Number(null)` as a perfectly good zero, and offered to pay a player with no stack set nothing at all — it asks Betting now, which owns that rule. The second was mine and the audit caught it: marking the departed row with opacity took the In and Won cells to 3.83:1, under AA, on the very list a dealer settles from. It is a stripe instead. 19 new tests, with the departed buy-in dropped from the check and the pot guard disabled on purpose to confirm they fail. Walked through in the browser: menu, confirm, payout, bank, standings, undo, and the refusal. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
15cec18256 | Add the clock round to the release | ||
|
|
0e2821257f |
Run the clock on a player, instead of counting in your head
The bar has had a stopwatch on the acting seat for a while: how long this player has been thinking, so the dealer knows when it's worth a nudge. What it never had was the thing a dealer actually does about it. Calling the clock is a procedure — a player asks, the dealer grants it, the player gets a set time and then a countdown said out loud, and if they still haven't acted the hand is decided for them. Running that from this tool meant counting in your head while the table argued about whether it had been thirty seconds yet. Thirty and then ten, which is the common form and what the TDA writes down. The countdown is the part the table hears, so it is marked apart rather than being ten more seconds of the same: the readout switches to a bare number and turns red. At zero the bar says the ruling in the dealer's own words and offers it as one button. The ruling depends on what they were facing, and getting that backwards is the part worth being careful about. Facing a bet, the hand is dead. Facing nothing, it is checked — nobody gets folded for being slow when checking was free. Three things about where it sits. The button appears only once somebody has been thinking long enough for the table to start asking, which is the same threshold the stopwatch already marks, so an ordinary hand costs no room at all: measured at 390x844, the bar is 152px with or without it. It is 44px like everything else a thumb has to find — it was drawn at 36 and the audit called it at every touch viewport the moment the check ran, which is the rule doing its job on a control that kills a hand. And once the count is over the Stop button goes, because there is nothing left to stop: at 640x360 its 94px pushed the undo control onto a second line and the taller bar covered the two bottom seats. The ordinary Fold/Check/Raise stay underneath the ruling. A player who speaks up as the count ends is still acting, and whether it was in time is the dealer's call, not the tool's. 11 new tests, and both halves of the ruling were inverted on purpose to check they catch it. Watched end to end in the browser at shortened intervals: the button appears, the count runs down, the ruling lands, and the hand is killed — or checked — with the undo control naming what happened. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
9c8d981e2d | Add the house-take round to the release | ||
|
|
c19575ab10 |
Count the house's cut instead of calling it missing chips
A game that takes a rake has chips leaving the table every hand, and the bank check had no idea. It added up the buy-ins, added up the chips on the felt, and told the dealer somebody had been paid for chips they were never given — a shortfall that grows all night and points at a miscount that never happened. Every home game that takes a drop for the beer fund hits this on hand one. So the tool takes it, rather than watching it go. Under Table Setup: a percentage, an optional cap, and "no flop, no drop" — the rule almost everyone plays, on by default, where a hand that ends before the flop pays nothing. Zero percent is a game that doesn't rake, which is most of them and the default, and nothing about that game changes. Where it shows up is the point. The pot wizard says "House takes $2 of $40 — 5%, capped at $5" above the payouts, before a chip moves, because the summary is the dealer's last chance to disagree with the arithmetic and money taken quietly is the one thing a tool like this must never do. The bank check gets a House take line and says "balanced ... once the $2 in the house's cut is counted". The hand log keeps the pot as the table saw it with the cut recorded beside it, and the export has a column for it, so the night's total is one sum in a spreadsheet. Two decisions worth stating. The rake rounds *down* to a chip the table actually has: a house that can't make 40c takes nothing and the players keep it, which is the only way to be wrong about this that nobody argues with. And on a split pot it comes off the top, then what is left is shared in proportion — with the rounding leftovers going back to the winners rather than to the house, which asked for a number and gets that number, not that number plus dust. Measured end to end through the UI: 5% capped at $5 on a $40 pot pays $38 and banks $2; four stacks plus the cut come to exactly the $800 bought in; the bank check reads balanced. With no-flop-no-drop on, a hand folded round pre-flop pays the full $15 and takes nothing; with it off the same hand pays $14 and takes $1. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
907e0165db | Add the stadium ring round to the release | ||
|
|
8aa90e90a4 |
Give a phone on its side the table shape it needs
The last two rounds left one thing failing and said so: a landscape phone with six or twelve seats stacked two of them 25px apart at each end of the felt, with pucks 64px tall. It has been there since seats were first spaced by distance rather than by angle. Three things were wrong at once, and it needed all three. The rim is the wrong shape when the table is very flat. An ellipse 616px wide and 96px tall has nowhere to put the seats near its ends: the curve doubles back on itself, so two seats a long walk apart come out one above the other. Past two and a half times wider than tall the rim is now a stadium — two straight rails with a half-round cap at each end, which is how a real poker table is built and exactly why it is built that way. A cap is a half-circle of the felt's own height, so an end takes one seat and the rest is rail. Only past that ratio: measured, the cut-off lands between a landscape phone at 4.4 and every other layout the tool has, 1.45 at 1280x900 and 1.0 to 1.4 upright, so no working table changes shape. The ring had less height than the felt did. The seat layer is inset by half a puck to keep the outermost seats on screen, and 26px off the top and bottom of a 96px felt left 44px to work with — less than half the table, which is why no rim shape could have fixed it alone. The inset is a percentage where the felt is short. The landscape branch never had that rule at all, because it is matched on height while the inset lives in a width query, so an 844px-wide phone on its side drew its ring against the felt's whole box; with a flat-topped stadium rail that put a dealer button over Resolve Pot in the header. And the puck was a line too tall. In the tight state the hole cards give up their row: of the four things on a puck, the cards are the one the dealer is holding in their other hand, and the seat editor still shows them. That took the puck from 64px to 55px, which is what finally cleared the ends. Measured across 36 combinations of nine viewports and 2, 6, 9 and 12 seats: the closest two seats at 640x360 go from 25px apart to 55px, and every combination is clean. Then 216 audits across thirteen viewports, three seat counts and both a mouse and a thumb: no failures, no console errors. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
86e1570618 | Add the phone felt round to the release | ||
|
|
dc5db16ffd |
Let the phone table use the width it has
Last round capped the felt so the ring would clear the action bar, and capped the wrong dimension. The wide layout sizes the felt from its width, so capping the width is how you shorten it there — on a phone the felt is already the full width of the column, and capping that threw away 96px of a 360px screen: a 240x240 table inside a 336px space, with nine pucks crowding it. A max-height instead leaves the width alone and lets the aspect ratio give way, which turns a short screen into a wide oval — the shape a poker table is. At 360x640: 336x240 rather than 240x240, same clearance over the bar, and the twelve-seat ring that was crowded at 240 comes out clean, which closes the one combination left failing last round. Where the room isn't the binding constraint, 390x844 and up, nothing changes and the table stays round. The landscape branch now says max-height: none, because it already spends the same measured room on its own height and applying it twice took the felt to the floor value. The measurement that decides whether pucks shrink was also wrong, in a way that only showed up once it was asked a hard question. Rim length per seat measures the walk between two seats, not the gap between them, and on a long oval the rim doubles back at the ends: twelve seats at 640x360 had 193px of rim each and sat 60px apart. It now measures the closest two neighbours directly, off the ring itself — and off the seat layer rather than the felt, because the layer is inset by half a puck on a phone and the felt's box put the answer out by 30px. That makes the rule geometric rather than about screen size, so it is no longer scoped to a phone media query: a wide landscape felt crowds at its ends while its rim claims there is room, and now it is told so. Pucks still touch at nine and twelve seats on a small screen. Tried 46px against 58px: on a roomy phone 46 cleared every overlap where 58 left two, but in a hand with a side pot, where the bar has grown and the felt has shrunk to suit, 46 overlapped four times and 58 twice. That is the felt being small, not the puck being wide, and paying for it with an ellipsis on every long name buys nothing. What has to hold is that each seat keeps its own centre, and that is measured at every size. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
6df400322e | Add the audit round to the release | ||
|
|
b9b2237dbe |
Run the two checks the audit had and never used
runAll() did not include the contrast check — it simply wasn't in the list — and
touch targets sat behind an opt-in flag nothing passed. So "the audit is clean"
has meant nine checks rather than eleven for a long run of rounds, and the two
left out are the two whose faults a dealer meets in a dim room with their thumb.
Both were written, tested, and then never called; the contrast checker was built
across two rounds and every sweep since has been silent about colour.
The standalone runner had a second version of the same hole: the 44px rule
stands itself down where `hover: hover` says there's a pointer, and a headless
desktop browser is a pointer, so a single pass had never measured a touch target
at any viewport. It now runs every viewport twice, once with a mouse and once
with a thumb.
A check that decides it doesn't apply now says so, rather than reporting an empty
offenders list — which reads exactly like a pass.
Faded text is measured instead of filed as unchecked. `opacity` on a box fades
the box and its text together onto whatever is behind it, so a dimmed seat is not
grey text on a dark puck: it is a faded puck and faded text both landing on the
page. Both halves are composited that way now. Two exemptions remain and both say
so out loud: a disabled control, which WCAG itself excuses, and a link inside a
sentence, whose size is set by the line it sits in.
What running them found, and this round fixes:
- the chair number on every seat: #8a8a8a at 9px, 3.39:1 once the empty seat's
fade is composited, on the label that answers "which seat is that?";
- Quick Split's amount field at 37px tall and its delete button at 30px across,
on a page whose targets had never been measured;
- and on a 360x640 phone with the bar grown by a side pot, the bottom seat
sitting 38px inside it.
That last one brings back a cap this project measured and rejected once: capping
the felt to the room left fixed the six-seat ring and broke the nine-seat one.
That trade was real then and isn't now — seats are spaced by distance around the
rim rather than by angle, so a shorter felt no longer piles them up. Re-measured
across 30 combinations of five phone sizes and 2 to 12 seats: every one clean
except twelve seats at 360x640, where a 240px felt and a 64px puck cannot both be
true. Where the rim can't give each seat a puck's width, the pucks come down a
size, measured from the rim itself rather than from a media query.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
ca9ac7b6af | Add the straddle round to the release | ||
|
|
fc866597eb |
Deal the straddle, instead of leaving the dealer to fake it
A straddle is a blind raise put out before the cards by the player left of the
big blind, for twice it. Home games run them constantly, and the tool had no
idea they existed — so a dealer's only way through was to deal the hand and have
the straddler "raise" to $4, which gets the money right and the other two rules
wrong.
There are three at once:
- the amount doubles as it goes left, and re-straddles carry on doubling;
- first to act is left of the *straddler*, not left of the big blind, and the
straddler keeps the option when it comes back round even if nobody raised;
- the minimum raise is twice the straddle. Measured off the big blind
underneath it, a $2/$4 game prices the next raise at $6, and every player at
the table will argue — correctly.
Tap Straddle on the deal prompt and the tool posts it, puts the right player on
the clock and prices the raise. The seat wears an STR badge for the hand, read
off what the deal actually posted rather than off the count, so the badge can't
outlive the chips it stands for. It's a one-hand decision, like posting in, and
it clears when the button moves.
The control sits on the deal row rather than a line of its own: measured at
390x844, its own line cost 44px of felt on every deal prompt, straddle or no
straddle, and this bar is docked over the seats. Three buttons across a 360px
phone want to wrap, which made the bar 64px taller and put the bottom seat three
fifths behind it — this layout deliberately doesn't shrink the felt to suit, so
the deal button is given a 7rem floor instead, which keeps the row to one line
and the cost to nothing.
20 new tests, checked by breaking each of the three rules on purpose first: the
wrong first-to-act, the minimum measured off the big blind, and a flat rather
than doubling chain each fail a different one.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
80dae56f0d | Add the landscape round to the release | ||
|
|
b97453fba1 |
Seat the table properly on a phone held sideways
Landscape is the one shape this layout has never really fitted, and the audit was calling two separate faults there. The felt worked its own height out as `100vh - bar - 7rem`, guessing that everything above it came to 7rem. That is 108px at 640x360, which the guess covers, and 119px at 844x390, which it doesn't — so on an iPhone on its side the bottom row of seats sat up to 26px inside the action bar. The measurement it needed was already being published for the wide layout: the bar's real height, the wrap's real offset and the bottom seat's overhang, all taken off the window. It uses that now. The floor under it drops from 220px to 96px, because 220 was not a size a 390px-tall window has to give — holding out for it was itself how seats ended up behind the Fold button, which is the one thing the floor exists to prevent. That still left the header taking two rows, and the second row is 44px of the 390. The title block and the five buttons came to 829px inside a 765px header, missing one line by 64px. The page title goes — the one thing on this screen a dealer already knows — hidden from the eye rather than removed, so the heading is still there for a screen reader. The buttons tighten across but never down: they stay a 44px thumb target. The seats themselves were spaced by angle, which is only even spacing on a circle. On a 601x126 felt, ten seats at 36° apart put four of them down each steep end, 20px apart, with pucks 72px tall — a seat's centre belonged to its neighbour, so tapping a player got the wrong one. They now walk the rim by distance instead, which puts them along the two long rails the way a real table seats people. Closest neighbours go from 33px apart to 59px. Measured at 640x360, 667x375 and 844x390 with 9-, 10- and 12-seat tables: every combination the audit used to fail is clean, bar a 10-seat table at 640x360, where two seats at the end of a rim that flat still overlap. Portrait, tablet and desktop are unchanged — the floor never bound there, and on a rounder felt walking the rim gives back the same angles it always did. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
6ca99fde6b |
Bring master's already-merged rounds into the release branch
Master merged #58-#61 (manual deal, less guessing, seat editor, landscape table) while the stack kept going. Those commits are already in this branch's history; this merge just reconciles the two shapes so the release can go in as one. |
||
|
|
419852a1f4 |
Let a dealer take the table off the device it's stuck on
The night lives in one browser's localStorage and nowhere else. A dealer whose phone dies, or who wants to carry on from the tablet after the break, loses the stacks, the buy-ins, where the button is and the whole hand log. The CSV export can't bring any of it back: it records what happened, not where the table stands. The table can now be written to a file and read back. Round-tripped through the real UI, a hand in progress comes back mid-hand — blinds posted, the same seat on the clock, the button where it was. Loading replaces everything, so it asks first and names both sides rather than saying "are you sure": how many players are in the file and when it was saved, against how many players and how many logged hands are about to go. It goes through the same undoable path as the header buttons, so the toast offers the way back, and the confirm says so instead of overstating it. Reading a file goes through exactly the same migration a save from storage does — load() had that logic inline and it is now a function both call. A second, looser path for imports is how a format grows two meanings of the same field. What it refuses, each with something a dealer can act on and never a half-loaded table: anything that isn't JSON, JSON with no seats in it, a save from some other tool that happens to carry a `kind`, and a file from a newer version — that last one because loading it risks reading a field whose meaning has changed, and the dealer would find out from the money. What it accepts anyway: a bare table with no envelope, for somebody who copied the storage value out by hand. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
8a2fc3e540 |
Give undo and the card picker's buttons a fingertip to land on
The first thing the repaired audit found, run on a real touch context at 44px: three
controls a dealer taps on a phone that are too small to tap.
.actionUndo 145x30 the "I recorded that wrong" pill
#cardPickerClearBtn 50x40 wipes a player's cards
#cardPickerCloseBtn 50x40 closes the picker
Undo is the worst of them. It is the control a dealer reaches for having just entered
the wrong action with five people waiting, and it was fourteen pixels short of a
fingertip. The picker's two are short because the touch rule that fixes every other
small button is scoped to .panelBody, and the picker is a sheet — it never reached
them. One of them is destructive.
All three now sit at 44px on touch. Nothing changes on a pointer device.
I tried an invisible hit-area pad on undo first, to avoid spending bar height, and the
measurement said it did nothing: the pill clips its overflow to keep a long label on
one line, and giving it a positioning context made it clip the pad too. Growing the
box is the honest fix, and it turned out to cost nothing anyway — the bar goes from
138px to 152px and the felt stays at 366px, because the felt had slack against its
220px floor rather than sitting on it as I had assumed.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
b64da7a787 |
Make the layout audit usable inside modals, and stop it passing what it can't measure
Last round turned up that Quick Split had never been audited. Chasing that, the reason
the dealer table's modals had never been audited either turns out to be the harness,
not neglect: it reported the pot wizard covering the seats as ten faults and the
rulings sheet as seven. A modal covering the page is what a modal is for, and an
instrument that calls that a fault is one nobody can use in those states.
It now finds whatever is open on top and scopes the reachability checks to it — the
overlays, plus the phone seat sheet, which is a body class rather than an element. The
seat-ownership check is skipped outright while a sheet is open: seats behind it aren't
fighting each other for taps, nothing is meant to reach them at all.
Two worse faults came out of proving the fix rather than assuming it. Injecting a 20x14
button into the open wizard, the contrast check caught it and the touch-target check
did not:
* effectiveTarget returns null when elementFromPoint can't find the control, and the
check treated that as a pass. Silent skips are how faults hide — the same lesson as
the aria-hidden and faded-text exemptions before this. Unmeasurable controls are
now reported as warnings.
* The probe failed on anything scrolled out of its container. That was hiding a real
control: "Confirm Winner(s)", the button that pays the pot out, had gone unmeasured
for thirty-six rounds. It is scrolled into view before probing, and passes.
A checkbox inside a label is now measured by its label, since tapping the label is what
activates it. Without that, the dedicated-dealer toggle read as an unprobeable 24px
target when it has a 335x44 label around it.
The tool itself came out clean: no failures and no warnings across the table, the pot
wizard, the rulings sheet and the seat editor, on a real touch context at 44px. The
value here is that those states can be checked at all from now on — verified by
injection, which the fixed check catches and the old one did not.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
3be93cdadf |
Chop a side pot on the screen that worked it out, and fix Quick Split's contrast
Quick Split lists the pots when somebody is all-in — main, sides, uncalled — and then
leaves the dealer there. Two players tying for a side pot is ordinary, and working out
each one's share meant switching to the other mode and retyping an amount the screen
had just calculated. Retyping a number with money on it is a transcription error
waiting to happen, and the number was already on screen.
Each pot with two or more people who can win it now carries a Chop, opening underneath
its own row: as many ways as there are contenders by default, a stepper for a chop
between fewer of them, and the same odd-chip and dust wording the main split uses,
because it is the same rule and the dealer shouldn't have to notice they are reading a
different screen. Per-row state, so opening the main pot's chop doesn't disturb what
the side pot below it says. Not offered on the uncalled row, which goes back to one
player, nor on the total.
Separately, this page had never been through the layout audit the dealer table gets
every round, and it was carrying nine contrast failures:
.unevenCurrency 4.11:1 the $ beside every amount typed
.unevenRemoveBtn 3.66:1 the ✕ that deletes a player's entry
.splitFootnote 3.94:1 the instruction for what to do with the pot list
All under the 4.5:1 small-text minimum, the worst of them on a destructive control.
Lifted to pass. The large $ on the even-mode pot input is left alone: at 1.6rem it is
large text, where 3:1 is the bar and 4.11:1 clears it.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
40fa16920b |
Offer bet sizes the table's chips can actually make
Half the pot lands on a half chip constantly. Pre-flop at 1/2 the honest half-pot is $4.50, and the smallest chip in the default legend is a dollar — the tool's own chip breakdown calls it "4×$1, $0.50 no chip covers". The button offered it anyway, so the dealer rounded it themselves every single time. The pot-derived quick amounts now round up to something the table can build. Up, not down or nearest: these are bet sizes, a pot-sized bet that has been shaved is no longer a pot-sized bet, and rounding up cannot drop an amount below a legal minimum the way rounding the other way can. Only when the legend describes this game. A smallest chip larger than the big blind means the denominations were never set for these stakes, and rounding a 5c/10c table up to whole dollars would be the tool doing real damage on the strength of a field somebody forgot to fill in. Those tables keep exact amounts, and the wrong legend stays visible rather than being quietly applied. A table with no legend at all is told nothing about chips and so has nothing applied either. Verified across five tables: default dollar chips at 1/2 give $4/$5/$7, cent chips at 5c/10c keep $0.20/$0.23/$0.35, five-dollar chips at 5/10 give $20/$25/$35, and both the stale-legend and no-legend tables are left exact. The minimum raise is deliberately not rounded. It is what the rules require, and moving it to suit the chip tray would change what is legal rather than what is convenient. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
09eac536b5 |
Make the pot button offer a pot-sized raise
The raise pad worked its quick amounts out as "the current bet plus the pot as it
stands". That leaves out the raiser's own call, which is in the pot before the raise
is measured off it, so every amount came out short. Measured pre-flop at 1/2:
offered: Min $4 ½ pot $4 Pot $5
correct: Min $4 ½ pot $4.50 Pot $7
A pot raise there is: call the $2, which makes the pot $5, then raise $5. And the
half-pot was so short it clamped up to the minimum and sat next to the Min button
showing the same $4 twice, which is how it went unnoticed — it looked like a table
where those two happened to coincide.
"Pot" is not a suggestion. It is a definite number players say out loud, and a dealer
tapping the button that claims to be it should be recording what was actually said.
The rule moves into Betting.potRaiseTo, next to the other rules and testable on its
own, rather than living as arithmetic inside a DOM renderer:
raise to = the current bet + (pot + what they owe) × fraction
Facing no bet it comes out as a plain pot-sized bet, because both the bet and the
amount owed are zero. Facing a bet it comes out as three times the bet plus the dead
money, which is the shape a player checks it against.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
8efc14f61f |
Show the banker the amount to count out, not just whether the player is up
The standings had a column headed "Cash out" and it did not hold an amount to pay.
It held the difference against the player's buy-in. Measured on a table where Ann
bought in for $200 and sits behind $340:
PLAYER NET CASH OUT
Ann $0 +$140
She is handed $340. The column called "Cash out" says $140, under a heading that
invites reading it as the payout, at the exact moment somebody is counting money onto
the table. The number a banker's hands actually follow — what the player is holding —
was computed all along and written to the CSV, and never shown on screen.
Three columns now, and they say what they are: Player, Chips, Up/down. Chips is a
plain amount and never signed, because it is a quantity of money to hand over. Up/down
keeps the sign and the colour, because it is a result. The two differ by exactly the
buy-in, which is asserted rather than assumed.
Chips counts what is out in the middle for the hand in progress as still theirs, for
the same reason the up/down figure does: the panel is read mid-hand constantly and a
stack that drops by the size of a bet looks like money going missing.
Net — won less paid in, from the hand log — comes off the screen to make room. It is
the weakest of the three: it only moves when a pot is resolved through the wizard, so
at a table where the dealer settles pots by hand it reads $0 all night beside two
columns that don't, and up/down already answers "how are they doing". It stays in the
CSV export, which loses nothing.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
c70cb150db |
Stop telling a brand-new table its players are broke, and give it a way out
Following on from seating the table: a dealer who names six people and hasn't yet
said what a buy-in is gets told
Nobody at the table has chips left. Add a buy-in to deal.
with no buttons on the bar at all. Both halves are wrong for that table. "Left" says
the chips were had and lost, which sends the dealer looking for a rebuy — and a
blocked prompt with nothing to press is a dead end, when the fix is one field in the
sidebar the message never pointed at.
A table where nobody has ever bought in is now told what is actually true, and given
the way out:
Nobody has bought in yet. Set what everyone starts with, or give each seat
their chips. [ Set starting stack ]
The button opens Table Setup, scrolls the starting stack into view and selects it, so
the next thing typed is the answer. A seat with no stack of its own falls back to the
table default, which is why one number is enough to seat everybody's chips at once.
A table that genuinely busted out is a different situation and still says so, with no
starting-stack button — there the fix is a rebuy, not a default.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
e9bc9afcdb |
Seat a player when the dealer names one, and stop hiding the bar on a full table
The first thing anybody does with this tool is seat the table, and it didn't work. Measured on a fresh table: click a seat, type a name, repeat six times — six names on the felt, six seats still not in the hand, and no action bar at all. No "post blinds and deal", no explanation, nothing to press. Six people at the table and a blank screen with no way forward. Three things were wrong and each hid the next. Naming a chair and dealing that chair in were separate acts. Putting a name on an empty chair is somebody sitting down, so it now deals them in. Only a chair that was empty — no name and nobody in it: renaming someone who is sitting out is a correction, not an arrival, and dealing them back in over a typo would be the tool overruling the dealer. The action bar hid itself whenever no seat was live, which was written for an empty felt and caught a named one too. A table with names on it is not an empty table; it's a table the dealer needs to be told something about. And the seat editor never redrew the bar. It refreshes narrowly on purpose — a full render on every keystroke would take the name field out from under the dealer mid-word — but the bar answers "is there a table here at all", and seating somebody is exactly what changes that answer. Even ticking "In hand" by hand left the bar hidden until something else happened to redraw. Verified the fix keeps focus and the caret where they were, typing a character at a time. For the states that can still reach it — a table where everyone was sat out, a save from before naming seated people — the prompt names them and offers "Deal in all 6" rather than a hunt through six seat editors. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
9f3d0dce4b |
Call who shows first at the showdown, instead of leaving the dealer to remember
Every showdown that isn't a fold needs somebody to turn over first, and it is the one
call at the table nobody volunteers for — everyone would rather see a hand than show
one. The tool held both facts the rule needs, who last raised and who sits left of
the button, and said neither. The dealer worked it out from memory while five people
waited, every hand.
The action bar now names the seat and the reason above the resolve button:
Dee shows first — last to bet or raise.
Ben shows first — checked round, first left of the button.
Dee shows first — last to put money in; nobody could bet the river.
The reason is there so the call can be repeated to whoever argues with it, which is
half of what makes it stick.
Three cases, in the order the rule asks them. Somebody bet or raised on the final
round: they show. Nobody did: first live player left of the button. Nobody *could*,
because the chips were already in: it falls to whoever last put money in, on whichever
street that was — "no bet on the final round" isn't a choice anybody made when there
was no round to bet in, so the seat left of the button hasn't earned it.
Aggression is tracked per street and per hand, because those two answers differ
exactly when it matters. A short all-in counts: not reopening the betting is a rule
about who may raise, and says nothing about who shows. Posting a blind doesn't — a
hand limped and checked all the way down has no aggressor at all, which is the case
that goes to the button.
A matching ruling goes in Common rulings, since the dealer will be asked.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
01f5139740 |
Let the dealer find one hand in the log instead of scrolling the night
The hand log exists for one moment: somebody at the rail says they won a pot an hour ago and wants it looked up. A night runs to sixty hands or more and the panel showed five of them at a time, in a box fixed at 260px whether the dealer was on a phone or a 27" screen with room for twenty. There was no way to ask for a hand — only to scroll and read every row. A filter box over the list now takes a name, an amount or a hand number. Matching is loose on purpose: "fay" whatever case it's typed in, "190" for a $190 pot or a $190 payout, "$1,90" for the same typed badly, "#12" when the dealer knows the hand and wants that exact one. Beside it, how much of the night is hidden — "8 of 60" — since a filter matching one hand otherwise looks like a table that only played one. The list also takes the room it has: clamp(200px, 42vh, 560px) rather than a flat 260, which is seven or eight rows instead of five on a desktop and no worse on a phone. Export stays the whole night, always. A CSV quietly missing the hands scrolled past would be a record with holes in it and nothing on the file to say so — if a filter is up when it's exported, the toast says how many went in. Matching deliberately covers only what the row displays. Searching the full ledger as well was the first attempt, so the hand that broke somebody could be found under the loser's name — but the ledger holds everyone who put a chip in, and measured on a seven-handed night where nobody folded, "Fay" returned 60 of 60. A filter that searches text the panel doesn't show looks broken even when it's right. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
c76ebf4d18 |
Say when somebody is being dealt in for free, and give the dealer the post
Sitting down between the button and the big blind means the blinds have gone past you. The tool dealt those players straight in, silently. Measured: Eve takes a seat, and the next hand she is dealt cards in late position having put nothing in — a free hand, out of everyone else's night a couple of dollars at a time, with nothing anywhere on screen to tell the dealer it had happened. At a table there are two answers and the player picks one: post the big blind and come in now, or wait for the blind to reach them. The tool did neither and named neither. The deal prompt now names anyone about to be dealt in who has not paid for it, and carries the post as one tap. Nothing is forced — a home game that doesn't care can deal as before, and the sentence is still worth having on its own, because the dealer at least knows. The post goes in as a big blind out of that player's own stack, is live like any blind, and is spent at the end of the hand rather than becoming a standing charge. Somebody back from sitting out is owed one the same as a newcomer. The big blind of the hand isn't — they're already paying — and neither is anyone at a table that has not dealt a hand yet, where the whole table is new and nobody has been skipped. Deliberately one line for however many have walked up, not one each: the bar is docked over the felt and the felt has already shrunk as far as it goes on a phone, so every line costs seat area. A line apiece put the bar over two fifths of the bottom seat at 390x844. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |