Skip to content

Fix wide-title tab drag hit testing - #215

Merged
austinywang merged 5 commits into
mainfrom
issue-10033-wide-tab-drag
Aug 12, 2026
Merged

austinywang merged 5 commits into
mainfrom
issue-10033-wide-tab-drag

Conversation

@austinywang

@austinywang austinywang commented Aug 12, 2026 •

Copy link
Copy Markdown

Summary

  • replace competing per-tab SwiftUI tab drop delegates with one pane-local AppKit drop destination;
  • resolve insertion gaps from live tab-frame midpoints, preserving model indices for translated and partially visible strips;
  • route same-pane and cross-pane tab moves through the shared controller APIs, retaining no-op suppression and file-drop behavior;
  • keep the full-width selected-tab trailing insertion indicator visible.

Regression provenance

The regression reported by manaflow-ai/cmux#10033 was introduced by bonsplit PR #197, specifically commit 4b5b563 (vendored by cmux commit manaflow-ai/cmux@2141de5). That change moved same-pane ownership into per-tab SwiftUI drop delegates to prevent duplicate middle-tab reorders, but left pointer-to-insertion resolution distributed across tab bounds. Wide titles make the source tab's large drop region retain routing while the pointer crosses insertion gaps.

This ports the strip-wide midpoint resolver and native destination architecture from bonsplit PR #202 (commits 7d2aa11 and 4da56ab), while retaining the pinned branch's later drag lifecycle and focus fixes.

Regression coverage

The first commit adds wide-title midpoint tests and native drop-handler/destination behavior coverage. The second commit adds the centralized resolver and destination implementation.

Validation

CI should run the Bonsplit Swift package tests. No local Xcode build or XCUITest was run on the development Mac.


Summary by cubic

Fixes drag-and-drop hit testing for wide tab titles by replacing per-tab SwiftUI delegates with one AppKit drop destination and tightening passive hit-test gating. Restores same-/cross-pane moves, keeps the trailing indicator visible in full-width mode, and addresses cmux issue #10033.

  • Bug Fixes

    • Replaced per-tab delegates with a pane-wide AppKit destination (TabBarDropDestinationNSView + TabBarDropHandler) for consistent hit testing across the strip and trailing chrome.
    • Added TabDropIndexResolver to compute midpoint-based insertion indices, stabilizing routing with wide titles and translated/partially visible strips; routed same-/cross-pane moves through shared controller APIs and kept the trailing indicator visible.
    • Tightened passive hit-test gating: passive events (mouseMoved, cursorUpdate, leftMouseUp) only capture with a live local drag; leftMouseDragged is always accepted for external Finder/cross-process drags to avoid stale pasteboard coverage.
    • Expanded tests to cover the final midpoint boundary and stale pasteboard gating; tests now skip if the native destination or pane tab hit region isn’t mounted.
  • Refactors

    • Centralized drop logic out of individual tab views; BonsplitController.reorderTab reports only real reorders.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved tab drag-and-drop behavior so stale drag data no longer interferes with regular mouse interactions.
    • External and cross-process tab drags are now handled correctly.
    • Improved drop targeting across wide, short, and trailing tab-bar areas.
  • Tests

    • Added coverage for tab insertion positioning and drag-event handling.
    • Updated drop-region checks for environments where tab-bar elements are not rendered.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The AppKit tab-bar drop destination now ignores stale pasteboard types during passive events, accepts valid external dragged events, and requires active local drag ownership for other event types. Tests cover hit-testing, wide-tab insertion positions, and native drop-region availability.

Changes

Tab-bar drag handling

Layer / File(s) Summary
Drag hit-testing rules
Sources/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift, Tests/BonsplitTests/TabWideTitleDragTests.swift
Hit-testing now requires an active local tab drag for passive, cursor, system, and mouse-up events. External and cross-process drags remain accepted during .leftMouseDragged.
Insertion regression coverage
Tests/BonsplitTests/TabWideTitleDragTests.swift, Tests/BonsplitTests/BonsplitTests.swift
Tests cover midpoint insertion indices across wide tabs. The drop test skips when either required native drop element is unavailable.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.13% 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
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: fixing tab drag hit testing for wide titles.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-10033-wide-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.

@austinywang
austinywang changed the base branch from main to fix-close-pane-focus-steal August 12, 2026 05:34
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Caution

CodeRabbit couldn't update its existing comment. The review summary may be out of date.

Error details
putComment timed out

@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

🧹 Nitpick comments (1)
Tests/BonsplitTests/BonsplitTests.swift (1)

2573-2584: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Include the tab hit region in the skip condition.

Line 2573 skips the test only when the trailing drop destination is absent. Line 2579 uses XCTUnwrap(tabPoint), so the test fails instead of skipping when the SwiftUI runtime mounts the destination but not the tab hit region. Both views depend on the same runtime mounting behavior. Move the tabPoint check into the skip condition to keep the failure mode consistent.

