Skip to content

🐛 fix: close companion pane immediately when Lazygit/Yazi exits#82

Merged
vaayne merged 3 commits intomainfrom
fix/lazygit-panel-cleanup-on-exit
Apr 19, 2026
Merged

🐛 fix: close companion pane immediately when Lazygit/Yazi exits#82
vaayne merged 3 commits intomainfrom
fix/lazygit-panel-cleanup-on-exit

Conversation

@vaayne
Copy link
Copy Markdown
Owner

@vaayne vaayne commented Apr 19, 2026

Summary

Fixes #79. The right-side Git / Files pane now closes immediately when the embedded tool exits, instead of leaving a blank panel that could only be dismissed by restarting Mori.

  • Intercept GHOSTTY_ACTION_SHOW_CHILD_EXITED in Mori's action callback — returning true suppresses ghostty's fallback "Process exited. Press any key to close." message, and we post .ghosttySurfaceDidClose ourselves so the surface tears down with no keypress.
  • Add an onSurfaceExited hook on TerminalAreaViewController; CompanionToolPaneController wires it to a new onToolExited callback that AppDelegate uses to collapse the pane and return focus to the main terminal (matches the manual ⌘G toggle path).
  • Preserve the existing empty-state + auto-reconnect behavior for the main terminal — only the companion pane opts into auto-close.

Root cause

vendor/ghostty/src/apprt/embedded.zig:533 force-sets wait-after-command = true whenever a command is passed to ghostty_surface_new, so on child exit ghostty rendered the "press any key" message and blocked. The close_surface_cb only fired after the keypress — the pane had no signal to close on its own.

Test plan

  • swift build -c release --product Mori
  • mise run test — all packages pass
  • Manual: Tmux → Open Lazygit → press q → pane closes immediately, no keypress required, focus returns to main terminal

🤖 Generated with Claude Code

vaayne added 2 commits April 19, 2026 21:45
When Lazygit or Yazi ran in the companion pane and the user quit the
process (e.g. `q` in lazygit), the Ghostty surface-close notification
was handled by the pane's TerminalAreaViewController as a dead tmux
session, so it rendered the empty state ("No active session") inside
the pane. The pane stayed docked with the old title, un-closable
without relaunching Mori.

Add an onSurfaceExited hook on TerminalAreaViewController that, when
set, replaces the default empty-state/auto-reconnect path. The
CompanionToolPaneController wires it to a new onToolExited callback
that AppDelegate uses to close the pane and return focus to the main
terminal — matching the manual ⌘G toggle behavior.

Fixes #79.

Assisted-by: claude-code:claude-opus-4-7
Ghostty's embedded API at vendor/ghostty/src/apprt/embedded.zig force-
sets `wait-after-command = true` whenever a command is passed, so on
child exit it would render "Process exited. Press any key to close."
inside the companion pane and wait for user input before firing the
surface-close callback. That's the press-any-key delay users were
hitting after `q` in lazygit.

Handle `GHOSTTY_ACTION_SHOW_CHILD_EXITED` in Mori's action callback:
look up the surface's userdata, post a `.ghosttySurfaceDidClose`
notification, and return true — which suppresses ghostty's fallback
terminal message. The existing close handler then tears the surface
down immediately, so the pane collapses with no keypress.

Refine the changelog entry to reflect the new behavior.

Assisted-by: claude-code:claude-opus-4-7
@vaayne vaayne force-pushed the fix/lazygit-panel-cleanup-on-exit branch from fe256ff to 6ded54f Compare April 19, 2026 13:46
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe256ff0ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

)
}
}
return true
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle child-exit only for active terminal surface

Returning true here suppresses Ghostty’s built-in child-exit UI for every surface, including cached/background surfaces that are not currently attached. TerminalAreaViewController.handleSurfaceClosed only acts when the notification matches currentSurface (see the currentSurface/userdata guards at Sources/Mori/App/TerminalAreaViewController.swift:525-531), so exits from non-current surfaces are dropped and their dead views remain cached. When users later switch back to that worktree/tool, they can land on a stale blank surface with no reconnect/close flow.

Useful? React with 👍 / 👎.

The previous fix intercepted GHOSTTY_ACTION_SHOW_CHILD_EXITED for every
surface but only cleaned up when the exit matched the current surface.
Surfaces cached but not currently attached (e.g. a background companion
pane) stayed in the cache pointing at a dead process, so the next attach
would bind to a surface that ghostty had already torn down.

Split the notification handler: when userdata matches currentSurface,
run the existing teardown path; otherwise evict the matching entry from
the surface cache via TerminalSurfaceCache.removeByUserdata.

The notification fans out to every TerminalAreaViewController; each walks
its own cache and exactly one owns the exited surface. Comment the
handler so a future reader does not try to "optimize" by routing it.

Assisted-by: claude-code:claude-opus-4-7
@vaayne
Copy link
Copy Markdown
Owner Author

vaayne commented Apr 19, 2026

Addressed in bc487f9.

Split handleSurfaceClosed so the cache-eviction path runs for every notification, not just when the exited userdata matches currentSurface. Cached-but-detached surfaces (e.g. a companion pane the user switched away from before its process exited) now get evicted via the new TerminalSurfaceCache.removeByUserdata instead of lingering as dead entries.

Fan-out to every TerminalAreaViewController is intentional — each walks its own cache and exactly one owns the exited surface. Comment added in the handler to head off a future "optimization" that routes the notification.

Diff: bc487f9

@vaayne vaayne merged commit 3c73248 into main Apr 19, 2026
5 checks passed
@vaayne vaayne deleted the fix/lazygit-panel-cleanup-on-exit branch April 19, 2026 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lazygit panel 退出后无法关闭,残留空白 Git 面板

1 participant