Add back-to-inventory header on infrastructure subviews

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.
This commit is contained in:
rcourtman 2026-04-18 10:43:38 +01:00
parent 2dc4e43033
commit 8d0aec6e4d
3 changed files with 69 additions and 5 deletions

View file

@ -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`.

View file

@ -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<InfrastructureWorkspaceProps> =
}
});
const subviewHeading = createMemo(() => {
switch (activeView()) {
case 'install':
return 'Install on a host';
case 'platforms':
return 'Platform connections';
case 'operations':
return 'Reporting';
default:
return '';
}
});
return (
<div class="space-y-6">
<Show when={activeView() !== 'inventory'}>
<div class="flex items-center gap-2 text-sm">
<button
type="button"
onClick={() => navigate(buildInfrastructureWorkspacePath('inventory'))}
class="inline-flex items-center gap-1 rounded-md border border-border bg-surface px-2.5 py-1 text-muted hover:bg-surface-hover hover:text-base-content"
>
<span aria-hidden="true"></span>
<span>Connections and Inventory</span>
</button>
<span class="text-muted" aria-hidden="true">/</span>
<span class="font-medium text-base-content">{subviewHeading()}</span>
</div>
</Show>
<Switch>
<Match when={activeView() === 'inventory'}>
<ConnectionsTable

View file

@ -158,6 +158,37 @@ describe('InfrastructureWorkspace', () => {
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';