f841ccf42526b51e33b563f6bcf112497e81bd36
10
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8136025544 |
Say what the undo would take back, and let the empty chairs be empty
Two things on the felt and the bar that were spending weight on the wrong half of what they had to say. The undo pill names the last action, and on a phone it had stopped doing it. It is a fixed width — its text changes on every tap, and a control that resizes under a thumb reads as the interface twitching — and that width leaves the label 73px. Measured across one hand, four of the five labels a hand produces wanted more: ↶ dealing the hand 104 ↶ Christopher's fold 109 ↶ Mike from work's fold 128 ↶ Alexandra's fold 100 ↶ Dee's fold 69 (the only one that fitted) so the safety net spent most of a night reading "↶ dealing …". Cut as one string it keeps the wrong half: "↶ Mike from wor…" names everybody except the action. So an action's name is handed to commit() in two halves now — who it was, and what they did — and the actor comes off whole when the act cannot fit beside it. The whole sentence stays on the button's title and aria-label, which is what the guidance asks of a control that has had to shorten its own name. Two things fell out of it. The label cannot overflow — the act inside it is the item with `overflow: hidden`, so it swallows the excess and the row measures as though everything fitted — which is why the fit asks the act and not the label; asked the label, "↶ dealing the hand" kept its actor and quietly cut "the hand" off inside it. And the pill was still printing a keycap on phones: every other control on the bar has dropped its cap under `hover: none` since that rule was written, but this one is a different class and had been left out of it, so the narrowest label on the bar was the only one spending 22px on a shortcut nothing could press. That 22px is exactly what "raise to $1,200" needed to keep its digits. Nothing is cut now at any viewport in the sweep: a phone reads "↶ the hand", "↶ Dee's fold", "↶ raise to $20", "↶ call"; a desktop and the landscape rail keep the actor wherever it fits. And the empty chairs. An open seat was drawn as a whole puck of near-black on the green — the same box a seated player gets, at the same weight, saying nothing. Five players nine-handed is four of them, and on the felt they read as holes punched in the table: the eye had to sort the loud empty boxes from the loud full ones before it could find who was in the hand. The wash was there for one reason, which is that the chair number's contrast cannot depend on whether its chair sits over the rim, the dark green or the light — about Lc 5 across one ring. So the wash moved off the chair and under the number, which is the only thing an open chair has to say. The dashes still say "open", the box a thumb lands on has not moved a pixel, and the felt gets four boxes' worth of quiet back. 1039 unit tests; 418 layout audits across 13 viewports, clean. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
d4c39f209a |
Follow the browser's text size instead of sliding out from under it
Every length in this stylesheet that isn't a structural clearance is in `rem`. That is the right way round — it means the page follows the browser's font size, which is what a phone's text-size slider changes — and it means there was a whole axis of behaviour that 320 audits had never once touched, because every one of them ran at the default 16px. The sweep gets a third pass: the same surfaces on the eight phone-shaped viewports at 20px of root text, which is 125% on an Android slider. Everything it found on its first run was caught by a check that had been sitting there for months with nothing to say. The page slid sideways. Three header buttons that will not break and will not shrink — `white-space: nowrap` and a `min-width: auto` floor apiece — were told `flex-wrap: nowrap`, so when they stopped fitting they had nowhere to go: "Resolve Pot" ended 23px past the right edge of a 360px window and put a horizontal scrollbar under the whole document; at 24px, 60px past the edge of a 390px one. They take a second row now on the day they need one, and still fit on the first and still grow into it at any ordinary size. Figures painted over the seat next door. Which of the two type steps an amount got was decided by counting characters — eight for one, ten for the other — which is a guess at how wide a figure is that happens to hold at one text size and one puck width. At 20px on a 44px puck, "$1,200" is six characters, took no step at all, and wanted 38px of the 34 it had. Render.fitAmounts measures now, in the batched passes fitSeatNames already uses, and takes the separators out before it takes a step of type: "$1200" at full size beats "$1,200" at three-quarters of it. And the pucks grew, which the ring cannot answer. The row heights are px tokens so an amount stepping its type down shortens its digits and not its row — but a token is a floor, not a ceiling. At 125% the puck went from 55px tall to 65 and twelve of them came down on each other, because at the 44px floor a thumb needs there is nowhere narrower to search. The two sizes a puck is written at are capped at exactly the sizes they have always been drawn at, so nothing changes for anybody at an ordinary text size — and the felt is a diagram, so the rule that already governed a puck's width now governs its height. Roomy pucks are exempt; they have the room. The same figure is in Standings in full, at whatever size its owner asked for. Two smaller things on the way. The landscape rail opened at its top, so its overflow came off the bottom — where Cancel and Raise to are; it opens at the end now, which is the same reason it is anchored low at all. And the check that called those buttons unreachable was wrong: the rail scrolls on purpose, and a sweep that cries wolf is a sweep nobody reads. 418 audits, clean. One shape it is not held to, with the arithmetic in runUiAudit.js: twelve players all-in on a 360x640 phone leaves the felt 290x163 once the bar has taken 283px of the 640, and twelve 44x62 pucks do not fit around an ellipse 163px deep. Reported as a note every run rather than a failure every run. 1026 unit tests. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
ab04c43ffd |
Stop the table resizing under the thumb that is reaching for it
Two things moved the felt, and neither of them was the felt's business. The raise pad. It is 57px taller than the row it replaces, and the table was sized against it — so pressing Raise shrank the whole thing, re-ran the ten-candidate width search, redrew every seat at a different size, and undid all of it when the dealer pressed Raise to. Measured, five players nine-handed: 1280x900 felt 738x532 -> 669x481, pucks 120 -> 109, worst seat moved 73px 360x640 felt 283x329 -> 283x279, pucks 44 -> 48, worst seat moved 50px 390x844 felt 309x463 -> 309x451, worst seat moved 11px Every raise, all night, at the one moment the dealer is reading the felt to decide what to type. A phone held sideways already scored 0px: the rail is a column down the side, and its height never was the table's business. So the felt is sized against the bar at rest and the pad floats up over the bottom of it, which is what a transient surface is supposed to do — the context it is over stays where it was. --actionBarHeight is untouched and still says what is really covering the window, because that is what the toast has to clear. And the status row. It wraps, and flex settles its line breaks before it decides what to shrink, so a name too long for the room left beside the clock and the undo pill did not give way — it pushed them onto a third line. That is 22px off a felt underneath a bar fixed to the bottom. Measured at 360x640 mid-street: "Dee · $600 behind" left the table 283x329 and "Christopher · $1,200 behind" made it 283x310, with every seat moving 24px between one player and the next. Whose turn it was decided how big the table was. An earlier round saw this and reserved two lines for the row; three is what it takes. Reserving three would spend the 22px on every table for the sake of some of them. So the actor, the clock and the undo pill get a line of their own that cannot wrap, and the name shrinks into it — with the stack held out of the shrink, because a number read wrong is a worse failure than a name read short. The row is 66px whoever is acting. It claims that line only when there is somebody on it. Claiming it unconditionally cost one in the state that can least afford it — a board running out has nobody to act, so the row is the undo pill alone, and it used to sit up beside "All-in — running out the board". The pill gives width back to the name on a phone: 10rem is 160px, and at 360 it took half the row, leaving "Christopher" cut to 23px of its 75. A media query, not `min(10rem, 34%)` — that was tried, and a percentage against a row sized to its own contents chases itself: the sweep caught the pill drawn 38px wide at ten viewports. Verified by walking a hand at five viewports: one felt size, one puck width, and not one seat moving, across a raise pad opened and shut and a full street of action. 1003 unit tests. 320 layout audits across 13 viewports with a mouse and a thumb, and this is the first round in a while with nothing in them — the chip behind seat 11 on the twelve-handed Galaxy S8, which master has carried for rounds, clears too. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
4a2ec1295b |
Make every seat on the ring the same seat, and measure it before believing it
Nine chairs, 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. 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. Three rows are held open instead: the name, the amount, and the stack. 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. What the puck says changes all night; how big it is does not. It costs nothing. measuredSeatBox already 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 to drop steps its type down instead of ellipsising. fitSeatNames turns "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; this is fitAmount's rule applied to the other thing a dealer reads across a table. 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 — three rules of it, indenting the first line so a long name could drop below the chip onto a second. There is no second line to drop to, and left in place it drew "Christopher" as an empty label. The padding every crowded ring already used is the rule at all sizes now, widened from 22px to 25 so it actually covers the chip and the gap it is inset by. Then the part that was quietly wrong. renderSeats builds 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. On a 390px phone that is a ring sized for empty seats — and 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: same viewport, same table, 90px pucks on load and 72px after a rotate. So the finished pucks are 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, and the second answer is cached. Fresh load and rotate-and-back now 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 gives up the 1.4 leading on its money rows to pay 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. 992 unit tests. 320 layout audits across 13 viewports with a mouse and a thumb: one failure, the chip behind seat 11 that master already carries. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
0f7213ec2d |
Open a seat on what is on it, and put the sheet in front of the table
The seat sheet is the second-most-used surface here — every buy-in, every rename, every correction goes through it — and it was the least finished one. Seven things were stacked flat at one weight and all open at once. Measured on a 390px phone it came to 594px and scrolled, and the three controls a dealer touches on nearly every visit were 132px of that. The chip counter alone was 174px, the tallest single thing in the sheet, for a job most tables do once a night or never; the two hole-card slots were 62px of dashed grey on every table that never asked to be shown a card. Cards, the contribution and the custom fields are behind a fold now, and which of the three stand open is a question about the seat rather than a setting: a seat holding cards opens its cards, a seat with chips counted opens its count, a seat with a note on it opens its notes. A shut section says the answer on its own row — the cards, the total, how many notes — so nothing already entered is further away than it was. An empty chair is 419px and does not scroll; a seat with everything filled in is 625 and opens all three. The fields have the width the sheet always had. `.fieldRow input` caps at 140px, which is right in a 320px sidebar of settings and wrong in a sheet holding a whole phone: "Name" sat against the left edge with 203px of nothing between it and a field a third of the row wide. Same 44px control, 300px instead of 140. The chip counter's values are text. They were a `title` attribute, which is a tooltip, which is nothing on the device this is used on — five identical number boxes told apart by five coloured dots. It also left the wrapping <label> with no text in it, so they had no accessible name either. And the sheet has a scrim. It was the only one of the tool's seven overlays without one, while covering the felt from 250px down and the action bar with it — modal in everything but appearance. What stayed lit above it was the header, with Misdeal, Next Hand and Resolve Pot live under a thumb reaching for a name field. It dismisses on a tap and on Escape now, like the other six; a card picker opened from inside still takes Escape first. In landscape the scrim stops where the sheet stops, so Fold, Call and Raise keep the rail. It is put away with visibility rather than opacity, which is the same lesson the sheet above it already learned about transforms — and the layout sweep caught it the same way. At `opacity: 0` a fixed, full-window box is invisible and still entirely there: bottomFurniture() read it as furniture standing 640px tall at the bottom of a 640px phone, in every state and on every screen, and asked scroll-padding to reserve the whole window for it. Ninety-eight scenarios failed on it; hidden is hidden, and the sweep is back to the five it started with. The toast steps over the sheet the way it already steps over the action bar. Both are fixed to the bottom of a phone, and a "Keeping the screen on" toast was landing squarely on the seat form with its Undo and its × live above the fields. The height is measured and published, because it changes the moment a section is unfolded. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
02c2a034cb |
Stop the fixed bar and the toast sitting on the keyboard's focus
WCAG 2.2 SC 2.4.11 asks that a focused control not be completely hidden
by something the page has put on top of it. The bar is fixed over the
bottom and is not in the flow, so the browser does not know it is there:
Tab to a control near the bottom and it is scrolled flush against the
edge of the window, under the bar, and the browser considers it done.
Tabbing the whole page measured 16 stops resting completely covered on a
390x844 phone and 27 more partly, plus four under a toast on the desktop.
It is now 0 and 0 on desktop, phone and a phone on its side — clean
against 2.4.12 (AAA) as well, which allows no overlap at all.
Three things were wrong and they needed different answers.
scroll-padding-bottom on the scroll container, sized from the bar's own
published height and the toast's, handles the case where the browser has
to scroll to reach the control. That is half of it, and the half that
looks like all of it: a control the browser already considers visible is
one it will not scroll to, and a fixed overlay on top does not enter into
that decision. Measured — tabbing to Common rulings with a toast up left
it 100% covered and scrollIntoView({block:"nearest"}) moved nothing. So a
focusin handler measures the overlap and scrolls by it, and when the page
has no room left (26px against 94px wanted) the toast gives way instead.
Sizing the page padding for the toast would have fixed that by making the
page grow and shrink 64px whenever anything was announced.
The page's room to scroll past the bar was 9rem on the sidebar alone,
which left the table's column — most of a desktop — with none. It is now
on the whole app and taken from the bar's real height, so it is also 0 in
the layout where the bar is a rail down the side.
And the seat editor's bottom sheet was put away with a transform, which
moves a thing without putting it away: its Done button sat at y=850 on an
844px screen, focusable, announced, and impossible to scroll to, because
a fixed element does not move when the page does. visibility:hidden puts
it away, transitioned so the slide-out still runs.
uiAudit.js gained focusHasRoom() and focusablesReachable() for the two
static halves. Audit unchanged at 2 failing configurations and 3
offenders, both pre-existing. 805 unit tests pass.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
2a080eab6a |
Give every bet on the felt somewhere to be
The chips were the last thing on the table nobody had measured, and the sweep had never seen one: it audited a table that had never had a bet placed on it, so a third of what is on the felt during a hand went unlooked at for the tool's whole life. It posts blinds, deals and calls three times now before it measures anything. What that found, and what this fixes: - Chips accumulated. Seats are kept and updated in place rather than rebuilt, so clearing the bets layer stopped clearing the ones anchored inside a seat and nothing else ever did. An iPhone 14 had one chip per seat after the deal and five by the third render; a dealer's every tap left another disc on the felt. - The ring-or-cling decision asked how wide the window was. A phone held sideways is 844px across and gives the felt 117px: it passed the width test, took the ring, and every chip on it was painted under a puck — the seats carry z-index 1 and the bets layer carries none, so a chip that lands under one is gone, and with it the only number saying what that player has in. Measured now, and only measured. - Getting clear of a box was `edgeReach(puck) + edgeReach(chip)`, which is right only straight up, down or sideways. Two axis-aligned boxes are clear when they are apart on one axis, so the travel is the reach of the box you get by adding their widths and their heights. At 768x1024 a $10 leaving a seat on the diagonal wanted 88px and got 77, and came to rest 11px inside its own player's puck — every seat off the four compass points, which is most of them. - Every chip was laid out at the first chip's size. A chip is as wide as the number on it: "$5" is 21px and "$10" is 27px on a phone, so each $10 was placed as if it were 6px narrower than it is. The widest is no better: at 768x1024 one 47px chip made the whole table give up a ring the other four had room for. - Anchored chips settled where they landed. At 640x360 the felt is 528x83 and a puck is 55 of those pixels tall: clearing it takes 41px, the players facing each other are 70px apart, and both chips set off 41px into a gap that is 70 wide. Three overlapping pairs on a twelve-handed table. Each chip now gives ground only to something it is actually standing on, a pixel at a time — a midline rule or a single scale factor both punish the seats at the ends of a rail, which were never going to hit anything. - Nothing knew the pot total was there. Seat 1's $10 was sitting on "Pot $65". Two things off the felt, from the same sweep: - The toast outlived its news. "Next hand — button on seat 2. [Undo]" stayed up while the hand was dealt and three players called: an offer to move the button, wired to a snapshot that predates the hand, so taking it would have thrown the hand away without saying so. It was also in the way of thirty small screens' seats and controls. The bar drops it as it takes the next action; the bar's own Undo names what it would actually take back. - The pot dialog cut a name off. Three players in the pot on a Galaxy S8, two rows visible, the third below a hairline that reads as the end of the list — a dealer ticking the two they could see would have paid two thirds of a three-way pot. The 85vh budget was still subtracting the action bar's height from a dialog that has sat above the bar since its z-index was fixed, and a player's ceiling moves beside their name on a short screen. All three fit now, and where a list does run on, it runs on under a fade rather than stopping dead on a row boundary. The pot badge goes on a phone held sideways: 86x66 of a 692x113 felt, for a number the action bar prints three inches below it, standing in the only band a bet chip has. The audit stopped calling one thing another. An element clipped away by a panel that scrolls is not covered by whatever is painted at that height — the wizard's third winner came back for four viewports as "fully covered by button Confirm Winner(s)". 190 audits: 25 failing configurations to 7, and every one of those seven is the seat ring hanging past the rail on a landscape phone, which is the known residual from the last round. 663 unit tests, 44 of them new. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
f93e0935ef |
Give every seat its own pixels
Six players at a nine-handed table is the most ordinary table this tool draws, and on every phone in the sweep one of them had their stack painted under the player next to them. Measured across nine viewports and six table sizes: seventeen of fifty-four tables hid a name or a stack behind something belonging to another seat. Two causes, and the layout only knew about a third of one of them. The felt decided how big a puck could be from a single number — the straight-line distance between the closest two *neighbouring* seats, judged against a fraction of a puck's width. Distance has one number in it and a puck is half again as wide as it is tall, so the same 84px was generous side by side and nowhere near enough on the diagonal. And neighbours are the wrong pairs to ask about: on a phone held sideways the felt is 528x87, the ring is very nearly a straight line, and the seat that lands on top of you is the one across the table. Ten of the tables in the sweep had a collision that a neighbours-only check cannot see — seats 1 and 7, overlapping by 68px, while every adjacent pair had room. seatsCollide() asks the real question instead: do any two of these boxes want the same pixels. Two equal axis-aligned boxes overlap exactly when their centres are closer than one box in both axes, so it is an answer rather than a proxy, and it looks at every pair. The second cause was the small blind badge, sitting at `left: -6px` — hanging off its own puck's left corner onto whatever was beside it. On an iPhone Pro Max at six seats, with the pucks not touching at all, "SB" was painted across the next player's $1,137. All three badges now sit inside the puck's width, and in the same place as each other: a seat only ever wears one, so two positions bought nothing except making the dealer's eye find which of two spots this seat was using before it could read which blind it is. Then the layout had to be able to act on the answer, and it couldn't. It chose between two widths — 96px or 58px — and if neither fitted it widened the ring and let the seats overlap, on the reasoning that a dealer can see an overhang but cannot see a tap landing on the wrong player. That trade is real; it was also a false choice, because the tables that overlapped did not want either number. At nine seats a Galaxy S8 had room for a 61px puck, an iPhone 14 for 68, a Pixel 7 for 74, an iPhone Pro Max for 79. Three media queries declared three more of these numbers — 78px, 64px, 62px — at three breakpoints, guessing at something a media query cannot see: the same 390px phone has room for a 90px puck at four seats and 61 at nine. So the width is searched for now, not chosen. render.js walks candidates from the top, applies each one, measures what the browser actually drew, and takes the widest that leaves every seat its own box. There was a width that worked on every table in the sweep; the code was never asking for it. Ten candidates is ten forced layouts, so the result is cached against the felt's size, the seat count and the names — measured 12.5ms cold, 0.8ms warm, and renderSeats runs after every tap. Three things this got wrong on the way, all caught by measuring rather than reading: The badge overhang was folded into the space between two seats. That collapsed the ring on a phone held sideways — 87px of height, and 18px of badge is most of the room the ring has to move in — until a six-handed landscape table was unsatisfiable at any width. It didn't belong there: the badge points *away* from the table at both ends, so two seats facing each other across the felt wear them back to back. box-sizing: border-box, which the puck needed so the searched number means the width it is drawn at, quietly turned every min-height from a content height into a drawn one. An empty chair went from a 52px tap target to a 42px one on every phone in the sweep. The audit found it; the four min-heights have each gained the padding and border they used to be quoted without. The new audit check reported a name under a neighbour on a table whose seats were 9px apart. A puck's name is a `-webkit-box` with `line-clamp`, and getClientRects() hands back a rect for every line the text *would* have taken, including the ones the clamp throws away — so the probe was hit-testing 30px below the puck, where nothing is painted. It clips to what is painted now. uiAudit gains "no seat covers another seat's writing", the complement of the check already there for a puck's own decoration on its own name — which is why nothing caught this: seatLabelsClear() only ever looked inside one seat. Fault-injected against the previous version: eighteen configurations, naming both causes. And the sweep itself now runs two table shapes. It had run one for its whole life — five players at a nine-handed table — and how crowded the felt is depends on how many people are sitting down far more than on anything a viewport can report. A full table was never once looked at. 632 tests, 0 failures. 190 audits across thirteen viewports at two table shapes, with a mouse and with a thumb. Every table from two seats to twelve now keeps its own text on every viewport in the sweep. One configuration still reports: a dozen players on a 640x360 phone, where the felt is 87px tall and two rows of 55px pucks cannot both fit and stay on the rail — the ring grows 8px past the edge to keep the seats off each other, which is the trade the old code named and got right. Left reported rather than tuned away. Names on a crowded phone table truncate harder than they did, because the puck is now sized to fit instead of allowed to overlap: "Christopher" is "Christ…" at nine seats on an iPhone 14. The full name is in the DOM, in the seat's form and in the sidebar. The next lever on that is the seat layer's inset, which is still a fixed 44px of "half a puck" for a puck that is no longer 96px wide — worth 54px to 60px where it was measured. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
c052ee32c8 |
Put the Confirm button back on screen
Opened the pot wizard — the one dialog in this tool that divides money — and its Confirm button was below a scroll. Not on a tall table, not on one screen: at every size I measured, with six players and a side pot. iPhone 14 390x844 244px of the dialog below the fold Galaxy S8 360x640 417px 844x390 landscape 583px desktop 1280x900 172px Nothing on screen said there was anything under the list of names, so a dealer with the table waiting had to scroll a list of players to find the only control that finishes the hand. Two goes at this, and the first was wrong. A sticky footer is the obvious answer and it cannot work here: the actions were the last child of the scrolling content, so their containing block ended exactly where they did and `bottom` had no room to lift them. Sticky on the *header* did work, for the mirror-image reason — its container extends below it — which is what made the failure confusing to read. Capping the player list wasn't enough either; the pot breakdown and the log still overflowed on a short screen. So the dialog is a proper column now: a title that doesn't move, a body that scrolls, and a footer outside the scroll. potWizard.js appends its buttons to the footer instead of into the body, and clears both together so a step can't inherit the previous step's buttons. Measured after: Confirm on screen and hit-testable at all six configurations, twelve-handed on a Galaxy S8 included, and the dialog itself no longer scrolls at all. The checkboxes are painted now, too. `accent-color` only ever coloured the tick — the box around it stayed the platform's own bright white square, which on a near-black dialog was the loudest thing on screen and the last raw control in a tool where everything else had been drawn. Same surface ramp, same corner radius, and a tick drawn with clip-path so it can't pick up a substituted font. uiAudit gains "an open dialog's buttons are on screen", which hit-tests rather than trusting geometry — a button can be inside the viewport and still be under something. Fault-injected: putting the footer back inside the scrolling body reports Confirm 105px below the fold. Two things checked and left alone, because they turned out to be right: the standings table hides three columns on narrow screens through a container query keyed to the sidebar rather than the viewport, which is why a desktop shows fewer columns than an iPad — the sidebar is the narrower box there. And In/Won/ Net read $0 for hands settled outside the wizard, which the comment above them already explains. Caught by my own guard on the way past: the token test read `scroll-padding` as `padding` and failed the spacing scale. Correct by its rules, wrong in substance — those values are a sticky bar's measured height, not a step in the rhythm — so the lookbehind now rejects a hyphen as well as a word character. Re-fault- injected to confirm it still catches a real ad-hoc gap. 619 tests, 0 failures. 65 layout audits across 13 viewports x 5 table sizes and 35 across the five overlays. The whole wizard walked end to end: side pot resolved, hand logged, $1,560 on the table before and after. 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 |