From 5345ba34bf2008ddc941b43fe0c33131ba2d01ad Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:15:06 -0700 Subject: [PATCH] Fix ownership for terminal and chat file drops (STA-6940, PR 1/6) (#25749) * Fix terminal and chat file drop destination ownership * fix runtime terminal drop ownership and queued chat retries --- .../native-chat-composer-drop-scope.test.tsx | 2 +- ...-external-attachments-destination.test.tsx | 214 ++++++++++++++++ .../native-chat-resolved-path-ownership.ts | 2 + ...-native-chat-external-attachments.test.tsx | 28 ++- .../use-native-chat-external-attachments.ts | 26 +- ...e-native-chat-resolved-path-attachments.ts | 65 +++-- .../TerminalPaneHeaderOverlay.test.tsx | 142 ++++++++++- .../TerminalPaneHeaderOverlay.tsx | 5 +- .../terminal-drop-handler.test.ts | 235 ++++++++++++------ .../terminal-pane/terminal-drop-handler.ts | 21 +- .../terminal-drop-internal-handler.test.ts | 202 ++++++++++----- .../terminal-drop-local-workspace.test.ts | 84 +++++++ .../terminal-drop-pane-resolution.test.ts | 39 +++ .../terminal-drop-pane-resolution.ts | 26 +- ...erminal-drop-runtime-catalog-owner.test.ts | 209 ++++++++++++++++ .../terminal-drop-transport-owner.test.ts | 41 +++ .../terminal-drop-transport-owner.ts | 46 ++++ .../terminal-drop-worktree-path.test.ts | 58 +++++ .../terminal-drop-worktree-path.ts | 36 ++- .../terminal-native-file-drop.ts | 28 ++- ...inal-pane-global-effects-file-drop.test.ts | 13 +- .../use-terminal-pane-global-effects.ts | 13 +- 22 files changed, 1296 insertions(+), 239 deletions(-) create mode 100644 src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx create mode 100644 src/renderer/src/components/terminal-pane/terminal-drop-local-workspace.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-drop-pane-resolution.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-drop-runtime-catalog-owner.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-drop-transport-owner.test.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-drop-transport-owner.ts create mode 100644 src/renderer/src/components/terminal-pane/terminal-drop-worktree-path.test.ts diff --git a/src/renderer/src/components/native-chat/native-chat-composer-drop-scope.test.tsx b/src/renderer/src/components/native-chat/native-chat-composer-drop-scope.test.tsx index 90029987cf4..73953f509a9 100644 --- a/src/renderer/src/components/native-chat/native-chat-composer-drop-scope.test.tsx +++ b/src/renderer/src/components/native-chat/native-chat-composer-drop-scope.test.tsx @@ -32,7 +32,7 @@ const intake = vi.hoisted(() => ({ readFile: vi.fn(), upload: vi.fn() })) -vi.mock('@/store', () => ({ useAppStore: { getState: () => ({}) } })) +vi.mock('@/store', () => ({ useAppStore: { getState: () => ({ tabsByWorktree: {} }) } })) // Keeps the real notice strings so the silent-failure guards assert what users see. vi.mock('./native-chat-attachment-upload', async (importOriginal) => ({ ...(await importOriginal()), diff --git a/src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx b/src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx new file mode 100644 index 00000000000..2b0fe635187 --- /dev/null +++ b/src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx @@ -0,0 +1,214 @@ +// @vitest-environment happy-dom +import { act, useRef, useState } from 'react' +import { createRoot, type Root } from 'react-dom/client' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import type * as AttachmentUploadModule from './native-chat-attachment-upload' +import type { NativeChatAttachmentOwner } from './native-chat-attachment-upload' +import { useNativeChatExternalAttachments } from './use-native-chat-external-attachments' +import { + clearNativeChatAttachmentCacheForTests, + useNativeChatComposerAttachments +} from './use-native-chat-composer-attachments' + +const mocks = vi.hoisted(() => { + const state: { + owner: NativeChatAttachmentOwner + remoteTarget: boolean + stat: ReturnType + upload: ReturnType + tabsByWorktree: Record + } = { + owner: { kind: 'local' }, + remoteTarget: false, + stat: vi.fn(), + upload: vi.fn(), + tabsByWorktree: { 'workspace-1': [{ id: 'tab-1' }] } + } + return state +}) +vi.mock('@/store', () => ({ + useAppStore: { getState: () => ({ tabsByWorktree: mocks.tabsByWorktree }) } +})) +vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) +vi.mock('@/runtime/runtime-terminal-inspection', () => ({ + isRemoteRuntimePtyId: () => mocks.remoteTarget +})) +vi.mock('./native-chat-attachment-upload', async (importOriginal) => ({ + ...(await importOriginal()), + resolveNativeChatAttachmentOwner: () => mocks.owner, + resolveNativeChatAttachmentOwnerForWorktree: () => mocks.owner, + uploadNativeChatAttachmentPaths: mocks.upload +})) + +type Api = ReturnType & + ReturnType +let api: Api | undefined +let root: Root +let container: HTMLDivElement +let composing = true + +function Probe({ worktreeId }: { worktreeId?: string }): React.JSX.Element { + const [draft, setDraft] = useState('') + const [caret, setCaret] = useState(0) + const [notice, setNotice] = useState(null) + const textareaRef = useRef(null) + const attachments = useNativeChatComposerAttachments({ + attachmentScopeKey: 'drop-test', + caret, + disabled: false, + isComposing: () => composing, + resolveTarget: () => ({ ptyId: 'pty-1', settings: { activeRuntimeEnvironmentId: null } }), + textareaRef, + setCaret, + setDraft, + setNotice + }) + const external = useNativeChatExternalAttachments({ + terminalTabId: 'tab-1', + structuredWorktreeId: worktreeId, + disabled: false, + attachResolvedPaths: attachments.attachResolvedPaths, + setNotice + }) + api = { ...attachments, ...external } + return ( + <> +