Skip to content

Copy off a terminal with a finger - #68

Open
karngyan wants to merge 2 commits into
mainfrom
feat/touch-selection
Open

Copy off a terminal with a finger#68
karngyan wants to merge 2 commits into
mainfrom
feat/touch-selection

Conversation

@karngyan

Copy link
Copy Markdown
Contributor

The gap

A phone could read the terminal and never lift anything out of it. The line saying to run claude --resume <id> was on screen with no way to get the id into the prompt below it.

Nothing was broken, and no control was missing. The screen is painted to a canvas, so there is no text on the page for the OS to offer Copy over, and user-select is off across .xterm besides — the browser has no idea the glyphs it drew are characters. Only flue knows that, so only flue can offer it.

The gesture

Long press selects the word under the finger. Drag widens the range, either direction, across as many rows as the finger travels. A menu offers Copy, Paste and Cancel at whichever end of the terminal the hand is not covering.

Words break on whitespace alone. That is not what a text editor would do and is what a terminal wants: paths, URLs, hashes, flags and session ids are made of the punctuation an editor breaks on, and a rule that stopped at the hyphen would turn a UUID into six presses.

The range is xterm's own. select(col, row, length) takes a length that wraps past the end of a row, which is the only way the public API can express a multi-row range — and it means the renderer draws the selection without flue owning a pixel of it.

What flue owns is telling the press apart from the scroll that shares the surface with it. A finger that travels more than PRESS_SLOP before LONG_PRESS_MS elapses is a scroll; once a selection begins the scroll path is abandoned for the rest of the gesture, because a drag that both graded a range and moved what was under it would be unusable.

Two things that fall out of it

The compatibility mousedown is swallowed. A touch nothing cancelled is replayed as a mouse, and xterm answers a mousedown by starting a selection of its own — clearing this one a frame after making it. A press that never moved has no touchmove to cancel, so the mouse event is the only place left to stop it. Capture phase on the inset, which is an ancestor of the element xterm listens on.

The transparent textarea is gone. It was there to give a long press some editable text to land on. It was also what drew the blue focus ring around the whole terminal, and what risked iOS zooming the page on every tap. flue owns the press now, so -webkit-touch-callout: none tells the platform to keep its own callout out of the way.

Tests

  • make test — 1353 web, 163 relay, every Go package green
  • make lint

New coverage: word bounds including the CJK-width case, extending forward across a row boundary and backwards from the anchored word, the fallback when a drag returns inside the anchor, and viewport-relative rows against a scrolled buffer; cellAt cancelling the scale a mirroring view is drawn at; and in the view — the press, the scroll that beats it to it, the drag that widens instead of scrolling, the lift that starts no glide, the swallowed mouse replay, Copy, Paste, the refusal message, and the menu placement.

Needs a device check

I can't drive a real phone from here, so the gesture itself is unverified on hardware. Worth checking: a long press selects, a drag widens it, Copy actually reaches the system clipboard, Paste lands in the prompt, and iOS shows no callout of its own over the menu.

🤖 Generated with Claude Code

karngyan and others added 2 commits August 12, 2026 21:32
A phone could read the terminal and never lift anything out of it. The
line telling you to run `claude --resume <id>` was on screen and there
was no way to get the id into the prompt below it.

Nothing was broken. The screen is painted to a canvas, so there is no
text on the page for the operating system to offer Copy over, and
`user-select` is off across xterm besides — the browser has no idea the
glyphs it drew are characters. Only flue knows that, so only flue can
offer it.

A long press selects the word under the finger and a drag widens the
range, either direction, across as many rows as the finger travels; a
menu offers Copy, Paste and Cancel at whichever end of the terminal the
hand is not covering. Words break on whitespace alone, which is not what
a text editor would do and is what a terminal wants: paths, URLs, hashes
and session ids are made of the punctuation an editor breaks on, and a
rule that stopped at the hyphen would turn a UUID into six presses.

The range is xterm's own — `select` takes a length that wraps past the
end of a row, which is the only way the public API can express a
multi-row range, and it means the renderer draws the selection without
flue owning a pixel of it. What flue owns is the gesture: the press has
to be told apart from the scroll that shares the surface with it, so a
finger that travels more than the slop before the hold elapses is a
scroll, and once a selection begins the scroll path is abandoned for the
rest of the gesture.

Two smaller things fall out of it. The compatibility mousedown behind a
press that never moved is swallowed, because xterm answers a mousedown by
starting a selection of its own and would clear this one a frame after
making it. And the transparent textarea from the release before this one
is gone — it was there to give a long press some editable text to land
on, and it was also what drew a focus ring around the whole terminal and
risked iOS zooming the page on every tap. flue owns the press now, so the
platform is told to keep its callout to itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The press only opened the menu when it found a word, so the one place a
paste is actually wanted — the empty prompt somebody is trying to paste
into — was the one place nothing happened.

Copy is the only verb in that menu that needs a selection. Paste never
did, and hiding both behind one is what left the terminal with no way to
paste into it at all.

So the press always opens the menu now, and Copy is left out rather than
greyed when there is nothing under the finger to copy: a press on blank
space is a press asking to paste, and the shortest menu that answers it
is the right one. Whichever verb the press was asking for is the one that
reads as loud.

A press on blank space also leaves the scroll alone. There is no range
for a drag to widen, so the scrollback is the only thing left for the
finger to do.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant