Skip to content

fix(editor): let the editor un-maximize on Wayland and stop its bounds drifting - #1271

Merged
EtienneLescot merged 1 commit into
mainfrom
fix/editor-window-bounds-wayland
Oct 15, 2026
Merged

EtienneLescot merged 1 commit into
mainfrom
fix/editor-window-bounds-wayland

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Summary

On GNOME Wayland the editor opens maximized and cannot be un-maximized. Its editor-window.json also ends up holding normal bounds the display cannot contain, and they grow with every session. Found in the manual pass of the installed v2.1.0-rc.1 .deb: Ubuntu 24.04, GNOME Shell 46, native Wayland, Electron 41.2.1, one 1920×1080 display at 100 %, 32 px top bar, Ubuntu dock at the bottom.

Observed

  • The title-bar restore button did nothing. Two clicks with the pointer on it (hover state visible) left the window maximized.
  • editor-window.json before the session was {"x":271,"y":24,"width":1410,"height":1087,"maximized":true}. After closing the editor it was {"x":256,"y":-10,"width":1440,"height":1121,"maximized":true}: taller than the 1080 px display, with a negative y.

Root cause. These are two separate Electron 41 bugs, both reached through code we control.

  1. Stuck maximized: Chromium Wayland, fix: window stuck maximized on Wayland after early maximize() electron/electron#53063. We call win.maximize() right after construction. Electron then shows the window and asks for maximize in the same breath. The compositor's state-less reply to the initial commit gets compared against the optimistically applied kMaximized, and that latches previously_maximized_ = true in WaylandToplevelWindow. After that, Restore() asks for kMaximized again, Chromium deduplicates it, and xdg_toplevel.unset_maximized is never sent. Upstream fixed this in a Chromium patch that went into Electron 42-x-y, 43-x-y and 44-x-y (#53126, #53125, #53124). It was not backported to 41.
  2. Drift: Electron's getNormalBounds() under Wayland CSD. While the window is maximized, getNormalBounds() takes the restored widget rect, which carries the client-side shadow (insets TLBR 10/16/32/16). It then subtracts the maximized insets (0/1/1/1) instead of the restored ones (native_window_views.cc, unchanged on Electron main). Each maximized close therefore writes the normal rect back 30×41 px larger and 15/10 px up and to the left. The arithmetic reproduces the observed file exactly: rc.1 clamped {271,24,1410,1087} to {271,0,1410,1080} and wrote back {256,-10,1440,1121}.

There is also a contributing factor. Wayland gives clients no work area, so Chromium reports the whole output ({0,0,1920,1080}), and the existing clamp let a 1080 px-tall normal window through under a 32 px top bar plus the dock. x/y mean nothing to a Wayland client either: it can neither set nor read its position.

Fix, Wayland backend only (usesWaylandBackend: Linux, Wayland session, and no explicit --ozone-platform=x11):

  • Defer the maximize until the compositor has mapped and focused the window (focus comes from wl_keyboard.enter, which only reaches a mapped surface), plus a 200 ms settle (WAYLAND_MAXIMIZE_SETTLE_MS) that waits out the activated/suspended configures sent with mapping. Until it lands, the saved state still says maximized: true, so an early close keeps the choice. A maximize the user makes first cancels ours. The export bench keeps its immediate maximize, because a maximize landing mid-run would resize what it measures.
  • Persist the real normal bounds: the last getBounds() read in the normal state, tracked on resize (resized/moved never fire on Linux), instead of getNormalBounds().
  • Clamp what is saved and what is loaded to the work area (fitEditorRect). Under Wayland, a centred tenth of each dimension is left for panels it cannot see: 1728×972 of a 1080p output.
  • Stop handing a saved position to the window under Wayland, so the compositor places it.

Windows, macOS and X11 keep the immediate maximize, the saved position and getNormalBounds(). The only change there is that the saved rect is clamped to the work area on write as well as on read. Restore was already clamping to the same display, so the window comes back identically. The checklist rule still holds: a hand-edited zero size or a non-boolean maximized falls back to the default window.

