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/web
Miguel Palhas 4c8217385a fix(maestro): scope assignment-skipping to wrappers that accept it
`command FOO=1 claude` and `nohup FOO=1 claude` are invalid shells (they
exec `FOO=1` as the program, exit 127), but the resolver skipped the
assignment unconditionally after any wrapper. Assignment prefixes are
only real at the start of a simple command or after a wrapper that itself
interprets them as env vars (env, sudo) or a keyword that restarts the
assignment grammar (time) — not after command, nohup, or a package
manager's subcommand, which just exec their next argument as the program.

Attach an accepts_assignment flag to each wrapper transition in both the
Rust and TS resolvers and gate the assignment-skip loop on the flag from
the most recently consumed wrapper. Extends the shared fixture with the
two reported cases plus every other wrapper transition and several nested
combinations (sudo+time valid, sudo+command / command+nohup / time+command
invalid, nohup+env valid) to lock the per-wrapper policy in for both
languages.
2026-09-05 14:25:57 +01:00
..

Maestro web UI

Browser UI for Maestro. The daemon embeds this bundle and serves it off its own listener, so the page talks back to the same origin over /api/* — session catalog, spawn/kill, and terminal I/O.

Framework choice: vanilla TypeScript

No frontend framework. The UI is small enough that the cost of pulling in Preact / Solid / Leptos doesn't pay back. xterm.js does all the heavy rendering; everything else is a session tree and a spawn form, both trivial in plain DOM.

This decision is non-architectural — swap in a framework later without touching the daemon if needed.

Layout

src/
  main.ts        # entry point, hash-route dispatch
  api.ts         # daemon HTTP API client
  events.ts      # `/api/events` WebSocket subscriber
   catalog.ts     # session tree view (groups by working directory)
   attach.ts      # xterm.js + attach WS view
   multiview.ts   # tabs + split panes
   spawnForm.ts   # shared spawn form (preset tiles, project picker, worktree)
   spawnModal.ts  # dialog chrome around the spawn form
  theme.ts       # dark/light theme toggle + persistence
  types.ts       # mirror of `maestro-protocol` JSON shapes
  styles.css     # design-system tokens + component styles
  mock.ts        # DEV-only review fixture (see Design)

Design

The visual system (OKLCH token palette, monospace-first typography, dark+light theming, motion, components) is documented in ../DESIGN.md; the tokens live in the :root block of src/styles.css. Strategic intent (register, brand, principles) is in ../PRODUCT.md. Theme follows the OS by default and can be pinned via the header toggle (persisted in localStorage).

mock.ts is a DEV-only fixture (stripped from production builds): visit the dev server with ?mock to seed a representative catalog without a running daemon, ?mock&spawn to open the spawn modal, and ?theme=light|dark to pin a theme.

Routing is hash-based:

  • #/ → catalog
  • #/attach/<session-id> → terminal

This keeps the static handler dumb (no SPA fallback needed). Concurrent attaches: open the same #/attach/<id> URL in another tab.

Dev

pnpm install
pnpm dev      # http://localhost:5173, proxies /api → http://127.0.0.1:7070
pnpm build    # → dist/, embedded into the maestro binary at cargo build time

The Vite dev server proxies /api and the WebSockets to a local daemon. Default is http://127.0.0.1:7070; point it elsewhere by exporting MAESTRO_DAEMON_URL before pnpm dev:

MAESTRO_DAEMON_URL=http://laptop.lan:7070 pnpm dev

Production

pnpm build writes the bundle to web/dist/. Under its default web feature the daemon embeds that directory at compile time and serves it from /, alongside the /api/* routes. See crates/maestro-daemon/src/static_bundle.rs.