feat(maestro): mobile special-key bar for web attach (NAP-342)

On touch devices the soft keyboard omits the keys terminals depend on
(Esc, Tab, Ctrl, arrows, Home/End, PgUp/PgDn). Add a Termux-style
scrollable key bar to the attach view that injects the right xterm
sequences into the existing PTY input path.

- Ctrl is a one-shot sticky modifier: arm it and the next character —
  tapped here or typed on the soft keyboard — is sent as its control
  code, so Ctrl+C and friends work on mobile.
- Taps preventDefault on pointerdown so the terminal keeps focus and the
  keyboard stays open.
- Bar is revealed only on coarse-pointer devices; a physical keyboard
  already has these keys.
- Shell height now tracks the visual viewport (installViewportSizing) so
  the bar lands above the soft keyboard instead of behind it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-20 13:37:51 +00:00
co-authored by Claude Opus 4.8
parent a05966ed2f
commit 4b775ec7cb
4 changed files with 232 additions and 4 deletions
+32
View File
@@ -0,0 +1,32 @@
/**
* Bind the app shell's height to the *visual* viewport.
*
* On mobile the layout viewport does not shrink when the on-screen keyboard
* opens — only the visual viewport does. A shell sized at `height: 100%` (or
* `100dvh`) therefore extends behind the keyboard, hiding anything pinned to the
* bottom: the attach view's special-key bar (NAP-342). Mirroring
* `visualViewport.height` onto a CSS custom property keeps the shell exactly as
* tall as the visible area, so the key bar lands just above the keyboard.
*
* No-ops where `visualViewport` is unavailable; the CSS falls back to `100%`.
*/
export function installViewportSizing(): void {
const vv = window.visualViewport;
if (!vv) return;
const root = document.documentElement;
let raf = 0;
const sync = () => {
raf = 0;
root.style.setProperty("--app-vh", `${Math.round(vv.height)}px`);
};
const schedule = () => {
if (raf) return;
raf = requestAnimationFrame(sync);
};
vv.addEventListener("resize", schedule);
vv.addEventListener("scroll", schedule);
sync();
}