diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 1c66020..9301b8c 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -41,6 +41,7 @@ | workplan | CB-WP-0031 | done | — | workplans/CB-WP-0031-the-comment-box-outlives-the-game.md | | workplan | CB-WP-0032 | done | — | workplans/CB-WP-0032-the-comments-in-the-account.md | | workplan | CB-WP-0033 | done | — | workplans/CB-WP-0033-a-game-is-the-unit.md | +| workplan | CB-WP-0034 | done | — | workplans/CB-WP-0034-who-you-are-bonding-with.md | | task | CB-WP-0001-T01 | done | — | workplans/CB-WP-0001-inner-loop.md | | task | CB-WP-0001-T02 | done | — | workplans/CB-WP-0001-inner-loop.md | | task | CB-WP-0001-T03 | done | — | workplans/CB-WP-0001-inner-loop.md | @@ -205,3 +206,4 @@ | task | CB-WP-0031-T01 | done | — | workplans/CB-WP-0031-the-comment-box-outlives-the-game.md | | task | CB-WP-0032-T01 | done | — | workplans/CB-WP-0032-the-comments-in-the-account.md | | task | CB-WP-0033-T01 | done | — | workplans/CB-WP-0033-a-game-is-the-unit.md | +| task | CB-WP-0034-T01 | done | — | workplans/CB-WP-0034-who-you-are-bonding-with.md | diff --git a/crates/cb-render-html/src/doc.rs b/crates/cb-render-html/src/doc.rs index 85a85f4..0212411 100644 --- a/crates/cb-render-html/src/doc.rs +++ b/crates/cb-render-html/src/doc.rs @@ -91,7 +91,9 @@ 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. + var sealed = false; function seal() { + sealed = true; // 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 @@ -206,8 +208,33 @@ pub const SCRIPT: &str = r#" status(t); if (t.indexOf('ok') === 0) { window.location.reload(); } else if (t.indexOf('closed') === 0) { seal(); } - }); + }).catch(gone); }); + + // CB-WP-0035. The session ended without this page being told. + // + // There was no rejection handler at all, so when the server had exited + // the promise rejected and the chain simply never ran -- not even the + // status line moved. `play again` looked like a dead button, and the + // linger timeout was invisible. A request that cannot be answered is + // information, and it was being thrown away. + function gone() { + status('the session is no longer available \u2014 the game server has ' + + 'stopped. Nothing on this page can act; it is a record now.'); + seal(); + } + + // And notice it WITHOUT being clicked, which is what the timeout needs: + // a player who walks away comes back to a sealed page rather than to a + // live-looking table that answers nothing. + if (window.CB_ALIVE) { + setInterval(function () { + if (sealed) { return; } + fetch(window.CB_ALIVE).then(function (r) { + if (!r.ok) { gone(); } + }).catch(gone); + }, 5000); + } })(); "#; @@ -949,6 +976,7 @@ pub fn document( Endpoints { command: endpoint, note: "/note", + alive: "/alive", }, seat, may_pass, @@ -969,6 +997,8 @@ pub struct Endpoints<'a> { pub command: &'a str, /// Free text. Nothing turns these into commands. pub note: &'a str, + /// Where the page checks the session is still there (CB-WP-0035). + pub alive: &'a str, } pub fn document_with_log( @@ -980,7 +1010,7 @@ pub fn document_with_log( log: Account<'_>, meta: &[String], ) -> String { - let (endpoint, note_to) = (to.command, to.note); + let (endpoint, note_to, alive) = (to.command, to.note, to.alive); let mut s = String::with_capacity(8192); let _ = write!( s, @@ -1019,9 +1049,10 @@ pub fn document_with_log( let _ = write!( s, "