From be3bbaf1dc418e3345d60b30bb862813eb1b3588 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 02:10:53 +0000 Subject: [PATCH] [jules] enhance: Add ErrorBoundary with retry support Added a comprehensive Error Boundary system to the web application: - Created `web/components/ErrorBoundary.tsx` with dual-theme support - Integrated `ErrorBoundary` into `web/App.tsx` wrapping `AppRoutes` - Added "Try Again" and "Home" actions for user recovery - Supports dev-mode stack trace display - Maintains consistent UI with Glassmorphism and Neobrutalism themes --- .Jules/changelog.md | 5 ++ .Jules/knowledge.md | 27 ++++++++++ .Jules/todo.md | 8 ++- web/App.tsx | 5 +- web/components/ErrorBoundary.tsx | 89 ++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 web/components/ErrorBoundary.tsx diff --git a/.Jules/changelog.md b/.Jules/changelog.md index d438210..df6536e 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -7,6 +7,11 @@ ## [Unreleased] ### Added +- **Error Boundary System**: Added `ErrorBoundary` component to catch React errors gracefully. + - Displays a user-friendly error card with "Try Again" and "Home" actions. + - Supports both Glassmorphism and Neobrutalism themes. + - Shows stack trace in development mode. + - Wraps `AppRoutes` to prevent white screen of death on route errors. - 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..72074b7 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -87,6 +87,33 @@ colors: { ## Component Patterns +### Error Boundary Pattern + +**Date:** 2026-01-14 +**Context:** Implemented `ErrorBoundary` in `web/components/ErrorBoundary.tsx` + +React Error Boundaries must be class components. However, to use hooks (like `useTheme`), you should split the UI into a functional component (`ErrorFallback`) and render it from the class component. + +```tsx +// Functional component for UI (can use hooks) +const ErrorFallback = ({ error, resetErrorBoundary }) => { + const { style } = useTheme(); // Hooks work here + return ...; +}; + +// Class component for logic +class ErrorBoundary extends Component { + // ... + render() { + if (this.state.hasError) { + return ; + } + return this.props.children; + } +} +``` +**Key Learning:** Wrap `ErrorFallback` in `ThemeWrapper` so the error page inherits the correct background and theme context. + ### Button Component Variants **Date:** 2026-01-01 diff --git a/.Jules/todo.md b/.Jules/todo.md index 894e27f..2a9896a 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 + - Completed: 2026-01-14 - 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 ### Mobile @@ -154,5 +154,9 @@ - Completed: 2026-01-11 - Files modified: `web/pages/Auth.tsx` - Impact: Users know immediately if input is valid via inline error messages and red borders. +- [x] **[ux]** Error boundary with retry for API failures + - Completed: 2026-01-14 + - Files modified: `web/components/ErrorBoundary.tsx`, `web/App.tsx` + - Impact: App recovers gracefully from crashes. _No tasks completed yet. Move tasks here after completion._ diff --git a/web/App.tsx b/web/App.tsx index 1461005..66511c9 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..8d9f3fb --- /dev/null +++ b/web/components/ErrorBoundary.tsx @@ -0,0 +1,89 @@ +import React, { Component, ErrorInfo } from 'react'; +import { ThemeWrapper } from './layout/ThemeWrapper'; +import { Card } from './ui/Card'; +import { Button } from './ui/Button'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; + +interface Props { + children: React.ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error | null, resetErrorBoundary: () => void }) => { + const isDev = import.meta.env.DEV; + + return ( + + + + + + + + + + We encountered an unexpected error. Please try again. + + + {isDev && error && ( + + {error.toString()} + + )} + + + + + Try Again + + window.location.href = '/'} + > + + Home + + + + + + + ); +}; + +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); + } + + private handleReset = () => { + this.setState({ hasError: false, error: null }); + window.location.reload(); + }; + + public render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +}
+ We encountered an unexpected error. Please try again. +