composer: a file's upload never waits for a sibling image's preview
Fixes an internal report: in a mixed paste or drop, an ordinary file's upload waited for a sibling image's preview to be decoded.
intakeFiles awaited every file's preparation, readAttachment(image) included, before it emitted uploading or called the batch upload. So a non-image file with no known local path, and its send-holding placeholder, waited for the image beside it (up to the image pool's 20 s job deadline).
What changes
- Each image's preview is made beside the transport. The batch's membership (known local paths, WebKit's unreadable zero-byte folders, files cancelled meanwhile) never depended on decoding, so the batch's one ordered upload now starts at once. What a known-path or refused file becomes (its link, or its failure) follows that file's own preview without holding the batch.
intakeFilesstill returns only after every preview is made. - The upload is unchanged: one authorised call in manifest order, with the same batch token, the same per-file cancellation and the same index mapping. It is not split into one call per file, since the native and cloud stagers depend on manifest order.
- Each file becomes its chip as soon as the upload answers. An image's original pairs with its preview once that is made (
originalstill comes after the image'sready). - An image's tile still holds a send until its own preview is made: its
uploadingmark waits for its preparation, and is never sent after the upload has answered. - Not affected: files with a known local path (already ready in their own callback), separate intake calls, and composers with images disabled.
The returned error is still the first failure reported; the only caller (useAttachmentIntake) ignores it.
Testing
Red/green: cd webviews && bun test test/composer-intake-events.test.ts
- Base (test commit a655af657e5f, before the fix): the 2 new tests time out at 5000 ms, because the upload never starts while the preview is held. Result: 6 pass, 2 fail.
- Review round 1 found the same wait with a known-path slow image. Its red commit b84a6197086 adds a third test, which times out at d0fd292a011 (8 pass, 1 fail).
- Head f4c28b93379: 9 pass, 0 fail.
New tests, using an image engine whose decode waits on a signal:
- A held image plus a zip. The upload starts with
[shot.png, notes.zip]in order, and the zip is itslinkchip, while the image is not yet a chip and its tile is not markeduploading. After the decode,original 0comes afterready 0 image, and the image keeps itsoriginalPath. - Local-path and cancellation controls. A known-local-path zip is a link at once and a cancelled zip stays out of the upload (
[shot.png, sent.zip]). Both happen before the decode. - A slow image whose path the host knows, plus a zip. The zip uploads and is its chip before the image's preview is made (added after review round 1).
Observed / Not run
- Observed: the red/green above. 26 attachment, intake, upload and paste test files (
bun testontest/*attach*,*intake*,*upload*,composer-paste*andnew-tab-*attach*): 167 pass, 0 fail at the head.scripts/web-lint.shpassed andscripts/cmux-next/prepush-gate.pyreported no new violations. - Not run: no runtime capture in the app, and nothing timed. Nothing changes visually: the image tile's states are the same, and the only difference is when a sibling file's chip arrives.
Overlap: #27073 changes the upload?.(...) call in the same function (a subscribe-able cancelled). It is not the same fix, and whichever PR lands second takes a small textual merge.
Changelog
- Fixed: a file dropped or pasted together with an image no longer waits for the image's preview before it uploads.
risk: normal (composer attachment intake ordering; no protocol or host change).
Agent session: agent session
š¤ Generated with Claude Code