• v3.6.1 2a404adcea

    v3.6.1
    All checks were successful
    Release / Create Forgejo release (push) Successful in 21s
    CI / Docker image build (push) Successful in 38s
    CI / Backend lint (ruff) (push) Successful in 40s
    Release / Build and push versioned images (push) Successful in 27s
    CI / Bot tests and audit (push) Successful in 1m52s
    CI / Frontend tests, audit, and build (push) Successful in 4m3s
    CI / Backend migration, tests, and audit (push) Successful in 7m5s
    Stable

    rbrooks released this 2026-07-17 22:40:29 +00:00 | 705 commits to main since this release

    Patch: 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 a location /calendar/ proxy block. (Caught on the dev
      deploy before v3.6.0 reached production.)
    Downloads
  • v3.6.0 314ffbc80d

    v3.6.0
    Some checks failed
    CI / Docker image build (push) Successful in 18s
    Release / Create Forgejo release (push) Successful in 21s
    CI / Backend lint (ruff) (push) Successful in 2m23s
    CI / Frontend tests, audit, and build (push) Successful in 2m43s
    Release / Build and push versioned images (push) Successful in 4m9s
    CI / Bot tests and audit (push) Successful in 7m45s
    CI / Backend migration, tests, and audit (push) Has been cancelled
    Stable

    rbrooks released this 2026-07-17 22:29:59 +00:00 | 706 commits to main since this release

    Scheduling: 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_VERSION stays 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
  • v3.5.0 2ea6f26531

    v3.5.0
    Some checks failed
    Release / Build and push versioned images (push) Failing after 24s
    Release / Create Forgejo release (push) Successful in 25s
    CI / Docker image build (push) Successful in 30s
    CI / Backend lint (ruff) (push) Successful in 55s
    CI / Frontend tests, audit, and build (push) Successful in 1m43s
    CI / Bot tests and audit (push) Successful in 2m22s
    CI / Backend migration, tests, and audit (push) Successful in 4m4s
    Stable

    rbrooks released this 2026-07-17 15:24:21 +00:00 | 725 commits to main since this release

    Frontend 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 for eslint-plugin-react-hooks, so
      exhaustive-deps had 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 pydantic from pydantic-core — the
      compiled bot/requirements.txt lockfile is now managed by Renovate's
      pip-compile manager (which regenerates the lock) instead of
      pip_requirements (which edits pins in place, one package per PR). pydantic
      pins pydantic-core to an exact version, so in-place bumps broke every
      pydantic update with ResolutionImpossible. Lock regenerated to a consistent
      pair (pydantic 2.13.4 / pydantic-core 2.46.4), which also picked up a
      missing pyjwt pin required by redis. (#178)
    Downloads
  • v3.4.1 b5b53ff478

    v3.4.1
    Some checks failed
    Release / Build and push versioned images (push) Failing after 9s
    Release / Create Forgejo release (push) Successful in 14s
    CI / Backend lint (ruff) (push) Successful in 1m58s
    CI / Frontend tests, audit, and build (push) Successful in 2m7s
    CI / Docker image build (push) Successful in 3m43s
    CI / Bot tests and audit (push) Successful in 4m17s
    CI / Backend migration, tests, and audit (push) Successful in 6m6s
    Stable

    rbrooks released this 2026-07-16 18:27:12 +00:00 | 737 commits to main since this release

    Patch: deploy healthcheck fix.

    Fixed

    • [ops] Frontend healthcheck probes 127.0.0.1 instead of localhost — the
      nginx:alpine image maps localhost to IPv6 ::1 only, but nginx binds IPv4
      0.0.0.0:80, so the #84 wget --spider http://localhost/ healthcheck got
      connection-refused and flapped the container unhealthy even though nginx
      served fine. (backend/bot use Debian images where localhost resolves to
      127.0.0.1, so they were unaffected.)
    Downloads
  • v3.4.0 2b6a8ff354

    v3.4.0
    Some checks failed
    CI / Docker image build (push) Successful in 13s
    Release / Create Forgejo release (push) Successful in 18s
    CI / Backend lint (ruff) (push) Successful in 36s
    Release / Build and push versioned images (push) Failing after 9s
    CI / Bot tests and audit (push) Successful in 1m46s
    CI / Backend migration, tests, and audit (push) Failing after 2m23s
    CI / Frontend tests, audit, and build (push) Successful in 3m40s
    Stable

    rbrooks released this 2026-07-16 06:08:49 +00:00 | 739 commits to main since this release

    Reliability, 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-event event_id idempotency, 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 runs alembic upgrade head before backend/worker/beat start; fresh
      installs self-provision and app services never boot against an old schema. (#89)
    • [webapp+bot] Version handshakeGET /api/bot/meta plus 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-Count header) 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 a sessions (status, confirmed_time) composite
      index. (#96)
    • [webapp+bot] Pinned Python base image to 3.12 to match CI and avoid the
      3.13+ stdlib audioop removal that discord.py voice needs. (#87)
    • [webapp] Tree-wide ruff format applied 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
  • v3.3.0 aa3b4299db

    v3.3.0
    All checks were successful
    CI / Frontend tests, audit, and build (push) Successful in 1m18s
    CI / Bot tests and audit (push) Successful in 2m2s
    Release / Create Forgejo release (push) Successful in 1m55s
    CI / Backend lint (ruff) (push) Successful in 2m17s
    CI / Docker image build (push) Successful in 2m48s
    CI / Backend migration, tests, and audit (push) Successful in 3m31s
    Stable

    claude-bot released this 2026-07-15 04:38:26 +00:00 | 776 commits to main since this release

    Hardening: 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 allowlistdiscord_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; scheme https; 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|stop now 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 supplied guild_id (404 on mismatch, no
      task queued), preventing cross-guild audio attachment. New
      GET /api/bot/guilds/{guild_id}/gms supports the bot-side check.
    • [webapp] Reject placeholder/weak secrets at startupSECRET_KEY (rejects
      the .env.example placeholder, requires ≥ 32 chars), DATABASE_URL /
      DATABASE_MIGRATE_URL (reject changeme/changeme_migrate passwords, parsed
      not substring-matched), and bot_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
      questboard role (DML only); POSTGRES_USER becomes a dedicated superuser
      questboard_admin used only for break-glass; pg_dump backups run as the
      schema-owning questboard_migrate role. Breaking for existing installs
      see the "Existing Installs" runbook in docs/OPERATIONS.md. Requires
      POSTGRES_USER=questboard_admin and a new POSTGRES_APP_PASSWORD in .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 /ask endpoint returns a
      generic detail and logs the real exception server-side.
    • [bot] HTTP server binds loopback by defaultHTTP_HOST defaults to
      127.0.0.1 (compose overrides to 0.0.0.0 for the internal network); the
      aiohttp request-body cap is now pinned explicitly.

    Bug fixes

    • [bot] /record start guard 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; the poll_session_reminders Beat task (deduped by
      session_reminders_sent rows) is the sole delivery mechanism, so a rescheduled
      session is reminded exactly once per offset. celery_task_ids is no longer
      written.
    • [webapp] Deterministic lore-match chunk index — replaced PYTHONHASHSEED-
      salted builtin hash() 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_embed helper keeps the
      whole embed (title + description + fields + footer) under Discord's 6000-char
      total, so /recap and 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 + /audio pipeline, no
      longer "not yet implemented"), the DB-role model, frontend stack versions, and
      Python base-image version across CLAUDE.md, webapp/CLAUDE.md,
      bot/CLAUDE.md, ARCHITECTURE.md, docs/API.md, and docs/DEVELOPMENT.md.
    Downloads
  • v3.2.0 73a63a0253

    v3.2.0 — Scheduling polish
    Some checks failed
    CI / Frontend tests, audit, and build (push) Has been cancelled
    CI / Backend migration, tests, and audit (push) Has been cancelled
    CI / Bot tests and audit (push) Has been cancelled
    Release / Create Forgejo release (push) Failing after 41s
    Stable

    rbrooks released this 2026-06-23 00:25:34 +00:00 | 915 commits to main since this release

    Scheduling 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), current main HEAD.

    Downloads
  • v3.1.0 dcf2589cb5

    v3.1.0 — Notifications & memory
    Some checks failed
    Release / Create Forgejo release (push) Failing after 42s
    Stable

    rbrooks released this 2026-06-23 00:25:32 +00:00 | 918 commits to main since this release

    Richer 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 /notify endpoint, 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
  • v3.0.0 7e39d16766

    v3.0.0 — Collaborative GM & player tooling
    Some checks failed
    Release / Create GitHub Release (push) Failing after 4s
    Stable

    rbrooks released this 2026-06-23 00:25:26 +00:00 | 940 commits to main since this release

    Major 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/planning with 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
  • v2.1.0 5459a63df2

    v2.1.0 — Import/restore & live recording dashboard
    Some checks failed
    CI / Frontend tests (push) Failing after 0s
    CI / Backend tests (push) Failing after 1s
    Release / Create GitHub Release (push) Failing after 4s
    Stable

    rbrooks released this 2026-06-23 00:25:20 +00:00 | 1004 commits to main since this release

    Campaign 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-safe with 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