refactor: remove ALL custom paste handlers — clean slate per COE review

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.
This commit is contained in:
Brian Krabach
2026-04-07 07:44:14 -07:00
parent e4a4c97eec
commit d2691b9205
2 changed files with 15 additions and 40 deletions
+3 -25
View File
@@ -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();
+12 -15
View File
@@ -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 ---