Files
initiative/specs/016-combatant-ac/tasks.md

7.9 KiB

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.

  • 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
  • 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
  • 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
  • T004 Export setAc, SetAcSuccess type, and AcSet event type from packages/domain/src/index.ts
  • 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

  • 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)
  • 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)
  • 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
  • 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
  • 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.

  • 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.

  • T012 Run pnpm check to verify full merge gate passes (knip + format + lint + typecheck + test)
  • 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

# 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