- 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.
- 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)
- 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)
- 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.
| 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. |
| 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.
| 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. |
| 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). |
| 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 | 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.
- 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)
- 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
- 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
- enter/exit:
- 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 painton session cards for performance with 30+ cards
- 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: 2pxon:focus-visible. Neveroutline: nonewithout 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"witharia-levelon non-heading elements styled as headings. Status changes announced viaaria-liveregions.
- Single source of truth: This file is the authoritative design spec for the repo, including
packages/web/. Do not create a second package-levelDESIGN.md. - Tokens over raw values: In React/Tailwind code, use CSS variables from
packages/web/src/app/globals.cssinstead of hardcoded hex, rgba, ordark:overrides. - No inline styles: Avoid
style={{ ... }}for theme values. Use Tailwind utilities withvar(--token)or add a named class inglobals.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.csswhen a pattern is theme-sensitive, uses pseudo-elements, gradients, or repeats 3+ times. - Theme handling:
:rootholds light tokens and.darkholds dark overrides. Use tokenized classes likebg-[var(--color-bg-surface)], notbg-white dark:bg-[#1a1918].
These are the concrete token names used in packages/web/src/app/globals.css. New UI code should reference these names directly.
| 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 |
| 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 |
| 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 |
| 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 |
┌─ 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 paintfor 30+ card performance
| 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)
| 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
- 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
- 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)
- Mono data: IDs, hashes, timestamps, branch names, and PR numbers should use
font-monowith10-11pxsizing 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)]orborder-[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 inpackages/web/src/components/. - Sharp edges remain the rule: Do not reintroduce rounded cards/buttons as a package-level convention.
rounded-fullis reserved for dots, pills, and avatars.
- Use
contain: layout style paintandcontent-visibility: autoon session cards - Animate only
transformandopacity(GPU-composited). Never animatepadding,margin,height,width,border, orbox-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
- 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 propertiesscale(0)entry animations — start fromscale(0.95)withopacity: 0ease-inon UI elements — useease-outfor 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: nonewithout a visible focus replacement
| 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 <abbr> with tooltip |
Minor: S/M/L diff badges were opaque. <abbr title="Small (<100 lines)"> gives meaning on hover without adding visual noise. |