From 155a34b0d02a7903e06cb33abf294bc1a9bfbf79 Mon Sep 17 00:00:00 2001 From: amico Date: Thu, 20 Aug 2026 15:16:27 +0000 Subject: [PATCH] fix(app): Ctrl+V pastes images in chat input (amicode#365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VS Code webview chat runs as a sandboxed cross-origin iframe where navigator.clipboard / clipboardData are empty — image data is only readable via the outer webview relay (chat_panel.ts → navigator.clipboard.read() → dataUrl). PromptInput v1 (the classic composer, still default when newLayoutDesigns is off) only wired platform.readClipboardImage (desktop/TUI native) and never the bridge, so an image-only clipboard on Ctrl+V produced no files, no text, and no attachment — fixed by mirroring v2's bridge-first read and the global-clipboard image slot. - import readClipboardImageViaBridge alongside readClipboardViaBridge - readClipboardImage: bridge first, then platform fallback (parity with prompt-input-v2.tsx:389) - setClipboardImageHandler -> addAttachments for the global Ctrl+V fallback (v2 pattern) Fixes harmoniqs/amicode#365 Closes harmoniqs/amicode#365 --- packages/app/src/components/prompt-input.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 39fbd9dfa..9d0b15a93 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -53,7 +53,7 @@ import { usePlatform } from "@/context/platform" import { createSessionTabs } from "@/pages/session/helpers" import { createTextFragment, getCursorPosition, setCursorPosition, setRangeEdge } from "./prompt-input/editor-dom" import { createPromptAttachments } from "./prompt-input/attachments" -import { readClipboardViaBridge } from "./prompt-input/clipboard-bridge" +import { readClipboardImageViaBridge, readClipboardViaBridge } from "./prompt-input/clipboard-bridge" import { ACCEPTED_FILE_TYPES, pickAttachmentFiles } from "./prompt-input/files" import { canNavigateHistoryAtCursor, @@ -83,6 +83,7 @@ import { createPromptInputTransientState } from "./prompt-input/transient-state" import { showToast } from "@/utils/toast" import { hiddenProjectWorktree } from "@/utils/amicode-hidden-project" import { ImagePreview } from "@opencode-ai/ui/image-preview" +import { setClipboardImageHandler } from "@/utils/global-clipboard" import type { ReferenceInfo } from "@opencode-ai/sdk/v2/client" export { createPromptInputHistory } @@ -1154,12 +1155,22 @@ export const PromptInput: Component = (props) => { setCursorPosition(editorRef, promptLength(prompt.current())) }, addPart, - readClipboardImage: platform.readClipboardImage, + // Amicode webview: image on the OS clipboard is only readable via the outer + // webview's navigator.clipboard.read() (chat_panel.ts). Bridge first so + // Ctrl+V in the iframe attaches the image directly (har-moniqs/amicode#365); + // platform fallback preserves desktop/TUI native clipboard. + readClipboardImage: async () => (await readClipboardImageViaBridge()) ?? (await platform.readClipboardImage?.()) ?? null, // Webview-iframe paste fallback; self-gates to a no-op outside the webview. readClipboardText: () => readClipboardViaBridge(), getPathForFile: platform.getPathForFile, }) + // Framed webview: the window-level fallback (global-clipboard.ts) is the + // sole ⌘V owner. When the clipboard carries no text it offers the media to + // this slot, landing it in the composer's attachment pipeline (mirrors v2). + setClipboardImageHandler((file) => void addAttachments([file])) + onCleanup(() => setClipboardImageHandler(undefined)) + const fileAttachmentInput = () => ( (fileInputRef = el)}