Implement the 032-inline-confirm-buttons feature that replaces single-click destructive actions with a reusable ConfirmButton component providing inline two-step confirmation (click to arm, click to execute), applied to the remove combatant and clear encounter buttons, with CSS scale pulse animation, 5-second auto-revert, click-outside/Escape/blur dismissal, full keyboard accessibility, and 13 unit tests via @testing-library/react

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-11 09:51:21 +01:00
parent d101906776
commit 0747d044f3
17 changed files with 1364 additions and 32 deletions

View File

@@ -0,0 +1,100 @@
# Feature Specification: Inline Confirmation Buttons
**Feature Branch**: `032-inline-confirm-buttons`
**Created**: 2026-03-11
**Status**: Draft
**Input**: User description: "Replace confirmation modals with inline confirmation buttons for destructive actions"
## User Scenarios & Testing *(mandatory)*
### User Story 1 - Confirm-to-delete for removing a combatant (Priority: P1)
A user hovering over a combatant row sees the remove (X) button. They click it and the button transitions into a red "confirm" state with a checkmark icon and a subtle scale pulse animation. If they click again within 5 seconds, the combatant is removed. If they wait, click elsewhere, or press Escape, the button quietly reverts to its original state without taking any action.
**Why this priority**: The remove combatant button currently has no confirmation at all — it deletes immediately. This is the highest-risk destructive action because it can be triggered accidentally during fast-paced encounter management and there is no undo.
**Independent Test**: Can be fully tested by adding a single combatant, clicking the X button once (verifying confirm state), then clicking again (verifying removal). Delivers immediate safety value.
**Acceptance Scenarios**:
1. **Given** a combatant row is visible, **When** the user clicks the remove (X) button once, **Then** the button transitions to a confirm state showing a checkmark icon on a red/danger background with a scale pulse animation.
2. **Given** the remove button is in confirm state, **When** the user clicks it again, **Then** the combatant is removed from the encounter.
3. **Given** the remove button is in confirm state, **When** 5 seconds elapse without a second click, **Then** the button reverts to its original X icon and default styling.
4. **Given** the remove button is in confirm state, **When** the user clicks outside the button, **Then** the button reverts to its original state without removing the combatant.
5. **Given** the remove button is in confirm state, **When** the user presses Escape, **Then** the button reverts to its original state without removing the combatant.
---
### User Story 2 - Confirm-to-clear for resetting the encounter (Priority: P2)
A user clicks the trash button to clear the entire encounter. Instead of a browser confirm dialog appearing, the trash button itself transitions into a red confirm state with a checkmark icon and a scale pulse. A second click clears the encounter; otherwise the button reverts after 5 seconds or on dismiss.
**Why this priority**: The clear encounter action already has a browser `window.confirm()` dialog. Replacing it with the inline pattern improves UX consistency but is lower priority since protection already exists.
**Independent Test**: Can be tested by adding combatants, clicking the trash button once (verifying confirm state), then clicking again (verifying encounter is cleared). Delivers a more modern, consistent experience.
**Acceptance Scenarios**:
1. **Given** an encounter has combatants, **When** the user clicks the clear encounter (trash) button once, **Then** the button transitions to a confirm state with a checkmark icon on a red/danger background with a scale pulse animation.
2. **Given** the trash button is in confirm state, **When** the user clicks it again, **Then** the entire encounter is cleared.
3. **Given** the trash button is in confirm state, **When** 5 seconds pass, the user clicks outside, or the user presses Escape, **Then** the button reverts to its original trash icon and default styling without clearing the encounter.
4. **Given** the encounter has no combatants, **When** the user views the trash button, **Then** it remains disabled and cannot enter confirm state.
---
### User Story 3 - Keyboard-accessible confirmation flow (Priority: P2)
A keyboard-only user can trigger the confirm state with Enter or Space, confirm the destructive action with a second Enter or Space, and cancel with Escape — all without needing a mouse.
**Why this priority**: Keyboard accessibility is a core project requirement (established in the quality gates feature). It shares priority with Story 2 because it is integral to the component's correctness rather than an add-on.
**Independent Test**: Can be tested by tabbing to a destructive button, pressing Enter (verifying confirm state), then pressing Enter again (verifying action executes). Escape cancels.
**Acceptance Scenarios**:
1. **Given** a destructive button has keyboard focus, **When** the user presses Enter or Space, **Then** the button enters confirm state.
2. **Given** a destructive button is in confirm state with focus, **When** the user presses Enter or Space, **Then** the destructive action executes.
3. **Given** a destructive button is in confirm state with focus, **When** the user presses Escape, **Then** the button reverts to its original state.
4. **Given** a destructive button is in confirm state, **When** the button loses focus (e.g., Tab away), **Then** the button reverts to its original state.
---
### Edge Cases
- What happens if the user rapidly clicks the button three or more times? The first click enters confirm state, the second executes the action — additional clicks are no-ops since the target is already removed/cleared.
- What happens if the component unmounts while in confirm state (e.g., navigating away)? The timer must be cleaned up to prevent memory leaks or stale state updates.
- What happens if two ConfirmButtons are in confirm state simultaneously (e.g., hovering over one combatant's X then another's)? Each button manages its own state independently.
- What happens if the remove button's combatant row is re-rendered while in confirm state (e.g., due to initiative changes)? The confirm state persists through re-renders as long as the combatant identity is stable.
## Requirements *(mandatory)*
### Functional Requirements
- **FR-001**: System MUST provide a reusable `ConfirmButton` component that wraps any icon button to add a two-step confirmation flow.
- **FR-002**: On first activation (click, Enter, or Space), the button MUST transition to a confirm state displaying a checkmark icon on a red/danger background.
- **FR-003**: The confirm state MUST enter with a subtle scale pulse animation to draw attention to the state change.
- **FR-004**: The button MUST automatically revert to its original state after 5 seconds if not confirmed.
- **FR-005**: Clicking outside the button, pressing Escape, or moving focus away MUST cancel the confirm state and revert the button.
- **FR-006**: A second activation (click, Enter, or Space) while in confirm state MUST execute the destructive action.
- **FR-007**: The clear encounter (trash) button MUST use `ConfirmButton` instead of `window.confirm()`.
- **FR-008**: The remove combatant (X) button MUST use `ConfirmButton` instead of deleting immediately.
- **FR-009**: The `ConfirmButton` MUST remain fully keyboard-accessible: focusable, activatable via Enter/Space, dismissable via Escape.
- **FR-010**: Each `ConfirmButton` instance MUST manage its confirm state independently of other instances.
## Success Criteria *(mandatory)*
### Measurable Outcomes
- **SC-001**: All destructive actions require exactly two deliberate user interactions to execute, eliminating single-click accidental deletions.
- **SC-002**: The confirmation flow completes (enter confirm state, execute action) in under 2 seconds for an experienced user, keeping the interaction faster than a modal dialog.
- **SC-003**: The confirm state is visually distinct enough that users can identify it without reading text — recognizable by color and icon change alone.
- **SC-004**: All confirmation flows are fully operable via keyboard alone, with no mouse dependency.
- **SC-005**: The auto-revert timer reliably resets the button after 5 seconds, preventing stale confirm states from persisting.
## Assumptions
- The checkmark icon will use the project's existing Lucide icon library (`Check` component).
- The red/danger background will use the project's existing destructive color tokens.
- The scale pulse animation will be a CSS animation, keeping it lightweight.
- The 5-second timeout is a fixed value. MVP baseline does not include configurability.
- MVP baseline does not include undo functionality — that would be a separate feature.