# Implementation Plan: Add Combatant **Branch**: `002-add-combatant` | **Date**: 2026-03-03 | **Spec**: [spec.md](./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) ```text 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) ```text 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.