Known limits

  • Under Wayland the editor now shows at its normal size for about 200 ms, then animates to maximized.
  • The settle delay is a margin, not a guarantee. A state-less configure that happens to be in flight at that instant (the user alt-tabbing at that exact moment) could still latch the Chromium flag. Moving to Electron ≥ 42 removes the need for the workaround.
  • If the compositor never focuses the window, it stays at its normal size until the user first focuses it.

Related issue

Found during the manual e2e pass of v2.1.0-rc.1 on Ubuntu 24.04 / GNOME 46 Wayland. No issue filed. Intended for cherry-pick onto release/v2.1.0.

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Screenshots / video

None: the GUI was not launched for this change (see Testing).

Testing

Unit (platform pinned in every platform-dependent test):

  • electron/editorWindowState.test.ts:

    • fitEditorRect with and without Wayland. The drifted rc.1 file becomes {256,54,1440,972} under Wayland and {256,0,1440,1080} elsewhere.
    • The minimum size is respected.
    • resolveEditorCreation drops the position and defers the maximize under Wayland only, and never for the bench.
    • A zero-size / maximized: "yes" file still opens the default window.
  • electron/wayland.test.ts: usesWaylandBackend is on in a Linux Wayland session, and off under Xwayland (--ozone-platform=x11), in an X11 session, and on win32/darwin.

  • electron/windows.editorWindow.test.ts, new. It drives createEditorWindow against a fake BrowserWindow that reproduces Electron 41's Wayland CSD bounds arithmetic. One test pins the fake to the field measurement ({271,0,1410,1080} → {256,-10,1440,1121}). The tests check that:

    • the maximize waits for focus plus the settle delay;
    • three maximized sessions save identical bounds, with no position passed;
    • an un-maximize, resize and re-maximize saves the user's size;
    • an early close still records maximized;
    • a user maximize is not overridden;
    • the bench maximizes at once;
    • Xwayland behaves like X11;
    • Windows keeps its saved position, its immediate maximize and getNormalBounds(), and clamps what it saves.

    All of the save-path tests fail against origin/main's windows.ts (the +30×+41 drift shows up as 1300×900 → 1330×941) and pass with the fix.

