Shelf: warm cold-open — recap, open loops, carried cards (#248) #266

Merged
claude-bot merged 2 commits from feat/v3.11-warm-cold-open into main 2026-07-27 18:47:55 +00:00
Contributor

Closes #248. Last slice of the v3.11.0 milestone.

Session N+1 shouldn't start from a blank board. GET /api/sessions/{id}/shelf/cold-open returns the three things that make it start warm, and the shelf shows them as a strip above the board.

The third leg — carried cards — already worked via the carry-forward pins from #254; this adds the recap and the open loops, and surfaces all three together.

Everything is derived, nothing is materialized

No new table, no migration. The recap, the loop list, and the carried count are all computed on read, so the panel can't drift out of date if the GM resolves a thread or edits a summary between sessions. It also means the endpoint mutates nothing — opening the shelf a second time can't double-apply anything.

Two judgement calls worth flagging

The recap walks back in play order rather than taking "the previous row." A cancelled session, or one that was never summarised, has nothing to say — so the recap steps over it and reaches for the last session that actually does. A campaign with a called-off session last week still opens with the recap from the one before it, rather than with a blank.

A card pinned from this session doesn't count as carried. It originated tonight. Only cards whose pin started at an earlier session are counted, so the "N cards carried forward" line means what it says.

Frontend

ColdOpenPanel sits above the scene bar. Its own component rather than more weight in SessionShelf.jsx, which is already carrying the board, the stage, the palette, the sweep, and the reveal composer.

  • Dismissal is per session, in localStorage. Once play starts the GM wants the board, not the preamble, and reloading mid-session shouldn't bring it back — while the next session gets its own key and so shows again.
  • Silent on failure. The board is the point of the page; an error banner above a perfectly working shelf would be worse than simply not showing the preamble.
  • Renders nothing when there's nothing to say, so a first session looks exactly as it does today.
  • A truncated recap (>1200 chars) links through to the full session rather than dumping it inline.

Testing

All run locally in containers matching the CI jobs:

  • Backend: 798 passed; 8 new covering the empty first-session case, most-recent-summarised selection, the skip-cancelled-and-unsummarised walk-back, ignoring later sessions, truncation + flag, open-loop filtering by status, the carried count excluding tonight's own pins, and the GM-only boundary.
  • Frontend: 427 passed; 6 new covering the full panel, the nothing-to-say case, silent failure, the truncation link, dismissal persisting across a remount, and a different session showing again.
  • ruff check / ruff format --check clean; eslint clean (the one warning is pre-existing in CampaignDetail.jsx).

🤖 Generated with Claude Code

Closes #248. Last slice of the v3.11.0 milestone. Session N+1 shouldn't start from a blank board. `GET /api/sessions/{id}/shelf/cold-open` returns the three things that make it start warm, and the shelf shows them as a strip above the board. The third leg — carried cards — already worked via the carry-forward pins from #254; this adds the recap and the open loops, and surfaces all three together. ## Everything is derived, nothing is materialized No new table, **no migration**. The recap, the loop list, and the carried count are all computed on read, so the panel can't drift out of date if the GM resolves a thread or edits a summary between sessions. It also means the endpoint mutates nothing — opening the shelf a second time can't double-apply anything. ## Two judgement calls worth flagging **The recap walks back in play order rather than taking "the previous row."** A cancelled session, or one that was never summarised, has nothing to say — so the recap steps over it and reaches for the last session that actually does. A campaign with a called-off session last week still opens with the recap from the one before it, rather than with a blank. **A card pinned *from* this session doesn't count as carried.** It originated tonight. Only cards whose pin started at an earlier session are counted, so the "N cards carried forward" line means what it says. ## Frontend `ColdOpenPanel` sits above the scene bar. Its own component rather than more weight in `SessionShelf.jsx`, which is already carrying the board, the stage, the palette, the sweep, and the reveal composer. - **Dismissal is per session, in localStorage.** Once play starts the GM wants the board, not the preamble, and reloading mid-session shouldn't bring it back — while the next session gets its own key and so shows again. - **Silent on failure.** The board is the point of the page; an error banner above a perfectly working shelf would be worse than simply not showing the preamble. - **Renders nothing when there's nothing to say**, so a first session looks exactly as it does today. - A truncated recap (>1200 chars) links through to the full session rather than dumping it inline. ## Testing All run locally in containers matching the CI jobs: - Backend: **798 passed**; 8 new covering the empty first-session case, most-recent-summarised selection, the skip-cancelled-and-unsummarised walk-back, ignoring later sessions, truncation + flag, open-loop filtering by status, the carried count excluding tonight's own pins, and the GM-only boundary. - Frontend: **427 passed**; 6 new covering the full panel, the nothing-to-say case, silent failure, the truncation link, dismissal persisting across a remount, and a different session showing again. - `ruff check` / `ruff format --check` clean; `eslint` clean (the one warning is pre-existing in `CampaignDetail.jsx`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Session N+1 shouldn't start from a blank board. `GET …/shelf/cold-open` returns
the three things that make it start warm: the previous session's recap, the
loops still open, and how many cards rode in from earlier.

All three are derived on read rather than materialized, so the panel can't drift
out of date if the GM resolves a thread or edits a summary between sessions —
and there is no new table and no migration.

Two judgement calls worth naming:

- The recap walks back in play order rather than taking "the previous row". A
  cancelled session, or one that was never summarised, has nothing to say, so
  the recap steps over it and reaches for the last session that does.
- A card pinned *from* this session doesn't count as carried — it originated
  tonight. Only cards whose pin started at an earlier session are counted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
feat(frontend): cold-open panel at the top of the shelf (#248)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 28s
CI / Docker image build (pull_request) Successful in 24s
CI / Frontend tests, audit, and build (pull_request) Successful in 1m40s
CI / Bot tests and audit (pull_request) Successful in 1m56s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m31s
fe3fb92d5c
Shows the previous session's recap, the still-open loops, and the carried-card
count above the board, so the GM opens the shelf already oriented.

Dismissal is remembered per session in localStorage: once play starts the GM
wants the board, not the preamble, and reloading mid-session shouldn't bring the
panel back — while the next session gets its own key and so shows again.

The fetch is best-effort and silent on failure. The board is the point of the
page, and an error banner above a perfectly working shelf would be worse than
simply not showing the preamble. The panel also renders nothing at all when
there's nothing to say, so a first session looks exactly as it does today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch feat/v3.11-warm-cold-open 2026-07-27 18:47:56 +00:00
Sign in to join this conversation.
No description provided.