435 lines
11 KiB
Markdown
435 lines
11 KiB
Markdown
|
|
# 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)
|