Commands:

  • npx vitest --run electron/editorWindowState.test.ts electron/wayland.test.ts electron/windows.editorWindow.test.ts electron/windows.test.ts electron/windows.contentProtection.test.ts electron/hudWindowBounds.test.ts: 77 passed.
  • Full npm run test: 4737 passed. The five electron/mcp/* suites fail locally only because the shared node_modules lacks @modelcontextprotocol/sdk. CI's npm ci installs it.
  • npx tsc --noEmit and npx tsc -p tsconfig.test.json --noEmit are clean outside electron/mcp, which fails for the same missing package.
  • npm run lint: exit 0. No diagnostics in the touched files.

Not run: the app itself. Another session was driving the desktop, so this change has not been seen on screen yet. The manual verification below is required before cherry-picking.

Manual verification on GNOME Wayland (Ubuntu 24.04, an installed build of this branch or npm run dev, echo $XDG_SESSION_TYPE → wayland):

  1. Quit OpenScreen. Back up ~/.config/openscreen/editor-window.json (dev build: ~/.config/Electron/… or whatever userData is), then write the drifted rc.1 value into it: {"x":256,"y":-10,"width":1440,"height":1121,"maximized":true}.
  2. Launch and open the editor. Expect it to appear at a normal size, then maximize itself within about a quarter of a second.
  3. Click the title-bar restore button. Expect a normal window that fits entirely between the top bar and the dock: 1440×972 on a 1080p output, bottom edge and resize corner reachable.
  4. Click maximize, then restore again, a few times. Expect each click to toggle. Also double-click the top bar's empty drag area and confirm it toggles too.
  5. With the editor maximized, close it and read the file. Expect "maximized":true, width/height of 1440/972, and x/y within the output (no negative value). Reopen and close maximized twice more, and confirm the file stays byte-identical across sessions (no growth).
  6. Restore, resize the window to something smaller (e.g. 1200×800), close, and read the file. Expect that size with "maximized":false. Reopen and expect that size, not maximized.
  7. Hand-edit the file to {"x":10,"y":20,"width":0,"height":0,"maximized":"true"}, then relaunch. Expect the default window (1200×800, then maximized) and the restore button working.
  8. Optional: launch with --ozone-platform=x11 (Xwayland) and confirm the editor maximizes at once, with no normal frame first, and still restores.
  9. Restore the backed-up file.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved editor window behavior on Wayland: restored windows fit the available screen area, and maximized windows wait until the window is ready.
    • Improved saving and restoring window size and position across sessions, including for maximized windows.
    • Prevented saved window dimensions from exceeding the available screen area.

…s drifting

On GNOME Wayland the editor opened maximized could not be restored: its
title-bar restore button did nothing. Electron 41's Chromium latches its
"restore to maximized" flag when maximize() is asked for while a configure
without the maximized state is still on its way, which is always the case
for the maximize issued right after construction (electron/electron#53063,
fixed in Electron 42+, not backported to 41). Under Wayland the editor now
maximizes once the compositor has mapped and focused it, after a short
settle; the bench keeps its immediate maximize.

editor-window.json also grew on every maximized close (+30x+41 px, origin
-15/-10): under Wayland getNormalBounds() read while maximized subtracts the
maximized frame insets from the restored, shadow-carrying widget rect. The
normal bounds are now taken from getBounds() while the window is in its
normal state. What is saved is clamped to the work area, which under
Wayland is the whole output, so a tenth of each dimension is left to
panels; and a saved position, which a Wayland client can neither set nor
read, is no longer handed back to the window there.

Windows, macOS and X11 (including --ozone-platform=x11) keep the immediate
maximize and getNormalBounds(); the hand-edited zero-size or non-boolean
state still falls back to the default window.
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dac5fed1-a6c4-45bf-8031-e206502371db
📥 Commits

Reviewing files that changed from the base of the PR and between 1b7ed86 and ea58fc7.

📒 Files selected for processing: 6
  • electron/editorWindowState.test.ts
  • electron/editorWindowState.ts
  • electron/wayland.test.ts
  • electron/wayland.ts
  • electron/windows.editorWindow.test.ts
  • electron/windows.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 0 remain after this review.

📝 Walkthrough

Editor windows now detect the Wayland backend, fit saved bounds within a reserved work area, and defer maximize until focus settles. Wayland persistence tracks normal bounds separately. Tests cover Wayland, Xwayland, Windows, and export bench behavior.

Changes: Wayland Editor Windows

Layer / File(s) Summary
Backend detection and window-state policy
electron/editorWindowState.ts, electron/wayland.ts, electron/editorWindowState.test.ts, electron/wayland.test.ts
Adds Wayland backend detection, rectangle fitting with a 10% reserve, and creation-state rules for deferred maximize. Tests cover backend detection, fitting, and creation policies.
Window creation and deferred maximize
electron/windows.ts, electron/windows.editorWindow.test.ts
Editor-window creation uses Wayland-aware bounds and retries pending maximize after focus and a 200 ms settle delay.
Normal-bounds persistence and validation
electron/windows.ts, electron/windows.editorWindow.test.ts
Wayland persistence tracks normal bounds and pending maximize state. Tests cover saved state across sessions and Windows restoration behavior.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant createEditorWindow
  participant BrowserWindow
  participant WaylandMaximizeRetry
  createEditorWindow->>BrowserWindow: Create editor window
  BrowserWindow->>createEditorWindow: Emit focus event
  createEditorWindow->>WaylandMaximizeRetry: Schedule retry after settle delay
  WaylandMaximizeRetry->>BrowserWindow: Maximize if still pending
Loading

Suggested reviewers: my-denia

Merge Risk: 🔵 Low · up to ea58f

The editor now defers maximizing on Wayland until the window is focused, and it tracks normal bounds so they stop drifting. The remaining risk is that compositor focus timing on GNOME Wayland has not been manually checked, so confirm that before release.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 6 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely identifies the two primary fixes: restoring editor windows on Wayland and preventing bounds drift.
Description check Passed The description follows the repository template and provides a detailed summary, issue context, change scope, platform impact, testing results, known limits, and required manual verification. The blan…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@EtienneLescot
EtienneLescot merged commit ba24332 into main Oct 15, 2026
19 checks passed
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