Implement the 005-set-initiative feature that adds initiative values to combatants with automatic descending sort and active turn preservation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-04 17:26:41 +01:00
parent a9df826fef
commit fea2bfe39d
17 changed files with 1107 additions and 1 deletions

View File

@@ -0,0 +1,179 @@
# Tasks: Set Initiative
**Input**: Design documents from `/specs/005-set-initiative/`
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/domain-api.md, quickstart.md
**Tests**: Tests are included as this project follows TDD conventions (test files exist for all domain functions).
**Organization**: Tasks are grouped by user story to enable independent implementation and testing of each story.
## 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 (Shared Infrastructure)
**Purpose**: No new project setup needed — existing monorepo. This phase covers foundational type and event changes shared across all user stories.
- [x] T001 Add optional `initiative` property to `Combatant` interface in `packages/domain/src/types.ts`
- [x] T002 Add `InitiativeSet` event type (with `combatantId`, `previousValue`, `newValue` fields) to `DomainEvent` union in `packages/domain/src/events.ts`
**Checkpoint**: Types compile, existing tests still pass (`pnpm check`)
---
## Phase 2: User Story 1 + User Story 2 — Set Initiative & Automatic Ordering (Priority: P1) MVP
**Goal**: Users can set/change/clear initiative values on combatants, and the encounter automatically reorders combatants from highest to lowest initiative.
**Independent Test**: Set initiative on multiple combatants and verify the combatant list is sorted descending by initiative value.
### Tests for User Stories 1 & 2
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
- [x] T003 [US1] Write acceptance tests for setting initiative (set, change, reject non-integer) in `packages/domain/src/__tests__/set-initiative.test.ts`
- [x] T004 [US2] Write acceptance tests for automatic ordering (descending sort, stable sort for ties, reorder on change) in `packages/domain/src/__tests__/set-initiative.test.ts`
- [x] T005 Write invariant tests (determinism, immutability, event shape, roundNumber unchanged) in `packages/domain/src/__tests__/set-initiative.test.ts`
### Implementation for User Stories 1 & 2
- [x] T006 [US1] [US2] Implement `setInitiative(encounter, combatantId, value)` domain function in `packages/domain/src/set-initiative.ts` — validate combatant exists, validate integer, update initiative, stable-sort descending, emit `InitiativeSet` event
- [x] T007 Export `setInitiative` and related types from `packages/domain/src/index.ts`
- [x] T008 Implement `setInitiativeUseCase(store, combatantId, value)` in `packages/application/src/set-initiative-use-case.ts` following existing use case pattern (get → call → check error → save → return events)
- [x] T009 Export `setInitiativeUseCase` from `packages/application/src/index.ts`
**Checkpoint**: Domain tests pass, `pnpm check` passes. Core initiative logic is complete.
---
## Phase 3: User Story 3 — Combatants Without Initiative (Priority: P2)
**Goal**: Combatants without initiative appear after all combatants with initiative, preserving their relative order.
**Independent Test**: Create a mix of combatants with and without initiative and verify ordering (initiative-set first descending, then unset in insertion order).
### Tests for User Story 3
- [x] T010 [US3] Write acceptance tests for unset-initiative ordering (unset after set, multiple unset preserve order, setting initiative moves combatant up) in `packages/domain/src/__tests__/set-initiative.test.ts`
### Implementation for User Story 3
- [x] T011 [US3] Verify that sort logic in `packages/domain/src/set-initiative.ts` already handles `undefined` initiative correctly (combatants without initiative sort after those with initiative, stable sort within each group) — add handling if not already present in T006
**Checkpoint**: All ordering scenarios pass including mixed set/unset combatants.
---
## Phase 4: User Story 4 — Active Turn Preservation During Reorder (Priority: P2)
**Goal**: The active combatant's turn is preserved when initiative changes cause the combatant list to be reordered.
**Independent Test**: Set active combatant, change another combatant's initiative causing reorder, verify active turn still points to the same combatant.
### Tests for User Story 4
- [x] T012 [US4] Write acceptance tests for active turn preservation (reorder doesn't shift active turn, active combatant's own initiative change preserves turn) in `packages/domain/src/__tests__/set-initiative.test.ts`
### Implementation for User Story 4
- [x] T013 [US4] Verify that `activeIndex` identity-tracking in `packages/domain/src/set-initiative.ts` works correctly when reordering occurs — the logic (record active id before sort, find new index after sort) should already exist from T006; add or fix if needed
**Checkpoint**: Active turn is preserved through all reorder scenarios. `pnpm check` passes.
---
## Phase 5: Web Adapter Integration
**Purpose**: Wire initiative into the React UI so users can actually set initiative values.
- [x] T014 Add `setInitiative` callback to `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — call `setInitiativeUseCase`, handle errors, append events
- [x] T015 Add initiative input field next to each combatant in `apps/web/src/App.tsx` — numeric input, display current value, clear button, call `setInitiative` on change
**Checkpoint**: Full feature works end-to-end in the browser. `pnpm check` passes.
---
## Phase 6: Polish & Cross-Cutting Concerns
**Purpose**: Edge case coverage and final validation.
- [x] T016 Write edge case tests (zero initiative, negative initiative, clearing initiative, all same value) in `packages/domain/src/__tests__/set-initiative.test.ts`
- [x] T017 Run `pnpm check` (format + lint + typecheck + test) and fix any issues
- [x] T018 Verify layer boundary compliance (domain imports no framework/adapter code)
---
## Dependencies & Execution Order
### Phase Dependencies
- **Phase 1 (Setup)**: No dependencies — types and events first
- **Phase 2 (US1+US2 MVP)**: Depends on Phase 1
- **Phase 3 (US3)**: Depends on Phase 2 (extends sort logic)
- **Phase 4 (US4)**: Depends on Phase 2 (extends activeIndex logic)
- **Phase 5 (Web Adapter)**: Depends on Phases 24 (needs complete domain + application layer)
- **Phase 6 (Polish)**: Depends on all previous phases
### User Story Dependencies
- **US1 + US2 (P1)**: Combined because sorting is inherent to setting initiative — they share the same domain function
- **US3 (P2)**: Extends the sort logic from US1+US2 to handle `undefined`. Can be developed immediately after Phase 2.
- **US4 (P2)**: Extends the `activeIndex` logic from US1+US2. Can be developed in parallel with US3.
### Parallel Opportunities
- **T001 and T002** can run in parallel (different files)
- **T003, T004, T005** can run in parallel (same file but different test groups — practically written together)
- **US3 (Phase 3) and US4 (Phase 4)** can run in parallel after Phase 2
- **T014 and T015** can run in parallel (different files)
---
## Parallel Example: Phase 2 (MVP)
```bash
# Tests first (all in same file, written together):
T003: Acceptance tests for setting initiative
T004: Acceptance tests for automatic ordering
T005: Invariant tests
# Then implementation:
T006: Domain function (core logic)
T007: Domain exports
T008: Application use case (after T006-T007)
T009: Application exports
```
---
## Implementation Strategy
### MVP First (User Stories 1 + 2)
1. Complete Phase 1: Type + event changes
2. Complete Phase 2: Domain function + use case with tests
3. **STOP and VALIDATE**: `pnpm check` passes, initiative setting and ordering works
4. Optionally wire up UI (Phase 5) for a minimal demo
### Incremental Delivery
1. Phase 1 → Types ready
2. Phase 2 → MVP: set initiative + auto-ordering works
3. Phase 3 → Unset combatants handled correctly
4. Phase 4 → Active turn preserved through reorders
5. Phase 5 → UI wired up, feature usable in browser
6. Phase 6 → Edge cases covered, quality verified
---
## Notes
- US1 and US2 are combined in Phase 2 because the domain function `setInitiative` inherently performs both setting and sorting — they cannot be meaningfully separated
- US3 and US4 are separable extensions of the sort and activeIndex logic respectively
- All domain tests follow existing patterns: helper functions for test data, acceptance scenarios mapped from spec, invariant tests for determinism/immutability
- Commit after each phase checkpoint