From d0c7e9d4b622b0dd69958026f6ddd5d89900f527 Mon Sep 17 00:00:00 2001 From: "Chief (agent)" <25110505+Lexus2016@users.noreply.github.com> Date: Sun, 8 Nov 2026 21:09:46 +0000 Subject: [PATCH] terminal: copy/paste keys, a pane context menu, opt-in copy-on-select (#131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selecting text in the live pane was unreliable and copying was a side effect of it: every mouseup with a selection overwrote the clipboard, and a right-click — the obvious way to reach Copy/Paste — had nothing to open. This is the first slice of #131, in the order written there. - Ctrl+Shift+C / Ctrl+Shift+V on Linux and Windows, Cmd+C / Cmd+V on macOS, through attachCustomKeyEventHandler. The handler returns false for a chord it handled: without that xterm forwards it to the TUI, so Ctrl+Shift+C would both copy and send ^C to the running agent. Plain Ctrl+C is untouched, so SIGINT still reaches it. - The pane answers right-click itself: Copy / Paste / Select all / Copy on select. Copy is disabled while nothing is selected, and a right-button release no longer copies first, so the selection the user just made is what Copy acts on. Paste reuses the existing bracketed-paste path (pasteToTerminal), which never auto-submits; it gained an optional { silent } so a paste from the keyboard does not toast. - Copy-on-select is now a setting, off by default, toggled from that menu and remembered in localStorage. It used to be unconditional and the only way to copy. - macOptionClickForcesSelection: true, rightClickSelectsWord: false, and a title on the pane saying Shift+drag selects when the app reports mouse events. Tests: test/terminal-clipboard.test.js runs the helpers and _createTerminal itself in a vm with a fake xterm, element and clipboard — chords per platform, Ctrl+C passthrough, copy-on-select default and persistence, the menu model, and the mouseup/contextmenu wiring. 12 checks. test/composer-terminal.test.js keeps its assertions but two of them described the old mouseup shape (no event argument, unconditional copy), so they are re-pointed rather than dropped. Verified: every test module that reads public/index.html passes (20 modules, including i18n completeness at 139 checks with the 6 new keys in all 5 languages); the three that need better-sqlite3 pass under Node 24, which is what it was built for; all three inline script blocks of index.html parse. Not in this slice: the addons (web-links, unicode11, search, clipboard, webgl) and the mobile proposals in the second comment on the issue. They change rendering or need vendored files, so they get their own PR. --- package.json | 2 +- public/index.html | 203 ++++++++++++++++++++--- test/composer-terminal.test.js | 14 +- test/terminal-clipboard.test.js | 274 ++++++++++++++++++++++++++++++++ 4 files changed, 466 insertions(+), 27 deletions(-) create mode 100644 test/terminal-clipboard.test.js diff --git a/package.json b/package.json index 68557e5..f6596f9 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "start": "node server.js", "dev": "node --watch server.js", - "test": "node --test test/render/*.test.mjs && node test/overload-detector.test.js && node test/usage-limit.test.js && node test/auth-errors.test.js && node test/env-load-order.test.js && node test/multi-agent-result.test.js && node test/terminal-session.test.js && node test/terminal-schema.test.js && node test/bots.test.js && node test/bot-inbox.test.js && node test/bots-api.test.js && node test/running-sessions.test.js && node test/session-liveness.test.js && node test/terminal-bridge.integration.test.js && node test/tmux-composite.test.js && node test/engine-spawn-cmd.test.js && node test/claude-firstrun.test.js && node test/telegram-format.test.js && node test/telegram-behaviour.test.js && node test/ask-user-question.test.js && node test/delegate-terminal.test.js && node test/agent-catalog.test.js && node test/update-flow.test.js && node test/kanban-schedule.test.js && node test/kanban-run-badges.test.js && node test/task-backlog.test.js && node test/i18n-completeness.test.js && node test/config-resolve.test.js && node test/chat-defaults.test.js && node test/chat-defaults-api.test.js && node test/run-continuation.test.js && node test/worktree-manager.test.js && node test/session-expiry.test.js && node test/setup-gate.test.js && node test/path-guard.test.js && node test/subscribe-nocatchup.test.js && node test/cli-import-remote.test.js && node test/remote-list-parse.test.js && node test/config-migrate.test.js && node test/engine-pane.test.js && node test/global-workspace.test.js && node test/ssh-secret.test.js && node test/agent-dag.test.js && node test/ssh-parser.test.js && node test/ssh-termination.test.js && node test/session-restart.test.js && node test/remote-env.test.js && node test/remote-files.test.js && node test/remote-files-api.test.js && node test/auth-token.test.js && node test/cmd-model.test.js && node test/origin-guard.test.js && node test/interrupted-recovery.test.js && node test/queue-persistence.test.js && node test/interrupt-idle-config.test.js && node test/agents-md.test.js && node test/editor-links.test.js && node test/editor-open-api.test.js && node test/interrupt-delivery.test.js && node test/terminal-pane-guard.integration.test.js && node test/composer-terminal.test.js && node test/claude-cli-status.test.js && node test/asar-helpers.test.js && node test/mac-signing.test.js && node test/release-mac.test.js && node test/task-workers.test.js && node test/git-clone.test.js && node test/responsive-ui.test.js && node test/kanban-delegation.test.js && node test/kanban-group-button.test.js && node test/terminal-keys.test.js && node test/electron-navigation.test.js && node test/cli-runtime.test.js && node test/worktree-safety.test.js && node test/auth-boundary.test.js && node test/cli-process-tree.test.js && node test/schedule-actions.test.js && node test/install-hooks.test.js && node test/frontend-recovery.test.js && node test/db-revisions.test.js && node test/frontend-refresh-races.test.js && node test/release-gate.test.js && node test/focused-workspace.test.js && node test/secondary-navigation.test.js && node test/section-controls-ux.test.js && node test/settings-ux.test.js && node test/team-forms-ux.test.js && node test/page-forms-ux.test.js && node test/sidebar-ux.test.js && node test/task-text-limit.test.js", + "test": "node --test test/render/*.test.mjs && node test/overload-detector.test.js && node test/usage-limit.test.js && node test/auth-errors.test.js && node test/env-load-order.test.js && node test/multi-agent-result.test.js && node test/terminal-session.test.js && node test/terminal-schema.test.js && node test/bots.test.js && node test/bot-inbox.test.js && node test/bots-api.test.js && node test/running-sessions.test.js && node test/session-liveness.test.js && node test/terminal-bridge.integration.test.js && node test/tmux-composite.test.js && node test/engine-spawn-cmd.test.js && node test/claude-firstrun.test.js && node test/telegram-format.test.js && node test/telegram-behaviour.test.js && node test/ask-user-question.test.js && node test/delegate-terminal.test.js && node test/agent-catalog.test.js && node test/update-flow.test.js && node test/kanban-schedule.test.js && node test/kanban-run-badges.test.js && node test/task-backlog.test.js && node test/i18n-completeness.test.js && node test/config-resolve.test.js && node test/chat-defaults.test.js && node test/chat-defaults-api.test.js && node test/run-continuation.test.js && node test/worktree-manager.test.js && node test/session-expiry.test.js && node test/setup-gate.test.js && node test/path-guard.test.js && node test/subscribe-nocatchup.test.js && node test/cli-import-remote.test.js && node test/remote-list-parse.test.js && node test/config-migrate.test.js && node test/engine-pane.test.js && node test/global-workspace.test.js && node test/ssh-secret.test.js && node test/agent-dag.test.js && node test/ssh-parser.test.js && node test/ssh-termination.test.js && node test/session-restart.test.js && node test/remote-env.test.js && node test/remote-files.test.js && node test/remote-files-api.test.js && node test/auth-token.test.js && node test/cmd-model.test.js && node test/origin-guard.test.js && node test/interrupted-recovery.test.js && node test/queue-persistence.test.js && node test/interrupt-idle-config.test.js && node test/agents-md.test.js && node test/editor-links.test.js && node test/editor-open-api.test.js && node test/interrupt-delivery.test.js && node test/terminal-pane-guard.integration.test.js && node test/composer-terminal.test.js && node test/claude-cli-status.test.js && node test/asar-helpers.test.js && node test/mac-signing.test.js && node test/release-mac.test.js && node test/task-workers.test.js && node test/git-clone.test.js && node test/responsive-ui.test.js && node test/kanban-delegation.test.js && node test/kanban-group-button.test.js && node test/terminal-keys.test.js && node test/electron-navigation.test.js && node test/cli-runtime.test.js && node test/worktree-safety.test.js && node test/auth-boundary.test.js && node test/cli-process-tree.test.js && node test/schedule-actions.test.js && node test/install-hooks.test.js && node test/frontend-recovery.test.js && node test/db-revisions.test.js && node test/frontend-refresh-races.test.js && node test/release-gate.test.js && node test/focused-workspace.test.js && node test/secondary-navigation.test.js && node test/section-controls-ux.test.js && node test/settings-ux.test.js && node test/team-forms-ux.test.js && node test/page-forms-ux.test.js && node test/sidebar-ux.test.js && node test/task-text-limit.test.js && node test/terminal-clipboard.test.js", "postinstall": "node scripts/install-hooks.js", "release": "node scripts/release.js", "release:mac": "node scripts/release-mac.js", diff --git a/public/index.html b/public/index.html index 581a613..c146501 100644 --- a/public/index.html +++ b/public/index.html @@ -1133,6 +1133,14 @@ #termDot { width: 8px; height: 8px; border-radius: 50%; background: #8b949e; flex: none; transition: background-color .25s cubic-bezier(.2,0,0,1); } #termHost { flex: 1; min-height: 0; padding: 6px 8px; position: relative; } +/* #131 — the pane's own Copy / Paste menu. The browser's native context menu is + unusable in the desktop build, and an app that reports mouse events swallows the + right-click before it opens one. `fixed` so a scrolled pane cannot clip it. */ +.term-menu { position: fixed; z-index: 4200; min-width: 184px; padding: 5px; border-radius: var(--r-md); border: 1px solid var(--border); background: var(--s2); box-shadow: var(--shadow-lg); display: flex; flex-direction: column; gap: 2px; } +.term-menu-item { text-align: left; padding: 7px 10px; border: 0; border-radius: var(--r-sm); background: transparent; color: var(--text); font-family: inherit; font-size: 13px; cursor: pointer; } +.term-menu-item:hover:not(:disabled) { background: var(--s3); } +.term-menu-item:disabled { opacity: .45; cursor: default; } +.term-menu-item.on { color: var(--green); } .term-send { display: flex; gap: 6px; align-items: center; padding: 6px 8px 8px; border-top: 1px solid var(--border); background: #0b0b0f; } .term-send input { flex: 1; min-width: 0; background: var(--s2); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-family: inherit; font-size: 13px; padding: 6px 10px; outline: none; } .term-send input:focus { border-color: var(--accent); } @@ -3765,7 +3773,7 @@