157 lines
7.9 KiB
Markdown
157 lines
7.9 KiB
Markdown
# Tasks: Combatant HP Tracking
|
|
|
|
**Input**: Design documents from `/specs/009-combatant-hp/`
|
|
**Prerequisites**: plan.md, spec.md, research.md, data-model.md
|
|
|
|
**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 (Shared Infrastructure)
|
|
|
|
**Purpose**: Extend domain types and events shared by all user stories
|
|
|
|
- [x] T001 Extend `Combatant` interface with optional `maxHp` and `currentHp` fields in `packages/domain/src/types.ts`
|
|
- [x] T002 Add `MaxHpSet` and `CurrentHpAdjusted` event types to `DomainEvent` union in `packages/domain/src/events.ts`
|
|
|
|
---
|
|
|
|
## Phase 2: Foundational (Blocking Prerequisites)
|
|
|
|
**Purpose**: Domain pure functions that all user stories depend on
|
|
|
|
**CRITICAL**: No user story work can begin until this phase is complete
|
|
|
|
- [x] T003 Implement `setHp` pure function in `packages/domain/src/set-hp.ts` — accepts (Encounter, CombatantId, maxHp: number | undefined), returns {encounter, events} | DomainError. Handles: set new maxHp (currentHp defaults to maxHp), update maxHp (full-health sync: if currentHp === previousMaxHp then currentHp = newMaxHp; otherwise clamp currentHp), clear maxHp (clear both), validate positive integer, combatant-not-found error
|
|
- [x] T004 Write tests for `setHp` in `packages/domain/src/__tests__/set-hp.test.ts` — cover acceptance scenarios (set, update, full-health sync on increase, clamp on reduce, clear), invariants (pure, immutable, event shape), error cases (not found, invalid values), edge cases (maxHp=1, reduce below currentHp)
|
|
- [x] T005 Implement `adjustHp` pure function in `packages/domain/src/adjust-hp.ts` — accepts (Encounter, CombatantId, delta: number), returns {encounter, events} | DomainError. Clamps result to [0, maxHp]. Errors: combatant-not-found, no-hp-tracking, zero-delta, invalid-delta
|
|
- [x] T006 Write tests for `adjustHp` in `packages/domain/src/__tests__/adjust-hp.test.ts` — cover acceptance scenarios (+1, -1, clamp at 0, clamp at max), invariants (pure, immutable, event shape with delta), error cases, edge cases (large delta beyond bounds)
|
|
- [x] T007 Re-export `setHp` and `adjustHp` from `packages/domain/src/index.ts`
|
|
- [x] T008 [P] Create `setHpUseCase` in `packages/application/src/set-hp-use-case.ts` following get-call-save pattern via `EncounterStore`
|
|
- [x] T009 [P] Create `adjustHpUseCase` in `packages/application/src/adjust-hp-use-case.ts` following get-call-save pattern via `EncounterStore`
|
|
- [x] T010 Re-export new use cases from `packages/application/src/index.ts`
|
|
|
|
**Checkpoint**: Domain and application layers complete. `pnpm test` and `pnpm typecheck` pass.
|
|
|
|
---
|
|
|
|
## Phase 3: User Story 1 — Set Max HP for a Combatant (Priority: P1) + User Story 2 — Quick Adjust Current HP (Priority: P1) MVP
|
|
|
|
**Goal**: A game master can set max HP on a combatant and use +/- controls to adjust current HP during combat. These two P1 stories are combined because the UI naturally presents them together (max HP input + current HP with +/- buttons in one combatant row).
|
|
|
|
**Independent Test**: Add a combatant, set max HP, verify it displays. Press -/+ buttons, verify current HP changes within bounds. Reduce max HP below current HP, verify clamping.
|
|
|
|
### Implementation
|
|
|
|
- [x] T011 [US1] [US2] Add `setHp` and `adjustHp` callbacks to `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — follow existing pattern (call use case, check error, update events)
|
|
- [x] T012 [US1] [US2] Add HP controls to combatant rows in `apps/web/src/App.tsx` — both Current HP and Max HP inputs always visible. Current HP input disabled when maxHp is undefined. +/- buttons only shown when HP tracking is active. Max HP input uses local draft state and commits on blur/Enter only (not per-keystroke) to prevent premature clearing of currentHp. Max HP input allows clearing (returning combatant to no-HP state). Clamp visual state matches domain invariants.
|
|
|
|
**Checkpoint**: US1 + US2 fully functional. User can set max HP, see current HP, and use +/- buttons. `pnpm check` passes.
|
|
|
|
---
|
|
|
|
## Phase 4: User Story 3 — Direct HP Entry (Priority: P2)
|
|
|
|
**Goal**: A game master can type a specific current HP value directly instead of using +/- buttons.
|
|
|
|
**Independent Test**: Set max HP to 50, type 35 in current HP field, verify it updates. Type 60, verify clamped to 50. Type -5, verify clamped to 0.
|
|
|
|
### Implementation
|
|
|
|
- [x] T013 [US3] Make current HP display editable (click-to-edit or inline input) in `apps/web/src/App.tsx` — on confirm, compute delta from current value and call `adjustHp`. Apply clamping via domain function.
|
|
|
|
**Checkpoint**: US3 functional. Direct numeric entry works alongside +/- controls. `pnpm check` passes.
|
|
|
|
---
|
|
|
|
## Phase 5: User Story 4 — HP Persists Across Reloads (Priority: P2)
|
|
|
|
**Goal**: HP values survive page reloads via existing localStorage persistence.
|
|
|
|
**Independent Test**: Set max HP and adjust current HP, reload the page, verify both values are restored.
|
|
|
|
### Implementation
|
|
|
|
- [x] T014 [US4] Extend `loadEncounter()` validation in `apps/web/src/persistence/encounter-storage.ts` — validate optional `maxHp` (positive integer) and `currentHp` (integer in [0, maxHp]) on each combatant during deserialization. Strip invalid HP fields per-combatant rather than failing the entire encounter.
|
|
|
|
**Checkpoint**: US4 functional. HP values persist across reloads. `pnpm check` passes.
|
|
|
|
---
|
|
|
|
## Phase 6: Polish & Cross-Cutting Concerns
|
|
|
|
**Purpose**: Final validation and cleanup
|
|
|
|
- [x] T015 Run `pnpm check` (knip + format + lint + typecheck + test) and fix any issues
|
|
- [x] T016 Verify layer boundary test still passes in `packages/domain/src/__tests__/layer-boundaries.test.ts`
|
|
|
|
---
|
|
|
|
## Dependencies & Execution Order
|
|
|
|
### Phase Dependencies
|
|
|
|
- **Setup (Phase 1)**: No dependencies — start immediately
|
|
- **Foundational (Phase 2)**: Depends on Phase 1 completion — BLOCKS all user stories
|
|
- **US1+US2 (Phase 3)**: Depends on Phase 2 completion
|
|
- **US3 (Phase 4)**: Depends on Phase 3 (extends the HP UI from US1+US2)
|
|
- **US4 (Phase 5)**: Can start after Phase 2 (independent of UI work), but naturally follows Phase 3
|
|
- **Polish (Phase 6)**: Depends on all previous phases
|
|
|
|
### Within Each Phase
|
|
|
|
- T001 and T002 are parallel (different files)
|
|
- T003 → T004 (implement then test setHp)
|
|
- T005 → T006 (implement then test adjustHp)
|
|
- T003 and T005 are parallel (different files, no dependency)
|
|
- T008 and T009 are parallel (different files)
|
|
- T008/T009 depend on T007 (need exports)
|
|
- T011 → T012 (hook before UI)
|
|
- T013 depends on T012 (extends existing HP UI)
|
|
|
|
### Parallel Opportunities
|
|
|
|
```text
|
|
Parallel group A (Phase 1): T001 || T002
|
|
Parallel group B (Phase 2): T003+T004 || T005+T006 (then T007, then T008 || T009, then T010)
|
|
Sequential (Phase 3): T011 → T012
|
|
Sequential (Phase 4): T013
|
|
Independent (Phase 5): T014
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Strategy
|
|
|
|
### MVP First (US1 + US2)
|
|
|
|
1. Complete Phase 1: Setup (types + events)
|
|
2. Complete Phase 2: Foundational (domain functions + use cases)
|
|
3. Complete Phase 3: US1 + US2 (set max HP + quick adjust)
|
|
4. **STOP and VALIDATE**: Can set HP and use +/- controls
|
|
5. Continue with US3 (direct entry) and US4 (persistence)
|
|
|
|
### Incremental Delivery
|
|
|
|
1. Phase 1 + 2 → Domain + application ready
|
|
2. Phase 3 → MVP: max HP + quick adjust functional
|
|
3. Phase 4 → Direct HP entry added
|
|
4. Phase 5 → Persistence extended
|
|
5. Phase 6 → Quality gate passes, ready to merge
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
- [P] tasks = different files, no dependencies
|
|
- [Story] label maps task to specific user story for traceability
|
|
- Commit after each phase checkpoint
|
|
- US1 and US2 are combined in Phase 3 because they share UI surface (HP controls on combatant row)
|
|
- Domain functions are designed for extensibility: `adjustHp` accepts any integer delta, so a future damage/heal dialog can call it directly
|