diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index a566bdc37..abeea5e42 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -371,10 +371,15 @@ an add-only capacity posture. `/settings/infrastructure/operations` remain reachable as detail routes for install, platform connections, and legacy reporting/control surfaces respectively, but the workspace shell must not gate inventory visibility - behind tab navigation. Read-only sessions must continue to redirect the - install detail route back to the unified inventory view and suppress the - add-system entry point on the base table so presentation-policy - restrictions still hold. + behind tab navigation. Every non-inventory detail route under + `/settings/infrastructure/*` must render a persistent breadcrumb header + that includes a one-click `Connections and Inventory` control returning + to the bare `/settings/infrastructure` route, so operators are never + stranded on a deeper install or platform-connection surface without a + visible path back to the unified table. Read-only sessions must continue + to redirect the install detail route back to the unified inventory view + and suppress the add-system entry point on the base table so + presentation-policy restrictions still hold. 8. Keep post-install lifecycle completion explicit inside `frontend-modern/src/components/Settings/InfrastructureInstallerSection.tsx` and `frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx`. diff --git a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx index 0c1639d60..af9c681c2 100644 --- a/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx +++ b/frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx @@ -1,4 +1,4 @@ -import { Component, Match, Switch, createEffect, createMemo, createSignal } from 'solid-js'; +import { Component, Match, Show, Switch, createEffect, createMemo, createSignal } from 'solid-js'; import { useLocation, useNavigate } from '@solidjs/router'; import { presentationPolicyIsReadOnly } from '@/stores/sessionPresentationPolicy'; import { InfrastructureInstallPanel } from './InfrastructureInstallPanel'; @@ -57,8 +57,36 @@ export const InfrastructureWorkspace: Component = } }); + const subviewHeading = createMemo(() => { + switch (activeView()) { + case 'install': + return 'Install on a host'; + case 'platforms': + return 'Platform connections'; + case 'operations': + return 'Reporting'; + default: + return ''; + } + }); + return (
+ +
+ + + {subviewHeading()} +
+
+ { expect(screen.getByTestId('reporting-panel')).toBeInTheDocument(); }); + it('renders a back-to-inventory header on the install subview', () => { + mockPathname = '/settings/infrastructure/install'; + renderWorkspace(); + + const backButton = screen.getByRole('button', { name: /Connections and Inventory/i }); + expect(backButton).toBeInTheDocument(); + expect(screen.getByText('Install on a host')).toBeInTheDocument(); + + fireEvent.click(backButton); + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure'); + }); + + it('renders a back-to-inventory header on the platforms subview', () => { + mockPathname = '/settings/infrastructure/platforms/truenas'; + renderWorkspace(); + + const backButton = screen.getByRole('button', { name: /Connections and Inventory/i }); + expect(backButton).toBeInTheDocument(); + expect(screen.getByText('Platform connections')).toBeInTheDocument(); + + fireEvent.click(backButton); + expect(navigateSpy).toHaveBeenCalledWith('/settings/infrastructure'); + }); + + it('does not render the back-to-inventory header on the inventory landing', () => { + mockPathname = '/settings/infrastructure'; + renderWorkspace(); + + expect(screen.queryByRole('button', { name: /^Connections and Inventory$/ })).toBeNull(); + }); + it('hides the add-system action and redirects install routes in read-only mode', () => { presentationPolicyIsReadOnlyMock.mockReturnValue(true); mockPathname = '/settings/infrastructure/install';