# Handoff: sethmux mobile toolbar — Google Workspace dark refresh + compose bar ## Overview This handoff covers two related changes to the sethmux web terminal's mobile UI (`static/toolbar.js`): 1. **Theme refresh** — re-skin the existing 2-row mobile button bar to match the Google Workspace dark vocabulary (palette, typography, hairline borders, hover behavior) while keeping sethmux's orange `#D35400` as the accent identity. 2. **Mobile autocorrect workaround** — add an opt-in compose bar (third row, toggled by a "Type" button) that gives Gboard / iOS keyboards a real `` to operate on. Typed text is flushed to the terminal stdin on Enter / Send so swipe, autocorrect, and predictions all work despite xterm.js's per-keystroke input model. ## About the Design Files The files in this bundle are **design references** — `toolbar.js` is the single source file to ship; the `preview*.html` files are visual mockups demonstrating intended look and behavior in a Chrome window and an Android phone frame, and the `*.jsx` files are throwaway frame components used only by the previews. The task is to drop the new `toolbar.js` into the existing sethmux project at `static/toolbar.js`, replacing the current file. No build step required; no other static files change. The previews are not meant to be served in production. ## Fidelity **High-fidelity.** All colors, type sizes, spacings, radii, and behaviors are final. Implement to spec. ## What changed in toolbar.js ### Visual system (Google Workspace dark) | Token | Hex | Usage | |------------------|-----------|-----------------------------------------------| | toolbar bg | `#202124` | bar background | | button surface | `#303134` | button face at rest | | border | `#3c4043` | 1px hairlines, group dividers | | primary text | `#e8eaed` | button labels | | secondary text | `#9aa0a6` | mono-class buttons at rest | | tertiary text | `#5f6368` | placeholder, disabled | | accent (sethmux) | `#D35400` | active/toggled fill, send button, focus ring | | accent at rest | `#f0a36b` | text color of `.hi` (orange-tinted) buttons | | accent bg-1 | `#2a1f15` | `.hi` button background at rest | | accent bg-2 | `#3a2a1a` | hover wash (subtle orange tint) | | accent border | `#5a3a22` | `.hi` border at rest | | success at rest | `#81c995` | Save button text | | success filled | `#1e8e3e` | Save button after confirm | ### Typography - Primary: `Roboto, 'Helvetica Neue', Arial, sans-serif`, 12px / 500, letter-spacing 0.1px - Mono (chord and arrow keys, terminal input): `'Roboto Mono', 'SF Mono', ui-monospace, Menlo, Consolas, monospace`, 14px / 400 in the compose input, 12px / 400 on `.mono` buttons ### Geometry - Bar padding: `6px 8px 7px` - Button: `height: 32px`, `min-width: 40px`, `padding: 0 10px`, `border-radius: 4px`, `border: 1px solid #3c4043` - Narrow-phone breakpoint at `max-width: 380px`: button `height: 30px`, `min-width: 34px`, `padding: 0 7px`, `font-size: 11.5px` - Compose input: `height: 36px`, `padding: 0 10px`, `border-radius: 4px`, orange caret + orange focus border `#D35400` - Send button: `height: 36px`, `min-width: 54px`, filled `#D35400` on text `#0a0a0a` - Newline button (`↵`): same height as Send, `min-width: 38px`, neutral surface (`#303134` / `#3c4043`) - Group dividers (`.sep`): 1px wide × 20px tall, `#3c4043`, 4px horizontal margin (2px on narrow phones) - Bar shadow: `0 -1px 0 rgba(0,0,0,.4), 0 -8px 24px rgba(0,0,0,.35)` - Transitions: `background .15s ease, border-color .15s ease, color .15s ease` (no bounces) ### Button states - Default: `#303134` bg, `#3c4043` border, `#e8eaed` text - Hover: `#3a2a1a` bg, `#fff` text - Active (mouse-down): full orange fill `#D35400` on `#0a0a0a` - `.hi` (accent at rest): orange-tinted bg `#2a1f15`, border `#5a3a22`, text `#f0a36b`. Hover deepens both - `.on` (toggled, e.g. selection mode active, Type active): full `#D35400` fill, hover lightens to `#e26416` - `.mono` (chord keys): mono font, `#9aa0a6` text → `#e8eaed` on hover - `.grn` (Save): `#81c995` text at rest; `.grn.on` (post-confirm) fills `#1e8e3e` and shows "✓ Saved" for 1500ms ### Layout / structure The bar is a single `position: fixed; bottom: 0` element with `flex-direction: column`. **Row 1** (always visible): ``` [+ Tab] [Next] [Prev] | [^C] [^D] [Clr] | [Esc] [Tab] [▲] [▼] ``` - `+ Tab` is `.hi`, the rest neutral, `^C ^D Esc Tab ▲ ▼` are `.mono`. **Row 2** (always visible): ``` [Sel] [Paste] [Zoom] [Save] | [V.Spl] [H.Spl] [Pane] [Kill] | [Type] ``` - `Sel`, `Paste`, `Type` are `.hi`. `Save` is `.grn`. The `Type` button is the new entry point for the compose bar. **Row 3 — compose** (only visible when `#mb` has class `composing`): ``` [ input field ] [↵] [Send] ``` - The input is full-width-flex, monospace, autocorrect on. `↵` and `Send` both flush. ### Interactions / behavior **Send to stdin (`send(k)`):** unchanged. Tries `term._core.coreService.triggerDataEvent(k)` first, falls back to `term._core._onData.fire(k)`, then `term.input(k)`. Always re-focuses the terminal afterward. **Selection mode (`Sel`):** unchanged. Toggles `body.selmode` which adds `pointer-events: none` to `.xterm-screen` and forces text selection. While in selmode the body also gets `filter: brightness(.92)` for a subtle visual cue. Sending any key auto-exits selmode. **Paste:** unchanged. Reads `navigator.clipboard.readText()` and feeds the result through `send()`. Alerts if HTTPS clipboard is unavailable. **Save:** unchanged behavior — sends `\x01S` (`Ctrl-A S`, the tmux capture binding). Adds the `.on` class and label `✓ Saved` for 1500ms, then reverts. **Compose / Type (new):** - Tapping `Type` toggles the `composing` class on `#mb`. - On open, `#mb-compose` gets focused via `setTimeout(...,0)` so iOS/Android keyboards open reliably. - Input attributes: `autocomplete="on" autocorrect="on" autocapitalize="sentences" spellcheck="true" enterkeyhint="send" inputmode="text"`. These are required — the whole point is to let the OS keyboard's correction layer mutate the input value, which xterm.js's hidden textarea cannot do. - Pressing `Enter` (or tapping `Send` or `↵`) calls `flushCompose(true)`: reads the input value, clears it, sends the value as a string to stdin, then sends `\r`. Re-focuses the input so the user can keep typing. - Other toolbar buttons remain active while composing. If a chord/arrow/etc. button is tapped with non-empty input text, the typed text is flushed first (preserving order), then the chord is sent. **The compose bar stays open**; the user is mid-thought. **Layout reflow (`relayout()`):** The terminal pane (`.xterm`) is sized via `height: calc(100vh - {barHeight + 4}px)` whenever the bar's height changes. This runs on: - DOM mutations (existing `MutationObserver`) - Window resize - Toggling the `composing` class (called from `toggleType()`) The previous implementation hard-coded `calc(100vh - 92px)`; the new code measures `bar.offsetHeight` so opening the third row reflows correctly without overlap. **Helper textarea hardening:** unchanged. Still sets `autocomplete=off`, `autocorrect=off`, `autocapitalize=off`, `spellcheck=false` on `.xterm-helper-textarea` once it appears. This is the standard xterm.js mitigation and is still useful — it prevents Gboard from trying to munge the per-keystroke stream when the user happens to type *outside* the compose bar. ### Mobile breakpoint The toolbar shows at `@media (max-width: 900px)`. Unchanged. Above that, sethmux assumes a real keyboard and the bar stays hidden. ## Why the compose bar is necessary xterm.js reads from a hidden `