Skip to content

Fix minimal mode pane tab drag routing - #4290

Merged
austinywang merged 21 commits into
mainfrom
issue-4289-minimal-mode-tab-drag
May 20, 2026
Merged

austinywang merged 21 commits into
mainfrom
issue-4289-minimal-mode-tab-drag

Conversation

@austinywang

@austinywang austinywang commented May 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4289.

Summary

  • Keeps the main cmux hosting view out of implicit AppKit window dragging, so app content cannot become titlebar drag chrome by accident.
  • Adds a Bonsplit tab-item hit registry that distinguishes real pane tabs from empty tab-bar chrome.
  • Makes the titlebar drag handle reject registered Bonsplit pane-tab hit regions before it can capture mouse-down, so visible pane tabs are never app-window drag chrome.
  • Starts a mouse-sequence suppression when a pane tab receives mouse-down, keeping the window non-movable through leftMouseDragged and restoring only on leftMouseUp.
  • Keeps empty non-tab chrome available for explicit app-window dragging.
  • Updates Bonsplit to f7a39b2b3037822b42c2cd149072279afbbaaadd, including near-titlebar tab-lane hit ownership and clear shared-backdrop active-tab fill.
  • Merges current main, which includes the settings startup side-effect deferral that avoids recursive Ghostty/settings singleton initialization during managed appearance loading.

Reproduction / validation

  • User verified earlier PR builds still moved the whole window from the minimal-mode pane tab.
  • Debug logs confirmed the old fix suppressed only the initial leftMouseDown, restored window.isMovable, then leftMouseDragged moved the window while tab.dragStart was active.
  • Follow-up feedback showed the titlebar drag handle could still win the initial hit over visible pane-tab pixels, so the latest code blocks that hit path directly.
  • User verified the follow-up build works for minimal-mode tab dragging.
  • Crash report showed recursive settings-store initialization through managed appearance side effects; the branch is now based on main's deferred settings side-effect path.
  • Cloud Mac video repro was attempted, but cloud-mac preflight is blocked on this machine by macfleet auth: No token stored for 'default'. Run macfleet login.
  • I did not run local tests, per repo policy. CI is the validation gate.

Submodule note

Testing

  • CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag issue-4289-minimal-mode-tab-drag
  • Not run locally beyond the tagged Debug build, per cmux repo policy.

@vercel

vercel Bot commented May 18, 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 May 20, 2026 12:23am
cmux-staging Building Building Preview, Comment May 20, 2026 12:23am

@coderabbitai

coderabbitai Bot commented May 18, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Short-circuit tab-bar pass-through on Bonsplit registry hits; add a window-move suppression reason and sequencing for Bonsplit pane/tab drags; disable hosting-view mouse-down window moves; update NSWindow event wiring and drag-handle capture; add tests and bump vendor submodule.

Changes

Tab-Bar Registry Hit Pass-Through

Layer / File(s) Summary
Registry hit early-return in pass-through logic
Sources/TerminalWindowPortal.swift
shouldPassThroughToPaneTabBar now returns true immediately when the Bonsplit registry reports a registryHit, bypassing the hosted-terminal fallback.
Test coverage for registry hit behavior
cmuxTests/PortalTabDragRoutingTests.swift
Test import switched to @testable; adds makeHostedTerminalView helper and testHostViewTrustsRegisteredTabStripRegionAboveHostedTerminal which registers a tab-strip region and asserts the host defers to that region even when a hosted terminal overlays it.

Window-Move Suppression and Tests

Layer / File(s) Summary
Suppression reason API and Bonsplit hook
Sources/App/ShortcutRoutingSupport.swift
Adds WindowMoveSuppressionReason, imports Bonsplit, and implements Bonsplit-based suppression detection plus reason-selection helpers (folderDrag prioritized over bonsplitPaneTabDrag).
Hosting view and NSWindow event handling
Sources/App/CmuxMainWindow.swift, Sources/AppDelegate.swift, Sources/ContentView.swift
MainWindowHostingView.mouseDownCanMoveWindow overridden to false; NSWindow.cmux_sendEvent(_:) now consults windowMoveSuppressionReasonForEvent and uses conditional suppression finishing logic with updated debug logging and safe hit-view computation; main-window drag configuration moved to configureCmuxMainWindowDragBehavior.
Drag-handle suppression sequence and capture gating
Sources/WindowDragHandleView.swift
Introduce associated-object-backed suppression sequence state and helpers to begin/ensure/finish suppression; update drag-handle capture to decline when left-mousedown falls inside BonsplitTabItemHitRegionRegistry.
Tests for window-move suppression
cmuxTests/WindowAndDragTests.swift
Tests use @testable import Bonsplit; add FakeBonsplitTabItemRegionView; assert MainWindowHostingView prevents mouse-down window moves; verify drag-handle capture and suppression lifecycle (trigger on registered tab frames, keep window immovable during drag, restore on mouse-up, and no suppression for empty chrome).

