This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/DESIGN.md
T
Miguel Palhas 51f91b3f5d docs(maestro): amend DESIGN.md for draft panes (#62)
The spawn modal is no longer one of the three surfaces: the Components
section now describes the draft pane, and the stale ≥600px
bottom-sheet→dialog breakpoint note is dropped.
2026-08-23 22:36:51 +01:00

117 lines
5.4 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.
# 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 web 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.
- 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). 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); session tree (collapsible
directory folders with counts, leaf rows with status dot, name, command meta,
exited badge, reveal-on-hover kill). Empty states are distinct and teach:
no-sessions, no-search-match.
- **Attach** — header (back, `session <id>`, live/closed/error state pill) over
a framed dark xterm with a green cursor.
- **Draft pane** — the spawn form mounted inline in an empty split pane,
focusable like any session pane; mono inputs with green focus ring; grouped
worktree fieldset. Spawn opens one (reusing an empty focused pane, else
splitting), and "Spawn similar" seeds one from a session's dir + command.
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
daemon: `?mock` (seed sessions), `?mock&spawn` (open a draft pane),
`?theme=light|dark` (pin a theme). Guarded by `import.meta.env.DEV`.