Skip to content

feat(web): faster drag autoscroll and richer terminal selection - #7305

Open
westwol wants to merge 6 commits into
pingdotgg:mainfrom
westwol:enhancement/terminal-ux
Open

feat(web): faster drag autoscroll and richer terminal selection#7305
westwol wants to merge 6 commits into
pingdotgg:mainfrom
westwol:enhancement/terminal-ux

Conversation

@westwol

@westwol westwol commented Aug 17, 2026

Copy link
Copy Markdown

Dragging a selection past the edge of the terminal scrolled at a fixed 1 row per 80ms regardless of how far past the edge the pointer went, so selecting a screenful of scrollback meant holding still for several seconds. Selection was also mouse-only: no select all, no block selection, no keyboard selection.

The autoscroll rate is now driven by how far past the edge the pointer sits, integrated over real elapsed time on rAF rather than a fixed interval — a slight overhang still creeps for fine control, while a long drag covers pages per second. It also skips the re-extension and its full repaint once scrollback hits either end, where the old loop repainted every frame while moving nothing.

Selection gained four capabilities, each built on a libghostty-vt primitive that was already vendored but unused:

  • Select all — Cmd+A, Ctrl+Shift+A off mac, plus a context menu entry. A bare Ctrl+A stays with the shell, since it is readline's beginning-of-line and tmux's default prefix.
  • Block selection — Alt+drag, via the rectangle flag on Ghostty's selection struct.
  • Keyboard selection — Shift+Arrows/Home/End/PageUp/PageDown, extending from the cursor and scrolling the moving end into view. Gated off on the alternate screen and under mouse tracking so vim, htop and friends keep their own shifted chords.
  • Select command output — needs OSC 133 marks from the shell. The server spawns $SHELL without injecting shell integration, so the entry is probed per right-click and omitted entirely when the shell emits no marks, rather than offering an action that silently does nothing.

Verification

61 unit tests pass across the new pure helpers (autoscroll rate, overshoot, keyboard move/target, the select-all shortcut, context menu composition); typecheck and lint are clean on all five files.

runtimeAbi.test.ts — the only test that exercises the real WASM — cannot collect locally due to a pre-existing .wasm?inline import-analysis issue, confirmed present on a clean tree. So the three WASM-touching paths were verified with standalone probes against the vendored ghostty-vt.wasm:

  • Block selection over ABCDEFGH/IJKLMNOP/QRSTUVWX from (2,0) to (4,2) yields CDE / KLM / STU, versus CDEFGH / IJKLMNOP / QRSTU for flow selection.
  • select_output returns exactly the command's output with OSC 133 marks present, and error -4 without them or on a prompt line.
  • select_all returns usable screen bounds for anchoring the copy affordance.

Outstanding

AGENTS.md asks for a short video on motion or timing changes. The autoscroll change is exactly that, and no clip is attached here — it needs a real browser session to capture. Happy to add one before merge.

Screen.Recording.2026-08-17.at.2.13.36.a.m.mov

Written by Claude Opus 5 (1M context) via Claude Code.

🤖 Generated with Claude Code


Note

Medium Risk
Touches core terminal input/selection and WASM APIs with shell shortcut conflicts mitigated by gating, but behavior is user-visible and easy to regress in vim/tmux or unmarked shells.

Overview
Improves terminal selection and scroll behavior on the Ghostty canvas terminal in the thread drawer.

Drag autoscroll replaces the fixed-interval ±1-row timer with rAF-driven scrolling whose speed scales with how far the pointer overshoots the grid edge; it skips useless repaints when scrollback is already at an end.

New selection capabilities wire through GhosttyTerminalCore / GhosttyTerminalSurface: Select all (Cmd+A / Ctrl+Shift+A, context menu), Alt+drag block selection (rectangle on setSelection), Shift+arrow/Home/End/Page keyboard extension (disabled on alternate screen and mouse tracking), and Select command output when OSC 133 marks exist—captured at right-click time with a buffer generation guard so async menus do not apply stale ranges.

Context menu in ThreadTerminalDrawer always offers Select all and Paste; copy/add-to-chat stay disabled without a selection; select-output appears only when a bounded output range is under the pointer.

Session buffer sync stops clearing selection on every append (Ghostty keeps selections pinned); full buffer replaces still reset selection via resetAndWrite.

Reviewed by Cursor Bugbot for commit f24d700. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add rectangular selection, keyboard selection, and distance-based autoscroll to terminal

  • Adds Alt+drag rectangular (block) selection and Cmd+A/Ctrl+Shift+A select-all to GhosttyTerminalSurface, with scrollback included and viewport position preserved.
  • Adds Shift+Arrow/Page/Home/End keyboard-driven selection extension when not in alternate screen or mouse tracking mode.
  • Replaces the setInterval-based autoscroll with a requestAnimationFrame integrator using distance-proportional rows-per-second, capping speed for throttled tabs.
  • Adds a "Select command output" context menu item that appears when right-clicking over bounded command output, using a generation stamp to guard against stale selections after buffer replacements.
  • Behavioral Change: selection is no longer cleared on terminal append writes; buffer replacements clear selection via buffer reset instead.

