Stop two empty rows in every seat puck setting the size of the table

A seat puck carries the name, the hole cards, the amount that player has put
in, and their stack. Two of those are empty most of the time and both held
their space anyway.

The amount pill was worse than empty space. It has a background, a pill radius
and side padding, so with nothing in it, it painted an 8x11px green lozenge —
measured on seven of nine seats at an ordinary table. One of those is a
component holding its place; nine is a rash, and a dealer has to learn to
ignore a colour that on the other two seats means "this player has money out".

The space was not free either. seatRingRadii sizes the ring from the tallest
puck on it, so two rows nobody had filled were setting how far apart the whole
table had to sit. Across 21 tables — seven viewports, six, nine and twelve
handed — every single puck comes back shorter and six come back wider: an
iPhone 14 nine-handed 78x88 -> 78x63, twelve-handed 44x65 -> 66x55, which is the
one name in the set that was clipped and now is not.

The collapse is only safe because the layout cache moved with it.
resolveSeatLayout measures ten candidate widths, each a forced layout, against
seatShapeSignature() — which listed the names and the dealer seat, everything
that could wrap a name onto a second line. It did not mention these two rows,
because while they always held their space they could never change a puck's
height. Now they can, so the signature names them: booleans over the whole
table, because measuredSeatBox takes the tallest puck on the ring, and because
a per-seat key would be correct and would throw the cache away on every tap.

The ring settles by 24-26px when the blinds go in — and it settled by 23-25px
there before this round too, since the action bar changes height at that moment
and the felt is measured against it. The one place it moves is a place it
already moved.

uiAudit gains emptyDecoration(): nothing on the felt may paint a background or
a border with no content in it. Its one exemption is on the list because the
check reported it rather than because it was predicted — .betChipDisc is a
circle filled with the denomination colour of the chip beside it, so the colour
is the content. Three others were written from memory first and matched no
element on the page at all.

The sweep is unchanged at 6 failing configurations and 63 offenders. All four
table failures are the all-in shape, where every pill is filled and the
collapse buys nothing; the ordinary tables this helps were already passing, so
what moved is room a name has rather than whether anything was covered.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-09-06 00:09:25 -06:00
co-authored by Claude Opus 5
parent 942919f3f7
commit f8ed4abc85
5 changed files with 284 additions and 1 deletions
+52
View File
@@ -967,6 +967,57 @@ const LayoutAudit = {
return { name: "hidden means hidden", offenders };
},
// A mark on the felt that stands for nothing.
//
// The amount pill inside every seat puck has a background, a pill radius and side
// padding, so a player who had not put chips in yet got an 8x11px green lozenge
// where their bet would go — measured on seven of nine seats at an ordinary table.
// One of those is a component holding its place; nine of them is a rash, and a
// dealer reading the felt has to learn to ignore a colour that on three other seats
// means "this player has money out".
//
// It cost room as well as attention. The ring is sized from the tallest puck on it,
// so a row nobody had filled was setting how far apart the seats had to sit.
//
// The rule is about paint, not about space: an element may hold its place invisibly,
// and several here do. What it may not do is draw a background or a border with
// nothing inside it.
//
// One real exemption, and it is on the list because the check reported it rather
// than because it was predicted: .betChipDisc is a 13x13px circle filled with the
// denomination colour of the chip beside it, published per chip from render.js. It
// has no text because the colour *is* the content. Anything carrying its own
// picture in a background-image is exempt on the same grounds, without needing to be
// named. Nothing else here needs an entry — three others were guessed at first and
// matched no element on the page, which is its own warning about writing this list
// from memory instead of from a run.
emptyDecoration() {
const DECORATIVE = new Set(["betChipDisc"]);
const opaque = value => value && value !== "none" && !/rgba\([^)]*,\s*0\s*\)/.test(value)
&& value !== "rgba(0, 0, 0, 0)" && value !== "transparent";
const offenders = [...document.querySelectorAll("#pokerTableWrap *")]
.filter(el => {
if (el.children.length || (el.textContent || "").trim()) return false;
if ([...el.classList].some(c => DECORATIVE.has(c))) return false;
const box = el.getBoundingClientRect();
if (box.width < 2 || box.height < 2) return false;
const cs = getComputedStyle(el);
if (cs.display === "none" || cs.visibility === "hidden" || Number(cs.opacity) === 0) return false;
if (opaque(cs.backgroundImage)) return false;
return opaque(cs.backgroundColor) ||
["Top", "Right", "Bottom", "Left"].some(side =>
parseFloat(cs[`border${side}Width`]) > 0 && opaque(cs[`border${side}Color`]));
})
.map(el => {
const box = el.getBoundingClientRect();
return `${this.describe(el)} :: nothing in it, painted ` +
`${Math.round(box.width)}x${Math.round(box.height)}px`;
});
return { name: "nothing on the felt is painted for nothing", offenders };
},
// A seat's name, painted under the decoration on its own puck.
//
// The chip that marks the button sits in the puck's top-right corner on a layer
@@ -1293,6 +1344,7 @@ const LayoutAudit = {
this.seatOwnership(),
this.oneDealerButton(),
this.hiddenButPainted(),
this.emptyDecoration(),
this.seatLabelsClear(),
this.seatsKeepTheirText(),
this.chipsKeepTheirAmounts(),