Repository navigation
GUI composer: an image paste is dropped in silence, so Cmd+V does nothing and no line is logged (Part 1/2) #1785
Description
Origin: rant 2026-09-30T09:35:04.553809+08:00
Part: 1/2
The host reported (translated from the Chinese original, 2026-09-30T09:33):
GUI input box: Cmd+V, nothing happens at all.
The image-attachment code has been on master since e46c160a (PR #1109, shipped in
v0.2.93) — and the evidence says it has never once run. This issue covers the paste and
drop paths: read every surface the event actually carries, and make every refusal visible.
The file-picker entry point and the format-alignment with the TUI are Part 2/2.
The measured facts
~/.emrg/emrg-gui.log— the full log from 2026-08-05 to 2026-09-30, 10,939[gui]lines —
containssaveImagezero times. The success path inmain.jslogs
[gui:saveImage] … (<n> bytes)unconditionally, so a single successful attach would be
visible. Zero hits across seven weeks of use deletes "it worked before".<session>/images/in this machine's sessions holds 41 files, all TUI-named
(Image1_<8hex>.png, latest 2026-09-28T13:13); no GUI-named file exists.- The
emrg:saveImageIPC is registered and its channel name matches the preload's
(preload.jsexposessaveImageover the same channel) — ruled out by reading, not
assumed.
Localisation: what was ruled out, and what could not be measured
Requirement 1 of the rant is reproduce it before you edit anything (translated from the
Chinese original). Here is what that attempt produced, plus what it honestly did not.
Ruled out by reading. The preload does expose the channel; the IPC name matches on both
sides. A missing bridge is not the cause.
Not measurable, and why.
- A real Electron probe: extracting the Electron binary from
~/Library/Caches/electron/…electron-v42.2.0-darwin-arm64.zipinto/tmpand executing it
yieldsKilled: 9for every executable there — including a copied/bin/echo. That is
an OS-layer policy on that path, not EMRG's sandbox (the profile is(allow default),
and reachable code adds nothing). - The running GUI: the host's GUI is running (
/Applications/EMRG.app, the packaged
v0.3.7 build, logging live), but it holds the released code, not this working tree, and it
exposes no debugging port — so it can be neither driven nor made to run the fix
interactively from a cycle. - A CDP probe: Chrome answers on
127.0.0.1:9222, butnavigator.clipboardisundefined
on adata:URL (insecure context), and dispatched paste events never fired even for
text — so the probe itself is unprovable and proves nothing. Stopped rather than
reported as a reading.
Conclusion, stated as what it is. No live reproduction was possible, so this issue does
not claim a measured root cause. The code reading is conclusive about the shape of the
failure, which is enough to fix it: Composer.tsx's handlePaste reads only
event.clipboardData.files, and a clipboard image from Chromium/macOS commonly arrives on
items (kind: "file") instead — so imgs.length === 0 holds, the handler return falses,
the browser does nothing with an image-only clipboard, and not one line is logged. The
same files-only read sits in the three drop paths. The whitelist
(png/jpeg/gif/webp/bmp/svg+xml) then rejects macOS's TIFF clipboard in silence — while the
TUI accepts it (emrg/client/app.py:488 lists «class TIFF» among the clipboard types it
reads and normalises to PNG). The two clients therefore disagree about the same clipboard, and
the GUI is the one that says nothing.
What this PR changes
renderer/src/lib/composer.ts— a new purecollectImageFiles(carrier)that unions
filesanditems(deduplicated by file identity), exported for unit test. Reading one
more surface is purely additive: whateverfilescarried is still returned.renderer/src/components/Composer.tsx— the paste handler, the tiptap drop handler and the
outer drop handler all go through that collector. A refusal is now visible: when the
event declared an image orFilesand nothing was collected, the notice names the types the
event actually carried; a rejected mime names the type it rejected;fileToBase64and the
emrg:saveImagerejection each name their own reason; the "no open session" early return
reports instead of vanishing. Every notice also writes a symmetric line through
window.emrg.log.renderer/src/lib/i18n-dicts.ts— the five notice strings (zh + en).renderer/css/layout.css—.composer-notice.gui/main.js— everythrowin theemrg:saveImagehandler now logs before it refuses, so
"the IPC was never reached" and "the IPC was reached and refused" stop being
indistinguishable in the log.
The guard
Composer.test.tsx gains real paste (with a File) and drop (with a DataTransfer)
events — the rant's third finding is that no test ever fired these, so nothing would have gone
red. Two mutation arms were run and verified red, then restored byte-for-byte:
- stop reading
items→ 2 lib + 4 component failures; - make the refusals silent again → the 4 refusal tests fail.
Honest boundary
- No live reproduction exists on this machine (above). This PR's claim is "both surfaces are
read, and every refusal speaks"; if the host's clipboard still produces nothing, the next
attempt now writes the layer into the log instead of failing mute. - Part 2/2 — the file-picker entry point, the TUI format alignment (TIFF), and the
app.imagePastewording — is not in this PR. - Nothing here stops, restarts or touches the auto-upgrade chain.
Acceptance
- Pasting an image logs
[gui:saveImage] … (<n> bytes), inserts[📷 …], and writes a file
under<session>/images/. - Dropping an image behaves identically.
- A type outside the whitelist produces a visible notice naming the type, plus a log line.
npm testinrenderer/covers paste, drop and the refusal path, with mutation arms that go
red.
Activity
argszero commentedon Sep 30, 2026
Handled by #1786
pm25coder commentedon Sep 30, 2026
Landed by #1786, squash-merged as
3aa14421(2026-09-30T13:45:26Z) — landing tree24b51c38c75ae8b4e32ed5db487d850d6438bc53, verified against master7f7816e2before the merge. The suite on that tree is clean apart fromtests/test_check_merge_order.py::TestAgainstRealGitHistory::test_the_shapes_git_really_prints, which fails identically on the base tree and belongs to this host's git shape, not to the PR.Closing: the three acceptance points this issue names are in the landed code — every surface the event carries is read (
collectImageFilesunionsfilesanditems), every refusal is visible (the composer notice plus its symmetric[composer:image]log line), andemrg:saveImagenow logs before it throws, so "the IPC was never reached" and "reached and refused" are distinguishable.Part 2/2 — the file-picker entry point and the format alignment with the TUI — remains #1787.