Skip to content

fix: editor windows remember size, position, and zoom state across launches#1092

Merged
datlechin merged 8 commits intomainfrom
fix/window-frame-autosave
May 7, 2026
Merged

fix: editor windows remember size, position, and zoom state across launches#1092
datlechin merged 8 commits intomainfrom
fix/window-frame-autosave

Conversation

@datlechin
Copy link
Copy Markdown
Member

@datlechin datlechin commented May 7, 2026

Summary

Editor windows now remember their size, position, and zoom state across app launches, matching standard macOS behavior. Net change is small: ~10 lines of code in 3 files, no new types or files.

Why

The editor window always opened at the same default frame regardless of what the user had done before. Three pieces of code combined to defeat NSWindow's frame persistence:

  1. MainSplitViewController.viewWillAppear had a "minimum size floor" that re-clamped any restored frame smaller than 1200×800 and re-centered.
  2. WindowManager.openTab called window.center() unconditionally, wiping the autosaved origin every reopen.
  3. setFrameAutosaveName(_:)'s implicit auto-save observer fires when contentViewController = NSSplitViewController shrinks the window to its intrinsic content size — overwriting the saved frame in UserDefaults with the small intrinsic size. The persisted size kept decaying toward NSSplitView's minimum.

Tracing this took several iterations because the bug looks different depending on which step you observe — the trace logs in the conversation history pinpoint the contentVC-resize-clobbers-saved-frame interaction as the root cause.

How

TabWindowController.swift

  • Removed applyAutosaveName / setFrameAutosaveName (its implicit save observer is what corrupts the saved frame during init).
  • Use setFrameUsingName(_:) AFTER super.init and after contentViewController is set, so the contentVC layout pass happens first and our explicit restore is the final word.
  • First-launch fallback: when setFrameUsingName returns false, setContentSize(1200×800) + center() so the user gets a sensible default instead of NSSplitView's minimum.
  • Save explicitly via NSWindowDelegate methods that the controller already implements:
    • windowDidResize (when !inLiveResize) — captures zoom and end-of-drag.
    • windowDidEndLiveResize — captures drag-resize end.
    • windowDidMove — captures move.
    • windowWillClose — final state safety net.

MainSplitViewController.swift

  • Deleted the upsize-floor block in viewWillAppear. window.minSize = 720×480 already enforces the minimum during user resize; the programmatic re-clamp was redundant and destroyed restored frames.

WindowManager.swift

  • Removed the unconditional window.center() for standalone windows. TabWindowController.init now owns frame setup (restore-or-default), so WindowManager just calls makeKeyAndOrderFront.

Why this is the canonical Apple pattern

  • setFrameUsingName(_:) and saveFrame(usingName:) are documented Apple APIs, paired by name. They share the UserDefaults key "NSWindow Frame {name}" whether or not setFrameAutosaveName was ever called.
  • Reacting to frame changes via NSWindowDelegate is the standard Cocoa pattern. The controller is already the delegate; we just add the methods.
  • No new types, no policies, no NotificationCenter observers, no fullscreen tracking. Pure delegate methods + two Apple frame APIs.

Test plan

  • First launch (defaults delete com.TablePro "NSWindow Frame MainEditorWindow" first): connect → window opens at 1200×800 centered.
  • Resize persistence: connect, drag-resize to a custom size, close, reconnect → window opens at the resized size.
  • Position persistence: connect, drag to a non-default position, close, reconnect → window opens at the same position.
  • Zoom persistence: connect, double-click toolbar to zoom (maximize), close, reconnect → window opens zoomed.
  • Below-default size: drag-resize below 1200×800, close, reconnect → window opens at the smaller size, NOT bumped up.
  • Native NSWindow tab group: open two connections in one tab group, resize the group, close, reconnect → window opens at the resized frame.
  • Off-screen recovery: position window on external display, close while connected, disconnect display, relaunch standalone, connect → window opens on the current main screen (AppKit clamps automatically via setFrameUsingName).

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit 20b1974 into main May 7, 2026
2 checks passed
@datlechin datlechin deleted the fix/window-frame-autosave branch May 7, 2026 14:35
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