Changelog
Shipped this week.
What's new, what's fixed, what was broken last week. Honest notes from a one-DM dev team.
May 24, 2026
v2.0.0-alpha.23
alphaFounder pricing is now an alpha/beta/founder ladder. The single founder pool became three waves: 3 alpha + 7 beta seats (free until launch) + 10 founder seats at the locked launch rate — 20 total, then standard Pro for everyone. The Founder plan, previously uncapped on AI, now carries a generous monthly cap (1,500 text / 100 portraits / 15 audio-hrs) so worst-case cost is bounded; BYOK still removes the cap. Added a Stripe price for the $10/mo locked founder rate (STRIPE_PRICE_FOUNDER_MONTHLY) and replaced the stale $199-once lifetime founder block on /pricing and the homepage.
Slash-command live-session chat. A new realtime chat panel rides the existing campaign Ably channel — type freely or use /roll NdS[+M], /note, /gmnote, /me, or /whisper @user. /roll commands hit the server-side dispatcher and persist as proper session events (dice in the activity feed, not just inline text formatting). The slash-menu autocomplete (already in the DM-assistant chat) was extended with the new command set. Schema: session_chat_message table with kind enum (say/me/whisper) + recipient. Migration 0045.
AI co-DM suggestion sidebar (pull-only). A GM-only "Suggest" button in the live session sidebar pulls a co-DM nudge — npc_voice, rules_reminder, tension_hook, or pacing — based on the campaign canon + recent session events + chat. Quota-limited per session (env DD_CODM_PER_SESSION_LIMIT, default 20) to keep costs sane. Suggestions can be dismissed or dropped straight into chat as a GM-only note. Push-style auto-trigger is intentionally deferred — too easy to spam.
Voice production polish. Audio constraints tightened (explicit echoCancellation/noiseSuppression/AGC + 48kHz mono). New /api/voice/ice-servers route mints Cloudflare Calls TURN credentials when CF_TURN_KEY_ID/SECRET are set (falls back to STUN-only). iceConnectionState=failed surfaces a Retry toast that re-initiates the offer. Per-plan participant caps: Free=0, Pro=6, Founder=8 — enforced server-side at room join, with cap badge + upgrade CTA in the UI.
Whole-table voice recording + transcripts. AudioContext mixer pulls every WebRTC remote track + the GM's mic into one MediaStream — the recording captures all speakers, not just the GM. New transcript_job table (migration 0046) queues uploads; a minutely Vercel Cron picks them up, runs Deepgram Nova-3 with speaker diarization, and writes a Markdown transcript with speaker labels + timestamps back to the session. Per-plan audio-quota gate. Env: DEEPGRAM_API_KEY.
Obsidian sync — server-rendered markdown + community plugin scaffold. All 10 /api/v1/me/* detail endpoints now accept ?format=markdown and return identical markdown to the in-app "Download" button. The CLI (pnpm sync:vault) was updated to use the new path — drops ~80 lines of client-side serialiser duplication. Plugin scaffold lands at plugins/obsidian/ with ribbon button, status bar ("DD: synced 2m ago"), settings UI (PAT mask-on-blur, campaign multi-select, poll interval), and auto-sync interval. Submission to the Obsidian community-plugin store is a separate wall-clock step.
Tactical maps Phase 1 — scenes + tokens + realtime drag. New /dashboard/campaigns/[slug]/scenes/ route with list, create, and editor pages. Schema: tactical_scene + scene_token tables (migration 0047). Konva-backed canvas: pan + wheel-zoom (0.25–4×), grid overlay, draggable tokens with HP bars + name initials. Token moves publish on a new campaign:{id}:scene:{sceneId} Ably channel so every connected client sees positions update in real time. GM can hit "Add to scene" from the combat tracker to populate a scene with the active combatants. Fog of war + dynamic vision + walls are explicitly Phase 2 — separate cards.
Combat action buttons (wired). Attack / Dash / Move / Other are back on the active-turn card — every button now actually does something. Attack deducts damage from the chosen target atomically (defeats NPCs at 0 hp; PCs at 0 hp stay in initiative for death saves). Dash and Move drop a one-line entry in the session activity feed. Other takes a free-text description for Help / Dodge / Ready / etc. Spell-cast + Item-use are intentionally deferred — the action schema already accepts those kinds so the wiring can land without a re-shape.
May 23, 2026
v2.0.0-alpha.21
alphaCross-campaign asset library. Every blob upload (manual portraits, AI-generated portraits/landscapes/maps, session audio) now indexes into a user-scoped `user_asset` row alongside the existing campaign-scoped path. A portrait painted for one campaign can be re-used in another via the new "From library" tab on portrait + landscape upload modals. Picker shows a grid of asset thumbnails with debounced search, rename, and soft-delete. Backed by migration 0039 (table + indexes) + 0040 (CHECK constraint on kind, since drizzle-kit's text-enum doesn't emit one by default — surfaced by the int tests).
Outbound webhook subscriptions for the public API. Users with an `admin`-scoped PAT can register webhook URLs that receive HMAC-SHA256-signed POSTs whenever entities are created/updated/deleted, sessions end, or dice are rolled in any campaign they belong to (or in a specific one, if scoped). Foundational for Obsidian sync, IFTTT/Zapier hookups, and custom DM dashboards. Manageable via /dashboard/settings/api under the PAT card OR via /api/v1/me/webhooks (admin scope). Mirrors the existing Foundry sync pattern but per-user. Migration 0041_webhook_endpoints + 0042/0043/0044 iterative aligns.
Obsidian sync starter. New `pnpm sync:vault` CLI polls the public /me/* API with a PAT and writes one Markdown file per entity into a local Obsidian-style vault folder (one folder per campaign, one subfolder per kind). Reuses the same serializer the in-app "Download Markdown" button uses, so output is identical. Per-entity skip when the local file's `dd_updated_at` front-matter is newer than the server's — manual edits survive until the entity is touched upstream again. Polling instead of webhooks because Obsidian plugins run locally and can't accept inbound POSTs without a public relay. Full docs at /docs/obsidian including cron / systemd snippets and three extension paths (plugin port, two-way sync, webhook relay).
May 23, 2026
v2.0.0-alpha.20
alphaIn-world calendar with tick-forward. Campaigns now have a structured clock — campaign.inGameMinute (bigint minutes since a campaign-defined epoch) + calendarSystem (simple / harptos / gregorian) + calendarEpochLabel. The GM advances time from the campaign hub via +10m / +1h / +8h (long rest) / +1d / Custom buttons. Timeline events optionally pin to an in-world minute; advancing the clock auto-triggers a "events that happened" modal listing what fired in the window. Three calendar systems shipped: simple (Day N, HH:MM), harptos (12 months × 30 days + 5 named festivals — Midwinter, Greengrass, Midsummer, Highharvestide, Feast of the Moon), and gregorian. Migration 0037_sleepy_landau.
Campaign templates / starters. New DMs can pick a pre-seeded bundle of regions, NPCs, factions, lore, and quests instead of starting blank. Four shipping templates: Blank (clean slate), Coastal Mystery (Harrowfen fishing town, smuggling guild, drowned-temple POI — recommended 1-5), Frontier Hamlet (Thornwick village at the edge of the moors — recommended 1-3), Urban Intrigue (Karathos merchant city with three rival factions — recommended 5-10). All content is original — no published-setting names. File-based: templates live as JSON in content/templates/. Picker mounts on /campaigns/new alongside the AI skeleton + blank-form paths. Clone is a single db.transaction; cross-references resolve by name (NPC.factionName → factionId etc).
Public API completion: rate limiting + character POST/PATCH/DELETE. The /api/v1/me/* surface now enforces a sliding-window 60 req/min/token rate limit (in-memory bucket keyed by SHA-256 token hash, configurable). Returns 429 with Retry-After when exhausted. Characters gain full CRUD (write:characters scope) — POST is GM/co-GM only; PATCH allows players to update their own character (ownerId === userId) while GM/co-GM can patch any; DELETE is GM/co-GM only, soft-delete. Updating someone else's character returns 404 (not 403) so the API doesn't leak existence. OpenAPI spec + /docs/api page updated to cover all 6 new operations + the 429 response shape.
Discord webhook integration. Per-campaign webhooks (not a bot account — simple HTTPS POSTs to a Discord-issued URL the channel admin pastes in once). Three event kinds supported: session-recap (auto-posted when an AI recap is generated; gold embed with the recap markdown), dice-roll (auto-mirrored from logSessionRoll; nat 20 = green, nat 1 = red, mid = blue), session-reminder (daily cron at /api/cron/discord-reminders scans for play sessions scheduled within 24h and posts a reminder). Per-webhook event toggles let the GM choose which kinds fan out. GM-only "Discord notifications" card in campaign settings: paste URL + label, live test post on connect, status indicator with lastPostAt + lastError tooltip. Migration 0038_discord_webhook.
PATCH endpoints on /api/v1/me/* now require at least one field in the body — empty {} returns 422 instead of silently bumping updatedAt with no real change. Surfaced during the API completion ship.
May 22, 2026
v2.0.0-alpha.19
alphaPublic REST API v1 + personal access tokens. New `personal_access_token` table issues `ddp_<base64>` tokens with scoped permissions (read:campaigns, read:npcs, read:world, read:sessions, read:characters, read:dice, write:* counterparts, admin) mintable at /dashboard/settings/api. Full /me/* surface: GET /me, GET/CRUD on campaigns, npcs, regions, settlements, pois, shops, factions, lore, quests, encounters, timeline, sessions; read-only for characters; GET /me/dice-rolls for the campaign roll log. Same visibility filtering as the app — non-GM tokens see only visibility=players entities and GM-private fields (gmNotes, backstory, prep) are stripped from responses. OpenAPI 3.1 spec at /api/v1/openapi.json. Docs page at /docs/api with curl/Node/Python samples.
Stripe billing wired end-to-end. `plan` column on `user` (free | pro | founder) plus stripeCustomerId / stripeSubscriptionId / stripeSubscriptionStatus / currentPeriodEnd. /pricing un-redirected, four tier cards POST to createCheckoutSession; /dashboard/settings/billing surfaces the customer portal; signed webhook handles checkout.session.completed, subscription.updated, subscription.deleted, invoice.payment_failed (idempotent). Founder is a manual flag — not Stripe-purchasable.
AI generation quotas enforced. New `ai_generation_log` partitioned by (userId, month, kind). 13 AI-producing actions (chat, world AI, NPC stat block, shop inventory, recap, recap-extract, loot, PDF import, campaign skeleton, copilot, proposal-enrich, portrait generation, map generation) call checkAndIncrementQuota() before the model call and return a 429-style ActionResult when over plan limits. Plans: free 30 text / 5 portrait / 0 audio; pro 500 / 50 / 10 hrs; founder unlimited. BYOK users bypass entirely (encryptedAiKey IS NOT NULL short-circuit). Soft 80% warning + hard 100% block surfaced in /dashboard layout via QuotaBanner.
BYOK (Bring Your Own Key) for OpenAI / Anthropic / OpenRouter. AES-256-GCM at rest using BYOK_ENCRYPTION_KEY env var; provider/added-at columns track which key is in use. Per-user model factory swap inside src/lib/ai.ts — `getOpenRouterForUser(userId)` returns a per-user provider with the decrypted key when set, falls back to the app key otherwise. UI at /dashboard/settings/ai validates the key with a 1-token test completion before storing. BYOK users skip AI quotas (logged but uncapped).
Entity version history. New `entity_version` table snapshots the full pre-update row before every db.update() across 12 entity types (npc, region, settlement, poi, shop, faction, lore, quest, encounter, timeline_event, character, session). Plan-gated retention via PLANS[plan].versionHistoryDepth: free 0 (off), pro 50, founder 200. Per-entity History sheet on every detail page with side-by-side diff (gold-accent on changed fields, dimmed unchanged) and one-click Restore. restoreRevision sanitizes immutable fields (id, campaignId, createdAt, deletedAt) and replays the snapshot through the entity's update action — a restore is itself a new version.
Voice chat MVP. WebRTC mesh (≤6 participants per room) over Ably signaling on `campaign:{id}:voice` channels. New voice_room + voice_participant tables; src/actions/voice.ts exposes joinVoiceRoom / leaveVoiceRoom / toggleVoiceMute. Pro-gated; free users see "Voice chat requires the Pro plan." UI mounts in the live-session page below the audio recorder: mic permission prompt → peer connections to existing participants → mute toggle → leave. Echo cancellation, recording, and advanced controls deferred.
Public-facing /api page now redirects to /docs/api (the previous "coming during beta" placeholder is gone). /pricing un-redirected from /beta into the live four-tier pricing UI.
Markdown download buttons on every world-entity detail page (regions, settlements, POIs, shops, factions, lore, NPCs, quests, encounters, timeline). Markdown serializer extended to cover encounters + timeline; timeline uses id-based lookup since the table has no slug column.
PWA install prompt now respects "Not now" for the rest of the session. Previously dismissing the install banner only set the localStorage 14-day suppress key; if the beforeinstallprompt event fired again in the same session (after install eligibility was re-evaluated by the browser), the banner re-appeared. Added session-scoped dismissal — sessionStorage + module-level flag — and gated the event handler on both.
Chat @-mention chips for NPCs no longer 404. entityDetailUrl was routing /dashboard/campaigns/{slug}/world/npcs/{slug} — but the actual NPC detail page lives at /dashboard/campaigns/{slug}/npcs/{slug} (no /world/ segment). Fixed for NPCs only; the world-tab entities (regions, settlements, etc.) correctly use the /world/ base.
Trash sweep window aligned with the FAQ. Cron used SWEEP_THRESHOLD_DAYS=30; FAQ copy said "28-day restore window". Picked 30 (the actual behavior) and updated the FAQ string. FOUNDER_CAP also reconciled to 10 to match the "10 founder seats" landing-page copy.
AI-edit NPC proposals from the chat now propose an edit to the existing entity instead of a new entity. New `npcEdit` proposal type carries `targetId` + sparse data fields; persistProposal routes it to updateNpc instead of createNpc. Proposal card renders as a per-field diff (gold left-border on changed fields, dimmed on unchanged, "(will be cleared)" for null patches) against the live NPC row, fetched via a new getNpcForDiff server action.
SRD seeder. New `pnpm db:seed:srd` script populates srd_condition (15 rows), srd_spell (319), srd_monster (334), srd_item (237) from dnd5eapi.co (5.1 SRD, open data). Idempotent — onConflictDoUpdate by slug. Supports --only=conditions|spells|monsters|items for partial refreshes.
May 22, 2026
v2.0.0-alpha.18
alphaLong-form recap prose now routes through Claude Sonnet 4.6 (new `narrator` slot in the admin model swapper, OpenRouter via anthropic/claude-sonnet-4.6). Sonnet has stronger narrative voice and continuity than the worldGen DeepSeek for flowing text. Structured-JSON phases (clarifying questions, entity extraction) stay on the cheaper slots — Sonnet is overkill for those.
Episodic memory across sessions. New campaign_episodic_fact table — after every recap generates, a second AI pass extracts 3-8 distilled facts (NPC voice quirks, faction beats, world events, party decisions). The 20 most recent facts inject into the system prompt of the NEXT recap as "Recurring threads from prior sessions", so continuity carries across sessions instead of each recap reading the world fresh.
PDF adventure import. Drag a published-adventure PDF (Curse of Strahd, Lost Mine of Phandelver, any third-party module) into campaign Settings and the AI extracts NPCs, regions, settlements, POIs, factions, lore, and quests into proposal save-cards. Reviewable per-entity with Save / Save-all using the same UI as chat + recap proposals. 50 MB ceiling, 20-per-type cap to keep the model honest. Server-side via pdf-parse + worldGen.generateObject.
co_gm role now honored across content + visibility. The role existed in campaign_player.role but was inconsistently applied — some queries gave co-GMs GM-equivalent access, others treated them as ordinary players. Audited every relevant action + page filter. Co-GMs now get full GM-equivalent access to CONTENT (read everything including visibility=gm, create/update/soft-delete entities, run combat, regenerate canon, import markdown, upload media). ADMIN remains GM-strict (transfer ownership, delete campaign, invite/revoke users, mint API tokens). 19-case integration test in tests/int/co-gm-access.int.spec.ts.
autolinkEntities longer-name-wins test was asserting via substring contains on URLs where the short slug is a prefix of the long slug (lenard / lenard-goodsong). False-positive caught a real link to the longer entity. Tightened to check for the complete markdown-link form. All 9 autolink unit tests now pass.
May 22, 2026
v2.0.0-alpha.17
alphaFoundry VTT sync goes incremental. The Foundry module no longer reloads the entire campaign on startup — instead it registers a HMAC-signed webhook with Dungeon Diary, and DD pushes per-entity events whenever a region, settlement, POI, shop, faction, lore entry, NPC, or quest is created/updated/soft-deleted. Foundry receives the payload, verifies the signature with SubtleCrypto, and upserts the matching journal entry. The full-replace "↻ Sync Dungeon Diary" button is kept as a reconciliation fallback when drift is suspected.
Session AI recap entity extraction. After the AI generates a recap, a second pass scans the prose for NPCs, locations, lore, factions, and quests mentioned but not yet in canon, and surfaces them as proposal save cards on the recap tab. One click commits each into the world (reuses the chat proposal-card UI + the new sessionRecapProposalAccept table for persisted ✓ Saved state across reloads). New "Extract entities" button on the recap tab kicks the extraction off; "Save all" handles the bulk case.
Demo account no longer burns OpenRouter budget. The Ravenmoor Reach demo campaign runs a daily reset cron and is shared across every "Try the demo" visitor — letting them all run AI calls torched the budget. New isDemoAccount() + guardAiForUser() helpers gate every AI entry point: chat stream, chat actions, all 5 image generators + the preview action, world AI generation, co-pilot conjure, session recap, recap-entity extraction, loot/skeleton/stat-block/shop-inventory, proposal enrich + save, and map generation. Returns "AI generation is disabled on the shared demo account. Sign up for a free account to use AI features." Demo can still browse, search, and use every non-AI feature.
May 22, 2026
v2.0.0-alpha.16
alphaWizard chat persona — the DM Assistant got a 🧙🏻♂️ avatar and a Gandalf-style voice (warm-laconic plain Anglo-Saxon, no corporate cadence, no stage directions). Markdown now renders properly in the bubble: **bold**, *italic*, `code`, lists, blockquotes, links. Empty-bubble streaming shows a TUI-style spinner (\ | / – with cycling phrases + elapsed timer) ported from pyre.
Primary chat model swapped to qwen/qwen3-235b-a22b-thinking-2507. Thinking-enabled MoE with 22B active params — emits <think> blocks before the answer, which translates to fewer hallucinations on sparse prompts. Admin-only model switcher is now visible in the rail user-menu popover (gated by isAdmin).
Large pastes in the chat composer collapse to 📎 paste-N.txt chips instead of flooding the textarea. Threshold: ≥500 chars or ≥5 lines. Click a chip on a sent message to expand the original text. Pastes are serialized as `dd:paste:` fenced blocks so the model sees the full content and the chip rehydrates on reload.
Chat streaming survives navigation. The fetch + accumulating reply now live in a module-scoped Zustand store — close the chat tab, browse to a different page, and the generation keeps running. A bottom-right floating pill appears on every dashboard page while a chat stream is active; the browser tab title prefixes with "🧙 Thinking…"; and a browser Notification fires when the reply lands if the tab is hidden.
Proposal cards overhaul. Tolerant parser aliases the model's natural field names (role → npcRole, description → summary/backstory, name → title for lore, etc.) so save cards render even when the model doesn't hit the strict schema exactly. Persistent ✓ Saved state via a new chat_proposal_accept table — refresh the page and the card stays marked saved with a link to the entity. New "Fill in N fields" button calls AI to complete null fields without overwriting grounded ones. "Save all (N)" bulk button when a message has 2+ unsaved proposals. Expandable "Show details · N fields" panel exposes every proposed field for review before saving.
Clarify-before-propose half-fields rule. When the model can ground less than half of an entity's schema, it now asks 2-4 specific clarifying questions instead of emitting a sparse proposal block. Exception: if the user pastes a source document with the answers in it, extract directly.
Multi-proposal staggering — when a streaming chat message will produce multiple proposal cards, they hold rendering until the stream completes. Single proposals still render as they parse. A "Drafting N proposals…" placeholder shows in the meantime so the GM knows something is coming.
Retry any user message in chat. Hover any user bubble to reveal a ↻ button; click to re-run from that message. If the target has follow-up messages, a confirm modal warns "the N messages after this will be deleted." Failed messages keep their existing inline error + Retry row.
Editable-prompt image modal. Clicking any "Generate image" button opens a modal pre-filled with the auto-built prompt; the GM can refine it before sending. The prompt is rebuilt fresh from the entity record next time so edits are scoped to this one generation.
Art-style picker with 6 thumbnail options: High Fantasy (default), Realistic, Painted Fantasy, Animated, Ink Sketch, Watercolor. Each option shows a 1024×1024 reference thumbnail of a dual-axe orc barbarian generated in that style so you can see what you're picking. Selection persists to localStorage across sessions.
Aspect ratio selector — Square / Landscape / Portrait segmented control in the image modal. Per-target defaults (portrait for characters/NPCs, square for monsters/items, landscape for regions/settlements/POIs) and the chosen aspect persists. Maps to OpenRouter's image_config.aspect_ratio so Gemini Flash Image actually honors the request.
Background image generation. Hitting Generate dispatches the job to a Zustand store and closes the modal immediately — you can navigate away while it runs. A bottom-right pill on every dashboard page shows in-flight + recently-completed jobs; click to open a popover listing each with subject, style, aspect, and a link to the result. Browser Notification fires when the tab is hidden and the image lands.
Zoomable image lightbox on every entity portrait, landscape, and map. Hover any image to reveal a ⛶ button in the corner; click to open a near-fullscreen viewer with mouse-wheel zoom (toward cursor), drag-to-pan, double-click reset, and pinch-zoom on touch. Toolbar with +/-/reset/close.
AI image generation back online. The default model was openai/gpt-image-1 — a Vercel-AI-Gateway slug that OpenRouter doesn't recognise — and every call 400'd. Default is now google/gemini-3.1-flash-image-preview (cheap, fast, supports image_config aspect ratios); openai/gpt-5.4-image-2 is available as an override for higher-fidelity portraits.
Save broke on entities with AI-generated portraits. The character + NPC update actions used z.string().url() which rejected our same-origin /api/blob/ proxy paths. New imageUrlField helper accepts absolute URLs, data URLs, and same-origin paths — save now works on every edit form with an AI portrait.
May 21, 2026
v2.0.0-alpha.15
alphaProposed entities now actually populate. Switched world generation, copilot, and campaign skeleton from generateObject to generateText + a <generated>…</generated> block parser. The structured-output path hit Gemini Flash limits that silently returned proposedEntities:[] in every benchmark prompt; the text-mode path mirrors the v1 pattern that worked reliably. The model emits the main entity plus any invented entities as an array inside one block; the parser handles truncation recovery and per-item malformed JSON gracefully.
Text-mode response parser. New `parseGeneratedContent` utility strips <generated> blocks from the AI prose, handles complete and partial (truncated) responses, and recovers individual items even when the JSON array is cut off mid-stream. 17 unit tests.
Entity-type field specs in system prompt. Every generation call now includes a compact inline JSON template (exact field names + enum values) for the target entity type, so the model never has to guess naming conventions (e.g. npcRole vs role, questStatus vs status).
May 21, 2026
v2.0.0-alpha.14
alphaLinked entities now actually get linked. Proposed entities are saved in dependency order (regions → settlements → POIs/shops → factions → NPCs → lore/quests) instead of all at once with no ordering. Each level runs in parallel via Promise.allSettled; levels run sequentially. Before each entity is saved, its FK fields are resolved: an NPC generated with "works_at" against the main shop draft gets shopId wired in automatically. No more dangling references.
proposedEntities returning empty fixed. Added concrete EXAMPLE strings to every .describe() on the proposedEntities schema so Gemini has a clear template to follow. Added a mandatory BEFORE RETURNING audit step to the grounding rules — the model must re-scan its own draft for proper nouns and add any missed inventions before finalising.
Category B refusal overshoot corrected. The pivot to "invent freely" was too aggressive — the model was inventing queens and temples that the GM implied already existed. Added an EXCEPTION clause: refuse (confidence='low') when the user asks for facts about a specific named entity not in canon ("who is the queen of Cardinia?"). Only invent when the user explicitly requests new content ("give me a queen for Cardinia").
Session co-pilot now surfaces proposedEntities. The conjure action threads through the full proposed-entity list so the co-pilot UI can display and save linked entities alongside the main conjured entity (UI panel coming in the next alpha).
May 21, 2026
v2.0.0-alpha.13
alphaAI pivot: invent freely, but materialize what you invent. The "never invent" rule is gone — inventing IS the AI's value-add. Now: when the AI references a named entity not in canon (an NPC, place, faction, item, deity), it must surface that entity in a new "Linked entities" panel under the draft. Check the ones you want to keep, edit names/summaries inline, and they'll be saved as world entries alongside the main draft. Backlinks resolve automatically once they're in the world.
Creative freedom toggle on the AI Generate sheet — three modes: Strict (canon only, no new entities), Balanced (invent small things like NPCs/shops/items, prefer existing for big things like territories), Free (invent anything that fits the tone, including continents/deities/major factions). Default is Balanced.
GROUNDING_RULES rewritten. The model is told that inventing IS the value; the rule is "always materialize what you invent." Proper-noun allowlist no longer forbids invention — it now just tells the model to PREFER canonical entities over invented ones at the same scale. Benchmark scorer updated: names in proposedEntities count as allowed, not hallucinated.
May 21, 2026
v2.0.0-alpha.12
alphaSession co-pilot — an amber Zap FAB appears in the bottom bar whenever a play session is live on the current campaign. Click it to open the co-pilot sheet: pick a kind (NPC / Encounter / Lore / Quest hook), type a quick improv prompt ("barkeep who knows too much"), optionally set a scene context, and hit Conjure. The AI grounds the generation in the last 10 session events plus the campaign's retrieval index so the output fits what just happened at the table — not just the world at large. Save to campaign with one click; delegates to the same create actions the world-AI sheet uses, so entities land in the right list pages immediately.
May 21, 2026
v2.0.0-alpha.11
alphaCanon-doc prefix — every AI generation call now begins with a 800–1500 char grounding paragraph summarising the campaign's tone, geography, factions in tension, and the 5–8 most recently-edited NPCs. Stored in a new `campaign_canon` table (one row per campaign) and auto-refreshed whenever world entities are created or updated via the existing reindexEntity hook. The GM can force a rebuild at any time from the campaign Settings page. Solves 70+ hallucinated proper nouns from the Whitespire benchmark — the model now has the world's truth in front of it on every call.
May 21, 2026
v2.0.0-alpha.10
alphaFew-shot tone fingerprinting — world AI generation now pulls up to 2 of the campaign's richest NPC backstories and 1 lore entry as exemplars and injects them into every AI system prompt. The model uses these as a voice reference (not a copy-paste source) so generated content matches the established prose density, vocabulary, and texture of this specific campaign rather than drifting to generic high-fantasy defaults. Wired into world entity generation, NPC stat blocks, shop inventory, loot tables, and session recaps.
May 21, 2026
v2.0.0-alpha.9
alphaEmbeddings moved to a dedicated Neon DB (EMBEDDINGS_DATABASE_URL). The vector ANN workload + HNSW index maintenance now happens on its own Postgres so it doesn't compete with OLTP reads on the user-data DB. Cross-DB FKs aren't possible in Postgres, so the embedding table's campaign + user FK constraints were dropped — cascade-on-delete is now enforced at the app layer via cleanupEmbeddingsForCampaign / cleanupEmbeddingsForUser.
Whitespire benchmark gets its own throwaway Neon DB (BENCHMARKS_DATABASE_URL). The eval runner reroutes EMBEDDINGS_DATABASE_URL → BENCHMARKS_DATABASE_URL for the duration of a run so test embeddings never pollute production retrieval. Test entities still land in the prod user-data DB (namespaced by slug + soft-deleted), but the HNSW index stays isolated.
May 21, 2026
v2.0.0-alpha.8
alphaAI provider swap: Vercel AI Gateway → OpenRouter. All text generation (world AI, campaign skeleton, NPC stat blocks, shop inventory, loot, session recap, chat) and embedding (768-dim, Matryoshka-truncated from openai/text-embedding-3-small) now route through OpenRouter's OpenAI-compatible API with the OPENROUTER_API_KEY env var. Side benefit: the model catalogue is now visible at openrouter.ai/models and we can swap any model with a single string change in src/lib/ai.ts. Vercel Gateway dep dropped.
Retrieval grounding restored — the previous google/text-embedding-004 model id had silently 404'd on Vercel AI Gateway, so every retrieval-grounded AI call had been running without retrieval context. Now back online via openai/text-embedding-3-small with leading-prefix truncation to the 768-dim pgvector schema.
May 21, 2026
v2.0.0-alpha.7
alphaWhitespire AI benchmark (internal) — a 42-prompt eval harness grounded in the developer's own homebrew campaign canon. Scores hallucinated proper nouns against an allowlist, refusal rate on sparse inputs, tone match (Sonnet 4.6 as judge), territory-adjacency correctness, biome / scale consistency, and per-capital cultural fingerprint. Reproducible runs land in `tests/ai-eval/whitespire/reports/<timestamp>.json` with a vanilla HTML viewer for diffing across model swaps and prompt changes. This is what we'll use to prove the alpha.6 hallucination fix actually worked.
v2 is now main — the v1 Payload-based codebase is fully replaced. Vercel deploys off main again, and the development → staging → main branch flow is back in standard order. v1 history is preserved on origin/backup/v2-dashboard-pre-v1-shell if anyone ever needs to reference it.
May 21, 2026
v2.0.0-alpha.6
alphaAI hallucination overhaul — every world-generation schema field is now nullable, and prompts explicitly forbid inventing facts. The model returns a confidence level alongside the entity; if confidence is low, it surfaces 1-3 clarifying questions for the GM instead of confabulating. System prompts include hard grounding rules: only the campaign's setting, the user's prompt, and retrieval context are valid sources of truth.
AI proper-noun audit — generated descriptions, backstories, and lore are scanned for capitalised proper nouns. Anything not in the campaign's known-entity list is flagged in the draft editor with a "AI may have invented this name" warning so GMs can replace or remove it before saving.
Campaign skeleton: clarifying-questions step — the AI now reads your brief and asks 3-5 targeted questions (tone, faction tension, central villain, etc.) before producing the world skeleton. Answers get appended to the generation context so the output reflects intent instead of guessing.
Thinking panel — the LM-Studio-style reasoning panel from pyre is now wired into the AI Generate sheet, session recap, and chat FAB. While the model is working you see a live "Thinking…" indicator with the elapsed time; the panel collapses to "Thought for Xs" once the response settles.
Retrieval grounding is now framed as canonical truth, not inspiration — the retrieval block reads "use ONLY these established facts" and lists every retrieved entity by name. The model is no longer allowed to introduce new proper nouns; for anything unfamiliar it must use generic descriptors ("a nearby ruin") or ask via clarifying questions.
May 21, 2026
v2.0.0-alpha.5
alphaFoundry VTT Sync module (foundry-module/) — install via the Foundry module browser and paste your API token + campaign slug. On first sync (or automatically on startup), the module pulls all player-visible NPCs, factions, lore, locations, and quests from the Dungeon Diary API and writes them into read-only journal compendiums inside Foundry. Full replace on every sync so stale entries never accumulate. Requires Foundry 12+ and an API token with read_public scope.
Media gallery at /media — every NPC portrait, map image, and session audio recording for a campaign in one responsive grid. Filter by Portraits / Maps / Audio. Click any tile to open a lightbox (images at full resolution; audio recordings with native player). Accessible to all campaign members; GM-only entities are hidden from players.
3D physics dice — clicking any die in the dice tray now launches 3D dice powered by @3d-dice/dice-box (Babylon.js + Ammo.js WASM physics). Dice tumble across a full-viewport canvas overlay and settle naturally. The tray shows "✦ 3D" once the physics engine is loaded. Falls back silently to text-only if WebGL or OffscreenCanvas isn't available.
Backlinks panel on all 6 world entity detail pages — Region, Settlement, POI, Shop, Faction, and Lore now each show a "Linked from" panel (wiki-style, like Obsidian) in their right column. Regions and Settlements show quests anchored there via quest locations; POIs show quests set at the location; Shops list NPCs who are anchored to the shop; Factions list their associated quests. NPC already had this.
Markdown / Obsidian import wizard — GMs can now drop .md files from their Obsidian vault directly onto the Campaign Settings page. Entity type is detected automatically from folder names, frontmatter fields (type:, tags:), and filename keywords. Every parsed file shows as a reviewable card before anything lands in the world. Up to 50 files per batch; wikilinks flatten to plain text.
Combat tracker: Prev turn — step the initiative cursor backward through the order, skipping defeated combatants. Wraps at the top of the round and decrements the round counter (minimum round 1).
Combat tracker: Presence pill — a live green "Broadcasting · N online" indicator appears in the tracker header whenever any user has the combat open, powered by Ably presence. Disappears after combat ends.
Combat tracker: Boss badge — mark any NPC/monster as "Boss" when adding a combatant. Renders a gold BOSS badge next to the name in the initiative ladder.
Rail nav tooltips — hover any sidebar item (POIs, Lore, Encounters, Loot tables, etc.) for a plain-language explanation. Cuts the jargon Sam-the-new-DM hit on his first run.
Session AI Recap — the "AI Recap" tab on every session detail page now works. The GM (or any campaign member) clicks "Generate recap" and the AI reads the session notes, transcript, and the full activity log (crits, loot, combat events, death saves, faction reputation changes) to write a narrative 3-to-5-paragraph journal entry. The recap is persisted back to the session so it loads instantly on revisit. GMs can regenerate at any time.
May 21, 2026
v2.0.0-alpha.4
alphaRemoved the six decorative combat-action buttons (Attack/Cast/Move/Dash/Item/Other) from the active-turn card — they were placeholder UI with no wiring. They'll come back individually as each underlying action (attack roll → damage application; spell cast → slot consumption) actually works.
Public player wiki at /c/<slug>/player — a shareable, no-login-required view of a campaign filtered to player-visible content. Shows party, quests (with objectives), factions (with reputation), notable NPCs, lore, and timeline. GMs share the link; players bookmark it.
AI FAB now opens an anchored bubble (not a side drawer) with context-aware quick prompts that change based on the page you're on — NPC detail, combat tracker, lore page, world hub, dashboard, etc. each get their own prompt suggestions.
Spell picker dialog on the Spells tab — search the SRD by name, filter by level (cantrip through 9th) and class, click Add to add to the character. Inline Remove button per spell row, and the prepared indicator is now a clickable toggle.
Class resources tracker — add Bardic Inspiration, Ki, Sorcery Points, Channel Divinity, Rage, Wild Shape, or any other tracked feature with a max + reset period (short rest / long rest / dawn). Click pips to spend or restore uses.
Hit dice spending UI on Short Rest — pick how many d6/d8/d10/d12 to spend, see each rolled value with CON-mod tallied, recover HP capped at max. Old "instant short rest" flow stays available when no hit dice are present.
Level Up dialog with class picker (multiclass-aware), HP gain via average or rolled, ASI/feat picker at the right class levels (4/8/12/16/19 + fighter 6/14 + rogue 10), and auto spell-slot recompute using the PHB multiclass table.
Inventory tab is now three sections: Equipment, Backpack, Chest. Move items between with a click; attunement is a star toggle with a hard 3/3 limit.
Inspiration toggle on the character hero band — gold filled star when inspired, click to spend.
Currency on the inventory tab is now inline-editable per denomination (platinum / gold / electrum / silver / copper).
D&D Beyond character import now reads class features (Bardic Inspiration, Pact Magic, Sneak Attack, etc.) and subclass features (Pact of the Tome, Path of the Berserker), species racial traits (Darkvision, Fey Ancestry, Trance, Lucky), and actual feats — previously the importer only read user-picked options.
D&D Beyond import: warlock pactMagic slots now merged into the spell-slots view, and bonus spells from race/class/item (Magical Secrets, Book of Ancient Secrets) are pulled into the known-spell list.
D&D Beyond import: HTML tags in item notes and feature descriptions are now stripped to plain text.
D&D Beyond import: depleted-stack items (quantity=0 for used-up potions or empty ammo slots) no longer break import — clamped to a safe range.
D&D Beyond import: currency (pp/gp/ep/sp/cp) is now imported into the character coin purse.
D&D Beyond import: validation errors now name the offending field path so failures are actionable instead of just "Invalid import payload".
Spell-slot pips: used slots fill solid red, available slots stay outlined squares (was inverted).
Overview rail link no longer stays highlighted when navigating to Characters, Sessions, or other campaign sub-pages.
Vercel builds are no longer crashed by the e2e test seed route — its production guard moved from module-load throw to per-request runtime check.
Ably channel-denied errors (stale-token capability mismatches) no longer surface as uncaught console rejections — the dice tray degrades to local-only with a single warning instead.
May 21, 2026
v2.0.0-alpha.3
alphaHomepage redesign — hero, three capability demos (World / AI / Combat), "For Players" split, pricing tiers, founding-member offer, FAQ, final CTA. Ported from the v2 prototype.
13 new marketing pages: /about, /contact, /press-kit, /hiring, /docs, /changelog, /roadmap, /build-log, /api, /srd, /status, /acceptable-use, /ai-policy.
5-column site footer (Brand · Product · Resources · Company · Legal) rolling out across every marketing page.
AI sparkle FAB now opens a quick-prompt overlay sheet instead of full-page navigation, with four pre-written prompt chips.
Demo campaign entities now ship with embeddings so the command palette search returns hits on the Ravenmoor Reach demo.
Public header nav now surfaces Pricing, Changelog, Roadmap, Docs, and Blog (was Blog + Beta only).
May 20, 2026
v2.0.0-alpha.2
alphaTrash + restore — every soft-deleted entity now lives in /dashboard/campaigns/[slug]/trash for 30 days, then a daily cron permanently sweeps. Restore brings entities back, embedding rows included.
JSON snapshot export — GMs can download a complete structured dump of their campaign from settings. Filters soft-deleted rows and omits embedding indexes.
Per-entity Markdown export via Accept: text/markdown content negotiation on NPCs, regions, settlements, POIs, shops, factions, lore, quests.
Public API v1 with per-campaign bearer tokens. Three endpoints: /api/v1/campaigns/[slug]/public.json, /full.json, /sessions.ics (iCal feed).
Form primitive layer: AIBanner, VisToggle, Chips, RichField, FormSection. Wired into NPC, shop, and quest forms with end-to-end visibility through to the action layer.
Retrieval-grounded AI generators — every world-AI generator (NPC, shop, region, encounter, loot) now pulls top-K similar entities from the campaign embedding index and injects them into the system prompt.
Session events are now in the embedding index — chat can answer "what did we do last session" instead of returning generic prose.
Dynamic DM Assistant prompts derived from your campaign state (active quests, recent NPCs, recent faction rep changes).
Global d shortcut opens the dice tray from any dashboard route. Rolls broadcast to all session participants via Ably and persist as session events.
Optimistic HP and condition updates in the combat tracker — actions feel instant, Ably refreshes reconcile in the background without clobbering in-flight edits.
Backlinks "Linked from" panel on the NPC detail page. Other entity kinds: in flight.
View-as-player ribbon — a crimson banner on every dashboard page when the GM has flipped to viewer mode, so you never forget which side you're on.
Ravenmoor Reach demo campaign — one-tap "Try the demo" login at /login, with a daily cron that resets the campaign.
SECURITY: combat mutation actions are now gated on GM role (advanceTurn / removeCombatant / endCombat / etc). Previously checked campaign membership only.
SECURITY: world-entity visibility defaults flipped from 'players' to 'gm' across all 10 worldbuilding tables. GM-created entities never auto-leak.
SECURITY: login ?next= parameter now rejects protocol-relative URLs (//evil.com).
Embedding visibility now cascades correctly when a region is hidden — child entities AND their embedding rows flip together.
Mobile: combat tracker stacks to a single column at 390px (was overflowing). Map editor uses a horizontal palette + bottom-sheet inspector instead of a 3-column desktop layout.
Mobile rail Sheet now includes Maps in the World group (was a desktop-only menu item).
Rail icons: Settlements → Building2, POIs → MapPin, Characters → UserCircle. Factions keeps the Shield.
Empty-state dashboard rewrite — new DMs see ≤3 CTAs instead of the populated dashboard with zeros, and the AI-generated opening quest now surfaces correctly on the campaign hub.
Early May 2026
v2.0.0-alpha.1
alphaWorldbuilding CRUD for all 12 entity types: regions, settlements, POIs, shops, factions, faction-relations, lore, NPCs, quests, quest-locations, encounters, timeline events.
Semantic search across every entity via pgvector HNSW + Gemini text-embedding-004 (768-dim).
Retrieval-grounded DM Assistant chat with persisted source metadata — every assistant turn writes which entities it pulled, with distance and age, to the message metadata.
AI image generation for NPC portraits with richer prompts from full backstory + voice notes.
Combat tracker with Ably real-time broadcast, initiative ladder, damage presets, conditions.
Live session view with audio recording, transcript, loot picker, concentration tracking, faction standings.
Character sheets with hero band, hit dice, death saves, conditions, spell slots, rest buttons, inventory with weight tracking, currency, backstory tab with personality traits.
Homebrew monsters and items with shareable schema.
SRD reference: 319 spells, 414 monsters, 248 items, plus conditions and rules.
Notification dropdown, command palette (⌘K), keyboard shortcut hints across the rail.
Quest anchoring to a region / settlement / POI via quest_location join table.
NPC pinning — pin one NPC per campaign as the "currently active" NPC, surfaced on the campaign overview.
Session prep checklist with carry-over and topbar live-session pill.
Soft delete across every entity, with visibility cascade.
Better-auth email/password + email verification (Resend) with autoSignInAfterVerification.
Public marketing site: home, blog (10 archived posts, paginated), pricing → founding-member beta request.
Terms + Privacy + dashboard footer + cursor-pointer + focus-ring polish.
Lexical rich-text editor for entity descriptions.
Pill-tab nav across the world hub. Sidebar redesign with collapsible groups and a campaign switcher.
AI calls routed through Vercel AI Gateway — Gemini 2.5 Flash primary, GPT-4.1-mini fallback.
Dungeon Diary