Summary
Dragging a non-image file into the TUI leaves the composer holding a shell-escaped path, which no file tool can open. Drop /tmp/a b/c d.txt and the composer gets /tmp/a\ b/c\ d.txt. That is correct shell quoting but it is not a path: Read/Edit resolve it literally, look for a directory component containing a backslash, and fail.
So the user drags a file in and the agent cannot read it. Images are unaffected (they become [image N] attachments); this only bites .txt/.rs/.pdf/etc.
Environment
- macOS 15, aarch64
- jcode v0.84.0 and current
master
Reproduction
$ mkdir -p "/tmp/jcode drop repro" && echo content > "/tmp/jcode drop repro/my notes.txt"
Drag that file from VS Code into the composer. It types:
/tmp/jcode\ drop\ repro/my\ notes.txt
$ [ -f '/tmp/jcode\ drop\ repro/my\ notes.txt' ] && echo opens || echo "does not open"
does not open
$ [ -f '/tmp/jcode drop repro/my notes.txt' ] && echo opens
opens
Root cause
parse_dropped_paths already does the hard part: it resolves shell escapes, '...', "...", and file:// URLs, and verifies each token is an existing file. Those resolved paths are just never written back for non-image drops.
promote_dropped_images (crates/jcode-tui/src/tui/app/input.rs) returns early unless every dropped path is an image:
pub(super) fn promote_dropped_images(app: &mut App) -> bool {
let Some(images) = dropped_image_files(&app.input) else {
return false; // <- a dropped .txt lands here, composer left escaped
};
dropped_image_files maps over the paths and ?-returns on the first non-image, so one text file discards the whole batch.
Suggested fix
When the composer is exactly dropped paths, rewrite it to the verified paths parse_dropped_paths already produced. Since it confirmed each file exists, the rewrite is lossless. Guards that matter:
- only fire when the whole composer parses as dropped paths, so prose merely containing a path is never rewritten
- no-op when the path is already clean, so a typed path does not burn an undo entry
- keep multi-file drops quoted so they stay separable
Branch with a fix and tests, if useful as a reference: https://github.com/aphelion31/jcode/tree/fix/macos-clipboard-image-applescript
The tests assert Path::is_file() on the resulting composer rather than a string shape, so they pin the actual requirement (openable). Verified against real files whose directory and filename both contain spaces:
[PASS] R1 vscode shell-escaped -> /tmp/.../my notes.txt
[PASS] R2 single-quoted [PASS] R3 double-quoted
[PASS] R4 file:// URL [PASS] R5 clean path untouched
[PASS] R6 prose not rewritten [PASS] R7 image drop still attaches
[PASS] R8 multi-file separable [PASS] R9 bracketed-paste route
Neutering the fix flips exactly R1-R4 to FAIL while the guard cases R5-R9 stay PASS, so the tests bind to the fix rather than passing incidentally. cargo test --test e2e is unaffected: 55 pass, and the 3 failures are pre-existing (target/selfdev/jcode missing in my checkout) and reproduce identically on the parent commit.
Notes / non-goals
Deliberately not handled, since neither can be distinguished from a legitimate filename:
~/... — the shell expands it before the terminal sends it, so a real drop never produces it
- percent-encoded paths without a
file:// scheme — indistinguishable from a filename that genuinely contains %20
Filing as an issue rather than a PR because pull_request_creation_policy is collaborators_only, so POST /repos/1jehuang/jcode/pulls returns aphelion31 does not have the correct permissions to execute CreatePullRequest. Happy to open one if that ever changes, or you're welcome to take the diff and rewrite it.
Summary
Dragging a non-image file into the TUI leaves the composer holding a shell-escaped path, which no file tool can open. Drop
/tmp/a b/c d.txtand the composer gets/tmp/a\ b/c\ d.txt. That is correct shell quoting but it is not a path:Read/Editresolve it literally, look for a directory component containing a backslash, and fail.So the user drags a file in and the agent cannot read it. Images are unaffected (they become
[image N]attachments); this only bites.txt/.rs/.pdf/etc.Environment
masterReproduction
$ mkdir -p "/tmp/jcode drop repro" && echo content > "/tmp/jcode drop repro/my notes.txt"Drag that file from VS Code into the composer. It types:
Root cause
parse_dropped_pathsalready does the hard part: it resolves shell escapes,'...',"...", andfile://URLs, and verifies each token is an existing file. Those resolved paths are just never written back for non-image drops.promote_dropped_images(crates/jcode-tui/src/tui/app/input.rs) returns early unless every dropped path is an image:dropped_image_filesmaps over the paths and?-returns on the first non-image, so one text file discards the whole batch.Suggested fix
When the composer is exactly dropped paths, rewrite it to the verified paths
parse_dropped_pathsalready produced. Since it confirmed each file exists, the rewrite is lossless. Guards that matter:Branch with a fix and tests, if useful as a reference: https://github.com/aphelion31/jcode/tree/fix/macos-clipboard-image-applescript
The tests assert
Path::is_file()on the resulting composer rather than a string shape, so they pin the actual requirement (openable). Verified against real files whose directory and filename both contain spaces:Neutering the fix flips exactly R1-R4 to FAIL while the guard cases R5-R9 stay PASS, so the tests bind to the fix rather than passing incidentally.
cargo test --test e2eis unaffected: 55 pass, and the 3 failures are pre-existing (target/selfdev/jcodemissing in my checkout) and reproduce identically on the parent commit.Notes / non-goals
Deliberately not handled, since neither can be distinguished from a legitimate filename:
~/...— the shell expands it before the terminal sends it, so a real drop never produces itfile://scheme — indistinguishable from a filename that genuinely contains%20Filing as an issue rather than a PR because
pull_request_creation_policyiscollaborators_only, soPOST /repos/1jehuang/jcode/pullsreturnsaphelion31 does not have the correct permissions to execute CreatePullRequest. Happy to open one if that ever changes, or you're welcome to take the diff and rewrite it.