Files
the-sunken-reach/CLAUDE_BUILD_PROMPT.md
2026-09-22 16:27:53 +02:00

10 KiB

Build Prompt: D&D Campaign Manager Web App

Hand this whole folder to Claude Code (or paste this file as the initial prompt with the rest of the folder alongside it) to build the app. Read this file fully before writing any code, then read prisma/schema.prisma, docs/GAME_RULES_REFERENCE.md, and docs/ROLL_YOUR_CHARACTER.md — those three are the authoritative data model and game-logic spec, not just background.

What we're building

A web app for a Dungeons & Dragons 5e Dungeon Master and their players to run a campaign together. Users log in and see the campaigns/stories they're active in. Opening a campaign shows a player their character card (stats, spells, inventory). A DM can configure campaign basics, add characters, and build full character sheets through a guided dice-roll flow. Players can also build and manage their own characters, take notes, and pull up a contextual "quick stats" panel mid-combat (attack/damage bonus, HP, special abilities). The DM can create and assign weapons, create monsters/enemies, run encounters, and track damage live.

Tech stack (fixed — do not substitute)

  • Backend: Node.js + Express (or Fastify if you strongly prefer, but default to Express), JavaScript (not TypeScript, to match the stack the user works in day to day), Prisma ORM, PostgreSQL.
  • Frontend: React + Vite + TypeScript.
  • Real-time: Socket.io over WebSockets, self-hosted on the Express server — no paid real-time SaaS (no Pusher, Ably, Supabase Realtime, etc.).
  • Auth: Email + password. Hash passwords with bcrypt. Issue a JWT on login, store it client-side, verify it as Express middleware. A DM invites players to a campaign via a short invite code or a shareable /join/:inviteCode link (see Campaign.inviteCode in the schema).
  • Repo layout: monorepo, two top-level folders:
/frontend   — React + Vite + TypeScript app
/backend    — Express + Prisma API, Socket.io server
/prisma     — (already provided) schema.prisma — move into /backend/prisma
/seed-data  — (already provided) JSON reference data — move into /backend/prisma or /backend/src/data, wire into a seed script
/docs       — (already provided) keep as top-level /docs for reference during and after the build

Move prisma/ and seed-data/ into /backend as part of scaffolding (or keep top-level and point backend/prisma/schema.prisma's location via package.json's prisma.schema field — either is fine, just be consistent).

First command to run

cd backend && npx prisma validate

This schema was authored and structurally reviewed (brace/paren balance, no duplicate models/enums, relation names cross-checked by hand) but could not be run through prisma validate in the environment that generated it — its sandbox blocked outbound access to Prisma's engine-binary CDN. Run it for real as your first step before trusting the schema further.

Database & seeding

  1. prisma migrate dev from the schema as provided (adjust only if prisma validate surfaces a real error).
  2. Write backend/prisma/seed.js (or .ts) that reads the four JSON files in seed-data/ and:
    • Does not need to load species/classes/skills into the database as rows — those are fixed game-rule tables, not user data. Instead, port them into a small constants module the calculation engine imports directly (e.g. backend/src/game/data/species.js etc., generated from the JSON so the numbers stay traceable to docs/GAME_RULES_REFERENCE.md).
    • Does seed weapons.json into the Weapon table, but only as part of a demo/dev campaign seed (weapons are per-campaign in the schema, since a DM should be able to add homebrew weapons) — don't seed them globally.
  3. Build the calculation engine described in docs/GAME_RULES_REFERENCE.md as a single module, fully unit tested (ability modifier, proficiency bonus, HP by level — including the level-1-uses-max-die rule, AC, save/skill bonuses, spell DC/attack bonus). Every place in the API that returns a character's derived stats must call this module — never duplicate the formulas.

Backend API — suggested route groups

  • POST /auth/register, POST /auth/login, GET /auth/me
  • GET /campaigns (mine, as DM or player), POST /campaigns (create, DM becomes owner), GET /campaigns/:id, PATCH /campaigns/:id (DM-only, updates startingItemCount/startingSpellCount/startingGold/etc.)
  • POST /campaigns/:id/invite (DM regenerates/fetches invite code), POST /campaigns/join/:inviteCode (player joins)
  • GET /campaigns/:id/characters, POST /campaigns/:id/characters (start a build — DM for anyone, player for themselves)
  • GET /characters/:id, PATCH /characters/:id (stats/notes updates), DELETE /characters/:id (DM only)
  • POST /characters/:id/build/roll (server-rolls the requested die for the current build step — never trust a client-submitted roll — see docs/ROLL_YOUR_CHARACTER.md), POST /characters/:id/build/choose (submit a choice for the current step), POST /characters/:id/build/finalize
  • GET /characters/:id/quick-stats (assembles the combat-context panel per docs/GAME_RULES_REFERENCE.md)
  • POST /characters/:id/weapons, PATCH /characters/:id/weapons/:weaponId (equip/unequip/quantity), weapon CRUD under /campaigns/:id/weapons (DM)
  • POST /characters/:id/inventory, PATCH /characters/:id/inventory/:itemId
  • Monster CRUD under /campaigns/:id/monsters (DM only)
  • POST /campaigns/:id/encounters, GET /encounters/:id, POST /encounters/:id/participants (add character/monster), PATCH /encounters/:id/participants/:participantId (HP/conditions/initiative updates — this is the hot path during combat), POST /encounters/:id/advance-round
  • POST /notes, GET /campaigns/:id/notes, GET /characters/:id/notes

