[Bot] Put a deep link in every Discord notification embed #391
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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_summarisedfooter 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](…), viasession_urlbuilt atwebapp/backend/app/tasks/reminder_tasks.py:1917.bot/questboard_bot/cogs/notifications.py:690— thesession_summarisedfooter text names the web app without linking it.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_urlalready exists and is used in one template — thread it through the other twelve. This is the audit's P21.Acceptance criteria
session_summarisedfooter's "Full transcript available to GMs on Quest Board" text is an actual link.Picking this up as part of v4.3.0 phase 1 (#514), shipping early as v4.2.3. The backend will put
session_url(orcampaign_urlfor campaign-level events) inextrafor 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.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_extrainapp/services/bot_pubsub.py, next tostamp_event_idbecause like it they shape the payload rather than talk to Redis. All twelve session-scoped enqueue sites now splat**session_url_extra(...)intoextra: 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. Forsession_summarisedthe splat lives inside_notify_bot_summarisedrather 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_URLis unset the helpers returnNoneand 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/:idand/campaigns/:idwere confirmed to be real frontend routes before pointing anything at them.No
BOT_CONTRACT_VERSIONbump: 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 thecampaign_urlfallback; backend covers both URL shapes, the blank-app_urlbehaviour, and an actually enqueued payload carrying and omittingsession_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_forships tested but uncalled: no backend event is campaign-only today, and the bot already readscampaign_urlas a fallback, so the first campaign-level event needs no bot change.