* fix(ui): lower-overhead Reduced effects path for constrained GPUs
Disable backdrop-filter glass and solidify chrome fills when data-effects is reduced (Calm default), with Appearance warning and docs for constrained rendering devices. Related to #1614.
* test(ui): harden Reduced effects glass e2e and docs wording
Parse Color Level 4 slash alpha so opacity asserts are not vacuously true, cover dialog/toast/overlay both ways with stable data-sn hooks, and soften troubleshooting copy that overstated the unmeasured diagnosis.
* docs(ui): lead constrained-GPU guidance with Reduced Motion
Rename the troubleshooting section to neutral GPU framing, point Appearance and docs at Reduced Motion first, and show the callout whenever Motion is off so Calm or Reduced effects alone do not hide the validated workaround.
* feat: add notification suppression rules
* fix: restore label routing and routing test mocks for suppression
* fix: allow bell mute shortcuts for history-only notification categories
Suppression rule validation used the routable category whitelist, which rejected history-only categories such as update_started that appear in the bell during stack updates.
* feat: expand Mute Rules UX with compose-first entry points and activity badges
* fix: add missing NodeContext mocks for notification suppression tests
Stack success events were hidden from the panel but still counted unread on the bell and dashboard.
Share one visibility helper across badge, panel, and Recent Alerts.
Harden mark-all-read against partial API failures.
Fixes#1513
* feat: add node update alerts with changelog tab and skip-version handling
- Add node_update_available notification category with blue/brand bell dot
- Route node_update_available notifications to Fleet -> Node updates sheet
- Add Changelog tab to NodeUpdatesSheet with GitHub release notes
- Add per-node skip-version persistence (node_update_skips table)
- Skip hides update CTA on node card and sheet; re-surfaces on newer version
- Skipped nodes excluded from Update all backend filter
- Add pulsating dot indicator on Changelog tab when updates available
- Always-visible View changelog action in notification row bottom
- Admin-only for all mutating controls (skip, unskip, update)
- Backend tests for skip-version semantics (15 tests)
- Update fleet-view.mdx, remote-updates.mdx, and OpenAPI spec
* fix: address audit findings - nested button, stale changelog, semver normalization, mobile intent
- Move View changelog button outside routable button (sibling element)
- Fix aria-label for node_update_available notification rows
- Support ?recheck=true on release-notes endpoint
- Invalidate release notes cache on forced recheck
- Store normalized semver (semver.valid strips v prefix)
- Skip fleetUpdatesIntent on mobile (desktop only)
- Add v-prefix normalization test
* fix: restore View changelog on same line as timestamp, opposite sides
The button is always visible at the bottom right of the notification card,
on the same row as the timestamp (just now), using justify-between layout.
* fix: update tests for node_update_available category and release-notes fetch
- Backend: monitor-service tests now expect node_update_available instead of system
- Frontend: NodeUpdatesSheet tests mock release-notes API call to prevent undefined then()
* fix: resolve ci lint failures
* feat(appearance): add Calm/Signature visual style, readability mode, and chart palette
Turn the "too intense / italic headers hurt / the security graph fights my
eyes" feedback into a token-driven Visual style with Calm as the new default
and Signature one click back to the prior look.
- Heading family routes through a `.font-heading` utility driven by
`--font-heading`/`--heading-style`: operational headings render upright in the
interface face under Calm and italic Instrument Serif under Signature. Base
rule sets family + style only, so each call site keeps its own weight/tracking
and Signature stays a true no-op; the Calm lift is a `[data-headings="clean"]`
descendant rule. Brand lockup, empty-state heroes, and onboarding stay serif.
- Severity charts resolve through `--sev-*` tokens with Muted, Heat, and
Signature palettes; FindingsByType routes its series through the severity ramp
plus a neutral so no brand-cyan sits next to rose. The risk trend flattens its
gradient under Muted/Heat/reduced and keeps the gradient under Signature.
- Appearance settings gain Visual style cards, a Security visualization palette,
a Readability master toggle, a Motion & effects group, and a "Reset to default"
button (restores the Calm axes, disabled while readability is on). Contrast
moves under Readability and Ambient glow under Motion & effects. A card is
selected only while the stored sub-axes match its preset, so a custom
combination de-selects both.
- The topbar Theme quick-switch swaps the interface/data font pickers for a
Visual style switch and a Readability toggle (text size kept); its footer
Settings link jumps straight to Appearance.
- Readability is a sticky master that forces the calm resolution and a contrast
lift at apply time without mutating the stored sub-axes.
- New users default to Calm; any pre-existing persisted appearance state keeps
the Signature look. The pre-paint script mirrors the store.
- SegmentedControl gains a `disabled` prop and a nullable value (no active
segment for a custom combination, with a roving-tabindex keyboard anchor).
Adds unit/component coverage for the store, migration, chart shape logic, the
disabled control, the reset/de-selection, and the quick-switch.
* fix(appearance): migrate Blueprint serif headings and surface readability locks
- Migrate the two operational Blueprint headings (catalog tile name, drift-policy
option title) from font-serif italic to the .font-heading utility; the first
pass only covered font-display, so Calm still left these italic. font-serif and
font-display both resolve to the same display face, so this is the same fix.
- Lock the Visual style cards under Readability (parity with the topbar switch and
the on-screen guidance to turn Readability off to choose a style by hand).
- Lock the Border brightness slider under Readability and show its forced +0.03
readout, since Readability overrides the stored value; dragging it previously
appeared to do nothing.
- Correct the Appearance docs sentence for the topbar quick switch (it listed
fonts; the quick switch now carries visual style, readability, and text size).
* fix(frontend): tighten bell notification panel toolbar
Restructure the popover so the segmented filter (All / Unread / Alerts)
and the action icons (filter toggle, mark all read, clear all) share a
single row inside the 360px panel. The previous layout wrapped the
segmented control onto a second row once the type filter was added.
Move the unread badge to the right of the title, collapse the node and
type dropdowns behind a filter-toggle icon (accent dot signals an active
filter), equalize both Select widths, and extract the duplicated
className strings into local constants.
* docs: refresh bell notification popover screenshot
Reflects the reworked toolbar (segmented filter + filter toggle +
mark-all-read + clear-all on a single row).
* feat(stack): per-stack activity timeline with actor attribution
Adds an Activity tab to the Stack Anatomy panel showing a timestamped
event log for each stack: deploys, restarts, starts, stops, and image
updates, attributed to the user who triggered them or 'system' for
automated actions.
Backend:
- Extends notification_history with actor_username column (idempotent
migration) and a partial composite index on (node_id, stack_name,
timestamp DESC) for efficient per-stack lookups.
- NotificationService.dispatchAlert() accepts an optional actor that
is written to the new column.
- Success-side dispatchAlert calls added after deploy, bulkContainerOp
(start/stop/restart), and update handlers in routes/stacks.ts so
user-initiated operations are recorded, not just failures.
- New GET /api/stacks/:stackName/activity?limit&before endpoint with
stack:read permission gate and cursor-based pagination.
Frontend:
- StackAnatomyPanel grows an Anatomy / Activity tab pair using the
existing Tabs primitive.
- StackActivityTimeline fetches the initial 50 events, paginates on
demand, and prepends live events arriving over the existing WS
notifications stream without duplicates.
- NotificationPanel bell dropdown suppresses user-initiated success
events (start/stop/restart/deploy/update triggered by a real user),
keeping the tray focused on alerts and system events.
* docs(stack): add stack activity timeline feature page and internal arch docs
* fix(test): add actor_username to notification-routing history assertions
dispatchAlert now passes actor_username to addNotificationHistory after
the activity timeline PR added the column. Update the two exact-match
assertions that were failing because the expected object shape was missing
this field.
- Add label_ids and categories columns to notification_routes via idempotent migration
- Matcher logic always evaluates routes (AND semantics across all non-empty matchers)
- getStackLabelIds skips DB call when no enabled route uses label filtering
- Extract ALL_NOTIFICATION_CATEGORIES array from NotificationService as single source of truth
- Derive VALID_CATEGORIES set from the array in the route handler
- Extract validateLabelIds and validateCategories helpers to remove POST/PUT duplication
- Extract tryAddColumn as a private DatabaseService class method (removes 5 local re-declarations)
- Extract CATEGORY_LABELS to frontend/src/lib/notificationCategories.ts (shared by NotificationPanel and NotificationRoutingSection)
- Frontend form adds label and category multiselects with AND-filter hint
- Route cards show label and category badges; empty-matcher routes show 'Matches all alerts'
- Add tests for category-only, label-only, and combined AND-semantics routing
Introduce a NotificationCategory string-literal union (11 values) and
thread it through dispatchAlert as a required second argument. All
callers (DockerEventService, AutoHealService, ImageUpdateService,
MonitorService, PolicyEnforcement, policyGate, SchedulerService,
imageUpdates route) pass an explicit category at every call site,
giving TypeScript compile-time enforcement that no new emit site can
be added without choosing a category.
DatabaseService gains an idempotent migration that adds a nullable
category TEXT column to notification_history; existing rows keep
category=NULL (displayed as Uncategorized in the UI). The
getNotificationHistory method accepts an optional category filter
that is forwarded from the GET /api/notifications/history route via
a ?category= query param.
NotificationPanel gains a category Select dropdown so users can
filter history by category. The frontend types mirror the backend
union so API responses are type-safe end-to-end.
All 75 test files (1410 tests) updated to the new 4-arg dispatchAlert
signature and passing.
Two polish improvements to the aggregated notifications inbox:
- Per-node filter dropdown in the bell panel (hidden on single-node
installs) so fleet operators can triage events from a specific box.
Selected node falls back to "All nodes" automatically if that node is
removed from the registry.
- 60-second safety-net poll that reconciles the list so events missed
during a WebSocket reconnect backoff appear without a manual refresh.
Uses a ref indirection to pin the interval to the latest
fetchNotifications closure.
Extract the app header into a dedicated TopBar component and apply the
same translucent chrome language used by the sidebar: bg-sidebar +
backdrop-blur-md + inset top highlight. A new --chrome-top-highlight
token drives the highlight in both themes via the shadow-chrome-top
utility alias.
Restore the pulsing-dot unread indicator on the notification bell
(replaces the temporary count pill) and keep its aria-label dynamic so
screen readers still hear the unread count. Tighten nav a11y with
aria-current on the active item, aria-label on icon-only buttons, and a
proper <nav> landmark around the desktop Highlight group.
Swap hardcoded Tailwind padding on NodeSwitcher, UserProfileDropdown,
and NotificationPanel for var(--density-row-x) / --density-row-y /
--density-cell-y / --density-tile-y custom properties so the three
sidebar/header overlays now tighten when the Appearance setting is
switched to Compact, matching the reference pattern in ui/table.tsx.
Add stack_name and container_name columns to notification_history so bell
rows can act as jump points. Producers (AutoHeal, Docker events) pass the
container context through dispatchAlert; the panel renders routable rows
as buttons that load the target stack and, when a container name is
present, open its logs modal. Non-structural notifications stay as
passive display rows.
* feat(ui): redesign user menu and notification panel per design audit
Align both floating surfaces with the stack-view design language: cyan
signal rails, identity headers, strip containers, day-banded streams,
severity rails, Instrument Serif for hero words, and tracked-mono for
metadata. Extract the notifications popover out of EditorLayout into its
own component and introduce a reusable SegmentedControl primitive for
3-way radiogroups (Auto/Light/Dark theme, All/Unread/Alerts filter).
* fix(ui): pin accessible names on avatar and bell buttons
After the redesign the avatar button's text content became the user's
initials and the bell button's text became the unread count, which
displaced the title-based accessible names and broke
getByRole('button', { name: /profile/i }). Set aria-label explicitly on
both triggers and mark the bell badge aria-hidden so screen readers and
role-based selectors see stable names regardless of dynamic content.