fix: the controls sit below the log, and ending a session greys the page
Some checks failed
ci / check (push) Failing after 4s

Tier S (fixes inside a boundary; chaos d8=3, no override). Two
observations from play, both about what the page says has happened.

The controls moved BELOW the log: you read what happened, then decide what
to do next. They were above it, which asks for the decision first.

And sealing now marks the WHOLE PAGE inert, not just the controls. A
greyed-out button beside a full-colour table still reads as a live game
with one broken control; the session has ended and everything on screen is
a record of it. The status line stays legible on purpose -- it is the one
thing still worth reading.

THE STUB NEEDED A BODY classList TO MAKE THAT TESTABLE. Without it "the
session visibly ended" would have been a claim about CSS with nothing
checking it, which is precisely CB-WP-0016's finding: a stub too thin to
express a failure is how the failure survives. The harness reports the
sealed page through the status channel with a NUL-separated marker --
ugly, deliberate, and documented, because widening the return type would
touch every caller for one boolean.

Both directions asserted: a `closed` reply seals the page, an `ok:
dealing` reply does NOT -- otherwise the seal test would pass for a page
that greys itself whenever it is touched, breaking `play again`.

63 render tests, 26 cb-play, check and loop-lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-06 22:38:32 +02:00
parent b12725566b
commit 51efe55d14
5 changed files with 224 additions and 3 deletions

View file

@ -92,6 +92,13 @@ pub const SCRIPT: &str = r#"
// No game vocabulary here (ADR-0007 D5). 'closed' is a server
// lifecycle word, exactly like the 'ok' branch below it.
function seal() {
// The WHOLE page goes inert, not only the controls. A greyed-out
// button beside a full-colour table still reads as a live game with
// one broken control; the session has ended and everything on screen
// is now a record of it.
if (document.body && document.body.classList) {
document.body.classList.add('sealed-page');
}
var all = document.querySelectorAll('[data-drop]');
for (var i = 0; i < all.length; i++) {
all[i].classList.add('sealed');
@ -264,6 +271,11 @@ h1,h2{font-size:1rem;margin:1.2rem 0 .4rem;color:#9cf}
styling alone would leave a dead control that still looks alive to
anything reading the DOM. */
.sealed{opacity:.3;cursor:default;pointer-events:none;filter:grayscale(1)}
/* The session has ended. Everything on screen is a record of a game that
is over, so the whole page says so -- not one greyed button beside a
live-looking table. */
.sealed-page{filter:grayscale(.9);opacity:.5;pointer-events:none;transition:opacity .2s}
.sealed-page #cb-status{filter:none;opacity:1;color:#fc9}
/* CB-WP-0027 T02. `minmax(0,...)` on both tracks, because a grid child
defaults to min-content width and the SVG table would refuse to shrink,
pushing the meta column off-screen instead of narrowing.
@ -1341,7 +1353,9 @@ pub fn ending(
s.push_str("</div>");
}
// Observations 6 and 7: the controls and the full log belong beside
// the result, not under it.
// the result, not under it. And the controls go BELOW the log — you
// read what happened, then decide what to do next.
log_section(&mut s, log);
let _ = write!(
s,
"<div class=\"row\">\
@ -1349,7 +1363,6 @@ pub fn ending(
<div class=\"card btn tap\" data-drop=\"done\">end session \u{2014} stops the game server</div>\
</div>",
);
log_section(&mut s, log);
s.push_str("</div></div>");
let _ = write!(
s,