From 771d583f4afcbff99bb28c3fa1493b8e6cb96cd3 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 23 Aug 2026 19:31:34 +0100 Subject: [PATCH] Sync Proxmox node table with workload search --- .../subsystems/frontend-primitives.md | 5 ++++ .../internal/subsystems/storage-recovery.md | 11 ++++--- frontend-modern/browser-verification.json | 29 +++++++----------- .../features/proxmox/ProxmoxPageSurface.tsx | 1 + .../ProxmoxPageSurface.contract.test.tsx | 30 +++++++++++++------ 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index d6a162dba..fe0f06b69 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -1613,6 +1613,11 @@ Agent`), with the plain-language source phrase available through accessible and responsive/mobile rows must present the same preferred label while retaining native diagnostics; page-local formatting such as `cluster (node)` must not replace the backend-authored presentation. + On the Proxmox overview, the committed Workloads search is shared state for + both the guest table and the node table: guest matches retain their owning + node, direct node matches retain that node, and unrelated nodes are hidden. + The node table must use the same provider-scoped search vocabulary rather + than receiving an unfiltered copy of the estate. 8. Keep summary chart interaction identity on one shared helper. Summary surfaces that expose row-hover, group-hover, chart-hover, or route-focus-driven chart emphasis must derive page/group/entity scope through `frontend-modern/src/components/shared/summaryCardInteraction.ts` and pass that same resolved scope into card-state, sparkline, and density-map primitives, rather than letting cards read `hovered || focused` while charts listen to a different page-local ID source. Hovering one summary chart must promote that series into the shared active entity so sibling cards highlight the same object instead of keeping chart-local hover islands, and hovering or pinning a workload group header, infrastructure cluster header, or storage pool-group header must scope the matching summary cards through that same shared contract instead of forking a page-local summary filter path. Sibling cards should surface that synchronized hover as one compact header readout through the shared summary-card contract, while the chart under the pointer keeps the only floating tooltip. Recovery is explicitly outside this interaction dialect: its retired posture-card strip must not return with row/group/chart hover behavior without a separate governed product decision. 9. Keep page summaries page-scoped when table rows enter contextual focus. Route-backed row selection may add a focused label and shared series emphasis, but infrastructure, workloads, and storage summary cards must continue to render the page-level series set instead of collapsing the summary down to the selected row or replacing the global trend view with row-local empty states. 10. Keep contextual row focus on the shared summary primitive. Summary surfaces and same-route table drill-ins must reuse `frontend-modern/src/components/shared/contextualFocus.ts` for interactive-series filtering, focused-name lookup, active-series derivation, local scroll preservation, and deliberate inline-detail reveal instead of rebuilding page-local `Set` filters, focused-label scans, drawer-aware scroll math, or ad hoc scroll restoration in each surface. diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 54d7105a7..c0d68ed38 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -437,10 +437,13 @@ That bounded node preview remains before the virtual guest inventory at every viewport; narrow layouts must not move it after the full guest list's virtual scroll extent, where the node estate would become effectively undiscoverable. The workload filter belongs immediately before the workload table it controls, -after the node preview; it must not filter the node table. The nodes table must -always receive the real already-loaded PVE node collection, so committed or -draft workload terms can never manufacture the estate-empty `No Proxmox VE -nodes` state. That empty state is reserved for genuinely absent node inventory. +after the node preview, and its committed search term must also flow into the +node table. The node table must use the shared Proxmox search model so a +matching guest retains its owning node while unrelated nodes disappear; a +node search must likewise retain the directly matching node. The table still +receives the real already-loaded PVE node collection as its source, so the +estate-empty `No Proxmox VE nodes` state remains reserved for genuinely absent +node inventory, while a narrowed result uses the distinct no-match state. Those counts and layout controls must not imply protection, verification, or restore readiness beyond the evidence held by the workflow-owned Storage, Backups, Ceph, and Mail surfaces. diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index d75170d60..92fb82338 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,21 +1,16 @@ { "version": 1, - "base_sha": "ae9cfb1da87235e81359e251ec5f0a6670398ad9", - "verified_at": "2026-08-23T18:06:00Z", + "base_sha": "35b8c0a4c2ccbc11a1bb67116cb2d1e8f6d72391", + "verified_at": "2026-08-23T18:28:00Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx", - "frontend-modern/src/features/proxmox/proxmoxPageModel.ts", - "frontend-modern/src/routing/resourceLinks.ts" + "frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx" ], "content_sha256": { - "frontend-modern/src/features/proxmox/ProxmoxBackupsTable.tsx": "050b7e4336a467a71376b471f3e53cbcf65f11de088cd076efe0afe0943ecf31", - "frontend-modern/src/features/proxmox/proxmoxPageModel.ts": "09cebaf862d67def8e560b49acf50bdbcdc13f814fec15ad7fc8b1ae412a7fc1", - "frontend-modern/src/routing/resourceLinks.ts": "8a22520911eb1b84cee0ab4ef4388ec8dbfd40380db93dbac451d26f8031db3c" + "frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx": "5fbcb11d536ff657dcbffbe25c3b749bcf8bd4ff545dbdd0c02d3414c703c759" }, "routes": [ - "/proxmox/backups/date", - "/proxmox/backups/coverage" + "/proxmox/overview" ], "viewports": [ { @@ -28,15 +23,13 @@ } ], "states": [ - "Desktop direct navigation to /proxmox/backups/date renders the By date section with its chronological backup table and Coverage link", - "Desktop navigation to /proxmox/backups/coverage selects Coverage and renders posture filters and coverage rows", - "Narrow Coverage renders the route-backed section rail, responsive columns, and no horizontal overflow", - "The running app reported no console errors during direct navigation and section switching" + "Desktop direct navigation to /proxmox/overview rendered the connected Proxmox app shell and its overview loading state while the large mock estate hydrated", + "The running app reported a connected backend/live data stream and no browser console errors during the overview navigation", + "The large-estate narrowed node result is covered by the focused Proxmox page-surface and node-table tests" ], "interactions": [ - "Loaded /proxmox/backups/date at the desktop viewport and inspected the active section rail and date table", - "Selected Coverage through the canonical section link and verified /proxmox/backups/coverage with active navigation", - "Loaded Coverage at 390x844 and verified responsive section navigation, posture table content, and overflow state", - "Reset the temporary narrow viewport after verification" + "Loaded /proxmox/overview at the desktop viewport and inspected the connected overview shell", + "Confirmed the page exposes the Proxmox overview route used by the shared workload search", + "Verified the guest-aware node narrowing contract through the focused page-surface and node-table test suites" ] } diff --git a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx index 0c463da68..bfed82a1b 100644 --- a/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx +++ b/frontend-modern/src/features/proxmox/ProxmoxPageSurface.tsx @@ -333,6 +333,7 @@ function ProxmoxOverview(props: ProxmoxOverviewProps) { ({ })); vi.mock('../ProxmoxNodesTable', () => ({ - ProxmoxNodesTable: (props: { nodes: Resource[]; topology?: unknown }) => { + ProxmoxNodesTable: (props: { nodes: Resource[]; search?: () => string; topology?: unknown }) => { mockNodesTableProps(props); - return
; + return ( +
+ ); }, })); @@ -212,7 +218,7 @@ describe('ProxmoxPageSurface contract', () => { ); }); - it('keeps the node inventory independent from committed workload search terms', () => { + it('passes committed workload search terms to the node table', () => { mockWorkloadSearch.mockReturnValue('reporting-api-01, wireguard-edge-01'); setResources([ makeResource({ @@ -242,11 +248,17 @@ describe('ProxmoxPageSurface contract', () => { renderSurface(); expect(screen.getByTestId('nodes-table')).toHaveAttribute('data-rows', '2'); - expect(mockNodesTableProps).toHaveBeenCalledWith( - expect.objectContaining({ nodes: expect.arrayContaining([expect.any(Object)]) }), + expect(screen.getByTestId('nodes-table')).toHaveAttribute( + 'data-search', + 'reporting-api-01, wireguard-edge-01', ); - expect(proxmoxPageSurfaceSource).toContain('nodes={currentModel().pveNodes}'); - expect(proxmoxPageSurfaceSource).not.toContain('filterProxmoxNodesForSearch'); + expect(mockNodesTableProps).toHaveBeenCalledWith( + expect.objectContaining({ + nodes: expect.arrayContaining([expect.any(Object)]), + search: mockWorkloadSearch, + }), + ); + expect(proxmoxPageSurfaceSource).toContain('search={workloadsState.search}'); }); it('does not call a retained version from a stopped Proxmox agent currently running', () => { @@ -312,7 +324,7 @@ describe('ProxmoxPageSurface contract', () => { ); }); - it('keeps workload search scoped to guests instead of hiding the node inventory', () => { + it('shares workload search with the node inventory', () => { mockWorkloadSearch.mockReturnValue('pve-1'); setResources([ makeResource({ @@ -325,7 +337,7 @@ describe('ProxmoxPageSurface contract', () => { renderSurface(); expect(mockNodesTableProps).toHaveBeenLastCalledWith( - expect.not.objectContaining({ search: expect.anything() }), + expect.objectContaining({ search: mockWorkloadSearch }), ); });