Skip to content

Fix terminal blank when last pane closes (#2665) - #2670

Closed
austinywang wants to merge 3 commits into
mainfrom
issue-2665-last-pane-blank
Closed

austinywang wants to merge 3 commits into
mainfrom
issue-2665-last-pane-blank

Conversation

@austinywang

@austinywang austinywang commented Apr 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • When closing terminal panes in a workspace down to the last remaining pane, the surviving terminal went blank and stopped accepting input
  • Root cause: the SwiftUI split-to-single-pane rebuild transiently detaches the terminal surface view, causing the first responder to be lost. The one-shot async focus reconcile fires before the view is reattached, and no further retry occurs
  • Fix: include the surviving panel in the layout follow-up as terminalFocusPanelId, so the existing retry loop (triggered by terminalSurfaceHostedViewDidMoveToWindow) keeps calling ensureFocus until the surface view successfully becomes first responder after reattachment

Test plan

  • Open a workspace with 2+ split panes
  • Close panes one by one until only 1 remains
  • Verify the surviving terminal is not blank and accepts keyboard input
  • Verify closing panes in a 3+ pane split (leaving 2+ remaining) still works correctly
  • Verify detaching a pane (drag to new workspace) still works correctly

Closes #2665

🤖 Generated with Claude Code


Note

Medium Risk
Adjusts workspace focus/layout follow-up logic around terminal first-responder handling and split-view rebuilds; regressions could leave focus stuck or cause extra layout retries in terminal panels.

Overview
Prevents the surviving terminal from going blank after collapsing split panes by starting an event-driven layout follow-up that keeps re-applying ensureFocus until the terminal’s preferred keyboard target is active.

This expands terminal focus follow-up beyond the .terminalFirstResponder trigger, adds terminalPanelKeyboardFocusNeedsFollowUp (checks window first responder vs. terminal preferred focus), and uses it to gate both follow-up scheduling and clearing. Pane close/collapse paths now prefer beginEventDrivenLayoutFollowUp(... includeGeometry: true) for the surviving terminal (skipping detach flows), falling back to the existing geometry reconcile otherwise.

Reviewed by Cursor Bugbot for commit f8e480a. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes blank terminals and lost input when collapsing to a single pane or creating a new workspace (Cmd+T). Keeps the surviving terminal—or its find field—in focus so input works (fixes #2665).

  • Bug Fixes
    • On pane collapse, start an event-driven layout follow-up with the surviving panel as terminalFocusPanelId so focus is reapplied after the split rebuild; applies to tab-close and pane-collapse; skipped on detach.
    • Trigger the same follow-up whenever the terminal isn’t at its preferred keyboard target (surface or find field), not only on .terminalFirstResponder; covers new workspace creation.
    • In the follow-up loop, stop only after the preferred keyboard target is focused to preserve terminal find focus when active.

Written for commit f8e480a. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved terminal focus recovery after pane closes or collapses so the remaining terminal reliably regains keyboard focus.
    • Reduced layout/geometry glitches after pane collapse by using a targeted, event-driven layout follow-up when possible, with a safe fallback to a full geometry reconcile when needed.

When closing panes down to the last remaining one, the surviving terminal
went blank because the SwiftUI split-to-single-pane rebuild transiently
detached the terminal surface, causing the first responder to be lost.
The one-shot async focus reconcile could fire before the view was
reattached, leaving no further retry.

Include the surviving panel in the layout follow-up as
terminalFocusPanelId so the existing retry loop keeps calling
ensureFocus until the surface view successfully becomes first responder
after reattachment.

Closes #2665

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 7, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 10, 2026 1:31am

@cubic-dev-ai

cubic-dev-ai Bot commented Apr 7, 2026

Copy link
Copy Markdown

This review could not be run because your cubic account has exceeded the monthly review limit. If you need help restoring access, please contact contact@cubic.dev.

@coderabbitai

coderabbitai Bot commented Apr 7, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Broadened Workspace terminal follow-up and pane-collapse logic: focus convergence is now checked via a new helper and, when a surviving focused panel is a terminal, Workspace schedules an event-driven layout follow-up targeting that terminal; otherwise it falls back to the existing geometry reconcile path. (50 words)

Changes

Cohort / File(s) Summary
Workspace / Bonsplit focus & follow-up
Sources/Workspace.swift
Refined terminal follow-up logic: added terminalPanelKeyboardFocusNeedsFollowUp helper and switched Workspace.focusPanel(_:trigger:) to use a TerminalPanel cast/path that schedules beginEventDrivenLayoutFollowUp when keyboard-focus convergence is needed. In BonsplitDelegate pane/tab-close handlers, replaced unconditional scheduleTerminalGeometryReconcile() with conditional beginEventDrivenLayoutFollowUp(reason: "workspace.paneCollapse", terminalFocusPanelId: ..., includeGeometry: true) when a surviving focused panel is a terminal; otherwise falls back to scheduleTerminalGeometryReconcile().

Sequence Diagram(s)

sequenceDiagram
    participant Split as BonsplitDelegate
    participant WS as Workspace
    participant Panel as TerminalPanel
    participant Win as Window / FirstResponder

    Split->>WS: pane/tab closed (didClosePane/didCloseTab)
    WS->>WS: determine surviving focusedPanelId
    alt surviving panel is TerminalPanel && not detaching
        WS->>Panel: beginEventDrivenLayoutFollowUp(reason: "workspace.paneCollapse", includeGeometry: true)
        Panel->>Win: query preferredKeyboardFocus
        Win-->>Panel: current firstResponder
        Panel->>WS: terminalPanelKeyboardFocusNeedsFollowUp? (compare)
        alt needs follow-up
            WS->>Panel: schedule focus re-apply / follow-up completion
        else
            WS->>WS: clear follow-up state
        end
    else
        WS->>WS: scheduleTerminalGeometryReconcile()
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hop where panes once lost their spark,
I poke the terminal, nudge out the dark,
A gentle follow-up, a tidy little fix,
Cursors return with nimble rabbit tricks,
Hooray — the prompt resumes its lark!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main fix: preventing terminal blank when closing panes to a single remaining one, directly addressing issue #2665.
Linked Issues check ✅ Passed The pull request directly addresses issue #2665 by fixing the terminal blank/unresponsive state when collapsing splits to a single pane, and maintains correct behavior for multi-pane and detach scenarios.
Out of Scope Changes check ✅ Passed All changes are scoped to terminal focus and layout follow-up logic directly related to fixing #2665; no unrelated modifications detected in the file changes.
Description check ✅ Passed The PR description is comprehensive and covers the required template sections: Summary (what/why), Test plan (how tested), and includes generated AI summaries and context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2665-last-pane-blank

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a terminal blank/input-loss bug (#2665) that occurred when closing split panes down to the last remaining pane. The root cause was the SwiftUI split-to-single-pane rebuild transiently detaching the terminal surface mid-focus-reconcile; the fix passes the surviving panel as terminalFocusPanelId to beginEventDrivenLayoutFollowUp in both didCloseTab and didClosePane, arming the existing retry loop so focus is re-applied once the view is reattached. The includeGeometry: true flag preserves the geometry reconcile that scheduleTerminalGeometryReconcile() previously provided.

Confidence Score: 5/5

Safe to merge; fix is surgical, well-commented, and handles all relevant edge cases

No P0 or P1 findings. The two change sites are symmetric and correct: pane-collapse detection in didCloseTab uses the explicit !bonsplitController.allPaneIds.contains(pane) guard while didClosePane implicitly implies a pane was removed. The includeGeometry flag preserves the prior geometry-reconcile behavior. No double-fire risk is introduced—the trailing scheduleFocusReconcile() calls were already present before this PR.

No files require special attention.

Important Files Changed

Filename Overview
Sources/Workspace.swift Correctly replaces the one-shot scheduleTerminalGeometryReconcile with a retry-loop beginEventDrivenLayoutFollowUp (includeGeometry:true) in both delegate callbacks; condition guards are appropriate and geometry coverage is preserved

Sequence Diagram

sequenceDiagram
    participant User
    participant BonsplitController
    participant Workspace
    participant SwiftUI
    participant TerminalSurface

    User->>BonsplitController: Close last tab in pane
    BonsplitController->>Workspace: didCloseTab(pane collapsed)
    Workspace->>Workspace: applyTabSelection(survivingPane)
    note over Workspace: isDetaching=false,<br/>pane no longer in allPaneIds
    Workspace->>Workspace: beginEventDrivenLayoutFollowUp(<br/>terminalFocusPanelId=survivingPanelId,<br/>includeGeometry=true)
    Workspace->>SwiftUI: layout observers installed
    SwiftUI-->>Workspace: split view rebuilds (detaches surface)
    TerminalSurface-->>Workspace: terminalSurfaceHostedViewDidMoveToWindow
    Workspace->>Workspace: scheduleLayoutFollowUpAttempt()
    Workspace->>TerminalSurface: ensureFocus() [retry loop]
    TerminalSurface-->>User: first responder restored ✓
Loading

Reviews (1): Last reviewed commit: "Fix terminal blank when last pane closes..." | Re-trigger Greptile

The focusPanel layout follow-up with terminalFocusPanelId was gated to
.terminalFirstResponder trigger only. When creating a new workspace via
Cmd+T, the trigger is .standard, so no focus retry loop was set up.
The new terminal surface goes through portal attach/detach cycles and
the one-shot ensureFocus fires before the view is ready.

Extend the follow-up to fire for any trigger when the terminal is not
yet first responder, so the retry loop handles portal churn on workspace
creation the same way it handles split-close churn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview — f8e480ab Deployed Apr 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

当删除到只剩一个工作区一个终端窗口时,终端窗口内内容会突然显示为全空。

3 participants