mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
There was no way to copy terminal text from a phone at all, and three layers ruled it out independently: `user-select: none` across the whole terminal subtree on touch devices (taps are cursor gestures there, so the OS callout had to go), the WebGL renderer drawing glyphs as pixels with only the accessibility tree behind them, and xterm's own selection being a mouse DRAG while the touch path dispatches a zero-movement mousedown/mouseup pair — a click. `copyTerminal()` exists but is wired to no button and calls `navigator.clipboard` directly, which is undefined on the plain-HTTP LAN install the installer offers. So the gesture drives xterm's `select()` directly: public API, renderer- independent, and the highlight is drawn by xterm itself. Long-press is free real estate — tap and swipe are taken, long-press and double-tap are used by nothing. - **Long-press** (350ms, finger still within the shared tap slop) selects the run of non-whitespace under the finger. Whitespace is the only delimiter on purpose: every punctuation-aware word rule cuts a path, URL or hash in half, which is what you came to copy. - **Drag** while held extends the selection; touchmove diverts from scrolling. - **Tap** while the bar is up extends it too. That is the ergonomic core: picking up a 4px handle with a fingertip is a coin flip, tapping the other end is not. Dismissal stays explicit (✕ or Copy), so no tap is spent leaving a mode the user is still using. - **Copy** goes through the existing `copyTerminalSelection()`, so it inherits the execCommand fallback that is the only route that works on plain HTTP. - **Line** takes the whole logical line, wraps included, trailing pad trimmed. Three guards are what make the gesture survive contact with a real phone, and each fixes a symptom measured on Android Chrome: 1. **The compat mouse pair after touchend.** xterm focuses from its screen-element mousedown and SelectionService resets the model there, so lifting your finger popped the keyboard and dissolved the selection in one go. The tap path already had a guard for those events; the selection path simply never armed it. Armed now, and the touchend is `preventDefault`ed so the synthesis is stopped at the source (that listener is no longer passive). 2. **The platform's own long-press.** Android Chrome runs its handling at ~500ms and focuses the nearest editable element — xterm's helper textarea, parked at the cursor — which no touch handler can preventDefault because it never sees an event. A focus guard blurs the terminal input for the duration of the gesture, whatever focused it, bounded by a self-expiring deadline so a stuck flag can never leave the keyboard unreachable. `contextmenu` is suppressed for the same window, and the threshold sits at 350ms so it lands clear of the platform's. 3. **Copy re-focusing the terminal.** `copyTerminalSelection()` ends with `terminal.focus()`, which is right on a desktop and wrong on a phone: the keyboard covers what was just copied with nothing waiting to be typed. The bar is built in JS because index.html is read once at server start, and its styles live in styles.css rather than mobile.css because the gesture is touch-driven, not width-driven — a touch tablet in landscape gets the gesture and would otherwise have no bar to copy from. 12 tests in `terminal-touch-tap.test.ts` cover the word rule, forward and backward extension, cross-row selection, Line, tap-to-extend, the copy path, and each of the three guards including the focus guard's expiry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>