From 2932822b60cb9c7dd165e0f7ae62c2fca56b048a Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 22 Apr 2026 19:11:40 +0100 Subject: [PATCH] Fix infrastructure dialog scrolling --- .../v6/internal/subsystems/agent-lifecycle.md | 6 +++++- .../v6/internal/subsystems/frontend-primitives.md | 9 +++++++++ .../Settings/ConnectionEditor/ConnectionEditor.tsx | 2 +- .../src/components/Settings/InfrastructureWorkspace.tsx | 4 ++-- .../Settings/__tests__/settingsArchitecture.test.ts | 2 ++ .../src/components/shared/__tests__/Dialog.test.tsx | 1 + frontend-modern/src/components/shared/dialogModel.ts | 2 +- 7 files changed, 21 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 1684d5066..8cca93d1e 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -341,7 +341,11 @@ an add-only capacity posture. slot; it must not bypass the probe endpoint or fabricate probe candidates, and the agent credential slot must continue to reach `InfrastructureInstallerSection.tsx` so install handoffs remain on the - canonical unified-agent install path. For PVE, PBS, and PMG, the + canonical unified-agent install path. Those governed add/edit dialogs + must also keep their form body scrollable inside the modal: + `InfrastructureWorkspace.tsx` and `ConnectionEditor.tsx` keep the + content shell on `min-h-0` flex columns so long lifecycle forms do not + clip lower fields behind the dialog boundary. For PVE, PBS, and PMG, the credential slot is `frontend-modern/src/components/Settings/ConnectionEditor/CredentialSlots/NodeCredentialSlot.tsx`, which reuses the existing `NodeModalBasicInfoSection`, diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index d1c41e600..b4266c0d5 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -232,6 +232,11 @@ work extends shared components instead of creating new local variants. helper plus runtime `monitored_system_capacity` reads rather than reconstructing raw `current / limit` slash math or `0 remaining` copy in the banner shell, state owner, or shared model. + Shared modal scroll containment follows that same owner split. The dialog + shell in `frontend-modern/src/components/shared/dialogModel.ts` must keep + shared panels `min-h-0`, and page-owned modal bodies may use + `overflow-y-auto` only under shrinkable flex columns instead of clipping + lower fields behind a fixed-height shell. 5. Keep shared infrastructure shell state on the reusable settings boundary: `frontend-modern/src/components/Settings/useSettingsInfrastructurePanelProps.ts` and `frontend-modern/src/components/Settings/InfrastructureWorkspace.tsx` must continue to derive provider counts, availability, and shared subtab copy from one infrastructure-settings source — via the unified aggregator through `frontend-modern/src/components/Settings/useConnectionsLedger.ts` — instead of creating provider-local summary fetches or VMware-only shell vocabulary. Phase 9 retired the old `PlatformConnectionsWorkspace` per-type shell, but setup guidance may still use `Platform connections` as the operator-facing label for the shared API-backed onboarding path. That same shared shell boundary now owns the default posture for `/settings/infrastructure`: the landing route should read as one @@ -245,6 +250,10 @@ work extends shared components instead of creating new local variants. Those secondary views must stay under the same single `Infrastructure` sidebar destination, but they may open in governed modal/dialog chrome when that preserves the persistent source-manager page behind them. + That governed dialog chrome must also preserve inner form scrolling: + `InfrastructureWorkspace.tsx` and `ConnectionEditor.tsx` keep the add/edit + shell on `min-h-0` flex columns so long credential forms scroll inside the + modal body instead of trapping the lower fields below the fold. That same shared shell boundary now owns one canonical infrastructure destination in the Settings sidebar. `InfrastructureWorkspace.tsx` owns the source-manager landing inside that destination, while route-backed add flows diff --git a/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx b/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx index b2412918d..761a8eaa2 100644 --- a/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx +++ b/frontend-modern/src/components/Settings/ConnectionEditor/ConnectionEditor.tsx @@ -114,7 +114,7 @@ export const ConnectionEditor: Component = (props) => { ); return ( -
+
= ariaLabel={addDialogTitle()} panelClass={isAgentDialog() ? 'max-w-6xl' : 'max-w-5xl'} > -
+

{addDialogTitle()}

@@ -712,7 +712,7 @@ const InfrastructureWorkspaceContent: Component = ariaLabel={editDialogTitle()} panelClass={connection.type === 'agent' ? 'max-w-5xl' : 'max-w-5xl'} > -
+

{editDialogTitle()}

diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts index 688775015..c5c82feee 100644 --- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts +++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts @@ -112,6 +112,7 @@ describe('settings architecture guardrails', () => { expect(infrastructureWorkspaceSource).toContain(''); + expect(infrastructureWorkspaceSource).toContain('flex h-full min-h-0 flex-col'); expect(infrastructureWorkspaceSource).toContain("showSlotHeader={false}"); expect(infrastructureWorkspaceSource).toContain( "trackInitialCatalogSelection={activeAddType() !== 'agent'}", @@ -146,6 +147,7 @@ describe('settings architecture guardrails', () => { expect(connectionEditorSource).toContain(' { )); expect(screen.getByRole('dialog')).toBeInTheDocument(); + expect(screen.getByRole('dialog')).toHaveClass('min-h-0'); const backdrop = document.querySelector('[data-dialog-backdrop]') as HTMLElement | null; expect(backdrop).not.toBeNull(); if (!backdrop) return; diff --git a/frontend-modern/src/components/shared/dialogModel.ts b/frontend-modern/src/components/shared/dialogModel.ts index 0588449a9..3735890b9 100644 --- a/frontend-modern/src/components/shared/dialogModel.ts +++ b/frontend-modern/src/components/shared/dialogModel.ts @@ -29,7 +29,7 @@ export function getDialogAlignmentClass(layout: DialogLayout): string { } export function getDialogPanelClass(layout: DialogLayout, panelClass?: string): string { - return `relative flex w-full flex-col overflow-hidden bg-surface border border-border outline-none pointer-events-auto ${ + return `relative flex min-h-0 w-full flex-col overflow-hidden bg-surface border border-border outline-none pointer-events-auto ${ layout === 'drawer-right' ? 'h-dvh max-w-[720px] rounded-none border-y-0 border-r-0 animate-slide-up sm:h-full sm:max-h-dvh sm:rounded-l-xl sm:border-y sm:border-r-0' : 'max-h-[calc(100dvh-2rem)] rounded-md animate-slide-up'