Files
sethmux/claude-design/design_handoff_sethmux_toolbar/README.md
T
Mortdecai b8a7810bfd chore: archive design handoff bundle for toolbar refresh
Stores SethMux_4-24-26.zip + extracted design_handoff_sethmux_toolbar/
so the spec, mockups, and reference jsx components stay in-repo for
future reference. Not served in production — the only file that ships
is static/toolbar.js, which already matches the design's toolbar.js
byte-for-byte.
2026-04-24 19:51:48 -04:00

146 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `<input>` 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 `<textarea>` and forwards `keydown` / `keypress` events plus IME `compositionstart` / `compositionend` to the terminal's data event one keystroke at a time. Mobile keyboards' autocorrect, swipe input, and word predictions don't fire per-key events — they replace the textarea's `.value` in bulk via `input` events with `inputType: "insertReplacementText"` or `"insertCompositionText"`. xterm.js's input handler discards those, so corrections silently fail. The compose bar sidesteps this by giving the OS keyboard a real text field, then sending the assembled string in one shot.
## Files in this bundle
| File | Purpose |
|----------------------------|-------------------------------------------------------------------|
| `toolbar.js` | **Ship this.** Drop into `static/toolbar.js`. |
| `preview.html` | Minimal mock terminal + the live `toolbar.js` for in-browser test |
| `preview-desktop.html` | Chrome window mock showing the full sethmux UI with tmux tabs |
| `preview-mobile.html` | Two Android frames: collapsed state + compose-open state |
| `browser-window.jsx` | Used only by `preview-desktop.html` |
| `android-frame.jsx` | Used only by `preview-mobile.html` |
## Acceptance checklist
- [ ] Bar appears only at viewport widths ≤ 900px.
- [ ] All 20 original buttons present and bound to the same key sequences.
- [ ] `Type` button toggles a third row with input + `↵` + `Send`.
- [ ] Pressing Enter inside the input sends the typed text plus `\r` and clears the field.
- [ ] Tapping any other toolbar button while composing flushes the typed text first, then sends the button's key. Compose bar stays open.
- [ ] Terminal pane `height` reflows when the bar grows/shrinks; nothing overlaps.
- [ ] Autocorrect / swipe / predictions work in the compose input on Gboard and iOS.
- [ ] Save button shows `✓ Saved` filled green for 1500ms after tap.
- [ ] Sel button toggles to `Done` (filled orange) and dims the terminal slightly.
- [ ] No console errors; `window._toolbar` guards against double-init.