Skip to content

cmux NEXT composer: a long paste scrolls to its end, caret after it - #22789

Merged
teamleaderleo merged 5 commits into
feat-cmux-nextfrom
fix/next-composer-paste-end
Oct 21, 2026
Merged

teamleaderleo merged 5 commits into
feat-cmux-nextfrom
fix/next-composer-paste-end

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

A long paste into the composer now scrolls the field to its end, with the caret right after the pasted text, as Claude desktop does.

The prompt field grows to eight lines and then scrolls. Milkdown's clipboard plugin inserts a paste without asking ProseMirror to scroll, so pasting 40 lines into a short draft left the field at the top and the caret out of view. The field now marks a paste that no surface handled itself. A small appendTransaction then adds scrollIntoView to whichever transaction inserts it. The markup-text paste path (HTML or JSX pasted as text) scrolls too.

This is one path in MarkdownField, so it covers every surface that uses it: chat, Quick Composer, sub-sessions and the Chief/Home composer. The shell field is a plain textarea, which already scrolls on paste.

Changelog

  • Fixed: A long paste into the composer scrolls to its end, with the caret after it.

Test

webviews/test/composer-paste-end.test.ts (browser lane, Chromium and WebKit): type a 3-line draft, paste 40 lines, then check that the field scrolled, the caret is at the end, and the caret and "pasted line 40" are inside the field's visible box. The test is committed red before the fix (85b0249), and the fix is eb2c7d4.

Observed / Not run

  • Ran: vp fmt on the changed files.
  • Not run: the test on base or head, bun run check, and web lint. Agents don't run bun or playwright on this Mac, so CI is the first run. The red-on-base claim comes from reading the code, not from a run.

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

Fixes pasting a long text into the composer so the field scrolls to its end, with the caret right after the pasted text.

Milkdown's clipboard plugin inserts a paste without asking ProseMirror to scroll, so pasting 40 lines into a short draft left the field at the top with the caret out of view. MarkdownField now flags a paste that no surface handled, and an appendTransaction adds scrollIntoView to the transaction that inserts it; the markup-text paste path scrolls too. Because the fix lives in MarkdownField, it covers chat, Quick Composer, sub-sessions, and the Chief/Home composer; the shell field is a plain textarea that already scrolls on paste.

Adds a browser test (Chromium and WebKit) that pastes 40 lines into a 3-line draft and verifies the field scrolled, the caret sits at the end, and the caret and last pasted line are in view.

Written for commit a631a7d. Summary will update on new commits.

View guided diff Turn on auto-fix

teamleaderleo and others added 2 commits October 20, 2026 19:15
…et after it (red)

