Skip to content

fix(uploads): say how many files a mixed pick, drop or paste skipped - #651

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
JDProfresh:fix/attach-partial-rejection-toast
Oct 18, 2026
Merged

Ark0N merged 1 commit into
Ark0N:masterfrom
JDProfresh:fix/attach-partial-rejection-toast

Conversation

@JDProfresh

Copy link
Copy Markdown
Contributor

When some files in a selection were refused, every upload surface just filtered them out. The Unsupported file type toast only came when nothing at all got through, and the Compose and paste-dialog pickers had no toast at all.

This is the follow-up I offered at the end of #622, including the Compose and paste-dialog pickers.

Problem. Pick a photo, a .sh and a .pdf together and the photo and the PDF upload while the script vanishes without a word. In the Compose dialog and the shell paste dialog it was worse: pick only an unsupported file and nothing happened at all. The paste dialog just stayed open with no explanation.

Cause. Each filter (terminal drop, toolbar Attach, the Ctrl+V paste trap, the tile grid drop, and the Compose and paste-dialog pickers and paste handlers) ran files.filter(_promptAttachKind) on its own. Only three of them toasted, and only when the filtered list came out empty.

Fix. All of those now go through one helper, _admitPromptAttachments() in image-input.js, next to the classifier. It returns the admitted files and reports what it refused:

  • some refused: 1 file skipped: unsupported type (or N files skipped) as an info toast, and the rest still upload
  • all refused: the existing Unsupported file type error toast, now on the Compose and paste-dialog pickers too
  • a clipboard paste that carries text and no admitted file: no toast. The text is pasted as before (copying from a document often puts both on the clipboard), so a toast there would be noise. If the paste has no text, the refused file does get the toast, because otherwise nothing at all would happen.

The helper keys only on what _promptAttachKind() refuses, so it does not care which kinds are admitted (it works the same before and after #621's documents). The two dialogs also reset their file input before handling a pick, the way the toolbar Attach input already does. Without that, picking the same refused file a second time fires no change event and so no toast. The new string has a zh-CN pattern in i18n.js covering singular and plural.

Invariants touched: [Prompt uploads](CLAUDE.md, "Prompt uploads": _promptAttachKind() stays the ONE classifier every pick and paste filter reads; the helper calls it and nothing else decides admission) and the frontend i18n rule in src/web/CLAUDE.md.

Tests.

  • test/toolbar-attach.test.ts: a mixed Attach pick uploads the photo and PDF and toasts the skip. A terminal drop goes through the same filter for both the partial toast and the full-refusal toast (the harness now mounts #terminalContainer). The helper is silent when it admits everything and quiet for a text paste with nothing admitted.
  • test/image-paste-trap.test.ts: image plus refused file uploads the image and toasts. Text plus refused file pastes the text with no toast. A refused file with no text toasts.
  • test/mobile-prompt-composer.test.ts: the Compose picker's mixed and fully-refused picks, the Compose paste with text plus a refused file, and the same three cases in the paste dialog. The harness binds the real helper from image-input.js, so these pin the shipped wording.
  • test/tile-grid-file-drop.test.ts and test/tile-grid-i18n.test.ts: the tile drop counts the skip, and the new string translates. The tile vm harness now loads image-input.js.

I removed each branch of the helper and reverted each call site one at a time, and each change turned at least one of these tests red.

Verified. I ran a throwaway instance from #621 and #622 plus this commit, before they merged, with headless Chromium in an iPhone 13 profile and a desktop profile and a throwaway shell session. I picked photo.jpg + run.sh + notes.pdf through each surface. Observed:

Surface Before After
Attach input (phone, via the filechooser) jpg + pdf paths on the prompt, no word about run.sh same paths, plus 1 file skipped: unsupported type
Terminal drop (phone and desktop) same, silent same paths, plus the skip toast
Compose picker (phone and desktop) paths in the editor, silent same, plus the skip toast
Paste-dialog picker (phone and desktop) paths on the prompt, silent same, plus the skip toast
Paste-dialog picker, run.sh alone dialog stays open, no toast dialog stays open, Unsupported file type
Compose paste of text + run.sh no toast no toast

run.sh never reached the server in either column. Inferred, not observed: the input reset matters on a real device. In headless Chromium, Playwright's setFiles fires change even for the same file, so a repeated pick toasted twice there either way.

Gate on this branch, rebased onto 1.42.0: npm test 11594 passed, 12 skipped. Typecheck, lint, format:check, check:frontend-syntax and check:browser-excludes are all green. The rebase only met the new worktree toasts in i18n.js, and both sets of entries are kept.

A selection mixing a file the server takes with one it refuses dropped the refused one without a word, and the Compose and paste-dialog pickers never toasted even when every file was refused. Every pick, drop and paste filter now goes through one helper that counts what the classifier refused: a partial refusal toasts how many were skipped while the rest upload, a full one keeps the existing Unsupported file type toast, and a clipboard paste that falls back to its text stays quiet.
@Ark0N

Ark0N commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Thanks @JDProfresh, and thanks for following through on #622. This PR routes every pick, drop and paste through one helper, _admitPromptAttachments(), so a mixed selection now says how many files it skipped, and the Compose and paste-dialog pickers finally report an unsupported pick instead of doing nothing.

I traced every call site. Admission still comes only from _promptAttachKind(). The text-paste paths behave exactly as before, and the dialogs still call preventDefault() only when a file was admitted. The input resets read files before clearing value. Typecheck, lint, format, the frontend syntax check and the full npm test gate are green here (11594 passed). I also removed the textPasted guard and the partial-skip branch one at a time, and your tests went red both times. Nice work on those.

One small thing, which I will do at merge time unless you want to push it first:

  • CLAUDE.md:218 (Prompt uploads) and docs/architecture-invariants.md:1127 still name _promptAttachKind() as the filter every surface reads. That stays true, but the next surface should admit through _admitPromptAttachments() so it keeps the toasts. I will add a clause saying new surfaces admit through the helper and never filter on _promptAttachKind() directly.

I will add the changeset and merge. Thanks again for closing the loop on this.

@Ark0N
Ark0N merged commit 2cfa278 into Ark0N:master Oct 18, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…t in the docs

CLAUDE.md (Prompt uploads) and architecture-invariants still named
_promptAttachKind() as the filter every surface reads. That stays true, but
the surfaces now admit through _admitPromptAttachments(), which is what says
how many files a pick skipped. A new surface that filtered on the classifier
directly would drop files silently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 18, 2026
…k, drop or paste skipped

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

# Conflicts:
#	CLAUDE.md
@Ark0N

Ark0N commented Oct 18, 2026

Copy link
Copy Markdown
Owner

Merged and shipped in 1.43.0. Thanks @JDProfresh! I added the clause to CLAUDE.md and architecture-invariants saying new upload surfaces admit through _admitPromptAttachments(), never by filtering on _promptAttachKind() directly, so the next surface keeps the toasts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants