This document supersedes the previous "Warm Terminal" system. AO's design language is Mission Control: a calm, high-signal control room for supervising a fleet of autonomous agents. The earlier warm-neutral direction (Geist Sans, amber/orange orchestrator CTA, brown-tinted surfaces) is retired. This file is the single source of truth — there is no second package-level
DESIGN.md. Origin: the dashboard design-language exploration indocs/design/dashboard-language.mdand its canonical mockups (kanban.html,session.html).
- What this is: A web dashboard for supervising fleets of parallel AI coding agents. Each agent gets its own git worktree, branch, and PR. The dashboard is the operator's single pane of glass.
- Who it's for: Developers running 10–30+ agents in parallel. It must stay calm and glanceable with 20+ agents running.
- Project type: Next.js 15 (App Router) + React 19 + Tailwind v4. A kanban fleet board (home) and a per-session detail view.
A calm, high-signal control room. Linear-grade restraint, dense but humane. State is glanceable, not noisy.
The blue/orange split. The mascot is the Claude Code character recolored blue — the conductor. This drives a deliberate two-color semantic split:
- Blue = the orchestrator (AO itself / "you"). Brand, the single solid-fill primary CTA (the Orchestrator button), active selection, focus, links.
- Orange = the agents being conducted. The per-agent identity and the
workingstatus — the one "an agent is alive right now" signal (a gently breathing dot, the terminal cursor).
Blue does not replace orange; they mean different things. The board reads as a blue conductor surrounded by orange agents.
Color = meaning. Most states get none. The UI is grayscale by default; color is rationed so it always signals something.
| Token | Hex | Use |
|---|---|---|
| Blue | #4d8dff |
orchestrator / you — primary action, selection, focus, links (the only solid-fill button) |
| Orange | #f59f4c |
a working agent (status dot + terminal cursor) |
| Amber | #e8c14a |
needs-your-input / attention (incl. unresolved review comments, changes requested) |
| Red | #ef6b6b |
failing / stuck (CI failed, crashed, conflicts) |
| Green | #74b98a |
mergeable / passed / resolved |
| Neutral grays | — | everything healthy & passive: in-review, idle, done, metadata |
Diff add/remove green & red are permitted in their literal context (the Changes view).
The product is dark-only mission control. The dark theme is authoritative.
| Token (literal) | Value | Maps to semantic token |
|---|---|---|
--bg |
#0a0b0d |
--color-bg-base (app base) |
--bg-side |
#08090b |
--color-bg-sidebar |
--card |
#15171b |
--color-bg-surface / --color-bg-card — the only bordered surface |
--card-hover |
#191b20 |
--color-bg-elevated / -elevated-hover |
--col |
#0e0f12 |
--color-column-bg (kanban trough) |
--term |
#0c0d10 |
xterm background (terminal-themes.ts) |
--line |
rgba(255,255,255,0.06) |
--color-border-subtle / -default |
--line-2 |
rgba(255,255,255,0.10) |
--color-border-strong |
--t1 … --t4 |
#f4f5f7 #9ba1aa #646a73 #444951 |
--color-text-primary/secondary/tertiary/muted |
These literals live at the top of the .dark block in
packages/web/src/app/globals.css; the existing --color-* semantic tokens
alias them, so all consuming CSS keeps working. Don't rename the semantic
tokens — add/alias and migrate.
Self-hosted via next/font/local (packages/web/src/fonts/). No external font CDN.
- UI = Schibsted Grotesk (
--font-sans). The product voice. Used for all chrome: titles, labels, buttons, body. A distinctive grotesk — not Inter/system. - Machine = JetBrains Mono (
--font-mono). Branches, IDs, PR numbers, costs, timestamps, terminal — anything the machine emits. - Numerals:
tabular-numswherever numbers appear (counts, costs, tokens). - Never render chrome in mono. The sans/mono split is itself a design device: product voice vs. machine voice.
(Geist Sans is removed. JetBrains Mono is no longer used for display headlines.)
A single semantic spectrum maps the canonical lifecycle to a {tone, label}
pair and is used everywhere — kanban card badge, sidebar dot, session topbar
pill. It lives in lib/status-spec.ts
(getStatusSpec) and renders through
StatusBadge.
| Tone | Color | Meaning |
|---|---|---|
working |
orange (breathing) | an agent is alive right now |
input |
amber | needs your input |
changes |
amber | changes requested |
fail |
red | CI failed / stuck / crashed / conflicts |
review |
neutral | in review / waiting on a reviewer |
ready / merged |
green | mergeable / landed |
neutral |
gray | idle / done / terminated |
Tone is refined from the (tested) attention-level bucket so a card's badge never disagrees with the column it sits in.
- Lead with the fleet, not the terminal. Answers "what are all my agents doing?" at a glance.
- Frameless columns: lifecycle columns Working → Needs you → In review → Ready to merge are borderless tinted troughs with a faint per-column semantic top-glow. The card is the only bordered surface — no box-in-box.
- Compact cards: status badge + id, task title (2-line clamp), branch, a thin footer. Done/Terminated collapses at the bottom.
- The sidebar always shows all projects' sessions; the board filters client-side. The SSE refresh interval is 5s (unchanged — C-14).
- Framed terminal as a real surface (header + viewport), flush to sidebar/topbar.
It is a live xterm.js/PTY — we do not style its content; we only set the
frame and the xterm.js
themeobject (background--term, orange cursor, blue selection, a 16-color ANSI palette tied to the tokens — seeterminal-themes.ts). Claude Code's own input lives inside the terminal; there is no separate composer. - Pluggable inspector rail (a registered-view slot):
SessionInspectorwith views Summary · Changes · Browser; adding more (Logs, Cost…) is just another entry.- Summary is ordered by supervision value: Pull request → Review comments → Activity → Overview (the PR card bundles PR + review comments).
- Review comments surface a soft-blue Address action (
askAgentToFix) that hands the comment — with itsfile:line— to the agent session to fix. - Browser is reserved for a web-preview / Playwright plugin.
- Topbar:
‹ Kanban(back) · title + inline branch · status pill · notifications · Kill (trash) · Orchestrator (blue primary, org-chart icon).
- Line icons only (Lucide-style, ~1.6px stroke,
currentColor, inline SVG). No emoji. - Motion is minimal & purposeful: a slow CSS-only "breathe" pulse on the
working dot / terminal cursor (
@keyframes breathe, 2.4s). No animation libraries (C-07). All motion respectsprefers-reduced-motion: reduce.
- Tokens over raw values. Use the
--color-*semantic tokens (or the literal--bg/--card/--t1…palette) fromglobals.css. No hardcoded hex/rgba in components. - No inline
style=for theme values (C-02). Tailwind utilities withvar(--token), or a named class inglobals.css. - No external UI kits (Radix, shadcn, Headless UI, …) (C-01).
- Tailwind vs CSS classes: Tailwind for one-off layout/spacing; add a class in
globals.csswhen a pattern is theme-sensitive, uses pseudo-elements/gradients, or repeats 3+ times. - App Router only (C-06). Component files ≤ 400 lines (C-04). Test files for new/changed components (C-12).
- Dark theme is always preserved (C-05). Light-mode tokens still exist for the theme toggle but mission control is designed and tuned for dark.
- Focus indicators:
outline: 2px solid var(--color-accent); outline-offset: 2pxon:focus-visible. Neveroutline: nonewithout a visible replacement. - Reduced motion:
@media (prefers-reduced-motion: reduce)disables animations/transitions. Non-negotiable. - Color independence: never encode meaning with color alone. Status badges always pair a colored dot with a text label.
- Contrast: body text ≥ 4.5:1; UI/borders/icons ≥ 3:1. The text ramp
--t1…--t3is for primary→labels on the--bg/--cardsurfaces;--t4is for faint/disabled only. - Keyboard nav: all interactive elements reachable via Tab; Escape closes popovers; logical order.
- ARIA labels on all icon-only buttons.
- C-01: No new UI component libraries
- C-02: No inline styles in new/modified code
- C-04: Component files max 400 lines
- C-05: Dark theme preserved
- C-06: Next.js App Router only
- C-07: No animation libraries (CSS-only motion)
- C-12: Test files for new/changed components
- C-14: SSE 5s interval unchanged
| Date | Decision | Rationale |
|---|---|---|
| 2026-05-27 | Mission Control supersedes Warm Terminal | A single source of truth. The product is a calm control room for a fleet of agents; cool restraint + rationed color reads better at 20+ agents than warm decoration. |
| 2026-05-27 | Blue = orchestrator/you, orange = working agent | The mascot is the blue conductor; orange is the Claude Code lineage. Two colors, two meanings — the product metaphor, visualized. |
| 2026-05-27 | Schibsted Grotesk (UI) + JetBrains Mono (machine), self-hosted | A distinctive grotesk for the product voice; mono reserved for machine data. Self-hosted via next/font/local — no external font CDN. |
| 2026-05-27 | One status system (getStatusSpec + StatusBadge) |
Kanban badge, sidebar dot, and topbar pill all render from one spectrum so status never disagrees with itself. |
| 2026-05-27 | The card is the only bordered surface | Frameless tinted columns with per-column glow; cards are flat --card with a hairline ring. No box-in-box nesting. |