7.9 KiB
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
- T001 Extend
Combatantinterface with optionalmaxHpandcurrentHpfields inpackages/domain/src/types.ts - T002 Add
MaxHpSetandCurrentHpAdjustedevent types toDomainEventunion inpackages/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
- T003 Implement
setHppure function inpackages/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 - T004 Write tests for
setHpinpackages/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) - T005 Implement
adjustHppure function inpackages/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 - T006 Write tests for
adjustHpinpackages/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) - T007 Re-export
setHpandadjustHpfrompackages/domain/src/index.ts - T008 [P] Create
setHpUseCaseinpackages/application/src/set-hp-use-case.tsfollowing get-call-save pattern viaEncounterStore - T009 [P] Create
adjustHpUseCaseinpackages/application/src/adjust-hp-use-case.tsfollowing get-call-save pattern viaEncounterStore - 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
- T011 [US1] [US2] Add
setHpandadjustHpcallbacks touseEncounterhook inapps/web/src/hooks/use-encounter.ts— follow existing pattern (call use case, check error, update events) - 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
- 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 calladjustHp. 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
- T014 [US4] Extend
loadEncounter()validation inapps/web/src/persistence/encounter-storage.ts— validate optionalmaxHp(positive integer) andcurrentHp(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
- T015 Run
pnpm check(knip + format + lint + typecheck + test) and fix any issues - 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
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)
- Complete Phase 1: Setup (types + events)
- Complete Phase 2: Foundational (domain functions + use cases)
- Complete Phase 3: US1 + US2 (set max HP + quick adjust)
- STOP and VALIDATE: Can set HP and use +/- controls
- Continue with US3 (direct entry) and US4 (persistence)
Incremental Delivery
- Phase 1 + 2 → Domain + application ready
- Phase 3 → MVP: max HP + quick adjust functional
- Phase 4 → Direct HP entry added
- Phase 5 → Persistence extended
- 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:
adjustHpaccepts any integer delta, so a future damage/heal dialog can call it directly