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.