composer: a file's upload never waits for a sibling image's preview

Merged
TE
7d ago
+257-56

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. intakeFiles still 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 (original still comes after the image's ready).
  • An image's tile still holds a send until its own preview is made: its uploading mark 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:

  1. A held image plus a zip. The upload starts with [shot.png, notes.zip] in order, and the zip is its link chip, while the image is not yet a chip and its tile is not marked uploading. After the decode, original 0 comes after ready 0 image, and the image keeps its originalPath.
  2. 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.
  3. 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 test on test/*attach*, *intake*, *upload*, composer-paste* and new-tab-*attach*): 167 pass, 0 fail at the head. scripts/web-lint.sh passed and scripts/cmux-next/prepush-gate.py reported 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