---
name: harden
description: Edge case and robustness review — checks error states, empty states, loading states, boundary values, and disabled interactions. Critical for forms and arrangement flows.
user-invocable: true
argument-hint: "[component or area to harden]"
---

Systematically review and fix edge cases, error states, and boundary conditions. Unlike audit/critique which only assess, **harden finds AND fixes issues**. This skill is especially critical for forms, stepped flows, and anything involving user input.

**Target:** $ARGUMENTS

## Preparation

1. Read `docs/design-system.md` for FA design conventions
2. Read `docs/memory/decisions-log.md` for design rationale (especially D024 on error states)
3. Read the target component/area source files and stories
4. Reference `docs/reference/impeccable/interaction-design.md` for interactive state requirements
5. Reference `docs/reference/impeccable/cognitive-load.md` for cognitive load under stress
6. Reference `docs/conventions/component-conventions.md` for structural rules

**FA context**: Funeral Arranger serves families in grief or distress. When something goes wrong, the interface must be gentle and guiding, never blaming or alarming. Error states use copper tones, not red (D024). Empty states should guide toward action, not leave users stranded. Loading states should reduce perceived wait time — grieving users have less patience for ambiguity.

## Hardening Checklist

Work through each category. **Fix issues as you find them** — do not just document.

### 1. Error States

For every input, form field, and async operation:

- [ ] **Error is visible**: Error message appears near the source of the problem
- [ ] **Error is gentle**: Uses copper tones (`feedback.error`), not aggressive red. Per D024, labels stay neutral
- [ ] **Error is specific**: Identifies the exact problem ("Please enter a valid email" not "Invalid input")
- [ ] **Error is actionable**: Tells the user how to fix it
- [ ] **Error is accessible**: Connected via `aria-describedby`, announced to screen readers
- [ ] **Error does not destroy work**: Form data is preserved when validation fails
- [ ] **Error timing**: Validates on blur for individual fields, on submit for cross-field validation
- [ ] **Network errors**: Graceful message for failed API calls with retry option
- [ ] **Unexpected errors**: Catch-all error boundary that does not show a blank screen

**FA-specific**: Error copy should never blame the user. Use passive voice for errors ("A valid email is needed" not "You entered an invalid email"). Offer help where possible.

### 2. Empty States

For every list, collection, search result, and data display:

- [ ] **Empty state exists**: Not just blank space or a bare container
- [ ] **Empty state guides**: Tells the user what this area is for and how to populate it
- [ ] **Empty state has a CTA**: Primary action to add/create/search is visible
- [ ] **Empty state feels warm**: Consistent with FA's supportive tone
- [ ] **Empty state is distinct**: Clearly different from loading state — user should never confuse "no data" with "still loading"

### 3. Loading States

For every async operation and data fetch:

- [ ] **Loading indicator exists**: User sees feedback that something is happening
- [ ] **Skeleton over spinner**: Use skeleton screens for content areas, spinners only for actions
- [ ] **No layout shift**: Content area maintains its dimensions during loading (prevents CLS)
- [ ] **Loading is fast-feeling**: Skeleton previews the content shape; perceived wait is minimised
- [ ] **Loading timeout**: If loading takes >5s, show a reassuring message ("This is taking longer than usual")
- [ ] **Button loading**: Buttons show inline loading state, remain disabled, and preserve their width
- [ ] **Optimistic updates**: For low-stakes actions, show success immediately and rollback on failure

### 4. Disabled States

For every interactive element that can be disabled:

- [ ] **Visually distinct**: Reduced opacity (0.38-0.5) or muted treatment — clearly non-interactive
- [ ] **`aria-disabled`**: Set alongside visual treatment for screen reader users
- [ ] **No pointer events**: `pointer-events: none` or equivalent — no hover/active states
- [ ] **Tooltip on disabled**: Explains WHY the element is disabled (e.g., "Complete the required fields first")
- [ ] **Cursor**: Shows `not-allowed` cursor on hover

### 5. Boundary Values

For every input that accepts user data:

- [ ] **Max length**: Text inputs have sensible `maxLength` and show remaining characters if relevant
- [ ] **Min/max values**: Number inputs have `min`/`max` attributes
- [ ] **Long content**: Component handles very long names, descriptions, and values without breaking layout
- [ ] **Short content**: Component handles single-character or minimal content gracefully
- [ ] **Special characters**: Handles ampersands, quotes, HTML entities, and emoji without rendering issues
- [ ] **Zero state**: Numeric displays handle $0.00, 0 items, 0 results
- [ ] **Large numbers**: Handles $999,999+ with proper formatting

### 6. Overflow & Truncation

For every text container and layout:

- [ ] **Text overflow**: Long text truncates with ellipsis or wraps gracefully — never overflows container
- [ ] **Responsive overflow**: No horizontal scroll at any viewport width
- [ ] **List overflow**: Long lists scroll within a container, not the page
- [ ] **Image overflow**: Images are constrained to their containers with `object-fit`

### 7. Keyboard & Focus

For every interactive element and flow:

- [ ] **Tab order**: Logical, matches visual order
- [ ] **Focus trap**: Modals and drawers trap focus correctly (use `inert` on background)
- [ ] **Focus return**: When a modal/popover closes, focus returns to the trigger element
- [ ] **Escape to close**: All overlays close on Escape key
- [ ] **Enter to submit**: Forms submit on Enter from the last field
- [ ] **Arrow navigation**: Tab lists, menus, and radio groups use roving tabindex with arrow keys

### 8. Concurrent & Async

For forms and flows with async operations:

- [ ] **Double-submit prevention**: Submit button disables after first click
- [ ] **Rapid interaction**: Debounced search, throttled scroll handlers
- [ ] **Stale data**: Component handles data that changes between render and interaction
- [ ] **Unmount safety**: Async operations clean up on component unmount (no state updates after unmount)

## Hardening Report

After fixing issues, provide a summary:

### Fixed
List each issue fixed with a one-line description.

### Verified OK
List categories that passed inspection without changes needed.

### Out of Scope
Note any structural issues found that require architectural changes (not hardening work).

**NEVER**:
- Use aggressive red for error states — always copper/warm tones per D024
- Show blank screens for empty or error states
- Leave async operations without loading feedback
- Allow double-submit on forms
- Remove focus indicators
- Make structural changes — hardening fixes edge cases within the existing architecture
