cmux NEXT composer: line breaks copy and paste as lines, never <br />

Merged
TE
16d ago
+329-12

Leo: "HOW ARE WE MAKING LINE BREAKS THAT COPY OVER TO OTHER TEXT BOXES AS
?"

Cause

The composer is Milkdown. Its clipboard plugin wrote the copy's text/plain as Milkdown's markdown, and Milkdown writes an empty paragraph (a blank line) as the placeholder <br />. So a prompt with a blank line, copied and pasted into any other text box, showed a literal <br /> line. Pasting that text back into the composer as plain text went in as typed tags: the field inserts markup-looking plain text verbatim (kept for pasted HTML/JSX source).

The transcript's copy was not the source: it copies the row's text. It only showed <br /> when the prompt had been sent with one.

Fix (webviews/src/agent-session/acpmux/MarkdownField.tsx)

  • Copy: a view-level clipboardTextSerializer, which runs ahead of the clipboard plugin's, writes the lines as shown. A block or line break is \n, and an empty paragraph is a blank line. There are no markdown escapes and no placeholders. ProseMirror's HTML stays as the secondary type and is real markup.
  • Accepted trade-offs: pasted rich text is flattened to plain text, and copy writes plain lines with no markdown syntax.
  • Paste: the field always takes the plain text. With HTML too, the plain text wins. HTML alone becomes its text, with each <br> and <p> ending a line. Milkdown's <br /> placeholder in an older copy becomes a blank line. The text goes in as lines (text plus hard breaks), never parsed as markdown or HTML. VS Code pastes (a code block) and pastes inside a code block keep Milkdown's handling. Pasted images keep their chip path.

Tests

webviews/test/composer-line-breaks.test.ts (Playwright, Chromium and WebKit) pushes "First paragraph\n\nSecond, line one\nSecond, line two\n\nThird" through four cases:

  • (a) a paste into the composer: the right lines, no <br, and the sent prompt is the same text;
  • (b) a copy from the composer: text/plain equals the original, a Shift-Return blank line copies as one\n\ntwo, and pasting the copy back gives the same lines;
  • (c) the transcript message's Copy: text/plain with \n and no <br;
  • (d) HTML with <br /> and <p>, alone or with plain text, plus plain text holding the old <br /> placeholder: clean text.

Fleet runs:
cmux-ci run --class light --script scripts/ci/run-webviews-tests.sh --ref <sha> --env CMUX_BROWSER_TESTS=1 --env CMUX_WEB_TEST_FILES=test/composer-line-breaks.test.ts

  • Red at the test-only commit: step ca0b18308b0d297d335e21bb at fd986a012fd6 (the test commits on base, no fix): 3 fail on Chromium. (d) shows a literal <br /> line in the composer after pasting one\n\n<br />\n\ntwo, and (a)/(b) lose the blank lines.
  • Green at the head: step a9857b582f3d09d07f9320e7 at b34305a12b36: PASS. App compile-check 82c34d2d1148e0ea485b7936 passed at the same head.

Observed / Not run

  • Observed: the fleet runs above; scripts/web-lint.sh and scripts/cmux-next/prepush-gate.py passed in the pre-push hook.
  • Not run: WebKit. Fleet light hosts cannot launch it (the test skips an engine that won't launch off GitHub Actions, where CI's browser lane runs both). The full webviews suite. CI runs it. The paste-related browser tests (composer-html-paste-edit, composer-paste-end, new-tab-paste-attachments) were not run on their own.

No capture: webview-only change to clipboard data; the browser test covers it.

Changelog

Fixed: Line breaks copied from or pasted into the agent chat composer no longer turn into a literal <br />.

Agent session: cosmic-gray

🤖 Generated with Claude Code