This page is for developers and technically curious readers who want to understand how Chronicle works, run it in development, or build on it. If you just want to play, you don't need any of this — start with the User Guide.
Chronicle is a mobile-first solo D&D 5e app. A Claude Agent SDK–powered DM engine runs each campaign with persistent, file-backed state (not just conversation history), which is the core bet: the two failure modes of existing AI-DM apps are state drift and content repetition, and both are fixed by giving the DM real memory (files on disk) plus seed-table-driven novelty constraints — not by tuning the model's personality.
The fullest treatment lives in
docs/design/chronicle-design-doc.md.
Architecturally significant decisions are recorded as ADRs in
docs/adr/, numbered sequentially — start with
0001-core-architecture.md.
Two decoupled services that communicate only through campaign state files on disk — never directly:
Phone / tablet (browser)
│ HTTP (LAN, no HTTPS — single household)
▼
┌─────────────────────────────────┐
│ Node HTTP server (src/server.ts)│ ← serves the built UI *and* the API
│ │ from one origin
│ ┌──────────────────────────┐ │
│ │ DM engine (per campaign)│ │ Claude Agent SDK (default)
│ │ src/dm-engine.ts │───┼──▶ or Grok CLI (per-campaign)
│ └──────────────────────────┘ │
└───────────────┬─────────────────┘
│ reads/writes plain files
▼
campaigns/<user>/<campaign>/ ← character-sheet.json, world-state.md,
character-sheet.json npc-roster.md, quest-log.md,
world-state.md content-registry.md, session-log/
npc-roster.md images/*.jpg
...
▲
│ reads state, writes images + video (decoupled)
┌───────────────┴─────────────────┐
│ Asset worker — pluggable │ scene art + portraits (Grok Build `grok`
│ image backend (Grok | ComfyUI) │ CLI, or local ComfyUI/SDXL on your GPU);
│ + on-demand video (Grok Imagine)│ optional "Animate" clips; off by default
│ src/image-backends/, video-*.ts │
└─────────────────────────────────┘
- The DM engine narrates, adjudicates 5e rules, and updates the campaign's state files every turn. It is a pluggable backend (ADR-0018): Claude Agent SDK by default, or the headless Grok CLI, chosen per campaign.
- The asset worker generates images through a pluggable image backend
(ADR-0027): the Grok Build
grokCLI (default, zero-infra) or a local ComfyUI/SDXL engine on the host's GPU (no per-image cost). Short video clips are produced on demand via Grok Imagine ("Animate", ADR-0026). It only ever reads state and writes media files — the two services never call each other, so an asset failure can't break a turn (design §2). - Persistent state is the anti-drift layer (design §3): plain JSON/Markdown
per campaign. Anti-repetition seed tables live in
data/seed-tables.json(design §4).
| Concern | What it uses | Notes |
|---|---|---|
| DM reasoning | Claude Agent SDK (@anthropic-ai/claude-agent-sdk) |
Default engine. Uses the host's Claude subscription login under ~/.claude (leave ANTHROPIC_API_KEY unset). See ADR-0017. |
| Alternate DM | Grok headless CLI | Per-campaign engine choice (ADR-0018). |
| Images | Pluggable backend — Grok Build grok CLI or local ComfyUI/SDXL |
Provider chosen per-campaign, live-switchable (ADR-0027). Grok is the zero-infra default (auth under ~/.grok / XAI_API_KEY); the local backend draws on the host's GPU with no per-image cost. Optional (ADR-0009). |
| Local inference | ComfyUI + SDXL checkpoints | comfyui.url in config.json (default http://localhost:8188). Per-tier quality (fast/standard/high, ADR-0029), scene-style adherence (ADR-0028), and LoRA-backed art styles (ADR-0032). Checked-in graphs in src/workflows/*.json. |
| Video (optional) | Grok Imagine grok CLI /imagine-video |
On-demand "Animate" clips, never auto-generated (ADR-0026); served from /campaigns/:id/videos/. |
| Rules grounding | SRD text in reference/srd/ |
SRD-grounded adjudication (ADR-0006). |
| Music (optional) | Local files or a LAN Navidrome server | Proxied server-side so the browser never sees Navidrome creds (ADR-0020). |
| Tool calls | MCP (@modelcontextprotocol/sdk) |
Dice / seed / texture / image tools exposed to the DM (src/mcp-servers/). |
Auth posture (ADR-0003): single-household LAN, no HTTPS, don't port-forward.
Per-user accounts (ADR-0019) — each person registers in-app and sees only their
own campaigns; sessions live under a gitignored users/.
- TypeScript / Node (≥22) everywhere — one language for a solo-maintained
project. The backend runs via
tsxwith no build step (and no hot-reload — restart after changingsrc/). - Backend (
src/): a plainnode:httpserver. Key modules:dm-engine.ts,dm-backend.ts+backends/(Claude vs Grok),narration.ts(strips backstage chatter from player-facing prose),campaign-store.ts,user-store.ts,music-store.ts,character-gen.ts,dice.ts,seed-selector.ts/texture-selector.ts. Media:image-generator.ts(thin dispatcher) +image-backends/(grok.ts,local.ts,style-loras.ts,index.ts,types.ts),image-prompt.ts,video-generator.ts/video-store.ts, and checked-in ComfyUI graphs inworkflows/*.json. Deps:@anthropic-ai/claude-agent-sdk,@modelcontextprotocol/sdk,dotenv,zod. - Frontend (
web/): React 18 + Vite 6, TypeScript. No router — a singleuseState<Screen>switch inweb/src/App.tsx. Built intopublic/, which the backend serves as its static root, so API and UI share one origin. - State: plain files per campaign under
campaigns/<user>/<campaign>/. Real campaign data is out of git (ADR-0005);test-campaignandcampaigns/_registry/are tracked exceptions.
Prerequisites and full LAN-hosting steps live in SETUP.md. The
short version:
# one-time: install backend + web deps and build the UI
npm run setup
# copy config (optional — a fresh checkout boots from config.example.json).
# Edit config.json to set server.host / port; put the bootstrap account in
# secrets.json. See docs/configuration.md.
cp config.example.json config.json
cp secrets.example.json secrets.json
# make sure the DM engine can reach Claude (subscription login)
claude # log in once; leave ANTHROPIC_API_KEY unset
# run the server (serves API + built UI on http://<HOST>:<PORT>, default :4317)
npm startFront-end iteration with hot-reload (proxies /campaigns and /models to the
backend on PORT):
cd web && npm run devUseful scripts:
| Command | What it does |
|---|---|
npm start / npm run serve |
Start the HTTP server (tsx src/server.ts). |
npm run setup |
Install both packages and build the UI into public/. |
npm run build:web |
Rebuild just the web UI. |
npm run dm |
Headless CLI DM loop (src/cli.ts) — the original Slice-1 entry point. |
npm run typecheck |
tsc --noEmit. |
npm run test:unit |
Backend unit tests. |
cd web && npm run test:e2e |
Playwright e2e (mobile + desktop projects). |
npm run migrate:multi-user |
One-time migration of pre-multi-user campaigns. |
npm run verify:grok-parity |
End-to-end Grok backend parity check on a throwaway campaign. |
Some verification/utility scripts under scripts/ are not wired into
package.json — run them directly with npx tsx scripts/<name>.ts:
verify-comfyui.ts (local ComfyUI reachable + draws a real image),
verify-lora-styles.ts (LoRA art-style recipes resolve/load, ADR-0032),
verify-video-gen.ts (Grok Imagine clip end-to-end, ADR-0026),
capture-docs-media.ts (regenerate the doc screenshots/GIFs), and
scratch-campaign.ts (create/delete a disposable throwaway campaign).
For an always-on host, run under systemd — see deploy/README.md.
Top-level screens switch in web/src/App.tsx
(Screen = "home" | "play" | "settings" | "gamesettings" | "newcharacter" | "auth"):
- Auth (
screens/Auth.tsx) — register / log in, server address. - Home (
screens/Home.tsx) — chronicle list, continue/switch/delete. - New Character (
screens/NewCharacter.tsx) — character creation, world/look/engine. - Play (
screens/Play.tsx) — the core game screen: narration transcript, turn input, scene images, music transport, side panels. - Game Settings (
screens/GameSettings.tsx) — per-campaign look/world/music. - Settings (
screens/Settings.tsx) — account defaults, connection, logout.
Play-screen panels (web/src/panels/): Self (SelfPanel / CharacterSheetFull),
Folk (FolkPanel), Quest (QuestPanel), Views (GalleryPanel).
Desktop vs mobile branches at 900px via web/src/lib/useIsDesktop.ts (ADR-0021).
The UI is instrumented with stable data-testid hooks used by the e2e suite and
by the documentation media capture script (scripts/capture-docs-media.ts).
Workflow conventions (from CLAUDE.md):
- ADR-first. Any architecturally significant change gets an ADR in
docs/adr/before or alongside implementation. - Vertical slices. Prefer many small, independently shippable/testable cycles over large monolithic ones.
- Definition of done: every unit of work traces to a GitHub issue, and each slice ends with its own committed + pushed change.
- Campaign data hygiene: never run destructive git operations against
anything under
campaigns/; ad-hoc validation uses a throwaway scratch campaign (scripts/scratch-campaign.ts), nevertest-campaignor a real game.
Good places to extend: additional DM backends (src/backends/) or image
backends (src/image-backends/), new MCP tools (src/mcp-servers/), art styles
— both prompt shaping (src/image-prompt.ts) and LoRA recipes
(src/image-backends/style-loras.ts, ADR-0032), music sources
(src/music-store.ts), and the desktop layout (ADR-0021/0022).
Start here: read docs/adr/0001-core-architecture.md,
then the design doc, then skim the ADR index for the area you're touching.