• v4.5.1-rc2 8033cdc179

    v4.5.1-rc2
    Some checks failed
    CI / Does this change touch the recording path? (push) Successful in 16s
    CI / Which journeys does this change need? (push) Successful in 16s
    CI / Bot/backend version sync (push) Successful in 27s
    CI / Backend lint (ruff) (push) Successful in 34s
    Release / Create Forgejo release (push) Successful in 13s
    CI / Feedback site tests (push) Successful in 45s
    Release / Build and push versioned images (push) Failing after 24s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 1m3s
    CI / Bot tests and audit (push) Successful in 2m5s
    CI / Docker image build (push) Successful in 5m3s
    CI / Frontend (shard 2/2) (push) Successful in 7m58s
    CI / Frontend (shard 1/2) (push) Successful in 9m43s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 11m33s
    CI / End-to-end journeys (phone) (phone) (push) Successful in 12m55s
    CI / End-to-end journeys (keyboard) (keyboard) (push) Successful in 14m14s
    CI / End-to-end journeys (desktop) (desktop) (push) Successful in 14m17s
    CI / Backend migration, tests, and audit (push) Successful in 33m20s
    Pre-release

    claude-bot released this 2026-10-13 21:26:25 +00:00 | 490 commits to main since this release

    Prerelease of 4.5.1 for testing on the dev server; not for production.

    Added

    • [webapp] A GM can delete a session nothing has happened on. Manage shows
      "Delete the session…" beside Cancel while the session is empty: no answers,
      votes, notes, shelf cards, prep, recording or summary, never started, and
      nobody told about it. Deleting tells nobody, and the session's number is not
      given again. A session with anything on it can only be cancelled, as before.
      A series does not make a deleted occurrence again. New
      POST /api/sessions/{id}/delete; DELETE /api/sessions/{id} still cancels.
      One migration (session_series_skips); no bot contract change. Decision D298,
      #1156.
    • [webapp] A GM can remind the people who haven't answered "Are you in?",
      and a confirmed session does it by itself 48 hours before.
      The session
      page's "Are you in?" has a "Remind who hasn't answered" button while
      anyone but the GM is unanswered; it can be used once per session every 12
      hours, shared with the automatic nudge, and says "Reminded 5 people at
      8:02 pm." afterwards. A group with no Discord is emailed instead, with the
      answer buttons. Settings → General has "Nudge people who haven't
      answered", with the time or off. Nothing is sent for a pencilled-in
      session, a vote, or a cancelled, started or played one. Adds migration
      e1158a1b2c3d (the campaign's time, default 48 hours, and two markers on
      the session). No BOT_CONTRACT_VERSION change.
    • [bot] One line in the channel mentions only the people who haven't
      answered, with the session's time in the campaign's zone, and a reaction on
      it counts.
      For example "Are you in for Session 14, Tuesday 13 October at
      8:00 pm CDT (your time beside it)?". The line holds no title or name. ✅ ❌ ❔
      on it answer as they do on the confirmed post. The bot lists the new
      rsvp_nudge feature, so an older bot is never sent the event.

    Changed

    • [webapp] The wording on the public pages, your profile and notifications, a campaign's other pages and its settings is shorter. The front door, sign-in, invitations, Privacy, Accessibility and first-run setup lose restated context; so do Profile, the bell, Sessions, the session form, Members, Highlights, Storyline, Analytics and the campaign settings pages. Confirmations keep what will happen; the privacy, recording and deletion statements keep their meaning. Part 2a of #1122.
    • [webapp] Shorter wording in the two Help guides, the answer-from-an-email pages, the calendar invite and test emails, and the error sentences people see in the UI.
      Part 2c of the wording pass (#1122): restated context and "try again" padding are cut, and the
      import refusals keep "Nothing was imported". Consent, privacy, retention and deletion wording is
      unchanged. See docs/design/wording/1122-emails-help-bot-errors.md.
    • [bot] Shorter replies from the bot's commands and reaction receipts.
      /link, /recap, /history, /note, /ask, /record, /concern, the failed-vote and failed-RSVP
      receipts and the test-channel post lose repeated explanation. The recording announcement and the
      privacy and deletion wording are unchanged.
    • [webapp] Shorter wording on the wiki, Prep and Planning, Discussions, character sheets and Admin.
      Part 2 of the wording pass (#1122): explanatory second sentences, restated context and "try again in a
      moment" lines beside a Try again button are cut. Audience, privacy, safety and Admin warning wording is
      unchanged. See docs/design/wording/1122-wiki-prep-talk-admin-sheets.md.
    • [webapp] A session with no title reads "Session N" everywhere a title is
      shown.
      Calendar feed entries, emails, search, the admin lists, the session
      pickers and what the bot posts to Discord no longer say "Untitled Session" or
      show a blank. The series form's title hint says so. A session with a title is
      unchanged. Decision D300, #1156.
    • [webapp] The "Remind who hasn't answered" button is on This week too, and
      the nudge's time travels with a campaign.
      A GM sees the same button under
      the next confirmed session's "Are you in?" on the campaign's This week page,
      with the same rules as on the session page (only while a player is
      unanswered, the 12-hour limit as its disabled reason). The This week
      payload gains gm.rsvp_nudged_at and gm.unanswered_players, for GMs only.
      A campaign's export now carries rsvp_nudge_minutes and an import reads it:
      "off" stays off, and a file made before this imports with the default 48
      hours. The export schema version stays 20. A player who has not linked Discord
      is reminded in Quest Board instead (a session reminder in their bell).
      When every unanswered player has not linked Discord, nothing is posted
      there and the button says "None of them has linked Discord, so they were
      reminded in Quest Board." (the response's via is then app). No migration.
    • [bot] The nudge says how many people it could not ping. When some
      unanswered players have not linked Discord, the line ends with "2 others
      have not linked Discord: /link connects your account, so your reactions
      count." (or "1 other has"), in the vote reminder's words. It counts and
      never names; the count comes from the backend's extra.unlinked_count (an
      additive field, no BOT_CONTRACT_VERSION change), is ignored unless it is a
      non-negative whole number, and stops at "99+".
    • [bot] Every post that shows a session time now names the campaign's
      timezone.
      The poll, a time added, "On", the reminders, cancelled, short-handed,
      the vote count, the Discord DM and the RSVP nudge read like Tuesday 13 October at 8:00 pm CEST (8:00 pm your time): the campaign's zone, then the reader's own
      as a Discord timestamp. A campaign without a zone, or one the bot cannot resolve,
      keeps Discord's own timestamp.
    • [webapp] Bot events carry the campaign's timezone as extra.timezone. An
      additive field on every event with a session time, and on the Discord DM;
      BOT_CONTRACT_VERSION is unchanged.
    • [repo] A pull request runs only the end-to-end journeys it can break. None
      when it touches only the bot, docs or tools; all three projects when
      it touches the journeys, their stack or a release branch; otherwise desktop
      alone. A push to main still runs all three (#1173).

    Fixed

    • [webapp] A campaign with a very large wiki, discussion or atlas can be exported. The export read each kind of row with a list that held one entry for every page, post, marker or session, and the database refuses a query with more than 32,767 of them, so a campaign an import had accepted could not be exported (#1113). The export now asks for those rows by campaign instead; what it writes is unchanged.
    • [repo] The release workflow can create a release whose notes are long.
      The notes went through a step output and an action input, which failed with
      argument list too long on 4.5.0 and its prereleases. They now go through a
      file and the Forgejo API; over 60,000 characters the page shows a summary
      and a link to the full text, and the job fails if the release is missing at
      the end. See scripts/changelog.py release-notes.
    • [webapp] Moving a series session onto a day the series already fills is
      now said before saving, and a series makes no second session on a day one
      of its own stands on.
      The Move it form names the other session ("Session 16
      is already on Tuesday 20 October at 8:00 pm.") as a nudge, and the Sessions
      list marks two sessions of one campaign on the same day with "Also that day".
      Decision D299, #1156.
    • [webapp] Wiki health's “Link it” now makes the link. It used to open the mentioning page in the editor and leave you to find the mention and Save. It now links the first plain mention in one press, as your own edit (it shows in History), and leaves a line “Linked in {page}.” with an Undo. A mention that has gone since the report says so. (#1157)
    • [webapp] A session can no longer end before it starts. A session moved by the 4.4.1 reschedule could keep an end from before its new start. A migration repairs those (the series length, or no end), a pencilled-in session now takes its end along when its time changes, the calendar, feeds and the auto-complete poller ignore an end that is not after the start, and saving one is refused with a plain message.
    • [webapp] Opening a discussion thread no longer marks it read from the GET itself. A link preview or prefetch could clear someone's unread dot; the page now asks with POST /api/discussions/{id}/read once it shows the thread. A test now lists the GET handlers allowed to write, so a new one is a deliberate choice (#1163).
    • [webapp] A confirmed session no longer says "Nobody has answered yet." beside the GM's own answer. The count is of players and says so ("4 of 5 players have answered"); with only the GM's answer it reads "No players have answered yet.", and with no players it shows no count at all.
    • [webapp] Map: the breadcrumb buttons and the marker panel's Close button are at least 24 px square at phone width.
    • [webapp] /favicon.ico serves the app icon instead of the app's HTML page.
    • [webapp] "Remind who hasn't answered" says what is true about the bot. An older bot gets "The bot is an older version and cannot post this yet. Ask whoever runs Quest Board to update it."; a bot that does not answer gets "The bot is not answering. Try again in a minute." (503).

    Security

    • [webapp] A wiki page now shows pictures from this app's own uploads only (#1138, D297).
      A picture in a page's text from an outside address (![](https://…), //host,
      or any path that is not an upload) shows as a link to it, named by its alt text
      or the address, and the reader's browser makes no request to that server, so a
      page can no longer tell an outside server who opened it. The same rule covers the
      editor preview, suggestions and drafts, and a page's own picture; the editor's
      picture button now uploads a file. The server refuses to store an outside address
      as a page's picture and drops one from an imported campaign, imported rules text
      turns a picture into a link, and the Content Security Policy allows images from
      the app itself only (img-src 'self'). A page that already holds an outside
      picture shows a link in its place.
    • [bot] The vote reminder pings only the people it names. Its mentions were
      allowed for any user, so a mention typed into a session title or a display
      name could ping that person. It now allows exactly the numeric ids the line
      mentions.
    • [webapp] Signing out is now a POST, so a link, image or prefetch to /auth/logout can no longer log a reader out; the old GET address just sends them home (#1163).
    • [bot] Text a person wrote can no longer be read as a mention, a link or
      markdown in a post, by structure rather than by escaping.
      The vote reminder
      names the session by number and counts the people who have not linked Discord
      instead of naming them. Names of people in a post (who is in, who added or
      cancelled, a quoted speaker) keep only letters, digits, spaces and ' - ., up
      to 40 characters. The "Open in Quest Board" link is built only from a URL that
      passes the same check as the RSVP nudge's, and is left off otherwise. The
      GM's own prose (the approved summary, the recap, a cancel reason, reveal and
      handout text) is trusted and keeps its markdown; see docs/INTEGRATIONS.md.
    Downloads