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

4.4 KiB

Implementation Plan: Roll Initiative

Branch: 026-roll-initiative | Date: 2026-03-10 | Spec: spec.md Input: Feature specification from /specs/026-roll-initiative/spec.md

Summary

Add a "roll initiative" feature that generates random initiative values (1d20 + modifier) for combatants linked to bestiary creatures. The initiative column uses a click-to-edit pattern: bestiary combatants without a value show a d20 icon (click to roll), combatants with a value show it as plain text (click to edit), and manual combatants show "--" (click to type). A "Roll All" button in the turn navigation bar batch-rolls for all eligible combatants that don't already have initiative. The domain layer receives pre-resolved dice values (never generates randomness itself), and the existing setInitiative domain function handles persistence and re-sorting.

Technical Context

Language/Version: TypeScript 5.8 (strict mode, verbatimModuleSyntax) Primary Dependencies: React 19, Tailwind CSS v4, Lucide React (icons), Vite 6 Storage: N/A (no storage changes — existing localStorage persistence handles initiative via setInitiativeUseCase) Testing: Vitest Target Platform: Browser (single-user, local-first) Project Type: Web application (monorepo: domain → application → web adapter) Performance Goals: Instant — single click produces immediate result Constraints: Domain layer must remain pure (no randomness, no I/O) Scale/Scope: Single-user encounter tracker

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

Principle Status Notes
I. Deterministic Domain Core PASS Dice rolls are resolved at the adapter boundary and passed as inputs to domain/application functions. Domain never calls Math.random().
II. Layered Architecture PASS New domain function receives pre-rolled values. Application use case orchestrates. React adapter generates random numbers and wires UI.
III. Agent Boundary N/A No agent features involved.
IV. Clarification-First PASS Spec is clear; no ambiguities remain.
V. Escalation Gates PASS All functionality is within spec scope.
VI. MVP Baseline Language PASS No permanent bans introduced.
VII. No Gameplay Rules PASS Initiative rolling mechanics are in the spec, not the constitution.

Project Structure

Documentation (this feature)

specs/026-roll-initiative/
├── plan.md              # This file
├── research.md          # Phase 0 output
├── data-model.md        # Phase 1 output
├── quickstart.md        # Phase 1 output
└── tasks.md             # Phase 2 output (via /speckit.tasks)

Source Code (repository root)

packages/domain/src/
├── roll-initiative.ts           # NEW — pure function: (diceRoll, modifier) → initiative value
├── initiative.ts                # EXISTING — calculateInitiative (reused for modifier)
├── set-initiative.ts            # EXISTING — reused to apply rolled values
├── types.ts                     # EXISTING — no changes needed
├── events.ts                    # EXISTING — no new event types needed (reuses InitiativeSet)
└── __tests__/
    └── roll-initiative.test.ts  # NEW — tests for the pure roll function

packages/application/src/
├── roll-initiative-use-case.ts      # NEW — single combatant roll orchestration
├── roll-all-initiative-use-case.ts  # NEW — batch roll orchestration
└── set-initiative-use-case.ts       # EXISTING — called by the new use cases

apps/web/src/
├── components/
│   ├── combatant-row.tsx        # MODIFIED — add d20 roll button next to initiative input
│   ├── turn-navigation.tsx      # MODIFIED — add "Roll All" button
│   └── d20-icon.tsx             # NEW — inline SVG component (paths copied from root d20.svg)
└── hooks/
    └── use-encounter.ts         # EXISTING — no changes needed (roll callbacks live in App.tsx)

Structure Decision: Follows the existing monorepo layering. New domain function is minimal (compute initiative from dice roll + modifier). Application use cases orchestrate the domain call and persistence. React components handle randomness generation and UI.

Complexity Tracking

No constitution violations — table not needed.