Skip to content

Fix #3708: redraw cmux window on focus regain even when cursor is over the sidebar resize handle - #3879

Merged
austinywang merged 3 commits into
mainfrom
issue-3708-window-redraw-focus-resize-handle
May 12, 2026
Merged

austinywang merged 3 commits into
mainfrom
issue-3708-window-redraw-focus-resize-handle

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3708.\n\n## Summary\n- Add a regression test for key-regain invalidation while the pointer is over the sidebar/main split divider.\n- Move redraw responsibility to the main-window key lifecycle path so divider cursor tracking cannot suppress content invalidation.\n\n## Testing\n- Not run locally by request; CI will verify the failing-test/fix two-commit sequence.


Note

Medium Risk
Moderate risk: changes main-window key focus handling to aggressively invalidate the entire view tree, which could impact redraw performance or introduce new focus/hover side effects on macOS UI components.

Overview
Fixes a focus-regain redraw bug by moving main-window refresh responsibilities into the didBecomeKey lifecycle path.

On key regain, the app now recursively invalidates the window’s content view hierarchy (and cursor rects) before restoring keyboard focus, ensuring the UI redraws even when cursor tracking (e.g., over the sidebar split divider) would otherwise suppress updates.

Adds a regression test (MainWindowFocusRedrawTests) that simulates a sidebar/main NSSplitView divider scenario and asserts the root content view is invalidated after resigning and regaining key focus.

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

The sidebar/main resize handle repro depends on a main window key-regain path that currently restores focus state without requiring a content redraw. This regression test builds a registered cmux main window with a split divider under the pointer and asserts that the root content view is invalidated after resign/become-key notifications.

Constraint: Do not run local tests or bare xcodebuild for this branch

Confidence: high

Scope-risk: narrow

Directive: Keep this as a behavior-level AppKit lifecycle test; do not replace it with a source-shape assertion

Tested: Not run locally by request

Not-tested: Local XCTest execution; CI is expected to show this test failing before the fix
A cursor hover over the sidebar/main divider should not decide whether a key-regain redraw occurs. The main window lifecycle handler now treats content invalidation as an app-level invariant for registered cmux windows before restoring subview focus state.

Constraint: Fix must avoid resize-handle-specific timing glue and must not use local bare xcodebuild

Rejected: Patch WindowTerminalHostView mouse tracking | redraw on focus regain belongs to the window lifecycle owner, not the current cursor owner

Confidence: medium

Scope-risk: narrow

Directive: Keep key-regain redraw ownership in the main-window lifecycle path; do not move it into divider tracking handlers

Tested: git diff --check

Not-tested: Local XCTest/build by request; CI will verify
@vercel

vercel Bot commented May 12, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled May 12, 2026 1:20am
cmux-staging Building Building Preview, Comment May 12, 2026 1:20am

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown

Warning

Rate limit exceeded

@austinywang has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 11 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 430bfa8a-2277-4ee7-83d6-815b73d9b7bd

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce2a24 and b1bf2b1.

📒 Files selected for processing (2)
  • Sources/CmuxLifecycleEventPublishing.swift
  • cmuxTests/WindowAndDragTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-3708-window-redraw-focus-resize-handle

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 May 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a focus-regain redraw regression where the pointer being over the NSSplitView sidebar divider could suppress the standard AppKit window-focus invalidation path. It introduces a MainWindowKeyRegainRefresh helper that unconditionally walks the full view tree marking every non-hidden view and its backing CALayer as needing display before restoring keyboard focus, and adds a regression test using only local view-frame coordinates.

  • Production fix (CmuxLifecycleEventPublishing.swift): replaces the direct keyboardFocusCoordinator call in handleCmuxWindowBecameKey with a full-tree display invalidation walk followed by the coordinator call, ensuring the redraw invariant is upheld regardless of cursor tracking state.
  • Regression test (WindowAndDragTests.swift): sets up an off-screen split-view window, fires resign+becomeKey notifications, and asserts contentView.needsDisplay is true; uses only local coordinates so the assertion is deterministic in headless CI.
  • Cursor rect scope: window.invalidateCursorRects(for: contentView) resets only the root content view's cursor rects; NSSplitView subview cursor areas remain untouched by this call.

Confidence Score: 5/5

Safe to merge — the change is localized to the key-regain path and adds a regression test; the tree-walk invalidation is unconditional and isolated behind MainWindowKeyRegainRefresh.

The tree-walk display invalidation is correct and self-contained. The only gap is that invalidateCursorRects(for: contentView) does not recurse into subviews, so NSSplitView cursor areas are not reset — but this does not affect display correctness and does not regress existing behavior. The regression test uses only local view-frame coordinates and is reliable in headless CI.

No files require special attention; the production change is small and the test is straightforward.

Important Files Changed

Filename Overview
Sources/CmuxLifecycleEventPublishing.swift Introduces MainWindowKeyRegainRefresh that walks the full view tree on key regain to guarantee display invalidation; invalidateCursorRects(for:) is called only on the root content view, which does not cover split-view subview cursor areas.
cmuxTests/WindowAndDragTests.swift Adds MainWindowFocusRedrawTests with a regression test that uses only local view-frame geometry (no screen-coordinate dependency) to assert contentView.needsDisplay is set after key-regain; setup is deterministic in headless environments.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[NSWindow.didBecomeKeyNotification] --> B[handleCmuxWindowBecameKey]
    B --> C[contextForMainTerminalWindow]
    C --> D[setActiveMainWindow]
    D --> E[publishCmuxWindowLifecycle]
    E --> F{context != nil?}
    F -- No --> G[Done]
    F -- Yes --> H[MainWindowKeyRegainRefresh.refresh]
    H --> I[invalidateContentDisplayTree]
    I --> J[invalidateDisplayTree recursive walk]
    J --> K[view.needsDisplay = true / view.layer?.setNeedsDisplay]
    K --> L{more subviews?}
    L -- Yes, not hidden --> J
    L -- No --> M[window.invalidateCursorRects for contentView]
    M --> N[keyboardFocusCoordinator.restoreTargetAfterWindowBecameKey]
    N --> G
Loading

Reviews (2): Last reviewed commit: "Clarify key-regain refresh ownership" | Re-trigger Greptile

Comment thread Sources/CmuxLifecycleEventPublishing.swift
Comment thread Sources/CmuxLifecycleEventPublishing.swift
Comment thread cmuxTests/WindowAndDragTests.swift
Review feedback correctly pointed out that the redraw invariant should be named as one post-focus contract instead of appearing as an orphaned pre-walk next to focus restoration. This keeps the app-window lifecycle as the single owner while calling the keyboard focus coordinator only after redraw invalidation is established.

Constraint: Continue avoiding local tests and bare xcodebuild on this branch

Rejected: Move redraw ownership into divider tracking | the bug must be impossible regardless of current cursor owner

Confidence: medium

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local XCTest/build by request; CI will verify
@austinywang
austinywang merged commit 0576fb9 into main May 12, 2026
31 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — b1bf2b15 Deployed May 12, 2026 by vercel[bot]
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.

Window fails to redraw on focus regain when cursor is over sidebar/main resize handle

1 participant