native macOS codings agent orchestrator
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Close canvas exit investigation

+48 -376
+45 -219
doc-onevcat/canvas-exit-terminal-blank-tracking.md
··· 1 - # Canvas Exit Terminal Blank Tracking 2 - 3 - Last updated: 2026-04-15 4 - Status: Open, intermittent, now confirmed to affect both Canvas exit and Canvas entry via host ownership races 5 - 6 - ## Symptom 7 - 8 - When leaving Canvas and returning to the normal worktree terminal view, the terminal area can appear blank. 9 - 10 - As of 2026-04-15, the reverse direction is also reproducible: 11 - 12 - - after the app has been running for a while, entering Canvas from a normal worktree tab can open a blank Canvas card 13 - - the selected tab/worktree remains logically correct 14 - - unlike the earlier exit symptom, tab switching, creating a new tab, or switching away and back does not reliably recover the blank card 15 - 16 - Typical behavior: 17 - 18 - - The normal terminal view is visible in SwiftUI. 19 - - The selected worktree and tab are correct. 20 - - The terminal becomes visible again only after switching to another tab and back. 21 - 22 - ## Reproduction Profile 23 - 24 - Current evidence suggests this is not a fresh-session deterministic bug. 25 - 26 - - In a newly launched Prowl session, the bug is difficult to reproduce. 27 - - After the app has been running for a long time, especially across system sleep/wake, the bug may start happening. 28 - - Once it starts happening in a given app session, it tends to reproduce reliably on every Canvas exit until the app is restarted. 29 - 30 - This strongly suggests a sticky stale state in the long-lived terminal/native view stack rather than a simple `toggleCanvas` reducer bug. 31 - 32 - ## Relevant Areas 33 - 34 - - `supacode/Features/Repositories/Reducer/RepositoriesFeature.swift` 35 - - `supacode/Features/App/Reducer/AppFeature.swift` 36 - - `supacode/Features/Terminal/BusinessLogic/WorktreeTerminalManager.swift` 37 - - `supacode/Features/Terminal/Models/WorktreeTerminalState.swift` 38 - - `supacode/Features/Terminal/Views/WorktreeTerminalTabsView.swift` 39 - - `supacode/Features/Terminal/Views/WindowFocusObserverView.swift` 40 - - `supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift` 41 - - `supacode/Infrastructure/Ghostty/GhosttyTerminalView.swift` 42 - - `supacode/Infrastructure/Ghostty/GhosttyRuntime.swift` 43 - - `supacode/Features/Canvas/Views/CanvasView.swift` 44 - 45 - ## Repair History 46 - 47 - Known commits that attempted to address this family of issues: 48 - 49 - - `161f38a0` Fix blank surface when exiting canvas via toggle shortcut 50 - - `516103e4` fix: invalidate occlusion cache when exiting canvas to prevent blank surfaces 51 - - `11e7d16c` Fix occlusion cache invalidation on surface reattachment 52 - - `7ed53813` fix: defer occlusion apply until surface is attached 53 - - `26273089` fix: simplify canvas exit occlusion handling 54 - - `d4e59155` Add canvas exit terminal diagnostics and occlusion refresh 55 - - `e2a29b2c` test: cover Ghostty attachment occlusion behavior 56 - - `32f51451` Fix canvas exit terminal occlusion recovery 57 - - current branch `fix/canvas-exit-surface-reattach` 58 - - add detach-intent stack logging before `GhosttySurfaceView` loses its superview/window 59 - - add wrapper host diagnostics (`hostKind`, wrapper id, surface id) 60 - - add terminal-only defensive reattach in `GhosttySurfaceScrollView.ensureSurfaceAttached()` 61 - - add focused tests for terminal-vs-canvas wrapper ownership behavior 62 - 63 - Relevant changelog entries: 64 - 65 - - `2026.4.2`: occlusion restored whenever a surface is reattached 66 - - `2026.4.5`: surface state refreshed immediately on Canvas exit 67 - - current unreleased `main`: occlusion recovery also resumes from `updateSurfaceSize()` 68 - 69 - ## Current Working Theory 70 - 71 - The highest-probability root cause is no longer "occlusion state got stale while the surface stayed attached". 72 - 73 - New evidence from a reduced two-tab repro points to a more concrete failure mode: 74 - 75 - - the selected surface (`desired=Optional(true)`, focused, first responder) is briefly attached during Canvas exit 76 - - it reaches the normal terminal layout size 77 - - it is later detached (`attached=false window=false`) 78 - - no subsequent log shows that surface reattached to the final terminal host 79 - 80 - This suggests the blank terminal is caused by host ownership loss: 81 - 82 - - SwiftUI/AppKit reparenting during Canvas teardown temporarily moves the `GhosttySurfaceView` 83 - - a later teardown or host rebuild removes the surface from the active view tree 84 - - the normal terminal host does not currently guarantee that its `documentView` still owns the surface after updates 85 - - once detached, occlusion recovery is irrelevant because there is no live host left to present the surface 86 - 87 - The newly observed Canvas-entry failure sharpens the theory further: 88 - 89 - - the canvas host can successfully take ownership of the selected surface 90 - - the previous terminal host may still run a defensive `ensureSurfaceAttached()` while it is already leaving the window hierarchy 91 - - because that reattach path only checked "not attached to my document view", it could steal the surface back from the live canvas host 92 - - once the stale terminal host deinitializes, AppKit removes that stolen surface again, leaving Canvas blank with no active host 93 - 94 - What now looks less likely: 95 - 96 - - wrong worktree selection 97 - - wrong selected tab restoration 98 - - pure reducer ordering bug in `toggleCanvas` 99 - - occlusion cache invalidation as the sole root cause 100 - 101 - ## Logs Added For Ongoing Investigation 102 - 103 - Two log markers should be collected together: 104 - 105 - - `[CanvasExit]` 106 - - `[TerminalWake]` 107 - 108 - ### `[CanvasExit]` 109 - 110 - Existing and previous diagnostics already cover: 111 - 112 - - `WorktreeTerminalTabsView.onAppear` 113 - - `WorktreeTerminalTabsView.onDisappear` 114 - - surface attachment changes 115 - - deferred occlusion 116 - - reapply occlusion 117 - - selected worktree transition when leaving Canvas 118 - - surface detach intent (`viewWillMove(toSuperview:/toWindow:)`) with call stack 119 - - host wrapper lifecycle (`hostMake`, `hostInit`, `hostUpdate`, `hostDeinit`, `hostReattach`) 120 - - host reattach completion snapshot (`hostReattachComplete`) 121 - - per-surface host metadata (`hostKind`, wrapper id) 122 - 123 - ### `[TerminalWake]` 124 - 125 - Added on 2026-04-11 to correlate future failures with sleep/wake and long-lived surface state: 126 - 127 - - `GhosttyRuntime` 128 - - `workspaceWillSleep` 129 - - `workspaceDidWake` 130 - - `screensDidSleep` 131 - - `screensDidWake` 132 - - runtime surface count 133 - - `GhosttySurfaceView` 134 - - per-surface state snapshot on workspace sleep/wake 135 - - per-surface state snapshot on `viewDidMoveToWindow` 136 - - per-surface state snapshot on `viewDidMoveToSuperview` 137 - - detach-time safety-net request back to the last known terminal host 138 - - `WindowFocusObserverView` 139 - - window activity changes (`key`, `visible`, `force`, `windowNumber`) 140 - 141 - Per-surface wake logs include: 142 - 143 - - `surface` 144 - - `hasSurface` 145 - - `attached` 146 - - `window` 147 - - `desired` 148 - - `focused` 149 - - `firstResponder` 150 - - `bounds` 151 - - `backing` 152 - - `windowVisible` 153 - - `windowKey` 154 - 155 - ## How To Collect Logs 156 - 157 - Use `make log-stream`, then reproduce the issue and save the section covering: 158 - 159 - - the last successful Canvas exit before the bug starts 160 - - the first failed Canvas exit after the bug starts 161 - - any sleep/wake events before that failure 162 - 163 - If filtering manually, focus on lines containing: 164 - 165 - - `[CanvasExit]` 166 - - `[TerminalWake]` 1 + # Canvas Exit Terminal Blank Closure 167 2 168 - If using `log stream` directly, a useful predicate is: 3 + Last updated: 2026-04-29 4 + Status: Closed 169 5 170 - ```bash 171 - log stream --style compact \ 172 - --predicate 'subsystem == "com.onevcat.prowl" && (eventMessage CONTAINS[c] "[CanvasExit]" || eventMessage CONTAINS[c] "[TerminalWake]")' 173 - ``` 6 + ## Outcome 174 7 175 - ## What To Compare Next Time 8 + The Canvas exit / entry blank terminal issue has not reappeared after the host 9 + ownership fix and occlusion reapply safeguards landed. Treat this investigation 10 + as closed unless a new report includes a fresh reproduction pattern. 176 11 177 - When the bug reproduces again, compare a healthy exit and a broken exit for: 12 + ## Root Cause 178 13 179 - - whether `workspaceDidWake` or `screensDidWake` happened shortly before failures started 180 - - whether the affected surface logs `detachIntent` before going blank, and which stack removes it 181 - - whether a terminal host logs `hostUpdate` but never `hostReattach` for the affected surface 182 - - whether the terminal host does log `hostReattach`, but the surface still remains blank afterward 183 - - whether `WindowFocusObserverView` still reports the window as visible/key when the blank terminal is shown 184 - 185 - ## Open Questions 186 - 187 - - Is sleep/wake the true trigger, or just the most common way to enter the stale state? 188 - - Which host teardown path actually performs the final detach: Canvas wrapper cleanup, terminal wrapper replacement, or another AppKit rebuild? 189 - - If terminal-side defensive reattach works, is it sufficient as the durable fix or just masking a lower-level host lifecycle race? 190 - - If reattach does not work, is the detached native view still valid, or do we need to recreate the underlying `ghostty_surface_t`? 191 - 192 - ## Next Step Candidates 193 - 194 - Do not do these preemptively unless new logs support them: 14 + The most likely failure mode was host ownership loss during SwiftUI/AppKit 15 + reparenting: 195 16 196 - - widen defensive reattach beyond the normal terminal host 197 - - recreate a surface when host reattach fails 198 - - add explicit post-wake repair for all active surfaces 199 - - force-resend occlusion and size after wake 200 - - force content-scale/display-id refresh after wake 17 + - Canvas and terminal wrappers both host the same `GhosttySurfaceView`. 18 + - A stale terminal wrapper could attempt to reattach a surface that was already 19 + owned by the live Canvas wrapper. 20 + - When that stale wrapper later deinitialized, AppKit removed the surface again, 21 + leaving the active host blank even though reducer selection and tab state were 22 + still correct. 201 23 202 - ## Notes 24 + ## Fixes Kept 203 25 204 - This document should be updated every time: 26 + - Terminal hosts only defensively reattach orphaned surfaces; they do not steal a 27 + surface from another live host. 28 + - Occlusion state invalidates on attachment changes so the latest desired value 29 + is resent after reattachment. 30 + - Un-occluding is deferred until a surface has both a superview and a window; 31 + occluding remains immediate so detached surfaces do not keep rendering. 32 + - Canvas-managed terminal states avoid normal window-activity sync while Canvas 33 + owns visibility. 205 34 206 - - a new hypothesis is formed 207 - - a new instrumentation point is added 208 - - a repro pattern changes 209 - - a candidate fix is attempted 210 - - a failed fix is ruled out 35 + ## Remaining Logs 211 36 212 - ## 2026-04-15 Snapshot 37 + Most investigation logs were removed. The retained low-frequency logs are: 213 38 214 - Latest reduced repro: 39 + - `[CanvasExit] enteringCanvas` 40 + - `[CanvasExit] setSelectedWorktreeID` 41 + - `[CanvasExit] deferOcclusion` 42 + - `[CanvasExit] hostReattach` 43 + - `[CanvasExit] hostReattachComplete` 44 + - `[TerminalWake]` runtime sleep/wake summaries 215 45 216 - - two tabs only 217 - - selected tab surface detached after briefly reaching terminal-sized bounds 218 - - no reattach log observed afterward 219 - - reverse repro also confirmed: entering Canvas can blank the selected card immediately 220 - - in the failing entry log, `hostReattach wrapper=<terminal>` fires after `host=canvas` is already attached and visible 221 - - the stale terminal wrapper later deinitializes and the surface ends up detached (`attached=false window=false`) 46 + These are enough to identify a regression without keeping wrapper lifecycle, 47 + tab appear/disappear, attachment-change, or call-stack logging in normal builds. 222 48 223 - Current tactical response: 49 + ## Residual Risk 224 50 225 - - add detach stack logging to identify who removes the surface 226 - - add host wrapper diagnostics to correlate `surface ↔ wrapper ↔ canvas/terminal` 227 - - attempt a narrow fix: terminal host reattaches the surface if updates/layout find it missing 228 - - add a detach-time safety net so a just-detached surface asks its last terminal host to try reattachment on the next main-loop turn 229 - - refine that narrow fix so terminal reattach only runs after the surface has actually left the view tree; it must not steal a surface currently owned by Canvas 51 + The remaining risk is in AppKit view lifecycle ordering. If a future SwiftUI 52 + layout change introduces another host that can own `GhosttySurfaceView`, it must 53 + follow the same rule: only adopt orphaned surfaces and never move a surface away 54 + from another live host. 230 55 231 - Expected interpretation of the next repro: 56 + Relevant coverage: 232 57 233 - - if `hostReattach` appears and the terminal becomes visible again, the bug is likely host ownership loss during Canvas teardown 234 - - if `hostReattach` appears but the terminal stays blank, the issue may still involve stale native surface/render state after detach 235 - - if no terminal `hostReattach` appears, the active terminal host may not be rebuilding/updating as expected 58 + - `GhosttySurfaceViewTests.terminalHostDoesNotStealSurfaceFromCanvasHost` 59 + - `GhosttySurfaceViewTests.canvasHostDoesNotStealDetachedSurfaceBack` 60 + - `GhosttySurfaceViewTests.terminalHostReattachesSurfaceOnlyAfterItLeavesTheViewTree` 61 + - occlusion reattachment tests in `GhosttySurfaceViewTests`
+1 -23
supacode/Features/Terminal/Views/WorktreeTerminalTabsView.swift
··· 1 1 import AppKit 2 2 import SwiftUI 3 3 4 - private let terminalTabsLogger = SupaLogger("TerminalTabs") 5 - 6 4 struct WorktreeTerminalTabsView: View { 7 5 let worktree: Worktree 8 6 let manager: WorktreeTerminalManager ··· 84 82 state.focusSelectedTab() 85 83 } 86 84 let activity = resolvedWindowActivity 87 - terminalTabsLogger.info( 88 - "[CanvasExit] onAppear worktree=\(worktree.id) " 89 - + "selectedTab=\(state.tabManager.selectedTabId?.rawValue.uuidString ?? "nil") " 90 - + "autoFocus=\(shouldAutoFocusTerminal) " 91 - + "windowKey=\(activity.isKeyWindow) windowVisible=\(activity.isVisible)" 92 - ) 93 85 state.syncFocus(windowIsKey: activity.isKeyWindow, windowIsVisible: activity.isVisible) 94 86 } 95 - .onDisappear { 96 - let activity = resolvedWindowActivity 97 - terminalTabsLogger.info( 98 - "[CanvasExit] onDisappear worktree=\(worktree.id) " 99 - + "selectedTab=\(state.tabManager.selectedTabId?.rawValue.uuidString ?? "nil") " 100 - + "windowKey=\(activity.isKeyWindow) windowVisible=\(activity.isVisible)" 101 - ) 102 - } 103 - .onChange(of: state.tabManager.selectedTabId) { _, newValue in 87 + .onChange(of: state.tabManager.selectedTabId) { _, _ in 104 88 if shouldAutoFocusTerminal { 105 89 state.focusSelectedTab() 106 90 } 107 91 let activity = resolvedWindowActivity 108 - terminalTabsLogger.info( 109 - "[CanvasExit] selectedTabChanged worktree=\(worktree.id) " 110 - + "selectedTab=\(newValue?.rawValue.uuidString ?? "nil") " 111 - + "autoFocus=\(shouldAutoFocusTerminal) " 112 - + "windowKey=\(activity.isKeyWindow) windowVisible=\(activity.isVisible)" 113 - ) 114 92 state.syncFocus(windowIsKey: activity.isKeyWindow, windowIsVisible: activity.isVisible) 115 93 } 116 94 }
+2 -123
supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift
··· 6 6 import SwiftUI 7 7 8 8 private let surfaceLogger = SupaLogger("Surface") 9 - private let surfaceHostLogger = SupaLogger("SurfaceHost") 10 9 11 10 final class GhosttySurfaceView: NSView, Identifiable { 12 11 struct OcclusionState { ··· 129 128 private var lastSurfaceFocus: Bool? 130 129 private var eventMonitor: Any? 131 130 private var notificationObservers: [NSObjectProtocol] = [] 132 - private var workspaceObservers: [NSObjectProtocol] = [] 133 131 private var prevPressureStage: Int = 0 134 132 private var isBackgroundOpaqueOverride = false 135 133 private lazy var cachedScreenContents = CachedValue<String>(duration: .milliseconds(500)) { ··· 283 281 } 284 282 } 285 283 registerForDraggedTypes(Array(Self.dropTypes)) 286 - registerWorkspaceObservers() 287 284 288 285 eventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.keyUp, .leftMouseDown]) { 289 286 [weak self] event in ··· 300 297 NSEvent.removeMonitor(eventMonitor) 301 298 } 302 299 clearNotificationObservers() 303 - clearWorkspaceObservers() 304 300 let id = ObjectIdentifier(self) 305 301 MainActor.assumeIsolated { 306 302 SecureInput.shared.removeScoped(id) ··· 401 397 }) 402 398 } 403 399 404 - private func registerWorkspaceObservers() { 405 - let center = NSWorkspace.shared.notificationCenter 406 - workspaceObservers.append( 407 - center.addObserver( 408 - forName: NSWorkspace.willSleepNotification, 409 - object: nil, 410 - queue: .main 411 - ) { [weak self] _ in 412 - Task { @MainActor [weak self] in 413 - self?.logLifecycleState("workspaceWillSleep") 414 - } 415 - }) 416 - workspaceObservers.append( 417 - center.addObserver( 418 - forName: NSWorkspace.didWakeNotification, 419 - object: nil, 420 - queue: .main 421 - ) { [weak self] _ in 422 - Task { @MainActor [weak self] in 423 - self?.logLifecycleState("workspaceDidWake") 424 - } 425 - }) 426 - workspaceObservers.append( 427 - center.addObserver( 428 - forName: NSWorkspace.screensDidSleepNotification, 429 - object: nil, 430 - queue: .main 431 - ) { [weak self] _ in 432 - Task { @MainActor [weak self] in 433 - self?.logLifecycleState("screensDidSleep") 434 - } 435 - }) 436 - workspaceObservers.append( 437 - center.addObserver( 438 - forName: NSWorkspace.screensDidWakeNotification, 439 - object: nil, 440 - queue: .main 441 - ) { [weak self] _ in 442 - Task { @MainActor [weak self] in 443 - self?.logLifecycleState("screensDidWake") 444 - } 445 - }) 446 - } 447 - 448 400 private func windowDidChangeScreen() { 449 401 guard let surface, let screen = window?.screen else { return } 450 402 let displayID = ··· 463 415 notificationObservers.removeAll() 464 416 } 465 417 466 - private func clearWorkspaceObservers() { 467 - let center = NSWorkspace.shared.notificationCenter 468 - for observer in workspaceObservers { 469 - center.removeObserver(observer) 470 - } 471 - workspaceObservers.removeAll() 472 - } 473 - 474 418 override func viewDidMoveToWindow() { 475 419 super.viewDidMoveToWindow() 476 420 if window == nil { ··· 497 441 updateContentScale() 498 442 updateSurfaceSize() 499 443 applyWindowBackgroundAppearance() 500 - logLifecycleState("viewDidMoveToWindow") 501 444 handleAttachmentChange() 502 445 } 503 446 504 447 override func viewDidMoveToSuperview() { 505 448 super.viewDidMoveToSuperview() 506 - logLifecycleState("viewDidMoveToSuperview") 507 449 handleAttachmentChange() 508 450 } 509 451 510 - override func viewWillMove(toSuperview newSuperview: NSView?) { 511 - if newSuperview == nil { 512 - logDetachIntent(event: "viewWillMoveToSuperview") 513 - } 514 - super.viewWillMove(toSuperview: newSuperview) 515 - } 516 - 517 - override func viewWillMove(toWindow newWindow: NSWindow?) { 518 - if newWindow == nil { 519 - logDetachIntent(event: "viewWillMoveToWindow") 520 - } 521 - super.viewWillMove(toWindow: newWindow) 522 - } 523 - 524 452 override func viewDidChangeBackingProperties() { 525 453 super.viewDidChangeBackingProperties() 526 454 if let window { ··· 1185 1113 // Re-parenting can temporarily detach the Metal layer from the visible 1186 1114 // tree and pause Ghostty's renderer. Invalidate the applied cache so the 1187 1115 // currently desired occlusion value is sent again after reattachment. 1188 - surfaceLogger.info( 1189 - "[CanvasExit] attachmentChange surface=\(debugID) " 1190 - + "desired=\(String(describing: occlusionState.desired)) " 1191 - + "attached=\(hasAttachedSuperview) window=\(hasAttachedWindow) " 1192 - + "host=\(scrollWrapper?.hostKind.rawValue ?? "none") " 1193 - + "wrapper=\(scrollWrapper?.debugIdentifier ?? "none")" 1194 - ) 1195 1116 _ = occlusionState.invalidateForAttachmentChange() 1196 1117 if superview == nil { 1197 1118 DispatchQueue.main.async { [weak self] in ··· 1217 1138 reapplyOcclusionIfNeeded() 1218 1139 } 1219 1140 1220 - private func logLifecycleState(_ event: String) { 1221 - let windowVisible = window?.occlusionState.contains(.visible) ?? false 1222 - let windowKey = window?.isKeyWindow ?? false 1223 - let firstResponderMatches = window?.firstResponder === self 1224 - surfaceLogger.info( 1225 - "[TerminalWake] event=\(event) surface=\(debugID) hasSurface=\(surface != nil) " 1226 - + "attached=\(hasAttachedSuperview) window=\(hasAttachedWindow) " 1227 - + "desired=\(String(describing: occlusionState.desired)) " 1228 - + "focused=\(focused) firstResponder=\(firstResponderMatches) " 1229 - + "bounds=\(Int(bounds.width))x\(Int(bounds.height)) " 1230 - + "backing=\(Int(lastBackingSize.width))x\(Int(lastBackingSize.height)) " 1231 - + "windowVisible=\(windowVisible) windowKey=\(windowKey) " 1232 - + "host=\(scrollWrapper?.hostKind.rawValue ?? "none") " 1233 - + "wrapper=\(scrollWrapper?.debugIdentifier ?? "none")" 1234 - ) 1235 - } 1236 - 1237 - private func logDetachIntent(event: String) { 1238 - let stack = Thread.callStackSymbols.prefix(12).joined(separator: " | ") 1239 - surfaceLogger.info( 1240 - "[CanvasExit] detachIntent event=\(event) surface=\(debugID) " 1241 - + "host=\(scrollWrapper?.hostKind.rawValue ?? "none") " 1242 - + "wrapper=\(scrollWrapper?.debugIdentifier ?? "none") " 1243 - + "superview=\(String(describing: superview)) window=\(window != nil) " 1244 - + "stack=\(stack)" 1245 - ) 1246 - } 1247 - 1248 1141 private func reapplyOcclusionIfNeeded() { 1249 1142 guard isReadyToApplyOcclusion, let desired = occlusionState.desired else { return } 1250 - surfaceLogger.info( 1251 - "[CanvasExit] reapplyOcclusion surface=\(debugID) desired=\(desired) " 1252 - + "attached=\(hasAttachedSuperview) window=\(hasAttachedWindow)" 1253 - ) 1254 1143 setOcclusion(desired) 1255 1144 } 1256 1145 ··· 2459 2348 super.init(frame: .zero) 2460 2349 addSubview(scrollView) 2461 2350 surfaceView.scrollWrapper = self 2462 - surfaceHostLogger.info( 2463 - "[CanvasExit] hostInit wrapper=\(debugID) host=\(hostKind.rawValue) " 2464 - + "surface=\(surfaceView.debugIdentifierForLogging) " 2465 - + "attached=\(isSurfaceAttachedToDocumentView)" 2466 - ) 2467 2351 refreshAppearance() 2468 2352 2469 2353 scrollView.contentView.postsBoundsChangedNotifications = true ··· 2540 2424 } 2541 2425 2542 2426 isolated deinit { 2543 - surfaceHostLogger.info( 2544 - "[CanvasExit] hostDeinit wrapper=\(debugID) host=\(hostKind.rawValue) " 2545 - + "surface=\(surfaceView.debugIdentifierForLogging) " 2546 - + "attached=\(isSurfaceAttachedToDocumentView)" 2547 - ) 2548 2427 observers.forEach { NotificationCenter.default.removeObserver($0) } 2549 2428 } 2550 2429 ··· 2582 2461 guard !isSurfaceAttachedToDocumentView else { return } 2583 2462 // Only adopt an orphaned surface; never steal it from a live host such as Canvas. 2584 2463 guard surfaceView.superview == nil else { return } 2585 - surfaceHostLogger.info( 2464 + surfaceLogger.info( 2586 2465 "[CanvasExit] hostReattach wrapper=\(debugID) host=\(hostKind.rawValue) " 2587 2466 + "surface=\(surfaceView.debugIdentifierForLogging) " 2588 2467 + "currentSuperview=\(String(describing: surfaceView.superview)) " ··· 2590 2469 ) 2591 2470 documentView.addSubview(surfaceView) 2592 2471 surfaceView.scrollWrapper = self 2593 - surfaceHostLogger.info( 2472 + surfaceLogger.info( 2594 2473 "[CanvasExit] hostReattachComplete wrapper=\(debugID) host=\(hostKind.rawValue) " 2595 2474 + "surface=\(surfaceView.debugIdentifierForLogging) " 2596 2475 + "superview=\(surfaceView.superview != nil) "
-11
supacode/Infrastructure/Ghostty/GhosttyTerminalView.swift
··· 20 20 return terminalHostLogger.interval("Ghostty.makeNSView") { 21 21 let view = GhosttySurfaceScrollView(surfaceView: surfaceView, hostKind: hostKind) 22 22 view.pinnedSize = pinnedSize 23 - terminalHostLogger.info( 24 - "[CanvasExit] hostMake wrapper=\(view.debugIdentifier) host=\(hostKind.rawValue) " 25 - + "surface=\(surfaceView.debugIdentifierForLogging) " 26 - + "pinned=\(pinnedSize != nil)" 27 - ) 28 23 return view 29 24 } 30 25 } ··· 32 27 func updateNSView(_ view: GhosttySurfaceScrollView, context: Context) { 33 28 terminalHostLogger.interval("Ghostty.updateNSView") { 34 29 view.pinnedSize = pinnedSize 35 - terminalHostLogger.info( 36 - "[CanvasExit] hostUpdate wrapper=\(view.debugIdentifier) host=\(hostKind.rawValue) " 37 - + "surface=\(surfaceView.debugIdentifierForLogging) " 38 - + "pinned=\(pinnedSize != nil) " 39 - + "attached=\(view.isSurfaceAttachedToDocumentView)" 40 - ) 41 30 view.ensureSurfaceAttached() 42 31 } 43 32 }