diff --git a/.Jules/changelog.md b/.Jules/changelog.md index d438210..8264430 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,7 @@ ## [Unreleased] ### Added +- Error Boundary system (`ErrorBoundary`, `ErrorFallback`) to catch and handle runtime errors gracefully with dual-theme support and retry mechanism. - Inline form validation in Auth page with real-time feedback and proper ARIA accessibility support (`aria-invalid`, `aria-describedby`, `role="alert"`). - Dashboard skeleton loading state (`DashboardSkeleton`) to improve perceived performance during data fetch. - Comprehensive `EmptyState` component for Groups and Friends pages to better guide new users. diff --git a/.Jules/knowledge.md b/.Jules/knowledge.md index d69c659..6195ced 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -338,6 +338,28 @@ Use `AnimatePresence` for exit animations. --- +## Verification Patterns + +### Error Boundary Verification + +**Date:** 2026-01-14 +**Context:** Verifying `ErrorBoundary` works as expected + +Since Error Boundaries only catch errors during rendering, lifecycle methods, or constructors (and not in event handlers or async code by default, though React handles some), verifying them requires triggering a render error. + +**How to verify:** +1. Temporarily modify a component (e.g., `Auth.tsx`) to throw an error inside the render body: + ```tsx + if (typeof window !== 'undefined') { + throw new Error('Test Error'); + } + ``` +2. Navigate to that page. +3. Verify the fallback UI appears. +4. **Revert the change** immediately after verification. + +--- + ## Best Practices Learned ### 1. Accessibility First diff --git a/.Jules/todo.md b/.Jules/todo.md index 894e27f..41e5eb5 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -34,12 +34,12 @@ - Impact: Guides new users, makes app feel polished - Size: ~70 lines -- [ ] **[ux]** Error boundary with retry for API failures +- [x] **[ux]** Error boundary with retry for API failures - Files: Create `web/components/ErrorBoundary.tsx`, wrap app - Context: Catch errors gracefully with retry button - Impact: App doesn't crash, users can recover - Size: ~60 lines - - Added: 2026-01-01 + - Completed: 2026-01-14 ### Mobile diff --git a/web/App.tsx b/web/App.tsx index 1461005..63612e0 100644 --- a/web/App.tsx +++ b/web/App.tsx @@ -6,6 +6,7 @@ import { AuthProvider, useAuth } from './contexts/AuthContext'; import { ThemeProvider } from './contexts/ThemeContext'; import { ToastProvider } from './contexts/ToastContext'; import { ToastContainer } from './components/ui/Toast'; +import { ErrorBoundary } from './components/ErrorBoundary'; import { Auth } from './pages/Auth'; import { Dashboard } from './pages/Dashboard'; import { Friends } from './pages/Friends'; @@ -51,8 +52,10 @@ const App = () => { + + diff --git a/web/components/ErrorBoundary.tsx b/web/components/ErrorBoundary.tsx new file mode 100644 index 0000000..9c7ee72 --- /dev/null +++ b/web/components/ErrorBoundary.tsx @@ -0,0 +1,95 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { AlertTriangle, RefreshCw } from 'lucide-react'; +import { useTheme } from '../contexts/ThemeContext'; +import { THEMES } from '../constants'; +import { Button } from './ui/Button'; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +const ErrorFallback: React.FC<{ error: Error | null; resetErrorBoundary: () => void }> = ({ + error, + resetErrorBoundary +}) => { + const { style } = useTheme(); + const isNeo = style === THEMES.NEOBRUTALISM; + + return ( +
+
+
+ +
+ +
+

+ Something went wrong +

+

+ We encountered an unexpected error. Try reloading the page to get back on track. +

+
+ + {error && process.env.NODE_ENV === 'development' && ( +
+ {error.toString()} +
+ )} + + +
+
+ ); +}; + +export class ErrorBoundary extends Component { + public state: State = { + hasError: false, + error: null + }; + + public static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Uncaught error:', error, errorInfo); + } + + public render() { + if (this.state.hasError) { + return ( + window.location.reload()} + /> + ); + } + + return this.props.children; + } +}