Skip to content

fix(file-drop): explorer, project sidebar, tab strip and editor own OS file drops (STA-6940 PR5) - #26133

Merged
brennanb2025 merged 32 commits into
mainfrom
brennanb2025/sta-6940-pr5-explorer-editor-owners
Oct 8, 2026
Merged

brennanb2025 merged 32 commits into
mainfrom
brennanb2025/sta-6940-pr5-explorer-editor-owners

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 18 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1460 $\color{#cf222e}{\Huge{\mathbf{−}}}$​134 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1326
Prod 23 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​683 $\color{#cf222e}{\Huge{\mathbf{−}}}$​401 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​282

ELI5

When you drag a file in from Finder, Explorer or a file manager, Orca should act on the exact spot where you let go. Before this PR, the file tree, the project sidebar, the editor's tab row and the editor itself never handled the drop themselves. A global listener guessed where the drop was meant to go, and it often guessed "the active workspace". This PR makes each of those four places handle drops on itself, so a file lands where you dropped it.

This is PR 5 of 6 for STA-6940.

PR 3 (#25781) and PR 4 (#26008) have merged; this PR has no open-PR dependency. Main commit 939288e51d342463df636f2e962e9e63a8c6c2f5 was merged into this branch without rebasing. The original PR 5 migration is commit ae8d3ab871a, followed by its review fixes; the final diff against the merged main commit is byte-for-byte identical to PR 5's reviewed changes after b4aa7f46ee4.

The problem

What you saw: a file dragged in from the operating system sometimes landed somewhere other than where you dropped it:

  • Dropped on the tab row of a split editor group (or the floating panel's tab row), it opened in the active workspace's active group, not the one you dropped on.
  • Dropped on the file tree, the import used whichever workspace was active when the drop finished preparing. If you switched workspaces in between, it imported into the wrong one.
  • Dropped on the editor area, it opened as a tab only by accident. Areas with no drop marker fell back to "open in the active workspace".

Why: Orca's preload script (the bridge between the page and Electron) caught every OS file drop at the top of the page and cancelled it, so no element ever saw the real event. It recorded rough location hints from data-native-file-drop-target attributes and sent them to the main process. Main sent the drop back to the whole window, where several listeners each guessed whether it was meant for them. The explorer and editor-open listener (useGlobalFileDrop) read the active worktree when the drop arrived, not the one under your cursor. The project sidebar also received the window-wide broadcast, even though its action is simply to add a local project.

The review also found a later timing gap: closing the destination split while Orca checked a local file or uploaded it still let the completed operation request an open in that closed group. Tab placement could then use the active split, redirecting the dropped file. This follow-up closes that window too.

What changed (as a user)

  • File tree: a file dropped on a folder row imports into that folder. Dropped on a file row, it goes to that file's folder. Dropped on empty space, it goes to the folder the tree is showing. That part already worked. Now the destination folder and workspace are fixed the moment you let go, so switching workspaces while the import starts can no longer redirect it. Hovering a row that is outside the displayed folder (a stale row) shows the "not allowed" cursor.
  • Project sidebar: dropping a folder still opens "Add Project" for it. No user-visible change to the normal remote-runtime refusal: the PR 4 base already set the cursor to "not allowed" during React dragover; preload did not stop that event from reaching React. The existing "Server runtime active / Use Add Project for host paths" drag hint stays. The existing unavailable-folder toast remains for a runtime becoming active after a local drop was accepted, including during preparation. This is verified from the base source; real operating-system drag dispatch has not been checked in Electron.
  • Editor tab row: a dropped file opens in that tab row's editor group and workspace, not the active one.
  • Editor area: dropping a file deliberately opens it as a tab in that group (approved decision: keep this behaviour, make it explicit). Switching to a terminal or browser tab while the file is being prepared no longer loses the drop: the destination group stays valid even though the editor content disappears. If the captured editor group is gone when preparation, a local file check or a remote upload finishes, the remaining opens are skipped. Preparation-failure toasts already shown stay, and no group-close message is added. The code editor never sees the drop, so it cannot paste the file's text.
  • Floating panel: behaviour change: a file dropped on the floating panel's tab row or editor now opens there, as a local floating tab. Before, it opened in the main window's active workspace.
  • Dropping several files opens their tabs in the order they were dropped.
  • Drops on chrome that has no drop handler (the "unmarked" areas) still use the old destination route until PR 6. One small ordering change: that legacy route now opens local multi-file drops one at a time, in input order, because it reuses the same editor-open module. Previously it launched each file concurrently. This keeps tab order deterministic; its destination behavior is unchanged.

Mechanism

Each surface now uses the shared owner hook from PR 2 (useOsFileDropOwner). The hook listens on the element itself, in the capture phase (it runs before any child element, including the code editor). It reads the dropped paths through the preload getPathForFile bridge, then asks main to prepare them (fs:prepareDroppedPaths, which copies macOS screenshot temp files). Each surface reads its identity from its own render, never from the active workspace.

  • File tree (useFileExplorerImport.ts): the tree root is the drop handler. At drop time it reads the target folder from the row under the cursor (data-file-explorer-drop-dir, falling back to the displayed root folder). It captures the explorer's host check (captureFileExplorerOperationGuard) at that moment, before rows can recycle, then reuses the existing import path. That path covers local, SSH, remote runtime and folder workspaces, and re-checks the host after every await. Rows now handle only drags that start inside Orca. The native-drop highlight cleanup runs on the drop handler's path. Edge auto-scroll moves to a capture handler, because the drop handler stops native dragover before it bubbles. The broadcast subscriber, its use of the active-worktree ref, and the data-native-file-drop-* markers are deleted.
  • Project sidebar (useSidebarProjectDrop.ts, sidebar/index.tsx): the sidebar is the drop handler while it is open. It refuses with "not allowed" while a remote runtime is active, and re-reads that setting after the drop is prepared. The broadcast subscriber and marker are deleted.
  • Tab row and editor area: a new use-editor-group-file-drop-owner.ts gives both elements of an editor group one shared ordering queue (PR 2's sequence object), keyed by props.worktreeId + group. A tab-row drop and then an editor-area drop are applied in drop order, even if the first takes longer to prepare. The editor-open/upload logic moved out of useGlobalFileDrop into editor-dropped-file-open.ts. It now takes an explicit worktree and group, captures that worktree's host when you drop, and refuses if the host changes before preparation finishes. It opens files with targetGroupId. Delivery checks the destination group instead of the editor content element; an optional destination-liveness callback in the shared hook empties successful paths for a closed group but preserves preparation failures. Owners without that callback retain the existing root-detachment behavior. The open step also checks that the captured group still exists immediately after each awaited local file check or remote upload and before opening files, so tab placement never receives a closed target group. The floating workspace uses the shared local-owner rule and the existing floating-document open function, which pins reads to this machine and suppresses focused-runtime fallback. Its explicit permanent-tab option matches the prior default.
  • Floating ownership consolidation: getRuntimeEnvironmentIdForWorktree, getKnownExecutionHostIdForWorktree and the explorer's operation-owner reader now read getFloatingWorkspaceOperationRoute, as editor provenance and SSH expectations already do. These existing readers serve floating-workspace operations beyond file drops; only their existing floating special cases change internally, with identical public return values and non-floating behavior. This consolidation has no user-visible behavior change: floating file operations remain local regardless of the focused runtime. No other callers or features were migrated.
  • useGlobalFileDrop now keeps only the rejection toasts and the legacy unmarked-chrome route. That route calls the extracted module with the active worktree, preserving its destination behavior; local multi-file opens now run in order as described above.
  • useOsFileDropOwner's canAccept now receives the drag event, so the file tree can refuse based on the row under the cursor.

Why this approach

The plan (STA-6940, approved) moves every surface to handling drops on its own element, because the window-wide broadcast with string filters is what let drops reach the wrong destination. Patching the active-worktree reads one by one would leave the broadcast in place for the next surface to get wrong. The tab row and editor area share one queue because they are one destination. Two queues would let a slow drop on one land after a later drop on the other.

Differences from the common pattern

  • Intended: the drop handlers listen in the capture phase and the nearest registered element wins, rather than relying on React's normal event bubbling. This is needed because the code editor sits inside the editor-area handler and must never see the drop. It is safe because it is the same rule PR 2–4 already use.
  • Intended: main still copies macOS screenshot temp files (fs:prepareDroppedPaths, consumer main-reader). The file tree, editor and sidebar may read an uncopied original, as main allows for that consumer.
  • Intended: the editor group's shared queue lives in a small module map keyed by worktree + group, created only after a committed mount or at drop time, counted per mounted element, and deleted after the last element unmounts and its queued drops finish. A discarded React render cannot leave a map entry, and a remount while preparation is pending keeps the original order. This is needed because the tab row can render in a different React tree than the editor area (the floating panel and the titlebar strip).
  • Intended: a refused sidebar drop is silent (cursor plus the existing drag hint), with no toast, matching the common pattern of silent refusal.
  • Temporary: useGlobalFileDrop's legacy route for unmarked chrome, and the editor shown before a workspace has a layout (TerminalLegacyEditorSurface, still unmarked), still open in the active worktree. PR 6 removes the preload drop interception, onFileDrop, the remaining markers and useGlobalFileDrop.
  • Temporary: the shared payload types and preload location resolver still list the explorer and sidebar targets. They are now unreachable for these surfaces and PR 6 deletes them.

Linked Issue

Linear: STA-6940 (maintainer PR; no GitHub issue).

Visual Proof

A separate QA agent ran this branch (head e767be62ca0) as a hidden Orca dev window with an isolated profile and two folder workspaces (ws-a and ws-b), and simulated OS file drops through the Chrome DevTools Protocol. All 8 scenarios passed: explorer folder-row and empty-area drops, an explorer drop in the non-active workspace, the editor area, the tab strip, split editor groups, the project sidebar (which shows its usual "open as folder" confirmation for a non-Git folder), and the floating panel's editor. The later round-2 fixes only add re-checks for a group closed mid-upload and consolidate the floating-workspace rule; they do not change these paths.

1. File dropped on the explorer's inbox folder row. It is imported into that folder.

Explorer folder-row drop

2. Explorer drop in the other workspace. With ws-b shown in the explorer, the dropped file lands in ws-b's inbox, not in ws-a.

Explorer drop goes to the explorer's own workspace

3. File dropped on the editor content area. It opens as its own tab (my report.txt); the file already open (readme.txt) is unchanged, so its text was not inserted.

Editor area drop opens the file as a tab

4. Split editor groups. With the left group focused, a file dropped on the right group's tab strip opens in the right group.

Drop opens in the group it was dropped on

Not covered: real SSH, WSL and remote-runtime hosts, Linux and Windows (covered by unit fixtures only).

Testing

Final sync validation (head 2ef4f37349a42118bf4580fbda72e50891b72ede): merged main 939288e51d342463df636f2e962e9e63a8c6c2f5 after PR 3 and PR 4 merged. Nine conflicted files take main unchanged; use-os-file-drop-owner.ts takes main plus exactly PR 5's event-aware acceptance and destination-liveness changes. Every auto-merged file was also audited: the final 41-file diff is byte-for-byte identical to the saved, reviewed PR 5 diff, with no stale PR 3/4 code or extra feature change. dropScopeKey is absent, and pnpm's generated @pnpm/exe lockfile block was discarded.

All 70 named test files / 596 tests passed, including PR 5's named suites, the conflicted modules, PR 1–4 drop regressions, and all three previously timing-out consumer suites. No current timeout remains and no timeout was increased. The command used a non-empty Bash file array, checked every path existed, and ran node_modules/.bin/vitest run --config config/vitest.config.ts "${F[@]}" with ORCA_BACKGROUND_LAUNCH=1.

Full pnpm run typecheck passed. The post-commit orca-ci-checks --no-typecheck run passed 22/22 static checks, using that already-passed full typecheck. The initial pre-commit React Doctor run included changes from main through the old merge base; it passed after the merge commit gave the changed-lines checks the correct base. No application was launched during this sync.

GitHub CI: full-unit run 37707844544 completed successfully on the first attempt for head 2ef4f37349a42118bf4580fbda72e50891b72ede. All ten Node 24 test shards, relay integration, static analysis/typecheck, both packaging jobs, repository guards, final verification and selection evidence passed. Across the PR, 18 checks passed and 17 were skipped by their workflow conditions, with no pending or failed checks. No rerun was needed. The PR remains draft, with no merge or auto-merge requested.

The following records describe earlier heads and remain as historical evidence.

Round 2 validation: the two additional caller suites, src/renderer/src/lib/worktree-runtime-owner.test.ts and src/renderer/src/components/right-sidebar/file-explorer-operation-generation.test.ts, passed 39/39 tests. They cover floating ownership with a different runtime focused and the existing non-floating cases. At follow-up commit 5ed9268b8f2888226531a5fa3786f16b414f2d22, the required 33-file sample: 30 files passed / 3 files timed out; 248 passed / 14 timed out / 262 tests in 746.77 seconds. All new late-close tests and the other 30 files passed. The timeouts are confined to the three consumer files in the isolated comparison below (one tab-strip test, six markdown-editor setup calls and seven tab-lifecycle setup calls). This run is not fully green.

Late group-close regression evidence: with only the new tests added and the opener still at e767be62ca0, the two named owner-hook files produced 12 passed / 4 failed. Both delayed-local-check cases (group removal and removal of that workspace's groups) and both delayed-upload cases (SSH and runtime) failed because openFile was called for a closed group. The tests use the actual owner delivery path and preserve a preparation-failure toast delivered before the later operation begins. After the fix, both owner-hook files passed 16/16 tests in the 33-file sample, including all four negative-control cases.

Isolated timeout comparison: ran exactly TabBar.os-file-drop.test.tsx, FloatingTerminalPanel.markdown-editor.test.tsx and FloatingTerminalPanel.tab-lifecycle.test.tsx in one Bash-array Vitest command, with no other checks running from this worker. At e767be62ca0, 20 passed / 3 timed out / 23 tests in 124.15 seconds: the tab-strip dynamic import hit 30 seconds and the first setup in each floating suite hit 60 seconds. On PR 4 base b4aa7f46ee4, the same three-file command gave 18 passed / 5 timed out / 23 tests in 172.57 seconds: the same tab-strip timeout and two setup timeouts in each floating suite. The base checkout was created with orca worktree create --parent-worktree active --setup skip, detached at the base, and removed after the run; only the PR 5 tab-strip test (absent on PR 4) was copied into it, with no production source changes. This reproduces the import/setup blocker before PR 5; its cause is still unverified, and it is recorded separately from file-drop correctness. No timeout limit was increased and no queue was bypassed.

Every Vitest command uses explicitly named files in a Bash array, node_modules/.bin/vitest run --config config/vitest.config.ts "${F[@]}", and ORCA_BACKGROUND_LAUNCH=1. pnpm tc:web passed. Full pnpm exec oxlint on all seven touched files and pnpm run check:react-doctor:changed passed; commit hooks also passed lint and formatting. pnpm run check:code-quality:changed passed with zero new findings, including its type-aware, React Doctor, design-system and checked-cast scans. orca-ci-checks --no-typecheck completed 21/22 checks. The full repository type-aware audit never started: the normal machine-wide gate could not obtain a slot for 1802 seconds and exited 75, without code diagnostics. All other checks passed, including repository lint, code-quality policies, localization and generated-file checks. That earlier push did not await GitHub CI; the final sync result above covers the current head. No queue was bypassed, and the PR stays draft. No new user-visible strings were added, and pnpm-lock.yaml was restored from HEAD and excluded from the commit.

Previous-round recorded validation: 33 explicitly named Vitest files, 258 tests passed at e767be62ca0; the re-review and current runs below reproduce timeouts, so this is historical evidence rather than a claim that the current sample is fully green. Commands used a Bash array and pnpm exec vitest run --config config/vitest.config.ts, with ORCA_BACKGROUND_LAUNCH=1 on the final run. This includes all 12 new/moved files requested by the review and its 14-file regression sample:

  • Required 12: right-sidebar/useFileExplorerImport.os-file-drop.test.tsx, right-sidebar/useFileExplorerImport.committed-scope.test.tsx, sidebar/useSidebarProjectDrop.os-file-drop.test.tsx, editor/use-editor-group-file-drop-owner.test.tsx, tab-bar/TabBar.os-file-drop.test.tsx, tab-group/TabGroupPanel.os-file-drop.test.tsx, editor/editor-dropped-file-open.test.ts, editor/editor-dropped-file-open.escaping-link.test.ts, hooks/useGlobalFileDrop.test.ts, hooks/use-os-file-drop-owner.test.tsx, sidebar/Sidebar.test.tsx, lib/local-file-access.test.ts.
  • PR 3/4 and consumer sample: NewWorkspaceComposerCard.file-drop.test.tsx, native-chat/native-chat-element-file-drop.test.tsx, native-chat/native-chat-pane-file-drop.test.tsx, native-chat/native-chat-floating-file-drop.test.tsx, terminal-pane/terminal-pane-element-file-drop.test.tsx, lib/os-file-drop-preload-integration.test.tsx, TerminalTitlebarTabs.test.tsx, tab-group/TabGroupSplitLayout.drag.test.tsx, floating-terminal/FloatingTerminalPanel.markdown-editor.test.tsx, floating-terminal/FloatingTerminalPanel.tab-lifecycle.test.tsx, right-sidebar/FileExplorerFilesTreePane.errors.test.tsx, right-sidebar/useFileExplorerDragDrop.test.ts, right-sidebar/useFileExplorerRowDrag.test.ts, right-sidebar/FileExplorerVirtualRows.row-handlers.test.tsx.
  • Additional 7: editor/use-editor-group-file-drop-owner.remote.test.tsx, right-sidebar/useFileExplorerImport.remote.test.tsx, right-sidebar/useFileExplorerDragDrop.os-file-drop.test.tsx, lib/ssh-mutation-expectation.test.ts, lib/open-document-in-floating-workspace.test.ts, lib/editor-file-operation-owner.test.ts, lib/worktree-operation-route.test.ts.

hooks/ and lib/ paths are under src/renderer/src/; other paths above are under src/renderer/src/components/.

Before/after evidence for the behavioral review fixes: temporarily restored the three relevant source files from review head ae8d3ab871a while retaining the new tests. Five tests failed: editor-area delivery after the content unmounts, preservation of preparation-failure feedback after the group closes, no sequence creation for an uncommitted suspended render, and both destination-liveness cases in the shared hook. Sources were restored afterwards and all these tests pass. The remount test also proves that pending drops retain order when both roots remount. Owners without a destination callback still discard pending delivery after their root detaches, preserving PR 3/4 behavior.

Coverage added for existing behavior: SSH and runtime uploads through the actual editor and explorer owner hooks, with another workspace active and a deliberately different runtime focused; SSH reconnection and workspace runtime-owner changes during delayed preparation; explorer guard-capture failure; collapsed sidebar detachment; and edge auto-scroll through the real FileExplorerFilesTreePane and Radix ScrollArea root, including the same rendered root element across a re-render. That assertion does not count callback-ref attachments or prove uninterrupted attachment during a pending drop. No real remote host is used by these tests.

Original migration evidence, recorded at ae8d3ab871a: the new destination tests passed on that commit and failed against PR 4 base b4aa7f46ee4; unchanged internal explorer row drags passed on both. The original run covered 127 named files / 945 tests. This follow-up reran the 33-file sample above, rather than repeating that full earlier run. The editor-drop assertions moved from useGlobalFileDrop.test.ts remain unchanged in the extracted module's tests.

Previous-round recorded static checks (not the current result): pnpm tc:web passed; full pnpm exec oxlint on the 18 follow-up files passed, including file-length checks; pnpm run check:code-quality:changed reported zero new findings, including React Doctor and design-system checks; pnpm run check:react-doctor:changed passed. Its existing warning about awaiting files in a loop is intentional: opening them in order keeps tab order deterministic. orca-ci-checks passed all 23 checks, including localization extraction, catalogs and coverage, repository lint and quality checks, and the full pnpm run typecheck. A final rerun of the four files affected by the delivery cleanup passed all 32 tests. No new user-visible strings were added; existing translation keys remain in use.

Not verified: no Electron or app launch; real operating-system drag dispatch, cross-world events, portals and floating-panel rendering require the separately arranged UI check. Real SSH, WSL and runtime hosts, the web build, and Windows/Linux execution were not tested. The PR remains draft and has not been merged or marked ready.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Review

Author: @BrennanKB5

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Cross-platform: paths are compared with the existing path utilities, with no hard-coded separators. SSH, remote runtime and WSL keep their existing upload and host-check code downstream of the drop handler. Folder workspaces resolve through the same host lookup. Remote mixed versions: no wire or IPC change in this PR.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…lumbing' into brennanb2025/sta-6940-pr3-chat-composer-feedback
Preserve element-owned drops while integrating the upstream multi-file
paperclip picker and all-or-nothing attachment limit.

Resolve NativeChatComposer through its existing file-drop hook, keep the
host-qualified attachment resolver with the current workspace state type,
retain captured destinations and unmount checks through attachment reads
and uploads, and keep attachment actions picker-only without a broadcast
subscriber. Update picker fixtures and the upstream subscriber test.

Validation: web and Node typechecks, full Oxlint on 39 PR3 files, and
372 tests across 45 explicitly named files pass.
…omposer-feedback' into brennanb2025/sta-6940-pr4-terminal-owners
Resolve NativeChatComposerField imports: keep PR3's flushSync (interrupted
composition settlement) and drop the old ImageOff notice import that main's
notice card replaced. Adapt PR3's IME file-drop test to main's notices and
@-file suggestion props.
…or own OS drops

STA-6940 PR5. The file explorer tree, the project sidebar, each editor
group's tab strip and its editor area now take OS file drops on their own
element through the shared owner hook, with identity from their own render
(the explorer's shown workspace and target row, the tab strip's and editor
group's worktree and group) instead of the active worktree. The explorer and
sidebar broadcast subscribers and their legacy drop markers are deleted;
editor-open moves out of useGlobalFileDrop into editor-dropped-file-open,
which the legacy unmarked-chrome route still uses until PR6.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary

This PR went through an independent code review, two fix rounds each followed by a focused re-review, and a rendered check in a real Orca window. The reviewers and the rendered-check agent did not write the code. Their reports are summarized here because they were kept off GitHub while the work was in progress.

First review (head ae8d3ab871a): no misrouted, doubled or lost drops in normal flows

Each surface takes its identity from its own render (the explorer's shown workspace and target row; the tab strip's and editor group's own worktree and group), the explorer reads the target folder at drop time and refuses folders outside its root, the tab strip and editor area share one delivery order per group, Monaco never sees the drop, and the change to the shared drop hook is compatible with the chat and terminal owners from PR 3 and PR 4. It found:

  1. The PR body claimed a remote-runtime sidebar used to show a toast; on the PR 4 base the drop was already refused during the drag, so nothing changed for the user.
  2. No tests for the editor group's host capture or SSH/remote uploads through the new owners.
  3. An editor-area drop was lost if you switched to another tab in that group while a large file was still being prepared.
  4. The per-group ordering map was written during render.
  5. The floating workspace's "local only" rule was written out again instead of reused.
  6. Some edge cases had no tests; one code comment named PR numbers; and multi-file drops on the old route now open one after another.

First fix round (head e767be62ca0), re-review: mostly confirmed, three items left

The body was corrected, editor drops now stay alive while their group exists (if the group is closed, nothing opens and real preparation failures still show their existing message), the ordering map moved out of render, and the tests were added. The re-review found that a group closed during an upload or file check could still send the file to the active group, that three older places still decided the floating-workspace rule themselves, and that three test files timed out.

Second fix round (head 5ed9268b8f2), re-review: approved

The editor group is re-checked after every upload or file check and before every open, so a dropped file never falls back to the active group (four new tests fail on the old code and pass now). The three older floating-workspace checks now use the one shared function with unchanged results elsewhere. The three timing-out test files (TabBar.os-file-drop, FloatingTerminalPanel.markdown-editor, FloatingTerminalPanel.tab-lifecycle) also time out on the PR 4 base, which the re-reviewer reproduced independently in its own checkout, so they are a pre-existing problem and not caused by this PR.

Rendered check (head e767be62ca0): all 8 scenarios passed

Screenshots are in the description.

Still to do before merge

This PR is stacked on #25781 and #26008. Following the rule of syncing with main only once, at the end, it will be brought up to date with main and run through CI after those merge. The local full type-aware audit could not get a slot on the shared machine; CI's static analysis and typecheck cover it.

Not verified

Real SSH, WSL and remote-runtime hosts, Linux and Windows.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Ready for review

PR 3 (#25781) and PR 4 (#26008) are now merged, so this PR no longer depends on anything open. It was brought up to date with main once, at the end (merge of 939288e51d3, head 2ef4f37349a):

  • Ten files conflicted because this branch still carried older copies of PR 3 and PR 4. Nine were taken from main as is. The shared drop hook was taken from main with this PR's own change re-applied.
  • This PR's 41 changed files are byte-identical to the reviewed head 5ed9268b8f2, so the review and rendered check below still describe exactly what would merge.
  • Local: CI's static checks (lint, code quality, localization, typecheck) and 70 test files (596 tests) passed.
  • CI on 2ef4f37349a, first attempt: 18 passed, 17 skipped because the changed paths don't need them, none failed. That covers all ten test shards, both packages and the relay integration.

The review summary is in the comment above and the rendered-check screenshots are in the description.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 8, 2026 00:50
@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

Native file drops now use OS-drop owners in editor groups, the file explorer, and the project sidebar. Editor drops capture worktree and group ownership before opening local files or uploading remote files. Explorer drops capture a destination and operation guard before importing prepared paths. Floating-workspace operation routing is shared across file-operation ownership and runtime-owner checks.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 2ef4f

If a remote workspace changes owner during a file drop, the editor may open a tab for the previous host, or the explorer may select a stale import result. These bounded cases warrant fixes or explicit acceptance before merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 63 functions across 40 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: local ownership of OS file drops across the explorer, project sidebar, tab strip, and editor.
Description check ✅ Passed The description is comprehensive and follows the repository template. It explains the user impact, implementation, rationale, linked issue, visual proof, testing, review notes, and known validation li…
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1b8a15a8-397d-4c54-ac7a-a77177b44aea
📥 Commits

Reviewing files that changed from the base of the PR and between 939288e and 2ef4f37.

📒 Files selected for processing (41)
  • src/renderer/src/components/editor/editor-dropped-file-open.escaping-link.test.ts
  • src/renderer/src/components/editor/editor-dropped-file-open.test.ts
  • src/renderer/src/components/editor/editor-dropped-file-open.ts
  • src/renderer/src/components/editor/use-editor-group-file-drop-owner.remote.test.tsx
  • src/renderer/src/components/editor/use-editor-group-file-drop-owner.test.tsx
  • src/renderer/src/components/editor/use-editor-group-file-drop-owner.ts
  • src/renderer/src/components/right-sidebar/FileExplorer.tsx
  • src/renderer/src/components/right-sidebar/FileExplorerFilesTreePane.tsx
  • src/renderer/src/components/right-sidebar/FileExplorerRow.tsx
  • src/renderer/src/components/right-sidebar/file-explorer-operation-generation.test.ts
  • src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts
  • src/renderer/src/components/right-sidebar/use-file-explorer-tree-pane-state.ts
  • src/renderer/src/components/right-sidebar/useFileExplorerDragDrop.os-file-drop.test.tsx
  • src/renderer/src/components/right-sidebar/useFileExplorerDragDrop.ts
  • src/renderer/src/components/right-sidebar/useFileExplorerImport.committed-scope.test.tsx
  • src/renderer/src/components/right-sidebar/useFileExplorerImport.os-file-drop.test.tsx
  • src/renderer/src/components/right-sidebar/useFileExplorerImport.remote.test.tsx
  • src/renderer/src/components/right-sidebar/useFileExplorerImport.ts
  • src/renderer/src/components/right-sidebar/useFileExplorerRowDrag.ts
  • src/renderer/src/components/sidebar/Sidebar.test.tsx
  • src/renderer/src/components/sidebar/index.tsx
  • src/renderer/src/components/sidebar/useSidebarProjectDrop.os-file-drop.test.tsx
  • src/renderer/src/components/sidebar/useSidebarProjectDrop.ts
  • src/renderer/src/components/tab-bar/TabBar.os-file-drop.test.tsx
  • src/renderer/src/components/tab-bar/TabBar.tsx
  • src/renderer/src/components/tab-bar/tab-bar-surface.tsx
  • src/renderer/src/components/tab-group/TabGroupPanel.os-file-drop.test.tsx
  • src/renderer/src/components/tab-group/TabGroupPanel.tsx
  • src/renderer/src/hooks/use-os-file-drop-owner.test.tsx
  • src/renderer/src/hooks/use-os-file-drop-owner.ts
  • src/renderer/src/hooks/useGlobalFileDrop.test.ts
  • src/renderer/src/hooks/useGlobalFileDrop.ts
  • src/renderer/src/lib/editor-file-operation-owner.ts
  • src/renderer/src/lib/local-file-access.test.ts
  • src/renderer/src/lib/open-document-in-floating-workspace.ts
  • src/renderer/src/lib/remote-workspace-drop-test-fixtures.ts
  • src/renderer/src/lib/ssh-mutation-expectation.test.ts
  • src/renderer/src/lib/ssh-mutation-expectation.ts
  • src/renderer/src/lib/worktree-operation-route.ts
  • src/renderer/src/lib/worktree-runtime-owner.test.ts
  • src/renderer/src/lib/worktree-runtime-owner.ts
💤 Files with no reviewable changes (1)
  • src/renderer/src/components/right-sidebar/FileExplorer.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Comment on lines +177 to +197
for (const result of results) {
if (!editorGroupStillExists(destination)) {
return
}
if (result.status !== 'imported' || result.kind === 'directory') {
continue
}
const maybeRelative = toWorktreeRelativePath(result.destPath, worktreePath)
store.setActiveTabType('editor', worktreeId)
store.openFile(
{
filePath: result.destPath,
relativePath: maybeRelative ?? result.destPath,
worktreeId,
runtimeEnvironmentId: runtimeEnvironmentId ?? undefined,
language: detectLanguage(result.destPath),
mode: 'edit'
},
{ suppressActiveRuntimeFallback: runtimeEnvironmentId === null, ...groupOptions }
)
}

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '68,258p' src/renderer/src/components/editor/editor-dropped-file-open.ts
rg -n 'function importExternalPathsToRuntime|const importExternalPathsToRuntime|assertCurrent|function statUserOpenedPath' src/renderer/src

Repository: stablyai/orca

Length of output: 18681


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- target imports and declarations ---'
sed -n '1,120p' src/renderer/src/components/editor/editor-dropped-file-open.ts
sed -n '1,250p' src/renderer/src/runtime/runtime-file-import-client.ts
sed -n '1,120p' src/renderer/src/lib/user-opened-local-path.ts
printf '%s\n' '--- callers and owner/context references ---'
rg -n -F -- 'captureEditorFileDropOpen' src/renderer/src || true
rg -n -F -- 'captureWorktreeSshMutationExpectation' src/renderer/src || true
rg -n -F -- 'openEditorFileDropPaths' src/renderer/src || true
printf '%s\n' '--- focused tests ---'
rg -n -F -- 'editor-dropped-file-open' src/renderer/src --glob '*test*' || true
rg -n -F -- 'ownerChanged' src/renderer/src/components src/renderer/src/runtime --glob '*test*' || true
rg -n -F -- 'importExternalPathsToRuntime' src/renderer/src/components src/renderer/src/runtime --glob '*test*' || true
printf '%s\n' '--- openFile definitions and runtime resolution ---'
rg -n -F -- 'openFile:' src/renderer/src/store src/renderer/src --glob '*.ts' --glob '*.tsx' || true
rg -n -F -- 'function openFile' src/renderer/src --glob '*.ts' --glob '*.tsx' || true

Repository: stablyai/orca

Length of output: 42337


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- SSH expectation and runtime stat routing ---'
sed -n '1,240p' src/renderer/src/lib/ssh-mutation-expectation.ts
rg -n -F -- 'function statRuntimePath' src/renderer/src/runtime src/renderer/src
sed -n '1,180p' src/renderer/src/runtime/runtime-file-metadata-client.ts
sed -n '1,180p' src/renderer/src/runtime/runtime-file-routing.ts
printf '%s\n' '--- editor drop tests ---'
sed -n '1,280p' src/renderer/src/components/editor/editor-dropped-file-open.test.ts
sed -n '1,220p' src/renderer/src/components/editor/use-editor-group-file-drop-owner.remote.test.tsx
sed -n '1,240p' src/renderer/src/components/editor/use-editor-group-file-drop-owner.test.tsx
sed -n '1,180p' src/renderer/src/components/editor/editor-dropped-file-open.escaping-link.test.ts
printf '%s\n' '--- importer ownership tests ---'
sed -n '240,470p' src/renderer/src/runtime/runtime-file-client-external-import.test.ts
printf '%s\n' '--- store open behavior ---'
sed -n '1,180p' src/renderer/src/store/slices/editor/actions/open-file-action.ts
sed -n '1,100p' src/renderer/src/store/slices/editor/types/editor-files-slice.ts

Repository: stablyai/orca

Length of output: 42963


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- filesystem IPC consumers and owner checks ---'
rg -n -F -- 'importExternalPaths' src --glob '*.ts' --glob '*.tsx' || true
rg -n -F -- 'expectedSshConnectionGeneration' src --glob '*.ts' --glob '*.tsx' || true
rg -n -F -- 'expectedExecutionHostId' src --glob '*.ts' --glob '*.tsx' || true
rg -n -F -- 'fs.stat' src --glob '*.ts' --glob '*.tsx' || true
printf '%s\n' '--- open-file state application ---'
sed -n '1,280p' src/renderer/src/store/slices/editor/actions/open-file-apply.ts
printf '%s\n' '--- exact remote owner-change test range ---'
rg -n -F -- 'refuses when the workspace runtime owner changes' src/renderer/src/components/editor/use-editor-group-file-drop-owner.remote.test.tsx || true
sed -n '180,360p' src/renderer/src/components/editor/use-editor-group-file-drop-owner.remote.test.tsx
printf '%s\n' '--- exact post-stat and post-import test ranges ---'
rg -n -F -- 'during upload' src/renderer/src/components/editor src/renderer/src/runtime --glob '*.test.ts' --glob '*.test.tsx' || true
rg -n -F -- 'during stat' src/renderer/src/components/editor --glob '*.test.ts' --glob '*.test.tsx' || true

Repository: stablyai/orca

Length of output: 41794


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- filesystem mutation validation ---'
rg -n --glob '*.ts' --glob '*.tsx' 'expectedSshConnectionGeneration|expectedExecutionHostId|importExternalPathsSsh' src/main src/shared src/preload
sed -n '130,220p' src/main/ipc/filesystem-mutations.ts
sed -n '1,130p' src/main/ipc/filesystem-import-ssh.ts
sed -n '1,180p' src/main/ipc/filesystem-import-ssh-ops.test.ts
printf '%s\n' '--- editor operation provenance ---'
rg -n --glob '*.ts' --glob '*.tsx' 'captureEditorFileOperationProvenance' src/renderer/src
sed -n '1,260p' src/renderer/src/lib/editor-file-operation-owner.ts
printf '%s\n' '--- runtime import guard ---'
sed -n '1,180p' src/renderer/src/runtime/runtime-file-mutation-rpc.ts

Repository: stablyai/orca

Length of output: 43022


Recheck the worktree owner after each awaited operation.

The runtime importer and SSH upload path guard their own runtime pairing and SSH generation. They do not replace a final check that the editor worktree still has the captured owner. A worktree owner can change while the import or stat is pending. The current code then checks only the group and can open a stale path or runtime tab.

Recapture the context after importExternalPathsToRuntime and statUserOpenedPath. Stop opening when sameEditorFileDropOwner returns false. Preserve the existing upload-failure notification.

Suggested owner checks
 async function openEditorFileDropPaths(
-  { fileContext, worktreePath, connectionId }: EditorFileDropContext,
+  captured: EditorFileDropContext,
   destination: EditorFileDropDestination,
   paths: readonly string[]
 ): Promise<void> {
+  const { fileContext, worktreePath, connectionId } = captured
   const store = useAppStore.getState()
...
       if (!editorGroupStillExists(destination)) {
         return
       }
-      for (const result of results) {
+      const current = captureEditorFileDropContext(worktreeId)
+      const ownerStillCurrent =
+        current !== null && sameEditorFileDropOwner(captured, current)
+      if (!ownerStillCurrent) {
+        showOwnerChangedError()
+      }
+      for (const result of ownerStillCurrent ? results : []) {
...
       const stat = await statUserOpenedPath(fileContext, filePath)
       if (!editorGroupStillExists(destination)) {
         return
       }
+      const current = captureEditorFileDropContext(worktreeId)
+      if (!current || !sameEditorFileDropOwner(captured, current)) {
+        showOwnerChangedError()
+        return
+      }

Comment on lines +109 to +149
expectedExecutionHostId: guard.route.expectedExecutionHostId,
expectedSshTargetId: guard.route.expectedSshTargetId,
expectedSshConnectionGeneration: guard.route.expectedSshConnectionGeneration
},
prepared.paths,
destinationDir,
{ assertCurrent: guard.assertCurrent }
)

const { paths, destinationDir } = data

void (async () => {
try {
if (getRelativePathInsideRoot(destinationDir, displayRootRef.current) === null) {
return
}
const operationGuard = captureFileExplorerOperationGuard(wtId, operationOwnerRef.current)
operationGuard.assertCurrent()
const { results } = await importExternalPathsToRuntime(
{
settings: operationGuard.route.settings,
worktreeId: wtId,
worktreePath: worktreePathRef.current,
connectionId: operationGuard.route.connectionId,
expectedExecutionHostId: operationGuard.route.expectedExecutionHostId,
expectedSshTargetId: operationGuard.route.expectedSshTargetId,
expectedSshConnectionGeneration: operationGuard.route.expectedSshConnectionGeneration
},
paths,
destinationDir,
{ assertCurrent: operationGuard.assertCurrent }
)

// Refresh the destination directory once per gesture
await refreshDirRef.current(destinationDir)
// Refresh the destination directory once per gesture
await refreshDir(destinationDir)

// Why: only select (highlight) the first imported file — don't trigger
// the full reveal machinery because watcher refreshes can otherwise
// snap the tree viewport away from the user's drop target.
const imported = results.filter((r) => r.status === 'imported')
const skipped = results.filter((r) => r.status === 'skipped')
const failed = results.filter((r) => r.status === 'failed')
// Why: only select (highlight) the first imported file — don't trigger
// the full reveal machinery because watcher refreshes can otherwise
// snap the tree viewport away from the user's drop target.
const imported = results.filter((r) => r.status === 'imported')
const skipped = results.filter((r) => r.status === 'skipped')
const failed = results.filter((r) => r.status === 'failed')

if (
imported.length > 0 &&
activeWorktreeIdRef.current === wtId &&
getRelativePathInsideRoot(imported[0].destPath, displayRootRef.current) !== null
) {
setSelectedPathRef.current(imported[0].destPath)
}
const shown = shownRef.current
if (
imported.length > 0 &&
shown.worktreeId === destination.worktreeId &&
getRelativePathInsideRoot(imported[0].destPath, shown.displayRootPath) !== null
) {
setSelectedPath(imported[0].destPath)
}

if (failed.length > 0) {
const noun = failed.length === 1 ? 'file' : 'files'
toast.error(
translate(
'auto.components.right.sidebar.useFileExplorerImport.132fd0e1e9',
'Failed to import {{value0}} {{value1}}.',
{ value0: failed.length, value1: noun }
)
if (failed.length > 0) {
const noun = failed.length === 1 ? 'file' : 'files'
toast.error(
translate(
'auto.components.right.sidebar.useFileExplorerImport.132fd0e1e9',
'Failed to import {{value0}} {{value1}}.',
{ value0: failed.length, value1: noun }
)
} else if (skipped.length > 0 && imported.length === 0) {
const noun = skipped.length === 1 ? 'file' : 'files'
toast.error(
translate(
'auto.components.right.sidebar.useFileExplorerImport.25919b2050',
'Skipped {{value0}} {{value1}}.',
{ value0: skipped.length, value1: noun }
)
)
} else if (skipped.length > 0 && imported.length === 0) {
const noun = skipped.length === 1 ? 'file' : 'files'
toast.error(
translate(

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '65,165p' src/renderer/src/components/right-sidebar/useFileExplorerImport.ts
rg -n 'importExternalPathsToRuntime|assertCurrent|assert.*Guard' src/renderer/src/lib src/renderer/src/components/right-sidebar

Repository: stablyai/orca

Length of output: 9233


🏁 Script executed:

printf '%s\n' '--- runtime-file-client references and implementation ---'
rg -n -F -- 'function importExternalPathsToRuntime' src/renderer/src/runtime src/renderer/src || test "$?" -eq 1
rg -n -F -- 'importExternalPathsToRuntime' src/renderer/src/runtime src/renderer/src/lib src/renderer/src/components/right-sidebar || test "$?" -eq 1
printf '%s\n' '--- operation guard implementation ---'
sed -n '1,240p' src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts
sed -n '1,150p' src/renderer/src/lib/worktree-operation-generation.ts
printf '%s\n' '--- import caller and focused tests ---'
sed -n '1,210p' src/renderer/src/components/right-sidebar/useFileExplorerImport.ts
sed -n '1,280p' src/renderer/src/components/right-sidebar/useFileExplorerImport.remote.test.tsx
sed -n '1,230p' src/renderer/src/components/right-sidebar/useFileExplorerImport.os-file-drop.test.tsx

Repository: stablyai/orca

Length of output: 37063


🏁 Script executed:

sed -n '1,260p' src/renderer/src/runtime/runtime-file-client.ts
sed -n '1,240p' src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts
sed -n '1,150p' src/renderer/src/lib/worktree-operation-generation.ts
sed -n '1,210p' src/renderer/src/components/right-sidebar/useFileExplorerImport.ts
sed -n '1,280p' src/renderer/src/components/right-sidebar/useFileExplorerImport.remote.test.tsx
sed -n '1,230p' src/renderer/src/components/right-sidebar/useFileExplorerImport.os-file-drop.test.tsx

Repository: stablyai/orca

Length of output: 34475


🏁 Script executed:

printf '%s\n' '--- importer implementation ---'
sed -n '1,320p' src/renderer/src/runtime/runtime-file-import-client.ts
printf '%s\n' '--- importer tests using assertCurrent and mutation ownership ---'
rg -n -F -- 'assertCurrent' src/renderer/src/runtime/runtime-file-import*.test.ts
sed -n '1,360p' src/renderer/src/runtime/runtime-file-client-mutation-ownership.test.ts
printf '%s\n' '--- importer external tests around all calls ---'
sed -n '1,470p' src/renderer/src/runtime/runtime-file-client-external-import.test.ts

Repository: stablyai/orca

Length of output: 35212


🏁 Script executed:

sed -n '1,320p' src/renderer/src/runtime/runtime-file-import-client.ts
rg -n -F -- 'assertCurrent' src/renderer/src/runtime/runtime-file-import*.test.ts
sed -n '1,360p' src/renderer/src/runtime/runtime-file-client-mutation-ownership.test.ts

Repository: stablyai/orca

Length of output: 20038


🏁 Script executed:

printf '%s\n' '--- runtime import session and mutation helpers ---'
rg -n -F -- 'createRuntimeImportSessionGuard' src/renderer/src/runtime
rg -n -F -- 'function createRuntimeImportSessionGuard' src/renderer/src/runtime
sed -n '1,360p' src/renderer/src/runtime/runtime-file-mutation-rpc.ts
sed -n '1,360p' src/renderer/src/runtime/runtime-file-upload-client.ts
printf '%s\n' '--- fs import IPC binding and implementation ---'
rg -n -F -- 'importExternalPaths' src/main src/shared src/renderer | head -80
rg -n -F -- 'expectedSshConnectionGeneration' src/main src/shared | head -120

Repository: stablyai/orca

Length of output: 25600


🏁 Script executed:

sed -n '1,360p' src/renderer/src/runtime/runtime-file-mutation-rpc.ts
sed -n '1,360p' src/renderer/src/runtime/runtime-file-upload-client.ts
rg -n -F -- 'importExternalPaths' src/main src/shared src/renderer | head -80
rg -n -F -- 'expectedSshConnectionGeneration' src/main src/shared | head -120

Repository: stablyai/orca

Length of output: 25067


🏁 Script executed:

printf '%s\n' '--- filesystem import implementation ---'
rg --files src/main src/renderer/src | rg 'filesystem.*import|import.*filesystem|filesystem-import'
rg -n -F -- 'fs:importExternalPaths' src/main src
rg -n -F -- 'importExternalPaths:' src/main src
printf '%s\n' '--- refreshDir bindings for useFileExplorerImport ---'
rg -n -F -- 'refreshDir:' src/renderer/src/components/right-sidebar
rg -n -F -- 'useFileExplorerImport({' src/renderer/src/components/right-sidebar

Repository: stablyai/orca

Length of output: 10358


🏁 Script executed:

rg --files src/main src/renderer/src | rg 'filesystem.*import|import.*filesystem|filesystem-import'
rg -n -F -- 'fs:importExternalPaths' src/main src
rg -n -F -- 'importExternalPaths:' src/main src
rg -n -F -- 'useFileExplorerImport({' src/renderer/src/components/right-sidebar

Repository: stablyai/orca

Length of output: 6995


🏁 Script executed:

sed -n '130,220p' src/main/ipc/filesystem-mutations.ts
sed -n '1,260p' src/main/ipc/filesystem-import-ssh.ts
sed -n '1,240p' src/main/ipc/filesystem-import-ssh-directory.ts
sed -n '190,255p' src/renderer/src/components/right-sidebar/use-file-explorer-tree-pane-state.ts

Repository: stablyai/orca

Length of output: 16555


🏁 Script executed:

rg -n -F -- 'const refreshDir' src/renderer/src/components/right-sidebar src/renderer/src
rg -n -F -- 'function refreshDir' src/renderer/src/components/right-sidebar src/renderer/src
rg -n -F -- 'refreshDir =' src/renderer/src/components/right-sidebar src/renderer/src
sed -n '1,240p' src/renderer/src/components/right-sidebar/useFileExplorerTree.ts
sed -n '1,240p' src/renderer/src/components/right-sidebar/useFileExplorerWatch.ts

Repository: stablyai/orca

Length of output: 34011


🏁 Script executed:

sed -n '250,335p' src/renderer/src/components/right-sidebar/useFileExplorerTree.ts
sed -n '1,260p' src/renderer/src/components/right-sidebar/file-explorer-directory-listing.ts

Repository: stablyai/orca

Length of output: 4489


Re-check the operation guard before refreshing and selecting the import result.

guard.assertCurrent() runs before the import, but the importer can resolve after its final awaited write. If the runtime or SSH generation changes in that interval, this callback can still refresh and select the returned path. The selection check only compares the worktree ID and display root.

Suggested fix
         const { results } = await importExternalPathsToRuntime(
           {
             settings: guard.route.settings,
             worktreeId: destination.worktreeId,
@@
           { assertCurrent: guard.assertCurrent }
         )

+        guard.assertCurrent()
         // Refresh the destination directory once per gesture
         await refreshDir(destinationDir)
+        guard.assertCurrent()

         // Why: only select (highlight) the first imported file — don't trigger

@brennanb2025
brennanb2025 merged commit d1e7d15 into main Oct 8, 2026
59 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/sta-6940-pr5-explorer-editor-owners branch October 8, 2026 02:03
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