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
This commit is contained in:
@@ -163,6 +163,44 @@ describe("numbers that change while the dealer watches", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("what an open chair is allowed to weigh", () => {
|
||||
// An empty seat used to be 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 they read as holes punched in the felt rather than
|
||||
// as chairs nobody is in — 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 the box carried was there for one reason: the chair number's contrast
|
||||
// cannot depend on whether its chair happens to sit over the brown rim, the dark green
|
||||
// or the light, which is 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.
|
||||
|
||||
test("the chair is quieter than the player who could sit in it", () => {
|
||||
const empty = css.match(/\.seat\.seatEmpty \{([^}]*)\}/);
|
||||
const seat = css.match(/\n\.seat \{([^}]*)\}/);
|
||||
assert.ok(empty && seat);
|
||||
assert.match(seat[1], /background:\s*linear-gradient/,
|
||||
"a taken seat is a puck with a ground of its own");
|
||||
const alpha = empty[1].match(/background:\s*rgba\([^)]*?([0-9.]+)\s*\)/);
|
||||
assert.ok(alpha, "an open chair should still be washed, only faintly");
|
||||
assert.ok(Number(alpha[1]) <= 0.35,
|
||||
`an open chair at ${alpha[1]} reads as a hole in the felt, not a chair`);
|
||||
});
|
||||
|
||||
test("and it still says it is open", () => {
|
||||
const empty = css.match(/\.seat\.seatEmpty \{([^}]*)\}/)[1];
|
||||
assert.match(empty, /border-style:\s*dashed/,
|
||||
"the dashes are what carry the meaning; they always were");
|
||||
});
|
||||
|
||||
test("the number keeps a ground of its own, because it is the part that is read", () => {
|
||||
const rule = css.match(/\.seatEmpty \.seatChairNumber \{([^}]*)\}/);
|
||||
assert.ok(rule, "no ground under the one thing an open chair says");
|
||||
assert.match(rule[1], /background:\s*rgba/);
|
||||
assert.match(rule[1], /border-radius:\s*var\(--r-pill\)/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("controls that must not resize under a thumb", () => {
|
||||
test("the undo pill has a fixed width, not a content width", () => {
|
||||
// It names the last action, so its label changes on every tap. Measured across
|
||||
@@ -182,9 +220,25 @@ describe("controls that must not resize under a thumb", () => {
|
||||
"the pill should still state a fixed width somewhere");
|
||||
});
|
||||
|
||||
test("and its label is the part that ellipsizes", () => {
|
||||
test("and its label is the part that gives, never the keycap", () => {
|
||||
const rule = css.match(/\.actionUndoLabel\s*\{[^}]*\}/);
|
||||
assert.ok(rule, ".actionUndoLabel rule not found");
|
||||
assert.match(rule[0], /text-overflow:\s*ellipsis/);
|
||||
assert.match(rule[0], /overflow:\s*hidden/);
|
||||
assert.match(css.match(/\.actionUndoKey\s*\{[^}]*\}/)[0], /flex:\s*none/);
|
||||
});
|
||||
|
||||
test("and within the label, the act outlives the actor", () => {
|
||||
// Three items, not one string: the arrow, who it was, and what they did. Cut as
|
||||
// one string, "↶ Mike from work's fold" reads "↶ Mike from wor…" on a phone —
|
||||
// everything except the thing you would be taking back. The actor is dropped
|
||||
// whole instead, so the act is what survives, and only the act may ellipsize.
|
||||
const lead = css.match(/\.actionUndoGlyph,\s*\n\.actionUndoLead\s*\{([^}]*)\}/);
|
||||
assert.ok(lead, "the arrow and the actor should be fixed items");
|
||||
assert.match(lead[1], /flex:\s*none/);
|
||||
assert.match(css, /\.actionUndoLead\[hidden\]\s*\{[^}]*display:\s*none/);
|
||||
const act = css.match(/\.actionUndoAct\s*\{([^}]*)\}/);
|
||||
assert.ok(act, ".actionUndoAct rule not found");
|
||||
assert.match(act[1], /text-overflow:\s*ellipsis/);
|
||||
assert.match(act[1], /min-width:\s*0/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user