Files
initiative/specs/017-combat-conditions/tasks.md

8.7 KiB

Tasks: Combat Conditions

Input: Design documents from /specs/017-combat-conditions/ Prerequisites: plan.md (required), spec.md (required), research.md, data-model.md, quickstart.md

Tests: Tests are included as this project follows a test-driven domain pattern (all domain operations have corresponding test files).

Organization: Tasks are grouped by user story to enable independent implementation and testing of each story.

Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependencies)
  • [Story]: Which user story this task belongs to (e.g., US1, US2, US3)
  • Include exact file paths in descriptions

Phase 1: Setup

Purpose: Define condition types, registry, and extend the domain model

  • T001 Define ConditionId union type, ConditionDefinition interface, and CONDITION_DEFINITIONS registry array with all 15 conditions (id, label, iconName, color) plus VALID_CONDITION_IDS set in packages/domain/src/conditions.ts
  • T002 Add readonly conditions?: readonly ConditionId[] field to Combatant interface in packages/domain/src/types.ts
  • T003 Add ConditionAdded and ConditionRemoved event interfaces and include them in the DomainEvent union in packages/domain/src/events.ts
  • T004 Re-export new symbols (ConditionId, ConditionDefinition, CONDITION_DEFINITIONS, VALID_CONDITION_IDS, ConditionAdded, ConditionRemoved) from packages/domain/src/index.ts

Checkpoint: Domain types compile; pnpm typecheck passes


Phase 2: Foundational (Domain Operations)

Purpose: Implement core domain operations that all user stories depend on

  • T005 [P] Implement toggleCondition(encounter, combatantId, conditionId) pure function in packages/domain/src/toggle-condition.ts — adds condition if absent (emits ConditionAdded), removes if present (emits ConditionRemoved), maintains sorted definition order, normalizes empty array to undefined
  • T006 Removed — toggleCondition handles both add and remove; a separate removeCondition was unnecessary
  • T007 [P] Write tests for toggleCondition in packages/domain/src/__tests__/toggle-condition.test.ts — toggle on, toggle off, ordering preserved, duplicate prevention, unknown condition rejected, combatant-not-found error, immutability, empty-to-undefined normalization
  • T008 Removed — covered by toggle tests
  • T009 Re-export toggleCondition and its success type from packages/domain/src/index.ts
  • T010 [P] Implement toggleConditionUseCase in packages/application/src/toggle-condition-use-case.ts following setAcUseCase pattern
  • T011 Removed — no separate removeConditionUseCase needed
  • T012 Re-export new use case from packages/application/src/index.ts

Checkpoint: pnpm check passes; domain operations fully tested


Phase 3: User Story 1 — Add a Condition (Priority: P1) + User Story 2 — Remove a Condition (Priority: P1) 🎯 MVP

Goal: DM can add conditions via picker and remove them by clicking icon tags in the combat row

Independent Test: Click "+" on a combatant, select a condition in the picker, verify icon tag appears. Click the icon tag, verify it disappears. Reload page, verify conditions persist.

Implementation

  • T013 Add conditions rehydration validation to loadEncounter in apps/web/src/persistence/encounter-storage.ts — filter stored condition values against VALID_CONDITION_IDS, normalize empty array to undefined
  • T014 Add toggleCondition callback to useEncounter hook in apps/web/src/hooks/use-encounter.ts following the setAc callback pattern
  • T015 Create ConditionTags component in apps/web/src/components/condition-tags.tsx — renders active condition icon tags (Lucide icons with color classes) in fixed definition order, plus always-visible "+" button; clicking a tag calls onRemove(conditionId); clicking "+" calls onOpenPicker()
  • T016 Create ConditionPicker component in apps/web/src/components/condition-picker.tsx — popover/dropdown showing all 15 conditions as icon + label rows; active conditions visually distinguished; clicking toggles on/off via onToggle(conditionId); closes on click-outside
  • T017 Integrate conditions into CombatantRow in apps/web/src/components/combatant-row.tsx — add onToggleCondition prop; render ConditionTags + ConditionPicker below the combatant name; wire picker open/close state; both tag clicks and picker use toggle

