diff --git a/.Jules/changelog.md b/.Jules/changelog.md
index d438210..16a7b0c 100644
--- a/.Jules/changelog.md
+++ b/.Jules/changelog.md
@@ -7,6 +7,14 @@
## [Unreleased]
### Added
+- **Error Boundary System:** Implemented a global React Error Boundary to catch render errors gracefully.
+ - **Features:**
+ - Dual-theme support (Glassmorphism & Neobrutalism) for the error fallback UI.
+ - "Retry" button to reset error state and re-render.
+ - "Home" button to navigate back to safety.
+ - Captures errors in `AppRoutes` and displays a user-friendly message instead of a white screen.
+ - **Technical:** Created `web/components/ErrorBoundary.tsx` using a hybrid Class+Functional approach to support Hooks in the fallback UI. Integrated into `web/App.tsx`.
+
- 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..ad48a44 100644
--- a/.Jules/knowledge.md
+++ b/.Jules/knowledge.md
@@ -87,6 +87,33 @@ colors: {
## Component Patterns
+### Error Boundary Pattern
+
+**Date:** 2026-01-14
+**Context:** Implementing global error handling
+
+React Error Boundaries must be class components to use `componentDidCatch`. However, to use hooks (like `useTheme` or `useNavigate`), you should split the fallback UI into a separate functional component.
+
+```tsx
+// 1. Functional Fallback (uses hooks)
+const ErrorFallback = ({ error, resetErrorBoundary }) => {
+ const { style } = useTheme();
+ return
...
;
+}
+
+// 2. Class Boundary (handles logic)
+class ErrorBoundary extends Component {
+ render() {
+ if (this.state.hasError) {
+ return ;
+ }
+ return this.props.children;
+ }
+}
+```
+
+**Testing Tip:** React Error Boundaries do **not** catch errors in event handlers. To verify them, you must throw inside the `render` method (e.g., `if (shouldThrow) throw new Error()`).
+
### Button Component Variants
**Date:** 2026-01-01
@@ -459,6 +486,30 @@ _Document errors and their solutions here as you encounter them._
## Recent Implementation Reviews
+### ✅ Successful PR Pattern: Error Boundary (#240)
+
+**Date:** 2026-01-14
+**Context:** Implementing global error handling
+
+**What was implemented:**
+1. Created `ErrorBoundary.tsx` with class component + functional fallback.
+2. Wrapped `AppRoutes` in `App.tsx` with `ErrorBoundary`.
+3. Styled fallback UI for both Neobrutalism and Glassmorphism.
+4. Added "Retry" and "Go Home" options.
+
+**Why it succeeded:**
+- ✅ Complete system (Component + Integration + Styling).
+- ✅ Solved "React Hooks inside Class Component" by splitting logic.
+- ✅ Verified using a temporary render-phase throw (not event handler).
+- ✅ Maintained dual-theme support.
+
+**Key learnings:**
+- Error Boundaries only catch errors in Render/Lifecycle, NOT event handlers.
+- Verification requires triggering an error during render (e.g., conditional throw).
+- Must wrap Router if using `useNavigate` or `Link` in fallback.
+
+---
+
### ✅ Successful PR Pattern: Toast Notification System (#227)
**Date:** 2026-01-13
diff --git a/.Jules/todo.md b/.Jules/todo.md
index 894e27f..4539a8a 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-14
+ - Files: Created `web/components/ErrorBoundary.tsx`, wrapped app
- 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
@@ -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 doesn't crash, users can recover
_No tasks completed yet. Move tasks here after completion._
diff --git a/web/App.tsx b/web/App.tsx
index 1461005..0a6d4c6 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,7 +52,9 @@ const App = () => {
-
+
+
+
diff --git a/web/components/ErrorBoundary.tsx b/web/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..b9be578
--- /dev/null
+++ b/web/components/ErrorBoundary.tsx
@@ -0,0 +1,86 @@
+import React, { Component, ErrorInfo, ReactNode } from 'react';
+import { AlertTriangle, RefreshCw, Home } from 'lucide-react';
+import { Button } from './ui/Button';
+import { Card } from './ui/Card';
+import { useTheme } from '../contexts/ThemeContext';
+import { THEMES } from '../constants';
+import { useNavigate } from 'react-router-dom';
+
+interface Props {
+ children: ReactNode;
+}
+
+interface State {
+ hasError: boolean;
+ error: Error | null;
+}
+
+// Functional component to access hooks
+const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error | null, resetErrorBoundary: () => void }) => {
+ const { style } = useTheme();
+ const navigate = useNavigate();
+
+ const isNeo = style === THEMES.NEOBRUTALISM;
+
+ const handleHome = () => {
+ resetErrorBoundary();
+ navigate('/');
+ };
+
+ return (
+