Skip to content

fix(focus): defer every terminal focus reassertion until surface geometry is usable - #14276

Merged
teamleaderleo merged 3 commits into
mainfrom
fix/focus-reassert-requires-geometry
Sep 24, 2026
Merged

teamleaderleo merged 3 commits into
mainfrom
fix/focus-reassert-requires-geometry

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Terminal focus could be reasserted onto a hidden or 0x0 surface, which is what the macOS 26 failures in WorkspaceTerminalFocusRecoverySwiftTests catch (see #13879 and the main full-suite run 36022521492).

Cause

GhosttySurfaceScrollView.prepareTerminalSurfaceFocusReassertion only checked geometry when a suppressed reapply was pending or the call was forced:

let requiresUsableGeometry = pendingSuppressedFirstResponderFocusReapply || force
guard requiresUsableGeometry else { return true }

A call-stack trace on macOS 26 (test-e2e run 36041791369) shows the path: makeKeyAndOrderFront runs _setUpFirstResponder → _selectFirstKeyView, which makes the terminal first responder and queues an automatic first-responder apply. When that apply runs from the main queue after the surface has gone hidden or 0x0, it goes applyFirstResponderIfNeeded → reassertTerminalSurfaceFocus → TerminalSurface.setFocus(true) without a geometry check, so Ghostty is focused while the hidden/tiny deferral is supposed to be pending.

Fix

Every reassertion now requires visible, usable portal and surface geometry. When it doesn't have it, it takes the existing deferral path (pendingSuppressedFirstResponderFocusReapply = true plus a scheduled retry), which reapplies focus once geometry is usable.

Results (test-e2e, the WorkspaceTerminalFocusRecoverySwiftTests suite)

runner before (main) after
macOS 15 (36043804416) 2 known failures all 7 tests in the suite pass
macOS 26 (36043800449, 36046498770) 3 to 4 failures hiddenTinyFirstResponderReappliesGhosttyFocusAfterGeometrySettles, forcedReparentFocusClearRetriesWhenSurfaceGeometryIsStillTiny, findTerminalRestorePreservesHiddenTinyFirstResponderDeferral pass; automaticApplyDoesNotBypassHiddenTinyFirstResponderDeferral passed in one run and failed in the other

The remaining automaticApply flake looks like a second race: during test setup, TabManager.selectedWorkspaceIdDidChange dispatches focusSelectedPanel → Workspace.activatePanel, which calls TerminalSurface.setFocus(true) with no geometry check. If it lands after the test shrinks the surface, Ghostty is focused early. It stays on app-host-known-failures.json (as do the other entries); this PR does not change that list.

Full app-host suite (unit-ci, run 36049292327, three attempts)

Shards 1, 2, 3, 5 and 7 passed. Every failure is unrelated to focus and also fails on main:

No focus, split, or TextBox suite failed. The unit-ci label is dropped so ci-status is not held on the VMClient suite.

🤖 Generated with Claude Code

teamleaderleo and others added 2 commits September 24, 2026 14:48
…etry is usable

prepareTerminalSurfaceFocusReassertion only checked geometry when a
suppressed reapply was pending or the call was forced. On macOS 26,
makeKeyAndOrderFront selects the terminal as the first key view and
queues an automatic first-responder apply; when that apply ran after the
surface had become hidden or 0x0, it reasserted Ghostty focus anyway.
Require usable, visible geometry for every reassertion; the existing
path defers and retries once geometry is usable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: manaflow-ai/cmux/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: f42549d6-6a3a-44f3-85d5-6ad9d241966d

📥 Commits

Reviewing files that changed from the base of the PR and between 2d821f1 and daefd4a.

📒 Files selected for processing (1)
  • Sources/GhosttyTerminalView.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Terminal focus reassertions now evaluate portal and surface geometry before proceeding. The early return for non-suppressed, non-forced reassertions was removed.

Changes

Terminal focus reassertion

Layer / File(s) Summary
Apply geometry checks to reassertions
Sources/GhosttyTerminalView.swift
The early return for non-suppressed, non-forced reassertions was removed. All reassertions now evaluate the geometry checks.

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Suggested reviewers: austinywang

Merge Risk: ⚪ Minimal · up to daefd

Focus reassertions now wait for usable geometry, and the supplied evidence shows deferred attempts can be retried. No actionable merge-blocking risk remains after normal checks.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error The production focus change materially expands a timing-based main-queue deferral. Before this diff, prepareTerminalSurfaceFocusReassertion returned immediately for non-forced reassertions without a… Avoid using the main-queue async retry as the geometry synchronization mechanism. Record the pending focus intent, then retry only from an explicit geometry or visibility transition signal, such as the existing layout/visibility reconciliat…
✅ Passed checks (24 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Cloud Persistent Session And Early Input ✅ Passed PASS. The PR changes only Sources/GhosttyTerminalView.swift. The patch adjusts geometry checks in prepareTerminalSurfaceFocusReassertion and does not modify Cloud terminal creation, cmux-tui clien…
Cmux Swift Actor Isolation ✅ Passed PASS: The diff only removes an early return and adds comments in prepareTerminalSurfaceFocusReassertion. It does not add or change models, protocols, Sendable reference types, actor annotations, o…
Cmux Browser Automation Off-Main ✅ Passed The authoritative PR diff changes only Sources/GhosttyTerminalView.swift. It removes an early return in prepareTerminalSurfaceFocusReassertion so terminal focus reassertion checks hidden or tiny g…
Cmux Expensive Synchronous Load ✅ Passed PASS: The PR changes only the geometry guard and comments in prepareTerminalSurfaceFocusReassertion. The diff adds no agent-history loader, file read, directory scan, JSON/JSONL parsing, syscall loo…
Cmux Cache Substitution Correctness ✅ Passed PASS. The PR changes only focus reassertion flow in Sources/GhosttyTerminalView.swift. It removes an early return and applies hidden/tiny geometry checks before TerminalSurface.setFocus(true), the…
Cmux No Hacky Sleeps ✅ Passed PASS. The review-scoped diff changes only Sources/GhosttyTerminalView.swift. It removes a geometry guard and does not add a fixed sleep, delayed dispatch, timer, polling loop, or wall-clock wait. Th…
Cmux Algorithmic Complexity ✅ Passed PASS. The PR changes only Sources/GhosttyTerminalView.swift and replaces an early return with scalar geometry and visibility checks in prepareTerminalSurfaceFocusReassertion (lines 12294–12325). I…
Cmux Swift Concurrency ✅ Passed The PR diff only removes the geometry early return and adds explanatory comments in prepareTerminalSurfaceFocusReassertion. It introduces no Task, background queue, Combine, completion-handler, or…
Cmux Swift @Concurrent ✅ Passed PASS. The diff changes only synchronous geometry gating in prepareTerminalSurfaceFocusReassertion. It adds no async, nonisolated, @concurrent, or actor-isolation declarations. The deferral use…
Cmux Swift Package Boundaries ✅ Passed PASS. The diff changes only the private prepareTerminalSurfaceFocusReassertion method in Sources/GhosttyTerminalView.swift. The method uses NSView geometry and visibility, AppKit responder behav…
Cmux Swiftpm Lockfiles ✅ Passed The pull request changes only Sources/GhosttyTerminalView.swift. It does not change a Package.swift, Package.resolved, .gitignore, workflow, or Xcode project package reference. Therefore, the …
Cmux Swift Logging ✅ Passed The diff changes only the geometry guard and adds explanatory comments in Sources/GhosttyTerminalView.swift. It adds no logging statements and does not materially change the existing #if DEBUG `cm…
Cmux User-Facing Error Privacy ✅ Passed The pull request changes focus geometry gating in Sources/GhosttyTerminalView.swift. It adds no user-facing error, alert, command output, API body, or recovery copy. The added prose is a developer-o…
Cmux Full Internationalization ✅ Passed PASS. The PR changes only focus-reassertion control flow in Sources/GhosttyTerminalView.swift and adds a developer-only explanatory comment. It introduces no user-facing Swift text, localization key…
Cmux Swiftui State Layout ✅ Passed PASS. The PR changes only Sources/GhosttyTerminalView.swift, and the diff removes a guard plus adds comments inside GhosttySurfaceScrollView: NSView. It introduces no SwiftUI state, `GeometryReade…
Cmux Architecture Rethink ✅ Passed PASS. The diff is a small local correctness fix in prepareTerminalSurfaceFocusReassertion. It removes a bypass so the existing geometry invariant applies to every reassertion. It adds no new flag, o…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The pull request changes only focus geometry logic in Sources/GhosttyTerminalView.swift. It does not add or materially change any NSWindow, NSPanel, window controller, SwiftUI Window, or `Wind…
Cmux Source Artifacts ✅ Passed The PR changes only Sources/GhosttyTerminalView.swift. The diff contains hand-written Swift logic and comments for focus geometry handling. It adds no local output, generated artifact, cache, tempor…
Cmux No Test Or Debug Seam In Production Source ✅ Passed The authoritative diff changes only Sources/GhosttyTerminalView.swift. It removes an early geometry guard and adds explanatory comments; it adds no #if DEBUG test-only extension/member, test/debug…
Title check ✅ Passed The title clearly and concisely describes the primary change: deferring terminal focus reassertion until surface geometry is usable.
Description check ✅ Passed The description explains the problem, cause, fix, affected code path, and test results. It is mostly complete, although it does not use the template's Testing heading, does not include a demo video or…
Full details: Cmux Swift Blocking Runtime

Explanation

The production focus change materially expands a timing-based main-queue deferral. Before this diff, prepareTerminalSurfaceFocusReassertion returned immediately for non-forced reassertions without a pending suppression. After the diff, every hidden or tiny reassertion sets pendingSuppressedFirstResponderFocusReapply and calls scheduleAutomaticFirstResponderApply, which uses DispatchQueue.main.async to retry. This is shipped focus-path synchronization, not test scaffolding or a user-visible animation delay. The documented exception for init-time or passive-callback AppKit safety deferrals does not cover this geometry-readiness retry.

Resolution

Avoid using the main-queue async retry as the geometry synchronization mechanism. Record the pending focus intent, then retry only from an explicit geometry or visibility transition signal, such as the existing layout/visibility reconciliation callbacks, and clear the pending state when that signal confirms usable portal and surface geometry. Do not add polling, delayed dispatch, sleeps, or another timing-based retry.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@teamleaderleo teamleaderleo added the unit-ci Run compile admission + app-host unit tests only, without full-ci's package/lag/Release lanes label Sep 24, 2026
@blacksmith-sh

This comment has been minimized.

@teamleaderleo teamleaderleo removed the unit-ci Run compile admission + app-host unit tests only, without full-ci's package/lag/Release lanes label Sep 24, 2026
@teamleaderleo
teamleaderleo enabled auto-merge (squash) September 24, 2026 21:48
@teamleaderleo
teamleaderleo merged commit 505adba into main Sep 24, 2026
86 checks passed
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.

1 participant