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>
2.8 KiB
| id | kind | title | status |
|---|---|---|---|
| CB-WP-0035 | product | A session that answers nothing | done |
Purpose
structural tier S (one endpoint, one rejection handler; no rule, no
dependency, no artifact contract moved)
chaos d8 = 4 → no override
declared tier S
Declaration 6 of chaos window 3.
The report, and it is one defect not two
"I think the server timing out is not noticed by the client. And 'Play Again' does not report that the session is no longer available."
Both come from the same three characters. The script's request was
fetch(...).then(...).then(...) // and no .catch
so when the process had exited the promise rejected, the chain never
ran, and not even the status line moved. A dead server and a click that
did nothing were indistinguishable — which is why play again read as a
dead button and the linger timeout was invisible.
A request that cannot be answered is information, and it was being thrown away.
Task: notice, and say so
id: CB-WP-0035-T01
status: done
priority: high
Controls:
- both paths — pressing a control, and noticing without pressing anything, which is the timeout case;
- both pages, because the timeout matters most during play;
- the heartbeat carries the token, or a refusal every five seconds would seal a live session;
- mutation-proven: remove the
.catchand the status goes empty, which is the reported symptom exactly.
Done 2026-08-07. .catch(gone), plus a 5-second heartbeat against a
new /alive — its own path so a beat costs a few bytes rather than a
whole re-render. It does not extend the linger: that deadline is
absolute, so a tab left open still cannot hold the process open.
The harness had the same hole as the code. 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, where a then-chain that never invoked its
callbacks let "the page still looks live" survive. Teaching the stub
__failing, .catch and a recorded setInterval was the fix; writing
the script defensively instead would have repeated the trap.
The first cut wired the heartbeat into the ending page only. An unused
variable caught it — luck, not a control — so every_page_can_tell_the_ session_has_gone now asserts it on both.
Not done here
- Five seconds is a guess. Nothing measures how long a player tolerates a dead-looking page, and this pass did not find out.
- A brief network stall seals the page, the same as a dead server. For a loopback-only server that is nearly always the truth, but it is a false positive waiting for the first non-loopback deployment.