Put the two numbers the panel glances at above the prose

Table Setup is the one panel the tool opens by itself, on a table nobody
has set up yet, so it is most of what a dealer sees the first time they
arrive. Measured on a fresh table: the panel stood 886px on a 667px
phone, and the whole page 2076.

The order was the problem more than the length. The two figures the
panel's own summary glances at — "10 seats · $1/$2" — were the seat count
at the top and the blinds 218px further down, under three switches and
the three paragraphs explaining them: a dedicated dealer, a card sheet at
every street, and whether the phone is allowed to sleep. A dealer setting
up a $1/$2 game read all of that on the way to the two boxes they came
for.

The three have gone into "While you deal", with the paragraphs. That is
not a new idea in this panel — House take and Without live betting were
already nested disclosures — it is the same idea applied to the things
that were not about money. Afterwards the panel is 529px, the page 1719
on the same phone and 1275 on a 1280x900 desktop, and the first two rows
are the seats and the blinds.

A switch nobody can see is a switch nobody remembers turning on, so the
disclosure carries a glance of its own: "Asking for cards", "House
dealer", or "Screen may sleep" when one of the three is away from where
it ships, and silence when none of them is. One of them, not a list:
joined they run to 49 characters into a row 44px tall with a label
already on it, and "House dealer · Asking for c…" has told nobody
anything.

