Repository navigation
Route browser-pane file drops by intent so they never silently become previews - #7634
Conversation
…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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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. ChangesBrowser file-drop routing and delivery
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (2 errors, 2 warnings)
✅ Passed checks (21 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR makes browser-pane file drops route by user intent instead of web-view availability. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (8): Last reviewed commit: "Deliver browser drops without cancelling..." | Re-trigger Greptile |
| /// Routes browser-pane file drops by intent only: intent never depends on transient webview | ||
| /// availability; delivery failure refuses the drop instead of reinterpreting it. |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
Sources/BrowserPaneDropTargetView.swiftSources/BrowserPaneFileDropRouting.swiftcmux.xcodeproj/project.pbxprojcmuxTests/BrowserPaneFileDropUploadRegressionTests.swift
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>
…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>
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
Sources/BrowserFileDropNavigationGuard.swiftSources/BrowserPaneDropTargetView.swiftSources/BrowserPaneFileDropRouting.swiftSources/FileDropOverlayView.swiftSources/FileDropOverlayViewHitTesting.swiftSources/Panels/BrowserNavigationDelegate.swiftSources/Panels/BrowserPanel+InteractiveModalHostWindow.swiftSources/Panels/BrowserPanel.swiftSources/Panels/WKWebView+CmuxPrintOperation.swiftcmux.xcodeproj/project.pbxprojcmuxTests/BrowserPaneFileDropUploadRegressionTests.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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.
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>
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 intoresolvedFileDropBehavior'sguard canDropAsText else { return .preview }. The webview is legitimately unresolvable in live states — discarded byBrowserHiddenWebViewDiscardManager, mid-reparent through the browser window portal, stale frame — so the drop silently degraded to a preview.shouldCaptureHitTestingadditionally hardcodedcanDropAsText: 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.
BrowserPaneFileDropRouting.disposition(pasteboardTypes:modifierFlags:isDockHosted:defaultBehavior:)— a pure seam with no webview-availability input — decides.forwardToPagevs.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..forwardToPage: slot lookup, then a same-pane-guardedBrowserWindowPortalRegistry.webViewAtWindowPointfallback (covers mid-reparent/stale-frame). A discarded webview is asked torestoreDiscardedWebViewIfNeededonce per drag session while hovering. If no webview resolves, the drop is refused (prepare/perform return false) — never reinterpreted as a preview.Two-commit regression-test structure
Verification
python3 scripts/swift_file_length_budget.pypasses with no TSV changes (BrowserPaneDropTargetView.swiftshrank 526 → 489; new files well under 500)../scripts/lint-pbxproj-test-wiring.shpasses (new test file wired into cmuxTests).🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith 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.dispositiondrives 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
BrowserWindowPortalRegistryfallback, one-shotrestoreDiscardedWebViewIfNeededper 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:
BrowserFileDropNavigationGuardrecords successful page deliveries;BrowserNavigationDelegatecancels matching main-framefile://.othernavigations and opens a file-preview split viahandleDroppedFileNavigation, including all files from multi-file drops.Overlay / polish:
FileDropOverlayViewavoids prematuredraggingExitedduring 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 inBrowserPaneFileDropUploadRegressionTests.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
BrowserPaneFileDropRouting.disposition(...)and wired hit-test/update/prepare/perform through it to avoid claim/perform mismatches.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.BrowserFileDropNavigationGuardto record file-drop deliveries and cancel matching main-frame.otherfile:// navigations inBrowserNavigationDelegate, opening a preview split instead of letting the page be overwritten. Multi-file drops now preview all dropped files.Tests
Written for commit 1434449. Summary will update on new commits.
Summary by CodeRabbit