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 `
` → `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 `
` → `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.