1291 tests pass; the sweep is clean at 428 audits across 13 viewports.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-09-14 01:31:38 -06:00
co-authored by Claude Opus 5
parent e9ec4afafe
commit 3458a6d144
6 changed files with 297 additions and 32 deletions
+2 -2
View File
@@ -175,8 +175,8 @@ describe("the rows that stay quiet until they have news", () => {
});
describe("what the row is allowed to be", () => {
const ROWS = ["glanceTableSetup", "glanceStandings", "glanceWaiting",
"glanceHands", "glanceChips", "glanceNotes"];
const ROWS = ["glanceTableSetup", "glanceWhileYouDeal", "glanceStandings",
"glanceWaiting", "glanceHands", "glanceChips", "glanceNotes"];
test("every glance the sidebar renders is one this knows about", () => {
// The list drives the DOM writes, so a glance computed and never listed is a
+210
View File
@@ -0,0 +1,210 @@
// What Table Setup holds while it is open.
//
// It is the one panel the tool opens by itself, on a table nobody has set up yet — so
// it is most of what a dealer sees the first time they arrive. Measured on a fresh
// table, before this: the panel stood 886px on a 667px phone, and the whole page 2076.
//
// The order was the problem more than the length. The two figures the panel's own
// summary glances at — "10 seats · $1/$2" — were the seat count at the top and the
// blinds 218px further down, under three switches and three paragraphs explaining
// them: a dedicated dealer, a card sheet at every street, and whether the phone is
// allowed to sleep. A dealer setting up a $1/$2 game read all of that on the way to
// the two boxes they came for.
//
// The three have gone into "While you deal", with the paragraphs that explain them.
// That is not a new idea in this panel — House take and Without live betting were
// already nested disclosures — it is the same idea applied to the things that were not
// about money. Afterwards: the panel is 529px, the page 1719 on the same phone and
// 1275 on a 1280x900 desktop, and the first two rows are the seats and the blinds.
//
// A switch nobody can see is a switch nobody remembers turning on, so the disclosure
// carries a glance of its own.
const test = require("node:test");
const { describe } = require("node:test");
const assert = require("node:assert");
const fs = require("fs");
const path = require("path");
const { Render, makeTable } = require("./load");
const ROOT = path.join(__dirname, "..", "..", "JoshHeaps.Net");
const PAGE = fs.readFileSync(path.join(ROOT, "Pages", "Casino", "TexasHoldem.cshtml"), "utf8");
const CSS = fs.readFileSync(path.join(ROOT, "wwwroot", "css", "casino", "holdem.css"), "utf8");
const MAIN = fs.readFileSync(path.join(ROOT, "wwwroot", "js", "casino", "holdem", "main.js"), "utf8");
// The Table Setup panel, from its opening tag to the panel after it.
const setupPanel = () => {
const at = PAGE.indexOf('<details class="sidebarPanel" id="tableSetupPanel">');
assert.ok(at >= 0, "there is no Table Setup panel");
const end = PAGE.indexOf('<details class="sidebarPanel"', at + 1);
assert.ok(end > at, "the panel after Table Setup has gone");
return PAGE.slice(at, end);
};
// What the panel shows without anything being opened: everything before the first
// nested disclosure.
const alwaysShowing = () => {
const panel = setupPanel();
const firstSub = panel.indexOf('<details class="subPanel">');
assert.ok(firstSub >= 0, "the panel has no nested disclosures at all");
return panel.slice(0, firstSub);
};
const ruleFor = selector => {
const at = CSS.indexOf(`\n${selector} {`);
assert.ok(at >= 0, `there is no \`${selector}\` rule`);
return CSS.slice(at, CSS.indexOf("}", at));
};
const glance = state => (Render.panelGlances(state).glanceWhileYouDeal || {}).text;
const table = () => makeTable({ seatCount: 9, seated: [], stack: 0 });
// ---------------------------------------------------------------------------
describe("what the open panel leads with", () => {
// The two the summary already glances at. They were first and seventh.
test("the seats and the blinds, in that order, above everything else", () => {
const body = alwaysShowing();
const seats = body.indexOf('id="seatCountInput"');
const small = body.indexOf('id="smallBlindInput"');
const big = body.indexOf('id="bigBlindInput"');
const stack = body.indexOf('id="defaultStackInput"');
assert.ok(seats >= 0 && small >= 0 && big >= 0 && stack >= 0,
"one of the four numbers is no longer in the part of the panel that shows");
assert.ok(seats < small, "the seat count is below the blinds");
assert.ok(small < big && big < stack, "the blinds and the stack have swapped");
});
test("and nothing in it is a switch about how the tool behaves", () => {
const body = alwaysShowing();
["dedicatedDealerToggle", "trackBoardToggle", "keepScreenAwakeToggle"].forEach(id => {
assert.ok(!body.includes(id), `${id} is still on the panel's first screen`);
});
});
// One line, and it is about the boxes directly above it.
test("one paragraph survives, the short one", () => {
const hints = alwaysShowing().match(/<p class="fieldHint">/g) || [];
assert.equal(hints.length, 1, "the panel's first screen is back to being prose");
assert.match(alwaysShowing(), /Blinds post themselves each hand/);
});
});
describe("where the three switches went", () => {
const dealing = () => {
const panel = setupPanel();
const at = panel.indexOf("<summary>While you deal");
assert.ok(at >= 0, 'there is no "While you deal" disclosure');
return panel.slice(panel.lastIndexOf("<details", at), panel.indexOf("</details>", at));
};
test("all three of them, with the paragraphs that explain them", () => {
const body = dealing();
["dedicatedDealerToggle", "trackBoardToggle", "keepScreenAwakeToggle"].forEach(id => {
assert.ok(body.includes(id), `${id} did not come with the rest`);
});
assert.match(body, /no seat shows the dealer button/);
assert.match(body, /a sheet asks which cards came off the deck/);
assert.match(body, /Stops the phone dimming and locking/);
});
// The same shape House take and Without live betting already had. A disclosure that
// ships open is a disclosure that has saved nobody anything.
test("it is shut, like the two money ones beside it", () => {
const panel = setupPanel();
assert.ok(!/<details class="subPanel"[^>]*\bopen\b/.test(panel),
"a nested disclosure in this panel ships open");
});
test("and it sits above them, because it is not about money", () => {
const panel = setupPanel();
assert.ok(panel.indexOf("While you deal") < panel.indexOf("<summary>House take"));
assert.ok(panel.indexOf("<summary>House take") < panel.indexOf("<summary>Without live betting"));
});
});
describe("the glance on a shut disclosure", () => {
test("silence when all three are where they ship", () => {
const state = table();
assert.equal(glance(state), "");
});
test("and a name when one of them is not", () => {
const state = table();
state.trackBoard = true;
assert.equal(glance(state), "Asking for cards");
const other = table();
other.dedicatedDealer = true;
assert.equal(glance(other), "House dealer");
});
// The only one that ships on, so it is its absence that is worth saying.
test("the screen setting speaks by going quiet, not by being on", () => {
const on = table();
on.keepScreenAwake = true;
assert.equal(glance(on), "");
const off = table();
off.keepScreenAwake = false;
assert.equal(glance(off), "Screen may sleep");
});
// Joined, the three run to 49 characters, and this row is 44px of a 320px column
// with a label already on it. "House dealer · Asking for c…" has told nobody
// anything; the one most likely to be the surprise has.
test("one of them, never a list of three", () => {
const state = table();
state.trackBoard = true;
state.dedicatedDealer = true;
state.keepScreenAwake = false;
assert.equal(glance(state), "Asking for cards");
assert.ok(glance(state).length <= 24);
});
test("it is a row this sidebar knows how to write", () => {
assert.ok(Render.PANEL_GLANCES.includes("glanceWhileYouDeal"),
"the glance is computed and never written, so the row keeps its old text all night");
const panel = setupPanel();
const at = panel.indexOf('id="glanceWhileYouDeal"');
assert.ok(at >= 0, "there is nowhere to write it");
assert.match(panel.slice(panel.lastIndexOf("<span", at), at + 40), /class="panelGlance subPanelGlance"/);
assert.match(panel.slice(at, panel.indexOf(">", at)), /hidden/);
});
});
describe("what had to be true for it to hang there", () => {
// Without this the span anchors to whichever ancestor is positioned, which is the
// sidebar — the glance would appear at the top right of the whole column.
test("the summary it hangs off is a positioned box", () => {
assert.match(ruleFor(".subPanel > summary"), /position:\s*relative/);
});
// A footnote on a line that is itself a footnote. At the weight of "Off by $40" the
// eye stops on a row saying nothing has gone wrong.
test("and it is drawn a step quieter than the panel headings' own", () => {
const sub = ruleFor(".subPanelGlance");
assert.match(sub, /font-size:\s*var\(--fs-xs\)/);
assert.match(sub, /color:\s*var\(--text-dim\)/);
assert.match(ruleFor(".panelGlance"), /font-size:\s*var\(--fs-sm\)/);
});
});
describe("the panel still opens itself only when it should", () => {
test("on a table nobody has touched, and not on the fourth hand", () => {
const at = MAIN.indexOf('document.getElementById("tableSetupPanel")');
assert.ok(at >= 0, "nothing decides whether the panel starts open");
const block = MAIN.slice(at, at + 400);
assert.match(block, /Number\(state\.handNumber\) \|\| 1\) === 1/);
assert.match(block, /!state\.seats\.some\(seat => HoldemState\.seatInPlay\(seat\)\)/);
assert.match(block, /panel\.open = fresh/);
});
test("what was measured is written beside the decision", () => {
const at = MAIN.indexOf("Table Setup used to ship open");
assert.ok(at >= 0);
const note = MAIN.slice(at, at + 1200);
assert.match(note, /886px/);
assert.match(note, /529px/);
});
});