From 323f334c15bde0756dd38ed40e757bd13e890088 Mon Sep 17 00:00:00 2001 From: Its My Work Date: Sat, 12 Sep 2026 21:16:07 +0000 Subject: [PATCH 1/2] fix(terminal): copy selected text to clipboard automatically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right-click on a terminal pane never offered "Copy" — xterm.js clears the text selection on the right mousedown before the browser's context menu opens, and Ctrl+C is unavailable for copying since it's sent through as SIGINT to the running process instead. The only way out was selecting then relying on Ctrl+C, which just killed whatever CLI was running. xterm v4's copyOnSelect option was removed upstream; the replacement is wiring onSelectionChange() ourselves. Copy the selection to the clipboard (via the existing copyText() helper, which already falls back to execCommand for non-secure-context/tunnel use) as soon as one is made, matching how most terminal apps behave. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011E2bJQ2sL9LgEWosjvydTR --- public/index.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/index.html b/public/index.html index 3f82dee..ec05eff 100644 --- a/public/index.html +++ b/public/index.html @@ -18183,6 +18183,15 @@

⌨️ Keyboard Shortcuts

if (term.hasSelection && term.hasSelection()) return; try { term.focus(); } catch {} }); + // xterm has no built-in copy-on-select (that was an xterm v4 option, removed + // upstream); a right-click here also can't fall back to the browser's native + // "Copy" because xterm's own selection handling clears the selection on the + // right mousedown before the context menu opens. Do the copy ourselves the + // moment a selection is made, same UX as most terminal apps offer. + term.onSelectionChange(() => { + if (!term.hasSelection()) return; + copyText(term.getSelection()).catch(() => {}); + }); _connectTerminalWs(sessionId); return entry; } From 696fd011b237745def82b836a8d4eb10ad736969 Mon Sep 17 00:00:00 2001 From: Lexus2016 Date: Sun, 13 Sep 2026 15:17:38 +0200 Subject: [PATCH 2/2] fix(terminal): copy the selection on mouseup, not on every selection change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit xterm fires onSelectionChange from the selection service's refresh on every animation frame while the mouse is still dragging, so the previous wiring was a clipboard write per frame — and over plain http copyText() falls back to a focused off-screen textarea, which stole focus from the pane mid-drag. The existing mouseup handler already knows whether a selection was dragged out; the copy now happens there, once, when the drag ends. test/composer-terminal.test.js pins the new shape and asserts that term.onSelectionChange is not used. Co-Authored-By: Claude Opus 5 --- public/index.html | 23 +++++++++++++---------- test/composer-terminal.test.js | 9 +++++++-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/public/index.html b/public/index.html index ec05eff..7964e5e 100644 --- a/public/index.html +++ b/public/index.html @@ -18179,19 +18179,22 @@

⌨️ Keyboard Shortcuts

// browser restoring focus after an alt-tab — leaves the pane visually active but // deaf. A click on it is an unambiguous "I am typing here"; don't steal focus back // when the user is dragging out a selection. + // + // Copy-on-select rides the same mouseup. xterm has no built-in option for it + // (that was an xterm v4 option, removed upstream), and a right-click can't + // fall back to the browser's native "Copy" because xterm clears the selection + // on the right mousedown before the context menu opens. It is NOT wired to + // `term.onSelectionChange`: that fires from the selection service's refresh on + // every animation frame while the mouse is still dragging, which is a + // clipboard write per frame — and over plain http `copyText()` falls back to a + // focused off-screen textarea, so it would also steal focus mid-drag. host.addEventListener('mouseup', () => { - if (term.hasSelection && term.hasSelection()) return; + if (term.hasSelection && term.hasSelection()) { + copyText(term.getSelection()).catch(() => {}); + return; + } try { term.focus(); } catch {} }); - // xterm has no built-in copy-on-select (that was an xterm v4 option, removed - // upstream); a right-click here also can't fall back to the browser's native - // "Copy" because xterm's own selection handling clears the selection on the - // right mousedown before the context menu opens. Do the copy ourselves the - // moment a selection is made, same UX as most terminal apps offer. - term.onSelectionChange(() => { - if (!term.hasSelection()) return; - copyText(term.getSelection()).catch(() => {}); - }); _connectTerminalWs(sessionId); return entry; } diff --git a/test/composer-terminal.test.js b/test/composer-terminal.test.js index d774711..efa6877 100644 --- a/test/composer-terminal.test.js +++ b/test/composer-terminal.test.js @@ -203,8 +203,13 @@ check('a reconnect refocuses the pane', /ws\.onopen = \(\) => \{[\s\S]{0,700}?requestAnimationFrame\(\(\) => \{ try \{ term\.focus\(\); \} catch \{\} \}\);/.test(TB), true); check('clicking the pane refocuses it', /host\.addEventListener\('mouseup'[\s\S]{0,260}?term\.focus\(\)/.test(TB), true); -check('but not while a selection is being dragged out', - /host\.addEventListener\('mouseup'[\s\S]{0,160}?if \(term\.hasSelection && term\.hasSelection\(\)\) return;/.test(TB), true); +check('but not while a selection is being dragged out — that mouseup copies the selection instead', + /host\.addEventListener\('mouseup'[\s\S]{0,160}?if \(term\.hasSelection && term\.hasSelection\(\)\) \{\s*copyText\(term\.getSelection\(\)\)[^\n]*\n\s*return;/.test(TB), true); +// xterm fires onSelectionChange from the selection service's refresh on every +// animation frame while the mouse is still dragging — a clipboard write per frame, +// and over plain http copyText()'s textarea fallback steals focus mid-drag. +check('copy-on-select is not wired to term.onSelectionChange', + /term\.onSelectionChange\(/.test(TB), false); console.log('\n— i18n: the three new keys exist in all five locales —');