diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index a3ae3366f..84fcd4e6e 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -3691,6 +3691,10 @@ drift back to a single-scope gate: a registered phone carries only mobile alert sync on upgrade (v6.1.0-rc.4 regression). Lists use bounded pagination with a maximum of 200 records and one bounded protection-posture batch. The summary path does not read recovery history. +The Patrol workbench may project the summary counts into its fixed lifecycle +filter labels at either desktop or narrow widths, but changing presentation +does not create a parallel count or filter vocabulary: every selection still +uses the canonical `AttentionFilter` request and the returned list summary. Lifecycle read failure returns a typed unavailable response and never a synthetic zero or calm state. Protection lookup failure may return lifecycle diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 3bd0f071c..f40bcf39d 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -1111,9 +1111,14 @@ not a replacement status card, CTA band, or page-local nested card. columns. Compact, stable, mutually-exclusive filters with two to five options should use `LabeledFilterToggleGroup` as a responsive control: toggle buttons at wide desktop widths and the native - select fallback below that. Dynamic, user/environment-sized, or six-plus - option filters remain `LabeledFilterSelect` surfaces so estate-sized lists - such as nodes never become button groups. Filters that change which other + select fallback below that. Dynamic and user/environment-sized option + filters remain `LabeledFilterSelect` surfaces so estate-sized lists such as + nodes never become button groups. A fixed six-state operational lifecycle + may use `LabeledFilterToggleGroup` when every choice is a frequent triage + action, the complete segmented group fits at its wide breakpoint, and the + native select fallback owns all narrower layouts; this bounded exception + must not be generalized to dynamic six-plus filters. Filters that change + which other filters exist, such as Workloads Type, must stay in a stable primary filter band ahead of the dependent estate/data filters so changing the parent filter does not move its own click target or the adjacent primary filters; diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index 3d87fc027..de45d323e 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -954,7 +954,11 @@ The primary Patrol workspace is now must not derive lifecycle state from legacy Patrol findings. Active, open, acknowledged, suppressed, stale/unknown, recent-resolved, calm, partial, and unavailable states all come from the same backend projection. Navigation and -the queue consume the same active summary. +the queue consume the same active summary. Those six fixed lifecycle views are +frequent operator triage choices: wide layouts retain one-click segmented +choices with the backend-projected count in every label, while narrower +layouts use the shared labeled select fallback. The control must not become a +horizontally scrolling rail that leaves a lifecycle view clipped or hidden. The default view owns only the ordered action queue. Legacy Patrol checks, investigations, and run history remain available under collapsed supporting diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 52339dccb..b92a96abe 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,25 +1,16 @@ { "version": 1, - "base_sha": "caaf5ed232c293331f9c3aec4d19788bf220f18a", - "verified_at": "2026-08-03T18:33:55Z", + "base_sha": "d00249fd7ddcc6cee0f5f35911dd6c0127cf1f33", + "verified_at": "2026-08-03T18:47:21Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/shared/horizontalRailVisibilityModel.ts", - "frontend-modern/src/components/shared/useActiveHorizontalRailItemVisibility.ts", - "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx", - "frontend-modern/src/pages/Alerts.tsx" + "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx" ], "content_sha256": { - "frontend-modern/src/components/shared/horizontalRailVisibilityModel.ts": "670f68a047a3e9dd8fda676e7a46819d2fd98b99ae19d493161ffb0c626c177d", - "frontend-modern/src/components/shared/useActiveHorizontalRailItemVisibility.ts": "19488bf7edf26d30c966b962254acfe588b26af86cc4d606dc32cf4d95360f54", - "frontend-modern/src/features/platformPage/sharedPlatformPage.tsx": "9c6abbb54e00786351d3185e70ecf231df1f4caabf110d3a69010ed0c5584a17", - "frontend-modern/src/pages/Alerts.tsx": "0c411a83c86d2aa448fe21fb01264794832154abc45b456e2a549d336f03d703" + "frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx": "87e314940a667c8ff49e2aaf63b45f8d3f6278c3c01747fcaf9f850295d3e413" }, "routes": [ - "/alerts/schedule", - "/alerts/overview", - "/alerts/notifications", - "/docker/networks" + "/patrol" ], "viewports": [ { @@ -32,17 +23,15 @@ } ], "states": [ - "Phone-width Alerts Schedule route loaded directly with the active destination fully visible", - "Phone-width Alerts Overview selected after starting from the right edge of the rail", - "Phone-width Alerts Notifications selected and fully contained", - "Desktop Alerts Notifications surface with canonical sidebar navigation", - "Phone-width Docker Networks surface after the platform rail adopted the shared visibility owner" + "Desktop Patrol attention workbench with all six counted lifecycle choices visible in the segmented control", + "Desktop Recent resolved selection with the resolved queue item loaded", + "Narrow Patrol attention workbench with the segmented control hidden and the labeled native select visible", + "Narrow Recent resolved selection with the resolved queue item loaded" ], "interactions": [ - "Loaded /alerts/schedule directly at 390 by 844 and confirmed the rail moved from scrollLeft 0 to 55 so Schedule was fully visible", - "Selected Overview and confirmed the active destination changed and the rail returned to scrollLeft 0", - "Selected Notifications and confirmed its active button remained fully inside the rail bounds", - "Resized to 1280 by 720 and confirmed Alerts retained its desktop sidebar navigation with no document overflow", - "Reloaded /docker/networks at 390 by 844 and confirmed the active Networks destination remained fully visible after the shared hook extraction" + "Selected Recent resolved from the desktop segmented control and confirmed its pressed state and resolved queue result", + "Reloaded /patrol at 390 by 844 and confirmed the Attention state select and every counted option remained available without horizontal overflow", + "Selected Recent resolved from the narrow native select and confirmed its selected state and resolved queue result", + "Measured the desktop segmented group inside the viewport and the narrow document at exactly 390 pixels wide with the wide group display set to none" ] } diff --git a/frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx b/frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx index 5c2f14efe..8dc4e64f9 100644 --- a/frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx +++ b/frontend-modern/src/features/patrol/PatrolAttentionWorkbench.tsx @@ -34,6 +34,7 @@ import { } from '@/api/patrolAttention'; import { ResourceActionsAPI } from '@/api/resourceActions'; import { Button, ButtonLink } from '@/components/shared/Button'; +import { LabeledFilterToggleGroup } from '@/components/shared/FilterToolbar'; import { LoadingSpinner } from '@/components/shared/LoadingSpinner'; import { MetadataBadge, type MetadataBadgeTone } from '@/components/shared/MetadataBadge'; import { ActionReviewDialog } from '@/features/actions/ActionReviewDialog'; @@ -95,6 +96,15 @@ export function PatrolAttentionWorkbench() { return undefined; } }; + const attentionFilterOptions = createMemo(() => + FILTERS.map((option) => { + const count = filterCount(option.id); + return { + value: option.id, + label: count === undefined ? option.label : `${option.label} ${count}`, + }; + }), + ); const loadCurrentFilter = () => patrolAttentionStore.load(patrolAttentionStore.filter()); const scrollDetailIntoView = () => { @@ -248,34 +258,15 @@ export function PatrolAttentionWorkbench() { -