Skip to content

Fix file drag hit-test amplification - #10783

Merged
austinywang merged 2 commits into
mainfrom
issue-7811-file-drag-per-event-main-thread-work
Aug 26, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-7811-file-drag-per-event-main-thread-work

Conversation

@austinywang

@austinywang austinywang commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #7811.

Profile evidence

Before editing, I reproduced the issue against the installed release app with 104 workspaces. The scripted drag used the issue's exact 80 mouse-moves over 4 seconds, from an AGENTS.md file-explorer row to the terminal center.

In the 4.41-second drag interval of the Time Profiler trace (/tmp/cmux-7811-baseline-profile):

  • 77 main-thread samples were under NSCoreDragReceiveMessageProc, matching the 80-event driver.
  • 313 ms were under transaction flushing (stepTransactionFlush).
  • 211 ms were under AppKit/SwiftUI hit-testing.
  • 76 ms were under NSView._setHidden.
  • 55 ms were under layout, 8 ms under accessibility work, and 5 ms under pasteboard work.

The release build strips the cmux method names, but the system stacks align with the current FileDropOverlayView source: each routing query hid the overlay and independently called contentView.hitTest.

Change

  • Resolve the underlying content view once for each draggingEntered / draggingUpdated event and reuse that immutable result for text, web, and terminal routing during that event.
  • Hit-test the overlay's known content sibling directly, so drag destination routing no longer toggles isHidden and triggers AppKit layout/display invalidation.
  • Stop toggling visibility for pane-target geometry traversal and skip the overlay subtree itself.

The snapshot is scoped to one mouse event and restored with defer; it is not cached across cursor positions.

Regression coverage

The commits intentionally preserve the required red/green order:

  1. 90dd8b6bd0 adds the regression test only.
  2. 6a29623c98 adds the fix.

FileDropOverlayHitTestPerformanceTests drives 80 distinct cursor positions through the real draggingEntered / draggingUpdated path and requires exactly 80 underlying AppKit hit-tests. Before the fix, the same event path performs several full hierarchy hit-tests per event; after the fix, it performs one current-position lookup per event.

Verification

  • PASS: the new Swift Testing file type-checks against the emitted app module with warnings treated as errors.
  • PASS: Xcode frontend compilation of ContentView.swift, FileDropOverlayView.swift, and FileDropOverlayViewHitTesting.swift.
  • PASS: ./scripts/lint-pbxproj-test-wiring.sh (706 test files).
  • PASS: ./scripts/check-pbxproj.sh.
  • PASS: git diff --check origin/main...HEAD.
  • PASS: new Swift file is 125 lines; touched file counts are 17,575 / 491 / 448 / 125 lines respectively, and FileDropOverlayViewHitTesting.swift shrinks by seven lines.
  • BLOCKED upstream: the compile-only cmux-unit scheme reaches this branch's files successfully, then current main fails in ExtensionWorktreePrototype.swift:44-45 because fix: preserve worktree identity initializer contract (follow-up to #10777) #10781 assigns let properties that retain declaration defaults. This is unrelated to the PR.
  • BLOCKED upstream: python3 scripts/swift_file_length_budget.py cannot run because main intentionally deleted that script and .github/swift-file-length-budget.tsv in Remove Swift file length budget #8125. The exact command was attempted and returned Errno 2; no removed budget infrastructure is reintroduced here.

Per the task constraint, I did not run a tagged reload or launch a development app, so no post-fix UI profile is claimed. The baseline trace is real; the regression cap is objective; CI can execute the app-hosted test. No user-facing strings changed, so localization catalogs were audited as unaffected.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Improved file drag-and-drop targeting across nested panes, web views, and terminal areas.
    • Dragged files now resolve the correct destination more reliably without overlay interference.
    • Preserved consistent copy behavior during repeated drag updates.
  • Performance

    • Reduced repeated hit-testing work during file drags for smoother interactions.
  • Tests

    • Added coverage to verify drag targets and hit-testing efficiency across repeated file-drop updates.

@coderabbitai

coderabbitai Bot commented Aug 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dee5e282-89e5-44d4-8743-55224c5a8642

