diff --git a/tui_gateway/server.py b/tui_gateway/server.py index 462a774ea41..2a1456e41be 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -3134,27 +3134,6 @@ def _(rid, params: dict) -> dict: _write_config_key("display.tui_mouse", nv) return _ok(rid, {"key": key, "value": "on" if nv else "off"}) - if key == "copy_on_select": - raw = str(value or "").strip().lower() - display = ( - _load_cfg().get("display") - if isinstance(_load_cfg().get("display"), dict) - else {} - ) - current = bool(display.get("tui_copy_on_select", True)) - - if raw in ("", "toggle"): - nv = not current - elif raw == "on": - nv = True - elif raw == "off": - nv = False - else: - return _err(rid, 4002, f"unknown copy_on_select value: {value}") - - _write_config_key("display.tui_copy_on_select", nv) - return _ok(rid, {"key": key, "value": "on" if nv else "off"}) - if key in ("prompt", "personality", "skin"): try: cfg = _load_cfg() @@ -3302,14 +3281,6 @@ def _(rid, params: dict) -> dict: display = _load_cfg().get("display") on = display.get("tui_mouse", True) if isinstance(display, dict) else True return _ok(rid, {"value": "on" if on else "off"}) - if key == "copy_on_select": - display = _load_cfg().get("display") - on = ( - display.get("tui_copy_on_select", True) - if isinstance(display, dict) - else True - ) - return _ok(rid, {"value": "on" if on else "off"}) if key == "mtime": cfg_path = _hermes_home / "config.yaml" try: diff --git a/ui-tui/src/app/interfaces.ts b/ui-tui/src/app/interfaces.ts index 27fca574c0b..1d7cdaead03 100644 --- a/ui-tui/src/app/interfaces.ts +++ b/ui-tui/src/app/interfaces.ts @@ -86,7 +86,6 @@ export interface UiState { bgTasks: Set busy: boolean compact: boolean - copyOnSelect: boolean detailsMode: DetailsMode detailsModeCommandOverride: boolean info: null | SessionInfo diff --git a/ui-tui/src/app/slash/commands/core.ts b/ui-tui/src/app/slash/commands/core.ts index 0461b2f92dc..2cad70b9a5d 100644 --- a/ui-tui/src/app/slash/commands/core.ts +++ b/ui-tui/src/app/slash/commands/core.ts @@ -107,27 +107,6 @@ export const coreCommands: SlashCommand[] = [ } }, - { - aliases: ['cos'], - help: 'toggle auto copy-on-drag-release [on|off|toggle]', - name: 'copyselect', - run: (arg, ctx) => { - const current = ctx.ui.copyOnSelect - const next = flagFromArg(arg, current) - - if (next === null) { - return ctx.transcript.sys('usage: /copyselect [on|off|toggle]') - } - - patchUiState({ copyOnSelect: next }) - ctx.gateway - .rpc('config.set', { key: 'copy_on_select', value: next ? 'on' : 'off' }) - .catch(() => {}) - - queueMicrotask(() => ctx.transcript.sys(`copy-on-select ${next ? 'on' : 'off'}`)) - } - }, - { aliases: ['new'], help: 'start a new session', diff --git a/ui-tui/src/app/uiStore.ts b/ui-tui/src/app/uiStore.ts index ad7a01fd33a..1b3a841e18c 100644 --- a/ui-tui/src/app/uiStore.ts +++ b/ui-tui/src/app/uiStore.ts @@ -1,6 +1,6 @@ import { atom } from 'nanostores' -import { COPY_ON_SELECT, MOUSE_TRACKING } from '../config/env.js' +import { MOUSE_TRACKING } from '../config/env.js' import { ZERO } from '../domain/usage.js' import { DEFAULT_THEME } from '../theme.js' @@ -10,7 +10,6 @@ const buildUiState = (): UiState => ({ bgTasks: new Set(), busy: false, compact: false, - copyOnSelect: COPY_ON_SELECT, detailsMode: 'collapsed', detailsModeCommandOverride: false, info: null, diff --git a/ui-tui/src/app/useConfigSync.ts b/ui-tui/src/app/useConfigSync.ts index 8e89e02ab8f..26d02d62046 100644 --- a/ui-tui/src/app/useConfigSync.ts +++ b/ui-tui/src/app/useConfigSync.ts @@ -44,7 +44,6 @@ export const applyDisplay = (cfg: ConfigFullResponse | null, setBell: (v: boolea setBell(!!d.bell_on_complete) patchUiState({ compact: !!d.tui_compact, - copyOnSelect: d.tui_copy_on_select !== false, detailsMode: resolveDetailsMode(d), detailsModeCommandOverride: false, inlineDiffs: d.inline_diffs !== false, diff --git a/ui-tui/src/app/useMainApp.ts b/ui-tui/src/app/useMainApp.ts index cc258892e89..9f2cae78d64 100644 --- a/ui-tui/src/app/useMainApp.ts +++ b/ui-tui/src/app/useMainApp.ts @@ -142,73 +142,11 @@ export function useMainApp(gw: GatewayClient) { const hasSelection = useHasSelection() const selection = useSelection() - const copyOnSelect = ui.copyOnSelect useEffect(() => { selection.setSelectionBgColor(ui.theme.color.selectionBg) }, [selection, ui.theme.color.selectionBg]) - // Auto copy-on-select: when a drag completes with a real selection - // (anchor + focus, not a bare click), push the text to the clipboard - // without clearing the highlight. Matches iTerm2's "Copy to pasteboard - // on selection" — drag → release → already on clipboard, paste anywhere. - // Subscribes once and tracks the previous isDragging on a ref so the - // effect doesn't re-attach on every selection tick. - const wasDraggingRef = useRef(false) - const lastCopiedRef = useRef<{ anchor: string; focus: string } | null>(null) - - useEffect(() => { - if (!copyOnSelect) { - wasDraggingRef.current = false - - return - } - - const fingerprint = (s: { anchor: { col: number; row: number } | null; focus: { col: number; row: number } | null }) => ({ - anchor: s.anchor ? `${s.anchor.row}:${s.anchor.col}` : '', - focus: s.focus ? `${s.focus.row}:${s.focus.col}` : '' - }) - - return selection.subscribe(() => { - const state = selection.getState() - - if (!state) { - return - } - - const dragging = state.isDragging - const prev = wasDraggingRef.current - wasDraggingRef.current = dragging - - if (!state.anchor) { - lastCopiedRef.current = null - - return - } - - if (!prev || dragging || !state.focus) { - return - } - - const fp = fingerprint(state) - const last = lastCopiedRef.current - - if (last && last.anchor === fp.anchor && last.focus === fp.focus) { - return - } - - lastCopiedRef.current = fp - - void selection.copySelectionNoClear().then(text => { - if (text) { - const len = text.length - - turnController.pushActivity(`copied ${len} char${len === 1 ? '' : 's'} · Esc to clear`, 'info') - } - }) - }) - }, [copyOnSelect, selection]) - const composer = useComposerState({ gw, onClipboardPaste: quiet => clipboardPasteRef.current(quiet), diff --git a/ui-tui/src/config/env.ts b/ui-tui/src/config/env.ts index f3616541b46..8fb9cf69a6e 100644 --- a/ui-tui/src/config/env.ts +++ b/ui-tui/src/config/env.ts @@ -2,9 +2,6 @@ const truthy = (v?: string) => /^(?:1|true|yes|on)$/i.test((v ?? '').trim()) export const STARTUP_RESUME_ID = (process.env.HERMES_TUI_RESUME ?? '').trim() export const MOUSE_TRACKING = !truthy(process.env.HERMES_TUI_DISABLE_MOUSE) -// Drag-release auto-copies the selected text. Off by default for terminals -// that do clipboard their own way; opt out with the env var or /copyselect. -export const COPY_ON_SELECT = !truthy(process.env.HERMES_TUI_DISABLE_COPY_ON_SELECT) export const NO_CONFIRM_DESTRUCTIVE = truthy(process.env.HERMES_TUI_NO_CONFIRM) // Skip AlternateScreen — TUI renders into the primary buffer so the host diff --git a/ui-tui/src/content/hotkeys.ts b/ui-tui/src/content/hotkeys.ts index bc3cb6406d5..b79d08061bf 100644 --- a/ui-tui/src/content/hotkeys.ts +++ b/ui-tui/src/content/hotkeys.ts @@ -16,11 +16,7 @@ const copyHotkeys: [string, string][] = isMac : [['Ctrl+C', 'copy selection / interrupt / clear draft / exit']] export const HOTKEYS: [string, string][] = [ - ['drag', 'select text in transcript (auto-copies on release)'], - ['double / triple click', 'select word / line'], - ['click input', 'position cursor inside the prompt'], ...copyHotkeys, - ['Esc', 'clear selection'], [action + '+D', 'exit'], [action + '+G / Alt+G', 'open $EDITOR (Alt+G fallback for VSCode/Cursor)'], [action + '+L', 'redraw / repaint'], diff --git a/ui-tui/src/gatewayTypes.ts b/ui-tui/src/gatewayTypes.ts index 96760a77f38..605d51213f7 100644 --- a/ui-tui/src/gatewayTypes.ts +++ b/ui-tui/src/gatewayTypes.ts @@ -61,7 +61,6 @@ export interface ConfigDisplayConfig { streaming?: boolean thinking_mode?: string tui_compact?: boolean - tui_copy_on_select?: boolean tui_mouse?: boolean tui_statusbar?: 'bottom' | 'off' | 'on' | 'top' | boolean } diff --git a/ui-tui/src/types/hermes-ink.d.ts b/ui-tui/src/types/hermes-ink.d.ts index 4d79544eb7a..5c3d85697d3 100644 --- a/ui-tui/src/types/hermes-ink.d.ts +++ b/ui-tui/src/types/hermes-ink.d.ts @@ -139,17 +139,12 @@ declare module '@hermes/ink' { export function useExternalProcess(): (run: RunExternalProcess) => Promise export function withInkSuspended(run: RunExternalProcess): Promise export function useInput(handler: InputHandler, options?: { readonly isActive?: boolean }): void - export type InkSelectionState = { - readonly anchor: { readonly col: number; readonly row: number } | null - readonly focus: { readonly col: number; readonly row: number } | null - readonly isDragging: boolean - } export function useSelection(): { readonly copySelection: () => Promise readonly copySelectionNoClear: () => Promise readonly clearSelection: () => void readonly hasSelection: () => boolean - readonly getState: () => InkSelectionState | null + readonly getState: () => unknown readonly subscribe: (cb: () => void) => () => void readonly shiftAnchor: (dRow: number, minRow: number, maxRow: number) => void readonly shiftSelection: (dRow: number, minRow: number, maxRow: number) => void