From ff9991acc2df8d234e3267c38e3189f510ca80b6 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 19 Jun 2026 10:13:23 +0700 Subject: [PATCH 1/8] Improve mobile terminal typing and selection --- .../harness/native-terminal-maestro-common.sh | 242 ++++ .../run-native-terminal-history-arrows.sh | 14 + .../run-native-terminal-scroll-no-focus.sh | 21 + .../run-native-terminal-selection-drag.sh | 26 + .../run-native-terminal-sidebar-swipe.sh | 17 + .../harness/run-native-terminal-tap-focus.sh | 14 + ...run-native-terminal-virtual-keyboard-ux.sh | 29 + .../native-terminal-history-arrows.yaml | 29 + ...native-terminal-scroll-does-not-focus.yaml | 38 + ...erminal-selection-drag-does-not-focus.yaml | 45 + ...terminal-sidebar-swipe-does-not-focus.yaml | 40 + .../native-terminal-tap-focus-keyboard.yaml | 31 + .../native-terminal-virtual-keyboard-ux.yaml | 109 ++ packages/app/src/app/_layout.tsx | 2 + .../terminal-copy-paste-actions.tsx | 141 +++ .../components/terminal-emulator.native.tsx | 1069 ++++++++++++++++- .../app/src/components/terminal-emulator.tsx | 34 +- packages/app/src/components/terminal-pane.tsx | 358 ++++-- packages/app/src/polyfills/navigator.ts | 28 + .../src/terminal/native-renderer/colors.ts | 238 ++++ .../terminal/native-renderer/font.native.ts | 57 + .../headless-terminal-output-drain.test.ts | 192 +++ .../headless-terminal-output-drain.ts | 215 ++++ .../headless-terminal-state.test.ts | 132 ++ .../headless-terminal-state.ts | 369 ++++++ .../native-terminal-benchmark-payload.ts | 25 + .../terminal-grid-metrics.test.ts | 35 + .../native-renderer/terminal-grid-metrics.ts | 45 + .../terminal-grid-projection.test.ts | 68 ++ .../terminal-grid-projection.ts | 78 ++ .../terminal-grid-view.native.tsx | 391 ++++++ .../terminal-input.native.test.ts | 202 ++++ .../native-renderer/terminal-input.native.tsx | 327 +++++ .../native-renderer/terminal-key-events.ts | 40 + .../native-renderer/terminal-performance.ts | 3 + .../terminal-renderer.bench.test.ts | 143 +++ .../terminal-resize-policy.test.ts | 294 +++++ .../native-renderer/terminal-resize-policy.ts | 110 ++ .../terminal-row-model.test.ts | 27 + .../native-renderer/terminal-row-model.ts | 133 ++ .../terminal-screen-model.test.ts | 202 ++++ .../native-renderer/terminal-screen-model.ts | 242 ++++ .../terminal-scrollback.test.ts | 170 +++ .../terminal-selection-gesture.test.ts | 274 +++++ .../terminal-selection-gesture.ts | 85 ++ .../terminal-selection.test.ts | 257 ++++ .../native-renderer/terminal-selection.ts | 303 +++++ .../terminal-size-measurement.test.ts | 14 + .../terminal-size-measurement.ts | 29 + .../runtime/terminal-emulator-runtime.test.ts | 42 +- .../runtime/terminal-emulator-runtime.ts | 71 +- .../app/src/terminal/runtime/terminal-font.ts | 32 + .../runtime/terminal-key-dispatch.test.ts | 81 ++ .../terminal/runtime/terminal-key-dispatch.ts | 55 + .../terminal/runtime/terminal-paste.test.ts | 48 + .../src/terminal/runtime/terminal-paste.ts | 38 + .../runtime/terminal-virtual-keyboard.test.ts | 108 ++ .../runtime/terminal-virtual-keyboard.ts | 68 ++ .../protocol/src/terminal-input-mode.test.ts | 20 + packages/protocol/src/terminal-input-mode.ts | 30 +- .../protocol/src/terminal-key-input.test.ts | 52 + packages/protocol/src/terminal-key-input.ts | 31 +- .../terminal-byte-headless-parity.e2e.test.ts | 341 ++++++ 63 files changed, 7818 insertions(+), 186 deletions(-) create mode 100755 packages/app/maestro/harness/native-terminal-maestro-common.sh create mode 100755 packages/app/maestro/harness/run-native-terminal-history-arrows.sh create mode 100755 packages/app/maestro/harness/run-native-terminal-scroll-no-focus.sh create mode 100755 packages/app/maestro/harness/run-native-terminal-selection-drag.sh create mode 100755 packages/app/maestro/harness/run-native-terminal-sidebar-swipe.sh create mode 100755 packages/app/maestro/harness/run-native-terminal-tap-focus.sh create mode 100755 packages/app/maestro/harness/run-native-terminal-virtual-keyboard-ux.sh create mode 100644 packages/app/maestro/native-terminal-history-arrows.yaml create mode 100644 packages/app/maestro/native-terminal-scroll-does-not-focus.yaml create mode 100644 packages/app/maestro/native-terminal-selection-drag-does-not-focus.yaml create mode 100644 packages/app/maestro/native-terminal-sidebar-swipe-does-not-focus.yaml create mode 100644 packages/app/maestro/native-terminal-tap-focus-keyboard.yaml create mode 100644 packages/app/maestro/native-terminal-virtual-keyboard-ux.yaml create mode 100644 packages/app/src/components/terminal-copy-paste-actions.tsx create mode 100644 packages/app/src/polyfills/navigator.ts create mode 100644 packages/app/src/terminal/native-renderer/colors.ts create mode 100644 packages/app/src/terminal/native-renderer/font.native.ts create mode 100644 packages/app/src/terminal/native-renderer/headless-terminal-output-drain.test.ts create mode 100644 packages/app/src/terminal/native-renderer/headless-terminal-output-drain.ts create mode 100644 packages/app/src/terminal/native-renderer/headless-terminal-state.test.ts create mode 100644 packages/app/src/terminal/native-renderer/headless-terminal-state.ts create mode 100644 packages/app/src/terminal/native-renderer/native-terminal-benchmark-payload.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-grid-metrics.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-grid-metrics.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-grid-projection.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-grid-projection.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-grid-view.native.tsx create mode 100644 packages/app/src/terminal/native-renderer/terminal-input.native.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-input.native.tsx create mode 100644 packages/app/src/terminal/native-renderer/terminal-key-events.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-performance.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-renderer.bench.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-resize-policy.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-resize-policy.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-row-model.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-row-model.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-screen-model.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-screen-model.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-scrollback.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-selection-gesture.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-selection-gesture.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-selection.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-selection.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-size-measurement.test.ts create mode 100644 packages/app/src/terminal/native-renderer/terminal-size-measurement.ts create mode 100644 packages/app/src/terminal/runtime/terminal-font.ts create mode 100644 packages/app/src/terminal/runtime/terminal-key-dispatch.test.ts create mode 100644 packages/app/src/terminal/runtime/terminal-key-dispatch.ts create mode 100644 packages/app/src/terminal/runtime/terminal-paste.test.ts create mode 100644 packages/app/src/terminal/runtime/terminal-paste.ts create mode 100644 packages/app/src/terminal/runtime/terminal-virtual-keyboard.test.ts create mode 100644 packages/app/src/terminal/runtime/terminal-virtual-keyboard.ts create mode 100644 packages/server/src/server/daemon-e2e/terminal-byte-headless-parity.e2e.test.ts diff --git a/packages/app/maestro/harness/native-terminal-maestro-common.sh b/packages/app/maestro/harness/native-terminal-maestro-common.sh new file mode 100755 index 0000000000..f45e5dd23e --- /dev/null +++ b/packages/app/maestro/harness/native-terminal-maestro-common.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +# Common helpers for native-terminal Maestro flows. Each per-flow harness +# sources this file and calls run_flow_with_setup. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)" +MAESTRO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +APP_ID="${PASEO_MAESTRO_APP_ID:-sh.paseo.debug}" +SIMULATOR_UDID="${PASEO_MAESTRO_UDID:-47FB40E1-3304-4516-B8BC-D75853EF1B47}" + +log() { + echo "[native-terminal-harness] $*" >&2 +} + +terminal_ids() { + cd "$REPO_ROOT" + npm run --silent cli -- terminal ls --all --json 2>/dev/null \ + | node -e 'const data=[]; process.stdin.on("data", c=>data.push(c)); process.stdin.on("end", ()=>{ const list=JSON.parse(data.join("")); for (const terminal of list) console.log(terminal.id); });' +} + +send_to_terminal() { + local terminal_id="$1" + shift + cd "$REPO_ROOT" + npm run --silent cli -- terminal send-keys "$terminal_id" "$@" +} + +set_simulator_clipboard() { + local text="$1" + if xcrun simctl pbcopy "$SIMULATOR_UDID" <<< "$text" 2>/dev/null; then + log "Set simulator clipboard" + else + log "WARNING: could not set simulator clipboard; flow may fail" + fi +} + +read_simulator_clipboard() { + xcrun simctl pbpaste "$SIMULATOR_UDID" 2>/dev/null || true +} + +capture_terminal() { + local terminal_id="$1" + cd "$REPO_ROOT" + npm run --silent cli -- terminal capture --scrollback "$terminal_id" +} + +write_temp_flow() { + local name="$1" + local flow_dir="${TMPDIR:-/tmp}/paseo-native-terminal-maestro-flows" + mkdir -p "$flow_dir" + local flow_file + flow_file="$(mktemp "$flow_dir/$name.XXXXXX")" + cat >"$flow_file" + printf '%s\n' "$flow_file" +} + +maestro_bin() { + local bin + bin="$(command -v maestro || true)" + if [[ -z "$bin" ]]; then + log "ERROR: maestro not found on PATH" + return 1 + fi + printf '%s\n' "$bin" +} + +run_maestro_flow() { + local flow_file="$1" + local bin + bin="$(maestro_bin)" + local output_dir + output_dir="$(mktemp -d "${TMPDIR:-/tmp}/paseo-native-terminal-maestro-output.XXXXXX")" + log "Running Maestro flow $(basename "$flow_file") with screenshots in $output_dir" + (cd "$output_dir" && "$bin" test --udid "$SIMULATOR_UDID" "$flow_file") >&2 +} + +run_maestro_flow_allow_failure() { + local flow_file="$1" + local bin + bin="$(maestro_bin)" + local output_dir + output_dir="$(mktemp -d "${TMPDIR:-/tmp}/paseo-native-terminal-maestro-output.XXXXXX")" + log "Running Maestro probe $(basename "$flow_file") with screenshots in $output_dir" + set +e + (cd "$output_dir" && "$bin" test --udid "$SIMULATOR_UDID" "$flow_file") >&2 + local status=$? + set -e + return "$status" +} + +assert_terminal_surface_visible() { + local flow_file + flow_file="$(write_temp_flow terminal-visible </dev/null + send_to_terminal "$terminal_id" "echo $marker" Enter >/dev/null + if assert_text_visible "$marker"; then + printf '%s\n' "$terminal_id" + return + fi + done <<< "$ids" + + log "ERROR: no listed terminal matched the visible terminal surface" + return 1 +} + +assert_terminal_output_contains() { + local terminal_id="$1" + local marker="$2" + local output + output="$(capture_terminal "$terminal_id")" + if [[ "$output" == *"$marker"* ]]; then + log "PASS: terminal output contains $marker" + return + fi + log "FAIL: terminal output did not contain $marker" + return 1 +} + +assert_terminal_output_count_at_least() { + local terminal_id="$1" + local marker="$2" + local minimum="$3" + local output + output="$(capture_terminal "$terminal_id")" + local count + count="$(MARKER="$marker" OUTPUT="$output" node -e 'const marker = process.env.MARKER ?? ""; const output = process.env.OUTPUT ?? ""; process.stdout.write(String(marker ? output.split(marker).length - 1 : 0));')" + if (( count >= minimum )); then + log "PASS: terminal output contains $marker $count times" + return + fi + log "FAIL: terminal output contained $marker $count times, expected at least $minimum" + return 1 +} + +assert_maestro_input_does_not_reach_terminal() { + local terminal_id="$1" + local marker="$2" + local flow_file + flow_file="$(write_temp_flow no-focus-input-probe <