The email answer page says a session started or was cancelled; the DM warning says which refusal (#901) #950

Merged
claude-bot merged 1 commit from fix/901-email-landings into main 2026-09-30 05:49:32 +00:00
Collaborator

Closes #901. This makes the email landings and the DM warning carry what lane 10's drawings show (lane8.html EXT-5 and HOME-6).

1 and 2: /email/answer

  • GET /api/email/answer gains:
    • session_id and campaign_name;
    • state: open, started or cancelled;
    • cancelled_by and cancel_reason.
  • Still the single "run out" 404: a bad or old token, a missing user or session, a lapsed member, or a session with no time. Membership is checked before any state is revealed.
  • POST: a started or cancelled session is a 409 ("Session N has started" / "was cancelled"), and nothing is recorded.
  • The page draws the three states:
    • started, with "Open Session N";
    • cancelled ("Dana cancelled it: “…” There’s nothing to answer."; a reason typed without a full stop gets one);
    • run out.
  • Other page changes:
    • The label is Session N · <campaign>, as drawn.
    • After Confirm, "Open Session N in Quest Board" sits beside the disabled button.
    • A 409 on Confirm reloads the page into the new state instead of showing "didn't go through".
  • /email/stop is unchanged, as drawn: its run-out state is captioned "the same words either way", and an HMAC can't tell a rotated secret from a tampered token.

3: the DM warning

  • The bot already reports closed / no_shared_server, and platform_links.dm_failed_reason already stores it. There's no bot change and no contract bump.
  • channel_availability picks the drawn sentence for each kind, and exposes warning_kind and warning_at (ChannelInfo). The old generic sentence stays for a legacy row with no reason.
  • The banner title matches the drawing ("Discord can’t reach you"), and the grid's Discord DM column header carries a warning icon.

Departures, all small:

  • the drawn sentences name the server and the day ("Amberwake & Friends", "on Tuesday"); the backend knows neither the guild's name nor the reader's zone here, so it says "that server" / "your campaign's server";
  • no "Sign in" button on the answer page's run-out state, to match the stop page;
  • the done banner omits "Dana can see it."

Checks

  • Backend: test_email_routes, test_email_templates, test_notifications, test_notifications_out and test_outbound_notifications_migration pass (156). These are all the modules that reference the touched symbols. New tests cover each state, the 409s, the lapsed member on a cancelled session, and every warning kind.
  • Frontend: the full vitest suite passes (2765), and the targeted files re-run after the last fix (30). eslint shows 0 errors.
  • e2e: J2's Ines journey now asserts the "Open Session N in Quest Board" link after Confirm.
  • Render check: the seeded e2e stack, at 390×844 and 1440×900 in light and dark, with Playwright:
    • every answer state, and the closed and no-shared-server warnings;
    • no horizontal overflow anywhere;
    • links point at /sessions/<id>.
  • Lint and changelog: ruff check and format are clean, and changelog.py check passes.

Changelog: changelog.d/901-email-landings.changed.md.

🤖 Generated with Claude Code

Closes #901. This makes the email landings and the DM warning carry what lane 10's drawings show (lane8.html EXT-5 and HOME-6). **1 and 2: `/email/answer`** - **`GET /api/email/answer`** gains: - `session_id` and `campaign_name`; - `state`: `open`, `started` or `cancelled`; - `cancelled_by` and `cancel_reason`. - **Still the single "run out" 404:** a bad or old token, a missing user or session, a lapsed member, or a session with no time. Membership is checked **before** any state is revealed. - **`POST`:** a started or cancelled session is a **409** ("Session N has started" / "was cancelled"), and nothing is recorded. - **The page draws the three states:** - *started*, with "Open Session N"; - *cancelled* ("Dana cancelled it: “…” There’s nothing to answer."; a reason typed without a full stop gets one); - *run out*. - **Other page changes:** - The label is `Session N · <campaign>`, as drawn. - After Confirm, "Open Session N in Quest Board" sits beside the disabled button. - A 409 on Confirm reloads the page into the new state instead of showing "didn't go through". - **`/email/stop` is unchanged, as drawn:** its run-out state is captioned "the same words either way", and an HMAC can't tell a rotated secret from a tampered token. **3: the DM warning** - The bot already reports `closed` / `no_shared_server`, and `platform_links.dm_failed_reason` already stores it. There's **no bot change and no contract bump.** - `channel_availability` picks the drawn sentence for each kind, and exposes `warning_kind` and `warning_at` (`ChannelInfo`). The old generic sentence stays for a legacy row with no reason. - The banner title matches the drawing ("Discord can’t reach you"), and the grid's Discord DM column header carries a warning icon. **Departures, all small:** - the drawn sentences name the server and the day ("Amberwake & Friends", "on Tuesday"); the backend knows neither the guild's name nor the reader's zone here, so it says "that server" / "your campaign's server"; - no "Sign in" button on the answer page's run-out state, to match the stop page; - the done banner omits "Dana can see it." **Checks** - **Backend:** `test_email_routes`, `test_email_templates`, `test_notifications`, `test_notifications_out` and `test_outbound_notifications_migration` pass (156). These are all the modules that reference the touched symbols. New tests cover each state, the 409s, the lapsed member on a cancelled session, and every warning kind. - **Frontend:** the full vitest suite passes (2765), and the targeted files re-run after the last fix (30). eslint shows 0 errors. - **e2e:** J2's Ines journey now asserts the "Open Session N in Quest Board" link after Confirm. - **Render check:** the seeded e2e stack, at 390×844 and 1440×900 in light and dark, with Playwright: - every answer state, and the closed and no-shared-server warnings; - no horizontal overflow anywhere; - links point at `/sessions/<id>`. - **Lint and changelog:** ruff check and format are clean, and `changelog.py check` passes. Changelog: `changelog.d/901-email-landings.changed.md`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(email): the answer page says a session started or was cancelled; the DM warning says why (#901)
All checks were successful
CI / Docker image build (pull_request) Successful in 28s
CI / Does this change touch the recording path? (pull_request) Successful in 28s
CI / Bot/backend version sync (pull_request) Successful in 29s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Backend lint (ruff) (pull_request) Successful in 1m0s
CI / Feedback site tests (pull_request) Successful in 1m28s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m39s
CI / Bot tests and audit (pull_request) Successful in 2m50s
CI / Frontend (shard 2/2) (pull_request) Successful in 5m22s
CI / End-to-end journeys (phone) (pull_request) Successful in 6m30s
CI / Frontend (shard 1/2) (pull_request) Successful in 7m19s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 7m22s
CI / End-to-end journeys (desktop) (pull_request) Successful in 7m27s
CI / Backend migration, tests, and audit (pull_request) Successful in 15m19s
f54adbc4bf
- GET /api/email/answer returns session_id, campaign_name and a state
  (open | started | cancelled, with cancelled_by and cancel_reason).
  Bad or old tokens and lapsed members stay the single "run out" 404,
  and membership is checked before any state is revealed. POST refuses
  a started or cancelled session with a 409 and records nothing.
- The page draws EXT-5's three states, links into the session, and a
  409 on Confirm reloads it into the new state.
- channel_availability picks the drawn sentence for the bot's reported
  refusal (closed / no_shared_server) and exposes warning_kind and
  warning_at; the grid's Discord DM header carries a warning icon.
- The stop page keeps one "run out" sentence, as drawn: an HMAC cannot
  tell a rotated secret from a tampered token.

No bot change and no contract bump: the bot already reported the kind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.