[Frontend] Add a web RSVP control — RSVP has no web surface at all #370

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

Impact: CRITICAL

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

What the user experiences

RSVP is a named step of the core session lifecycle, and the web app has no control for it whatsoever. A player who is not on Discord, or who has not linked their Discord account, has no way to tell the GM they're coming. The only web-side attendance controls are GM tools gated to completed sessions, which is the opposite of what RSVP needs (a pre-session signal from the player).

Evidence

  • webapp/frontend/src/pages/SessionDetail.jsx:1384-1411 — the only web attendance control is GM-only and gated on session.status === "completed".
  • webapp/frontend/src/pages/RecordingDashboard.jsx:414-441 — a second, GM-only, differently-labelled copy of the same idea, live-session only.
  • Discord-side RSVP is reaction-only on a confirmation message (bot/questboard_bot/cogs/notifications.py:386-413) that never references the poll it resolved, and acknowledges nothing on either success or failure (see the companion "acknowledge Discord votes and RSVPs" issue in this milestone).

Why it matters for a hosted product

A hosted customer whose group isn't Discord-native — or whose players haven't linked their accounts — cannot use RSVP at all today. This is the single biggest hole in the product's own stated core loop, and it blocks anyone selling the product as usable without Discord.

Proposed fix

On SessionDetail, for any confirmed or tentative session, show a three-state "Are you in?" control for the current user, writing to the same attendance table the Discord reactions already write to. Show the party's responses as a roster underneath. This is the audit's P3.

Acceptance criteria

  • SessionDetail shows a self-service RSVP control (yes/maybe/no) for the signed-in user on confirmed/tentative sessions.
  • The control writes to the same attendance data Discord reactions populate, so the two paths stay consistent.
  • The party's current RSVP roster is visible to all members, not just the GM.
  • The control gives the user visible confirmation that their RSVP was saved.
