From bdf94926ec137ba4bc3d265bf9a766ddefb620bc Mon Sep 17 00:00:00 2001 From: Jaw <53564781+jaw52@users.noreply.github.com> Date: Thu, 5 Nov 2026 15:18:42 +0800 Subject: [PATCH] fix(vcs): cap the Push dialog's height so a long commit list scrolls inside it (#1491) With many outgoing commits the Push dialog grew to the full screen height and its whole body scrolled besides the changed-files tree, because the body was not a flex container and the commit list's flex had no effect. The single- and multi-repository Push dialogs now open at most 720px tall with the commit list filling the rest and scrolling on its own; Dialog gets a maxHeight that applies until the user resizes. Closes #1485 Co-authored-by: jaw52 <53564781+jaw52@users.noreply.github.com> --- src/features/vcs/common.module.css | 5 +++++ src/features/vcs/remoteUi.tsx | 7 +++++++ src/ui/Dialog.test.tsx | 15 +++++++++++++++ src/ui/Dialog.tsx | 4 ++++ 4 files changed, 31 insertions(+) diff --git a/src/features/vcs/common.module.css b/src/features/vcs/common.module.css index cadf6f9d..da45e600 100644 --- a/src/features/vcs/common.module.css +++ b/src/features/vcs/common.module.css @@ -111,6 +111,10 @@ border-radius: var(--radius-small); } /* Push dialogs: the list fills the body so a resizable window shows more rows. */ +.pushDialog > [data-dialog-body] { + display: flex; + flex-direction: column; +} .pushTreeWrap { flex: 1; min-height: 220px; @@ -127,6 +131,7 @@ } /* Push dialogs: the outgoing commits' changed files below the commits, the diff beside them. */ .pushChanges { + flex: none; display: flex; flex-direction: column; min-height: 0; diff --git a/src/features/vcs/remoteUi.tsx b/src/features/vcs/remoteUi.tsx index 63554591..6bedffcd 100644 --- a/src/features/vcs/remoteUi.tsx +++ b/src/features/vcs/remoteUi.tsx @@ -149,6 +149,9 @@ interface PushNode { children?: PushNode[]; } +// A long outgoing list scrolls inside the dialog instead of growing it to the screen (IDEA: 800×450 centre panel). +const PUSH_DIALOG_MAX_HEIGHT = 720; + function MultiPushDialog({ onClose }: { onClose: () => void }) { const roots = useVcs((s) => s.roots); const [repos, setRepos] = useState(null); @@ -228,6 +231,8 @@ function MultiPushDialog({ onClose }: { onClose: () => void }) { title="Push Commits" onClose={onClose} width={repos?.some((r) => r.commits.length) ? 860 : 620} + maxHeight={PUSH_DIALOG_MAX_HEIGHT} + className={styles.pushDialog} resizable="both" footerStart={ <> @@ -441,6 +446,8 @@ function PushDialog({ target: pushTarget, onClose }: { target: PushTarget; onClo title={head ? tx`Push Commits to ${upstream ?? "remote"}` : "Push"} onClose={onClose} width={commits?.length ? 860 : 620} + maxHeight={PUSH_DIALOG_MAX_HEIGHT} + className={styles.pushDialog} resizable="both" footerStart={ <> diff --git a/src/ui/Dialog.test.tsx b/src/ui/Dialog.test.tsx index 7b5bef89..3a986495 100644 --- a/src/ui/Dialog.test.tsx +++ b/src/ui/Dialog.test.tsx @@ -168,6 +168,21 @@ describe("Dialog chrome", () => { expect([dialog.style.width, dialog.style.height]).toEqual(["600px", "400px"]); }); + it("caps a content-sized dialog at maxHeight until it is resized", () => { + render( + {}} title="Push" resizable="both" maxHeight={720}> +
rows
+
, + ); + const dialog = screen.getByRole("dialog"); + expect(dialog.style.maxHeight).toBe("min(720px, 100%)"); + fireEvent.pointerDown(screen.getByRole("separator", { name: "Resize" }), { button: 0, clientX: 0, clientY: 0 }); + fireEvent.pointerMove(window, { buttons: 1, clientX: 500, clientY: 800 }); + fireEvent.pointerUp(window); + expect(dialog.style.maxHeight).toBe(""); + expect(dialog.style.height).not.toBe(""); + }); + it("still drags a window dialog by its title bar", () => { render( {}} title="Usages Detected" buttons={safeDelete()} platform="other"> diff --git a/src/ui/Dialog.tsx b/src/ui/Dialog.tsx index 940c6e90..8c488a58 100644 --- a/src/ui/Dialog.tsx +++ b/src/ui/Dialog.tsx @@ -117,6 +117,8 @@ export interface DialogProps { resizable?: "both"; minWidth?: number; minHeight?: number; + /** Height cap while content-sized; a manual resize lifts it. */ + maxHeight?: number; /** Size to open at, such as one remembered from `onResized`; kept inside the window. */ initialSize?: { w: number; h: number } | null; /** The size a resize ended at. */ @@ -210,6 +212,7 @@ function DialogInner({ resizable, minWidth, minHeight, + maxHeight, initialSize, onResized, position = "center", @@ -392,6 +395,7 @@ function DialogInner({ style={{ width: size ? size.w : width, height: size?.h, + maxHeight: !size && maxHeight ? `min(${maxHeight}px, 100%)` : undefined, translate: offset.x || offset.y ? `${offset.x}px ${offset.y}px` : undefined, }} >