fix(clipboard): report native/tmux success, keep Ctrl+Shift+C on dashboard

Follow-up on #16020 salvage. Three corrections:

1. Truth signal for /copy
   Before: success was 'OSC 52 sequence was emitted to stdout'. That's
   false on local Linux inside tmux (emitSequence=false), so /copy kept
   printing 'clipboard copy failed' to users whose xclip/wl-copy had
   already succeeded fire-and-forget.
   Fix: setClipboard() now returns { sequence, success } where success =
   native-fired OR tmux-buffer-loaded OR osc52-emitted. copyNative()
   returns a boolean telling setClipboard whether a native attempt was
   made. /copy only shows 'failed' when literally no path was taken.

2. Dashboard keybinding
   Before: Ctrl+C for copy on non-Mac (Ctrl+Shift+C for paste).
   That swallows SIGINT when a stale selection is present and breaks
   the xterm/gnome-terminal/konsole/Windows-Terminal convention where
   Ctrl+C in a terminal emulator is always SIGINT. The real bug was
   that clipboard writes lost user-gesture through OSC-52 round-trips,
   which the direct writeText already fixes.
   Fix: revert copyModifier to Ctrl+Shift+C on non-Mac. Direct
   writeText in the keydown handler preserves user gesture. term.write
   Escape replaced with term.clearSelection() (works without relying
   on TUI input mode).

3. Error toast text
   Before: 'see HERMES_TUI_DEBUG_CLIPBOARD' — tells users how to
   debug but not how to fix.
   Fix: point users at HERMES_TUI_FORCE_OSC52=1 first (the actual
   escape hatch), mention the debug var second.
This commit is contained in:
Teknium 2026-04-26 05:44:38 -07:00 committed by Teknium
parent 2511207cb0
commit e8441c4c0f
9 changed files with 120 additions and 50 deletions

View file

@ -1296,16 +1296,12 @@ export default class Ink {
this.prevFrameContaminated = true
}
/**
* Copy the current selection to the clipboard without clearing the
* highlight. Matches iTerm2's copy-on-select behavior where the selected
* region stays visible after the automatic copy.
*/
/**
* Copy the current text selection to the system clipboard without clearing the
* selection. Returns the copied text on success (empty if no selection or
* clipboard operation failed). Success is determined by whether an OSC 52
* sequence was emitted (native/tmux paths do not produce a sequence).
* selection. Returns the copied text when a clipboard path succeeded (native
* tool fired, tmux buffer loaded, or OSC 52 emitted), or '' when no path was
* taken (e.g. headless Linux without tmux). Matches iTerm2's copy-on-select
* behavior where the selected region stays visible after the automatic copy.
*/
async copySelectionNoClear(): Promise<string> {
if (!hasSelection(this.selection)) {
@ -1316,17 +1312,22 @@ export default class Ink {
if (text) {
try {
const raw = await setClipboard(text)
if (raw) {
this.options.stdout.write(raw)
const { sequence, success } = await setClipboard(text)
if (sequence) {
this.options.stdout.write(sequence)
}
if (success) {
return text
}
if (process.env.HERMES_TUI_DEBUG_CLIPBOARD) {
console.error('[clipboard] [osc52] no sequence emitted — native clipboard or tmux buffer path in use')
console.error('[clipboard] no path reached the clipboard (headless + no tmux?) — set HERMES_TUI_FORCE_OSC52=1 to force the escape sequence')
}
} catch (err) {
if (process.env.HERMES_TUI_DEBUG_CLIPBOARD) {
console.error('[clipboard] [osc52] error:', err)
console.error('[clipboard] error:', err)
}
}
}