# Shadowdark Character Sheet Manager — Design Spec ## Overview A web app for managing Shadowdark RPG character sheets in real-time. Players in a campaign can view and edit characters simultaneously, with changes syncing live across all connected clients. Hosted locally and exposed via ngrok for group access. ## Tech Stack - **Frontend:** React + Vite (TypeScript) - **Backend:** Node.js + Express + Socket.IO - **Database:** SQLite (single file, zero config) - **Project location:** `/Users/aaron.wood/workspace/shadowdark/` - **Structure:** `client/` and `server/` directories ## Data Model ### campaigns | Column | Type | Notes | | ---------- | ------- | -------------------------- | | id | INTEGER | Primary key, autoincrement | | name | TEXT | Campaign name | | created_by | TEXT | For future auth | | created_at | TEXT | ISO timestamp | ### characters | Column | Type | Notes | | ----------- | ------- | -------------------------------------------------- | | id | INTEGER | Primary key, autoincrement | | campaign_id | INTEGER | FK to campaigns | | created_by | TEXT | For future auth | | name | TEXT | Character name | | class | TEXT | Fighter / Priest / Thief / Wizard | | ancestry | TEXT | Human / Elf / Dwarf / Halfling / Goblin / Half-Orc | | level | INTEGER | Default 1 | | xp | INTEGER | Default 0 | | hp_current | INTEGER | | | hp_max | INTEGER | | | ac | INTEGER | | | alignment | TEXT | Lawful / Neutral / Chaotic | | title | TEXT | Optional, e.g. "the Brave" | | notes | TEXT | Freeform notes | ### character_stats | Column | Type | Notes | | ------------ | ------- | --------------------------------- | | character_id | INTEGER | FK to characters | | stat_name | TEXT | STR / DEX / CON / INT / WIS / CHA | | value | INTEGER | Raw score (3-18 typically) | One row per stat per character. Ability modifiers are derived, not stored: | Score | Modifier | | ----- | -------- | | 1-3 | -4 | | 4-5 | -3 | | 6-7 | -2 | | 8-9 | -1 | | 10-11 | +0 | | 12-13 | +1 | | 14-15 | +2 | | 16-17 | +3 | | 18 | +4 | ### character_gear | Column | Type | Notes | | ------------ | ------- | ----------------------------------------------------- | | id | INTEGER | Primary key, autoincrement | | character_id | INTEGER | FK to characters | | name | TEXT | Item name | | type | TEXT | weapon / armor / gear / spell | | slot_count | INTEGER | Gear slots used (Shadowdark inventory system) | | properties | TEXT | JSON — e.g. `{"damage":"1d6","melee":true,"bonus":1}` | Spells are stored as gear entries with `type: "spell"` and a `tier` property in the JSON. ### character_talents | Column | Type | Notes | | ------------ | ------- | ----------------------------------------- | | id | INTEGER | Primary key, autoincrement | | character_id | INTEGER | FK to characters | | name | TEXT | Talent name | | description | TEXT | Flavor/rules text | | effect | TEXT | JSON — mechanical bonuses for dice roller | ## UI Layout ### Campaign List (home page) - Grid/list of existing campaigns - "+ New Campaign" button - Click a campaign to enter it ### Campaign View (main screen) - Campaign name header - Responsive character card grid: - Desktop: 4 cards across - Tablet: 2 cards across - Mobile: 1 card, stacked - Cards flow naturally — 6 characters = 4 + 2 rows on desktop, just scroll - "+ Add Character" button - Future: dice log panel (right side on desktop, bottom drawer on mobile) ### Character Card (in the grid) - Name, class, ancestry, level header - HP bar with +/- buttons (current / max) - AC display - Six stats in compact 3x2 layout: score, modifier, +/- buttons - Gear summary (slots used / total) - Click to expand into full detail view ### Character Detail (expanded / modal) - Full stat editing with +/- buttons - Gear/inventory management: add/remove items with type and properties - Talents list: add/remove with name, description, effect - Spell list (for Priests/Wizards — gear items with type "spell") - Notes freeform text field - Future: dice roll buttons next to weapons/spells ## Real-time Sync - Client joins a Socket.IO room per campaign (room ID = campaign ID) - Mutation flow: client -> server API -> save to SQLite -> broadcast to room - Other clients receive update and patch local state (no full reload) - Optimistic UI: editing client updates immediately, server confirms - On reconnect, client fetches latest full state from server ## Auth (v1: none, future-ready) - v1 is open free-for-all: anyone with the link can create/edit anything - `created_by` fields exist in the schema for future use - Migration path to light auth (name/pin per character) or GM controls requires adding middleware, not restructuring ## Future: Dice Rolling (not in v1) Design accommodations for future dice rolling: - **Shared dice log panel** — layout reserves space for it - **Inline roll buttons** — next to weapons/spells and stats - **Server-side rolls** — server calculates d20 + ability modifier + gear/talent bonuses, broadcasts result - **Log entries** — show who rolled, what for, full breakdown (e.g. "Kira attacks with Longsword +1: d20(14) + STR(+2) + weapon(+1) = 17"), and timestamp - **Data model supports it** — `character_gear.properties` and `character_talents.effect` JSON fields carry the mechanical data the roller needs ## Deployment - Run `server/` on localhost with a configured port - `client/` built and served by Express (or Vite dev server during development) - Expose via ngrok for friends to access - SQLite file lives in `server/data/shadowdark.db` ## Out of Scope for v1 - Authentication / permissions - Dice rolling - Character import/export - Multiple themes / dark mode - Mobile native app