Spend the accent on the one thing to do next, not on two at once

The green is a promise: this is the step. Measured over a whole hand — seventeen
steps — it was on two controls at thirteen of them, the bar's Call and the header's
Resolve Pot painting the identical gradient at opposite ends of a phone, and on the
header alone at the other four while the bar held the only button there was to press.
At showdown the most consequential tap in the hand, "Showdown — resolve pot", was
blue-grey while the corner shortcut was green.

So the accent moves to .actionAdvance, which is always the next step by definition,
and comes off the header shortcut, which never is. Twenty-two measured steps of a
real hand now show exactly one.

The sweep checks it: oneNextStep() counts the accent-coloured controls that are
actually on screen, scoped to an open sheet when there is one. Six broken versions
were run at it, and the two that took three goes to catch were both the same mistake
in the injection rather than a hole in the check — a button painted green with the
panel shut isn't rendered, and one painted green below the fold is deliberately
ignored. Scrolled into view it is named alongside the bar's Call, and goes clean
again when the green comes off.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-09-09 04:10:53 -06:00
co-authored by Claude Opus 5
parent 14fb68dc04
commit e90bcccada
3 changed files with 98 additions and 4 deletions
+72
View File
@@ -1018,6 +1018,77 @@ const LayoutAudit = {
return { name: "nothing on the felt is painted for nothing", offenders };
},
// One green, and it is the thing to do next.
//
// The accent is a promise: this is the step. It only works while it is spent on one
// control at a time, and it was being spent on two. Measured over a whole hand,
// seventeen steps, before this went in: two accent controls at thirteen of them —
// the bar's Call and the header's Resolve Pot, the identical gradient at opposite
// ends of a phone — and at the other four the header alone, while the bar held the
// only button there was to press. At showdown the bar's own "Showdown — resolve
// pot" was blue-grey and the corner shortcut was green.
//
// A modal is its own view, so while one is open only what is inside it is judged —
// a wizard's confirm is the primary action of the wizard, and the bar underneath it
// is not on screen in any sense that matters. That is the same scoping rule the
// occlusion check uses, and it comes from the same helper.
//
// Zero is not a failure. Between hands with nobody seated there is genuinely no next
// step, and inventing one to keep a colour company would be worse than the silence.
//
// Six broken versions were run at it and six were caught, but two of them only after
// the injection itself was fixed, and both failures were the same mistake: painting
// a control green is not the same as putting a green control in front of anybody.
// The first attempt turned every sidebar button green with the panels shut, so the
// rule landed on nothing that was rendered — `getComputedStyle` reported the accent
// gradient and `offsetParent` was still null. The second opened the panels first,
// which rendered the button and left it four hundred pixels below the fold, where
// this check is meant to ignore it. Scrolled into view, the same green "Add" is
// named in the same breath as the bar's Call, and taking the green off goes clean
// again. So: read back the computed paint AND the box, on both counts, before
// believing a clean result means the check is looking.
ACCENT_FILL: [22, 120, 58],
ACCENT_TOLERANCE: 24,
wearsTheAccent(el) {
const cs = getComputedStyle(el);
const paint = `${cs.backgroundColor} ${cs.backgroundImage}`;
return [...paint.matchAll(/rgba?\(([^)]+)\)/g)]
.map(m => m[1].split(",").map(Number))
.some(([r, g, b, a]) => (a === undefined || a > 0.5) &&
Math.abs(r - this.ACCENT_FILL[0]) <= this.ACCENT_TOLERANCE &&
Math.abs(g - this.ACCENT_FILL[1]) <= this.ACCENT_TOLERANCE &&
Math.abs(b - this.ACCENT_FILL[2]) <= this.ACCENT_TOLERANCE);
},
oneNextStep() {
const scope = this.modalScope();
const root = scope || document;
const onScreen = el => {
const r = el.getBoundingClientRect();
if (!r.width || !r.height) return false;
const cs = getComputedStyle(el);
if (cs.visibility === "hidden" || +cs.opacity === 0) return false;
return r.bottom > 0 && r.top < innerHeight && r.right > 0 && r.left < innerWidth;
};
const green = [...root.querySelectorAll("button, a.holdemBtn")]
.filter(el => el.offsetParent !== null && onScreen(el) && this.wearsTheAccent(el));
if (green.length <= 1) {
return { name: "one green, and it is the next step", offenders: [] };
}
return {
name: "one green, and it is the next step",
offenders: [`${green.length} controls wear the accent at once` +
`${scope ? " inside the open sheet" : ""}: ` +
green.map(el => `"${(el.textContent || "").trim().slice(0, 24)}" in ` +
`${el.closest("#holdemHeader") ? "the header"
: el.closest("#actionBar") ? "the action bar"
: el.closest("#holdemSidebar") ? "the sidebar" : "the page"}`).join(", ")],
};
},
// The status a shut sidebar panel wears, and the three ways it can go wrong.
//
// Each of the eight <details> in the sidebar now carries the one fact you would
@@ -1428,6 +1499,7 @@ const LayoutAudit = {
this.oneDealerButton(),
this.hiddenButPainted(),
this.emptyDecoration(),
this.oneNextStep(),
this.glancesFitTheirRows(),
this.seatLabelsClear(),
this.seatsKeepTheirText(),