• v4.8.0-rc1 1124bbfc0a

    v4.8.0-rc1
    All checks were successful
    CI / Does this change touch the recording path? (push) Successful in 34s
    CI / Backend lint (ruff) (push) Successful in 36s
    CI / Which journeys does this change need? (push) Successful in 35s
    CI / Bot/backend version sync (push) Successful in 58s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 59s
    Release / Create Forgejo release (push) Successful in 25s
    CI / Bot tests and audit (push) Successful in 2m18s
    CI / Feedback site tests (push) Successful in 4m7s
    Release / Build and push versioned images (push) Successful in 6m6s
    CI / Frontend (shard 2/2) (push) Successful in 9m31s
    CI / Docker image build (push) Successful in 10m40s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 8m15s
    CI / Frontend (shard 1/2) (push) Successful in 11m54s
    CI / End-to-end journeys (phone) (push) Successful in 26m33s
    CI / Backend migration, tests, and audit (push) Successful in 39m1s
    CI / End-to-end journeys (desktop) (push) Successful in 28m38s
    CI / End-to-end journeys (keyboard) (push) Successful in 29m5s
    Pre-release

    claude-bot released this 2026-11-07 16:32:38 +00:00 | 10 commits to main since this release

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

    Added

    • [webapp] The weekly email digest. Turn on "Weekly digest" in Settings → Notifications (it appears wherever the instance can send mail, and starts off), pick the day of the week, and once a week, at 8am in your own time zone on that day, you get one email across all your live campaigns: the sessions coming up in the next seven days with your answer, the sessions played in the last seven days, and the time votes still waiting for you. It lists titles and links only, never a summary or transcript, skips archived, deleted and muted campaigns, and is not sent in a week with nothing to say. Every digest ends with a "Stop these emails" link, and carries the one-click unsubscribe headers so a mail app's own Unsubscribe button turns it off (#11).
    • [repo] The feedback site has a company-rating page at /companies. It rolls a
      whole business offline from reviewed recipes (23 trades, about 140 jobs, with the
      names from the name generator); testers mark each trade up or down and leave notes,
      and the admin readout tallies them. Slice 1 of "Fill it in" (#1328): the recipes only,
      nothing in Quest Board changes yet.
    • [webapp] Convert a sheet saved for another game into the campaign's, as a draft (#958, IS-424, D5).
      A sheet whose system is not the campaign's says so (to the GM and the
      character's player) and offers "Convert into , as a draft" or "Keep it
      as it is"; with no AI set up the button says AI is needed. The campaign's own
      AI reads the old sheet and the new definition and proposes values and
      feats, spells and items, each with how sure it was and why, which the person
      reviews, edits or leaves out before one confirm. Every value is held to the
      new definition (type, range, choices); what does not fit, and what nothing
      took, is listed as not converted with the reason; entries are matched by name
      to the new system's rules or kept as text. Nothing is saved until the
      confirm, and the model is not asked again then. Converting moves the page's
      sheet to the new definition in place and keeps the old sheet in its history
      (versions now carry their game system, and one written for the old game can't
      be put back onto the new). A GM's locks hold: a player can't convert a sheet
      whose locks the new sheet can't keep, and a GM's original locks are recorded.
      Migration d958c0a1b2c3 adds character_sheet_versions.system_id
      (backfilled). BOT_CONTRACT_VERSION is unchanged.
    • [webapp] Import a Pathfinder 2e character from Pathbuilder. "Import from Pathbuilder…" is in a character sheet's More menu, and beside "Make a sheet" on a player character's page that has none. Give the six-digit code from Pathbuilder (Menu, Export, Export JSON), paste its JSON or choose a .json file, or upload the character sheet PDF it fills in. A preview comes first and saves nothing: what will happen, what was not read and why, which feats, items and spells matched the loaded content, and where Pathbuilder's numbers differ from Quest Board's. Replacing a sheet that came from somewhere else asks first. Your browser fetches the code from pathbuilder2e.com, so the web server's connect-src now allows that one origin (#958).
    • [webapp] Pathbuilder import, the backend: a Pathfinder 2e character comes in from Pathbuilder's build JSON or its filled character-sheet PDF (#958, D82).
      A preview first, with a report of what was read and what was not (and the
      numbers Pathbuilder worked out held against Quest Board's); then one confirm
      makes the page's sheet or, when the provenance matches, updates the same one.
      The mapping is the pack's imports.pathbuilder map in the Pathfinder 2e
      definition. Feats, items and spells are matched to the campaign's rules by
      name and kept as text when not found. The server never calls Pathbuilder
      (its site blocks servers): the browser fetches the JSON and posts it. The
      PDF's form fields are read, never rendered, off the event loop, with size,
      page and field limits. The page that uses it follows.
    • [webapp] Character sheets: Update to version n, with what changes (#958, D83). A sheet made under an older version of its system's definition keeps reading as it is; when a newer version exists and ships a migration, the GM sees "Saved against version 1 of the Pathfinder 2e sheet" with Update to version 2 and What changes (what is renamed, moved, filled in, dropped or worked out, from and to). Nothing happens until the GM confirms, and it is the GM's alone. The sheet as it was is kept as a version first, so a dropped value is recoverable and the update can be put back from Versions (which also puts the old definition back). A definition's migrations are now typed and checked when a pack is uploaded: rename, move, default, drop and compute (the formula language, no dice), with paths checked against the version they start from and the one they arrive at; chains (v1 to v2 to v3) run in order. Every built-in definition is still version 1, so nothing offers an update yet.

    Changed

    • [webapp] The recording panel and Table mode now share one live recording stream per session in a tab, instead of each opening its own; it closes when the last view of the session goes away (#1247).
    • [repo] The secret scanner no longer reports 34 known-false findings (#132): rules-pack record keys, model names beside a provider host, and the invented Discord ids and dummy key in tests are allowlisted by exact value shape in .gitleaks.toml, each with its reason, so a real finding stands out.
    • [webapp] A new version is picked up without pressing Reload, at a safe moment. When one is waiting, Quest Board applies it on your next move to another page, when the tab is hidden, or after two minutes with no input, but never with anything unsaved, while offline, on the table or on the page of a session being played. Afterwards a short notice says "Quest Board was updated." The Reload banner now appears only if the update has waited half an hour (#1341).
    • [webapp] Suggested wiki fact changes also ask about pages the transcript names, not only pages linked to the session (#1342): every live, non-proposed page of the campaign whose title or alias is spoken in the session (and whose type has facts to suggest) is now asked about, after the linked ones and the most-named first, up to 12 a session; the rest are counted as skipped_cap. Titles shorter than three characters and small words are never used to find a page. The backfill task takes force_zero_asked to re-run sessions that asked about nothing before.
    • [repo] The backend image no longer downloads the PostgreSQL apt signing key at build time. The PGDG key is committed at docker/keys/pgdg.asc (fingerprint recorded in the Dockerfile and checked by a test) and copied in, so a build no longer depends on postgresql.org being reachable. apt still verifies the repository against it. (#1346)
    • [docs] D341 records where a sheet conversion is reviewed: in the dialog, not Suggested wiki updates (#958).

    Removed

    • [webapp] Cleanup after the built-in name generator: the old AI names code is gone, and names runs nothing used are kept for a day, not 30 days. The model-written names function and its prompts were no longer reached by any route; they are removed (Describe, the one line a model writes under a name, is unchanged). The daily sweep now deletes a names run older than 24 hours unless it is in a prep sheet or For later, or one of its names is now a page in the wiki. The campaign's memory of names already offered is untouched, so a name is still never offered twice.

    Fixed

    • [webapp] Settings → Notifications no longer skips a heading level (#1360): "Per campaign" is now a second-level heading under the page title, so screen readers navigate the page in order. It looks the same.
    Downloads
  • v4.7.1 e88538adb1

    v4.7.1
    All checks were successful
    CI / Backend lint (ruff) (push) Successful in 36s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 2m32s
    CI / Feedback site tests (push) Successful in 55s
    CI / Bot tests and audit (push) Successful in 2m14s
    CI / Bot/backend version sync (push) Successful in 53s
    CI / Does this change touch the recording path? (push) Successful in 22s
    Release / Create Forgejo release (push) Successful in 19s
    CI / Which journeys does this change need? (push) Successful in 23s
    Release / Build and push versioned images (push) Successful in 3m28s
    CI / Frontend (shard 2/2) (push) Successful in 10m0s
    CI / Docker image build (push) Successful in 9m9s
    CI / Frontend (shard 1/2) (push) Successful in 23m25s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 9m4s
    CI / Backend migration, tests, and audit (push) Successful in 47m57s
    CI / End-to-end journeys (phone) (push) Successful in 12m31s
    CI / End-to-end journeys (keyboard) (push) Successful in 23m14s
    CI / End-to-end journeys (desktop) (push) Successful in 24m31s
    Stable

    claude-bot released this 2026-11-06 08:02:42 +00:00 | 78 commits to main since this release

    One security fix for the bot, found by scanning the published 4.7.0 images.
    No migration, and BOT_CONTRACT_VERSION is unchanged (2). To upgrade from
    4.7.0: make backup-now, git checkout v4.7.1 && docker compose pull && docker compose up -d. Tried on the dev server as 4.7.1-rc1.

    Security

    • [bot] The bot's certificate bundle is updated (certifi 2025.11.12 → 2026.10.23, GHSA-6q48-6c7w-9gw2): the old bundle trusted emSign root CAs whose intermediate keys were compromised. The backend and frontend images were already clean (#1343).
    Downloads
  • v4.7.1-rc1 dce6006ee8

    v4.7.1-rc1
    All checks were successful
    CI / Docker image build (push) Successful in 25s
    CI / Bot/backend version sync (push) Successful in 34s
    CI / Backend lint (ruff) (push) Successful in 38s
    CI / Does this change touch the recording path? (push) Successful in 16s
    Release / Create Forgejo release (push) Successful in 12s
    CI / Which journeys does this change need? (push) Successful in 15s
    CI / Feedback site tests (push) Successful in 54s
    Release / Build and push versioned images (push) Successful in 44s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 2m4s
    CI / Bot tests and audit (push) Successful in 3m59s
    CI / Frontend (shard 1/2) (push) Successful in 9m48s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 8m59s
    CI / End-to-end journeys (keyboard) (push) Successful in 12m8s
    CI / End-to-end journeys (desktop) (push) Successful in 12m11s
    CI / Frontend (shard 2/2) (push) Successful in 15m47s
    CI / End-to-end journeys (phone) (push) Successful in 23m29s
    CI / Backend migration, tests, and audit (push) Successful in 1h10m51s
    Pre-release

    claude-bot released this 2026-11-06 03:44:27 +00:00 | 80 commits to main since this release

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

    Security

    • [bot] The bot's certificate bundle is updated (certifi 2025.11.12 → 2026.10.23, GHSA-6q48-6c7w-9gw2): the old bundle trusted emSign root CAs whose intermediate keys were compromised. The backend and frontend images were already clean (#1343).
    Downloads
  • v4.7.0 d01ef4ee2f

    v4.7.0
    All checks were successful
    CI / Backend lint (ruff) (push) Successful in 35s
    CI / Does this change touch the recording path? (push) Successful in 17s
    CI / Feedback site tests (push) Successful in 53s
    CI / Which journeys does this change need? (push) Successful in 18s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 1m11s
    CI / Bot/backend version sync (push) Successful in 1m7s
    CI / Bot tests and audit (push) Successful in 2m18s
    Release / Create Forgejo release (push) Successful in 17s
    Release / Build and push versioned images (push) Successful in 3m28s
    CI / Frontend (shard 2/2) (push) Successful in 9m57s
    CI / Frontend (shard 1/2) (push) Successful in 11m25s
    CI / Docker image build (push) Successful in 11m26s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 8m44s
    CI / End-to-end journeys (keyboard) (push) Successful in 11m56s
    CI / End-to-end journeys (phone) (push) Successful in 11m39s
    CI / End-to-end journeys (desktop) (push) Successful in 12m23s
    CI / Backend migration, tests, and audit (push) Successful in 40m44s
    Stable

    claude-bot released this 2026-11-05 22:50:57 +00:00 | 86 commits to main since this release

    Browse the Rules. The loaded rules content can be browsed, not only searched:
    by kind in a campaign, in a library per game system, with each system's own
    filters, and in place where it is used (the sheet's pickers, Prep's drawer and
    Find at the table). Also: copies of a creature on the shelf, the wiki page's
    infobox with suggested and known facts, facts the session may have changed
    (suggested by the campaign's own AI, for the GM to use), names without the AI
    in Prep, PostgreSQL 18, and the fixes and dependency updates since 4.6.2.
    Tried on the dev server as 4.7.0-rc1 to -rc4.

    Upgrading from 4.6.2

    • PostgreSQL 16 to 18, once, before starting 4.7.0 (#1245): make backup-now, copy the newest dump and .env off the host, git checkout v4.7.0 && docker compose pull, then make pg-upgrade and read the per-table
      counts it prints, then docker compose up -d. The pg-upgrade-guard service
      stops the stack until the upgrade has been done. See OPERATIONS.md,
      "Upgrading PostgreSQL 16 → 18".
    • Five database migrations (b1242c0d1e2f, d1321c0e5a7b, e1279a0b1c2d,
      f1280a0b1c2d, a1257b0c1d2e), applied by migrate on up.
    • BOT_CONTRACT_VERSION is unchanged (2): the bot and backend images can
      be upgraded one at a time.
    • Optional, once, after the upgrade: suggest facts for the sessions already
      processed: docker compose exec -T worker celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill. Nothing is shown to
      players, and nothing is changed until a GM uses a suggestion.

    Added

    • [webapp] Browse the rules: the reference content can be read through, not only searched (#1240).
      The rules page lists the kinds of record a game system has (spells, creatures, feats, equipment and so on,
      in the system's order, each with how many there are), and opens a kind as an A-Z list in numbered pages that
      reaches every record, narrowed by level, by pack and by a search. Only what the reader may already see is
      listed or counted: disabled packs, other campaigns' packs and, for players, GM-only records are left out.
    • [webapp] The rules library: anyone signed in can browse the rules loaded on this server, outside any campaign. GET /api/reference/library lists each game system that has an active server pack with records, and the existing kinds, page and entry routes, called with no campaign, give a server-wide view of server packs only (never a campaign's). GM-only records show to admins and to anyone who runs a live campaign of that game system; everyone else's counts leave them out. An admin can browse one pack, switched off or not, GM-only records included, at /api/admin/content-packs/{id}/browse/kinds, /page and /{entry_id}.
    • [webapp] Browse the rules by each game system's own filters. A PF2e spell has tradition, rank, cantrip, focus and rarity; a creature size, traits and rarity; an item price (in gp), Bulk, category, traits and rarity; a feat type and class or ancestry. A 5e spell has school, class, ritual and concentration; a creature CR, type and size; an item category (rarity and attunement appear once a pack has them). Each choice shows how many records it would leave, counted with the other filters applied, and a kind can sort by level, rank, price or CR as well as A-Z. The filters are declared per system in app/sheets/builtin/<system>/browse.json, not in code. GET /api/game-systems/{key}/reference/filters (and the admin pack's /browse/filters) returns them with their counts, and /reference/page takes f_<key> and sort. A new reference_entries.facets column holds the filter values; the worker fills it at start after the upgrade, with no manual step (python -m app.cli refill_facets does the same by hand). Migration b1242c0d1e2f.
    • [webapp] Table mode's Find can browse the rules (#1243). "Browse the rules ›" in Find opens the kinds and a kind's list inside the same dialog; choosing a row opens it in the table's preview, where it can be added to the shelf. "‹ Back to the search" and Escape return to the search as it was.
    • [webapp] Prep's Rules & bestiary drawer can browse. A Search | Browse switch shows the campaign's kinds with counts, then a kind as a filtered, paged list; each row previews and inserts like a search result. Creatures open at the party's range (PF2e level -2 to +3, 5e CR a quarter of the level to level + 2), taken from the active player characters' sheet levels. (#1243)
    • [bot] The "Played" post in the campaign's Discord channel now asks the
      players for one star and one wish (#1255).
      When a session completes and
      Quest Board is asking anyone about it, the post the bot already makes gains a
      line asking for a star and a wish, saying when the ask closes, and its link
      goes to Home, where they are answered. It is one line in a post that goes out
      anyway, once: the channel is asked, not each player, and nothing brings it
      back. Nothing about the session's content reaches Discord.
    • [webapp] The Names drawer in Prep draws names from a built-in generator instead of asking an AI. Forty-four styles (the peoples, places by size and kind, groups, ships, items, books, battles and festivals), ten names at a time, free and instant, in a campaign with no AI as well. A campaign is never offered the same name twice, and never a name its wiki already holds; a style that names a place can use one you give it. Describe, the one line under a name, still asks the campaign's AI and is offered only when there is one. The ready-made name prefetch and its Redis cache are gone. (#1257)
    • [webapp] A wiki page's margin is one box with suggested facts. A page starts with the facts its type usually has in the campaign's system (Pathfinder 2e, D&D 5e or Generic), empty and never shown until filled; where Quest Board already knows a value (First met and Last seen from the sessions, a character's player and class from the campaign and the sheet, a place's region from its map, a creature's level from the rules entry it is based on) readers see it until a GM types their own. A page can show a map as its picture, with a caption, and a GM can remove a suggested fact for good. New columns lore_entries.image_caption, .image_map_id and .facts_dismissed (one migration); GET /api/campaigns/{id}/wiki/suggested-facts; the article read gains image_caption, image_map and facts. The campaign file carries the caption, the removed facts and the page's map.
    • [webapp] Facts the session may have changed. After a session is summarised, the campaign's own LLM is asked, page by page, whether a status, ruler, leader, owner or allegiance changed. Each suggestion carries a quote found in the transcript that itself says the change (it names the new value and a word for it, such as "rules", "owns" or "died"), with the speaker and time of the line it came from, and waits for a GM to use it, change it or turn it down; nothing is shown to players. New table fact_suggestions (migration f1280a0b1c2d) and a task to run once after the upgrade for sessions already processed: celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill.
    • [webapp] Two of the same creature can be on a session's shelf (#1321). Adding a rules entry that is already there with another: true makes a numbered copy with its own HP; a pin carries the first card forward and copies stay with their session. Cards carry copy_number, and a rules card copies.
    • [repo] The synthetic-session harness runs leg 3 in one command, and can
      send its vocabulary as hotwords as well as initial_prompt (#506).

      python -m evals.synthetic_session summary-leg goes audio, ASR, merged
      transcript, LLM summary, scores through the same summarisation and scoring
      code leg 2 uses, and reports leg 3 beside leg 2 (--baseline); --provider stub runs it offline. asr-leg and summary-leg take
      --vocabulary-mechanism {initial_prompt,hotwords,both}, which sets the
      product's own QB_ASR_VOCABULARY_STYLE switch for the run without touching
      the provider code. The README records the measured matrix.

    Changed

    • [webapp] New campaign → "The setup of one of yours" now opens the full copy dialog.
      It is the same one as Settings → Archive → "Start a new campaign from this one":
      choose the pages, maps, lines and veils, packs and people to take, with the
      scheduling and reminder settings always copied. The old settings-only copy
      on POST /api/campaigns (template: {"from_campaign_id": …}) is removed.
    • [webapp] The character sheet's pickers open on what fits the character.
      GET /api/campaigns/{cid}/sheets/{sid}/picker and .../options now answer
      fits (the browse filters that match the character at that step, declared as
      fits rules in each system's browse.json), fits_label, filters, sorts
      and total, and take stage 3's f_<key>, sort, page, per_page and
      all=1; with any of those the "belongs to this character" narrowing is
      skipped. .../options now answers {results, …} instead of a bare list.
    • [repo] PostgreSQL moves from 16 to 18, and an existing install must run
      make pg-upgrade before it starts the new release.
      The 18 image keeps its
      data in a new place, so the database lives in a new volume
      (postgres18_data) and the old one cannot simply be reused: pointing 18 at
      it would start an empty database. make pg-upgrade (from the new release's
      checkout, before docker compose up -d) backs up, dumps the old database
      with its own PostgreSQL 16 tools, restores it into the new volume, and checks
      every table's row count and the migration revision. It never removes the old
      volume, which stays as the rollback and archive; the pre-upgrade dump is
      written to ./backups. A new pg-upgrade-guard service stops the stack, with
      a message naming make pg-upgrade, if the old volume holds a database and the
      new one is empty, so a plain docker compose up -d cannot give you an empty
      database. A fresh install and an already-upgraded install start as usual. The
      backend image's pg_dump client moves to 18 with it, so make migrate-safe
      refuses to back up against a 16 server until the upgrade is done. Renovate
      now sees the PG_MAJOR in the Dockerfile and moves every PostgreSQL pin in
      one pull request. See docs/OPERATIONS.md, "Upgrading PostgreSQL 16 to 18"
      (#1245, supersedes #627).
    • [webapp] Campaign names reach the transcriber as hotwords by default. On the bundled WhisperX server, the campaign's vocabulary list now goes in hotwords rather than initial_prompt when the server declares support; on a 100-name list this got 89–90% of names right against 80–82% (#506). A server without hotwords gets the prompt, as before, quietly. Set QB_ASR_VOCABULARY_STYLE=initial_prompt to keep the old behaviour.
    • [repo] Dependencies brought up to date for 4.7.0: React and React DOM 19.3.1, authlib 1.9.1, eslint 10.13.0, @axe-core/playwright 4.14.0 (with the accessible-name fixes its stricter check found, #1244), and Mailpit 1.32 in the end-to-end stack.
    • [webapp] [bot] "GM only" is now "GMs only" everywhere it is shown. The
      wiki's front page and recent changes, the campaign search, a hidden page's
      screen-reader label, the table's Find, character sheets, Prep's lookups,
      the copy dialog, content-pack visibility and the bot's /help all say it
      the way the map and the rules already did (owner's decision, 30 Oct).

    Fixed

    • [webapp] Two reads no longer write. The character-creations list
      ("Carry on with ...") only hides creations left untouched for 30 days instead
      of deleting them while it lists, and a page's picture stored publicly before
      #894 is served where it is instead of being moved on read. A daily
      background task now deletes the stale creations, and another moves the old
      pictures into the private store; both are safe to repeat. The only
      remaining GETs that write are the two sign-in callbacks and the content-pack
      exports (#1206).
    • [webapp] Screen readers hear what is on screen. Prep's "File it" / "Move" button and the storyline's chapter links now have accessible names that contain their visible words, which axe 4.14's label-content-name-mismatch rule checks (#1229). The scene button reads "File it in a scene: …" or "Move to a scene: …".
    • [webapp] Fetching a starter content pack no longer freezes the server, and a busy server no longer signs you out. Reading and normalising a pack ran on the web server's event loop, so fetching two packs at once could miss the health check and get both workers restarted; the work now runs in a thread. The sign-in check also treats only a 401 as "signed out": an error or dropped connection from /api/me keeps you waiting and retries instead of showing the sign-in page. (#1239)
    • [webapp] Prep → Names: a long "In the wiki as …" after saving no longer squeezes the name into one letter a line. The name keeps at least 45% of the row, and the receipt wraps in its own column.
    • [webapp] Admin → Content packs reads properly. A pack's contents say "9 ancestries", not "ancestrys"; its version no longer gains a stray "v" ("pf2e-8.6.1", not "vpf2e-8.6.1"); the licence's terms link is underlined, not told apart by colour alone; and on a phone the app bar's home link is named by the "QB" it shows (#1302).
    • [webapp] A moment without Redis now answers "try again" (503), not a server error (#1326). Found by the live checks (#1246): a Redis restart turned signed-in requests into 500s for a fraction of a second. Nobody was signed out then, and nobody is now. The manual healthcheck checklist in OPERATIONS.md also had a worker step that couldn't work; it now stops the process from the host.
    Downloads
  • v4.7.0-rc4 4cdc5cfbd7

    v4.7.0-rc4
    All checks were successful
    CI / Does this change touch the recording path? (push) Successful in 32s
    Release / Create Forgejo release (push) Successful in 15s
    CI / Which journeys does this change need? (push) Successful in 20s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 57s
    CI / Bot/backend version sync (push) Successful in 59s
    CI / Backend lint (ruff) (push) Successful in 1m3s
    CI / Feedback site tests (push) Successful in 1m51s
    Release / Build and push versioned images (push) Successful in 3m33s
    CI / Bot tests and audit (push) Successful in 4m7s
    CI / Docker image build (push) Successful in 9m12s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 8m52s
    CI / Frontend (shard 2/2) (push) Successful in 9m53s
    CI / Frontend (shard 1/2) (push) Successful in 11m32s
    CI / End-to-end journeys (phone) (push) Successful in 11m36s
    CI / End-to-end journeys (keyboard) (push) Successful in 12m37s
    CI / End-to-end journeys (desktop) (push) Successful in 29m37s
    CI / Backend migration, tests, and audit (push) Successful in 46m4s
    Pre-release

    claude-bot released this 2026-11-05 21:46:26 +00:00 | 88 commits to main since this release

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

    Added

    • [webapp] Browse the rules: the reference content can be read through, not only searched (#1240).
      The rules page lists the kinds of record a game system has (spells, creatures, feats, equipment and so on,
      in the system's order, each with how many there are), and opens a kind as an A-Z list in numbered pages that
      reaches every record, narrowed by level, by pack and by a search. Only what the reader may already see is
      listed or counted: disabled packs, other campaigns' packs and, for players, GM-only records are left out.
    • [webapp] The rules library: anyone signed in can browse the rules loaded on this server, outside any campaign. GET /api/reference/library lists each game system that has an active server pack with records, and the existing kinds, page and entry routes, called with no campaign, give a server-wide view of server packs only (never a campaign's). GM-only records show to admins and to anyone who runs a live campaign of that game system; everyone else's counts leave them out. An admin can browse one pack, switched off or not, GM-only records included, at /api/admin/content-packs/{id}/browse/kinds, /page and /{entry_id}.
    • [webapp] Browse the rules by each game system's own filters. A PF2e spell has tradition, rank, cantrip, focus and rarity; a creature size, traits and rarity; an item price (in gp), Bulk, category, traits and rarity; a feat type and class or ancestry. A 5e spell has school, class, ritual and concentration; a creature CR, type and size; an item category (rarity and attunement appear once a pack has them). Each choice shows how many records it would leave, counted with the other filters applied, and a kind can sort by level, rank, price or CR as well as A-Z. The filters are declared per system in app/sheets/builtin/<system>/browse.json, not in code. GET /api/game-systems/{key}/reference/filters (and the admin pack's /browse/filters) returns them with their counts, and /reference/page takes f_<key> and sort. A new reference_entries.facets column holds the filter values; the worker fills it at start after the upgrade, with no manual step (python -m app.cli refill_facets does the same by hand). Migration b1242c0d1e2f.
    • [webapp] Table mode's Find can browse the rules (#1243). "Browse the rules ›" in Find opens the kinds and a kind's list inside the same dialog; choosing a row opens it in the table's preview, where it can be added to the shelf. "‹ Back to the search" and Escape return to the search as it was.
    • [webapp] Prep's Rules & bestiary drawer can browse. A Search | Browse switch shows the campaign's kinds with counts, then a kind as a filtered, paged list; each row previews and inserts like a search result. Creatures open at the party's range (PF2e level -2 to +3, 5e CR a quarter of the level to level + 2), taken from the active player characters' sheet levels. (#1243)
    • [bot] The "Played" post in the campaign's Discord channel now asks the
      players for one star and one wish (#1255).
      When a session completes and
      Quest Board is asking anyone about it, the post the bot already makes gains a
      line asking for a star and a wish, saying when the ask closes, and its link
      goes to Home, where they are answered. It is one line in a post that goes out
      anyway, once: the channel is asked, not each player, and nothing brings it
      back. Nothing about the session's content reaches Discord.
    • [webapp] The Names drawer in Prep draws names from a built-in generator instead of asking an AI. Forty-four styles (the peoples, places by size and kind, groups, ships, items, books, battles and festivals), ten names at a time, free and instant, in a campaign with no AI as well. A campaign is never offered the same name twice, and never a name its wiki already holds; a style that names a place can use one you give it. Describe, the one line under a name, still asks the campaign's AI and is offered only when there is one. The ready-made name prefetch and its Redis cache are gone. (#1257)
    • [webapp] A wiki page's margin is one box with suggested facts. A page starts with the facts its type usually has in the campaign's system (Pathfinder 2e, D&D 5e or Generic), empty and never shown until filled; where Quest Board already knows a value (First met and Last seen from the sessions, a character's player and class from the campaign and the sheet, a place's region from its map, a creature's level from the rules entry it is based on) readers see it until a GM types their own. A page can show a map as its picture, with a caption, and a GM can remove a suggested fact for good. New columns lore_entries.image_caption, .image_map_id and .facts_dismissed (one migration); GET /api/campaigns/{id}/wiki/suggested-facts; the article read gains image_caption, image_map and facts. The campaign file carries the caption, the removed facts and the page's map.
    • [webapp] Facts the session may have changed. After a session is summarised, the campaign's own LLM is asked, page by page, whether a status, ruler, leader, owner or allegiance changed. Each suggestion carries a quote found in the transcript that itself says the change (it names the new value and a word for it, such as "rules", "owns" or "died"), with the speaker and time of the line it came from, and waits for a GM to use it, change it or turn it down; nothing is shown to players. New table fact_suggestions (migration f1280a0b1c2d) and a task to run once after the upgrade for sessions already processed: celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill.
    • [webapp] Two of the same creature can be on a session's shelf (#1321). Adding a rules entry that is already there with another: true makes a numbered copy with its own HP; a pin carries the first card forward and copies stay with their session. Cards carry copy_number, and a rules card copies.
    • [repo] The synthetic-session harness runs leg 3 in one command, and can
      send its vocabulary as hotwords as well as initial_prompt (#506).

      python -m evals.synthetic_session summary-leg goes audio, ASR, merged
      transcript, LLM summary, scores through the same summarisation and scoring
      code leg 2 uses, and reports leg 3 beside leg 2 (--baseline); --provider stub runs it offline. asr-leg and summary-leg take
      --vocabulary-mechanism {initial_prompt,hotwords,both}, which sets the
      product's own QB_ASR_VOCABULARY_STYLE switch for the run without touching
      the provider code. The README records the measured matrix.

    Changed

    • [webapp] New campaign → "The setup of one of yours" now opens the full copy dialog.
      It is the same one as Settings → Archive → "Start a new campaign from this one":
      choose the pages, maps, lines and veils, packs and people to take, with the
      scheduling and reminder settings always copied. The old settings-only copy
      on POST /api/campaigns (template: {"from_campaign_id": …}) is removed.
    • [webapp] The character sheet's pickers open on what fits the character.
      GET /api/campaigns/{cid}/sheets/{sid}/picker and .../options now answer
      fits (the browse filters that match the character at that step, declared as
      fits rules in each system's browse.json), fits_label, filters, sorts
      and total, and take stage 3's f_<key>, sort, page, per_page and
      all=1; with any of those the "belongs to this character" narrowing is
      skipped. .../options now answers {results, …} instead of a bare list.
    • [repo] PostgreSQL moves from 16 to 18, and an existing install must run
      make pg-upgrade before it starts the new release.
      The 18 image keeps its
      data in a new place, so the database lives in a new volume
      (postgres18_data) and the old one cannot simply be reused: pointing 18 at
      it would start an empty database. make pg-upgrade (from the new release's
      checkout, before docker compose up -d) backs up, dumps the old database
      with its own PostgreSQL 16 tools, restores it into the new volume, and checks
      every table's row count and the migration revision. It never removes the old
      volume, which stays as the rollback and archive; the pre-upgrade dump is
      written to ./backups. A new pg-upgrade-guard service stops the stack, with
      a message naming make pg-upgrade, if the old volume holds a database and the
      new one is empty, so a plain docker compose up -d cannot give you an empty
      database. A fresh install and an already-upgraded install start as usual. The
      backend image's pg_dump client moves to 18 with it, so make migrate-safe
      refuses to back up against a 16 server until the upgrade is done. Renovate
      now sees the PG_MAJOR in the Dockerfile and moves every PostgreSQL pin in
      one pull request. See docs/OPERATIONS.md, "Upgrading PostgreSQL 16 to 18"
      (#1245, supersedes #627).
    • [webapp] Campaign names reach the transcriber as hotwords by default. On the bundled WhisperX server, the campaign's vocabulary list now goes in hotwords rather than initial_prompt when the server declares support; on a 100-name list this got 89–90% of names right against 80–82% (#506). A server without hotwords gets the prompt, as before, quietly. Set QB_ASR_VOCABULARY_STYLE=initial_prompt to keep the old behaviour.
    • [repo] Dependencies brought up to date for 4.7.0: React and React DOM 19.3.1, authlib 1.9.1, eslint 10.13.0, @axe-core/playwright 4.14.0 (with the accessible-name fixes its stricter check found, #1244), and Mailpit 1.32 in the end-to-end stack.
    • [webapp] [bot] "GM only" is now "GMs only" everywhere it is shown. The
      wiki's front page and recent changes, the campaign search, a hidden page's
      screen-reader label, the table's Find, character sheets, Prep's lookups,
      the copy dialog, content-pack visibility and the bot's /help all say it
      the way the map and the rules already did (owner's decision, 30 Oct).

    Fixed

    • [webapp] Two reads no longer write. The character-creations list
      ("Carry on with ...") only hides creations left untouched for 30 days instead
      of deleting them while it lists, and a page's picture stored publicly before
      #894 is served where it is instead of being moved on read. A daily
      background task now deletes the stale creations, and another moves the old
      pictures into the private store; both are safe to repeat. The only
      remaining GETs that write are the two sign-in callbacks and the content-pack
      exports (#1206).
    • [webapp] Screen readers hear what is on screen. Prep's "File it" / "Move" button and the storyline's chapter links now have accessible names that contain their visible words, which axe 4.14's label-content-name-mismatch rule checks (#1229). The scene button reads "File it in a scene: …" or "Move to a scene: …".
    • [webapp] Fetching a starter content pack no longer freezes the server, and a busy server no longer signs you out. Reading and normalising a pack ran on the web server's event loop, so fetching two packs at once could miss the health check and get both workers restarted; the work now runs in a thread. The sign-in check also treats only a 401 as "signed out": an error or dropped connection from /api/me keeps you waiting and retries instead of showing the sign-in page. (#1239)
    • [webapp] Prep → Names: a long "In the wiki as …" after saving no longer squeezes the name into one letter a line. The name keeps at least 45% of the row, and the receipt wraps in its own column.
    • [webapp] Admin → Content packs reads properly. A pack's contents say "9 ancestries", not "ancestrys"; its version no longer gains a stray "v" ("pf2e-8.6.1", not "vpf2e-8.6.1"); the licence's terms link is underlined, not told apart by colour alone; and on a phone the app bar's home link is named by the "QB" it shows (#1302).
    • [webapp] A moment without Redis now answers "try again" (503), not a server error (#1326). Found by the live checks (#1246): a Redis restart turned signed-in requests into 500s for a fraction of a second. Nobody was signed out then, and nobody is now. The manual healthcheck checklist in OPERATIONS.md also had a worker step that couldn't work; it now stops the process from the host.
    Downloads
  • v4.7.0-rc3 f50684dc98

    v4.7.0-rc3
    All checks were successful
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 55s
    CI / Bot/backend version sync (push) Successful in 29s
    CI / Backend lint (ruff) (push) Successful in 33s
    CI / Which journeys does this change need? (push) Successful in 21s
    CI / Does this change touch the recording path? (push) Successful in 21s
    Release / Create Forgejo release (push) Successful in 15s
    CI / Feedback site tests (push) Successful in 59s
    CI / Docker image build (push) Successful in 1m4s
    CI / Bot tests and audit (push) Successful in 2m3s
    Release / Build and push versioned images (push) Successful in 1m15s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 8m50s
    CI / End-to-end journeys (keyboard) (push) Successful in 11m41s
    CI / End-to-end journeys (phone) (push) Successful in 12m55s
    CI / End-to-end journeys (desktop) (push) Successful in 13m41s
    CI / Frontend (shard 2/2) (push) Successful in 20m29s
    CI / Frontend (shard 1/2) (push) Successful in 23m2s
    CI / Backend migration, tests, and audit (push) Successful in 34m58s
    Pre-release

    claude-bot released this 2026-11-05 00:53:32 +00:00 | 106 commits to main since this release

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

    Added

    • [webapp] Browse the rules: the reference content can be read through, not only searched (#1240).
      The rules page lists the kinds of record a game system has (spells, creatures, feats, equipment and so on,
      in the system's order, each with how many there are), and opens a kind as an A-Z list in numbered pages that
      reaches every record, narrowed by level, by pack and by a search. Only what the reader may already see is
      listed or counted: disabled packs, other campaigns' packs and, for players, GM-only records are left out.
    • [webapp] The rules library: anyone signed in can browse the rules loaded on this server, outside any campaign. GET /api/reference/library lists each game system that has an active server pack with records, and the existing kinds, page and entry routes, called with no campaign, give a server-wide view of server packs only (never a campaign's). GM-only records show to admins and to anyone who runs a live campaign of that game system; everyone else's counts leave them out. An admin can browse one pack, switched off or not, GM-only records included, at /api/admin/content-packs/{id}/browse/kinds, /page and /{entry_id}.
    • [webapp] Browse the rules by each game system's own filters. A PF2e spell has tradition, rank, cantrip, focus and rarity; a creature size, traits and rarity; an item price (in gp), Bulk, category, traits and rarity; a feat type and class or ancestry. A 5e spell has school, class, ritual and concentration; a creature CR, type and size; an item category (rarity and attunement appear once a pack has them). Each choice shows how many records it would leave, counted with the other filters applied, and a kind can sort by level, rank, price or CR as well as A-Z. The filters are declared per system in app/sheets/builtin/<system>/browse.json, not in code. GET /api/game-systems/{key}/reference/filters (and the admin pack's /browse/filters) returns them with their counts, and /reference/page takes f_<key> and sort. A new reference_entries.facets column holds the filter values; the worker fills it at start after the upgrade, with no manual step (python -m app.cli refill_facets does the same by hand). Migration b1242c0d1e2f.
    • [webapp] Table mode's Find can browse the rules (#1243). "Browse the rules ›" in Find opens the kinds and a kind's list inside the same dialog; choosing a row opens it in the table's preview, where it can be added to the shelf. "‹ Back to the search" and Escape return to the search as it was.
    • [webapp] Prep's Rules & bestiary drawer can browse. A Search | Browse switch shows the campaign's kinds with counts, then a kind as a filtered, paged list; each row previews and inserts like a search result. Creatures open at the party's range (PF2e level -2 to +3, 5e CR a quarter of the level to level + 2), taken from the active player characters' sheet levels. (#1243)
    • [bot] The "Played" post in the campaign's Discord channel now asks the
      players for one star and one wish (#1255).
      When a session completes and
      Quest Board is asking anyone about it, the post the bot already makes gains a
      line asking for a star and a wish, saying when the ask closes, and its link
      goes to Home, where they are answered. It is one line in a post that goes out
      anyway, once: the channel is asked, not each player, and nothing brings it
      back. Nothing about the session's content reaches Discord.
    • [webapp] The Names drawer in Prep draws names from a built-in generator instead of asking an AI. Forty-four styles (the peoples, places by size and kind, groups, ships, items, books, battles and festivals), ten names at a time, free and instant, in a campaign with no AI as well. A campaign is never offered the same name twice, and never a name its wiki already holds; a style that names a place can use one you give it. Describe, the one line under a name, still asks the campaign's AI and is offered only when there is one. The ready-made name prefetch and its Redis cache are gone. (#1257)
    • [webapp] A wiki page's margin is one box with suggested facts. A page starts with the facts its type usually has in the campaign's system (Pathfinder 2e, D&D 5e or Generic), empty and never shown until filled; where Quest Board already knows a value (First met and Last seen from the sessions, a character's player and class from the campaign and the sheet, a place's region from its map, a creature's level from the rules entry it is based on) readers see it until a GM types their own. A page can show a map as its picture, with a caption, and a GM can remove a suggested fact for good. New columns lore_entries.image_caption, .image_map_id and .facts_dismissed (one migration); GET /api/campaigns/{id}/wiki/suggested-facts; the article read gains image_caption, image_map and facts. The campaign file carries the caption, the removed facts and the page's map.
    • [webapp] Facts the session may have changed. After a session is summarised, the campaign's own LLM is asked, page by page, whether a status, ruler, leader, owner or allegiance changed. Each suggestion carries a quote found in the transcript that itself says the change (it names the new value and a word for it, such as "rules", "owns" or "died"), with the speaker and time of the line it came from, and waits for a GM to use it, change it or turn it down; nothing is shown to players. New table fact_suggestions (migration f1280a0b1c2d) and a task to run once after the upgrade for sessions already processed: celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill.
    • [webapp] Two of the same creature can be on a session's shelf (#1321). Adding a rules entry that is already there with another: true makes a numbered copy with its own HP; a pin carries the first card forward and copies stay with their session. Cards carry copy_number, and a rules card copies.
    • [repo] The synthetic-session harness runs leg 3 in one command, and can
      send its vocabulary as hotwords as well as initial_prompt (#506).

      python -m evals.synthetic_session summary-leg goes audio, ASR, merged
      transcript, LLM summary, scores through the same summarisation and scoring
      code leg 2 uses, and reports leg 3 beside leg 2 (--baseline); --provider stub runs it offline. asr-leg and summary-leg take
      --vocabulary-mechanism {initial_prompt,hotwords,both}, which sets the
      product's own QB_ASR_VOCABULARY_STYLE switch for the run without touching
      the provider code. The README records the measured matrix.

    Changed

    • [webapp] New campaign → "The setup of one of yours" now opens the full copy dialog.
      It is the same one as Settings → Archive → "Start a new campaign from this one":
      choose the pages, maps, lines and veils, packs and people to take, with the
      scheduling and reminder settings always copied. The old settings-only copy
      on POST /api/campaigns (template: {"from_campaign_id": …}) is removed.
    • [webapp] The character sheet's pickers open on what fits the character.
      GET /api/campaigns/{cid}/sheets/{sid}/picker and .../options now answer
      fits (the browse filters that match the character at that step, declared as
      fits rules in each system's browse.json), fits_label, filters, sorts
      and total, and take stage 3's f_<key>, sort, page, per_page and
      all=1; with any of those the "belongs to this character" narrowing is
      skipped. .../options now answers {results, …} instead of a bare list.
    • [repo] PostgreSQL moves from 16 to 18, and an existing install must run
      make pg-upgrade before it starts the new release.
      The 18 image keeps its
      data in a new place, so the database lives in a new volume
      (postgres18_data) and the old one cannot simply be reused: pointing 18 at
      it would start an empty database. make pg-upgrade (from the new release's
      checkout, before docker compose up -d) backs up, dumps the old database
      with its own PostgreSQL 16 tools, restores it into the new volume, and checks
      every table's row count and the migration revision. It never removes the old
      volume, which stays as the rollback and archive; the pre-upgrade dump is
      written to ./backups. A new pg-upgrade-guard service stops the stack, with
      a message naming make pg-upgrade, if the old volume holds a database and the
      new one is empty, so a plain docker compose up -d cannot give you an empty
      database. A fresh install and an already-upgraded install start as usual. The
      backend image's pg_dump client moves to 18 with it, so make migrate-safe
      refuses to back up against a 16 server until the upgrade is done. Renovate
      now sees the PG_MAJOR in the Dockerfile and moves every PostgreSQL pin in
      one pull request. See docs/OPERATIONS.md, "Upgrading PostgreSQL 16 to 18"
      (#1245, supersedes #627).
    • [webapp] Campaign names reach the transcriber as hotwords by default. On the bundled WhisperX server, the campaign's vocabulary list now goes in hotwords rather than initial_prompt when the server declares support; on a 100-name list this got 89–90% of names right against 80–82% (#506). A server without hotwords gets the prompt, as before, quietly. Set QB_ASR_VOCABULARY_STYLE=initial_prompt to keep the old behaviour.
    • [webapp] [bot] "GM only" is now "GMs only" everywhere it is shown. The
      wiki's front page and recent changes, the campaign search, a hidden page's
      screen-reader label, the table's Find, character sheets, Prep's lookups,
      the copy dialog, content-pack visibility and the bot's /help all say it
      the way the map and the rules already did (owner's decision, 30 Oct).

    Fixed

    • [webapp] Two reads no longer write. The character-creations list
      ("Carry on with ...") only hides creations left untouched for 30 days instead
      of deleting them while it lists, and a page's picture stored publicly before
      #894 is served where it is instead of being moved on read. A daily
      background task now deletes the stale creations, and another moves the old
      pictures into the private store; both are safe to repeat. The only
      remaining GETs that write are the two sign-in callbacks and the content-pack
      exports (#1206).
    • [webapp] Fetching a starter content pack no longer freezes the server, and a busy server no longer signs you out. Reading and normalising a pack ran on the web server's event loop, so fetching two packs at once could miss the health check and get both workers restarted; the work now runs in a thread. The sign-in check also treats only a 401 as "signed out": an error or dropped connection from /api/me keeps you waiting and retries instead of showing the sign-in page. (#1239)
    • [webapp] Prep → Names: a long "In the wiki as …" after saving no longer squeezes the name into one letter a line. The name keeps at least 45% of the row, and the receipt wraps in its own column.
    • [webapp] Admin → Content packs reads properly. A pack's contents say "9 ancestries", not "ancestrys"; its version no longer gains a stray "v" ("pf2e-8.6.1", not "vpf2e-8.6.1"); the licence's terms link is underlined, not told apart by colour alone; and on a phone the app bar's home link is named by the "QB" it shows (#1302).
    • [webapp] A moment without Redis now answers "try again" (503), not a server error (#1326). Found by the live checks (#1246): a Redis restart turned signed-in requests into 500s for a fraction of a second. Nobody was signed out then, and nobody is now. The manual healthcheck checklist in OPERATIONS.md also had a worker step that couldn't work; it now stops the process from the host.
    Downloads
  • v4.7.0-rc2 097c3ca548

    v4.7.0-rc2
    All checks were successful
    CI / Which journeys does this change need? (push) Successful in 30s
    CI / Does this change touch the recording path? (push) Successful in 33s
    CI / Bot/backend version sync (push) Successful in 50s
    CI / Backend lint (ruff) (push) Successful in 54s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 55s
    CI / Feedback site tests (push) Successful in 1m31s
    Release / Create Forgejo release (push) Successful in 11s
    CI / Bot tests and audit (push) Successful in 2m7s
    Release / Build and push versioned images (push) Successful in 3m12s
    CI / Docker image build (push) Successful in 7m3s
    CI / Frontend (shard 2/2) (push) Successful in 8m49s
    CI / Frontend (shard 1/2) (push) Successful in 10m7s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 9m36s
    CI / End-to-end journeys (desktop) (push) Successful in 13m42s
    CI / End-to-end journeys (phone) (push) Successful in 26m53s
    CI / End-to-end journeys (keyboard) (push) Successful in 29m33s
    CI / Backend migration, tests, and audit (push) Successful in 40m33s
    Pre-release

    claude-bot released this 2026-11-04 20:33:23 +00:00 | 112 commits to main since this release

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

    Added

    • [webapp] Browse the rules: the reference content can be read through, not only searched (#1240).
      The rules page lists the kinds of record a game system has (spells, creatures, feats, equipment and so on,
      in the system's order, each with how many there are), and opens a kind as an A-Z list in numbered pages that
      reaches every record, narrowed by level, by pack and by a search. Only what the reader may already see is
      listed or counted: disabled packs, other campaigns' packs and, for players, GM-only records are left out.
    • [webapp] The rules library: anyone signed in can browse the rules loaded on this server, outside any campaign. GET /api/reference/library lists each game system that has an active server pack with records, and the existing kinds, page and entry routes, called with no campaign, give a server-wide view of server packs only (never a campaign's). GM-only records show to admins and to anyone who runs a live campaign of that game system; everyone else's counts leave them out. An admin can browse one pack, switched off or not, GM-only records included, at /api/admin/content-packs/{id}/browse/kinds, /page and /{entry_id}.
    • [webapp] Browse the rules by each game system's own filters. A PF2e spell has tradition, rank, cantrip, focus and rarity; a creature size, traits and rarity; an item price (in gp), Bulk, category, traits and rarity; a feat type and class or ancestry. A 5e spell has school, class, ritual and concentration; a creature CR, type and size; an item category (rarity and attunement appear once a pack has them). Each choice shows how many records it would leave, counted with the other filters applied, and a kind can sort by level, rank, price or CR as well as A-Z. The filters are declared per system in app/sheets/builtin/<system>/browse.json, not in code. GET /api/game-systems/{key}/reference/filters (and the admin pack's /browse/filters) returns them with their counts, and /reference/page takes f_<key> and sort. A new reference_entries.facets column holds the filter values; the worker fills it at start after the upgrade, with no manual step (python -m app.cli refill_facets does the same by hand). Migration b1242c0d1e2f.
    • [webapp] Table mode's Find can browse the rules (#1243). "Browse the rules ›" in Find opens the kinds and a kind's list inside the same dialog; choosing a row opens it in the table's preview, where it can be added to the shelf. "‹ Back to the search" and Escape return to the search as it was.
    • [webapp] Prep's Rules & bestiary drawer can browse. A Search | Browse switch shows the campaign's kinds with counts, then a kind as a filtered, paged list; each row previews and inserts like a search result. Creatures open at the party's range (PF2e level -2 to +3, 5e CR a quarter of the level to level + 2), taken from the active player characters' sheet levels. (#1243)
    • [bot] The "Played" post in the campaign's Discord channel now asks the
      players for one star and one wish (#1255).
      When a session completes and
      Quest Board is asking anyone about it, the post the bot already makes gains a
      line asking for a star and a wish, saying when the ask closes, and its link
      goes to Home, where they are answered. It is one line in a post that goes out
      anyway, once: the channel is asked, not each player, and nothing brings it
      back. Nothing about the session's content reaches Discord.
    • [webapp] The Names drawer in Prep draws names from a built-in generator instead of asking an AI. Forty-four styles (the peoples, places by size and kind, groups, ships, items, books, battles and festivals), ten names at a time, free and instant, in a campaign with no AI as well. A campaign is never offered the same name twice, and never a name its wiki already holds; a style that names a place can use one you give it. Describe, the one line under a name, still asks the campaign's AI and is offered only when there is one. The ready-made name prefetch and its Redis cache are gone. (#1257)
    • [webapp] A wiki page's margin is one box with suggested facts. A page starts with the facts its type usually has in the campaign's system (Pathfinder 2e, D&D 5e or Generic), empty and never shown until filled; where Quest Board already knows a value (First met and Last seen from the sessions, a character's player and class from the campaign and the sheet, a place's region from its map, a creature's level from the rules entry it is based on) readers see it until a GM types their own. A page can show a map as its picture, with a caption, and a GM can remove a suggested fact for good. New columns lore_entries.image_caption, .image_map_id and .facts_dismissed (one migration); GET /api/campaigns/{id}/wiki/suggested-facts; the article read gains image_caption, image_map and facts. The campaign file carries the caption, the removed facts and the page's map.
    • [webapp] Facts the session may have changed. After a session is summarised, the campaign's own LLM is asked, page by page, whether a status, ruler, leader, owner or allegiance changed. Each suggestion carries a quote found in the transcript, with the speaker and time of the line it came from, and waits for a GM to use it, change it or turn it down; nothing is shown to players. New table fact_suggestions (migration f1280a0b1c2d) and a task to run once after the upgrade for sessions already processed: celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill.
    • [webapp] Two of the same creature can be on a session's shelf (#1321). Adding a rules entry that is already there with another: true makes a numbered copy with its own HP; a pin carries the first card forward and copies stay with their session. Cards carry copy_number, and a rules card copies.
    • [repo] The synthetic-session harness runs leg 3 in one command, and can
      send its vocabulary as hotwords as well as initial_prompt (#506).

      python -m evals.synthetic_session summary-leg goes audio, ASR, merged
      transcript, LLM summary, scores through the same summarisation and scoring
      code leg 2 uses, and reports leg 3 beside leg 2 (--baseline); --provider stub runs it offline. asr-leg and summary-leg take
      --vocabulary-mechanism {initial_prompt,hotwords,both}, which sets the
      product's own QB_ASR_VOCABULARY_STYLE switch for the run without touching
      the provider code. The README records the measured matrix.

    Changed

    • [webapp] New campaign → "The setup of one of yours" now opens the full copy dialog.
      It is the same one as Settings → Archive → "Start a new campaign from this one":
      choose the pages, maps, lines and veils, packs and people to take, with the
      scheduling and reminder settings always copied. The old settings-only copy
      on POST /api/campaigns (template: {"from_campaign_id": …}) is removed.
    • [webapp] The character sheet's pickers open on what fits the character.
      GET /api/campaigns/{cid}/sheets/{sid}/picker and .../options now answer
      fits (the browse filters that match the character at that step, declared as
      fits rules in each system's browse.json), fits_label, filters, sorts
      and total, and take stage 3's f_<key>, sort, page, per_page and
      all=1; with any of those the "belongs to this character" narrowing is
      skipped. .../options now answers {results, …} instead of a bare list.
    • [repo] PostgreSQL moves from 16 to 18, and an existing install must run
      make pg-upgrade before it starts the new release.
      The 18 image keeps its
      data in a new place, so the database lives in a new volume
      (postgres18_data) and the old one cannot simply be reused: pointing 18 at
      it would start an empty database. make pg-upgrade (from the new release's
      checkout, before docker compose up -d) backs up, dumps the old database
      with its own PostgreSQL 16 tools, restores it into the new volume, and checks
      every table's row count and the migration revision. It never removes the old
      volume, which stays as the rollback and archive; the pre-upgrade dump is
      written to ./backups. A new pg-upgrade-guard service stops the stack, with
      a message naming make pg-upgrade, if the old volume holds a database and the
      new one is empty, so a plain docker compose up -d cannot give you an empty
      database. A fresh install and an already-upgraded install start as usual. The
      backend image's pg_dump client moves to 18 with it, so make migrate-safe
      refuses to back up against a 16 server until the upgrade is done. Renovate
      now sees the PG_MAJOR in the Dockerfile and moves every PostgreSQL pin in
      one pull request. See docs/OPERATIONS.md, "Upgrading PostgreSQL 16 to 18"
      (#1245, supersedes #627).
    • [webapp] Campaign names reach the transcriber as hotwords by default. On the bundled WhisperX server, the campaign's vocabulary list now goes in hotwords rather than initial_prompt when the server declares support; on a 100-name list this got 89–90% of names right against 80–82% (#506). A server without hotwords gets the prompt, as before, quietly. Set QB_ASR_VOCABULARY_STYLE=initial_prompt to keep the old behaviour.
    • [webapp] [bot] "GM only" is now "GMs only" everywhere it is shown. The
      wiki's front page and recent changes, the campaign search, a hidden page's
      screen-reader label, the table's Find, character sheets, Prep's lookups,
      the copy dialog, content-pack visibility and the bot's /help all say it
      the way the map and the rules already did (owner's decision, 30 Oct).

    Fixed

    • [webapp] Two reads no longer write. The character-creations list
      ("Carry on with ...") only hides creations left untouched for 30 days instead
      of deleting them while it lists, and a page's picture stored publicly before
      #894 is served where it is instead of being moved on read. A daily
      background task now deletes the stale creations, and another moves the old
      pictures into the private store; both are safe to repeat. The only
      remaining GETs that write are the two sign-in callbacks and the content-pack
      exports (#1206).
    • [webapp] Fetching a starter content pack no longer freezes the server, and a busy server no longer signs you out. Reading and normalising a pack ran on the web server's event loop, so fetching two packs at once could miss the health check and get both workers restarted; the work now runs in a thread. The sign-in check also treats only a 401 as "signed out": an error or dropped connection from /api/me keeps you waiting and retries instead of showing the sign-in page. (#1239)
    • [webapp] Admin → Content packs reads properly. A pack's contents say "9 ancestries", not "ancestrys"; its version no longer gains a stray "v" ("pf2e-8.6.1", not "vpf2e-8.6.1"); the licence's terms link is underlined, not told apart by colour alone; and on a phone the app bar's home link is named by the "QB" it shows (#1302).
    • [webapp] A moment without Redis now answers "try again" (503), not a server error (#1326). Found by the live checks (#1246): a Redis restart turned signed-in requests into 500s for a fraction of a second. Nobody was signed out then, and nobody is now. The manual healthcheck checklist in OPERATIONS.md also had a worker step that couldn't work; it now stops the process from the host.
    Downloads
  • v4.7.0-rc1 8edadd9d56

    v4.7.0-rc1
    All checks were successful
    CI / Does this change touch the recording path? (push) Successful in 16s
    CI / Bot/backend version sync (push) Successful in 28s
    Release / Create Forgejo release (push) Successful in 13s
    CI / Which journeys does this change need? (push) Successful in 19s
    CI / Backend lint (ruff) (push) Successful in 1m20s
    CI / Feedback site tests (push) Successful in 2m10s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 2m18s
    CI / Docker image build (push) Successful in 2m27s
    Release / Build and push versioned images (push) Successful in 2m47s
    CI / Bot tests and audit (push) Successful in 3m36s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 8m26s
    CI / Frontend (shard 1/2) (push) Successful in 8m55s
    CI / Frontend (shard 2/2) (push) Successful in 11m53s
    CI / End-to-end journeys (phone) (push) Successful in 11m12s
    CI / End-to-end journeys (keyboard) (push) Successful in 12m5s
    CI / End-to-end journeys (desktop) (push) Successful in 12m10s
    CI / Backend migration, tests, and audit (push) Successful in 55m5s
    Pre-release

    claude-bot released this 2026-11-04 17:00:10 +00:00 | 118 commits to main since this release

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

    Added

    • [webapp] Browse the rules: the reference content can be read through, not only searched (#1240).
      The rules page lists the kinds of record a game system has (spells, creatures, feats, equipment and so on,
      in the system's order, each with how many there are), and opens a kind as an A-Z list in numbered pages that
      reaches every record, narrowed by level, by pack and by a search. Only what the reader may already see is
      listed or counted: disabled packs, other campaigns' packs and, for players, GM-only records are left out.
    • [webapp] The rules library: anyone signed in can browse the rules loaded on this server, outside any campaign. GET /api/reference/library lists each game system that has an active server pack with records, and the existing kinds, page and entry routes, called with no campaign, give a server-wide view of server packs only (never a campaign's). GM-only records show to admins and to anyone who runs a live campaign of that game system; everyone else's counts leave them out. An admin can browse one pack, switched off or not, GM-only records included, at /api/admin/content-packs/{id}/browse/kinds, /page and /{entry_id}.
    • [webapp] Browse the rules by each game system's own filters. A PF2e spell has tradition, rank, cantrip, focus and rarity; a creature size, traits and rarity; an item price (in gp), Bulk, category, traits and rarity; a feat type and class or ancestry. A 5e spell has school, class, ritual and concentration; a creature CR, type and size; an item category (rarity and attunement appear once a pack has them). Each choice shows how many records it would leave, counted with the other filters applied, and a kind can sort by level, rank, price or CR as well as A-Z. The filters are declared per system in app/sheets/builtin/<system>/browse.json, not in code. GET /api/game-systems/{key}/reference/filters (and the admin pack's /browse/filters) returns them with their counts, and /reference/page takes f_<key> and sort. A new reference_entries.facets column holds the filter values; the worker fills it at start after the upgrade, with no manual step (python -m app.cli refill_facets does the same by hand). Migration b1242c0d1e2f.
    • [webapp] Table mode's Find can browse the rules (#1243). "Browse the rules ›" in Find opens the kinds and a kind's list inside the same dialog; choosing a row opens it in the table's preview, where it can be added to the shelf. "‹ Back to the search" and Escape return to the search as it was.
    • [webapp] Prep's Rules & bestiary drawer can browse. A Search | Browse switch shows the campaign's kinds with counts, then a kind as a filtered, paged list; each row previews and inserts like a search result. Creatures open at the party's range (PF2e level -2 to +3, 5e CR a quarter of the level to level + 2), taken from the active player characters' sheet levels. (#1243)
    • [bot] The "Played" post in the campaign's Discord channel now asks the
      players for one star and one wish (#1255).
      When a session completes and
      Quest Board is asking anyone about it, the post the bot already makes gains a
      line asking for a star and a wish, saying when the ask closes, and its link
      goes to Home, where they are answered. It is one line in a post that goes out
      anyway, once: the channel is asked, not each player, and nothing brings it
      back. Nothing about the session's content reaches Discord.
    • [webapp] The Names drawer in Prep draws names from a built-in generator instead of asking an AI. Forty-four styles (the peoples, places by size and kind, groups, ships, items, books, battles and festivals), ten names at a time, free and instant, in a campaign with no AI as well. A campaign is never offered the same name twice, and never a name its wiki already holds; a style that names a place can use one you give it. Describe, the one line under a name, still asks the campaign's AI and is offered only when there is one. The ready-made name prefetch and its Redis cache are gone. (#1257)
    • [webapp] A wiki page's margin is one box with suggested facts. A page starts with the facts its type usually has in the campaign's system (Pathfinder 2e, D&D 5e or Generic), empty and never shown until filled; where Quest Board already knows a value (First met and Last seen from the sessions, a character's player and class from the campaign and the sheet, a place's region from its map, a creature's level from the rules entry it is based on) readers see it until a GM types their own. A page can show a map as its picture, with a caption, and a GM can remove a suggested fact for good. New columns lore_entries.image_caption, .image_map_id and .facts_dismissed (one migration); GET /api/campaigns/{id}/wiki/suggested-facts; the article read gains image_caption, image_map and facts. The campaign file carries the caption, the removed facts and the page's map.
    • [webapp] Facts the session may have changed. After a session is summarised, the campaign's own LLM is asked, page by page, whether a status, ruler, leader, owner or allegiance changed. Each suggestion carries a quote found in the transcript, with the speaker and time of the line it came from, and waits for a GM to use it, change it or turn it down; nothing is shown to players. New table fact_suggestions (migration f1280a0b1c2d) and a task to run once after the upgrade for sessions already processed: celery -A app.tasks.celery_app call app.tasks.fact_tasks.suggest_fact_changes_backfill.
    • [webapp] Two of the same creature can be on a session's shelf (#1321). Adding a rules entry that is already there with another: true makes a numbered copy with its own HP; a pin carries the first card forward and copies stay with their session. Cards carry copy_number, and a rules card copies.
    • [repo] The synthetic-session harness runs leg 3 in one command, and can
      send its vocabulary as hotwords as well as initial_prompt (#506).

      python -m evals.synthetic_session summary-leg goes audio, ASR, merged
      transcript, LLM summary, scores through the same summarisation and scoring
      code leg 2 uses, and reports leg 3 beside leg 2 (--baseline); --provider stub runs it offline. asr-leg and summary-leg take
      --vocabulary-mechanism {initial_prompt,hotwords,both}, which sets the
      product's own QB_ASR_VOCABULARY_STYLE switch for the run without touching
      the provider code. The README records the measured matrix.

    Changed

    • [webapp] New campaign → "The setup of one of yours" now opens the full copy dialog.
      It is the same one as Settings → Archive → "Start a new campaign from this one":
      choose the pages, maps, lines and veils, packs and people to take, with the
      scheduling and reminder settings always copied. The old settings-only copy
      on POST /api/campaigns (template: {"from_campaign_id": …}) is removed.
    • [webapp] The character sheet's pickers open on what fits the character.
      GET /api/campaigns/{cid}/sheets/{sid}/picker and .../options now answer
      fits (the browse filters that match the character at that step, declared as
      fits rules in each system's browse.json), fits_label, filters, sorts
      and total, and take stage 3's f_<key>, sort, page, per_page and
      all=1; with any of those the "belongs to this character" narrowing is
      skipped. .../options now answers {results, …} instead of a bare list.
    • [repo] PostgreSQL moves from 16 to 18, and an existing install must run
      make pg-upgrade before it starts the new release.
      The 18 image keeps its
      data in a new place, so the database lives in a new volume
      (postgres18_data) and the old one cannot simply be reused: pointing 18 at
      it would start an empty database. make pg-upgrade (from the new release's
      checkout, before docker compose up -d) backs up, dumps the old database
      with its own PostgreSQL 16 tools, restores it into the new volume, and checks
      every table's row count and the migration revision. It never removes the old
      volume, which stays as the rollback and archive; the pre-upgrade dump is
      written to ./backups. A new pg-upgrade-guard service stops the stack, with
      a message naming make pg-upgrade, if the old volume holds a database and the
      new one is empty, so a plain docker compose up -d cannot give you an empty
      database. A fresh install and an already-upgraded install start as usual. The
      backend image's pg_dump client moves to 18 with it, so make migrate-safe
      refuses to back up against a 16 server until the upgrade is done. Renovate
      now sees the PG_MAJOR in the Dockerfile and moves every PostgreSQL pin in
      one pull request. See docs/OPERATIONS.md, "Upgrading PostgreSQL 16 to 18"
      (#1245, supersedes #627).
    • [webapp] Campaign names reach the transcriber as hotwords by default. On the bundled WhisperX server, the campaign's vocabulary list now goes in hotwords rather than initial_prompt when the server declares support; on a 100-name list this got 89–90% of names right against 80–82% (#506). A server without hotwords gets the prompt, as before, quietly. Set QB_ASR_VOCABULARY_STYLE=initial_prompt to keep the old behaviour.
    • [webapp] [bot] "GM only" is now "GMs only" everywhere it is shown. The
      wiki's front page and recent changes, the campaign search, a hidden page's
      screen-reader label, the table's Find, character sheets, Prep's lookups,
      the copy dialog, content-pack visibility and the bot's /help all say it
      the way the map and the rules already did (owner's decision, 30 Oct).

    Fixed

    • [webapp] Two reads no longer write. The character-creations list
      ("Carry on with ...") only hides creations left untouched for 30 days instead
      of deleting them while it lists, and a page's picture stored publicly before
      #894 is served where it is instead of being moved on read. A daily
      background task now deletes the stale creations, and another moves the old
      pictures into the private store; both are safe to repeat. The only
      remaining GETs that write are the two sign-in callbacks and the content-pack
      exports (#1206).
    • [webapp] Fetching a starter content pack no longer freezes the server, and a busy server no longer signs you out. Reading and normalising a pack ran on the web server's event loop, so fetching two packs at once could miss the health check and get both workers restarted; the work now runs in a thread. The sign-in check also treats only a 401 as "signed out": an error or dropped connection from /api/me keeps you waiting and retries instead of showing the sign-in page. (#1239)
    • [webapp] Admin → Content packs reads properly. A pack's contents say "9 ancestries", not "ancestrys"; its version no longer gains a stray "v" ("pf2e-8.6.1", not "vpf2e-8.6.1"); the licence's terms link is underlined, not told apart by colour alone; and on a phone the app bar's home link is named by the "QB" it shows (#1302).
    • [webapp] A moment without Redis now answers "try again" (503), not a server error (#1326). Found by the live checks (#1246): a Redis restart turned signed-in requests into 500s for a fraction of a second. Nobody was signed out then, and nobody is now. The manual healthcheck checklist in OPERATIONS.md also had a worker step that couldn't work; it now stops the process from the host.
    Downloads
  • v4.6.2 881d0f582b

    v4.6.2
    All checks were successful
    CI / Does this change touch the recording path? (push) Successful in 14s
    CI / Which journeys does this change need? (push) Successful in 14s
    Release / Create Forgejo release (push) Successful in 16s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 59s
    CI / Bot/backend version sync (push) Successful in 1m4s
    CI / Backend lint (ruff) (push) Successful in 1m15s
    CI / Feedback site tests (push) Successful in 1m47s
    CI / Docker image build (push) Successful in 2m34s
    Release / Build and push versioned images (push) Successful in 3m46s
    CI / Bot tests and audit (push) Successful in 4m46s
    CI / Frontend (shard 2/2) (push) Successful in 10m48s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 10m38s
    CI / Frontend (shard 1/2) (push) Successful in 12m44s
    CI / End-to-end journeys (desktop) (push) Successful in 15m22s
    CI / End-to-end journeys (phone) (push) Successful in 28m24s
    CI / End-to-end journeys (keyboard) (push) Successful in 31m32s
    CI / Backend migration, tests, and audit (push) Successful in 35m44s
    Stable

    claude-bot released this 2026-10-30 18:21:36 +00:00 | 299 commits to main since this release

    The map, as planned: everything the map's plan had that 4.6.0 left out (#1263),
    and the fixes from trying it on the dev server as 4.6.2-rc1 to -rc4. Maps
    and atlases can be renamed, binned and restored; markers edited, moved and
    linked; portals link maps, or two places on one map; the party's trail runs in
    session order, with stops added, reordered and reached by portal; and a large
    map opens from smaller zoom levels with its thumbnail first.

    Upgrading from 4.6.1

    • Two database migrations (f1263a0b1c2d, a1269b0c1d2e). Back up first:
      make backup-now, then make migrate-safe, then docker compose up -d.
      The second puts the trail stops already recorded in session order.
    • Then cut the zoom levels and thumbnails of the maps already uploaded,
      once:
      docker compose exec -T worker celery -A app.tasks.celery_app call app.tasks.map_tiles.backfill_map_thumbs. It runs daily anyway; until it
      has, a large map loads as slowly as before.
    • BOT_CONTRACT_VERSION is unchanged (2). The backend and the bot can be
      updated one at a time.
    • Starter content packs are on this release's page too, so Admin → Content
      packs keeps offering them.

    Added

    • [repo] The feedback site has a name-rating page at /names. Testers draw
      names from the built-in generator (no AI), mark each up or down, and leave
      notes per style; the admin readout tallies them (#1257).
    • [webapp] A map marker can be edited: its label, line, kind, page, map, holding, position and who sees it (#1260).
      PATCH /api/campaigns/{id}/markers/{marker_id}. A GM edits any marker; a player edits only her own, as a note, place or danger, with no links. A marker's page can no longer be one in the wiki's bin, on create or edit.
    • [webapp] Maps and atlases can be renamed, changed and deleted into a bin, and travel links and trail stops can be removed (#1263).
      PATCH and DELETE for maps and atlases (GM only), GET /api/campaigns/{id}/maps-bin with restore routes, and a daily purge that removes the rows, pictures and tiles after the same grace period as the wiki bin. DELETE for a travel link and for a trail stop; a travel link can be GM-only. Migration f1263a0b1c2d.
    • [webapp] A map has a thumbnail, at most 1024 px on its longest side (#1263).
      The worker writes it when it cuts the map's tiles, and GET /api/campaigns/{id}/maps/{map_id}/thumb serves it with the same audience check as a tile. A daily task writes it for maps cut before this release.
    • [webapp] The map, planned parts that were missing (#1263). ![[Map: X]] in
      a page now shows a small picture of that map, linking to it. Place and
      travel-link labels that would sit on top of each other no longer do: the
      more important one keeps its label and the other shows on hover or focus.
      A marker that opens a map of its own wears a "+" and says so in its name. A
      group's page says "Holdings on the map" for the markers that name it as
      holder. The party page shows where the party is and its trail, for the party
      being viewed.
    • [webapp] The map, the rest of what was planned (#1263, #1260). A GM or a
      marker's author can Edit a marker (what is here, kind, a line, who sees
      it) and Move it; a GM also links it to a page ("The page it's about"),
      a holder ("Held by", for a holding) and a map it opens. A travel-link
      marker has Link to another map…, with a note and "Everyone at the
      table" or "GMs only", and each link can be removed. Every marker a player
      added says whose it is and who sees it, whatever its kind. A GM gets a
      Maps in this atlas list with rename, level name, who sees it, Replace
      the picture
      and delete for each map, atlas settings, and a Bin with
      Restore. Each trail stop links to its session and a GM can remove one or
      end the trail at a spot on the map; a campaign with two parties has a party
      switcher; a party on a map below shows on the map above; the trail is drawn
      across travel links; the party's own marker opens a small panel; the kind
      chips are remembered. Every removal asks first, and every failure shows the
      server's sentence with Try again.
    • [webapp] A trail stop reached by portal or magic has no line to it (#1273).
      After a stop at a portal, "Add a stop" asks where the party came out: a
      marker on any map (that portal's linked exits first), a spot, or nothing
      yet, so a session can end with the party mid-portal. "No line to it: they
      went by portal or magic" marks any stop the same way. The trail is drawn
      broken there, and also where it changes map with no link to show the way.
      Two portals on the same map can now be linked, such as the ways in and out
      of somewhere with no map of its own.
    • [webapp] The party's trail: "Add a stop…", stops in an order a GM can change, and a shorter list (#1275).
      "Where the party ended…" is now "Add a stop…". A GM moves a stop earlier or
      later within its session. The list under the map is grouped by session,
      newest first, with only the latest open.
      POST /api/campaigns/{id}/parties/{party_id}/trail/{stop_id}/move.

    Changed

    • [repo] The feedback site's readout is tabbed (#1257). The name sampler
      opens first, with a sub-tab per generator style holding its marks and notes;
      the finished redesign rounds sit under "Completed: redesign".
    • [webapp] "On this map" stays short (#1276). The markers under the map
      are grouped by kind, folded, with a "Find a marker" search that opens every
      kind with a match.
    • [webapp] A "travel link" marker is now called a portal. The map's legend
      says "Portals", a marker's kind is "A portal", and a marker made at the far
      end of a link is "Portal to …". Any marker can still open a map of its own.

    Fixed

    • [webapp] The wiki editor's [[ link menu opens under the line you are
      typing (#1261).
      It stood near the top of the text box whatever line you
      were on, so on a page with text it covered the first lines and the link
      being written. It now opens under the cursor's line, stays inside the
      editor, and opens above the line when there is no room below.
    • [webapp] A map whose picture is refused is no longer made twice (#1263).
      "Add a map" makes the map (and its atlas) once. If the picture is refused,
      Try again sends only the picture; or the map is left without one and its
      page offers "Add the picture". The ![[Map: X]] embed loads the map's
      thumbnail, falling back to the whole picture.
    • [webapp] Removing a marker that a trail stop uses no longer fails with a server error (#1263).
      A stop made from a marker now keeps the marker's map and position, so it stays on the trail when the marker goes. The migration fills them in for existing stops.
    • [webapp] Choosing a page in the article picker is clicking its name (#1268).
      The separate "Choose" link is gone, and on the map's marker form the name and
      the link no longer run together as one word.
    • [webapp] The party's trail runs in session order (#1269). Stops were
      ordered by when they were entered, so filling in earlier sessions after the
      latest one moved "The party is here" back to the last one entered. A stop
      made between sessions comes after the last session played when it was
      entered. Migration a1269b0c1d2e places the stops already recorded.
    • [webapp] "Where did the party end up?" finds a marker by searching its
      name, and offers every kind (#1271).
      Dangers and notes were missing from
      the list, and a dropdown of every marker would not have scaled to a long
      campaign's map. With no marker chosen, it goes on to choose a spot.
    • [webapp] Embedding a map in a wiki page: the [[ menu finds maps, and the preview draws them (#1277).
      After Map: the menu offers the campaign's maps by name, not "Create" a
      page; a map whose name starts with what is typed is offered among the
      pages. The editor's preview showed the embed as a page still to be written.
    • [webapp] A large map opens in moments, not 10–15 seconds (#1282). The
      worker now cuts each map at half size, a quarter and so on, until the whole
      map fits in one tile, and the page reads the level that fits the screen: an
      8192 × 6144 map opened zoomed out needs a few dozen tiles, not all 768. Its
      thumbnail shows at once underneath and sharpens as the tiles arrive. Maps
      already uploaded get their levels from the same backfill as the thumbnails
      (celery -A app.tasks.celery_app call app.tasks.map_tiles.backfill_map_thumbs),
      and daily after that.
    • [webapp] The party's trail and "Add a stop", from the 4.6.2-rc2 review (#1283).
      An older session in the trail list says "Show", and choosing a stop's place
      on the map opens its session, so its stops' Remove and Earlier/Later are
      easy to find. The marker searches in "Add a stop" list nothing until you
      type, apart from a portal's linked exits, so the form stays short.
    • [webapp] A map sharpens as you zoom in, and fills most of a phone's screen (#1286).
      The thumbnail shown while a map loads sat above its tiles, so zooming in
      never got sharper; it now sits underneath them. On a phone the map's box is
      most of the screen tall, not a short strip in the map's own shape.
    Downloads
  • v4.6.2-rc4 882122ec75

    v4.6.2-rc4
    All checks were successful
    CI / Bot/backend version sync (push) Successful in 31s
    CI / Backend lint (ruff) (push) Successful in 38s
    Release / Create Forgejo release (push) Successful in 21s
    CI / Feedback site tests (push) Successful in 52s
    CI / Summarisation accuracy eval harness (stub provider) (push) Successful in 55s
    CI / Which journeys does this change need? (push) Successful in 30s
    CI / Does this change touch the recording path? (push) Successful in 30s
    CI / Bot tests and audit (push) Successful in 2m11s
    CI / Docker image build (push) Successful in 6m40s
    CI / Frontend (shard 2/2) (push) Successful in 8m7s
    Release / Build and push versioned images (push) Successful in 7m45s
    CI / Frontend (shard 1/2) (push) Successful in 9m44s
    CI / Synthetic session harness (no GPU, no LLM) (push) Successful in 11m3s
    CI / End-to-end journeys (phone) (push) Successful in 11m11s
    CI / End-to-end journeys (keyboard) (push) Successful in 12m14s
    CI / End-to-end journeys (desktop) (push) Successful in 12m21s
    CI / Backend migration, tests, and audit (push) Successful in 34m44s
    Pre-release

    claude-bot released this 2026-10-30 09:10:37 +00:00 | 303 commits to main since this release

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

    Added

    • [repo] The feedback site has a name-rating page at /names. Testers draw
      names from the built-in generator (no AI), mark each up or down, and leave
      notes per style; the admin readout tallies them (#1257).
    • [webapp] A map marker can be edited: its label, line, kind, page, map, holding, position and who sees it (#1260).
      PATCH /api/campaigns/{id}/markers/{marker_id}. A GM edits any marker; a player edits only her own, as a note, place or danger, with no links. A marker's page can no longer be one in the wiki's bin, on create or edit.
    • [webapp] Maps and atlases can be renamed, changed and deleted into a bin, and travel links and trail stops can be removed (#1263).
      PATCH and DELETE for maps and atlases (GM only), GET /api/campaigns/{id}/maps-bin with restore routes, and a daily purge that removes the rows, pictures and tiles after the same grace period as the wiki bin. DELETE for a travel link and for a trail stop; a travel link can be GM-only. Migration f1263a0b1c2d.
    • [webapp] A map has a thumbnail, at most 1024 px on its longest side (#1263).
      The worker writes it when it cuts the map's tiles, and GET /api/campaigns/{id}/maps/{map_id}/thumb serves it with the same audience check as a tile. A daily task writes it for maps cut before this release.
    • [webapp] The map, planned parts that were missing (#1263). ![[Map: X]] in
      a page now shows a small picture of that map, linking to it. Place and
      travel-link labels that would sit on top of each other no longer do: the
      more important one keeps its label and the other shows on hover or focus.
      A marker that opens a map of its own wears a "+" and says so in its name. A
      group's page says "Holdings on the map" for the markers that name it as
      holder. The party page shows where the party is and its trail, for the party
      being viewed.
    • [webapp] The map, the rest of what was planned (#1263, #1260). A GM or a
      marker's author can Edit a marker (what is here, kind, a line, who sees
      it) and Move it; a GM also links it to a page ("The page it's about"),
      a holder ("Held by", for a holding) and a map it opens. A travel-link
      marker has Link to another map…, with a note and "Everyone at the
      table" or "GMs only", and each link can be removed. Every marker a player
      added says whose it is and who sees it, whatever its kind. A GM gets a
      Maps in this atlas list with rename, level name, who sees it, Replace
      the picture
      and delete for each map, atlas settings, and a Bin with
      Restore. Each trail stop links to its session and a GM can remove one or
      end the trail at a spot on the map; a campaign with two parties has a party
      switcher; a party on a map below shows on the map above; the trail is drawn
      across travel links; the party's own marker opens a small panel; the kind
      chips are remembered. Every removal asks first, and every failure shows the
      server's sentence with Try again.
    • [webapp] A trail stop reached by portal or magic has no line to it (#1273).
      After a stop at a portal, "Add a stop" asks where the party came out: a
      marker on any map (that portal's linked exits first), a spot, or nothing
      yet, so a session can end with the party mid-portal. "No line to it: they
      went by portal or magic" marks any stop the same way. The trail is drawn
      broken there, and also where it changes map with no link to show the way.
      Two portals on the same map can now be linked, such as the ways in and out
      of somewhere with no map of its own.
    • [webapp] The party's trail: "Add a stop…", stops in an order a GM can change, and a shorter list (#1275).
      "Where the party ended…" is now "Add a stop…". A GM moves a stop earlier or
      later within its session. The list under the map is grouped by session,
      newest first, with only the latest open.
      POST /api/campaigns/{id}/parties/{party_id}/trail/{stop_id}/move.

    Changed

    • [repo] The feedback site's readout is tabbed (#1257). The name sampler
      opens first, with a sub-tab per generator style holding its marks and notes;
      the finished redesign rounds sit under "Completed: redesign".
    • [webapp] "On this map" stays short (#1276). The markers under the map
      are grouped by kind, folded, with a "Find a marker" search that opens every
      kind with a match.
    • [webapp] A "travel link" marker is now called a portal. The map's legend
      says "Portals", a marker's kind is "A portal", and a marker made at the far
      end of a link is "Portal to …". Any marker can still open a map of its own.

    Fixed

    • [webapp] The wiki editor's [[ link menu opens under the line you are
      typing (#1261).
      It stood near the top of the text box whatever line you
      were on, so on a page with text it covered the first lines and the link
      being written. It now opens under the cursor's line, stays inside the
      editor, and opens above the line when there is no room below.
    • [webapp] A map whose picture is refused is no longer made twice (#1263).
      "Add a map" makes the map (and its atlas) once. If the picture is refused,
      Try again sends only the picture; or the map is left without one and its
      page offers "Add the picture". The ![[Map: X]] embed loads the map's
      thumbnail, falling back to the whole picture.
    • [webapp] Removing a marker that a trail stop uses no longer fails with a server error (#1263).
      A stop made from a marker now keeps the marker's map and position, so it stays on the trail when the marker goes. The migration fills them in for existing stops.
    • [webapp] Choosing a page in the article picker is clicking its name (#1268).
      The separate "Choose" link is gone, and on the map's marker form the name and
      the link no longer run together as one word.
    • [webapp] The party's trail runs in session order (#1269). Stops were
      ordered by when they were entered, so filling in earlier sessions after the
      latest one moved "The party is here" back to the last one entered. A stop
      made between sessions comes after the last session played when it was
      entered. Migration a1269b0c1d2e places the stops already recorded.
    • [webapp] "Where did the party end up?" finds a marker by searching its
      name, and offers every kind (#1271).
      Dangers and notes were missing from
      the list, and a dropdown of every marker would not have scaled to a long
      campaign's map. With no marker chosen, it goes on to choose a spot.
    • [webapp] Embedding a map in a wiki page: the [[ menu finds maps, and the preview draws them (#1277).
      After Map: the menu offers the campaign's maps by name, not "Create" a
      page; a map whose name starts with what is typed is offered among the
      pages. The editor's preview showed the embed as a page still to be written.
    • [webapp] A large map opens in moments, not 10–15 seconds (#1282). The
      worker now cuts each map at half size, a quarter and so on, until the whole
      map fits in one tile, and the page reads the level that fits the screen: an
      8192 × 6144 map opened zoomed out needs a few dozen tiles, not all 768. Its
      thumbnail shows at once underneath and sharpens as the tiles arrive. Maps
      already uploaded get their levels from the same backfill as the thumbnails
      (celery -A app.tasks.celery_app call app.tasks.map_tiles.backfill_map_thumbs),
      and daily after that.
    • [webapp] The party's trail and "Add a stop", from the 4.6.2-rc2 review (#1283).
      An older session in the trail list says "Show", and choosing a stop's place
      on the map opens its session, so its stops' Remove and Earlier/Later are
      easy to find. The marker searches in "Add a stop" list nothing until you
      type, apart from a portal's linked exits, so the form stays short.
    • [webapp] A map sharpens as you zoom in, and fills most of a phone's screen (#1286).
      The thumbnail shown while a map loads sat above its tiles, so zooming in
      never got sharper; it now sits underneath them. On a phone the map's box is
      most of the screen tall, not a short strip in the map's own shape.
    Downloads