Files
initiative/specs/003-remove-combatant/plan.md

3.1 KiB

Implementation Plan: Remove Combatant

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

Summary

Add a removeCombatant pure domain function that removes a combatant by ID from an Encounter, correctly adjusts activeIndex to preserve turn integrity, keeps roundNumber unchanged, and emits a CombatantRemoved event. Wire through an application-layer use case and expose via a minimal UI remove action per combatant.

Technical Context

Language/Version: TypeScript 5.x (strict mode, verbatimModuleSyntax) Primary Dependencies: React 19, Vite Storage: In-memory React state (local-first, single-user MVP) Testing: Vitest Target Platform: Web (localhost:5173 dev, production build via Vite) Project Type: Web application (monorepo: packages/domain, packages/application, apps/web) Performance Goals: N/A (local-first, small data sets) Constraints: Domain must be pure (no I/O); layer boundaries enforced by automated script Scale/Scope: Single-user, single encounter at a time

Constitution Check

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

Principle Status Evidence
I. Deterministic Domain Core PASS removeCombatant is a pure function: same input → same output, no I/O
II. Layered Architecture PASS Domain function → use case → React hook/UI. No layer violations.
III. Agent Boundary N/A No agent layer involved in this feature
IV. Clarification-First PASS Spec fully specifies all activeIndex adjustment rules; no ambiguity
V. Escalation Gates PASS All functionality is within spec scope
VI. MVP Baseline Language PASS No permanent bans introduced
VII. No Gameplay Rules PASS Removal is encounter management, not gameplay mechanics

Gate result: PASS — no violations.

Project Structure

Documentation (this feature)

specs/003-remove-combatant/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
└── tasks.md

Source Code (repository root)

packages/domain/src/
├── remove-combatant.ts          # Pure domain function
├── events.ts                    # Add CombatantRemoved to DomainEvent union
├── types.ts                     # Existing types (no changes expected)
├── index.ts                     # Re-export removeCombatant
└── __tests__/
    └── remove-combatant.test.ts # Acceptance scenarios from spec

packages/application/src/
├── remove-combatant-use-case.ts # Orchestrates store.get → domain → store.save
└── index.ts                     # Re-export use case

apps/web/src/
├── hooks/use-encounter.ts       # Add removeCombatant callback
└── App.tsx                      # Add remove button per combatant + event display

Structure Decision: Follows the existing monorepo layered architecture (packages/domain → packages/application → apps/web) exactly mirroring the addCombatant feature's file layout.