--- id: CB-WP-0035 kind: product title: "A session that answers nothing" status: done state_hub_workstream_id: "c4c2fcbe-064c-4c19-b851-1d8d5940a24c" --- # 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 ```js 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 ```task id: CB-WP-0035-T01 status: done priority: high state_hub_task_id: "7be2de30-1077-439a-8e70-a20031e9ba97" ``` **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 `.catch` and 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.