💚 Proposed change
-        guard dropDestination(at: trailingEmptyPoint) != nil else {
+        guard dropDestination(at: trailingEmptyPoint) != nil, tabPoint != nil else {
             throw XCTSkip(
-                "This SwiftUI runtime did not mount the native tab-bar drop destination"
+                "This SwiftUI runtime did not mount the native tab-bar drop destination or the pane tab hit region"
             )
         }
🤖 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 `@Tests/BonsplitTests/BonsplitTests.swift` around lines 2573 - 2584, Update the
setup guard around dropDestination(at:) to also require a non-nil tabPoint,
skipping the test when either the trailing drop destination or tab hit region is
not mounted. Remove the later XCTUnwrap(tabPoint) and continue using the safely
unwrapped tab point for coordinate conversion and registry validation.
🤖 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/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift`:
- Around line 29-41: Update hitTest(_:) in the tab-bar drop destination so it
returns self only when an active drag session is present, requiring
handler?.hasLiveTabDrag to be true in addition to the existing
shouldCaptureHitTest and operation checks. Ensure stale drag pasteboard types
cannot capture mouseMoved or cursorUpdate when no live tab drag exists.

---

Nitpick comments:
In `@Tests/BonsplitTests/BonsplitTests.swift`:
- Around line 2573-2584: Update the setup guard around dropDestination(at:) to
also require a non-nil tabPoint, skipping the test when either the trailing drop
destination or tab hit region is not mounted. Remove the later
XCTUnwrap(tabPoint) and continue using the safely unwrapped tab point for
coordinate conversion and registry validation.
🪄 Autofix

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 3a25cbf8-3fb9-46f6-b3fe-ca9d13678dc8

📥 Commits

Reviewing files that changed from the base of the PR and between e150c3b and 3ec1fb8.

📒 Files selected for processing (11)
  • Sources/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift
  • Sources/Bonsplit/Internal/Views/TabBarDropDestinationView.swift
  • Sources/Bonsplit/Internal/Views/TabBarDropHandler.swift
  • Sources/Bonsplit/Internal/Views/TabBarView.swift
  • Sources/Bonsplit/Internal/Views/TabDropIndexResolver.swift
  • Sources/Bonsplit/Public/BonsplitController.swift
  • Tests/BonsplitTests/BonsplitTests.swift
  • Tests/BonsplitTests/TabBarDropHandlerTests.swift
  • Tests/BonsplitTests/TabDropDelegateSamePaneFallbackTests.swift
  • Tests/BonsplitTests/TabDropIndexResolverTests.swift
  • Tests/BonsplitTests/TabWideTitleDragTests.swift
💤 Files with no reviewable changes (1)
  • Tests/BonsplitTests/TabDropDelegateSamePaneFallbackTests.swift

Comment thread Sources/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift
@austinywang
austinywang changed the base branch from fix-close-pane-focus-steal to main August 12, 2026 06:22

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

🧹 Nitpick comments (1)
Tests/BonsplitTests/TabWideTitleDragTests.swift (1)

20-30: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add probes for the final tab midpoint.

The final tab midpoint is x=1070. The current probes do not test the interval from x=936 to x=1070. A resolver that returns index 4 too early can pass this test. Add probes immediately before and at x=1070.

Proposed test addition
             (875.9, 2),
             (936, 3),
+            (1_069.9, 3),
+            (1_070, 4),
             (1_200, 4),
🤖 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 `@Tests/BonsplitTests/TabWideTitleDragTests.swift` around lines 20 - 30, Extend
the probes array in the tab drag test with cases immediately before the final
tab midpoint and at x=1070, expecting the index before the midpoint and the
final index respectively.
🤖 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.

Nitpick comments:
In `@Tests/BonsplitTests/TabWideTitleDragTests.swift`:
- Around line 20-30: Extend the probes array in the tab drag test with cases
immediately before the final tab midpoint and at x=1070, expecting the index
before the midpoint and the final index respectively.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ea34bad-1e3d-4614-a598-9861ef08eea4

📥 Commits

Reviewing files that changed from the base of the PR and between 3ec1fb8 and 7ad5cf3.

📒 Files selected for processing (3)
  • Sources/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift
  • Tests/BonsplitTests/BonsplitTests.swift
  • Tests/BonsplitTests/TabWideTitleDragTests.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/Bonsplit/Internal/Views/TabBarDropDestinationNSView.swift
  • Tests/BonsplitTests/BonsplitTests.swift

@austinywang
austinywang merged commit 5b61b18 into main Aug 12, 2026
6 checks passed
@austinywang
austinywang deleted the issue-10033-wide-tab-drag branch August 12, 2026 06:42
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