# Tasks: Combatant Armor Class Display **Input**: Design documents from `/specs/016-combatant-ac/` **Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md **Tests**: Domain tests and persistence round-trip tests are included (consistent with existing testing patterns in the codebase). **Organization**: Tasks are grouped by user story. US1 (set AC) and US2 (display AC) are both P1 and share foundational work, so they are combined into a single phase. US3 (edit AC) is P2 but is naturally fulfilled by the same inline input from US1/US2. ## 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 + Application Layer) **Purpose**: Add AC to the domain model, create the `setAc` pure function, event type, and application use case. All user stories depend on this phase. **⚠️ CRITICAL**: No UI or persistence work can begin until this phase is complete. - [x] T001 [P] Add `readonly ac?: number` to `Combatant` interface and add `AcSet` event interface to the `DomainEvent` union in `packages/domain/src/types.ts` and `packages/domain/src/events.ts` - [x] T002 [P] Create `setAc` pure function in `packages/domain/src/set-ac.ts` following the `set-initiative.ts` pattern: find combatant by ID, validate AC is non-negative integer when defined (>= 0), return updated encounter with `AcSet` event or `DomainError` - [x] T003 Write domain tests in `packages/domain/src/__tests__/set-ac.test.ts`: set AC to valid value, set AC to 0, clear AC (undefined), combatant not found error, negative AC error, non-integer AC error, AC unchanged preserves other fields - [x] T004 Export `setAc`, `SetAcSuccess` type, and `AcSet` event type from `packages/domain/src/index.ts` - [x] T005 Create `setAcUseCase` in `packages/application/src/set-ac-use-case.ts` following `set-initiative-use-case.ts` pattern, and export from `packages/application/src/index.ts` **Checkpoint**: Domain function and use case ready. Run `pnpm test` — all tests should pass including new `set-ac.test.ts`. --- ## Phase 2: User Story 1 + 2 — Set and Display AC (Priority: P1) 🎯 MVP **Goal**: Users can set AC for a combatant and see it displayed as a shield icon with the numeric value in the encounter list. **Independent Test**: Add a combatant, set its AC via the inline input, and verify the shield icon + value appears next to the name. Verify combatants without AC show no icon. ### Implementation - [x] T006 [P] [US1] Add AC rehydration validation in `apps/web/src/persistence/encounter-storage.ts`: validate `typeof entry.ac === "number" && Number.isInteger(entry.ac) && entry.ac >= 0`, attach to combatant object during deserialization (follow the `initiative` validation pattern) - [x] T007 [P] [US1] Add AC round-trip tests in `apps/web/src/persistence/__tests__/encounter-storage.test.ts`: persist and load combatant with AC, persist without AC, reject invalid AC values (negative, non-integer, string) - [x] T008 [US1] Add `setAc` callback to `apps/web/src/hooks/use-encounter.ts` following the `setInitiative` callback pattern: call `setAcUseCase`, check for domain error, append events - [x] T009 [US1] [US2] Add `onSetAc` prop to `CombatantRowProps` and implement AC display + inline editable input in `apps/web/src/components/combatant-row.tsx`: Lucide `Shield` icon + numeric value between name and HP columns, inline input following `MaxHpInput` pattern (click to edit, blur/Enter to commit, empty clears), hidden when AC is `undefined` - [x] T010 [US2] Wire `setAc` callback from `useEncounter` hook to `CombatantRow` `onSetAc` prop in the encounter list parent component (likely `apps/web/src/components/encounter-tracker.tsx` or equivalent) **Checkpoint**: US1 + US2 fully functional. Run `pnpm check` — full merge gate should pass. Manually verify: add combatant, set AC inline, see shield icon + value. Clear AC, icon disappears. --- ## Phase 3: User Story 3 — Edit AC for Existing Combatant (Priority: P2) **Goal**: Users can change or remove an existing combatant's AC value. **Independent Test**: Set a combatant's AC to 15, then change it to 18 and verify the display updates. Clear the AC field and verify the shield icon disappears. **Note**: This story is naturally fulfilled by the inline editable input implemented in Phase 2 (T009). This phase is a verification checkpoint — no additional code tasks are needed unless the inline input from T009 does not yet support editing existing values. - [x] T011 [US3] Verify and confirm that the inline AC input in `apps/web/src/components/combatant-row.tsx` handles all US3 acceptance scenarios: changing AC from 15 to 18 updates display, clearing AC field removes shield icon, setting AC on a combatant that previously had none shows shield icon **Checkpoint**: All user stories functional. Run `pnpm check` — full merge gate must pass. --- ## Phase 4: Polish & Cross-Cutting Concerns **Purpose**: Final validation and cleanup. - [x] T012 Run `pnpm check` to verify full merge gate passes (knip + format + lint + typecheck + test) - [x] T013 Verify no regression in existing functionality: HP tracking, initiative sorting, turn navigation, combatant add/remove/rename all work with AC field present --- ## Dependencies & Execution Order ### Phase Dependencies - **Foundational (Phase 1)**: No dependencies — can start immediately - **US1+US2 (Phase 2)**: Depends on Phase 1 completion (domain function + use case must exist) - **US3 (Phase 3)**: Depends on Phase 2 completion (inline input must exist) - **Polish (Phase 4)**: Depends on Phase 3 completion ### Within Phase 1 (Foundational) ``` T001 (types + events) ──┐ ├──→ T003 (tests) ──→ T004 (exports) ──→ T005 (use case) T002 (setAc function) ──┘ ``` T001 and T002 can run in parallel (different files). T003 depends on both. T004 depends on T002. T005 depends on T004. ### Within Phase 2 (US1+US2) ``` T006 (persistence) ──┐ T007 (storage tests) ┤ ├──→ T009 (combatant row UI) ──→ T010 (wire to parent) T008 (hook callback) ─┘ ``` T006, T007, T008 can run in parallel (different files). T009 depends on T008. T010 depends on T009. ### Parallel Opportunities - **Phase 1**: T001 and T002 in parallel (types.ts/events.ts vs set-ac.ts) - **Phase 2**: T006, T007, and T008 in parallel (persistence vs tests vs hook) --- ## Parallel Example: Phase 1 ```bash # Launch in parallel (different files): Task: "Add ac field to Combatant interface in packages/domain/src/types.ts and AcSet event in packages/domain/src/events.ts" Task: "Create setAc pure function in packages/domain/src/set-ac.ts" # Then sequentially: Task: "Write domain tests in packages/domain/src/__tests__/set-ac.test.ts" Task: "Export from packages/domain/src/index.ts" Task: "Create use case in packages/application/src/set-ac-use-case.ts" ``` --- ## Implementation Strategy ### MVP First (US1 + US2) 1. Complete Phase 1: Foundational (domain + application) 2. Complete Phase 2: US1 + US2 (persistence + hook + UI) 3. **STOP and VALIDATE**: Set AC on combatants, verify shield icon display 4. Run `pnpm check` — merge gate must pass ### Incremental Delivery 1. Phase 1 → Domain model and logic ready 2. Phase 2 → AC visible and settable in encounter list (MVP!) 3. Phase 3 → Verify editing works (should be automatic from Phase 2) 4. Phase 4 → Final polish and regression check --- ## Notes - [P] tasks = different files, no dependencies - [Story] label maps task to specific user story for traceability - US1 and US2 are combined because they share all implementation work and are both P1 - US3 requires no new code — the inline input from US1/US2 inherently supports editing - Follow `set-initiative.ts` as the primary pattern reference throughout - Commit after each phase or logical group of tasks