# 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 `