diff --git a/.Jules/changelog.md b/.Jules/changelog.md index d438210..59e26ef 100644 --- a/.Jules/changelog.md +++ b/.Jules/changelog.md @@ -12,8 +12,11 @@ - Comprehensive `EmptyState` component for Groups and Friends pages to better guide new users. - Toast notification system (`ToastContext`, `Toast` component) for providing non-blocking user feedback. - Keyboard navigation support for Groups page, enabling accessibility for power users. +- Global `ErrorBoundary` component to catch React render errors and display a dual-theme friendly fallback UI. +- "Try Again" and "Back to Home" recovery actions for crashed application states. ### Changed +- Wrapped `AppRoutes` in `web/App.tsx` with `ErrorBoundary` to ensure global error catching coverage. - Updated JULES_PROMPT.md based on review of successful PRs: - Emphasized complete system implementation over piecemeal changes - Added best practices from successful PRs (Toast system, keyboard navigation iteration) diff --git a/.Jules/knowledge.md b/.Jules/knowledge.md index d69c659..4ff96a2 100644 --- a/.Jules/knowledge.md +++ b/.Jules/knowledge.md @@ -619,6 +619,29 @@ _Document errors and their solutions here as you encounter them._ --- +### ✅ Successful PR Pattern: Error Boundary with Dual Theme + +**Date:** 2026-01-14 +**Context:** Implementing global error handling + +**What was implemented:** +1. Created `ErrorBoundary` class component (required for `componentDidCatch`). +2. Created separate `ErrorFallback` functional component to support hooks (`useTheme`, `useNavigate` equivalent). +3. Wrapped `AppRoutes` but kept `ToastContainer` outside to allow notifications during crashes. +4. Implemented dual-theme styles (Glassmorphism/Neobrutalism) for the fallback UI. + +**Why it succeeded:** +- ✅ Solved the "Class components can't use hooks" limitation by separating logic and UI. +- ✅ Preserved the dual-theme requirement even in error states. +- ✅ Verified using a temporary Playwright script that simulated specific render errors. + +**Key learnings:** +- **Hooks in Error Boundaries:** You cannot use hooks in the `ErrorBoundary` class itself. Pass props or use a child component for the UI. +- **Verification:** To verify `componentDidCatch`, you must throw a real error in the render phase. Event handler errors are NOT caught by React boundaries. +- **Playwright Mocking:** When mocking routes for verification, ensure specific paths (e.g., `/users/me/balance-summary`) are registered *after* general wildcards (e.g., `**/users/me`) if the generic one handles the request first, or use strict matching. + +--- + ## Dependencies Reference ### Web diff --git a/.Jules/todo.md b/.Jules/todo.md index 894e27f..7caafa4 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-01-14 + - Files modified: `web/components/ErrorBoundary.tsx`, `web/App.tsx` + - Impact: Catches render errors, prevents white screens, and provides "Try Again" / "Back to Home" recovery options with dual-theme support. ### Mobile 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..29d4764 --- /dev/null +++ b/web/components/ErrorBoundary.tsx @@ -0,0 +1,104 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; +import { Button } from './ui/Button'; +import { useTheme } from '../contexts/ThemeContext'; +import { THEMES } from '../constants'; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +// Functional component for the fallback UI to use Hooks +const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error | null, resetErrorBoundary: () => void }) => { + const { style } = useTheme(); + + const isNeo = style === THEMES.NEOBRUTALISM; + + const handleHome = () => { + window.location.href = window.location.origin; + }; + + const containerClasses = isNeo + ? "bg-neo-bg border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]" + : "bg-white/10 backdrop-blur-xl border border-white/20 shadow-2xl"; + + const textClasses = isNeo + ? "text-black font-mono" + : "text-white"; + + return ( +
+
+
+ +
+ +
+

+ Something went wrong +

+

+ We encountered an unexpected error. +

+ {error && ( +
+ {error.message} +
+ )} +
+ +
+ + +
+
+
+ ); +}; + +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 resetErrorBoundary = () => { + this.setState({ hasError: false, error: null }); + window.location.reload(); // Hard reload to ensure clean state + }; + + public render() { + if (this.state.hasError) { + return ; + } + + return this.props.children; + } +}