From 6cfe72717316072c15dd2977f5fad7e7a03470c0 Mon Sep 17 00:00:00 2001 From: Lexus2016 Date: Wed, 23 Sep 2026 14:04:37 +0200 Subject: [PATCH] feat(terminal): on-screen Esc/Tab/Ctrl/arrow keys for phone keyboards (#117) Gboard has no Esc, Tab, Ctrl or arrow keys, so a terminal tab on a phone could not autocomplete, walk history or send Ctrl+C. A row of seven keys now sits under the pane on touch devices (pointer: coarse); desktops do not see it. - Each key sends the same {type:'input'} frame a keystroke produces, through _reviveTerminal, so a tap on a half-dead pane reconnects. - Arrows honour DECCKM (SS3 under application cursor mode); Ctrl+arrow sends CSI 1;5 X. - Ctrl is one-shot: it applies to the NEXT key, typed on the phone keyboard (via term.onData) or tapped in the row, then releases. - A tap never takes focus: the action runs on pointerdown with its default prevented and touchstart is cancelled, or Gboard would close per key. test/terminal-keys.test.js runs the real block in a vm. 'Tab' joins the keycap allowlist of the i18n markup scan next to Ctrl and Esc. Fixes #117 Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 4 +- package.json | 2 +- public/index.html | 95 ++++++++++++++++++++++-- test/i18n-completeness.test.js | 2 +- test/terminal-keys.test.js | 130 +++++++++++++++++++++++++++++++++ 5 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 test/terminal-keys.test.js diff --git a/CLAUDE.md b/CLAUDE.md index 3aec665..dcc6ee1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ docker compose up -d docker compose logs -f claude-chat ``` -No linting and no build step configured. `npm test` chains 83 test files under `test/`: 19 DOM-less render/UI-logic tests (`test/render/*.test.mjs`, run through `node --test`) plus 64 plain-`node` suites in `test/` covering the overload detector, env load order, multi-agent results, terminals, bots, telegram, updates, kanban scheduling, the Kanban card's run-settings badges (`kanban-run-badges.test.js` pins the card's model/effort/engine chain against the one `startTask` actually resolves — the two live in different files and the card silently lies when they drift), the board-only `create_task` status (`task-backlog.test.js`), the Kanban group form (`kanban-group-button.test.js` RUNS the real `buildChainForm` in a `vm` context so a stray free variable in its template throws there instead of silently killing the modal — see #115 — and parses every inline `