feat(maestro): add paging and symbol keys to the mobile key bar
ci / web (push) Successful in 1m22s
ci / rust (push) Successful in 7m24s
ci / flake (push) Successful in 8m50s

PgUp/PgDn and the `- / | ~` symbols the soft keyboard buries behind a page
switch join the bar, in an 8-column grid that keeps the cursor inverted-T.
Columns hold a thumb's width, so a narrow phone scrolls the bar sideways
instead of shrinking the caps.

Closes #7
This commit is contained in:
Miguel Palhas
2026-09-02 16:01:08 +01:00
parent 8cb6aeb61e
commit 3ce1a6f331
4 changed files with 76 additions and 17 deletions
+32 -7
View File
@@ -5,7 +5,7 @@ import "@xterm/xterm/css/xterm.css";
import { attachWsUrl } from "./routes.js";
import { escapeHtml } from "./html.js";
import { INITIAL_RECONNECT_MS, MAX_RECONNECT_MS } from "./reconnect.js";
import { applyModsToChar, cursorSeq } from "./keys.js";
import { applyModsToChar, cursorSeq, tildeSeq } from "./keys.js";
import { requestToggleDrawer } from "./drawer.js";
import { splitCommand, toolIconSvg } from "./toolIcon.js";
import type { ClientControl, Uuid } from "./types.js";
@@ -433,11 +433,17 @@ export function createAttachConnection(
* - `mod` is a sticky one-shot modifier toggle (Ctrl / Alt).
* - `cursor` is a cursor/navigation key, identified by its CSI final byte, so a
* pending modifier can promote it to the `\x1b[1;<n><final>` form.
* - `tilde` is a `~`-terminated navigation key (PgUp/PgDn), identified by its
* CSI parameter, whose modified form is `\x1b[<code>;<n>~`.
* - `char` is a literal character (the symbols the soft keyboard buries behind
* a page switch), folded through a pending modifier like typed input.
* - `seq` is a plain key (Esc, Tab) sent verbatim.
*/
type KeyDef =
| { label: string; mod: Modifier }
| { label: string; cursor: CursorFinal }
| { label: string; tilde: number }
| { label: string; char: string }
| { label: string; seq: string };
type Modifier = "ctrl" | "alt";
@@ -445,14 +451,17 @@ type Modifier = "ctrl" | "alt";
type CursorFinal = "A" | "B" | "C" | "D" | "H" | "F";
/**
* The keys the mobile soft keyboard hides, ordered to flow row-major into a
* 5-column / 2-row grid (NAP-342 review):
* The keys the mobile soft keyboard hides, ordered to flow row-major into an
* 8-column / 2-row grid (#7):
*
* Esc Tab Home ↑ End
* Ctrl Alt ← ↓ →
* Esc Tab Home ↑ End PgUp - /
* Ctrl Alt ← ↓ → PgDn | ~
*
* — Esc/Tab + the Ctrl/Alt sticky modifiers on the left, and the cursor keys as
* a real-keyboard inverted-T on the right, with Home/End filling its top corners.
* — Esc/Tab + the Ctrl/Alt sticky modifiers on the left, the cursor keys as a
* real-keyboard inverted-T in the middle with Home/End filling its top corners,
* then the paging pair and the shell symbols that cost a soft-keyboard page
* switch to reach. Narrower screens scroll the bar sideways rather than
* shrinking the caps below a thumb's width.
*/
const KEY_DEFS: KeyDef[] = [
{ label: "Esc", seq: "\x1b" },
@@ -460,11 +469,17 @@ const KEY_DEFS: KeyDef[] = [
{ label: "Home", cursor: "H" },
{ label: "↑", cursor: "A" },
{ label: "End", cursor: "F" },
{ label: "PgUp", tilde: 5 },
{ label: "-", char: "-" },
{ label: "/", char: "/" },
{ label: "Ctrl", mod: "ctrl" },
{ label: "Alt", mod: "alt" },
{ label: "←", cursor: "D" },
{ label: "↓", cursor: "B" },
{ label: "→", cursor: "C" },
{ label: "PgDn", tilde: 6 },
{ label: "|", char: "|" },
{ label: "~", char: "~" },
];
/**
@@ -521,6 +536,16 @@ function buildKeyBar(send: (data: string) => void): KeyBar {
send(cursorSeq(def.cursor, armed.ctrl, armed.alt));
clearMods();
});
} else if ("tilde" in def) {
btn.addEventListener("click", () => {
send(tildeSeq(def.tilde, armed.ctrl, armed.alt));
clearMods();
});
} else if ("char" in def) {
btn.addEventListener("click", () => {
send(applyModsToChar(def.char, armed.ctrl, armed.alt));
clearMods();
});
} else {
btn.addEventListener("click", () => {
send(def.seq);