From a6cd1b08af240e7be0b07163d78dac5efa6b1752 Mon Sep 17 00:00:00 2001 From: Lukas <134181853+bo2themax@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:35:49 +0100 Subject: [PATCH] macOS: fix intrinsicContentSize race in windowDidLoad (#11256) Add initialContentSize fallback on TerminalViewContainer so intrinsicContentSize returns the correct value immediately, without waiting for @FocusedValue to propagate. This removes the need for the DispatchQueue.main.asyncAfter 40ms delay. Co-Authored-By: Claude Opus 4.6 --- .../Terminal/TerminalController.swift | 29 ++++++++-------- .../Terminal/TerminalViewContainer.swift | 20 ++++++++--- .../Terminal/IntrinsicSizeTimingTests.swift | 33 ++++++++----------- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 7a5bd1d4b..0c1bd38ad 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -1038,27 +1038,26 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr } // Initialize our content view to the SwiftUI root - window.contentView = TerminalViewContainer { + let container = TerminalViewContainer { TerminalView(ghostty: ghostty, viewModel: self, delegate: self) } + // Set the initial content size on the container so that + // intrinsicContentSize returns the correct value immediately, + // without waiting for @FocusedValue to propagate through the + // SwiftUI focus chain. + container.initialContentSize = focusedSurface?.initialSize + + window.contentView = container + // If we have a default size, we want to apply it. if let defaultSize { - switch defaultSize { - case .frame: - // Frames can be applied immediately - defaultSize.apply(to: window) + defaultSize.apply(to: window) - case .contentIntrinsicSize: - // Content intrinsic size requires a short delay so that AppKit - // can layout our SwiftUI views. - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(40)) { [weak self, weak window] in - guard let self, let window else { return } - defaultSize.apply(to: window) - if let screen = window.screen ?? NSScreen.main { - let frame = self.adjustForWindowPosition(frame: window.frame, on: screen) - window.setFrameOrigin(frame.origin) - } + if case .contentIntrinsicSize = defaultSize { + if let screen = window.screen ?? NSScreen.main { + let frame = self.adjustForWindowPosition(frame: window.frame, on: screen) + window.setFrameOrigin(frame.origin) } } } diff --git a/macos/Sources/Features/Terminal/TerminalViewContainer.swift b/macos/Sources/Features/Terminal/TerminalViewContainer.swift index bd696a2bf..dd0190c4c 100644 --- a/macos/Sources/Features/Terminal/TerminalViewContainer.swift +++ b/macos/Sources/Features/Terminal/TerminalViewContainer.swift @@ -33,11 +33,23 @@ class TerminalViewContainer: NSView { fatalError("init(coder:) has not been implemented") } - /// To make ``TerminalController/DefaultSize/contentIntrinsicSize`` - /// work in ``TerminalController/windowDidLoad()``, - /// we override this to provide the correct size. + /// The initial content size to use as a fallback before the SwiftUI + /// view hierarchy has completed layout (i.e. before @FocusedValue + /// propagates `lastFocusedSurface`). Once the hosting view reports + /// a valid intrinsic size, this fallback is no longer used. + var initialContentSize: NSSize? + override var intrinsicContentSize: NSSize { - terminalView.intrinsicContentSize + let hostingSize = terminalView.intrinsicContentSize + // The hosting view returns a valid size once SwiftUI has laid out + // with the correct idealWidth/idealHeight. Before that (when + // @FocusedValue hasn't propagated), it returns a tiny default. + // Fall back to initialContentSize in that case. + if let initialContentSize, + hostingSize.width < initialContentSize.width || hostingSize.height < initialContentSize.height { + return initialContentSize + } + return hostingSize } private func setup() { diff --git a/macos/Tests/Terminal/IntrinsicSizeTimingTests.swift b/macos/Tests/Terminal/IntrinsicSizeTimingTests.swift index ef7818661..640f2dbdb 100644 --- a/macos/Tests/Terminal/IntrinsicSizeTimingTests.swift +++ b/macos/Tests/Terminal/IntrinsicSizeTimingTests.swift @@ -75,11 +75,11 @@ struct IntrinsicSizeTimingTests { OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle) } - // TODO: Fix #11256 — set initialContentSize on the container so - // intrinsicContentSize returns the correct value immediately. - // await MainActor.run { - // container.initialContentSize = expectedSize - // } + // Set initialContentSize so intrinsicContentSize returns the + // correct value immediately, without waiting for @FocusedValue. + await MainActor.run { + container.initialContentSize = expectedSize + } let window = await NSWindow( contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), @@ -111,10 +111,9 @@ struct IntrinsicSizeTimingTests { OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle) } - // TODO: Fix #11256 — set initialContentSize on the container. - // await MainActor.run { - // container.initialContentSize = NSSize(width: 600, height: 400) - // } + await MainActor.run { + container.initialContentSize = NSSize(width: 600, height: 400) + } let window = await NSWindow( contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), @@ -156,12 +155,9 @@ struct IntrinsicSizeTimingTests { OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle) } - // TODO: Fix #11256 — set initialContentSize on the container so - // intrinsicContentSize returns the correct value immediately, - // eliminating the need for the async delay. - // await MainActor.run { - // container.initialContentSize = NSSize(width: 600, height: 400) - // } + await MainActor.run { + container.initialContentSize = NSSize(width: 600, height: 400) + } let window = await NSWindow( contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), @@ -204,10 +200,9 @@ struct IntrinsicSizeTimingTests { OptionalIdealSizeView(idealWidth: nil, idealHeight: nil, titlebarStyle: titlebarStyle) } - // TODO: Fix #11256 — set initialContentSize on the container. - // await MainActor.run { - // container.initialContentSize = NSSize(width: 600, height: 400) - // } + await MainActor.run { + container.initialContentSize = NSSize(width: 600, height: 400) + } let window = await NSWindow( contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), -- 2.51.2