Files
initiative/specs/026-roll-initiative/quickstart.md

2.8 KiB

Quickstart: Roll Initiative

Overview

This feature adds dice-rolling to the encounter tracker. The initiative column uses a click-to-edit pattern: bestiary combatants without initiative show a d20 icon that rolls 1d20 + modifier on click; once set, the value displays as plain text (click to edit/clear). Manual combatants show "--" (click to type). A "Roll All" button in the top bar batch-rolls for all bestiary combatants that don't yet have initiative.

Key Files to Touch

Domain Layer (packages/domain/src/)

  1. roll-initiative.ts (NEW) — Pure function: rollInitiative(diceRoll: number, modifier: number) → number. Validates dice roll range [1,20], returns diceRoll + modifier.

  2. __tests__/roll-initiative.test.ts (NEW) — Tests for the pure function covering normal, boundary (1, 20), and negative modifier cases.

Application Layer (packages/application/src/)

  1. roll-initiative-use-case.ts (NEW) — Single combatant roll. Receives (store, combatantId, diceRoll, getCreature). Looks up creature via creatureId, computes modifier via calculateInitiative, computes final value via rollInitiative, delegates to setInitiative domain function.

  2. roll-all-initiative-use-case.ts (NEW) — Batch roll. Receives (store, diceRolls: Map<CombatantId, number>, getCreature). Iterates eligible combatants, applies setInitiative in sequence on evolving encounter state, saves once.

Web Adapter (apps/web/src/)

  1. components/d20-icon.tsx (NEW) — React component rendering the d20.svg inline. Accepts className for sizing.

  2. components/combatant-row.tsx (MODIFY) — Add d20 button next to initiative input. Only shown when combatant.creatureId is defined. New prop: onRollInitiative: (id: CombatantId) => void.

  3. components/turn-navigation.tsx (MODIFY) — Add "Roll All Initiative" d20 button in right section. New prop: onRollAllInitiative: () => void.

  4. hooks/use-encounter.ts (MODIFY) — Add rollInitiative(id) and rollAllInitiative() callbacks that generate dice rolls via Math.floor(Math.random() * 20) + 1 and call the respective use cases.

  5. App.tsx (MODIFY) — Wire new callbacks to components.

Architecture Pattern

[Browser Math.random()] → diceRoll (number 1-20)
        ↓
[Application Use Case] → looks up creature, computes modifier, calls domain
        ↓
[Domain rollInitiative()] → diceRoll + modifier = initiative value
        ↓
[Domain setInitiative()] → updates combatant, re-sorts encounter
        ↓
[Store.save()] → persists to localStorage

Running

pnpm --filter web dev          # Dev server at localhost:5173
pnpm test                      # Run all tests
pnpm vitest run packages/domain/src/__tests__/roll-initiative.test.ts  # Single test
pnpm check                     # Full merge gate