From f026829c9944a48bdf95e054a4fb1b5a662d2bd3 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 01:57:24 +0000 Subject: [PATCH] [jules] ux: Add ErrorBoundary with retry mechanism --- .Jules/changelog.md | 1 + .Jules/knowledge.md | 24 ++++++++ .Jules/todo.md | 8 +-- web/App.tsx | 3 + web/components/ErrorBoundary.tsx | 97 ++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 web/components/ErrorBoundary.tsx diff --git a/.Jules/changelog.md b/.Jules/changelog.md index d438210..117e786 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,7 @@ ## [Unreleased] ### Added +- Global `ErrorBoundary` component with dual-theme support to catch runtime errors and provide a "Try Again" or "Go Home" recovery option. - 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..0f60731 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -459,6 +459,30 @@ _Document errors and their solutions here as you encounter them._ ## Recent Implementation Reviews +### ✅ Successful PR Pattern: Error Boundary Implementation (#237) + +**Date:** 2026-01-13 +**Context:** Review of Error Boundary implementation + +**What was implemented:** +1. Created `ErrorBoundary` class component (required for error catching) +2. Created `ErrorFallback` functional component (for hooks/theme support) +3. Integrated into `App.tsx` wrapping `AppRoutes` but inside Providers +4. Built-in dual-theme support (Neo/Glass) + +**Why it succeeded:** +- ✅ Separated Logic (Class) from UI (Function) to support hooks +- ✅ Leveraged existing `useTheme` context +- ✅ Provided clear recovery options (Reload, Go Home) +- ✅ Integrated safe fallback UI + +**Key learnings:** +- React Error Boundaries *must* be class components +- To use hooks (like `useTheme`) in the fallback UI, create a separate functional component +- Wrap specific routes rather than the entire app to keep Providers active (like Theme/Auth) + +--- + ### ✅ Successful PR Pattern: Toast Notification System (#227) **Date:** 2026-01-13 diff --git a/.Jules/todo.md b/.Jules/todo.md index 894e27f..104a226 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 - - Files: Create `web/components/ErrorBoundary.tsx`, wrap app +- [x] **[ux]** Error boundary with retry for API failures + - Completed: 2026-01-13 + - Files modified: `web/components/ErrorBoundary.tsx`, `web/App.tsx` - Context: Catch errors gracefully with retry button - Impact: App doesn't crash, users can recover - - Size: ~60 lines - - Added: 2026-01-01 + - Size: ~80 lines ### 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..e1c17a7 --- /dev/null +++ b/web/components/ErrorBoundary.tsx @@ -0,0 +1,97 @@ +import React, { Component, ReactNode } from 'react'; +import { useTheme } from '../contexts/ThemeContext'; +import { THEMES } from '../constants'; +import { Button } from './ui/Button'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; + +interface ErrorFallbackProps { + error: Error | null; + resetError: () => void; +} + +const ErrorFallback: React.FC = ({ error, resetError }) => { + const { style, mode } = useTheme(); + + const isNeo = style === THEMES.NEOBRUTALISM; + const isDark = mode === 'dark'; + + let containerClasses = ""; + if (isNeo) { + containerClasses = `border-2 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)] bg-white ${isDark ? 'bg-zinc-800 text-white' : 'text-black'}`; + } else { + containerClasses = `rounded-2xl border border-white/10 shadow-2xl backdrop-blur-xl ${isDark ? 'bg-black/60 text-white' : 'bg-white/60 text-gray-900'}`; + } + + return ( + + + + + + + + + + Something went wrong + + + + {error?.message || "An unexpected error occurred. We're sorry for the inconvenience."} + + + + + + Try Again + + + window.location.href = '/'} + className="w-full" + > + + Back to Home + + + + + ); +}; + +interface ErrorBoundaryProps { + children: ReactNode; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + } + + resetError = () => { + this.setState({ hasError: false, error: null }); + window.location.reload(); + }; + + render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +}
+ {error?.message || "An unexpected error occurred. We're sorry for the inconvenience."} +