Repository navigation
Chat: pasting a copied file attaches it, like dropping it - #29089
Conversation
…ttaches it Copying an image file in Finder and pasting into Chat typed the file name and attached nothing: the paste only attached a bitmap from the clipboard. Copied files now go through the composer's drop route, on both the app-menu paste and the paste event. Fixes STA-10652
…ts for the clipboard read
|
Copying an image file in Finder and pasting it into Chat could type its name or attach Finder’s small icon instead of the file. This PR sends copied files through the existing file attachment route, so pasting attaches the same original file as dropping it. It also preserves screenshot pixels when the clipboard includes the screenshot’s saved-file location, even after that saved file is deleted. Review at
The temporary pending attachment is replaced by a separately started file attachment after classification. Its removal and original draft lifetime do not govern that second attachment. Fixing is paused at the required design review; no change has been pushed by this review session. Earlier pending-admission, focus, composition, capability and late-completion repairs remain in place. The screenshot-with-missing-file regression is source-fixed by the new macOS rule. Unusual filename labels, alternate clipboard producers and inherited drop-specific error wording remain deferred concerns without new proven failures. Verified: correctness, architecture and readiness/performance source reviews on the exact head; full PR workflow 38986389847, including static analysis, all five unit shards, relay, both package jobs and the successful required aggregate gate. No additional local tests, static checks or app launches were run in this review. Real final-head Electron QA and its screenshots are coordinator-owned and have not been supplied to this session; no app-level pass or ready certification is claimed. For Brennan’s review Final main-versus-branch screenshots remain outstanding. The places requiring attention are the macOS source decision and the pending-to-file handoff. Update for head Verified: the full PR workflow 38996477676 passed on this exact head. Real hidden-Electron QA on macOS at this head passed every case (results and screenshots in the PR description): Finder copies attach the real file, a deleted Finder file shows the drop route's notice and never the icon, a screenshot whose saved file is gone pastes its picture, the pending chip holds Send during a slowed clipboard read, and removing a chip during the read cancels that file. A true close-and-reopen of the composer could not be produced live (Orca keeps it alive while away), so that case rests on the integration test. Update for head |
A local file drop checked each file before adding it, with nothing in the composer meanwhile: Send during the check went without the file, which then landed in the next draft; unmounting mid-check lost it; and files queued behind an input-method composition were lost on unmount. Drops now reserve a pending chip per file before the check, so Send waits as it already does for server-upload drops. The prepared attachment travels through the existing composition queue as one operation, settles into its draft scope after unmount, releases its chips when refused or disabled, and reads the scope's current image capability when it applies.
…025/sta-10652-paste-copied-files
…025/sta-10652-paste-copied-files
…025/sta-10652-paste-copied-files # Conflicts: # src/renderer/src/components/native-chat/native-chat-composer-paste-options.ts # src/renderer/src/components/native-chat/use-native-chat-composer-attachments.ts
…wn hook Main's server-owned paste pushed the attachments hook past the file length limit.
…even beside its saved file macOS lists a screenshot tool's saved file beside its picture, so the paste read as a file copy and attached nothing once the file was gone. Only a Finder copy also names its files as text.
…inder files from a screenshot The paste event hides Finder's file names, so a path-backed picture reserves its chip at once and the native clipboard decides: named files go to the drop route, a screenshot's picture is saved from the clipboard. Both paste routes now share the clipboard-picture save.
…pending' into brennanb2025/sta-10652-paste-copied-files
…ures file The paste tests outgrew the test file length limit.
…ame, not a stacked icon A local drop now waits as a pending chip, which drew the kind icon, the file name and a centered spinner on top of each other. Without a thumbnail the chip shows the spinner above the name; with one, the dimmed picture keeps a spinner strip along its bottom.
…pending' into brennanb2025/sta-10652-paste-copied-files
…ipboard decides A macOS paste of Finder files reserved one placeholder chip, asked the native clipboard whether it held files or a screenshot picture, and then started a separate attach for the files. Removing the placeholder did not cancel that attach, and leaving the chat before the answer lost the paste. The paste now reserves one chip per copied file before asking, and captures the drop route's intake (owner, destination, chips) at the same moment. Files continue those exact chips through the existing local check or server upload, so removal cancels by the original chip and an unmounted composer still finishes into its draft. A picture keeps the first chip and drops the rest. Ordinary drops and the picker keep their fresh-admission checks.
…isable through the real intake Drives the real pending cache, composer attachments, external intake and paste hook with a delayed clipboard answer: removed chips never attach (before or after the answer, through input-method composition), an unmount finishes both the local and paired-server routes into the original draft with the original chip IDs, and disabling the composer meanwhile does not bring the paste back.
…2-paste-copied-files # Conflicts: # src/renderer/src/components/native-chat/native-chat-external-attachments-destination.test.tsx # src/renderer/src/components/native-chat/native-chat-resolved-path-ownership.ts # src/renderer/src/components/native-chat/native-chat-session-attachment-drop.ts # src/renderer/src/components/native-chat/use-native-chat-composer-attachments.ts # src/renderer/src/components/native-chat/use-native-chat-external-attachments.ts # src/renderer/src/components/native-chat/use-native-chat-resolved-path-attachments.ts
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
📝 Walkthrough
Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to
|
ELI5
If you copied an image file in Finder and pasted it into Chat, Chat typed the file's name into the message box and attached nothing. Now pasting a copied file attaches it, exactly as dragging the same file onto Chat does.
What Changed
The problem. A user reported in Discord that they couldn't paste images into Chat. A live reproduction on current main showed:
shot.pngand attached nothing. No message explained why.Why it happened. Chat's paste only attached a picture read from the clipboard. A Finder copy carries the file's location and its name as text, but on macOS no picture of the file. So the save step found nothing, and Chat fell back to typing the name, on purpose ("a copied file that brings no image still types its name"). On 1.4.224, a copy that carried both a file location and a PNG also attached nothing; #27346 fixed that case in 1.4.225.
Before / after, as the user sees it
The mechanism. Chat already has one route for files from outside: the drop route (
captureExternalDrop, behindattachExternalPaths), used for drag-and-drop and the file picker. It checks the files can be read, uploads them when the chat runs on a paired Orca server, and shows the existing notice when the chat can't take local files. Pasting now uses that route whenever the clipboard holds copied files and nothing else (no text, or only text that names those files):readClipboardFilePaths, which already reads copied files on macOS, Windows and Linux. Before, Chat only used those paths to decide whether the text was just a label.getPathForFile, the same call the drop handler uses. A pasted picture has no path, so it keeps the existing picture flow.When copied files are found, the picture on the clipboard is ignored. A file manager's picture can be the file's icon, not the file itself.
Screenshot tools on macOS. A screenshot tool puts the picture plus a link to its saved file on the clipboard, and macOS then also lists that file as a copied file. Treating that as a file copy attached nothing once the saved file was deleted (main attached the picture). A native clipboard probe showed the one reliable difference: a Finder copy also puts the file's name on the clipboard as text, a screenshot tool puts no text. So on macOS, a clipboard with files, no text and a picture pastes the picture, whether or not the saved file still exists; a Finder copy stays a file copy and never becomes its icon; a deleted Finder file shows the drop route's "Couldn't read the dropped files." Windows and Linux are unchanged.
Right-click Paste on macOS. That paste event hides the clipboard text, so Chat can only tell a Finder copy from a screenshot by reading the native clipboard, which takes a moment. Chat reserves one pending chip per copied file before that read, so Send is held from the first moment, and those same chips carry the files through the drop route (or, for a screenshot, the first one becomes the picture). Removing a chip during the read cancels that file; a chat view that closes during the read still gets its files in its draft.
One ordering change on the app-menu path: before it decides about a picture, Chat now waits for its clipboard text and file-list read, a single local call to the main process, so it can tell whether files were copied. A screenshot's chip therefore appears after that read, not alongside it. Text still never waits on the picture, which was the guarantee from #23784.
Why
The alternative was to keep "attach a picture" as the only paste-to-attach path and teach the macOS main process to read a copied image file into a picture, as it already does on Windows. That still loses non-image files, keeps two meanings of "paste a file", and attaches a re-encoded copy instead of the file. Treating a pasted file as a dropped file gives one rule for both gestures, and inherits the drop route's readability checks, paired-server upload and refusal notices without new code.
Differences from the common pattern
The common pattern treats every file on a paste as an attachment, taken from the paste event's files. This PR matches that for the paste event (Ctrl+V on Windows and Linux). Differences:
Linked Issue
Linear STA-10652 (from a Discord report).
Visual Proof
Real hidden-Electron QA on macOS at
915cc102363(clipboards written natively: a Finder copy carries the file link, the file list, the file name as text and a small icon; a screenshot tool's board carries a PNG and its saved file's link, no text). Earlier rounds compared main on the M4Air: main attached Finder's 64-pixel icon for a Finder copy, typed or dropped PDFs, and showed a broken preview for a screenshot whose saved file was deleted.After merging main (head
ae2255e6b73). #29173 was squash-merged, so this PR now targets main with main merged in. Its diff against main is byte-identical to the reviewed head above. A smoke round on the merged build, on macOS (M4Air) in a hidden test copy with a throwaway home, passed:Testing
Updated
use-native-chat-composer-paste.test.tsx: a Finder copy pasted from the app menu, and a pasted event whose files have local paths, both go to the drop route with nothing typed and no picture saved. The menu-paste tests that pinned the old "type the name" behavior were replaced.macOS screenshot rule: a menu paste of files + no text + a picture pastes the picture on macOS and stays a file copy elsewhere.
Right-click Paste on macOS, through the real pending-chip cache and drop route: removing a chip before or after the clipboard read never attaches that file; closing and reopening the composer during the read lands the files in the original draft with the original chips (local and paired-server upload); disabling and re-enabling during the read never brings the paste back.
Unit tests and typecheck: CI (full PR workflow green on this head).
Live: hidden-Electron QA on macOS, above. Windows: an earlier round confirmed Explorer's Ctrl+V carries the real file paths; Windows code is unchanged since.
I manually tested these changes locally
Automated tests added/updated, or explained why not below
Review
Agent skill upstream boundary
Notes
getPathForFileand itsreadClipboardFilePathsreturns nothing, so its paste behavior is unchanged.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)