Skip to content

Commit 7498a74

Browse files
austinywangHoracehxw
authored andcommitted
Fix manaflow-ai#1972: resync terminal portal after restore-time bind (manaflow-ai#1973)
* test: cover queued restore-time terminal portal shift * fix: resync terminal portal after restore-time bind
1 parent 441118b commit 7498a74

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

Sources/TerminalWindowPortal.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,10 @@ final class WindowTerminalPortal: NSObject {
11301130

11311131
synchronizeHostedView(withId: hostedId)
11321132
scheduleDeferredFullSynchronizeAll()
1133+
// Session/window restore can queue additional ancestor layout shifts (sidebar width,
1134+
// split positions) after the initial bind tick. Queue a later external sync so the
1135+
// portal catches that settled geometry instead of staying at the seeded frame.
1136+
scheduleExternalGeometrySynchronize()
11331137
pruneDeadEntries()
11341138
}
11351139

cmuxTests/TerminalAndGhosttyTests.swift

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,6 +2939,83 @@ final class TerminalWindowPortalLifecycleTests: XCTestCase {
29392939
)
29402940
}
29412941

2942+
func testBindQueuesExternalGeometrySyncForQueuedLayoutShift() {
2943+
let window = NSWindow(
2944+
contentRect: NSRect(x: 0, y: 0, width: 700, height: 420),
2945+
styleMask: [.titled, .closable],
2946+
backing: .buffered,
2947+
defer: false
2948+
)
2949+
defer {
2950+
NotificationCenter.default.post(name: NSWindow.willCloseNotification, object: window)
2951+
window.orderOut(nil)
2952+
}
2953+
2954+
let surface = TerminalSurface(
2955+
tabId: UUID(),
2956+
context: GHOSTTY_SURFACE_CONTEXT_SPLIT,
2957+
configTemplate: nil,
2958+
workingDirectory: nil
2959+
)
2960+
guard let contentView = window.contentView else {
2961+
XCTFail("Expected content view")
2962+
return
2963+
}
2964+
2965+
let shiftedContainer = NSView(frame: NSRect(x: 40, y: 60, width: 260, height: 180))
2966+
contentView.addSubview(shiftedContainer)
2967+
let anchor = NSView(frame: NSRect(x: 0, y: 0, width: 260, height: 180))
2968+
shiftedContainer.addSubview(anchor)
2969+
let hosted = surface.hostedView
2970+
TerminalWindowPortalRegistry.bind(
2971+
hostedView: hosted,
2972+
to: anchor,
2973+
visibleInUI: true,
2974+
expectedSurfaceId: surface.id,
2975+
expectedGeneration: surface.portalBindingGeneration()
2976+
)
2977+
TerminalWindowPortalRegistry.synchronizeForAnchor(anchor)
2978+
2979+
let anchorCenter = NSPoint(x: anchor.bounds.midX, y: anchor.bounds.midY)
2980+
let originalWindowPoint = anchor.convert(anchorCenter, to: nil)
2981+
let originalAnchorFrameInWindow = anchor.convert(anchor.bounds, to: nil)
2982+
XCTAssertNotNil(
2983+
TerminalWindowPortalRegistry.terminalViewAtWindowPoint(originalWindowPoint, in: window),
2984+
"Initial hit-testing should resolve the portal-hosted terminal at its original window position"
2985+
)
2986+
2987+
DispatchQueue.main.async {
2988+
shiftedContainer.frame.origin.x += 72
2989+
contentView.layoutSubtreeIfNeeded()
2990+
window.displayIfNeeded()
2991+
}
2992+
2993+
RunLoop.current.run(until: Date().addingTimeInterval(0.05))
2994+
2995+
let shiftedAnchorFrameInWindow = anchor.convert(anchor.bounds, to: nil)
2996+
XCTAssertGreaterThan(
2997+
shiftedAnchorFrameInWindow.minX,
2998+
originalAnchorFrameInWindow.minX + 1,
2999+
"The queued layout shift should move the anchor to the right"
3000+
)
3001+
let retiredStaleWindowPoint = NSPoint(
3002+
x: (originalAnchorFrameInWindow.minX + shiftedAnchorFrameInWindow.minX) / 2,
3003+
y: shiftedAnchorFrameInWindow.midY
3004+
)
3005+
let shiftedWindowPoint = NSPoint(
3006+
x: (originalAnchorFrameInWindow.maxX + shiftedAnchorFrameInWindow.maxX) / 2,
3007+
y: shiftedAnchorFrameInWindow.midY
3008+
)
3009+
XCTAssertNil(
3010+
TerminalWindowPortalRegistry.terminalViewAtWindowPoint(retiredStaleWindowPoint, in: window),
3011+
"Bind should queue a later external sync so restore-like ancestor shifts do not leave a stale portal in the sidebar region"
3012+
)
3013+
XCTAssertNotNil(
3014+
TerminalWindowPortalRegistry.terminalViewAtWindowPoint(shiftedWindowPoint, in: window),
3015+
"Bind should refresh the portal after queued ancestor layout settles"
3016+
)
3017+
}
3018+
29423019
func testScheduledExternalGeometrySyncKeepsDragDrivenResizeResponsive() {
29433020
let window = NSWindow(
29443021
contentRect: NSRect(x: 0, y: 0, width: 700, height: 420),

0 commit comments

Comments
 (0)