Checkpoint: Full add/remove flow works; conditions persist across reload; pnpm check passes


Phase 4: User Story 3 — Tooltip on Hover (Priority: P2)

Goal: Hovering over a condition icon tag shows the condition name in a tooltip

Independent Test: Hover over any active condition icon tag, verify tooltip appears with condition name; move cursor away, verify tooltip disappears.

Implementation

  • T018 [US3] Add title attribute or tooltip to condition icon buttons in ConditionTags component in apps/web/src/components/condition-tags.tsx — display conditionDefinition.label on hover

Checkpoint: Tooltips visible on hover for all condition icons


Phase 5: User Story 4 — Multiple Conditions (Priority: P2)

Goal: Multiple conditions on one combatant display compactly with correct ordering

Independent Test: Apply 3+ conditions to a combatant in non-alphabetical order, verify they display in fixed definition order; verify row wraps without widening the tracker.

Implementation

  • T019 [US4] Verify wrapping behavior in ConditionTags component in apps/web/src/components/condition-tags.tsx — ensure flex-wrap is applied so multiple conditions wrap within the row width; verify layout does not increase tracker width

Checkpoint: Multiple conditions wrap correctly; fixed order maintained regardless of application order


Phase 6: Polish & Cross-Cutting Concerns

Purpose: Final validation and cleanup

  • T020 Run pnpm check (knip + format + lint + typecheck + test) and fix any issues
  • T021 Verify layer boundary compliance — domain does not import React/Lucide components; icon name strings only in domain

Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies — T001 → T002 → T003 → T004 (sequential, same files)
  • Foundational (Phase 2): Depends on Phase 1 — T005/T007 parallel, then T009, then T010, then T012
  • US1+US2 (Phase 3): Depends on Phase 2 — T013 → T014 → T015/T016 parallel → T017
  • US3 (Phase 4): Depends on Phase 3 (T015 must exist) — T018 standalone
  • US4 (Phase 5): Depends on Phase 3 (T015 must exist) — T019 standalone
  • Polish (Phase 6): Depends on all previous phases — T020 → T021

User Story Dependencies

  • US1+US2 (P1): Merged into one phase — add and remove are two sides of the same interaction; both require the picker and tags components
  • US3 (P2): Depends on US1+US2 (needs condition tags to exist); can run parallel with US4
  • US4 (P2): Depends on US1+US2 (needs condition tags to exist); can run parallel with US3

Parallel Opportunities

  • T005, T007 can run in parallel (different files)
  • T015, T016 can run in parallel (different files)
  • T018, T019 can run in parallel (different stories, different concerns)

Parallel Example: Foundational Phase

# Launch domain operation and tests together:
Task: "Implement toggleCondition in packages/domain/src/toggle-condition.ts"
Task: "Write toggle-condition tests in packages/domain/src/__tests__/toggle-condition.test.ts"

Implementation Strategy

MVP First (US1 + US2)

  1. Complete Phase 1: Setup (types and registry)
  2. Complete Phase 2: Foundational (domain operations + use cases)
  3. Complete Phase 3: US1+US2 (full add/remove UI flow + persistence)
  4. STOP and VALIDATE: Add and remove conditions in the browser; reload page; verify persistence
  5. Deploy/demo if ready

Incremental Delivery

  1. Setup + Foundational → Domain layer complete
  2. US1+US2 → Full condition management flow (MVP!)
  3. US3 → Tooltips for discoverability
  4. US4 → Layout polish for heavy condition usage
  5. Each increment adds value without breaking previous work

Notes

  • US1 and US2 are merged into Phase 3 because they share the same components (ConditionTags, ConditionPicker) and cannot be meaningfully separated
  • US3 (tooltip) is a single-task phase — just adding title attributes to existing icon buttons
  • US4 (wrapping) is a single-task phase — verifying and ensuring CSS flex-wrap behavior
  • The Lucide icon → component mapping lives in the web layer only; domain uses string icon names
  • Commit after each task or logical group