docs(maestro): add PRODUCT.md + DESIGN.md design context

Capture the web UI's strategic register (product) and the visual design
system (OKLCH token palette, monospace-first type, dark+light theming) for
the impeccable workflow. Supersedes the transient .impeccable.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 18:40:42 +00:00
co-authored by Claude Opus 4.8
parent c2f32779a3
commit e909790b56
2 changed files with 188 additions and 0 deletions
+116
View File
@@ -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 <id>`, 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`.