cmux NEXT composer: Cmd-V anywhere in an agent chat pastes into the composer; a copied image is one chip, never markdown

Merged
TE
16d ago
+346-2

Leo-ask: ask-68647e03c0
Leo-ask: ask-1eb1fd1100

Leo said:

  • "pastes need to be able to paste even when the composer isn't literally focused"
  • "ME PASTING IT DOESNT JUST PASTE RIGHT IN THE COMPOSER IMMEDIATELY"
  • "copying and pasting images straight puts weird things in composer"

He copied the images from Edge and Chrome. The composer got text like ![Screenshot · 778×696]([image] "Screenshot · 778×696") next to the image chip.

Now:

  • In an agent pane, Cmd-V goes into the composer when focus is on the transcript, on a clicked message, on a button or on nothing. The composer takes focus, and the text goes in after what is already there.
  • A copied image becomes one image chip, and the <img> or ![…](…) the browser copies with it stays out of the prompt.
  • A text field elsewhere in the pane keeps its own paste: a prompt being edited, a search, a question's answer, or anything in a modal dialog.

Root cause

  • Paste outside the prompt went nowhere. The composer's only paste listener, useAttachmentIntake (webviews/src/agent-session/acpmux/composer/attachmentIntake.tsx:121-136), ignores any paste whose target is not the prompt or shell field. pasteTarget (Composer.tsx:441-446) returns undefined for those. So a paste with focus on the transcript was dropped.
    • On WebKit the paste event never fires there at all. WebKit enables Paste outside editable content only when a beforepaste handler cancels the event (Editor::canDHTMLPaste), and nothing in webviews/ or Packages/ listens for beforepaste.
    • The native side does not swallow Cmd-V:
      • The KeyRouter has no plain Cmd-V binding outside terminals (TerminalActionCatalog.swift:118), and BrowserChordTable.swift:53 keeps the key for the page.
      • PageWKWebView.paste and AgentPaneWKWebView.paste forward to WebKit's own paste:.
      • CEF's pre-key hook passes the key on.
    • So no Swift change is needed.
  • Markdown came in with the image. When the clipboard carries the image file, the field's DOM paste handler (MarkdownField.tsx:436-449, added in #23100 / 84494d5c904) still puts the clipboard's plain text in the prompt (lifted.text). That text is the ![alt](src "title") markup for the same picture.

Fix

  • New composer/pasteAnywhere.ts, installed by the Composer:
    • Cmd-V outside a text field focuses the prompt (or the shell field) with the caret at the end, so the app's Paste lands in it.
    • A beforepaste listener turns Paste on in WebKit.
    • A paste event that still lands outside a field (Edit > Paste, Chromium) is handed to the focused prompt as its own paste. Everything a paste does in the field still applies: image chips, Pasted text chips, the caret.
  • MarkdownField paste: when the clipboard carries an image (a file or a data: <img>), the text it inserts no longer includes markdown images (withoutMarkdownImages, inlineImages.ts).

Changelog

  • Fixed: In an agent chat, Cmd-V pastes into the composer even when the composer is not focused, and a pasted image becomes an image chip instead of markdown text.

Regression test

cd webviews && CMUX_BROWSER_TESTS=1 bun test test/agent-pane-paste-anywhere.test.ts (Chromium and WebKit, real agent pane):

  • Base (ca294a90123 plus the test commit 78dbe89d296): 8 fail, 2 pass. Both passes are "a paste into another text field stays in that field", the guard.
    • "text pasted with the keyboard on the transcript goes into the composer": paste was not cancelled, and the prompt stayed empty.
    • "goes at the end of the draft": got "first part".
    • "Cmd-V outside a field moves the keyboard into the composer": focus stayed out.
    • "an image copied from a browser is one image chip": the paste was not taken.
    • The sixth case ("…pasted in the focused composer puts no markdown in it") was added after that run. A later base re-run also fails it on WebKit. Chromium's runs in that re-run were broken by a full disk on huge-blue, so they don't count.
  • Head 637945e913a: 12 pass, 0 fail.

Observed / Not run

  • Ran on huge-blue:
    • The new test, red on base and green on head.
    • bun run check passes.
    • The existing paste suites all pass (27 pass, 0 fail): composer-paste-images, composer-paste-text, paste-chip-round-trip, composer-html-paste-edit, composer-line-breaks, composer-paste-end and new-tab-paste-attachments.
    • The browser files agent-pane-focus-content, composer-click-near-focuses and gallery-composer-paste pass when each runs on its own.
  • Not run: a real Cmd-V in the Mac app; headless browsers don't run the system Paste. The native path was read, not exercised.

No capture: webview-only change (behavior, no new look).

Agent session: cosmic-gray

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Pasting with Cmd-V in an agent chat now goes into the composer even when the composer isn't focused, and a copied image lands as one image chip instead of markdown text.

Previously, a paste with focus on the transcript, a message, or nothing was dropped, and copying an image from a browser put ![…](…) markup in the prompt next to the image chip.

  • Cmd-V outside a text field focuses the prompt with the caret at the end; WebKit gets a beforepaste handler so Paste works outside editable content.
  • A paste that carries the image (a file or a data: <img>) drops the markdown image text via withoutMarkdownImages.
  • Text fields elsewhere in the pane (editing a prompt, search, modal dialogs) keep their own paste.