Vendor

Layer / File(s) Summary
Submodule pointer update
vendor/bonsplit
Advance vendor/bonsplit Git submodule reference to the new SHA.

Sequence Diagram

sequenceDiagram
  participant User as Client
  participant Window as NSWindow
  participant DragHandle as WindowDragHandleView
  participant Registry as BonsplitTabItemHitRegionRegistry
  participant Host as WindowTerminalHostView
  participant Hosted as GhosttySurfaceScrollView

  User->>Window: mouseDown event
  Window->>DragHandle: windowDragHandleShouldCaptureHit?
  DragHandle->>Registry: containsWindowPoint?
  Registry-->>DragHandle: hit / no-hit
  alt Registry hit
    DragHandle-->>Window: decline capture
    Window->>Host: performHitTest -> asks registry
    Host->>Registry: query registryHit
    Registry-->>Host: registryHit == true
    Host-->>Window: defer to tab-strip region
  else No registry hit
    DragHandle-->>Window: capture (start suppression sequence as needed)
    Window->>Hosted: hit test hosted terminal
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibble keys and hop with cheer,
"Registry first" — I twitch my ear,
Clicks routed clean, the host steps back,
The window waits; no accidental track.
Tiny paws test, the fix hops near. 🐇✨


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Architecture Rethink ❌ Error Multiple owners manage window.isMovable state. Stale-recovery path clears suppressionDepth but not moveSuppressionSequence, leaving window immovable. Flagged in review comment. Apply review fix: clearWindowDragSuppression should call finishWindowMoveSuppressionSequence. Better: unify isMovable ownership in a single controller.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix minimal mode pane tab drag routing' accurately and concisely describes the primary change in the changeset, which addresses the drag routing issue for pane tabs in minimal mode.
Linked Issues check ✅ Passed The code changes fully address the requirements from issue #4289: prevent implicit AppKit window dragging from preempting pane-tab events, add proper hit-region registry for pane tabs, ensure drag-to-reorder functionality works in minimal mode, and implement mouse-sequence suppression to protect drag gestures.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing minimal mode pane tab drag routing. No unrelated modifications to architecture, dependencies, or unrelated features were introduced.
Cmux Swift Actor Isolation ✅ Passed New window helper functions follow existing patterns in WindowDragHandleView.swift. Lack of explicit @MainActor matches 16+ pre-existing functions. No new isolation violations introduced.
Cmux Swift Blocking Runtime ✅ Passed PR does not introduce blocking synchronization. Window-move suppression uses non-blocking state via objc_setAssociatedObject and AppKit event routing. No semaphores, locks, sleeps, or polling added.
Cmux No Hacky Sleeps ✅ Passed Check is scoped to TypeScript, JavaScript, shell, and build scripts. PR contains only Swift files and a submodule—Swift sleeps are covered by separate rule.
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns. All new functions use synchronous execution appropriate for AppKit mouse-event dispatch and window state management via objc_setAssociatedObject.
Cmux Swift @Concurrent ✅ Passed All new Swift functions in the PR are synchronous and use proper isolation annotations. No @concurrent misuse, no missing @concurrent, no heavy async work from UI isolation without hops.
Cmux Swift File And Package Boundaries ✅ Passed All changes comply with boundaries rules. ShortcutRoutingSupport.swift at 808 lines with coherent responsibility. Incidental touches to existing oversized files with no mixed responsibilities.
Cmux Swift Logging ✅ Passed All logging complies with swift-logging.md rules. Only debug-only cmuxDebugLog and #if DEBUG-guarded NSLog statements were added. No violations found.
Cmux User-Facing Error Privacy ✅ Passed PR complies with user-facing error privacy rules. All DEBUG logging is properly guarded, user-facing alerts are localized, and no sensitive data is exposed to users.
Cmux Full Internationalization ✅ Passed PR contains only internal refactoring. No user-facing text added. All debug logging with WindowMoveSuppressionReason wrapped in #if DEBUG. No .xcstrings files modified.
Cmux Swiftui State Layout ✅ Passed No new SwiftUI state patterns introduced. All changes confined to AppKit bridge views and event dispatch handlers. No violations of ObservableObject, @Published, @StateObject patterns.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not create new user-visible NSWindow/NSPanel/NSWindowController/Window/WindowGroup. Changes only modify drag behavior of existing windows, configure window helpers, and add test-only fixtures.
Description check ✅ Passed The PR description is comprehensive and detailed, covering all required template sections with substantive content.
✨ 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-4289-minimal-mode-tab-drag

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.

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

