Skip to content

fix(transcript): show Copied only when error or diff copy succeeds - #29218

Merged
teamleaderleo merged 7 commits into
feat-cmux-nextfrom
research/sw14-copy-refusal-20261031
Nov 1, 2026
Merged

teamleaderleo merged 7 commits into
feat-cmux-nextfrom
research/sw14-copy-refusal-20261031

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Why

HQ #4203 found that transcript Copy controls could say Copied when the async clipboard was refused and the legacy document.execCommand("copy") fallback returned false.

The current code has already improved the lower-level helper: copyTextResult(text) returns a real boolean, preserves the fallback's false/throw result, removes its temporary textarea and restores focus. Fenced code and card corners already consume that result through CopyButton. Two surviving direct consumers still use the legacy copyText(text): Promise<void> wrapper, which awaits the boolean but discards it:

  • failed-turn card's Copy error, in TurnRows.tsx;
  • edit diff's Copy, in EditDiff.tsx.

Both treat the wrapper's fulfilled void promise as proof the clipboard changed, and present Copied after a false fallback.

Fix

  • Switch those two call sites to the existing copyTextResult helper.
  • Drive their existing copied state from the actual boolean. Keep their rejection callback defensive. Successful fallback and later retry still work.
  • Leave the shared legacy wrapper and unrelated callers alone; no new copy service, global feedback API, or UI changes.

Validation and tests

  • Added webviews/test/agent-pane-copy-refusal.test.tsx with a controllable async clipboard and execCommand fallback:
    • async success, fallback true and explicit false;
    • thrown fallback and removal of the temporary textarea;
    • the real failed-turn Copy error control stays in Copy on refusal and recovers after a successful retry;
    • the edit-diff Copy control does the same.
  • Source-level verification: Executed the exact GitHub-saved body of copyTextResult (only its TypeScript signature erased) in a JS test harness: refused false, accepted fallback true, thrown fallback false, async success true; 3 fallbacks appended and cleaned up 3 temporary nodes, with focus restored. Also inspected the two committed callers for use of the boolean.
  • Not run here: the actual Bun/JSDOM/React tests, web lint, a tagged WKWebView app, or clipboard paste in native. CI/review must confirm them.

Ownership/overlap

  • HQ #4203 is the existing source-audited owner issue. Its CodeBlock finding is already addressed by a different, newer CopyButton path; this PR fixes only the remaining direct consumers.
  • Recent relevant copy/card PR #28057 has merged and its CopyButton remains unchanged; no open competing PR was identified for these exact controls.
  • No clipboard schema, permission, platform protocol, backend or unrelated UI file changed.

Acceptance

A fallback false or throw keeps Copy error and Copy diff in their original state; success changes to Copied; a subsequent successful retry recovers without an app restart.


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


Summary by cubic

Fixes transcript Copy controls showing Copied when the clipboard was refused and the legacy execCommand("copy") fallback returned false or threw.

The failed-turn card's Copy error and the edit diff's Copy now use copyTextResult and update their copied state from the actual boolean, so a refused copy keeps the button unchanged and a successful retry recovers normally.

  • Adds webviews/test/agent-pane-copy-refusal.test.tsx covering refusal, fallback success, thrown fallback, and retry behavior for both controls; the edit-diff case renders the real lazy-loaded hunks with a valid edit fixture.

Written for commit 45b8c99. Summary will update on new commits.

View guided diff Turn on auto-fix

@github-actions github-actions Bot added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Oct 31, 2026
@blacksmith-sh

blacksmith-sh Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Found 10 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmp27esn4_9/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpy6i_kv8i/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs
convention violations found (ERROR lines above)./
convention violations found (ERROR lines above).
View Logs
test_every_part_of_bun_run_check_runs_in_the_warn_branch (main.WebviewsWarnSplitCov
ersCheck.test_every_part_of_bun_run_check_runs_in_the_warn_branch)/
test_every_part_of_bun_run_check_runs_in_the_warn_branch (main.WebviewsWarnSplitCov
ersCheck.test_every_part_of_bun_run_check_runs_in_the_warn_branch)
View Logs
test_runtime_added_app_capabilities_are_runtime_only (main.StaticCapabilityRuntimeO
nlyTests.test_runtime_added_app_capabilities_are_runtime_only)/
test_runtime_added_app_capabilities_are_runtime_only (main.StaticCapabilityRuntimeO
nlyTests.test_runtime_added_app_capabilities_are_runtime_only)
View Logs
test_the_nightly_is_covered (main.NightlyStepCoverageTests.test_the_nightly_is_cove
red)/
test_the_nightly_is_covered (main.NightlyStepCoverageTests.test_the_nightly_is_cove
red)
View Logs

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

@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

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

Review c4fdbc1: 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=pr29218 -f ref=research/sw14-copy-refusal-20261031 -f platform=macos -f backend_mode=local

Gallery before/after for 45b8c99: 16 states rendered, 0 images differ, 991 more touched states not rendered. Images are in the run's artifact, not on this public PR; the independent review of this head reads them on its own, so the PR needs no hand-made screenshots of these states.

gh run download 41274006660 -R manaflow-ai/cmux -n gallery-advisory-29218-45b8c9986c00f90f3f21cb2408f011d280adf965 (then read gallery-review.json)

States
State Theme Status Diff Picked by
agent-pane.access-offer/turn-no-credits Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-no-credits Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-low-credits Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-low-credits Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-no-offer Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.access-offer/turn-no-offer Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/claude-key Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/claude-key Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/deepseek-key Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/deepseek-key Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/sign-in Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/sign-in Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/rate-limited Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/rate-limited Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/on-coderouter Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/on-coderouter Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/moving Apple System Colors Light unchanged 0% own TurnRows.tsx
agent-pane.coderouter-offer/moving Apple System Colors unchanged 0% own TurnRows.tsx
agent-pane.transcript/activity-tone-secondary Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/activity-tone-secondary Apple System Colors unchanged 0% own EditDiff.tsx
agent-pane.transcript/activity-tone-tertiary Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/activity-tone-tertiary Apple System Colors unchanged 0% own EditDiff.tsx
agent-pane.transcript/activity-tone-readable Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/activity-tone-readable Apple System Colors unchanged 0% own EditDiff.tsx
agent-pane.transcript/message-surfaces-bordered Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/message-surfaces-bordered Apple System Colors unchanged 0% own EditDiff.tsx
agent-pane.transcript/message-surfaces-soft Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/message-surfaces-soft Apple System Colors unchanged 0% own EditDiff.tsx
agent-pane.transcript/message-surfaces-borderless Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/message-surfaces-borderless Apple System Colors unchanged 0% own EditDiff.tsx
agent-pane.transcript/conversation Apple System Colors Light unchanged 0% own EditDiff.tsx
agent-pane.transcript/conversation Apple System Colors unchanged 0% own EditDiff.tsx

Pick other states with a Gallery: entry.id, entry.id/variant line in the PR body.

Passes: CI passes on 45b8c9986c.