**Impact: CRITICAL** Found in the August 2026 session lifecycle review (#319). ## What the user experiences RSVP is a named step of the core session lifecycle, and the web app has no control for it whatsoever. A player who is not on Discord, or who has not linked their Discord account, has no way to tell the GM they're coming. The only web-side attendance controls are GM tools gated to completed sessions, which is the opposite of what RSVP needs (a pre-session signal from the player). ## Evidence - `webapp/frontend/src/pages/SessionDetail.jsx:1384-1411` — the only web attendance control is GM-only and gated on `session.status === "completed"`. - `webapp/frontend/src/pages/RecordingDashboard.jsx:414-441` — a second, GM-only, differently-labelled copy of the same idea, live-session only. - Discord-side RSVP is reaction-only on a confirmation message (`bot/questboard_bot/cogs/notifications.py:386-413`) that never references the poll it resolved, and acknowledges nothing on either success or failure (see the companion "acknowledge Discord votes and RSVPs" issue in this milestone). ## Why it matters for a hosted product A hosted customer whose group isn't Discord-native — or whose players haven't linked their accounts — cannot use RSVP at all today. This is the single biggest hole in the product's own stated core loop, and it blocks anyone selling the product as usable without Discord. ## Proposed fix On `SessionDetail`, for any `confirmed` or `tentative` session, show a three-state "Are you in?" control for the current user, writing to the same attendance table the Discord reactions already write to. Show the party's responses as a roster underneath. This is the audit's P3. ## Acceptance criteria - [ ] `SessionDetail` shows a self-service RSVP control (yes/maybe/no) for the signed-in user on `confirmed`/`tentative` sessions. - [ ] The control writes to the same attendance data Discord reactions populate, so the two paths stay consistent. - [ ] The party's current RSVP roster is visible to all members, not just the GM. - [ ] The control gives the user visible confirmation that their RSVP was saved.
Author
Contributor

Picking this up as v4.3.0 phase 4 (#514), first on the lane, followed by #380 and #368. Decisions: a member-facing endpoint on the same table the Discord reactions write to, a three-state "Are you in?" control on confirmed and tentative sessions with the party's roster underneath, an inline confirmation after saving, and the same notification the Discord path already publishes when an RSVP changes (no new event type). The GM's completed-session attendance tools stay as they are.

Picking this up as v4.3.0 phase 4 (#514), first on the lane, followed by #380 and #368. Decisions: a member-facing endpoint on the same table the Discord reactions write to, a three-state "Are you in?" control on confirmed and tentative sessions with the party's roster underneath, an inline confirmation after saving, and the same notification the Discord path already publishes when an RSVP changes (no new event type). The GM's completed-session attendance tools stay as they are.
Author
Contributor

Done in PR #526 (auto-merging on green); ships with v4.3.0.

Reused the row the Discord / reaction already writes. That path calls PUT /api/bot/sessions/{id}/attendance/{discord_id}session_attendance.attended, a NOT NULL DEFAULT false boolean, so "said no" and "never answered" were the same value. Added a nullable rsvp_status enum and rsvp_at to the same table (migration fa0b1c2d3e4f, chained after #369's f0ab1c2d3e4f), because NULL-as-"no answer" is what the roster and #368 both key on.

  • GET /api/sessions/{id}/rsvps: one entry per campaign member, status: null for the unanswered, readable by any member.
  • PUT /api/sessions/{id}/rsvp {"status": "yes"|"maybe"|"no"}: members only, for themselves only (no user id in the path). Open on confirmed sessions and on tentative-mode sessions still proposed; a vote-mode proposed session is excluded because its time-slot vote already asks the question.
  • The bot endpoint now writes rsvp_status alongside its unchanged effect on attended, so a reaction in Discord shows in the web roster. Purely additive; no bot code change, no contract bump.

One decision worth recording: the RSVP path does not touch attended. Mirroring yes→true would match the bot exactly, but it asserts someone attended a session that has not happened, and, because bulk_insert_attendance_absent never overwrites an existing row, it would silently disable the #114 recording auto-fill for every member who RSVPed. The GM's completed-session tools are unchanged.

No notification is published on a web RSVP: the Discord path publishes no event either, so there was nothing to mirror and nothing was invented.

UI: SessionRsvp.jsx, first thing in the session page's main column. Yes/Maybe/No segmented control, party-wide roster with an explicit "No answer" state, inline confirmation, no alert(). Tests: 22 backend, 9 component, 2 page-wiring. A follow-up is filed as #525: feeding an RSVP of "no" into the at-risk evaluation, which currently counts only slot votes.

Done in PR #526 (auto-merging on green); ships with v4.3.0. Reused the row the Discord ✅/❌ reaction already writes. That path calls `PUT /api/bot/sessions/{id}/attendance/{discord_id}` → `session_attendance.attended`, a `NOT NULL DEFAULT false` boolean, so "said no" and "never answered" were the same value. Added a nullable `rsvp_status` enum and `rsvp_at` to the same table (migration `fa0b1c2d3e4f`, chained after #369's `f0ab1c2d3e4f`), because NULL-as-"no answer" is what the roster and #368 both key on. - `GET /api/sessions/{id}/rsvps`: one entry per campaign member, `status: null` for the unanswered, readable by any member. - `PUT /api/sessions/{id}/rsvp` `{"status": "yes"|"maybe"|"no"}`: members only, for themselves only (no user id in the path). Open on confirmed sessions and on tentative-mode sessions still proposed; a vote-mode proposed session is excluded because its time-slot vote already asks the question. - The bot endpoint now writes `rsvp_status` *alongside* its unchanged effect on `attended`, so a reaction in Discord shows in the web roster. Purely additive; no bot code change, no contract bump. One decision worth recording: **the RSVP path does not touch `attended`.** Mirroring yes→true would match the bot exactly, but it asserts someone attended a session that has not happened, and, because `bulk_insert_attendance_absent` never overwrites an existing row, it would silently disable the #114 recording auto-fill for every member who RSVPed. The GM's completed-session tools are unchanged. No notification is published on a web RSVP: the Discord path publishes no event either, so there was nothing to mirror and nothing was invented. UI: `SessionRsvp.jsx`, first thing in the session page's main column. Yes/Maybe/No segmented control, party-wide roster with an explicit "No answer" state, inline confirmation, no `alert()`. Tests: 22 backend, 9 component, 2 page-wiring. A follow-up is filed as #525: feeding an RSVP of "no" into the at-risk evaluation, which currently counts only slot votes.
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#370
No description provided.