Skip to content

Fix floating tab drag ownership and fallback - #192

Open
lawrencecchen wants to merge 15 commits into
mainfrom
feat/floating-tab-drag-owner
Open

lawrencecchen wants to merge 15 commits into
mainfrom
feat/floating-tab-drag-owner

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 22, 2026 •

Copy link
Copy Markdown

Standard tab bars use the SwiftUI item-provider drag as their primary owner. Hosts that need AppKit tracking in custom window chrome can opt into manualTabReorderFallbackEnabled.

The fallback observes the same mouse sequence, yields when the matching item-provider drag starts, and defers recovery after cancellation. Recovery mutates only when the source pane and its exact tab order are unchanged, preventing a successful SwiftUI drop from being applied twice. cmux Floating Docks enable this host-scoped fallback through manaflow-ai/cmux#8351.

Empty tab chrome uses one screen-space window-drag session in standard and minimal modes. The actual AppKit drag-zone view accepts the first mouse event, so an inactive child window starts moving on its first gesture while tab hit regions retain tab-drag ownership.

Regression history is preserved as test-only commits followed by fixes for event ownership, host-scoped fallback behavior, cancelled item-provider recovery, and inactive-window first-mouse routing. The focused inactive-window regression passes; the prior full suite passed 204 tests.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Improved tab reordering behavior so manual drag handling is limited to minimal-mode tab bars when reordering is enabled.
    • Prevented unintended manual drag handling in non-minimal tab bar layouts.
  • Tests

    • Added coverage for tab reordering behavior across minimal and non-minimal modes.

Summary by cubic

Standard tab bars now use SwiftUI item‑provider drags as the single owner with a safe AppKit fallback. Empty tab chrome uses a screen‑space window‑drag session across modes and accepts the first click in inactive windows so the initial drag always works; the leading inset now draws a separator at the window‑chrome boundary.

  • New Features

    • Added manualTabReorderFallbackEnabled to BonsplitConfiguration to opt into the fallback outside minimal mode.
    • Added BonsplitWindowDragSession and unified empty tab‑bar window dragging across standard and minimal modes.
    • Mode‑aware double‑clicks on empty chrome: new tab in standard mode, titlebar action in minimal mode.
    • Draw a separator at the end of the leading tab‑bar inset when the pane owns window‑leading chrome.
  • Bug Fixes

    • Centralized reorder ownership with TabBarManualReorderPolicy, including yield‑to‑item‑provider and a deferred fallback that applies only when the source pane is unchanged.
    • Replaced native performDrag(with:) with BonsplitWindowDragSession for empty tab chrome in all modes, fixing child‑window dragging and standard‑mode empty‑chrome dragging, and preserving the first drag on inactive windows by accepting the first mouse.

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

Review in cubic

@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Manual reorder tracking now installs only when tab reordering is allowed and the tab bar is in minimal mode. Unit tests cover enabled and disabled policy combinations.

Changes

Manual reorder policy

Layer / File(s) Summary
Gate manual reorder tracking by mode
Sources/Bonsplit/Internal/Views/TabBarView.swift, Tests/BonsplitTests/EmbeddedConfigurationTests.swift
Adds TabBarManualReorderPolicy, applies it to tracker installation, and tests the three relevant configuration combinations.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: austinywang

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 matches the main change: resolving tab drag ownership and retaining a minimal-mode fallback.
✨ 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 feat/floating-tab-drag-owner

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.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR narrows the AppKit manual tab-reorder tracker so it only fires in minimal mode, leaving standard tab bars with a single drag owner (the SwiftUI item-provider). The change is small, well-motivated, and covered by a targeted unit test.

  • TabBarManualReorderPolicy — new enum-as-namespace that encodes the install predicate (allowsTabReordering && isMinimalMode), replacing the previous bare allowTabReordering guard in manualReorderTracker.
  • testManualTabReorderFallbackOnlyOwnsMinimalModeDrags — exercises three of the four input combinations; the fourth (false, false → false) is trivially correct and omitted without impact.

Confidence Score: 5/5

Safe to merge — the change is a targeted, well-tested narrowing of the AppKit drag tracker to minimal mode only.

The fix is a single boolean predicate change with a clear, accurate doc comment explaining why minimal mode needs the AppKit path while standard tab bars do not. The unit test directly verifies the three meaningful input combinations, and 202 passing tests provide broader regression confidence. No data flow changes, no new async paths, and no API surface alterations.

No files require special attention.

Important Files Changed

Filename Overview
Sources/Bonsplit/Internal/Views/TabBarView.swift Adds TabBarManualReorderPolicy enum and tightens the manualReorderTracker guard to require isMinimalMode; logic is correct and well-documented.
Tests/BonsplitTests/EmbeddedConfigurationTests.swift New test covers three out of four policy combinations; the missing (false, false) case is trivially correct and has no meaningful impact.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Tab Bar Mouse Event] --> B{isMinimalMode?}
    B -- Yes --> C{allowTabReordering?}
    B -- No --> D[SwiftUI Item-Provider Drag\nSole owner]
    C -- Yes --> E[AppKit Manual Reorder Tracker\n+ SwiftUI Item-Provider Drag]
    C -- No --> F[No drag handling]
Loading

Reviews (1): Last reviewed commit: "Give standard tab drags one event owner" | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3bc2c9dac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +2162 to +2166
TabBarManualReorderPolicy.shouldApplyFallback(
sourceTabId: session.sourceTab.id,
activeDragTabId: splitViewController?.activeDragTab?.id,
draggingTabId: splitViewController?.draggingTab?.id
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope fallback yielding to the current gesture

If an item-provider drag for this tab is canceled without reaching the normal drop clear path, the controller can retain activeDragTab/draggingTab for that tab; this guard then treats that stale state as ownership of every later manual fallback gesture for the same tab. Because the removed defer no longer clears matching controller drag state, the fallback keeps returning before reordering and never recovers until some unrelated drop path clears the stale state. Consider tying the yield to drag state created during this mouse sequence, or clearing stale matching state before yielding.

Useful? React with 👍 / 👎.

@lawrencecchen lawrencecchen changed the title Give standard tab drags one event owner Fix floating tab drag ownership and fallback Jul 23, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

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