Copy link
Copy Markdown

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

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented May 18, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes minimal-mode pane-tab drag routing by making the main window permanently isMovable = false and routing all app-window movement through explicit drag-chrome zones that temporarily re-enable movability via withTemporaryWindowMovableEnabled. It also adds a BonsplitTabItemHitRegionRegistry early-exit to windowDragHandleShouldCaptureHit so the titlebar drag handle can never steal a registered pane-tab hit, and elevates the previous single-event folder-drag suppression into a stateful sequence model that stays active through the full left-mouse down→drag→up sequence.

  • Sequence-based suppression (WindowMoveSuppressionReason, beginOrContinueWindowMoveSuppressionSequenceForEvent): replaces the single-event isMovable toggle with per-sequence state stored as an NSWindow associated object, keeping the window immovable from leftMouseDown through leftMouseUp for both pane-tab and folder-drag triggers.
  • Titlebar drag-handle hit rejection: windowDragHandleShouldCaptureHit now consults BonsplitTabItemHitRegionRegistry before the sibling-view walk, so registered pane-tab pixels are never silently promoted to app-window drag chrome.
  • Terminal portal trust order: TerminalWindowPortal returns early when decision.registryHit is true, ensuring a registered tab-strip region passes through even when a hosted terminal view overlaps it.

Confidence Score: 5/5

Safe to merge; the changes are narrowly scoped to the mouse-event dispatch path and are well-covered by targeted regression tests.

The fix addresses the root cause directly: the main window starts immovable, explicit drag chrome is the only path to movement, and the new sequence model keeps that invariant through the full down→drag→up lifecycle. The BonsplitTabItemHitRegionRegistry early exit in windowDragHandleShouldCaptureHit closes the remaining hit-capture escape hatch. Test coverage is thorough — sequence lifecycle, stale-sequence replacement, drag-handle rejection, empty-chrome availability, and terminal portal trust order are all exercised. No production logging, no concurrency primitives, and no user-facing strings were introduced.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/CmuxMainWindow.swift Adds mouseDownCanMoveWindow = false override to MainWindowHostingView and extracts configureCmuxMainWindowDragBehavior to set both isMovableByWindowBackground = false and isMovable = false; replaces the previous per-site inline code.
Sources/App/ShortcutRoutingSupport.swift Adds WindowMoveSuppressionReason enum and the full sequence-lifecycle API, extending the existing folder-drag suppression model to cover the full left-mouse sequence.
Sources/WindowDragHandleView.swift Introduces WindowMoveSuppressionSequenceState and sequence management helpers; adds a BonsplitTabItemHitRegionRegistry early-exit guard to windowDragHandleShouldCaptureHit; updates clearWindowDragSuppression to flush any active sequence.
Sources/AppDelegate.swift Replaces the single-event folder-drag suppression with beginOrContinueWindowMoveSuppressionSequenceForEvent; defer-based finishWindowMoveSuppressionSequence on leftMouseUp correctly closes the sequence after dispatch.
Sources/ContentView.swift Replaces duplicate inline isMovable / isMovableByWindowBackground setup with a call to configureCmuxMainWindowDragBehavior; minimal change, comment updated to reflect the new design.
Sources/TerminalWindowPortal.swift Adds early-return for decision.registryHit so a registered tab-strip region passes through even when a hosted terminal view overlaps it.
cmuxTests/WindowAndDragTests.swift Adds FakeBonsplitTabItemRegionView test double; covers mouseDownCanMoveWindow, configureCmuxMainWindowDragBehavior, titlebar drag-handle rejection, and the full suppress/continue/restore sequence lifecycle.
cmuxTests/PortalTabDragRoutingTests.swift Adds testHostViewTrustsRegisteredTabStripRegionAboveHostedTerminal covering the new registryHit early return in TerminalWindowPortal; switches to @testable import Bonsplit.

