# Design System — Agent Orchestrator ## Product Context - **What this is:** A web-based dashboard for managing 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+ AI coding agents in parallel. From solo devs to engineering teams. - **Space/industry:** AI agent orchestration. Competitors: Conductor.build, T3 Code, OpenAI Codex app. All are native Mac apps with cool blue-gray dark mode. Agent Orchestrator is the web-based alternative. - **Project type:** Web app (Next.js 15, React 19, Tailwind v4). Kanban board with 6 attention-priority columns. ## Aesthetic Direction - **Direction:** Warm Terminal - **Decoration level:** Intentional — subtle surface depth through warm gradients, inset highlights that catch light like brushed aluminum, ambient glow on active states. No decorative blobs, no gratuitous effects. - **Mood:** High-end audio gear meets flight deck. Dense, scannable, utilitarian, with enough warmth that developers want to live in it for 10 hours. Every competitor is cold blue-gray. This is the warm one. - **Reference sites:** Conductor.build (layout baseline), linear.app (density standard), t3.codes (terminal aesthetic) ## Typography - **Display/Hero:** JetBrains Mono, weight 500, letter-spacing -0.02em — monospace for headlines. In a dashboard where 40% of visible text is already monospace (agent output, branch names, commit hashes), leaning into mono for display creates a unified typographic voice instead of two competing voices. - **Body:** Geist Sans, weight 400, letter-spacing -0.011em — purpose-built for dense interfaces at 13px. Better digit alignment than IBM Plex Sans, designed for exactly this density level. - **UI/Labels:** Geist Sans, weight 600, letter-spacing 0.06em, uppercase, 10-11px — column headers, section labels, status indicators. - **Data/Tables:** JetBrains Mono, weight 400, 11-13px, tabular-nums — agent IDs, branch names, timestamps, commit hashes, diff stats, PR numbers. - **Code:** JetBrains Mono, weight 400 — terminal output, code blocks, inline code. - **Loading:** Google Fonts via next/font/google. CSS variables: `--font-sans` (Geist), `--font-mono` (JetBrains Mono). Display strategy: swap. - **Scale:** - xs: 10px (timestamps, metadata) - sm: 11px (secondary text, captions, labels) - base: 13px (body text, card content) - lg: 15px (section titles) - xl: 17px (page titles) - display: clamp(22px, 2.8vw, 32px) (hero headings) ## Color - **Approach:** Restrained with signal accents. Color is a priority channel, not decoration. Warm tones throughout. - **Accent (interactive):** #8b9cf7 — warm periwinkle. Links, focus rings, active states. Blue = clickable is muscle memory. This warm-leaning blue fits the palette without colliding with status colors. - **Accent hover:** #a3b1fa - **Accent tint:** rgba(139, 156, 247, 0.12) - **Attention (warm):** #e2a336 — states requiring human input. Amber is universally "needs attention" without the panic of red. ### Surfaces (Dark Mode) | Token | Value | Usage | Rationale | |-------|-------|-------|-----------| | bg-base | #121110 | Page background | Brown-tinted black. Warmer than neutral #111 or blue-tinted #0a0d12. Sets the warm foundation. | | bg-surface | #1a1918 | Card/column backgrounds | One stop lighter, same warm undertone. Surface hierarchy through subtle warmth, not just lightness. | | bg-elevated | #222120 | Modals, popovers, hover states | Two stops up. Warm enough to feel distinct from surface without being muddy. | | bg-elevated-hover | #2a2928 | Hover on elevated surfaces | Subtle lift on interaction. | | bg-subtle | rgba(255, 240, 220, 0.04) | Subtle tints, pill backgrounds | Warm-tinted transparency. Reads as "highlighted" without introducing a new color. | ### Surfaces (Light Mode) | Token | Value | Usage | Rationale | |-------|-------|-------|-----------| | bg-base | #f5f3f0 | Page background | Warm parchment, not clinical white or cool gray. Matches the warm dark mode without being beige. | | bg-surface | #ffffff | Card/column backgrounds | True white for cards creates contrast against the warm base. Cards "float" on warm paper. | | bg-elevated | #ffffff | Modals, popovers | Same as surface. Light mode doesn't need as many elevation steps because shadows do the work. | | bg-elevated-hover | #f7f5f2 | Hover states | Warm tint on hover, matching the base temperature. | | bg-subtle | rgba(120, 100, 80, 0.05) | Subtle tints | Brown-tinted transparency for warm highlighting. | **Light mode strategy:** Warm parchment base (#f5f3f0) with white cards. The same brown undertone that makes dark mode warm also makes light mode feel like quality paper, not sterile lab equipment. Accent darkened in light mode (#5c64b5) to maintain 5.3:1 contrast on white. Status colors shifted darker (green #16a34a, amber #b8860b, red #dc2626, cyan #0891b2) to maintain contrast on light backgrounds. Drop shadows replace inset highlights for surface hierarchy. ### Text (Dark Mode) | Token | Value | Usage | |-------|-------|-------| | text-primary | #f0ece8 | Headings, card titles, body. Cream, not pure white or blue-white. Warm and easy on the eyes at 3am. | | text-secondary | #a8a29e | Descriptions, metadata. Stone-toned, not neutral gray. Readable in dense layouts. | | text-tertiary | #78716c | Timestamps, placeholders, disabled states. Warm tertiary that recedes without disappearing. | ### Text (Light Mode) | Token | Value | Usage | |-------|-------|-------| | text-primary | #1c1917 | Headings, card titles, body. Warm near-black, not pure black. | | text-secondary | #57534e | Descriptions, metadata. Stone-500. | | text-tertiary | #736e6b | Timestamps, placeholders. Darkened from #a8a29e to pass WCAG AA (5.0:1 on white, 4.5:1 on base). | ### Borders (Dark Mode) | Token | Value | Usage | |-------|-------|-------| | border-subtle | rgba(255, 240, 220, 0.06) | Dividers, section separators. Warm-tinted transparency. | | border-default | rgba(255, 240, 220, 0.10) | Card edges, input borders. | | border-strong | rgba(255, 240, 220, 0.18) | Hover states, focus indicators. | ### Status Colors | Status | Dark Mode | Light Mode | CSS Token | Usage | |--------|-----------|------------|-----------|-------| | Working | #22c55e | #16a34a | `--status-working` | Agent actively coding. Green dot with pulse ring animation. | | Ready | #8b9cf7 | #6b73c4 | `--status-ready` | Queued, awaiting start or CI pending. | | Respond | #e2a336 | #b8860b | `--status-respond` | Needs human input. Amber = attention without panic. **NOT red** — "respond" is a normal workflow state. | | Review | #06b6d4 | #0891b2 | `--status-review` | Code ready for review. Cyan = "look when ready." | | Error | #ef4444 | #dc2626 | `--status-error` | CI failed, agent crashed. Red = broken. Distinct from Respond. | | Done | #57534e | #d6d3d1 | `--status-done` | Completed. Fades to stone. Done items recede. | **Critical:** `--status-respond` and `--status-error` are separate tokens with different semantic meanings. Respond = human decision needed (amber). Error = something broke (red). Never conflate them. - **Dark mode strategy:** Warm charcoal palette (brown-tinted, not neutral or blue-tinted gray). Reduce font weight by one step in dark mode (semibold becomes 500, bold becomes 600). Inset highlights on elevated surfaces: `inset 0 1px 0 rgba(255,255,255,0.03)`. Subtle radial gradients on body for ambient depth. ## Spacing - **Base unit:** 4px - **Density:** Comfortable — dense enough for 30+ cards, spacious enough for 10-hour sessions - **Scale:** 1(4) 2(8) 3(12) 4(16) 5(20) 6(24) 8(32) 10(40) 12(48) 16(64) ## Layout - **Approach:** Grid-disciplined - **Kanban grid:** 6 equal-width columns on desktop, 3 on tablet, stacked on mobile - **Mobile column order:** Respond > Review > Pending > Working (urgency-first) - **Max content width:** 1280px for settings/detail pages - **Border radius:** - 0px everywhere. No rounding on cards, buttons, inputs, modals, dropdowns. Hard edges are the identity. The only exception is status dots (circles by nature) and avatar images. - full: 9999px (status dots, avatar circles only) - **Card inset highlight:** `inset 0 1px 0 rgba(255,255,255,0.03)` in dark mode - **Status accent:** 2px solid left border on session cards, colored by status ## Motion - **Approach:** Intentional — every animation has a clear purpose and passes the frequency test - **Easing:** - enter/exit: `cubic-bezier(0.16, 1, 0.3, 1)` (spring-like deceleration, feels responsive) - move/morph: `cubic-bezier(0.77, 0, 0.175, 1)` (natural acceleration/deceleration) - hover/color: `ease-out` - constant (spinner, marquee): `linear` - **Duration:** - micro: 100-160ms (button press, hover state) - short: 150-200ms (tooltips, popovers, card entrance) - medium: 200-300ms (modals, drawers, card expand) - long: 2s (status dot pulse, continuous indicators) - **Card entrance:** `translateY(8px)` + opacity, 0.2s with 40ms stagger between siblings - **Status pulse:** GPU-composited pseudo-element on Working dots. `transform: scale(0.8→1.3)` + `opacity: 0.5→0`, 2s ease-in-out infinite. Not box-shadow (triggers paint). - **Button press:** `transform: scale(0.97)` on `:active`, 160ms ease-out - **Rules:** - Never animate keyboard-initiated actions (command palette toggle, shortcuts) - One animation per element, one purpose per animation - CSS transitions for interruptible UI, keyframes for continuous indicators - All animations must respect `prefers-reduced-motion: reduce` - Use `contain: layout style paint` on session cards for performance with 30+ cards ## Accessibility - **Touch targets:** Minimum 44x44px on all interactive elements (buttons, links, toggles). Icon buttons that render smaller visually must have padding to meet 44px minimum hit area. - **Contrast ratios (WCAG AA):** - Body text (13px): 4.5:1 minimum against surface backgrounds - Large text (18px+ or 14px bold): 3:1 minimum - UI components (borders, icons): 3:1 minimum against adjacent colors - Dark: text-primary #f0ece8 on bg-surface #1a1918: 14.9:1 ✓ - Dark: text-secondary #a8a29e on bg-surface #1a1918: 7.0:1 ✓ - Dark: text-tertiary #78716c on bg-surface #1a1918: 3.7:1 ✓ (labels only, not body text) - Dark: accent #8b9cf7 on bg-surface #1a1918: 6.9:1 ✓ - Light: text-primary #1c1917 on bg-surface #ffffff: 17.5:1 ✓ - Light: text-secondary #57534e on bg-surface #ffffff: 7.6:1 ✓ - Light: text-tertiary #736e6b on bg-surface #ffffff: 5.0:1 ✓ - Light: accent #5c64b5 on bg-surface #ffffff: 5.3:1 ✓ - **Focus indicators:** `outline: 2px solid var(--accent); outline-offset: 2px` on `:focus-visible`. Never `outline: none` without a visible replacement. - **Reduced motion:** `@media (prefers-reduced-motion: reduce)` disables all animations and transitions globally. Non-negotiable. - **Color independence:** Never encode meaning with color alone. Always pair colored dots with text labels. Status pills include both dot and text. - **Keyboard navigation:** All interactive elements reachable via Tab. Logical tab order. Escape closes modals/popovers. Arrow keys navigate within lists. - **Screen reader:** ARIA labels on all icon-only buttons. `role="heading"` with `aria-level` on non-heading elements styled as headings. Status changes announced via `aria-live` regions. ## Web Implementation Rules - **Single source of truth:** This file is the authoritative design spec for the repo, including `packages/web/`. Do not create a second package-level `DESIGN.md`. - **Tokens over raw values:** In React/Tailwind code, use CSS variables from `packages/web/src/app/globals.css` instead of hardcoded hex, rgba, or `dark:` overrides. - **No inline styles:** Avoid `style={{ ... }}` for theme values. Use Tailwind utilities with `var(--token)` or add a named class in `globals.css`. - **No external UI kits:** Do not introduce Radix, shadcn, Headless UI, or similar component libraries for core UI primitives. - **Tailwind vs CSS classes:** Use Tailwind for one-off layout and spacing. Add a class in `globals.css` when a pattern is theme-sensitive, uses pseudo-elements, gradients, or repeats 3+ times. - **Theme handling:** `:root` holds light tokens and `.dark` holds dark overrides. Use tokenized classes like `bg-[var(--color-bg-surface)]`, not `bg-white dark:bg-[#1a1918]`. ## Token Mapping These are the concrete token names used in `packages/web/src/app/globals.css`. New UI code should reference these names directly. ### Core Surface Tokens | CSS Token | Meaning | |-----------|---------| | `--color-bg-base` | Page background | | `--color-bg-surface` | Standard card/panel background | | `--color-bg-elevated` | Elevated surfaces, popovers, modals | | `--color-bg-elevated-hover` | Hover state for elevated surfaces | | `--color-bg-subtle` | Subtle fill for chips, hovers, muted emphasis | | `--color-bg-sidebar` | Sidebar-specific background | ### Core Text and Border Tokens | CSS Token | Meaning | |-----------|---------| | `--color-text-primary` | Primary headings/body copy | | `--color-text-secondary` | Supporting text | | `--color-text-tertiary` | Captions/placeholders | | `--color-text-muted` | Low-emphasis meta text | | `--color-text-inverse` | Text on accent or dark fills | | `--color-border-subtle` | Hairline dividers | | `--color-border-default` | Standard borders | | `--color-border-strong` | Emphasized borders/focus-adjacent borders | ### Accent and Utility Tokens | CSS Token | Meaning | |-----------|---------| | `--color-accent` | Primary interactive accent | | `--color-accent-hover` | Hover state for accent surfaces | | `--color-accent-subtle` | Accent-tinted background | | `--color-accent-blue` | Semantic blue alias | | `--color-accent-green` | Semantic green alias | | `--color-accent-yellow` | Semantic amber alias | | `--color-accent-orange` | Semantic orange alias | | `--color-accent-red` | Semantic red alias | | `--color-tint-blue` | Blue pill/badge background | | `--color-tint-green` | Green pill/badge background | | `--color-tint-yellow` | Yellow pill/badge background | | `--color-tint-orange` | Orange pill/badge background | | `--color-tint-red` | Red pill/badge background | | `--color-tint-neutral` | Neutral pill/badge background | ### Status and Alert Tokens | CSS Token | Meaning | |-----------|---------| | `--color-status-working` | Agent actively working | | `--color-status-ready` | Ready/queued state | | `--color-status-respond` | Human response needed | | `--color-status-review` | Review-needed state | | `--color-status-pending` | Pending/queued emphasis | | `--color-status-merge` | Merge-ready/merged emphasis | | `--color-status-idle` | Idle/inactive state | | `--color-status-done` | Completed/receding state | | `--color-status-error` | Error/broken state | | `--color-ci-pass` | CI passing | | `--color-ci-fail` | CI failing | | `--color-alert-ci` / `--color-alert-ci-bg` | CI failure callout row | | `--color-alert-review` / `--color-alert-review-bg` | Review-requested callout row | | `--color-alert-changes` / `--color-alert-changes-bg` | Changes-requested callout row | | `--color-alert-conflict` / `--color-alert-conflict-bg` | Merge-conflict callout row | | `--color-alert-comment` / `--color-alert-comment-bg` | New-comment callout row | ## Component Anatomy ### Session Card ``` ┌─ 2px left border (status color) ─────────────────────┐ │ ┌─ Card (bg-surface, 1px border-default, 2px radius) │ │ │ Title (text-primary, 12px, weight 500) │ │ │ Branch · PR # (mono, text-tertiary, 10px) │ │ │ ┌─ Status pill ────────────────────┐ │ │ │ │ ● dot (6px, status color) Label │ │ │ │ └──────────────────────────────────┘ │ │ │ inset 0 1px 0 rgba(255,255,255,0.03) (dark only) │ │ └─────────────────────────────────────────────────────│ └───────────────────────────────────────────────────────┘ ``` - **Padding:** 10px 12px - **Spacing:** 4px between title and meta, 6px between meta and status - **Hover:** bg-elevated-hover, border-color transition 0.12s - **Active:** scale(0.99), 80ms - **Containment:** `contain: layout style paint` for 30+ card performance ### Button States | State | Primary | Secondary | Ghost | Danger | |-------|---------|-----------|-------|--------| | Rest | bg: accent, text: #121110 | bg: elevated, border: border-default | bg: transparent | bg: transparent, border: red/30% | | Hover | bg: accent-hover | bg: elevated-hover, border: border-strong | bg: bg-subtle | bg: red/8%, border: red | | Active | scale(0.97) | scale(0.97) | scale(0.97) | scale(0.97) | | Focus | outline: 2px accent | outline: 2px accent | outline: 2px accent | outline: 2px accent | | Disabled | opacity: 0.5, cursor: not-allowed | opacity: 0.5 | opacity: 0.5 | opacity: 0.5 | - **Padding:** 8px 16px - **Font:** Geist Sans, 13px, weight 500 - **Border-radius:** 0 - **Min touch target:** 44px height (add padding if needed) ### Input Fields | State | Appearance | |-------|------------| | Rest | bg: bg-base, border: border-default, text: text-primary | | Placeholder | color: text-tertiary | | Focus | border-color: accent, no outline (border IS the indicator) | | Error | border-color: status-error, error message below in status-error color | | Disabled | opacity: 0.5, cursor: not-allowed, bg: bg-subtle | - **Padding:** 8px 12px - **Font:** Geist Sans, 13px - **Border-radius:** 0 ### Status Pill - **Layout:** inline-flex, center-aligned, gap 6px - **Dot:** 6px circle, filled with status color - **Text:** 11px, weight 600, text-secondary - **Background:** bg-subtle - **Padding:** 4px 10px - **Border-radius:** 0 ### Alert / Banner - **Layout:** flex, padding 12px 16px - **Left border:** 2px solid, colored by severity - **Background:** status color at 6% opacity - **Text:** status color, 13px - **Border-radius:** 0 - **Variants:** success (green), warning (amber), error (red), info (cyan) ## Web Patterns - **Mono data:** IDs, hashes, timestamps, branch names, and PR numbers should use `font-mono` with `10-11px` sizing and slightly wider tracking. - **Status text:** Session/card status labels should stay mono and low-emphasis unless the status itself is the primary signal. - **Alert rows:** Inline alert/callout rows inside cards should use a 2px left border plus paired foreground/background alert tokens. - **Dividers:** Use `border-[var(--color-border-subtle)]` or `border-[var(--color-border-default)]` instead of ad hoc neutral grays. - **Existing reusable components:** Prefer current primitives/components like `ActivityDot`, `CIBadge`, `PRStatus`, `Toast`, and shared layout patterns already in `packages/web/src/components/`. - **Sharp edges remain the rule:** Do not reintroduce rounded cards/buttons as a package-level convention. `rounded-full` is reserved for dots, pills, and avatars. ## Performance Guidelines - Use `contain: layout style paint` and `content-visibility: auto` on session cards - Animate only `transform` and `opacity` (GPU-composited). Never animate `padding`, `margin`, `height`, `width`, `border`, or `box-shadow`. - Status dot pulse must use pseudo-element with `will-change: transform, opacity`, not box-shadow rings - Backdrop blur on nav capped at 12px (diminishing returns above 12) - Pause all non-essential animations when tab is hidden ## Anti-Patterns (Never Do) - Purple/violet gradients as default accent - 3-column feature grid with icons in colored circles - Centered everything with uniform spacing - Uniform bubbly border-radius (8-12px) on all elements - Gradient buttons as primary CTA pattern - `transition: all` — always specify exact properties - `scale(0)` entry animations — start from `scale(0.95)` with `opacity: 0` - `ease-in` on UI elements — use `ease-out` for responsiveness - Animations over 300ms on frequently-triggered UI elements - Neutral gray surfaces (#111, #222) — always use warm-tinted variants - Blue-white text (#eef3ff) — use cream (#f0ece8) to maintain warmth - `outline: none` without a visible focus replacement ## Decisions Log | Date | Decision | Rationale | |------|----------|-----------| | 2026-03-28 | Initial design system created | Created by /design-consultation with competitive research (Conductor.build, T3 Code, OpenAI Codex, Emdash) + 4 design voices | | 2026-03-28 | Geist Sans + JetBrains Mono (2 fonts only) | Emil review: 4 fonts creates cognitive gear-shifts on scan-heavy dashboards | | 2026-03-28 | 2px base border-radius (v1) | Full 0px risks looking unstyled. 2px reads as intentionally sharp while feeling designed. | | 2026-04-05 | 0px border-radius everywhere | Hard edges are the identity. With warm surfaces and inset highlights providing depth, rounding adds nothing. Zero radius is the most honest expression of Industrial/Warm Terminal. | | 2026-03-28 | Keep dot pulse, remove border heartbeat | Emil review: 4s border animation on 15+ cards is "decorative anxiety" with high perf cost. | | 2026-04-05 | Fresh design system: Warm Terminal | Every competitor converges on cool blue-gray. Warm charcoal with cream text and warm periwinkle accent creates instant visual distinction. | | 2026-04-05 | JetBrains Mono for display + data | Mono headlines in a mono-heavy dashboard create typographic cohesion instead of two competing voices. Free, open source, already in the codebase. | | 2026-04-05 | Warm periwinkle #8b9cf7 accent (not gold) | Gold collides semantically with amber attention state. Blue = clickable is muscle memory. Warm periwinkle fits the palette without signal confusion. | | 2026-04-05 | Brown-tinted surfaces, not neutral or blue-tinted | #121110 / #1a1918 / #222120 — warm undertone sets AO apart from every Linear clone. Light mode uses warm parchment #f5f3f0. | | 2026-04-05 | Added accessibility section | Missing from v1. Touch targets 44px min, WCAG AA contrast, focus-visible, prefers-reduced-motion. | | 2026-04-05 | Added component anatomy section | Missing from v1. Button states, input states, card structure, status pill, alert anatomy. | | 2026-04-05 | Added light mode rationale | v1 listed values without explaining why. Warm parchment base, white card float, desaturated accent. | | 2026-04-07 | `--status-respond` is amber, never red | Critique found Respond column using `--status-error` (red). Respond = human decision needed, not error. Separate token. See status colors table. | | 2026-04-07 | Two-stage delete confirmation pattern | P0 safety: trash button first click enters amber "kill?" state for 2s; second click confirms. No modal. In-place via CSS `::after` + JS class toggle. Prevents accidental agent termination. | | 2026-04-07 | Working card titles at full weight | P2: Working state is the primary operational state. Never dim active card titles. Dimming is reserved for Done/archived cards only. | | 2026-04-07 | No current-project group label in sidebar sessions | P2: Current project label in the sessions list is redundant — the project switcher chip 12px above already names the project. Only other projects need group labels. | | 2026-04-07 | Remove column shadows | P3: `18px/42px` column box-shadow created competing depth layers with card shadows. Border + background contrast does separation. No column-level shadow needed. | | 2026-04-07 | Topbar shows page name only, not project name | Minor: Topbar "vinesight-rn / kanban" duplicated project name visible in sidebar. Topbar now shows "Kanban" + freshness. | | 2026-04-07 | Diff size badges use `` with tooltip | Minor: S/M/L diff badges were opaque. `` gives meaning on hover without adding visual noise. | | 2026-04-27 | Sidebar always shows ALL projects' sessions | P0: Sidebar is not filtered by active project — `useSessionEvents` called without project filter, kanban filters client-side via `projectSessions` memo. |