From 43657f3d69a1fe195ef6dd8d22763d49d39242fe Mon Sep 17 00:00:00 2001 From: t3-turbo-simulation Date: Wed, 5 Aug 2026 14:43:25 -0400 Subject: [PATCH 1/2] fix(web): keep pasted link text when the clipboard also carries an image Copying a link from a browser or chat app often puts the URL text and a preview image on the clipboard together. The composer's paste handler saw the image, prevented the default paste to route the file into the attachment flow, and silently discarded the text half - the pasted link never entered the message. Expose an insertText handle on the prompt editor and re-insert the clipboard text after attaching the images. Co-Authored-By: Claude Fable 5 --- apps/web/src/components/ComposerPromptEditor.tsx | 13 ++++++++++++- apps/web/src/components/chat/ChatComposer.tsx | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/ComposerPromptEditor.tsx b/apps/web/src/components/ComposerPromptEditor.tsx index 169126788ae8..d419fc0d87d0 100644 --- a/apps/web/src/components/ComposerPromptEditor.tsx +++ b/apps/web/src/components/ComposerPromptEditor.tsx @@ -868,6 +868,7 @@ export interface ComposerPromptEditorHandle { focus: () => void; focusAt: (cursor: number) => void; focusAtEnd: () => void; + insertText: (text: string) => void; readSnapshot: () => { value: string; cursor: number; @@ -1691,9 +1692,19 @@ function ComposerPromptEditorInner({ ), ); }, + insertText: (text: string) => { + editor.update(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + selection.insertRawText(text); + return; + } + $getRoot().selectEnd().insertRawText(text); + }); + }, readSnapshot, }), - [focusAt, readSnapshot], + [editor, focusAt, readSnapshot], ); const handleEditorChange = useCallback((editorState: EditorState) => { diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index e92ecd497e3d..4e0ebb0d8aed 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -2392,6 +2392,14 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) if (imageFiles.length === 0) return; event.preventDefault(); void addComposerImages(imageFiles); + // Copying a link from a browser or chat app often puts the URL text and a + // preview image on the clipboard together. preventDefault above suppresses + // the native text insertion, so re-insert the text half instead of + // silently dropping it with the attachment. + const text = event.clipboardData.getData("text/plain"); + if (text.length > 0) { + composerEditorRef.current?.insertText(text); + } }; const onComposerDragEnter = (event: React.DragEvent) => { From a25c8d53d6c52b8b425412a46f467273b6d89365 Mon Sep 17 00:00:00 2001 From: t3-turbo-simulation Date: Wed, 5 Aug 2026 15:10:18 -0400 Subject: [PATCH 2/2] fix(web): only re-insert URL-shaped paste text alongside image attachments --- apps/web/src/components/chat/ChatComposer.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 4e0ebb0d8aed..bcf918ea7711 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -273,6 +273,17 @@ function isInsideComposerFloatingLayer(element: Element): boolean { return element.closest(COMPOSER_FLOATING_LAYER_SELECTOR) !== null; } +/** A pasted link the user meant to keep, as opposed to accessory clipboard text. */ +function isHttpUrlText(text: string): boolean { + if (!/^https?:\/\//i.test(text)) return false; + try { + const url = new URL(text); + return url.protocol === "http:" || url.protocol === "https:"; + } catch { + return false; + } +} + const ComposerFooterModeControls = memo(function ComposerFooterModeControls(props: { showInteractionModeToggle: boolean; interactionMode: ProviderInteractionMode; @@ -2395,9 +2406,11 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) // Copying a link from a browser or chat app often puts the URL text and a // preview image on the clipboard together. preventDefault above suppresses // the native text insertion, so re-insert the text half instead of - // silently dropping it with the attachment. - const text = event.clipboardData.getData("text/plain"); - if (text.length > 0) { + // silently dropping it with the attachment. Only URL-shaped text + // qualifies: OS file copies and image copies can carry accessory + // text/plain (file names, local paths) the user never meant to insert. + const text = event.clipboardData.getData("text/plain").trim(); + if (isHttpUrlText(text)) { composerEditorRef.current?.insertText(text); } };