Skip to content

fix(terminal): arm native-paste suppression on middle-click in mouse-tracking TUIs - #21834

Merged
buf0-bot[bot] merged 2 commits into
mainfrom
middle-click-issues
Sep 20, 2026
Merged

buf0-bot[bot] merged 2 commits into
mainfrom
middle-click-issues

Conversation

@buf0-bot

@buf0-bot buf0-bot Bot commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​151 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​151
Prod 1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​34 $\color{#cf222e}{\Huge{\mathbf{−}}}$​20 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​14

ELI5

Middle-clicking into a terminal running an app like Claude Code or Codex (anything that turns on mouse tracking) pasted your selected text twice instead of once. This PR makes it paste once.

What Changed

Before: when you middle-click, Orca checks whether the terminal pane is in "mouse tracking" mode (used by full-screen TUIs). If it is, Orca completely stepped aside — it didn't call preventDefault() and didn't arm the suppression window that a previous fix (#8993) relies on to swallow Chromium's follow-up native paste.

That meant three things happened from one click:

  1. Chromium's built-in Linux "middle click pastes PRIMARY selection" fired unsuppressed, landing in xterm's hidden textarea and reaching the PTY (copy Linux support #1).
  2. xterm forwarded the click to the app as a mouse report.
  3. The TUI (e.g. Claude Code) did its own PRIMARY paste in response to that mouse report (copy Fix Ctrl+Backspace not deleting word in terminal #2).

After: Orca now always blocks Chromium's native paste and arms the suppression window on any middle-click in a terminal pane, regardless of tracking mode. Only the decision to paste directly into the PTY itself (and to stop the click from propagating to xterm) stays gated on mouseTrackingMode === 'none' — so a tracking TUI still receives the click as a mouse report and performs its own single paste, while Chromium's redundant native paste is suppressed.

Why

The alternative of always letting Orca perform the paste (rather than deferring to the TUI in tracking mode) isn't viable: mouse-tracking apps expect the raw mouse event, not text injected by the host. Silencing the whole path when Orca can't own the paste (the pre-existing bug) let Chromium's own behavior slip through unchecked. Separating "should Orca write to the PTY" from "should the native OS-level paste be suppressed" fixes the double-paste without touching how tracking-mode apps receive input.

I also checked this against a middle-click "open URL" feature merged earlier this week (#21438) that lets middle-click open links in tracking-mode panes. That feature works by intercepting the mouse report at the xterm element itself and never depended on this suppression path stopping propagation — this fix's tracking-mode branch never calls stopPropagation(), so link clicks continue to reach that handler unaffected. Verified via the existing terminal-link-* test suite (167 tests, all passing).

Linked Issue

Fixes #21762

Visual Proof

N/A — this is a headless input-handling fix (event listener logic); the observable difference is "pastes once" vs. "pastes twice" in a terminal running a mouse-tracking TUI, which isn't meaningfully shown via screenshot. Verified via automated repro test instead (see Testing).

Testing

  • Automated tests added/updated
  • Added issue-21762-tracking-mode-middle-click-double-paste.repro.test.ts, which reproduces the bug against the pre-fix code (asserts preventDefault was called and suppression was armed on a middle-click into a mouse-tracking pane — this failed before the fix) and confirms non-tracking-mode paste-to-PTY behavior is unchanged.
  • Ran usePrimarySelectionPaste.test.tsx, usePrimarySelectionPaste.terminal-native-paste.test.tsx, primary-selection.test.ts, primary-selection-paste.test.ts, and all 16 terminal-link-*.test.ts files (167 tests) — all pass.
  • pnpm tc:web and pnpm run check:code-quality:changed pass.
  • Not manually tested against a real Linux desktop with wl-paste/xclip/xsel and a live Claude Code/Codex session in this environment — the automated repro isolates the exact regression path described in the issue (suppression arming + preventDefault gating), which is the root cause identified by the reporter's code pointer against the shipped bundle.

AI Disclosure

This PR was authored by Claude (Anthropic), operating as an autonomous coding agent in Claude Code, based on the linked GitHub issue.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

  • Security: no change to what's suppressed or how clipboard/PRIMARY data is read; behavior only changes when suppression arms.
  • Cross-platform: armPrimarySelectionNativePasteSuppression already no-ops off Linux, so macOS/Windows behavior is unchanged.
  • Remote SSH / Mobile: unaffected — this is purely renderer-side DOM event handling around the middle-click gesture, before any PTY/SSH transport is involved.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…tracking TUIs (#21762)

Orca's own middle-click paste path bailed out entirely whenever the pane was
in mouse tracking mode (Claude Code, Codex, ...), skipping preventDefault()
and never arming the #8993 native-paste suppression window. Chromium's
native Linux middle-click paste then landed unsuppressed alongside the TUI's
own PRIMARY paste from the forwarded mouse report, pasting the selection
twice.

Split pane lookup from the tracking-mode gate: any terminal pane target now
arms suppression and blocks the native paste, while only the paste-to-PTY
(and the propagation stop that would swallow the click before xterm can
report it) stays gated on mouseTrackingMode === 'none'.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — two minor suggestions inline.

Reviewed changes

  • Arm native-paste suppression in tracking mode — findTerminalPaneForMiddleClick now resolves any terminal pane, and both handlePrimarySelectionMiddleMouseDown and handlePrimarySelectionAuxClick call preventDefault() + armPrimarySelectionNativePasteSuppression() unconditionally; the PTY paste, focus(), and stopPropagation() stay gated on mouseTrackingMode === 'none' so a tracking TUI still receives the click as a mouse report (use-terminal-pane-mobile-actions.ts:97, :130, :214).
  • Regression test added — issue-21762-...repro.test.ts pins that the tracking-mode path preventDefaults and arms suppression, and that the non-tracking path still focuses the pane.

I confirmed the repro test fails against the pre-fix logic (temporarily routing mousedown back through getPrimarySelectionMiddleClickPane makes the tracking case assert false === true), ran the surrounding suites (terminal-link-*, terminal-url-link-*, usePrimarySelectionPaste*, primary-selection* — 228 passing), and checked xterm's source to confirm the added capture-phase preventDefault() cannot break mouse-report forwarding: MouseService._handleMouseDown calls ev.preventDefault() and ctx.focus() itself and never reads defaultPrevented, and the #21438 link path only reads defaultPrevented on the separate mouseup event.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

// oxlint-disable-next-line react-hooks/exhaustive-deps -- Preserve the pre-split dependency contract.
[terminalShouldHandleMiddleClick]
)
const getPrimarySelectionMiddleClickPane = useCallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After the split this wrapper has no remaining caller — both handlers use findTerminalPaneForMiddleClick, and getPrimarySelectionMiddleClickPane is only re-exported (line 266). Removing it (and its return-object slot) keeps the controller surface honest; if it's deliberately kept for an external consumer, that intent isn't visible here.

preventDefault: () => {
defaultPrevented = true
},
stopPropagation: () => {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

stopPropagation is stubbed as a no-op and never asserted, so the test can't catch the regression that matters most for tracking mode: stopping propagation would swallow the click before xterm forwards it as a mouse report, breaking the TUI. Recording the call and asserting it was not made in the sgr case (and was made in the none case) would pin that invariant.

- Fix a mis-attributing comment: the suppression window (not preventDefault,
  which only helps on mousedown while Chromium's native paste fires on
  mouseup) is what swallows the duplicate native paste.
- Drop the now-unused getPrimarySelectionMiddleClickPane.
- Assert stopPropagation is/isn't called per tracking mode in the repro test.
@buf0-bot
buf0-bot Bot merged commit 5b8b01b into main Sep 20, 2026
29 of 30 checks passed
buf0-bot Bot added a commit that referenced this pull request Sep 21, 2026
…21858)

* fix(terminal): let Shift+middle-click paste in mouse-tracking panes

Follow-up to #21834 (issue #21762). That fix arms the native-paste
suppression window for every terminal middle-click, then returns early
when the pane is in mouse-tracking mode so the TUI performs the paste
from the forwarded mouse report.

xterm's SelectionService.shouldForceSelection deliberately withholds
that report for a shifted click (Option-click on Mac), so the TUI never
pastes. With the native follow-up paste now suppressed as well,
Shift+middle-click in Claude Code, Codex, and other tracking TUIs pasted
nothing at all. Previously Chromium's native paste was the one paste.

Mirror xterm's platform rule: when the click's modifier forces
selection, fall through to Orca's own paste-to-PTY path (stop
propagation, focus, paste) exactly as in a non-tracking pane. The
auxclick handler gates stopPropagation the same way.

* test(terminal): pin Alt+middle-click to the TUI-owned path off Mac

---------

Co-authored-by: bench <bench@example.invalid>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

0 participants