Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
51 changes: 51 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div className={style...}>...</div>;
}

// 2. Class Boundary (handles logic)
class ErrorBoundary extends Component {
render() {
if (this.state.hasError) {
return <ErrorFallback ... />;
}
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
Expand Down Expand Up @@ -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
Expand Down
12 changes: 8 additions & 4 deletions .Jules/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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._
Comment on lines +157 to 162
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stale placeholder text remains after completed tasks.

Line 162 states "No tasks completed yet. Move tasks here after completion." but there are already four completed tasks listed above it. This placeholder should be removed.

Suggested fix
 - [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._
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- [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._
- [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
🤖 Prompt for AI Agents
In @.Jules/todo.md around lines 157 - 162, Remove the stale placeholder line
"_No tasks completed yet. Move tasks here after completion._" from
.Jules/todo.md so the file reflects the actual completed tasks; leave the
completed tasks entries (e.g., the checklist item for Error boundary with retry)
intact and ensure no other surrounding content is unintentionally altered.

5 changes: 4 additions & 1 deletion web/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,7 +52,9 @@ const App = () => {
<ToastProvider>
<AuthProvider>
<HashRouter>
<AppRoutes />
<ErrorBoundary>
<AppRoutes />
</ErrorBoundary>
<ToastContainer />
</HashRouter>
</AuthProvider>
Expand Down
86 changes: 86 additions & 0 deletions web/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="min-h-[60vh] flex items-center justify-center p-4">
<Card className="max-w-md w-full text-center">
<div className="flex flex-col items-center gap-4">
<div className={`p-4 rounded-full ${isNeo ? 'bg-red-100 border-2 border-black' : 'bg-red-500/10'}`}>
<AlertTriangle size={48} className="text-red-500" aria-hidden="true" />
</div>

<div className="space-y-2">
<h2 className="text-2xl font-bold" role="alert">Something went wrong</h2>
<p className="text-sm opacity-80 break-words max-h-32 overflow-y-auto">
{error?.message || "An unexpected error occurred."}
</p>
</div>

<div className="flex gap-3 mt-4 w-full justify-center">
<Button variant="ghost" onClick={handleHome} className="flex-1">
<Home size={18} />
Home
</Button>
<Button variant="primary" onClick={resetErrorBoundary} className="flex-1">
<RefreshCw size={18} />
Retry
</Button>
</div>
</div>
</Card>
</div>
);
};

export class ErrorBoundary extends Component<Props, State> {
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 });
};

public render() {
if (this.state.hasError) {
return <ErrorFallback error={this.state.error} resetErrorBoundary={this.resetErrorBoundary} />;
}

return this.props.children;
}
}
Loading