Fix terminal focus retry after tiny responder handoff

Merged
AU
5mo ago
+574-6

Summary

  • add regression coverage for the hidden/tiny first-responder focus handoff
  • schedule the existing automatic first-responder reconciliation when GhosttyNSView.becomeFirstResponder() suppresses focus because geometry is hidden or tiny
  • keep the retry on the existing coalesced focus-apply path so it reasserts Ghostty focus only after the pane is visible, active, key-window, and still the current focus target

Root cause

Code inspection confirmed a reachable AppKit-focused/Ghostty-unfocused divergence: becomeFirstResponder() sets the view-level desiredFocus before geometry gating, then the hidden/tiny branch logs and returns without calling ghostty_surface_set_focus or scheduling the normal focus reconciliation path. If AppKit makes the Ghostty view first responder during transient bonsplit/window portal layout churn, subsequent plain key input can route to the view while the runtime surface still has desiredFocusState == false.

The branch dates to February 2026 (5070b137a4), so this looks like a latent race exposed by later timing changes rather than code introduced directly by the June refactors. The June candidates mostly changed surrounding timing: #6147 moved portal installation through window-chrome overlay resolution, #6278 moved terminal surface lifecycle into the macOS package and added restore/input-demand paths, and #6227/#6267 did not show a direct focus-state change in this path.

Tests

  • Added deterministic DEBUG app-target coverage for first-responder while the inner Ghostty view is zero-sized, then geometry settling before the deferred apply drains.
  • Per issue instructions, I did not run local tests or local builds; CI is the validation path for this branch.

Closes #6342


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Retries terminal focus after AppKit hands first responder to a hidden or tiny terminal, deferring apply until the portal and inner surface are usable. Forced reparent focus uses the same geometry guard and queues a retry on layout/visibility; dock retries only run when the dock owns first responder (fixes #6342).

  • Bug Fixes

    • Defer suppressed first-responder focus; reapply only when the window is key, pane and surface are visible, portal >1x1, and (for suppressed handoffs) inner surface >1x1. Automatic apply and Find-restore keep this deferral.
    • Gate forced reparent reassert by geometry; if hidden/tiny, queue a retry from layout/visibility and clear pending state on success or foreign focus. Skip redundant scans when an apply is queued. In the right-sidebar dock, require current first-responder ownership and readiness so visibility alone never steals focus.
  • Tests

    • Reapply after geometry settles; automatic apply doesn’t bypass deferral; Find “Restore Terminal” preserves it.
    • Forced reparent reassert queues while tiny and recovers once usable; already-first-responder reparent clear waits for surface geometry. Right-sidebar dock: deferred reapply after geometry and no focus theft on visibility apply.

Summary by CodeRabbit

  • Bug Fixes

    • Improved terminal focus recovery when the focused surface is hidden or shrinks to a tiny size. Suppressed first-responder focus is now deferred and conditionally retried once the view is active/visible and the surface geometry is usable. Pending retries are reliably cleared after unrelated focus handoffs to prevent stale reapplication.
  • Tests

    • Added a DEBUG-only regression test that verifies deferred suppressed focus is re-applied after resizing the surface back to a usable size.