Skip to content

fix(web): reliable terminal copy with fallback, feedback, and shift-drag hint#2120

Open
Bdandc wants to merge 1 commit into
AgentWrapper:mainfrom
Bdandc:session/ca-2
Open

fix(web): reliable terminal copy with fallback, feedback, and shift-drag hint#2120
Bdandc wants to merge 1 commit into
AgentWrapper:mainfrom
Bdandc:session/ca-2

Conversation

@Bdandc

@Bdandc Bdandc commented Jun 12, 2026

Copy link
Copy Markdown

Problem

Copy/paste from the dashboard terminal (xterm.js + tmux) was unreliable, with zero feedback when it failed:

  1. Silent failures — every copy path ended in navigator.clipboard.writeText(...).catch(() => {}). navigator.clipboard is undefined on non-secure origins (dashboard opened via LAN IP instead of localhost) and can be permission-denied, so copies silently no-oped.
  2. Mouse capture — agent TUIs (Claude Code) enable mouse reporting in tmux, so xterm.js forwards drags to the app instead of selecting text. Shift+drag forces local selection, but nothing told the user.

Changes

  • writeClipboardText() layered helper (terminal-clipboard.ts): navigator.clipboard.writeTextdocument.execCommand('copy') via a temporary sr-only textarea (works on non-secure origins, restores focus afterwards) → resolves false so the failure is surfaced instead of swallowed. Used by both terminal copy paths: the OSC 52 handler and the Cmd+C / Ctrl+Shift+C key handler (these are the only terminal copy paths — verified by sweep).
  • Copy feedback: registerClipboardHandlers reports every copy outcome via a new optional onCopyResult callback. DirectTerminal flashes a small transient Copied / Copy failed pill (role="status") over the terminal for 2s. Design tokens + Tailwind classes only — no inline styles, no new libraries (C-01, C-02, C-07).
  • Discoverability hint: useXtermTerminal detects active mouse reporting via terminal.modes.mouseTrackingMode (checked in the parsed-write callback, setState only on transitions). While active, TerminalControls shows a subtle ⇧+drag to select hint with an explanatory tooltip, in both the chrome bar and the chromeless floating controls. It disappears when the TUI releases the mouse.

The optional copy-on-select toggle was deliberately left out — reliable selection-end detection on top of onSelectionChange isn't low-risk, and the core fix doesn't depend on it. Happy to follow up if wanted.

Invariants preserved

  • The terminal-creation effect's dependency list is unchanged — the copy callback flows through a ref (onCopyResultRef) so a changing identity can't tear down/recreate the terminal or WebSocket.
  • Selection-preserving write buffering, follow-output scrolling, and the XDA/OSC 52 wiring are untouched apart from passing the write-parsed callback.

Tests

  • terminal-clipboard.test.ts extended (15 new tests): writeClipboardText matrix — clipboard present / absent / rejected, execCommand ok / false / throws / missing, textarea cleanup — and onCopyResult reporting for OSC 52 success, decode failure, fallback success/failure, and Cmd+C.
  • New DirectTerminal.copy-feedback.test.tsx: toast show/auto-dismiss/timer-reset, hint visible while mouse reporting active (chrome + chromeless), hidden otherwise.

Verification

  • pnpm --filter @aoagents/ao-web typecheck
  • pnpm --filter @aoagents/ao-web test ✅ (92 files, 1050 passed, 5 skipped)
  • pnpm lint ✅ (0 errors; remaining warnings pre-existing)
  • Prettier check on all touched files ✅

🤖 Generated with Claude Code

…rag hint

Copy from the dashboard terminal silently no-oped on non-secure origins
(LAN IP instead of localhost) because every copy path ended in
navigator.clipboard.writeText(...).catch(() => {}), and agent TUIs that
enable mouse reporting made drag-selection appear broken with no
explanation.

- Add writeClipboardText() helper: navigator.clipboard.writeText →
  document.execCommand('copy') via a temporary sr-only textarea →
  surfaced failure. Used by both terminal copy paths (OSC 52 handler
  and Cmd+C / Ctrl+Shift+C).
- registerClipboardHandlers now reports every copy outcome via an
  onCopyResult callback; DirectTerminal flashes a transient
  'Copied' / 'Copy failed' pill over the terminal (design tokens,
  Tailwind only).
- Detect active mouse reporting via terminal.modes.mouseTrackingMode
  after parsed writes and show a subtle '⇧+drag to select' hint in
  TerminalControls (chrome bar and chromeless overlay) while active.
- Tests: execCommand fallback matrix (clipboard present/absent/
  rejected, execCommand ok/false/throw/missing), onCopyResult
  reporting, toast show/dismiss/reset, hint visibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant