[Table Tools] 'Previously on…' pre-session recap post #110

Closed
opened 2026-07-14 19:50:32 +00:00 by claude-bot · 1 comment
Contributor

Context / Motivation

Approved session summaries exist (stored in Session.summary, webapp/backend/app/models/session.py:115; approved via approve_audio, webapp/backend/app/routers/sessions.py:436-504) and reminder delivery exists (poll_session_reminders, webapp/backend/app/tasks/reminder_tasks.py:998). But the moment players actually want a recap is right before the next session — currently they have to go find it.

Spec

When the final pre-session reminder fires (the smallest offset in campaign.reminder_offsets_minutes, read at reminder_tasks.py:1054) — or a configurable dedicated T-24h slot — include a recap block in the reminder:

Backend (poll_session_reminders payload construction, reminder_tasks.py:1090-1104): when building the session_reminder event dict (event_type, session_id, guild_id, channel_id, extra{confirmed_time, hours_until, title, campaign_name}), additionally attach:

  • recap_excerpt: excerpt of the most recent session in the campaign whose audio status is approved-side (AudioProcessingStatus in trashed/approved, models/session.py:27-34) and has a non-empty summary — truncated server-side to fit the Discord embed budget (field value ≤ 1024 chars; keep the whole embed ≤ 6000)
  • open_threads: up to ~5 unresolved plot threads (PlotThread.resolved == False, webapp/backend/app/models/plot_thread.py:22) as short titles, if any exist

Bot (bot/questboard_bot/cogs/notifications.py: dispatch map :169 routes session_reminder_handle_reminder at :319-338): when recap_excerpt is present, render a "Previously on…" embed field (and an "Open threads" field when provided). Absent keys → render exactly as today.

Toggle: campaign-level setting recap_in_reminder (default on; harmless because the block is skipped when no approved summary exists). Surface in campaign settings next to reminder offsets.

Graceful skip: campaigns with no prior approved summary get today's reminder unchanged — no empty "Previously on" section.

Out of scope

  • LLM re-summarization for the recap (use the stored summary verbatim, truncated).
  • Recaps in email reminders (send_recap_email already covers post-session email; evaluate separately).
  • Web-side recap surfacing.

Acceptance criteria

  • Campaign with an approved summary: the chosen reminder (final offset) carries the recap exactly once — earlier offsets (e.g. 7-day) do not repeat it.
  • Campaign with no approved summaries: payload and embed byte-identical in shape to current behavior.
  • Toggle off restores current behavior even with summaries present.
  • Excerpt truncation respects Discord's 1024-char field limit with a transcript-length summary fixture.

References

  • webapp/backend/app/tasks/reminder_tasks.py:998 (poll_session_reminders), :1054 (offsets), :1090-1104 (payload)
  • webapp/backend/app/services/bot_pubsub.py:29 (publish_bot_event)
  • bot/questboard_bot/cogs/notifications.py:169 (dispatch), :319-338 (_handle_reminder)
  • webapp/backend/app/models/session.py:115 (summary), :27-34 (AudioProcessingStatus)
  • webapp/backend/app/models/plot_thread.py:22 (resolved)
  • webapp/backend/app/routers/sessions.py:436 (approve_audio)

Filed from the July 2026 full-project review.

