Repository navigation
fix(terminal): arm native-paste suppression on middle-click in mouse-tracking TUIs - #21834
Conversation
…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'.
There was a problem hiding this comment.
ℹ️ No critical issues — two minor suggestions inline.
Reviewed changes
- Arm native-paste suppression in tracking mode —
findTerminalPaneForMiddleClicknow resolves any terminal pane, and bothhandlePrimarySelectionMiddleMouseDownandhandlePrimarySelectionAuxClickcallpreventDefault()+armPrimarySelectionNativePasteSuppression()unconditionally; the PTY paste,focus(), andstopPropagation()stay gated onmouseTrackingMode === '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.tspins 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.
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( |
There was a problem hiding this comment.
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: () => {} |
There was a problem hiding this comment.
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.
…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>

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:
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 existingterminal-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
issue-21762-tracking-mode-middle-click-double-paste.repro.test.ts, which reproduces the bug against the pre-fix code (assertspreventDefaultwas 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.usePrimarySelectionPaste.test.tsx,usePrimarySelectionPaste.terminal-native-paste.test.tsx,primary-selection.test.ts,primary-selection-paste.test.ts, and all 16terminal-link-*.test.tsfiles (167 tests) — all pass.pnpm tc:webandpnpm run check:code-quality:changedpass.wl-paste/xclip/xseland 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
docs/reference/agent-skill-sharing-upstream-boundary.mdand 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
armPrimarySelectionNativePasteSuppressionalready no-ops off Linux, so macOS/Windows behavior is unchanged.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)