CI passes on 45b8c9986c (run 41274006835 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

web and size checks: green

Shipped in:

@teamleaderleo
teamleaderleo marked this pull request as ready for review November 1, 2026 00:56

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="webviews/src/agent-session/acpmux/conversation/TurnRows.tsx">

<violation number="1" location="webviews/src/agent-session/acpmux/conversation/TurnRows.tsx:677">
P2: Rapid clicks can let an older copy result overwrite a newer one, hiding `Copied` even though the latest copy succeeded. Track the latest request before applying its result, or disable the button until the current copy settles.</violation>
</file>

<file name="webviews/src/agent-session/acpmux/conversation/EditDiff.tsx">

<violation number="1" location="webviews/src/agent-session/acpmux/conversation/EditDiff.tsx:89">
P2: A stale copy attempt can overwrite the latest result because this handler has no request ordering. Track a request generation or disable the button, and update state only for the latest click.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

onClick={() =>
void copyText(message).then(
() => setCopied(true),
void copyTextResult(message).then(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Rapid clicks can let an older copy result overwrite a newer one, hiding Copied even though the latest copy succeeded. Track the latest request before applying its result, or disable the button until the current copy settles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/conversation/TurnRows.tsx, line 677:

<comment>Rapid clicks can let an older copy result overwrite a newer one, hiding `Copied` even though the latest copy succeeded. Track the latest request before applying its result, or disable the button until the current copy settles.</comment>

<file context>
@@ -674,8 +674,8 @@ function TurnFailure({
                 onClick={() =>
-                  void copyText(message).then(
-                    () => setCopied(true),
+                  void copyTextResult(message).then(
+                    (accepted) => setCopied(accepted),
                     () => setCopied(false),
</file context>

void copyText(patches.join("")).then(
() => setCopied(true),
void copyTextResult(patches.join("")).then(
(accepted) => setCopied(accepted),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: A stale copy attempt can overwrite the latest result because this handler has no request ordering. Track a request generation or disable the button, and update state only for the latest click.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/conversation/EditDiff.tsx, line 89:

<comment>A stale copy attempt can overwrite the latest result because this handler has no request ordering. Track a request generation or disable the button, and update state only for the latest click.</comment>

<file context>
@@ -85,8 +85,8 @@ export function EditDiff({
-            void copyText(patches.join("")).then(
-              () => setCopied(true),
+            void copyTextResult(patches.join("")).then(
+              (accepted) => setCopied(accepted),
               () => setCopied(false),
             )
</file context>

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at e6d1293

The failed-turn "Copy error" button (TurnRows.tsx) and the edit-diff Copy button (EditDiff.tsx) now call copyTextResult and setCopied(accepted), so "Copied" shows only when the async clipboard write or the execCommand("copy") fallback actually succeeds; a rejection still sets false. copyTextResult returns false on a refused or throwing fallback and always removes its textarea; copyText remains for other callers. The auto-merged TurnRows.tsx is coherent (no stray copyText call). The new test's imports all exist at HEAD, its JSDOM setup/teardown follows turn-error-details.test.tsx, its mocks stay on its own JSDOM globals, and it covers success, fallback success, refused fallback, throwing fallback with cleanup, and refuse-then-retry on both controls.

Follow-ups:

  • [follow-up] copied never resets after a success (pre-existing).
  • [follow-up] The test matches the literal English "Copied"; comparing against the translated key would be sturdier.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at 17acfe8

Delta review over the prior SHIP at e6d1293. The only change is in webviews/test/agent-pane-copy-refusal.test.tsx: the as TurnFile cast (which failed vp check with TS2352) becomes an annotated TurnFile literal that sets every required field (path, displayPath, edits, additions, deletions, created). EditDiff never reads displayPath or created, and the copy button still uses edits: [], so the render and the test's intent are unchanged.

Follow-ups: none

…d a customElements registry

The edit-diff case renders EditDiff, whose lazily loaded hunks import @pierre/diffs: that module reads customElements at load, and an edit-less file hands it an empty patch it refuses. Either failed the test whenever the chunk resolved inside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-fc739fd2f4e557ae7189
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (subagent claude-opus-5-5 medium): SHIP at c4fdbc1

Delta review over the prior SHIP at e6d1293. Only webviews/test/agent-pane-copy-refusal.test.tsx changed: the as TurnFile cast (TS2352 in vp check) becomes a fully typed TurnFile literal with one real DiffEdit, and customElements joins the JSDOM globals (captured in saved and restored in afterAll), since the lazily loaded diff hunks import @pierre/diffs, which reads customElements and rejects an empty patch. Assertions and product code are unchanged. Focused test passed 20/20 (--rerun-each 5) and bun run check passed at this head.

Follow-ups: none

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (claude opus, correctness first): SHIP at

@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 04:40 Inactive
@cmux-bot
cmux-bot Bot temporarily deployed to ci-routing-agent November 1, 2026 04:55 Inactive
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (carry): SHIP at 45b8c99 (base merge only since c4fdbc1; patch-id unchanged)

@teamleaderleo
teamleaderleo merged commit 367a388 into feat-cmux-next Nov 1, 2026
189 of 198 checks passed
@teamleaderleo
teamleaderleo deleted the research/sw14-copy-refusal-20261031 branch November 1, 2026 09:23

This branch was previously deployed

1 inactive deployment
ci-routing-agent — 45b8c998 Deployed Nov 1, 2026 by cmux-bot[bot] via wait for same-key tree publisher #93699
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant