diff --git a/docs/superpowers/specs/2026-04-21-alerts-design-system-alignment-design.md b/docs/superpowers/specs/2026-04-21-alerts-design-system-alignment-design.md
new file mode 100644
index 00000000..e31646ac
--- /dev/null
+++ b/docs/superpowers/specs/2026-04-21-alerts-design-system-alignment-design.md
@@ -0,0 +1,180 @@
+# Alerts pages — design-system alignment
+
+**Status:** Approved (2026-04-21)
+**Scope:** All pages and helper components under `/alerts` in `ui/src/pages/Alerts/` plus `ui/src/components/NotificationBell.tsx` (audit only — already on DS).
+**Non-goals:** Backend changes, DS package changes, alert semantics, `MustacheEditor` restyling.
+
+## Problem
+
+Pages under `/alerts` don't fully adhere to the `@cameleer/design-system` styling and component conventions used by the rest of the SPA (Admin, Audit, Apps, Runtime). Concretely:
+
+1. **Undefined CSS variables.** `alerts-page.module.css` and `wizard.module.css` use tokens (`--bg`, `--fg`, `--muted`, `--accent`) that are **not** defined by the DS (verified against `@cameleer/design-system/dist/style.css`). These fall back to browser defaults and do not theme correctly in dark mode. (Note: `--border` and `--amber-bg` **are** valid DS tokens, but `--border-subtle` is the convention used by the rest of the app for card chrome.)
+2. **Raw HTML where DS components exist.** Raw `
` (RulesList, Silences), raw `` (RulesList promote), custom centered-div empty states, custom "promote banner" div.
+3. **Inconsistent page layout.** Toolbars built ad-hoc with inline styles. Admin / Audit pages use a consistent `SectionHeader + sectionStyles.section / tableStyles.tableSection` shell.
+4. **Native `confirm()`** instead of DS `ConfirmDialog`.
+
+## Design principles
+
+1. **Consistency over novelty** — all three list pages (Inbox / All / History) share one `DataTable` shell; they differ only in toolbar controls.
+2. **Double-encode severity** — DS `SeverityBadge` column **and** `rowAccent` tint — accessible to colorblind users.
+3. **Expandable rows** give the inbox-style preview affordance without needing a separate feed layout.
+4. **Relative time** (`2m ago`) with tooltip for absolute ISO — industry-standard for alert consoles.
+5. **Use DS tokens only** — `--bg-surface`, `--border-subtle`, `--radius-lg`, `--shadow-card`, `--text-primary/secondary/muted`, `--space-sm/md/lg`.
+
+## Per-page design
+
+### Inbox (`/alerts/inbox`)
+
+Personal triage queue — user-targeted FIRING/ACKNOWLEDGED alerts.
+
+- Shell: `Inbox ` → bulk-action toolbar (`Mark selected read`, `Mark all read`) → `tableStyles.tableSection` wrapping `DataTable`.
+- Columns: **☐ checkbox | Severity | State | Title | App/Rule | Age | Ack**.
+- `rowAccent`: map severity → `error | warning | info`. Unread (FIRING) rows render with DataTable's inherent accent tint; additional bold weight on title via `render`.
+- `expandedContent`: message body, targeted users, fireMode, absolute firedAt/updatedAt.
+- Empty state: DS ` } title="All clear" description="No open alerts for you in this environment." />`.
+
+### All alerts (`/alerts/all`)
+
+Env-wide operational awareness.
+
+- Same shell as Inbox, minus the checkbox column.
+- Filter bar: DS `ButtonGroup` with items `Open` / `Firing` / `Acked` / `All`. Replaces the current four-`Button` row.
+- Columns: **Severity | State | Title | App/Rule | Fired at | Silenced**.
+- `expandedContent`: same as Inbox.
+- Empty state: `EmptyState` with filter-specific message.
+
+### History (`/alerts/history`)
+
+Retrospective lookup — RESOLVED alerts only.
+
+- Same shell as All.
+- Filter bar: DS `DateRangePicker` (default: last 7 days). Replaces the static "retention window" label.
+- Columns: **Severity | Title | App/Rule | Fired at | Resolved at | Duration**.
+- `expandedContent`: message body, rule snapshot pointer, full timestamps.
+- Empty state: `EmptyState` with "No resolved alerts in selected range."
+
+### Rules list (`/alerts/rules`)
+
+- Shell: `New rule}>` with DS `action` slot — replaces the inline flex container that currently wraps them.
+- Raw `` → `DataTable` inside `tableStyles.tableSection`.
+- Columns: **Name (Link) | Kind (Badge) | Severity (SeverityBadge) | Enabled (Toggle) | Targets (count) | Actions**.
+- Actions cell: DS `Dropdown` for **Promote to env** (replaces raw ``), DS `Button variant="ghost"` **Delete** opening a `ConfirmDialog`.
+- Empty state: `EmptyState` with CTA linking to `/alerts/rules/new`.
+
+### Silences (`/alerts/silences`)
+
+- Shell: `Alert silences `.
+- Create form: kept in `sectionStyles.section`, but grid laid out via `FormField`s with proper `Label` and `hint` props — no inline-style grid.
+- List: raw `` → `DataTable` below the form.
+- Columns: **Matcher (MonoText) | Reason | Starts | Ends | End action**.
+- `End` action → `ConfirmDialog`.
+- Empty state: `EmptyState` "No active or scheduled silences."
+
+### Rule editor wizard (`/alerts/rules/new`, `/alerts/rules/:id`)
+
+Keep the current custom tab stepper — DS has no `Stepper`, and the existing layout is appropriate.
+
+Changes:
+- `wizard.module.css` — replace undefined tokens with DS tokens. `.wizard` uses `--space-md` gap; `.steps` underline uses `--border-subtle`; `.stepActive` border uses `--amber` (the DS accent color); `.step` idle color uses `--text-muted`, active/done uses `--text-primary`.
+- Promote banner → DS ``.
+- Warnings block → DS `` with the list as children.
+- Step body wraps in `sectionStyles.section` for card affordance matching other forms.
+
+## Shared changes
+
+### `alerts-page.module.css`
+
+Reduced to layout-only:
+
+```css
+.page {
+ padding: var(--space-md);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+}
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: var(--space-sm);
+}
+.filterBar {
+ display: flex;
+ gap: var(--space-sm);
+ align-items: center;
+}
+```
+
+Delete: `.row`, `.rowUnread`, `.body`, `.meta`, `.time`, `.message`, `.actions`, `.empty` — all replaced by DS components.
+
+### `AlertRow.tsx`
+
+**Delete.** Logic migrates into:
+- A column renderer for the Title cell (handles the `Link` + `markRead` side-effect on click).
+- An `expandedContent` renderer shared across the three list pages (extracted into `ui/src/pages/Alerts/alert-expanded.tsx`).
+- An Ack action button rendered via DataTable `Actions` column.
+
+### `ConfirmDialog` migration
+
+Replaces native `confirm()` in `RulesListPage` (delete), `SilencesPage` (end), and the wizard if it grows a delete path (not currently present).
+
+### Helpers
+
+Two small pure-logic helpers, co-located in `ui/src/pages/Alerts/`:
+
+- `time-utils.ts` — `formatRelativeTime(iso: string, now?: Date): string` returning `2m ago` / `1h ago` / `3d ago`. With a Vitest.
+- `severity-utils.ts` — `severityToAccent(severity: AlertSeverity): DataTableRowAccent` mapping `CRITICAL→error`, `MAJOR→warning`, `MINOR→warning`, `INFO→info`. With a Vitest.
+
+## Components touched
+
+| File | Change |
+|------|--------|
+| `ui/src/pages/Alerts/InboxPage.tsx` | Rewrite: DataTable + bulk toolbar + EmptyState |
+| `ui/src/pages/Alerts/AllAlertsPage.tsx` | Rewrite: DataTable + ButtonGroup filter + EmptyState |
+| `ui/src/pages/Alerts/HistoryPage.tsx` | Rewrite: DataTable + DateRangePicker + EmptyState |
+| `ui/src/pages/Alerts/RulesListPage.tsx` | Table → DataTable; select → Dropdown; confirm → ConfirmDialog |
+| `ui/src/pages/Alerts/SilencesPage.tsx` | Table → DataTable; FormField grid; confirm → ConfirmDialog |
+| `ui/src/pages/Alerts/AlertRow.tsx` | **Delete** |
+| `ui/src/pages/Alerts/alert-expanded.tsx` | **New** — shared expandedContent renderer |
+| `ui/src/pages/Alerts/time-utils.ts` | **New** |
+| `ui/src/pages/Alerts/time-utils.test.ts` | **New** |
+| `ui/src/pages/Alerts/severity-utils.ts` | **New** |
+| `ui/src/pages/Alerts/severity-utils.test.ts` | **New** |
+| `ui/src/pages/Alerts/alerts-page.module.css` | Slim down to layout-only, DS tokens |
+| `ui/src/pages/Alerts/RuleEditor/wizard.module.css` | Replace legacy tokens → DS tokens |
+| `ui/src/pages/Alerts/RuleEditor/RuleEditorWizard.tsx` | Promote banner / warnings → DS `Alert`; step body → section-card wrap |
+
+## Testing
+
+1. **Unit (Vitest):**
+ - `time-utils.test.ts` — relative time formatting at 0s / 30s / 5m / 2h / 3d / 10d boundaries.
+ - `severity-utils.test.ts` — all four severities map correctly.
+2. **Component tests:**
+ - Existing `AlertStateChip.test.tsx`, `SeverityBadge.test.tsx` keep passing (no change).
+ - `NotificationBell.test.tsx` — unchanged (this component already uses DS correctly per audit).
+3. **E2E (Playwright):**
+ - Inbox empty state renders.
+ - AllAlerts filter ButtonGroup switches active state and requery fires.
+ - Rules list delete opens ConfirmDialog, confirms, row disappears.
+ - Wizard promote banner renders as `Alert`.
+4. **Manual smoke:**
+ - Light + dark theme on all five pages — verify no raw `` borders bleeding through; all surfaces use DS card shadows.
+ - Screenshot comparison pre/post via already-present Playwright config.
+
+## Open questions
+
+None — DS v0.1.56 ships every primitive we need (`DataTable`, `EmptyState`, `Alert`, `ButtonGroup`, `Dropdown`, `ConfirmDialog`, `DateRangePicker`, `FormField`, `MonoText`). If a gap surfaces during implementation, flag it as a separate DS-change discussion per user's standing rule.
+
+## Out of scope
+
+- Keyboard shortcuts (j/k nav, e to ack) — future enhancement.
+- Grouping alerts by rule (collapse duplicates) — future enhancement.
+- `MustacheEditor` visual restyling — separate concern.
+- Replacing native `confirm()` outside `/alerts` — project-wide pattern; changing it all requires separate decision.
+
+## Migration risk
+
+- **Low.** Changes are localized to `ui/src/pages/Alerts/` plus one CSS module file for the wizard. No backend, no DS, no router changes.
+- Openapi schema regeneration **not required** (no controller changes).
+- Existing tests don't exercise feed-row DOM directly (component tests cover badges/chips only), so row-to-table conversion won't break assertions.