From e63917b2c25162963e28ea15f4fc1c05bbe6f6e1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 15 Jan 2026 01:58:03 +0000 Subject: [PATCH] [jules] ux: Add global error boundary with retry --- .Jules/changelog.md | 1 + .Jules/knowledge.md | 14 +++++ .Jules/todo.md | 10 ++-- web/App.tsx | 19 +++--- web/components/ErrorBoundary.tsx | 100 +++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 web/components/ErrorBoundary.tsx diff --git a/.Jules/changelog.md b/.Jules/changelog.md index d438210..826dbce 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,7 @@ ## [Unreleased] ### Added +- **Error Boundary:** Added a global `ErrorBoundary` component that catches unexpected errors and displays a friendly, dual-themed fallback UI with a retry action. - 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..99f13e8 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -44,6 +44,20 @@ mobile/ ## Theming System +### Error Boundary Pattern + +**Date:** 2026-02-01 +**Context:** Implemented global error handling + +**Implementation:** +- Use Class Component for `componentDidCatch` +- Use inner Functional Component for UI to support hooks (`useTheme`) +- Place inside `ThemeProvider` but outside `Router` or deeply nested providers +- Use `import.meta.env.DEV` to show stack traces in development +- Ensure fallback UI supports both `NEOBRUTALISM` and `GLASSMORPHISM` + +**Files:** `web/components/ErrorBoundary.tsx` + ### Web Dual-Theme Pattern **Date:** 2026-01-01 **Context:** Understanding theme-switching mechanism diff --git a/.Jules/todo.md b/.Jules/todo.md index 894e27f..3b4b5ba 100644 --- a/.Jules/todo.md +++ b/.Jules/todo.md @@ -34,12 +34,10 @@ - Impact: Guides new users, makes app feel polished - Size: ~70 lines -- [ ] **[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 +- [x] **[ux]** Error boundary with retry for API failures + - Completed: 2026-02-01 + - Files modified: `web/components/ErrorBoundary.tsx`, `web/App.tsx` + - Impact: App doesn't crash, users can recover gracefully ### Mobile diff --git a/web/App.tsx b/web/App.tsx index 1461005..d083abe 100644 --- a/web/App.tsx +++ b/web/App.tsx @@ -5,6 +5,7 @@ import { ThemeWrapper } from './components/layout/ThemeWrapper'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import { ThemeProvider } from './contexts/ThemeContext'; import { ToastProvider } from './contexts/ToastContext'; +import { ErrorBoundary } from './components/ErrorBoundary'; import { ToastContainer } from './components/ui/Toast'; import { Auth } from './pages/Auth'; import { Dashboard } from './pages/Dashboard'; @@ -48,14 +49,16 @@ const AppRoutes = () => { const App = () => { return ( - - - - - - - - + + + + + + + + + + ); }; diff --git a/web/components/ErrorBoundary.tsx b/web/components/ErrorBoundary.tsx new file mode 100644 index 0000000..bc7649b --- /dev/null +++ b/web/components/ErrorBoundary.tsx @@ -0,0 +1,100 @@ +import React, { Component, 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; +} + +const ErrorFallback = ({ error, resetErrorBoundary }: { error?: Error, resetErrorBoundary: () => void }) => { + const { style, mode } = useTheme(); + const isNeo = style === THEMES.NEOBRUTALISM; + const isDark = mode === 'dark'; + + return ( +
+
+
+
+ +
+

Something went wrong

+

+ We encountered an unexpected error. Please try reloading the page. +

+ + {import.meta.env.DEV && error && ( +
+

Error details:

+
+                {error.message}
+              
+
+ )} +
+ + +
+
+ ); +}; + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('ErrorBoundary caught an error:', error, errorInfo); + } + + resetErrorBoundary = () => { + this.setState({ hasError: false, error: undefined }); + window.location.reload(); + }; + + render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +}