···11# Canvas Exit Terminal Blank Tracking
2233-Last updated: 2026-04-11
44-Status: Open, intermittent, likely long-lived native state issue
33+Last updated: 2026-04-15
44+Status: Open, intermittent, narrowed from occlusion-only suspicion to host reattachment failure
5566## Symptom
77···3232- `supacode/Features/Terminal/Views/WorktreeTerminalTabsView.swift`
3333- `supacode/Features/Terminal/Views/WindowFocusObserverView.swift`
3434- `supacode/Infrastructure/Ghostty/GhosttySurfaceView.swift`
3535+- `supacode/Infrastructure/Ghostty/GhosttyTerminalView.swift`
3536- `supacode/Infrastructure/Ghostty/GhosttyRuntime.swift`
3637- `supacode/Features/Canvas/Views/CanvasView.swift`
3738···4748- `d4e59155` Add canvas exit terminal diagnostics and occlusion refresh
4849- `e2a29b2c` test: cover Ghostty attachment occlusion behavior
4950- `32f51451` Fix canvas exit terminal occlusion recovery
5151+- current branch `fix/canvas-exit-surface-reattach`
5252+ - add detach-intent stack logging before `GhosttySurfaceView` loses its superview/window
5353+ - add wrapper host diagnostics (`hostKind`, wrapper id, surface id)
5454+ - add terminal-only defensive reattach in `GhosttySurfaceScrollView.ensureSurfaceAttached()`
5555+ - add focused tests for terminal-vs-canvas wrapper ownership behavior
50565157Relevant changelog entries:
5258···56625763## Current Working Theory
58645959-The highest-probability root cause is a stale native terminal surface state after reparenting, likely amplified by long app lifetime and sleep/wake transitions.
6565+The highest-probability root cause is no longer "occlusion state got stale while the surface stayed attached".
60666161-Current best hypothesis:
6767+New evidence from a reduced two-tab repro points to a more concrete failure mode:
62686363-- Exiting Canvas causes `GhosttySurfaceView` to be reattached into the normal terminal hierarchy.
6464-- Reparenting invalidates the occlusion-applied cache.
6565-- In some sessions, especially after sleep/wake, the expected "surface is ready again, now reapply visible occlusion" chain does not complete reliably.
6666-- SwiftUI has already switched back to the normal terminal view, but Ghostty's renderer remains effectively paused or not fully resumed for that surface.
6767-- Switching tabs forces another round of visibility/focus activity, which recovers the surface.
6969+- the selected surface (`desired=Optional(true)`, focused, first responder) is briefly attached during Canvas exit
7070+- it reaches the normal terminal layout size
7171+- it is later detached (`attached=false window=false`)
7272+- no subsequent log shows that surface reattached to the final terminal host
7373+7474+This suggests the blank terminal is caused by host ownership loss:
68756969-What is less likely at this point:
7676+- SwiftUI/AppKit reparenting during Canvas teardown temporarily moves the `GhosttySurfaceView`
7777+- a later teardown or host rebuild removes the surface from the active view tree
7878+- the normal terminal host does not currently guarantee that its `documentView` still owns the surface after updates
7979+- once detached, occlusion recovery is irrelevant because there is no live host left to present the surface
70807171-- Wrong worktree selection
7272-- Wrong selected tab restoration
7373-- Basic reducer ordering bug in `toggleCanvas`
8181+What now looks less likely:
74827575-Those paths have been observed as correct in diagnostic logs while the surface still remained blank.
8383+- wrong worktree selection
8484+- wrong selected tab restoration
8585+- pure reducer ordering bug in `toggleCanvas`
8686+- occlusion cache invalidation as the sole root cause
76877788## Logs Added For Ongoing Investigation
7889···8697Existing and previous diagnostics already cover:
87988899- `WorktreeTerminalTabsView.onAppear`
100100+- `WorktreeTerminalTabsView.onDisappear`
89101- surface attachment changes
90102- deferred occlusion
91103- reapply occlusion
92104- selected worktree transition when leaving Canvas
105105+- surface detach intent (`viewWillMove(toSuperview:/toWindow:)`) with call stack
106106+- host wrapper lifecycle (`hostMake`, `hostInit`, `hostUpdate`, `hostDeinit`, `hostReattach`)
107107+- host reattach completion snapshot (`hostReattachComplete`)
108108+- per-surface host metadata (`hostKind`, wrapper id)
9310994110### `[TerminalWake]`
95111···105121 - per-surface state snapshot on workspace sleep/wake
106122 - per-surface state snapshot on `viewDidMoveToWindow`
107123 - per-surface state snapshot on `viewDidMoveToSuperview`
124124+ - detach-time safety-net request back to the last known terminal host
108125- `WindowFocusObserverView`
109126 - window activity changes (`key`, `visible`, `force`, `windowNumber`)
110127···147164When the bug reproduces again, compare a healthy exit and a broken exit for:
148165149166- whether `workspaceDidWake` or `screensDidWake` happened shortly before failures started
150150-- whether the affected surface reports `desired=Optional(true)` but never logs `reapplyOcclusion`
167167+- whether the affected surface logs `detachIntent` before going blank, and which stack removes it
168168+- whether a terminal host logs `hostUpdate` but never `hostReattach` for the affected surface
169169+- whether the terminal host does log `hostReattach`, but the surface still remains blank afterward
151170- whether `WindowFocusObserverView` still reports the window as visible/key when the blank terminal is shown
152152-- whether the affected surface is attached to a superview and window but still does not recover
153153-- whether `bounds` and `backing` stop changing for the affected surface while the view is visibly present
154171155172## Open Questions
156173157174- Is sleep/wake the true trigger, or just the most common way to enter the stale state?
158158-- Is the bad state owned by `GhosttySurfaceView`, underlying `ghostty_surface_t`, or AppKit/Metal attachment?
159159-- Does the failure always affect the same surface instance for a worktree, or any surface after the session becomes "poisoned"?
160160-- Would an explicit post-wake surface refresh solve the actual root cause, or only mask a lower-level Ghostty/AppKit lifecycle issue?
175175+- Which host teardown path actually performs the final detach: Canvas wrapper cleanup, terminal wrapper replacement, or another AppKit rebuild?
176176+- If terminal-side defensive reattach works, is it sufficient as the durable fix or just masking a lower-level host lifecycle race?
177177+- If reattach does not work, is the detached native view still valid, or do we need to recreate the underlying `ghostty_surface_t`?
161178162179## Next Step Candidates
163180164181Do not do these preemptively unless new logs support them:
165182183183+- widen defensive reattach beyond the normal terminal host
184184+- recreate a surface when host reattach fails
166185- add explicit post-wake repair for all active surfaces
167186- force-resend occlusion and size after wake
168187- force content-scale/display-id refresh after wake
169169-- invalidate more cached state after wake, not only after attachment changes
170188171189## Notes
172190···177195- a repro pattern changes
178196- a candidate fix is attempted
179197- a failed fix is ruled out
198198+199199+## 2026-04-15 Snapshot
200200+201201+Latest reduced repro:
202202+203203+- two tabs only
204204+- selected tab surface detached after briefly reaching terminal-sized bounds
205205+- no reattach log observed afterward
206206+207207+Current tactical response:
208208+209209+- add detach stack logging to identify who removes the surface
210210+- add host wrapper diagnostics to correlate `surface ↔ wrapper ↔ canvas/terminal`
211211+- attempt a narrow fix: terminal host reattaches the surface if updates/layout find it missing
212212+- 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
213213+214214+Expected interpretation of the next repro:
215215+216216+- if `hostReattach` appears and the terminal becomes visible again, the bug is likely host ownership loss during Canvas teardown
217217+- if `hostReattach` appears but the terminal stays blank, the issue may still involve stale native surface/render state after detach
218218+- if no terminal `hostReattach` appears, the active terminal host may not be rebuilding/updating as expected