From 8d0aec6e4dbecf90de9830554f105b33b11c35e8 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 18 Apr 2026 10:43:38 +0100 Subject: [PATCH] Add back-to-inventory header on infrastructure subviews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The connections-table flip in 00c6dc2dd removed the workspace's top-of- page subtab strip but left the install panel and platforms workspace rendering bare with no path back to the inventory table. Operators who clicked Add a system, picked a platform, and landed on /settings/infrastructure/install or /settings/infrastructure/platforms/* had to leave Settings entirely and return to the tab to get back to the table. Render a persistent breadcrumb header above every non-inventory view inside InfrastructureWorkspace: a "← Connections and Inventory" button that navigates back to the bare /settings/infrastructure route, plus a heading naming the current subview. Inventory itself is unchanged. Bring agent-lifecycle rule 7 into line by requiring this back-to- inventory affordance on every detail route under /settings/infrastructure so deeper surfaces are never reachable without a visible return path. Cover the new header on install and platforms subviews — and its absence on the inventory landing — in InfrastructureWorkspace.test.tsx. --- .../v6/internal/subsystems/agent-lifecycle.md | 13 +++++--- .../Settings/InfrastructureWorkspace.tsx | 30 +++++++++++++++++- .../InfrastructureWorkspace.test.tsx | 31 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) 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';