Skip to content

feat(terminal): mobile virtual key bar - #647

Open
mrytsr wants to merge 1 commit into
xintaofei:mainfrom
mrytsr:feat/terminal-mobile-keybar
Open

feat(terminal): mobile virtual key bar#647
mrytsr wants to merge 1 commit into
xintaofei:mainfrom
mrytsr:feat/terminal-mobile-keybar

Conversation

@mrytsr

@mrytsr mrytsr commented Sep 3, 2026

Copy link
Copy Markdown

What

Add an on-screen virtual key bar for the terminal panel so mobile users can send escape / tab / control / arrow / page keys without a hardware keyboard.

Why

Without a physical keyboard, mobile terminal usage is essentially broken: no ESC to leave insert mode in vim, no Ctrl+C for SIGINT, no arrow keys for readline history. The mobile Drawer container plus a two-row button grid closes that gap.

Design

  • Pure encoding (lib/terminal/keybar.ts): 12 keys + termKeySeq + applyTermMods + kbdLiftPx. 18 unit tests.
  • <TermKeybar />: presentational, shadcn Button + Tailwind, no raw CSS. 5 component tests.
  • Write-path integration: the key bar pushes bytes into the existing ordered single-flight write pump (writeQueueRef), not terminalWrite directly — otherwise concurrent soft-keyboard and key-bar presses would scramble under Tauri invoke reordering.
  • Latch semantics: CTRL/ALT are one-shot sticky keys. The next onData (or the next key-bar press) wraps itself then auto-clears. Refs hold the truth, state drives the highlight.
  • Soft-keyboard follow: visualViewport listener computes kbdLift = max(0, innerHeight - vvHeight - vvOffsetTop) and applies max-height: calc(100% - kbdLift) to the outer flex column so the bar lifts above the keyboard and xterm auto-refits via the existing ResizeObserver.
  • Collapse state: persisted to localStorage codeg:term-keybar at the panel level (not per tab) so switching tabs stays consistent.
  • Desktop untouched: useMediaQuery("(max-width: 768px)") short-circuits both the toggle button and the bar — desktop users see no change.

Verification

  • pnpm test: 5764 passed (includes 23 new keybar tests)
  • pnpm eslint .: clean
  • pnpm build: 32/32 static pages generated
  • tsc --noEmit: clean

i18n

16 new keys under Folder.terminal.keybar across all 10 locales. The symbols (ESC/TAB/↑↓←→ etc.) are language-neutral; show / hide / label are translated for zh-CN/zh-TW/ja/ko/es/de/fr/pt/ar.

Side fix

Mobile Drawer height bumped from 70vh to 95vh to give the new bar enough room when folded in.

Add an on-screen virtual key bar for the terminal panel so mobile users
can send escape / tab / control / arrow / page keys without a hardware
keyboard. The bar mirrors the pios implementation: two rows of 14 buttons
(ESC / / — HOME ↑ END PGUP | TAB CTRL ALT ← ↓ → PGDN) with a CTRL/ALT
latch that wraps the next soft-keyboard input as a control byte or ESC
prefix respectively.

Pure encoding lives in lib/terminal/keybar.ts — 12 keys + termKeySeq +
applyTermMods + kbdLiftPx — covered by 18 unit tests. The new
<TermKeybar /> component is purely presentational and uses shadcn Button
+ Tailwind utilities, so there is no raw CSS.

Wiring:
  - terminal-panel.tsx adds useMediaQuery('(max-width: 768px)') and a
    localStorage-backed collapse flag (codeg:term-keybar) shared across
    all tabs, then forwards keybarVisible down.
  - terminal-tab-bar.tsx gains a ⌨ toggle button (mobile-only, with
    tooltip + active highlight) that flips the flag.
  - terminal-view.tsx exposes writeQueueRef so the key bar's bytes feed
    the existing ordered single-flight write pump (without this the key
    bar would bypass the queue and scramble with onData on slow transport).
    onData is wrapped with applyTermMods to honor the latch. The
    visualViewport observer drives a kbdLift max-height on the outer flex
    column so the bar lifts above the soft keyboard and xterm auto-refits
    via the existing ResizeObserver.
  - workspace/layout.tsx bumps the mobile Drawer from 70vh to 95vh to
    give the new bar enough room when folded in.

i18n: 16 new keys under Folder.terminal.keybar across all 10 locales
(symbols are language-neutral; show/hide/label are translated for
zh-CN/zh-TW/ja/ko/es/de/fr/pt/ar). English fallbacks keep the type
contract aligned.

Desktop path is untouched: useMediaQuery stays false, the toggle and
the bar never render, and the writeQueueRef sits at null.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant