feat: 'Previously on…' pre-session recap in reminders (#110) #198

Merged
claude-bot merged 2 commits from feat/110-previously-on-recap into main 2026-07-18 04:55:20 +00:00
Contributor

Closes #110. The final v3.7.0 Table Tools issue. 🎬

Adds a "Previously on…" recap to the final pre-session Discord reminder — the last approved session's summary + a few open player-facing quests — so players get a refresher exactly when they want it. Stored summary verbatim, no LLM.

Backend (1007bb6)

  • Campaign gains recap_in_reminder (bool, default on — harmless, the block self-skips when there's no approved summary). Migration f9a0b1c2d3e4 (round-trips).
  • _build_reminder_recap: newest session with audio_processing_status in (trashed, approved) + non-empty summary (excluding the upcoming session), truncated to ≤1024 chars on a word boundary; open_threads = up to 5 threads filtered at the SQL level to player_visible=true AND status in (active, rumored). Returns {} when there's nothing to show (graceful skip).
  • Attached to the smallest offset only (offset == min(offsets) && recap_in_reminder), spread into the session_reminder extra via **recap; non-final offsets (7-day, 24h) stay byte-identical to today. Never logs summary/thread text.

Bot (additive — no BOT_CONTRACT_VERSION bump)

  • _handle_reminder renders "📖 Previously on…" and "🧵 Open threads" embed fields when present (fit_embed-budgeted); absent → embed exactly as today.

Frontend (c4523ab)

  • "Previously on…" recap-in-reminders campaign setting toggle (default on), next to the other Discord/recap settings.

Two correctness points (things a generic recap couldn't get right)

  1. Recap fires once, on the final reminder — not repeated on the 7-day/24h reminders.
  2. open_threads respects player_visible#110's spec predated #115 (which replaced PlotThread.resolved with status/player_visible); since this posts to the players' channel, the filter is applied in SQL so GM-private threads can never leak (explicit test asserts a GM-private + a completed thread are excluded).

Tests

  • Backend: +12 (test_previously_on_recap.py) — final-offset-only wiring, excerpt selection/exclusion/truncation, the player_visible+status thread filter + cap, and the recap_in_reminder=false opt-out. Full suite 528 pass; migration round-trip verified.
  • Bot: +4 (test_notifications.py) — field present/absent/empty. Bot suite 192 pass (run explicitly, since additive reminder-payload changes have tripped the bot's exact-call tests before).
  • Frontend: +1 (CampaignDetail). 263 pass, eslint clean, vite build green.

Note

Recap is attached only on the bot-notify path, not the webhook/email fallback (webhook is the no-guild_id degraded path) — those payloads are untouched and unaffected.

🤖 Generated with Claude Code

Closes #110. **The final v3.7.0 Table Tools issue.** 🎬 Adds a "Previously on…" recap to the final pre-session Discord reminder — the last approved session's summary + a few open player-facing quests — so players get a refresher exactly when they want it. Stored summary verbatim, no LLM. ## Backend (`1007bb6`) - Campaign gains `recap_in_reminder` (bool, default **on** — harmless, the block self-skips when there's no approved summary). Migration `f9a0b1c2d3e4` (round-trips). - `_build_reminder_recap`: newest session with `audio_processing_status in (trashed, approved)` + non-empty `summary` (excluding the upcoming session), truncated to **≤1024 chars** on a word boundary; `open_threads` = up to 5 threads filtered **at the SQL level** to `player_visible=true AND status in (active, rumored)`. Returns `{}` when there's nothing to show (graceful skip). - Attached to the **smallest offset only** (`offset == min(offsets) && recap_in_reminder`), spread into the `session_reminder` `extra` via `**recap`; non-final offsets (7-day, 24h) stay **byte-identical** to today. Never logs summary/thread text. ## Bot (additive — no `BOT_CONTRACT_VERSION` bump) - `_handle_reminder` renders "📖 Previously on…" and "🧵 Open threads" embed fields when present (fit_embed-budgeted); absent → embed exactly as today. ## Frontend (`c4523ab`) - "Previously on…" recap-in-reminders campaign setting toggle (default on), next to the other Discord/recap settings. ## Two correctness points (things a generic recap couldn't get right) 1. **Recap fires once, on the final reminder** — not repeated on the 7-day/24h reminders. 2. **`open_threads` respects `player_visible`** — #110's spec predated #115 (which replaced `PlotThread.resolved` with `status`/`player_visible`); since this posts to the players' channel, the filter is applied in SQL so **GM-private threads can never leak** (explicit test asserts a GM-private + a completed thread are excluded). ## Tests - Backend: +12 (`test_previously_on_recap.py`) — final-offset-only wiring, excerpt selection/exclusion/truncation, the player_visible+status thread filter + cap, and the `recap_in_reminder=false` opt-out. Full suite **528 pass**; migration round-trip verified. - Bot: +4 (`test_notifications.py`) — field present/absent/empty. **Bot suite 192 pass** (run explicitly, since additive reminder-payload changes have tripped the bot's exact-call tests before). - Frontend: +1 (`CampaignDetail`). **263 pass**, eslint clean, vite build green. ## Note Recap is attached only on the bot-notify path, not the webhook/email fallback (webhook is the no-`guild_id` degraded path) — those payloads are untouched and unaffected. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Attaches a recap block to the FINAL pre-session Discord reminder: the last
approved session's summary + a few open player-facing quests. Uses the stored
summary verbatim (no LLM). Additive and graceful.

Backend:
- Campaign gains recap_in_reminder (bool, default true); migration
  f9a0b1c2d3e4 (round-trips).
- _build_reminder_recap: newest session with audio status trashed/approved +
  non-empty summary (excluding the upcoming one), truncated to <=1024 chars on
  a word boundary; open_threads = up to 5 threads filtered at the SQL level to
  player_visible=true AND status in (active, rumored) — so GM-private threads
  can never leak into the players' channel (post-#115 correctness). Returns {}
  when there's nothing to show.
- Wired to the smallest offset only (offset == min(offsets) && recap_in_reminder),
  spread into the session_reminder extra via **recap; non-final offsets stay
  byte-identical. Never logs summary/thread text.

Bot (additive — no BOT_CONTRACT_VERSION bump):
- _handle_reminder renders "📖 Previously on…" and "🧵 Open threads" embed
  fields when present (fit_embed-budgeted); absent → embed unchanged.

Tests: backend +12 (final-offset-only, excerpt selection/truncation, the
player_visible+status thread filter, opt-out); bot +4 (field present/absent).
Full backend suite 528 pass; bot suite 192 pass; migration round-trip verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(frontend): recap-in-reminder campaign setting toggle (#110)
All checks were successful
CI / Frontend tests, audit, and build (pull_request) Successful in 1m24s
CI / Backend lint (ruff) (pull_request) Successful in 1m42s
CI / Docker image build (pull_request) Successful in 2m23s
CI / Bot tests and audit (pull_request) Successful in 3m41s
CI / Backend migration, tests, and audit (pull_request) Successful in 7m19s
c4523ab2b3
Adds the "Previously on…" recap-in-reminders checkbox to the campaign edit
form (default on), wired into editForm init + the update payload, next to the
other Discord/recap settings.

Tests: +1 (CampaignDetail). 263 pass, eslint clean, vite build succeeds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-bot deleted branch feat/110-previously-on-recap 2026-07-18 04:55:21 +00:00
Sign in to join this conversation.
No description provided.