Skip to content

feat: persist and restore main window size and position#3673

Open
DhivinX wants to merge 1 commit into
pingdotgg:mainfrom
DhivinX:persist-window-state
Open

feat: persist and restore main window size and position#3673
DhivinX wants to merge 1 commit into
pingdotgg:mainfrom
DhivinX:persist-window-state

Conversation

@DhivinX

@DhivinX DhivinX commented Jul 3, 2026

Copy link
Copy Markdown

What

The main window now remembers its size, position, and maximized state across launches. Before this, it always reopened at the hardcoded 1100×780 in DesktopWindow.ts, throwing away whatever size/position you left it at.

How

  • New DesktopWindowState service (apps/desktop/src/window/DesktopWindowState.ts) persists geometry to window-state.json in the existing state dir, following the DesktopAppSettings idiom (effect FileSystem + Schema, falls back to defaults on a missing/corrupt file).
  • DesktopWindow loads the saved geometry before creating the window, then writes it back on resize / move / maximize / unmaximize / close. Writes are debounced with a restartable fiber (same pattern as the existing dev-load retry in that file) so dragging/resizing doesn't thrash the disk.
  • Saved positions are validated against the connected displays' work areas: an off-screen position (e.g. after unplugging an external monitor) falls back to size-only so the window can't open where you can't see it.

Scope / notes

  • Behavior-only change; no visual UI change.
  • Persists normal bounds + maximized flag; fullscreen is intentionally not persisted (restores to normal/maximized instead).
  • A failed write is logged and otherwise ignored — worst case is the window opens at the last-known or default size.

Testing

  • pnpm --filter @t3tools/desktop typecheck — clean
  • vp check — clean
  • Desktop test suite: 339 passed, including a new DesktopWindowState.test.ts (off-screen / maximized / size-only cases) and updated DesktopWindow.test.ts doubles.

Note

Low Risk
Local UX and JSON persistence in userdata only; corrupt/missing files fall back to defaults with no security or backend impact.

Overview
The desktop main window remembers size, position, and maximized state across launches instead of always opening at fixed 1100×780.

A new DesktopWindowState service reads/writes window-state.json in the state directory (same Effect + Schema pattern as other desktop settings). DesktopWindow loads saved geometry before BrowserWindow creation, applies maximize when saved, and persists on resize/move/maximize/unmaximize/close with 400ms debounced writes. Off-screen saved positions are dropped using display work areas (e.g. unplugged monitor) while keeping size and maximized flag. Fullscreen is not persisted; writes failures are logged only.

Wiring adds windowStatePath to DesktopEnvironment, registers the layer in main.ts, and extends tests (DesktopWindowState.test.ts, updated DesktopWindow.test mocks).

Reviewed by Cursor Bugbot for commit cd5a7ba. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Persist and restore main window size, position, and maximized state

  • Adds a new DesktopWindowState service in DesktopWindowState.ts that reads/writes window geometry to window-state.json in the app state directory.
  • On window creation, loads saved bounds and maximized state; uses resolveInitialWindowBounds to constrain the window to visible displays, falling back to defaults if off-screen or no saved state exists.
  • Persists window geometry (size, position, maximized) on resize, move, maximize, and unmaximize with debouncing, plus a final save on close.
  • Behavioral Change: the desktop window now opens at its last-used size and position instead of always using the default size.
📊 Macroscope summarized cd5a7ba. 4 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b6742745-09d0-4faa-b0ce-784d7a4cfc0e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 3, 2026
Comment on lines +301 to +302
if (window.isDestroyed() || window.isMinimized() || window.isFullScreen()) {
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium window/DesktopWindow.ts:301

persistWindowState bails out when window.isMinimized() is true, and the close handler calls it after canceling the pending debounce. When the user quits the app while the window is minimized, no state is written even though getNormalBounds() still returns valid restorable bounds in that state, so the next launch restores stale geometry. Consider removing the isMinimized() guard from persistWindowState so the final close always writes the current bounds.

Suggested change
if (window.isDestroyed() || window.isMinimized() || window.isFullScreen()) {
return;
if (window.isDestroyed() || window.isFullScreen()) {
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/window/DesktopWindow.ts around lines 301-302:

`persistWindowState` bails out when `window.isMinimized()` is true, and the `close` handler calls it after canceling the pending debounce. When the user quits the app while the window is minimized, no state is written even though `getNormalBounds()` still returns valid restorable bounds in that state, so the next launch restores stale geometry. Consider removing the `isMinimized()` guard from `persistWindowState` so the final close always writes the current bounds.

}

const rect: WindowRect = { x: state.x, y: state.y, width: state.width, height: state.height };
const onScreen = displays.some((display) => rectsIntersect(display, rect));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium window/DesktopWindowState.ts:86

resolveInitialWindowBounds only requires any pixel of overlap with a display via rectsIntersect. A saved state like { x: 100, y: -590, width: 800, height: 600 } still intersects the screen by a few pixels, so this code restores the off-screen x/y instead of dropping them. Because the app uses a hidden title bar, this can reopen the window with its draggable area completely off-screen and effectively inaccessible, even though the PR is meant to prevent off-screen restores. Consider requiring the title-bar/caption region (or a larger minimum visible margin) to intersect a display before restoring x/y.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/window/DesktopWindowState.ts around line 86:

`resolveInitialWindowBounds` only requires any pixel of overlap with a display via `rectsIntersect`. A saved state like `{ x: 100, y: -590, width: 800, height: 600 }` still intersects the screen by a few pixels, so this code restores the off-screen `x`/`y` instead of dropping them. Because the app uses a hidden title bar, this can reopen the window with its draggable area completely off-screen and effectively inaccessible, even though the PR is meant to prevent off-screen restores. Consider requiring the title-bar/caption region (or a larger minimum visible margin) to intersect a display before restoring `x`/`y`.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cd5a7ba. Configure here.

let windowStateSaveFiber: Fiber.Fiber<void, never> | undefined;
const persistWindowState = () => {
if (window.isDestroyed() || window.isMinimized() || window.isFullScreen()) {
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimized window skips close persist

Medium Severity

When the main window closes while minimized, persistWindowState returns early, preventing the final window geometry from being saved. This can lead to stale window state persistence.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cd5a7ba. Configure here.

@macroscopeapp

macroscopeapp Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces new window state persistence with unresolved review comments identifying bugs: window state isn't saved when closing while minimized, and off-screen detection may leave windows inaccessible. These substantive issues warrant human review.

You can customize Macroscope's approvability policy. Learn more.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant