Skip to content

Route browser-pane file drops by intent so they never silently become previews - #7634

Merged
austinywang merged 10 commits into
mainfrom
issue-7632-browser-file-drop-upload
Jul 9, 2026
Merged

austinywang merged 10 commits into
mainfrom
issue-7632-browser-file-drop-upload

Conversation

@austinywang

@austinywang austinywang commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7632

Problem

Dragging an image from Finder onto a web page in a workspace browser pane sometimes opened the file in a cmux file-preview pane instead of delivering the drag to the page as an HTML5 upload — intermittently, under the default file-drop setting where the expected routing was already "forward to the page".

Root cause: the pane routed a file-URL drop to the hosted WKWebView only when hostedWebViewForFileDrop(at:) resolved a live webview at that instant, feeding availability into resolvedFileDropBehavior's guard canDropAsText else { return .preview }. The webview is legitimately unresolvable in live states — discarded by BrowserHiddenWebViewDiscardManager, mid-reparent through the browser window portal, stale frame — so the drop silently degraded to a preview. shouldCaptureHitTesting additionally hardcoded canDropAsText: true, so the pane could claim a drop as an upload during hit-testing and execute it as a preview.

Fix (class, not symptom)

Intent is now pure; delivery never rewrites intent.

  • New BrowserPaneFileDropRouting.disposition(pasteboardTypes:modifierFlags:isDockHosted:defaultBehavior:) — a pure seam with no webview-availability input — decides .forwardToPage vs .previewInWorkspace. Hit-testing, update, prepare, and perform all branch on this one function, eliminating the claim/perform divergence. The existing Dock rule (file URL over Dock page content always forwards to the page) is centralized in the seam.
  • Delivery on .forwardToPage: slot lookup, then a same-pane-guarded BrowserWindowPortalRegistry.webViewAtWindowPoint fallback (covers mid-reparent/stale-frame). A discarded webview is asked to restoreDiscardedWebViewIfNeeded once per drag session while hovering. If no webview resolves, the drop is refused (prepare/perform return false) — never reinterpreted as a preview.
  • Preserved paths: Shift inversion, preview-default routing, pane-edge split-zone drops, Dock live-surface transfer guard, tab drags (Dragging tabs over browser triggers file upload drop zone instead of panel rearrange #921), cmux file-explorer preview transfers, focus-after-drop.

Two-commit regression-test structure

  1. Commit 1 adds the regression tests only — the no-webview refusal tests fail on pre-fix code (CI red on that commit proves they catch the bug).
  2. Commit 2 adds the fix plus pure-seam unit tests (default → page upload regardless of availability; Shift inversion; Dock always-forward; file-URL payload gate).

Verification

  • python3 scripts/swift_file_length_budget.py passes with no TSV changes (BrowserPaneDropTargetView.swift shrank 526 → 489; new files well under 500).
  • ./scripts/lint-pbxproj-test-wiring.sh passes (new test file wired into cmuxTests).
  • Tagged compile-only Debug build succeeds; no new Swift warnings.
  • Localization audit: no user-facing strings added or changed (verified via rg over the changed Swift files — routing/delivery logic and tests only).

🤖 Generated with Claude Code


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


Note

Medium Risk
Touches drag routing, WKWebView delivery, and navigation policy cancellation; behavior is heavily regression-tested but edge cases around portal reparenting and async WebKit navigation remain possible.

Overview
Fixes intermittent Finder-to-browser drops opening cmux file previews instead of HTML5 uploads (#7632) by separating intent from delivery.

Routing: New BrowserPaneFileDropRouting.disposition drives hit-test, drag update, prepare, and perform. Unmodified file drags over browser page content go to the hosted page; Shift opens a workspace preview. Dock-hosted panes always forward to the page. The global file-drop default no longer steers browser panes via webview availability.

Delivery: Forward-to-page uses slot hit-test, then a same-pane BrowserWindowPortalRegistry fallback, one-shot restoreDiscardedWebViewIfNeeded per drag, and refuses the drop when no webview resolves (no silent preview). Drops over non-page areas of a live webview (e.g. docked inspector chrome) are refused via geometry checks.

WebKit fallback: BrowserFileDropNavigationGuard records successful page deliveries; BrowserNavigationDelegate cancels matching main-frame file:// .other navigations and opens a file-preview split via handleDroppedFileNavigation, including all files from multi-file drops.

Overlay / polish: FileDropOverlayView avoids premature draggingExited during prepare/perform/conclude; records guard on overlay webview drops. Hint badge over web content suggests Shift for preview. Small file moves (BrowserPanel+InteractiveModalHostWindow, WKWebView+CmuxPrintOperation). Regression tests in BrowserPaneFileDropUploadRegressionTests.

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


Summary by cubic

Routes browser-pane file drops by intent so unmodified drags always reach the web page (native upload) regardless of the default setting; Shift explicitly opens a preview. Intercepts WebKit file:// fallback so dropped files open in a preview split instead of overwriting the page, preserves multi-file drops, and fixes an overlay hand-off that cancelled DOM drops. Fixes #7632.

  • Bug Fixes

    • Added BrowserPaneFileDropRouting.disposition(...) and wired hit-test/update/prepare/perform through it to avoid claim/perform mismatches.
    • Delivery now falls back to BrowserWindowPortalRegistry.webViewAtWindowPoint, requests a one-time webview restore during a drag, and refuses drops when no webview resolves or when the point is outside a live webview’s bounds.
    • Browser panes are native-first: the default file-drop setting no longer affects browser panes; Shift remains the explicit preview gesture. Dock-hosted panes always forward file-URL drops to the page.
    • Introduced BrowserFileDropNavigationGuard to record file-drop deliveries and cancel matching main-frame .other file:// navigations in BrowserNavigationDelegate, opening a preview split instead of letting the page be overwritten. Multi-file drops now preview all dropped files.
    • Scheduled a coalesced TTL sweep so drop-guard records expire without needing a later guard call.
    • Prevented the window overlay from cancelling in-flight WebKit drag sessions by exiting only targets the drag actually left; also arm the drop guard only for handled deliveries.
  • Tests

    • Added regression tests for: native-first routing across default settings, refusal over non-page areas of a live webview, no-webview refusal, hit-test vs prepare agreement, Shift inversion, Dock always-forward, file-URL payload gating, dock-status laziness, navigation-guard cancellation, multi-file fallback preview preservation, and drop-guard expiry.
    • Converted the regression suite to Swift Testing with serialized execution.

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

Review in cubic

Summary by CodeRabbit

  • New Features
    • Updated browser-pane file drag-and-drop routing to support page-forwarding vs in-workspace preview based on file-URL payloads and the Shift modifier, with consistent handling for dock-hosted panes.
    • Added improved handling for dropped-file fallback navigations so they can be intercepted and consumed reliably.
  • Bug Fixes
    • Improved hit-testing and drag lifecycle behavior when the destination web view can’t be resolved, including correct refusal over non-page regions.
  • Tests
    • Added regression tests for upload routing, drag callback ordering, and fallback navigation guarding.

austinywang and others added 2 commits July 8, 2026 10:30
…ading to preview

Dragging a file from Finder onto a web page in a workspace browser pane must
deliver the drag to the page (HTML5 upload) under the default file-drop
setting. When the pane's hosted WKWebView is transiently unresolvable
(discarded by the hidden-webview manager, mid-reparent through the browser
window portal), the drop is silently converted into a cmux file-preview
instead. These tests cover: default setting + webview available routes to the
page; webview unavailable must not be claimed/accepted as a preview; hit-test
claim and prepare-time routing must agree; Shift inversion still previews.

The no-webview refusal tests fail without the fix (next commit) so CI proves
they catch the bug.

Issue: #7632

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… previews

A file URL dragged over a workspace browser pane's page content was routed to
the hosted WKWebView only when the webview was resolvable at that instant:
resolvedFileDropBehavior's `guard canDropAsText else { return .preview }`
silently converted the drop into a cmux file-preview whenever
hostedWebViewForFileDrop(at:) returned nil — a legitimately transient state
(webview discarded by BrowserHiddenWebViewDiscardManager, mid-reparent through
the browser window portal, stale frame). shouldCaptureHitTesting additionally
computed the behavior with a hardcoded `canDropAsText: true`, so the pane
could claim a drop as an upload during hit-testing and execute it as a
preview.

Fix the class, not the symptom: introduce BrowserPaneFileDropRouting, a pure
disposition seam (pasteboard payload, modifier flags, dock-hosted flag,
file-drop default setting) with no webview-availability input. Hit-testing,
update, prepare, and perform all branch on the same seam, eliminating the
claim/perform divergence, and the existing Dock rule (file URL over Dock page
content always forwards to the page) is centralized there.

Webview availability is now purely a delivery concern: delivery falls back
from the slot lookup to a same-pane BrowserWindowPortalRegistry lookup, a
discarded webview is asked to restore once per drag session while hovering,
and if no webview resolves the drop is refused (prepare/perform return false)
— never reinterpreted as a preview. Shift inversion, preview-default routing,
pane-edge split zones, Dock live-surface transfers, tab drags, and cmux
file-explorer preview transfers keep their existing paths.

Closes #7632

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 8, 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 Jul 9, 2026 9:44am
cmux-staging Building Building Preview, Comment Jul 9, 2026 9:44am

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

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

This PR adds file-drop disposition routing for browser panes, updates drag handling and fallback navigation to use it, wires in new panel utilities and print support, and adds regression coverage for routing, delivery, and guard behavior.

Changes

Browser file-drop routing and delivery

Layer / File(s) Summary
Routing contract and delivery guard
Sources/BrowserPaneFileDropRouting.swift, Sources/BrowserFileDropNavigationGuard.swift
Adds the file-drop disposition resolver, the navigation delivery guard, and the browser-pane helpers for dock-hosted detection, delivery targeting, drag-state updates, and restore requests.
Drop target and overlay flow
Sources/BrowserPaneDropTargetView.swift, Sources/FileDropOverlayView.swift, Sources/FileDropOverlayViewHitTesting.swift
Updates browser-pane hit-testing, drag preparation, perform-time web-view selection, drag cleanup, overlay delivery recording, and hint-badge behavior for file-drop routing.
Fallback navigation and panel integration
Sources/Panels/BrowserNavigationDelegate.swift, Sources/Panels/BrowserPanel.swift, Sources/Panels/BrowserPanel+InteractiveModalHostWindow.swift, Sources/Panels/WKWebView+CmuxPrintOperation.swift
Adds navigation interception for file-drop fallback, moves interactive modal window helpers into a shared file, and adds the WKWebView print-operation extension.
Project wiring and regression tests
cmuxTests/BrowserPaneFileDropUploadRegressionTests.swift, cmux.xcodeproj/project.pbxproj
Wires the new sources and tests into the Xcode project and adds regression coverage for routing, fallback navigation, and navigation-guard behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#3684: Related file-drop routing changes for Shift/destination behavior in the same browser-pane drag-and-drop flow.
  • manaflow-ai/cmux#3782: Related browser-pane hit-testing and portal routing logic around drag target eligibility.
  • manaflow-ai/cmux#4245: Related discard/restore behavior used by the new browser-pane file-drop restore path.

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors, 2 warnings)

Check name Status Explanation Resolution
Cmux Architecture Rethink ❌ Error PR adds BrowserFileDropNavigationGuard.shared, a mutable singleton side channel linking drag delivery to later navigation policy. Keep the correlation in one owner (e.g. BrowserPanel/WebView coordinator) and pass the drop metadata through the existing callback path instead of a global guard.
Cmux No Ambient Global State ❌ Error New ambient globals were added: BrowserFileDropNavigationGuard.shared (BrowserFileDropNavigationGuard.swift:7) plus file-scope helper funcs in BrowserPanel+InteractiveModalHostWindow.swift:4-17. Move the guard onto an owning type like BrowserPanel and inject it into the drop target/delegate; make the window helpers private/fileprivate or instance methods, and avoid the new caseless routing namespace.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers the bug, fix, and verification, but it omits several required template sections and headings. Add the required Summary, Testing, Demo Video, Review Trigger, and Checklist sections, or explicitly mark any inapplicable items.
✅ Passed checks (21 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes match #7632: routing is intent-based, hit-testing and perform stay aligned, Shift still previews, no-webview cases refuse, and regression tests cover the bug.
Out of Scope Changes check ✅ Passed The added files and refactors are tied to file-drop routing, navigation handling, or test support, so no clear unrelated scope creep stands out.
Cmux Swift Actor Isolation ✅ Passed No new actor-isolation debt: shared drag/navigation state is @MainActor UI code, and the new pure routing enum stays nonisolated.
Cmux Swift Blocking Runtime ✅ Passed PASS: The new production Swift code adds no semaphores, sleeps, asyncAfter, main-sync, or locks; only test-only RunLoop/layout scaffolding appears in the regression suite.
Cmux Browser Automation Off-Main ✅ Passed PASS: this PR only touches drag/drop and browser navigation helpers; it doesn't change browser socket commands or TerminalController/ControlCommandExecutionPolicy, so the off-main rule isn't implic...
Cmux Expensive Synchronous Load ✅ Passed PASS: The HEAD diff only touches file-drop routing/navigation files; no agent-history/session-store/transcript/jsonl/RestorableAgentSessionIndex.load changes appear.
Cmux Cache Substitution Correctness ✅ Passed Only transient drag/navigation state is cached; it has fresh lookup fallback plus TTL/identity checks, and no persistence/history/undo/snapshot path is affected.
Cmux No Hacky Sleeps ✅ Passed The PR only changes Swift sources and an Xcode project file; no covered non-Swift runtime scripts were touched, and no new fixed waits were added.
Cmux Algorithmic Complexity ✅ Passed No new nested scans or unbounded per-event rescans were introduced; the added loops are single-pass over small drag-state data or an existing window lookup fallback.
Cmux Swift Concurrency ✅ Passed Touched Swift changes are synchronous/callback-based; no new DispatchQueue, Task, Combine state, or custom async completion APIs were introduced.
Cmux Swift @Concurrent ✅ Passed New file-drop code is synchronous or @MainActor; no changed nonisolated async helper lacks @concurrent, and no invalid @concurrent annotation was added.
Cmux Swift File And Package Boundaries ✅ Passed PASS: New files are small (18-120 lines), app-target glue is focused, and only incidental edits hit existing oversized files; no clear package-boundary split is missing.
Cmux Swiftpm Lockfiles ✅ Passed PR only adds Swift sources and source-file refs; no Package.swift/.gitignore/workflow changes or SwiftPM package-reference edits, so no Package.resolved diff was required.
Cmux Swift Logging ✅ Passed Diff adds only DEBUG-gated cmuxDebugLog in drop paths; no new production print/NSLog/Logger usage or file-scoped Logger constants.
Cmux User-Facing Error Privacy ✅ Passed Diff adds only internal debug/logging and routing code; no user-facing error, alert, or recovery copy exposing vendor/internal details.
Cmux Full Internationalization ✅ Passed No new user-facing text or locale assets were added; the diff is routing logic, debug logging, and tests only.
Cmux Swiftui State Layout ✅ Passed The diff is AppKit drag-routing/tests only; no new SwiftUI state/layout patterns (@Observable, @Published, GeometryReader, render-time mutation) were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: the PR only adds window-selection helpers and a print-operation wrapper; no new standalone NSWindow/NSPanel/WindowGroup or close-shortcut registration was introduced.
Cmux Source Artifacts ✅ Passed Changed paths are intentional source, test, and Xcode project files; no logs, caches, build output, temp dirs, or other artifacts were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Only production routing helpers were added; the lone new #if DEBUG gates a log line, and no new ForTesting/TestHook or test-only accessors appear in Sources.
Title check ✅ Passed The title is concise and accurately summarizes the main behavior change around routing browser-pane file drops.
✨ 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-7632-browser-file-drop-upload

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 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes browser-pane file drops route by user intent instead of web-view availability. The main changes are:

  • Adds a browser-pane routing seam for page upload versus workspace preview.
  • Refuses page-forwarded drops when no matching web view can receive them.
  • Records delivered drops so WebKit file fallback navigations can open workspace previews.
  • Updates overlay drop handling and adds regression tests for the browser file-drop flow.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
Sources/BrowserPaneDropTargetView.swift Routes browser-pane file drops through the new disposition flow and records successful page deliveries.
Sources/BrowserPaneFileDropRouting.swift Adds the browser-pane file-drop routing decision and web-view delivery helpers.
Sources/BrowserFileDropNavigationGuard.swift Adds a main-actor guard for matching delivered drops to WebKit fallback file navigations.
Sources/Panels/BrowserNavigationDelegate.swift Cancels matched file-drop fallback navigations and hands them to the preview flow.
Sources/FileDropOverlayView.swift Updates overlay drag target cleanup and records successful web-view file-drop deliveries.
cmuxTests/BrowserPaneFileDropUploadRegressionTests.swift Adds tests for browser-pane upload routing, refusal behavior, modifier handling, and fallback navigation guarding.

Reviews (8): Last reviewed commit: "Deliver browser drops without cancelling..." | Re-trigger Greptile

Comment on lines +12 to +13
/// Routes browser-pane file drops by intent only: intent never depends on transient webview
/// availability; delivery failure refuses the drop instead of reinterpreting it.

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 Ambient Routing Namespace

This adds BrowserPaneFileDropRouting as a production static-only namespace under Sources/. The repo rule asks new routing behavior to live on an owning, constructable type unless it is a private file helper, so this module-level surface can keep growing as global drop-routing state instead of being scoped and injected.

Rule Used: Flag new ambient global state in production Swift:... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The static-only shape here is intentional and matches the sibling policy types this function composes with in this exact drop-routing surface: DragOverlayRoutingPolicy and BrowserPaneDropRouting are both caseless enums exposing pure static decision functions, and disposition(...) is consumed alongside them. It is a pure function whose dependencies are all parameters (pasteboard types, modifier flags, dock-hosted flag, injected defaultBehavior), so an owning constructable type would carry zero constructor-injected state. Migrating the whole drop-routing policy family to instance services is a coordinated follow-up beyond this bug fix's scope; introducing a mixed pattern for one member of the family would be worse than keeping it consistent.

— Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Intentional: BrowserPaneFileDropRouting.disposition is a pure decision function — every input (pasteboard types, modifier flags, dock-hosted status) arrives as a parameter, so there is no state or dependency to constructor-inject; an instance would be meaningless. It follows the established sibling convention in this exact layer (DragOverlayRoutingPolicy in Sources/DragOverlayRoutingPolicy.swift, the caseless enum this function's inputs come from), and the stateful side of the feature does live on owning instances (BrowserFileDropNavigationGuard, BrowserPaneDropTargetView). Keeping the decision free of view/AppKit state is what makes the drop-order regression tests possible.

— Claude Code

@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/BrowserPaneFileDropRouting.swift`:
- Around line 5-10: Mark the pure file-drop routing types as nonisolated so they
do not inherit MainActor isolation under Swift 6. Update
`BrowserPaneFileDropDisposition` and the stateless decision path in
`BrowserPaneFileDropRouting.disposition(...)` to be explicitly usable off the
main actor, and verify any helpers they call such as
`DragOverlayRoutingPolicy.hasFileURL`,
`DragOverlayRoutingPolicy.resolvedFileDropBehavior`, and
`FileDropBehaviorSettings.behavior()` are also nonisolated or adjusted
accordingly so the whole chain compiles cleanly. Keep the change limited to
these value/decision symbols and avoid introducing actor-bound state.
🪄 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: 1799d998-82c3-4903-bf29-1a575eef51f7

📥 Commits

Reviewing files that changed from the base of the PR and between dbd3ff2 and 56fe3ff.

📒 Files selected for processing (4)
  • Sources/BrowserPaneDropTargetView.swift
  • Sources/BrowserPaneFileDropRouting.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserPaneFileDropUploadRegressionTests.swift

Comment thread Sources/BrowserPaneFileDropRouting.swift Outdated
Comment thread Sources/BrowserPaneFileDropRouting.swift
austinywang and others added 3 commits July 8, 2026 10:49
The registry fallback in webViewForFileDropDelivery hit-tests the whole
slot container, while the primary hostedWebViewForFileDrop path requires
the drop point inside the hosted web view's own frame. When the web view
is live but does not fill the slot (a docked Web Inspector splits the
slot with WebKit companion views), a file dropped over the non-page area
was forwarded into the page upload path instead of being refused.

Keep the fallback for the detached-web-view transient gap it exists for,
but when the resolved web view is attached to this window, require the
drop point inside its bounds, mirroring the primary path. Adds a
regression test that drives the real portal registry with a live web
view occupying half its slot and asserts a drop over the other half is
refused and never reaches the page.

Found by structured review on PR #7634.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shouldCaptureHitTesting ran AppDelegate.dockForPane (an all-windows x
workspaces x docks ownership sweep) as an eagerly evaluated argument on
every hitTest call, and allowsPaneDropHitTesting admits pointer-hover
events, so mouse moves over a browser pane paid for the sweep. The value
cannot change the capture result: a file-URL payload yields a disposition
either way (shouldCaptureFileDrop true), and without a file URL the
disposition is nil either way. Drop the parameter and hit-test with
isDockHosted: false; prepare/perform still resolve the real dock-aware
disposition.

Also make disposition's isDockHosted an autoclosure evaluated only after
the file-URL guard, so drag callbacks for non-file payloads (tab drags,
preview transfers) no longer pay for the dock sweep either, and add a
regression test pinning the laziness contract.

Found by structured review on PR #7634.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Repo testing policy requires Swift Testing for new non-UI test files:
convert BrowserPaneFileDropUploadRegressionTests from XCTestCase to a
@mainactor @suite(.serialized) struct (serialized because the drag
lifecycle tests flow the file-drop default through the production
UserDefaults.standard read, matching LastSurfaceClosePreferenceTests).
Assertions map 1:1; the harness classes are unchanged.

Also nest the two-case Disposition result enum inside
BrowserPaneFileDropRouting, its only producer, tightening the file to a
single top-level type.

Found by structured review on PR #7634.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
austinywang and others added 2 commits July 8, 2026 18:30
…first

Browser page content should receive every unmodified file drag natively
(like Safari/Chrome), independent of the fileDrop.defaultBehavior
setting; Shift is the explicit cmux preview gesture. Red pre-fix:
previewDefaultStillRoutesBrowserDropToHostedPage (preview default does
not forward to the hosted web view), dispositionDefaultsToPageUpload-
RegardlessOfWebViewAvailability (preview + no Shift), and
dispositionShiftAlwaysRoutesToPreview (preview + Shift).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…page

A file drag over browser page content now always forwards to the hosted
WKWebView (Safari/Chrome-style page delivery) regardless of the
fileDrop.defaultBehavior setting, which continues to govern terminals
and editors. Shift is the explicit cmux gesture: it skips the page and
opens the file-preview/split flow directly.

When the page does not consume a forwarded drop, WebKit falls back to
navigating the main frame to the dropped file:// URL, destroying the
page (confirmed in the field: performAsWebView handled=1 followed 49ms
later by a main-frame .other file:// navigation). Every WKWebView
file-drop delivery now records into BrowserFileDropNavigationGuard
(weak webview identity, 5s TTL, consume-once), and
BrowserNavigationDelegate cancels a matching main-frame .other file://
navigation, opening the file in a cmux preview split beside the browser
pane instead. Unmatched file:// navigations (omnibar, links,
back/forward, Dock and popup webviews) fall through natively.

WKWebView.cmuxRunPrintOperation and the interactive-modal host-window
fallback are extracted verbatim into satellite files to keep
BrowserNavigationDelegate.swift and BrowserPanel.swift within their
file-length budgets.

Closes #7632

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread Sources/BrowserPaneDropTargetView.swift Outdated

@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/Panels/BrowserNavigationDelegate.swift`:
- Around line 238-251: The drop-fallback navigation in
BrowserNavigationDelegate’s decidePolicy branch is only cancelled after
handleDroppedFileNavigation?(url) succeeds, so a routing failure can still fall
through to .allow. Update the logic around
BrowserFileDropNavigationGuard.isDropFallbackNavigation and
BrowserFileDropNavigationGuard.shared.consumeDropNavigation so any recognized
drop fallback is cancelled as soon as it is detected, regardless of whether
handleDroppedFileNavigation returns false.
🪄 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: a1b2aabf-3fe6-4817-ba63-5395c7de6c6a

📥 Commits

Reviewing files that changed from the base of the PR and between 6b15c38 and b675632.

📒 Files selected for processing (11)
  • Sources/BrowserFileDropNavigationGuard.swift
  • Sources/BrowserPaneDropTargetView.swift
  • Sources/BrowserPaneFileDropRouting.swift
  • Sources/FileDropOverlayView.swift
  • Sources/FileDropOverlayViewHitTesting.swift
  • Sources/Panels/BrowserNavigationDelegate.swift
  • Sources/Panels/BrowserPanel+InteractiveModalHostWindow.swift
  • Sources/Panels/BrowserPanel.swift
  • Sources/Panels/WKWebView+CmuxPrintOperation.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/BrowserPaneFileDropUploadRegressionTests.swift

Comment thread Sources/Panels/BrowserNavigationDelegate.swift
…-file drop

WebKit's fallback main-frame navigation names only one file of a multi-file
drop, so the drop guard now returns the full ordered URL list it recorded
(consume-once unchanged) and the navigation delegate hands all of them to
handleExternalFileDrop, matching the normal external file-drop path instead
of silently previewing just the navigated file.

Found by structured review (codex P2) on PR #7634.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 630a3dd. Configure here.

Comment thread Sources/Panels/BrowserNavigationDelegate.swift
austinywang and others added 2 commits July 8, 2026 18:59
A successful page upload never triggers the fallback navigation, so the
guard's opportunistic prune-on-call left recorded file paths retained until
some future drop. Schedule one coalesced, cancellable expiry sweep per drop
batch (at most one pending task, alive for timeToLive plus a small margin)
so records are released on their TTL. TTL is injectable so the regression
test can observe expiry through @testable state without a production seam.

Found by structured review (codex P1) on PR #7634.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
An automated real-drag repro (probe page with capture-phase listeners
preventDefaulting every drag event) showed the DOM never receives drop:
it sees dragenter, dragover, then dragleave at drop time. The window
file-drop overlay's prepare/perform routes called exitActiveDragTargets
before delegating to the same pane target, and the pane's draggingExited
forwards webView.draggingExited — cancelling WebKit's drag session, so
the following performDragOperation had no live session, the page could
not receive the drop, and WebKit ran its navigate-to-file fallback. This
was the root cause of uploads intermittently degrading ever since the
overlay hand-off existed.

Exits are now selective: a target chosen for prepare/perform/conclude of
the current drag never receives draggingExited mid-session; targets the
drag genuinely left, the real draggingExited path, and the capture-miss
cleanup branches still exit everything.

Also arm BrowserFileDropNavigationGuard only for handled drops (review
feedback): a rejected delivery no longer records paths that a later
unrelated file:// navigation could match within the TTL.

Verified end-to-end with the scripted Finder drag: the probe page now
receives the DOM drop (dragenter/dragover/drop), and dogfood confirms
page uploads (Vanta .pdf policy upload) work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@austinywang
austinywang enabled auto-merge (squash) July 9, 2026 05:25
@austinywang
austinywang merged commit 5f7a033 into main Jul 9, 2026
38 of 40 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 14344492 Deployed Jul 9, 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

1 participant