diff --git a/DESIGN.md b/DESIGN.md index aa2786272..eefafd433 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -3,17 +3,17 @@ ## Product Context - **What this is:** A web-based dashboard for managing fleets of parallel AI coding agents. Each agent gets its own git worktree, branch, and PR. The dashboard is the operator's single pane of glass. - **Who it's for:** Developers running 10-30+ AI coding agents in parallel. From solo devs to engineering teams. -- **Space/industry:** AI agent orchestration. Competitors: Conductor.build, T3 Code, OpenAI Codex app, Emdash. All are native Mac apps. Agent Orchestrator is the web-based alternative. +- **Space/industry:** AI agent orchestration. Competitors: Conductor.build, T3 Code, OpenAI Codex app. All are native Mac apps with cool blue-gray dark mode. Agent Orchestrator is the web-based alternative. - **Project type:** Web app (Next.js 15, React 19, Tailwind v4). Kanban board with 6 attention-priority columns. ## Aesthetic Direction -- **Direction:** Industrial Precision -- **Decoration level:** Intentional — subtle depth through surface hierarchy, ambient glow on active states, gradient surfaces in dark mode. No decorative blobs, no gratuitous gradients. -- **Mood:** Trading terminal meets control room. Dense, scannable, utilitarian, with just enough warmth that developers want to live in it for 10 hours. "I'm running an operation" not "I'm organizing tasks." -- **Reference sites:** Conductor.build, t3.codes, openai.com/codex, emdash.dev +- **Direction:** Warm Terminal +- **Decoration level:** Intentional — subtle surface depth through warm gradients, inset highlights that catch light like brushed aluminum, ambient glow on active states. No decorative blobs, no gratuitous effects. +- **Mood:** High-end audio gear meets flight deck. Dense, scannable, utilitarian, with enough warmth that developers want to live in it for 10 hours. Every competitor is cold blue-gray. This is the warm one. +- **Reference sites:** Conductor.build (layout baseline), linear.app (density standard), t3.codes (terminal aesthetic) ## Typography -- **Display/Hero:** Geist Sans, weight 680, letter-spacing -0.035em — same font as body but differentiated through weight and tracking. Tighter and heavier creates display hierarchy without a font swap. No cognitive gear-shifts on a scan-heavy dashboard. +- **Display/Hero:** JetBrains Mono, weight 500, letter-spacing -0.02em — monospace for headlines. In a dashboard where 40% of visible text is already monospace (agent output, branch names, commit hashes), leaning into mono for display creates a unified typographic voice instead of two competing voices. - **Body:** Geist Sans, weight 400, letter-spacing -0.011em — purpose-built for dense interfaces at 13px. Better digit alignment than IBM Plex Sans, designed for exactly this density level. - **UI/Labels:** Geist Sans, weight 600, letter-spacing 0.06em, uppercase, 10-11px — column headers, section labels, status indicators. - **Data/Tables:** JetBrains Mono, weight 400, 11-13px, tabular-nums — agent IDs, branch names, timestamps, commit hashes, diff stats, PR numbers. @@ -28,62 +28,64 @@ - display: clamp(22px, 2.8vw, 32px) (hero headings) ## Color -- **Approach:** Restrained with signal accents. Color is a priority channel, not decoration. -- **Accent (cool):** #5B7EF8 — interactive elements, links, focus rings. Used sparingly. -- **Accent hover:** #7a96ff -- **Accent tint:** rgba(91, 126, 248, 0.12) -- **Attention (warm):** #f1be64 — states requiring human input. Amber is universally "needs attention" without the panic of red. +- **Approach:** Restrained with signal accents. Color is a priority channel, not decoration. Warm tones throughout. +- **Accent (interactive):** #8b9cf7 — warm periwinkle. Links, focus rings, active states. Blue = clickable is muscle memory. This warm-leaning blue fits the palette without colliding with status colors. +- **Accent hover:** #a3b1fa +- **Accent tint:** rgba(139, 156, 247, 0.12) +- **Attention (warm):** #e2a336 — states requiring human input. Amber is universally "needs attention" without the panic of red. ### Surfaces (Dark Mode) -| Token | Value | Usage | -|-------|-------|-------| -| bg-base | #0a0d12 | Page background | -| bg-surface | #11161d | Card/column backgrounds | -| bg-elevated | #171d26 | Modals, popovers, hover states | -| bg-elevated-hover | #1c2430 | Hover on elevated surfaces | -| bg-subtle | rgba(177, 206, 255, 0.05) | Subtle tints, pill backgrounds | +| Token | Value | Usage | Rationale | +|-------|-------|-------|-----------| +| bg-base | #121110 | Page background | Brown-tinted black. Warmer than neutral #111 or blue-tinted #0a0d12. Sets the warm foundation. | +| bg-surface | #1a1918 | Card/column backgrounds | One stop lighter, same warm undertone. Surface hierarchy through subtle warmth, not just lightness. | +| bg-elevated | #222120 | Modals, popovers, hover states | Two stops up. Warm enough to feel distinct from surface without being muddy. | +| bg-elevated-hover | #2a2928 | Hover on elevated surfaces | Subtle lift on interaction. | +| bg-subtle | rgba(255, 240, 220, 0.04) | Subtle tints, pill backgrounds | Warm-tinted transparency. Reads as "highlighted" without introducing a new color. | ### Surfaces (Light Mode) -| Token | Value | Usage | -|-------|-------|-------| -| bg-base | #f5f5f7 | Page background | -| bg-surface | #ffffff | Card/column backgrounds | -| bg-elevated | #ffffff | Modals, popovers | -| bg-elevated-hover | #f7f7f8 | Hover states | -| bg-subtle | #f0f0f2 | Subtle tints | +| Token | Value | Usage | Rationale | +|-------|-------|-------|-----------| +| bg-base | #f5f3f0 | Page background | Warm parchment, not clinical white or cool gray. Matches the warm dark mode without being beige. | +| bg-surface | #ffffff | Card/column backgrounds | True white for cards creates contrast against the warm base. Cards "float" on warm paper. | +| bg-elevated | #ffffff | Modals, popovers | Same as surface. Light mode doesn't need as many elevation steps because shadows do the work. | +| bg-elevated-hover | #f7f5f2 | Hover states | Warm tint on hover, matching the base temperature. | +| bg-subtle | rgba(120, 100, 80, 0.05) | Subtle tints | Brown-tinted transparency for warm highlighting. | + +**Light mode strategy:** Warm parchment base (#f5f3f0) with white cards. The same brown undertone that makes dark mode warm also makes light mode feel like quality paper, not sterile lab equipment. Accent darkened in light mode (#5c64b5) to maintain 5.3:1 contrast on white. Status colors shifted darker (green #16a34a, amber #b8860b, red #dc2626, cyan #0891b2) to maintain contrast on light backgrounds. Drop shadows replace inset highlights for surface hierarchy. ### Text (Dark Mode) | Token | Value | Usage | |-------|-------|-------| -| text-primary | #eef3ff | Headings, card titles, body. Blue-white, not pure white. | -| text-secondary | #a5afc4 | Descriptions, metadata. Readable in dense layouts. | -| text-tertiary | #6f7c94 | Timestamps, placeholders, disabled states. | +| text-primary | #f0ece8 | Headings, card titles, body. Cream, not pure white or blue-white. Warm and easy on the eyes at 3am. | +| text-secondary | #a8a29e | Descriptions, metadata. Stone-toned, not neutral gray. Readable in dense layouts. | +| text-tertiary | #78716c | Timestamps, placeholders, disabled states. Warm tertiary that recedes without disappearing. | ### Text (Light Mode) | Token | Value | Usage | |-------|-------|-------| -| text-primary | #1b1b1f | Headings, card titles, body. | -| text-secondary | #5e5e66 | Descriptions, metadata. | -| text-tertiary | #8e8e96 | Timestamps, placeholders. | +| text-primary | #1c1917 | Headings, card titles, body. Warm near-black, not pure black. | +| text-secondary | #57534e | Descriptions, metadata. Stone-500. | +| text-tertiary | #736e6b | Timestamps, placeholders. Darkened from #a8a29e to pass WCAG AA (5.0:1 on white, 4.5:1 on base). | ### Borders (Dark Mode) | Token | Value | Usage | |-------|-------|-------| -| border-subtle | rgba(160, 190, 255, 0.08) | Dividers, section separators | -| border-default | rgba(160, 190, 255, 0.14) | Card edges, input borders | -| border-strong | rgba(185, 214, 255, 0.24) | Hover states, focus indicators | +| border-subtle | rgba(255, 240, 220, 0.06) | Dividers, section separators. Warm-tinted transparency. | +| border-default | rgba(255, 240, 220, 0.10) | Card edges, input borders. | +| border-strong | rgba(255, 240, 220, 0.18) | Hover states, focus indicators. | ### Status Colors | Status | Dark Mode | Light Mode | Usage | |--------|-----------|------------|-------| | Working | #22c55e | #16a34a | Agent actively coding. Green dot with pulse ring animation. | -| Ready | #5B7EF8 | #5e6ad2 | Queued, awaiting start or CI pending. | -| Respond | #f1be64 | #ca8a04 | Needs human input. Amber = attention without panic. | +| Ready | #8b9cf7 | #6b73c4 | Queued, awaiting start or CI pending. | +| Respond | #e2a336 | #b8860b | Needs human input. Amber = attention without panic. | | Review | #06b6d4 | #0891b2 | Code ready for review. Cyan = "look when ready." | | Error | #ef4444 | #dc2626 | CI failed, agent crashed. Red = broken. | -| Done | #3a4252 | #d0d7de | Completed. Fades to secondary text. Done items recede. | +| Done | #57534e | #d6d3d1 | Completed. Fades to stone. Done items recede. | -- **Dark mode strategy:** Blue-tinted graphite palette (not neutral gray). Reduce font weight by one step in dark mode (semibold becomes 500, bold becomes 600). Inset highlights on elevated surfaces: `inset 0 1px 0 rgba(255,255,255,0.04)`. Subtle radial gradients on body for ambient depth. +- **Dark mode strategy:** Warm charcoal palette (brown-tinted, not neutral or blue-tinted gray). Reduce font weight by one step in dark mode (semibold becomes 500, bold becomes 600). Inset highlights on elevated surfaces: `inset 0 1px 0 rgba(255,255,255,0.03)`. Subtle radial gradients on body for ambient depth. ## Spacing - **Base unit:** 4px @@ -96,12 +98,9 @@ - **Mobile column order:** Respond > Review > Pending > Working (urgency-first) - **Max content width:** 1280px for settings/detail pages - **Border radius:** - - base: 2px (cards, buttons, inputs — consistent, sharp, intentional) - - sm: 4px (tooltips, small transient elements) - - md: 6px (dropdowns, floating interactive elements) - - lg: 8px (modals, large floating overlays) - - full: 9999px (pills, badges, count indicators) -- **Card inset highlight:** `inset 0 1px 0 rgba(255,255,255,0.04)` in dark mode + - 0px everywhere. No rounding on cards, buttons, inputs, modals, dropdowns. Hard edges are the identity. The only exception is status dots (circles by nature) and avatar images. + - full: 9999px (status dots, avatar circles only) +- **Card inset highlight:** `inset 0 1px 0 rgba(255,255,255,0.03)` in dark mode - **Status accent:** 2px solid left border on session cards, colored by status ## Motion @@ -126,6 +125,88 @@ - All animations must respect `prefers-reduced-motion: reduce` - Use `contain: layout style paint` on session cards for performance with 30+ cards +## Accessibility +- **Touch targets:** Minimum 44x44px on all interactive elements (buttons, links, toggles). Icon buttons that render smaller visually must have padding to meet 44px minimum hit area. +- **Contrast ratios (WCAG AA):** + - Body text (13px): 4.5:1 minimum against surface backgrounds + - Large text (18px+ or 14px bold): 3:1 minimum + - UI components (borders, icons): 3:1 minimum against adjacent colors + - Dark: text-primary #f0ece8 on bg-surface #1a1918: 14.9:1 ✓ + - Dark: text-secondary #a8a29e on bg-surface #1a1918: 7.0:1 ✓ + - Dark: text-tertiary #78716c on bg-surface #1a1918: 3.7:1 ✓ (labels only, not body text) + - Dark: accent #8b9cf7 on bg-surface #1a1918: 6.9:1 ✓ + - Light: text-primary #1c1917 on bg-surface #ffffff: 17.5:1 ✓ + - Light: text-secondary #57534e on bg-surface #ffffff: 7.6:1 ✓ + - Light: text-tertiary #736e6b on bg-surface #ffffff: 5.0:1 ✓ + - Light: accent #5c64b5 on bg-surface #ffffff: 5.3:1 ✓ +- **Focus indicators:** `outline: 2px solid var(--accent); outline-offset: 2px` on `:focus-visible`. Never `outline: none` without a visible replacement. +- **Reduced motion:** `@media (prefers-reduced-motion: reduce)` disables all animations and transitions globally. Non-negotiable. +- **Color independence:** Never encode meaning with color alone. Always pair colored dots with text labels. Status pills include both dot and text. +- **Keyboard navigation:** All interactive elements reachable via Tab. Logical tab order. Escape closes modals/popovers. Arrow keys navigate within lists. +- **Screen reader:** ARIA labels on all icon-only buttons. `role="heading"` with `aria-level` on non-heading elements styled as headings. Status changes announced via `aria-live` regions. + +## Component Anatomy + +### Session Card +``` +┌─ 2px left border (status color) ─────────────────────┐ +│ ┌─ Card (bg-surface, 1px border-default, 2px radius) │ +│ │ Title (text-primary, 12px, weight 500) │ +│ │ Branch · PR # (mono, text-tertiary, 10px) │ +│ │ ┌─ Status pill ────────────────────┐ │ +│ │ │ ● dot (6px, status color) Label │ │ +│ │ └──────────────────────────────────┘ │ +│ │ inset 0 1px 0 rgba(255,255,255,0.03) (dark only) │ +│ └─────────────────────────────────────────────────────│ +└───────────────────────────────────────────────────────┘ +``` +- **Padding:** 10px 12px +- **Spacing:** 4px between title and meta, 6px between meta and status +- **Hover:** bg-elevated-hover, border-color transition 0.12s +- **Active:** scale(0.99), 80ms +- **Containment:** `contain: layout style paint` for 30+ card performance + +### Button States +| State | Primary | Secondary | Ghost | Danger | +|-------|---------|-----------|-------|--------| +| Rest | bg: accent, text: #121110 | bg: elevated, border: border-default | bg: transparent | bg: transparent, border: red/30% | +| Hover | bg: accent-hover | bg: elevated-hover, border: border-strong | bg: bg-subtle | bg: red/8%, border: red | +| Active | scale(0.97) | scale(0.97) | scale(0.97) | scale(0.97) | +| Focus | outline: 2px accent | outline: 2px accent | outline: 2px accent | outline: 2px accent | +| Disabled | opacity: 0.5, cursor: not-allowed | opacity: 0.5 | opacity: 0.5 | opacity: 0.5 | +- **Padding:** 8px 16px +- **Font:** Geist Sans, 13px, weight 500 +- **Border-radius:** 0 +- **Min touch target:** 44px height (add padding if needed) + +### Input Fields +| State | Appearance | +|-------|------------| +| Rest | bg: bg-base, border: border-default, text: text-primary | +| Placeholder | color: text-tertiary | +| Focus | border-color: accent, no outline (border IS the indicator) | +| Error | border-color: status-error, error message below in status-error color | +| Disabled | opacity: 0.5, cursor: not-allowed, bg: bg-subtle | +- **Padding:** 8px 12px +- **Font:** Geist Sans, 13px +- **Border-radius:** 0 + +### Status Pill +- **Layout:** inline-flex, center-aligned, gap 6px +- **Dot:** 6px circle, filled with status color +- **Text:** 11px, weight 600, text-secondary +- **Background:** bg-subtle +- **Padding:** 4px 10px +- **Border-radius:** 0 + +### Alert / Banner +- **Layout:** flex, padding 12px 16px +- **Left border:** 2px solid, colored by severity +- **Background:** status color at 6% opacity +- **Text:** status color, 13px +- **Border-radius:** 0 +- **Variants:** success (green), warning (amber), error (red), info (cyan) + ## Performance Guidelines - Use `contain: layout style paint` and `content-visibility: auto` on session cards - Animate only `transform` and `opacity` (GPU-composited). Never animate `padding`, `margin`, `height`, `width`, `border`, or `box-shadow`. @@ -143,15 +224,22 @@ - `scale(0)` entry animations — start from `scale(0.95)` with `opacity: 0` - `ease-in` on UI elements — use `ease-out` for responsiveness - Animations over 300ms on frequently-triggered UI elements +- Neutral gray surfaces (#111, #222) — always use warm-tinted variants +- Blue-white text (#eef3ff) — use cream (#f0ece8) to maintain warmth +- `outline: none` without a visible focus replacement ## Decisions Log | Date | Decision | Rationale | |------|----------|-----------| -| 2026-03-28 | Initial design system created | Created by /design-consultation with competitive research (Conductor.build, T3 Code, OpenAI Codex, Emdash) + 4 design voices (primary, Codex CLI, Claude subagent, Emil Kowalski design eng) | -| 2026-03-28 | Geist Sans + JetBrains Mono (2 fonts only) | Emil review: 4 fonts creates cognitive gear-shifts on scan-heavy dashboards. Two fonts, hierarchy through weight + tracking. | -| 2026-03-28 | Accent #5B7EF8 instead of Tailwind blue-500 | Emil review: stock Tailwind blue reads "default" and undermines Industrial Precision identity. Existing token-reference blue is more sophisticated. | -| 2026-03-28 | Dual accent (cool blue + warm amber) | All 3 outside voices independently proposed amber for attention states. Two accents with clear semantic roles beat a single color. | -| 2026-03-28 | Fixed-width kanban columns (not variable) | Emil review: variable width breaks spatial memory. Encode urgency in column header dots instead. | -| 2026-03-28 | 2px base border-radius | Full 0px risks looking unstyled. 2px reads as intentionally sharp while feeling designed. Consistent across cards, buttons, inputs. Distinct from competitors' 8-12px rounded corners. | -| 2026-03-28 | Keep dot pulse, remove border heartbeat | Emil review: 4s border animation on 15+ cards is "decorative anxiety" with high perf cost. Existing 8px dot pulse is correct pattern — small surface, GPU-composited. | -| 2026-03-28 | Simplify ready-to-merge to single animation | Emil review: 3 concurrent keyframe animations on merge-ready cards is over-engineered. One animation per element, one purpose. Keep dot pulse only. | +| 2026-03-28 | Initial design system created | Created by /design-consultation with competitive research (Conductor.build, T3 Code, OpenAI Codex, Emdash) + 4 design voices | +| 2026-03-28 | Geist Sans + JetBrains Mono (2 fonts only) | Emil review: 4 fonts creates cognitive gear-shifts on scan-heavy dashboards | +| 2026-03-28 | 2px base border-radius (v1) | Full 0px risks looking unstyled. 2px reads as intentionally sharp while feeling designed. | +| 2026-04-05 | 0px border-radius everywhere | Hard edges are the identity. With warm surfaces and inset highlights providing depth, rounding adds nothing. Zero radius is the most honest expression of Industrial/Warm Terminal. | +| 2026-03-28 | Keep dot pulse, remove border heartbeat | Emil review: 4s border animation on 15+ cards is "decorative anxiety" with high perf cost. | +| 2026-04-05 | Fresh design system: Warm Terminal | Every competitor converges on cool blue-gray. Warm charcoal with cream text and warm periwinkle accent creates instant visual distinction. | +| 2026-04-05 | JetBrains Mono for display + data | Mono headlines in a mono-heavy dashboard create typographic cohesion instead of two competing voices. Free, open source, already in the codebase. | +| 2026-04-05 | Warm periwinkle #8b9cf7 accent (not gold) | Gold collides semantically with amber attention state. Blue = clickable is muscle memory. Warm periwinkle fits the palette without signal confusion. | +| 2026-04-05 | Brown-tinted surfaces, not neutral or blue-tinted | #121110 / #1a1918 / #222120 — warm undertone sets AO apart from every Linear clone. Light mode uses warm parchment #f5f3f0. | +| 2026-04-05 | Added accessibility section | Missing from v1. Touch targets 44px min, WCAG AA contrast, focus-visible, prefers-reduced-motion. | +| 2026-04-05 | Added component anatomy section | Missing from v1. Button states, input states, card structure, status pill, alert anatomy. | +| 2026-04-05 | Added light mode rationale | v1 listed values without explaining why. Warm parchment base, white card float, desaturated accent. | diff --git a/packages/web/src/__tests__/components.test.tsx b/packages/web/src/__tests__/components.test.tsx index 0571993eb..f5ae3b86f 100644 --- a/packages/web/src/__tests__/components.test.tsx +++ b/packages/web/src/__tests__/components.test.tsx @@ -575,7 +575,23 @@ describe("AttentionZone", () => { it("renders empty state when sessions array is empty", () => { render(); - expect(screen.getByText("No sessions")).toBeInTheDocument(); + expect(screen.getByText("No agents need your input.")).toBeInTheDocument(); + }); + + it("renders zone-specific empty messages for all attention zones", () => { + const cases: Array<[string, string]> = [ + ["review", "No code waiting for review."], + ["pending", "Nothing blocked."], + ["working", "No agents running."], + ["done", "No completed sessions."], + ]; + for (const [level, expectedMessage] of cases) { + const { unmount } = render( + , + ); + expect(screen.getByText(expectedMessage)).toBeInTheDocument(); + unmount(); + } }); it("shows session cards when not collapsed", () => { diff --git a/packages/web/src/app/globals.css b/packages/web/src/app/globals.css index 9e57b0d2a..2c2968310 100644 --- a/packages/web/src/app/globals.css +++ b/packages/web/src/app/globals.css @@ -27,71 +27,71 @@ /* ── Light mode (default) ─────────────────────────────────────────── */ :root { /* Base surfaces — design-system light palette */ - --color-bg-base: #f5f5f7; + --color-bg-base: #f5f3f0; --color-bg-surface: #ffffff; --color-bg-elevated: #ffffff; - --color-bg-elevated-hover: #f7f7f8; - --color-bg-subtle: #f0f0f2; + --color-bg-elevated-hover: #f7f5f2; + --color-bg-subtle: rgba(120, 100, 80, 0.05); /* Backward-compat aliases */ --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: #e7e7ec; - --color-border-default: #d9d9de; - --color-border-strong: #c9c9d2; + /* Borders — warm stone tint */ + --color-border-subtle: #e7e5e4; + --color-border-default: #d6d3d1; + --color-border-strong: #c4bfba; /* Backward-compat aliases */ --color-border-muted: var(--color-border-subtle); --color-border-emphasis: var(--color-border-strong); - /* Text */ - --color-text-primary: #1b1b1f; - --color-text-secondary: #5e5e66; - --color-text-tertiary: #8e8e96; - --color-text-muted: #8e8e96; + /* Text — warm near-black and stone tones */ + --color-text-primary: #1c1917; + --color-text-secondary: #57534e; + --color-text-tertiary: #736e6b; + --color-text-muted: #736e6b; --color-text-inverse: #ffffff; - /* Interactive accent */ - --color-accent: #5B7EF8; - --color-accent-hover: #7a96ff; - --color-accent-subtle: rgba(91, 126, 248, 0.12); + /* Interactive accent — warm periwinkle, darkened for light mode contrast */ + --color-accent: #5c64b5; + --color-accent-hover: #6b73c4; + --color-accent-subtle: rgba(92, 100, 181, 0.12); /* Status / semantic colors */ --color-status-working: #16a34a; - --color-status-ready: #5e6ad2; - --color-status-respond: #ca8a04; + --color-status-ready: #6b73c4; + --color-status-respond: #b8860b; --color-status-review: #0891b2; - --color-status-attention: #9a6700; - --color-status-idle: #8b949e; - --color-status-done: #d0d7de; + --color-status-attention: #b8860b; + --color-status-idle: #a8a29e; + --color-status-done: #d6d3d1; --color-status-error: #dc2626; /* Semantic aliases */ - --color-accent-blue: #5B7EF8; + --color-accent-blue: #5c64b5; --color-accent-green: #16a34a; - --color-accent-yellow: #9a6700; + --color-accent-yellow: #b8860b; --color-accent-orange: #bc4c00; --color-accent-red: #dc2626; - --color-accent-violet: #5B7EF8; - --color-accent-purple: #5B7EF8; + --color-accent-violet: #5c64b5; + --color-accent-purple: #5c64b5; /* Theme-adaptive tints (bg tints behind status pills) */ - --color-tint-blue: rgba(91, 126, 248, 0.08); + --color-tint-blue: rgba(92, 100, 181, 0.08); --color-tint-green: rgba(22, 163, 74, 0.08); - --color-tint-yellow: rgba(154, 103, 0, 0.08); + --color-tint-yellow: rgba(184, 134, 11, 0.08); --color-tint-red: rgba(220, 38, 38, 0.08); - --color-tint-violet: rgba(91, 126, 248, 0.08); + --color-tint-violet: rgba(92, 100, 181, 0.08); --color-tint-orange: rgba(188, 76, 0, 0.08); --color-tint-neutral: rgba(0, 0, 0, 0.04); /* Chip/badge background */ - --color-chip-bg: #f2f2f2; + --color-chip-bg: #f0ece8; /* Hover overlay */ - --color-hover-overlay: #f7f7f8; + --color-hover-overlay: #f7f5f2; /* Scrollbar */ --color-scrollbar: rgba(0, 0, 0, 0.08); @@ -105,32 +105,32 @@ /* Nav */ --color-nav-bg: #ffffff; - /* Card surfaces — clean white on light gray */ + /* Card surfaces — warm white on warm parchment */ --card-bg: #ffffff; - --card-merge-bg: #f6fbf8; - --card-expanded-bg: #f7f7f8; + --card-merge-bg: #f6faf5; + --card-expanded-bg: #f7f5f2; --card-shadow: none; --card-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.08); --card-inset: none; - --card-border: #d5d7de; + --card-border: #d6d3d1; /* Done card surfaces */ - --card-done-bg: #fafafa; - --card-done-border: #e8e8ec; + --card-done-bg: #faf9f7; + --card-done-border: #e7e5e4; - --done-pill-exited-bg: #f2f2f2; - --done-pill-exited-color: #8b8b93; + --done-pill-exited-bg: #f0ece8; + --done-pill-exited-color: #78716c; --done-pill-merged-bg: rgba(26, 127, 55, 0.08); --done-pill-merged-color: #1a7f37; --done-pill-killed-bg: rgba(207, 34, 46, 0.06); --done-pill-killed-color: #cf222e; - --done-meta-chip-bg: #f2f2f2; - --done-meta-chip-border: #e8e8ec; - --done-section-border: #ececf0; - --done-title-color: #5e5e66; - --done-restore-bg: rgba(9, 105, 218, 0.06); - --done-restore-border: rgba(9, 105, 218, 0.2); - --done-restore-hover-bg: rgba(9, 105, 218, 0.12); + --done-meta-chip-bg: #f0ece8; + --done-meta-chip-border: #e7e5e4; + --done-section-border: #e7e5e4; + --done-title-color: #57534e; + --done-restore-bg: rgba(92, 100, 181, 0.06); + --done-restore-border: rgba(92, 100, 181, 0.2); + --done-restore-hover-bg: rgba(92, 100, 181, 0.12); /* Detail card */ --detail-card-bg: #ffffff; @@ -142,18 +142,18 @@ --btn-inset: none; /* Kanban column */ - --color-column-bg: #f4f5f7; + --color-column-bg: #f3f1ee; --color-column-header: transparent; /* Alert colors */ - --color-alert-ci: #6366f1; - --color-alert-ci-bg: #4f46e5; - --color-alert-ci-unknown: #9a6700; + --color-alert-ci: #5c64b5; + --color-alert-ci-bg: #4f4da0; + --color-alert-ci-unknown: #b8860b; --color-alert-review: #1a7f37; --color-alert-review-bg: #1a7f37; - --color-alert-changes: #5B7EF8; - --color-alert-changes-bg: #5B7EF8; - --color-alert-conflict: #9a6700; + --color-alert-changes: #5c64b5; + --color-alert-changes-bg: #5c64b5; + --color-alert-conflict: #b8860b; --color-alert-conflict-bg: #92400e; --color-alert-comment: #bc4c00; --color-alert-comment-bg: #c2410c; @@ -165,130 +165,130 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - /* Base surfaces — graphite dashboard */ - --color-bg-base: #0a0d12; - --color-bg-surface: #11161d; - --color-bg-elevated: #171d26; - --color-bg-elevated-hover: #1c2430; - --color-bg-subtle: rgba(177, 206, 255, 0.05); - - /* Borders */ - --color-border-subtle: rgba(160, 190, 255, 0.08); - --color-border-default: rgba(160, 190, 255, 0.14); - --color-border-strong: rgba(185, 214, 255, 0.24); - - /* Text */ - --color-text-primary: #eef3ff; - --color-text-secondary: #a5afc4; - --color-text-tertiary: #6f7c94; - --color-text-muted: #6f7c94; - --color-text-inverse: #0a0d12; - - /* Interactive accent */ - --color-accent: #5B7EF8; - --color-accent-hover: #7a96ff; - --color-accent-subtle: rgba(91, 126, 248, 0.12); + /* Base surfaces — warm charcoal (brown-tinted, not blue/neutral) */ + --color-bg-base: #121110; + --color-bg-surface: #1a1918; + --color-bg-elevated: #222120; + --color-bg-elevated-hover: #2a2928; + --color-bg-subtle: rgba(255, 240, 220, 0.04); + + /* Borders — warm-tinted transparency */ + --color-border-subtle: rgba(255, 240, 220, 0.06); + --color-border-default: rgba(255, 240, 220, 0.10); + --color-border-strong: rgba(255, 240, 220, 0.18); + + /* Text — cream, not blue-white */ + --color-text-primary: #f0ece8; + --color-text-secondary: #a8a29e; + --color-text-tertiary: #78716c; + --color-text-muted: #78716c; + --color-text-inverse: #121110; + + /* Interactive accent — warm periwinkle */ + --color-accent: #8b9cf7; + --color-accent-hover: #a3b1fa; + --color-accent-subtle: rgba(139, 156, 247, 0.12); /* Status */ --color-status-working: #22c55e; - --color-status-ready: #5B7EF8; - --color-status-respond: #f1be64; + --color-status-ready: #8b9cf7; + --color-status-respond: #e2a336; --color-status-review: #06b6d4; - --color-status-attention: #f1be64; - --color-status-idle: #293142; - --color-status-done: #3a4252; + --color-status-attention: #e2a336; + --color-status-idle: #44403c; + --color-status-done: #57534e; --color-status-error: #ef4444; /* Semantic aliases */ - --color-accent-blue: #5B7EF8; + --color-accent-blue: #8b9cf7; --color-accent-green: #22c55e; - --color-accent-yellow: #f1be64; + --color-accent-yellow: #e2a336; --color-accent-orange: #ff9d57; --color-accent-red: #ef4444; - --color-accent-violet: #5B7EF8; - --color-accent-purple: #5B7EF8; + --color-accent-violet: #8b9cf7; + --color-accent-purple: #8b9cf7; /* Theme-adaptive tints */ - --color-tint-blue: rgba(91, 126, 248, 0.12); + --color-tint-blue: rgba(139, 156, 247, 0.12); --color-tint-green: rgba(34, 197, 94, 0.12); - --color-tint-yellow: rgba(241, 190, 100, 0.12); + --color-tint-yellow: rgba(226, 163, 54, 0.12); --color-tint-red: rgba(239, 68, 68, 0.12); - --color-tint-violet: rgba(91, 126, 248, 0.12); + --color-tint-violet: rgba(139, 156, 247, 0.12); --color-tint-orange: rgba(255, 157, 87, 0.12); --color-tint-neutral: rgba(255, 255, 255, 0.05); /* Chip/badge background */ - --color-chip-bg: rgba(255, 255, 255, 0.06); + --color-chip-bg: rgba(255, 240, 220, 0.06); /* Hover overlay */ - --color-hover-overlay: rgba(143, 180, 255, 0.05); + --color-hover-overlay: rgba(255, 240, 220, 0.05); /* Scrollbar */ --color-scrollbar: rgba(255, 255, 255, 0.08); --color-scrollbar-hover: rgba(255, 255, 255, 0.15); --color-scrollbar-active: rgba(255, 255, 255, 0.25); - /* Body */ - --color-body-gradient-blue: rgba(110, 143, 255, 0.2); - --color-body-gradient-violet: rgba(91, 126, 248, 0.08); + /* Body — warm ambient glow, not blue radials */ + --color-body-gradient-blue: rgba(139, 156, 247, 0.08); + --color-body-gradient-violet: rgba(139, 120, 100, 0.06); /* Nav glass */ - --color-nav-bg: rgba(10, 13, 18, 0.82); + --color-nav-bg: rgba(18, 17, 16, 0.82); - /* Card surfaces */ - --card-bg: linear-gradient(180deg, rgba(22, 28, 37, 0.98) 0%, rgba(16, 21, 29, 0.98) 100%); - --card-merge-bg: rgba(17, 23, 31, 0.98); + /* Card surfaces — warm gradients */ + --card-bg: linear-gradient(180deg, rgba(26, 25, 24, 0.98) 0%, rgba(18, 17, 16, 0.98) 100%); + --card-merge-bg: rgba(20, 22, 18, 0.98); --card-expanded-bg: linear-gradient( 180deg, - rgba(26, 34, 45, 0.98) 0%, - rgba(18, 24, 33, 0.98) 100% + rgba(34, 33, 32, 0.98) 0%, + rgba(26, 25, 24, 0.98) 100% ); - --card-shadow: 0 18px 36px rgba(2, 6, 12, 0.24); - --card-shadow-hover: 0 24px 54px rgba(2, 6, 12, 0.34); - --card-inset: inset 0 1px 0 rgba(255, 255, 255, 0.04); - --card-border: rgba(166, 190, 226, 0.18); + --card-shadow: 0 18px 36px rgba(0, 0, 0, 0.24); + --card-shadow-hover: 0 24px 54px rgba(0, 0, 0, 0.34); + --card-inset: inset 0 1px 0 rgba(255, 255, 255, 0.03); + --card-border: rgba(255, 240, 220, 0.12); /* Done card surfaces */ - --card-done-bg: linear-gradient(180deg, rgba(18, 24, 33, 0.72) 0%, rgba(14, 19, 27, 0.72) 100%); - --card-done-border: rgba(160, 190, 255, 0.1); + --card-done-bg: linear-gradient(180deg, rgba(22, 21, 20, 0.72) 0%, rgba(18, 17, 16, 0.72) 100%); + --card-done-border: rgba(255, 240, 220, 0.08); - --done-pill-exited-bg: rgba(143, 180, 255, 0.08); - --done-pill-exited-color: #8a9ab6; + --done-pill-exited-bg: rgba(255, 240, 220, 0.06); + --done-pill-exited-color: #a8a29e; --done-pill-merged-bg: rgba(95, 211, 154, 0.14); --done-pill-merged-color: #5fd39a; --done-pill-killed-bg: rgba(255, 123, 114, 0.12); --done-pill-killed-color: #ff7b72; - --done-meta-chip-bg: rgba(143, 180, 255, 0.05); - --done-meta-chip-border: rgba(160, 190, 255, 0.1); - --done-section-border: rgba(160, 190, 255, 0.08); - --done-title-color: #9ba8bf; - --done-restore-bg: rgba(143, 180, 255, 0.1); - --done-restore-border: rgba(143, 180, 255, 0.24); - --done-restore-hover-bg: rgba(143, 180, 255, 0.18); + --done-meta-chip-bg: rgba(255, 240, 220, 0.04); + --done-meta-chip-border: rgba(255, 240, 220, 0.08); + --done-section-border: rgba(255, 240, 220, 0.06); + --done-title-color: #a8a29e; + --done-restore-bg: rgba(139, 156, 247, 0.1); + --done-restore-border: rgba(139, 156, 247, 0.24); + --done-restore-hover-bg: rgba(139, 156, 247, 0.18); /* Detail card */ - --detail-card-bg: linear-gradient(180deg, rgba(20, 26, 35, 0.98) 0%, rgba(14, 19, 27, 0.98) 100%); + --detail-card-bg: linear-gradient(180deg, rgba(26, 25, 24, 0.98) 0%, rgba(18, 17, 16, 0.98) 100%); --detail-card-shadow: 0 18px 42px rgba(0, 0, 0, 0.28); /* Orchestrator button */ - --btn-shadow: 0 10px 24px rgba(34, 63, 116, 0.16); - --btn-shadow-hover: 0 16px 34px rgba(34, 63, 116, 0.22); - --btn-inset: inset 0 1px 0 rgba(255, 255, 255, 0.08); + --btn-shadow: 0 10px 24px rgba(0, 0, 0, 0.16); + --btn-shadow-hover: 0 16px 34px rgba(0, 0, 0, 0.22); + --btn-inset: inset 0 1px 0 rgba(255, 255, 255, 0.06); /* Kanban column */ - --color-column-bg: #0d1218; + --color-column-bg: #161514; --color-column-header: transparent; - /* Alert colors — Linear-muted */ - --color-alert-ci: #7b85e0; - --color-alert-ci-bg: rgba(94, 106, 210, 0.25); - --color-alert-ci-unknown: #d4a72c; + /* Alert colors — warm-tinted */ + --color-alert-ci: #8b9cf7; + --color-alert-ci-bg: rgba(139, 156, 247, 0.25); + --color-alert-ci-unknown: #e2a336; --color-alert-review: #4dab6e; --color-alert-review-bg: rgba(77, 171, 110, 0.25); - --color-alert-changes: #5B7EF8; - --color-alert-changes-bg: rgba(91, 126, 248, 0.25); - --color-alert-conflict: #d4a72c; - --color-alert-conflict-bg: rgba(212, 167, 44, 0.25); + --color-alert-changes: #8b9cf7; + --color-alert-changes-bg: rgba(139, 156, 247, 0.25); + --color-alert-conflict: #e2a336; + --color-alert-conflict-bg: rgba(226, 163, 54, 0.25); --color-alert-comment: #c88a2e; --color-alert-comment-bg: rgba(200, 138, 46, 0.25); @@ -300,7 +300,7 @@ --font-mono: var(--font-jetbrains-mono), "SF Mono", "Menlo", "Consolas", monospace; /* ── Border radius ───────────────────────────────────────────────── */ - --radius-base: 2px; + --radius-base: 0; --radius-sm: 4px; --radius-md: 6px; --radius-lg: 8px; @@ -353,36 +353,36 @@ 0 4px 8px rgba(0, 0, 0, 0.85), 0 14px 36px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.1); - /* Gradient surfaces */ + /* Gradient surfaces — warm tints */ --gradient-session-card: linear-gradient( 175deg, - rgba(30, 30, 35, 1) 0%, - rgba(23, 23, 27, 1) 100% + rgba(30, 29, 28, 1) 0%, + rgba(23, 22, 21, 1) 100% ); --gradient-session-card-expanded: linear-gradient( 175deg, - rgba(38, 38, 44, 1) 0%, - rgba(30, 30, 35, 1) 100% + rgba(38, 37, 36, 1) 0%, + rgba(30, 29, 28, 1) 100% ); --gradient-session-card-merge: linear-gradient( 175deg, - rgba(23, 29, 25, 1) 0%, - rgba(17, 22, 19, 1) 100% + rgba(23, 25, 22, 1) 0%, + rgba(17, 18, 16, 1) 100% ); - --gradient-detail-card: linear-gradient(175deg, rgba(30, 30, 35, 1) 0%, rgba(23, 23, 27, 1) 100%); + --gradient-detail-card: linear-gradient(175deg, rgba(30, 29, 28, 1) 0%, rgba(23, 22, 21, 1) 100%); --gradient-orchestrator-btn: linear-gradient( 175deg, - rgba(94, 106, 210, 0.12) 0%, - rgba(94, 106, 210, 0.06) 100% + rgba(139, 156, 247, 0.12) 0%, + rgba(139, 156, 247, 0.06) 100% ); --gradient-orchestrator-btn-hover: linear-gradient( 175deg, - rgba(94, 106, 210, 0.18) 0%, - rgba(94, 106, 210, 0.1) 100% + rgba(139, 156, 247, 0.18) 0%, + rgba(139, 156, 247, 0.1) 100% ); --gradient-status-strip: linear-gradient( to bottom, - rgba(94, 106, 210, 0.04) 0%, + rgba(139, 156, 247, 0.04) 0%, transparent 100% ); } @@ -597,7 +597,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { /* ── Orchestrator button ──────────────────────────────────────────────── */ .orchestrator-btn { - border-radius: 2px; + border-radius: 0; color: var(--color-accent); background: linear-gradient( 175deg, @@ -660,7 +660,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { position: relative; overflow: visible; border: 1px solid var(--color-border-default); - border-radius: 2px; + border-radius: 0; background: var(--color-bg-surface); box-shadow: var(--card-shadow); } @@ -718,10 +718,12 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .dashboard-title { + font-family: var(--font-mono); font-size: clamp(22px, 2.8vw, 32px); line-height: 1; - letter-spacing: -0.05em; - font-weight: 600; + letter-spacing: -0.02em; + font-weight: 500; + text-wrap: balance; color: var(--color-text-primary); } @@ -778,6 +780,94 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { font-variant-numeric: tabular-nums; } +.dashboard-stat-card__value--working { + color: var(--color-status-working); +} + +.dashboard-stat-card__value--review { + color: var(--color-accent-orange); +} + +/* ── Project metric tone colors ──────────────────────────────────────── */ + +.project-metric__value[data-tone="ready"] { color: var(--color-status-ready); } +.project-metric__value[data-tone="error"] { color: var(--color-status-error); } +.project-metric__value[data-tone="orange"] { color: var(--color-accent-orange); } +.project-metric__value[data-tone="attention"] { color: var(--color-status-attention); } +.project-metric__value[data-tone="working"] { color: var(--color-status-working); } + +/* ── Board legend dot colors ─────────────────────────────────────────── */ + +.board-legend-item__dot[data-tone="error"] { background: var(--color-status-error); } +.board-legend-item__dot[data-tone="orange"] { background: var(--color-accent-orange); } +.board-legend-item__dot[data-tone="ready"] { background: var(--color-status-ready); } + +/* ── Sidebar metric value colors ─────────────────────────────────────── */ + +.project-sidebar__metric-value--attention { color: var(--color-status-attention); } +.project-sidebar__metric-value--error { color: var(--color-status-error); } + +/* ── Sidebar session dot colors ──────────────────────────────────────── */ + +.sidebar-session-dot[data-level="merge"] { background: var(--color-status-ready); } +.sidebar-session-dot[data-level="respond"] { background: var(--color-status-error); } +.sidebar-session-dot[data-level="review"] { background: var(--color-accent-orange); } +.sidebar-session-dot[data-level="pending"] { background: var(--color-status-attention); } +.sidebar-session-dot[data-level="working"] { background: var(--color-status-working); } +.sidebar-session-dot[data-level="done"] { background: var(--color-text-tertiary); } + +/* ── Sidebar health dot colors ───────────────────────────────────────── */ + +.sidebar-health-dot[data-health="red"], +.project-sidebar__health-indicator[data-health="red"] { background: var(--color-status-error); } +.sidebar-health-dot[data-health="yellow"], +.project-sidebar__health-indicator[data-health="yellow"] { background: var(--color-status-attention); } +.sidebar-health-dot[data-health="green"], +.project-sidebar__health-indicator[data-health="green"] { background: var(--color-status-ready); } +.sidebar-health-dot[data-health="gray"], +.project-sidebar__health-indicator[data-health="gray"] { background: var(--color-text-tertiary); } + +/* ── Kanban / accordion / mobile dot colors (by level) ───────────────── */ + +.kanban-column__dot[data-level="working"] { background: var(--color-status-working); } +.kanban-column__dot[data-level="pending"] { background: var(--color-status-attention); } +.kanban-column__dot[data-level="review"] { background: var(--color-accent-orange); } +.kanban-column__dot[data-level="respond"] { background: var(--color-status-error); } +.kanban-column__dot[data-level="merge"] { background: var(--color-status-ready); } +.kanban-column__dot[data-level="done"] { background: var(--color-text-tertiary); } + +/* ── Activity dot / pill colors ──────────────────────────────────────── */ + +/* Base fallbacks for null / unknown activity states */ +.activity-dot { background: var(--color-text-tertiary); } +.activity-pill { background: var(--color-tint-neutral); } +.activity-pill__text { color: var(--color-text-muted); } + +.activity-dot[data-activity="active"] { background: var(--color-status-working); } +.activity-dot[data-activity="ready"] { background: var(--color-status-ready); } +.activity-dot[data-activity="idle"] { background: var(--color-status-idle); } +.activity-dot[data-activity="waiting_input"] { background: var(--color-status-attention); } +.activity-dot[data-activity="blocked"] { background: var(--color-status-error); } +.activity-dot[data-activity="exited"] { background: var(--color-status-done); } + +.activity-pill[data-activity="active"] { background: var(--color-tint-green); } +.activity-pill[data-activity="ready"] { background: var(--color-tint-blue); } +.activity-pill[data-activity="idle"] { background: var(--color-tint-neutral); } +.activity-pill[data-activity="waiting_input"] { background: var(--color-tint-yellow); } +.activity-pill[data-activity="blocked"] { background: var(--color-tint-red); } +.activity-pill[data-activity="exited"] { background: var(--color-tint-neutral); } + +.activity-pill__text[data-activity="active"] { color: var(--color-status-working); } +.activity-pill__text[data-activity="ready"] { color: var(--color-status-ready); } +.activity-pill__text[data-activity="idle"] { color: var(--color-text-secondary); } +.activity-pill__text[data-activity="waiting_input"] { color: var(--color-status-attention); } +.activity-pill__text[data-activity="blocked"] { color: var(--color-status-error); } +.activity-pill__text[data-activity="exited"] { color: var(--color-text-muted); } + +/* ── Done card title color ───────────────────────────────────────────── */ + +.session-card-done__title { color: var(--done-title-color); } + .dashboard-stat-card__label { font-size: 10px; font-weight: 700; @@ -792,7 +882,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .dashboard-alert { - border-radius: 2px; + border-radius: 0; box-shadow: var(--detail-card-shadow); } @@ -801,7 +891,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .detail-card { background: var(--detail-card-bg); box-shadow: var(--detail-card-shadow); - border-radius: 2px; + border-radius: 0; } .session-detail-page { @@ -822,7 +912,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { padding: 14px 16px; background: var(--detail-card-bg); box-shadow: var(--detail-card-shadow); - border-radius: 2px; + border-radius: 0; } @media (min-width: 900px) { @@ -890,7 +980,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { min-height: 28px; padding: 0 10px; border: 1px solid var(--color-border-subtle); - border-radius: 2px; + border-radius: 0; background: color-mix(in srgb, var(--color-bg-elevated) 88%, transparent); color: var(--color-text-secondary); font-size: 11px; @@ -957,18 +1047,10 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { z-index: 1; } -.session-detail-status-pill--active { - animation: session-status-active-breathe 2.8s ease-in-out infinite; -} - .session-detail-status-pill--active .session-detail-status-pill__dot { animation: session-status-dot-pulse 1.8s ease-in-out infinite; } -.session-detail-status-pill--ready { - animation: session-status-ready-breathe 3.2s ease-in-out infinite; -} - .session-detail-status-pill--ready .session-detail-status-pill__dot { animation: session-status-dot-pulse 2.2s ease-in-out infinite; } @@ -977,8 +1059,8 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { opacity: 0.92; } -.session-detail-status-pill--waiting { - animation: session-status-waiting-breathe 3s ease-in-out infinite; +.session-detail-status-pill--waiting .session-detail-status-pill__dot { + animation: session-status-dot-pulse 2s ease-in-out infinite; } @keyframes session-status-dot-pulse { @@ -992,51 +1074,12 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } } -@keyframes session-status-active-breathe { - 0%, 100% { - transform: translateY(0); - opacity: 0.96; - } - 50% { - transform: translateY(-1px); - opacity: 1; - } -} - -@keyframes session-status-ready-breathe { - 0%, 100% { - transform: translateY(0); - opacity: 0.96; - } - 50% { - transform: translateY(-1px); - opacity: 1; - } -} - -@keyframes session-status-waiting-breathe { - 0%, 100% { - transform: translateY(0); - opacity: 0.95; - } - 50% { - transform: translateY(-1px); - opacity: 1; - } -} - -.dark .detail-card { - --color-text-secondary: #9898a0; - --color-text-muted: #5c5c66; - --color-text-tertiary: #5c5c66; -} - /* ── Session cards — flat, Linear-style ──────────────────────────────── */ .session-card { background: var(--card-bg); border-color: var(--card-border); - border-radius: 2px; + border-radius: 0; box-shadow: inset 0 1px 0 color-mix(in srgb, white 4%, transparent), 0 10px 20px rgba(0, 0, 0, 0.08); @@ -1072,9 +1115,6 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .dark .session-card { - --color-text-secondary: #9898a0; - --color-text-muted: #5c5c66; - --color-text-tertiary: #5c5c66; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 14px 30px rgba(0, 0, 0, 0.34); @@ -1322,8 +1362,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { display: flex; flex-direction: column; gap: 8px; - padding: 10px 12px; - border-top: 1px solid var(--color-border-default); + padding: 8px 16px 4px; } .quick-reply__summary { @@ -1543,7 +1582,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .project-sidebar nav::-webkit-scrollbar-thumb { background: var(--color-scrollbar); - border-radius: 2px; + border-radius: 0; } .project-sidebar { @@ -1790,7 +1829,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { align-items: center; justify-content: center; border: 1px solid var(--color-border-subtle); - border-radius: 8px; + border-radius: 0; background: var(--color-bg-surface); cursor: pointer; transition: @@ -1862,7 +1901,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .board-section-head__title { font-size: 12px; font-weight: 700; - letter-spacing: 0.16em; + letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-text-secondary); } @@ -1921,7 +1960,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .dark .kanban-column { - border-color: rgba(170, 195, 230, 0.14); + border-color: rgba(255, 240, 220, 0.14); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.02), 0 18px 42px rgba(0, 0, 0, 0.24); @@ -1958,6 +1997,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { letter-spacing: normal; text-transform: none; color: var(--color-text-primary); + text-wrap: balance; } .kanban-column__count { @@ -1997,7 +2037,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .kanban-column-body::-webkit-scrollbar-thumb { background: var(--color-scrollbar); - border-radius: 2px; + border-radius: 0; } /* ── Done / Terminated collapsible bar ────────────────────────────────── */ @@ -2043,7 +2083,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { justify-content: center; font-size: 10px; font-weight: 700; - border-radius: 999px; + border-radius: 0; background: var(--color-border-subtle); color: var(--color-text-muted); padding: 0 5px; @@ -2503,6 +2543,16 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { color: var(--color-text-secondary); } + /* ── Mobile action pill tone colors ──────────────────────────────────── */ + + .mobile-action-pill__dot[data-level="respond"] { background: var(--color-status-error); } + .mobile-action-pill__dot[data-level="merge"] { background: var(--color-status-ready); } + .mobile-action-pill__dot[data-level="review"] { background: var(--color-accent-orange); } + + .mobile-action-pill__count[data-level="respond"] { color: var(--color-status-error); } + .mobile-action-pill__count[data-level="merge"] { color: var(--color-status-ready); } + .mobile-action-pill__count[data-level="review"] { color: var(--color-accent-orange); } + /* -- Kanban board: stack columns vertically -- */ .kanban-board { flex-direction: column; @@ -2946,6 +2996,18 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { flex-shrink: 0; } + .accordion-header__dot[data-level="working"] { background: var(--color-status-working); } + .accordion-header__dot[data-level="pending"] { background: var(--color-status-attention); } + .accordion-header__dot[data-level="review"] { background: var(--color-accent-orange); } + .accordion-header__dot[data-level="respond"] { background: var(--color-status-error); } + .accordion-header__dot[data-level="merge"] { background: var(--color-status-ready); } + + .mobile-session-row__dot[data-level="working"] { background: var(--color-status-working); } + .mobile-session-row__dot[data-level="pending"] { background: var(--color-status-attention); } + .mobile-session-row__dot[data-level="review"] { background: var(--color-accent-orange); } + .mobile-session-row__dot[data-level="respond"] { background: var(--color-status-error); } + .mobile-session-row__dot[data-level="merge"] { background: var(--color-status-ready); } + .accordion-header__label { font-size: 16px; font-weight: 600; @@ -3171,7 +3233,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { color: var(--color-text-primary); padding: 10px 16px; max-width: 340px; - border-radius: 6px; + border-radius: 0; font-size: 13px; line-height: 1.4; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); @@ -3238,7 +3300,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { width: 36px; height: 4px; background: var(--color-border-default); - border-radius: 2px; + border-radius: 0; margin: 0 auto 16px; } @@ -3413,7 +3475,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .connection-bar--reconnecting { height: 4px; - background: #d4a017; /* amber — no token for this in the design system */ + background: var(--color-status-attention); animation: connection-pulse 1.5s ease-in-out infinite; } @@ -3428,3 +3490,15 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } + +/* ── Reduced motion ─────────────────────────────────────────────────── */ + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/packages/web/src/app/layout.tsx b/packages/web/src/app/layout.tsx index ae789ffe3..47bc5e29b 100644 --- a/packages/web/src/app/layout.tsx +++ b/packages/web/src/app/layout.tsx @@ -23,8 +23,8 @@ export const viewport: Viewport = { initialScale: 1, viewportFit: "cover", themeColor: [ - { media: "(prefers-color-scheme: light)", color: "#ffffff" }, - { media: "(prefers-color-scheme: dark)", color: "#0a0d12" }, + { media: "(prefers-color-scheme: light)", color: "#f5f3f0" }, + { media: "(prefers-color-scheme: dark)", color: "#121110" }, ], }; diff --git a/packages/web/src/app/manifest.ts b/packages/web/src/app/manifest.ts index c5a359a3e..6977692da 100644 --- a/packages/web/src/app/manifest.ts +++ b/packages/web/src/app/manifest.ts @@ -9,8 +9,8 @@ export default function manifest(): MetadataRoute.Manifest { description: "Dashboard for managing parallel AI coding agents", start_url: "/", display: "standalone", - background_color: "#0a0d12", - theme_color: "#0a0d12", + background_color: "#121110", + theme_color: "#121110", icons: [ { src: "/apple-icon", sizes: "180x180", type: "image/png" }, { src: "/icon-192", sizes: "192x192", type: "image/png", purpose: "any" }, diff --git a/packages/web/src/components/ActivityDot.tsx b/packages/web/src/components/ActivityDot.tsx index e60725682..7f638a9d8 100644 --- a/packages/web/src/components/ActivityDot.tsx +++ b/packages/web/src/components/ActivityDot.tsx @@ -2,22 +2,13 @@ import { cn } from "@/lib/cn"; -const activityConfig: Record< - string, - { label: string; dot: string; bg: string; text: string } -> = { - active: { label: "active", dot: "var(--color-status-working)", bg: "rgba(34,197,94,0.1)", text: "var(--color-status-working)" }, - ready: { label: "ready", dot: "var(--color-status-ready)", bg: "rgba(91,126,248,0.1)", text: "var(--color-status-ready)" }, - idle: { label: "idle", dot: "var(--color-status-idle)", bg: "rgba(72,79,88,0.25)", text: "var(--color-text-secondary)" }, - waiting_input: { label: "waiting", dot: "var(--color-status-attention)", bg: "rgba(210,153,34,0.12)", text: "var(--color-status-attention)" }, - blocked: { label: "blocked", dot: "var(--color-status-error)", bg: "rgba(248,81,73,0.1)", text: "var(--color-status-error)" }, - exited: { label: "exited", dot: "var(--color-status-done)", bg: "rgba(48,54,61,0.5)", text: "var(--color-text-muted)" }, -}; - -const fallbackConfig = { - dot: "var(--color-text-tertiary)", - bg: "rgba(74,74,74,0.2)", - text: "var(--color-text-muted)", +const activityLabels: Record = { + active: "active", + ready: "ready", + idle: "idle", + waiting_input: "waiting", + blocked: "blocked", + exited: "exited", }; interface ActivityDotProps { @@ -28,32 +19,31 @@ interface ActivityDotProps { } export function ActivityDot({ activity, dotOnly = false, size = 6 }: ActivityDotProps) { - const c = (activity !== null && activityConfig[activity]) || { - label: activity ?? "unknown", - ...fallbackConfig, - }; + const label = (activity !== null && activityLabels[activity]) || activity || "unknown"; + const dataActivity = activity ?? undefined; const isPulsing = activity === "active"; if (dotOnly) { return (
); } return ( - - {c.label} + + {label} ); diff --git a/packages/web/src/components/AttentionZone.tsx b/packages/web/src/components/AttentionZone.tsx index c5e6416a4..9afe6590c 100644 --- a/packages/web/src/components/AttentionZone.tsx +++ b/packages/web/src/components/AttentionZone.tsx @@ -32,39 +32,39 @@ const zoneConfig: Record< AttentionLevel, { label: string; - color: string; caption: string; + emptyMessage: string; } > = { merge: { label: "Ready", - color: "var(--color-status-ready)", caption: "Cleared to land", + emptyMessage: "Nothing cleared to land yet.", }, respond: { label: "Respond", - color: "var(--color-status-error)", caption: "Human judgment needed", + emptyMessage: "No agents need your input.", }, review: { label: "Review", - color: "var(--color-accent-orange)", caption: "Code waiting on eyes", + emptyMessage: "No code waiting for review.", }, pending: { label: "Pending", - color: "var(--color-status-attention)", caption: "Blocked on system state", + emptyMessage: "Nothing blocked.", }, working: { label: "Working", - color: "var(--color-status-working)", caption: "Agents are actively moving", + emptyMessage: "No agents running.", }, done: { label: "Done", - color: "var(--color-text-tertiary)", caption: "Completed or exited", + emptyMessage: "No completed sessions.", }, }; @@ -118,14 +118,15 @@ function AttentionZoneView({ className="accordion-header" onClick={() => onToggle(level)} aria-expanded={!collapsed} + aria-controls={`accordion-body-${level}`} > - + {config.label} {sessions.length} -
+
{sessions.length > 0 ? (
{visibleSessions.map((session) => @@ -159,7 +160,7 @@ function AttentionZoneView({
) : compactMobile ? (
-
No sessions
+
{config.emptyMessage}
) : null}
@@ -171,7 +172,7 @@ function AttentionZoneView({
-
+
{config.label} {sessions.length}
@@ -194,7 +195,7 @@ function AttentionZoneView({
) : (
- No sessions + {config.emptyMessage}
)}
@@ -247,7 +248,7 @@ function MobileSessionRow({
- - - + + +
@@ -874,23 +874,11 @@ function ProjectOverviewGrid({
- - - - - + + + + +
@@ -935,7 +923,7 @@ function ProjectMetric({ label, value, tone }: { label: string; value: number; t
{label}
-
+
{value}
@@ -943,22 +931,10 @@ function ProjectMetric({ label, value, tone }: { label: string; value: number; t } const MOBILE_ACTION_STRIP_LEVELS = [ - { - level: "respond" as const, - label: "respond", - color: "var(--color-status-error)", - }, - { - level: "merge" as const, - label: "merge", - color: "var(--color-status-ready)", - }, - { - level: "review" as const, - label: "review", - color: "var(--color-accent-orange)", - }, -] satisfies Array<{ level: AttentionLevel; label: string; color: string }>; + { level: "respond" as const, label: "respond" }, + { level: "merge" as const, label: "merge" }, + { level: "review" as const, label: "review" }, +] satisfies Array<{ level: AttentionLevel; label: string }>; function MobileActionStrip({ grouped, @@ -981,7 +957,7 @@ function MobileActionStrip({ return (
- {activePills.map(({ level, label, color }) => ( + {activePills.map(({ level, label }) => ( @@ -255,19 +240,13 @@ function ProjectSidebarInner({ active
- + {reviewLoadCount} review
- + {needsInputCount} blocked diff --git a/packages/web/src/components/SessionCard.tsx b/packages/web/src/components/SessionCard.tsx index dc86cb295..10fad1561 100644 --- a/packages/web/src/components/SessionCard.tsx +++ b/packages/web/src/components/SessionCard.tsx @@ -232,8 +232,7 @@ function SessionCardView({ session, onSend, onKill, onMerge, onRestore }: Sessio {/* Row 2: Title */}

{title}

@@ -590,6 +589,62 @@ function SessionCardView({ session, onSend, onKill, onMerge, onRestore }: Sessio
)} + {/* Quick reply — inside card body, above footer */} + {level === "respond" && ( +
e.stopPropagation()}> + {session.summary && !session.summaryIsFallback && ( +

{session.summary}

+ )} +
+ + + +
+