Skip to content

Feature: Remember the window's size, position and maximized state - #684

Open
Almighty-Shogun wants to merge 1 commit into
eneskirca:mainfrom
Almighty-Shogun:feat/remember-window-size-and-position
Open

Feature: Remember the window's size, position and maximized state#684
Almighty-Shogun wants to merge 1 commit into
eneskirca:mainfrom
Almighty-Shogun:feat/remember-window-size-and-position

Conversation

@Almighty-Shogun

Copy link
Copy Markdown

This PR adds persistence for the main window's size, position and maximized state. The window was created at a hard-coded 1400x900 on every launch and nothing ever read or wrote its geometry, so anyone who works maximized re-maximized it every single time they opened the app. Electron persists none of this on its own. It surfaced on Fedora but was never platform-specific — macOS and Windows forgot it too.

State lands in <userData>/window-state.json through renameAtomicSync with a per-call unique temp, like every other store. main/window-state.ts imports no electron, in the keydown-intercept.ts shape: pure decisions over plain rectangles, a structural window for the tracker, and screen.getAllDisplays() called at the seam in index.ts. That was the point of the split — the interesting behaviour here only happens on hardware (a second monitor, a Mac in fullscreen), and this way it can be pressed by a test instead. What gets passed in is the displays' work areas, not their raw bounds: a saved position has to be judged against the space a window can actually occupy.

The refusals are the feature, because each is a way the naive version is worse than the fixed size it replaces. getNormalBounds(), never getBounds() — while maximized the latter returns the maximized rectangle, so saving it makes the next un-maximize hand back a screen-sized window: state that looks right and behaves wrong, and only for the users who maximize. An unreachable position is dropped, not clamped — a laptop undocked from the monitor its window was on would otherwise reopen the app off-screen, running and focusable from the dock but visible nowhere, with no gesture that rescues it; reachability is a real overlap with some work area, judged against the clamped size, and dropping keeps the user's size while letting the platform place a window it knows how to place. No capture while minimized or fullscreenisMaximized() is false while a macOS window is fullscreen, so capturing there records maximized: false and erases exactly the preference this exists to remember; the last non-fullscreen state stands, which also means the app never reopens into fullscreen, deliberately, since that mode is usually temporary and much harder to escape on first launch. Maximizing happens before the first paint while the window is still show: false, saves are debounced through a drag and flushed synchronously on close (an awaited write there races the process exit), every field is re-validated as a number on read because the file is hand-editable and its values reach the BrowserWindow constructor before there is a window in which to report a failure, and NT_MULTI is excluded so a dev sandbox sharing the real userData cannot move the window of the app being developed.

Alternatives I rejected: clamping an off-screen position into the nearest display, which invents a corner the user never chose and is indistinguishable from a bug when it happens; and restoring fullscreen alongside maximized, which is trivial to add and unpleasant to land in on a first launch.

Surfaces — Desktop: the whole feature. Server Edition: not applicable, a browser tab's geometry belongs to the browser, which is why none of this is in src/core. Mobile: not applicable, nodeterm mobile has no window concept; nothing for @eneskirca to carry over.

Verified: npm run typecheck, and the src/main + src/shared suites plus the atomic-write guard (2525 passing). 37 unit tests cover the module. Following the mutation habit in CONTRIBUTING.md I inverted twelve of the guards above; three survived the first version of the suite — a fixture that could not tell the clamped size from the saved one, a timer fake that hid a missing debounce cancel, and no case for an overflowing exponent (1e999 parses to Infinity, so typeof === 'number' only looks sufficient). All twelve are killed now.

Not verified: I tested this by hand only on Fedora 44 / GNOME, where size and maximized restore correctly across quit and relaunch. The macOS fullscreen path, the Windows path, and multi-monitor undocking are covered by unit tests against synthetic work areas and by nothing else. One known limit: a native-Wayland client cannot set its own position, so x/y is honoured under XWayland and ignored otherwise — size and maximized restore either way, which is what the drop-don't-clamp rule already degrades to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant