Skip to content

feat(alerts): alerting sections as real pages in the sidebar - #2426

Merged
Makisuo merged 36 commits into
mainfrom
ux/alerting-pages
Nov 10, 2026
Merged

Makisuo merged 36 commits into
mainfrom
ux/alerting-pages

Conversation

@Makisuo

@Makisuo Makisuo commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #2419. Each alerting section gets its own page instead of /alerts?tab=x, and the sections move into the sidebar.

What changed

  • Routes: /alerts (Inbox), /alerts/rules, /alerts/uptime, /alerts/slos, /alerts/silences, /alerts/maintenance, /alerts/on-call, /alerts/routing. One hub page serves them (pinned to one instance), so switching sections adds no requests beyond today.
  • Rule detail moves to /alerts/rules/<ruleId>. The old /alerts/<uuid> replace-redirects there, keeping its params, so links in notifications, Slack, email and iOS keep working. Old ?tab= links redirect too.
  • Sidebar: under Triage > Alerts, the eight sections show as sub-items, grouped by small gaps (Inbox | Rules, Uptime, SLOs | Silences, Maintenance | On-call, Routing), with the unacknowledged count on Inbox. The hub's own left rail is removed (it duplicated the same eight links and is missing on detail pages). The sidebar scrolls the open section into view on navigation.
  • One primary action per section: New rule on Inbox and Rules, then New monitor, New SLO, New silence, New window, New schedule, Add destination. A section whose empty state already offers the create button shows no header action until it has rows, and members who can't create that thing see none.
  • Back links: rule page, editor Cancel/Save and breadcrumbs now go to Rules, not the Inbox. Settings links use the new paths.
  • Silence panel: "New silence" opens in the compact side panel from feat(shell): foldable sidebar groups, on-call pill, URL-held side panel #2419 (?sheet=silence, Silences only), with Cancel / Save in a sticky footer. Services, Environments and Tags are chip inputs with suggestions from the rules already loaded. The end time is in your own time zone, named in the label ("Ends, CET"). Back closes the panel; it can't reopen from a clock tick after Cancel or Save; viewers without manage rights get the param removed. The rule and incident pages reuse the form without URL state.
  • Sidebar kit: sub-items mark the most specific match as current (also fixes Infrastructure > Overview). Segmented select gains an lg size.

Before / after

Before After

Silence panel in dark and on a phone:

Verification

  • bunx vitest run src/page/alerts src/shell src/route src/lab src/page/kit and root src/*.test.ts: 868 tests pass (route table and collisions, legacy redirects keep params, the rule /alerts/<uuid> redirect, sidebar active state, one primary action per section, silence panel open/save/Cancel/Escape/Back and the reopen race, chip inputs, the zone label across DST changes).
  • Typecheck clean; oxlint -c .oxlintrc.effect.json clean on changed files; ui:index, ui:patterns:check pass.
  • An independent code review (9 issues) and a design critique at 1440, 1280 and 390px (wrong primary action per section, free-text scope fields, UTC end time, small controls, noisy separators) are both fixed.

Not verified yet: the live Inbox count and sidebar scrolling against real data (preview test pending). The Maintenance window dialog still takes comma-separated fields.

🤖 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.
@coderabbitai

coderabbitai Bot commented Nov 9, 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 14 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: d84bf5d8-8256-437d-8476-488a1bc8e2d5
Commits

Reviewing files that changed from the base of the PR and between 7c6c09c and 6a9012e.

Files selected for processing: 85

  • apps/web/e2e/tests/alert-rules.e2e.ts
  • apps/web/src/components/settings/audit-log-resource.ts
  • apps/web/src/lab/alerts-hub-lab.ts
  • apps/web/src/lab/anomaly-alerts-lab.ts
  • apps/web/src/lab/entries/alerts-hub.ts
  • apps/web/src/lab/escalation-lab.ts
  • apps/web/src/lab/maintenance-lab.ts
  • apps/web/src/lab/oncall-lab.ts
  • apps/web/src/lab/registry.ts
  • apps/web/src/lab/shell-lab.ts
  • apps/web/src/lab/slos-lab.ts
  • apps/web/src/page/alerts/editor/actions.ts
  • apps/web/src/page/alerts/editor/editor-chains.scene.test.ts
  • apps/web/src/page/alerts/editor/editor.scene.test.ts
  • apps/web/src/page/alerts/editor/view-action-bar.ts
  • apps/web/src/page/alerts/editor/view.ts
  • apps/web/src/page/alerts/hub/command.ts
  • apps/web/src/page/alerts/hub/derive.test.ts
  • apps/web/src/page/alerts/hub/derive.ts
  • apps/web/src/page/alerts/hub/hub-anomalies.scene.test.ts
  • apps/web/src/page/alerts/hub/hub-chains.scene.test.ts
  • apps/web/src/page/alerts/hub/hub-maintenance.scene.test.ts
  • apps/web/src/page/alerts/hub/hub-oncall.scene.test.ts
  • apps/web/src/page/alerts/hub/hub-silences.scene.test.ts
  • apps/web/src/page/alerts/hub/hub-slos.scene.test.ts
  • apps/web/src/page/alerts/hub/hub-uptime.scene.test.ts
  • apps/web/src/page/alerts/hub/hub.scene.test.ts
  • apps/web/src/page/alerts/hub/index.ts
  • apps/web/src/page/alerts/hub/model.ts
  • apps/web/src/page/alerts/hub/update.ts
  • apps/web/src/page/alerts/hub/view-empty.ts
  • apps/web/src/page/alerts/hub/view-inbox.ts
  • apps/web/src/page/alerts/hub/view-oncall.ts
  • apps/web/src/page/alerts/hub/view-silences.ts
  • apps/web/src/page/alerts/hub/view.ts
  • apps/web/src/page/alerts/incident/view.ts
  • apps/web/src/page/alerts/rule/index.ts
  • apps/web/src/page/alerts/rule/model.ts
  • apps/web/src/page/alerts/rule/rule.scene.test.ts
  • apps/web/src/page/alerts/rule/update.ts
  • apps/web/src/page/alerts/rule/view-diagnosis.ts
  • apps/web/src/page/alerts/rule/view.ts
  • apps/web/src/page/alerts/shared/escalation-chains.ts
  • apps/web/src/page/alerts/shared/links.ts
  • apps/web/src/page/alerts/shared/maintenance-windows.ts
  • apps/web/src/page/alerts/shared/matcher-fields.ts
  • apps/web/src/page/alerts/shared/oncall-schedules.ts
  • apps/web/src/page/alerts/shared/silences.test.ts
  • apps/web/src/page/alerts/shared/silences.ts
  • apps/web/src/page/alerts/shared/slo-commands.ts
  • apps/web/src/page/alerts/shared/test-rows.ts
  • apps/web/src/page/alerts/shared/uptime-commands.ts
  • apps/web/src/page/alerts/silence-dialog/dialog.test.ts
  • apps/web/src/page/alerts/silence-dialog/dialog.ts
  • apps/web/src/page/alerts/silence-dialog/view.ts
  • apps/web/src/page/kit/timezone-zones.ts
  • apps/web/src/page/services/detail/uptime-card.scene.test.ts
  • apps/web/src/page/settings/audit-log/view-detail.ts
  • apps/web/src/page/settings/automation/view.ts
  • apps/web/src/page/settings/setup-health/derive.ts
  • apps/web/src/route/app-route.ts
  • apps/web/src/route/index.ts
  • apps/web/src/route/redirect.ts
  • apps/web/src/route/route.test.ts
  • apps/web/src/route/routers.ts
  • apps/web/src/route/search.ts
  • apps/web/src/shell/alerts-inbox.ts
  • apps/web/src/shell/index.ts
  • apps/web/src/shell/message.ts
  • apps/web/src/shell/model.ts
  • apps/web/src/shell/mounts.ts
  • apps/web/src/shell/nav.ts
  • apps/web/src/shell/oncall.scene.test.ts
  • apps/web/src/shell/shell.scene.test.ts
  • apps/web/src/shell/update.ts
  • apps/web/src/shell/view-nav.ts
  • apps/web/src/subscription.ts
  • apps/web/src/view.lazy.test.ts
  • packages/views/src/ui/INDEX.md
  • packages/views/src/ui/combobox-view.ts
  • packages/views/src/ui/combobox.scene.test.ts
  • packages/views/src/ui/segmented-select.ts
  • packages/views/src/ui/sheet.ts
  • packages/views/src/ui/sidebar.ts
  • packages/web-data/src/lib/timezone-format.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.

…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.
# Conflicts:
#	apps/web/src/shell/oncall.scene.test.ts
#	apps/web/src/shell/view-nav.ts
@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
…rst schedule shows the on-call row

The marker lived a day and only a schedule write in the same tab cleared it, so the
row stayed hidden across reloads after someone else created the org's first schedule.
It now lives five minutes, and coming back into focus asks again once it has aged.
…sidebar

A section row whose sub-item is the current page keeps data-active="child" and
leaves aria-current to the sub-item. The on-call footer row is a plain anchor, so
it never claims the page its nav row already marks.
@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: all eight sections load directly, survive a reload and follow back and forward; the sidebar highlights the current sub-item and the Inbox count follows acknowledge; every legacy ?tab= link and /alerts/<ruleId> lands on its new path with its params; unknown sections show "Page not found"; the silence panel opens from the button and from ?sheet=silence, closes by Escape, the close button, an outside click and Back, traps and returns focus, and creating and ending silences works.

Found and fixed in this push:

  • The silence form had a disabled save with no reason given when Reason was empty. Save now shows what is missing under each field (78ebb4a212).
  • Chips: a comma now splits values, and typing an existing chip no longer removes it (5afb72aa2b). The typing hint no longer covers the next label (a037e66d70).
  • The save button named its end time on a 12-hour clock while the picker is 24-hour (f6754063a0).
  • First-run Inbox and Rules showed the header "New rule" on top of the empty state's own create actions (717a12fa65).
  • A ?sheet= value that opens nothing is now dropped from the URL (994eb5af29).
  • The preview's end-to-end test expected a saved rule to return to /alerts; it now expects /alerts/rules (fc1ac22c51).

Still open: a silence form with input closes on Escape or an outside click without asking; the Routing section shows three create actions; on a phone the Inbox and Rules tables scroll sideways inside their own box.

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. Alert routes: GET /v2/alerts/rules p95 305 ms, GET /v2/alerts/silences p95 139 ms, GET /v2/alerts/deliveries p95 200 ms.

silence panel
silence created
routing section

@maple-review-bot

This comment has been minimized.

Base automatically changed from ux/shell to main November 10, 2026 14:05
# 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

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

Copy link
Copy Markdown

Maple review

🟢 Confidence 10/10 · safe to merge. Routing, redirects and the silence panel check out; the hub and rule scene tests plus editor/rule/lab files went unread here.
no findings · tests covered · risk medium

Each alerting section now lives at its own /alerts/<section> path with sidebar sub-items, and the new-silence form became a URL-held side panel. The redirect and permission paths I could check hold up; nothing to fix before merge.

  • urlToAppRoute parses AlertsSection and keeps /alerts/rules/<id> reachable; AlertRuleLegacy redirects old rule links
  • routeRedirect turns a legacy ?tab= into its section path, keeping the other params
  • followSilenceSheet syncs the dialog to ?sheet=silence, dropping the param for viewers without alerts:manage
  • matchSubItem/activeSubHrefOf pick the most specific sub-item, including alsoActive pages

6a9012e · 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 dc4dae2 into main Nov 10, 2026
29 checks passed
@Makisuo
Makisuo deleted the ux/alerting-pages branch November 10, 2026 14:26
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