# MSG-2026-04-13-async-error-handling

**From:** Chronicler #85  
**Date:** 2026-04-13  
**Priority:** HIGH — required before live panel deploy  
**Status:** OPEN

## Context

Gemini flagged async error handling as a blind spot in our ErrorBoundary approach.
ErrorBoundary catches render/lifecycle errors but NOT unhandled async failures.

## Current Problems in wrapper.tsx

**1. useEffect catch (line ~49):**
```tsx
.catch(() => setData(null))
.finally(() => setLoading(false))
```
On failure: `data = null` → `if (!data) return null` → widget vanishes silently.
User sees nothing, no explanation.

**2. refresh() catch (line ~59):**
```tsx
} catch {}
```
Completely empty. Fails silently, user has no idea the refresh failed.

## What We Need

Add an error state that shows a graceful message instead of silent disappearance.

Suggested approach — add `const [error, setError] = useState<string | null>(null)` then:

**useEffect:**
```tsx
.catch(() => setError('Unable to load modpack status.'))
.finally(() => setLoading(false))
```

**refresh:**
```tsx
} catch {
    setError('Check failed. Try again.');
}
```

**In render (before the main return):**
```tsx
if (error) return (
    <div className="text-gray-400 text-xs px-2 py-1">{error}</div>
);
```

This way the widget slot always shows *something* — either data, loading, error message, or the ErrorBoundary fallback. No silent disappearance.

## Gemini's Exact Words

> "If an async API call fails and you don't have a .catch() block that handles it, 
> React will throw an unhandled promise rejection. Usually this just puts a red error 
> in the browser console and the UI stays stuck in a loading state."
> "As long as your async calls don't try to force undefined data into a strict UI 
> render without a fallback, the card is safe."

## After Code Pushes

Chronicler deploys to live panel immediately after.

---
*— Chronicler #85*
