From cd546b8a8ef32fc9526a706b7499e3b20c8e1983 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 29 Apr 2026 17:47:42 +0100 Subject: [PATCH] Stop retired routes highlighting primary nav --- .../subsystems/frontend-primitives.md | 6 +++ .../shared/__tests__/MobileNavBar.test.tsx | 42 +++++++++++++++++++ .../components/shared/mobileNavBarModel.ts | 2 +- .../src/routing/__tests__/navigation.test.ts | 3 +- frontend-modern/src/routing/navigation.ts | 6 ++- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 1c003b9c6..2debaa877 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -1733,6 +1733,12 @@ route-level data orchestration, section anchors, or Assistant prompt handoffs; they must not restore `frontend-modern/src/pages/Dashboard.tsx`, `frontend-modern/src/features/dashboardOverview/`, or deleted dashboard-only presentation helpers as compatibility paths. +The primary navigation active-tab contract follows that retirement boundary: +retired or unknown routes such as `/dashboard` must not be coerced into the +Infrastructure tab just because Infrastructure is the authenticated landing +surface. Shared desktop and mobile navigation must tolerate a missing active tab +for those paths while still highlighting canonical active routes such as +Infrastructure, Workloads, Storage, Recovery, Alerts, Patrol, and Settings. The recovery feature shell now also depends on the shared `frontend-modern/src/components/shared/Subtabs.tsx` primitive for its primary protected-items versus recovery-events workspace switch. The recovery lane may diff --git a/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx b/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx index c57c24112..0456de632 100644 --- a/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/MobileNavBar.test.tsx @@ -74,6 +74,48 @@ describe('MobileNavBar', () => { expect(within(navList).queryByRole('button', { name: 'Pulse Patrol Patrol' })).toBeNull(); }); + it('allows retired shell routes to render without an active mobile tab', () => { + const { container } = render(() => ( + null} + platformTabs={() => [ + { + id: 'infrastructure', + label: 'Infrastructure', + route: '/infrastructure', + settingsRoute: '/settings', + tooltip: 'Infrastructure', + enabled: true, + live: true, + icon: InfrastructureIcon, + alwaysShow: true, + }, + ]} + utilityTabs={() => [ + { + id: 'settings', + label: 'Settings', + route: '/settings', + tooltip: 'Settings', + badge: null, + count: undefined, + breakdown: undefined, + icon: SettingsIcon, + }, + ]} + onPlatformClick={() => {}} + onUtilityClick={() => {}} + /> + )); + + const buttons = container.querySelectorAll('button[data-tab-id]'); + expect(buttons).toHaveLength(2); + buttons.forEach((button) => { + expect(button).not.toHaveClass('bg-blue-50'); + expect(button).not.toHaveClass('text-blue-700'); + }); + }); + it('orders tabs, renders alert badges, and shows fades from scroll state', async () => { const onPlatformClick = vi.fn(); const onUtilityClick = vi.fn(); diff --git a/frontend-modern/src/components/shared/mobileNavBarModel.ts b/frontend-modern/src/components/shared/mobileNavBarModel.ts index 2691dacb7..2b80e1b45 100644 --- a/frontend-modern/src/components/shared/mobileNavBarModel.ts +++ b/frontend-modern/src/components/shared/mobileNavBarModel.ts @@ -27,7 +27,7 @@ export type MobileNavBarUtilityTab = { }; export type MobileNavBarProps = { - activeTab: () => string; + activeTab: () => string | null; platformTabs: () => MobileNavBarPlatformTab[]; utilityTabs: () => MobileNavBarUtilityTab[]; onPlatformClick: (platform: MobileNavBarPlatformTab) => void; diff --git a/frontend-modern/src/routing/__tests__/navigation.test.ts b/frontend-modern/src/routing/__tests__/navigation.test.ts index 6d6dc4ced..ec39980e0 100644 --- a/frontend-modern/src/routing/__tests__/navigation.test.ts +++ b/frontend-modern/src/routing/__tests__/navigation.test.ts @@ -3,7 +3,8 @@ import { getActiveTabForPath } from '../navigation'; describe('navigation routing helpers', () => { it('maps paths to the correct primary tab', () => { - expect(getActiveTabForPath('/unknown')).toBe('infrastructure'); + expect(getActiveTabForPath('/unknown')).toBeNull(); + expect(getActiveTabForPath('/dashboard')).toBeNull(); expect(getActiveTabForPath('/infrastructure')).toBe('infrastructure'); expect(getActiveTabForPath('/workloads?type=pod')).toBe('workloads'); expect(getActiveTabForPath('/storage')).toBe('storage'); diff --git a/frontend-modern/src/routing/navigation.ts b/frontend-modern/src/routing/navigation.ts index 4d1c4039c..862e43305 100644 --- a/frontend-modern/src/routing/navigation.ts +++ b/frontend-modern/src/routing/navigation.ts @@ -9,7 +9,9 @@ export type AppTabId = | 'ai' | 'settings'; -export function getActiveTabForPath(path: string): AppTabId { +export type ActiveAppTabId = AppTabId | null; + +export function getActiveTabForPath(path: string): ActiveAppTabId { if (path.startsWith(INFRASTRUCTURE_PATH)) return 'infrastructure'; if (path.startsWith(WORKLOADS_PATH)) return 'workloads'; if (path.startsWith('/storage')) return 'storage'; @@ -19,5 +21,5 @@ export function getActiveTabForPath(path: string): AppTabId { if (path.startsWith(PATROL_PATH) || path.startsWith('/ai')) return 'ai'; if (path.startsWith('/settings')) return 'settings'; if (path.startsWith('/operations')) return 'settings'; - return 'infrastructure'; + return null; }