diff --git a/docs/superpowers/specs/2026-04-17-failure-display-design.md b/docs/superpowers/specs/2026-04-17-failure-display-design.md
new file mode 100644
index 0000000..8ad8dd0
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-17-failure-display-design.md
@@ -0,0 +1,238 @@
+# Failure Display — Design Spec
+
+**Date:** 2026-04-17
+**Scope:** Define a single "failing source" state and surface it on the Dashboard as per-platform health bars plus a filtered failing-source list. Make the threshold configurable via Settings.
+
+---
+
+## Motivation
+
+The user is seeing a large number of source failures and needs a way to spot which platforms are affected at a glance. Today, the Dashboard's "Sources Needing Attention" card shows any source with a failed download in the last 7 days — a transient, noisy definition. A single bad run puts a source on the list; a later success does nothing to clear it.
+
+This spec introduces a stricter, self-clearing definition: a source is *failing* when it has N consecutive failures (default 5), and a single success resets the counter. That semantic already exists in the data model — `Source.error_count` increments on failure and is set to 0 on success — so this is a presentation change with one small settings addition, not a schema change.
+
+---
+
+## Design
+
+### Backend
+
+**File:** `backend/app/models/setting.py`
+
+Add one entry to `DEFAULT_SETTINGS`:
+
+```python
+DEFAULT_SETTINGS = {
+ # ... existing keys unchanged ...
+ "dashboard.failure_threshold": 5,
+}
+```
+
+No migration. The `settings` table is a key-value store and `get_setting_value(session, key, default)` already falls back through `DEFAULT_SETTINGS`. No new endpoint — the existing `GET /api/settings` returns it in the merged `settings` dict.
+
+### Settings page — `frontend/src/views/Settings.vue`
+
+Add a new section above the Notifications divider:
+
+```vue
+
+
+
Dashboard
+
+
+
+
+
+```
+
+The existing save handler in `Settings.vue` already patches any key present in the `settings` object, so no handler changes are needed.
+
+### Dashboard — `frontend/src/views/Dashboard.vue`
+
+Replace the body of the existing "Sources Needing Attention" card. The card title, outer shell, and card actions remain unchanged. The inner `v-card-text` is rewritten.
+
+#### New computeds
+
+```js
+// Read threshold from settings store, default 5 if unset
+const settingsStore = useSettingsStore()
+const failureThreshold = computed(() =>
+ settingsStore.settings?.['dashboard.failure_threshold'] ?? 5
+)
+
+// Fixed platform display order
+const PLATFORM_ORDER = [
+ 'patreon', 'subscribestar', 'hentaifoundry',
+ 'discord', 'pixiv', 'deviantart',
+]
+
+// Per-platform health: { platform, total, failing } for platforms with ≥1 enabled source
+const platformHealth = computed(() => {
+ const enabled = sourcesStore.sources.filter(s => s.enabled)
+ return PLATFORM_ORDER
+ .map(platform => {
+ const sources = enabled.filter(s => s.platform === platform)
+ if (sources.length === 0) return null // hide zero-enabled platforms
+ const failing = sources.filter(s => (s.error_count || 0) >= failureThreshold.value).length
+ return { platform, total: sources.length, failing }
+ })
+ .filter(Boolean)
+})
+
+// Sources currently in a failing state, most-recent-check first
+const failingSources = computed(() => {
+ return sourcesStore.sources
+ .filter(s => s.enabled && (s.error_count || 0) >= failureThreshold.value)
+ .sort((a, b) => new Date(b.last_check || 0) - new Date(a.last_check || 0))
+})
+
+const totalFailingCount = computed(() => failingSources.value.length)
+```
+
+`useSettingsStore` already exists at `frontend/src/stores/settings.js` with `fetchSettings()` and a reactive `settings` ref. `Dashboard.vue` must call `settingsStore.fetchSettings()` in `loadDashboardData` (alongside the existing `fetchSources`/`fetchStats`/etc calls) so `settings` is populated before the threshold is read.
+
+#### Template — replace `v-card-text` body
+
+```vue
+
+
+
+
Platform Health
+
+
+ {{ getPlatformIcon(row.platform) }}
+
+
{{ row.platform }}
+
+
+ {{ row.failing }}/{{ row.total }}
+
+
+
+
+
+
+
+
+
+
+ | Source |
+ Consecutive failures |
+ Last check |
+ Actions |
+
+
+
+
+ | {{ getSourceLabel(source) }} |
+
+ {{ source.error_count }}
+ |
+ {{ formatDate(source.last_check) }} |
+
+
+ Retry
+
+ View
+ |
+
+
+
+
+ No sources in failing state
+
+
+```
+
+#### Header chip
+
+The card title already has:
+```vue
+
+ {{ sourcesWithErrors.length }}
+
+```
+
+Swap the binding to `totalFailingCount`:
+```vue
+
+ {{ totalFailingCount }}
+
+```
+
+#### Removed code
+
+- `sourcesWithErrors` computed (derived from recent activity) is no longer used. Delete it.
+- The existing `v-else` empty state block ("All sources healthy" big icon) is removed — its role is now played by the always-visible bars plus the small inline "No sources in failing state" line.
+
+#### Minor CSS (scoped block)
+
+```css
+.platform-label {
+ min-width: 110px;
+ text-transform: capitalize;
+}
+.count-label {
+ min-width: 50px;
+ text-align: right;
+}
+```
+
+---
+
+## Data flow
+
+1. `Dashboard.vue` on mount fetches sources (already happens), recent activity (already happens), and settings (new — one call to `settingsStore.fetchSettings()`).
+2. `platformHealth` and `failingSources` are purely derived from `sourcesStore.sources` + `failureThreshold`.
+3. When a download completes or fails, the existing `download.completed` / `download.failed` websocket handlers already trigger `sourcesStore.fetchSources()` through `source.updated`. Re-render happens via Vue reactivity.
+
+No polling changes; no new backend work beyond the one default value.
+
+---
+
+## Visual behavior
+
+- **All platforms healthy:** each bar is 100% green.
+- **One platform has failures:** that bar shows red fill proportional to `failing / total` against a neutral track; the others stay green.
+- **Platform has zero enabled sources:** row omitted entirely.
+- **Header chip:** visible only when `totalFailingCount > 0`, shows that count.
+- **Failing sources list:** rendered when non-empty; otherwise inline "No sources in failing state" caption sits directly under the bars.
+
+---
+
+## Files changed
+
+| File | Change |
+|---|---|
+| `backend/app/models/setting.py` | Add `"dashboard.failure_threshold": 5` to `DEFAULT_SETTINGS` |
+| `frontend/src/views/Settings.vue` | Add Dashboard section with threshold `v-text-field` (1–20, default 5) |
+| `frontend/src/views/Dashboard.vue` | Replace "Sources Needing Attention" card body; add `platformHealth`, `failingSources`, `totalFailingCount`, `failureThreshold` computeds; fetch settings on mount; remove `sourcesWithErrors` computed and the old empty state; small scoped CSS |
+
+---
+
+## Non-goals
+
+- No changes to `Source.error_count` increment/reset behavior — the logic in `backend/app/tasks/downloads.py` already has the correct semantics.
+- No alembic migration (the settings table is key-value; new keys arrive via `DEFAULT_SETTINGS`).
+- No new API endpoints.
+- No click-through navigation on platform rows — bars are pure display.
+- No changes to `Subscriptions.vue` or `Downloads.vue`.
+- No per-subscription health view (only per-platform).