- Move docs/superpowers/{plans,specs}/ → docs/{plans,specs}/
- Add 4 previously untracked implementation plans to git
- Update CLAUDE.md with docs path overrides for superpowers skills
- Update HANDBOOK.md repo structure and workflow paths
- Add per-enemy dice rolls to ROADMAP planned section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
25 KiB
Handbook & Changelog Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Create CHANGELOG.md and docs/HANDBOOK.md capturing the current state of Darkwatch, plus two project-local Claude skills for maintaining them.
Architecture: Four markdown files — two are project docs (CHANGELOG.md at repo root, docs/HANDBOOK.md for collaborators), two are skill files (.claude/skills/) that instruct Claude how to update those docs when features change. Skills are committed to the repo so all collaborators get them.
Tech Stack: Markdown · Keep a Changelog format · Semver
File Map
| Action | Path | Purpose |
|---|---|---|
| Create | CHANGELOG.md |
Version history, Keep a Changelog format, v0.1.0 initial entry |
| Create | docs/HANDBOOK.md |
Full project reference: product overview + technical guide |
| Create | .claude/skills/update-changelog.md |
Skill: update CHANGELOG.md when features ship |
| Create | .claude/skills/update-handbook.md |
Skill: update HANDBOOK.md when features change |
| Modify | CLAUDE.md |
Document available project skills |
| Modify | .gitignore |
Ensure .claude/settings.local.json is ignored, skills are not |
Task 1: CHANGELOG.md
Files:
-
Create:
CHANGELOG.md -
Step 1: Create
CHANGELOG.mdat repo root
# Changelog
All notable changes to Darkwatch are documented here.
Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
---
## [Unreleased]
## [0.1.0] - 2026-04-11
### Added
**Auth & Access**
- Email/password authentication with JWT httpOnly cookies
- Register, login, and logout flows
- Campaign membership — DM and player roles
- Invite link system for joining campaigns
- Per-campaign role enforcement — API middleware, socket auth, and frontend gating
**Campaign Management**
- Campaign CRUD with real-time sync via Socket.IO
- DM and player roles with distinct permissions per campaign
**Characters**
- Full character CRUD — stats (STR, DEX, CON, INT, WIS, CHA), gear, talents, HP, XP, currency
- Auto-calculated AC and derived attacks
- Talent effects — stat bonuses, AC, attack/damage, gear slots, HP scaling
- Click-to-edit stats with view/edit mode split
- Per-character color picker
- DiceBear avatars with style picker
- Character title auto-derived from class + alignment + level
- Character creation wizard — 4-step modal: name/class/ancestry → 3d6 stat rolling → background/alignment/deity → review (HP, gold, gear slots, title all auto-derived)
**Spellcasting**
- 34 Tier 1–2 spells (Wizard and Priest)
- 1d20 cast checks with exhaustion on failure
- Wizard mishap table (d12, auto-applied with undo)
- Priest penance mechanic
- Rest to recover spells
- Spell focus indicator on DM character cards
**Dice Rolling**
- 3D animated dice with predetermined outcomes
- Character-colored dice
- Delayed roll log
- Advantage/disadvantage support
- Long-press for advantage/disadvantage on mobile
**Torch Timer & Luck Tokens**
- 60-minute torch countdown, synced across all clients
- Luck token toggle per character
**Atmosphere Effects**
- Fog overlay with intensity slider — DM-only, synced via socket
- Fire effect (Three.js particles)
- Rain and embers (tsParticles)
**Initiative Tracker**
- Team-based Shadowdark initiative: party rolls d20 individually (system takes highest) vs DM's single d20 for enemies
- Rolling phase: each player rolls for their character, DM rolls for enemies
- Active phase: turn order display, round counter, side highlighting
- Enemy HP tracking — visible to DM only, hidden from players
- Add/remove enemies mid-combat
- Real-time sync via Socket.IO
- Combat state persisted to database (survives server restarts)
**DM Tools**
- Compact DM character cards — 3-up grid showing HP, AC, luck, torch, and stat modifiers
- Atmosphere control panel (DM-only)
**UI & Platform**
- Medieval visual theme — Cinzel + Alegreya fonts, parchment texture, gold filigree, dark color scheme
- Mobile layout — full-screen character sheet, roll log drawer
- Custom SelectDropdown — consistent cross-platform styling
- MariaDB 11 via Docker (replaced SQLite)
- Step 2: Verify the file looks correct
cat CHANGELOG.md | head -20
Expected: header lines and ## [0.1.0] - 2026-04-11 visible.
- Step 3: Commit
git add CHANGELOG.md
git commit -m "docs: add CHANGELOG.md at v0.1.0"
Task 2: docs/HANDBOOK.md
Files:
- Create:
docs/HANDBOOK.md
This is the largest task. The handbook has two parts: a product overview (for anyone) and a technical guide (for developers). Write the complete file in one step.
- Step 1: Create
docs/HANDBOOK.md
# Darkwatch Handbook
**Last updated:** 2026-04-11
A reference for everyone working on or with Darkwatch — whether you're a DM discovering the tool, a developer joining the project, or a collaborator adding features.
## Table of Contents
- [Part 1: Product Overview](#part-1-product-overview)
- [What is Darkwatch?](#what-is-darkwatch)
- [Who is it for?](#who-is-it-for)
- [How a session works](#how-a-session-works)
- [Features](#features)
- [What's coming next](#whats-coming-next)
- [Known limitations](#known-limitations)
- [Part 2: Technical Guide](#part-2-technical-guide)
- [Stack](#stack)
- [Running locally](#running-locally)
- [Repo structure](#repo-structure)
- [Key patterns](#key-patterns)
- [Development workflow](#development-workflow)
---
# Part 1: Product Overview
## What is Darkwatch?
Darkwatch is a real-time digital session companion for [Shadowdark RPG](https://www.thearcanelibrary.com/pages/shadowdark). It runs in a web browser — the DM opens a campaign and players join via invite link. Everything stays in sync automatically: HP changes, dice rolls, spell slots, atmosphere effects, and combat all update live for everyone at the table.
It's not a virtual tabletop (no maps or tokens). It's the digital layer that sits alongside your physical table: character sheets, dice, a torch timer, initiative tracking, and the right amount of atmosphere.
## Who is it for?
- **Dungeon Masters** running Shadowdark campaigns who want a real-time view of the whole party without paper shuffling
- **Players** who want a clean digital character sheet that stays synced without everyone shouting HP updates across the table
- **Groups** playing in-person, online, or hybrid — it works in any browser
## How a session works
1. The DM creates a campaign and gets an invite link
2. Players click the link and join with their account (or create one)
3. The DM sees all characters in a compact card grid; players see their own full character sheet
4. Dice rolls, HP changes, spell casts, and atmosphere effects all sync in real time
5. When combat starts, the DM opens the initiative tracker — everyone rolls, the tracker resolves who goes first, and the DM advances turns
Sessions are stateful: pausing mid-combat and resuming later works because combat state is saved to the database.
## Features
### Characters
Each character has the full Shadowdark stat block: STR, DEX, CON, INT, WIS, CHA, HP, AC, XP, gold, gear slots, and currency. Stats are click-to-edit inline. AC and attacks are auto-calculated from stats and talents.
**Character creation wizard** walks through four steps: name/class/ancestry → roll 3d6 for each stat → background/alignment/deity → review. HP, starting gold, gear slots, and character title are all derived automatically at the end.
**Talents** can be added with name and effect (e.g. "+2 STR", "+1 gear slot"). They apply to the relevant derived values immediately.
**Avatars** use DiceBear with a style picker. Each character also has a color used for their dice and name display.
### Dice Rolling
Dice roll in 3D with animation. Results are predetermined on the server (not client-side) so they're trustworthy. Dice match the character's color. Results appear in a roll log that all players can see.
Supported: d4, d6, d8, d10, d12, d20, d100. Advantage and disadvantage are supported (roll two dice, take higher/lower). Long-press the roll button on mobile to trigger advantage or disadvantage.
### Spellcasting
34 Tier 1 and Tier 2 spells for Wizard and Priest classes. Casting rolls a d20 against a difficulty; failure causes exhaustion (one spell slot lost). The spell list shows current/max slots and highlights exhausted spells.
**Wizard mishaps:** On a critical fail, a d12 mishap table is rolled automatically. The effect is applied and can be undone if the DM made an error.
**Priest penance:** Failed casts trigger the Shadowdark penance mechanic. Rest recovers all spell slots.
### Torch Timer
A 60-minute countdown synced across all clients. The DM controls start/stop/reset. When the torch goes out, everyone knows.
### Luck Tokens
Each character has a luck token toggle. Players and the DM can both see and toggle luck token state.
### Atmosphere Effects
The DM can layer atmosphere effects that all players see in real time:
- **Fog** — a dark gradient overlay with adjustable intensity
- **Fire** — Three.js particle simulation, full screen
- **Rain / Embers** — tsParticles overlay
Atmosphere is DM-only to control; all players see the active effects.
### Initiative Tracker
Shadowdark uses team-based initiative — the whole party acts together, then all enemies act together. Darkwatch implements this directly:
**Rolling phase:** Each player clicks "Roll Initiative" for their character. The server takes the highest individual roll as the party's result. The DM rolls once for all enemies. Either side can roll in any order.
**Active phase:** Whichever side rolled higher acts first (ties go to the party). The DM clicks "Next Turn" to flip sides. The round counter increments each time the turn passes back to the party.
**Enemy management (DM only):** Enemies are added at combat start with a name and max HP. HP is tracked inline and visible only to the DM — players see enemy names but no HP values. Enemies can be added or removed mid-combat.
Combat state persists to the database. If the server restarts mid-fight, the tracker reappears when players rejoin.
### DM View
The DM sees all characters in a compact 3-up card grid showing: HP (current/max), AC, luck token state, torch timer, and all stat modifiers at a glance. The full character detail is one click away.
## What's coming next
Features currently planned or in progress:
- **Talent roll UI** — roll 2d6 on the class talent table when leveling up; currently talents are added manually
- **Death timer** — when a character is dying: 1d4 + CON rounds to live, d20 each turn to rise at 1 HP
- **Conditions / status effects** — poisoned, stunned, dying, etc. shown on character cards
- **Per-enemy dice rolls** — DM triggers attack/damage rolls from individual enemies in the tracker
- **Party loot / shared inventory** — campaign-level shared item pool, DM-managed
- **Creature gallery / NPC system** — saved enemy stat blocks, quick-add to initiative tracker
Longer-term ideas: multi-system support (Cairn, Knave, Cyberpunk RED), metrics and analytics, API documentation, and a public marketing site.
## Known limitations
- **Shadowdark only (for now)** — rules, stat blocks, and spell lists are Shadowdark-specific. Multi-system support is planned but not started.
- **No map or token support** — Darkwatch is a companion tool, not a full VTT.
- **Single combat displayed** — the initiative tracker shows one combat at a time. The backend supports multiple simultaneous combats (rare edge case) but the UI only shows the first.
- **No offline mode** — everything requires a server connection. There's no local-only or offline fallback.
- **No profile image uploads** — avatars use DiceBear. Custom image uploads are not yet supported.
---
# Part 2: Technical Guide
## Stack
| Layer | Technology |
|---|---|
| Client | React 18 + Vite + TypeScript + CSS Modules |
| Server | Express + Socket.IO + TypeScript (tsx runner) |
| Database | MariaDB 11 (Docker container `darkwatch-maria`, port 3307) |
| DB client | mysql2/promise |
| Real-time | Socket.IO (rooms per campaign: `campaign:{id}`) |
| Auth | JWT in httpOnly cookies |
| 3D dice | @react-three/fiber + cannon-es (Ammo.js physics) |
| Particles | tsParticles (rain/embers), Three.js (fire) |
| Avatars | DiceBear |
## Running locally
**Prerequisites:** Node 18+, Docker
**1. Start the database**
```bash
docker start darkwatch-maria
If the container doesn't exist yet, it was created with:
docker run -d --name darkwatch-maria \
-e MYSQL_DATABASE=darkwatch \
-e MYSQL_USER=darkwatch \
-e MYSQL_PASSWORD=darkwatch \
-e MYSQL_ROOT_PASSWORD=root \
-p 3307:3306 mariadb:11
2. Set up environment
Copy server/.env.example to server/.env (or ensure server/.env exists with DB credentials pointing to port 3307).
3. Start the server
cd server
npm install
npm run dev
The server runs migrations automatically on startup, then listens on http://localhost:3000.
4. Start the client
cd client
npm install
npm run dev -- --host
The client runs on http://localhost:5173 (or the next available port). The --host flag exposes it on the local network (useful for testing on a phone via ngrok).
Dev seed accounts:
- DM:
dm@darkwatch.test/password - Player:
player@darkwatch.test/password
Repo structure
shadowdark/
├── client/ # React + Vite frontend
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── pages/ # Route-level pages (CampaignView, Login, etc.)
│ │ ├── context/ # React context (AuthContext)
│ │ ├── lib/ # Utility libraries
│ │ ├── types.ts # Shared TypeScript types
│ │ └── socket.ts # Socket.IO singleton
│ └── public/ # Static assets (dice themes, etc.)
├── server/
│ ├── src/
│ │ ├── routes/ # Express REST routes + socket handler files
│ │ ├── index.ts # Express app setup
│ │ ├── socket.ts # Socket.IO setup, connection handler
│ │ ├── db.ts # MariaDB pool
│ │ ├── dice.ts # Server-side dice rolling
│ │ ├── auth.ts # JWT helpers
│ │ └── migrate.ts # Migration runner
│ └── migrations/ # Numbered SQL migration files (001_, 002_, ...)
├── docs/
│ ├── ROADMAP.md # Feature backlog and ideas
│ ├── HANDBOOK.md # This file
│ └── superpowers/
│ ├── specs/ # Design specs (brainstorming output)
│ └── plans/ # Implementation plans
├── .claude/
│ └── skills/ # Project-local Claude skills
├── CHANGELOG.md # Version history
└── CLAUDE.md # Claude Code instructions for this project
Key patterns
Socket rooms
Every campaign has a Socket.IO room named campaign:{id}. Clients join on page load:
socket.emit("join-campaign", String(campaignId));
Server handlers broadcast to the room:
io.to(`campaign:${campaignId}`).emit("event-name", data);
The initiative tracker uses a role-aware pattern: socket.to(room) sends to all except the emitting socket; socket.emit() sends back to the emitter. This lets the DM receive full data while players receive HP-stripped data in one operation.
CSS Modules and variables
All components use CSS Modules (.module.css files co-located with the component). Global CSS variables are defined in client/src/theme.css:
Key variables: --gold, --gold-rgb, --bg-modal, --bg-input, --text-primary, --text-secondary, --text-tertiary, --btn-active-text, --danger, --danger-rgb, --font-display.
Always use CSS variables for colors — never hardcode hex values in component CSS.
Database migrations
Migrations live in server/migrations/ as numbered SQL files: 001_initial_schema.sql, 002_spells.sql, 003_combat_state.sql, etc. The migration runner (server/src/migrate.ts) tracks applied migrations in a _migrations table and runs new ones on server startup.
To add a migration: create the next numbered file. It runs automatically on the next server restart.
Auth and roles
Auth uses JWT stored in httpOnly cookies. The token contains { userId }. On socket connection, the auth middleware reads the cookie and populates socket.data.user.
Campaign roles (DM or player) are stored in the campaign_members table. Role enforcement happens:
- REST routes: middleware checks
campaign_membersbefore handling requests - Socket handlers:
checkDM()queriescampaign_membersfor DM-only events - Frontend:
role === "dm"gates DM-only UI elements
All imports use .js extensions
The project uses ES modules. Even when importing .tsx or .ts files, use .js in the import path:
import InitiativeTracker from "../components/InitiativeTracker.js";
import type { CombatState } from "../types.js";
Vite and tsx resolve .js to the TypeScript source at build/dev time.
Development workflow
We use a structured approach: brainstorm → spec → plan → implement.
- Brainstorm (
/brainstormskill) — collaborative design session; produces a spec doc - Spec saved to
docs/superpowers/specs/YYYY-MM-DD-feature-design.md - Plan (
/writing-plansskill) — detailed implementation plan with code in every step - Plan saved to
docs/superpowers/plans/YYYY-MM-DD-feature.md - Implement — subagent-driven development, one task at a time with spec + quality reviews
All specs and plans are committed to the repo. Check docs/superpowers/ to understand why things were built the way they were.
Project-local Claude skills live in .claude/skills/. See CLAUDE.md for the list of available skills and how to invoke them.
- [ ] **Step 2: Verify the file was created**
```bash
wc -l docs/HANDBOOK.md
Expected: over 200 lines.
- Step 3: Commit
git add docs/HANDBOOK.md
git commit -m "docs: add HANDBOOK.md with product overview and technical guide"
Task 3: update-changelog skill
Files:
-
Create:
.claude/skills/update-changelog.md -
Step 1: Create
.claude/skills/directory and the skill file
mkdir -p .claude/skills
Then create .claude/skills/update-changelog.md:
# update-changelog
Use this skill when a feature has shipped, a bug has been fixed, or a feature has been removed. It updates CHANGELOG.md with a new versioned entry.
## Process
1. Read `CHANGELOG.md` — find the current latest version number (top `## [x.y.z]` entry)
2. Check recent git history for context on what changed:
```bash
git log --oneline -20
-
Read
docs/ROADMAP.md— check what moved to Completed since the last changelog entry -
Determine the version bump:
- Patch (0.1.x → 0.1.x+1): bug fixes, minor UI polish, no new user-facing features
- Minor (0.x.0 → 0.x+1.0): new features or meaningful enhancements
- Major (x.0.0 → x+1.0.0): breaking changes or significant redesigns (rare)
-
Write a new entry above the previous version, using today's date and the bumped version:
## [0.2.0] - YYYY-MM-DD ### Added - Feature name — brief description of what it does for users ### Changed - What changed and why ### Fixed - Bug description — what was wrong and what's right now ### Removed - What was removed and whyOnly include sections that apply. Skip empty sections.
-
Update
docs/HANDBOOK.mdif the change affects feature descriptions or known limitations (use theupdate-handbookskill or do it inline). -
Commit:
git add CHANGELOG.md git commit -m "chore: changelog v0.x.y"
Versioning rules
- Write entries for users, not developers ("Added initiative tracker" not "Added registerInitiativeHandlers to socket.ts")
- One entry per version bump — don't create multiple entries for the same version
- The
[Unreleased]section at the top is for changes not yet assigned a version; move them down when cutting a release - Keep descriptions brief but specific — "Fixed HP leak exposing enemy stats to players" beats "Fixed bug"
- [ ] **Step 2: Verify the skill file exists**
```bash
ls .claude/skills/
Expected: update-changelog.md
- Step 3: Commit
git add .claude/skills/update-changelog.md
git commit -m "feat: add update-changelog project skill"
Task 4: update-handbook skill
Files:
-
Create:
.claude/skills/update-handbook.md -
Step 1: Create
.claude/skills/update-handbook.md
# update-handbook
Use this skill when a feature has been added, changed, or removed and the handbook needs updating. It keeps `docs/HANDBOOK.md` accurate.
## Process
1. Read `docs/HANDBOOK.md` in full.
2. Identify what changed. Ask the user, or review recent git commits and CHANGELOG.md:
```bash
git log --oneline -10
cat CHANGELOG.md | head -40
-
Determine which sections need updating:
- New feature added → add a subsection under Features in Part 1; update What's coming next to remove it from planned
- Feature removed → remove or note it; update Known limitations if relevant
- Feature changed → update the relevant Features subsection
- New dev pattern introduced → update Key patterns in Part 2
- Stack change → update the Stack table in Part 2
- New workflow step → update Development workflow in Part 2
-
Make only the necessary edits. Don't rewrite sections that aren't affected.
-
Update the
Last updateddate at the top of the file to today's date. -
Commit:
git add docs/HANDBOOK.md git commit -m "docs: update handbook for [feature name]"
Tone guidelines
Part 1 (Product Overview): Write for someone who plays TTRPGs. Explain what something does from the user's perspective, not how it's implemented. "Players roll a d20 for their character; the system takes the highest" not "party_roll = Math.max(Object.values(party_rolls))".
Part 2 (Technical Guide): Write for a developer. Be specific — include exact file paths, commands, and patterns. Don't leave anything as "see the code."
- [ ] **Step 2: Commit**
```bash
git add .claude/skills/update-handbook.md
git commit -m "feat: add update-handbook project skill"
Task 5: Register skills in CLAUDE.md and update .gitignore
Files:
-
Modify:
CLAUDE.md -
Modify:
.gitignore -
Step 1: Read the current CLAUDE.md
cat CLAUDE.md
- Step 2: Add a Project Skills section to CLAUDE.md
Append the following section to the end of CLAUDE.md:
## Project Skills
Project-local skills live in `.claude/skills/`. Invoke them by asking Claude to "use the [skill name] skill" or by reading the skill file directly.
| Skill | File | When to use |
|---|---|---|
| `update-changelog` | `.claude/skills/update-changelog.md` | After a feature ships, bug is fixed, or feature is removed |
| `update-handbook` | `.claude/skills/update-handbook.md` | After a feature is added, changed, or removed |
- Step 3: Add
.claude/settings.local.jsonto .gitignore
Append to .gitignore:
.claude/settings.local.json
- Step 4: Verify .gitignore still tracks the skills
git check-ignore -v .claude/skills/update-changelog.md
Expected: no output (file is NOT ignored — it should be committed).
- Step 5: Commit
git add CLAUDE.md .gitignore
git commit -m "docs: register project skills in CLAUDE.md, gitignore local settings"
Self-Review Checklist
After all tasks complete, verify against the spec:
CHANGELOG.mdat repo root, Keep a Changelog format, v0.1.0 entry covers all shipped features — Task 1docs/HANDBOOK.mdhas both Part 1 (product overview) and Part 2 (technical guide) — Task 2- Part 1 covers: what Darkwatch is, who it's for, how a session works, all features, what's coming, known limitations — Task 2
- Part 2 covers: stack, running locally, repo structure, key patterns, dev workflow — Task 2
.claude/skills/update-changelog.mdexists and includes: git log check, version bump rules, entry format, commit step — Task 3.claude/skills/update-handbook.mdexists and includes: section identification, tone guidelines, commit step — Task 4CLAUDE.mdlists both skills with when-to-use guidance — Task 5.claude/settings.local.jsonis gitignored; skill files are not — Task 5