veza/apps/web/src/components/ui/ERROR_DISPLAY_STRATEGY.md

489 lines
11 KiB
Markdown
Raw Normal View History

# Error Display Strategy
**Date**: 2025-01-27
**Action**: 3.1.1.5 - Create error display strategy document
**Status**: ✅ Complete
## Overview
This document defines the strategy for when to use `toast.error()` vs `ErrorDisplay` component for error presentation across the application. This strategy ensures consistent, user-friendly error handling while maintaining appropriate UX patterns for different error types.
## Core Principles
1. **Persistent errors** → Use `ErrorDisplay` (user needs to see and potentially act on the error)
2. **Transient errors** → Use `toast.error()` (quick feedback for temporary actions)
3. **Form validation** → Keep inline validation errors (field-level feedback)
4. **Critical errors** → Use `ErrorDisplay` with retry capability
5. **User action errors** → Use `ErrorDisplay` banner (dismissible, contextual)
## Decision Tree
```
Is the error related to form validation?
├─ YES → Use inline validation error (text-red-500, keep existing pattern)
└─ NO → Continue
Is the error from a query (data fetching)?
├─ YES → Use ErrorDisplay card/inline variant with retry
└─ NO → Continue
Is the error from a mutation (data update)?
├─ YES → Use ErrorDisplay banner variant (dismissible)
└─ NO → Continue
Is the error from a quick action (copy link, quick toggle)?
├─ YES → Use toast.error() (transient feedback)
└─ NO → Continue
Is the error critical (network failure, auth failure, runtime crash)?
├─ YES → Use ErrorDisplay with appropriate variant and retry
└─ NO → Use toast.error() as fallback
```
## ErrorDisplay Usage Guidelines
### When to Use ErrorDisplay
#### 1. Query Errors (Data Fetching)
**Use**: `ErrorDisplay` card or inline variant
**Variant**: `card` for page-level errors, `inline` for component-level errors
**Features**: Retry button, error details (dev mode)
**Examples**:
- Failed to load tracks/library
- Failed to load playlist details
- Failed to load user profile
- Failed to load marketplace items
```tsx
{
isError && (
<ErrorDisplay
error={error}
variant="card"
severity="error"
context={{ action: 'fetching tracks', resource: 'tracks' }}
onRetry={() => refetch()}
/>
);
}
```
#### 2. Mutation Errors (Data Updates)
**Use**: `ErrorDisplay` banner variant
**Variant**: `banner` (dismissible, appears at top of page/content)
**Features**: Dismiss button, contextual error message
**Examples**:
- Failed to add track to playlist
- Failed to update profile
- Failed to delete resource
- Failed to share resource
```tsx
{
mutationError && (
<ErrorDisplay
error={mutationError}
variant="banner"
severity="error"
onDismiss={() => setMutationError(null)}
/>
);
}
```
#### 3. Network Errors (API Failures)
**Use**: `ErrorDisplay` banner variant
**Variant**: `banner` or `card` depending on context
**Features**: Retry button, network-specific messaging
**Examples**:
- Connection timeout
- Server unavailable
- Network request failed
```tsx
<ErrorDisplay
error={networkError}
variant="banner"
severity="error"
context={{ action: 'connecting to server', resource: 'api' }}
onRetry={() => retryRequest()}
/>
```
#### 4. Runtime Errors (Component Crashes)
**Use**: `ErrorDisplay` in ErrorBoundary fallback
**Variant**: `card` or `modal`
**Features**: Error details, reload page action
**Examples**:
- Component render error
- Unhandled exception
- React error boundary catch
```tsx
<ErrorBoundary
fallback={
<ErrorDisplay
error={error}
variant="card"
severity="error"
actions={[
{ label: 'Reload Page', onClick: () => window.location.reload() },
]}
/>
}
>
{children}
</ErrorBoundary>
```
#### 5. Auth Errors (Authentication Failures)
**Use**: `ErrorDisplay` inline variant
**Variant**: `inline`
**Features**: Contextual auth error message
**Examples**:
- Login failed
- Session expired
- Unauthorized access
```tsx
<ErrorDisplay
error={authError}
variant="inline"
severity="error"
context={{ action: 'authenticating', resource: 'auth' }}
/>
```
#### 6. Player Errors (Audio Playback)
**Use**: `ErrorDisplay` card variant
**Variant**: `card`
**Features**: Retry button, error type detection
**Examples**:
- Audio decode error
- Network error during playback
- Source file not found
```tsx
<ErrorDisplay
error={playerError}
variant="card"
severity="error"
context={{ action: 'playing audio', resource: 'player' }}
onRetry={() => retryPlayback()}
/>
```
### When NOT to Use ErrorDisplay
#### 1. Form Validation Errors
**Use**: Inline validation errors (existing pattern)
**Reason**: Field-level feedback is more appropriate
**Pattern**: Keep existing `text-red-500` inline errors
```tsx
// ✅ Keep this pattern
{
errors.field && (
<p className="text-sm text-red-500">{errors.field.message}</p>
);
}
```
#### 2. Quick Action Errors (Transient)
**Use**: `toast.error()`
**Reason**: Quick feedback for temporary actions
**Examples**:
- Copy link to clipboard
- Quick toggle actions
- Temporary state changes
```tsx
// ✅ Keep toast for quick actions
try {
await navigator.clipboard.writeText(link);
toast.success('Link copied');
} catch {
toast.error('Failed to copy link');
}
```
#### 3. Validation Messages (Non-Critical)
**Use**: `toast.error()` or inline validation
**Reason**: User input validation feedback
**Examples**:
- "Username is required"
- "Please type DELETE to confirm"
- "Cart is empty"
```tsx
// ✅ Keep toast for validation messages
if (!username) {
toast.error('Username is required');
return;
}
```
## Variant Selection Guide
### `inline` Variant
**Use when**:
- Error is contextual to a specific component
- Error appears within content flow
- Error doesn't need to interrupt user flow
**Examples**:
- Form submission errors
- Component-level query errors
- Inline validation errors (if not using form validation pattern)
### `banner` Variant
**Use when**:
- Error affects page-level functionality
- Error is dismissible
- Error should be prominent but not blocking
**Examples**:
- Mutation errors (add/update/delete)
- Network errors
- Session expiration warnings
### `card` Variant
**Use when**:
- Error replaces main content
- Error needs visual prominence
- Error is part of content layout
**Examples**:
- Query errors (no data to show)
- Player errors (replaces player UI)
- Profile load errors
### `modal` Variant
**Use when**:
- Error is critical and blocking
- User must acknowledge error
- Error requires immediate attention
**Examples**:
- Critical system errors
- Payment failures
- Security violations
## Severity Selection Guide
### `error` Severity (Default)
**Use when**:
- Operation failed
- Data cannot be loaded
- Action cannot be completed
**Visual**: Red colors, AlertCircle icon
### `warning` Severity
**Use when**:
- Operation partially succeeded
- Warning that doesn't block functionality
- User should be aware but can continue
**Visual**: Yellow/gold colors, AlertTriangle icon
### `info` Severity
**Use when**:
- Informational message
- Non-critical notification
- Helpful context
**Visual**: Cyan colors, Info icon
## Migration Strategy
### Phase 1: Query Errors (HIGH Priority)
1. Replace query error displays with ErrorDisplay card variant
2. Add retry functionality
3. Files: LibraryPage ✅, TrackDetailPage, MarketplaceHome, RolesPage, SettingsPage
### Phase 2: Mutation Errors (MEDIUM Priority)
1. Replace mutation toast.errors with ErrorDisplay banner variant
2. Add dismiss functionality
3. Files: All feature files with mutations
### Phase 3: Component Errors (MEDIUM Priority)
1. Replace PlayerError with ErrorDisplay card variant
2. Replace AuthErrorMessage with ErrorDisplay inline variant
3. Update ErrorBoundary fallback UI
### Phase 4: Network Errors (HIGH Priority)
1. Replace API client toast.errors with ErrorDisplay banner
2. Add retry functionality
3. File: api/client.ts
### Phase 5: Keep Toast for Transient Actions (LOW Priority)
1. Keep toast.error() for copy link, quick actions
2. Document exceptions
3. No changes needed
## Code Examples
### Query Error Pattern
```tsx
const { data, isError, error, refetch } = useQuery({
queryKey: ['tracks'],
queryFn: fetchTracks,
});
return (
<>
{isError ? (
<ErrorDisplay
error={error}
variant="card"
severity="error"
context={{ action: 'fetching tracks', resource: 'tracks' }}
onRetry={() => refetch()}
/>
) : (
<TrackList tracks={data} />
)}
</>
);
```
### Mutation Error Pattern
```tsx
const [mutationError, setMutationError] = useState<Error | null>(null);
const handleMutation = async () => {
try {
setMutationError(null);
await mutateAsync(data);
toast.success('Success');
} catch (error) {
const apiError = parseApiError(error);
setMutationError(new Error(apiError.message));
}
};
return (
<>
{mutationError && (
<ErrorDisplay
error={mutationError}
variant="banner"
severity="error"
onDismiss={() => setMutationError(null)}
/>
)}
<Form onSubmit={handleMutation} />
</>
);
```
### Form Validation Pattern (Keep)
```tsx
// ✅ Keep existing pattern for form validation
<FormField label="Email" error={errors.email?.message}>
<Input type="email" {...register('email')} />
</FormField>
```
### Quick Action Pattern (Keep Toast)
```tsx
// ✅ Keep toast for quick actions
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(link);
toast.success('Link copied');
} catch {
toast.error('Failed to copy link');
}
};
```
## Accessibility Considerations
### ErrorDisplay Component
- ✅ ARIA `role="alert"` and `aria-live="polite"`
- ✅ Keyboard navigation for retry/dismiss buttons
- ✅ Screen reader announcements
- ✅ Focus management
### Toast Notifications
- ✅ ARIA live regions (handled by toast library)
- ✅ Auto-dismiss with appropriate timing
- ✅ Keyboard accessible
### Form Validation
- ✅ Inline errors associated with form fields
- ✅ ARIA `aria-invalid` and `aria-describedby`
- ✅ Screen reader announcements
## Testing Strategy
1. **Query Errors**: Test retry functionality
2. **Mutation Errors**: Test dismiss functionality
3. **Network Errors**: Test retry and offline handling
4. **Form Validation**: Test inline error display
5. **Toast Errors**: Test transient error display
## Rollback Plan
If ErrorDisplay causes issues:
1. Revert to toast.error() for affected areas
2. Keep ErrorDisplay for query errors (most critical)
3. Gradually reintroduce ErrorDisplay for mutations
## References
- [ErrorDisplay Component API](./ERROR_DISPLAY_COMPONENT_API.md)
- [Error Display Patterns Audit](../../docs/ERROR_DISPLAY_PATTERNS_AUDIT.md)
- [Error Messages Utility](../../utils/errorMessages.ts)
- [API Error Handler](../../utils/apiErrorHandler.ts)