The composer field caps at eight lines and scrolls inside. A long paste
left it at the top: the caret and the last pasted line out of view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray
Milkdown's clipboard plugin dispatches the pasted slice without
scrollIntoView, so ProseMirror kept the field's scroll where it was.
MarkdownField now marks a paste the surface did not consume and appends
a scrollIntoView to the transaction that inserts it (whichever plugin
dispatches it); the markup-text paste path scrolls too. Every surface on
MarkdownField gets it: the chat composer, Quick Composer, sub-sessions
and Chief/Home (PromptEditor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review a631a7d: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr22789 -f ref=fix/next-composer-paste-end -f platform=macos -f backend_mode=local

Pending: CI is running on a631a7d04d (run); the result below was for 026f59792e and no longer applies.

Last result: Probably yours: composer-suggested-prompt.test.tsx, composer-suggested-prompt.test.tsx, composer-suggested-prompt.test.tsx and 2 more fails here but not on main's latest full suite. Seen on other PRs too (likely flaky): composer-history.test.ts, quiet-access-chip.test.ts, quiet-access-chip.test.ts and 15 more.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); it is rewritten when this head's CI completes.

red: web / react-apps-check (1); no finished base run on 37b2e60 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (2); no finished base run on 37b2e60 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (3); no finished base run on 37b2e60 to compare; do not merge until green; see lane-rules
red: web / react-apps-check (4); no finished base run on 37b2e60 to compare; do not merge until green; see lane-rules

CI fast guards failed on 026f59792e (https://github.com/manaflow-ai/cmux/actions/runs/39534596311). It does not block the merge; a red guard merged into main breaks it for every open PR.
The log named no failed step; see the run.

Agents: python3 scripts/ci/guard_attribution.py fix applies the mechanical fixes locally.

Tour of a631a7d04d (run): no media. The tour did not run: only webview code changed (2 path(s)), which the gallery advisory run checks in Linux Chromium on the pull request (its artifact holds any captures); add the media-tour-mac label to tour it on a Mac.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Bead cx-fxzf3 created for this PR by the cmux coordinator.

@blacksmith-sh

blacksmith-sh Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Found 1 test failure on Blacksmith runners:

Failure

Test View Logs
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)/
test_the_repository_highlights_validate (main.DigestTests.test_the_repository_highl
ights_validate)
View Logs

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

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

owner: 01a15ba2-63d6-7533-b232-21f35d99d2b2

@teamleaderleo
teamleaderleo marked this pull request as ready for review October 21, 2026 03:21
@teamleaderleo
teamleaderleo merged commit e0b9a3b into feat-cmux-next Oct 21, 2026
@teamleaderleo
teamleaderleo deleted the fix/next-composer-paste-end branch October 21, 2026 03:21
@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

@github-actions

Copy link
Copy Markdown
Contributor

Shipped in:

teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…wn text block

Leo 2026-10-21, as in Claude: a paste of more than 20 lines or 2,000
characters into the composer no longer lands as pages of prompt. It becomes a
"Pasted text" attachment chip in the chips row (its first words over
"Pasted text · N lines"), and the prompt keeps what was typed and its caret.

- pastedText.ts holds the limits (PASTE_CHIP_LINES, PASTE_CHIP_CHARS), the
  label and line count, and the one wire-shape helper, pastedTextBlocks: each
  paste goes to the agent as its own ACP text block after the prompt's, with
  _meta {"cmux":{"pasted":true}} (promptBlocks). The bubble and queue text stay
  the typed prompt.
- The field reports a paste to onBeforeInput as such (state.paste, with the
  clipboard's plain text as copied); the composer makes the chip there
  (composer/pastedChips.ts). A paste under the limit goes in as before and
  still scrolls to its end (#22789).
- Paste as Plain Text keeps the raw paste: the prompt menu's item and the
  Cmd-Shift-V key (Paste and Match Style, a system menu key the bridge sends no
  keycap for) mark the next paste, within 2 s, as raw.
- Click on the chip: the simple expand. Its text goes back into the prompt at
  the caret as a paste of it, and the chip goes. Its right-click menu has Edit
  in Prompt and Remove; x removes it. (No Backspace-removes-chip exists for
  image chips, so none here.)
- A pasted chip is kept with the chat's unsent chips like pictures (#23139):
  kind "pasted" with its text; the host keeps both as they are.
- Strings: composer.pastedText, composer.pastedText.edit and the plural
  composer.pastedText.lines, in the catalog's 21 languages.
- Gallery: agent-pane.composer#text-pasted. composer-paste-end now pastes 15
  lines, under the chip limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
* test(acpmux): a huge paste into the composer is a Pasted text chip (red)

A paste over 20 lines or 2,000 characters becomes a "Pasted text" chip (first
words, line count) and the prompt keeps what was typed; a smaller paste goes
in as text; Paste as Plain Text (Cmd-Shift-V) keeps the raw paste; x removes
the chip; a click puts its text back in the prompt; and the text goes to the
agent as its own text block after the prompt's, with _meta.cmux.pasted.

Fails on the base: no chip exists, every paste lands in the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: a huge paste is a Pasted text chip, sent as its own text block

Leo 2026-10-21, as in Claude: a paste of more than 20 lines or 2,000
characters into the composer no longer lands as pages of prompt. It becomes a
"Pasted text" attachment chip in the chips row (its first words over
"Pasted text · N lines"), and the prompt keeps what was typed and its caret.

- pastedText.ts holds the limits (PASTE_CHIP_LINES, PASTE_CHIP_CHARS), the
  label and line count, and the one wire-shape helper, pastedTextBlocks: each
  paste goes to the agent as its own ACP text block after the prompt's, with
  _meta {"cmux":{"pasted":true}} (promptBlocks). The bubble and queue text stay
  the typed prompt.
- The field reports a paste to onBeforeInput as such (state.paste, with the
  clipboard's plain text as copied); the composer makes the chip there
  (composer/pastedChips.ts). A paste under the limit goes in as before and
  still scrolls to its end (#22789).
- Paste as Plain Text keeps the raw paste: the prompt menu's item and the
  Cmd-Shift-V key (Paste and Match Style, a system menu key the bridge sends no
  keycap for) mark the next paste, within 2 s, as raw.
- Click on the chip: the simple expand. Its text goes back into the prompt at
  the caret as a paste of it, and the chip goes. Its right-click menu has Edit
  in Prompt and Remove; x removes it. (No Backspace-removes-chip exists for
  image chips, so none here.)
- A pasted chip is kept with the chat's unsent chips like pictures (#23139):
  kind "pasted" with its text; the host keeps both as they are.
- Strings: composer.pastedText, composer.pastedText.edit and the plural
  composer.pastedText.lines, in the catalog's 21 languages.
- Gallery: agent-pane.composer#text-pasted. composer-paste-end now pastes 15
  lines, under the chip limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): pasted text reaches every harness whole and expands exactly (red)

Review of #23396:
- uniichat-agent and the CLI agent with --last-block send only the last text
  block, so a typed question and a pasted log reached the agent as the log
  alone. A pasted block (_meta.cmux.pasted) now goes with the typed text, a
  blank line between them (cli_agent.rs, uniichat_agent.rs tests).
- A chip's click must put its text back exactly as a raw paste does: blank
  lines kept, none added between lines.
- Paste as Plain Text by the key's position (KeyV on a non-Latin layout)
  keeps the paste raw; one that pasted only a file arms nothing for later.
- The bubble shows a pasted chip's text as acpmux's echo will (text blocks
  joined by a newline), and Retry/Edit, which send the echoed text, drop the
  pasted chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: pasted text reaches every harness whole; a chip expands exactly

Review of #23396:
- acpmux's CLI agent (--last-block) and uniichat-agent sent only the last text
  block, so "why does this fail?" with a pasted log reached the agent as the
  log alone. A block marked _meta.cmux.pasted now always goes with the typed
  text, after a blank line (cli_agent::prompt_text, which uniichat-agent now
  uses); prompts without a paste go as before.
- A chip's click put its text back one paragraph per line, blank lines
  dropped. MarkdownField's pasteText now pastes with the text as the
  clipboard's plain text, so it takes a person's paste path (Milkdown's
  markdown paste) and the prompt holds the text exactly as pasted.
- The bubble and queue row show a pasted chip's text as acpmux's echo will
  (text blocks joined by a newline), so a send of only a paste is not an empty
  bubble and nothing jumps when the echo lands.
- Retry and Edit send the echoed text, which has each paste: the pasted chip
  is no longer sent with it again (promptAttachments).
- Paste as Plain Text: Cmd-Shift-V by the key's position (KeyV) counts on a
  non-Latin layout, and any paste, one of only files too, ends its turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: the pasted chip's edit hint is a title, not aria-description

Oxlint's jsx-a11y role-supports-aria-props warns on aria-description on a button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): the paste-chip test waits for the editor, not a fixed time

check-fixed-waits (new on base) counts every fixed setTimeout wait. Opening waits for the
combobox with until(), and a paste yields one macrotask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: the paste chip counts and names as the transcript's, and the bubble has its chip at once

The composer's chip uses promptCollapse.ts pasteLines/pasteName and transcript.pastedLines
(#23431) in place of its own copies. The optimistic bubble carries the paste ranges
user_message.pasted will name (#23407), so the transcript chip shows from the send on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): paste-chip jsdom tests start from an empty prompt and measure no layout

Every test in composer-paste-text opens chat "chat-a", and the composer keeps a chat's unsent
prompt in the page's localStorage remount cache (composerDraft.ts). The jsdom window is shared
across the file, so the first test's typed "why does this fail?" (and later pastes) came back in
the next tests' prompts: the 2,000-character and x tests saw that text, and the send test sent it
joined to its own. afterEach now clears localStorage once the root is unmounted.

A chip's click puts its text back as a paste, and ProseMirror scrolls a paste into view by
measuring a Range. jsdom's Range has no getClientRects, so the click threw inside
scrollToSelection, which also left act() overlapping and the next test's handle null. The test
gives Range an empty box, as markdown-comments.test.ts does; the product path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): the long-paste browser tests paste under the chip's limit and still overflow

A paste over 20 lines or 2,000 characters is now a Pasted text chip, so composer-scroll-fade's
40-line paste never reached the prompt (each test timed out waiting for its last line), and
composer-paste-end's 15 short lines, which this PR chose to stay raw, did not overflow the field:
the prompt shows pasted lines joined as one paragraph, and 15 short ones wrap to about three rows,
under the field's eight-line cap. The small-paste path is the one on base (take() returns false
under the limit and the field pastes as before); only the fixtures were too small.

Both now paste long lines under both limits (15 and 20 lines of 70 to 90 characters), which wrap
well past eight rows at the tests' 900 px viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…s the same Pasted chip (#23622)

* test(acpmux): a huge paste into the composer is a Pasted text chip (red)

A paste over 20 lines or 2,000 characters becomes a "Pasted text" chip (first
words, line count) and the prompt keeps what was typed; a smaller paste goes
in as text; Paste as Plain Text (Cmd-Shift-V) keeps the raw paste; x removes
the chip; a click puts its text back in the prompt; and the text goes to the
agent as its own text block after the prompt's, with _meta.cmux.pasted.

Fails on the base: no chip exists, every paste lands in the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: a huge paste is a Pasted text chip, sent as its own text block

Leo 2026-10-21, as in Claude: a paste of more than 20 lines or 2,000
characters into the composer no longer lands as pages of prompt. It becomes a
"Pasted text" attachment chip in the chips row (its first words over
"Pasted text · N lines"), and the prompt keeps what was typed and its caret.

- pastedText.ts holds the limits (PASTE_CHIP_LINES, PASTE_CHIP_CHARS), the
  label and line count, and the one wire-shape helper, pastedTextBlocks: each
  paste goes to the agent as its own ACP text block after the prompt's, with
  _meta {"cmux":{"pasted":true}} (promptBlocks). The bubble and queue text stay
  the typed prompt.
- The field reports a paste to onBeforeInput as such (state.paste, with the
  clipboard's plain text as copied); the composer makes the chip there
  (composer/pastedChips.ts). A paste under the limit goes in as before and
  still scrolls to its end (#22789).
- Paste as Plain Text keeps the raw paste: the prompt menu's item and the
  Cmd-Shift-V key (Paste and Match Style, a system menu key the bridge sends no
  keycap for) mark the next paste, within 2 s, as raw.
- Click on the chip: the simple expand. Its text goes back into the prompt at
  the caret as a paste of it, and the chip goes. Its right-click menu has Edit
  in Prompt and Remove; x removes it. (No Backspace-removes-chip exists for
  image chips, so none here.)
- A pasted chip is kept with the chat's unsent chips like pictures (#23139):
  kind "pasted" with its text; the host keeps both as they are.
- Strings: composer.pastedText, composer.pastedText.edit and the plural
  composer.pastedText.lines, in the catalog's 21 languages.
- Gallery: agent-pane.composer#text-pasted. composer-paste-end now pastes 15
  lines, under the chip limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): pasted text reaches every harness whole and expands exactly (red)

Review of #23396:
- uniichat-agent and the CLI agent with --last-block send only the last text
  block, so a typed question and a pasted log reached the agent as the log
  alone. A pasted block (_meta.cmux.pasted) now goes with the typed text, a
  blank line between them (cli_agent.rs, uniichat_agent.rs tests).
- A chip's click must put its text back exactly as a raw paste does: blank
  lines kept, none added between lines.
- Paste as Plain Text by the key's position (KeyV on a non-Latin layout)
  keeps the paste raw; one that pasted only a file arms nothing for later.
- The bubble shows a pasted chip's text as acpmux's echo will (text blocks
  joined by a newline), and Retry/Edit, which send the echoed text, drop the
  pasted chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: pasted text reaches every harness whole; a chip expands exactly

Review of #23396:
- acpmux's CLI agent (--last-block) and uniichat-agent sent only the last text
  block, so "why does this fail?" with a pasted log reached the agent as the
  log alone. A block marked _meta.cmux.pasted now always goes with the typed
  text, after a blank line (cli_agent::prompt_text, which uniichat-agent now
  uses); prompts without a paste go as before.
- A chip's click put its text back one paragraph per line, blank lines
  dropped. MarkdownField's pasteText now pastes with the text as the
  clipboard's plain text, so it takes a person's paste path (Milkdown's
  markdown paste) and the prompt holds the text exactly as pasted.
- The bubble and queue row show a pasted chip's text as acpmux's echo will
  (text blocks joined by a newline), so a send of only a paste is not an empty
  bubble and nothing jumps when the echo lands.
- Retry and Edit send the echoed text, which has each paste: the pasted chip
  is no longer sent with it again (promptAttachments).
- Paste as Plain Text: Cmd-Shift-V by the key's position (KeyV) counts on a
  non-Latin layout, and any paste, one of only files too, ends its turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: the pasted chip's edit hint is a title, not aria-description

Oxlint's jsx-a11y role-supports-aria-props warns on aria-description on a button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): the paste-chip test waits for the editor, not a fixed time

check-fixed-waits (new on base) counts every fixed setTimeout wait. Opening waits for the
combobox with until(), and a paste yields one macrotask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: the paste chip counts and names as the transcript's, and the bubble has its chip at once

The composer's chip uses promptCollapse.ts pasteLines/pasteName and transcript.pastedLines
(#23431) in place of its own copies. The optimistic bubble carries the paste ranges
user_message.pasted will name (#23407), so the transcript chip shows from the send on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): paste-chip jsdom tests start from an empty prompt and measure no layout

Every test in composer-paste-text opens chat "chat-a", and the composer keeps a chat's unsent
prompt in the page's localStorage remount cache (composerDraft.ts). The jsdom window is shared
across the file, so the first test's typed "why does this fail?" (and later pastes) came back in
the next tests' prompts: the 2,000-character and x tests saw that text, and the send test sent it
joined to its own. afterEach now clears localStorage once the root is unmounted.

A chip's click puts its text back as a paste, and ProseMirror scrolls a paste into view by
measuring a Range. jsdom's Range has no getClientRects, so the click threw inside
scrollToSelection, which also left act() overlapping and the next test's handle null. The test
gives Range an empty box, as markdown-comments.test.ts does; the product path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): the long-paste browser tests paste under the chip's limit and still overflow

A paste over 20 lines or 2,000 characters is now a Pasted text chip, so composer-scroll-fade's
40-line paste never reached the prompt (each test timed out waiting for its last line), and
composer-paste-end's 15 short lines, which this PR chose to stay raw, did not overflow the field:
the prompt shows pasted lines joined as one paragraph, and 15 short ones wrap to about three rows,
under the field's eight-line cap. The small-paste path is the one on base (take() returns false
under the limit and the field pastes as before); only the fixtures were too small.

Both now paste long lines under both limits (15 and 20 lines of 70 to 90 characters), which wrap
well past eight rows at the tests' 900 px viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* webviews: a long paste round-trips composer to transcript as the same Pasted chip

A JSDOM test runs the real composer (type, paste 2,300 lines, Send), the real
AcpmuxDirectClient against a fake acpmux that records the sent blocks as
hub/session.rs prompt_text_parts does (text blocks joined with a newline,
UTF-16 ranges of the _meta.cmux.pasted ones), and the real UserBubble. It
checks the typed text comes first and the chip after, and that the pending
bubble drawn at send matches the one drawn from acpmux's user_message echo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba3-d09a-7940-9fd6-158e10477c0e&host=cosmic-gray

* Take feat-cmux-next's Localizable.xcstrings; this PR adds no strings

The base merge's string-catalog driver reordered the catalog; this branch changes no strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba3-d09a-7940-9fd6-158e10477c0e&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 21, 2026
…past (#23626)

* test(acpmux): a huge paste into the composer is a Pasted text chip (red)

A paste over 20 lines or 2,000 characters becomes a "Pasted text" chip (first
words, line count) and the prompt keeps what was typed; a smaller paste goes
in as text; Paste as Plain Text (Cmd-Shift-V) keeps the raw paste; x removes
the chip; a click puts its text back in the prompt; and the text goes to the
agent as its own text block after the prompt's, with _meta.cmux.pasted.

Fails on the base: no chip exists, every paste lands in the prompt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: a huge paste is a Pasted text chip, sent as its own text block

Leo 2026-10-21, as in Claude: a paste of more than 20 lines or 2,000
characters into the composer no longer lands as pages of prompt. It becomes a
"Pasted text" attachment chip in the chips row (its first words over
"Pasted text · N lines"), and the prompt keeps what was typed and its caret.

- pastedText.ts holds the limits (PASTE_CHIP_LINES, PASTE_CHIP_CHARS), the
  label and line count, and the one wire-shape helper, pastedTextBlocks: each
  paste goes to the agent as its own ACP text block after the prompt's, with
  _meta {"cmux":{"pasted":true}} (promptBlocks). The bubble and queue text stay
  the typed prompt.
- The field reports a paste to onBeforeInput as such (state.paste, with the
  clipboard's plain text as copied); the composer makes the chip there
  (composer/pastedChips.ts). A paste under the limit goes in as before and
  still scrolls to its end (#22789).
- Paste as Plain Text keeps the raw paste: the prompt menu's item and the
  Cmd-Shift-V key (Paste and Match Style, a system menu key the bridge sends no
  keycap for) mark the next paste, within 2 s, as raw.
- Click on the chip: the simple expand. Its text goes back into the prompt at
  the caret as a paste of it, and the chip goes. Its right-click menu has Edit
  in Prompt and Remove; x removes it. (No Backspace-removes-chip exists for
  image chips, so none here.)
- A pasted chip is kept with the chat's unsent chips like pictures (#23139):
  kind "pasted" with its text; the host keeps both as they are.
- Strings: composer.pastedText, composer.pastedText.edit and the plural
  composer.pastedText.lines, in the catalog's 21 languages.
- Gallery: agent-pane.composer#text-pasted. composer-paste-end now pastes 15
  lines, under the chip limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): pasted text reaches every harness whole and expands exactly (red)

Review of #23396:
- uniichat-agent and the CLI agent with --last-block send only the last text
  block, so a typed question and a pasted log reached the agent as the log
  alone. A pasted block (_meta.cmux.pasted) now goes with the typed text, a
  blank line between them (cli_agent.rs, uniichat_agent.rs tests).
- A chip's click must put its text back exactly as a raw paste does: blank
  lines kept, none added between lines.
- Paste as Plain Text by the key's position (KeyV on a non-Latin layout)
  keeps the paste raw; one that pasted only a file arms nothing for later.
- The bubble shows a pasted chip's text as acpmux's echo will (text blocks
  joined by a newline), and Retry/Edit, which send the echoed text, drop the
  pasted chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: pasted text reaches every harness whole; a chip expands exactly

Review of #23396:
- acpmux's CLI agent (--last-block) and uniichat-agent sent only the last text
  block, so "why does this fail?" with a pasted log reached the agent as the
  log alone. A block marked _meta.cmux.pasted now always goes with the typed
  text, after a blank line (cli_agent::prompt_text, which uniichat-agent now
  uses); prompts without a paste go as before.
- A chip's click put its text back one paragraph per line, blank lines
  dropped. MarkdownField's pasteText now pastes with the text as the
  clipboard's plain text, so it takes a person's paste path (Milkdown's
  markdown paste) and the prompt holds the text exactly as pasted.
- The bubble and queue row show a pasted chip's text as acpmux's echo will
  (text blocks joined by a newline), so a send of only a paste is not an empty
  bubble and nothing jumps when the echo lands.
- Retry and Edit send the echoed text, which has each paste: the pasted chip
  is no longer sent with it again (promptAttachments).
- Paste as Plain Text: Cmd-Shift-V by the key's position (KeyV) counts on a
  non-Latin layout, and any paste, one of only files too, ends its turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: the pasted chip's edit hint is a title, not aria-description

Oxlint's jsx-a11y role-supports-aria-props warns on aria-description on a button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): the paste-chip test waits for the editor, not a fixed time

check-fixed-waits (new on base) counts every fixed setTimeout wait. Opening waits for the
combobox with until(), and a paste yields one macrotask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: the paste chip counts and names as the transcript's, and the bubble has its chip at once

The composer's chip uses promptCollapse.ts pasteLines/pasteName and transcript.pastedLines
(#23431) in place of its own copies. The optimistic bubble carries the paste ranges
user_message.pasted will name (#23407), so the transcript chip shows from the send on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): paste-chip jsdom tests start from an empty prompt and measure no layout

Every test in composer-paste-text opens chat "chat-a", and the composer keeps a chat's unsent
prompt in the page's localStorage remount cache (composerDraft.ts). The jsdom window is shared
across the file, so the first test's typed "why does this fail?" (and later pastes) came back in
the next tests' prompts: the 2,000-character and x tests saw that text, and the send test sent it
joined to its own. afterEach now clears localStorage once the root is unmounted.

A chip's click puts its text back as a paste, and ProseMirror scrolls a paste into view by
measuring a Range. jsdom's Range has no getClientRects, so the click threw inside
scrollToSelection, which also left act() overlapping and the next test's handle null. The test
gives Range an empty box, as markdown-comments.test.ts does; the product path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* test(acpmux): the long-paste browser tests paste under the chip's limit and still overflow

A paste over 20 lines or 2,000 characters is now a Pasted text chip, so composer-scroll-fade's
40-line paste never reached the prompt (each test timed out waiting for its last line), and
composer-paste-end's 15 short lines, which this PR chose to stay raw, did not overflow the field:
the prompt shows pasted lines joined as one paragraph, and 15 short ones wrap to about three rows,
under the field's eight-line cap. The small-paste path is the one on base (take() returns false
under the limit and the field pastes as before); only the fixtures were too small.

Both now paste long lines under both limits (15 and 20 lines of 70 to 90 characters), which wrap
well past eight rows at the tests' 900 px viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15ba2-63d6-7533-b232-21f35d99d2b2&host=cosmic-gray

* cmux NEXT composer: test that the attachment row fades at its scrolled sides

A row of chips wider than the composer scrolls sideways. The test expects the
side with chips past it to fade like the prompt's edges: data-fade-left /
data-fade-right, an alpha mask by the prompt's band (--acpmux-md-fade-band),
none at a side with nothing past it, and the composer behind showing through
the faded side. Chromium and WebKit. Red until the row fades.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fdc7-79b0-b869-8ac78b0edd90&host=cosmic-gray

* cmux NEXT composer: the attachment row fades at the sides it scrolls past

A row of chips wider than the composer scrolls sideways. Its side with chips
past it is now marked (data-fade-left / data-fade-right, followScrollEdges
across) and faded with an alpha mask on the row itself, as the prompt's edges
are, so the composer's glass shows through. The band is the prompt's:
MarkdownField.tsx exports FADE_BAND_PX and the row sets --acpmux-md-fade-band
from it. Reduce Transparency keeps plain sides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?harness=claude&session=01a15bf1-fdc7-79b0-b869-8ac78b0edd90&host=cosmic-gray

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant