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
ConditionIdunion type,ConditionDefinitioninterface, andCONDITION_DEFINITIONSregistry array with all 15 conditions (id, label, iconName, color) plusVALID_CONDITION_IDSset inpackages/domain/src/conditions.ts - T002 Add
readonly conditions?: readonly ConditionId[]field toCombatantinterface inpackages/domain/src/types.ts - T003 Add
ConditionAddedandConditionRemovedevent interfaces and include them in theDomainEventunion inpackages/domain/src/events.ts - T004 Re-export new symbols (
ConditionId,ConditionDefinition,CONDITION_DEFINITIONS,VALID_CONDITION_IDS,ConditionAdded,ConditionRemoved) frompackages/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 inpackages/domain/src/toggle-condition.ts— adds condition if absent (emitsConditionAdded), removes if present (emitsConditionRemoved), maintains sorted definition order, normalizes empty array toundefined T006Removed —toggleConditionhandles both add and remove; a separateremoveConditionwas unnecessary- T007 [P] Write tests for
toggleConditioninpackages/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 T008Removed — covered by toggle tests- T009 Re-export
toggleConditionand its success type frompackages/domain/src/index.ts - T010 [P] Implement
toggleConditionUseCaseinpackages/application/src/toggle-condition-use-case.tsfollowingsetAcUseCasepattern T011Removed — no separateremoveConditionUseCaseneeded- 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
loadEncounterinapps/web/src/persistence/encounter-storage.ts— filter stored condition values againstVALID_CONDITION_IDS, normalize empty array toundefined - T014 Add
toggleConditioncallback touseEncounterhook inapps/web/src/hooks/use-encounter.tsfollowing thesetAccallback pattern - T015 Create
ConditionTagscomponent inapps/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 callsonRemove(conditionId); clicking "+" callsonOpenPicker() - T016 Create
ConditionPickercomponent inapps/web/src/components/condition-picker.tsx— popover/dropdown showing all 15 conditions as icon + label rows; active conditions visually distinguished; clicking toggles on/off viaonToggle(conditionId); closes on click-outside - T017 Integrate conditions into
CombatantRowinapps/web/src/components/combatant-row.tsx— addonToggleConditionprop; renderConditionTags+ConditionPickerbelow 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
titleattribute or tooltip to condition icon buttons inConditionTagscomponent inapps/web/src/components/condition-tags.tsx— displayconditionDefinition.labelon 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
ConditionTagscomponent inapps/web/src/components/condition-tags.tsx— ensureflex-wrapis 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)
- Complete Phase 1: Setup (types and registry)
- Complete Phase 2: Foundational (domain operations + use cases)
- Complete Phase 3: US1+US2 (full add/remove UI flow + persistence)
- STOP and VALIDATE: Add and remove conditions in the browser; reload page; verify persistence
- Deploy/demo if ready
Incremental Delivery
- Setup + Foundational → Domain layer complete
- US1+US2 → Full condition management flow (MVP!)
- US3 → Tooltips for discoverability
- US4 → Layout polish for heavy condition usage
- 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
titleattributes 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