Implement the 023-clear-encounter feature that adds a clear encounter button with confirmation dialog to remove all combatants and reset round/turn counters, with the cleared state persisting across page refreshes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-09 13:43:42 +01:00
parent 11c4c0237e
commit 24198c25f1
19 changed files with 703 additions and 16 deletions

View File

@@ -0,0 +1,146 @@
# Tasks: Clear Encounter
**Input**: Design documents from `/specs/023-clear-encounter/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md
**Tests**: Included — domain tests follow existing patterns (pure function assertions).
**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)
- Include exact file paths in descriptions
---
## Phase 1: Foundational (Blocking Prerequisites)
**Purpose**: Domain event type and pure function that all subsequent work depends on
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
- [x] T001 Add `EncounterCleared` event interface (with `type: "EncounterCleared"` and `combatantCount: number` fields) and add it to the `DomainEvent` union type in `packages/domain/src/events.ts`
- [x] T002 Create `clearEncounter` pure function in `packages/domain/src/clear-encounter.ts` — takes an `Encounter`, returns `ClearEncounterSuccess` (with `{ encounter: { combatants: [], activeIndex: 0, roundNumber: 1 }, events: [EncounterCleared] }`) or `DomainError` (code `"encounter-already-empty"` when `combatants.length === 0`). Export `ClearEncounterSuccess` type. Follow the pattern in `packages/domain/src/remove-combatant.ts`
- [x] T003 Export `clearEncounter`, `ClearEncounterSuccess`, and `EncounterCleared` from `packages/domain/src/index.ts`
**Checkpoint**: Domain layer complete — `clearEncounter` is a pure, testable function
---
## Phase 2: User Story 1 — Clear Encounter to Start Fresh (Priority: P1) 🎯 MVP
**Goal**: Users can clear all combatants and reset round/turn counters with a single action, with the cleared state persisting across page refreshes.
**Independent Test**: Add several combatants, advance through rounds, clear the encounter, verify all combatants removed and counters reset to round 1 / index 0. Refresh the page and verify empty state persists.
### Tests for User Story 1
- [x] T004 [P] [US1] Write domain tests for `clearEncounter` in `packages/domain/src/__tests__/clear-encounter.test.ts` — cover: (1) clearing encounter with multiple combatants at round 3 returns empty encounter with roundNumber 1 and activeIndex 0, (2) clearing encounter with single combatant works, (3) clearing encounter with combatants that have HP/AC/conditions/concentration removes all data, (4) clearing already-empty encounter returns DomainError with code `"encounter-already-empty"`, (5) emits `EncounterCleared` event with correct `combatantCount`, (6) determinism — same input always produces same output
### Implementation for User Story 1
- [x] T005 [P] [US1] Create `clearEncounterUseCase` in `packages/application/src/clear-encounter-use-case.ts` — follow the pattern in `remove-combatant-use-case.ts`: get encounter from store, call `clearEncounter`, check for `DomainError`, save result, return events
- [x] T006 [US1] Export `clearEncounterUseCase` from `packages/application/src/index.ts`
- [x] T007 [US1] Update `loadEncounter` in `apps/web/src/persistence/encounter-storage.ts` to handle empty combatant arrays — when `combatants` is a valid empty array, return `{ combatants: [], activeIndex: 0, roundNumber: 1 }` directly instead of routing through `createEncounter()` (which rejects empty arrays)
- [x] T008 [US1] Add `clearEncounter` callback to `useEncounter` hook in `apps/web/src/hooks/use-encounter.ts` — call `clearEncounterUseCase(makeStore())`, check for `DomainError`, update events. Also reset `nextId.current` to 0 after clearing. Return `clearEncounter` from the hook
- [x] T009 [P] [US1] Add `onClearEncounter` prop and clear button (using Trash2 icon from Lucide) to the `TurnNavigation` component in `apps/web/src/components/turn-navigation.tsx` — place it near the round/turn controls, use `variant="ghost"` and `size="icon"` styling consistent with existing buttons
- [x] T010 [US1] Wire `clearEncounter` from `useEncounter` hook to `TurnNavigation` as `onClearEncounter` prop in `apps/web/src/App.tsx`
**Checkpoint**: User Story 1 is fully functional — clearing works end-to-end and persists across refreshes
---
## Phase 3: User Story 2 — Confirmation Before Clearing (Priority: P1)
**Goal**: Users are prompted for confirmation before the encounter is cleared, preventing accidental data loss. The clear button is disabled when there are no combatants.
**Independent Test**: Click the clear button, verify a confirmation prompt appears, cancel and verify encounter unchanged, confirm and verify encounter cleared. Verify the button is disabled when the encounter is already empty.
### Implementation for User Story 2
- [x] T011 [US2] Add `window.confirm("Clear the entire encounter? This cannot be undone.")` gate before executing `clearEncounterUseCase` in the `clearEncounter` callback in `apps/web/src/hooks/use-encounter.ts` — if user cancels, return early without modifying state (FR-006)
- [x] T012 [US2] Disable the clear encounter button when `encounter.combatants.length === 0` in `apps/web/src/components/turn-navigation.tsx` — pass combatant count or a `disabled` flag via props (FR-008)
**Checkpoint**: User Story 2 complete — accidental clears are prevented, button disabled when empty
---
## Phase 4: Polish & Cross-Cutting Concerns
**Purpose**: Verify all checks pass and feature is merge-ready
- [x] T013 Run `pnpm check` (knip + format + lint + typecheck + test) to verify merge gate passes
---
## Dependencies & Execution Order
### Phase Dependencies
- **Foundational (Phase 1)**: No dependencies — can start immediately
- **User Story 1 (Phase 2)**: Depends on Phase 1 completion
- **User Story 2 (Phase 3)**: Depends on Phase 2 completion (US2 modifies files created/modified in US1)
- **Polish (Phase 4)**: Depends on all phases complete
### User Story Dependencies
- **User Story 1 (P1)**: Can start after Foundational — no dependencies on other stories
- **User Story 2 (P1)**: Depends on User Story 1 — adds confirmation gate to the clearEncounter callback and disabled state to the button created in US1
### Within Each Phase
- Tasks marked [P] can run in parallel (different files)
- Sequential tasks depend on prior tasks in the same phase
### Parallel Opportunities
- **Phase 1**: T001 must complete before T002 (T002 imports EncounterCleared). T003 depends on both.
- **Phase 2**: T004 (tests) and T005 (use case) can run in parallel [P]. T009 (UI) can run in parallel with T005T008 [P]. T010 depends on T008 and T009.
- **Phase 3**: T011 and T012 modify different files but T012 depends on the prop interface from T009, so they should run sequentially.
---
## Parallel Example: User Story 1
```bash
# After Phase 1 completes, launch in parallel:
Task T004: "Write domain tests in packages/domain/src/__tests__/clear-encounter.test.ts"
Task T005: "Create clearEncounterUseCase in packages/application/src/clear-encounter-use-case.ts"
Task T009: "Add clear button to apps/web/src/components/turn-navigation.tsx"
# Then sequentially:
Task T006: "Export use case from packages/application/src/index.ts"
Task T007: "Update loadEncounter in apps/web/src/persistence/encounter-storage.ts"
Task T008: "Add clearEncounter to apps/web/src/hooks/use-encounter.ts"
Task T010: "Wire clearEncounter in apps/web/src/App.tsx"
```
---
## Implementation Strategy
### MVP First (User Story 1 Only)
1. Complete Phase 1: Foundational (T001T003)
2. Complete Phase 2: User Story 1 (T004T010)
3. **STOP and VALIDATE**: Test clearing end-to-end, verify persistence
4. Feature is usable at this point (clearing works, just no confirmation gate)
### Full Delivery
1. Complete Phases 12 → Clear encounter works
2. Complete Phase 3 → Confirmation prevents accidental clears
3. Complete Phase 4 → Merge gate verified
4. Feature is complete and merge-ready
---
## Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- US2 is intentionally sequenced after US1 because it modifies the same files (hook, component)
- The `window.confirm()` approach keeps the domain pure — confirmation logic stays in the adapter layer
- Commit after each phase for clean git history