Files
initiative/specs/018-combatant-concentration/quickstart.md

2.5 KiB

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

  1. packages/application/src/toggle-concentration-use-case.ts (new) — Thin orchestration following toggle-condition-use-case.ts pattern.
  2. packages/application/src/index.ts — Re-export new use case.

Web Adapter

  1. apps/web/src/persistence/encounter-storage.ts — Add isConcentrating to combatant rehydration.
  2. apps/web/src/hooks/use-encounter.ts — Add toggleConcentration callback.
  3. apps/web/src/components/combatant-row.tsx — Add Brain icon toggle, left border accent, and pulse animation.
  4. 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.