diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..aa1fca9 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,116 @@ +# Design + +Visual system for the Maestro web UI. Source of truth is +`web/src/styles.css` (`:root` token block); this document mirrors it. Register: +**product** — design serves the task. See [PRODUCT.md](PRODUCT.md) for strategy. + +## Theme + +Dark is the default (terminal context); light is a full peer. Everything is +driven by CSS custom-property tokens, so a theme is a token swap, never a +restyle. Resolution order: + +1. `:root[data-theme="light"|"dark"]` — explicit user pin (persisted in + `localStorage` as `maestro-theme`), set by the header toggle. +2. `@media (prefers-color-scheme: light)` — followed when the user hasn't + pinned a theme. +3. Otherwise the `:root` defaults (dark). + +The xterm terminal stays dark in both themes by convention; `--term-bg` is dark +in light mode too. + +## Color (OKLCH) + +Strategy: **Restrained** (product floor) with one committed signal hue — +**green = "alive"**, carrying running status, primary action, focus, and +selection. Neutrals are a cool slate (hue ~256). Green is the signal, never a +costume: text stays neutral high-contrast, surfaces stay slate, so the result +reads terminal-native without CRT novelty. + +| Role | Dark | Light | +|---|---|---| +| `--bg` | `oklch(0.178 0.012 256)` | `oklch(0.985 0.003 256)` | +| `--surface` | `oklch(0.214 0.014 256)` | `oklch(1 0 0)` | +| `--surface-2` | `oklch(0.252 0.016 256)` | `oklch(0.965 0.004 256)` | +| `--surface-3` | `oklch(0.292 0.018 256)` | `oklch(0.93 0.006 256)` | +| `--border` | `oklch(0.318 0.018 256)` | `oklch(0.892 0.008 256)` | +| `--border-strong` | `oklch(0.4 0.022 256)` | `oklch(0.82 0.012 256)` | +| `--ink` (body text) | `oklch(0.945 0.006 256)` | `oklch(0.26 0.014 256)` | +| `--muted` (secondary) | `oklch(0.722 0.014 256)` | `oklch(0.47 0.016 256)` | +| `--faint` (tertiary/decorative) | `oklch(0.58 0.014 256)` | `oklch(0.62 0.016 256)` | +| `--accent` / `--live` | `oklch(0.83–0.84 0.16–0.17 152)` | `oklch(0.6 0.15–0.16 151)` | +| `--accent-ink` (text on accent) | `oklch(0.2 0.03 256)` | `oklch(0.99 0.01 152)` | +| `--danger` | `oklch(0.72 0.18 25)` | `oklch(0.55 0.21 27)` | +| `--warn` | `oklch(0.82 0.14 85)` | `oklch(0.66 0.15 75)` | + +Body and secondary text meet WCAG AA (≥4.5:1) against their surface in both +themes; `--faint` is reserved for decorative/large use only. Status never relies +on color alone — a running dot pulses and is filled; a dead session is a hollow +ring **and** carries an "exited" badge. + +Tints/glow derive from the accent at low alpha: `--accent-tint` (0.12), +`--accent-ring` (focus, ~0.55), `--live-glow` (status halo). + +## Typography + +**Monospace is the UI typeface** (brand identity), system-stack only (offline- +safe, zero web-font dependency, embedded clean into the registry binary): + +- `--font-mono`: `ui-monospace, "SF Mono", "Cascadia Code", "JetBrains Mono", "Roboto Mono", Menlo, Consolas, monospace` — chrome, labels, data, ids, the wordmark. +- `--font-sans`: `system-ui` stack — reserved for the few prose strings (empty-state sentences). + +Fixed rem scale (product register; not fluid/clamped): `--fs-11` 0.6875rem +through `--fs-20` 1.25rem, in ~1.12–1.2 steps. Base body `--fs-15` (0.9375rem), +line-height 1.45. No display sizes — the largest type is the 1.25rem wordmark. + +## Spacing, radius, sizing + +- Spacing scale: `--sp-1`..`--sp-8` = 4 / 8 / 12 / 16 / 20 / 24 / 32 px. +- Radius: `--r-1` 6 · `--r-2` 9 · `--r-3` 14 · `--r-pill` 999. +- Touch/row sizing: `--row-h` 46, `--control-h` 38, `--tap` 40 (mobile-first + touch targets). + +## Motion + +State and feedback only; 120–240 ms, ease-out (`cubic-bezier(0.22,1,0.36,1)`), +no bounce. Tokens `--dur-fast`/`--dur`/`--dur-slow`. + +- Living signals: brand dot + running-status ripple pulse (the "alive" cue); + `connecting` state pill pulses. +- Interaction: row hover/focus, button press (1px), input focus ring, chevron + rotation, theme-toggle sun/moon swap. +- Modal: bottom-sheet slide-up on mobile, scale-pop on ≥600px. +- Every animation has a `prefers-reduced-motion: reduce` path (pulses off, + transitions near-instant). + +## Layout & structure + +Mobile-first, structural responsiveness (not fluid type). Single `≤520px` +breakpoint stacks the header (full-width search, wrapped actions) and drops the +daemon chips to their own wrapped line. `≥600px` switches the spawn modal from +bottom-sheet to centered dialog. A faint slate grid texture sits on `--bg` as +terminal atmosphere (very low contrast, not decoration). + +## Components + +Three surfaces, all DOM built in TS, styled by class: + +- **Catalog** — sticky blurred header (pulsing wordmark, search, theme toggle, + Spawn); status bar (`N sessions · M live` tally + daemon chips); session tree + (collapsible folders with counts, leaf rows with status dot, name, `@daemon` + + command meta, exited badge, reveal-on-hover kill). Empty states are + distinct and teach: waiting-for-daemon, no-sessions, no-search-match. +- **Attach** — header (back, `session `, live/closed/error state pill) over + a framed dark xterm with a green cursor. +- **Spawn modal** — bottom-sheet/dialog form; mono inputs with green focus + ring; grouped worktree fieldset. + +Interactive elements ship default / hover / focus-visible / active / disabled +states; the accent green is used only for action, selection, focus, and live +status — never decoration. + +## Dev review aids (DEV-only, stripped from production) + +`web/src/mock.ts` + query params seed a representative catalog without a running +registry: `?mock` (seed sessions/daemons), `?mock&spawn` (open the spawn +modal), `?theme=light|dark` (pin a theme). Guarded by `import.meta.env.DEV`. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..87c4bac --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,72 @@ +# Product + +## Register + +product + +## Users + +A single technical operator (a developer) managing every interactive shell and +agent — Claude Code, opencode, plain bash — running across multiple machines. +The job-to-be-done: from any device (laptop, desktop, **phone**), get an +accurate at-a-glance picture of all live sessions across all hosts, then attach +to one and drive its terminal. Usage is often glance-and-go: checking on a +long-running agent, killing a stray session, or hopping into a shell mid-task. +Mobile-first is a hard requirement — touch targets, viewport-fit safe areas, +and bottom-sheet modals already reflect this. + +## Product Purpose + +Maestro is a unified terminal-session orchestrator: one place to see and attach +to every interactive session running across every machine the operator cares +about. tmux for persistence, WebSocket for transport, a daemon-per-host +federated by a small always-on registry. The web UI is the browser face of +that system — it renders the cross-host session catalog and provides the attach +terminal (xterm.js). Success = the operator trusts the picture is accurate and +current, and can get from "open the page" to "typing in the right session" in +seconds, on any device. + +## Brand Personality + +Three words: **precise, calm, alive.** This is infrastructure tooling — it must +read as trustworthy and exact, never toy-like — but it watches living +processes, so status must feel present and current, not static. Voice is terse +and technical, matching the CLI and the repo's terse house style. It is a +craftsman's tool: opinionated, monospace-literate, with quiet personality +rather than decoration. Emotional goals: in control / confident, fast / +efficient, calm / uncluttered, with earned moments of delight. + +## Anti-references + +- Generic Tailwind-admin / SaaS-cream dashboards. +- Heavy gradients, glassmorphism, glass cards as decoration. +- Playful, rounded, "friendly app" aesthetics that undercut the + infrastructure-tool trust. +- Whitespace-maximalist layouts that trade away the at-a-glance scan for + "elegance." +- Anything where you could guess the design from the category alone + ("terminal tool → CRT-green-on-black novelty"): the orchestrator identity is + earned through restraint, not costume. + +## Design Principles + +1. **The scan is the product.** Optimize for instant comprehension of "what's + running, where, and is it healthy" across many sessions and hosts. Status + legibility beats everything else. +2. **Calm under load.** Stay uncluttered whether there are 3 sessions or 300; + hierarchy and grouping carry the weight, not color-shouting every row. +3. **Fast and keyboard-literate.** Low-friction, efficient, gets out of the + way; honor keyboard navigation, don't make the operator hunt. +4. **Monospace as identity.** Treat the mono/sans pairing as deliberate brand + typography, not just terminal output. Ids, hosts, counts, and state read as + precise technical data. +5. **Earned delight.** Personality lives in motion and micro-detail (live-status + pulse, attach transitions, cursor cues), never in noise — always craft. + +## Accessibility & Inclusion + +Standard good practice, applied rigorously: WCAG AA contrast for all text and +status, visible keyboard focus on every interactive element, and a full +`prefers-reduced-motion` alternative for every animation. Status must never rely +on color alone (pair the dot with a label/shape). Dark and light themes are +equal citizens, driven entirely by CSS custom-property tokens.