Skip to content

Fix #1972: resync terminal portal after restore-time bind#1973

Merged
austinywang merged 2 commits intomainfrom
issue-1972-terminal-sidebar-bleed
Mar 23, 2026
Merged

Fix #1972: resync terminal portal after restore-time bind#1973
austinywang merged 2 commits intomainfrom
issue-1972-terminal-sidebar-bleed

Conversation

@austinywang
Copy link
Copy Markdown
Contributor

@austinywang austinywang commented Mar 23, 2026

Summary

  • Fix Terminal content bleeds into sidebar area after app restart #1972 by resynchronizing the terminal window portal after the initial bind so restore-time ancestor layout shifts from sidebar/split restoration do not leave the hosted terminal at a stale frame.
  • Add a regression test that reproduces the restore-like timing: bind the terminal, queue an ancestor layout shift on the next main-queue turn, and assert the portal moves off the stale position.

Testing

  • Added testBindQueuesExternalGeometrySyncForQueuedLayoutShift in cmuxTests/TerminalAndGhosttyTests.swift.
  • Built and launched ./scripts/reload.sh --tag issue-1972 successfully.
  • Manual verification beyond the tagged app launch was not performed locally.

Demo Video

For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link).

  • Video URL or attachment: N/A

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Improved window geometry synchronization to correctly update when ancestor layouts change after binding (e.g., sidebar width or split position shifts).

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 23, 2026

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 23, 2026 3:28am

@austinywang
Copy link
Copy Markdown
Contributor Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented Mar 23, 2026

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@austinywang I have started the AI code review. It will take a few minutes to complete.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 20eab0ed-c419-4d75-a1f0-f3b10e376cc8

📥 Commits

Reviewing files that changed from the base of the PR and between 56a4d25 and 296e25d.

📒 Files selected for processing (2)
  • Sources/TerminalWindowPortal.swift
  • cmuxTests/TerminalAndGhosttyTests.swift

📝 Walkthrough

Walkthrough

The pull request adds a scheduleExternalGeometrySynchronize() call during TerminalWindowPortal.bind() to trigger deferred external-geometry synchronization after initial bind. This ensures the portal updates based on ancestor layout changes occurring after bind completes, preventing geometry misalignment. A corresponding test validates the queued synchronization behavior.

Changes

Cohort / File(s) Summary
Portal External Geometry Sync
Sources/TerminalWindowPortal.swift
Adds scheduleExternalGeometrySynchronize() call after seeding hosted view frame and reconciling geometry during bind to handle subsequent ancestor layout shifts.
Test Coverage
cmuxTests/TerminalAndGhosttyTests.swift
Adds testBindQueuesExternalGeometrySyncForQueuedLayoutShift test that verifies external geometry sync is scheduled when container layout shifts occur post-bind, confirming hit-testing behavior updates correctly.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 A portal that binds with grace,
Now schedules syncs to find its place,
When ancestors shift and rearrange,
Geometry flows through every change—
No more content bleeds through the frame! ✨

