CampaignRepo is past 1.0 and into the reliability wave. The platform surface is broad - wiki, maps, sessions, quests, publishing, dashboards, imports, AI tools, and local/GitHub storage - so the focus has shifted from adding surfaces to making the existing app trustworthy at scale.
The guiding promise remains the same: every campaign remains a portable folder of Markdown, YAML, JSON, and media files. CampaignRepo makes that folder feel like a modern worldbuilding app, while Git acts as durable archive and sync layer instead of the live database for every small UI action.
Effort key: S = days, M = about a week, L = multi-week.
Looking for what comes after this roadmap? New, not-yet-scheduled feature directions live in ROADMAP-IDEAS.md.
The next critical work is architectural reliability. CampaignRepo should keep the "own your repo" promise, but GitHub should not be the live database for every small app action.
Current risk: page state can be represented in several places at once:
- Markdown page files.
- SQLite page cache.
.campaignrepo/index.json.wiki/search/index.json.- Sidebar/list snapshots.
- Public-site snapshots.
- GitHub repository state.
That creates failure modes where page content is intact but the UI shows an empty body, a stale sidebar, missing children, or public/GM views that disagree. It also makes GitHub latency and merge conflicts visible during ordinary work such as organizing pages or approving player-visible content.
Target model:
User edit
-> local DB working copy
-> instant UI update
-> queued rebuild/sync
-> batched Git commit and push
Markdown/repo files remain portable. Generated files are disposable snapshots. The app must always be able to rebuild generated state from the canonical page source.
Shipped: POST /api/campaigns/[id]/repair rebuilds the SQLite page cache,
wiki/search/index.json, and .campaignrepo/index.json from canonical page
source with a per-step report (the cache rebuild re-parses every page rather
than trusting sha-matching rows, so poisoned rows cannot survive). "Repair
indexes" buttons live in the Health center and campaign Settings. Health now
reports generated-state drift: manifest/search/cache counts vs page files,
missing or invalid manifest and snapshot, cache refresh errors, and cached
pages that lost their body while source has content. Empty source pages are
reported, never silently filled. Page detail reads already recover from empty
cache rows. Regression tests cover manifest/snapshot recreation, poisoned
cache rows, empty source pages, and named-step failures.
Remaining for later waves: last-Git-sync status surfacing (R4 territory) and
the campaignrepo repair CLI form (R6).
The original R1 plan, for reference:
- Read canonical pages.
- Rebuild
.campaignrepo/index.json. - Rebuild
wiki/search/index.json. - Refresh the SQLite page cache.
- Report counts and failures.
Health checks should show Markdown/page count, manifest count, search document count, cache count, empty page bodies, invalid parents, public/GM visibility mismatches, unsynced local changes, and last successful Git sync.
Fallbacks:
- If a cache row has an empty body but the page source has content, replace the cache row from source.
- If generated indexes disagree with page source, prefer the page source and mark indexes stale.
- If repair cannot complete, leave content untouched and report the exact failed step.
Tests:
- Corrupt cache page -> detail view recovers from source.
- Delete manifest -> repair recreates it.
- Delete search index -> repair recreates it.
- Parent in page source -> sidebar and public site nest correctly after repair.
- Empty real source page remains empty and is reported, not silently filled.
Shipped: bulk organize updates page frontmatter first, upserts the manifest in
the same commit as the page files, updates the cache in-request, and rebuilds
the search snapshot from source afterward. The R2 fallback is in place: when
the snapshot rebuild fails after page writes landed, the response reports
indexesStale with the reason instead of failing the edit, and health/Repair
reconciles. The committed snapshot is now lean (metadata + excerpt, no full
bodies) — full text lives in Markdown (canonical) and SQLite FTS (live search).
Also shipped toward R3/R4: local-first page lists. Reads serve the SQLite cache with zero GitHub calls inside a 5-minute remote-check window; once stale, one HEAD lookup either re-arms the window or hydrates remote changes with a background refresh. Remote outages degrade to the local copy. Health reports the last remote check and HEAD sha.
The original R2 plan, for reference:
Bulk organize should update page frontmatter first, then rebuild generated snapshots from those pages. Generated JSON should be output, not the main edit target.
Flow:
bulk edit selected pages
-> update page source/frontmatter
-> rebuild manifest/search from page source
-> commit changed page files and snapshots together
Fallbacks:
- If page writes succeed but snapshot rebuild fails, keep the page writes, mark indexes stale, and show a repair action.
- If GitHub commit fails, preserve the intended local changes for retry.
Tests:
- Move 10 locations under a parent and verify page frontmatter, manifest, search index, sidebar, and public site.
- Bulk approval updates player and public views consistently.
- GitHub file listings with missing file text never produce blank pages.
- Generated rebuilds do not drop page bodies, media docs, aliases, parent metadata, approval state, or visibility.
Shipped: the page cache is now a true working copy. Rows carry dirty,
lastSyncedSha, and lastSyncError; a save that cannot reach Git lands in the
working copy as dirty and the editor reports "saved locally — Git sync failed
and will retry" instead of losing the edit. Dirty rows survive full cache
refreshes and Repair (local edits outrank the remote copy until synced or
resolved), and reads already serve the cache first with the 5-minute remote
window. Remaining: moving organize/review/public-preview reads fully onto
working-copy queries and a dirty-state badge in page lists.
The original R3 plan, for reference:
Introduce a page working-copy table that the app reads first:
- slug
- title
- category/type
- parent
- visibility
- approvalStatus
- content
- raw markdown
- frontmatter JSON
- updatedAt
- dirty flag
- lastSyncedSha
- lastSyncError
The UI should read from the DB working copy for page lists, detail pages, organize, approval queues, public preview, and health checks. GitHub becomes a sync backend, not the hot path.
Fallbacks:
- If a page is missing from DB, hydrate it from Git/local storage once.
- If DB and Git disagree and the DB row is dirty, keep the DB row and surface a sync warning.
- If DB is unavailable, existing local/Git-backed read paths still work in a degraded mode.
Tests:
- Edit page while GitHub is unreachable; UI keeps the edit and marks it dirty.
- Reload app; dirty edits survive.
- Organize parent changes are instant without GitHub requests.
- Public preview reads the same working copy as GM view, filtered by player safety rules.
Shipped: lib/sync-queue.ts flushes every dirty page in one commit
("CampaignRepo: sync N pages"), with a conflict guard per page before writing.
Commit failure keeps all dirty flags and records the error per row; success
clears flags and schedules the index rebuild. Sync Now lives in the Health
center, campaign Settings, and POST /api/campaigns/[id]/sync (API-token
capable). Failed local saves schedule a 60-second background retry. Health
shows the dirty count. Note: the retry timer is in-process — a restart drops
it, and the manual Sync Now or next failed-save reschedules.
The original R4 plan, for reference:
Create a queue for durable sync work:
- page created/updated/deleted
- page parent/category/approval changed
- media metadata changed
- generated index rebuild needed
- campaign settings changed
Sync should batch changes into one commit after a debounce or explicit Sync Now action.
Flow:
collect dirty records
-> serialize Markdown/YAML/JSON
-> rebuild generated snapshots
-> commit once
-> push
-> clear dirty flags
Fallbacks:
- If GitHub times out, leave dirty rows intact with retry.
- If push is rejected, fetch remote and enter conflict handling.
- If generated snapshot write fails, do not clear dirty flags.
Tests:
- 20 edits produce one commit.
- Timeout during sync leaves all edits in DB.
- Restart during sync resumes safely.
- Generated indexes match the synced page set.
Shipped: when a flush finds the remote moved past a dirty page's base sha with
different content, the page is excluded from the commit and recorded in
page_conflicts (base sha, local raw, remote text) — content is never silently
overwritten in either direction. The Health center lists conflicts with
"Keep local" (writes the local version against the remote sha, an explicit
choice) and "Take remote" (adopts the remote copy, discards the local edit).
Remaining: a side-by-side three-way merge view instead of whole-page choice.
The original R5 plan, for reference:
GitHub remains valuable for collaboration, so conflicts need explicit UX.
Conflict policy:
- If remote changed a clean page, hydrate the new remote version.
- If local DB row is dirty and remote changed the same page, create a conflict record.
- Use three-way merge where possible: last synced version, local DB version, and remote Git version.
- Never silently overwrite page content.
Fallbacks:
- If merge is uncertain, preserve both versions.
- If generated files conflict, regenerate them from the resolved page set instead of asking the user to merge JSON.
Tests:
- Remote edit after local edit.
- Remote delete after local edit.
- Local rename while remote page changes.
- Generated index conflict after page conflict resolution.
Shipped: repair rebuilds every generated artifact from page source (R1),
preserves unsynced local edits, and is scriptable headless via
node scripts/repair-campaign.mjs --campaign <id> --token <api-token> [--sync]
against a running instance (the repair and sync endpoints accept API tokens).
The disposable rule holds app-wide: manifest, search snapshot, and cache are
outputs; dirty working-copy rows and Markdown are the inputs.
The original R6 plan, for reference:
Make this rule true across the app:
Manifest, search index, sidebar snapshots, public-site snapshots, and DB cache are rebuildable outputs.
Add a CLI/admin command:
campaignrepo repair --campaign <id>
Tests:
- Corrupt every generated file and repair from source.
- Large repo repair completes without GitHub directory walking in the common path.
- Repair reports exact skipped/failed pages.
Hidden Risks To Track
- Generated files overwriting good data. A stale snapshot can make good page files look missing or empty.
- GitHub latency and timeouts. GitHub is excellent as durable storage but poor as the database for live editing.
- Parent/title/slug mismatch. Relationships should store stable page IDs or slugs, while the UI displays names.
- Renames. A rename touches filenames, slugs, links, backlinks, parents, public URLs, manifest IDs, search docs, and history.
- Public and GM view drift. Both should derive from the same working source, then apply visibility filters.
- Media repo size. Large media makes pulls, tree reads, and generated snapshots slower. See the media strategy map below.
- Silent partial success. The app must say "content saved, Git sync failed"
instead of making users guess whether data was lost — shipped for editor
saves (dirty working copy) and bulk edits (
indexesStale). - Bad cache poisoning. An empty or malformed cache row must never outrank a non-empty canonical page source — mitigated: page detail recovers empty cache bodies from source, cache refreshes refuse to sweep a populated cache down by more than half, and page lists distrust a cache far thinner than the last remote index count.
- In-process timers. The rebuild debounce, sync retry, and background refresh dedupe live in process memory; a container restart drops any scheduled work until the next request or manual Sync/Repair. A persisted queue with a boot sweep would close this.
Today's campaign repos are ~85% media by size (kingdomdivided: ~100 MB of portraits in a 147 MB repo). Media is why clones are slow, why history bloat hurts, and where GitHub's limits bite first (100 MB/file hard cap, ~1 GB soft repo cap). The app already lazy-fetches single files, so reads are fine — the cost is clone/pull weight and history growth from re-uploaded images.
Options, in the order they should be tried:
- Shrink at the door (S) — shipped. Raster uploads (png/jpg/webp) are converted to WebP (quality 82) and capped at 1600px on the longest edge before they are committed, in both the app upload and MCP upload_media. Small in-bounds images and files where WebP would not actually shrink them pass through untouched, as do gif/svg/pdf/audio and undecodable data. The repo only ever sees the small version.
- History cleanup, one-time (S, tooling exists).
git filter-reposweep per repo dropping superseded media blobs and old fat snapshots from history, with bundle backups first. Fixes the past; option 1 fixes the future. GitHub shrinks server-side after its own GC. - Git LFS (M, if 1+2 are not enough). Media stays "in the repo" from the user's point of view (portability promise intact), clones become pointers + on-demand fetch. Costs: LFS bandwidth quotas on GitHub free tier, extra setup for manual cloners, and the app's raw-media reads need the LFS media endpoint. Good fit only if campaigns start pushing multi-GB.
- Object storage sidecar (L, last resort).
wiki/media/holds small pointer files; bytes live in S3/R2/MinIO. Breaks the "one folder is the whole campaign" promise unless export bundles re-inline media — that re-inlining is mandatory if this path is ever taken.
Recommendation: ship 1 now, run 2 opportunistically per repo, and only revisit 3/4 when a real campaign outgrows them.
CampaignRepo currently ships:
- GitHub and local-folder campaign storage.
- Markdown wiki pages with YAML frontmatter, wiki links, aliases, backlinks, GM blocks, named secret groups, covers, galleries, and transclusion.
- Configurable campaign categories and per-game template packs.
- A pinned, auto-provisioned Campaign home page per campaign (concept, the table, how to connect, house rules) at the top of the workspace side-nav.
- Demo data for every supported game system: seedable, cross-linked example pages (location, faction, NPC, sample PC, threat, item, opening situation, GM primer & checklist), browsable from the dashboard Demo Library and seeded/removed per campaign with one click.
- Media manager with metadata, captions, alt text, tags, saved filters, nested media folders, bulk move/caption/tag cleanup, and page rendering.
- Interactive maps with image pins, player/GM layers, nested map links, routes, regions, measuring scale, and journeys.
- Nested page hierarchy through
parentfrontmatter and collapsible sidebars. - Relationship graph with typed relationship frontmatter, inverse labels, hierarchy/family layout, dense-campaign clusters, graph editing, search, and health checks.
- Dedicated family tree view with an editable tree builder, imported-genealogy datasets rendered Family Echo-style, and fullscreen viewing.
- Full-text search plus a global command palette.
- Player portal and public no-login sites for approved player-visible content.
- Public campaign gallery, custom public link names, ratings, recently-updated discovery signals, clone-this-world flow, contribution guidance, GitHub compare/PR handoff, and public quest display.
- Sessions, quests, fantasy calendar, overview widgets, boards, manuscripts, and lexicon/naming tools.
- Traveller, D&D 5e, Pathfinder 2e, World of Darkness, and Sword Chronicle character-sheet renderers, with Traveller rolling, editor controls, and a print/PDF action on sheet-bearing pages.
- Reusable category property schemas, custom typed fields, inventories, abilities, resources, and safe formula fields stored in campaign YAML/page frontmatter.
- Review queue, campaign health center, bulk page organization (including one-commit bulk delete), manual repo refresh, snapshot-backed page lists, notifications, assignments, mentions, page watches, and page history/diff/restore workflow.
- Importers for Foundry actors, generic character JSON, Obsidian, Notion, OneNote, Google Docs, CSV, journals, Roll20, LegendKeeper, World Anvil, full ZIP/JSON backup export, and character re-import diffing — consolidated in one Import & Export hub (single-character JSON included; bulk goes straight to the git repo).
- Genre theme presets (fantasy, horror, sci-fi, generic, plus flagship themes) applied per campaign, and a global preferred-theme picker on every page that overrides the campaign default per browser.
- Phone-class mobile treatment: bottom tab bar, editor Write/Preview toggle, compact dashboard grid, and touch-sized controls.
- AI generation, AI campaign Q&A, and MCP JSON-RPC tools.
- Docker/GHCR deployment path.
Phone widths get a fixed bottom tab bar (Repos / Wiki / Review / Portal), a Write/Preview editor toggle, a 2-up connected-repos grid, stacked topbars with full-width actions, and overlay repositioning. Tablet collapse and the side-nav hamburger were already in place; this pass made phones feel deliberate.
A bottom-left picker on every app page lets each user choose a preferred genre theme. An explicit pick fully overrides the campaign default — including per-campaign custom accents — and persists per browser; Auto restores the campaign's own theme and accents.
Every campaign gets a pinned, always-reachable "Campaign" wiki page seeded with concept, system & tone, the table (players and characters), connection methods (web, GitHub, MCP), house rules, and current state. It is a normal wiki page: editable, versioned, searchable.
Every supported game system has a demo kit: original, genre-appropriate sample
pages that cross-link so the wiki, graph, and portal light up immediately. GMs
opt in from a first-load prompt or the Create page panel; pages are tagged and
removable in one click. Each kit carries a GM Primer & Checklist page and a
character-sheet direction brief recording what that system still needs: exact
field names, sheet layout, visual notes, and cleanup items. Research status per
game (first-pass → needs-reference → ready-for-polish) tracks the
reformatting effort below.
Single-character JSON import moved from the workspace into the Import & Export hub alongside every other importer, with an explicit note that bulk loads belong in the git repo directly. OneNote import joined the hub. The Create page form was expanded and now remembers the last category/template/visibility per campaign.
A dedicated family tree view with an editable tree builder, Family Echo-style rendering for imported genealogy datasets, and fullscreen viewing. Trees are repo files like everything else, and the relationship graph's family layout remains for quick looks.
A display-only sword-chronicle-sheet fenced block renders the printed Green
Ronin sheet: the full ability list (honoring a setting's variant list, e.g.
Kingdom Divided's Admiralty/Nautical/Warcraft), specialties with bonus dice,
derived Intrigue Defense / Combat Defense / Composure / Health computed by the
printed formulas, destiny and damage/injury/wound tracks, attacks, armor,
retainers, appearance, and history. The Sword Chronicle template pack seeds the
real sheet, and the sheet-mangling Markdown bug this surfaced was fixed for the
D&D and WoD sheets too.
The Obsidian RPG Manager scan surfaced a useful gap: CampaignRepo has strong storage, publishing, and player-safety workflows, but it can do more to help GMs shape a session before play. First slice: agenda beats can carry scene type, expected minutes, and an "external stakes" marker; the session editor summarizes expected runtime, typed beats, scene variety, action pressure, and a readiness score. Session handouts now round-trip through session frontmatter as intended.
Follow-up candidates from the same scan:
- Clues as first-class campaign elements, especially for investigation games: clue source, clue points-to, discovered status, and reveal session.
- Guided creation wizards for NPCs, chapters/arcs, clue trails, and session prep packets.
- Optional narrative structure fields in template packs: Story Circle, ABT, Kishotenketsu, conflict, major clues, sensory imprint, NPC want/need/lie, ghost, arc, strengths, and weaknesses.
- Campaign-level GM task board that unifies prep tasks, review items, unresolved clues, player follow-ups, and health findings.
- Import adapter for Obsidian RPG Manager vault metadata/codeblocks, mapping their elements into CampaignRepo pages, relationships, sessions, and custom properties.
This remains important, but it should resume after the reliability and sync work above. The demo system exists precisely to drive it: each game's GM Primer & Checklist page lists the field groups, layout direction, and cleanup notes for that system. Working the checklist means, per game:
- Confirm vocabulary — replace generic stat names with the system's exact terms (careers vs. classes, dots vs. dice, hunger vs. stress).
- Build or adapt the sheet — either a dedicated renderer (like Traveller, D&D, Pathfinder, WoD today) or a well-shaped template-pack sheet page.
- Reformat the template pack — align starter bodies and category templates with the system's real play loop.
- Fill the demo sheet — swap the design brief on the sample PC for a real filled-in sheet.
- Mark the game
ready-for-polishin the research status.
Suggested working order (status-driven):
-
Tier 1a — renderer + filled demo sheet, closed: Dungeons & Dragons, Traveller, Sword Chronicle, Vampire: The Masquerade, Werewolf: The Apocalypse, Mage: The Ascension. Each ships a dedicated sheet renderer and a real, filled-in demo sample PC (Rilla Windmere, Renner, Lady Elyse Vaelor, Nico Alvarez, Ash Redhand, Jax) rather than a design brief.
-
Tier 1b —
ready-for-polish, still need a sheet. Concept, template, and demo kit are ready; each needs a fenced-block renderer (or a well-shaped template sheet), an editor insert button, and a filled demo sample PC. The work list, easiest first:Game Block Core mechanics the sheet must carry Sheet shape Demo PC to fill Fate Core fate-sheetAspects (high concept, trouble, +3), skill pyramid (+4..+1), stunts, stress boxes (physical/mental), consequences (2/4/6) Simplest of the seven — aspects list + pyramid grid the Fate demo PC Blades in the Dark blades-sheetPlaybook, action dots in 3 attributes (Insight/Prowess/Resolve), stress (9) + trauma, harm tiers, load, special abilities, crew link Dot grids + tracks, gothic ink styling the Blades demo PC Mothership mothership-sheetStats (Str/Spd/Int/Cbt), saves (Sanity/Fear/Body), stress + panic, class, skills tree picks, wounds, loadout Terminal/retro-computer look fits the theme presets the Mothership demo PC Delta Green dg-sheetCoC-derived stats ×5, Sanity + breaking point, bonds with scores (the signature mechanic), professional skills %, unnatural exposure Percentile skill table + bonds panel the Delta Green demo PC Call of Cthulhu coc-sheetCharacteristics (%), HP/MP/Luck/SAN with maximums, occupation + skill %, weapons table, backstory panel Percentile table layout; shares bones with Delta Green — build these two together the CoC demo PC Alien RPG alien-sheetAttributes + skills (Year Zero d6 pools), stress dice, health, talents, gear + consumables (air/food/power), signature item, buddy/rival Year Zero pool math; stress panel prominent the Alien demo PC Cyberpunk RED cyberpunk-sheet10 STATs, role + role ability rank, skills (66, show trained), cyberware with humanity cost, HP/wound state, armor SP, weapons Densest of the seven; needs collapsible skill groups like Traveller's the Cyberpunk demo PC Per game, the loop that closed Tier 1a: renderer honoring the system's exact vocabulary → editor insert button + template-pack seeding → filled demo PC → regression render test → mark
ready-for-polishverified. CoC + Delta Green share a percentile core and should land as one pass. -
Tier 2 — popular systems needing reference work: Pathfinder, Warhammer Fantasy Roleplay, Warhammer 40,000 Roleplay, Starfinder, The One Ring, Dragonbane, Shadowdark RPG, Mörk Borg, Old-School Essentials, Savage Worlds.
-
Tier 3 — the long tail: Dark Ages line, Pendragon, Reign, Burning Wheel, Fabula Ultima, Candela Obscura, Changeling, Demon, Hunter, Mummy, Wraith, The King in Yellow, Twilight: 2000, 2300AD, Coriolis.
Note on research: bulk automated research of all systems at once proved a poor token/value trade. Work game-by-game against the in-app checklist instead, consulting SRDs and official references per system as each is picked up.
Phone chrome shipped (item 32). Remaining: keyboard navigation and screen reader passes as release criteria, virtualized lists, paged media loading, cached repo reads, incremental search updates, thumbnails, reduced-motion sweeps, and automated accessibility checks. Connected repos already support keyboard-accessible reordering.
Full field editors for D&D 5e, Pathfinder 2e, and World of Darkness sheets, plus deeper print/theme polish. Feeds directly into the deferred sheet pass: each Tier 1 system that gets a real sheet should also get editor + print treatment. Print/PDF is already exposed in the editor toolbar for sheet-bearing pages.
Re-import previews using source IDs and hashes across every importer, VTT mapping for reusable properties/components, and optional Foundry module push/pull with explicit previews and conflict handling. Single-character JSON imports already store source path/ID/hash and report new/unchanged/changed.
Clearer in-editor "who is editing" surfaces, soft locks, stale-lock handling, and richer conflict resolution for simultaneous edits.
Recent campaigns readable offline, drafts while remotes are down, queued local writes, and clear sync/conflict state.
Versioned APIs for article types, templates, importers, validators, renderers, MCP tools, and automation rules such as "notify GMs when an import is approved" or "add new events to the active timeline."
Mostly landed: demo seeding, the Campaign home page, local-folder-first creation, and the dashboard Demo Library cover the first-run story. Remaining: tighten the create-campaign path ordering and the local-folder → GitHub upgrade flow.
Self-hosted Docker remains the core path. A managed option may eventually help non-technical groups, but should not compromise the own-your-data model.
Build on the started session-readiness work: scene pacing summaries in session lists and dashboards, clue trails as first-class prep objects, GM task boards, and creation wizards for high-friction prep jobs. Keep the data portable by storing these as Markdown/YAML/frontmatter, not opaque app state.
- Ship the repair/rebuild action for manifest, search index, and page cache.
- Expand health checks for empty bodies, stale generated state, invalid parents, mismatched counts, and last Git sync status.
- Make page detail reads recover from empty cache when source content exists.
- Make generated indexes explicitly disposable and rebuildable.
- Add regression tests for blank cache, parent nesting, public/GM drift, and generated-index rebuilds.
- Rework bulk organize to update page source first.
- Rebuild generated snapshots from page source after bulk changes.
- Commit page changes and generated snapshots together when Git sync succeeds.
- Preserve local intent and mark indexes stale when Git sync or rebuild fails.
- Add large-campaign tests for parent moves, approvals, category changes, and GitHub listings with missing file text.
- Introduce the page working-copy table.
- Move page list/detail/organize/review/public preview reads to the working copy.
- Add dirty flags, last sync SHA, and last sync error.
- Batch Git writes behind a sync queue and explicit Sync Now action.
- Show "saved locally, Git sync failed" instead of making failed pushes look like lost edits.
- Add three-way page conflict records for local dirty vs. remote changed pages.
- Regenerate generated JSON after conflict resolution instead of manually merging snapshot files.
- Make dirty local edits survive restart and network outage.
- Lay the foundation for offline/PWA work.
- Resume the per-game sheet/template checklist pass.
- Keyboard/screen-reader accessibility pass.
- Performance budgets, virtualized lists, thumbnails, and large-campaign testing.
- Re-import previews, Foundry module sync research, and extension rules.
- Expand session readiness into clue trails, prep tasks, and guided creation wizards.