feat: Session Reference Shelf MVP — model, board, add-to-shelf (#239, #240, #241) #249

Merged
claude-bot merged 7 commits from feat/v3.11-session-shelf into main 2026-07-22 05:26:36 +00:00
Contributor

Session Reference Shelf — v3.11 MVP

The first slice of the Session Reference Shelf epic (#235): a per-session surface a GM keeps open during play, holding generated content and pinned wiki entries as freeform, draggable cards.

What's in this PR

#239 — Data model + API

  • ShelfCard model + migration 5a6b7c8d9e0f (shelf_cards table). Each card belongs to a session, references a GenerationResult or a LoreEntry via SET NULL FKs (cards survive retention pruning), and carries a self-sufficient snapshot so it renders even if the source is gone. position (JSONB), tonight_notes, scene, and a card_kind CHECK (generation/lore/note).
  • GM-only CRUD API at /api/sessions/{id}/shelf via the get_session_for_gm dependency (a non-GM gets 403). Cross-campaign sources are rejected (422).
  • 9 endpoint tests (add generation/lore card, list, update, delete, GM-only 403, 404s, 422s).

#240 — Board surface

  • /sessions/:id/shelf: cards laid out freeform (never auto-reflowed), pointer-drag to reposition (persisted best-effort), inline tonight-notes, remove, and an empty state. Linked from SessionDetail (GM only).

#241 — Add to shelf

  • Shared AddToShelf control: a session picker (defaulting to the campaign's in-progress, else next upcoming, session) + an "Add to shelf" button.
  • Rendered under every GM Workbench generation result (card_kind=generation) and in the wiki entry's GM action row (card_kind=lore). Renders nothing when the campaign has no upcoming session to pin to.

Notes

  • Migration head advances to 5a6b7c8d9e0f.
  • Follow-on slices (glance cards, spotlight, scene packs, reveal-to-table, fast retrieval, end-of-session sweep, warm cold-open) tracked under #235 and remain out of scope here.

Closes #239, closes #240, closes #241. Part of #235.

🤖 Generated with Claude Code

## Session Reference Shelf — v3.11 MVP The first slice of the Session Reference Shelf epic (#235): a per-session surface a GM keeps open during play, holding generated content and pinned wiki entries as freeform, draggable cards. ### What's in this PR **#239 — Data model + API** - `ShelfCard` model + migration `5a6b7c8d9e0f` (`shelf_cards` table). Each card belongs to a session, references a `GenerationResult` **or** a `LoreEntry` via `SET NULL` FKs (cards survive retention pruning), and carries a self-sufficient `snapshot` so it renders even if the source is gone. `position` (JSONB), `tonight_notes`, `scene`, and a `card_kind` CHECK (`generation`/`lore`/`note`). - GM-only CRUD API at `/api/sessions/{id}/shelf` via the `get_session_for_gm` dependency (a non-GM gets 403). Cross-campaign sources are rejected (422). - 9 endpoint tests (add generation/lore card, list, update, delete, GM-only 403, 404s, 422s). **#240 — Board surface** - `/sessions/:id/shelf`: cards laid out freeform (never auto-reflowed), pointer-drag to reposition (persisted best-effort), inline tonight-notes, remove, and an empty state. Linked from SessionDetail (GM only). **#241 — Add to shelf** - Shared `AddToShelf` control: a session picker (defaulting to the campaign's in-progress, else next upcoming, session) + an "Add to shelf" button. - Rendered under every GM Workbench generation result (`card_kind=generation`) and in the wiki entry's GM action row (`card_kind=lore`). Renders nothing when the campaign has no upcoming session to pin to. ### Notes - Migration head advances to `5a6b7c8d9e0f`. - Follow-on slices (glance cards, spotlight, scene packs, reveal-to-table, fast retrieval, end-of-session sweep, warm cold-open) tracked under #235 and remain out of scope here. Closes #239, closes #240, closes #241. Part of #235. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The per-session shelf foundation (v3.11 #235). Adds a ShelfCard model - a
per-session card referencing a GenerationResult or a LoreEntry (or a free note),
with a self-sufficient snapshot captured at add-time so it survives retention
pruning / source deletion (source FKs are SET NULL), plus board position,
tonight_notes, and a scene tag. GM-only per-session endpoints
(GET/POST/PATCH/DELETE /api/sessions/{id}/shelf) via get_session_for_gm.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Covers add generation/lore card (with captured snapshot), list, update
(position/tonight-notes/scene), delete, the GM-only 403 boundary, 404s for
unknown session/card, and the 422 guards (missing source id, cross-campaign
source).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A per-session, full-screen board (/sessions/:id/shelf) that renders the shelf's
cards (generation- or lore-backed), lets the GM drag each to a fixed position
(persisted, never auto-reflowed), jot tonight-notes, and remove them. Reachable
via a GM-only "Shelf" link in the session header. Empty state guides the GM to
add content via the Workbench/wiki (#241).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(frontend): add-to-shelf from Workbench + wiki (#241)
Some checks failed
CI / Docker image build (pull_request) Successful in 42s
CI / Backend lint (ruff) (pull_request) Failing after 47s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m13s
CI / Bot tests and audit (pull_request) Successful in 2m23s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m42s
1ea98ebbec
Shared AddToShelf control: a session picker (defaulting to the campaign's
in-progress, else next upcoming, session) plus an "Add to shelf" button that
POSTs a ShelfCardCreate. Rendered under each GeneratorPanel result
(card_kind=generation) and in the wiki entry's GM action row
(card_kind=lore). GM-only; renders nothing when the campaign has no upcoming
session to pin to.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
style(backend): ruff format shelf schema (#239)
All checks were successful
CI / Docker image build (pull_request) Successful in 11s
CI / Backend lint (ruff) (pull_request) Successful in 1m3s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m5s
CI / Bot tests and audit (pull_request) Successful in 2m13s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m29s
64bb273200
Wrap the over-length generation-source validator condition; ruff format
check was the only red CI job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(frontend): resizable, scrolling shelf cards + stale-chunk auto-reload (#240)
All checks were successful
CI / Frontend tests, audit, and build (pull_request) Successful in 4m29s
CI / Docker image build (pull_request) Successful in 38s
CI / Backend lint (ruff) (pull_request) Successful in 2m33s
CI / Bot tests and audit (pull_request) Successful in 9m35s
CI / Backend migration, tests, and audit (pull_request) Successful in 12m53s
3cb8ee6c1d
Shelf cards were fixed-width with a 6-line clamp that truncated longer
entries. Cards now have explicit width/height, a scrollable body (no clamp),
and a corner grip to resize; geometry (x/y/w/h) persists in `position`
alongside placement, best-effort like drag. Header drag = move, grip = resize,
sharing one pointer gesture.

Also add a `vite:preloadError` handler in main.jsx: when a redeploy rotates
asset hashes, a stale tab's lazy route import 404s ("error loading dynamically
imported module …"). Reload once (time-guarded against loops) to pull fresh
hashes instead of dead-ending on the error boundary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(frontend): render every generation shape on shelf cards (#240)
All checks were successful
CI / Docker image build (pull_request) Successful in 1m2s
CI / Backend lint (ruff) (pull_request) Successful in 1m21s
CI / Frontend tests, audit, and build (pull_request) Successful in 2m25s
CI / Bot tests and audit (pull_request) Successful in 2m34s
CI / Backend migration, tests, and audit (pull_request) Successful in 5m45s
33de00240b
cardText only understood paragraph/one_liner, so rumor, random-table, loot,
NPC-cue and prep cards rendered blank. Add generationPreviewText: a shared,
pure mapper from each tool's output shape (see workbenchTools.jsx) to compact
text, with a first-list / string-field fallback for unknown shapes. Existing
cards fill in immediately since the snapshot already stores output. Unit-tested
across all shapes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot deleted branch feat/v3.11-session-shelf 2026-07-22 05:26:37 +00:00
Sign in to join this conversation.
No description provided.