Files
initiative/specs/002-add-combatant/plan.md

3.5 KiB

Implementation Plan: Add Combatant

Branch: 002-add-combatant | Date: 2026-03-03 | Spec: spec.md Input: Feature specification from /specs/002-add-combatant/spec.md

Summary

Add a pure domain function addCombatant that appends a new combatant to the end of an encounter's combatant list without altering the active turn or round. The feature follows the same pattern as advanceTurn: a pure function returning updated state plus domain events, with an application-layer use case and a React adapter hook.

Technical Context

Language/Version: TypeScript 5.x (strict mode, verbatimModuleSyntax) Primary Dependencies: None for domain; React 19 for web adapter Storage: In-memory (React state via hook) Testing: Vitest Target Platform: Browser (Vite dev server) Project Type: Monorepo (pnpm workspaces): domain library + application library + web app Performance Goals: N/A (pure synchronous function) Constraints: Domain must remain pure — no I/O, no randomness Scale/Scope: Single-user local app

Constitution Check

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

Principle Status Notes
I. Deterministic Domain Core PASS addCombatant is a pure function. CombatantId is passed in as input, not generated internally.
II. Layered Architecture PASS Domain function in packages/domain, use case in packages/application, hook in apps/web. No reverse imports.
III. Agent Boundary PASS No agent layer involvement in this feature.
IV. Clarification-First PASS Spec has no NEEDS CLARIFICATION markers. Key assumption (id passed in) is documented.
V. Escalation Gates PASS Implementation stays within spec scope.
VI. MVP Baseline Language PASS Out-of-scope items use "MVP baseline does not include".
VII. No Gameplay Rules PASS No gameplay mechanics in constitution.

All gates pass. No violations to justify.

Project Structure

Documentation (this feature)

specs/002-add-combatant/
├── 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/
├── types.ts             # Encounter, Combatant, CombatantId (existing)
├── events.ts            # DomainEvent union (add CombatantAdded)
├── add-combatant.ts     # NEW: addCombatant pure function
├── advance-turn.ts      # Existing (unchanged)
├── index.ts             # Re-exports (add new exports)
└── __tests__/
    ├── advance-turn.test.ts  # Existing (unchanged)
    └── add-combatant.test.ts # NEW: acceptance + invariant tests

packages/application/src/
├── ports.ts                      # EncounterStore (unchanged)
├── add-combatant-use-case.ts     # NEW: orchestrates addCombatant
├── advance-turn-use-case.ts      # Existing (unchanged)
└── index.ts                      # Re-exports (add new exports)

apps/web/src/
├── App.tsx              # Update: add combatant input + button
└── hooks/
    └── use-encounter.ts # Update: expose addCombatant callback

Structure Decision: Follows the established monorepo layout. Each domain operation gets its own file (matching advance-turn.ts pattern). No new packages or directories needed beyond the existing structure.