--- id: CB-WP-0031 kind: product title: "The comment box outlives the game" status: done state_hub_workstream_id: "7b7450cd-3972-4c78-a53c-fba54b3fc02b" --- # Purpose ``` structural tier S (one channel reopened on an existing page; no new decision, no new dependency, no budget moved) chaos d8 = 2 → no override declared tier S ``` **Declaration 2 of chaos window 3**, opened 2026-08-07 by ADR-0017. ## The report > *"Before we move on, i want to be able to add comments after the game > finished."* ## What was actually broken, and it was two things **The channel closed at the moment it is worth most.** A player who has just seen the outcome is the one with something to say — *"so that is why that failed"*, *"I never understood what that card did"* — and that is the reading the whole trial protocol (GameDesign §5) exists to collect. It was collectable at every moment of the game **except** the one after it. Two independent defects, either of which alone shuts the channel: | where | what | |---|---| | `doc::ending` | never rendered the comment box at all | | `hotseat::serve_end` | had no `POST /note` arm — a hand-built post fell through to `404 the game is over` | **Fixing either alone leaves it shut**, which is why the test asserts both and why a markup-only test would have passed against a server that still refused. ## The thing that needed deciding: what a post-game note is bound to A note is bound to a position by `state_hash` (ADR-0014 D3), and the position is genuinely the final state. But **`round` and `step` say *when the player said it***, and a note written after the outcome is on screen is not an observation made at the last decision point. `RoundStep` has an `End` variant — **the last step of a round, not the end of the game.** Logging a post-game note under it would file an after-the-fact reading as an in-play one: a value that is correct about the wrong subject, which is the family ADR-0018 was written for two hours earlier. So `record_note` takes the step **as an argument** rather than reading it off the state, and the post-game path passes `"after the end"` — which cannot collide with a `Debug` identifier. ## No box where there is nothing to bind to A game that stopped without a result has no final position. The box is **not offered** there, and the page says why. Offering one that would answer `409` is a control that exists to be hit. ## Task: reopen the channel ```task id: CB-WP-0031-T01 status: done priority: high state_hub_task_id: "6b92d470-b3a2-4cde-a307-d20cb6a45ab4" ``` **Controls:** - **the test fails on each half separately** — mutation-proven: blanking the form gives *"the ending page offered no comment box"*, removing the server arm gives `404`; both green when restored; - **a note must not end the session** — every other POST in `serve_end` breaks the loop, and a player must be able to write a second note and then still press `play again`. The test posts a command after the note and requires it to still work; - **the redirect carries the token**, which is the defect that twice made a *saved* note read as a failed one; - **the log distinguishes when it was written** from the position it binds to. **Done 2026-08-07.** `note_form` extracted and shared by both pages — a hand-written twin is how one of them quietly stops posting anywhere useful. The placeholder differs, and only that: what a player has to say mid-turn and what they have to say having seen the result are not the same prompt. **A test-harness defect worth recording.** The first draft wrote `Content-Length: 21` beside a 22-byte body; the server read 21 bytes and answered `400 unrecognised field`, which looked exactly like the feature being broken. The fix was to derive the length from the body in the shared `post` helper — **a hand-written length is a second copy of a fact the body already carries**, the same shape as the 62 untagged literals `facts-check` counts. ## Not done here - **Notes are still not shown back to the player.** Neither page echoes what has been written; the trial log is the only place they exist. That is a real gap and it is not this report.