diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md
index 14bc5f9f6..b15e3ee7d 100644
--- a/docs/release-control/v6/internal/subsystems/alerts.md
+++ b/docs/release-control/v6/internal/subsystems/alerts.md
@@ -479,6 +479,10 @@ route through that same alert overview presentation owner and the alert-owned
`frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx` surface instead
of living as a dashboard-page-local panel plus a second dashboard-only alert
presentation helper.
+That same dashboard recent-alert surface must rank structural health warnings
+above state-only warning messages such as powered-off, stopped, or paused
+workloads before falling back to recency, so chosen workload state does not
+hide active infrastructure or storage problems on the first dashboard screen.
Alert threshold and schedule surfaces must now also treat
`discoveryTarget` as optional frontend input and keep grouping-card state on
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 8a1b3002f..e26bda297 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -1642,6 +1642,10 @@ canonical connected-infrastructure projection, fall back only to the compact
dashboard summary that the route already owns, and keep the explicit
Infrastructure handoff above detailed problem, storage, recovery, or trend
rows without restoring platform-special navigation.
+That compact fallback must keep speaking in system terms. When the connected
+projection has not arrived yet, estate orientation copy may say how many
+systems are reporting or syncing, but it must not slide back to generic
+resource-count language that blurs the v6 system model.
That first-viewport copy must distinguish system-level estate health from
resource, alert, storage, or recovery issues that remain elsewhere on the
dashboard, and partial/empty dashboard states must describe synchronization or
@@ -1755,6 +1759,11 @@ recovery data warms.
that same dashboard overview boundary so the problem-resource severity contract
stays shared with `ProblemResourcesTable.tsx` instead of floating as an
unowned helper.
+Problem-resource table readability belongs to that same owner. Repeated rows
+may collapse only when they share the same governed display label, resource
+type, and problem signal; the header count and Pulse Brief counts must continue
+to represent the underlying affected resources, and grouped links must route to
+the broad owning surface rather than inventing a synthetic resource target.
That same dashboard overview boundary must consume the governed Patrol finding
presentation helpers when it surfaces Patrol findings in compact form. In
`frontend-modern/src/features/dashboardOverview/ActionRequiredPanel.tsx`,
diff --git a/frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx b/frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx
index f2c03e6fc..c5b0e76b5 100644
--- a/frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx
+++ b/frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx
@@ -21,9 +21,33 @@ interface RecentAlertsPanelProps {
alerts: Alert[];
}
-function sortByStartTimeDesc(alerts: Alert[]): Alert[] {
+// "State-only" alerts describe a state the operator typically chose (a VM that
+// is stopped, a container that is powered off). Mixed alongside structural
+// health signals like "ZFS device /dev/sda4 has errors" they drown out the
+// signal that actually requires attention. We still surface them below
+// structural warnings of the same level so the first screen leads with real
+// problems.
+const STATE_ONLY_MESSAGE_PATTERNS: RegExp[] = [/is powered off/i, /is stopped/i, /is paused/i];
+
+function isStateOnlyAlert(alert: Alert): boolean {
+ const message = alert.message ?? '';
+ return STATE_ONLY_MESSAGE_PATTERNS.some((pattern) => pattern.test(message));
+}
+
+function alertRank(alert: Alert): number {
+ // Lower rank = higher priority in the list.
+ if (alert.level === 'critical') return 0;
+ if (alert.level === 'warning') {
+ return isStateOnlyAlert(alert) ? 3 : 1;
+ }
+ return 2;
+}
+
+function sortAlertsForDashboard(alerts: Alert[]): Alert[] {
const sorted = [...alerts];
sorted.sort((a, b) => {
+ const rankDelta = alertRank(a) - alertRank(b);
+ if (rankDelta !== 0) return rankDelta;
const aMs = Date.parse(a.startTime) || 0;
const bMs = Date.parse(b.startTime) || 0;
return bMs - aMs;
@@ -42,7 +66,7 @@ export function RecentAlertsPanel(props: RecentAlertsPanelProps) {
} = useAlertAcknowledgementState({
alerts: () => props.alerts,
});
- const recent = createMemo(() => sortByStartTimeDesc(effectiveAlerts()).slice(0, MAX_SHOWN));
+ const recent = createMemo(() => sortAlertsForDashboard(effectiveAlerts()).slice(0, MAX_SHOWN));
const activeCriticalCount = createMemo(
() =>
effectiveAlerts().filter((alert) => !alert.acknowledged && alert.level === 'critical').length,
diff --git a/frontend-modern/src/components/Alerts/__tests__/RecentAlertsPanel.test.tsx b/frontend-modern/src/components/Alerts/__tests__/RecentAlertsPanel.test.tsx
index 4aec8acff..295a2184f 100644
--- a/frontend-modern/src/components/Alerts/__tests__/RecentAlertsPanel.test.tsx
+++ b/frontend-modern/src/components/Alerts/__tests__/RecentAlertsPanel.test.tsx
@@ -65,11 +65,43 @@ describe('RecentAlertsPanel', () => {
expect(screen.getByText('No active alerts')).toBeInTheDocument();
});
+ it('keeps structural warnings above state-only warnings of the same severity', () => {
+ render(() => (
+
- {infrastructurePresentation.label}
+
+ {infrastructurePresentation.label}
+
+ {props.infrastructure.total}
+
+
+ {props.infrastructure.online}
+ {' '}
+ online
+ {props.infrastructure.attention !== undefined &&
+ props.infrastructure.attention > 0 ? (
+ <>
+ {' · '}
+
+ {props.infrastructure.attention}
+ {' '}
+ attention
+ >
+ ) : null}
- {props.infrastructure.total}
-
-
- {props.infrastructure.online}
- {' '}
- online
-
{props.brief.body}
++ {props.brief.body} +