🚥 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 references the issue (#1972) and concisely describes the primary change: resyncing the terminal portal after restore-time bind.
Description check ✅ Passed The description follows the template with all required sections (Summary, Testing, Demo Video, Review Trigger, Checklist) properly completed and filled out.
Linked Issues check ✅ Passed The code changes directly address issue #1972 by adding external geometry resynchronization after bind and a regression test that validates the fix.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the terminal portal binding and testing the fix; no extraneous modifications are present.

✏️ 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-1972-terminal-sidebar-bleed

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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 23, 2026

@austinywang Sure! I'll start reviewing PR #1973 now.


🧠 Learnings used
Learnt from: mdsakalu
Repo: manaflow-ai/cmux PR: 510
File: Sources/GhosttyTerminalView.swift:2276-2336
Timestamp: 2026-03-11T04:48:32.420Z
Learning: In cmux Sources/GhosttyTerminalView.swift, when implementing initial-present recovery for Ghostty terminals, avoid ghostty_surface_draw loops. Re-read self.surface after view.forceRefreshSurface() (since the surface may be torn down/reparented), then use occlusion toggle (ghostty_surface_set_occlusion false/true) plus ghostty_surface_refresh to wake the renderer. Cancel retries once the layer has contents or the retry budget is exhausted.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T08:16:21.950Z
Learning: Applies to **/GhosttyTerminalView.swift : `SurfaceSearchOverlay` must be mounted from `GhosttySurfaceScrollView` in `Sources/GhosttyTerminalView.swift` (AppKit portal layer), not from SwiftUI panel containers. Portal-hosted terminal views can sit above SwiftUI during split/workspace churn.

Learnt from: CR
Repo: manaflow-ai/cmux PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T08:16:21.950Z
Learning: Applies to **/TerminalWindowPortal.swift : In `WindowTerminalHostView.hitTest()` in `TerminalWindowPortal.swift`: all divider/sidebar/drag routing must be gated to pointer events only. Do not add work outside the `isPointerEvent` guard as this path is called on every event including keyboard input.
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

1 similar comment
@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 23, 2026

Greptile Summary

This PR fixes #1972 by adding a single scheduleExternalGeometrySynchronize() call inside WindowTerminalPortal.bind() so that restore-time ancestor layout shifts (sidebar widths, split positions) that are queued asynchronously after the initial bind tick are caught and reflected in the portal frame before they settle.

Key changes:

  • Sources/TerminalWindowPortal.swift: One-line addition — scheduleExternalGeometrySynchronize() is called at the end of bind() (after scheduleDeferredFullSynchronizeAll()). Because the function captures requiresSettledLayout=true at bind time (not in live-resize or drag), it schedules a double-hop async dispatch, which naturally fires after any layout shifts enqueued on the same or next main-queue turn.
  • cmuxTests/TerminalAndGhosttyTests.swift: New regression test testBindQueuesExternalGeometrySyncForQueuedLayoutShift mirrors the structure of the pre-existing testScheduledExternalGeometrySyncWaitsForQueuedLayoutShift. It binds a surface, queues a 72-pt container shift on the next main-queue turn, runs the RunLoop for 50 ms, and then asserts the portal hit-tests at the new position — not the stale one. The test does not call TerminalWindowPortalRegistry.scheduleExternalGeometrySynchronize explicitly, confirming that bind() now schedules the sync on its own.

The fix is minimal and the double-hop mechanics are already well-tested by the existing sync test suite. No regressions are expected because scheduleExternalGeometrySynchronize is guarded by hasExternalGeometrySyncScheduled, and any bind that occurs inside realizeWindowLayout (e.g. testScheduledExternalGeometrySyncKeepsDragDrivenResizeResponsive) drains the bind-triggered sync inside the 50 ms RunLoop.run before the drag-mode assertions run.

Confidence Score: 5/5

  • Safe to merge — the change is a one-line, low-risk addition guarded by an existing idempotency flag, and the regression test is structurally consistent with the existing test suite.
  • The production-code delta is a single call site addition inside bind(). The underlying scheduleExternalGeometrySynchronize function already exists, is already tested, and is guarded by hasExternalGeometrySyncScheduled to prevent duplicate dispatches. The new test closely mirrors a pre-existing test (testScheduledExternalGeometrySyncWaitsForQueuedLayoutShift) and uses the same established timing pattern (RunLoop.current.run(until: 0.05)). No existing tests are broken by the change (the bind-triggered sync is drained inside realizeWindowLayout in the drag-responsive test). There are no data-loss, security, or correctness risks identified.
  • No files require special attention.

Important Files Changed

Filename Overview
Sources/TerminalWindowPortal.swift Adds a single scheduleExternalGeometrySynchronize() call inside the bind method after the initial sync, queuing a double-hop async resync so restore-time ancestor layout shifts (sidebar/split) settle before the portal frame is finalized.
cmuxTests/TerminalAndGhosttyTests.swift Adds testBindQueuesExternalGeometrySyncForQueuedLayoutShift which mirrors the structure of the existing testScheduledExternalGeometrySyncWaitsForQueuedLayoutShift test but omits an explicit scheduleExternalGeometrySynchronize call, relying on bind() to trigger it implicitly; uses a time-based 50 ms RunLoop drain consistent with neighbouring tests.

Sequence Diagram

sequenceDiagram
    participant App as App / Restore
    participant Portal as WindowTerminalPortal
    participant MQ1 as Main Queue (hop 1)
    participant MQ2 as Main Queue (hop 2)

    App->>Portal: bind(hostedView:to:anchor)
    Portal->>Portal: synchronizeHostedView() — seeds frame
    Portal->>Portal: scheduleDeferredFullSynchronizeAll()
    Portal->>MQ1: scheduleExternalGeometrySynchronize() [NEW]<br/>enqueue outer block
    App->>MQ1: sidebar/split layout shift (queued after bind returns)

    note over MQ1: Main queue processes in order
    MQ1->>MQ2: outer block fires → hasExternalGeometrySyncScheduled=false<br/>enqueue performSync (hop 2)
    MQ1->>Portal: ancestor layout shift applied (frame.origin.x += Δ)

    MQ2->>Portal: performSync fires<br/>synchronizeAllEntriesFromExternalGeometryChange()
    Portal->>Portal: portal frame updated to settled geometry ✅
Loading

Reviews (1): Last reviewed commit: "fix: resync terminal portal after restor..." | Re-trigger Greptile

window.displayIfNeeded()
}

RunLoop.current.run(until: Date().addingTimeInterval(0.05))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Time-based wait may be flaky under CI load

RunLoop.current.run(until: Date().addingTimeInterval(0.05)) has to drain three async hops in 50 ms (outer block from scheduleExternalGeometrySynchronize → inner performSync block → the queued layout-shift block). On a heavily loaded CI runner the budget can be tight.

The pre-existing testScheduledExternalGeometrySyncWaitsForQueuedLayoutShift uses the same 50 ms pattern and is presumably passing, so this is not a new risk — but if either test ever becomes intermittent, the fix is two sequential drainMainQueue() calls instead of the wall-clock sleep:

// instead of RunLoop.current.run(until: Date().addingTimeInterval(0.05))
drainMainQueue()  // fires hop-1 → queues hop-2; layout-shift block also dispatched by now
drainMainQueue()  // fires hop-2 (performSync) — portal is now at settled geometry

No action required unless CI flakiness is observed.

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.

Terminal content bleeds into sidebar area after app restart

1 participant