`);
}
public override destroy(): void {
if (this.refreshTimer) clearInterval(this.refreshTimer);
super.destroy();
}
}
```
### Option C — Plain fetch, no resilience layer
For non-critical panels or mock/dev data where retries add no value:
```typescript
private async fetchData(): Promise {
if (this.isFetching) return;
this.setFetching(true);
try {
const resp = await fetch('/api/config');
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
this.render(data);
} catch (err) {
this.showError('Could not load config', () => this.fetchData());
} finally {
this.setFetching(false);
}
}
```
---
## Decision Guide — Which resilience layer?
```
Does the project have an existing fetchX() service function?
YES -> wrap it: breaker.execute(() => fetchX(), defaultValue) <- Option A
NO -> does the endpoint fail intermittently under normal load?
YES -> use fetchWithRetry() for per-request retry <- Option B
NO -> plain fetch() with showError/retry is enough <- Option C
Is the endpoint unreliable or rate-limited for extended periods?
YES -> add CircuitBreaker (prevents hammering during outages)
NO -> fetchWithRetry or plain fetch is sufficient
Do you need panel state (size, collapsed) to survive page reloads?
YES -> call this.loadState() in constructor (after super())
call this.saveState() after a successful render
```
---
## Protected API Reference
All members below are accessible from subclasses without importing or inspecting `Panel.ts` directly:
| Member / Method | Type | Description |
|---|---|---|
| `element` | `HTMLElement` | Outer container div (`.panel`) |
| `header` | `HTMLElement` | Header bar div — append extra controls here |
| `content` | `HTMLElement` | Content area div (`.panel-content`) |
| `countEl` | `HTMLElement \| null` | Count badge, or `null` if `showCount` not set |
| `panelId` | `string` | The `id` from `PanelOptions` |
| `retryAttempts` | `number` | Current retry count for `fetchWithRetry` |
| `retryDelay` | `number` | Current delay (ms) for `fetchWithRetry`; reset to 1000 before retry |
| `isFetching` | `boolean` (getter) | `true` while an async fetch is in progress |
| `setFetching(v)` | `void` | Set the fetching guard flag |
| `showLoading(msg?)` | `void` | Replace content with a loading spinner |
| `showError(msg?, onRetry?)` | `void` | Replace content with an error state and optional retry button |
| `setContent(html)` | `void` | Set raw HTML into the content area |
| `setCount(n)` | `void` | Update the count badge (no-op if `countEl` is null) |
| `fetchWithRetry(url)` | `Promise` | Fetch with exponential-backoff retry (3 attempts) |
| `saveState()` | `void` | Persist expanded/size state to localStorage |
| `loadState()` | `void` | Restore state from localStorage |
> **Example — appending a button to the header in a subclass:**
> ```typescript
> constructor() {
> super({ id: 'insights', title: 'Insights', className: 'panel-wide' });
> const btn = document.createElement('button');
> btn.className = 'panel-refresh-btn';
> btn.textContent = 'Refresh';
> btn.addEventListener('click', () => this.generate());
> this.header.appendChild(btn); // 'header' is protected — safe to use here
> }
> ```
---
## Key Patterns (checklist)
1. **Constructor**: call `super()` with `PanelOptions`, optionally call `loadState()`, then trigger initial data fetch.
2. **fetchData()**: async, use `isFetching` guard, wrap service calls with `breaker.execute()` when available.
3. **render()**: build HTML strings, call `this.setContent(html)`, then `this.saveState()`.
4. **Error recovery**: `showError(message, () => this.fetchData())` — retry button is wired automatically.
5. **destroy()**: clear all `setInterval` / `setTimeout` handles, then call `super.destroy()`.
6. **Sparklines**: import `miniSparkline` from `src/utils/sparkline.ts`, embed return value directly in HTML template strings.
7. **Header controls**: `this.header.appendChild(el)` — `header` is `protected`, safe to use in subclasses.
---
## localStorage Utilities (optional helpers)
See `examples/localStorageUtils.ts` for typed wrappers when you need to persist additional per-panel data beyond the built-in size/collapsed state (e.g. user filter selections, last-viewed item).