darkwatch/docs/specs/2026-04-08-shadowdark-character-manager-design.md

6.8 KiB

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 itcharacter_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