fix: attach clipboard images despite text/html and synthesize filenames (#2489) - #2520
Conversation
…es (#2489) The chat composer's paste handler (added in #2340) deferred to a text paste whenever the clipboard carried text/plain OR text/html. A textarea can only ever insert the text/plain representation, so for clipboards with text/html but no text/plain — a browser's right-click Copy Image, an Office chart copy — the whole gesture was a silent no-op: no attachment, no text, nothing. Changes: - Defer to text only when text/plain is actually present. Image-bearing clipboards without insertable text now attach through the SAME onUploadFiles path the file picker and drag-and-drop use, so pasted and picked images are indistinguishable downstream (size cap, type allowlist, max-file cap, and the upload error banner all apply). - Synthesize pasted-image-<timestamp>.<ext> filenames for clipboard images that arrive unnamed (rejected by the server's extension allowlist) or with the browser's fixed 'image.png' placeholder (identical chip labels for every pasted screenshot). Files carrying a real name keep it. A batch index disambiguates multiple images in one paste. - Keep the macOS Office guard: clipboards with real text/plain still paste as text, never attaching the junk image rendering Office ships alongside copied text. Tests: paste attaches (ChatInput unit + ChatPage funnel); text paste untouched; oversize pasted image rejected with the same file_too_large banner a picked file gets, without reaching the server; filename synthesis (placeholder, unnamed, real-name, batch, non-image). Evidence: temp-screenshots/chat-clipboard-image-paste/ captured by scripts/capture-clipboard-image-paste.mjs (asserting harness). Closes #2489
Design Review (Fable 5) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Defer condition now matches what a textarea can actually insert (text/plain), fixing the root cause; naming and funnel reuse follow established precedents. [DESIGN-REVIEWED] 55e018b |
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsI've reviewed the diff, the surrounding paste handler, and the base AUTOSDE rules. The core change is sound:
The batch/identity logic, extension mapping, and No findings. [OPUS-REVIEWED] 55e018b Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsFINDING -- website/src/components/ChatInput.tsx:87 -- a real pasted file named False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — 🟡 CONCERNSAdvisory UX-level review of UX-Verdict: CONCERNS Both evidence screenshots show the pasted-image chip as a broken thumbnail with a raw server path overflowing the composer — the feature's one visual proof looks broken. Watch
Suggestions
[UX-REVIEWED] 55e018b |
…es (kirodotdev#2489) (kirodotdev#2520) The chat composer's paste handler (added in kirodotdev#2340) deferred to a text paste whenever the clipboard carried text/plain OR text/html. A textarea can only ever insert the text/plain representation, so for clipboards with text/html but no text/plain — a browser's right-click Copy Image, an Office chart copy — the whole gesture was a silent no-op: no attachment, no text, nothing. Changes: - Defer to text only when text/plain is actually present. Image-bearing clipboards without insertable text now attach through the SAME onUploadFiles path the file picker and drag-and-drop use, so pasted and picked images are indistinguishable downstream (size cap, type allowlist, max-file cap, and the upload error banner all apply). - Synthesize pasted-image-<timestamp>.<ext> filenames for clipboard images that arrive unnamed (rejected by the server's extension allowlist) or with the browser's fixed 'image.png' placeholder (identical chip labels for every pasted screenshot). Files carrying a real name keep it. A batch index disambiguates multiple images in one paste. - Keep the macOS Office guard: clipboards with real text/plain still paste as text, never attaching the junk image rendering Office ships alongside copied text. Tests: paste attaches (ChatInput unit + ChatPage funnel); text paste untouched; oversize pasted image rejected with the same file_too_large banner a picked file gets, without reaching the server; filename synthesis (placeholder, unnamed, real-name, batch, non-image). Evidence: temp-screenshots/chat-clipboard-image-paste/ captured by scripts/capture-clipboard-image-paste.mjs (asserting harness). Closes kirodotdev#2489
Summary
The chat composer's paste handler (from #2340) deferred to a text paste whenever the clipboard carried
text/plainortext/html. A<textarea>can only ever insert thetext/plainrepresentation — so for clipboards withtext/htmlbut notext/plain(a browser's right-click Copy Image, an Office chart copy) the whole gesture was a silent no-op: no attachment, no text, nothing. Separately, clipboard images arrive unnamed (rejected by the server's extension allowlist) or as the browser's fixedimage.pngplaceholder (identical chip labels for every pasted screenshot).Changes
text/plainis present. Image-bearing clipboards without insertable text now attach through the SAMEonUploadFilespath the file picker and drag-and-drop use — pasted and picked images are indistinguishable downstream, so the 50 MB cap, 20-file cap, MIME allowlist, and the upload error banner all apply identically.pasted-image-<timestamp>[-<n>].<ext>for clipboard images arriving unnamed or asimage.png(same class asuseScreenSnip'ssnip-<ts>.pngprecedent). Real filenames are kept; the batch suffix counts only renamed files, so a real-named sibling never produces an orphan-2.text/plainstill paste as text. The issue's "attach AND insert on mixed payloads" variant was deliberately rejected — Office ships a junk image rendering alongside every copied text selection, and attaching it on every Office paste would be a regression of feat: opt-in Slack setup and multi-channel repositioning #2340's fix.Tests
ChatInput.paste.test.tsx: Copy-Image clipboard attaches; Office text+image clipboard still does NOT; Files-only paste unchanged; filename synthesis (placeholder, unnamed, real-name kept, batch disambiguation, renamed-only counting, non-image never renamed); text-paste suite untouched.ChatPagePasteImage.test.tsx(new): page-level funnel — a pasted image reachesapi.uploadFiles; an OVERSIZE pasted image surfaces the samefile_too_largebanner a picked file gets and never reaches the server.npx tsc -bclean; vitest 11 802 passed (877 files); backend isort/flake8/mypy clean (no backend files changed).Screenshots
Captured by the new asserting harness
website/scripts/capture-clipboard-image-paste.mjs(real built SPA, gateway-free):Review
Pre-push fleet: Opus 5 — CLEAN, 0/2 blocking (both optional LOWs addressed: batch counter applied + test-pinned; harness cleanup matches sibling convention). GPT reviewer spawn timed out then was governance-barred; a contract-driven self-review against the codex charter covered guard-narrowing clipboard shapes,
new Filesupport, SVG extension surface (pre-existing posture, unchanged trust boundary), and cross-browserclipboardData— no Critical/High.Closes #2489