Skip to content

fix(web): attach a picture pasted into the message box - #152

Merged
brianjfox merged 2 commits into
mainfrom
fix/paste-attachments
Nov 4, 2026
Merged

brianjfox merged 2 commits into
mainfrom
fix/paste-attachments

Conversation

@brianjfox

Copy link
Copy Markdown
Contributor

Closes #151

What

A picture (or any readable file) pasted into the message box attaches, the same as one chosen with the paperclip or dropped.

Why

The box took no files from the clipboard, so a pasted screenshot did nothing and said nothing. Details in #151.

Changes

  • apps/web/src/pages/workspace/pasted-files.ts: pastedFiles reads the clipboard's files; a clipboard picture, which browsers name image.png every time, gets a stamped name such as pasted-2026-11-04-10-52-07.png, so a second paste is not refused as a duplicate. A named file keeps its name.
  • apps/web/src/pages/workspace/index.tsx: the textarea's paste handler hands those files to the same addFiles the picker and the drop zone use, with its limits and refusals; a text paste is left to the browser.
  • pasted-files.test.ts covers the naming and the empty cases.

The message box took no files from the clipboard, so a pasted screenshot did nothing. Pasted files attach like chosen ones; a clipboard picture, which always arrives as image.png, gets a stamped name so two pastes are not refused as duplicates.
@corbits-triage

corbits-triage Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Triage: Pass

No changes needed from you. A maintainer will review this pull request.

Approve held: code review has 2 findings at P2 or above open on this head.

Comment on lines +1396 to +1401
onPaste={(event) => {
const files = pastedFiles(event.clipboardData);
if (files.length === 0) return;
event.preventDefault();
addFiles(files);
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Paste attaches files while attaching is turned off

When the provider is missing or no MIME types are allowed, the drop zone is disabled and the paperclip is hidden. The new paste handler checks neither condition, so pasting still attaches files.

Paste a screenshot when providerMissing is true or limits.mimeTypes is empty.

Resolve this comment. Triage will not finish while it is open.

Comment on lines +1398 to +1400
if (files.length === 0) return;
event.preventDefault();
addFiles(files);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Paste is silently swallowed while limits are still loading

The handler calls preventDefault() before addFiles. addFiles returns early when limits is undefined, so the paste is cancelled and nothing appears: no attachment and no message. This is the same silent failure the PR is meant to fix.

Paste an image before the attachment limits have loaded.

Resolve this comment. Triage will not finish while it is open.

Comment on lines +1398 to +1399
if (files.length === 0) return;
event.preventDefault();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Mixed text-and-image clipboard drops the text

Whenever the clipboard carries any file, preventDefault() runs, even if it also carries text. Office apps and some browsers put a picture next to the text when you copy, so a paste that should insert text attaches an image and loses the text.

Copy cells from Excel or text from Word, which also puts an image/png on the clipboard, then paste into the message box.

Resolve this comment. Triage will not finish while it is open.

Comment on lines +28 to +32
const stamp = at
.toISOString()
.slice(0, 19)
.replace("T", "-")
.replaceAll(":", "-");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Pasted names are only unique to the second, so close pastes collide as duplicates

The name is built from the timestamp cut down to whole seconds. Two pastes of a clipboard picture within the same second get the same pasted-…png name. Within one paste, a later file is pushed forward by index * 1000 ms, so its name can also match the first file of a paste made a second later. The attachment list then rejects the second file as already attached, which is the bug this PR set out to fix.

Press Ctrl+V twice quickly with a screenshot on the clipboard, or paste two images and paste again a second later.

Resolve this comment. Triage will not finish while it is open.

…astes, and never collides names

The paste handler attached files while attaching was off or its limits were still loading, cancelled the paste either way, and took a picture an office app copies beside text in place of the text. Pasted pictures were named to the second, so two quick pastes collided. Paste follows the drop zone's condition, a paste with text is the browser's, and each pasted picture gets a counted name.
Comment on lines +1401 to +1402
const files = pastedFiles(event.clipboardData);
if (files.length === 0) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · A copied file that also puts text on the clipboard is not attached

The handler leaves the paste alone whenever pastedFiles returns nothing. pastedFiles (in pasted-files.ts, not part of this diff) returns nothing whenever the clipboard carries any non-blank text/plain. When you copy a file in a file manager, the browser usually also puts the file name on the clipboard as text/plain. So pasting a real file inserts its name instead of attaching it, which goes against the PR's goal of attaching any readable file.

Copy a PDF or image file in macOS Finder and paste it into the message box in Chrome.

Resolve this comment. Triage will not finish while it is open.

): File[] {
if (clipboard === null) return [];
if (clipboard.getData("text/plain").trim() !== "") return [];
return Array.from(clipboard.files, (file) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 · Any text on the clipboard throws away the pasted files

If the clipboard has any non-blank text/plain, the code skips every file. Copying a file in a file manager (Finder, Explorer) or using "Copy Image" in some browsers puts the file name or image URL on the clipboard as text/plain next to the file. The file is then silently dropped: the paste inserts a name or URL instead of attaching anything, which is the silent failure #151 is meant to fix.

Copy report.pdf in macOS Finder and paste it into the message box: the clipboard holds the file plus text/plain "report.pdf".

Resolve this comment. Triage will not finish while it is open.

Comment on lines +32 to +36
.toISOString()
.slice(0, 19)
.replace("T", "-")
.replaceAll(":", "-");
const extension = EXTENSIONS[type];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 · Name stamp uses UTC instead of the person's local time

toISOString() always gives UTC. A stamp meant to show "the time for the person" is wrong by their UTC offset; outside UTC it can even show a different date from the one they pasted on.

Paste a screenshot from a browser set to any time zone other than UTC.

Resolve this comment. Triage will not finish while it is open.

@brianjfox
brianjfox merged commit 724ec1c into main Nov 4, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A screenshot pasted from the clipboard is not attached to the message

1 participant