darkwatch/docs/plans/2026-04-11-handbook-changelog.md
Aaron Wood 7c7bdf2ee5 chore: consolidate docs into flat structure and commit all plans
- 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>
2026-04-11 23:55:45 -04:00

25 KiB
Raw Blame History

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.md at 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 12 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_members before handling requests
  • Socket handlers: checkDM() queries campaign_members for 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.

  1. Brainstorm (/brainstorm skill) — collaborative design session; produces a spec doc
  2. Spec saved to docs/superpowers/specs/YYYY-MM-DD-feature-design.md
  3. Plan (/writing-plans skill) — detailed implementation plan with code in every step
  4. Plan saved to docs/superpowers/plans/YYYY-MM-DD-feature.md
  5. 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
  1. Read docs/ROADMAP.md — check what moved to Completed since the last changelog entry

  2. 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)
  3. 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 why
    

    Only include sections that apply. Skip empty sections.

  4. Update docs/HANDBOOK.md if the change affects feature descriptions or known limitations (use the update-handbook skill or do it inline).

  5. 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
  1. 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
  2. Make only the necessary edits. Don't rewrite sections that aren't affected.

  3. Update the Last updated date at the top of the file to today's date.

  4. 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.json to .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.md at repo root, Keep a Changelog format, v0.1.0 entry covers all shipped features — Task 1
  • docs/HANDBOOK.md has 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.md exists and includes: git log check, version bump rules, entry format, commit step — Task 3
  • .claude/skills/update-handbook.md exists and includes: section identification, tone guidelines, commit step — Task 4
  • CLAUDE.md lists both skills with when-to-use guidance — Task 5
  • .claude/settings.local.json is gitignored; skill files are not — Task 5