# Implementation Plan: Remove Combatant **Branch**: `003-remove-combatant` | **Date**: 2026-03-03 | **Spec**: [spec.md](./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) ```text specs/003-remove-combatant/ ├── plan.md ├── research.md ├── data-model.md ├── quickstart.md └── tasks.md ``` ### Source Code (repository root) ```text 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.