Add UX enhancement design doc: notification system

Design document for Tier 1 UX enhancement implementing toast
notifications using sonner library.

- Comprehensive context on current state problems
- Phase-by-phase implementation plan
- Design patterns and best practices
- Complete verification checklist
- Risk assessment and mitigation strategies

Related: #33, #34, #35

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-07 13:53:14 -05:00
parent 40e588e559
commit ff7894b977

View File

@@ -0,0 +1,427 @@
# Tier 1 UX Enhancement: Consistent Notification System
## Context
ResolutionFlow (Patherly) currently lacks a consistent feedback system for user actions. MSP engineers switching between multiple contexts need immediate confirmation that actions succeeded to reduce cognitive load and prevent errors. Research shows that 200-300ms feedback timing dramatically improves perceived reliability and user confidence.
### Current State Problems
- **No success notifications**: Users don't get confirmation when trees/folders/sessions are saved or deleted
- **Inconsistent patterns**: Mix of button state changes, modal errors, and page-level banners
- **Silent operations**: "Add to Folder" silently succeeds with no feedback
- **No dismissible errors**: Error messages persist until page reload or modal close
- **No notification history**: Users can't review recent actions
### User Impact
Engineers experience cognitive overload troubleshooting client issues. Without clear feedback:
- They second-guess themselves and repeat actions
- They abandon tasks when uncertain if changes saved
- They miss critical errors that appear briefly
- They waste time verifying actions succeeded
### Why This Feature First
1. **Foundational infrastructure**: Toast system benefits all future features
2. **Immediate wins**: Every save/delete/export action gets better UX
3. **Low risk**: Additive feature, doesn't break existing flows
4. **Quick implementation**: 1-2 days to full deployment
5. **High perceived value**: Users notice and appreciate immediate feedback
---
## Design Overview
We'll integrate **sonner** (by shadcn creator) - a modern, accessible toast notification library that:
- Works perfectly with Tailwind CSS and our dark mode
- Provides beautiful default styling matching our design system
- Supports promise tracking (show loading → success/error automatically)
- Handles stacking, positioning, and auto-dismiss elegantly
- Only ~10KB gzipped
- Fully accessible (ARIA attributes, keyboard navigation)
### Architecture: Toast as Global Service
```
frontend/src/
├── main.tsx
│ └── Wrap <App> with <Toaster /> provider
├── lib/
│ └── toast.ts (re-export sonner's toast with custom defaults)
├── components/
│ ├── library/TreeLibraryPage.tsx (add toast.success on delete)
│ ├── library/FolderEditModal.tsx (add toast.success on save)
│ ├── tree-editor/TreeEditorPage.tsx (replace error banner with toast)
│ ├── session/SessionDetailPage.tsx (add toast on export)
│ └── ...other components using toast
└── api/
└── client.ts (optional: global error toast interceptor)
```
**Key Design Decisions:**
1. **Single import point**: `import { toast } from '@/lib/toast'` everywhere
2. **Custom defaults**: Pre-configured duration, position, dark mode sync
3. **Promise pattern**: Use `toast.promise()` for async operations
4. **Consistent vocabulary**: "Saved", "Deleted", "Exported" (not "Success!")
5. **Error details**: Show action + reason ("Failed to delete tree: Network error")
---
## Implementation Plan
### Phase 1: Install and Configure Sonner
**1.1 Install dependencies**
```bash
cd patherly/frontend
npm install sonner
```
**1.2 Create toast utility wrapper** (`frontend/src/lib/toast.ts`):
```typescript
import { toast as sonnerToast } from 'sonner';
// Re-export with custom defaults
export const toast = {
success: (message: string, options?: any) =>
sonnerToast.success(message, { duration: 4000, ...options }),
error: (message: string, options?: any) =>
sonnerToast.error(message, { duration: 6000, ...options }),
info: (message: string, options?: any) =>
sonnerToast.info(message, { duration: 4000, ...options }),
loading: (message: string, options?: any) =>
sonnerToast.loading(message, { ...options }),
promise: sonnerToast.promise,
dismiss: sonnerToast.dismiss
};
```
**1.3 Add Toaster provider** to `frontend/src/main.tsx`:
```typescript
import { Toaster } from 'sonner';
// Inside root render, wrap App:
<React.StrictMode>
<Toaster
position="top-right"
richColors
closeButton
theme={themeStore.theme === 'dark' ? 'dark' : 'light'}
/>
<App />
</React.StrictMode>
```
**1.4 Sync theme with Toaster**: Update `themeStore.ts` to re-render Toaster on theme change
---
### Phase 2: Add Notifications to Core Actions
**Success notifications to add:**
| Component | Action | Toast Message | Type |
|-----------|--------|---------------|------|
| TreeLibraryPage | Delete tree | "Tree deleted" | success |
| FolderEditModal | Save folder | "Folder saved" | success |
| FolderSidebar | Delete folder | "Folder deleted" | success |
| AddToFolderMenu | Add tree to folder | "Added to {folderName}" | success |
| AddToFolderMenu | Remove from folder | "Removed from {folderName}" | success |
| TreeEditorPage | Save tree | "Tree saved" | success |
| TreeEditorPage | Publish tree | "Tree published" | success |
| SessionDetailPage | Export session | "Session exported" | success |
| SettingsPage | Save preferences | "Settings saved" | success |
| AccountSettingsPage | Update account | "Account updated" | success |
**2.1 Tree Library Actions** ([TreeLibraryPage.tsx](patherly/frontend/src/pages/TreeLibraryPage.tsx)):
- Remove confirmation dialog result logging
- Add `toast.success('Tree deleted')` after successful delete
- Replace inline error state with `toast.error(error)`
**2.2 Folder Management** ([FolderEditModal.tsx](patherly/frontend/src/components/library/FolderEditModal.tsx), [FolderSidebar.tsx](patherly/frontend/src/components/library/FolderSidebar.tsx)):
- Add success toast on folder create/update/delete
- Remove inline error messages (use toast.error instead)
- Keep loading state but add toast feedback on completion
**2.3 Tree Editor** ([TreeEditorPage.tsx](patherly/frontend/src/pages/TreeEditorPage.tsx)):
- Replace current error banner with toast notifications
- Add `toast.promise()` for autosave operations:
```typescript
toast.promise(saveTree(), {
loading: 'Saving tree...',
success: 'Tree saved',
error: 'Failed to save tree'
});
```
- Remove `saveError` state and error banner div
**2.4 Session Export** ([SessionDetailPage.tsx](patherly/frontend/src/pages/SessionDetailPage.tsx)):
- Add toast on successful export: `toast.success('Session exported')`
- Add toast on copy to clipboard: `toast.success('Copied to clipboard')`
- Replace current inline copy feedback with toast
**2.5 Settings Pages**:
- Add success toasts on settings save
- Remove inline success messages if any exist
---
### Phase 3: Standardize Error Handling
**3.1 Global API Error Interceptor** ([client.ts](patherly/frontend/src/api/client.ts)):
```typescript
apiClient.interceptors.response.use(
response => response,
error => {
// Show toast for non-form errors (4xx/5xx)
const message = error.response?.data?.detail || 'An error occurred';
// Don't toast validation errors (handled inline)
if (error.response?.status !== 422) {
toast.error(message);
}
return Promise.reject(error);
}
);
```
**3.2 Remove redundant error handling**:
- Keep form validation errors inline (modal errors)
- Use toast for unexpected/network errors
- Remove page-level error banner states where replaced by toast
---
### Phase 4: Add Copy Feedback Consistency
**4.1 Standardize clipboard operations**:
All "Copy to Clipboard" buttons should:
1. Use `navigator.clipboard.writeText()`
2. Show toast on success: `toast.success('Copied to clipboard')`
3. Show toast on error: `toast.error('Failed to copy')`
4. Remove button state toggle (icon + text change)
**Components to update:**
- [SessionDetailPage.tsx](patherly/frontend/src/pages/SessionDetailPage.tsx) (export copy button)
- [ExportPreviewModal.tsx](patherly/frontend/src/components/session/ExportPreviewModal.tsx) (copy button)
- [AddToFolderMenu.tsx](patherly/frontend/src/components/library/AddToFolderMenu.tsx) (if any copy functionality)
---
## Critical Files to Modify
| File Path | Changes |
|-----------|---------|
| `patherly/frontend/package.json` | Add `sonner` dependency |
| `patherly/frontend/src/main.tsx` | Add `<Toaster>` provider component |
| `patherly/frontend/src/lib/toast.ts` | **NEW FILE** - Toast utility wrapper |
| `patherly/frontend/src/store/themeStore.ts` | Sync theme changes to Toaster |
| `patherly/frontend/src/api/client.ts` | Add global error interceptor |
| `patherly/frontend/src/pages/TreeLibraryPage.tsx` | Add success/error toasts for delete |
| `patherly/frontend/src/pages/TreeEditorPage.tsx` | Replace error banner with toast.promise() |
| `patherly/frontend/src/pages/SessionDetailPage.tsx` | Add export/copy success toasts |
| `patherly/frontend/src/pages/SettingsPage.tsx` | Add settings save toast |
| `patherly/frontend/src/pages/AccountSettingsPage.tsx` | Add account update toast |
| `patherly/frontend/src/components/library/FolderEditModal.tsx` | Add folder save toast, remove inline errors |
| `patherly/frontend/src/components/library/FolderSidebar.tsx` | Add delete folder toast |
| `patherly/frontend/src/components/library/AddToFolderMenu.tsx` | Add add/remove from folder toasts |
| `patherly/frontend/src/components/session/ExportPreviewModal.tsx` | Standardize copy feedback |
---
## Design Patterns and Best Practices
### When to Use Each Toast Type
**Success (green, 4s duration)**:
- Action completed successfully
- Examples: "Tree saved", "Folder deleted", "Exported"
- Only show for user-initiated actions, not automatic operations
**Error (red, 6s duration)**:
- Action failed unexpectedly
- Examples: "Failed to save tree: Network error", "Could not delete folder"
- Include reason when available from API response
**Info (blue, 4s duration)**:
- Neutral information
- Examples: "Session resumed", "Autosave enabled"
- Use sparingly - don't spam user with info toasts
**Loading (infinite duration until dismissed)**:
- Long-running operations (>500ms expected)
- Examples: "Saving tree...", "Exporting session..."
- Use `toast.promise()` to automatically transition to success/error
### Promise Pattern Best Practice
For async operations with loading states:
```typescript
// DON'T do this (manual dismiss logic):
const toastId = toast.loading('Saving...');
try {
await saveTree();
toast.success('Saved', { id: toastId });
} catch (error) {
toast.error('Failed', { id: toastId });
}
// DO this (automatic state transitions):
toast.promise(saveTree(), {
loading: 'Saving tree...',
success: 'Tree saved',
error: (err) => `Failed to save: ${err.message}`
});
```
### Error Message Format
Always provide context:
- ❌ "Error" (too vague)
- ❌ "Failed" (what failed?)
- ✅ "Failed to delete tree: Permission denied"
- ✅ "Could not load folders: Network error"
### When NOT to Use Toasts
Keep inline error messages for:
1. **Form validation errors**: Show next to invalid field
2. **Modal-contained errors**: Errors within a modal workflow
3. **Real-time feedback**: Input validation as user types
4. **Critical blocking errors**: Full-page error states
---
## Verification Plan
### Manual Testing Checklist
**Tree Operations:**
- [ ] Create new tree → "Tree saved" toast appears
- [ ] Edit existing tree → "Tree saved" toast appears
- [ ] Delete tree → Confirm dialog → "Tree deleted" toast appears
- [ ] Delete fails (network off) → "Failed to delete tree" toast with reason
- [ ] Autosave triggers → "Tree saved" toast (via promise pattern)
**Folder Operations:**
- [ ] Create folder → "Folder saved" toast appears
- [ ] Edit folder name → "Folder saved" toast appears
- [ ] Delete folder → "Folder deleted" toast appears
- [ ] Add tree to folder → "Added to [folder name]" toast appears
- [ ] Remove tree from folder → "Removed from [folder name]" toast appears
**Session Operations:**
- [ ] Export session (any format) → "Session exported" toast appears
- [ ] Copy to clipboard → "Copied to clipboard" toast appears
- [ ] Copy fails (permissions) → "Failed to copy" toast appears
- [ ] Download session → "Session exported" toast appears
**Settings:**
- [ ] Save user preferences → "Settings saved" toast appears
- [ ] Update account details → "Account updated" toast appears
**Error Scenarios:**
- [ ] Network offline → API errors show toast with meaningful message
- [ ] Permission denied → Toast shows "Permission denied" reason
- [ ] 500 server error → Toast shows generic "Server error" message
- [ ] 422 validation error → NO toast (inline validation only)
**Dark Mode:**
- [ ] Switch to dark mode → Toast background is dark themed
- [ ] Switch to light mode → Toast background is light themed
- [ ] Toast colors match theme (success green, error red, etc.)
**Accessibility:**
- [ ] Screen reader announces toast messages (ARIA live region)
- [ ] Toasts can be dismissed with keyboard (close button focusable)
- [ ] Toast position doesn't obscure critical UI (top-right safe zone)
- [ ] Color contrast meets WCAG AA standards
**Stacking Behavior:**
- [ ] Multiple toasts stack vertically without overlap
- [ ] Oldest toasts auto-dismiss while newer ones remain
- [ ] Maximum 3-4 toasts visible at once (sonner default)
---
## Rollout Strategy
### Phase 1: Core Infrastructure (Day 1, Morning)
1. Install sonner package
2. Create toast utility wrapper (`lib/toast.ts`)
3. Add Toaster provider to `main.tsx`
4. Sync with theme store
5. Test basic toast functionality in dev
### Phase 2: High-Impact Actions (Day 1, Afternoon)
1. Tree save/delete operations
2. Folder CRUD operations
3. Session export/copy
4. Remove old error banners
### Phase 3: Error Standardization (Day 2, Morning)
1. Add global API error interceptor
2. Clean up redundant error handling
3. Test error scenarios (network offline, permissions, etc.)
### Phase 4: Refinement (Day 2, Afternoon)
1. Verify all toast messages use consistent vocabulary
2. Check dark mode appearance
3. Test accessibility with screen reader
4. Performance check (ensure no memory leaks from unmounted toasts)
5. Update user documentation if needed
---
## Future Enhancements (Out of Scope)
These can be added later if needed:
- **Undo actions**: Toast with "Undo" button for reversible operations
- **Progress toasts**: Show percentage for uploads/exports
- **Grouped toasts**: Collapse multiple similar actions ("3 trees deleted")
- **Persistent notifications**: Critical alerts that don't auto-dismiss
- **Sound effects**: Subtle audio feedback (accessibility feature)
- **Action buttons in toasts**: "View details" or "Retry" buttons
---
## Risks and Mitigation
| Risk | Impact | Mitigation |
|------|--------|------------|
| **Toast spam** | Users annoyed by too many notifications | Only toast user-initiated actions, not automatic events |
| **Theme sync issues** | Toast theme doesn't match app theme | Subscribe to themeStore changes, update Toaster theme prop |
| **Mobile viewport** | Toasts obscure content on small screens | Use `top-right` position (least obtrusive), verify on mobile |
| **Bundle size increase** | +10KB to frontend bundle | Acceptable for significant UX improvement; sonner is already optimized |
| **Breaking existing error handling** | Some errors go unnoticed | Keep inline validation errors; only replace page-level banners |
---
## Success Metrics
**Qualitative:**
- Users report feeling more confident about action completion
- Support tickets decrease for "Did my changes save?" questions
- User feedback mentions improved responsiveness
**Quantitative:**
- 0 console errors related to toast implementation
- Toast render time <50ms (measured in React DevTools)
- No accessibility violations in Lighthouse audit
- Frontend bundle size increase <15KB gzipped
---
## References
- **Sonner Documentation**: https://sonner.emilkowal.ski/
- **shadcn/ui Toast Component**: https://ui.shadcn.com/docs/components/sonner
- **UX Research on Feedback Timing**: Nielsen Norman Group - "Response Times: The 3 Important Limits"
- **Accessibility Guidelines**: WCAG 2.1 Success Criterion 4.1.3 (Status Messages)