Ironsworn TTRPG character tracker. Manage characters, track progress, roll moves, consult oracles, and run expeditions — all in a web-based interface with real-time state persistence.
- This was a first foray into using Claude to do all the coding. It's an experiment not a social commentary.
- That being said, the AI generated images for foes are slop, I'd be open to working with a willing artist to make these much nicer.
- I am not a designer, suggestions for improving the UI are most welcome.
- I have probably misintrepreted or missed a rule here or there, bug reports are definitely welcome.
- Ironsworn, the Delve expansion, and Lodestar are incredible RPGs, I highly encourage you to support Shawn Tomkin and purchase his work.
A final note: The YRT expansion that is referred to is a side project I'm working on set in a post-apocalyptic world where technology is indistinguishable from magic. I'm not ready to release anything yet but you can see some of the the workings in the Conclave spells, the use of mana, and some Oracles.
| Layer | Technology |
|---|---|
| Frontend | SvelteKit 2 + Svelte 5 (runes), adapter-node SSR |
| API | Fastify 5, TypeScript, Zod validation |
| Database | PostgreSQL 16 with Row-Level Security (Drizzle ORM) |
| Cache | Redis (Valkey) — rate limiting, maintenance mode |
| Auth | JWT RS256 (15-min access + 30-day rotating refresh tokens, auto-refresh in hooks) |
| Process | PM2 (cluster mode for API, fork for web) |
| Proxy | Nginx with TLS (Let's Encrypt), rate limiting |
ironledger/
apps/
api/ Fastify REST API (port 3000)
web/ SvelteKit frontend (port 5173 dev / 3001 prod)
packages/
shared/ Shared TypeScript types and game data
infra/
nginx/ Production Nginx config
scripts/ Server setup, deploy, backup scripts
docker/ Dev database containers
docs/ Feature documentation
- Node.js 22+ (pinned in
.nvmrc— runnvm useto select it) - PostgreSQL 16+ (or Docker)
- Redis 7+ (or Docker)
# 1. Clone and install
git clone <repo-url> && cd ironledger
npm install
# 2. Start database services (Docker)
docker compose up -d
# Or use: ./infra/scripts/dev-db.sh up
# 3. Create .env from template
cp .env.example .env
# Edit .env — generate JWT keys with:
# openssl genrsa -out private.pem 2048
# openssl rsa -in private.pem -pubout -out public.pem
# (Optional) enable the AI Storyteller — generate AI_KEY_ENC_SECRET with:
# node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
# 4. Run database migrations
npm run migrate
# 5. Seed development data (optional)
npm run seed --workspace=apps/api
# 6. Start dev servers (two terminals)
npm run dev # API on :3000
npm run dev:web # Web on :5173| Account | Password | |
|---|---|---|
| Admin | admin@ironledger.local |
adminpassword123! |
| User | dev@ironledger.local |
devpassword123! |
ESLint and Prettier are wired into the monorepo and enforced in CI (the build fails on ESLint errors or a Prettier diff). Run from the repo root:
npm run lint # ESLint across api, web (incl. Svelte), and shared
npm run lint:fix # ESLint with autofix
npm run format # Prettier — rewrite all files in place
npm run format:check # Prettier — verify formatting (what CI runs)Config lives in eslint.config.js (flat config) and .prettierrc.json.
Prettier uses 2-space indent for apps/api and packages/shared, and tabs
for apps/web (matching the Svelte tab convention). any and unused-var
findings are surfaced as warnings, not errors, so they don't block CI.
- Create and manage Ironsworn characters
- Full character sheet with stats, tracks, assets, and vows
- Auto-save with 1500ms debounce
- Complete Ironsworn move reference with picker grid
- Action, progress, and spell rolls with 3D animated dice (per-livery colours + texture, customizable in Settings → Dice)
- Live roll formula display (e.g.,
d6 + iron[3] + adds[+1] vs d10 & d10) between spinners and roll button - Data-driven progress routing: each progress move reads from the correct track (foe, journey, site, bonds, failures) via
progressSourcefield - Move precondition checking (disables moves with unmet requirements)
- Burn momentum to upgrade roll outcomes
- Clickable move cross-references in outcome text
- Strong Hit / Weak Hit / Miss outcomes shown in move detail view
- Oracle table consultation with random rolls
- Linked oracle chains
- "Roll Twice" results auto-combine two independent re-rolls into one result
- Full foe catalogue from Ironsworn core, Delve supplement, and YRT homebrew
- Per-character encounter tracking with progress tracks
- Ranks 1–5 (Troublesome → Epic) with progress mechanics
- Escalating Harm (YRT): foe harm starts at 1 and increases on each Miss; capped at effective rank
- Escalating Defense (YRT): foe projects a mana shield that absorbs strikes; defense erodes on each Miss and blocks progress marks while active; cap = progress-per-hit for that rank
- Journey, Delve Site, and Scene Challenge (from Ironsworn: Lodestar) expedition types
- Progress tracks with waypoint/room mechanics
- Delve tables for site exploration
- Scene Challenges add a 4-segment countdown track alongside the progress track
- Track settlements and the people who inhabit them
- Oracle-powered random generation for community names, locations (inland/coastal), and NPC names (Ironlander, Elf, Giants, Varou, Trolls variants)
- Free-form notes, region, trouble, and relationship fields per entry
- 3-column responsive grid layout
- Liveries — pick a display font paired with a chrome palette in Settings → Appearance. All named after mythic or literary figures whose visual world matches the theme. Ships with Beowulf (Skranji stone-cut titles over weathered verdigris, default), Merlin (calligraphic Simonetta over amber), Yorick (Metamorphous over granite), Vlad (Nosifer dripping crimson), Jack (Berkshire Swash pumpkin), Elspeth (witch violet + moss), and Deckard (Orbitron sci-fi over void-navy)
- Drop-in livery packs — each livery is a single
liveries/<id>/livery.json(display font, dark/light chrome palette, and 3D-dice skin); a build-time generator compiles them into a manifest + CSS, so adding a new theme needs no code changes. See docs/liveries.md - Independent light / dark / auto (system) theme, orthogonal to the livery
- Per-livery 3D dice colours + texture, overridable in Settings → Dice
- Responsive layout down to 360px; tested on Surface Duo (540px breakpoint for icon-only tabs)
- Horizontal swipe-to-switch-tab on the home page (direction and distance thresholds filter out vertical scrolls and taps); opt out per-element with
data-no-swipe-tabs - On Adventure, a drag-to-resize split panel replaces the desktop side-by-side layout: GCB on top, session log below, both scroll independently, split persists to localStorage
- Picker dialogs (Foes, Moves, Oracles) size to
min(85dvh, 720px)so they don't collapse behind mobile browser chrome - Collapsible glass search pill on Expeditions and Communities to reclaim toolbar space on narrow screens
- Body scroll lock when modal pickers or the Adventure split panel are active
- Persistent session log with interactive links
- Resource changes, moves, oracles, progress, initiative, debilities, menace links
- State-modifying links with strikethrough after click
- Auto-appended "Momentum: Burn Available" log entry when burn is possible on action roll results
- Cascade rules auto-append log entries on resource floor events:
- Overflow: health/spirit drops below 0 → excess converts to momentum loss
- Floor: supply hits 0 → "Supply: Exhausted" with Unprepared debility link; momentum hits −6 → note appended
- Floor overflow: resource already at minimum → "Face a Setback", "Face Death", "Face Desolation", or "Out of Supply" entries with per-point clickable exchange links
- Export includes timestamps for each entry
- Turn a recorded slice of the session log into narrative prose (Claude, ChatGPT, or Gemini)
- Keys server-side, encrypted at rest (AES-256-GCM); the browser never holds a provider key or calls a provider directly — it streams through a
/api/ai/generateproxy - Per-provider key/model in Settings; a single global Setup Instructions system prompt shared across storytellers
- Editable output box (tweak the prose before saving), regenerate-in-place on story entries, and a Markdown Stories export
- Requires the
AI_KEY_ENC_SECRETenv var; opt-in, off until a key is configured. See docs/ai-story.md
- Per-character initiative badges (sword/shield icons) on GCB and character sheet
- Automatically clears when foe is deselected
Integrated into the home page as a dedicated Admin tab (screwdriver-wrench icon, only visible to admin-role users).
- Users tab — sortable user table with pagination, role management (promote/demote/suspend), user delete
- Logs tab — live PM2 server log viewer (API/web stdout & stderr), filter, expandable JSON rows
- Maintenance tab — enable/disable maintenance mode with countdown, message, session revocation
- Registration tab — lock/unlock new account creation with a custom message
- Redis-backed system maintenance state shared across instances
- Global countdown banner for all users (polls every 10s)
- Blocks non-admin logins during maintenance
- Revokes all refresh tokens to force logoff
- Admin bypass for login during maintenance
- Full audit logging of maintenance events
- Admin can disable new user registration without triggering full maintenance mode
- Locked state shows a custom message on the
/registerpage with a link to sign in - Backed by Redis — survives server restarts
| Command | Description |
|---|---|
npm run dev |
Start API dev server |
npm run dev:web |
Start web dev server |
npm run build |
Build both API and web |
npm run test |
Run API test suite (vitest) |
npm run migrate |
Run database migrations |
npm run check:web |
TypeScript check for web |
See docs/deployment.md for complete IONOS VPS deployment instructions including:
- One-time server provisioning
- Pushbutton CI/CD via GitHub Actions
- Manual deploy script with rollback
- Database backup strategy
- Horizontal scaling guide
- Maintenance mode workflow
Feature docs are in the docs/ directory:
| Doc | Description |
|---|---|
| admin.md | Admin panel — users, logs, maintenance, registration lock |
| ai-story.md | AI Storyteller — encrypted keys, providers, proxy |
| architecture_decisions.md | Architecture and design decisions |
| campaign-map.md | Campaign map — markers, connections, icons |
| character-sheet.md | Character sheet component |
| command-bar.md | Command bar — slash commands and completion |
| communities.md | Communities & NPCs — data model, oracle generation |
| data-schema.md | Catalogue data format spec (moves, assets, oracles, foes) |
| deployment.md | IONOS VPS deployment guide |
| dice-rolling.md | Dice rolling implementation |
| expansion-toggles.md | Delve / YRT expansion toggle system |
| expeditions.md | Expeditions system (Journey, Delve Site, Scene Challenge) |
| extensions.md | Extension system — content packs, build-time manifest |
| foes.md | Foes and encounters |
| global-context-bar.md | GlobalContextBar tile layout |
| import-schema.md | User-data export/import JSON (characters, expeditions, connections, log) |
| liveries.md | Liveries — drop-in font + chrome + dice theme packs |
| log.md | Session log with interactive links |
| mobile.md | Mobile layout, swipe gestures, and viewport behaviour |
| moves.md | Moves system and dice rolling |
| notes.md | Notes dialog |
| oracles.md | Oracle tables |
| ui-components.md | Shared UI design specs (cards, pills, tooltips, side labels) |
| extensions/yrt/README.md | YRT homebrew data format extensions |
- Ironsworn, Ironsworn: Delve, and Ironsworn: Lodestar by Shawn Tomkin — licensed under CC BY-NC-SA 4.0. Game data sourced from Datasworn by rsek. The Scene Challenge moves and official oracle names live in
extensions/lodestar/. - game-icons.net — game-themed SVG icons (CC BY 3.0)
- Font Awesome — UI icons (Free tier, CC BY 4.0)
- The Noun Project — additional UI icons
- Caeora — hand-drawn settlement/structure map icons
- SSH key-only auth, UFW firewall, fail2ban
- HTTPS with HSTS, CSP, X-Frame-Options (Helmet + Nginx)
- Rate limiting at Nginx and API layers (Redis-backed)
- PostgreSQL Row-Level Security
- Argon2id password hashing (OWASP 2024)
- HaveIBeenPwned password checking
- hCaptcha on registration
- JWT RS256 with refresh token rotation and theft detection
- 2 MB request body limit
The Iron Ledger source code is licensed under the MIT License — use it freely, keep the copyright notice. The MIT license covers the code only. The bundled game content is not MIT-licensed and is instead under CC BY-NC-SA 4.0:
- The Ironsworn / Delve / Lodestar data (
apps/api/data/,extensions/lodestar/) — Shawn Tomkin's work, attributed below. - The original YRT homebrew expansion (
extensions/yrt/) — © Carl Orthlieb, released under the same CC BY-NC-SA 4.0 (ShareAlike, since it builds on Ironsworn).
Because that license is NonCommercial, the bundled whole may not be sold.
Iron Ledger is an independent fan project and is not affiliated with or endorsed by Shawn Tomkin or Ironsworn.
This work is based on Ironsworn, Ironsworn: Delve, and Ironsworn: Lodestar, created by Shawn Tomkin, and licensed for use under the CC BY-NC-SA 4.0 license. Game data sourced from Datasworn by rsek. Icons by game-icons.net, Font Awesome, The Noun Project, and Caeora. Iron Ledger is free and open source — source available on GitHub.