Sequence Diagram

sequenceDiagram
    participant User
    participant AppKit
    participant sendEvent as NSWindow.sendEvent
    participant Sequence as SuppressionSequence
    participant DragHandle as WindowDragHandleView
    participant Registry as BonsplitTabItemHitRegionRegistry

    Note over User,Registry: Pane-tab click (bug fixed path)
    User->>AppKit: leftMouseDown on tab
    AppKit->>sendEvent: dispatch
    sendEvent->>Registry: containsWindowPoint?
    Registry-->>sendEvent: true - begin sequence
    sendEvent->>Sequence: beginWindowMoveSuppressionSequence(.bonsplitPaneTabDrag)
    Sequence-->>sendEvent: "isMovable=false, sequence active"
    sendEvent->>AppKit: cmux_sendEvent (tab receives click)
    User->>AppKit: leftMouseDragged
    AppKit->>sendEvent: dispatch
    sendEvent->>Sequence: continueSequence - ensureImmovable
    sendEvent->>AppKit: cmux_sendEvent (drag goes to tab, not window)
    User->>AppKit: leftMouseUp
    AppKit->>sendEvent: dispatch
    sendEvent->>Sequence: "defer - finishSequence - restore isMovable=false"
    sendEvent->>AppKit: cmux_sendEvent
    Note over User,Registry: Empty-chrome drag (window move path)
    User->>AppKit: leftMouseDown on empty chrome
    DragHandle->>Registry: containsWindowPoint? - false
    DragHandle->>AppKit: withTemporaryWindowMovableEnabled
    Note over DragHandle: isMovable=true temporarily
    DragHandle->>AppKit: window.performDrag(with: event)
    AppKit-->>User: window moves / tiling triggers
    Note over DragHandle: defer restores isMovable=false
Loading

Reviews (12): Last reviewed commit: "Update Bonsplit minimal tab drag hit tes..." | Re-trigger Greptile

Comment thread cmuxTests/PortalTabDragRoutingTests.swift

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 4 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Sources/App/ShortcutRoutingSupport.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AppDelegate.swift (1)

14833-14873: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Finish the active suppression sequence even when the current event no longer maps to a suppression reason.

If a Bonsplit/tab drag starts inside the registered hit region and the leftMouseUp lands outside it, windowMoveSuppressionReasonForEvent(...) can return nil on the terminating event. Lines 14833-14845 then bypass finishWindowMoveSuppressionSequence(...), so the window can stay suppressed after the drag ends.

