Rework all pages under /alerts to use @cameleer/design-system components and tokens. Unified DataTable shell for Inbox/All/History with expandable rows; DataTable + Dropdown + ConfirmDialog for Rules list; FormField grid + DataTable for Silences; DS Alert for wizard banners. Replaces undefined CSS variables (--bg, --fg, --muted, --accent) with DS tokens and removes raw <table>/<select>/confirm() usage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.7 KiB
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:
- Undefined CSS variables.
alerts-page.module.cssandwizard.module.cssuse 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:--borderand--amber-bgare valid DS tokens, but--border-subtleis the convention used by the rest of the app for card chrome.) - Raw HTML where DS components exist. Raw
<table>(RulesList, Silences), raw<select>(RulesList promote), custom centered-div empty states, custom "promote banner" div. - Inconsistent page layout. Toolbars built ad-hoc with inline styles. Admin / Audit pages use a consistent
SectionHeader + sectionStyles.section / tableStyles.tableSectionshell. - Native
confirm()instead of DSConfirmDialog.
Design principles
- Consistency over novelty — all three list pages (Inbox / All / History) share one
DataTableshell; they differ only in toolbar controls. - Double-encode severity — DS
SeverityBadgecolumn androwAccenttint — accessible to colorblind users. - Expandable rows give the inbox-style preview affordance without needing a separate feed layout.
- Relative time (
2m ago) with tooltip for absolute ISO — industry-standard for alert consoles. - 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:
<SectionHeader>Inbox</SectionHeader>→ bulk-action toolbar (Mark selected read,Mark all read) →tableStyles.tableSectionwrappingDataTable. - 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 viarender.expandedContent: message body, targeted users, fireMode, absolute firedAt/updatedAt.- Empty state: DS
<EmptyState icon={<Inbox />} 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
ButtonGroupwith itemsOpen/Firing/Acked/All. Replaces the current four-Buttonrow. - Columns: Severity | State | Title | App/Rule | Fired at | Silenced.
expandedContent: same as Inbox.- Empty state:
EmptyStatewith 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:
EmptyStatewith "No resolved alerts in selected range."
Rules list (/alerts/rules)
- Shell:
<SectionHeader action={<Button>New rule</Button>}>with DSactionslot — replaces the inline flex container that currently wraps them. - Raw
<table>→DataTableinsidetableStyles.tableSection. - Columns: Name (Link) | Kind (Badge) | Severity (SeverityBadge) | Enabled (Toggle) | Targets (count) | Actions.
- Actions cell: DS
Dropdownfor Promote to env (replaces raw<select>), DSButton variant="ghost"Delete opening aConfirmDialog. - Empty state:
EmptyStatewith CTA linking to/alerts/rules/new.
Silences (/alerts/silences)
- Shell:
<SectionHeader>Alert silences</SectionHeader>. - Create form: kept in
sectionStyles.section, but grid laid out viaFormFields with properLabelandhintprops — no inline-style grid. - List: raw
<table>→DataTablebelow the form. - Columns: Matcher (MonoText) | Reason | Starts | Ends | End action.
Endaction →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..wizarduses--space-mdgap;.stepsunderline uses--border-subtle;.stepActiveborder uses--amber(the DS accent color);.stepidle color uses--text-muted, active/done uses--text-primary.- Promote banner → DS
<Alert variant="info">. - Warnings block → DS
<Alert variant="warning">with the list as children. - Step body wraps in
sectionStyles.sectionfor card affordance matching other forms.
Shared changes
alerts-page.module.css
Reduced to layout-only:
.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+markReadside-effect on click). - An
expandedContentrenderer shared across the three list pages (extracted intoui/src/pages/Alerts/alert-expanded.tsx). - An Ack action button rendered via DataTable
Actionscolumn.
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): stringreturning2m ago/1h ago/3d ago. With a Vitest.severity-utils.ts—severityToAccent(severity: AlertSeverity): DataTableRowAccentmappingCRITICAL→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
- 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.
- Component tests:
- Existing
AlertStateChip.test.tsx,SeverityBadge.test.tsxkeep passing (no change). NotificationBell.test.tsx— unchanged (this component already uses DS correctly per audit).
- Existing
- 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.
- Manual smoke:
- Light + dark theme on all five pages — verify no raw
<table>borders bleeding through; all surfaces use DS card shadows. - Screenshot comparison pre/post via already-present Playwright config.
- Light + dark theme on all five pages — verify no raw
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.
MustacheEditorvisual 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.