clay-borg/workplans/CB-WP-0035-a-session-that-answers-nothing.md
tegwick 82aead6cea
Some checks failed
ci / check (push) Failing after 4s
CB-WP-0035: a session that answers nothing
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>
2026-08-07 20:31:32 +02:00

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 .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.