Authorization rule of thumb: DM (campaign owner / CampaignMember.role === 'DM') can read/write everything in their campaign; a player can read their own character fully, can read other players' character cards at whatever visibility the DM allows (default: full read for party members — keep it simple for v1), and can only write to their own character and their own notes.

Real-time events (Socket.io)

Join a Socket.io room per campaign (campaign:<id>) on connect. Emit on:

  • Character HP/condition changes (character:updated)
  • Encounter participant HP/condition/initiative changes (encounter:participant-updated)
  • Encounter round advances (encounter:round-advanced)
  • New character added / build finalized (character:created)

This is what makes HP changes and combat tracking show up live for everyone looking at the same campaign/encounter — keep the payloads small (just the changed record) and let the frontend re-fetch details if needed rather than pushing full derived-stat blobs over the socket.

Frontend — key screens

  1. Login / Register
  2. Campaign list (post-login home) — campaigns the user is a DM or player in; a "Join campaign" flow that takes an invite code/link
  3. Campaign home — DM sees campaign settings + character list + "start encounter"; player sees their character card front and center
  4. Character card / sheet — stats, AC, HP (editable inline for authorized users), skills, saves, spells, inventory, weapons, notes
  5. Character builder — walks docs/ROLL_YOUR_CHARACTER.md step by step: big "Roll d_N_" buttons, reveal choices, assign ability scores, pick skills, pick starting equipment, finalize
  6. Quick Stats panel — a compact overlay/drawer callable from anywhere the character is on screen, showing attack/damage bonus (per equipped weapon), HP, AC, and special abilities — this is what a player pulls up mid-attack, so it must be fast to open (no full page nav) and legible at a glance
  7. DM encounter/combat tracker — add characters and monsters to an encounter, initiative order, per-combatant HP/condition tracking, round counter, all live via Socket.io

Use React Query (or an equivalent fetch/cache layer) for API state and a lightweight Socket.io client hook that invalidates/updates the relevant query cache on each event above, rather than hand-rolling a parallel state store.

Build phases (do these in order; each should be independently runnable/testable)

  1. Monorepo scaffold: /frontend (Vite React-TS template) + /backend (Express, npm init, Prisma installed, schema in place), root README with dev-run instructions, .env.example in both.
  2. Prisma migrate + seed script + the calculation engine module + its unit tests (this is the foundation everything else depends on — get it fully correct and tested before building UI on top of it).
  3. Auth (register/login/JWT middleware) + campaign CRUD + invite/join flow.
  4. Character builder end-to-end (dice-roll flow → finalized character), backed by the calculation engine.
  5. Character sheet view (read) + stat/note editing (write) on the frontend.
  6. Quick Stats panel.
  7. Weapons (campaign-level CRUD + assign to character) and inventory.
  8. Monsters + encounters + combat/damage tracking.
  9. Socket.io wiring for all the real-time events above.
  10. Polish pass: loading/error states, mobile-usable layout (a DM running combat from a phone or tablet at the table is a realistic use case), basic automated tests for the calculation engine and the auth/campaign/character API routes.

Non-functional requirements

  • Validate all request bodies server-side (e.g. zod) — don't trust the client for anything that affects game state, especially dice rolls.
  • Never let a non-DM, non-owning-player mutate a character or campaign they don't have rights to — enforce this in middleware, not just by hiding UI.
  • Keep the calculation engine pure and dependency-free (no DB calls inside it) so it stays unit-testable in isolation.
  • Match the naming and structure in prisma/schema.prisma and the JSON files in seed-data/ exactly — they're the result of a full review pass and are meant to be used as-is, not redesigned.

Reference material in this folder

  • prisma/schema.prisma — full data model, described above
  • docs/GAME_RULES_REFERENCE.md — every D&D formula the app needs, verified against a hand-checked Excel calculator built earlier for this same campaign
  • docs/ROLL_YOUR_CHARACTER.md — the dice-roll character build flow, step by step, for the builder UI
  • seed-data/species.json, classes.json, skills.json, weapons.json — structured versions of the tables in GAME_RULES_REFERENCE.md, ready to import into code or a seed script