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:
427
docs/plans/2026-02-07-notification-system-design.md
Normal file
427
docs/plans/2026-02-07-notification-system-design.md
Normal 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)
|
||||
|
||||
Reference in New Issue
Block a user