diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index 815312f95..c58d4c968 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -105,6 +105,8 @@ regression protection. 83. `frontend-modern/src/components/Dashboard/GuestDrawer.test.tsx` 84. `frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts` 85. `frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx` +86. `frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts` +87. `frontend-modern/src/components/Dashboard/__tests__/useDashboardWorkloadViewportSync.test.tsx` ## Shared Boundaries @@ -130,7 +132,7 @@ regression protection. 12. Extend dashboard guest metadata cache persistence, metadata refresh, org-scope switching, and optimistic custom-URL updates through `frontend-modern/src/components/Dashboard/useDashboardGuestMetadataState.ts` rather than rebuilding dashboard-local storage caches, event listeners, or guest metadata API wiring inside `frontend-modern/src/components/Dashboard/useDashboardState.ts` 13. Extend dashboard deep-link selection and hovered-row continuity semantics through `frontend-modern/src/components/Dashboard/dashboardSelectionModel.ts`, and extend table scroll preservation plus reactive selection state through `frontend-modern/src/components/Dashboard/useDashboardSelectionState.ts`, rather than rebuilding resource-query parsing, selected-row scroll pinning, or hovered-row invalidation inside `frontend-modern/src/components/Dashboard/useDashboardState.ts` 14. Extend dashboard workload route ownership, route-driven option catalogs, and toolbar filter config through `frontend-modern/src/components/Dashboard/useDashboardWorkloadRouteState.ts`, `frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts`, `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteModel.ts`, `frontend-modern/src/components/Dashboard/dashboardWorkloadFilterConfigModel.ts`, and `frontend-modern/src/components/Dashboard/dashboardWorkloadRouteStateModel.ts`, and extend query-param synchronization plus managed workload URL semantics through `frontend-modern/src/components/Dashboard/useDashboardWorkloadUrlSync.ts` and `frontend-modern/src/components/Dashboard/dashboardWorkloadUrlSyncModel.ts`, rather than rebuilding route sync, alias parsing, option derivation, toolbar callback/config wiring, reset policy, node-selection compatibility rules, param precedence, or managed workload URLs inside `frontend-modern/src/components/Dashboard/useDashboardState.ts` -15. Extend grouped dashboard workload derivation, summary fallbacks, and grouped/windowed table presentation through `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts`, and extend node parent mapping through `frontend-modern/src/components/Dashboard/workloadTopology.ts`, rather than rebuilding grouped selectors, summary snapshot math, or topology lookups inside `frontend-modern/src/components/Dashboard/useDashboardState.ts` +15. Extend grouped dashboard workload derivation, summary fallbacks, and grouped/windowed table presentation through `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts`, extend viewport-driven grouped table synchronization through `frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts`, and extend node parent mapping through `frontend-modern/src/components/Dashboard/workloadTopology.ts`, rather than rebuilding grouped selectors, summary snapshot math, scroll listeners, or topology lookups inside `frontend-modern/src/components/Dashboard/useDashboardState.ts` 16. Extend dashboard control defaults, persistent view preferences, keyboard reset behavior, column-visibility ownership, and tag-search flow through `frontend-modern/src/components/Dashboard/useDashboardControlsState.ts` and `frontend-modern/src/components/Dashboard/dashboardFilterModel.ts` rather than rebuilding sort/search/grouping state, reset drift, or column-toggle plumbing inside `frontend-modern/src/components/Dashboard/useDashboardState.ts` 17. Extend dashboard filter active-count, reset semantics, and mobile toolbar state through `frontend-modern/src/components/Dashboard/dashboardFilterModel.ts` and `frontend-modern/src/components/Dashboard/useDashboardFilterState.ts`, rather than rebuilding filter-local state inside `frontend-modern/src/components/Dashboard/DashboardFilter.tsx` 18. Extend threshold-slider value-position math, title/label derivation, and drag scroll-lock runtime through `frontend-modern/src/components/Dashboard/thresholdSliderModel.ts` and `frontend-modern/src/components/Dashboard/useThresholdSliderState.ts` rather than rebuilding slider-local state and pointer lifecycle inside `frontend-modern/src/components/Dashboard/ThresholdSlider.tsx` @@ -138,7 +140,7 @@ regression protection. 20. Extend stacked memory-bar capacity math, balloon/swap derivation, and resize-observer runtime through `frontend-modern/src/components/Dashboard/stackedMemoryBarModel.ts` and `frontend-modern/src/components/Dashboard/useStackedMemoryBarState.ts` rather than rebuilding memory-bar-local state, tooltip shaping, and label-fit logic inside `frontend-modern/src/components/Dashboard/StackedMemoryBar.tsx` 21. Extend metric-bar width, label-fit logic, and resize-observer runtime through `frontend-modern/src/components/Dashboard/metricBarModel.ts` and `frontend-modern/src/components/Dashboard/useMetricBarState.ts` rather than rebuilding metric-local state and threshold mapping inside `frontend-modern/src/components/Dashboard/MetricBar.tsx` 22. Extend enhanced CPU bar usage/anomaly presentation and tooltip runtime through `frontend-modern/src/components/Dashboard/enhancedCpuBarModel.ts` and `frontend-modern/src/components/Dashboard/useEnhancedCPUBarState.ts` rather than rebuilding tooltip-local state and CPU-threshold formatting inside `frontend-modern/src/components/Dashboard/EnhancedCPUBar.tsx` -23. Extend grouped dashboard row windowing, reveal-index clamping, overscan math, and per-group visible-slice derivation through `frontend-modern/src/components/Dashboard/useGroupedTableWindowing.ts` rather than rebuilding scroll handlers, mounted-row budgets, or group-slice math inside `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts` +23. Extend grouped dashboard row windowing, reveal-index clamping, overscan math, and per-group visible-slice derivation through `frontend-modern/src/components/Dashboard/useGroupedTableWindowing.ts`, and extend viewport event wiring through `frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts` rather than rebuilding scroll handlers, mounted-row budgets, viewport listeners, or group-slice math inside `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts` 24. Extend dashboard shell rendering through `frontend-modern/src/components/Dashboard/DashboardStateCards.tsx`, `frontend-modern/src/components/Dashboard/DashboardWorkloadTable.tsx`, and `frontend-modern/src/components/Dashboard/DashboardStatsStrip.tsx` rather than accreting loading cards, workload table markup, or stats-strip presentation back into `frontend-modern/src/components/Dashboard/Dashboard.tsx` 25. Extend dashboard workload table shell ownership through `frontend-modern/src/components/Dashboard/WorkloadTableHeader.tsx` and `frontend-modern/src/components/Dashboard/WorkloadPanel.tsx` rather than rebuilding sortable header markup, grouped node rows, row expansion, or guest-drawer rendering inside `frontend-modern/src/components/Dashboard/DashboardWorkloadTable.tsx` @@ -178,6 +180,9 @@ behavior, column visibility, and summary display preferences, while `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts` owns grouped workload derivation, summary fallbacks, parent-node mapping, and grouped/windowed table math, while +`frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts` +owns grouped workload viewport synchronization and the scroll/resize listener +lifecycle, while `frontend-modern/src/components/Dashboard/useDashboardGuestMetadataState.ts` owns guest metadata cache persistence, optimistic custom-URL updates, org-scope switching, and metadata refresh, and @@ -191,6 +196,12 @@ owns row-window thresholds, overscan behavior, reveal-index clamping, and per-group visible-slice derivation. Future dashboard table windowing changes must extend through that hook instead of rebuilding scroll math or mounted-row budgets inline inside `frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts`. +Viewport-driven grouped table synchronization now also routes through +`frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts`, +which owns the dashboard table body measurement and the scroll/resize listener +lifecycle. Future viewport sync changes must extend through that hook rather +than rebuilding browser-event wiring or table-body geometry reads inside +`frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts`. The dashboard guest-row path now follows the same pattern: the render shell stays in `frontend-modern/src/components/Dashboard/GuestRow.tsx`, tooltip-backed cell presentation lives in `frontend-modern/src/components/Dashboard/GuestRowCells.tsx`, diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index e8da4d821..e3d0a7d2c 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -2591,6 +2591,7 @@ "frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts", "frontend-modern/src/components/Dashboard/useDashboardWorkloadRouteState.ts", "frontend-modern/src/components/Dashboard/useDashboardWorkloadUrlSync.ts", + "frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts", "frontend-modern/src/components/Dashboard/useDiskListState.ts", "frontend-modern/src/components/Dashboard/useEnhancedCPUBarState.ts", "frontend-modern/src/components/Dashboard/useGroupedTableWindowing.ts", @@ -2628,6 +2629,7 @@ "frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx", "frontend-modern/src/components/Dashboard/__tests__/useDashboardFilterState.test.ts", "frontend-modern/src/components/Dashboard/__tests__/useDashboardSelectionState.test.ts", + "frontend-modern/src/components/Dashboard/__tests__/useDashboardWorkloadViewportSync.test.tsx", "frontend-modern/src/components/Dashboard/__tests__/useEnhancedCPUBarState.test.tsx", "frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts", "frontend-modern/src/components/Dashboard/__tests__/useMetricBarState.test.tsx", @@ -2704,6 +2706,7 @@ "frontend-modern/src/components/Dashboard/useDashboardWorkloadFilterOptions.ts", "frontend-modern/src/components/Dashboard/useDashboardWorkloadRouteState.ts", "frontend-modern/src/components/Dashboard/useDashboardWorkloadUrlSync.ts", + "frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts", "frontend-modern/src/components/Dashboard/useDiskListState.ts", "frontend-modern/src/components/Dashboard/useEnhancedCPUBarState.ts", "frontend-modern/src/components/Dashboard/useGroupedTableWindowing.ts", @@ -2738,6 +2741,7 @@ "frontend-modern/src/components/Dashboard/__tests__/StackedDiskBar.test.tsx", "frontend-modern/src/components/Dashboard/__tests__/useDashboardFilterState.test.ts", "frontend-modern/src/components/Dashboard/__tests__/useDashboardSelectionState.test.ts", + "frontend-modern/src/components/Dashboard/__tests__/useDashboardWorkloadViewportSync.test.tsx", "frontend-modern/src/components/Dashboard/__tests__/useEnhancedCPUBarState.test.tsx", "frontend-modern/src/components/Dashboard/__tests__/useGroupedTableWindowing.test.ts", "frontend-modern/src/components/Dashboard/__tests__/useMetricBarState.test.tsx", diff --git a/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx b/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx index 93f3cadc0..84d7f7ab7 100644 --- a/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx +++ b/frontend-modern/src/components/Dashboard/__tests__/Dashboard.performance.contract.test.tsx @@ -21,6 +21,7 @@ import dashboardWorkloadFilterConfigModelSource from '../dashboardWorkloadFilter import dashboardWorkloadRouteModelSource from '../dashboardWorkloadRouteModel.ts?raw'; import dashboardWorkloadRouteStateModelSource from '../dashboardWorkloadRouteStateModel.ts?raw'; import dashboardWorkloadUrlSyncModelSource from '../dashboardWorkloadUrlSyncModel.ts?raw'; +import dashboardWorkloadViewportSyncSource from '../useDashboardWorkloadViewportSync.ts?raw'; import dashboardWorkloadRouteStateSource from '../useDashboardWorkloadRouteState.ts?raw'; import dashboardWorkloadUrlSyncSource from '../useDashboardWorkloadUrlSync.ts?raw'; import dashboardStateSource from '../useDashboardState.ts?raw'; @@ -646,6 +647,13 @@ describe('Dashboard performance contract', () => { expect(dashboardWorkloadDerivedStateSource).toContain('buildNodeByInstance('); expect(dashboardWorkloadDerivedStateSource).toContain('buildGuestParentNodeMap('); expect(dashboardWorkloadDerivedStateSource).toContain('useGroupedTableWindowing'); + expect(dashboardWorkloadDerivedStateSource).toContain('useDashboardWorkloadViewportSync'); + expect(dashboardWorkloadDerivedStateSource).not.toContain('window.addEventListener'); + expect(dashboardWorkloadDerivedStateSource).not.toContain('getBoundingClientRect'); + expect(dashboardWorkloadViewportSyncSource).toContain('window.addEventListener'); + expect(dashboardWorkloadViewportSyncSource).toContain('window.removeEventListener'); + expect(dashboardWorkloadViewportSyncSource).toContain('getBoundingClientRect'); + expect(dashboardWorkloadViewportSyncSource).toContain('groupedWindowing.onScroll'); expect(dashboardWorkloadRouteStateSource).not.toContain("from './workloadTopology'"); expect(dashboardWorkloadRouteModelSource).toContain("from './workloadTopology'"); expect(dashboardWorkloadRouteModelSource).toContain('workloadNodeScopeId'); @@ -701,6 +709,7 @@ describe('Dashboard performance contract', () => { expect(dashboardWorkloadRouteStateSource).toContain('hostFilterConfig'); expect(dashboardWorkloadRouteStateSource).toContain('namespaceFilterConfig'); expect(dashboardWorkloadDerivedStateSource).toContain('useGroupedTableWindowing'); + expect(dashboardWorkloadDerivedStateSource).toContain('useDashboardWorkloadViewportSync'); }); it('keeps threshold slider runtime and derivations in canonical slider owners', () => { diff --git a/frontend-modern/src/components/Dashboard/__tests__/useDashboardWorkloadViewportSync.test.tsx b/frontend-modern/src/components/Dashboard/__tests__/useDashboardWorkloadViewportSync.test.tsx new file mode 100644 index 000000000..ffb6aa377 --- /dev/null +++ b/frontend-modern/src/components/Dashboard/__tests__/useDashboardWorkloadViewportSync.test.tsx @@ -0,0 +1,83 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { cleanup, render, waitFor } from '@solidjs/testing-library'; +import { createSignal } from 'solid-js'; + +import { useDashboardWorkloadViewportSync } from '@/components/Dashboard/useDashboardWorkloadViewportSync'; +import type { UseGroupedTableWindowingResult } from '@/components/Dashboard/useGroupedTableWindowing'; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +describe('useDashboardWorkloadViewportSync', () => { + it('owns grouped workload viewport sync and listener cleanup', async () => { + const onScroll = vi.fn(); + const addEventListenerSpy = vi.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = vi.spyOn(window, 'removeEventListener'); + const groupedWindowing: UseGroupedTableWindowingResult = { + endIndex: () => 10, + getVisibleSlice: (_groupKey, guests) => guests, + isWindowed: () => true, + mountedCount: () => 10, + onScroll, + revealIndex: vi.fn(), + startIndex: () => 0, + }; + + const Harness = () => { + const [bodyRef, setBodyRef] = createSignal(null); + + useDashboardWorkloadViewportSync({ + filteredGuestCount: () => 640, + groupedWindowing, + rowHeight: 32, + tableBodyRef: bodyRef, + }); + + return ( + + { + vi.spyOn(element, 'getBoundingClientRect').mockReturnValue({ + bottom: 400, + height: 320, + left: 0, + right: 0, + toJSON: () => ({}), + top: -96, + width: 800, + x: 0, + y: -96, + } as DOMRect); + setBodyRef(element); + }} + /> +
+ ); + }; + + const { unmount } = render(() => ); + + await waitFor(() => { + expect(onScroll).toHaveBeenCalledWith(96, window.innerHeight, 32); + }); + + expect(addEventListenerSpy).toHaveBeenCalledWith( + 'scroll', + expect.any(Function), + { passive: true }, + ); + expect(addEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function)); + + window.dispatchEvent(new Event('scroll')); + await waitFor(() => { + expect(onScroll).toHaveBeenCalledTimes(2); + }); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith('resize', expect.any(Function)); + }); +}); diff --git a/frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts b/frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts index 80a78e479..8964d1103 100644 --- a/frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts +++ b/frontend-modern/src/components/Dashboard/useDashboardWorkloadDerivedState.ts @@ -1,4 +1,4 @@ -import { createEffect, createMemo, onCleanup, type Accessor } from 'solid-js'; +import { createMemo, type Accessor } from 'solid-js'; import type { Node } from '@/types/api'; import type { WorkloadGuest } from '@/types/workloads'; @@ -17,6 +17,7 @@ import { buildNodeByInstance, buildGuestParentNodeMap, } from './workloadTopology'; +import { useDashboardWorkloadViewportSync } from './useDashboardWorkloadViewportSync'; import { useGroupedTableWindowing } from './useGroupedTableWindowing'; type GroupingMode = 'grouped' | 'flat'; @@ -212,38 +213,16 @@ export function useDashboardWorkloadDerivedState( groupedWindowing.isWindowed() ? Math.max( 0, - (options.filteredGuests().length - groupedWindowing.endIndex()) * - DASHBOARD_TABLE_ESTIMATED_ROW_HEIGHT, + (options.filteredGuests().length - groupedWindowing.endIndex()) * 32, ) : 0, ); - const syncGuestWindowToViewport = () => { - if (!groupedWindowing.isWindowed() || typeof window === 'undefined') return; - const body = options.tableBodyRef(); - if (!body) return; - const rect = body.getBoundingClientRect(); - const scrollTop = Math.max(0, -rect.top); - groupedWindowing.onScroll(scrollTop, window.innerHeight, DASHBOARD_TABLE_ESTIMATED_ROW_HEIGHT); - }; - - createEffect(() => { - if (typeof window === 'undefined') return; - options.filteredGuests().length; - if (!groupedWindowing.isWindowed()) return; - if (!options.tableBodyRef()) return; - - const handleViewportChange = () => { - syncGuestWindowToViewport(); - }; - - handleViewportChange(); - window.addEventListener('scroll', handleViewportChange, { passive: true }); - window.addEventListener('resize', handleViewportChange); - onCleanup(() => { - window.removeEventListener('scroll', handleViewportChange); - window.removeEventListener('resize', handleViewportChange); - }); + useDashboardWorkloadViewportSync({ + filteredGuestCount: () => options.filteredGuests().length, + groupedWindowing, + rowHeight: DASHBOARD_TABLE_ESTIMATED_ROW_HEIGHT, + tableBodyRef: options.tableBodyRef, }); const totalStats = createMemo(() => computeWorkloadStats(options.filteredGuests())); diff --git a/frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts b/frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts new file mode 100644 index 000000000..441068dff --- /dev/null +++ b/frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts @@ -0,0 +1,46 @@ +import { createEffect, onCleanup, type Accessor } from 'solid-js'; + +import type { UseGroupedTableWindowingResult } from './useGroupedTableWindowing'; + +interface DashboardWorkloadViewportSyncOptions { + filteredGuestCount: Accessor; + groupedWindowing: UseGroupedTableWindowingResult; + rowHeight: number; + tableBodyRef: Accessor; +} + +export function useDashboardWorkloadViewportSync( + options: DashboardWorkloadViewportSyncOptions, +) { + const syncGuestWindowToViewport = () => { + if (!options.groupedWindowing.isWindowed() || typeof window === 'undefined') return; + const body = options.tableBodyRef(); + if (!body) return; + const rect = body.getBoundingClientRect(); + const scrollTop = Math.max(0, -rect.top); + options.groupedWindowing.onScroll( + scrollTop, + window.innerHeight, + options.rowHeight, + ); + }; + + createEffect(() => { + if (typeof window === 'undefined') return; + options.filteredGuestCount(); + if (!options.groupedWindowing.isWindowed()) return; + if (!options.tableBodyRef()) return; + + const handleViewportChange = () => { + syncGuestWindowToViewport(); + }; + + handleViewportChange(); + window.addEventListener('scroll', handleViewportChange, { passive: true }); + window.addEventListener('resize', handleViewportChange); + onCleanup(() => { + window.removeEventListener('scroll', handleViewportChange); + window.removeEventListener('resize', handleViewportChange); + }); + }); +} diff --git a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts index b9999ea4b..9c60e3eac 100644 --- a/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts +++ b/frontend-modern/src/utils/__tests__/frontendResourceTypeBoundaries.test.ts @@ -92,6 +92,7 @@ import dashboardGuestMetadataStateSource from '@/components/Dashboard/useDashboa import dashboardSelectionModelSource from '@/components/Dashboard/dashboardSelectionModel.ts?raw'; import dashboardSelectionStateSource from '@/components/Dashboard/useDashboardSelectionState.ts?raw'; import dashboardWorkloadDerivedStateSource from '@/components/Dashboard/useDashboardWorkloadDerivedState.ts?raw'; +import dashboardWorkloadViewportSyncSource from '@/components/Dashboard/useDashboardWorkloadViewportSync.ts?raw'; import dashboardWorkloadFilterOptionsSource from '@/components/Dashboard/useDashboardWorkloadFilterOptions.ts?raw'; import dashboardWorkloadFilterConfigModelSource from '@/components/Dashboard/dashboardWorkloadFilterConfigModel.ts?raw'; import dashboardWorkloadRouteModelSource from '@/components/Dashboard/dashboardWorkloadRouteModel.ts?raw'; @@ -698,6 +699,9 @@ describe('frontend resource type boundaries', () => { expect(dashboardWorkloadRouteStateSource).toContain('hostFilterConfig'); expect(dashboardWorkloadRouteStateSource).toContain('namespaceFilterConfig'); expect(dashboardWorkloadDerivedStateSource).toContain('useGroupedTableWindowing'); + expect(dashboardWorkloadDerivedStateSource).toContain('useDashboardWorkloadViewportSync'); + expect(dashboardWorkloadDerivedStateSource).not.toContain('window.addEventListener'); + expect(dashboardWorkloadDerivedStateSource).not.toContain('getBoundingClientRect'); expect(dashboardStateSource).not.toContain('const DEFAULT_WINDOW_SIZE ='); expect(dashboardStateSource).not.toContain('const DEFAULT_ENABLE_THRESHOLD ='); expect(dashboardStateSource).not.toContain('const DEFAULT_OVERSCAN_ROWS ='); @@ -707,6 +711,10 @@ describe('frontend resource type boundaries', () => { expect(groupedTableWindowingSource).toContain('getVisibleSlice'); expect(groupedTableWindowingSource).toContain('onScroll'); expect(groupedTableWindowingSource).toContain('revealIndex'); + expect(dashboardWorkloadViewportSyncSource).toContain('window.addEventListener'); + expect(dashboardWorkloadViewportSyncSource).toContain('window.removeEventListener'); + expect(dashboardWorkloadViewportSyncSource).toContain('getBoundingClientRect'); + expect(dashboardWorkloadViewportSyncSource).toContain('groupedWindowing.onScroll'); expect(workloadTopologySource).toContain('export const workloadNodeScopeId'); expect(workloadTopologySource).toContain('export const getKubernetesContextKey'); expect(workloadTopologySource).toContain('export const getWorkloadDockerHostId'); diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 2aec42df6..20b217108 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -2160,6 +2160,33 @@ class SubsystemLookupTest(unittest.TestCase): "dashboard-workload-hot-path", ) + def test_lookup_paths_assigns_dashboard_workload_viewport_sync_runtime_to_performance_and_scalability( + self, + ) -> None: + result = lookup_paths( + ["frontend-modern/src/components/Dashboard/useDashboardWorkloadViewportSync.ts"] + ) + self.assertEqual(result["unowned_runtime_files"], []) + self.assertEqual( + {item["subsystem"] for item in result["impacted_subsystems"]}, + {"performance-and-scalability"}, + ) + file_entry = result["files"][0] + self.assertEqual(file_entry["classification"], "runtime") + self.assertEqual( + {match["subsystem"] for match in file_entry["matches"]}, + {"performance-and-scalability"}, + ) + match = file_entry["matches"][0] + self.assertEqual( + match["contract"], + "docs/release-control/v6/internal/subsystems/performance-and-scalability.md", + ) + self.assertEqual( + match["verification_requirement"]["id"], + "dashboard-workload-hot-path", + ) + def test_lookup_paths_assigns_dashboard_controls_state_runtime_to_performance_and_scalability( self, ) -> None: