mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
feat(mobile): shell keyboard bar with a one-shot Ctrl modifier (#262)
The mobile accessory bar was built around coding-agent commands, so a shell session had no way to send Ctrl chords at all. A shell-mode session now gets its own bar automatically: Ctrl, Esc, Tab, four arrows, paste, dismiss. Agent sessions (claude, codex, opencode, gemini, antigravity) keep the existing bar unchanged. Ctrl is a one-shot modifier: tap it and it lights up, the next character typed on the system keyboard is sent as its control byte, and Ctrl disarms. Tapping it again cancels. That puts Ctrl+C/D/Z/R/L/A/E/W/U/K on a nine-button bar without a button per chord. Implementation notes: * The interception lives in terminal.onData, not a keydown handler: a virtual keyboard reports no usable key events, so the character only exists as onData text. It sits after shouldSuppressTerminalQueryResponse (xterm answers DA/CPR queries through onData too, and letting one of those spend the modifier would silently eat the user's Ctrl) and before every send path, so the control byte follows the normal control-char route. * ctrlByteFor() maps `code & 0x1f` over @A-Z[\]^_ and a-z, plus Ctrl+Space = NUL and Ctrl+? = DEL. Characters with no control equivalent pass through unchanged, like a hardware keyboard. * The bar now separates the base layout (the extendedKeyboardBar setting) from the effective one, resolved per session by refreshForActiveSession(). A settings save during a shell session cannot yank the bar away, and switching back to an agent tab restores the user's choice. * Ctrl disarms on use, a second tap, any other accessory key, a session switch, keyboard dismissal and a layout swap. * Ctrl joins the refocus set, so tapping it keeps the terminal focused and the keyboard open. * The armed style needs three classes to outrank mobile.css's light-skin .accessory-btn rule at (0,2,1). Verified end to end against a real shell session on an isolated instance: tapping Ctrl then typing c interrupted a running `sleep 300` (^C in the pane), the modifier disarmed, plain typing stayed literal, Ctrl+L cleared, and a cancelled Ctrl typed a literal c. Tests: test/mobile-shell-keyboard.test.ts (new, runs in CI) covers the mapping table, layout selection per session mode, base-mode memory and every disarm path; test/mobile/keyboard.test.ts adds nine browser regressions that drive the real xterm with page.keyboard.type() and assert on the bytes that would go out. Closes #262 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -270,6 +270,8 @@ Frontend JS modules have `@fileoverview` with `@dependency`/`@loadorder` tags. L
|
||||
|
||||
**WebGL renderer toggle** (`webglRendererEnabled`, per-device): the GPU-stall watchdog's sticky `codeman-webgl-disabled` marker survives page loads and is cleared only by an explicit OFF→ON save or `?webgl=force`. `?nowebgl` forces the DOM renderer per-load. → [architecture-invariants#webgl-renderer-toggle](docs/architecture-invariants.md#webgl-renderer-toggle)
|
||||
|
||||
**Shell keyboard accessory bar + one-shot Ctrl** (issue #262, `keyboard-accessory.js`): a **shell**-mode session automatically swaps the mobile accessory bar for terminal controls (Ctrl, Esc, Tab, four arrows, paste, dismiss); every other mode keeps the agent bar. `setMode()` now records the user's `extendedKeyboardBar` preference as the **base** layout and `refreshForActiveSession()` (called from `selectSession`) resolves base-vs-shell, so a settings save during a shell session cannot yank the bar away and switching back restores the user's choice. ⚠️ **Ctrl is a ONE-SHOT modifier applied in `terminal.onData`, not in a keydown handler**: a virtual keyboard emits no usable key events, so the character only exists as onData text. The hook sits AFTER `shouldSuppressTerminalQueryResponse` (xterm answers DA/CPR through onData too, and one of those would silently spend the modifier) and BEFORE every send path, so the control byte follows the normal control-char route. ⚠️ It must disarm on ALL of: use, second tap, any other accessory key, session switch, keyboard dismissal, and a layout swap; a modifier left armed turns the next innocent keystroke into a control byte. Mapping is `ctrlByteFor()` (`code & 0x1f` over @A-Z[\]^_ and a-z, plus Ctrl+Space=NUL / Ctrl+?=DEL); characters with no control equivalent pass through unchanged, like a hardware keyboard. The armed style needs **three classes** (`.accessory-btn.accessory-btn-ctrl.armed`) to outrank mobile.css's light-skin `.accessory-btn` rule at (0,2,1).
|
||||
|
||||
**Phone toolbar: Enter replaces Shell** (post-1.8.0): inside `@media (max-width: 430px)` `btn-shell` is `display:none` and `btn-enter` takes its slot (`order: 4`); starting a shell moved into the Run dropdown (`Terminal / Shell` → `setRunMode('shell')` → `run()` → `runShell()`, button label "Run SH"). `runMode` is `z.string().max(20)` server-side, so new modes need no schema change. Desktop and tablet keep the green Run Shell button unchanged.
|
||||
|
||||
⚠️ **`sendEnterKey()` MUST go through `terminal._core.coreService.triggerDataEvent('\r', true)`** — not `sendInput()`, and never a raw POST to `/api/sessions/:id/input`. `localEchoEnabled` defaults to `MobileDetection.isTouchDevice()`, so on every phone the characters you type are buffered in the `LocalEchoOverlay` and have **never reached the PTY**; the `onData` Enter branch in terminal-ui.js is what flushes `pendingText` first and only then sends `\r` (after an 80ms delay so text lands first). Sending a bare `\r` submits an empty line and strands the typed text on screen, so the button looks dead. Replaying the keypress reuses the overlay flush, the flushed-offset cleanup and the ordering instead of reimplementing them. `KeyboardAccessory.sendKey()` is for escape sequences (arrows/Esc) and is the WRONG template to copy for input.
|
||||
|
||||
Reference in New Issue
Block a user