# 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 - [x] 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`. - [x] 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). - [x] 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 - [x] 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. - [x] 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 `
` -- 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 - [x] 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 - [x] T007 Run `pnpm check` to validate all automated checks pass (knip, format, lint, typecheck, test) - [x] 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 ```bash # 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.