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