## Context / Motivation Approved session summaries exist (stored in `Session.summary`, `webapp/backend/app/models/session.py:115`; approved via `approve_audio`, `webapp/backend/app/routers/sessions.py:436-504`) and reminder delivery exists (`poll_session_reminders`, `webapp/backend/app/tasks/reminder_tasks.py:998`). But the moment players actually want a recap is right before the next session — currently they have to go find it. ## Spec When the **final pre-session reminder** fires (the smallest offset in `campaign.reminder_offsets_minutes`, read at `reminder_tasks.py:1054`) — or a configurable dedicated T-24h slot — include a recap block in the reminder: **Backend** (`poll_session_reminders` payload construction, `reminder_tasks.py:1090-1104`): when building the `session_reminder` event dict (`event_type`, `session_id`, `guild_id`, `channel_id`, `extra{confirmed_time, hours_until, title, campaign_name}`), additionally attach: - `recap_excerpt`: excerpt of the most recent session in the campaign whose audio status is approved-side (`AudioProcessingStatus` in `trashed`/`approved`, `models/session.py:27-34`) and has a non-empty `summary` — truncated server-side to fit the Discord embed budget (field value ≤ 1024 chars; keep the whole embed ≤ 6000) - `open_threads`: up to ~5 unresolved plot threads (`PlotThread.resolved == False`, `webapp/backend/app/models/plot_thread.py:22`) as short titles, if any exist **Bot** (`bot/questboard_bot/cogs/notifications.py`: dispatch map `:169` routes `session_reminder` → `_handle_reminder` at `:319-338`): when `recap_excerpt` is present, render a "Previously on…" embed field (and an "Open threads" field when provided). Absent keys → render exactly as today. **Toggle**: campaign-level setting `recap_in_reminder` (default **on**; harmless because the block is skipped when no approved summary exists). Surface in campaign settings next to reminder offsets. **Graceful skip**: campaigns with no prior approved summary get today's reminder unchanged — no empty "Previously on" section. ## Out of scope - LLM re-summarization for the recap (use the stored summary verbatim, truncated). - Recaps in email reminders (`send_recap_email` already covers post-session email; evaluate separately). - Web-side recap surfacing. ## Acceptance criteria - Campaign with an approved summary: the chosen reminder (final offset) carries the recap exactly once — earlier offsets (e.g. 7-day) do not repeat it. - Campaign with no approved summaries: payload and embed byte-identical in shape to current behavior. - Toggle off restores current behavior even with summaries present. - Excerpt truncation respects Discord's 1024-char field limit with a transcript-length summary fixture. ## References - `webapp/backend/app/tasks/reminder_tasks.py:998` (`poll_session_reminders`), `:1054` (offsets), `:1090-1104` (payload) - `webapp/backend/app/services/bot_pubsub.py:29` (`publish_bot_event`) - `bot/questboard_bot/cogs/notifications.py:169` (dispatch), `:319-338` (`_handle_reminder`) - `webapp/backend/app/models/session.py:115` (`summary`), `:27-34` (`AudioProcessingStatus`) - `webapp/backend/app/models/plot_thread.py:22` (`resolved`) - `webapp/backend/app/routers/sessions.py:436` (`approve_audio`) _Filed from the July 2026 full-project review._
Author
Contributor

Done — merged in PR #198 (backend+bot 1007bb6, frontend c4523ab). CI green. This closes out the v3.7.0 Table Tools milestone.

Shipped:

  • Campaign recap_in_reminder (default on); migration f9a0b1c2d3e4 (round-trips).
  • _build_reminder_recap attaches, on the final (smallest-offset) reminder only: the newest approved-side (trashed/approved) session summary, truncated ≤1024 chars on a word boundary, plus up to 5 open player-visible quests. Returns {} (graceful skip) when there's nothing to show; non-final offsets stay byte-identical.
  • Bot renders "📖 Previously on…" + "🧵 Open threads" embed fields when present; additive → no BOT_CONTRACT_VERSION bump.
  • Campaign settings toggle.

Two correctness points beyond the original spec: the recap fires once on the final offset (not repeated on the 7-day/24h reminders); and open_threads is filtered in SQL to player_visible=true AND status in (active, rumored) — because #110's spec predated #115 (which replaced resolved with status/player_visible), and this posts to the players' channel, so GM-private threads can never leak (explicit test).

Tests: backend 528 pass (+12); bot 192 pass (+4, run explicitly); frontend 263 (+1).

Acceptance criteria met: recap carried exactly once on the final offset; no approved summaries → byte-identical to current behavior; toggle off restores current behavior; excerpt respects the 1024-char field limit. (Webhook/email fallback recap left out of scope — degraded no-guild_id path, untouched.)

Closing.

Done — merged in PR #198 (backend+bot `1007bb6`, frontend `c4523ab`). CI green. **This closes out the v3.7.0 Table Tools milestone.** **Shipped:** - Campaign `recap_in_reminder` (default on); migration `f9a0b1c2d3e4` (round-trips). - `_build_reminder_recap` attaches, on the **final (smallest-offset) reminder only**: the newest approved-side (`trashed`/`approved`) session summary, truncated ≤1024 chars on a word boundary, plus up to 5 open **player-visible** quests. Returns `{}` (graceful skip) when there's nothing to show; non-final offsets stay byte-identical. - Bot renders "📖 Previously on…" + "🧵 Open threads" embed fields when present; additive → no `BOT_CONTRACT_VERSION` bump. - Campaign settings toggle. **Two correctness points beyond the original spec:** the recap fires once on the final offset (not repeated on the 7-day/24h reminders); and `open_threads` is filtered in SQL to `player_visible=true AND status in (active, rumored)` — because #110's spec predated #115 (which replaced `resolved` with `status`/`player_visible`), and this posts to the players' channel, so GM-private threads can never leak (explicit test). **Tests:** backend 528 pass (+12); bot 192 pass (+4, run explicitly); frontend 263 (+1). **Acceptance criteria** met: recap carried exactly once on the final offset; no approved summaries → byte-identical to current behavior; toggle off restores current behavior; excerpt respects the 1024-char field limit. (Webhook/email fallback recap left out of scope — degraded no-`guild_id` path, untouched.) Closing.
rbrooks referenced this issue from a commit 2026-07-18 04:59:00 +00:00
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#110
No description provided.