Appearance
Terminal Touch Selection (Mobile)
The app's terminal renders through xterm.js onto a <canvas>. On a touch device the browser has no native way to select canvas text - a finger drag is consumed by viewport scrolling, and there are no native selection handles - so Hive provides its own selection layer that mimics the native Android experience.
This applies only to coarse-pointer (touch) devices. On desktop, right-click still opens the regular terminal context menu (Copy / Paste / Select All / Find / Clear) and selection works with the mouse as usual.
How to use it
- Long-press anywhere in the terminal. The word under your finger is selected (or the whole line when you press on trailing whitespace), two draggable handles appear at the selection ends, and a floating toolbar opens.
- Drag either handle to grow or shrink the selection. Dragging across rows extends the selection; dragging into the top or bottom edge auto-scrolls the viewport so you can reach off-screen content.
- Tap a toolbar action:
- Copy - copies the selection to the system clipboard and exits.
- Paste - sends the clipboard contents to the session (bracketed-paste wrapped for multi-line text) and exits. This is the touch equivalent of Ctrl+V, which is otherwise hard to reach on a phone.
- Select All - selects the entire scrollback buffer.
- Cancel - clears the selection and exits.
- Tapping anywhere outside the handles or toolbar also exits selection mode.
The on-screen keyboard stays out of the way for the whole gesture: entering selection mode closes it, and neither the toolbar actions nor the tap that leaves selection mode bring it back. Tap the terminal again when you want to type - that tap is what raises the keyboard.
Text selection outside the terminal
On mobile operating systems, selecting regular page or editor text opens Hive's global Copy / Cut / Select All toolbar. This applies in both the native mobile app and the web client running in a mobile browser or installed as a PWA. While that toolbar is visible, Hive suppresses the browser or WebView context menu so only one set of copy actions appears.
Why a custom layer
A few mobile-specific problems this design solves:
- No vanishing menu. A long-press on Android emits a trailing pointer/click event right after
contextmenufires. A naive "close on tap outside" menu catches that trailing event and dismisses itself instantly. The selection toolbar ignores dismiss taps for a short window after it opens, so it stays up. - Keyboard stays down while selecting. A browser replays a finished touch gesture as synthesized mouse events, and xterm focuses its hidden input on any mousedown - so a long-press used to pop the keyboard over the text being selected. Those replayed events are swallowed while a selection is open and briefly after it closes, and entering selection mode blurs the hidden input. Only a deliberate tap on the terminal raises the keyboard.
- Real range selection. Instead of only auto-selecting a word, the handles let you select an arbitrary multi-row range.
Implementation notes
The selection layer lives in crates/clusto-app/src/components/session/TerminalSelectionLayer.vue and is driven entirely by xterm.js public API - it does not touch xterm internals:
- The range is set with
term.select(col, row, length). xterm's selection model wrapslengthacross rows (computing the end column modulocols), so a single flat cell count expresses an arbitrary multi-line span. - The canvas renderer draws the highlight;
term.getSelection()yields the copy text;term.scrollLines()drives edge auto-scroll. - Selection boundaries are owned by the component in 0-based absolute buffer coordinates, and the handles are positioned with
position: fixedrecomputed each animation frame, so they follow scrolling and the soft-keyboard slide.
The component is mounted by PtyTerminal.vue, which enters touch-selection mode from the terminal's contextmenu (long-press) handler when isCoarsePointerDevice() is true.