Skip to content

Recorder window grows horizontally without bound on fractional display scaling (Windows, 125%) #56

Description

Summary

On Windows with fractional display scaling (verified at 125% / DPI 120), the recorder HUD widens continuously from the moment it launches, with no user interaction, until it spans the display.

Environment

  • Windows 11 x64, display scaling 125% (GetDpiForWindow returns 120)
  • Commit 87fdf397ea85f07595b2645bc52cdbcda14eb36f, installed via install.ps1
  • Electron 43.1.1, Node 24.19.0

Steps to reproduce

  1. Set Windows display scaling to 125%.
  2. Launch Skill Recorder.
  3. Watch the HUD window. No clicks required.

Observed

Polling GetWindowRect on the HUD window:

elapsed outer size
0.0s 401 x 500
1.2s 418 x 494
5.0s 507 x 494
20.0s 1306 x 494
45.0s 1403 x 494 (still growing)

Height stays at 494. Only the width grows, at roughly 15-20 px/s.

Analysis

fitRecorderHeight in electron/recorder-window-sizing.ts reads the outer width via getSize() and feeds it straight back into setSize():

const [outerWidth, outerHeight] = win.getSize();
// ...
win.setSize(outerWidth, targetOuterHeight);

Both APIs operate in DIPs. Under fractional scaling the DIP -> physical -> DIP round trip is lossy, so every call widens the window slightly. The width change reflows the renderer, the ResizeObserver in src/Recorder.tsx fires again, and fitRecorderHeight runs again. The result is a self-sustaining rAF-paced loop.

At 100% scaling the round trip is lossless and the height early-return terminates the loop, which is likely why 1a582cc ("fix(ui): prevent recorder window width growth") appeared to resolve this.

Related: the same rounding perturbs the resulting content height, so Math.abs(currentContentHeight - targetContentHeight) < 1 may never hold and the loop never settles on its own.

Suggested fix

Pin the outer width to its first observed value rather than re-reading it on each call, and give the height comparison a small tolerance:

const pinnedOuterWidths = new WeakMap<RecorderWindowSizingTarget, number>();
const HEIGHT_TOLERANCE = 2;

// ...
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);
}
// ...
win.setSize(pinnedWidth, targetOuterHeight);

With this applied locally the window settles at 402 x 494 and stays there (verified over 45s), and recorder-window-sizing.test.ts still passes.

Note that the existing test double models a lossless DIP mapping, so it cannot catch this class of bug. A case whose setSize simulates fractional-scale rounding would help.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions