cmux NEXT composer: an attached video shows its poster and length, and plays on click
Leo, 2026-10-25: "also why dont we have previews of videos we should ... oh attaching a video froze the composer..."
His capture: a dropped "Screen Recording 2026-10-25 ā¦" showed only its file name next to an image thumbnail. This PR gives that chip a preview. It builds on #25179 (the host's video.info and video.quickLook). Until #25179 lands, the host refuses the call and the chip stays a file name, so the two PRs can land in either order.
What it does
- A video link chip (dropped, pasted or chosen in a chat on this Mac;
.mov,.mp4,.m4v, known by name because the + menu's chooser gives no type) asks the host for its info once (composer/videoAttachments.ts,useVideoInfo). While the host reads it, the tile spins in the image chip's shape. It then shows the poster with a small glass play glyph and its length ("0:42"), like a reply's video thumbnail. A click (or the right-click menu's Play video) plays it in Quick Look. - The answer rides on the chip (
ComposerAttachment.video). This PR does not send the frames yet: a video still goes to the agent exactly as on base (its path and the "Attached file" note). The key frames and a one-line note go to the agent in the next PR, together with the sent bubble that hides that note and shows the poster; sending them alone would put the note in every sent bubble as text. - Each read has its own token (a late end of one read never clears another of the same chip); a composer that goes away ends its reads; Quick Chat counts as on this Mac (
localChat); a kept draft never carries the poster or frames (they are read again). - A send waits for the read for 5 s at most:
useSendWhenAttached(loading + videoReading), per composer-opus and Leo's "the composer should never ever freeze". Removing the chip, a refusal, an older host, or a chat on another machine (nolocalPaths) holds nothing, and the chip stays a file chip that goes as its path. - The page decodes no video and the CSP is unchanged.
intakeFiles.ts,attach()andPendingAttachmentare untouched (composer-opus's huge-files branch owns them).
Before / after
Gallery states agent-pane.composer: video-dropped (after: the poster tile beside a screenshot) and video-reading (the busy tile). Before, the same drop was folder-dropped-as-link's file chip. The poster is a synthetic editor frame (gallery/fixtures/videoPoster.ts), with no user data.
Stills: pending. scripts/gallery-compare.sh needs the gallery host (cmux-lawrence), which huge-blue could not reach at the time of opening; they are added here before landing.
Tests
webviews/test/composer-video-attachment.test.tsx (jsdom, real AttachmentStrip and useVideoInfo): a movie is known by its name; the host's answer is checked (poster must be a JPEG data URL); a drop spins, then shows the poster, "0:42" and its label, and a click asks video.quickLook, read once; a kept draft drops the video info; a refused read stays a file chip and holds nothing; a remote chat asks nothing, and one found to be local reads the chip it holds; the same video removed and added again is read again and the first read's late end leaves the second busy; a composer that goes away leaves nothing busy; other files are not read; removing the chip mid-read ends the wait; past the deadline the send goes and a late answer still draws (timer driven by hand, no wall-clock wait). 12 pass. Related suites (composer-attachments, attachment-thumbnails, kept-attachments, attach-links, attachment-matrix, session-paths): 73 pass, 2 browser skips.
Observed / Not run
- Review (Codex gpt-6-astra xhigh): round 1 found the read races, the unmount, the
enableddependency and Quick Chat (fixed in the second commit); round 2 found them fixed ("no stale-closure or render-loop issue"; deferring the frames "acceptable because prompt serialization remains unchanged from base"), its only blocker being that #25179's host methods are not on base yet. This PR lands after #25179 and merges base first. bun testabove;bunx vp check --no-fmt --no-lint(types: no errors in 2682 files);vp fmt,vp linton changed files;bun run check:design-tokens,check:fixed-waits; prepush gate clean.bun run check's fullvp lintis red on base 92a20e7f5ce with 4 warnings in files this PR does not touch (DynamicWorkflow.gallery.tsx, chat-environment-scroll-stability.test.tsx). That is a base finding; this push skipped the web-lint hook naming it.- Not run: a real-app capture (it needs #25179's host on a fleet build). The gallery shows the states.
Changelog
Added: An attached video in an agent chat shows its first frame and length, and plays in Quick Look on click.
risk: normal (UI behaviour in the composer; the send's wait is bounded)
Agent session: huge-blue
š¤ Generated with Claude Code