Files
initiative/specs/013-hp-status-indicators/tasks.md

6.2 KiB

Tasks: HP Status Indicators

Input: Design documents from /specs/013-hp-status-indicators/ Prerequisites: plan.md, spec.md, research.md, data-model.md, quickstart.md

Tests: Included -- domain function requires unit tests per project convention.

Organization: Tasks grouped by user story. US1 and US2 share the same domain function (foundational), then diverge at the UI layer.

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: Foundational (Domain Function + Tests)

Purpose: Create the pure domain function that all user stories depend on

CRITICAL: No UI work can begin until this phase is complete

  • T001 Create HpStatus type and deriveHpStatus pure function in packages/domain/src/hp-status.ts -- returns "healthy" | "bloodied" | "unconscious" | undefined based on currentHp and maxHp inputs. Check currentHp <= 0 first (unconscious), then currentHp < maxHp / 2 (bloodied), else healthy. Return undefined when either input is undefined.
  • T002 Write unit tests for deriveHpStatus in packages/domain/src/__tests__/hp-status.test.ts covering: healthy (currentHp >= maxHp/2), bloodied (0 < currentHp < maxHp/2), unconscious (currentHp <= 0), unconscious with negative HP, undefined when maxHp undefined, undefined when currentHp undefined, undefined when both undefined, edge case maxHp=1 (no bloodied state possible), edge case maxHp=2 (1/2 is healthy not bloodied), odd maxHp=21 (10 is bloodied since 10 < 10.5), currentHp exceeding maxHp (healthy).
  • T003 Export deriveHpStatus and HpStatus type from packages/domain/src/index.ts -- add export { type HpStatus, deriveHpStatus } from "./hp-status.js";

Checkpoint: pnpm check passes. Domain function is tested and exported.


Phase 2: User Story 1 - Bloodied Indicator (Priority: P1) + User Story 2 - Unconscious/Dead Indicator (Priority: P1)

Goal: Apply visual status indicators to the combatant row -- amber HP text for bloodied, red HP text + muted row for unconscious/dead

Independent Test: Set a combatant's max HP, reduce current HP below half -- verify amber text. Reduce to 0 -- verify red text and muted row. Heal above half -- verify normal appearance restored.

Implementation

  • T004 [US1] [US2] Modify apps/web/src/components/combatant-row.tsx to import deriveHpStatus from @initiative/domain and call it with the combatant's currentHp and maxHp at the top of the CombatantRow component. Store result in a status variable.
  • T005 [US1] [US2] In apps/web/src/components/combatant-row.tsx, apply conditional CSS classes based on status: (1) On the CurrentHpInput wrapper/value -- add text-amber-400 when bloodied, text-red-400 when unconscious. (2) On the row's outer <div> -- add opacity-50 when unconscious. Ensure the active-turn border styling still takes precedence for active combatants. Use the existing cn() utility for conditional class merging.

Checkpoint: pnpm check passes. Bloodied and unconscious indicators are visible in the UI.


Phase 3: User Story 3 - Status Transitions (Priority: P2)

Goal: Ensure visual indicators update in real time as HP changes through quick HP input, direct entry, and +/- controls

Independent Test: Use the quick HP input to deal damage across thresholds (healthy -> bloodied -> unconscious) and heal back. Verify indicator changes instantly with no flicker or delay.

Implementation

  • T006 [US3] Verify in apps/web/src/components/combatant-row.tsx that deriveHpStatus is called with the current combatant props (not stale state) so status updates in the same render cycle as HP changes. No additional code should be needed if T004-T005 are implemented correctly -- this task is a manual verification and visual QA pass.

Checkpoint: All transitions (healthy->bloodied->unconscious->healthy) work smoothly.


Phase 4: Polish & Cross-Cutting Concerns

  • T007 Run pnpm check to validate all automated checks pass (knip, format, lint, typecheck, test)
  • T008 Verify layer boundary test still passes (domain must not import from React/UI) in packages/domain/src/__tests__/layer-boundaries.test.ts

Dependencies & Execution Order

Phase Dependencies

  • Foundational (Phase 1): No dependencies -- can start immediately
  • US1+US2 (Phase 2): Depends on Phase 1 completion (needs domain function)
  • US3 (Phase 3): Depends on Phase 2 completion (needs UI indicators in place)
  • Polish (Phase 4): Depends on all phases complete

Within Phases

  • T001 before T002 (need function to test it)
  • T002 before T003 (tests pass before exporting)
  • T004 before T005 (import before styling)

Parallel Opportunities

  • T001 and T002 can be done together if writing tests alongside implementation (TDD)
  • US1 and US2 are implemented together in Phase 2 since they modify the same file and component

Parallel Example: Phase 1

# T001 and T002 can be written together (TDD style):
Task: "Create deriveHpStatus in packages/domain/src/hp-status.ts"
Task: "Write tests in packages/domain/src/__tests__/hp-status.test.ts"

Implementation Strategy

MVP First (Phase 1 + Phase 2)

  1. Complete Phase 1: Domain function + tests + export
  2. Complete Phase 2: UI styling for both bloodied and unconscious
  3. STOP and VALIDATE: Manually test all scenarios from spec
  4. Run pnpm check

Full Delivery

  1. Phase 1: Domain function (T001-T003)
  2. Phase 2: UI indicators (T004-T005)
  3. Phase 3: Transition verification (T006)
  4. Phase 4: Final validation (T007-T008)

Notes

  • US1 (bloodied) and US2 (unconscious) are combined into Phase 2 because they modify the same component and the domain function handles both statuses. Implementing them separately would require touching the same lines twice.
  • T006 is a verification task, not a code change -- if the React component re-renders on prop changes (which it does by default), transitions work automatically.
  • Total: 8 tasks across 4 phases. Minimal scope -- no new application layer, no storage changes.