9.3 KiB
Tasks: Inline Confirmation Buttons
Input: Design documents from /specs/032-inline-confirm-buttons/
Prerequisites: plan.md, spec.md, research.md, data-model.md, quickstart.md
Tests: Not explicitly requested in spec. Tests included for the ConfirmButton component since its state machine logic is well-suited to unit testing and the spec defines precise acceptance scenarios.
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: CSS animation and reusable component that all user stories depend on
- T001 Add
animate-confirm-pulsescale pulse keyframe animation inapps/web/src/index.css— define a@keyframes confirm-pulse(scale 1 → 1.15 → 1) and register it as@utility animate-confirm-pulsefollowing the existinganimate-concentration-pulsepattern - T002 Create
ConfirmButtoncomponent inapps/web/src/components/ui/confirm-button.tsx— wraps the existingButtoncomponent with a two-step confirmation flow: acceptsonConfirm,icon,label,className?,disabled?props; managesisConfirmingstate viauseState; on first click setsisConfirming=trueand starts a 5-secondsetTimeoutto auto-revert; on second click callsonConfirm(); in confirm state rendersCheckicon withbg-destructive text-primary-foreground rounded-md animate-confirm-pulse; addsmousedownclick-outside listener andkeydownEscape listener (both with cleanup); reverts on focus loss viaonBlur; callse.stopPropagation()on all clicks; updatesaria-labelto reflect confirm state (e.g., "Confirm remove combatant" when armed, usinglabelprop as base); cleans up timer on unmount
Phase 2: Foundational (Blocking Prerequisites)
Purpose: Unit tests for the ConfirmButton state machine
- T003 Write unit tests for
ConfirmButtoninapps/web/src/__tests__/confirm-button.test.tsx— test: (1) renders default icon in idle state, (2) first click transitions to confirm state with Check icon and destructive background, (3) second click in confirm state callsonConfirm, (4) auto-reverts after 5 seconds (usevi.useFakeTimers), (5) Escape key reverts to idle, (6) click outside reverts to idle, (7) disabled prop prevents entering confirm state, (8) unmount cleans up timer, (9) independent instances don't interfere with each other
Checkpoint: ConfirmButton component is fully functional and tested. User story integration can begin.
Phase 3: User Story 1 — Confirm-to-delete for removing a combatant (Priority: P1) MVP
Goal: The remove combatant (X) button uses ConfirmButton instead of deleting immediately.
Independent Test: Add a combatant, click X once (verify confirm state with checkmark), click again (verify removal). Wait 5s after first click (verify revert). Press Escape (verify cancel).
Implementation for User Story 1
- T004 [US1] Replace the remove
<Button>with<ConfirmButton>inapps/web/src/components/combatant-row.tsx— swap the existing<Button variant="ghost" size="icon" ... onClick={onRemove(id)}>(around line 546) with<ConfirmButton icon={<X size={16} />} label="Remove combatant" onConfirm={() => onRemove(id)} className="h-7 w-7 text-muted-foreground opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto focus:opacity-100 focus:pointer-events-auto transition-opacity" />; the ConfirmButton handlesstopPropagationinternally (per T002) so the parent row click is not triggered
Checkpoint: Remove combatant now requires two clicks. Single-click accidental deletion is eliminated.
Phase 4: User Story 2 — Confirm-to-clear for resetting the encounter (Priority: P2)
Goal: The clear encounter (trash) button uses ConfirmButton instead of window.confirm().
Independent Test: Add combatants, click trash once (verify confirm state), click again (verify encounter cleared). Verify disabled state when no combatants.
Implementation for User Story 2
- T005 [US2] Replace the trash
<Button>with<ConfirmButton>inapps/web/src/components/turn-navigation.tsx— swap the existing<Button variant="ghost" size="icon" ... onClick={onClearEncounter}>(around line 77) with<ConfirmButton icon={<Trash2 className="h-5 w-5" />} label="Clear encounter" onConfirm={onClearEncounter} disabled={!hasCombatants} className="h-8 w-8 text-muted-foreground" /> - T006 [US2] Remove
window.confirm()guard fromclearEncountercallback inapps/web/src/hooks/use-encounter.ts— delete theif (!window.confirm(...)) return;block (around line 229) since confirmation is now handled by the ConfirmButton UI component
Checkpoint: Clear encounter now uses inline confirmation instead of browser dialog. Both destructive actions have consistent UX.
Phase 5: User Story 3 — Keyboard-accessible confirmation flow (Priority: P2)
Goal: All confirmation flows are fully operable via keyboard (Enter/Space to activate, Escape to cancel, Tab-away to revert).
Independent Test: Tab to a destructive button, press Enter (confirm state), Enter again (action executes). Tab to button, press Enter (confirm state), press Escape (reverts). Tab to button, press Enter (confirm state), Tab away (reverts).
Implementation for User Story 3
- T007 [US3] Verify keyboard handling in
apps/web/src/components/ui/confirm-button.tsx— ensure the component uses a native<button>element (via theButtonwrapper) so Enter/Space activation works by default; verify that theonKeyDownhandler for Escape is attached and works in confirm state; verifyonBlurrevert fires when tabbing away; verify dynamicaria-label(built into T002) works correctly; run manual keyboard testing - T008 [US3] Add keyboard-specific unit tests in
apps/web/src/__tests__/confirm-button.test.tsx— append tests: (1) Enter key triggers confirm state, (2) Space key triggers confirm state, (3) Enter in confirm state callsonConfirm, (4) Escape in confirm state reverts, (5) blur event reverts confirm state
Checkpoint: All confirmation flows work identically via mouse and keyboard.
Phase 6: Polish & Cross-Cutting Concerns
Purpose: Quality gates and final validation
- T009 Run
pnpm checkto verify all quality gates pass (audit, knip, biome, typecheck, test/coverage, jscpd) - T010 Run quickstart.md manual validation — start dev server with
pnpm --filter web dev, test all scenarios from quickstart.md: add combatant, click X (confirm state), click again (removal), click X then wait 5s (revert), click trash (confirm), click again (clear), keyboard flows (Tab/Enter/Escape)
Dependencies & Execution Order
Phase Dependencies
- Setup (Phase 1): No dependencies — can start immediately
- Foundational (Phase 2): Depends on T001, T002 from Setup
- User Story 1 (Phase 3): Depends on Phase 2 completion
- User Story 2 (Phase 4): Depends on Phase 2 completion — independent of US1
- User Story 3 (Phase 5): Depends on Phase 2 completion — verifies keyboard behavior built into T002
- Polish (Phase 6): Depends on all user stories being complete
User Story Dependencies
- User Story 1 (P1): Can start after Phase 2 — no dependencies on other stories
- User Story 2 (P2): Can start after Phase 2 — independent of US1 (different files)
- User Story 3 (P2): Can start after Phase 2 — may refine T002 implementation, so best done after US1/US2 to avoid rework
Parallel Opportunities
- T001 and T002 are sequential (T002 uses the animation from T001)
- T004 (US1) and T005+T006 (US2) can run in parallel after Phase 2 (different files)
- T007 and T008 (US3) are sequential within their story
Parallel Example: User Stories 1 & 2
# After Phase 2 completes, launch US1 and US2 in parallel:
Task: T004 [US1] "Replace remove Button with ConfirmButton in combatant-row.tsx"
Task: T005 [US2] "Replace trash Button with ConfirmButton in turn-navigation.tsx"
Task: T006 [US2] "Remove window.confirm() from use-encounter.ts"
Implementation Strategy
MVP First (User Story 1 Only)
- Complete Phase 1: Setup (T001, T002) — create animation and component
- Complete Phase 2: Foundational (T003) — test the component
- Complete Phase 3: User Story 1 (T004) — integrate with combatant remove
- STOP and VALIDATE: Test remove combatant confirmation independently
- Commit and verify
pnpm checkpasses
Incremental Delivery
- Setup + Foundational → ConfirmButton ready and tested
- Add User Story 1 → Remove combatant has confirmation → MVP
- Add User Story 2 → Clear encounter uses inline confirm → Consistent UX
- Add User Story 3 → Keyboard accessibility verified and tested → Complete
- Polish → Quality gates, manual validation → Ship
Notes
- [P] tasks = different files, no dependencies
- [Story] label maps task to specific user story for traceability
- Each user story is independently completable and testable
- Commit after each task or logical group
- Stop at any checkpoint to validate story independently
- The ConfirmButton component (T002) is the critical path — all stories depend on it