📥 Commits

Reviewing files that changed from the base of the PR and between 12430de and 6a29623.

📒 Files selected for processing (5)
  • Sources/ContentView.swift
  • Sources/FileDropOverlayView.swift
  • Sources/FileDropOverlayViewHitTesting.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/FileDropOverlayHitTestPerformanceTests.swift

Included review availability: Your plan provides up to 10 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

File-drop hit testing now uses the configured reference view, caches the result for each drag update, and reuses it for routing. Inline traversal avoids hiding the overlay. A performance test verifies one underlying hit test per drag event.

Changes

File-drop hit testing

Layer / File(s) Summary
Reference view and cached hit-test state
Sources/ContentView.swift, Sources/FileDropOverlayView.swift, Sources/FileDropOverlayViewHitTesting.swift
The overlay stores the configured reference view and reuses the hit-tested view during each drag update. Shared hit-testing no longer hides the overlay.
Shared routing lookup
Sources/FileDropOverlayViewHitTesting.swift
WebView, terminal, and inline pane lookup use the shared hit-test result. Recursive traversal skips the overlay view.
Performance regression coverage
cmuxTests/FileDropOverlayHitTestPerformanceTests.swift, cmux.xcodeproj/project.pbxproj
The test target includes an 80-event drag test that checks .copy results and one underlying hit test per event.

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

Merge Risk: ⚪ Minimal · up to 6a296