Proposed fix
-        guard let suppressionReason = windowMoveSuppressionReasonForEvent(window: self, event: event) else {
+        let suppressionReason = windowMoveSuppressionReasonForEvent(window: self, event: event)
+        let hasActiveSuppressionSequence = windowDragSuppressionDepth(window: self) > 0
+        guard suppressionReason != nil || hasActiveSuppressionSequence else {
 `#if` DEBUG
             if event.type == .keyDown {
                 folderGuardMs = (ProcessInfo.processInfo.systemUptime - folderGuardStart) * 1000.0
                 let originalDispatchStart = ProcessInfo.processInfo.systemUptime
                 cmux_sendEvent(event)
@@
-        let shouldFinishSuppression = shouldFinishWindowMoveSuppressionSequenceAfterDispatch(window: self, event: event)
+        let shouldFinishSuppression =
+            hasActiveSuppressionSequence &&
+            shouldFinishWindowMoveSuppressionSequenceAfterDispatch(window: self, event: event)
@@
-                cmuxDebugLog("window.sendEvent.\(finishedReason?.rawValue ?? suppressionReason.rawValue) finish nowMovable=\(isMovable)")
+                cmuxDebugLog("window.sendEvent.\(finishedReason?.rawValue ?? suppressionReason?.rawValue ?? "activeSequence") finish nowMovable=\(isMovable)")
             } else {
-                cmuxDebugLog("window.sendEvent.\(suppressionReason.rawValue) keepSuppressed nowMovable=\(isMovable)")
+                cmuxDebugLog("window.sendEvent.\(suppressionReason?.rawValue ?? "activeSequence") keepSuppressed nowMovable=\(isMovable)")
             }
 `#endif`
         }
@@
-        cmuxDebugLog("window.sendEvent.\(suppressionReason.rawValue) suppress=1 hit=\(hitDesc) movable=\(isMovable) depth=\(depth)")
+        cmuxDebugLog("window.sendEvent.\(suppressionReason?.rawValue ?? "activeSequence") suppress=1 hit=\(hitDesc) movable=\(isMovable) depth=\(depth)")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate.swift` around lines 14833 - 14873, When
windowMoveSuppressionReasonForEvent(window:event:) returns nil you still need to
end any active suppression; in the early-return branch (the guard else) call
shouldFinishWindowMoveSuppressionSequenceAfterDispatch(window:self, event:event)
and if it returns true invoke finishWindowMoveSuppressionSequence(window: self)
(and emit the same cmuxDebugLog path under DEBUG) before cmux_sendEvent(event)
and return so suppression cannot remain active after the terminating event.
Ensure you preserve the existing DEBUG timing code and logging behavior when
adding this check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/WindowAndDragTests.swift`:
- Around line 645-655: The test helper FakeBonsplitTabItemRegionView is
duplicated across WindowDragHandleHitTests and
WindowMoveSuppressionHitPathTests; extract this class into a single shared test
helper (e.g., a new file or a file-level extension) and replace both local
definitions with references to that single implementation. Locate the
FakeBonsplitTabItemRegionView declaration used in WindowDragHandleHitTests and
WindowMoveSuppressionHitPathTests, move it to a shared test-support location,
ensure it remains nonisolated(unsafe) with tabFrames,
containsBonsplitTabItemHit(localPoint:) and overridden hitTest(_:), and remove
the duplicate declaration from the other test file so both tests import/use the
shared helper.

---

Outside diff comments:
In `@Sources/AppDelegate.swift`:
- Around line 14833-14873: When
windowMoveSuppressionReasonForEvent(window:event:) returns nil you still need to
end any active suppression; in the early-return branch (the guard else) call
shouldFinishWindowMoveSuppressionSequenceAfterDispatch(window:self, event:event)
and if it returns true invoke finishWindowMoveSuppressionSequence(window: self)
(and emit the same cmuxDebugLog path under DEBUG) before cmux_sendEvent(event)
and return so suppression cannot remain active after the terminating event.
Ensure you preserve the existing DEBUG timing code and logging behavior when
adding this check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4de03d6f-529d-4e91-89c2-17a5db84c18f

📥 Commits

Reviewing files that changed from the base of the PR and between e794174 and 735dde1.

📒 Files selected for processing (5)
  • Sources/App/ShortcutRoutingSupport.swift
  • Sources/AppDelegate.swift
  • Sources/WindowDragHandleView.swift
  • cmuxTests/WindowAndDragTests.swift
  • vendor/bonsplit

Comment thread cmuxTests/WindowAndDragTests.swift Outdated
Comment thread Sources/AppDelegate.swift Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes May 18, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 14856-14861: The DEBUG block reimplements hit-testing against
contentView only, causing titlebar/theme-frame hits to be reported as nil and
misleading suppression diagnostics; replace the inline contentView hit-test in
the hitView initializer with the same hit-test helper used by cmux_sendEvent(_:)
(i.e., call the existing centralized hit-test function the event path uses) so
the DEBUG logging reflects the actual view resolution across the whole window
chrome, not just contentView. Ensure you reference the existing helper instead
of duplicating logic and keep the fallback nil behavior intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8c2f130c-a54a-42be-ad46-8a0144598b3d

📥 Commits

Reviewing files that changed from the base of the PR and between 24fa6d4 and 73583e5.

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

Comment thread Sources/AppDelegate.swift Outdated

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 18, 2026

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

@lawrencecchen
lawrencecchen dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] May 18, 2026 22:47

Stale automated review from an older commit. The active suppression-sequence issue was fixed in 73583e5 and the latest CodeRabbit review did not report this as an open issue.

@austinywang

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 19, 2026

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

coderabbitai[bot]
coderabbitai Bot previously requested changes May 19, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/AppDelegate.swift (1)

15113-15160: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Start the suppression sequence before dispatch.

