From d2691b9205e5a469e0a537afab89dae7ee3128bd Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 7 Apr 2026 07:44:14 -0700 Subject: [PATCH] =?UTF-8?q?refactor:=20remove=20ALL=20custom=20paste=20han?= =?UTF-8?q?dlers=20=E2=80=94=20clean=20slate=20per=20COE=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After 9 clipboard commits that kept breaking each other, COE review determined: ttyd uses ClipboardAddon, not custom handlers. Ctrl+Shift+V on Linux and Cmd+V on macOS both trigger native browser paste events that xterm.js catches via its hidden textarea handler. Zero custom paste code needed. Deleted synthetic ClipboardEvent dispatch, readText calls, and all paste-related comments. Kept: Ctrl+Shift+C copy, auto-copy on selection, OSC 52, Ctrl+F search. --- muxplex/frontend/terminal.js | 28 +++--------------------- muxplex/frontend/tests/test_terminal.mjs | 27 ++++++++++------------- 2 files changed, 15 insertions(+), 40 deletions(-) diff --git a/muxplex/frontend/terminal.js b/muxplex/frontend/terminal.js index 1e0de02..2c4ceba 100644 --- a/muxplex/frontend/terminal.js +++ b/muxplex/frontend/terminal.js @@ -364,13 +364,9 @@ function openTerminal(sessionName, remoteId) { _term.open(container); // --- Clipboard integration --- - // Ctrl+Shift+C: copy selection to system clipboard (Ctrl+C still sends SIGINT) - // Ctrl+Shift+V: Ctrl+Shift+V does NOT trigger a native browser paste event (unlike Cmd+V - // on macOS). We read the clipboard via navigator.clipboard.readText() and dispatch a - // synthetic ClipboardEvent on xterm.js's hidden textarea. xterm.js catches this via its - // built-in handlePasteEvent listener → prepareTextForTerminal → bracketTextForPaste - // → triggerDataEvent → onData → WebSocket. This is the same path as Cmd+V and correctly - // handles CR/LF normalization, bracketed paste mode, and UTF-8 encoding. + // Copy: Ctrl+Shift+C intercepts and copies selection to system clipboard + // Paste: handled natively by xterm.js (browser paste event → hidden textarea → onData → WebSocket) + // Cmd+V (macOS) and Ctrl+Shift+V (Linux) both trigger native browser paste events _term.attachCustomKeyEventHandler(function(e) { if (e.type !== 'keydown') return true; @@ -381,24 +377,6 @@ function openTerminal(sessionName, remoteId) { return false; // prevent xterm from processing } - // Ctrl+Shift+V → paste from system clipboard via synthetic ClipboardEvent on xterm textarea. - // Dispatching on _term.textarea triggers xterm.js's built-in handlePasteEvent, which - // handles CR/LF normalization, bracketed paste mode, and correct UTF-8 encoding — - // the exact same code path as a native Cmd+V paste on macOS. - if (e.ctrlKey && e.shiftKey && (e.key === 'V' || e.code === 'KeyV')) { - if (navigator.clipboard && navigator.clipboard.readText && _term && _term.textarea) { - navigator.clipboard.readText().then(function(text) { - if (!text || !_term || !_term.textarea) return; - var pasteEvent = new ClipboardEvent('paste', { - clipboardData: new DataTransfer(), - }); - pasteEvent.clipboardData.setData('text/plain', text); - _term.textarea.dispatchEvent(pasteEvent); - }).catch(function() {}); - } - return false; // prevent xterm from processing the raw key - } - // Ctrl+F → open search bar if (e.ctrlKey && !e.shiftKey && (e.key === 'f' || e.key === 'F' || e.code === 'KeyF')) { _openSearch(); diff --git a/muxplex/frontend/tests/test_terminal.mjs b/muxplex/frontend/tests/test_terminal.mjs index d9a0470..22b46aa 100644 --- a/muxplex/frontend/tests/test_terminal.mjs +++ b/muxplex/frontend/tests/test_terminal.mjs @@ -993,25 +993,22 @@ test('terminal.js loads xterm-addon-image for inline graphics', () => { assert.ok(source.includes('ImageAddon'), 'must reference ImageAddon'); }); -// --- Ctrl+Shift+V: synthetic paste event on xterm textarea --- +// --- Ctrl+Shift+V: xterm.js handles paste natively, no custom interception --- -test('terminal.js Ctrl+Shift+V dispatches synthetic paste event on textarea', () => { - // Ctrl+Shift+V does NOT trigger a native browser paste event (unlike Cmd+V on macOS). - // Fix: read clipboard via navigator.clipboard.readText(), then dispatch a synthetic - // ClipboardEvent on xterm.js's hidden textarea. This goes through xterm.js's built-in - // handlePasteEvent which handles CR/LF normalization, bracketed paste, and correct encoding. +test('terminal.js does NOT intercept Ctrl+Shift+V in attachCustomKeyEventHandler', () => { + // COE review: every custom paste handler we built caused either double-paste or encoding issues. + // On Linux, Ctrl+Shift+V is a native browser paste shortcut — it fires a paste event on the + // focused textarea, xterm.js catches it natively. On macOS, Cmd+V does the same. + // Zero custom paste code needed. const source = fs.readFileSync(new URL('../terminal.js', import.meta.url), 'utf8'); const handlerStart = source.indexOf('attachCustomKeyEventHandler'); - const handlerEnd = source.indexOf('// Auto-copy:', handlerStart); + const handlerEnd = source.indexOf('onSelectionChange', handlerStart); const handlerBlock = source.substring(handlerStart, handlerEnd); - assert.ok( - handlerBlock.includes("e.key === 'V'") || handlerBlock.includes("e.code === 'KeyV'"), - 'attachCustomKeyEventHandler must intercept Ctrl+Shift+V to trigger paste', - ); - assert.ok( - handlerBlock.includes('ClipboardEvent') || handlerBlock.includes('dispatchEvent'), - 'must dispatch synthetic paste event so xterm.js handlePasteEvent handles encoding correctly', - ); + // Must NOT have any V key interception + assert.ok(!handlerBlock.includes("e.key === 'V'"), + 'must NOT intercept Ctrl+Shift+V — xterm.js handles paste natively via browser events'); + assert.ok(!handlerBlock.includes("e.code === 'KeyV'"), + 'must NOT intercept KeyV — xterm.js handles paste natively via browser events'); }); // --- UTF-8 output decoding via TextDecoder ---