Split infrastructure page route sync owner

This commit is contained in:
rcourtman 2026-03-23 00:28:01 +00:00
parent 44249ed048
commit 96b67fbcdd
10 changed files with 298 additions and 171 deletions

View file

@ -153,8 +153,10 @@ Top-level route files are now also expected to stay thin when a feature owns
the real product surface. `frontend-modern/src/pages/Infrastructure.tsx` now
acts only as the route boundary, while
`frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
and `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
own the actual infrastructure page shell and state contract. Future feature
owns the shell, `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
owns page-control composition, and
`frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
owns infrastructure route/deep-link synchronization. Future feature
surfaces under `frontend-modern/src/features/` should follow that same pattern
instead of letting page files accumulate route sync, filter, and modal
orchestration inline.

View file

@ -3171,6 +3171,7 @@
"frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts",
"frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts",
"frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx",
"frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts",
"frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts",
"frontend-modern/src/hooks/useDashboardTrends.ts",
"frontend-modern/src/hooks/useUnifiedResources.ts",
@ -3255,6 +3256,7 @@
"frontend-modern/src/components/Infrastructure/useResourceDetailDrawerState.ts",
"frontend-modern/src/components/Infrastructure/useUnifiedResourceTableState.ts",
"frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx",
"frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts",
"frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts",
"frontend-modern/src/hooks/useDashboardTrends.ts",
"frontend-modern/src/hooks/useUnifiedResources.ts",
@ -3271,6 +3273,7 @@
"frontend-modern/src/components/Infrastructure/__tests__/resourceDetailMappers.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.workloads-link.test.tsx",
"frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts",
"frontend-modern/src/hooks/__tests__/useDashboardTrends.test.ts",
"frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts",
"frontend-modern/src/pages/__tests__/Infrastructure.empty-state.test.tsx",

View file

