# 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.