Implement the 018-combatant-concentration feature that adds a per-combatant concentration toggle with Brain icon, purple border accent, and damage pulse animation in the encounter tracker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-06 14:34:28 +01:00
parent febe892e15
commit e59fd83292
19 changed files with 779 additions and 7 deletions

View File

@@ -0,0 +1,48 @@
# Quickstart: Combatant Concentration
**Feature**: 018-combatant-concentration | **Date**: 2026-03-06
## Overview
This feature adds a per-combatant boolean `isConcentrating` state with a Brain icon toggle, a colored left border accent for visual identification, and a pulse animation when a concentrating combatant takes damage.
## Key Files to Modify
### Domain Layer
1. **`packages/domain/src/types.ts`** — Add `isConcentrating?: boolean` to `Combatant` interface.
2. **`packages/domain/src/events.ts`** — Add `ConcentrationStarted` and `ConcentrationEnded` event types to the `DomainEvent` union.
3. **`packages/domain/src/toggle-concentration.ts`** (new) — Pure function mirroring `toggle-condition.ts` pattern.
4. **`packages/domain/src/index.ts`** — Re-export new function and event types.
### Application Layer
5. **`packages/application/src/toggle-concentration-use-case.ts`** (new) — Thin orchestration following `toggle-condition-use-case.ts` pattern.
6. **`packages/application/src/index.ts`** — Re-export new use case.
### Web Adapter
7. **`apps/web/src/persistence/encounter-storage.ts`** — Add `isConcentrating` to combatant rehydration.
8. **`apps/web/src/hooks/use-encounter.ts`** — Add `toggleConcentration` callback.
9. **`apps/web/src/components/combatant-row.tsx`** — Add Brain icon toggle, left border accent, and pulse animation.
10. **`apps/web/src/App.tsx`** — Wire `onToggleConcentration` prop through to `CombatantRow`.
## Implementation Pattern
Follow the existing `toggleCondition` pattern end-to-end:
```
Domain: toggleConcentration(encounter, combatantId) → { encounter, events } | DomainError
App: toggleConcentrationUseCase(store, combatantId) → events | DomainError
Hook: toggleConcentration = useCallback((id) => { ... toggleConcentrationUseCase(makeStore(), id) ... })
Component: <Brain onClick={() => onToggleConcentration(id)} />
```
## Testing Strategy
- **Domain tests**: `toggle-concentration.test.ts` — toggle on/off, combatant not found, immutability, correct events emitted.
- **UI behavior**: Manual verification of hover show/hide, tooltip, left border accent, pulse animation on damage.
## Key Decisions
- Concentration is **not** a condition — it has its own boolean field and separate UI treatment.
- Pulse animation uses **CSS keyframes** triggered by transient React state, not a domain event.
- Damage detection for pulse uses **HP comparison** in the component (prevHp vs currentHp), not domain events.
- No localStorage migration needed — optional boolean field is backward-compatible.