This PR makes drag hit-testing reuse one content lookup per mouse event and avoids unnecessary overlay visibility invalidation, with targeted regression coverage and checks passing; no actionable merge-blocking risk remains.

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 3 files. (2 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: fixing excessive file-drag hit testing.
Description check ✅ Passed The description provides detailed change rationale, reproduction evidence, regression coverage, verification results, and known upstream blockers. It omits the template headings, demo video, review-tr…
Linked Issues check ✅ Passed The changes directly address issue #7811 by reducing repeated per-event hit testing, avoiding overlay visibility toggles, and adding a regression test that verifies one underlying hit-test per drag ev…
Out of Scope Changes check ✅ Passed All changes are related to the file-drag hit-test performance fix and its regression coverage. The Xcode project wiring supports the added test and is in scope.
Cmux Swift Actor Isolation ✅ Passed PASS. The production diff adds only synchronous state and hit-testing logic to the existing NSView UI coordinator: hitTestReferenceView and the temporary dragUpdateHitTest tuple. It adds no `Sen…
Cmux Swift Blocking Runtime ✅ Passed PASS. The PR adds no semaphore, blocking wait, sleep, delayed dispatch, polling loop, main-queue sync, or manual lock. Production changes only cache and restore hit-test state with defer, then reuse…
Cmux Browser Automation Off-Main ✅ Passed PASS: The pull request changes only file-drag hit testing, ContentView, project wiring, and its performance test. It does not modify Sources/TerminalController.swift, the control-command execution…
Cmux Expensive Synchronous Load ✅ Passed PASS: The production diff adds only NSView hit-test reference/state and reuses NSView.hitTest during drag routing. It adds no agent-history loader, agent-owned file read, directory scan, JSON/JSON…
Cmux Cache Substitution Correctness ✅ Passed PASS — the diff caches only the NSView hit result for the duration of one draggingEntered/draggingUpdated call. dragUpdateHitTest is set from uncachedViewUnderPoint, reused by transient drag…
Cmux No Hacky Sleeps ✅ Passed PASS: The pull request changes four Swift files, one Xcode project file, and adds one Swift test. It does not change TypeScript, JavaScript, shell, or non-Swift build/runtime code covered by `runtime-…
Cmux Algorithmic Complexity ✅ Passed PASS. The production diff adds one underlying NSView.hitTest lookup per drag update and reuses that result; it does not add a nested scan, per-target batch rescan, sorting/filtering loop, or in-memo…
Cmux Swift Concurrency ✅ Passed PASS. The pull request does not introduce or materially expand any listed legacy async pattern. The production diff adds synchronous hit-test state and defer restoration, plus a weak NSView refere…
Cmux Swift @Concurrent ✅ Passed PASS: The PR introduces no async function, await call, or @concurrent annotation. The changed hit-testing path remains synchronous UI/AppKit work. The new test is explicitly @MainActor and its…
Cmux Swift Package Boundaries ✅ Passed PASS: The production diff is AppKit UI glue, not independently reusable domain logic. FileDropOverlayViewHitTesting.swift imports and directly uses NSDraggingInfo, NSView, NSTextView, `WKWebVi…
Cmux Swiftpm Lockfiles ✅ Passed PASS. The diff changes only Swift source files, adds one test file, and registers that test in cmux.xcodeproj/project.pbxproj. The project diff contains no SwiftPM package-reference or dependency ch…
Cmux Swift Logging ✅ Passed PASS: The PR diff adds no print, debugPrint, dump, NSLog, Logger, file logging, or stdout/stderr diagnostics. Existing cmuxDebugLog calls are unchanged and remain debug-only. The new `#exp…
Cmux User-Facing Error Privacy ✅ Passed PASS — The production diff adds only hit-testing state and routing logic. It adds no user-facing error, alert, command output, API error body, or recovery copy. The only new strings are in the regress…
Cmux Full Internationalization ✅ Passed PASS. The diff adds no user-facing production text, localization keys, catalogs, Info.plist entries, or web messages. The only added string literals are test name/assertion text in `cmuxTests/FileDrop…
Cmux Swiftui State Layout ✅ Passed PASS. The diff adds no ObservableObject, @Published, SwiftUI property wrapper, GeometryReader, lazy/list row store reference, or render-time state mutation. The new state is plain AppKit state o…
Cmux Architecture Rethink ✅ Passed PASS: The diff introduces no new sleeps, delayed dispatch, polling, locks, observers, or duplicate drag entrypoints. The new hit-test value is a local, event-scoped snapshot owned by `FileDropOverlayV…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS. The diff changes file-drop overlay hit testing and adds a test-only NSWindow fixture. It does not add or materially change a user-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window, o…
Cmux Source Artifacts ✅ Passed PASS. The diff changes only Swift source, a Swift regression test, and Xcode project configuration. The new cmuxTests/FileDropOverlayHitTestPerformanceTests.swift is a deliberate test-system fixture…
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: The production diff adds no test/debug seam. The only new members are hitTestReferenceView and dragUpdateHitTest in Sources/FileDropOverlayView.swift; production code uses them for normal …
Cmux No Ambient Global State ✅ Passed PASS. The production diff adds hitTestReferenceView and dragUpdateHitTest as instance properties on FileDropOverlayView, plus a private instance helper on its extension. The `ContentView.swift…
Full details: Description check

Explanation

The description provides detailed change rationale, reproduction evidence, regression coverage, verification results, and known upstream blockers. It omits the template headings, demo video, review-trigger block, and checklist, but the core required information is present.

Full details: Linked Issues check

Explanation

The changes directly address issue #7811 by reducing repeated per-event hit testing, avoiding overlay visibility toggles, and adding a regression test that verifies one underlying hit-test per drag event.

Full details: Docstring Coverage

Explanation

Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 3 files. (2 skipped: 1 unsupported, 1 too large.)

Full details: Cmux Swift Actor Isolation

Explanation

PASS. The production diff adds only synchronous state and hit-testing logic to the existing NSView UI coordinator: hitTestReferenceView and the temporary dragUpdateHitTest tuple. It adds no Sendable, Codable, Identifiable, async service, actor, logger, or background access. The changed methods run through AppKit draggingEntered/draggingUpdated callbacks. The new test code is @MainActor and is explicitly allowed by the check. No actor-isolation failure condition is introduced or materially worsened.

Full details: Cmux Swift Blocking Runtime

Explanation

PASS. The PR adds no semaphore, blocking wait, sleep, delayed dispatch, polling loop, main-queue sync, or manual lock. Production changes only cache and restore hit-test state with defer, then reuse the result during one drag event. The added test uses deterministic AppKit scaffolding and cleanup defer; it adds no timing-based synchronization. Existing timer and delayed-dispatch code in Sources/ContentView.swift is unchanged by the diff.

Full details: Cmux Browser Automation Off-Main

Explanation

PASS: The pull request changes only file-drag hit testing, ContentView, project wiring, and its performance test. It does not modify Sources/TerminalController.swift, the control-command execution policy, or policy tests. The only browser-related diff context is existing WKWebView type handling in file-drop traversal; no browser.* socket command, worker routing, WebKit wait, or browser state mutation is added or moved. The check is therefore not applicable, and no policy-test requirement is triggered.

Full details: Cmux Expensive Synchronous Load

Explanation

PASS: The production diff adds only NSView hit-test reference/state and reuses NSView.hitTest during drag routing. It adds no agent-history loader, agent-owned file read, directory scan, JSON/JSONL parse, transcript/trajectory access, or syscall loop on a main-actor or interactive path. The new file is a performance test only. Existing SharedLiveAgentIndex references in ContentView.swift are outside the changed hunks and are not worsened.

Full details: Cmux Cache Substitution Correctness

Explanation

PASS — the diff caches only the NSView hit result for the duration of one draggingEntered/draggingUpdated call. dragUpdateHitTest is set from uncachedViewUnderPoint, reused by transient drag-routing lookups, and restored with defer; no changed code persists, snapshots, undoes, or records this value. The cache is therefore an allowed transient non-persisted UI hint, and the cache-substitution cold/stale rules do not apply.

Full details: Cmux No Hacky Sleeps

Explanation

PASS: The pull request changes four Swift files, one Xcode project file, and adds one Swift test. It does not change TypeScript, JavaScript, shell, or non-Swift build/runtime code covered by runtime-no-hacky-sleeps.md. The added test uses a deterministic 80-iteration loop and contains no sleep, timer, polling, or wall-clock wait. The existing DispatchQueue.main.async retry code in ContentView.swift is unchanged and therefore is not worsened by this pull request.

Full details: Cmux Algorithmic Complexity

Explanation

PASS. The production diff adds one underlying NSView.hitTest lookup per drag update and reuses that result; it does not add a nested scan, per-target batch rescan, sorting/filtering loop, or in-memory join. The pane-target recursion remains a linear view-hierarchy traversal and was already present; the change only removes overlay hiding and skips the overlay view. The new 80-update loop is test-only and has an explicit fixed bound.

Full details: Cmux Swift Concurrency

Explanation

PASS. The pull request does not introduce or materially expand any listed legacy async pattern. The production diff adds synchronous hit-test state and defer restoration, plus a weak NSView reference. The new test is @MainActor and uses synchronous AppKit drag callbacks. The only DispatchQueue, Combine, and Task usages found in the touched production file are pre-existing; the nearby DispatchQueue.main.async code is unchanged. No new completion-handler API or fire-and-forget task appears in the diff.

Full details: Cmux Swift `@Concurrent`

Explanation

PASS: The PR introduces no async function, await call, or @concurrent annotation. The changed hit-testing path remains synchronous UI/AppKit work. The new test is explicitly @MainActor and its nonisolated(unsafe) stored properties are not async work. Therefore the diff does not trigger any failure condition in the Swift concurrency annotation rule.

Full details: Cmux Swift Package Boundaries

Explanation

PASS: The production diff is AppKit UI glue, not independently reusable domain logic. FileDropOverlayViewHitTesting.swift imports and directly uses NSDraggingInfo, NSView, NSTextView, WKWebView, GhosttyNSView, and app-specific portal registries. The changes cache one NSView hit result during a drag event and traverse AppKit view hierarchies. ContentView.swift only supplies the overlay's reference view. The new performance test is test code. These uses match the policy's allowed small UI/AppKit bridge and app-lifecycle composition cases.

Full details: Cmux Swiftpm Lockfiles

Explanation

PASS. The diff changes only Swift source files, adds one test file, and registers that test in cmux.xcodeproj/project.pbxproj. The project diff contains no SwiftPM package-reference or dependency changes. No Package.swift, Package.resolved, or .gitignore files changed, and the root Xcode Package.resolved remains unchanged. Therefore the lockfile requirements in .github/review-bot-rules/swiftpm-package-resolved.md are not triggered.

Full details: Cmux Swift Logging

Explanation

PASS: The PR diff adds no print, debugPrint, dump, NSLog, Logger, file logging, or stdout/stderr diagnostics. Existing cmuxDebugLog calls are unchanged and remain debug-only. The new #expect messages are test harness output, which the policy allows.

Full details: Cmux User-Facing Error Privacy

Explanation

PASS — The production diff adds only hit-testing state and routing logic. It adds no user-facing error, alert, command output, API error body, or recovery copy. The only new strings are in the regression test and are covered by the rule's test exemption.

Full details: Cmux Full Internationalization

Explanation

PASS. The diff adds no user-facing production text, localization keys, catalogs, Info.plist entries, or web messages. The only added string literals are test name/assertion text in cmuxTests/FileDropOverlayHitTestPerformanceTests.swift, which the rule explicitly allows as tests. Production changes are view references, hit-test state, and routing logic only.

Full details: Cmux Swiftui State Layout

Explanation

PASS. The diff adds no ObservableObject, @Published, SwiftUI property wrapper, GeometryReader, lazy/list row store reference, or render-time state mutation. The new state is plain AppKit state on FileDropOverlayView: NSView and is updated in the drag event handler. The ContentView.swift change only assigns an AppKit reference during overlay attachment. The new regression test uses AppKit views and NSDraggingInfo. Existing SwiftUI state in ContentView.swift is unchanged.

Full details: Cmux Architecture Rethink

Explanation

PASS: The diff introduces no new sleeps, delayed dispatch, polling, locks, observers, or duplicate drag entrypoints. The new hit-test value is a local, event-scoped snapshot owned by FileDropOverlayView; updateDragTarget sets it once and restores the prior value with defer. hitTestReferenceView is configured by the existing overlay attachment path. The serialized test suite uses test-only synchronization, which the rule allows. The existing installation retry with DispatchQueue.main.async is unchanged.

Full details: Cmux Swift Auxiliary Window Close Shortcuts

Explanation

PASS. The diff changes file-drop overlay hit testing and adds a test-only NSWindow fixture. It does not add or materially change a user-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup. The only added NSWindow code is in cmuxTests/FileDropOverlayHitTestPerformanceTests.swift, which the rule explicitly allows. No auxiliary window identifier, close-shortcut workaround, or shared close-shortcut registration is required.

Full details: Cmux Source Artifacts

Explanation

PASS. The diff changes only Swift source, a Swift regression test, and Xcode project configuration. The new cmuxTests/FileDropOverlayHitTestPerformanceTests.swift is a deliberate test-system fixture and is wired into cmuxTests in project.pbxproj. No changed path uses a forbidden scratch or artifact directory, and the diff adds no logs, screenshots, recordings, caches, build output, or copied artifacts.

Full details: Cmux No Test Or Debug Seam In Production Source

Explanation

PASS: The production diff adds no test/debug seam. The only new members are hitTestReferenceView and dragUpdateHitTest in Sources/FileDropOverlayView.swift; production code uses them for normal overlay configuration and per-drag hit-test caching. No new #if DEBUG or test-build guard was added, and neither member has a test/debug-only name or accessor role. The new test remains in cmuxTests/ and exercises the production drag path without accessing either member. Existing debug helpers in the touched files are unchanged.

Full details: Cmux No Ambient Global State

Explanation

PASS. The production diff adds hitTestReferenceView and dragUpdateHitTest as instance properties on FileDropOverlayView, plus a private instance helper on its extension. The ContentView.swift change only touches the existing private attachFileDropOverlay function. No new file-scope API, mutable global, static-only namespace, or singleton was introduced. The new mutable hit-test state is owned by the overlay instance.

  • Fix all pre-merge checks with AI
✨ 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-7811-file-drag-per-event-main-thread-work

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 merged commit e2f9170 into main Aug 26, 2026
7 checks passed
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.

File drag: chip trails the cursor; every drag mouse-move triggers full-window layout flush + accessibility focus walk

1 participant