Skip to content

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

Open
austinywang wants to merge 2 commits intomainfrom
issue-2665-last-pane-blank
Open

Fix terminal blank when last pane closes (#2665)#2670
austinywang wants to merge 2 commits intomainfrom
issue-2665-last-pane-blank

Conversation

@austinywang
Copy link
Copy Markdown
Contributor

@austinywang austinywang commented Apr 7, 2026

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
Moderate risk because it changes focus/first-responder and layout-follow-up timing around SwiftUI split-view rebuilds, which could impact keyboard focus behavior across panel types.

Overview
Prevents the surviving terminal from going blank/losing input when split panes collapse to a single pane by re-running the existing event-driven layout follow-up until the terminal surface is reattached and becomes first responder.

This also broadens when terminal focus follow-ups are started during focusPanel: they now run whenever the terminal isn’t first responder (or for .terminalFirstResponder triggers), and pane-close paths now prefer beginEventDrivenLayoutFollowUp(..., includeGeometry: true) for surviving terminals instead of a one-shot scheduleTerminalGeometryReconcile.

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


Summary by cubic

Fixes blank terminals when collapsing to a single pane and when creating a new workspace (Cmd+T), ensuring the terminal keeps focus and accepts input (fixes #2665).

  • Bug Fixes
    • On pane collapse, start an event-driven layout follow-up with the surviving panel as terminalFocusPanelId so the retry loop re-applies focus after the split view rebuild; applies to both tab-close and pane-collapse; skipped on detach.
    • When focusing a terminal, start the same follow-up if the terminal isn’t first responder (not just on .terminalFirstResponder), covering new workspace creation where the surface isn’t attached yet.

Written for commit 9c7cb55. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved terminal focus handling when closing or collapsing panes so the remaining terminal reliably regains focus.
    • Reduced layout/geometry glitches after pane collapse by using a targeted 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
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Apr 7, 2026 9:00am

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Apr 7, 2026

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
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

Adjusted Workspace/Bonsplit pane-close handling to avoid unconditional terminal geometry reconciliation. Now, when a pane collapse closes a pane and a surviving focused panel maps to a terminal, an event-driven layout follow-up targeting that terminal is scheduled; otherwise the code falls back to the existing geometry reconcile path.

Changes

Cohort / File(s) Summary
Workspace Bonsplit / Terminal follow-up
Sources/Workspace.swift
Replaced unconditional scheduleTerminalGeometryReconcile() in splitTabBar(_:didCloseTab:fromPane:) and splitTabBar(_:didClosePane:) with conditional logic: when not detaching and a surviving focusedPanelId resolves to a TerminalPanel, call beginEventDrivenLayoutFollowUp(reason: "workspace.paneCollapse", terminalFocusPanelId: ..., includeGeometry: true); otherwise call scheduleTerminalGeometryReconcile(). Also broadened terminal-first-responder follow-up condition in Workspace.focusPanel(_:trigger:) to use a TerminalPanel cast and to trigger when the hosted surface is not first responder.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Poem

🐰 I nudge the panes when they tumble low,
I whisper to terminals where cursors go,
A tiny hop, a follow-up call,
No blank screens lurking after the fall,
Hooray — the prompt is back for all!

🚥 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 clearly summarizes the main bug fix: terminal becomes blank when the last pane closes.
Linked Issues check ✅ Passed The PR directly addresses issue #2665 by fixing the terminal blank/unresponsive state when panes collapse to one. The fix uses event-driven layout follow-up to restore focus after view reattachment, matching the core requirement.
Out of Scope Changes check ✅ Passed All changes are focused on the terminal focus/first-responder issue during pane collapse. The modifications to focusPanel and BonsplitDelegate handling are directly scoped to address the blank terminal problem.
Description check ✅ Passed The pull request description provides a clear summary of the problem, root cause, and solution. It includes a test plan with specific verification steps and links to the related issue #2665.

✏️ 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
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 7, 2026

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>
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