Skip to content
Draft
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
1 change: 1 addition & 0 deletions .Jules/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
## [Unreleased]

### Added
- Error Boundary system (`ErrorBoundary`, `ErrorFallback`) to catch and handle runtime errors gracefully with dual-theme support and retry mechanism.
- 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
22 changes: 22 additions & 0 deletions .Jules/knowledge.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,28 @@ Use `AnimatePresence` for exit animations.

---

## Verification Patterns

### Error Boundary Verification

**Date:** 2026-01-14
**Context:** Verifying `ErrorBoundary` works as expected

Since Error Boundaries only catch errors during rendering, lifecycle methods, or constructors (and not in event handlers or async code by default, though React handles some), verifying them requires triggering a render error.

**How to verify:**
1. Temporarily modify a component (e.g., `Auth.tsx`) to throw an error inside the render body:
```tsx
if (typeof window !== 'undefined') {
throw new Error('Test Error');
}
```
2. Navigate to that page.
3. Verify the fallback UI appears.
4. **Revert the change** immediately after verification.

---

## Best Practices Learned

### 1. Accessibility First
Expand Down
4 changes: 2 additions & 2 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
- [x] **[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
- Completed: 2026-01-14

### Mobile

Expand Down
3 changes: 3 additions & 0 deletions 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,8 +52,10 @@ const App = () => {
<ToastProvider>
<AuthProvider>
<HashRouter>
<ErrorBoundary>
<AppRoutes />
<ToastContainer />
</ErrorBoundary>
</HashRouter>
</AuthProvider>
</ToastProvider>
Expand Down
95 changes: 95 additions & 0 deletions web/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
import { THEMES } from '../constants';
import { Button } from './ui/Button';

interface Props {
children: ReactNode;
}

interface State {
hasError: boolean;
error: Error | null;
}

const ErrorFallback: React.FC<{ error: Error | null; resetErrorBoundary: () => void }> = ({
error,
resetErrorBoundary
}) => {
const { style } = useTheme();
const isNeo = style === THEMES.NEOBRUTALISM;

return (
<div className="min-h-[50vh] w-full flex items-center justify-center p-4">
<div
className={`
max-w-md w-full p-8 text-center flex flex-col items-center gap-6
${isNeo
? 'bg-white border-4 border-black shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]'
: 'bg-white/10 backdrop-blur-md border border-white/20 rounded-2xl shadow-xl'
}
`}
>
<div className={`
p-4 rounded-full
${isNeo ? 'bg-neo-second border-2 border-black' : 'bg-red-500/20 text-red-200'}
`}>
<AlertTriangle size={48} className={isNeo ? 'text-black' : 'text-red-400'} />
</div>

<div className="space-y-2">
<h2 className={`text-2xl font-bold ${isNeo ? 'font-black uppercase' : 'text-white'}`}>
Something went wrong
</h2>
<p className={isNeo ? 'text-black font-medium' : 'text-gray-300'}>
We encountered an unexpected error. Try reloading the page to get back on track.
</p>
</div>

{error && process.env.NODE_ENV === 'development' && (
<div className="w-full text-left p-4 bg-black/5 rounded text-xs font-mono overflow-auto max-h-32">
{error.toString()}
</div>
)}

<Button
onClick={resetErrorBoundary}
variant="primary"
className="w-full"
>
<RefreshCw size={18} />
Reload Application
</Button>
</div>
</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 render() {
if (this.state.hasError) {
return (
<ErrorFallback
error={this.state.error}
resetErrorBoundary={() => window.location.reload()}
/>
);
}

return this.props.children;
}
}
Loading