Macroscope summarized f24d700.

Dragging a selection past the edge of the terminal scrolled at a fixed
1 row per 80ms regardless of how far past the edge the pointer went, so
selecting a screenful of scrollback meant holding still for seconds.

The autoscroll rate is now driven by how far past the edge the pointer
sits and integrated over real elapsed time on rAF, so a slight overhang
still creeps for fine control while a long drag covers pages. It also
stops re-rendering once scrollback reaches either end.

Selection also gained four capabilities, all built on libghostty-vt
primitives that were already vendored but unused:

- Select all (Cmd+A, Ctrl+Shift+A off mac, and a context menu entry).
  A bare Ctrl+A stays with the shell for readline and tmux.
- Block selection on Alt+drag, via the selection struct's rectangle flag.
- Keyboard selection with Shift+Arrows/Home/End/PageUp/PageDown, gated
  off on the alternate screen and under mouse tracking so a running
  application keeps its own shifted chords.
- Select command output, which needs OSC 133 marks from the shell; the
  menu entry is probed per click and hidden when the shell emits none.

Written by Claude Opus 5 (1M context) via Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9af61ad2-31ee-4587-aaba-7fa67cc3cbb9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Aug 17, 2026
Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx Outdated
Comment thread apps/web/src/terminal/ghostty/surface.ts
Comment thread apps/web/src/terminal/ghostty/surface.ts
@macroscopeapp

macroscopeapp Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces substantial new terminal selection features (Select All, command output selection, keyboard selection, block selection, improved autoscroll). While well-tested and authored by the primary maintainer of these files, the scope of new user-facing behavior warrants human review.

You can customize Macroscope's approvability policy. Learn more.

Two defects in the selection work, both found in review.

Ghostty reports a cursor scrolled out of the viewport as -1, and grid
refs clamp negatives to zero, so the first Shift+Arrow taken while
viewing scrollback anchored at the top left of the viewport instead of
at the cursor. An out-of-viewport cursor is now no origin at all, so the
press does nothing rather than selecting a region the user never chose.

The terminal context menu also probed the command under the pointer
before awaiting the menu, then re-resolved the same coordinates after it
closed, so output arriving meanwhile could select a different command or
none. The output range is captured once up front and the action applies
that range; screen coordinates stay pinned to their content, so it
survives the wait.

Written by Claude Opus 5 (1M context) via Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread apps/web/src/terminal/ghostty/surface.ts Outdated
A right-click in the canvas padding or the slack below the grid resolved
to the nearest cell, so an OSC 133 output along that edge could be
offered and selected even though the click landed outside the terminal.
The probe now hit tests exactly, the way link hovering already does, and
reports no command for a click that is on no cell. Drag selection keeps
clamping, which is what a drag past the edge wants.

Written by Claude Opus 5 (1M context) via Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 116455d. Configure here.

Comment thread apps/web/src/components/ThreadTerminalDrawer.tsx
A right-click captures the command output under the pointer and applies
it after the menu answers. A full buffer replace in that window (a
session replay, not an append) repoints every screen coordinate at new
content, and nothing cancelled the pending action: the drawer clears the
selection on a buffer update, but shouldClearTerminalSelectionAction only
cancels for the popup path, so the context menu's request id never moved
and the stale range was applied to unrelated rows.

Captures now carry the buffer generation they were taken from and are
dropped when it no longer matches. Appends leave the generation alone,
since those coordinates stay pinned to their rows.

Written by Claude Opus 5 (1M context) via Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread apps/web/src/terminal/ghostty/surface.ts
westwol and others added 2 commits August 17, 2026 02:36
Every buffer update cleared the selection, so any output arriving while
text was selected wiped it — holding a selection on a live log was not
possible. The clear dates from the xterm.js renderer (pingdotgg#2978), six weeks
before libghostty-vt replaced it (pingdotgg#4860), and was carried over unchanged.
Ghostty pins a selection to its content, so an append no longer needs it.

Only a full buffer replace clears now, where every coordinate genuinely
does repoint at new content.

Written by Claude Opus 5 (1M context) via Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
resetAndWrite bumped the buffer generation but left selectionAnchorScreen
and selectionEndScreen pointing into the buffer it had just discarded, so
a Shift+Arrow after a replace would resume from that stale end and select
unrelated content. Only the callers happening to clear afterwards kept it
from biting.

The surface owns those coordinates, so it drops them itself now, and the
drawer no longer clears on the caller side. Behaviour is unchanged for
appends, which still keep the selection.

Written by Claude Opus 5 (1M context) via Claude Code.

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant