docs: add design research artifacts — briefs, token reference, screenshots

Comprehensive design research package for the ao dashboard, session
detail page, and orchestrator terminal. Produced via competitive analysis
of 14 products (Linear, Vercel, Railway, Fly.io, Inngest, WandB, LangSmith,
Supabase, and more) + Playwright CSS extraction from live sites + full
codebase audit.

Artifacts:
- docs/design/design-brief.md            Main design brief (v2, Playwright-updated)
- docs/design/session-detail-design-brief.md   /sessions/[id] design spec
- docs/design/orchestrator-terminal-design-brief.md  Orchestrator page spec
- docs/design/token-reference.css        Drop-in CSS replacement for globals.css
- docs/design/competitive-analysis-raw.md  Raw research notes, all 14 sites
- docs/design/design-brief-v1.md         Original text-only brief (pre-Playwright)
- docs/design/README.md                  Index + research methods summary
- docs/design/screenshots/linear-homepage.png   Playwright-captured screenshot
- docs/design/screenshots/railway-homepage.png  Playwright-captured screenshot

Key findings:
- Linear CSS token values verified via Playwright (body bg #08090A, accent
  #7070FF, Berkeley Mono monospace, type scale, radius, transitions)
- Recommended palette: #0C0C11 base (blue-cast dark vs current GitHub #0d1117)
- Highest-impact change: load Inter Variable via next/font/google
- Orchestrator terminal needs visual differentiation (violet accent, status strip)
- token-reference.css is ready to drop into packages/web/src/app/globals.css

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Prateek 2026-02-19 06:52:48 +05:30
parent 0e2ca70b0b
commit f599c3edc6
9 changed files with 2417 additions and 0 deletions

63
docs/design/README.md Normal file
View File

@ -0,0 +1,63 @@
# ao Dashboard — Design Research Artifacts
*Compiled February 2026 via competitive analysis, Playwright CSS extraction, and codebase audit.*
---
## Documents
| File | Description |
|------|-------------|
| [`design-brief.md`](./design-brief.md) | **Main design brief** — competitive analysis, full color palette, typography, all component specs, anti-patterns, implementation stack recommendation, and current codebase audit |
| [`session-detail-design-brief.md`](./session-detail-design-brief.md) | Design spec for `/sessions/[id]` — the single-agent investigation view |
| [`orchestrator-terminal-design-brief.md`](./orchestrator-terminal-design-brief.md) | Design spec for the orchestrator terminal — full-viewport command center with status strip |
| [`token-reference.css`](./token-reference.css) | **Ready-to-use CSS** — drop-in replacement for `globals.css` `@theme` block |
| [`competitive-analysis-raw.md`](./competitive-analysis-raw.md) | Raw research notes from all 14 competitor sites (Linear, Vercel, Railway, Fly.io, Inngest, Temporal, Grafana, WandB, LangSmith, Retool, Render, PlanetScale, Supabase, GitHub Copilot) |
| [`design-brief-v1.md`](./design-brief-v1.md) | Original v1 brief (text-only research, pre-Playwright CSS extraction) — kept for reference |
## Screenshots
| File | Description |
|------|-------------|
| [`screenshots/linear-homepage.png`](./screenshots/linear-homepage.png) | Linear.app captured via Playwright (311KB) — source for verified CSS token extraction |
| [`screenshots/railway-homepage.png`](./screenshots/railway-homepage.png) | Railway.app captured via Playwright (444KB) — visual palette reference |
---
## Research Methods
**Phase 1 — Text analysis**: Two parallel research agents analyzed 14 competitor product sites via WebFetch, extracting visual patterns, color systems, and design philosophy from HTML/CSS content.
**Phase 2 — Playwright CSS extraction**: Installed `@playwright/mcp` and extracted live CSS custom properties from [linear.app](https://linear.app) using `document.styleSheets` enumeration. This yielded ground-truth values for Linear's token system — the most rigorous competitive design data available without access to their Figma files.
**Phase 3 — Playwright screenshots**: Captured screenshots of Linear and Railway via headless Chromium for visual reference.
**Phase 4 — Codebase audit**: Read the entire `packages/web/` source (components, types, CSS tokens) to map research recommendations against the actual implementation. Produced implementation audit sections in each brief with prioritized delta tables.
---
## Key Findings
1. **Linear's verified token system** (via Playwright CSS extraction) is the closest design benchmark. See `design-brief.md §1`.
2. **Current dashboard uses GitHub-inspired colors** (`#0d1117` base). Recommended shift: blue-cast dark (`#0C0C11`). See `design-brief.md §7`.
3. **The 6-level attention zone system** (`merge → respond → review → pending → working → done`) is architecturally correct and well-implemented. Visual polish is the primary gap.
4. **Highest-impact single change**: load Inter Variable via `next/font/google`. Immediately elevates the typography to Linear/Supabase tier.
5. **Orchestrator terminal** needs visual differentiation (violet accent, status strip, full-viewport height) — currently indistinguishable from agent session pages.
---
## Design Token Quick Reference
The recommended palette is in [`token-reference.css`](./token-reference.css). The current palette is in `packages/web/src/app/globals.css`.
```
Current base: #0d1117 (GitHub blue-green dark)
Recommended base: #0C0C11 (neutral blue-cast dark)
Current accent: #58a6ff (GitHub blue)
Recommended accent: #5B7EF8 (blue-indigo, between Linear blue and brand purple)
```
---
*All Linear CSS values are ground-truth verified from live CSS. Railway values are visually estimated from screenshot. Other competitor values are from text/HTML analysis.*

View File

@ -0,0 +1,102 @@
# Competitive Visual Analysis — Raw Research Notes
*Compiled February 2026 via text/HTML analysis of 14 competitor sites*
*Used as source material for design-brief.md*
---
## Batch 1: Infrastructure & Developer Tool Dashboards
### Linear (linear.app)
**CSS token system** — Inter Variable, Berkeley Mono, Tiempos Headline. 9-level title scale. Quaternary text hierarchy.
Dark mode background: near-black `#08090A`. Panel bg: `#0F1011`. Brand accent: `#7070FF`.
Status colors: Blue `#4EA7FC`, Red `#EB5757`, Green `#27A644`, Orange `#FC7840`, Yellow `#F0BF00`.
Font weights: 300/400/510/590/680 (variable font, non-standard values).
*Ground-truth values confirmed via Playwright CSS extraction — see design-brief.md.*
### Vercel (vercel.com)
Pure black/white: `#000000` dark bg, `#FAFAFA` light bg. Zero saturation philosophy.
Geist + Geist Mono (proprietary typefaces). Status dots: colored circles inline with resource names.
Tone: Austere technical authority.
### Railway (railway.app)
Multi-theme architecture. Signature dark: `hsl(250, 24%, 9%)``#13111C` (dark desaturated purple).
Inter + Inter Tight + JetBrains Mono + IBM Plex Serif. Semantic palette: `hsl(152)` green, `hsl(1)` red.
Shadow: `0 0 30px hsla(0,0%,30%,.25)` — diffuse ambient glow. Vaporwave Easter egg theme.
Tone: Playful sophistication.
### Fly.io
Dark navy `#0A0E27`. Electric purple/magenta CTAs.
Fricolage Grotesque (quirky headline) + Mackinac (warm serif body) + Fragment Mono.
Most typographically adventurous stack in the group.
Tone: Confident and rebellious.
### Inngest (inngest.com) — workflow orchestration
Stone-950 `#0A0A0A` warm black. "Inngest Lux" amber `#CBB26A`. Supplementary: green `#2C9B63`, rust `#CB5C32`.
Whyte + Whyte Inktrap (display), Circular (body). Top-border accent cards, `rounded-xl` 12px.
Grid background texture at 0.3 opacity. Stone-400 borders (`rgba(white, 0.1)`).
Tone: Warm enterprise dark mode.
### Temporal (temporal.io) — workflow orchestration
Deep ultraviolet/indigo backdrop, star-grid hero pattern. Magenta `~#D946EF` CTAs.
Rainbow gradients in testimonial sections. System sans-serif typography.
Tone: Cosmic reliability.
### Grafana (grafana.com)
White background (the outlier). Orange `#F46800` brand. Blue `#0066FF` CTAs.
Generous whitespace. High information density in actual product panels.
Tone: OSS pragmatism.
### Weights & Biases / wandb.ai
`#1A1C1F` charcoal. Cards: `#212429`/`#282A2F`. Cyan `#00AFC2` accent. Yellow-gold CTA gradient `#FFCC33→#FFAD33`.
Borders: `1px solid #34373C`. Source Serif 4 (headings!), Source Sans 3, Source Code Pro.
Upward box shadow: `0px -1px 16px rgba(10,14,21,0.5)`. Border-radius 816px.
Tone: Scientific gravitas meets ML ambition.
---
## Batch 2: LLM Observability, Internal Tools & Databases
### LangSmith (smith.langchain.com) — LLM observability
`#030710` near-black with blue cast. Electric blue `#4D65FF` interactive. JetBrains Mono as PRIMARY font.
Dense trace tree (left panel) + detail (right panel). Green ✓ / Red ✗ status icons.
Run types distinguished by icon + hierarchy position. Heat maps for evaluation results.
Tone: Analytical transparency. "Know what your agents are really doing."
### Retool (retool.com)
Dark charcoal bg. Px Grotesk (display) + Saans variable font (body). Canvas + sidebar model.
Tone: Enterprise sophistication with approachability.
### Render (render.com) — deployment dashboard
Purple-900 → orange gradient in announcement banner. Dark mode. Standard semantic status colors.
Service cards with inline status. Log explorer: terminal-style dark view.
Tone: Trustworthy speed and simplicity.
### PlanetScale (planetscale.com) — database dashboard
OKLCH-based green scale. Both light and dark mode. Lato + Montserrat.
Schema diff: green additions, red deletions. 117 documented UI screens, 49 components.
Branch status badges, deploy request workflow panels.
Tone: Engineering credibility at scale.
### Supabase (supabase.com)
Brand green: `#3ECF8E` / `#34B27B`. Background: `#11181C`. Inter font.
Built on Radix UI + Tailwind 3.4 + shadcn/ui. Table editor dense/spreadsheet-like.
Left sidebar: Table Editor / Database / Auth / Storage / Edge Functions / Realtime.
RLS enabled/disabled badge in table editor.
Tone: Radical developer empathy.
### GitHub Copilot agent mode (VS Code)
Deep purple brand. VS Code dark background inherits. Sequential tool invocation list.
Each tool call: labeled step, collapsible details. Undo Last Edit control after edits.
Agent "thinking" state: animated ellipsis. No dramatic animations — UI recedes for code.
Tone: Capability amplification.
---
## Cross-Cutting Patterns
**Dark mode as default**: LangSmith, Supabase, GitHub Copilot, Retool, WandB — all dark native.
**Status color convergence**: Green success / Red error / Amber warning universal. Differentiation in shape + animation.
**Typography split**: Monospace-forward (LangSmith with JetBrains Mono as primary) vs. clean sans (Supabase with Inter, Retool with Px Grotesk).
**Semantic color as brand**: Supabase `#3ECF8E` green = inseparable from identity. PlanetScale OKLCH green scale.
**Progressive disclosure**: All use expandable rows, tabs, collapsible sections to manage complexity.
**AI activity representation**: Copilot = inline step disclosure in dev environment. LangSmith = separate observability dashboard with trace trees. ao sits between these — a monitoring dashboard for the agent lifecycle.

View File

@ -0,0 +1,637 @@
# Agent Orchestrator Dashboard — Design Brief
*Research-backed design specification for the ao dashboard*
---
## Product Context
The Agent Orchestrator dashboard is **mission control for parallel AI coding agents**. Users are senior engineers and CTOs who routinely spawn 1030 agents at once and need to:
1. Triage at a glance (who needs me right now?)
2. Merge PRs that are ready
3. Intervene on blocked or stuck agents
4. Understand what each agent is doing without attaching to it
Primary interaction model: **scan → identify → act**. Not explore, not browse. The interface must surface actionable items immediately and suppress noise. Speed and density beat friendliness. This is closer to Grafana or an APM dashboard than to a product onboarding flow.
---
## 1. Competitive Visual Analysis
### Linear (linear.app)
**Palette**: Pure token-based CSS system — near-black in dark mode, warm neutrals. No vibrant accents; status colors (green, red) are the only chromatic moments. Custom property hierarchy goes four levels deep (`--color-text-primary` → `--color-text-quaternary`).
**Typography**: Inter Variable exclusively. Nine-level title scale. `text-micro` (the smallest label) at 910px. Body and UI labels use a 6-level named scale: large → regular → small → mini → micro → tiny.
**Density**: Marketing site is airy (128px+ section gaps); the product is tighter. Cards use `rightBottomFade` gradient corners — no drop shadows.
**Status indicators**: Small colored circles — the Linear "state dot" became an industry pattern. Green for done/success, red for blocked/error.
**Tone**: Engineered restraint. Every element earns its place. Nothing decorative.
**Takeaway for ao**: The quaternary text hierarchy and named type scale enable information density without visual chaos. Adopt a similarly granular token system.
---
### Vercel (vercel.com)
**Palette**: Binary extremes — `#000000` in dark mode, `#FAFAFA` in light. Zero saturation. Borders are `rgba(255,255,255,0.08)` — barely visible. Status colors (green = Ready, red = Error, amber = Building) are the only chromatic intrusions.
**Typography**: Geist and Geist Mono — Vercel's proprietary type system. Clear functional split: sans for UI copy, mono for code/deployment names.
**Density**: Very sparse in marketing; the dashboard itself is moderate — deployment list rows are compact with inline status dots.
**Status indicators**: Small colored dots (810px) inline with resource names. Unmistakable but minimal.
**Tone**: Austere technical authority. "We don't need color to project confidence."
**Takeaway for ao**: The deployment list row pattern (name + status dot + metadata on one line) is directly applicable to session cards in compact mode. Monochrome discipline keeps status colors maximally signal-to-noise.
---
### Railway (railway.app)
**Palette**: `hsl(250, 24%, 9%)``#13111C` — dark desaturated purple-tinted background. Not pure black, not navy. A signature color. Semantic palette: `hsl(152)` green, `hsl(1)` red, `hsl(44)` yellow, `hsl(180)` cyan.
**Typography**: Inter + Inter Tight for UI; JetBrains Mono for code; IBM Plex Serif as an accent. The three-family system ranges from utilitarian to editorial.
**Density**: Moderate. Services presented as visual cards in a grid. Shadow: `0 0 30px hsla(0,0%,30%,.25)` — diffuse ambient glow, not directional.
**Tone**: Playful sophistication. "Ship software peacefully" — the vaporwave easter egg theme reveals a team with personality.
**Takeaway for ao**: The purple-tinted dark base (`#13111C`) is more distinctive than pure black without being loud. Worth considering as the background. The ambient glow shadow on cards is more premium than flat borders.
---
### Fly.io
**Palette**: Deep navy `#0a0e27`. Electric purple/magenta CTAs. High contrast.
**Typography**: Fricolage Grotesque (quirky geometric grotesque) for headlines, Mackinac (warm serif) for body, Fragment Mono for code. The most typographically adventurous stack in the group.
**Tone**: "Fearless confidence." Loud type choices signal that the team has opinions.
**Takeaway for ao**: The adventurous type choice is a lesson about brand identity. ao should pick *one* distinctive typographic decision and commit to it.
---
### Inngest (inngest.com) — workflow orchestration
**Palette**: Stone-950 `#0a0a0a` — warm black (not cool/blue). "Inngest Lux" amber `#CBB26A` as primary accent. Supplementary: green `#2C9B63`, rust `#CB5C32`, purple `#655279`. Stone-400 `rgba(white, 0.1)` for borders.
**Typography**: Whyte + Whyte Inktrap for display; Circular for body. Font investment signals brand maturity.
**Density**: Generous. 80rem max-width, 2024px padding, 23 column grids.
**Cards**: Stone-900 background, top-border accent lines, `rounded-xl` (12px) radius.
**Tone**: Warm enterprise. The stone palette (not cold gray) gives unexpected approachability.
**Takeaway for ao**: The amber accent color for an infrastructure product is distinctive and memorable. Grid background textures at 0.3 opacity add depth without clutter.
---
### Temporal (temporal.io) — workflow orchestration
**Palette**: Deep ultraviolet/indigo backdrop, star-grid hero pattern. Magenta `~#d946ef` CTAs. Green badge for system status.
**Tone**: "Cosmic reliability." Rainbow gradients in testimonial sections break monochrome tension.
**Takeaway for ao**: The star-grid subtle texture on a dark background is a quality signal without being decorative noise.
---
### Grafana — high-density monitoring
**Palette**: White background (counterpoint to the dark-mode consensus). Orange `#F46800` brand. Blue `#0066ff` CTAs.
**Density**: Monitoring tools require extreme density — Grafana panels pack many metrics per square centimeter. Time-series panels, gauge panels, table panels all coexist at density levels that would horrify Linear's designers.
**Tone**: OSS pragmatism. The white background is a deliberate "we are transparent and open" statement.
**Takeaway for ao**: ao should reference Grafana for density targets, not aesthetic. The ao dashboard with 30 sessions needs panel-level density discipline.
---
### Weights & Biases / wandb.ai — ML experiment tracking
**Palette**: `#1A1C1F` charcoal background. `#212429` card surface. `#00AFC2` cyan accent. Yellow-gold gradient CTAs (`#FFCC33` → `#FFAD33`). Borders: `#34373C`. Secondary text: `#ADB0B5`.
**Typography**: Source Serif 4 (headings), Source Sans 3 (body), Source Code Pro (code). The *only* product in this group using a serif for headings — gives academic/research gravitas.
**Cards**: `border-radius: 816px`. Explicit `1px solid #34373C` borders. Upward-direction box shadow: `0px -1px 16px rgba(10,14,21,0.5)`.
**Tone**: Scientific gravitas meets ML ambition. Dense, trustworthy, research-credibility.
**Takeaway for ao**: The `#1A1C1F` charcoal with `#34373C` explicit borders is the most production-battle-tested dark card system in this research. The upward shadow (rather than downward) creates a distinctive floating-from-below effect.
---
### LangSmith (smith.langchain.com) — LLM observability
**Palette**: `#030710` near-black with blue cast. Electric blue `#4d65ff` primary interactive. Green checkmarks for success, red icons for failure.
**Typography**: JetBrains Mono as the *primary* font — not relegated to code blocks. This is the most distinctive typographic choice for a developer observability tool.
**Density**: Extremely high. Trace tree on left, detail panel on right. Many rows of data: token counts, latency, inputs, outputs. Progressive disclosure via expandable rows.
**Status system**: Hierarchical run tree with icon-based type indicators (chain icon for chain runs, LLM icon for LLM runs). Green ✓ / red ✗ for success/failure.
**Tone**: Analytical transparency. "Know what your agents are really doing."
**Takeaway for ao**: LangSmith is the closest design analogue to ao — it's a dashboard for understanding AI agent behavior. The hierarchical trace tree, dense row layout, and JetBrains Mono-first typography are all directly relevant. ao is LangSmith for the agent lifecycle rather than the agent's internal trace.
---
### GitHub Copilot Workspace — AI agent UI
**Palette**: Deep purple Copilot brand, VS Code dark background.
**Agent step display**: Each tool invocation shown as a labeled step in the chat panel ("Analyzing files...", "Running tests...", "Proposing edits...") — sequential, transparent, collapsible.
**Status for AI activity**: Animated ellipsis/spinner for "thinking"; static result for "complete." Undo controls appear after edit.
**Tone**: "Capability amplification." UI recedes so work foregrounds.
**Takeaway for ao**: The pattern of **transparent sequential step disclosure** (each tool call labeled and visible) is the right model for the terminal/activity panel. ao should show what the agent is doing in the same style: labeled steps, not a raw log dump.
---
### Supabase (supabase.com)
**Palette**: `#3ECF8E` mint-green as brand-primary (also the success color). `#11181C` dark background. Built on Radix UI + Tailwind + shadcn/ui.
**Tone**: "Radical developer empathy." Open source, portable, one of us.
**Takeaway for ao**: The shadcn/ui + Tailwind stack is a practical choice for ao's implementation. Supabase's success with it validates the approach for a serious developer tool.
---
## 2. Design Direction
### Philosophy
**Mission control, not social feed.** The ao dashboard should feel like a fighter pilot's heads-up display — dense, high-contrast, every element load-bearing. Closest visual analogues: Vercel deployment list + Grafana panel density + LangSmith trace density + Linear state dot pattern.
**Dark mode native.** Not dark mode as a feature — dark mode as the only mode designed with conviction. Light mode can exist but shouldn't drive design decisions.
**Color = signal, not decoration.** Every chromatic element is semantic. The palette outside of status colors is near-monochrome. This maximizes the signal of status colors.
---
### Color Palette
#### Base Palette
| Token | Hex | Usage |
|-------|-----|-------|
| `--bg-base` | `#0C0C11` | Page/app background |
| `--bg-surface` | `#141418` | Card backgrounds |
| `--bg-elevated` | `#1C1C24` | Hover states, terminal background, popover surfaces |
| `--bg-subtle` | `#232330` | Input backgrounds, code blocks |
| `--border-subtle` | `#222230` | Lowest-visibility borders |
| `--border-default` | `#2E2E40` | Standard card borders |
| `--border-strong` | `#3E3E54` | Focus rings, interactive borders |
#### Text Hierarchy
| Token | Hex | Usage |
|-------|-----|-------|
| `--text-primary` | `#EEEEF4` | Main content, session names, ticket titles |
| `--text-secondary` | `#8888A4` | Metadata, timestamps, secondary labels |
| `--text-tertiary` | `#50506A` | Disabled states, de-emphasized content |
| `--text-inverse` | `#0C0C11` | Text on colored buttons |
*Note: Text has a slight blue cast (`EEEEF4` not `EEEEEE`) to harmonize with the blue-cast background.*
#### Status Colors (semantic — must not be overridden for decoration)
| Token | Hex | State | Usage |
|-------|-----|-------|-------|
| `--status-working` | `#5B7EF8` | Working/active agent | Pulsing dot, card left-border, zone header |
| `--status-ready` | `#22C55E` | Needs human action (merge/review) | Dot, merge button, zone header |
| `--status-attention` | `#F59E0B` | Blocked, waiting for response, CI failing | Dot, zone header, badge |
| `--status-idle` | `#6B6B8A` | Agent idle, not running | Dot (dim) |
| `--status-done` | `#3E3E54` | Session closed/archived | Dot (very dim), text de-emphasized |
| `--status-error` | `#EF4444` | Crash, hard failure, exited with error | Dot, badge |
#### Interactive Accent
| Token | Hex | Usage |
|-------|-----|-------|
| `--accent-blue` | `#5B7EF8` | Links, focus rings, active nav items, working state |
| `--accent-blue-hover` | `#7B9CFB` | Hover on blue elements |
| `--accent-blue-subtle` | `rgba(91,126,248,0.12)` | Subtle highlight backgrounds |
#### Full Dark Surface System
The surface layers use a consistent `+812 lightness step` between each level (in OKLCH terms), so layering always reads as elevation:
```
Background: #0C0C11 (L≈5%)
Surface: #141418 (L≈8%) ← cards
Elevated: #1C1C24 (L≈12%) ← hover, dropdowns
Subtle: #232330 (L≈15%) ← inputs, code
```
---
### Typography
#### Font Stack
```
UI sans-serif: Inter Variable (weights 300700)
fallback: -apple-system, system-ui
Code/mono: JetBrains Mono (weights 400, 600)
fallback: 'Fira Code', Menlo, monospace
```
Inter Variable for all UI prose. JetBrains Mono for: session IDs, branch names, commit hashes, terminal output, agent status messages, any data that is "produced by a machine."
No display typeface. No serif. The monospace *is* the personality — it signals "this is infrastructure" without needing a quirky grotesque.
#### Type Scale
| Name | Size | Weight | Line Height | Usage |
|------|------|--------|-------------|-------|
| `zone-label` | 10px | 600 | 1 | Attention zone headers (uppercase, 0.1em tracking) |
| `label` | 11px | 500 | 1 | Status badges, tag text, column headers |
| `caption` | 11px | 400 | 1.4 | Timestamps, secondary metadata (mono) |
| `body-sm` | 12px | 400 | 1.5 | Card metadata rows, description lines |
| `body` | 13px | 400 | 1.5 | Primary body copy within cards |
| `card-title` | 14px | 500 | 1.4 | Ticket/issue titles on session cards |
| `section` | 13px | 600 | 1 | Section headings within panels |
| `page-title` | 18px | 600 | 1.2 | Dashboard title, view names |
Inspired by Linear's granular scale. The 1014px range covers 90% of the UI.
---
### Component Style
**No drop shadows.** Elevation via background color — `--bg-surface` card on `--bg-base` page. Borders provide edge definition.
**3px left-border accent strips** on session cards to indicate state at a glance (colored by status). This is faster to scan than dots — the entire left edge of the card communicates state without requiring focus.
**Pill badges** for CI status, review status, and labels. Height: 20px. Padding: 0 8px. Border-radius: 10px (fully rounded). Background at 12% opacity of the status color, text at 100% opacity. No solid-fill badges except for the merge button.
**Consistent 6px border-radius** for cards. 4px for badges and inputs. 8px for modals and dropdowns.
**1px borders in `--border-default`** on cards. On hover, transitions to `--border-strong`. No box shadows.
---
### Density
**Target: 68 session cards visible without scrolling** in the "Working" zone at 1440px viewport width.
This requires:
- Card height: ~160px (compact) or ~200px (expanded with CI detail)
- Card width: ~280320px
- Gap between cards: 12px
- Zone header height: 32px
- Padding: 16px page margin
Three columns at 1280px, four columns at 1920px. Grid is `auto-fill` with `minmax(260px, 1fr)`.
The density target is closer to Grafana than Linear. This is not a spacious single-project view — it's 30 agents at once.
---
### Animation and Motion
**Guiding principle: motion must be informative, never decorative.**
| Element | Animation | Spec |
|---------|-----------|------|
| Working state dot | Pulse (box-shadow ring expands) | `2s ease-in-out infinite` |
| CI pending badge | Spinner icon only (no layout shift) | `1.5s linear infinite` |
| Card state transition | Background + border color change | `transition: 200ms ease` |
| New session card | Fade in + 4px slide up | `150ms ease-out` |
| Session removal | Fade out | `200ms ease-in` |
| Terminal open/close | Height expand/collapse | `200ms ease` with `overflow: hidden` |
| Merge button hover | Translate Y -1px | `100ms ease` |
| Status badge change | Cross-fade | `150ms ease` |
No page transition animations. No parallax. No entrance animations on initial load — just appear.
The pulsing activity dot is the *only* continuous animation at rest. Everything else is triggered by state change or user interaction.
---
### Iconography
**Icon library: Lucide Icons** (used by shadcn/ui, 2px stroke, clean geometric forms).
Specific icons to use:
- Session state: `Circle` (dot for idle), `RefreshCw` (pulsed for working), `CheckCircle2` (done), `XCircle` (error)
- CI: `CheckCircle2` (pass), `XCircle` (fail), `Loader2` (pending/running)
- Git: `GitBranch`, `GitPullRequest`, `GitMerge`
- Review: `MessageSquare`, `ThumbsUp`, `ThumbsDown`
- Terminal: `Terminal`
- Attention: `AlertTriangle`, `Bell`
- Merge action: `GitMerge`
Icon sizes:
- Status dots/indicators: 8px (CSS circles, not SVG)
- Inline with text: 14px
- Action buttons: 16px
- Empty state illustrations: 40px
---
## 3. Component Designs
### Session Card
The primary unit of the dashboard. Every card represents one agent session.
**Dimensions**: 280320px wide, 156px tall (compact) / 200px tall (with CI details expanded).
**Anatomy** (top to bottom):
```
┌─╴[status-strip]╶──────────────────────────────────┐
│ │
│ ● working ao-58 [···] │ ← row 1: status + session ID + menu
│ │
│ Implement UI/UX research dashboard │ ← row 2: ticket title (14px, 500)
│ GitHub #58
│ │
│ ⎇ session/ao-58 ↑ PR #104 │ ← row 3: branch + PR link
│ │
│ ✓ CI passing ✓ Approved 3m ago │ ← row 4: CI + review + timestamp
│ │
│ [ Terminal ] [ Merge PR → ] │ ← row 5: actions (conditional)
│ │
└─────────────────────────────────────────────────────┘
```
**Visual details**:
- **Left border strip**: 3px wide, full card height, color = status color. This is the fastest-scan element. On a 30-card dashboard, these strips form a left-edge column of colors that communicate zone membership instantly.
- **Background**: `--bg-surface` (`#141418`)
- **Border**: `1px solid --border-default` (`#2E2E40`)
- **Border-radius**: 6px
- **Padding**: 12px 14px
- **Session ID** (row 1): 11px JetBrains Mono, `--text-tertiary`, top-right. Not primary — just identification.
- **Status indicator** (row 1 left): 8px filled circle, color by state, + text label 11px `--text-secondary`. For working state, the circle has a CSS `box-shadow` pulse ring (not the circle itself scaling — avoiding layout shift).
- **Ticket title** (row 2): 14px Inter 500, `--text-primary`. Max 2 lines, `overflow: ellipsis`. Below it: tracker reference (GitHub #N or Linear INT-N) in 11px `--text-tertiary`.
- **Branch** (row 3): 11px JetBrains Mono, `--text-secondary`, `⎇` prefix. PR link to the right: `↑ PR #N` in 11px, `--accent-blue`.
- **Status row** (row 4): CI badge + review badge + relative timestamp. All 11px. Left-aligned badges, timestamp right-aligned.
- **Actions row** (row 5): Only shown when human action is available. Merge button right-aligned; Terminal button left-aligned. Row absent if no actions needed (saves vertical space for working-only cards).
**States**:
| State | Left strip | Card border | Card background |
|-------|-----------|-------------|----------------|
| Working | `--status-working` blue | default | default |
| Ready/Merge | `--status-ready` green | `rgba(34,197,94,0.2)` | `rgba(34,197,94,0.03)` |
| Attention | `--status-attention` amber | `rgba(245,158,11,0.2)` | `rgba(245,158,11,0.03)` |
| Error | `--status-error` red | `rgba(239,68,68,0.2)` | `rgba(239,68,68,0.03)` |
| Done | `--status-done` dim | `--border-subtle` | `--bg-base` (recedes) |
The tinted border and very-subtle tinted background for Ready/Attention states make entire cards scannable by zone membership without relying on the strip alone.
---
### Activity Indicator
The dot + label system indicating what an agent is doing right now.
**Dot specs**: 8px × 8px circle, `border-radius: 50%`, `display: inline-block`.
**States**:
```
● Working — #5B7EF8 filled, + animated ring pulse
● Idle — #6B6B8A filled, static
● Ready — #22C55E filled, static (no animation — green is enough signal)
● Exited — #3E3E54 filled, static
● Error — #EF4444 filled, static
```
**Pulse animation for Working state**:
```css
@keyframes activity-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(91, 126, 248, 0.4); }
50% { box-shadow: 0 0 0 4px rgba(91, 126, 248, 0); }
}
.dot--working {
background: #5B7EF8;
animation: activity-pulse 2s ease-in-out infinite;
}
```
The ring expands and fades — it doesn't change dot size, so no layout shift. Period is 2s — feels alive but not anxious.
**Label alongside dot**: 11px Inter 500, same color as dot. Shown on cards. In condensed views (list mode), only the dot.
**In zone headers**: A larger version (10px dot) with the zone count badge.
---
### CI Status Badge
**Anatomy**: `[icon] [text]` in a pill container.
**Dimensions**: height 20px, padding 0 8px, border-radius 10px.
**Visual spec per state**:
| State | Background | Text | Border | Icon |
|-------|-----------|------|--------|------|
| Passing | `rgba(34,197,94,0.12)` | `#22C55E` | none | `CheckCircle2` 12px |
| Failing | `rgba(239,68,68,0.12)` | `#EF4444` | none | `XCircle` 12px |
| Running | `rgba(91,126,248,0.12)` | `#5B7EF8` | none | `Loader2` 12px, spinning |
| Skipped | `rgba(107,107,138,0.12)` | `#6B6B8A` | none | `Minus` 12px |
| Queued | `rgba(245,158,11,0.12)` | `#F59E0B` | none | `Clock` 12px |
**Text labels**: "Passing", "Failing", "Running", "Queued". 11px Inter 500. Never just a color with no text (accessibility).
**Multiple CI jobs**: Show the worst-state badge. On hover, expand to show all job names + individual states in a popover (dark background, same visual system).
**Position on card**: Row 4, inline with review badge and timestamp.
---
### PR Merge Button
This is the highest-priority action on the dashboard. When ready, it must visually compete for attention.
**Ready to merge**:
```
background: #22C55E
color: #0C0C11 (dark text on green)
border-radius: 6px
height: 28px
padding: 0 12px
font: 12px Inter 600
icon: GitMerge 14px (left of text)
label: "Merge PR"
hover: transform: translateY(-1px), brightness(1.05)
active: transform: translateY(0)
```
The green button at 28px tall on a dark card background is the most visually dominant element on the Ready card. It should be.
**Blocked — CI failing**:
```
background: #1C1C24
color: #50506A
border: 1px solid #2E2E40
cursor: not-allowed
label: "Merge PR" (same label)
tooltip: "CI failing — 2 checks must pass"
```
**Blocked — review required**:
```
Same as CI failing
tooltip: "Awaiting review approval"
```
**Blocked — conflicts**:
```
background: rgba(239,68,68,0.12)
color: #EF4444
border: 1px solid rgba(239,68,68,0.3)
label: "Conflicts"
```
**After merge confirmation** (optimistic UI):
```
background: rgba(34,197,94,0.12)
color: #22C55E
label: "Merged ✓"
```
The merge button only appears in the card actions row (row 5) when a PR exists. In zone view, the "Needs Merge" zone header's count badge pulses softly green when there are merge-ready sessions.
---
### Terminal Panel
The embedded terminal for a session — shows raw agent output, can be used for interactive access.
**Location**: Expands below the card (pushes other cards down in the grid) or opens as a right-side drawer panel (preferred for 30-agent view — doesn't reflow the grid).
**Right-side drawer variant** (recommended):
- Width: 480px or 40% of viewport, whichever is larger
- Slides in from right: `transform: translateX(100%)``translateX(0)`, 200ms ease
- Overlay: `rgba(0,0,0,0.4)` backdrop
- Header: session name + `[Detach]` + `[Close ✕]`
**Terminal area**:
```
background: #0A0A0F (slightly darker than page bg — the terminal is "deeper")
font: JetBrains Mono 13px / 1.5 line height
text color: #D4D4D8 (standard terminal foreground — not pure white)
cursor: block cursor, #5B7EF8 (brand blue, not white — distinguishes from content)
scrollbar: 4px, --border-default color, no track
padding: 12px 16px
```
**Log entry prefixes**: Timestamp in `--text-tertiary`, then content. System messages from ao itself (not the agent) in `--status-attention` amber so the user can distinguish orchestrator-injected messages from agent output.
```
10:23:41 Spawning agent on branch session/ao-58...
10:23:44 Agent ready. Claude session: f81637f1
10:24:12 Running tests...
10:24:45 Tests passed. Committing.
```
**Interactive input**: Standard xterm.js implementation. When the agent is running and has accepted stdin, show a `[Agent has control]` banner. When the orchestrator has injected a message, briefly highlight the injected text in `rgba(91,126,248,0.2)`.
**Resize handle**: 4px drag handle on left edge of the drawer. Minimum 320px, maximum 60vw.
---
### Attention Zone Headers
The dashboard is divided into horizontal zones. Each zone is a group of session cards. The zone header is the navigation and triage anchor.
**Zones (ordered by priority, top to bottom)**:
1. **Needs Merge** — PRs approved + CI passing, waiting for you to click Merge
2. **Needs Response** — review comments, CI failures, conflicts requiring human input
3. **Working** — agents actively doing their job, no action needed
4. **Idle** — agents running but inactive
5. **Done** — completed sessions ready for cleanup
**Zone header anatomy**:
```
[●] NEEDS MERGE ───────────────────────────────── [3]
```
- Left: Status dot (10px, zone's primary color)
- Zone name: 10px Inter 600, uppercase, 0.12em letter-spacing, `--text-secondary`
- Divider line: `flex: 1`, `1px solid --border-subtle` — full-width horizontal rule
- Right: Count badge — pill with session count, 11px, zone color text, subtle zone-color background
**Left border accent on zone name**: 2px left border in the zone's status color, 16px tall, centered vertically. Adds a crisp zone-identification stripe without dominating.
**Spacing**: 20px top margin above zone header (8px if first zone), 12px bottom margin before cards.
**Visual differentiation by zone**:
| Zone | Dot color | Name color | Count badge bg |
|------|-----------|------------|----------------|
| Needs Merge | `--status-ready` `#22C55E` | `#22C55E` | `rgba(34,197,94,0.12)` |
| Needs Response | `--status-attention` `#F59E0B` | `#F59E0B` | `rgba(245,158,11,0.12)` |
| Working | `--status-working` `#5B7EF8` | `#5B7EF8` | `rgba(91,126,248,0.12)` |
| Idle | `--status-idle` `#6B6B8A` | `#8888A4` | `rgba(107,107,138,0.12)` |
| Done | `--status-done` `#3E3E54` | `#50506A` | `rgba(62,62,84,0.12)` |
**Empty zones**: Collapse entirely (no header shown) unless the user has toggled "show empty zones" in settings. This keeps the viewport focused on actionable content.
**Zone collapse**: Clicking the zone header toggles card visibility. Collapsed state shows header + count only (32px). Useful for suppressing the "Done" zone after reviewing.
---
## 4. Inspiration References
### Vercel Deployments List
**URL**: https://vercel.com/dashboard (requires auth) / https://vercel.com
**Why relevant**: The deployment list row pattern — compact rows with inline status dot, deployment name (monospace), branch, and a merge-like "Promote to Production" action — is the closest existing product to what ao's session card in compact/list mode should be. Pure restraint: the status dot carries enormous signal on a dark background with nothing competing for attention.
### Linear Issue List
**URL**: https://linear.app
**Why relevant**: The tightest example of information-dense card/row design in developer tools. Each issue shows: state dot, priority indicator, title, assignee, estimate, and labels — all in a compact row with zero wasted pixels. The state dot system (colored circles for Todo/In Progress/Done/Cancelled) is directly applicable to ao's working/idle/ready/exited states. The row hover state (very subtle background change) is the right level of interactivity feedback.
### Grafana Dashboard (Dense Panel Layout)
**URL**: https://grafana.com/grafana/dashboards (community dashboards)
**Why relevant**: Grafana demonstrates that 30+ data panels can coexist in a single viewport without overwhelming users — when the data is organized by visual weight and semantic color. The panel border system (1px subtle borders, consistent padding), the metric display pattern (large number + small label + sparkline), and the zone/row organization all have direct analogues to ao's session grid.
### GitHub Actions Workflow Visualization
**URL**: https://docs.github.com/en/actions
**Why relevant**: The job graph in GitHub Actions shows dependency chains between CI steps with status-colored nodes (green pass, red fail, amber in-progress, gray skipped). This is the best existing model for representing "pipeline state at a glance." The status dot → label → duration pattern for each job is directly applicable to ao's CI status display.
### VS Code GitHub Copilot Chat Panel (Agent Mode)
**URL**: https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode
**Why relevant**: The sequential tool-invocation list ("Analyzing files... Running tests... Proposing edits...") inside the Copilot chat panel is the right model for ao's terminal activity feed — transparent, labeled steps rather than raw log output. Each step is collapsible. This makes AI activity *legible* rather than just *visible*.
---
## 5. What to Avoid
### Anti-patterns specific to this product category
**1. Spacious single-project layout**
Linear, Notion, and most project management tools are designed for one project at a time. ao has 30 sessions simultaneously. Do not adopt their card proportions. A 320×240px card works for one project on screen; at 30 sessions, it requires 4 viewports of scrolling.
**2. Status color overloading**
Using the same amber for "warning" as for "this agent has been idle for 3 hours" vs. "CI is failing" vs. "review changes requested" — these are different severities that should have different visual treatments. Define strict semantic rules for each status color and don't let the same color mean two different things.
**3. Badge spam**
Every badge must earn its visual weight. A card shouldn't have 6 badges. If a session is "Working, CI passing, review approved, 3 commits, labeled feature, linked to epic" — the rule is: show only the badges that are *different from the happy path*. CI passing and review approved are expected; don't show badges for them. Show CI *failing* and review *rejected*.
**4. Modals for primary actions**
The merge action should be one click on the card — not "click merge → confirm modal → click confirm in modal." Destructive actions (kill session, delete worktree) use confirmation. Merging a pre-approved PR is not destructive — it's the *goal*. One click.
**5. Sidebar-heavy navigation**
A wide left sidebar eats horizontal space that could show more columns of cards. ao has two states: dashboard view (all sessions) and single-session view (one terminal). Navigation should be a top bar (32px) or a narrow 48px icon rail, not a 240px sidebar.
**6. Light mode as the design authority**
If you design light mode first and adapt to dark, you'll get light-mode design thinking applied to dark backgrounds (gray instead of true near-black, drop shadows instead of border-based elevation, etc.). Dark mode must be the primary design direction.
**7. Animations that compete with status**
If both the working-state dot AND card borders AND zone headers are all animating simultaneously, the eye has no resting point. The *only* continuous animation is the working-state dot pulse. Everything else is transition-on-change.
**8. Full-width single-column layout**
Some dashboards present each item in a full-width row (like a GitHub notifications list). At 30 sessions, this creates an impossibly long page. The grid layout (34 columns) with zone headers is mandatory.
**9. Hiding the session terminal behind many clicks**
The terminal is a primary debugging surface. It should be one click from any session card — not buried in a detail page behind 3 navigation layers.
**10. Generic icon set without semantic consistency**
Mixing Heroicons for some components and FontAwesome for others, or using `info` icons for 6 different meanings. Pick Lucide (or Heroicons — but pick one) and define a mapping: each semantic concept has one icon, used consistently.
**11. Treating "Done" sessions identically to active ones**
Done sessions should visually recede. Lower contrast, dimmed colors, collapsed by default. The eye should naturally skip over them. Don't give them the same visual weight as "Needs Merge" sessions.
**12. Conflating "activity state" and "attention state"**
These are different dimensions:
- **Activity state**: Is the agent running, idle, or stopped? (working/idle/exited)
- **Attention state**: Does the human need to act? (needs merge/needs response/fine/done)
A working agent can be in "Needs Response" (CI failing while it runs). An idle agent can be in "Needs Merge" (PR ready, agent finished). The UI must show both — the left border strip shows attention state; the activity dot shows activity state.
---
## Implementation Notes
**Recommended stack**: Next.js 15 + Tailwind CSS + shadcn/ui (Radix UI primitives). This is what Supabase uses — validated for serious developer tooling at scale.
**Terminal**: xterm.js with `@xterm/addon-fit`. Use the dracula or custom dark theme matching the color system above.
**Real-time updates**: Server-Sent Events (already in ao's architecture) for status changes. Status transitions should animate (200ms ease) — not snap — so the user can track what changed.
**Design token implementation**: CSS custom properties on `:root`. All color tokens defined as custom properties, not Tailwind color classes directly. This enables runtime theme switching and makes the semantic token system enforceable.
**Accessibility**: Status indicators must not rely on color alone. Every status dot also has a text label. Badge text is always present (not icon-only). Focus rings use `--accent-blue` at 2px offset. Tab order follows visual reading order.
---
*Design brief compiled February 2026. Based on visual analysis of: Linear, Vercel, Railway, Fly.io, Inngest, Temporal, Grafana, WandB, LangSmith, Retool, Render, PlanetScale, Supabase, GitHub Copilot.*

737
docs/design/design-brief.md Normal file
View File

@ -0,0 +1,737 @@
# Agent Orchestrator Dashboard — Design Brief
*Research-backed design specification for the ao dashboard*
*Version 2 — Updated with Playwright CSS extraction from live sites*
---
## Product Context
The Agent Orchestrator dashboard is **mission control for parallel AI coding agents**. Users are senior engineers and CTOs who routinely spawn 1030 agents at once and need to:
1. Triage at a glance (who needs me right now?)
2. Merge PRs that are ready
3. Intervene on blocked or stuck agents
4. Understand what each agent is doing without attaching to it
Primary interaction model: **scan → identify → act**. Not explore, not browse. The interface must surface actionable items immediately and suppress noise. Speed and density beat friendliness. This is closer to Grafana or an APM dashboard than to a product onboarding flow.
---
## 1. Competitive Visual Analysis
### Linear (linear.app) — **Ground truth via CSS extraction**
*Playwright was used to extract exact token values from the live site. See `screenshots/linear-homepage.png`.*
**Verified color palette:**
- Body background: `rgb(8, 9, 10)``#08090A` — near-pure black with imperceptible warm cast
- Product panel background: `#0F1011` (CSS token `--color-bg-panel`)
- Marketing background: `#010102` (CSS token `--color-bg-marketing`)
- Brand accent / link color: `#7070FF` (CSS token `--color-accent`, `--color-brand-bg`)
- Focus ring: `#5E6AD2` (CSS token `--color-indigo`)
**Verified semantic status colors:**
```
--color-blue: #4EA7FC (informational, active)
--color-red: #EB5757 (error, critical)
--color-green: #27A644 (success, done)
--color-orange: #FC7840 (warning, medium priority)
--color-yellow: #F0BF00 (caution, low priority)
--color-teal: #00B8CC (informational variant)
--color-indigo: #5E6AD2 (brand, focus ring)
```
**Verified typography:**
- UI font: `"Inter Variable"` with `"SF Pro Display"` fallback
- **Monospace: `"Berkeley Mono"`** — a premium licensed monospace, not JetBrains Mono as commonly assumed. Fallback: `"SFMono Regular"`, `Consolas`, `Menlo`
- Display/serif: `"Tiempos Headline"` — premium editorial serif (used sparingly for marketing copy)
- Font weights (non-standard variable font values): light=300, normal=400, **medium=510**, **semibold=590**, bold=680
**Verified type scale:**
```
--text-tiny-size: 0.625rem = 10px
--text-micro-size: 0.75rem = 12px
--text-mini-size: 0.8125rem = 13px
--text-small-size: 0.875rem = 14px
--text-regular-size: 0.9375rem = 15px
--text-large-size: 1.0625rem = 17px
```
**Letter spacing:** Negative tracking at most sizes (0.010em to 0.013em for body/mini). Titles use 0.022em at large sizes.
**Verified spacing/radius:**
```
--radius-4: 4px (inputs, small badges)
--radius-6: 6px (buttons, cards)
--radius-8: 8px (modals, panels)
--radius-12: 12px (larger surfaces)
--radius-16: 16px (overlays)
```
**Verified transitions:**
```
--speed-quickTransition: 0.1s
--speed-regularTransition: 0.25s
```
**Shadows:** Zero visible shadows in dark mode (all `--shadow-*` = `var(--shadow-none)` on dark backgrounds). Elevation is purely background-based.
**Scrollbar:** `rgba(255,255,255,0.1)` at rest → `rgba(255,255,255,0.2)` hover → `rgba(255,255,255,0.4)` active. 6px width, 10px on hover.
**Tone**: Engineered restraint. Everything earns its place.
---
### Railway (railway.app) — **Visually analyzed via screenshot**
*See `screenshots/railway-homepage.png`.*
**Visual palette observations:**
- Background (hero): Dark desaturated blue-purple, approximately `hsl(250, 24%, 9%)``#13111C`
- App UI panel (visible in screenshot): Dark surface, same purple-tinted dark family
- CTA "Deploy" button: Solid purple, approximately `#7C3AED` (Tailwind `purple-600`)
- Navigation background: Near-black with slight purple tint
- Tab text (active): White. Inactive: muted gray.
- Announcement banner: Dark purple/indigo gradient
**Visual typography observations:**
- Font appears to be Inter (very clean, standard weight interpolation)
- Navigation uses medium weight (~500)
- Body copy uses regular weight (400)
**Key visual identity:**
- The purple-tinted dark is *not* the same as WandB's neutral charcoal or Linear's near-black. It has a visible chromatic quality — a cool indigo cast that reads immediately.
- The "Deploy →" CTA is the most vibrant element on the page — solid purple, high contrast.
---
### Vercel, Inngest, WandB, LangSmith, Grafana — Prior Research
*(Based on detailed text analysis from earlier research agents — see `competitive-analysis-raw.md`)*
**Vercel**: `#000000` pure black. Geist + Geist Mono (proprietary). Zero saturation, zero compromise.
**Inngest**: Stone-950 `#0A0A0A` warm black. Amber `#CBB26A`. Whyte + Circular fonts. The warmest palette in the group.
**WandB**: `#1A1C1F` charcoal. Cyan `#00AFC2` accent. Source Serif 4 headings (unique). Yellow-gold CTAs (`#FFCC33→#FFAD33`). Explicit `1px solid #34373C` card borders.
**LangSmith**: `#030710` near-black with blue cast. Electric blue `#4D65FF`. JetBrains Mono as primary font — the only site to use monospace as the main UI font.
**Grafana**: The outlier — white background. Orange `#F46800` brand. OSS pragmatism aesthetic.
---
## 2. Design Direction
### Philosophy
**Mission control, not social feed.** The ao dashboard should feel like a fighter pilot's HUD — dense, high-contrast, every element load-bearing. Visual analogues: Vercel deployment list discipline + Grafana panel density + LangSmith trace density + Linear state dot pattern.
**Dark mode native.** Not dark mode as a feature — dark mode as the only mode designed with conviction.
**Color = signal, not decoration.** Every chromatic element is semantic. Outside status colors, the palette is near-monochrome. This maximizes the signal-to-noise ratio of status indicators.
---
### Color Palette
Inspired by Linear's `#08090A` and Railway's purple-tinted dark, but finding its own position: **a very dark blue-black** that reads as technical/precise without being as stark as Vercel's pure black.
#### Base Palette
| Token | Hex | RGB | Usage |
|-------|-----|-----|-------|
| `--bg-base` | `#0C0C11` | 12,12,17 | Page/app background |
| `--bg-surface` | `#141419` | 20,20,25 | Card backgrounds |
| `--bg-elevated` | `#1C1C25` | 28,28,37 | Hover states, terminal bg, dropdowns |
| `--bg-subtle` | `#23232F` | 35,35,47 | Input backgrounds, code blocks |
| `--border-subtle` | `#22222E` | 34,34,46 | Lowest-visibility borders |
| `--border-default` | `#2E2E3E` | 46,46,62 | Standard card borders |
| `--border-strong` | `#3E3E52` | 62,62,82 | Focus rings, active borders |
*The blue tint is subtle (~hue 250, saturation ~12%) — enough to read as "technical" rather than "warm", but not as loud as Railway's purple.*
#### Text Hierarchy
Modeled on Linear's verified 4-tier system:
| Token | Hex | Usage |
|-------|-----|-------|
| `--text-primary` | `#EEEEF5` | Main content, titles, session names |
| `--text-secondary` | `#8888A6` | Metadata, timestamps, labels |
| `--text-tertiary` | `#50506A` | Disabled, de-emphasized |
| `--text-inverse` | `#0C0C11` | Text on solid-colored buttons |
*Slight blue tint (`EEEEF5` not `EEEEEE`) harmonizes with the blue-cast background.*
#### Status Colors — Semantic only, never decorative
| Token | Hex | State | Notes |
|-------|-----|-------|-------|
| `--status-working` | `#5B7EF8` | Agent actively running | Animated pulse |
| `--status-ready` | `#22C55E` | Needs merge / human to act | Highest priority signal |
| `--status-attention` | `#F59E0B` | Blocked, CI failing, review needed | Action required |
| `--status-idle` | `#6B6B8A` | Agent idle/paused | Low visual weight |
| `--status-done` | `#3E3E54` | Session complete | Visually recedes |
| `--status-error` | `#EF4444` | Crash, hard failure | Urgent |
*Note: Linear uses `#27A644` for green, `#EB5757` for red, `#F0BF00` for yellow. ao's status palette is aligned but not identical.*
#### Interactive Accent
| Token | Hex | Usage |
|-------|-----|-------|
| `--accent` | `#5B7EF8` | Links, focus rings, active nav, working state |
| `--accent-hover` | `#7B9CFB` | Hover state |
| `--accent-subtle` | `rgba(91,126,248,0.12)` | Highlight backgrounds |
*Derived from Linear's `#4EA7FC` blue + their `#7070FF` brand purple — splitting the difference at a mid-blue-purple that works for both "active" and "interactive" semantic meanings.*
#### Surface Elevation System
Uses background stepping (verified from Linear's approach — no shadows in dark mode):
```
Page: #0C0C11 (L ≈ 5%)
Surface: #141419 (L ≈ 8%) ← cards, panels
Elevated: #1C1C25 (L ≈ 12%) ← hovers, dropdowns, terminals
Subtle: #23232F (L ≈ 15%) ← inputs, inline code
```
---
### Typography
#### Font Stack
```css
/* UI — all prose, labels, body */
font-family: "Inter Variable", "SF Pro Display", -apple-system, system-ui, sans-serif;
/* Monospace — branch names, session IDs, terminals, all machine-produced data */
font-family: "Berkeley Mono", "JetBrains Mono", "SF Mono", Menlo, monospace;
```
**Primary recommendation: use `Inter Variable`** — universally available, verified in use by Linear and Supabase (both best-in-class developer dashboards). If budget permits a premium license, `Berkeley Mono` for monospace matches Linear's actual stack.
**Alternative monospace**: JetBrains Mono (free, used by LangSmith) — excellent fallback with strong developer tool credibility.
#### Type Scale
Directly derived from Linear's verified CSS token system:
| Name | Size (rem) | Size (px) | Weight | Usage |
|------|-----------|-----------|--------|-------|
| `tiny` | 0.625rem | 10px | 500 | Zone headers (uppercase, +0.10em tracking) |
| `micro` | 0.75rem | 12px | 400 | Timestamps, secondary metadata (mono) |
| `mini` | 0.8125rem | 13px | 400500 | Status badges, card metadata rows |
| `small` | 0.875rem | 14px | 500 | Card titles, primary labels |
| `regular` | 0.9375rem | 15px | 400 | Body copy within panels |
| `large` | 1.0625rem | 17px | 600 | Section headings, zone names |
**Letter spacing**: 0.010em to 0.013em for body/mini (matches Linear's verified values). Uppercase zone labels use +0.10em.
**Font weights (non-standard for Inter Variable)**:
- Regular: 400
- Medium: 500 (or 510 per Linear's precise variable-font value)
- Semibold: 590
- Bold: 680
---
### Component Style
**No drop shadows** — verified from Linear's CSS: all `--shadow-*` values resolve to `var(--shadow-none)` on dark backgrounds. Elevation is exclusively via background color stepping.
**1px explicit borders** (`--border-default: #2E2E3E`) on cards — verified from WandB's `1px solid #34373C` approach. Border-based cards, not shadow-based.
**Border-radius**: 6px for cards (Linear's `--radius-6`). 4px for badges and inputs (`--radius-4`). 8px for modals/dropdowns (`--radius-8`).
**Transitions**: `0.1s` for quick (hover states). `0.25s` for regular (state changes). Matches Linear's `--speed-quickTransition` and `--speed-regularTransition`.
---
### Density
**Target**: 68 session cards visible without scrolling in the "Working" zone at 1440px.
- Card height: ~156px compact / ~200px expanded
- Card width: `minmax(260px, 1fr)` — 3 columns at 1280px, 4 at 1920px
- Grid gap: 12px
- Zone header: 32px
- Page margin: 20px
This density is closer to Grafana than to Linear. The ao dashboard with 30 agents is a monitoring tool, not a project management tool.
---
### Animation and Motion
**One continuous animation only**: the working-state activity dot pulse. Everything else is triggered by state change.
```css
@keyframes activity-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(91, 126, 248, 0.4); }
50% { box-shadow: 0 0 0 4px rgba(91, 126, 248, 0); }
}
/* Matches Linear's --speed-regularTransition */
transition: background-color 250ms ease, border-color 250ms ease, color 250ms ease;
```
| Element | Animation | Duration |
|---------|-----------|----------|
| Working dot | Ring pulse (box-shadow) | 2s infinite |
| CI pending | Spinner rotation | 1.5s linear infinite |
| Card state change | bg + border color | 250ms ease |
| New card appear | Fade + 4px slide up | 150ms ease-out |
| Card removal | Fade out | 200ms ease-in |
| Terminal expand | Height (overflow: hidden) | 200ms ease |
| Merge button hover | translateY(1px) | 100ms ease |
---
### Iconography
**Lucide Icons** — the shadcn/ui default. 2px stroke weight, clean geometric forms. Pick one library and commit.
| Semantic | Icon |
|----------|------|
| Working state | Custom CSS dot (not SVG) |
| CI passing | `CheckCircle2` |
| CI failing | `XCircle` |
| CI running | `Loader2` (animated) |
| Branch | `GitBranch` |
| PR | `GitPullRequest` |
| Merge | `GitMerge` |
| Review comment | `MessageSquare` |
| Terminal | `Terminal` |
| Alert/attention | `AlertTriangle` |
---
## 3. Component Designs
### Session Card
The primary unit. Each card = one agent session.
**Anatomy:**
```
┌─ 3px status strip ──────────────────────────────────────┐
│ │
│ ● working session/ao-58 [···] │ ← 10px dot + 11px mono ID + menu
│ │
│ Implement UI/UX research dashboard │ ← 14px/500 ticket title (2 lines max)
│ GitHub #58 │ ← 11px tertiary tracker ref
│ │
│ ⎇ session/ao-58 ↑ PR #104 │ ← 11px mono branch + PR link
│ │
│ ✓ CI passing ✓ Approved 3m ago │ ← 11px badges + timestamp
│ │
│ [ Terminal ] [ Merge PR → ] │ ← actions (conditional row)
│ │
└──────────────────────────────────────────────────────────┘
```
**Visual spec:**
- Background: `--bg-surface` (`#141419`)
- Border: `1px solid --border-default` (`#2E2E3E`)
- Border-radius: `6px` (Linear's `--radius-6`)
- Padding: `12px 14px`
- Left status strip: `3px` wide, full height, color = zone's status color
- Shadow: none (Linear-verified: no shadows in dark mode)
**State variants** (tinted border + very subtle tinted background for scanability):
| State | Strip | Border | Background tint |
|-------|-------|--------|-----------------|
| Working | `#5B7EF8` | default | none |
| Needs Merge | `#22C55E` | `rgba(34,197,94,0.2)` | `rgba(34,197,94,0.03)` |
| Needs Attention | `#F59E0B` | `rgba(245,158,11,0.2)` | `rgba(245,158,11,0.03)` |
| Error | `#EF4444` | `rgba(239,68,68,0.2)` | `rgba(239,68,68,0.03)` |
| Done | `#3E3E54` | `--border-subtle` | `--bg-base` (recedes) |
---
### Activity Indicator
8×8px CSS circle (not SVG — simpler, no scaling artifacts).
```css
/* Working — the only continuous animation */
.dot--working {
background: #5B7EF8;
animation: activity-pulse 2s ease-in-out infinite;
}
/* States */
.dot--ready { background: #22C55E; } /* static — green is enough */
.dot--attention { background: #F59E0B; } /* static */
.dot--idle { background: #6B6B8A; } /* dim, static */
.dot--error { background: #EF4444; } /* static */
.dot--done { background: #3E3E54; } /* very dim, static */
```
Label alongside dot: 11px Inter 500, same color as dot.
---
### CI Status Badge
Height 20px, padding 0 8px, border-radius 10px (fully rounded pill).
| State | Background | Text | Icon |
|-------|-----------|------|------|
| Passing | `rgba(34,197,94,0.12)` | `#22C55E` | `CheckCircle2` 12px |
| Failing | `rgba(239,68,68,0.12)` | `#EF4444` | `XCircle` 12px |
| Running | `rgba(91,126,248,0.12)` | `#5B7EF8` | `Loader2` 12px, spinning |
| Queued | `rgba(245,158,11,0.12)` | `#F59E0B` | `Clock` 12px |
| Skipped | `rgba(107,107,138,0.12)` | `#6B6B8A` | `Minus` 12px |
Text: always present (never icon-only). 11px Inter 500. "Passing" / "Failing" / "Running".
Multiple jobs: show worst-state badge. Hover expands to popover with all job names.
---
### PR Merge Button
The highest-priority action. When ready, must visually dominate the card.
**Ready:**
```css
background: #22C55E;
color: #0C0C11; /* dark text on green */
height: 28px;
padding: 0 12px;
border-radius: 6px; /* Linear's --radius-6 */
font: 12px/1 "Inter Variable" 600;
/* icon: GitMerge 14px left of "Merge PR" label */
transition: transform 100ms ease, filter 100ms ease;
&:hover { transform: translateY(-1px); filter: brightness(1.05); }
```
**Blocked — CI/review:**
```css
background: #1C1C25;
color: #50506A;
border: 1px solid #2E2E3E;
cursor: not-allowed;
/* tooltip explains why */
```
**Conflicts:**
```css
background: rgba(239,68,68,0.12);
color: #EF4444;
border: 1px solid rgba(239,68,68,0.3);
label: "Conflicts";
```
**After merge (optimistic):**
```css
background: rgba(34,197,94,0.12);
color: #22C55E;
label: "Merged ✓";
```
One click, no confirmation modal. Merging a reviewed PR is the goal state — friction is the enemy.
---
### Terminal Panel
**Location**: Right-side drawer (preferred — doesn't reflow the 30-card grid).
```
Width: 480px or 40vw (whichever larger)
Slide in: transform translateX(100%) → translateX(0), 200ms ease
Backdrop: rgba(0,0,0,0.4)
```
**Terminal area:**
```css
background: #0A0A0F; /* deeper than card surface */
font: 13px/1.5 "Berkeley Mono", "JetBrains Mono", Menlo, monospace;
color: #D4D4D8; /* standard terminal foreground */
/* cursor: block, #5B7EF8 — brand blue distinguishes from content */
padding: 12px 16px;
scrollbar-width: 4px;
scrollbar-color: rgba(255,255,255,0.1) transparent;
```
**Log format**: `timestamp` in `--text-tertiary` · content in `--text-primary`. Orchestrator-injected messages shown in `rgba(91,126,248,0.2)` highlight.
---
### Attention Zone Headers
5 zones ordered by priority (top to bottom):
1. **Needs Merge** — PRs approved + CI passing
2. **Needs Response** — review comments, CI failures, conflicts
3. **Working** — agents running fine
4. **Idle** — agents paused/inactive
5. **Done** — completed, ready for cleanup
**Header anatomy:**
```
[●] NEEDS MERGE ───────────────────────────── [3]
↑ ↑ ↑
8px dot flex-1 1px border-subtle count pill
```
```css
.zone-header {
display: flex;
align-items: center;
gap: 8px;
padding: 0;
margin: 20px 0 12px;
}
.zone-label {
font: 10px/1 "Inter Variable" 600;
letter-spacing: 0.10em;
text-transform: uppercase;
/* color: zone's status color */
}
.zone-divider {
flex: 1;
height: 1px;
background: var(--border-subtle);
}
.zone-count {
font: 11px/1 "Inter Variable" 500;
padding: 2px 7px;
border-radius: 10px;
/* background + color: zone's status color at 12% opacity */
}
```
| Zone | Color | Empty behavior |
|------|-------|----------------|
| Needs Merge | `#22C55E` | Collapse entirely |
| Needs Response | `#F59E0B` | Collapse entirely |
| Working | `#5B7EF8` | Collapse if zero |
| Idle | `#6B6B8A` | Collapse by default |
| Done | `#3E3E54` | Collapsed by default |
---
## 4. Inspiration References
### Linear issue list — *the* density benchmark
**URL**: https://linear.app (see `screenshots/linear-homepage.png`)
**Why**: The product UI visible in Linear's homepage hero screenshot shows exactly the information density and row compactness that ao session cards should match. Issue ID in muted monospace, title truncated, labels as small pill badges, state dot left-aligned. Zero wasted pixels.
### Vercel deployment list
**URL**: https://vercel.com/dashboard
**Why**: Each deployment row = name (mono) + status dot + branch + timestamp. The status dot on a near-black background carries the full state signal with nothing competing. This is the right level of restraint for ao's done/idle sessions.
### GitHub Actions job graph
**URL**: https://docs.github.com/en/actions/writing-workflows/quickstart
**Why**: Status-colored nodes (green pass, red fail, gray skip, amber running) in a dependency graph. The best existing model for "pipeline state at a glance." The icon+color combination for each step is directly applicable to ao's CI status display.
### Grafana panel layout — density reference
**URL**: https://grafana.com/grafana/dashboards
**Why**: Demonstrates that 30+ data panels in one viewport is possible — when organized by visual weight, semantic color, and zone separation. The panel border system (1px subtle border, consistent padding, consistent label typography) is the right density model for ao's session card grid.
### GitHub Copilot agent mode in VS Code
**URL**: https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode
**Why**: Sequential labeled tool invocations ("Analyzing files... Running tests... Proposing edits...") is the right model for ao's terminal activity feed — transparent step disclosure rather than raw log dump. Each step collapsible. Makes AI activity *legible* rather than just *visible*.
---
## 5. What to Avoid
### Anti-patterns for this product specifically
**1. Spacious card proportions** — ao has 30 sessions. Linear/Notion card sizes assume 1 project per screen.
**2. Status color overloading** — Define strict semantic rules. Amber = "needs human attention." Green = "positive outcome / merge-ready." Red = "error/failure." Never use the same color for two different meanings.
**3. Badge spam** — Show only badges that diverge from the happy path. CI passing = expected = no badge. CI failing = show badge. If everything is highlighted, nothing is.
**4. Modal confirmation for merge** — Pre-approved PRs are the goal state. One click. Destructive actions (kill session, delete worktree) use confirmation. Merge does not.
**5. Wide sidebar** — A 240px sidebar wastes 240px of card grid space. Use a 48px icon rail or a 32px top bar.
**6. Light mode as design authority** — Design dark first. If you design light first you get light-mode thinking applied to dark backgrounds.
**7. Competing animations** — One continuous animation (working dot pulse). Everything else is state-change triggered.
**8. Full-width single-column** — 30 sessions in a single column is 5 viewports tall. Grid layout is mandatory.
**9. Burying the terminal** — Terminal is a primary debugging surface. One click from any session card.
**10. Generic icon library mixing** — Pick Lucide or Heroicons. Use it everywhere. One icon per semantic concept.
**11. Done sessions at full visual weight** — Done cards must visually recede (dim colors, collapsed by default). The eye should skip them automatically.
**12. Conflating activity state and attention state** — These are separate dimensions. A working agent can need response (CI failing). An idle agent can be merge-ready. Show both independently: left strip = attention state, dot = activity state.
---
## 6. Implementation Stack Recommendation
**Frontend**: Next.js 15 (App Router) + Tailwind CSS 4 + shadcn/ui (Radix UI primitives)
- Matches ao's existing stack
- Used by Supabase at scale — validated for serious developer tooling
**Design tokens**: CSS custom properties on `:root`. All colors defined as tokens, not Tailwind classes directly. Enables runtime theming and makes the semantic system enforceable.
**Terminal**: xterm.js + `@xterm/addon-fit`. Dark theme with the color system above.
**Real-time**: Server-Sent Events (existing in ao's architecture). State transitions animate via CSS transition (250ms ease).
**Icons**: `lucide-react` (npm package, tree-shakeable). Same package used by shadcn/ui components.
**Accessibility**: Status indicators never rely on color alone — always paired with text label or icon. Focus rings use `--accent` at 2px offset. Tab order follows visual reading order.
---
## 7. Current Implementation Audit
*Added after reading the actual `packages/web/` codebase. Maps recommendations to real files.*
### Existing Component Inventory
| File | Purpose | Status |
|------|---------|--------|
| `packages/web/src/app/globals.css` | CSS design tokens (`@theme` block) | ✅ Token system in place, needs repalette |
| `packages/web/src/components/Dashboard.tsx` | Top-level layout: header, stats bar, attention zones, PR table | ✅ Well structured |
| `packages/web/src/components/AttentionZone.tsx` | Zone header + collapsible session list | ✅ Functional, visual polish needed |
| `packages/web/src/components/SessionCard.tsx` | Primary card unit with left border strip, alerts, expand panel | ✅ Core logic solid, visual refinement needed |
| `packages/web/src/components/CIBadge.tsx` | CI check status display | ✅ Exists |
| `packages/web/src/components/PRStatus.tsx` | PR state display + table row | ✅ Exists |
| `packages/web/src/components/DirectTerminal.tsx` | xterm.js + WebSocket terminal (full-page `/sessions/:id`) | ✅ Fully implemented |
| `packages/web/src/lib/types.ts` | `DashboardSession`, `AttentionLevel`, `getAttentionLevel()` | ✅ Well typed |
### Current Color Token System vs. Recommended
`packages/web/src/app/globals.css` uses a **GitHub-inspired palette**. The recommended brief palette is a **blue-cast dark**. Mapping:
| Token | Current (GitHub) | Recommended (Brief) | Delta |
|-------|-----------------|---------------------|-------|
| `--bg-base` / `--color-bg-primary` | `#0d1117` (13,17,23) | `#0C0C11` (12,12,17) | Very close. GitHub has more blue+green, brief more neutral-blue. |
| Surface / `--color-bg-secondary` | `#161b22` (22,27,34) | `#141419` (20,20,25) | GitHub bluer, brief more neutral. Similar luminance. |
| Elevated / `--color-bg-tertiary` | `#1c2128` (28,33,40) | `#1C1C25` (28,28,37) | GitHub bluer. |
| `--color-border-default` | `#30363d` | `#2E2E3E` | GitHub has green tint, brief is blue-neutral. |
| `--color-text-primary` | `#e6edf3` | `#EEEEF5` | GitHub slightly cooler-white; brief slightly warmer. |
| `--color-text-secondary` | `#7d8590` | `#8888A6` | GitHub gray; brief has slight blue cast. |
| Accent blue | `#58a6ff` | `#5B7EF8` | Brief's blue is shifted toward indigo. |
| Green | `#3fb950` | `#22C55E` | Brief's green is more vibrant/saturated. |
| Red | `#f85149` | `#EF4444` | Very similar. |
| Yellow | `#d29922` | `#F59E0B` | Brief is brighter/more saturated. |
**Assessment**: The current palette is functional and coherent. The recommended palette shifts from GitHub's blue-green cast to a more neutral blue-indigo cast. Both work; the brief's palette aligns more closely with Linear/LangSmith's positioning than GitHub's.
### Current Typography vs. Recommended
`globals.css` body font:
```css
font-family: -apple-system, BlinkMacSystemFont, "SF Pro", system-ui, sans-serif;
```
`--font-mono`:
```css
--font-mono: "SF Mono", "Menlo", "Consolas", monospace;
```
**Brief recommendation**: `"Inter Variable"` as primary, `"Berkeley Mono"` / `"JetBrains Mono"` as monospace.
**Practical path**:
- Swap body to `"Inter Variable", ...` — load via `next/font/google` (no license cost). This is the highest-impact single change.
- Swap monospace to `"JetBrains Mono", "SF Mono", Menlo, monospace` — JetBrains Mono is free, available via Google Fonts, and widely recognized in developer tooling (LangSmith uses it).
- Berkeley Mono requires a paid license — use as optional upgrade if budget permits.
### Attention Zone: Actual vs. Recommended
**Current `AttentionZone.tsx`** header structure:
```
[ZONE LABEL] [description text] [count pill] [▼]
```
**Brief recommendation:**
```
[●] ZONE LABEL ─────────────────────────────────────── [count pill]
```
The current implementation adds a description text (e.g., "PRs ready to merge"). This is informative for new users but creates visual clutter at density. The brief's divider-line approach is more compact and scales better with 30+ sessions.
**Collapse behavior**: Current correctly collapses "done" by default and returns `null` when empty — matches brief recommendation.
**6-level zone system** matches brief: `merge → respond → review → pending → working → done`.
### Session Card: Actual vs. Recommended
**What the current card gets right:**
- 3px left border strip colored by attention level ✅
- Left strip color mapped to `borderColorByLevel` record ✅
- Merge-ready: green border highlight + prominent "merge PR #N" button ✅
- Alert badges for CI failures, review requests, conflicts, unresolved comments ✅
- Expandable detail panel with CI checks, unresolved comments, PR diff stats ✅
- Activity icons for each `ActivityState`
**What diverges from brief:**
1. **Activity icons are Unicode emoji** (`⚡`, `🟢`, `💤`, `❓`, `🚧`, `💀`) — brief recommends 8×8px CSS circles with color semantics. Emoji are charmingly informal but inconsistently sized across platforms and don't carry precise color semantics. The CSS dot approach is more precise.
2. **Merge button is translucent** (`rgba(63,185,80,0.2)` bg with `#3fb950` text) — brief recommends solid green (`#22C55E` background, dark text). Solid green for the primary action makes it visually dominate correctly.
3. **Card border-radius is `10px`** (set inline with `style={{ borderRadius: 10 }}`) — brief recommends `6px` (Linear's `--radius-6`). Minor but affects overall tightness.
4. **`confirm()` dialog on merge** — brief explicitly recommends one-click merge without modal. The current `if (!confirm(...))` on line 66 of `Dashboard.tsx` adds friction for the primary happy-path action. Kill/terminate should keep the confirm; merge should not.
5. **Single-column list layout** — cards stack vertically within each zone. Brief recommends a multi-column grid for density. At 30 sessions, single-column requires significant scrolling. Grid at `minmax(300px, 1fr)` with `gap: 12px` would show 3 columns at 1100px viewport, matching brief density target.
### Real-time Updates: Gap
`packages/web/src/lib/types.ts` defines `SSESnapshotEvent` and `SSEActivityEvent` — the SSE contract is designed. However, the dashboard currently fetches data at page load only (server-side, via `page.tsx`). No client-side SSE listener exists.
**Brief recommendation**: SSE subscription on `/api/events` to receive `session.activity` updates and re-render affected cards without full refresh.
**Files to add this to**:
- `packages/web/src/components/Dashboard.tsx` — add `useEffect` with `EventSource` to subscribe to `/api/events`
- On `session.activity` event: update the session in local state and allow CSS transition to reflect new attention level
### Design Deltas Summary (Priority Order)
| Priority | Change | File | Impact |
|----------|--------|------|--------|
| 1 | Load Inter Variable via `next/font/google` | `packages/web/src/app/layout.tsx` | Typography lift — biggest visual delta |
| 2 | Load JetBrains Mono via `next/font/google` | `packages/web/src/app/layout.tsx` | Monospace consistency |
| 3 | Swap `globals.css` color tokens to brief palette | `packages/web/src/app/globals.css` | Color system coherence |
| 4 | Merge button: solid green background, no confirm | `packages/web/src/components/SessionCard.tsx`, `Dashboard.tsx` | Primary action clarity |
| 5 | Replace emoji activity icons with CSS dots | `packages/web/src/components/SessionCard.tsx` | Visual precision, cross-platform consistency |
| 6 | Zone header: divider-line layout instead of description text | `packages/web/src/components/AttentionZone.tsx` | Density |
| 7 | Multi-column grid for session cards | `packages/web/src/components/AttentionZone.tsx` | Density at scale |
| 8 | Card border-radius: 10px → 6px | `packages/web/src/components/SessionCard.tsx` | Tighter, more Linear-aligned |
| 9 | SSE live-reload subscription | `packages/web/src/components/Dashboard.tsx` | Real-time updates |
| 10 | Right-side drawer for terminal (vs. full-page nav) | New component or `DirectTerminal.tsx` | Workflow: stay in dashboard while monitoring |
---
## Appendix: Raw Research Notes
See `competitive-analysis-raw.md` for full text-based analysis of all 14 competitor sites.
See `screenshots/` for Playwright-captured screenshots of Linear and Railway.
## Companion Documents
All three pages share this token system and theme:
| Document | Page | Focus |
|----------|------|-------|
| `design-brief.md` (this file) | `/` — Main dashboard | Session grid, attention zones, triage |
| `session-detail-design-brief.md` | `/sessions/[id]` | Terminal + PR investigation |
| `orchestrator-terminal-design-brief.md` | `/sessions/[orchestrator-id]` | Command center, full-viewport terminal |
---
*Design brief v2. Compiled February 2026.*
*Research methods: Playwright CSS extraction (Linear), Playwright screenshots (Linear, Railway), text-based web analysis (all others).*
*Codebase audit: Read packages/web/src — globals.css, Dashboard.tsx, SessionCard.tsx, AttentionZone.tsx, types.ts, DirectTerminal.tsx.*
*Precision note: Linear color/typography values are verified from live CSS. Railway values are visually estimated from screenshot. All others are from text/HTML analysis.*

View File

@ -0,0 +1,299 @@
# Orchestrator Terminal Page — Design Brief
*Design specification for `/sessions/[orchestrator-id]` (the orchestrator session)*
*Companion to `design-brief.md` (main dashboard) and `session-detail-design-brief.md`.*
---
## Product Context
The orchestrator terminal is the **command center**. While agent session pages show one agent's work, the orchestrator session is the parent — the process that spawns, monitors, and auto-handles all agent sessions.
Users open this page to:
1. Watch the orchestrator's decision-making in real-time ("why did it just spawn a new session?")
2. Issue high-level commands ("spawn sessions for issues 299305")
3. Debug orchestrator-level problems (config errors, plugin failures, connectivity issues)
4. Get a "god view" — see the state of all sessions from the orchestrator's perspective
**Key distinction from agent session pages**: There is no PR, no CI, no code review. The orchestrator manages processes, not code. The terminal is 100% of the content payload. Everything else is context scaffolding.
**User profile**: Senior engineer or CTO who has already built fluency with the dashboard. The orchestrator terminal is a power-user surface. It can lean into density and technical detail.
---
## Layout Architecture
The orchestrator terminal page should diverge from the generic session detail layout. It needs two things the agent session page does not:
1. **A status strip** — a compact real-time summary of what the orchestrator is managing (session counts by zone). This replaces the PR Card entirely.
2. **A command history / quick actions strip** — recent orchestrator commands and one-click shortcuts.
```
┌─ Nav bar ───────────────────────────────────────────────────────────────┐
│ ← Agent Orchestrator orchestrator │
└─────────────────────────────────────────────────────────────────────────┘
┌─ Status strip ──────────────────────────────────────────────────────────┐
│ 3 merge-ready 2 needs-response 12 working 4 done │
│ ao-orchestrator ● Active · Uptime: 2h 14m │
└─────────────────────────────────────────────────────────────────────────┘
┌─ Terminal (fills remaining height) ─────────────────────────────────────┐
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ● ao-orchestrator Connected XDA [↕ fullscreen] │ │
│ │─────────────────────────────────────────────────────────────────────│ │
│ │ │ │
│ │ [Orchestrator] Spawning session for issue #299... │ │
│ │ [Orchestrator] Session ao-62 created. │ │
│ │ [Orchestrator] Checking CI for ao-58: failing (2 checks) │ │
│ │ [Orchestrator] Sent fix-ci message to ao-58. │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
**Height distribution**: Nav 40px · Status strip ~64px · Terminal fills `calc(100vh - 104px)`. The terminal is the dominant surface. No scrolling below the fold.
---
## Visual Differentiation from Agent Pages
The orchestrator page must be visually distinct so users always know which "level" they're on. Three signals achieve this without new components:
1. **Accent color shift**: Agent pages use `--accent` blue (`#5B7EF8`) for interactive elements. The orchestrator page uses **indigo/violet** (`#7070FF` — Linear's brand color, which happens to match our `--accent-violet: #a371f7` range). This signals "orchestrator = control layer, not work layer."
2. **Nav label**: The "orchestrator" label persists in the nav bar (right-aligned). Always visible even in fullscreen-adjacent states.
3. **Terminal chrome**: Instead of the session ID `ao-62`, the orchestrator chrome shows `ao-orchestrator` in a different color — `--accent-violet` instead of the default `--text-muted`.
These three changes are purely visual and require no structural changes to shared components.
---
## Component Designs
### Navigation Bar
Same as session detail bar, plus a persistent right-side label:
```
← Agent Orchestrator [orchestrator]
```
```css
.nav-orchestrator-badge {
font: 11px/1 "Inter Variable" 500;
letter-spacing: 0.04em;
color: var(--accent-violet); /* #a371f7 */
padding: 2px 8px;
border-radius: 4px;
background: rgba(163, 113, 247, 0.08);
border: 1px solid rgba(163, 113, 247, 0.15);
}
```
---
### Status Strip
A compact horizontal summary bar. Reads left-to-right by urgency (mirrors dashboard zone order).
```
[3 merge-ready] [2 responding] [12 working] [4 done] ───── ao-orchestrator ● Active · 2h 14m
```
```css
.orchestrator-status-strip {
display: flex;
align-items: center;
gap: 16px;
padding: 10px 32px;
background: var(--bg-surface);
border-bottom: 1px solid var(--border-subtle);
font-size: 12px;
}
.status-count {
display: flex;
align-items: baseline;
gap: 4px;
}
.status-count__value {
font-weight: 600;
font-size: 15px;
}
.status-count__label {
color: var(--text-secondary);
font-size: 11px;
}
/* Colors match dashboard zone colors */
.status-count--merge { color: var(--status-ready); } /* green */
.status-count--respond { color: var(--status-error); } /* red */
.status-count--working { color: var(--accent); } /* blue */
.status-count--done { color: var(--text-tertiary); } /* dim */
```
Right side of the strip shows session identity and uptime:
```css
.orchestrator-identity {
margin-left: auto;
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
color: var(--text-muted);
}
/* "ao-orchestrator" in --font-mono */
/* Activity dot: same 8px CSS dot as elsewhere */
/* Uptime: computed from session.createdAt */
```
**Data sourcing**: The status counts come from the sessions API (already available on the page since the page loads session data). Uptime is `Date.now() - session.createdAt`. No new API calls required.
---
### Terminal Panel
The terminal takes all remaining height. The orchestrator should feel like a true terminal emulator, not an embedded panel.
**Height**: `calc(100vh - 104px)` (full viewport minus nav + status strip). Never a fixed `600px` on this page.
**Terminal theme** — same as session detail recommendation, but with the cursor in `--accent-violet` instead of `--accent` blue, reinforcing the orchestrator identity:
```typescript
theme: {
background: "#0A0A0F",
foreground: "#D4D4D8",
cursor: "#a371f7", // violet: orchestrator identity signal
cursorAccent: "#0A0A0F",
selection: "rgba(163, 113, 247, 0.25)",
fontSize: 13,
fontFamily: '"JetBrains Mono", "SF Mono", Menlo, Monaco, monospace',
}
```
**Terminal chrome bar** (the header bar with connection status):
```
[● violet] ao-orchestrator Connected XDA [↕ fullscreen]
```
The session ID `ao-orchestrator` is displayed in `--accent-violet` instead of `--text-muted` to maintain the identity signal. All other chrome elements same as `DirectTerminal.tsx`.
**Log coloring** (aspirational — requires orchestrator to emit structured ANSI output):
If the orchestrator emits ANSI escape codes, the terminal will naturally show color-coded output. Even without ANSI, xterm.js renders the raw terminal faithfully. No special handling needed in the UI layer.
Structured orchestrator log prefix pattern (for the orchestrator to implement, not the UI):
```
\e[35m[Orchestrator]\e[0m Spawning session for issue #299... (violet prefix)
\e[32m[ao-62]\e[0m Session created on branch feat/issue-299 (green for new)
\e[33m[ao-58]\e[0m CI failing — 2 checks (amber for issues)
\e[31m[ao-45]\e[0m Crashed — activity: exited (red for errors)
```
---
## Fullscreen Mode
In fullscreen:
- Nav bar and status strip both hide (fixed position inset-0)
- Terminal takes full viewport
- A minimal overlay in the top-right corner maintains context:
```
[ao-orchestrator] [● Active] [exit fullscreen]
```
```css
.fullscreen-overlay {
position: fixed;
top: 8px;
right: 12px;
z-index: 100;
display: flex;
align-items: center;
gap: 8px;
padding: 4px 10px;
background: rgba(20, 20, 25, 0.85);
backdrop-filter: blur(4px);
border-radius: 6px;
border: 1px solid var(--border-subtle);
font-size: 11px;
}
```
This is a small additive change to `DirectTerminal.tsx` — pass an optional `overlayContent` prop or a specialized `OrchestratorTerminal` wrapper component.
---
## Implementation Strategy
The orchestrator terminal currently renders as a standard `SessionDetail` page (same component, orchestrator has no PR so the PR card is absent). The terminal itself is identical.
**Option A — Minimal differentiation** (recommended for v1):
- Detect orchestrator session in `page.tsx` by `id.endsWith("-orchestrator")`
- Pass a prop `isOrchestrator={true}` to `SessionDetail`
- `SessionDetail` conditionally renders the status strip + nav badge + violet terminal theme
- No new components, no routing changes
**Option B — Dedicated page** (better for v2):
- Create `app/orchestrator/page.tsx` and `components/OrchestratorTerminal.tsx`
- Cleaner separation, allows orchestrator-specific features (command history, session shortcuts) without polluting `SessionDetail`
**Option A is sufficient for design parity.** Option B becomes worthwhile when orchestrator-specific features (command shortcuts, live session list in sidebar) are added.
---
## Current Implementation Audit
### What exists
- `SessionDetail` component handles no-PR state correctly (PR card is absent, terminal is the full content) ✅
- `DirectTerminal` handles fullscreen, XDA clipboard, responsive sizing ✅
- The dashboard links to `/sessions/${orchestratorId}` (from `Dashboard.tsx` L93) ✅
- Session polling every 5s keeps activity state current ✅
### What's missing
| Gap | Description | Priority |
|-----|-------------|----------|
| Visual identity | No distinction between orchestrator and agent session pages | High |
| Status strip | No live session count summary on the orchestrator page | High |
| Nav badge | No persistent "orchestrator" label in nav | Medium |
| Terminal height | Fixed `600px` instead of filling viewport | Medium |
| Terminal theme | Generic xterm black instead of brand-differentiated theme | Medium |
| Violet cursor | Cursor not differentiated from agent sessions | Low |
| Fullscreen overlay | No context overlay in fullscreen mode | Low |
### Design deltas (priority order)
| Priority | Change | File | Notes |
|----------|--------|------|-------|
| 1 | Detect orchestrator + pass `isOrchestrator` prop | `packages/web/src/app/page.tsx`, `SessionDetail.tsx` | Enables all other changes |
| 2 | Status strip: session counts by zone | `SessionDetail.tsx` (conditional) or new `OrchestratorStatus.tsx` | Main UX differentiation |
| 3 | Terminal height: `calc(100vh - 104px)` | `DirectTerminal.tsx` or wrapper | Full-viewport terminal |
| 4 | Nav badge: "orchestrator" pill | `SessionDetail.tsx` nav | Identity signal |
| 5 | Terminal theme: violet cursor + `#0A0A0F` bg | `DirectTerminal.tsx` (via `theme` prop) | Identity + quality |
| 6 | Fullscreen overlay with context | `DirectTerminal.tsx` (via `overlayContent` prop) | Fullscreen UX |
---
## Shared Design System Reference
All three pages share the same design tokens. Use the token names from the main design brief:
| Token | Value | Usage here |
|-------|-------|-----------|
| `--bg-base` | `#0C0C11` | Page background |
| `--bg-surface` | `#141419` | Nav bar, status strip |
| `--bg-elevated` | `#1C1C25` | Terminal chrome bar |
| `--accent` | `#5B7EF8` | Agent session links, focus rings |
| `--accent-violet` | `#a371f7` | Orchestrator identity color |
| `--status-ready` | `#22C55E` | Merge-ready count |
| `--status-error` | `#EF4444` | Respond-needed count, crashes |
| `--font-mono` | JetBrains Mono | Terminal, session IDs, branch names |
---
*Companion document to `design-brief.md` and `session-detail-design-brief.md`.*
*Compiled February 2026.*

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB

View File

@ -0,0 +1,309 @@
# Session Detail Page — Design Brief
*Design specification for `/sessions/[id]`*
*Companion to `design-brief.md` (main dashboard). Shares the same token system and theme.*
---
## Product Context
The session detail page is the **investigation surface**. The user arrives here when they need to go beyond the card summary — to read terminal output, debug a CI failure, review and dispatch unresolved comments, or watch an agent work in real-time.
**User intent on arrival:**
1. "What is this agent actually doing right now?" → terminal
2. "Why is CI failing?" → CI check details + terminal logs
3. "There are review comments — let me dispatch them to the agent" → PR card comment list
4. "The agent is stuck — let me see the last output" → terminal
The session detail page is a **single-task focused view**, not a dashboard. The terminal dominates. Everything above it provides context, not content.
**Primary navigation pattern**: User comes from dashboard card → clicks "terminal" link or session ID → lands here → returns to dashboard via back link. The page must support this flow without friction.
---
## Layout Architecture
```
┌─ Nav bar ───────────────────────────────────────────────────────────────┐
│ ← Agent Orchestrator │
└─────────────────────────────────────────────────────────────────────────┘
┌─ Header ────────────────────────────────────────────────────────────────┐
│ ao-58 [● Active] │
│ Implement UI/UX research dashboard │
│ [project-id] · [#104] · [session/ao-58] · [INT-58] │
│ Working · Created 2h ago · Active 3m ago │
└─────────────────────────────────────────────────────────────────────────┘
┌─ PR Card (conditional) ─────────────────────────────────────────────────┐
│ PR #104: Implement UI/UX research dashboard +142 -23 │
│ ───────────────────────────────────────────────────────────────────── │
│ Issues: ✗ CI failing — 2 checks failed │
│ ○ Not approved — awaiting reviewer │
│ │
│ CI CHECKS │
│ [▶ lint] [▶ typecheck] [✗ test] [✗ build] │
│ │
│ UNRESOLVED COMMENTS (3) │
│ ▶ Missing error handling · equinox [view →] │
│ packages/web/src/... │
│ "Consider wrapping this..." │
│ [Ask Agent to Fix] │
└─────────────────────────────────────────────────────────────────────────┘
┌─ Terminal ──────────────────────────────────────────────────────────────┐
│ TERMINAL │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ● ao-58 Connected XDA [fullscreen] │ │
│ │─────────────────────────────────────────────────────────────────────│ │
│ │ │ │
│ │ $ claude --dangerously-skip-permissions │ │
│ │ ✻ Thinking... │ │
│ │ ⎿ Reading packages/web/src/components/Dashboard.tsx │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
```
**Proportions**: Nav 40px · Header ~100px · PR Card 0240px (hidden if no PR) · Terminal fills the rest. On a 900px-tall viewport without a PR, the terminal gets ~720px.
**Max content width**: `900px` (narrower than dashboard's `1100px` — this is a focused single-session view, not a grid).
---
## Component Designs
### Navigation Bar
```css
/* Current implementation matches recommendation */
height: 40px;
background: var(--bg-surface); /* #141419 recommended, currently --color-bg-secondary */
border-bottom: 1px solid var(--border-subtle);
padding: 0 32px;
```
**Back link**: `← Agent Orchestrator` in `--text-secondary`. On hover: `--text-primary`. No underline, `tracking-wide`.
**Addition (not currently present)**: Show current session ID as a breadcrumb:
```
← Agent Orchestrator / ao-58
```
`ao-58` in monospace, `--text-muted`. Helps orient the user without reading the header.
---
### Header
**Session ID line:**
```
[session-id in 20px semibold] [● Activity Badge]
```
Activity badge: `rounded-full`, 10px font, `color-mix` background at 15% opacity. This matches the current implementation but benefits from the CSS dot replacing the emoji:
| Current | Recommended |
|---------|-------------|
| `⚡ Active` | `● Active` — 8px green CSS dot + "Active" label |
| `🟢 Ready` | `● Ready` — 8px blue CSS dot |
| `😴 Idle` | `● Idle` — 8px muted CSS dot |
| `❓ Waiting for input` | `● Waiting for input` — 8px amber dot |
| `🚧 Blocked` | `● Blocked` — 8px red dot |
| `💀 Exited` | `○ Exited` — 8px hollow/dark circle (terminated state recedes) |
**Summary line**: 14px, `--text-secondary`, `mt-2`. Truncate at 2 lines.
**Meta chips**: Current implementation uses `--bg-tertiary` pill backgrounds. This is correct. Font size 11px for branch (monospace), 12px for other chips.
```css
.meta-chip {
background: var(--bg-elevated); /* #1C1C25 */
border-radius: 4px; /* --radius-4 for small chips */
padding: 2px 8px;
font-size: 11px;
color: var(--text-secondary);
text-decoration: none;
}
.meta-chip:hover { color: var(--text-primary); }
.meta-chip--mono { font-family: var(--font-mono); font-size: 11px; }
```
**Status / timestamps line**: `--text-muted`, `text-xs`. Dots as separators. Currently implemented with `relativeTime()` helper — correct.
---
### PR Card
The PR card is a mini-dashboard for this session's PR. It contains three logically separate sub-sections:
**1. Title row** (always visible):
```
PR #104: Implement UI/UX research dashboard +142 -23
```
- Title: `14px medium`, `--text-primary`, links to PR URL
- Stats: `+additions` in `--status-ready` (`#22C55E`), `-deletions` in `--status-error` (`#EF4444`)
- Draft badge: `--text-muted semibold`. Merged badge: `--accent-violet` (current `#bc8cff`).
**2. Merge readiness / issues section:**
*Ready state:* Single green line `✓ Ready to merge` — should dominate visually. This should be a distinct banner rather than a small text line:
```css
.merge-ready-banner {
background: rgba(34, 197, 94, 0.08);
border: 1px solid rgba(34, 197, 94, 0.2);
border-radius: 6px;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
}
/* CheckCircle2 icon 16px + "Ready to merge" in 13px/600 green */
```
*Issues list:* Matches current implementation well. Icons `✗` (red), `●` (amber), `○` (muted) are semantically clear. Consider switching to Lucide icons (`XCircle`, `Clock`, `Circle`) for consistency with the icon system.
**3. CI Checks section:**
```
CI CHECKS
[✓ lint] [✓ typecheck] [✗ test failed →] [✗ build failed →]
```
- Passing check: `--bg-elevated` background, `--status-ready` text, `CheckCircle2` 12px
- Failed check: `rgba(--status-error, 0.12)` background, `--status-error` text, `XCircle` 12px, **links to check URL**
- When failures exist: expand to full list (current `layout="expanded"` behavior — correct)
- When all pass: inline collapsed row (current `layout="inline"` behavior — correct)
**4. Unresolved Comments section:**
The `<details>` / `<summary>` accordion pattern is correct for this use case. Visual refinements:
```
▶ Missing error handling · equinox [view →]
packages/web/src/components/Dashboard.tsx
┊ "Consider wrapping this fetch() call in a try/catch..."
[Ask Agent to Fix]
```
- Chevron: `▶` rotates to `▼` on open — use `transition-transform` (current `group-open:rotate-90` — correct)
- `c.path` in monospace, `--text-muted`, 10px
- Comment body: left-border (`2px solid --border-default`), `pl-3`, `--text-secondary` — current implementation correct
- "Ask Agent to Fix" button states:
| State | Background | Text |
|-------|-----------|------|
| Default | `--accent` (`#5B7EF8`) | white |
| Sending... | `--accent` at 70% opacity, disabled | white |
| Sent! (3s) | `--status-ready` (`#22C55E`) | white |
| Failed (3s) | `--status-error` (`#EF4444`) | white |
*Current implementation uses `--color-accent-blue`, `--color-accent-green`, `--color-accent-red` for these states — semantically correct, will just need color token update.*
**Card border-radius**: Current is `rounded-lg` (8px). This matches the brief's `--radius-8` for panels — correct for a card of this complexity.
---
### Terminal Panel
The terminal is rendered via `DirectTerminal.tsx` (xterm.js + WebSocket). Design refinements:
**Terminal chrome (the top bar):**
Current:
```
[● green] ao-58 Connected XDA [fullscreen]
```
Recommended additions:
```
[● green] ao-58 Connected [XDA] ──────────── [↕ fullscreen]
```
- Connection status dot: green/red/amber pulsing — current correct, uses CSS classes from design token colors
- Session ID: `--font-mono`, `--text-muted`, `12px` — current correct
- Status text: uppercase, `tracking-wide`, semantic color — current correct
- XDA badge: keep as-is, explains the clipboard feature (useful for power users)
- Divider: `flex: 1` spacer pushing fullscreen button to the right — better than `ml-auto`
- Fullscreen button: `↕` icon (Lucide `Maximize2` / `Minimize2`) instead of text label. Or text is fine — don't over-engineer.
**Terminal area:**
Current xterm.js config:
```typescript
{
background: "#000000", // pure black
foreground: "#ffffff",
cursor: "#ffffff",
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace'
}
```
Recommended:
```typescript
{
background: "#0A0A0F", // slightly off-black, matches brief's terminal bg
foreground: "#D4D4D8", // warmer white (VS Code-style)
cursor: "#5B7EF8", // brand blue distinguishes cursor from content
cursorAccent: "#0A0A0F",
selection: "rgba(91, 126, 248, 0.3)",
fontSize: 13, // 13px recommended (current 14px is fine too)
fontFamily: '"JetBrains Mono", "SF Mono", Menlo, Monaco, monospace'
}
```
*The pure black `#000000` is jarring against the dark surface background when the terminal doesn't fill the frame. `#0A0A0F` is less stark and harmonizes.*
**Height:**
- Normal: `600px` (current) — fine for most viewports
- Fullscreen: `calc(100vh - 40px)` (current, height minus chrome bar) — correct
- **Consideration**: `600px` fixed height means on a 768px laptop, terminal is cut short and page still scrolls. Consider `max(400px, calc(100vh - 360px))` to fill remaining viewport. On 900px viewport with no PR: terminal gets ~760px. With PR: terminal gets ~400px. Both reasonable.
---
## Page States
| State | Behavior |
|-------|---------|
| Loading | Full-screen centered spinner: `text-sm text-[--text-muted] "Loading session..."`. No skeleton — the terminal itself will show connecting state |
| Error / 404 | Full-screen centered red error text. Add link back to dashboard |
| No PR | PR Card section hidden entirely. Terminal moves up. |
| PR merged | PR Card shows with purple "Merged" badge. Terminal still usable. |
| Session exited | Activity badge: `● Exited` (red). Terminal shows last output (disconnected state). Restore button visible |
| Fullscreen | Nav and header hidden (`position: fixed; inset: 0`). Only terminal. `?fullscreen=true` in URL |
---
## Current Implementation Audit
### What's already correct
- Nav bar structure and back link ✅
- Activity badge with color-mix backgrounds ✅
- Meta chips (project, PR #, branch, issue) ✅
- Relative timestamps (`relativeTime()` helper) ✅
- PR title + diff stats line ✅
- IssuesList with semantic icons ✅
- CI check list with expanded/inline layout modes ✅
- `<details>` accordion for unresolved comments ✅
- "Ask Agent to Fix" with sending/sent/error states ✅
- XDA terminal clipboard support ✅
- Fullscreen mode with URL sync ✅
- 5-second polling for session updates ✅
### Design deltas (priority order)
| Priority | Change | File | Notes |
|----------|--------|------|-------|
| 1 | Breadcrumb in nav: `← Agent Orchestrator / ao-58` | `SessionDetail.tsx` | Orientation |
| 2 | Activity indicator: CSS dot instead of emoji | `SessionDetail.tsx``activityLabel` map | Visual precision |
| 3 | "Ready to merge" → banner card instead of text line | `SessionDetail.tsx``PRCard` | Primary action prominence |
| 4 | Terminal theme: `#0A0A0F` bg, `#5B7EF8` cursor, JetBrains Mono | `DirectTerminal.tsx` | Terminal quality |
| 5 | Terminal height: dynamic `calc(100vh - Npx)` instead of fixed `600px` | `DirectTerminal.tsx` | Viewport utilization |
| 6 | Meta chip border-radius: `4px` instead of `rounded-md` (6px) | `SessionDetail.tsx` | Token consistency |
| 7 | Color tokens: update to recommended palette when `globals.css` is updated | All | Follows main dashboard token migration |
---
*Companion document to `design-brief.md`. Same token system, same typography, same component style.*
*Compiled February 2026.*

View File

@ -0,0 +1,270 @@
/**
* ao Dashboard Design Token Reference
* Drop-in replacement for packages/web/src/app/globals.css @theme block.
*
* Research basis:
* - Linear.app CSS: Playwright-extracted ground-truth values (body bg, panel bg,
* accent, status colors, type scale, radius, transitions)
* - WandB, Supabase, LangSmith: text/HTML analysis
* - Design direction: blue-cast dark, closer to LangSmith/Linear than GitHub
*
* Usage:
* Replace the @theme block in packages/web/src/app/globals.css with this file's
* @theme block, and update font-family in body{} to use Inter Variable.
*
* Font setup (in layout.tsx):
* import { Inter } from "next/font/google";
* import { JetBrains_Mono } from "next/font/google";
* const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
* const mono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono" });
* // Apply: <body className={`${inter.variable} ${mono.variable}`}>
*/
@import "tailwindcss";
@theme {
/* ── Base surfaces (background stepping, no shadows in dark mode) ── */
--color-bg-base: #0C0C11; /* Page / app background. L≈5%. near-pure black, blue cast */
--color-bg-surface: #141419; /* Card backgrounds, nav bar, status strip. L≈8% */
--color-bg-elevated: #1C1C25; /* Hover states, terminal chrome, dropdowns. L≈12% */
--color-bg-subtle: #23232F; /* Input backgrounds, inline code, kbd. L≈15% */
/* Legacy aliases — keep for backward compat with existing components */
--color-bg-primary: var(--color-bg-base);
--color-bg-secondary: var(--color-bg-surface);
--color-bg-tertiary: var(--color-bg-elevated);
/* ── Borders ─────────────────────────────────────────────────────── */
--color-border-subtle: #22222E; /* Lowest-visibility borders */
--color-border-default: #2E2E3E; /* Standard card borders */
--color-border-strong: #3E3E52; /* Focus rings, active borders, emphasis */
/* Legacy alias */
--color-border-muted: var(--color-border-subtle);
--color-border-emphasis: var(--color-border-strong);
/* ── Text hierarchy (4-tier, modeled on Linear's verified system) ─ */
--color-text-primary: #EEEEF5; /* Main content, titles, session names */
--color-text-secondary: #8888A6; /* Metadata, timestamps, labels */
--color-text-tertiary: #50506A; /* Disabled, de-emphasized */
--color-text-muted: #50506A; /* Alias for tertiary */
--color-text-inverse: #0C0C11; /* Text on solid colored buttons */
/* ── Interactive accent ──────────────────────────────────────────── */
/* Derived from Linear #4EA7FC blue + #7070FF brand — split at blue-indigo */
--color-accent: #5B7EF8; /* Links, focus rings, active nav, working state */
--color-accent-hover: #7B9CFB; /* Hover state */
--color-accent-subtle: rgba(91, 126, 248, 0.12); /* Highlight backgrounds */
/* Aliases used in current components */
--color-accent-blue: var(--color-accent);
/* ── Status colors — semantic only, never decorative ────────────── */
/* Verified against Linear's palette: green #27A644, red #EB5757, etc. */
/* ao uses higher-saturation equivalents for better dark-bg contrast. */
--color-status-working: #5B7EF8; /* Agent actively running — animated pulse */
--color-status-ready: #22C55E; /* Merge-ready / success — highest priority */
--color-status-attention: #F59E0B; /* Blocked, CI failing, review needed */
--color-status-idle: #6B6B8A; /* Agent idle / paused — low visual weight */
--color-status-done: #3E3E54; /* Complete — visually recedes */
--color-status-error: #EF4444; /* Crash, hard failure — urgent */
/* Semantic aliases for components */
--color-accent-green: var(--color-status-ready);
--color-accent-yellow: var(--color-status-attention);
--color-accent-orange: #d18616; /* review zone (between yellow and red) */
--color-accent-red: var(--color-status-error);
--color-accent-violet: #a371f7; /* Orchestrator identity / merged PR state */
--color-accent-purple: #bc8cff; /* Additional purple variant */
/* ── Typography ──────────────────────────────────────────────────── */
/* Set via next/font/google CSS variables in layout.tsx */
--font-sans: "Inter Variable", "SF Pro Display", -apple-system, system-ui, sans-serif;
--font-mono: "JetBrains Mono", "SF Mono", "Menlo", "Consolas", monospace;
/* Premium upgrade: replace JetBrains Mono with "Berkeley Mono" (paid license) */
/* Linear uses "Berkeley Mono" — verified via Playwright CSS extraction */
/* ── Type scale (derived from Linear's verified CSS token system) ─ */
--text-tiny: 0.625rem; /* 10px — zone headers (uppercase + 0.10em tracking) */
--text-micro: 0.75rem; /* 12px — timestamps, secondary metadata (mono) */
--text-mini: 0.8125rem; /* 13px — status badges, card metadata rows */
--text-small: 0.875rem; /* 14px — card titles, primary labels */
--text-regular: 0.9375rem; /* 15px — body copy within panels */
--text-large: 1.0625rem; /* 17px — section headings, zone names */
/* ── Spacing / border-radius (Linear's verified --radius-* values) ─ */
--radius-4: 4px; /* Inputs, small badges, meta chips */
--radius-6: 6px; /* Buttons, session cards */
--radius-8: 8px; /* Modals, PR card, larger panels */
--radius-12: 12px; /* Larger surfaces */
--radius-16: 16px; /* Overlays, fullscreen elements */
/* ── Transitions (Linear's verified --speed-* values) ───────────── */
--transition-quick: 0.1s; /* Hover states */
--transition-regular: 0.25s; /* State changes (bg, border, color) */
/* ── Scrollbar ───────────────────────────────────────────────────── */
/* Based on Linear's verified scrollbar token values */
--scrollbar-width: 6px;
--scrollbar-color-default: rgba(255, 255, 255, 0.10);
--scrollbar-color-hover: rgba(255, 255, 255, 0.20);
--scrollbar-color-active: rgba(255, 255, 255, 0.40);
}
/* ── Base styles ─────────────────────────────────────────────────────── */
body {
font-family: var(--font-sans);
background: var(--color-bg-base);
color: var(--color-text-primary);
min-height: 100vh;
/* Verified from Linear: negative letter-spacing at most sizes */
letter-spacing: -0.011em;
}
a {
color: var(--color-accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* ── Scrollbar styling ───────────────────────────────────────────────── */
/* Verified from Linear's CSS: thin, barely visible at rest */
::-webkit-scrollbar {
width: var(--scrollbar-width);
height: var(--scrollbar-width);
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-color-default);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-color-hover);
}
::-webkit-scrollbar-thumb:active {
background: var(--scrollbar-color-active);
}
/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-color-default) transparent;
}
/* ── Animations ──────────────────────────────────────────────────────── */
/* Working state: ring pulse on the activity dot — the ONLY continuous animation */
@keyframes activity-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(91, 126, 248, 0.4); }
50% { box-shadow: 0 0 0 4px rgba(91, 126, 248, 0); }
}
/* CI pending spinner */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Generic opacity pulse (for loading/connecting states) */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* Card entrance — used when new session appears */
@keyframes slide-in {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Utility classes ─────────────────────────────────────────────────── */
/* Activity dot — 8×8px CSS circle, replaces Unicode emoji */
.activity-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.activity-dot--working { background: var(--color-status-working); animation: activity-pulse 2s ease-in-out infinite; }
.activity-dot--ready { background: var(--color-status-ready); }
.activity-dot--attention { background: var(--color-status-attention); }
.activity-dot--idle { background: var(--color-status-idle); }
.activity-dot--error { background: var(--color-status-error); }
.activity-dot--done { background: var(--color-status-done); }
.activity-dot--exited { background: var(--color-status-done); border: 1px solid var(--color-status-idle); }
/*
* Reference: xterm.js terminal theme
*
* Agent session terminal (blue cursor):
* {
* background: "#0A0A0F",
* foreground: "#D4D4D8",
* cursor: "#5B7EF8", // brand blue
* cursorAccent: "#0A0A0F",
* selectionBackground: "rgba(91, 126, 248, 0.3)",
* fontSize: 13,
* fontFamily: '"JetBrains Mono", "SF Mono", Menlo, Monaco, monospace',
* }
*
* Orchestrator terminal (violet cursor identity differentiation):
* {
* background: "#0A0A0F",
* foreground: "#D4D4D8",
* cursor: "#a371f7", // violet: orchestrator identity
* cursorAccent: "#0A0A0F",
* selectionBackground: "rgba(163, 113, 247, 0.25)",
* fontSize: 13,
* fontFamily: '"JetBrains Mono", "SF Mono", Menlo, Monaco, monospace',
* }
*/
/*
* Competitor palette reference
* (Kept for cross-referencing during implementation)
*
* Linear (Playwright-verified):
* Body bg: #08090A
* Panel bg: #0F1011 (--color-bg-panel)
* Brand accent: #7070FF (--color-accent)
* Blue status: #4EA7FC
* Red status: #EB5757
* Green status: #27A644
* Orange status: #FC7840
* Yellow status: #F0BF00
* UI font: Inter Variable
* Mono font: Berkeley Mono (premium licensed)
* Serif font: Tiempos Headline (display only)
*
* Railway (visually estimated from screenshot):
* Dark bg: hsl(250, 24%, 9%) #13111C
* CTA purple: ~#7C3AED
*
* WandB:
* Background: #1A1C1F
* Card: #212429 / #282A2F
* Cyan accent: #00AFC2
* Border: 1px solid #34373C
*
* LangSmith:
* Background: #030710 (blue-cast near-black)
* Electric blue: #4D65FF
* Primary font: JetBrains Mono (monospace as UI font unique)
*
* Supabase:
* Brand green: #3ECF8E / #34B27B
* Background: #11181C
* UI font: Inter
*
* Vercel:
* Pure black: #000000
* Pure white: #FAFAFA
* Font: Geist (proprietary)
*/