165 lines
8.7 KiB
Markdown
165 lines
8.7 KiB
Markdown
# 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
|
|
|
|
- [x] 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`
|
|
- [x] T002 Add `readonly conditions?: readonly ConditionId[]` field to `Combatant` interface in `packages/domain/src/types.ts`
|
|
- [x] T003 Add `ConditionAdded` and `ConditionRemoved` event interfaces and include them in the `DomainEvent` union in `packages/domain/src/events.ts`
|
|
- [x] 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
|
|
|
|
- [x] 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`
|
|
- [x] ~~T006~~ Removed — `toggleCondition` handles both add and remove; a separate `removeCondition` was unnecessary
|
|
- [x] 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
|
|
- [x] ~~T008~~ Removed — covered by toggle tests
|
|
- [x] T009 Re-export `toggleCondition` and its success type from `packages/domain/src/index.ts`
|
|
- [x] T010 [P] Implement `toggleConditionUseCase` in `packages/application/src/toggle-condition-use-case.ts` following `setAcUseCase` pattern
|
|
- [x] ~~T011~~ Removed — no separate `removeConditionUseCase` needed
|
|
- [x] 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
|
|
|
|
- [x] 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`
|
|
- [x] T014 Add `toggleCondition` callback to `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` following the `setAc` callback pattern
|
|
- [x] 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()`
|
|
- [x] 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
|
|
- [x] 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
|
|
|
|
- [x] 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
|
|
|
|
- [x] 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
|
|
|
|
- [x] T020 Run `pnpm check` (knip + format + lint + typecheck + test) and fix any issues
|
|
- [x] 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
|
|
|
|
```bash
|
|
# 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
|