From cf5a2d55bc90a996c76fe448bac5b64f55757df5 Mon Sep 17 00:00:00 2001 From: MiguelVivar Date: Sat, 8 Aug 2026 17:19:32 -0500 Subject: [PATCH 1/2] fix(ui): pin outer width to prevent recorder window growth under fractional scaling Fixes #56 On Windows with fractional display scaling (e.g. 125%), reading outer size on every resize pass and feeding it back into setSize is lossy due to DIP <-> physical pixel conversions. This causes the recorder window width to grow endlessly on every ResizeObserver callback. Pin the outer window width on the first fit call using a WeakMap, pass the pinned width to setSize, and add a 2px tolerance threshold for height settling. --- electron/recorder-window-sizing.test.ts | 46 +++++++++++++++++++++++++ electron/recorder-window-sizing.ts | 13 +++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/electron/recorder-window-sizing.test.ts b/electron/recorder-window-sizing.test.ts index 413b048..92b5f26 100644 --- a/electron/recorder-window-sizing.test.ts +++ b/electron/recorder-window-sizing.test.ts @@ -72,3 +72,49 @@ test("recorder fitting clamps content height and ignores invalid requests", () = fitRecorderHeight(win, 500); assert.deepEqual(win.size, size); }); + +class FractionalScalingWindow implements RecorderWindowSizingTarget { + destroyed = false; + resizable = false; + size: [number, number] = [401, 500]; + + isDestroyed(): boolean { + return this.destroyed; + } + + getContentSize(): [number, number] { + const frame: [number, number] = this.resizable ? [16, 39] : [2, 31]; + return [this.size[0] - frame[0], this.size[1] - frame[1] + 1]; + } + + getSize(): [number, number] { + return [...this.size]; + } + + isResizable(): boolean { + return this.resizable; + } + + setResizable(resizable: boolean): void { + this.resizable = resizable; + } + + setSize(width: number, height: number): void { + // Simulate fractional display scaling round-trip perturbation where getSize() expands width + this.size = [width + 2, height]; + } +} + +test("recorder fitting prevents width growth and settles height under fractional display scaling", () => { + const win = new FractionalScalingWindow(); + + fitRecorderHeight(win, 600); + const widthAfterFirstFit = win.size[0]; + + for (let i = 0; i < 10; i++) { + fitRecorderHeight(win, 600); + } + + assert.equal(win.size[0], widthAfterFirstFit); +}); + diff --git a/electron/recorder-window-sizing.ts b/electron/recorder-window-sizing.ts index 27676ac..f0ccdd1 100644 --- a/electron/recorder-window-sizing.ts +++ b/electron/recorder-window-sizing.ts @@ -10,6 +10,9 @@ export interface RecorderWindowSizingTarget { setSize(width: number, height: number, animate?: boolean): void; } +const pinnedOuterWidths = new WeakMap(); +const HEIGHT_TOLERANCE = 2; + /** * Fit the recorder to its rendered content while preserving its fixed outer width. * Windows changes the non-client frame thickness when resizability is toggled, so @@ -26,14 +29,20 @@ export function fitRecorderHeight( Math.max(RECORDER_MIN_HEIGHT, Math.min(RECORDER_MAX_HEIGHT, contentHeight)), ); const [, currentContentHeight] = win.getContentSize(); - if (Math.abs(currentContentHeight - targetContentHeight) < 1) return; + if (Math.abs(currentContentHeight - targetContentHeight) <= HEIGHT_TOLERANCE) return; const [outerWidth, outerHeight] = win.getSize(); + let pinnedWidth = pinnedOuterWidths.get(win); + if (pinnedWidth === undefined) { + pinnedWidth = outerWidth; + pinnedOuterWidths.set(win, pinnedWidth); + } + const targetOuterHeight = outerHeight + targetContentHeight - currentContentHeight; const wasResizable = win.isResizable(); if (!wasResizable) win.setResizable(true); try { - win.setSize(outerWidth, targetOuterHeight); + win.setSize(pinnedWidth, targetOuterHeight); } finally { if (!wasResizable && !win.isDestroyed()) win.setResizable(false); } From 0a28c0a0af416936aa84c1a77e59b7c052ec2332 Mon Sep 17 00:00:00 2001 From: adilei Date: Mon, 10 Aug 2026 22:24:42 +0300 Subject: [PATCH 2/2] test(ui): exercise repeated fractional resizing Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 463ac1dc-1999-4e15-a294-9fa0ae7ff7c8 --- electron/recorder-window-sizing.test.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/electron/recorder-window-sizing.test.ts b/electron/recorder-window-sizing.test.ts index 92b5f26..bbf2ddb 100644 --- a/electron/recorder-window-sizing.test.ts +++ b/electron/recorder-window-sizing.test.ts @@ -77,6 +77,9 @@ class FractionalScalingWindow implements RecorderWindowSizingTarget { destroyed = false; resizable = false; size: [number, number] = [401, 500]; + setSizeCalls = 0; + + constructor(private readonly heightRoundingError: number) {} isDestroyed(): boolean { return this.destroyed; @@ -84,7 +87,7 @@ class FractionalScalingWindow implements RecorderWindowSizingTarget { getContentSize(): [number, number] { const frame: [number, number] = this.resizable ? [16, 39] : [2, 31]; - return [this.size[0] - frame[0], this.size[1] - frame[1] + 1]; + return [this.size[0] - frame[0], this.size[1] - frame[1]]; } getSize(): [number, number] { @@ -100,13 +103,13 @@ class FractionalScalingWindow implements RecorderWindowSizingTarget { } setSize(width: number, height: number): void { - // Simulate fractional display scaling round-trip perturbation where getSize() expands width - this.size = [width + 2, height]; + this.setSizeCalls++; + this.size = [width + 2, height + this.heightRoundingError]; } } -test("recorder fitting prevents width growth and settles height under fractional display scaling", () => { - const win = new FractionalScalingWindow(); +test("recorder fitting prevents width growth across repeated fractional-scale resizing", () => { + const win = new FractionalScalingWindow(3); fitRecorderHeight(win, 600); const widthAfterFirstFit = win.size[0]; @@ -115,6 +118,15 @@ test("recorder fitting prevents width growth and settles height under fractional fitRecorderHeight(win, 600); } + assert.equal(win.setSizeCalls, 11); assert.equal(win.size[0], widthAfterFirstFit); }); +test("recorder fitting accepts fractional-scale height differences within tolerance", () => { + const win = new FractionalScalingWindow(2); + + fitRecorderHeight(win, 600); + fitRecorderHeight(win, 600); + + assert.equal(win.setSizeCalls, 1); +});