[Frontend] Show players the same "Previously on" recap the GM already gets #388

Closed
opened 2026-08-25 20:42:25 +00:00 by claude-bot · 2 comments
Contributor

Impact: MEDIUM

Found in the August 2026 session lifecycle review (#319).

What the user experiences

The "Previously on" recap and open-loops panel is genuinely one of the best pieces of UX in the product — it renders exactly the recap a returning player wants — but it only renders on the GM's Session Shelf. Players get no equivalent anywhere: SessionTable, the player mirror of the shelf, only shows cards the GM has explicitly revealed during play, not a session recap.

Evidence

  • webapp/frontend/src/components/ColdOpenPanel.jsx:76-147 — the recap/open-loops component, already computing exactly what's needed.
  • webapp/frontend/src/pages/SessionShelf.jsx:1212 — the only place it renders, and SessionShelf is GM-only.
  • webapp/frontend/src/pages/SessionTable.jsx:93-98 — the player-facing mirror shows only explicitly-revealed cards, with no recap equivalent.

Why it matters for a hosted product

The data and the component already exist; withholding them from players is the one part of this finding that costs nothing to fix and directly serves the "find what happened" journey players are otherwise left to reconstruct from Discord scrollback.

Proposed fix

Render ColdOpenPanel on SessionTable for players, and at the top of a confirmed SessionDetail for anyone who hasn't yet seen it. This is the audit's M3.

Acceptance criteria

  • SessionTable renders the same cold-open recap panel the GM's Session Shelf already shows.
  • A confirmed SessionDetail shows the recap for players, not just GMs.
  • The recap content matches what the GM sees (same data source, no divergent second implementation).
**Impact: MEDIUM** Found in the August 2026 session lifecycle review (#319). ## What the user experiences The "Previously on" recap and open-loops panel is genuinely one of the best pieces of UX in the product — it renders exactly the recap a returning player wants — but it only renders on the GM's Session Shelf. Players get no equivalent anywhere: `SessionTable`, the player mirror of the shelf, only shows cards the GM has explicitly revealed during play, not a session recap. ## Evidence - `webapp/frontend/src/components/ColdOpenPanel.jsx:76-147` — the recap/open-loops component, already computing exactly what's needed. - `webapp/frontend/src/pages/SessionShelf.jsx:1212` — the only place it renders, and `SessionShelf` is GM-only. - `webapp/frontend/src/pages/SessionTable.jsx:93-98` — the player-facing mirror shows only explicitly-revealed cards, with no recap equivalent. ## Why it matters for a hosted product The data and the component already exist; withholding them from players is the one part of this finding that costs nothing to fix and directly serves the "find what happened" journey players are otherwise left to reconstruct from Discord scrollback. ## Proposed fix Render `ColdOpenPanel` on `SessionTable` for players, and at the top of a confirmed `SessionDetail` for anyone who hasn't yet seen it. This is the audit's M3. ## Acceptance criteria - [ ] `SessionTable` renders the same cold-open recap panel the GM's Session Shelf already shows. - [ ] A confirmed `SessionDetail` shows the recap for players, not just GMs. - [ ] The recap content matches what the GM sees (same data source, no divergent second implementation).
Author
Contributor

Picking this up as v4.3.0 phase 7 (#514), first on the last frontend lane (#388#389#382). The same ColdOpenPanel is mounted on the player table and near the top of a confirmed session page for every member, collapsible and remembered per session; the table fetches its session once so it also learns its campaign, which gives the nav bar its campaign links there (a gap left by #378). If the recap endpoint turns out to be GM-gated on the backend it will be widened to members, since it holds nothing players should not see.

Picking this up as v4.3.0 phase 7 (#514), first on the last frontend lane (#388 → #389 → #382). The same `ColdOpenPanel` is mounted on the player table and near the top of a confirmed session page for every member, collapsible and remembered per session; the table fetches its session once so it also learns its campaign, which gives the nav bar its campaign links there (a gap left by #378). If the recap endpoint turns out to be GM-gated on the backend it will be widened to members, since it holds nothing players should not see.
Author
Contributor

Done in the phase 7b PR (auto-merging on green); ships with v4.3.0.

GET /api/sessions/{id}/shelf/cold-open moves from the GM gate to the member gate, and SessionTable and SessionDetail mount the existing ColdOpenPanel; no second implementation.

One thing worth recording, because the issue (and the plan) assumed otherwise: the recap payload is not entirely player-safe as it stands. open_loops includes plot threads with player_visible: false, and that flag is an invariant enforced in three other places (reminder_tasks.py says GM-only threads "must never be included here"; routers/bot.py and routers/campaigns.py both filter on it). So the widening is a narrowing on the way out, done in the same service call rather than a second endpoint: previously_on is unchanged (the session summary is already member-readable via the session detail endpoint, so nothing new is exposed); open_loops is filtered to player_visible=True for non-GMs; carried_count is reported as 0, since it counts cards on the GM's private shelf. Non-members still get 403.

On the front end the panel gained a collapsible mode for the two player surfaces: a fold/unfold toggle on its own local-storage key rather than the shelf's permanent ×, because a player who puts the recap aside before the game wants it back during it, and because the GM's dismissal on the shelf must not silently swallow it on the session page. SessionTable also fetches its session for the campaign id and declares it, so the nav bar gains that campaign's links there (the #378 gap).

Tests: 4 new backend, the old GM-only test replaced; frontend covers both surfaces and the collapsible mode. Full suites green: backend 2504 passed / 13 skipped, frontend 787 passed.

Done in the phase 7b PR (auto-merging on green); ships with v4.3.0. `GET /api/sessions/{id}/shelf/cold-open` moves from the GM gate to the member gate, and `SessionTable` and `SessionDetail` mount the existing `ColdOpenPanel`; no second implementation. One thing worth recording, because the issue (and the plan) assumed otherwise: the recap payload is **not** entirely player-safe as it stands. `open_loops` includes plot threads with `player_visible: false`, and that flag is an invariant enforced in three other places (`reminder_tasks.py` says GM-only threads "must never be included here"; `routers/bot.py` and `routers/campaigns.py` both filter on it). So the widening is a narrowing on the way out, done in the same service call rather than a second endpoint: `previously_on` is unchanged (the session summary is already member-readable via the session detail endpoint, so nothing new is exposed); `open_loops` is filtered to `player_visible=True` for non-GMs; `carried_count` is reported as 0, since it counts cards on the GM's private shelf. Non-members still get 403. On the front end the panel gained a collapsible mode for the two player surfaces: a fold/unfold toggle on its own local-storage key rather than the shelf's permanent ×, because a player who puts the recap aside before the game wants it back during it, and because the GM's dismissal on the shelf must not silently swallow it on the session page. `SessionTable` also fetches its session for the campaign id and declares it, so the nav bar gains that campaign's links there (the #378 gap). Tests: 4 new backend, the old GM-only test replaced; frontend covers both surfaces and the collapsible mode. Full suites green: backend 2504 passed / 13 skipped, frontend 787 passed.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#388
No description provided.