CB-WP-0035: a session that answers nothing
Some checks failed
ci / check (push) Failing after 4s

Two reports, one cause: the script's fetch had no .catch. When the server
had exited the promise rejected, the chain never ran, and not even the
status line moved — so a dead server and a click that did nothing were
indistinguishable. play again read as a dead button, and the linger
timeout was invisible.

Now a rejection says the session is gone and seals the page, and a
5-second heartbeat against a new /alive notices it without needing a
click, which is what the timeout case requires. The beat carries the
token, and does not extend the linger: that deadline is absolute.

The harness had the same hole. jsrun's fetch stub had no .catch, so the
branch that notices a dead server would have been unreachable in every
test — the very defect the stub's own comment records from CB-WP-0024.
Teaching it __failing, .catch and a recorded setInterval was the fix;
writing the script defensively would have repeated the trap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-07 20:31:32 +02:00
parent c2a7e40b67
commit 82aead6cea
9 changed files with 448 additions and 7 deletions

View file

@ -351,6 +351,7 @@ impl Server {
cb_render_html::Endpoints {
command: &self.guard.endpoint(),
note: &self.guard.note_endpoint(),
alive: &self.guard.alive_endpoint(),
},
Some(seat),
may_pass,
@ -365,6 +366,10 @@ impl Server {
);
respond(&mut stream, 200, "text/html; charset=utf-8", &page);
}
// CB-WP-0035. Cheap proof the session is still here. It
// advances nothing and decides nothing -- the loop keeps
// waiting for the seat's actual move.
("GET", "/alive") => respond(&mut stream, 200, "text/plain", "here"),
("POST", "/command") => {
let fact = match PointerFact::parse(&req.body) {
Ok(f) => f,
@ -490,6 +495,7 @@ impl Server {
message,
&self.guard.endpoint(),
note_to.as_deref(),
&self.guard.alive_endpoint(),
cb_render_html::doc::Account {
lines: &self.log_lines(),
notes: &self.log_notes(),
@ -525,6 +531,10 @@ impl Server {
Err(why) => respond(&mut stream, 400, "text/plain", &why),
}
}
// CB-WP-0035. Answered here too, and it does NOT extend
// the linger: the deadline is absolute, so a tab left
// open still cannot hold the process forever.
("GET", "/alive") => respond(&mut stream, 200, "text/plain", "here"),
("POST", "/command") => {
// CB-WP-0020 T05: the browser could not start a second
// game without going back to a terminal, which made it
@ -1049,6 +1059,7 @@ mod tests {
"it broke",
"/c",
None,
"/alive?t=x",
cb_render_html::doc::Account::of(&[]),
&[],
);
@ -1266,6 +1277,7 @@ mod tests {
"P1 ran out of input",
"/command?t=x",
None,
"/alive?t=x",
cb_render_html::doc::Account::of(&[]),
&[],
);