Skip to content

fix(alerts): quieter Inbox and a tidier Rules table - #2451

Merged
Makisuo merged 49 commits into
mainfrom
ux/alerts-hub-polish
Nov 10, 2026
Merged

Makisuo merged 49 commits into
mainfrom
ux/alerts-hub-polish

Conversation

@Makisuo

@Makisuo Makisuo commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #2426. Polish for the Inbox and Rules screens, from a design critique of the alerting pages.

What changed

  • Rules toolbar and table. A fixed-width search keeps the four status chips on one line at 1440 and 1280 (the "Disabled" chip used to wrap alone). "Routes to" gets the width freed from "Last 7 days", "Fired 30d" stays on one line, and a lone "No tag" group header is hidden.
  • Quiet Inbox is quiet. Headroom bars share one tone function with the rules table: neutral, warning under 10% headroom, error when breached (the "near" threshold moves from 20% to 10% in packages/web-data, so both pages agree). Bars are thinner and the threshold tick is neutral.
  • Copy: "MTTR n/a" for an empty value, and the Anomalies toggle hides when the window has no anomaly at all.
  • Ungrouped rules show no group. An ungrouped reading or incident has no group line or "on ..." suffix, whether storage spells ungrouped as an empty key or __total__ (incident titles now use the same helper as the incident timeline). The test fixtures used the query engine's "all", which storage never holds, so the lab showed an "all" line that real data never has; they now carry the stored key.
  • Acknowledge and Unacknowledge are the same small outline button.
  • The whole rule row is already one link (name, summary, meter, routes); a scene test now pins that with its actions menu.

The rule condition text in the table and incident header is unchanged here: it will reuse the rule editor's sentence builder once #2425 lands.

Before / after

Before After

Phone:

