fix(windows): pin a screenshot window's size when it is built, not from inside its layout pass - #3092
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
A connection window opened while TablePro was in the background, with its size pinned through
TABLEPRO_SCREENSHOT_FRAME, came up broken and stayed broken after the app was activated:Found while capturing the screenshots for #3060: 3 of 3 background-created windows broken, 0 of 2 active ones.
Root cause
ScreenshotEnvironment.pinWindowSizeresized the window fromMainContentView.configureWindow, which runs fromWindowAccessorView.viewDidMoveToWindow. SwiftUI calls that while it renders insideNSHostingView.layout(), inside the window's own layout pass. The call stack from a standalone probe:-[NSWindow layoutIfNeeded]→-[NSView _layoutSubtreeWithOldSize:](several levels) →NSHostingView.layout→addSubview→viewDidMoveToWindow→setFrame. That pass has already captured the old size, so aftersetFrameit resizes the content view by the difference a second time.Measured from the app's accessibility tree, background URL open, window pinned from 1200x800 to 1512x861:
1824 = 1512 + 312 and 922 = 861 + 61: the delta twice. The split view starting 61pt above the window is what put the sidebar's top row under the titlebar, and its inspector divider at x 1824, past the 1512pt window, is what pushed the toolbar section that tracks it into the overflow menu.
A standalone AppKit probe (unified toolbar,
NSSplitViewController,NSHostingControllerdetail, the same resize fromviewDidMoveToWindow) gives the same 1824x922 whether the window is visible or not and whether the app is active or not, and 1512x861 when the same resize runs from a plain main-queue block. So the defect is where the resize is issued. Activation only changed whether TablePro's SwiftUI update landed inside the window's layout pass.Which paths hit it
Only runs that set
TABLEPRO_SCREENSHOT_FRAME, whichScreenshotEnvironmentreads only under the UI test storage sandbox: screenshot captures, and the three UI suites that pin a frame (InspectorToolbarPlacementUITests,ConnectionWindowChromeUITests,ResultStatusBarUITests). A shipping build never pins, and its first frame comes fromsetFrameUsingNameinTabWindowController.init, before the window is shown or laid out.Measured to be sure: the same background URL open with a saved 1512x861 frame and no pin lays out correctly (split view 1512x861, chooser at y 56, inspector toggle in the toolbar). Reopening the last session at launch, a URL or file from another app, and MCP or AppleScript opens all build the window through that same initializer.
The pin has been in
configureWindowsince #2180. #3041 made it more visible by moving the chooser into the sidebar, above the filter field that was already there.Fix
The pinned size is decided where every other first frame is decided.
TabWindowController.initapplies the pin, else the autosaved frame, else the default size, before the window is shown, and before the controller becomes the window's delegate so a starting frame is not saved as one the user chose.ScreenshotEnvironmentonly computes the centred frame and no longer touches a window, andconfigureWindowno longer resizes anything.Side effect: the pin used to run again each time a connection's content mounted, re-centring a window a second connection was adopted into. It now runs once per window.
Tests
EditorWindowInitialFrameTests(new):TabWindowController.initstarts at its pinned size before it is shown. Moving the pin anywhere after construction fails this.WindowAccessorcallback inside the layout pass leaves the content view the delta too large. This pins the AppKit behaviour the placement rule rests on.ScreenshotEnvironmentTests: the pinned frame is centred on the visible frame.verify.sh test EditorWindowInitialFrameTests ScreenshotEnvironmentTests EditorWindowChromeTests ConnectionWindowInitialFocusTests: 29 executed, 29 passed. Build passes on the rebased commit.No new UI test. The failure needs SwiftUI to mount the content inside the window's layout pass, which in the app happened for a window built in the background, and XCUITest activates the app it launches. The unit test reproduces the mechanism deterministically instead.
Before / After
Both from sandboxed Debug builds opened with
open -gand apostgresql://URL while another app stayed frontmost, window pinned to 1512x861, the connect confirmed through accessibility without activating TablePro. Before ismainat f626e9a, after is this branch.Before: chooser under the traffic lights, filter field at the top edge, inspector toggle in the overflow menu, the empty state off-centre.
After: chooser and filter field below the titlebar, full toolbar with the inspector toggle.
No CHANGELOG entry: the defect never reached a shipping build.