[Bot] Put a deep link in every Discord notification embed #391

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

Impact: HIGH

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

What the user experiences

Of thirteen Discord notification event templates, exactly one contains a link back to the web app. Every other embed — reminders, confirmations, the session-summary notification — makes the user find the relevant session by hand. The session_summarised footer even claims "Full transcript available to GMs on Quest Board" without linking to it.

Evidence

  • bot/questboard_bot/cogs/notifications.py:489 — the only template that builds [Open in Quest Board](…), via session_url built at webapp/backend/app/tasks/reminder_tasks.py:1917.
  • bot/questboard_bot/cogs/notifications.py:690 — the session_summarised footer text names the web app without linking it.
  • The only other URL the bot builds anywhere is the account-link DM (bot/questboard_bot/cogs/linking.py:71-74).

Why it matters for a hosted product

Combined with the dashboard "needs you" surface in this milestone, this is most of the fix for "nobody knows anything is waiting" — a notification that doesn't link back to the app requires the user to independently navigate there and find the right page themselves.

Proposed fix

session_url already exists and is used in one template — thread it through the other twelve. This is the audit's P21.

Acceptance criteria

  • All thirteen Discord notification templates include a link back to the relevant web app page.
  • The session_summarised footer's "Full transcript available to GMs on Quest Board" text is an actual link.
**Impact: HIGH** Found in the August 2026 session lifecycle review (#319). ## What the user experiences Of thirteen Discord notification event templates, exactly one contains a link back to the web app. Every other embed — reminders, confirmations, the session-summary notification — makes the user find the relevant session by hand. The `session_summarised` footer even claims *"Full transcript available to GMs on Quest Board"* without linking to it. ## Evidence - `bot/questboard_bot/cogs/notifications.py:489` — the only template that builds `[Open in Quest Board](…)`, via `session_url` built at `webapp/backend/app/tasks/reminder_tasks.py:1917`. - `bot/questboard_bot/cogs/notifications.py:690` — the `session_summarised` footer text names the web app without linking it. - The only other URL the bot builds anywhere is the account-link DM (`bot/questboard_bot/cogs/linking.py:71-74`). ## Why it matters for a hosted product Combined with the dashboard "needs you" surface in this milestone, this is most of the fix for "nobody knows anything is waiting" — a notification that doesn't link back to the app requires the user to independently navigate there and find the right page themselves. ## Proposed fix `session_url` already exists and is used in one template — thread it through the other twelve. This is the audit's P21. ## Acceptance criteria - [ ] All thirteen Discord notification templates include a link back to the relevant web app page. - [ ] The `session_summarised` footer's "Full transcript available to GMs on Quest Board" text is an actual link.
Author
Contributor

Picking this up as part of v4.3.0 phase 1 (#514), shipping early as v4.2.3. The backend will put session_url (or campaign_url for campaign-level events) in extra for every notification it enqueues, built by one helper, and every bot template renders the link when present. Additive payload field, so no contract bump; both sides in one commit per CLAUDE.md.

Picking this up as part of v4.3.0 phase 1 (#514), shipping early as v4.2.3. The backend will put `session_url` (or `campaign_url` for campaign-level events) in `extra` for every notification it enqueues, built by one helper, and every bot template renders the link when present. Additive payload field, so no contract bump; both sides in one commit per CLAUDE.md.
rbrooks referenced this issue from a commit 2026-09-06 02:00:15 +00:00
Author
Contributor

Implemented in PR #517 (merged), both sides in one commit, shipping in v4.2.3.

Backend: app_url_for / session_url_for / campaign_url_for / session_url_extra in app/services/bot_pubsub.py, next to stamp_event_id because like it they shape the payload rather than talk to Redis. All twelve session-scoped enqueue sites now splat **session_url_extra(...) into extra: reminders, at-risk, vote posts and vote updates, confirmations and RSVP prompts, proposals, cancellations, completions, summary ready/failed/approved, audio-deletion warnings, and shelf reveals. The vote reminder's inline f-string, the only link that existed, is now the same helper. For session_summarised the splat lives inside _notify_bot_summarised rather than at its three callers, so success, failure and the queue timeout all carry it and none can be the one that forgets.

When APP_URL is unset the helpers return None and the field is omitted, never a relative or half-built URL: a Discord embed can only carry an absolute one and a partial renders as literal text in the channel.

Bot: one _add_quest_board_link(embed, extra) helper called unconditionally by all thirteen templates; the "should there be a link" decision belongs to the backend that either sent the key or did not. The summary footer's "Full transcript available to GMs on Quest Board", a signpost with no link because Discord embed footers cannot hold one, moves into that field as a real link, falling back to the footer only when there is no URL. /sessions/:id and /campaigns/:id were confirmed to be real frontend routes before pointing anything at them.

No BOT_CONTRACT_VERSION bump: additive event field, and the contract governs /api/bot/* shapes, not the event vocabulary. Tests: a 13-case parametrised pair over every template (link renders as markdown when present; embed still posts with no ]( when absent), the footer conversion, a vote-reminder regression guard, and the campaign_url fallback; backend covers both URL shapes, the blank-app_url behaviour, and an actually enqueued payload carrying and omitting session_url. docs/DEVELOPMENT.md's "adding a notification event" section now says to include the link, so the next event gets one by construction.

campaign_url_for ships tested but uncalled: no backend event is campaign-only today, and the bot already reads campaign_url as a fallback, so the first campaign-level event needs no bot change.

Implemented in PR #517 (merged), both sides in one commit, shipping in v4.2.3. Backend: `app_url_for` / `session_url_for` / `campaign_url_for` / `session_url_extra` in `app/services/bot_pubsub.py`, next to `stamp_event_id` because like it they shape the payload rather than talk to Redis. All twelve session-scoped enqueue sites now splat `**session_url_extra(...)` into `extra`: reminders, at-risk, vote posts and vote updates, confirmations and RSVP prompts, proposals, cancellations, completions, summary ready/failed/approved, audio-deletion warnings, and shelf reveals. The vote reminder's inline f-string, the only link that existed, is now the same helper. For `session_summarised` the splat lives inside `_notify_bot_summarised` rather than at its three callers, so success, failure and the queue timeout all carry it and none can be the one that forgets. When `APP_URL` is unset the helpers return `None` and the field is omitted, never a relative or half-built URL: a Discord embed can only carry an absolute one and a partial renders as literal text in the channel. Bot: one `_add_quest_board_link(embed, extra)` helper called unconditionally by all thirteen templates; the "should there be a link" decision belongs to the backend that either sent the key or did not. The summary footer's "Full transcript available to GMs on Quest Board", a signpost with no link because Discord embed footers cannot hold one, moves into that field as a real link, falling back to the footer only when there is no URL. `/sessions/:id` and `/campaigns/:id` were confirmed to be real frontend routes before pointing anything at them. No `BOT_CONTRACT_VERSION` bump: additive event field, and the contract governs `/api/bot/*` shapes, not the event vocabulary. Tests: a 13-case parametrised pair over every template (link renders as markdown when present; embed still posts with no `](` when absent), the footer conversion, a vote-reminder regression guard, and the `campaign_url` fallback; backend covers both URL shapes, the blank-`app_url` behaviour, and an actually enqueued payload carrying and omitting `session_url`. `docs/DEVELOPMENT.md`'s "adding a notification event" section now says to include the link, so the next event gets one by construction. `campaign_url_for` ships tested but uncalled: no backend event is campaign-only today, and the bot already reads `campaign_url` as a fallback, so the first campaign-level event needs no bot change.
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#391
No description provided.