(The screenshots predate the merge of #2419's sidebar changes; the page content is the same.)

Verification

  • bunx vitest run src/page/alerts src/shell src/route src/lab src/page/kit and root src/*.test.ts: 872 tests pass; packages/web-data alerts tests 161 pass.
  • Typecheck clean; oxlint -c .oxlintrc.effect.json clean on changed files; ui:patterns:check passes.

Not verified against live data (the lab uses fixtures).

🤖 Generated with Claude Code

- Kit: rightSheet over the Dialog Submodel (672px form size, scrolling body,
  sticky footer, slide dropped under reduced motion) with followUrl/isDismissed;
  page/kit/sheet-param holds the ?sheet= helpers (push to open, replace to close).
- Sidebar group labels fold their rows (sidebarGroupToggle, aria-expanded),
  saved to localStorage; the group holding the current page and the collapsed
  rail always show every row.
- Footer on-call pill from the schedules list's on_call_now and the session
  user: on call until the handoff, not on call, or hidden without schedules.
  Re-read at each handoff; a dot with a tooltip on the rail.
- /lab/shell shows all three, with a demo form in the sheet.
- Sheet: opening pushes a history entry marked as this tab's; dismissing
  goes back past it, and only a deep link replaces the param. Kit renamed
  to sidePanelSheet, footer receives close; sheet-param tests sit beside it.
- On-call pill: a shift past its end reads as unknown; reads again at the
  handoff, hourly while off call, on focus and visibility, and with backoff
  (60s doubling, capped) after a stale or failed answer, keeping the last
  good data. Schedule and override writes announce a re-read.
- An org with no schedules is cached per org for a day and skips the request;
  a schedule write clears it.
- "until" uses the viewer's time zone with a short zone name.
- Sidebar: the collapsed rail renders plain labels (no tab stops); the group
  holding the current page can fold and keeps only that row. The pill uses
  aria-label on the rail instead of a title.
… side panel

- Each hub section has its own path (/alerts/rules, /alerts/uptime, /alerts/slos,
  /alerts/silences, /alerts/maintenance, /alerts/on-call, /alerts/routing); /alerts
  stays the Inbox. One page instance serves them all, so a section change reads
  nothing it already has.
- Old /alerts?tab= links replace themselves with the section's path and keep
  their other params ("oncall" and "settings" map to on-call and routing).
- The sidebar's Alerts row lists the sections when active and replaces the hub's
  own section rail. Sub-item aria-current now follows the most specific match.
- New silence opens in a URL-held side panel (?sheet=silence) with a sticky
  Cancel / Save footer; Back, Cancel, Escape and a save close it.
  sidePanelSheet gains onSubmit so the panel is a form.
- The silence panel clears ?sheet= in the model as soon as it closes itself,
  so a clock tick before the URL change can't reopen it. It opens only on the
  Silences section, and a viewer who can't manage loses a linked ?sheet= once
  the grants load (replaced away).
- A rule lives at /alerts/rules/<ruleId>; /alerts/<ruleId> (notifications, the
  apps) replaces itself with it, keeping its tab and window. The rule page and
  editor go back to Rules and their crumbs go through it.
- Settings automation and setup health link to the new section paths.
- The sidebar reveals the open section, every sub-item past the edge fades, on
  each route change; sub-rows are tighter, thin rules split the former groups,
  and the Inbox shows its unacknowledged count from the alert_incidents
  collection, read only while an alerts page is open.
- One AlertsTab schema next to AlertsSection; the sheet commands answer with a
  clock read instead of a no-op message.
…size

- A `scrim-panel` token (12% black, 24% in dark) with no blur sits beside a
  side panel, so the page it edits stays readable. Dialogs and other sheets
  keep `scrim`.
- `sidePanelSheet` takes `size`: `compact` (28rem) for a few short fields,
  `form` (42rem, the default) or `wide`.
- The on-call pill is one 32px row of the footer list: a status dot and
  "On call until Mon 09:00" in the viewer's zone (the date instead of the
  weekday once the handoff is over six days out), or a muted "Not on call".
  The zone, date and schedules are its tooltip and accessible name. On the
  collapsed rail it is a calendar glyph badged with the dot.
- Group chevrons show faintly at rest; the toggle is 28px tall.
- A folded group holding the current page shows that page's parent row
  alone, highlighted, without its sub-items.
- Hover is a neutral wash. When a sub-item is the current page only it is
  tinted; its parent keeps the medium weight on a plain background.
- Sub-item rows are 28px on desktop and 36px in the mobile sheet.
- /lab/shell gains Services and Logs pages, a far handoff and both panel sizes.
… the silence panel

- Each section's header has one primary action: New rule (split) on the Inbox
  and Rules, New monitor, New SLO, New silence, New window, New schedule and
  Add destination on their own sections. A section whose empty state already
  offers it shows none until it has rows.
- The Alerts sub-list sets its groups apart with a 4px gap instead of rules,
  and its rows take the kit's height again.
- The silence panel's Services, Environments and Tags are chips inputs that
  suggest what the org's rules name (and the services Uptime listed); a typed
  name can still be added. Its labels share one style.
- A picked end is on the viewer's clock and the field names the zone
  ("Ends, CET"); a scheduled row names its day once. The lab reads the
  viewer's zone instead of UTC.
- The duration choice uses the larger segmented size.
- Rules toolbar: a fixed-width search keeps the four status chips on one line.
  The ribbon column gives its spare width to Routes, "Fired 30d" stays on one
  line, and a lone "No tag" group header is left out.
- One headroom tone for the rules table and the Inbox: neutral unless under a
  tenth of the threshold is left (warning) or it is breached (error). The
  Inbox's bars are thinner and their threshold tick is neutral.
- Inbox: Acknowledge and Unacknowledge are the same small outline button, an
  empty MTTR reads "n/a", the Anomalies toggle is left out when the window has
  no anomaly (and shows no zero count), and an ungrouped reading says
  "all services".
- A scene test pins that a rule's row is one link to the rule with its actions
  menu beside it.
# Conflicts:
#	apps/web/src/shell/oncall.scene.test.ts
#	apps/web/src/shell/view-nav.ts
@maple-review-bot

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1aec51c5-3325-44ec-9ea1-fab743a25ac9
Commits

Reviewing files that changed from the base of the PR and between dc4dae2 and 761f02c.

Files selected for processing: 10

  • apps/web/src/page/alerts/hub/hub.scene.test.ts
  • apps/web/src/page/alerts/hub/view-anomalies.ts
  • apps/web/src/page/alerts/hub/view-inbox.ts
  • apps/web/src/page/alerts/hub/view-rules.ts
  • apps/web/src/page/alerts/hub/view.ts
  • apps/web/src/page/alerts/shared/headroom-tone.ts
  • apps/web/src/page/alerts/shared/test-rows.ts
  • packages/web-data/src/alerts/headroom.ts
  • packages/web-data/src/alerts/overview-derive.test.ts
  • packages/web-data/src/alerts/overview-derive.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@maple-review-bot

This comment has been minimized.

@Makisuo

Makisuo commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

F1 fixed in 109147b: the Inbox imports ENGINE_UNGROUPED_GROUP_KEY from @maple/query-engine.

@maple-review-bot

Copy link
Copy Markdown

The import now points at the real export, but it's the wrong vocabulary, so F1 isn't fixed — it swapped one constant for another. row.groupLabel never holds the engine's key: it comes from AlertRuleStateRow.group_key, which is written as toStorageGroupKey(plan, ENGINE_UNGROUPED_GROUP_KEY):

// packages/backend/src/services/alerts/AlertRuleModel.ts:332
: (isGroupedPlan(plan) ? engineGroupKey : UNGROUPED_GROUP_KEY)

so storage holds "__total__" (packages/domain/src/http/alerts.ts:124), not "all" (packages/query-engine/src/group-key.ts:17). silences.ts:169 and view-impact.ts:165 already compare against UNGROUPED_GROUP_KEY from @maple/domain/http for exactly this value, and packages/web-data/src/alerts/headroom.ts:80 does too.

There's a second reason the branch can't fire: headroom.ts:80-84 maps the ungrouped state to groupKey: null → groupLabel: null, and view-inbox.ts:546 only renders the label when it is non-null. The new test at hub.scene.test.ts:422 passes only because the fixture says group_key: "all" (shared/test-rows.ts:69), i.e. the test asserts the same wrong spelling as the code.

// view-inbox.ts:552 — row.groupLabel is "…__total__…" or null, never "all"
row.groupLabel === ENGINE_UNGROUPED_GROUP_KEY ? "all services" : row.groupLabel

What would settle it, and the shape of the fix: read UNGROUPED_GROUP_KEY from @maple/domain/http here like the sibling call sites, and for ungrouped readings to actually say "all services", ruleHeadroom has to stop nulling groupLabel for that key (and the fixture should carry "__total__"). That's a product decision about whether an ungrouped reading shows the subline at all, so I haven't staged it.

@Makisuo

Makisuo commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

You're right, thanks. Fixed in 26c44d4: the "all services" branch is gone (it could never fire: an ungrouped state row is __total__, which ruleHeadroom maps to no label). The fixtures in shared/test-rows.ts now carry UNGROUPED_GROUP_KEY instead of the engine's "all", and the test asserts an ungrouped reading has no group line. While there: incidentTitle fell back to the raw key, so an incident stored with __total__ would have read "... on total"; it now uses incidentGroup like the timeline, with a unit test for both spellings. Product call: an ungrouped reading shows no subline.

@maple-review-bot

This comment has been minimized.

@Makisuo

Makisuo commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

F2 fixed in 48df8a0: incidentScope returns incidentGroup(incident) directly, and its doc comment no longer claims uptime and SLO incidents are scoped differently.

@maple-review-bot

This comment has been minimized.

# Conflicts:
#	apps/web/src/shell/command.ts
#	apps/web/src/shell/flags.ts
# Conflicts:
#	apps/web/src/page/settings/audit-log/view-detail.ts
#	apps/web/src/route/search.ts
@maple-review-bot

This comment has been minimized.

@Makisuo

Makisuo commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

Preview test

Tested on the combined preview of the whole stack (#2466, app-pr-2466.maple.dev) by a separate test agent in a real browser: happy paths, break attempts, light and dark, and a 390x844 viewport. No uncaught page errors.

Passed: the Rules toolbar keeps its four chips on one line at 1440 and 1280; a click on any cell opens the rule and the actions menu opens without navigating; chips and search filter, with "No rules match these filters" when nothing matches; a quiet Inbox reads "Nothing firing · quiet for 24h+" with "MTTR n/a" and no Anomalies toggle; Acknowledge and Unacknowledge flip the heading to "1 firing · 1 acknowledged"; a breached rule's headroom bar is red with "over threshold".

Fixed in this push: merging the parent's first-run change left the Inbox filters without hasAnomalies (a type error, caught before pushing) (c4bc214bdd).

Not covered on the preview: the neutral and under-10% headroom tones (no rule sat below its threshold with data) and an ungrouped rule's reading. Both are covered by unit tests.

Traces (preview environment pr-2466, about 1,900 API requests during the test): no 5xx on any route and no error spans in the API, web worker or alerting services.

inbox with firing incidents
rules table

@Makisuo

Makisuo commented Nov 10, 2026

Copy link
Copy Markdown
Collaborator Author

F3: fixed in 139093fc5b. headroomTone now reads isNear (breached is error, near is warning, else neutral), so the near rule lives in one place, and the field's comment says so.

@maple-review-bot

This comment has been minimized.

@maple-review-bot

This comment has been minimized.

# Conflicts:
#	apps/web/src/shell/message.ts
#	apps/web/src/shell/model.ts
#	apps/web/src/shell/oncall.scene.test.ts
#	apps/web/src/shell/update.ts
#	apps/web/src/shell/view-nav.ts
#	packages/views/src/ui/sheet.ts
@maple-review-bot

This comment has been minimized.

Base automatically changed from ux/alerting-pages to main November 10, 2026 14:26
# Conflicts:
#	apps/web/src/page/alerts/hub/hub.scene.test.ts
@maple-review-bot

maple-review-bot Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 10/10 · safe to merge. Only real behavior change is NEAR_HEADROOM 20%→10% (both pages) and the __total__ scope fix, each pinned by new tests.
no findings · tests covered · risk medium

Polish for the alerting Inbox and Rules table: one headroomTone helper shared by both pages (with a breach tone), a fixed-width rules search, hidden lone "No tag" header, ungrouped incidents losing their "on ..." suffix, and a quieter Inbox toolbar. Safe to merge.

  • headroomTone + HEADROOM_FILL/HEADROOM_TEXT added, shared by the Rules meter and Inbox bars
  • NEAR_HEADROOM drops from 20% to 10% in overview-derive
  • isLoneUntagged hides the sole "No tag" group header in the rules table
  • inboxFilters returns nothing for an org with no anomaly incident in the window

761f02c · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@Makisuo
Makisuo merged commit 3c1b353 into main Nov 10, 2026
29 checks passed
@Makisuo
Makisuo deleted the ux/alerts-hub-polish branch November 10, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant