Files
initiative/specs/032-inline-confirm-buttons/tasks.md

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-pulse scale pulse keyframe animation in apps/web/src/index.css — define a @keyframes confirm-pulse (scale 1 → 1.15 → 1) and register it as @utility animate-confirm-pulse following the existing animate-concentration-pulse pattern
  • T002 Create ConfirmButton component in apps/web/src/components/ui/confirm-button.tsx — wraps the existing Button component with a two-step confirmation flow: accepts onConfirm, icon, label, className?, disabled? props; manages isConfirming state via useState; on first click sets isConfirming=true and starts a 5-second setTimeout to auto-revert; on second click calls onConfirm(); in confirm state renders Check icon with bg-destructive text-primary-foreground rounded-md animate-confirm-pulse; adds mousedown click-outside listener and keydown Escape listener (both with cleanup); reverts on focus loss via onBlur; calls e.stopPropagation() on all clicks; updates aria-label to reflect confirm state (e.g., "Confirm remove combatant" when armed, using label prop 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 ConfirmButton in apps/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 calls onConfirm, (4) auto-reverts after 5 seconds (use vi.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> in apps/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 handles stopPropagation internally (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> in apps/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 from clearEncounter callback in apps/web/src/hooks/use-encounter.ts — delete the if (!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 the Button wrapper) so Enter/Space activation works by default; verify that the onKeyDown handler for Escape is attached and works in confirm state; verify onBlur revert fires when tabbing away; verify dynamic aria-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 calls onConfirm, (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 check to 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)

  1. Complete Phase 1: Setup (T001, T002) — create animation and component
  2. Complete Phase 2: Foundational (T003) — test the component
  3. Complete Phase 3: User Story 1 (T004) — integrate with combatant remove
  4. STOP and VALIDATE: Test remove combatant confirmation independently
  5. Commit and verify pnpm check passes

Incremental Delivery

  1. Setup + Foundational → ConfirmButton ready and tested
  2. Add User Story 1 → Remove combatant has confirmation → MVP
  3. Add User Story 2 → Clear encounter uses inline confirm → Consistent UX
  4. Add User Story 3 → Keyboard accessibility verified and tested → Complete
  5. 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