-
v3.6.1
StableAll checks were successfulRelease / Create Forgejo release (push) Successful in 21sCI / Docker image build (push) Successful in 38sCI / Backend lint (ruff) (push) Successful in 40sRelease / Build and push versioned images (push) Successful in 27sCI / Bot tests and audit (push) Successful in 1m52sCI / Frontend tests, audit, and build (push) Successful in 4m3sCI / Backend migration, tests, and audit (push) Successful in 7m5sreleased this
2026-07-17 22:40:29 +00:00 | 705 commits to main since this releasePatch: make the calendar feed reachable behind nginx.
Fixed
- [ops] The iCal calendar feed (#107) is now proxied to the backend. The
feed lives at the app root (/calendar/feed/{token}.ics), but nginx only
proxied/api,/auth,/public, and/media— so the feed URL fell
through to the SPA catch-all and returned the app HTML instead of the
calendar. Added alocation /calendar/proxy block. (Caught on the dev
deploy before v3.6.0 reached production.)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [ops] The iCal calendar feed (#107) is now proxied to the backend. The
-
v3.6.0
StableSome checks failedCI / Docker image build (push) Successful in 18sRelease / Create Forgejo release (push) Successful in 21sCI / Backend lint (ruff) (push) Successful in 2m23sCI / Frontend tests, audit, and build (push) Successful in 2m43sRelease / Build and push versioned images (push) Successful in 4m9sCI / Bot tests and audit (push) Successful in 7m45sCI / Backend migration, tests, and audit (push) Has been cancelledreleased this
2026-07-17 22:29:59 +00:00 | 706 commits to main since this releaseScheduling: timezones, recurrence, availability & calendar sync.
Campaign-anchored timezone correctness, recurring session series, standing
player availability with quorum-based at-risk warnings, a subscribable iCal
feed, and emailed calendar invites. No API contract change
(BOT_CONTRACT_VERSIONstays 1). Ships four Alembic migrations.Added
- [webapp] Emailed calendar invites — opt in (Profile → "Email me calendar
invites") to receive a real calendar invite by email when a session is
confirmed. It adds straight to Gmail, Apple Calendar, or Outlook, updates in
place when the session is rescheduled, and is removed if it's cancelled — no
Google sign-in or setup required. (#189) - [webapp] Subscribable calendar feed — each user can generate a personal
iCal feed URL (Profile → Calendar subscription) and subscribe to it in Google
Calendar, Apple Calendar, or Outlook to see all their confirmed sessions,
updated automatically when a session is rescheduled or cancelled. (#107) - [webapp+bot] Standing availability, absences, and session-at-risk warnings —
players can set recurring weekly availability (in their own timezone) and mark
date-range absences on their profile; when a GM proposes times, the voting grid
and create form flag who is unavailable. Campaigns can set a minimum-players
quorum, and when a confirmed session is likely to fall below it (from absences
or explicit "no" votes) the GM gets a one-time Discord warning and the session
is flagged in the web app. (#104) - [webapp] Recurring session series — GMs can set a game to repeat (weekly,
every other week, or monthly on the same weekday) at a fixed campaign-time,
and Quest Board auto-creates the sessions a few weeks ahead. Times stay put
across daylight-saving changes. Individual occurrences can be rescheduled,
cancelled, or detached without affecting the rest, and editing the series can
either apply to just future sessions or also update upcoming unmodified ones.
Reminders work exactly as they do for one-off sessions. (#98)
Fixed
- [webapp] Session times are now anchored to the campaign's timezone — the
scheduler previously read the naive picker value in the browser's zone, so a
GM scheduling "7:00 PM" from a different timezone than the campaign stored (and
everyone saw) the wrong instant. Scheduling inputs now convert from campaign
time to UTC correctly (DST-aware), every displayed time carries an explicit
zone label, and the session detail page and voting grid add a "your time" line
when your zone differs from the campaign's. Milestone dates are treated as
floating calendar dates so they never shift a day across zones. Campaign
settings offer the full IANA timezone list. Backend and bot were already
correct (UTC storage; Discord dynamic timestamps). (#95)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] Emailed calendar invites — opt in (Profile → "Email me calendar
-
v3.5.0
StableSome checks failedRelease / Build and push versioned images (push) Failing after 24sRelease / Create Forgejo release (push) Successful in 25sCI / Docker image build (push) Successful in 30sCI / Backend lint (ruff) (push) Successful in 55sCI / Frontend tests, audit, and build (push) Successful in 1m43sCI / Bot tests and audit (push) Successful in 2m22sCI / Backend migration, tests, and audit (push) Successful in 4m4sreleased this
2026-07-17 15:24:21 +00:00 | 725 commits to main since this releaseFrontend platform & PWA.
Modernises the SPA foundation: a shared API client with session-expiry
handling, route-level code splitting, real ESLint enforcement, and installable
PWA scaffolding. Frontend suite grew 69 → 106 tests. No API contract change.Added
- [webapp] Quest Board is installable — a web manifest, icons (including a
maskable one) and a service worker mean you can add it to a phone home screen
and open it as a standalone app. The app shell and every route chunk are
precached, so a refresh with no signal renders the real UI instead of the
browser's offline error. Authenticated API responses are deliberately never
cached: they always go to the network, so nothing stale is ever shown as
current. New deploys activate immediately rather than leaving anyone on a
stale bundle. (#108)
Changed
- [webapp] First visit now follows your system light/dark setting instead of
always starting dark, and the theme is applied before first paint, so
light-mode users no longer get a dark flash on every load. An explicit choice
via the toggle still wins and persists; until you make one, the app keeps
following the OS. (#108) - [webapp] Pages now load on demand — routes are code-split, cutting the
initial JavaScript download by 64% (686 kB → 244 kB; 172 kB → 78 kB
gzipped). Opening the dashboard no longer downloads the admin panel and the
entire markdown toolchain first: the wiki's 169 kB markdown renderer is
fetched only when you actually open a wiki article. (#105)
Fixed
- [webapp] An expired session now sends you to the login page — every
src/api/*module runs through a shared client that handles 401 centrally.
Previously auth was only checked once at mount, so when the 8h session expired
mid-use every request failed while the app carried on rendering a dead page.
The client also aborts in-flight requests on navigation (a slow response from
a route you already left can no longer resolve late and overwrite fresher
state) and de-duplicates identical concurrent GETs behind a short TTL cache,
so a dashboard with N campaigns no longer issues N uncached requests per
mount. (#103) - [frontend] ESLint now actually enforces the React hooks rules — the config
had registered a no-op stub foreslint-plugin-react-hooks, so
exhaustive-depshad never linted anything. Wiring up the real plugin
surfaced five genuine findings, including a recording-dashboard effect that
would have rebuilt its live-status EventSource on every render. (#21) - [deps] Renovate no longer decouples
pydanticfrompydantic-core— the
compiledbot/requirements.txtlockfile is now managed by Renovate's
pip-compilemanager (which regenerates the lock) instead of
pip_requirements(which edits pins in place, one package per PR). pydantic
pinspydantic-coreto an exact version, so in-place bumps broke every
pydantic update withResolutionImpossible. Lock regenerated to a consistent
pair (pydantic 2.13.4/pydantic-core 2.46.4), which also picked up a
missingpyjwtpin required byredis. (#178)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] Quest Board is installable — a web manifest, icons (including a
-
v3.4.1
StableSome checks failedRelease / Build and push versioned images (push) Failing after 9sRelease / Create Forgejo release (push) Successful in 14sCI / Backend lint (ruff) (push) Successful in 1m58sCI / Frontend tests, audit, and build (push) Successful in 2m7sCI / Docker image build (push) Successful in 3m43sCI / Bot tests and audit (push) Successful in 4m17sCI / Backend migration, tests, and audit (push) Successful in 6m6sreleased this
2026-07-16 18:27:12 +00:00 | 737 commits to main since this releasePatch: deploy healthcheck fix.
Fixed
- [ops] Frontend healthcheck probes
127.0.0.1instead oflocalhost— the
nginx:alpineimage mapslocalhostto IPv6::1only, but nginx binds IPv4
0.0.0.0:80, so the #84wget --spider http://localhost/healthcheck got
connection-refused and flapped the container unhealthy even though nginx
served fine. (backend/bot use Debian images wherelocalhostresolves to
127.0.0.1, so they were unaffected.)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [ops] Frontend healthcheck probes
-
v3.4.0
StableSome checks failedCI / Docker image build (push) Successful in 13sRelease / Create Forgejo release (push) Successful in 18sCI / Backend lint (ruff) (push) Successful in 36sRelease / Build and push versioned images (push) Failing after 9sCI / Bot tests and audit (push) Successful in 1m46sCI / Backend migration, tests, and audit (push) Failing after 2m23sCI / Frontend tests, audit, and build (push) Successful in 3m40sreleased this
2026-07-16 06:08:49 +00:00 | 739 commits to main since this releaseReliability, operations & self-hosting.
All 14 milestone issues. Makes failures visible and recoverable and smooths
deploy/upgrade for self-hosters. Backend and bot suites green (backend 395,
bot 186).Added
- [webapp+bot] At-least-once bot event delivery — backend→bot session events
now flow over a durable Redis Stream (qb:bot:events) with a consumer group
and per-eventevent_ididempotency, replacing fire-and-forget pub/sub that
silently dropped events while the bot was down and could double-post on the
HTTP fast path. (#81) - [webapp] Automatic ordered migrations on deploy — a one-shot
migrate
service runsalembic upgrade headbefore backend/worker/beat start; fresh
installs self-provision and app services never boot against an old schema. (#89) - [webapp+bot] Version handshake —
GET /api/bot/metaplus a bot startup
check refuse to run on a contract-version mismatch and warn on drift. (#93) - [webapp] VAD silence-trim before transcription (opt-in, Admin → Bot
Settings) cuts Whisper time/cost by transcribing only speech spans, with exact
session-relative timestamp offsets. (#101) - [webapp] Pagination (
limit/offset+X-Total-Countheader) on list
endpoints, backward compatible. (#99) - [webapp] Background-job failure counters (
backup_failed_total,
celery_task_failed_total,bot_event_delivery_failed_total), example
Prometheus alert rules, and an optional Sentry hook. (#86) - [ops] Container hardening — worker/beat/frontend healthchecks, memory
limits, and Redis AOF persistence. (#84) - [ci] Versioned Docker images published to the Forgejo container registry
on tag. (#91) - [ops] Disaster-recovery docs — restore runbook, off-host backup copies,
and media-volume backup. (#80)
Changed
- [bot] Recording streams to disk instead of buffering whole sessions in RAM
— memory is now bounded (was ~20 GB for a 5-person/6-hour session), and audio
survives a crash. (#82) - [webapp] Backend hot-path performance — module-level Redis connection
pools, SSE releases its pooled DB session, N+1 vote/NPC queries replaced with
grouped aggregates, and asessions (status, confirmed_time)composite
index. (#96) - [webapp+bot] Pinned Python base image to 3.12 to match CI and avoid the
3.13+ stdlibaudioopremoval that discord.py voice needs. (#87) - [webapp] Tree-wide
ruff formatapplied and enforced as a CI gate. (#20)
Fixed
- [webapp] Celery event-loop crashes — every task now uses a per-call
NullPool session (task_session()), fixing intermittent "Future attached to a
different loop" failures in recap-email and vote auto-close. (#129)
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp+bot] At-least-once bot event delivery — backend→bot session events
-
v3.3.0
StableAll checks were successfulCI / Frontend tests, audit, and build (push) Successful in 1m18sCI / Bot tests and audit (push) Successful in 2m2sRelease / Create Forgejo release (push) Successful in 1m55sCI / Backend lint (ruff) (push) Successful in 2m17sCI / Docker image build (push) Successful in 2m48sCI / Backend migration, tests, and audit (push) Successful in 3m31sreleased this
2026-07-15 04:38:26 +00:00 | 776 commits to main since this releaseHardening: bugs & security.
Correctness and security fixes from the July 2026 full-project review. All 13
milestone issues; backend and bot test suites green.Security
- [webapp] Discord webhook SSRF allowlist —
discord_webhook_url(campaign
and admin-fallback) is validated with real URL parsing against a Discord host
allowlist (discord.com,discordapp.com,ptb.discord.com,
canary.discord.com; schemehttps; path/api/webhooks/) at write time,
and re-checked immediately before every server-side POST so legacy rows are
neutralised — a non-Discord URL is logged and skipped, never fetched. Closes a
server-side request forgery vector against the deployment's internal network. - [webapp+bot] Recording authorization —
/record start|stopnow require the
invoker to hold Discord Manage Server or be the campaign's verified-linked GM
(fails closed on lookup error); the audio-upload endpoint verifies the
session's campaign belongs to the suppliedguild_id(404 on mismatch, no
task queued), preventing cross-guild audio attachment. New
GET /api/bot/guilds/{guild_id}/gmssupports the bot-side check. - [webapp] Reject placeholder/weak secrets at startup —
SECRET_KEY(rejects
the.env.exampleplaceholder, requires ≥ 32 chars),DATABASE_URL/
DATABASE_MIGRATE_URL(rejectchangeme/changeme_migratepasswords, parsed
not substring-matched), andbot_api_key(rejects placeholders / < 16 chars
when set) fail fast with an actionable message. - [webapp] DML-only app DB role — the app now connects as a non-superuser
questboardrole (DML only);POSTGRES_USERbecomes a dedicated superuser
questboard_adminused only for break-glass;pg_dumpbackups run as the
schema-owningquestboard_migraterole. Breaking for existing installs —
see the "Existing Installs" runbook indocs/OPERATIONS.md. Requires
POSTGRES_USER=questboard_adminand a newPOSTGRES_APP_PASSWORDin.env. - [webapp] Recap-email HTML escaping — GM- and LLM-supplied values
(session_title,campaign_name,summary) are HTML-escaped in recap
emails, and CR/LF are stripped from the subject (header-injection guard). - [webapp] Stop echoing raw LLM errors — the
/askendpoint returns a
genericdetailand logs the real exception server-side. - [bot] HTTP server binds loopback by default —
HTTP_HOSTdefaults to
127.0.0.1(compose overrides to0.0.0.0for the internal network); the
aiohttp request-body cap is now pinned explicitly.
Bug fixes
- [bot]
/record startguard leak — a bad session UUID (or the user not
being in voice) no longer strands the guild in the "starting" set, which had
permanently locked out recording until a bot restart. - [bot] Audio cleanup no longer deletes queued recordings — the
audio_temp
sweep runs once per process (not on every gateway reconnect) and skips any
session directory the bot has marked as handed off to the backend, so
recordings awaiting or undergoing processing survive reconnects and restarts. - [webapp] Duplicate reminder delivery removed — the legacy ETA-scheduled
reminder path is retired; thepoll_session_remindersBeat task (deduped by
session_reminders_sentrows) is the sole delivery mechanism, so a rescheduled
session is reminded exactly once per offset.celery_task_idsis no longer
written. - [webapp] Deterministic lore-match chunk index — replaced
PYTHONHASHSEED-
salted builtinhash()with a SHA-1 digest, so lore consolidation survives a
worker restart mid-pipeline instead of failing with a consolidation timeout. - [bot] Discord embed size budget — a shared
fit_embedhelper keeps the
whole embed (title + description + fields + footer) under Discord's 6000-char
total, so/recapand summary embeds no longer fail outright on long content. - [webapp] Public analytics share page — the frontend now proxies
/public/
to the backend (nginx + Vite dev), so analytics share links load instead of
returning the SPA HTML.
Docs
- Corrected the recording architecture (per-speaker WAV +
/audiopipeline, no
longer "not yet implemented"), the DB-role model, frontend stack versions, and
Python base-image version acrossCLAUDE.md,webapp/CLAUDE.md,
bot/CLAUDE.md,ARCHITECTURE.md,docs/API.md, anddocs/DEVELOPMENT.md.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- [webapp] Discord webhook SSRF allowlist —
-
v3.2.0 — Scheduling polish
Stablereleased this
2026-06-23 00:25:34 +00:00 | 915 commits to main since this releaseScheduling accuracy and lifecycle improvements.
- Tighten the reminder poll window to avoid early delivery
- Allow cancelling not-yet-started sessions and reopening them for a fresh vote
- Wiki link / lore dedupe fixes
Retroactive release tagged at
73a63a0(2026-06-17), currentmainHEAD.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v3.1.0 — Notifications & memory
StableSome checks failedRelease / Create Forgejo release (push) Failing after 42sreleased this
2026-06-23 00:25:32 +00:00 | 918 commits to main since this releaseRicher notifications, session memory search, and a dependency/security sweep.
Notifications & reminders
- Optional fourth session reminder offset (backend accepts four, rejects five+)
- Vote non-responder reminders: one-time three-day nudge for still-proposed sessions; linked non-voters pinged via the bot, unlinked listed by name for GM context
- Deliver proposed sessions directly to the bot's
/notifyendpoint, Redis pub/sub fallback - Restore session-confirmation notifications
Memory & wiki
- Session memory / summary + transcript search (Postgres full-text; transcript matching GM-only)
- Structured character visibility controls
- Preserve imported member character links; import legacy milestone session references
- Expose wiki article creation; wiki-link and lore-proposal cap fixes
Security & CI
- Patch vulnerable dependencies; resolve bot voice dependency security pins; bump cryptography
- Move CI automation to Forgejo
Retroactive release tagged at
dcf2589(2026-05-19).Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v3.0.0 — Collaborative GM & player tooling
StableSome checks failedRelease / Create GitHub Release (push) Failing after 4sreleased this
2026-06-23 00:25:26 +00:00 | 940 commits to main since this releaseMajor release. Quest Board shifts from a GM-authored knowledge base to a collaborative platform: players can now own and edit their own content, and GMs get a dedicated planning workspace.
Player-editable wiki (lore step 4c)
- Ownership data model with primary/secondary roles and player-private notes
- Permission-aware API responses; player and owner editing flows; GM owner-management UI
- Visibility-aware structured character data (public / GM-only / player-private) across articles, versions, and search
- Alias and name-history with non-destructive rename and effective date/session ranges
GM Campaign Planning Workspace
- GM-only
/campaigns/:id/planningwith name generator (Redis-cached background pre-generation), arc board (CRUD + reorder), session beat planner, and plot-thread tracker
Wiki-native editing
- Article edit/create move into the article view (
/wiki/:entryId/edit,/wiki/new) - Markdown formatting toolbar;
[[Entry Title]]links preserved in Markdown body - Lore proposal review queue restored on the wiki landing; LLM draft-review pipeline (rephrase, merge, feedback iterations, versioned approval)
Layout
- Responsive 3-column page layouts across major pages
Retroactive release tagged at
7e39d16(2026-05-07). Third major: player collaboration + GM planning surface.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
released this
2026-06-23 00:25:20 +00:00 | 1004 commits to main since this releaseCampaign portability and a real-time recording dashboard, with migration and export hardening.
Import / export
- Campaign import/restore from a backup zip (lossless
data.json+ Markdown) - Bundle lore/wiki media files into export zips (schema 3)
- Timezone-aware export timestamps
Live recording dashboard
- Redis pub/sub bot messaging; backend events publish to Redis and the bot dispatches
- Recording status streamed over Server-Sent Events, replacing the 2s polling loop
- Show captured speakers plus present-but-silent voice-channel members
Operations
- Guarded production migration workflow (
make backup-now/make migrate-safewith DB preflight + snapshot) - Hardened drifted-dev-DB migrations; idempotent NPC-table repair at head
- Install libmagic in backend images
Retroactive release tagged at
5459a63(2026-05-04).Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- Campaign import/restore from a backup zip (lossless