@ -60,7 +60,8 @@ cross-source deduplication.
38. `frontend-modern/src/components/Discovery/DiscoveryTab.tsx`
39. `frontend-modern/src/components/Discovery/useDiscoveryTabState.ts`
40. `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
41. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
41. `frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
42. `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
## Shared Boundaries
@ -772,6 +773,13 @@ The route file `frontend-modern/src/pages/Infrastructure.tsx` is now only the
navigation boundary for that surface; canonical infrastructure filter, search,
deep-link, and expansion state now live behind the dedicated infrastructure
feature owner instead of accumulating in the page shell itself.
That infrastructure feature now also follows an explicit shell/composition/route
split: `frontend-modern/src/features/infrastructure/InfrastructurePageSurface.tsx`
owns the render shell, `frontend-modern/src/features/infrastructure/useInfrastructurePageState.ts`
owns page controls and filtered-resource composition, and
`frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts`
owns URL-sync, deep-link expansion, highlight continuity, and managed
infrastructure-route navigation.
Shared unified-resource consumers now also normalize org scope through
`frontend-modern/src/utils/orgScope.ts` before building cache keys or
multi-tenant resource fetch state, so the canonical resource hooks do not

View file

@ -1,4 +1,5 @@
import { For, Show } from 'solid-js';
import { useNavigate } from '@solidjs/router';
import { EmptyState } from '@/components/shared/EmptyState';
import { Card } from '@/components/shared/Card';
import { FilterSegmentedControl, LabeledFilterSelect } from '@/components/shared/FilterToolbar';
@ -22,6 +23,7 @@ import {
import { useInfrastructurePageState, type GroupingMode } from './useInfrastructurePageState';
export function InfrastructurePageSurface() {
const navigate = useNavigate();
const sourceOptions = DEFAULT_INFRASTRUCTURE_SOURCE_OPTIONS;
const {
loading,
@ -59,7 +61,6 @@ export function InfrastructurePageSurface() {
clearFilters,
filteredResources,
hasFilteredResources,
handleNavigateToSettings,
} = useInfrastructurePageState();
const infrastructureEmptyState = () => getInfrastructureEmptyState();
@ -118,7 +119,7 @@ export function InfrastructurePageSurface() {
actions={
<button
type="button"
onClick={handleNavigateToSettings}
onClick={() => navigate('/settings')}
class="inline-flex items-center gap-2 rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content shadow-sm hover:bg-slate-50"
>
<SettingsIcon class="h-3.5 w-3.5" />

View file

@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import infrastructurePageSurfaceSource from '@/features/infrastructure/InfrastructurePageSurface.tsx?raw';
import infrastructurePageStateSource from '@/features/infrastructure/useInfrastructurePageState.ts?raw';
import infrastructurePageRouteStateSource from '@/features/infrastructure/useInfrastructurePageRouteState.ts?raw';
describe('InfrastructurePageSurface guardrails', () => {
it('keeps the feature shell separate from route-sync ownership', () => {
expect(infrastructurePageSurfaceSource).toContain('useInfrastructurePageState');
expect(infrastructurePageSurfaceSource).toContain('useNavigate');
expect(infrastructurePageSurfaceSource).not.toContain('useLocation(');
expect(infrastructurePageSurfaceSource).not.toContain('buildInfrastructurePath(');
expect(infrastructurePageStateSource).toContain('useInfrastructurePageRouteState');
expect(infrastructurePageStateSource).not.toContain('useLocation(');
expect(infrastructurePageStateSource).not.toContain('useNavigate(');
expect(infrastructurePageStateSource).not.toContain('parseInfrastructureLinkSearch(');
expect(infrastructurePageStateSource).not.toContain('buildInfrastructurePath(');
expect(infrastructurePageStateSource).not.toContain('areSearchParamsEquivalent(');
expect(infrastructurePageRouteStateSource).toContain('useLocation');
expect(infrastructurePageRouteStateSource).toContain('useNavigate');
expect(infrastructurePageRouteStateSource).toContain('parseInfrastructureLinkSearch');
expect(infrastructurePageRouteStateSource).toContain('buildInfrastructurePath');
expect(infrastructurePageRouteStateSource).toContain('areSearchParamsEquivalent');
});
});

View file

@ -0,0 +1,192 @@
import { createEffect, createSignal, onCleanup, type Accessor, type Setter, untrack } from 'solid-js';
import { useLocation, useNavigate } from '@solidjs/router';
import type { Resource } from '@/types/resource';
import { normalizeSourcePlatformKey } from '@/utils/sourcePlatforms';
import {
buildInfrastructurePath,
INFRASTRUCTURE_PATH,
INFRASTRUCTURE_QUERY_PARAMS,
parseInfrastructureLinkSearch,
} from '@/routing/resourceLinks';
import { areSearchParamsEquivalent } from '@/utils/searchParams';
interface InfrastructurePageRouteStateOptions {
resources: Accessor<Resource[]>;
filteredResources: Accessor<Resource[]>;
initialLoadComplete: Accessor<boolean>;
selectedSource: Accessor<string>;
setSelectedSource: Setter<string>;
searchQuery: Accessor<string>;
setSearchQuery: Setter<string>;
}
export function useInfrastructurePageRouteState(options: InfrastructurePageRouteStateOptions) {
const {
resources,
filteredResources,
initialLoadComplete,
selectedSource,
setSelectedSource,
searchQuery,
setSearchQuery,
} = options;
const location = useLocation();
const navigate = useNavigate();
const [expandedResourceId, setExpandedResourceId] = createSignal<string | null>(null);
const [hoveredResourceId, setHoveredResourceId] = createSignal<string | null>(null);
const [highlightedResourceId, setHighlightedResourceId] = createSignal<string | null>(null);
const [handledResourceId, setHandledResourceId] = createSignal<string | null>(null);
const [handledSourceParam, setHandledSourceParam] = createSignal<string | null>(null);
const [handledQueryParam, setHandledQueryParam] = createSignal('');
let highlightTimer: number | undefined;
let pendingUrlSyncHandle: number | null = null;
let pendingUrlSyncPath: string | null = null;
const scheduleUrlSyncNavigate = (nextPath: string) => {
pendingUrlSyncPath = nextPath;
if (pendingUrlSyncHandle !== null) return;
pendingUrlSyncHandle = window.setTimeout(() => {
pendingUrlSyncHandle = null;
const target = pendingUrlSyncPath;
pendingUrlSyncPath = null;
if (!target) return;
const current = `${untrack(() => location.pathname)}${untrack(() => location.search)}`;
if (current === target) return;
navigate(target, { replace: true });
}, 0);
};
createEffect(() => {
const { resource: resourceId } = parseInfrastructureLinkSearch(location.search);
if (!resourceId || resourceId === handledResourceId()) return;
const matching = resources().some((resource) => resource.id === resourceId);
if (!matching) return;
setExpandedResourceId(resourceId);
setHighlightedResourceId(resourceId);
setHandledResourceId(resourceId);
if (highlightTimer) {
window.clearTimeout(highlightTimer);
}
highlightTimer = window.setTimeout(() => {
setHighlightedResourceId(null);
}, 2000);
});
createEffect(() => {
const { resource: resourceId } = parseInfrastructureLinkSearch(location.search);
if (resourceId) return;
if (handledResourceId() === null) return;
if (expandedResourceId() !== null) {
setExpandedResourceId(null);
}
if (highlightedResourceId() !== null) {
setHighlightedResourceId(null);
}
setHandledResourceId(null);
});
createEffect(() => {
const { source: sourceParam } = parseInfrastructureLinkSearch(location.search);
if (!sourceParam) {
const previous = (handledSourceParam() ?? '').trim();
if (previous) {
if (selectedSource() !== '') setSelectedSource('');
setHandledSourceParam('');
} else if (handledSourceParam() === null) {
setHandledSourceParam('');
}
return;
}
if (sourceParam === handledSourceParam()) return;
const normalized = normalizeSourcePlatformKey(sourceParam) ?? '';
setSelectedSource(normalized);
setHandledSourceParam(sourceParam);
});
createEffect(() => {
const { query: nextSearch } = parseInfrastructureLinkSearch(location.search);
const normalized = nextSearch ?? '';
if (normalized !== handledQueryParam()) {
if (normalized !== untrack(searchQuery)) {
setSearchQuery(normalized);
}
setHandledQueryParam(normalized);
}
});
createEffect(() => {
if (location.pathname !== INFRASTRUCTURE_PATH) return;
const parsed = parseInfrastructureLinkSearch(location.search);
const urlSource = parsed.source ?? '';
const urlQuery = parsed.query ?? '';
const urlResource = parsed.resource ?? '';
if ((handledSourceParam() ?? '') !== urlSource) return;
if (handledQueryParam() !== urlQuery) return;
if (urlResource && handledResourceId() !== urlResource && !initialLoadComplete()) return;
const nextSource = selectedSource();
const nextQuery = searchQuery().trim();
const currentLinkedResource = parsed.resource;
const selectedResourceId = expandedResourceId();
const shouldPreserveIncomingResource =
!selectedResourceId && Boolean(currentLinkedResource) && !initialLoadComplete();
const nextResource = shouldPreserveIncomingResource
? currentLinkedResource
: (selectedResourceId ?? '');
const managedPath = buildInfrastructurePath({
source: nextSource || null,
query: nextQuery || null,
resource: nextResource || null,
});
const managedUrl = new URL(managedPath, 'http://pulse.local');
const currentParams = new URLSearchParams(location.search);
const nextParams = new URLSearchParams(location.search);
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.source);
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.query);
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.resource);
managedUrl.searchParams.forEach((value, key) => {
nextParams.set(key, value);
});
if (!areSearchParamsEquivalent(currentParams, nextParams)) {
const nextSearch = nextParams.toString();
const nextPath = nextSearch ? `${INFRASTRUCTURE_PATH}?${nextSearch}` : INFRASTRUCTURE_PATH;
scheduleUrlSyncNavigate(nextPath);
}
});
createEffect(() => {
const hoveredId = hoveredResourceId();
if (!hoveredId) return;
const exists = filteredResources().some((resource) => resource.id === hoveredId);
if (!exists) {
setHoveredResourceId(null);
}
});
onCleanup(() => {
if (pendingUrlSyncHandle !== null) {
window.clearTimeout(pendingUrlSyncHandle);
pendingUrlSyncHandle = null;
pendingUrlSyncPath = null;
}
if (highlightTimer) {
window.clearTimeout(highlightTimer);
}
});
return {
expandedResourceId,
setExpandedResourceId,
hoveredResourceId,
setHoveredResourceId,
highlightedResourceId,
};
}
export type InfrastructurePageRouteState = ReturnType<typeof useInfrastructurePageRouteState>;

View file

@ -1,5 +1,4 @@
import { createEffect, createMemo, createSignal, onCleanup, untrack } from 'solid-js';
import { useLocation, useNavigate } from '@solidjs/router';
import { createEffect, createMemo, createSignal } from 'solid-js';
import type { TimeRange } from '@/api/charts';
import { useUnifiedResources } from '@/hooks/useUnifiedResources';
import { usePersistentSignal } from '@/hooks/usePersistentSignal';
@ -14,14 +13,7 @@ import {
collectAvailableStatuses,
buildStatusOptions,
} from '@/components/Infrastructure/infrastructureSelectors';
import { normalizeSourcePlatformKey } from '@/utils/sourcePlatforms';
import {
buildInfrastructurePath,
INFRASTRUCTURE_PATH,
INFRASTRUCTURE_QUERY_PARAMS,
parseInfrastructureLinkSearch,
} from '@/routing/resourceLinks';
import { areSearchParamsEquivalent } from '@/utils/searchParams';
import { useInfrastructurePageRouteState } from './useInfrastructurePageRouteState';
export type GroupingMode = 'grouped' | 'flat';
@ -32,8 +24,6 @@ type DeployCluster = {
export function useInfrastructurePageState() {
const { resources, loading, error, refetch } = useUnifiedResources();
const location = useLocation();
const navigate = useNavigate();
const kioskMode = useKioskMode();
const { isMobile } = useBreakpoint();
@ -55,12 +45,6 @@ export function useInfrastructurePageState() {
'grouped',
{ deserialize: (raw) => (raw === 'grouped' || raw === 'flat' ? raw : 'grouped') },
);
const [expandedResourceId, setExpandedResourceId] = createSignal<string | null>(null);
const [hoveredResourceId, setHoveredResourceId] = createSignal<string | null>(null);
const [highlightedResourceId, setHighlightedResourceId] = createSignal<string | null>(null);
const [handledResourceId, setHandledResourceId] = createSignal<string | null>(null);
const [handledSourceParam, setHandledSourceParam] = createSignal<string | null>(null);
const [handledQueryParam, setHandledQueryParam] = createSignal('');
const [deployCluster, setDeployCluster] = createSignal<DeployCluster | null>(null);
const [filtersOpen, setFiltersOpen] = createSignal(false);
@ -85,24 +69,15 @@ export function useInfrastructurePageState() {
),
);
const hasFilteredResources = createMemo(() => filteredResources().length > 0);
let highlightTimer: number | undefined;
let pendingUrlSyncHandle: number | null = null;
let pendingUrlSyncPath: string | null = null;
const scheduleUrlSyncNavigate = (nextPath: string) => {
pendingUrlSyncPath = nextPath;
if (pendingUrlSyncHandle !== null) return;
pendingUrlSyncHandle = window.setTimeout(() => {
pendingUrlSyncHandle = null;
const target = pendingUrlSyncPath;
pendingUrlSyncPath = null;
if (!target) return;
const current = `${untrack(() => location.pathname)}${untrack(() => location.search)}`;
if (current === target) return;
navigate(target, { replace: true });
}, 0);
};
const routeState = useInfrastructurePageRouteState({
resources,
filteredResources,
initialLoadComplete,
selectedSource,
setSelectedSource,
searchQuery,
setSearchQuery,
});
const clearFilters = () => {
setSelectedSource('');
@ -110,136 +85,12 @@ export function useInfrastructurePageState() {
setSearchQuery('');
};
const handleNavigateToSettings = () => navigate('/settings');
createEffect(() => {
if (!loading() && !initialLoadComplete()) {
setInitialLoadComplete(true);
}
});
createEffect(() => {
const { resource: resourceId } = parseInfrastructureLinkSearch(location.search);
if (!resourceId || resourceId === handledResourceId()) return;
const matching = resources().some((resource) => resource.id === resourceId);
if (!matching) return;
setExpandedResourceId(resourceId);
setHighlightedResourceId(resourceId);
setHandledResourceId(resourceId);
if (highlightTimer) {
window.clearTimeout(highlightTimer);
}
highlightTimer = window.setTimeout(() => {
setHighlightedResourceId(null);
}, 2000);
});
createEffect(() => {
const { resource: resourceId } = parseInfrastructureLinkSearch(location.search);
if (resourceId) return;
if (handledResourceId() === null) return;
if (expandedResourceId() !== null) {
setExpandedResourceId(null);
}
if (highlightedResourceId() !== null) {
setHighlightedResourceId(null);
}
setHandledResourceId(null);
});
createEffect(() => {
const { source: sourceParam } = parseInfrastructureLinkSearch(location.search);
if (!sourceParam) {
const previous = (handledSourceParam() ?? '').trim();
if (previous) {
if (selectedSource() !== '') setSelectedSource('');
setHandledSourceParam('');
} else if (handledSourceParam() === null) {
setHandledSourceParam('');
}
return;
}
if (sourceParam === handledSourceParam()) return;
const normalized = normalizeSourcePlatformKey(sourceParam) ?? '';
setSelectedSource(normalized);
setHandledSourceParam(sourceParam);
});
createEffect(() => {
const { query: nextSearch } = parseInfrastructureLinkSearch(location.search);
const normalized = nextSearch ?? '';
if (normalized !== handledQueryParam()) {
if (normalized !== untrack(searchQuery)) {
setSearchQuery(normalized);
}
setHandledQueryParam(normalized);
}
});
createEffect(() => {
if (location.pathname !== INFRASTRUCTURE_PATH) return;
const parsed = parseInfrastructureLinkSearch(location.search);
const urlSource = parsed.source ?? '';
const urlQuery = parsed.query ?? '';
const urlResource = parsed.resource ?? '';
if ((handledSourceParam() ?? '') !== urlSource) return;
if (handledQueryParam() !== urlQuery) return;
if (urlResource && handledResourceId() !== urlResource && !initialLoadComplete()) return;
const nextSource = selectedSource();
const nextQuery = searchQuery().trim();
const currentLinkedResource = parsed.resource;
const selectedResourceId = expandedResourceId();
const shouldPreserveIncomingResource =
!selectedResourceId && Boolean(currentLinkedResource) && !initialLoadComplete();
const nextResource = shouldPreserveIncomingResource
? currentLinkedResource
: (selectedResourceId ?? '');
const managedPath = buildInfrastructurePath({
source: nextSource || null,
query: nextQuery || null,
resource: nextResource || null,
});
const managedUrl = new URL(managedPath, 'http://pulse.local');
const currentParams = new URLSearchParams(location.search);
const nextParams = new URLSearchParams(location.search);
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.source);
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.query);
nextParams.delete(INFRASTRUCTURE_QUERY_PARAMS.resource);
managedUrl.searchParams.forEach((value, key) => {
nextParams.set(key, value);
});
if (!areSearchParamsEquivalent(currentParams, nextParams)) {
const nextSearch = nextParams.toString();
const nextPath = nextSearch ? `${INFRASTRUCTURE_PATH}?${nextSearch}` : INFRASTRUCTURE_PATH;
scheduleUrlSyncNavigate(nextPath);
}
});
createEffect(() => {
const hoveredId = hoveredResourceId();
if (!hoveredId) return;
const exists = filteredResources().some((resource) => resource.id === hoveredId);
if (!exists) {
setHoveredResourceId(null);
}
});
onCleanup(() => {
if (pendingUrlSyncHandle !== null) {
window.clearTimeout(pendingUrlSyncHandle);
pendingUrlSyncHandle = null;
pendingUrlSyncPath = null;
}
if (highlightTimer) {
window.clearTimeout(highlightTimer);
}
});
return {
loading,
error,
@ -258,11 +109,7 @@ export function useInfrastructurePageState() {
setSummaryCollapsed,
groupingMode,
setGroupingMode,
expandedResourceId,
setExpandedResourceId,
hoveredResourceId,
setHoveredResourceId,
highlightedResourceId,
...routeState,
isMobile,
deployCluster,
setDeployCluster,
@ -276,6 +123,5 @@ export function useInfrastructurePageState() {
clearFilters,
filteredResources,
hasFilteredResources,
handleNavigateToSettings,
};
}

View file

@ -508,6 +508,7 @@ import aiChatPresentationSource from '@/utils/aiChatPresentation.ts?raw';
import infrastructureDetailsDrawerSource from '@/components/shared/InfrastructureDetailsDrawer.tsx?raw';
import infrastructurePageSurfaceSource from '@/features/infrastructure/InfrastructurePageSurface.tsx?raw';
import infrastructurePageStateSource from '@/features/infrastructure/useInfrastructurePageState.ts?raw';
import infrastructurePageRouteStateSource from '@/features/infrastructure/useInfrastructurePageRouteState.ts?raw';
const aiSettingsSource = [
aiSettingsShellSource,
@ -533,6 +534,7 @@ const resourceDetailDrawerSource = [
const infrastructurePageSource = [
infrastructurePageShellSource,
infrastructurePageSurfaceSource,
infrastructurePageRouteStateSource,
infrastructurePageStateSource,
].join('\n');
@ -3897,6 +3899,21 @@ describe('frontend resource type boundaries', () => {
});
it('keeps infrastructure page empty-state copy in a shared presentation utility', () => {
expect(infrastructurePageSurfaceSource).toContain('useInfrastructurePageState');
expect(infrastructurePageSurfaceSource).toContain('useNavigate');
expect(infrastructurePageSurfaceSource).not.toContain('useLocation(');
expect(infrastructurePageSurfaceSource).not.toContain('buildInfrastructurePath(');
expect(infrastructurePageStateSource).toContain('useInfrastructurePageRouteState');
expect(infrastructurePageStateSource).not.toContain('useLocation(');
expect(infrastructurePageStateSource).not.toContain('useNavigate(');
expect(infrastructurePageStateSource).not.toContain('parseInfrastructureLinkSearch(');
expect(infrastructurePageStateSource).not.toContain('buildInfrastructurePath(');
expect(infrastructurePageStateSource).not.toContain('areSearchParamsEquivalent(');
expect(infrastructurePageRouteStateSource).toContain('useLocation');
expect(infrastructurePageRouteStateSource).toContain('useNavigate');
expect(infrastructurePageRouteStateSource).toContain('parseInfrastructureLinkSearch');
expect(infrastructurePageRouteStateSource).toContain('buildInfrastructurePath');
expect(infrastructurePageRouteStateSource).toContain('areSearchParamsEquivalent');
expect(infrastructurePageSource).toContain('getInfrastructureEmptyState');
expect(infrastructurePageSource).toContain('getInfrastructureFilterEmptyState');
expect(infrastructurePageSource).toContain('getInfrastructureLoadFailureState');

View file

@ -504,6 +504,7 @@ class CanonicalCompletionGuardTest(unittest.TestCase):
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.workloads-link.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/resourceDetailMappers.test.ts",
"frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts",
"frontend-modern/src/hooks/__tests__/useDashboardTrends.test.ts",
"frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts",
"frontend-modern/src/pages/__tests__/Infrastructure.empty-state.test.tsx",

View file

@ -66,6 +66,36 @@ class SubsystemLookupTest(unittest.TestCase):
"shared-component-guardrails",
)
def test_lookup_paths_assigns_infrastructure_page_route_state_to_unified_resources(self) -> None:
result = lookup_paths(
["frontend-modern/src/features/infrastructure/useInfrastructurePageRouteState.ts"]
)
self.assertEqual(result["unowned_runtime_files"], [])
self.assertEqual(
{item["subsystem"] for item in result["impacted_subsystems"]},
{"unified-resources"},
)
file_entry = result["files"][0]
self.assertEqual(file_entry["classification"], "runtime")
self.assertEqual(
{match["subsystem"] for match in file_entry["matches"]},
{"unified-resources"},
)
match = file_entry["matches"][0]
self.assertEqual(
match["contract"],
"docs/release-control/v6/internal/subsystems/unified-resources.md",
)
self.assertEqual(match["lane_context"]["lane_id"], "L13")
self.assertEqual(
match["verification_requirement"]["id"],
"resource-consumers",
)
self.assertIn(
"frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts",
match["verification_requirement"]["exact_files"],
)
def test_lookup_paths_assigns_recent_alerts_panel_to_alerts(self) -> None:
result = lookup_paths(["frontend-modern/src/components/Alerts/RecentAlertsPanel.tsx"])
self.assertEqual(result["unowned_runtime_files"], [])
@ -3909,6 +3939,7 @@ class SubsystemLookupTest(unittest.TestCase):
"frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.workloads-link.test.tsx",
"frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts",
"frontend-modern/src/components/Infrastructure/__tests__/resourceDetailMappers.test.ts",
"frontend-modern/src/features/infrastructure/__tests__/InfrastructurePageSurface.guardrails.test.ts",
"frontend-modern/src/hooks/__tests__/useDashboardTrends.test.ts",
"frontend-modern/src/hooks/__tests__/useUnifiedResources.test.ts",
"frontend-modern/src/pages/__tests__/Infrastructure.empty-state.test.tsx",