Implement the 014-inline-hp-delta feature that replaces the damage/heal mode toggle with explicit action buttons and Enter-to-damage keyboard shortcut

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-05 23:52:03 +01:00
parent 97d3918cef
commit 56bced8481
8 changed files with 521 additions and 49 deletions

View File

@@ -5,8 +5,6 @@ import { cn } from "../lib/utils";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
type Mode = "damage" | "heal";
interface QuickHpInputProps { interface QuickHpInputProps {
readonly combatantId: CombatantId; readonly combatantId: CombatantId;
readonly disabled?: boolean; readonly disabled?: boolean;
@@ -18,75 +16,46 @@ export function QuickHpInput({
disabled, disabled,
onAdjustHp, onAdjustHp,
}: QuickHpInputProps) { }: QuickHpInputProps) {
const [mode, setMode] = useState<Mode>("damage");
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const apply = useCallback(() => { const parsedValue =
if (inputValue === "") return; inputValue === "" ? null : Number.parseInt(inputValue, 10);
const n = Number.parseInt(inputValue, 10); const isValid =
if (Number.isNaN(n) || n <= 0) return; parsedValue !== null && !Number.isNaN(parsedValue) && parsedValue > 0;
const delta = mode === "damage" ? -n : n;
onAdjustHp(combatantId, delta);
setInputValue("");
}, [inputValue, mode, combatantId, onAdjustHp]);
const toggleMode = useCallback(() => { const applyDelta = useCallback(
setMode((m) => (m === "damage" ? "heal" : "damage")); (sign: -1 | 1) => {
}, []); if (inputValue === "") return;
const n = Number.parseInt(inputValue, 10);
if (Number.isNaN(n) || n <= 0) return;
onAdjustHp(combatantId, sign * n);
setInputValue("");
},
[inputValue, combatantId, onAdjustHp],
);
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => { (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") { if (e.key === "Enter") {
apply(); applyDelta(-1);
} else if (e.key === "Escape") { } else if (e.key === "Escape") {
setInputValue(""); setInputValue("");
} else if (e.key === "Tab") {
e.preventDefault();
toggleMode();
} }
}, },
[apply, toggleMode], [applyDelta],
); );
const isDamage = mode === "damage";
return ( return (
<div className="flex items-center gap-0.5"> <div className="flex items-center gap-0.5">
<Button
type="button"
variant="ghost"
size="icon"
disabled={disabled}
className={cn(
"h-7 w-7 shrink-0",
isDamage
? "text-red-400 hover:bg-red-950 hover:text-red-300"
: "text-emerald-400 hover:bg-emerald-950 hover:text-emerald-300",
)}
onClick={toggleMode}
title={
isDamage
? "Damage mode (click to switch to heal)"
: "Heal mode (click to switch to damage)"
}
aria-label={isDamage ? "Switch to heal mode" : "Switch to damage mode"}
>
{isDamage ? <Sword size={14} /> : <Heart size={14} />}
</Button>
<Input <Input
ref={inputRef} ref={inputRef}
type="text" type="text"
inputMode="numeric" inputMode="numeric"
disabled={disabled} disabled={disabled}
value={inputValue} value={inputValue}
placeholder={isDamage ? "Dmg" : "Heal"} placeholder="±HP"
className={cn( className="h-7 w-[7ch] text-center text-sm tabular-nums"
"h-7 w-[7ch] text-center text-sm tabular-nums",
isDamage
? "focus-visible:ring-red-500/50"
: "focus-visible:ring-emerald-500/50",
)}
onChange={(e) => { onChange={(e) => {
const v = e.target.value; const v = e.target.value;
if (v === "" || /^\d+$/.test(v)) { if (v === "" || /^\d+$/.test(v)) {
@@ -95,6 +64,36 @@ export function QuickHpInput({
}} }}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
/> />
<Button
type="button"
variant="ghost"
size="icon"
disabled={disabled || !isValid}
className={cn(
"h-7 w-7 shrink-0",
"text-red-400 hover:bg-red-950 hover:text-red-300",
)}
onClick={() => applyDelta(-1)}
title="Apply damage"
aria-label="Apply damage"
>
<Sword size={14} />
</Button>
<Button
type="button"
variant="ghost"
size="icon"
disabled={disabled || !isValid}
className={cn(
"h-7 w-7 shrink-0",
"text-emerald-400 hover:bg-emerald-950 hover:text-emerald-300",
)}
onClick={() => applyDelta(1)}
title="Apply healing"
aria-label="Apply healing"
>
<Heart size={14} />
</Button>
</div> </div>
); );
} }

View File

@@ -0,0 +1,34 @@
# Specification Quality Checklist: Inline HP Delta Input
**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-05
**Feature**: [spec.md](../spec.md)
## Content Quality
- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed
## Requirement Completeness
- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified
## Feature Readiness
- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification
## Notes
- All items pass. Spec is ready for `/speckit.clarify` or `/speckit.plan`.

View File

@@ -0,0 +1,42 @@
# Data Model: Inline HP Delta Input
## No data model changes
This feature does not modify any domain entities, events, or state transitions. The existing data model fully supports the new UI:
### Existing Entities (unchanged)
**Combatant**
- `id`: CombatantId (branded string)
- `name`: string
- `initiative?`: number
- `maxHp?`: number
- `currentHp?`: number
**Encounter**
- `combatants`: readonly Combatant[]
- `activeIndex`: number
- `roundNumber`: number
### Existing Domain Function (unchanged)
**adjustHp(encounter, combatantId, delta) -> AdjustHpSuccess | DomainError**
- Negative delta = damage, positive delta = heal
- Clamps result to [0, maxHp]
- Emits `CurrentHpAdjusted` event
### Existing Domain Event (unchanged)
**CurrentHpAdjusted**
- `combatantId`: CombatantId
- `previousHp`: number
- `newHp`: number
- `delta`: number
### UI State Changes
The only state change is in the QuickHpInput component:
- **Removed**: `mode: "damage" | "heal"` state variable
- **Kept**: `value: string` state variable for the input field
The action (damage vs heal) is now determined at invocation time by which button/key triggered it, not by a stored mode.

View File

@@ -0,0 +1,70 @@
# Implementation Plan: Inline HP Delta Input
**Branch**: `014-inline-hp-delta` | **Date**: 2026-03-05 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `/specs/014-inline-hp-delta/spec.md`
## Summary
Replace the existing QuickHpInput component (which uses a damage/heal mode toggle) with a simpler inline HP delta input. Users type a number and press Enter to apply damage by default, or click explicit damage/heal buttons. No domain changes needed -- the existing `adjustHp` pure function already accepts positive and negative deltas. This is a UI-only refactor of the quick HP input component.
## Technical Context
**Language/Version**: TypeScript 5.8 (strict mode, verbatimModuleSyntax)
**Primary Dependencies**: React 19, Vite 6, Tailwind CSS v4, shadcn/ui-style components, Lucide React (icons)
**Storage**: N/A (no storage changes -- existing localStorage persistence unchanged)
**Testing**: Vitest
**Target Platform**: Web browser (local-first, single-user)
**Project Type**: Web application (monorepo: domain + application + web adapter)
**Performance Goals**: Instant feedback on HP adjustment (<100ms perceived)
**Constraints**: Local-first, single-user MVP
**Scale/Scope**: Single encounter screen, ~1-20 combatants
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle | Status | Notes |
|-----------|--------|-------|
| I. Deterministic Domain Core | PASS | No domain changes. Existing `adjustHp` is a pure function. |
| II. Layered Architecture | PASS | Change is entirely in the adapter layer (web UI). Domain and application layers unchanged. |
| III. Agent Boundary | N/A | No agent features involved. |
| IV. Clarification-First | PASS | Spec is clear; no ambiguities remain. |
| V. Escalation Gates | PASS | Feature is within spec scope -- replacing existing UI with simplified version. |
| VI. MVP Baseline Language | PASS | Assumptions use "MVP baseline does not include" language. |
| VII. No Gameplay Rules | PASS | No gameplay mechanics in this feature. |
**Gate result**: PASS -- all principles satisfied.
## Project Structure
### Documentation (this feature)
```text
specs/014-inline-hp-delta/
├── plan.md # This file
├── research.md # Phase 0 output
├── data-model.md # Phase 1 output
├── quickstart.md # Phase 1 output
└── tasks.md # Phase 2 output (/speckit.tasks command)
```
### Source Code (repository root)
```text
packages/domain/src/
├── adjust-hp.ts # Existing — no changes needed
├── types.ts # Existing — no changes needed
└── __tests__/adjust-hp.test.ts # Existing — no changes needed
packages/application/src/
└── adjust-hp-use-case.ts # Existing — no changes needed
apps/web/src/
├── components/
│ ├── quick-hp-input.tsx # MODIFY — replace mode toggle with damage/heal buttons
│ └── combatant-row.tsx # Existing — integration point (no changes expected)
└── hooks/
└── use-encounter.ts # Existing — no changes needed
```
**Structure Decision**: Existing monorepo structure (domain → application → web adapter) is used. This feature only modifies the web adapter layer, specifically the `quick-hp-input.tsx` component. No new files or packages needed.

View File

@@ -0,0 +1,43 @@
# Quickstart: Inline HP Delta Input
## What changes
One file: `apps/web/src/components/quick-hp-input.tsx`
The QuickHpInput component is refactored from a mode-toggle design to an explicit-button design:
**Before** (011-quick-hp-input):
- Mode toggle button (Sword/Heart icon) + numeric input
- User switches mode, then types number, then presses Enter
- Mode state determines whether delta is positive or negative
**After** (014-inline-hp-delta):
- Numeric input + damage button (Sword) + heal button (Heart)
- Enter key always applies damage (negative delta)
- Damage button applies damage (negative delta)
- Heal button applies healing (positive delta)
- No mode state
## How to implement
1. Remove the `mode` state variable and the toggle button
2. Replace with two action buttons (damage + heal) alongside the input
3. Enter key handler calls `onAdjustHp(combatantId, -n)` (always damage)
4. Damage button calls `onAdjustHp(combatantId, -n)`
5. Heal button calls `onAdjustHp(combatantId, +n)`
6. Remove Tab key override (restore default browser behavior)
7. Clear input after any successful apply
## How to verify
```bash
pnpm check # Must pass (knip + format + lint + typecheck + test)
```
Manual verification:
1. Set a combatant's max HP to 20 (currentHp initializes to 20)
2. Type 7 in the delta input, press Enter → HP should show 13
3. Type 5 in the delta input, click heal button → HP should show 18
4. Type 3 in the delta input, click damage button → HP should show 15
5. Verify input clears after each action
6. Verify no mode toggle exists in the UI

View File

@@ -0,0 +1,37 @@
# Research: Inline HP Delta Input
## R-001: Can the existing domain function support the new UI?
**Decision**: Yes -- the existing `adjustHp(encounter, combatantId, delta)` function already accepts any integer delta. Negative deltas deal damage, positive deltas heal. No domain changes required.
**Rationale**: The domain function is already delta-based and mode-agnostic. The current QuickHpInput component translates its damage/heal mode into a signed delta before calling `onAdjustHp`. The new UI simply changes how the sign is determined (Enter = negative, heal button = positive, damage button = negative).
**Alternatives considered**: None -- the domain API is already ideal for this use case.
## R-002: UI pattern for inline delta with explicit action buttons
**Decision**: Replace the mode toggle button + input with a single numeric input flanked by two small icon buttons (damage on left, heal on right). Enter key applies damage by default.
**Rationale**: This eliminates the mode toggle state entirely. Users always see both options. The most common action (damage) is the keyboard default. Less common action (heal) requires a button click -- still only 2-3 interactions.
**Alternatives considered**:
- Separate damage and heal inputs per combatant: rejected as too much UI clutter for the compact row layout.
- Negative number = damage, positive = heal (user types sign): rejected as slower (extra keystroke) and error-prone.
## R-003: What happens to the Tab key behavior?
**Decision**: Remove the Tab key override. Tab returns to its default browser behavior (move focus to next element).
**Rationale**: The Tab key was previously overridden to toggle between damage/heal modes. Since modes no longer exist, Tab should not be intercepted. This improves accessibility and standard keyboard navigation.
**Alternatives considered**: None -- restoring default Tab behavior is the obvious choice.
## R-004: Button icons and visual distinction
**Decision**: Use Sword icon (red) for damage button and Heart icon (green) for heal button, consistent with the existing color scheme from the mode toggle.
**Rationale**: These icons are already imported (Lucide React) and used in the current mode toggle. Users familiar with the current UI will recognize them. Red = damage, green = heal is a universal convention.
**Alternatives considered**:
- Minus/Plus icons: too generic, less evocative of the D&D context.
- Text labels ("Dmg"/"Heal"): take more space than icons in the compact row.

View File

@@ -0,0 +1,104 @@
# Feature Specification: Inline HP Delta Input
**Feature Branch**: `014-inline-hp-delta`
**Created**: 2026-03-05
**Status**: Draft
**Input**: User description: "change the quick hp input to be an inline HP delta input per combatant. Users type a number and press Enter to apply damage by default, or click small damage/heal buttons to apply the value explicitly. Clear the input after applying. No toggle for damage and heal anymore."
## User Scenarios & Testing
### User Story 1 - Apply Damage via Enter Key (Priority: P1)
As a game master in the heat of combat, I want to type a damage number and press Enter to immediately subtract it from a combatant's HP, so that damage application is as fast as possible with minimal interaction.
**Why this priority**: Damage is the most frequent HP change during combat. Enter-to-damage is the fastest possible workflow for the most common action.
**Independent Test**: Can be fully tested by having a combatant with HP, typing a number, pressing Enter, and verifying HP decreases.
**Acceptance Scenarios**:
1. **Given** a combatant has 20/20 HP, **When** the user types 7 into the delta input and presses Enter, **Then** current HP decreases to 13.
2. **Given** a combatant has 10/20 HP, **When** the user types 15 and presses Enter, **Then** current HP is clamped to 0 (not negative).
3. **Given** a combatant has 20/20 HP, **When** the user types a number and presses Enter, **Then** the input field clears and is ready for the next entry.
4. **Given** a combatant has 5/20 HP, **When** the user types 0 and presses Enter, **Then** the input is rejected and HP remains unchanged.
---
### User Story 2 - Apply Damage or Heal via Explicit Buttons (Priority: P1)
As a game master, I want small damage and heal buttons next to the input so that I can explicitly choose whether to apply the entered value as damage or healing.
**Why this priority**: While Enter defaults to damage, users still need a way to heal. Explicit buttons remove ambiguity and eliminate the need for a mode toggle.
**Independent Test**: Can be fully tested by typing a number and clicking the heal button, verifying HP increases.
**Acceptance Scenarios**:
1. **Given** a combatant has 10/20 HP and the user has typed 5, **When** the user clicks the heal button, **Then** current HP increases to 15 and the input clears.
2. **Given** a combatant has 18/20 HP and the user has typed 10, **When** the user clicks the heal button, **Then** current HP is clamped to 20 (max HP) and the input clears.
3. **Given** a combatant has 20/20 HP and the user has typed 7, **When** the user clicks the damage button, **Then** current HP decreases to 13 and the input clears.
4. **Given** a combatant has 0/20 HP and the user has typed 8, **When** the user clicks the heal button, **Then** current HP increases to 8 and the input clears.
---
### User Story 3 - Keyboard-Driven Workflow (Priority: P2)
As a game master, I want to use only the keyboard to apply damage quickly so that I can keep up with fast-paced combat without reaching for the mouse.
**Why this priority**: Speed is critical during combat encounters. The keyboard workflow should cover the most common action (damage) end-to-end.
**Independent Test**: Can be fully tested by using only keyboard interactions to apply damage values.
**Acceptance Scenarios**:
1. **Given** the delta input is focused, **When** the user types a number and presses Enter, **Then** damage is applied immediately and the input clears.
2. **Given** the delta input is focused, **When** the user presses Escape, **Then** the input value is cleared without applying any change.
---
### Edge Cases
- What happens when the user enters a non-numeric value? The input only accepts digits; non-numeric characters are rejected.
- What happens when the user enters a very large number (e.g., 99999)? The value is applied normally; clamping to 0 or max HP ensures no invalid state.
- What happens when the combatant has no HP tracking (no max HP set)? The delta input is not shown for that combatant.
- What happens when the user submits an empty input? No change is applied; the input remains ready for the next entry.
- What happens when the user clicks a button with an empty input? No change is applied.
- What happens when the user rapidly enters multiple values? Each entry is applied sequentially; no values are lost or merged.
## Requirements
### Functional Requirements
- **FR-001**: The system MUST provide an inline numeric input per combatant for entering HP delta amounts.
- **FR-002**: When the user types a number and presses Enter, the system MUST apply the value as damage (subtract from current HP), clamping to 0.
- **FR-003**: The system MUST provide a small damage button that applies the entered value as damage (subtract from current HP), clamping to 0.
- **FR-004**: The system MUST provide a small heal button that applies the entered value as healing (add to current HP), clamping to max HP.
- **FR-005**: After any value is applied (via Enter or button click), the input MUST clear automatically.
- **FR-006**: The input MUST only accept positive integers. Zero, negative numbers, and non-numeric input MUST be rejected.
- **FR-007**: Pressing Escape while the input is focused MUST clear the input value without applying any change.
- **FR-008**: The delta input MUST only be available for combatants that have HP tracking active (max HP is set).
- **FR-009**: There MUST NOT be a toggle between damage and heal modes. The input is mode-free; the action is determined by which button is clicked or by pressing Enter (which defaults to damage).
- **FR-010**: Direct editing of the current HP absolute value MUST remain available alongside the delta input.
- **FR-011**: The damage and heal buttons MUST be visually distinct from each other (e.g., different colors or icons).
### Key Entities
- **HP Delta**: A numeric value entered by the user, applied as either damage or healing to a combatant's current HP.
## Success Criteria
### Measurable Outcomes
- **SC-001**: A user can apply damage to a combatant in 2 interactions or fewer (type number + press Enter).
- **SC-002**: A user can apply healing to a combatant in 3 interactions or fewer (type number + click heal button).
- **SC-003**: Current HP never exceeds max HP or drops below 0 after any delta input operation.
- **SC-004**: No mode toggle exists in the interface -- damage and heal are always available as distinct actions.
## Assumptions
- This feature replaces the existing quick HP input (011-quick-hp-input) which used a damage/heal mode toggle. The toggle is removed in favor of explicit actions.
- Enter key defaults to damage because damage is the most frequent HP change during combat.
- Direct HP entry (absolute value) remains available for overrides and corrections.
- There is no undo/redo for HP changes in the MVP baseline.
- There is no damage type tracking (fire, slashing, etc.) in the MVP baseline.
- There is no hit log or damage history in the MVP baseline.

View File

@@ -0,0 +1,143 @@
# Tasks: Inline HP Delta Input
**Input**: Design documents from `/specs/014-inline-hp-delta/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md
**Tests**: Tests are included because domain tests already exist and the spec has testable acceptance scenarios.
**Organization**: Tasks are grouped by user story. US1 and US2 share the same component refactor but have distinct acceptance criteria.
## 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**: No new project setup needed. This feature modifies a single existing component.
- [x] T001 Verify existing `pnpm check` passes before starting work (run from repo root)
---
## Phase 2: Foundational
**Purpose**: No foundational changes needed. The existing `adjustHp` domain function, `adjustHpUseCase`, and `useEncounter` hook all support the new UI without modification.
**Checkpoint**: No blocking prerequisites -- proceed directly to user story implementation.
---
## Phase 3: User Story 1 - Apply Damage via Enter Key (Priority: P1) MVP
**Goal**: Users type a number and press Enter to apply damage. Input clears after applying.
**Independent Test**: Set a combatant to 20/20 HP, type 7, press Enter, verify HP shows 13 and input is cleared.
### Tests for User Story 1
- [x] T002 [US1] Write acceptance tests for Enter-to-damage behavior in `packages/domain/src/__tests__/adjust-hp.test.ts` -- verify existing domain tests cover: damage subtraction, clamp to 0, zero rejection. Add any missing scenarios from spec (US1 scenarios 1-4).
### Implementation for User Story 1
- [x] T003 [US1] Refactor `apps/web/src/components/quick-hp-input.tsx`: remove `Mode` type, remove `mode` state variable, remove mode toggle button, remove Tab key override for mode switching. Keep the numeric input and the `value` state variable.
- [x] T004 [US1] Update Enter key handler in `apps/web/src/components/quick-hp-input.tsx`: when user presses Enter with a valid positive integer, call `onAdjustHp(combatantId, -n)` (always damage), then clear input. Reject zero, empty, and non-numeric values.
**Checkpoint**: At this point, Enter-to-damage works. No heal capability yet (added in US2). (Escape key verification merged into T009.)
---
## Phase 4: User Story 2 - Apply Damage or Heal via Explicit Buttons (Priority: P1)
**Goal**: Small damage and heal buttons next to the input allow explicit action choice.
**Independent Test**: Set a combatant to 10/20 HP, type 5, click heal button, verify HP shows 15 and input clears.
### Implementation for User Story 2
- [x] T006 [US2] Add damage button (Sword icon, red styling) to `apps/web/src/components/quick-hp-input.tsx` that reads the current input value and calls `onAdjustHp(combatantId, -n)`, then clears input. Button is disabled when input is empty or invalid.
- [x] T007 [US2] Add heal button (Heart icon, green styling) to `apps/web/src/components/quick-hp-input.tsx` that reads the current input value and calls `onAdjustHp(combatantId, +n)`, then clears input. Button is disabled when input is empty or invalid.
- [x] T008 [US2] Ensure damage and heal buttons are visually distinct (red vs green, Sword vs Heart icons) and match existing Tailwind/shadcn styling conventions in `apps/web/src/components/quick-hp-input.tsx`.
**Checkpoint**: Both Enter-to-damage and explicit damage/heal buttons work. Full feature is functional.
---
## Phase 5: User Story 3 - Keyboard-Driven Workflow (Priority: P2)
**Goal**: Keyboard-only workflow for damage is seamless (type + Enter). Escape clears without applying.
**Independent Test**: Focus input, type number, press Enter -- damage applied. Focus input, type number, press Escape -- input cleared, no HP change.
### Implementation for User Story 3
- [x] T009 [US3] Verify that the refactored component in `apps/web/src/components/quick-hp-input.tsx` supports full keyboard workflow: focus input, type digits, Enter applies damage, Escape clears. Confirm Tab key is NOT overridden (default browser focus behavior). Also verify FR-008: delta input is not rendered for combatants without HP tracking (no max HP set). Fix any issues found.
**Checkpoint**: All user stories functional and keyboard workflow verified.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Final validation across all stories.
- [x] T010 Run `pnpm check` (knip + format + lint + typecheck + test) and fix any issues
- [ ] T011 Run quickstart.md manual verification steps against the dev server (`pnpm --filter web dev`). Also verify FR-010: direct editing of the current HP absolute value still works alongside the delta input.
---
## Dependencies & Execution Order
### Phase Dependencies
- **Setup (Phase 1)**: No dependencies
- **Foundational (Phase 2)**: N/A (no foundational tasks)
- **US1 (Phase 3)**: Depends on Phase 1 passing
- **US2 (Phase 4)**: Depends on T003 (component refactor from US1)
- **US3 (Phase 5)**: Depends on T004 (Enter key handler from US1)
- **Polish (Phase 6)**: Depends on all user stories complete
### User Story Dependencies
- **User Story 1 (P1)**: No dependencies on other stories. Core refactor.
- **User Story 2 (P1)**: Depends on US1 component refactor (T003) since buttons are added to the refactored component.
- **User Story 3 (P2)**: Depends on US1 (T004) since it validates the keyboard workflow established there.
### Within Each User Story
- Tests/verification before or alongside implementation
- Core logic before visual polish
- Commit after each task or logical group
### Parallel Opportunities
- T006 and T007 (damage button and heal button) can be implemented in parallel since they are independent click handlers in the same file -- but given they're both small additions to one component, sequential is equally efficient.
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. T001: Verify clean state
2. T002: Verify/add domain tests
3. T003-T005: Refactor component, Enter-to-damage, verify Escape
4. **STOP and VALIDATE**: Type number + Enter applies damage, input clears
### Incremental Delivery
1. US1: Enter-to-damage works (MVP)
2. US2: Add damage/heal buttons (full feature)
3. US3: Verify keyboard workflow (polish)
4. T010-T011: Final quality gate
---
## Notes
- This is a single-component refactor. All implementation tasks modify `apps/web/src/components/quick-hp-input.tsx`.
- No domain or application layer changes needed.
- The existing `adjustHp` domain function already handles positive (heal) and negative (damage) deltas with clamping.
- Total: 10 tasks across 6 phases (T005 merged into T009).