Line 15113 computes a fresh suppression reason, but this path never begins/ensures the suppression sequence before cmux_sendEvent(event). On the first Bonsplit/folder-drag event, finishWindowMoveSuppressionSequence(window:) has nothing to finish, so the window can still start moving while the drag is being dispatched. Call the begin/ensure helper as soon as suppressionReason becomes non-nil, before the active-sequence check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/AppDelegate.swift` around lines 15113 - 15160, The code computes
suppressionReason via windowMoveSuppressionReasonForEvent but never starts the
suppression sequence before dispatch, so on first folder-drag there is nothing
for finishWindowMoveSuppressionSequence(window:) to finish; fix by calling the
begin/ensure helper for the suppression sequence as soon as suppressionReason !=
nil (before checking activeWindowMoveSuppressionSequenceReason and before
calling cmux_sendEvent), e.g. invoke your
beginWindowMoveSuppressionSequence(window: self, reason: suppressionReason!) or
ensureWindowMoveSuppressionSequence(window: self, reason: suppressionReason!) so
the sequence is active for subsequent
shouldFinishWindowMoveSuppressionSequenceAfterDispatch and
finishWindowMoveSuppressionSequence logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Sources/WindowDragHandleView.swift`:
- Around line 299-366: The stale-recovery path still uses
clearWindowDragSuppression(...) which only decrements the suppression depth and
leaves the move-sequence associated object intact, so
activeWindowMoveSuppressionSequenceReason(window:) can remain set and keep a
window immovable; fix by unifying cleanup with
finishWindowMoveSuppressionSequence(window:): either have
clearWindowDragSuppression call finishWindowMoveSuppressionSequence(window:)
when a WindowMoveSuppressionSequenceState exists, or replicate
finishWindowMoveSuppressionSequence’s logic (read the
WindowMoveSuppressionSequenceState via objc_getAssociatedObject, clear that
associated object, call endWindowDragSuppression(window:), and call
restoreWindowDragging(window:previousMovableState:) with the saved
previousMovableState) so the associated reason is removed and window.isMovable
is restored.

---

Outside diff comments:
In `@Sources/AppDelegate.swift`:
- Around line 15113-15160: The code computes suppressionReason via
windowMoveSuppressionReasonForEvent but never starts the suppression sequence
before dispatch, so on first folder-drag there is nothing for
finishWindowMoveSuppressionSequence(window:) to finish; fix by calling the
begin/ensure helper for the suppression sequence as soon as suppressionReason !=
nil (before checking activeWindowMoveSuppressionSequenceReason and before
calling cmux_sendEvent), e.g. invoke your
beginWindowMoveSuppressionSequence(window: self, reason: suppressionReason!) or
ensureWindowMoveSuppressionSequence(window: self, reason: suppressionReason!) so
the sequence is active for subsequent
shouldFinishWindowMoveSuppressionSequenceAfterDispatch and
finishWindowMoveSuppressionSequence logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4fb07899-b41c-4094-85b5-c0fabbec4443

📥 Commits

Reviewing files that changed from the base of the PR and between 0180861 and dc1e31c.

📒 Files selected for processing (7)
  • Sources/App/CmuxMainWindow.swift
  • Sources/App/ShortcutRoutingSupport.swift
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/WindowDragHandleView.swift
  • cmuxTests/WindowAndDragTests.swift
  • vendor/bonsplit

Comment thread Sources/WindowDragHandleView.swift
@austinywang
austinywang dismissed coderabbitai[bot]’s stale review May 19, 2026 04:46

Addressed in 2df920f: stale cleanup now routes active move-sequence state through finishWindowMoveSuppressionSequence before draining suppression depth, with coverage; the NSWindow event path now calls beginOrContinueWindowMoveSuppressionSequenceForEvent so sequence start/continuation is explicit before dispatch.

@austinywang
austinywang merged commit d6ec6b2 into main May 20, 2026
18 checks passed
@austinywang
austinywang deleted the issue-4289-minimal-mode-tab-drag branch May 20, 2026 00:36
austinywang added a commit that referenced this pull request May 20, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jul 21, 2026
6 tasks done

This branch was successfully deployed

1 active deployment
Preview – cmux — 36fc880f Deployed May 20, 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.

Minimal mode: can't drag pane tabs to reorganize panes

2 participants