diff --git a/.gitignore b/.gitignore index f3bc5833c..93f75b637 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ dist/ .pnpm-store/ .next/ +.next-dev/ *.tsbuildinfo coverage/ *.patch diff --git a/CLAUDE.md b/CLAUDE.md index 20e63a70f..ce2cc6c6f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -201,7 +201,8 @@ Hash = SHA-256 of config directory (first 12 chars). Prevents collision across m | `packages/web/src/components/SessionCard.tsx` | Kanban session card | | `packages/web/src/hooks/useSessionEvents.ts` | SSE consumer hook | | `packages/web/src/lib/types.ts` | Dashboard types | -| `packages/web/src/app/globals.css` | Design tokens and base styles | +| `packages/web/src/app/globals.css` | Design tokens and base styles (full token definitions) | +| `DESIGN.md` | **Design system reference** — design principles, token mapping, component patterns, anti-patterns (read this before writing any web UI) | | `agent-orchestrator.yaml` | Project-level config (user-created) | | `eslint.config.js` | ESLint flat config | | `tsconfig.base.json` | Shared TypeScript base config | diff --git a/DESIGN.md b/DESIGN.md index aa2786272..b606a2c89 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,66 @@ - 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. | -| 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. | +| Status | Dark Mode | Light Mode | CSS Token | Usage | +|--------|-----------|------------|-----------|-------| +| Working | #22c55e | #16a34a | `--status-working` | Agent actively coding. Green dot with pulse ring animation. | +| Ready | #8b9cf7 | #6b73c4 | `--status-ready` | Queued, awaiting start or CI pending. | +| Respond | #e2a336 | #b8860b | `--status-respond` | Needs human input. Amber = attention without panic. **NOT red** — "respond" is a normal workflow state. | +| Review | #06b6d4 | #0891b2 | `--status-review` | Code ready for review. Cyan = "look when ready." | +| Error | #ef4444 | #dc2626 | `--status-error` | CI failed, agent crashed. Red = broken. Distinct from Respond. | +| Done | #57534e | #d6d3d1 | `--status-done` | Completed. Fades to stone. Done items recede. | -- **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. +**Critical:** `--status-respond` and `--status-error` are separate tokens with different semantic meanings. Respond = human decision needed (amber). Error = something broke (red). Never conflate them. + +- **Dark mode strategy:** Warm charcoal palette (brown-tinted, not neutral or blue-tinted gray). Reduce font weight by one step in dark mode (semibold becomes 500, bold becomes 600). Inset highlights on elevated surfaces: `inset 0 1px 0 rgba(255,255,255,0.03)`. Subtle radial gradients on body for ambient depth. ## Spacing - **Base unit:** 4px @@ -96,12 +100,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 +127,167 @@ - All animations must respect `prefers-reduced-motion: reduce` - Use `contain: layout style paint` on session cards for performance with 30+ cards +## Accessibility +- **Touch targets:** Minimum 44x44px on all interactive elements (buttons, links, toggles). Icon buttons that render smaller visually must have padding to meet 44px minimum hit area. +- **Contrast ratios (WCAG AA):** + - Body text (13px): 4.5:1 minimum against surface backgrounds + - Large text (18px+ or 14px bold): 3:1 minimum + - UI components (borders, icons): 3:1 minimum against adjacent colors + - Dark: text-primary #f0ece8 on bg-surface #1a1918: 14.9:1 ✓ + - Dark: text-secondary #a8a29e on bg-surface #1a1918: 7.0:1 ✓ + - Dark: text-tertiary #78716c on bg-surface #1a1918: 3.7:1 ✓ (labels only, not body text) + - Dark: accent #8b9cf7 on bg-surface #1a1918: 6.9:1 ✓ + - Light: text-primary #1c1917 on bg-surface #ffffff: 17.5:1 ✓ + - Light: text-secondary #57534e on bg-surface #ffffff: 7.6:1 ✓ + - Light: text-tertiary #736e6b on bg-surface #ffffff: 5.0:1 ✓ + - Light: accent #5c64b5 on bg-surface #ffffff: 5.3:1 ✓ +- **Focus indicators:** `outline: 2px solid var(--accent); outline-offset: 2px` on `:focus-visible`. Never `outline: none` without a visible replacement. +- **Reduced motion:** `@media (prefers-reduced-motion: reduce)` disables all animations and transitions globally. Non-negotiable. +- **Color independence:** Never encode meaning with color alone. Always pair colored dots with text labels. Status pills include both dot and text. +- **Keyboard navigation:** All interactive elements reachable via Tab. Logical tab order. Escape closes modals/popovers. Arrow keys navigate within lists. +- **Screen reader:** ARIA labels on all icon-only buttons. `role="heading"` with `aria-level` on non-heading elements styled as headings. Status changes announced via `aria-live` regions. + +## Web Implementation Rules +- **Single source of truth:** This file is the authoritative design spec for the repo, including `packages/web/`. Do not create a second package-level `DESIGN.md`. +- **Tokens over raw values:** In React/Tailwind code, use CSS variables from `packages/web/src/app/globals.css` instead of hardcoded hex, rgba, or `dark:` overrides. +- **No inline styles:** Avoid `style={{ ... }}` for theme values. Use Tailwind utilities with `var(--token)` or add a named class in `globals.css`. +- **No external UI kits:** Do not introduce Radix, shadcn, Headless UI, or similar component libraries for core UI primitives. +- **Tailwind vs CSS classes:** Use Tailwind for one-off layout and spacing. Add a class in `globals.css` when a pattern is theme-sensitive, uses pseudo-elements, gradients, or repeats 3+ times. +- **Theme handling:** `:root` holds light tokens and `.dark` holds dark overrides. Use tokenized classes like `bg-[var(--color-bg-surface)]`, not `bg-white dark:bg-[#1a1918]`. + +## Token Mapping +These are the concrete token names used in `packages/web/src/app/globals.css`. New UI code should reference these names directly. + +### Core Surface Tokens +| CSS Token | Meaning | +|-----------|---------| +| `--color-bg-base` | Page background | +| `--color-bg-surface` | Standard card/panel background | +| `--color-bg-elevated` | Elevated surfaces, popovers, modals | +| `--color-bg-elevated-hover` | Hover state for elevated surfaces | +| `--color-bg-subtle` | Subtle fill for chips, hovers, muted emphasis | +| `--color-bg-sidebar` | Sidebar-specific background | + +### Core Text and Border Tokens +| CSS Token | Meaning | +|-----------|---------| +| `--color-text-primary` | Primary headings/body copy | +| `--color-text-secondary` | Supporting text | +| `--color-text-tertiary` | Captions/placeholders | +| `--color-text-muted` | Low-emphasis meta text | +| `--color-text-inverse` | Text on accent or dark fills | +| `--color-border-subtle` | Hairline dividers | +| `--color-border-default` | Standard borders | +| `--color-border-strong` | Emphasized borders/focus-adjacent borders | + +### Accent and Utility Tokens +| CSS Token | Meaning | +|-----------|---------| +| `--color-accent` | Primary interactive accent | +| `--color-accent-hover` | Hover state for accent surfaces | +| `--color-accent-subtle` | Accent-tinted background | +| `--color-accent-blue` | Semantic blue alias | +| `--color-accent-green` | Semantic green alias | +| `--color-accent-yellow` | Semantic amber alias | +| `--color-accent-orange` | Semantic orange alias | +| `--color-accent-red` | Semantic red alias | +| `--color-tint-blue` | Blue pill/badge background | +| `--color-tint-green` | Green pill/badge background | +| `--color-tint-yellow` | Yellow pill/badge background | +| `--color-tint-orange` | Orange pill/badge background | +| `--color-tint-red` | Red pill/badge background | +| `--color-tint-neutral` | Neutral pill/badge background | + +### Status and Alert Tokens +| CSS Token | Meaning | +|-----------|---------| +| `--color-status-working` | Agent actively working | +| `--color-status-ready` | Ready/queued state | +| `--color-status-respond` | Human response needed | +| `--color-status-review` | Review-needed state | +| `--color-status-pending` | Pending/queued emphasis | +| `--color-status-merge` | Merge-ready/merged emphasis | +| `--color-status-idle` | Idle/inactive state | +| `--color-status-done` | Completed/receding state | +| `--color-status-error` | Error/broken state | +| `--color-ci-pass` | CI passing | +| `--color-ci-fail` | CI failing | +| `--color-alert-ci` / `--color-alert-ci-bg` | CI failure callout row | +| `--color-alert-review` / `--color-alert-review-bg` | Review-requested callout row | +| `--color-alert-changes` / `--color-alert-changes-bg` | Changes-requested callout row | +| `--color-alert-conflict` / `--color-alert-conflict-bg` | Merge-conflict callout row | +| `--color-alert-comment` / `--color-alert-comment-bg` | New-comment callout row | + +## Component Anatomy + +### Session Card +``` +┌─ 2px left border (status color) ─────────────────────┐ +│ ┌─ Card (bg-surface, 1px border-default, 2px radius) │ +│ │ Title (text-primary, 12px, weight 500) │ +│ │ Branch · PR # (mono, text-tertiary, 10px) │ +│ │ ┌─ Status pill ────────────────────┐ │ +│ │ │ ● dot (6px, status color) Label │ │ +│ │ └──────────────────────────────────┘ │ +│ │ inset 0 1px 0 rgba(255,255,255,0.03) (dark only) │ +│ └─────────────────────────────────────────────────────│ +└───────────────────────────────────────────────────────┘ +``` +- **Padding:** 10px 12px +- **Spacing:** 4px between title and meta, 6px between meta and status +- **Hover:** bg-elevated-hover, border-color transition 0.12s +- **Active:** scale(0.99), 80ms +- **Containment:** `contain: layout style paint` for 30+ card performance + +### Button States +| State | Primary | Secondary | Ghost | Danger | +|-------|---------|-----------|-------|--------| +| Rest | bg: accent, text: #121110 | bg: elevated, border: border-default | bg: transparent | bg: transparent, border: red/30% | +| Hover | bg: accent-hover | bg: elevated-hover, border: border-strong | bg: bg-subtle | bg: red/8%, border: red | +| Active | scale(0.97) | scale(0.97) | scale(0.97) | scale(0.97) | +| Focus | outline: 2px accent | outline: 2px accent | outline: 2px accent | outline: 2px accent | +| Disabled | opacity: 0.5, cursor: not-allowed | opacity: 0.5 | opacity: 0.5 | opacity: 0.5 | +- **Padding:** 8px 16px +- **Font:** Geist Sans, 13px, weight 500 +- **Border-radius:** 0 +- **Min touch target:** 44px height (add padding if needed) + +### Input Fields +| State | Appearance | +|-------|------------| +| Rest | bg: bg-base, border: border-default, text: text-primary | +| Placeholder | color: text-tertiary | +| Focus | border-color: accent, no outline (border IS the indicator) | +| Error | border-color: status-error, error message below in status-error color | +| Disabled | opacity: 0.5, cursor: not-allowed, bg: bg-subtle | +- **Padding:** 8px 12px +- **Font:** Geist Sans, 13px +- **Border-radius:** 0 + +### Status Pill +- **Layout:** inline-flex, center-aligned, gap 6px +- **Dot:** 6px circle, filled with status color +- **Text:** 11px, weight 600, text-secondary +- **Background:** bg-subtle +- **Padding:** 4px 10px +- **Border-radius:** 0 + +### Alert / Banner +- **Layout:** flex, padding 12px 16px +- **Left border:** 2px solid, colored by severity +- **Background:** status color at 6% opacity +- **Text:** status color, 13px +- **Border-radius:** 0 +- **Variants:** success (green), warning (amber), error (red), info (cyan) + +## Web Patterns +- **Mono data:** IDs, hashes, timestamps, branch names, and PR numbers should use `font-mono` with `10-11px` sizing and slightly wider tracking. +- **Status text:** Session/card status labels should stay mono and low-emphasis unless the status itself is the primary signal. +- **Alert rows:** Inline alert/callout rows inside cards should use a 2px left border plus paired foreground/background alert tokens. +- **Dividers:** Use `border-[var(--color-border-subtle)]` or `border-[var(--color-border-default)]` instead of ad hoc neutral grays. +- **Existing reusable components:** Prefer current primitives/components like `ActivityDot`, `CIBadge`, `PRStatus`, `Toast`, and shared layout patterns already in `packages/web/src/components/`. +- **Sharp edges remain the rule:** Do not reintroduce rounded cards/buttons as a package-level convention. `rounded-full` is reserved for dots, pills, and avatars. + ## Performance Guidelines - Use `contain: layout style paint` and `content-visibility: auto` on session cards - Animate only `transform` and `opacity` (GPU-composited). Never animate `padding`, `margin`, `height`, `width`, `border`, or `box-shadow`. @@ -143,15 +305,29 @@ - `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. | +| 2026-04-07 | `--status-respond` is amber, never red | Critique found Respond column using `--status-error` (red). Respond = human decision needed, not error. Separate token. See status colors table. | +| 2026-04-07 | Two-stage delete confirmation pattern | P0 safety: trash button first click enters amber "kill?" state for 2s; second click confirms. No modal. In-place via CSS `::after` + JS class toggle. Prevents accidental agent termination. | +| 2026-04-07 | Working card titles at full weight | P2: Working state is the primary operational state. Never dim active card titles. Dimming is reserved for Done/archived cards only. | +| 2026-04-07 | No current-project group label in sidebar sessions | P2: Current project label in the sessions list is redundant — the project switcher chip 12px above already names the project. Only other projects need group labels. | +| 2026-04-07 | Remove column shadows | P3: `18px/42px` column box-shadow created competing depth layers with card shadows. Border + background contrast does separation. No column-level shadow needed. | +| 2026-04-07 | Topbar shows page name only, not project name | Minor: Topbar "vinesight-rn / kanban" duplicated project name visible in sidebar. Topbar now shows "Kanban" + freshness. | +| 2026-04-07 | Diff size badges use `` with tooltip | Minor: S/M/L diff badges were opaque. `` gives meaning on hover without adding visual noise. | diff --git a/packages/core/package.json b/packages/core/package.json index 9c1c99bcc..e760f4a77 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,6 +11,42 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js" }, + "./activity-log": { + "types": "./dist/activity-log.d.ts", + "import": "./dist/activity-log.js" + }, + "./config": { + "types": "./dist/config.d.ts", + "import": "./dist/config.js" + }, + "./decomposer": { + "types": "./dist/decomposer.d.ts", + "import": "./dist/decomposer.js" + }, + "./lifecycle-manager": { + "types": "./dist/lifecycle-manager.d.ts", + "import": "./dist/lifecycle-manager.js" + }, + "./metadata": { + "types": "./dist/metadata.d.ts", + "import": "./dist/metadata.js" + }, + "./observability": { + "types": "./dist/observability.d.ts", + "import": "./dist/observability.js" + }, + "./orchestrator-prompt": { + "types": "./dist/orchestrator-prompt.d.ts", + "import": "./dist/orchestrator-prompt.js" + }, + "./paths": { + "types": "./dist/paths.d.ts", + "import": "./dist/paths.js" + }, + "./plugin-registry": { + "types": "./dist/plugin-registry.d.ts", + "import": "./dist/plugin-registry.js" + }, "./types": { "types": "./dist/types.d.ts", "import": "./dist/types.js" @@ -22,6 +58,10 @@ "./scm-webhook-utils": { "types": "./dist/scm-webhook-utils.d.ts", "import": "./dist/scm-webhook-utils.js" + }, + "./session-manager": { + "types": "./dist/session-manager.d.ts", + "import": "./dist/session-manager.js" } }, "files": [ diff --git a/packages/core/src/__tests__/lifecycle-manager.test.ts b/packages/core/src/__tests__/lifecycle-manager.test.ts index 1298d9edb..01e6a4b1b 100644 --- a/packages/core/src/__tests__/lifecycle-manager.test.ts +++ b/packages/core/src/__tests__/lifecycle-manager.test.ts @@ -51,16 +51,32 @@ function setupCheck( configOverride?: OrchestratorConfig; }, ) { - vi.mocked(mockSessionManager.get).mockResolvedValue(opts.session); - - writeMetadata(env.sessionsDir, sessionId, { + const persistedMetadata = { worktree: "/tmp", branch: opts.session.branch ?? "main", status: opts.session.status, project: "my-app", + runtimeHandle: opts.session.runtimeHandle + ? JSON.stringify(opts.session.runtimeHandle) + : undefined, ...opts.metaOverrides, + }; + const persistedStringMetadata = Object.fromEntries( + Object.entries(persistedMetadata).filter( + (entry): entry is [string, string] => typeof entry[1] === "string", + ), + ); + + vi.mocked(mockSessionManager.get).mockResolvedValue({ + ...opts.session, + metadata: { + ...opts.session.metadata, + ...persistedStringMetadata, + }, }); + writeMetadata(env.sessionsDir, sessionId, persistedMetadata); + return createLifecycleManager({ config: opts.configOverride ?? config, registry: opts.registry ?? mockRegistry, @@ -98,6 +114,52 @@ describe("check (single session)", () => { expect(meta!["status"]).toBe("working"); }); + it("does not kill a spawning session when its runtime handle has not been persisted yet", async () => { + vi.mocked(plugins.runtime.isAlive).mockResolvedValue(false); + + const lm = setupCheck("app-1", { + session: makeSession({ + status: "spawning", + runtimeHandle: { id: "app-1", runtimeName: "mock", data: {} }, + metadata: {}, + }), + metaOverrides: { + runtimeHandle: undefined, + tmuxName: undefined, + }, + }); + + await lm.check("app-1"); + + expect(lm.getStates().get("app-1")).toBe("working"); + expect(plugins.runtime.isAlive).not.toHaveBeenCalled(); + }); + + it("still probes a working session when it relies on a synthesized runtime handle", async () => { + vi.mocked(plugins.runtime.isAlive).mockResolvedValue(false); + + const lm = setupCheck("app-1", { + session: makeSession({ + status: "working", + runtimeHandle: { id: "app-1", runtimeName: "mock", data: {} }, + metadata: {}, + }), + metaOverrides: { + runtimeHandle: undefined, + tmuxName: undefined, + }, + }); + + await lm.check("app-1"); + + expect(plugins.runtime.isAlive).toHaveBeenCalledWith({ + id: "app-1", + runtimeName: "mock", + data: {}, + }); + expect(lm.getStates().get("app-1")).toBe("killed"); + }); + it("uses worker-specific agent fallback when metadata does not persist an agent", async () => { const codexAgent: Agent = { ...plugins.agent, diff --git a/packages/core/src/lifecycle-manager.ts b/packages/core/src/lifecycle-manager.ts index 613d86156..99105fd42 100644 --- a/packages/core/src/lifecycle-manager.ts +++ b/packages/core/src/lifecycle-manager.ts @@ -371,9 +371,14 @@ export function createLifecycleManager(deps: LifecycleManagerDeps): LifecycleMan // Track activity state across steps so stuck detection can run after PR checks let detectedIdleTimestamp: Date | null = null; + const hasPersistedRuntimeIdentity = + typeof session.metadata["runtimeHandle"] === "string" || + typeof session.metadata["tmuxName"] === "string"; + const canProbeRuntimeIdentity = + hasPersistedRuntimeIdentity || session.status !== SESSION_STATUS.SPAWNING; // 1. Check if runtime is alive - if (session.runtimeHandle) { + if (session.runtimeHandle && canProbeRuntimeIdentity) { const runtime = registry.get("runtime", project.runtime ?? config.defaults.runtime); if (runtime) { const alive = await runtime.isAlive(session.runtimeHandle).catch(() => true); @@ -381,12 +386,17 @@ export function createLifecycleManager(deps: LifecycleManagerDeps): LifecycleMan } } - // 2. Check agent activity — prefer JSONL-based detection (runtime-agnostic) - if (agent && session.runtimeHandle) { + // 2. Check agent activity. + // JSONL-based activity detection is runtime-agnostic, but terminal probing + // and process checks should only run once a real runtime identity has been + // persisted. During spawn, sessionManager may fabricate a temporary handle + // to keep the object shape stable; treating that as a real tmux target can + // falsely mark a just-reserved session as killed before launch completes. + if (agent && (session.runtimeHandle || session.workspacePath)) { try { // If the agent implements recordActivity, capture terminal output and record // BEFORE calling getActivityState so the JSONL has fresh data to read. - if (agent.recordActivity && session.workspacePath) { + if (agent.recordActivity && session.workspacePath && session.runtimeHandle && canProbeRuntimeIdentity) { try { const runtime = registry.get( "runtime", @@ -420,17 +430,19 @@ export function createLifecycleManager(deps: LifecycleManagerDeps): LifecycleMan // proceed to PR checks below } else { // getActivityState returned null — fall back to terminal output parsing - const runtime = registry.get( - "runtime", - project.runtime ?? config.defaults.runtime, - ); - const terminalOutput = runtime ? await runtime.getOutput(session.runtimeHandle, 10) : ""; - if (terminalOutput) { - const activity = agent.detectActivity(terminalOutput); - if (activity === "waiting_input") return "needs_input"; + if (session.runtimeHandle && canProbeRuntimeIdentity) { + const runtime = registry.get( + "runtime", + project.runtime ?? config.defaults.runtime, + ); + const terminalOutput = runtime ? await runtime.getOutput(session.runtimeHandle, 10) : ""; + if (terminalOutput) { + const activity = agent.detectActivity(terminalOutput); + if (activity === "waiting_input") return "needs_input"; - const processAlive = await agent.isProcessRunning(session.runtimeHandle); - if (!processAlive) return "killed"; + const processAlive = await agent.isProcessRunning(session.runtimeHandle); + if (!processAlive) return "killed"; + } } } } catch { diff --git a/packages/web/src/__tests__/components.test.tsx b/packages/web/src/__tests__/components.test.tsx index 49fd3cad6..982d467d4 100644 --- a/packages/web/src/__tests__/components.test.tsx +++ b/packages/web/src/__tests__/components.test.tsx @@ -288,7 +288,7 @@ describe("SessionCard", () => { }); const session = makeSession({ status: "ci_failed", activity: "idle", pr }); render(); - expect(screen.getByText("1 CI check failing")).toBeInTheDocument(); + expect(screen.getByRole("link", { name: "1 check failing" })).toBeInTheDocument(); }); it("shows CI status unknown when ciStatus is failing but no failed checks", () => { @@ -393,7 +393,7 @@ describe("SessionCard", () => { }); const session = makeSession({ activity: "idle", pr }); render(); - expect(screen.getByText("ask to fix")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Ask to fix" })).toBeInTheDocument(); }); it("shows action buttons even when agent is active", () => { @@ -412,13 +412,13 @@ describe("SessionCard", () => { }); const session = makeSession({ activity: "active", pr }); render(); - expect(screen.getByText("ask to fix")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Ask to fix" })).toBeInTheDocument(); }); it("shows issue details in the compact card footer", () => { const session = makeSession({ id: "test-1", issueId: "INT-100", pr: null }); render(); - expect(screen.getAllByText("INT-100")).toHaveLength(2); + expect(screen.getAllByText("INT-100")).toHaveLength(1); }); it("shows icon-only terminate button in the footer", () => { @@ -548,7 +548,7 @@ describe("SessionCard", () => { render(); - const actionButton = screen.getByRole("button", { name: "ask to fix" }); + const actionButton = screen.getByRole("button", { name: "Ask to fix" }); fireEvent.click(actionButton); await waitFor(() => { @@ -558,7 +558,7 @@ describe("SessionCard", () => { await waitFor(() => { expect(screen.getByRole("button", { name: "failed" })).toBeInTheDocument(); }); - expect(screen.queryByRole("button", { name: "sent!" })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Sent!" })).not.toBeInTheDocument(); }); }); @@ -575,7 +575,26 @@ describe("AttentionZone", () => { it("renders empty state when sessions array is empty", () => { render(); - expect(screen.getByText("No sessions")).toBeInTheDocument(); + expect(screen.getByText("Respond")).toBeInTheDocument(); + expect(screen.getByText("0")).toBeInTheDocument(); + expect(screen.queryByText("No agents need your input.")).not.toBeInTheDocument(); + }); + + it("renders zone-specific empty messages for all attention zones", () => { + const cases: Array<[string, string]> = [ + ["review", "Review"], + ["pending", "Pending"], + ["working", "Working"], + ["done", "Done"], + ]; + for (const [level, expectedLabel] of cases) { + const { unmount } = render( + , + ); + expect(screen.getByText(expectedLabel)).toBeInTheDocument(); + expect(screen.getByText("0")).toBeInTheDocument(); + unmount(); + } }); it("shows session cards when not collapsed", () => { diff --git a/packages/web/src/__tests__/setup.ts b/packages/web/src/__tests__/setup.ts index 533b774d3..b3258b10f 100644 --- a/packages/web/src/__tests__/setup.ts +++ b/packages/web/src/__tests__/setup.ts @@ -1,4 +1,11 @@ -import "@testing-library/jest-dom/vitest"; +import { cleanup } from "@testing-library/react"; +import * as matchers from "@testing-library/jest-dom/matchers"; +import { afterEach, expect } from "vitest"; + +expect.extend(matchers); +afterEach(() => { + cleanup(); +}); // jsdom does not implement window.matchMedia. Provide a minimal stub so // components that call useMediaQuery (e.g. Dashboard) work in unit tests. diff --git a/packages/web/src/app/globals.css b/packages/web/src/app/globals.css index 9e57b0d2a..30f4d9f23 100644 --- a/packages/web/src/app/globals.css +++ b/packages/web/src/app/globals.css @@ -27,71 +27,84 @@ /* ── 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: #f9f7f5; + --color-bg-elevated-hover: #f7f5f2; + --color-bg-subtle: rgba(120, 100, 80, 0.06); + --color-bg-hover: #f7f5f2; + --color-bg-inset: #f0ede9; + --color-bg-sidebar: #f0ede9; + --color-bg-card: #ffffff; /* 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: rgba(0, 0, 0, 0.06); + --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: #78716c; + --color-text-muted: #a8a29e; --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: #4a52a0; + --color-accent-subtle: rgba(92, 100, 181, 0.1); + + /* Amber accent — orchestrator / CTA */ + --color-accent-amber: #d97706; + --color-accent-amber-dim: rgba(217, 119, 6, 0.1); + --color-accent-amber-border: rgba(217, 119, 6, 0.3); /* Status / semantic colors */ --color-status-working: #16a34a; - --color-status-ready: #5e6ad2; - --color-status-respond: #ca8a04; + --color-status-ready: #6b73c4; + --color-status-respond: #ea580c; --color-status-review: #0891b2; - --color-status-attention: #9a6700; - --color-status-idle: #8b949e; - --color-status-done: #d0d7de; + --color-status-pending: #ca8a04; + --color-status-attention: #b8860b; + --color-status-idle: #a8a29e; + --color-status-done: #a8a29e; --color-status-error: #dc2626; + --color-status-merge: #16a34a; + --color-ci-pass: #16a34a; + --color-ci-fail: #dc2626; /* Semantic aliases */ - --color-accent-blue: #5B7EF8; + --color-accent-blue: #2563eb; --color-accent-green: #16a34a; - --color-accent-yellow: #9a6700; + --color-accent-yellow: #ca8a04; --color-accent-orange: #bc4c00; --color-accent-red: #dc2626; - --color-accent-violet: #5B7EF8; - --color-accent-purple: #5B7EF8; + --color-accent-violet: #7c3aed; + --color-accent-purple: #7c3aed; /* Theme-adaptive tints (bg tints behind status pills) */ - --color-tint-blue: rgba(91, 126, 248, 0.08); - --color-tint-green: rgba(22, 163, 74, 0.08); - --color-tint-yellow: rgba(154, 103, 0, 0.08); - --color-tint-red: rgba(220, 38, 38, 0.08); - --color-tint-violet: rgba(91, 126, 248, 0.08); + --color-tint-blue: rgba(37, 99, 235, 0.08); + --color-tint-green: rgba(22, 163, 74, 0.07); + --color-tint-yellow: rgba(184, 134, 11, 0.07); + --color-tint-red: rgba(220, 38, 38, 0.07); + --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 +118,32 @@ /* Nav */ --color-nav-bg: #ffffff; - /* Card surfaces — clean white on light gray */ - --card-bg: #ffffff; - --card-merge-bg: #f6fbf8; - --card-expanded-bg: #f7f7f8; + /* Card surfaces — warm white on warm parchment */ + --card-bg: linear-gradient(180deg, #ffffff 0%, #faf9f7 100%); + --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,22 +155,21 @@ --btn-inset: none; /* Kanban column */ - --color-column-bg: #f4f5f7; + --color-column-bg: #ece9e4; --color-column-header: transparent; /* Alert colors */ - --color-alert-ci: #6366f1; - --color-alert-ci-bg: #4f46e5; - --color-alert-ci-unknown: #9a6700; - --color-alert-review: #1a7f37; - --color-alert-review-bg: #1a7f37; - --color-alert-changes: #5B7EF8; - --color-alert-changes-bg: #5B7EF8; - --color-alert-conflict: #9a6700; - --color-alert-conflict-bg: #92400e; - --color-alert-comment: #bc4c00; - --color-alert-comment-bg: #c2410c; - + --color-alert-ci: #dc2626; + --color-alert-ci-bg: rgba(220,38,38,0.06); + --color-alert-ci-unknown: #b8860b; + --color-alert-review: #2563eb; + --color-alert-review-bg: rgba(37,99,235,0.06); + --color-alert-changes: #d97706; + --color-alert-changes-bg: rgba(217,119,6,0.06); + --color-alert-conflict: #ca8a04; + --color-alert-conflict-bg: rgba(202,138,4,0.06); + --color-alert-comment: #7c3aed; + --color-alert-comment-bg: rgba(124,58,237,0.06); } /* ── Dark mode ────────────────────────────────────────────────────── */ @@ -165,133 +177,145 @@ -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); + /* 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.05); + --color-bg-hover: rgba(255, 240, 220, 0.08); + --color-bg-inset: #161514; + --color-bg-sidebar: #0e0d0c; + --color-bg-card: #1c1b19; - /* 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); + /* Borders — warm-tinted transparency */ + --color-border-subtle: rgba(255, 240, 220, 0.07); + --color-border-default: rgba(255, 240, 220, 0.13); + --color-border-strong: rgba(255, 240, 220, 0.24); - /* Text */ - --color-text-primary: #eef3ff; - --color-text-secondary: #a5afc4; - --color-text-tertiary: #6f7c94; - --color-text-muted: #6f7c94; - --color-text-inverse: #0a0d12; + /* Text — cream, not blue-white */ + --color-text-primary: #f0ece8; + --color-text-secondary: #a8a29e; + --color-text-tertiary: #78716c; + --color-text-muted: #57534e; + --color-text-inverse: #121110; - /* Interactive accent */ - --color-accent: #5B7EF8; - --color-accent-hover: #7a96ff; - --color-accent-subtle: rgba(91, 126, 248, 0.12); + /* Interactive accent — warm periwinkle */ + --color-accent: #8b9cf7; + --color-accent-hover: #a3b1fa; + --color-accent-subtle: rgba(139, 156, 247, 0.12); + + /* Amber accent — orchestrator / CTA */ + --color-accent-amber: #f97316; + --color-accent-amber-dim: rgba(249, 115, 22, 0.12); + --color-accent-amber-border: rgba(249, 115, 22, 0.35); /* Status */ --color-status-working: #22c55e; - --color-status-ready: #5B7EF8; - --color-status-respond: #f1be64; + --color-status-ready: #8b9cf7; + --color-status-respond: #ea580c; --color-status-review: #06b6d4; - --color-status-attention: #f1be64; - --color-status-idle: #293142; - --color-status-done: #3a4252; + --color-status-pending: #eab308; + --color-status-attention: #e2a336; + --color-status-idle: #44403c; + --color-status-done: #44403c; --color-status-error: #ef4444; + --color-status-merge: #22c55e; + --color-ci-pass: #22c55e; + --color-ci-fail: #ef4444; /* Semantic aliases */ - --color-accent-blue: #5B7EF8; + --color-accent-blue: #60a5fa; --color-accent-green: #22c55e; - --color-accent-yellow: #f1be64; + --color-accent-yellow: #eab308; --color-accent-orange: #ff9d57; --color-accent-red: #ef4444; - --color-accent-violet: #5B7EF8; - --color-accent-purple: #5B7EF8; + --color-accent-violet: #a78bfa; + --color-accent-purple: #a78bfa; /* Theme-adaptive tints */ - --color-tint-blue: rgba(91, 126, 248, 0.12); + --color-tint-blue: rgba(96, 165, 250, 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 — match cards.html dark reference */ + --card-bg: var(--color-bg-card); + --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: var(--color-border-subtle); /* 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(30, 29, 28, 0.8) 0%, rgba(24, 23, 22, 0.8) 100%); + --card-done-border: rgba(255, 240, 220, 0.12); - --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: var(--color-bg-subtle); --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; - --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-comment: #c88a2e; - --color-alert-comment-bg: rgba(200, 138, 46, 0.25); - + /* Alert colors — warm-tinted */ + --color-alert-ci: #ef4444; + --color-alert-ci-bg: rgba(239,68,68,0.08); + --color-alert-ci-unknown: #e2a336; + --color-alert-review: #60a5fa; + --color-alert-review-bg: rgba(96,165,250,0.08); + --color-alert-changes: #f97316; + --color-alert-changes-bg: rgba(249,115,22,0.08); + --color-alert-conflict: #eab308; + --color-alert-conflict-bg: rgba(234,179,8,0.08); + --color-alert-comment: #a78bfa; + --color-alert-comment-bg: rgba(167,139,250,0.08); } @theme { @@ -300,7 +324,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 +377,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% ); } @@ -495,6 +519,173 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { background: rgba(0, 0, 0, 0.4); } +/* ── Terminal exited placeholder ─────────────────────────────────────── */ + +.terminal-exited-placeholder { + display: flex; + align-items: center; + justify-content: center; + background: var(--color-bg-inset); + border: 1px solid var(--color-border-subtle); + border-radius: var(--radius-lg); +} + +.terminal-exited-placeholder__text { + color: var(--color-text-tertiary); + font-size: 13px; +} + +/* ── Empty State + Ghost Kanban ──────────────────────────────────────── */ + +.board-wrapper { + position: relative; + flex: 1; + overflow: hidden; +} + +.kanban-ghost { + display: grid; + grid-template-columns: repeat(5, 1fr); + gap: 6px; + position: absolute; + inset: 10px 0 0; + pointer-events: none; +} + +.kanban-ghost__col { + border-radius: 7px; + border: 1px dashed var(--color-border-subtle); + display: flex; + flex-direction: column; + opacity: 0.7; +} + +.kanban-ghost__head { + padding: 9px 9px 7px; + border-bottom: 1px solid var(--color-border-subtle); + font-size: 10.5px; + font-weight: 500; + color: var(--color-text-muted); + letter-spacing: 0.05em; + text-transform: uppercase; + font-family: var(--font-mono); +} + +.dark .kanban-ghost__col { + border-color: rgba(255, 240, 220, 0.08); +} + +.dark .kanban-ghost__head { + border-bottom-color: rgba(255, 240, 220, 0.07); +} + +.board-center { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + transform: translateY(-12px); +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 28px 20px; +} + +@keyframes empty-state-enter { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.empty-state__icon { + width: 50px; + height: 50px; + border-radius: 50%; + background: var(--color-bg-subtle); + border: 1px solid var(--color-border-subtle); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 14px; + animation: empty-state-enter 280ms cubic-bezier(0.23, 1, 0.32, 1) both; +} + +.empty-state__headline { + font-size: 13px; + font-weight: 500; + color: var(--color-text-secondary); + letter-spacing: -0.01em; + margin-bottom: 5px; + animation: empty-state-enter 280ms cubic-bezier(0.23, 1, 0.32, 1) 45ms both; +} + +.empty-state__hint { + font-size: 12px; + color: var(--color-text-muted); + max-width: 210px; + line-height: 1.65; + margin-bottom: 18px; + animation: empty-state-enter 280ms cubic-bezier(0.23, 1, 0.32, 1) 90ms both; +} + +.empty-state__text { + font-size: 13px; + color: var(--color-text-muted); + animation: empty-state-enter 280ms cubic-bezier(0.23, 1, 0.32, 1) 40ms both; +} + +.empty-state__cta { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 0 12px; + height: 29px; + border-radius: 5px; + border: 1px solid var(--color-border-strong); + background: transparent; + color: var(--color-text-secondary); + font-family: var(--font-sans); + font-size: 12px; + font-weight: 500; + cursor: pointer; + text-decoration: none; + transition: background 100ms ease, border-color 100ms ease, color 100ms ease, + transform 120ms cubic-bezier(0.23, 1, 0.32, 1); + animation: empty-state-enter 280ms cubic-bezier(0.23, 1, 0.32, 1) 135ms both; +} + +.empty-state__cta:hover { + background: var(--color-bg-subtle); + border-color: var(--color-border-strong); + color: var(--color-text-primary); + text-decoration: none; +} + +.empty-state__cta:active { + transform: scale(0.97); +} + +@media (prefers-reduced-motion: reduce) { + .empty-state__icon, + .empty-state__headline, + .empty-state__hint, + .empty-state__text, + .empty-state__cta { + animation: none; + } +} + /* ── Animations ──────────────────────────────────────────────────────── */ @keyframes progress-shimmer { @@ -588,16 +779,43 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { animation: dot-ring 2s ease-in-out infinite; } -/* ── Kanban card entrance ─────────────────────────────────────────────── */ +/* ── Kanban card entrance — Emil staggered animation ─────────────────── */ .kanban-card-enter { - animation: slide-up 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; + animation: card-enter 0.2s cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.kanban-column-body .kanban-card-enter:nth-child(1) { + animation-delay: 0ms; +} +.kanban-column-body .kanban-card-enter:nth-child(2) { + animation-delay: 40ms; +} +.kanban-column-body .kanban-card-enter:nth-child(3) { + animation-delay: 80ms; +} +.kanban-column-body .kanban-card-enter:nth-child(4) { + animation-delay: 120ms; +} +.kanban-column-body .kanban-card-enter:nth-child(5) { + animation-delay: 160ms; +} + +@keyframes card-enter { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } } /* ── Orchestrator button ──────────────────────────────────────────────── */ .orchestrator-btn { - border-radius: 2px; + border-radius: 0; color: var(--color-accent); background: linear-gradient( 175deg, @@ -643,26 +861,251 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { position: relative; } +.dashboard-app-shell { + display: flex; + min-height: 100vh; + flex-direction: column; + background: var(--color-bg-base); +} + +.dashboard-app-header { + display: flex; + height: 48px; + align-items: center; + gap: 10px; + padding: 0 14px; + border-bottom: 1px solid var(--color-border-subtle); + background: var(--color-bg-base); + flex-shrink: 0; + z-index: 10; +} + +.dashboard-app-sidebar-toggle { + width: 28px; + height: 28px; + border: none; + border-radius: 5px; + background: transparent; + color: var(--color-text-muted); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + flex-shrink: 0; + transition: background 100ms, color 100ms; +} + +.dashboard-app-sidebar-toggle:hover { + background: var(--color-bg-subtle); + color: var(--color-text-secondary); +} + +.dashboard-app-header__brand { + display: inline-flex; + align-items: center; + gap: 7px; + font-size: 13px; + font-weight: 500; + letter-spacing: -0.01em; + color: var(--color-text-primary); +} + +.dashboard-app-header__brand-dot { + width: 7px; + height: 7px; + border-radius: 999px; + flex-shrink: 0; + background: #22c55e; + box-shadow: 0 0 6px rgba(34, 197, 94, 0.55); +} + +.dashboard-app-header__sep { + width: 1px; + height: 16px; + margin: 0 2px; + background: var(--color-border-default); +} + +.dashboard-app-header__project { + font-size: 12px; + font-weight: 400; + color: var(--color-text-secondary); +} + +.dashboard-app-header__spacer { + flex: 1; +} + +.dashboard-app-header__actions { + display: flex; + align-items: center; + gap: 6px; +} + +.dashboard-app-btn { + display: inline-flex; + align-items: center; + gap: 5px; + height: 27px; + padding: 0 10px; + border: 1px solid var(--color-border-default); + border-radius: 5px; + background: transparent; + color: var(--color-text-secondary); + text-decoration: none; + font-size: 12px; + font-weight: 500; + transition: + background 100ms ease, + border-color 100ms ease, + color 100ms ease, + transform 120ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.dashboard-app-btn:hover { + background: var(--color-bg-subtle); + border-color: var(--color-border-strong); + color: var(--color-text-primary); + text-decoration: none; +} + +.dashboard-app-btn:active { + transform: scale(0.97); +} + +.dashboard-app-btn--amber { + border-color: var(--color-accent-amber-border); + background: var(--color-accent-amber-dim); + color: var(--color-accent-amber); +} + +.dashboard-app-btn--amber:hover { + background: color-mix(in srgb, var(--color-accent-amber) 20%, transparent); + color: var(--color-accent-amber); +} + +.dashboard-shell--desktop { + display: grid; + grid-template-columns: auto 1fr; + flex: 1; + min-height: 0; + overflow: hidden; +} + +.dashboard-shell--desktop.dashboard-shell--sidebar-collapsed { + grid-template-columns: 0 minmax(0, 1fr); +} + +.dashboard-shell--desktop > .project-sidebar { + width: 224px; + height: auto; + min-height: 0; + align-self: stretch; + flex-shrink: 0; +} + +.dashboard-shell--desktop > .project-sidebar.project-sidebar--collapsed { + width: 0; + border-right-color: transparent; +} + +.dashboard-shell--desktop > .dashboard-main--desktop { + min-width: 0; + align-self: stretch; +} + .dashboard-main { position: relative; } -.dashboard-main::before { - content: ""; - position: absolute; - inset: 0 0 auto 0; - height: 140px; - pointer-events: none; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.015) 0%, transparent 100%); +.dashboard-main--desktop { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + overflow: hidden; + background: var(--color-bg-base); +} + +.dashboard-main__subhead { + padding: 14px 16px 0; + flex-shrink: 0; +} + +.dashboard-main__title { + font-size: 15px; + font-weight: 600; + letter-spacing: -0.02em; + color: var(--color-text-primary); +} + +.dashboard-main__subtitle { + margin-top: 1px; + font-size: 12px; + color: var(--color-text-muted); +} + +.dashboard-main__body { + display: flex; + flex: 1; + min-height: 0; + flex-direction: column; + overflow-y: auto; + padding: 0 16px 16px; +} + +.dashboard-main__body .kanban-board-wrap, +.dashboard-main__body .board-wrapper { + flex: 1; + min-height: 0; +} + +.project-sidebar__footer { + padding: 8px 12px; + border-top: 1px solid var(--sidebar-border); + display: flex; + align-items: center; + flex-shrink: 0; +} + +.project-sidebar__footer-btn { + width: 100%; + height: 29px; + padding: 0 10px; + border-radius: 5px; + border: 1px solid transparent; + background: transparent; + color: var(--color-text-muted); + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + font-size: 11px; + font-weight: 500; + letter-spacing: -0.01em; + transition: background 100ms, color 100ms, border-color 100ms; +} + +.project-sidebar__footer-btn:hover { + background: var(--sidebar-row-hover); + color: var(--color-text-secondary); + border-color: color-mix(in srgb, var(--color-border-default) 72%, transparent); +} + +.project-sidebar__footer-btn svg { + width: 15px; + height: 15px; + flex-shrink: 0; } .dashboard-hero { position: relative; overflow: visible; - border: 1px solid var(--color-border-default); - border-radius: 2px; - background: var(--color-bg-surface); - box-shadow: var(--card-shadow); + border: none; + border-bottom: 1px solid color-mix(in srgb, var(--color-border-default) 92%, transparent); + border-radius: 0; + background: transparent; + box-shadow: none; } .dashboard-hero__backdrop { @@ -673,25 +1116,33 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { position: relative; display: flex; flex-wrap: wrap; - align-items: flex-start; + align-items: center; justify-content: space-between; - gap: 14px; - padding: 14px 18px; + gap: 12px; + min-height: 46px; + padding: 16px 16px 10px; } .dashboard-hero__primary { display: flex; - flex: 1 1 720px; - align-items: flex-start; - justify-content: space-between; - gap: 18px; + flex: 1 1 auto; + align-items: center; + justify-content: flex-start; + gap: 12px; } .dashboard-hero__heading { display: flex; - flex-direction: column; - gap: 8px; - max-width: 760px; + flex-direction: row; + align-items: baseline; + gap: 12px; + min-width: 0; +} + +.dashboard-hero__copy { + display: flex; + align-items: baseline; + gap: 12px; min-width: 0; } @@ -718,19 +1169,22 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .dashboard-title { - font-size: clamp(22px, 2.8vw, 32px); - line-height: 1; - letter-spacing: -0.05em; - font-weight: 600; + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.1; + letter-spacing: -0.02em; + font-weight: 500; + white-space: nowrap; color: var(--color-text-primary); } .dashboard-subtitle { - margin-top: 3px; - max-width: 56ch; + margin-top: 0; + max-width: none; font-size: 11px; line-height: 1.4; color: var(--color-text-muted); + white-space: nowrap; } .dashboard-hero__meta { @@ -738,10 +1192,113 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { flex: 0 0 auto; flex-direction: row; align-items: center; - gap: 10px; + gap: 12px; margin-left: auto; } +.dashboard-topbar-meta { + display: flex; + align-items: center; + gap: 12px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--color-text-muted); + white-space: nowrap; +} + +.dashboard-topbar-summary { + color: var(--color-text-muted); +} + +.dashboard-topbar-link { + display: inline-flex; + align-items: center; + gap: 7px; + color: var(--color-text-muted); + text-decoration: none; + transition: + color 120ms ease-out, + border-color 120ms ease-out, + background-color 120ms ease-out, + box-shadow 120ms ease-out, + transform 120ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.dashboard-topbar-link:hover { + color: var(--color-text-primary); + text-decoration: none; +} + +.dashboard-topbar-link--orchestrator { + position: relative; + gap: 9px; + border: 1px solid color-mix(in srgb, var(--color-accent) 30%, transparent); + background: linear-gradient( + 180deg, + color-mix(in srgb, white 14%, var(--color-accent-hover)) 0%, + var(--color-accent) 100% + ); + padding: 6px 12px 6px 8px; + color: rgba(255, 255, 255, 0.96); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.28), + 0 1px 0 color-mix(in srgb, var(--color-accent) 28%, transparent), + 0 8px 18px color-mix(in srgb, var(--color-accent) 18%, transparent); + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.12); +} + +@media (hover: hover) and (pointer: fine) { + .dashboard-topbar-link--orchestrator:hover { + color: #ffffff; + border-color: color-mix(in srgb, var(--color-accent) 42%, transparent); + background: linear-gradient( + 180deg, + color-mix(in srgb, white 18%, var(--color-accent-hover)) 0%, + color-mix(in srgb, var(--color-accent) 92%, black) 100% + ); + transform: translateY(-1px); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.32), + 0 1px 0 color-mix(in srgb, var(--color-accent) 30%, transparent), + 0 10px 20px color-mix(in srgb, var(--color-accent) 22%, transparent); + } +} + +.dashboard-topbar-link--orchestrator:active { + transform: scale(0.98); +} + +.dashboard-topbar-link__chip { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 34px; + padding: 3px 6px; + border: 1px solid rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.14); + color: rgba(255, 255, 255, 0.92); + font-size: 9px; + line-height: 1; + letter-spacing: 0.12em; + text-transform: uppercase; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16); +} + +.dashboard-topbar-link__label { + color: inherit; + font-size: 11px; + line-height: 1.1; + letter-spacing: 0.05em; + white-space: nowrap; +} + +.dashboard-topbar-link__arrow { + color: rgba(255, 255, 255, 0.88); + font-size: 12px; + line-height: 1; + transform: translateY(-0.5px); +} + .dashboard-stat-cards { display: flex; flex-wrap: wrap; @@ -761,8 +1318,8 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { gap: 6px; border: 1px solid color-mix(in srgb, var(--color-border-default) 88%, transparent); border-radius: 0; - background: color-mix(in srgb, var(--color-bg-base) 72%, transparent); - padding: 6px 9px; + background: color-mix(in srgb, var(--color-bg-surface) 65%, transparent); + padding: 5px 8px; box-shadow: none; } @@ -778,6 +1335,208 @@ 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 { + width: 6px; + height: 6px; +} +.sidebar-session-dot--glow { + box-shadow: 0 0 4px rgba(34, 197, 94, 0.5); +} +.dark .sidebar-session-dot--glow { + box-shadow: 0 0 5px rgba(34, 197, 94, 0.6); +} +.sidebar-session-dot[data-level="working"] { + background: var(--color-status-working); +} +.sidebar-session-dot[data-level="pending"] { + background: var(--color-status-attention); +} +.sidebar-session-dot[data-level="review"] { + background: var(--color-accent-orange); +} +.sidebar-session-dot[data-level="respond"] { + background: var(--color-status-respond); +} +.sidebar-session-dot[data-level="merge"] { + background: var(--color-status-ready); +} +.sidebar-session-dot[data-level="done"] { + background: var(--color-status-done); +} + +/* ── 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="amber"], +.project-sidebar__health-indicator[data-health="amber"] { + background: var(--color-status-respond); +} +.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); +} +.sidebar-health-dot[data-health="done"], +.project-sidebar__health-indicator[data-health="done"] { + background: var(--color-status-done); +} + +/* ── 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-respond); +} +.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 +1551,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 +1560,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 +1581,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) { @@ -884,32 +1643,122 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } } +.session-detail-layout { + max-width: 1200px; + padding: 16px 24px 48px; +} + +.session-detail-top-strip { + margin-bottom: 18px; +} + +.session-detail-crumbs { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 14px; +} + +.session-detail-crumb-back { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + font-weight: 500; + color: var(--color-text-muted); + text-decoration: none; + transition: color 100ms; +} + +.session-detail-crumb-back:hover { + color: var(--color-text-primary); + text-decoration: none; +} + +.session-detail-crumb-sep { + color: var(--color-text-muted); + font-size: 10px; +} + +.session-detail-crumb-id { + font-family: var(--font-mono); + font-size: 10.5px; + color: var(--color-text-muted); +} + +.session-detail-identity { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 14px; +} + +.session-detail-identity__info { + flex: 1; + min-width: 0; +} + +.session-detail-identity__title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 16px; + font-weight: 600; + line-height: 1.35; + letter-spacing: -0.03em; + color: var(--color-text-primary); +} + +.session-detail-identity__pills { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + margin-top: 8px; +} + +.session-detail-identity__actions { + display: flex; + flex-shrink: 0; + gap: 6px; + padding-top: 2px; +} + +.session-detail-identity__actions--custom { + flex-wrap: wrap; + align-items: center; +} + +.session-detail-mode-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border: 1px solid var(--color-accent-amber-border); + background: var(--color-accent-amber-dim); + color: var(--color-accent-amber); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.05em; +} + .session-detail-link-pill { display: inline-flex; align-items: center; - min-height: 28px; - padding: 0 10px; + gap: 4px; + min-height: 24px; + padding: 3px 9px; border: 1px solid var(--color-border-subtle); - border-radius: 2px; - background: color-mix(in srgb, var(--color-bg-elevated) 88%, transparent); + border-radius: 4px; + background: var(--color-bg-subtle); color: var(--color-text-secondary); - font-size: 11px; + font-size: 10.5px; font-weight: 500; -} - -.session-detail-link-pill { - transition: - border-color 0.14s ease, - color 0.14s ease, - background 0.14s ease; -} - -.session-detail-link-pill--link { - color: var(--color-accent-blue); - border-color: color-mix(in srgb, var(--color-accent-blue) 22%, var(--color-border-subtle)); - background: color-mix(in srgb, var(--color-accent-blue) 9%, transparent); + line-height: 1; text-decoration: none; - box-shadow: inset 0 1px 0 color-mix(in srgb, white 16%, transparent); + transition: + border-color 0.12s ease, + color 0.12s ease, + background 0.12s ease; } .session-detail-link-pill:hover { @@ -917,34 +1766,54 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { color: var(--color-text-primary); } -.session-detail-link-pill--link:hover { - border-color: color-mix(in srgb, var(--color-accent-blue) 44%, var(--color-border-strong)); +.session-detail-link-pill--branch { + font-family: var(--font-mono); + font-size: 10px; +} + +.session-detail-link-pill--branch-link { color: var(--color-accent-blue); - background: color-mix(in srgb, var(--color-accent-blue) 13%, transparent); + border-color: color-mix(in srgb, var(--color-accent-blue) 18%, var(--color-border-subtle)); + background: color-mix(in srgb, var(--color-accent-blue) 6%, transparent); } -.session-detail-link-pill--accent { - color: var(--color-accent); - border-color: color-mix(in srgb, var(--color-accent) 20%, var(--color-border-subtle)); - background: color-mix(in srgb, var(--color-accent) 8%, transparent); +.session-detail-link-pill--branch-link:hover { + color: var(--color-accent-blue); + border-color: color-mix(in srgb, var(--color-accent-blue) 36%, var(--color-border-strong)); + background: color-mix(in srgb, var(--color-accent-blue) 10%, transparent); } -.session-detail-link-pill--link.session-detail-link-pill--accent { - color: var(--color-accent); - border-color: color-mix(in srgb, var(--color-accent) 28%, var(--color-border-subtle)); - background: color-mix(in srgb, var(--color-accent) 10%, transparent); +.session-detail-link-pill--pr { + font-family: var(--font-mono); + font-size: 10.5px; + font-weight: 600; + color: var(--color-accent-amber); + border-color: var(--color-accent-amber-border); + background: var(--color-accent-amber-dim); } -.session-detail-link-pill--link.session-detail-link-pill--accent:hover { - border-color: color-mix(in srgb, var(--color-accent) 46%, var(--color-border-strong)); - color: var(--color-accent); - background: color-mix(in srgb, var(--color-accent) 14%, transparent); +.session-detail-link-pill--pr:hover { + color: var(--color-accent-amber); + border-color: var(--color-accent-amber-border); + background: rgba(249, 115, 22, 0.18); +} + +.session-detail-link-pill--diff { + font-family: var(--font-mono); + font-size: 10px; + gap: 3px; + padding: 2px 7px; } .session-detail-status-pill { - position: relative; - overflow: hidden; - box-shadow: inset 0 1px 0 color-mix(in srgb, white 16%, transparent); + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 9px; + border: 1px solid; + border-radius: 4px; + font-size: 10.5px; + font-weight: 600; transition: transform 0.16s ease-out, opacity 0.16s ease-out, @@ -952,37 +1821,69 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { border-color 0.16s ease-out; } +.session-detail-status-pill__label { + color: inherit; +} + .session-detail-status-pill__dot { - position: relative; - z-index: 1; + width: 6px; + height: 6px; + border-radius: 999px; + flex-shrink: 0; } .session-detail-status-pill--active { - animation: session-status-active-breathe 2.8s ease-in-out infinite; + color: var(--color-status-working); + border-color: color-mix(in srgb, var(--color-status-working) 20%, transparent); + background: color-mix(in srgb, var(--color-status-working) 8%, transparent); +} + +.session-detail-status-pill--ready { + color: var(--color-status-merge); + border-color: color-mix(in srgb, var(--color-status-merge) 20%, transparent); + background: color-mix(in srgb, var(--color-status-merge) 8%, transparent); +} + +.session-detail-status-pill--waiting { + color: var(--color-status-respond); + border-color: color-mix(in srgb, var(--color-status-respond) 20%, transparent); + background: color-mix(in srgb, var(--color-status-respond) 8%, transparent); +} + +.session-detail-status-pill--error { + color: var(--color-status-error, var(--color-accent-red)); + border-color: color-mix(in srgb, var(--color-status-error, var(--color-accent-red)) 20%, transparent); + background: color-mix(in srgb, var(--color-status-error, var(--color-accent-red)) 8%, transparent); +} + +.session-detail-status-pill--neutral { + color: var(--color-text-muted); + border-color: color-mix(in srgb, var(--color-text-muted) 16%, transparent); + background: color-mix(in srgb, var(--color-text-muted) 6%, transparent); } .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; } .session-detail-status-pill--idle { + color: var(--color-text-muted); + border-color: color-mix(in srgb, var(--color-text-muted) 16%, transparent); + background: color-mix(in srgb, var(--color-text-muted) 6%, transparent); 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 { - 0%, 100% { + 0%, + 100% { transform: scale(1); opacity: 0.88; } @@ -992,65 +1893,81 @@ 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; - } +.session-detail-pr-section { + margin-bottom: 16px; } -@keyframes session-status-ready-breathe { - 0%, 100% { - transform: translateY(0); - opacity: 0.96; - } - 50% { - transform: translateY(-1px); - opacity: 1; - } +.session-detail-terminal-wrap { + margin-bottom: 18px; } -@keyframes session-status-waiting-breathe { - 0%, 100% { - transform: translateY(0); - opacity: 0.95; - } - 50% { - transform: translateY(-1px); - opacity: 1; - } +.session-detail-section-label { + display: flex; + align-items: center; + gap: 7px; + margin-bottom: 8px; } -.dark .detail-card { - --color-text-secondary: #9898a0; - --color-text-muted: #5c5c66; - --color-text-tertiary: #5c5c66; +.session-detail-section-label__bar { + width: 2px; + height: 11px; + border-radius: 1px; } -/* ── Session cards — flat, Linear-style ──────────────────────────────── */ +.session-detail-section-label__text { + font-size: 10px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--color-text-muted); +} + +.session-detail-terminal-placeholder { + border: 1px solid var(--color-border-default); + border-radius: 6px; + background: color-mix(in srgb, var(--color-bg-elevated) 92%, transparent); +} + +/* ── Session cards — Emil design vision ──────────────────────────────── */ .session-card { background: var(--card-bg); - border-color: var(--card-border); - border-radius: 2px; - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 4%, transparent), - 0 10px 20px rgba(0, 0, 0, 0.08); + border: 1px solid var(--card-border); + border-left-width: 3px; + border-left-style: solid; + border-radius: 6px; + box-shadow: none; + color: color-mix(in srgb, var(--color-text-primary) 96%, transparent); contain: layout style paint; transition: - transform 0.12s ease, - border-color 0.12s ease, - background 0.12s ease, - box-shadow 0.12s ease; + transform 120ms ease-out, + border-color 120ms ease-out, + box-shadow 120ms ease-out; +} + +.session-card--accent-working { + border-left-color: var(--color-status-working); +} + +.session-card--accent-attention { + border-left-color: var(--color-status-attention); +} + +.session-card--accent-respond { + border-left-color: var(--color-status-respond); +} + +.session-card--accent-merge { + border-left-color: var(--color-status-ready); +} + +.session-card--accent-default { + border-left-color: var(--color-border-default); } .session-card--fixed { display: flex; - min-height: 242px; + min-height: 150px; flex-direction: column; overflow: hidden; } @@ -1064,20 +1981,15 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .session-card--alert-frame { - min-height: 292px; + min-height: 174px; } .session-card--merge-frame { - min-height: 264px; + min-height: 166px; } .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); + box-shadow: none; } /* ── Dark mode weight reduction ──────────────────────────────────────── @@ -1100,25 +2012,304 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .session-card:hover { transform: translateY(-1px); border-color: var(--color-border-strong); - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 5%, transparent), - 0 18px 34px rgba(0, 0, 0, 0.09); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); +} + +.session-card:active { + transform: scale(0.985); + transition-duration: 100ms; } .session-card__header, -.session-card__title-wrap, -.session-card__meta, +.session-card__body, +.card__title-wrap, +.card__meta, +.card__alerts, .session-card__actions { position: relative; z-index: 1; } -.session-card__title-wrap::after { - display: none; +.session-card__header { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 10px 3px; } -.session-card__title { +.card__id { + font-family: var(--font-mono); + font-size: 9.5px; + letter-spacing: 0.04em; + color: var(--color-text-muted); + flex: 1; +} + +.card__adot { + position: relative; + width: 7px; + height: 7px; + flex-shrink: 0; + border-radius: 999px; +} + +.card__adot--working { + background: var(--color-status-working); +} + +@keyframes design-ref-adot-pulse { + 0%, + 100% { + opacity: 0.42; + transform: scale(1); + } + 50% { + opacity: 0; + transform: scale(1.55); + } +} + +.card__adot--working::after { + content: ""; + position: absolute; + inset: -2px; + border: 1px solid color-mix(in srgb, var(--color-status-working) 42%, transparent); + border-radius: inherit; + animation: design-ref-adot-pulse 2s ease-in-out infinite; +} + +.card__adot--ready { + background: var(--color-status-ready); + opacity: 0.5; +} + +.card__adot--idle { + background: var(--color-text-muted); +} + +.card__adot--waiting { + background: var(--color-status-respond); +} + +.card__adot--blocked { + background: var(--color-ci-fail); +} + +.card__adot--exited { + background: var(--color-status-done); +} + +.card__title-wrap { + padding: 0 10px 4px; +} + +.card__title { + font-size: 12.5px; + font-weight: 600; + line-height: 1.45; + color: var(--color-text-primary); + display: -webkit-box; + -webkit-box-orient: vertical; -webkit-line-clamp: 2; + overflow: hidden; +} + +.card__title--dim { + font-weight: 500; + color: var(--color-text-secondary); +} + +.card__meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 4px; + padding: 0 10px 5px; +} + +.card__branch { + font-family: var(--font-mono); + font-size: 9.5px; + color: var(--color-text-muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 104px; +} + +.card__meta-sep { + color: var(--color-text-muted); + font-size: 10px; +} + +.card__pr { + font-family: var(--font-mono); + font-size: 10px; + font-weight: 600; + color: var(--color-text-secondary); + background: var(--color-bg-subtle); + border: 1px solid var(--color-border-default); + padding: 1px 5px; + border-radius: 3px; + text-decoration: none; + white-space: nowrap; + transition: color 80ms, border-color 80ms, background 80ms; +} + +.card__pr:hover { + color: var(--color-text-primary); + border-color: var(--color-border-strong); + background: var(--color-bg-elevated); + text-decoration: none; +} + +.card__diff { + background: var(--color-bg-subtle); + padding: 1px 5px; + border-radius: 3px; + font-family: var(--font-mono); + font-size: 9.5px; + gap: 2px; +} + +.card__diff-add { + color: var(--color-ci-pass); +} + +.card__diff-del { + color: var(--color-ci-fail); +} + +.card__diff-size { + color: var(--color-text-muted); +} + +.card__ci { + display: flex; + flex-wrap: wrap; + gap: 3px; + padding: 0 10px 5px; +} + +.ci-chip { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 9.5px; + font-family: var(--font-mono); + padding: 1px 6px; + border-radius: 3px; +} + +.ci-chip--pass { + color: var(--color-ci-pass); + border: 1px solid rgba(34, 197, 94, 0.2); + background: rgba(34, 197, 94, 0.06); +} + +.card__alerts { + padding: 4px 10px 6px; + display: flex; + flex-direction: column; + gap: 3px; + align-items: stretch; +} + +/* ── Alert rows ────────────────────────────────────────────────── */ + +.alert-row { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px 4px 7px; + border-radius: 4px; + font-size: 11px; + border-left: 2px solid var(--ar-color); + background: var(--ar-bg); + color: var(--ar-color); + transition: background 80ms; +} + +.alert-row--ci { --ar-color: var(--color-alert-ci); --ar-bg: var(--color-alert-ci-bg); } +.alert-row--changes { --ar-color: var(--color-alert-changes); --ar-bg: var(--color-alert-changes-bg); } +.alert-row--review { --ar-color: var(--color-alert-review); --ar-bg: var(--color-alert-review-bg); } +.alert-row--conflict { --ar-color: var(--color-alert-conflict); --ar-bg: var(--color-alert-conflict-bg); } +.alert-row--comment { --ar-color: var(--color-alert-comment); --ar-bg: var(--color-alert-comment-bg); } + +.alert-row__icon { + flex-shrink: 0; + display: flex; + align-items: center; +} + +.alert-row__text { + flex: 1; + min-width: 0; + font-size: 11px; + font-weight: 400; + color: var(--color-text-secondary); + min-width: 0; +} + +.alert-row__text a { + color: inherit; + text-decoration: none; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} + +.alert-row__text a:hover { + color: var(--color-text-primary); + text-decoration: underline; + text-underline-offset: 2px; +} + +.alert-row__notified { + opacity: 0.65; + font-weight: 400; +} + +.alert-row__action { + flex-shrink: 0; + padding: 2px 7px; + border: 1px solid var(--ar-color); + border-radius: 3px; + background: transparent; + color: var(--ar-color); + font-size: 9.5px; + font-weight: 600; + opacity: 0.85; + transition: background 100ms, color 100ms; + cursor: pointer; + white-space: nowrap; + text-transform: lowercase; + letter-spacing: 0.03em; +} + +.alert-row__action:hover { + background: var(--ar-color); + color: #fff; + opacity: 1; +} + +.alert-row__action:active { + transform: scale(0.96); +} + +.alert-row__action:disabled { + opacity: 0.4; + cursor: default; +} + +.card__issue { + font-size: 11px; + color: var(--color-accent); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .session-card__secondary { @@ -1127,6 +2318,20 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-height: 1.45; + font-size: 10.5px; + color: var(--color-text-muted); +} + +.session-card__secondary--merge { + display: flex; + align-items: center; + gap: 4px; + color: var(--color-text-muted); +} + +.session-card__secondary--merge svg { + flex-shrink: 0; + color: var(--color-ci-pass); } .session-card__alert-grid { @@ -1141,18 +2346,47 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .session-card__footer { margin-top: auto; - min-height: 34px; - background: color-mix(in srgb, var(--color-bg-base) 35%, transparent); + min-height: 0; + display: flex; + align-items: center; + gap: 6px; + border-top: 1px solid var(--color-border-subtle); + background: transparent; + padding: 5px 10px 7px; +} + +.card__status { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 10.5px; + line-height: 1.2; + font-family: var(--font-mono); + font-weight: 400; + color: var(--color-text-muted); } .session-card__control { - height: 28px; - padding-left: 10px; - padding-right: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + text-decoration: none; + transition: + transform 160ms ease-out, + border-color 120ms ease-out, + color 120ms ease-out; + height: 26px; box-sizing: border-box; white-space: nowrap; } +.session-card__control:active { + transform: scale(0.97); +} + .session-card__control-icon { width: 12px; height: 12px; @@ -1160,40 +2394,91 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .session-card__terminate { - border-radius: 0; line-height: 1; - padding-left: 8px; - padding-right: 8px; +} + +.session-card__terminal-link { + font-size: 10.5px; + font-family: var(--font-sans); + font-weight: 500; + padding: 2px 8px 2px 6px; + border-radius: 4px; + border: 1px solid var(--color-border-default); + background: var(--color-bg-elevated); + color: var(--color-text-secondary); +} + +.session-card__terminal-link:hover { + background: var(--color-accent-amber-dim); + border-color: var(--color-accent-amber-border); + color: var(--color-accent-amber); + text-decoration: none; +} + +.btn--danger { + width: 26px; + height: 26px; + padding: 0; + border-radius: 4px; + border: 1px solid rgba(239, 68, 68, 0.2); + background: rgba(239, 68, 68, 0.04); + color: rgba(239, 68, 68, 0.45); +} + +.btn--danger:hover { + background: rgba(239, 68, 68, 0.1); + border-color: rgba(239, 68, 68, 0.4); + color: var(--color-ci-fail); +} + +.btn--danger.is-confirming { + width: auto; + min-width: 44px; + padding: 0 8px; + background: rgba(239, 68, 68, 0.14); + border-color: rgba(239, 68, 68, 0.6); + color: var(--color-ci-fail); + animation: kill-confirm-pulse 0.6s cubic-bezier(0.23, 1, 0.32, 1); +} + +@keyframes kill-confirm-pulse { + 0% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.22); + } + 55% { + transform: scale(1.04); + box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); + } + 100% { + transform: scale(1); + box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); + } +} + +.btn--danger.is-confirming::after { + content: none; } .session-card__merge-control { - border-color: color-mix(in srgb, var(--color-status-ready) 68%, transparent); - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-status-ready) 34%, transparent) 0%, - color-mix(in srgb, var(--color-status-ready) 18%, transparent) 100% - ); - color: var(--color-status-ready); - text-decoration: none; + font-size: 11px; font-weight: 600; - letter-spacing: 0.01em; - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 26%, transparent), - 0 8px 18px color-mix(in srgb, var(--color-status-ready) 16%, transparent); + padding: 3px 10px; + border-radius: 4px; + border: 1px solid var(--color-accent-amber-border); + background: var(--color-accent-amber-dim); + color: var(--color-accent-amber); + text-decoration: none; + box-shadow: none; + cursor: pointer; } .session-card__merge-control:hover { - border-color: var(--color-status-ready); - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-status-ready) 46%, transparent) 0%, - color-mix(in srgb, var(--color-status-ready) 24%, transparent) 100% - ); + border-color: var(--color-accent-amber-border); + background: rgba(249, 115, 22, 0.2); text-decoration: none; - transform: translateY(-1px); - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 30%, transparent), - 0 10px 22px color-mix(in srgb, var(--color-status-ready) 20%, transparent); + transform: none; + box-shadow: none; } .session-card__merge-control .session-card__control-icon { @@ -1206,110 +2491,20 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .session-card.card-merge-ready { position: relative; - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-status-ready) 10%, var(--card-merge-bg)) 0%, - color-mix(in srgb, var(--color-status-ready) 6%, var(--card-merge-bg)) 28%, - color-mix(in srgb, var(--color-status-ready) 3%, var(--card-merge-bg)) 100% - ); - border-color: color-mix(in srgb, var(--color-status-ready) 30%, var(--color-border-default)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 4%, transparent), - 0 22px 40px color-mix(in srgb, var(--color-status-ready) 18%, transparent); + background: var(--card-bg); + border-color: var(--card-border); } -.session-card.card-merge-ready::before { - display: none; +.dark .session-card.card-merge-ready { + box-shadow: none; } -.session-card.card-merge-ready::after { - content: ""; - position: absolute; - inset: -14px -10px -18px -10px; - pointer-events: none; - z-index: 0; - background: - radial-gradient( - ellipse at 50% 100%, - color-mix(in srgb, var(--color-status-ready) 18%, transparent) 0%, - color-mix(in srgb, var(--color-status-ready) 8%, transparent) 42%, - transparent 72% - ), - radial-gradient( - ellipse at 50% 0%, - color-mix(in srgb, var(--color-status-ready) 10%, transparent) 0%, - transparent 58% - ); - filter: blur(10px); - opacity: 0.9; -} - -.session-card.card-merge-ready > * { - position: relative; - z-index: 1; -} - -.dark .session-card.card-merge-ready::after { - background: - radial-gradient( - ellipse at 50% 100%, - color-mix(in srgb, var(--color-status-ready) 24%, transparent) 0%, - color-mix(in srgb, var(--color-status-ready) 10%, transparent) 44%, - transparent 72% - ), - radial-gradient( - ellipse at 50% 0%, - color-mix(in srgb, var(--color-status-ready) 12%, transparent) 0%, - transparent 58% - ); - opacity: 0.75; -} - -.session-card.card-merge-ready:hover::after { - opacity: 1; - filter: blur(12px); - background: - radial-gradient( - ellipse at 50% 100%, - color-mix(in srgb, var(--color-status-ready) 24%, transparent) 0%, - color-mix(in srgb, var(--color-status-ready) 10%, transparent) 44%, - transparent 74% - ), - radial-gradient( - ellipse at 50% 0%, - color-mix(in srgb, var(--color-status-ready) 12%, transparent) 0%, - transparent 58% - ); -} - -.dark .session-card.card-merge-ready:hover::after { - background: - radial-gradient( - ellipse at 50% 100%, - color-mix(in srgb, var(--color-status-ready) 28%, transparent) 0%, - color-mix(in srgb, var(--color-status-ready) 12%, transparent) 44%, - transparent 74% - ), - radial-gradient( - ellipse at 50% 0%, - color-mix(in srgb, var(--color-status-ready) 14%, transparent) 0%, - transparent 58% - ); -} - -.session-card.card-merge-ready::after { - transition: - opacity 0.18s ease, - filter 0.18s ease, - background 0.18s ease; -} +/* Merge card glow removed for cleaner Emil design */ .session-card.card-merge-ready:hover { - transform: translateY(-2px); - border-color: color-mix(in srgb, var(--color-status-ready) 42%, var(--color-border-strong)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 6%, transparent), - 0 28px 52px color-mix(in srgb, var(--color-status-ready) 22%, transparent); + transform: translateY(-1px); + border-color: var(--color-border-strong); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); } .session-card.card-merge-ready .session-card__header > span:first-child > span:first-child { @@ -1319,89 +2514,101 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { /* ── Quick-reply section (respond cards) ─────────────────────────────── */ .quick-reply { - display: flex; - flex-direction: column; - gap: 8px; - padding: 10px 12px; - border-top: 1px solid var(--color-border-default); + padding: 0 10px 5px; } -.quick-reply__summary { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - font-size: 12px; - line-height: 1.5; - color: var(--color-text-secondary); - margin: 0; -} - -.quick-reply__presets { +.card__agent-msg { display: flex; gap: 6px; + padding: 0 0 4px; + font-size: 10px; + color: var(--color-text-secondary); + line-height: 1.45; } -.quick-reply__preset-btn { - min-height: 44px; - padding: 0 14px; +.card__agent-msg-icon { + color: var(--color-status-respond); + flex-shrink: 0; + margin-top: 1px; +} + +.card__view-context { + display: block; + font-size: 9.75px; + color: var(--color-text-muted); + padding: 0 0 6px; + text-decoration: none; + transition: color 100ms; +} + +.card__view-context:hover { + color: var(--color-text-secondary); + text-decoration: none; +} + +.card__presets { + display: flex; + gap: 3px; + margin-bottom: 5px; +} + +.card__preset { + flex: 1; + padding: 3px 0; + border-radius: 4px; border: 1px solid var(--color-border-default); - background: var(--color-bg-subtle); + background: transparent; color: var(--color-text-secondary); font-family: var(--font-sans); - font-size: 12px; + font-size: 10.5px; + font-weight: 500; cursor: pointer; - transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease; + text-align: center; + transition: + transform 160ms ease-out, + border-color 120ms ease-out; } -.quick-reply__preset-btn:hover { - border-color: var(--color-accent); - color: var(--color-accent); - background: var(--color-tint-blue); -} - -.quick-reply__input { - min-height: 44px; - height: 44px; - padding: 12px; - border: 1px solid var(--color-border-default); +.card__preset:hover { background: var(--color-bg-subtle); + border-color: var(--color-border-strong); color: var(--color-text-primary); - font-family: var(--font-sans); - font-size: 13px; - resize: none; - overflow: hidden; - transition: height 0.15s ease, border-color 0.12s ease; - box-sizing: border-box; +} + +.card__preset:active { + transform: scale(0.96); +} + +.card__reply-wrap { + padding: 0; +} + +.card__reply { width: 100%; -} - -.quick-reply__input::placeholder { - color: var(--color-text-muted); -} - -.quick-reply__input:focus { + padding: 5px 8px; + font-family: var(--font-sans); + font-size: 10.75px; + background: var(--color-bg-subtle); + border: 1px solid var(--color-border-default); + border-radius: 4px; + color: var(--color-text-primary); + resize: none; outline: none; - border-color: var(--color-accent); - height: 80px; - overflow: auto; + transition: border-color 120ms ease-out; +} + +.card__reply::placeholder { + color: var(--color-text-muted); + font-size: 10.5px; +} + +.card__reply:focus { + border-color: var(--color-border-strong); } @media (min-width: 768px) { - .quick-reply__preset-btn { - min-height: 32px; - padding: 0 10px; - font-size: 11px; - } - - .quick-reply__input { - min-height: 32px; - height: 32px; - padding: 8px 10px; - } - - .quick-reply__input:focus { - height: 60px; + .card__preset { + font-size: 10px; } } @@ -1533,6 +2740,386 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { opacity: 0.6; } +/* ── Session detail redesign — compact PR card ─────────────────────── */ + +.session-detail-pr-card { + border: 1px solid var(--color-border-default); + border-radius: 6px; + background: var(--color-bg-card, var(--detail-card-bg, var(--color-bg-elevated))); + overflow: hidden; +} + +.session-detail-pr-card--green { + border-color: color-mix(in srgb, var(--color-accent-green) 35%, var(--color-border-default)); +} + +.session-detail-pr-card__row { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + min-height: 0; +} + +.session-detail-pr-card__title-link { + font-size: 12.5px; + font-weight: 600; + color: var(--color-text-primary); + text-decoration: none; + transition: color 120ms; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} + +.session-detail-pr-card__title-link:hover { + color: var(--color-accent-amber); +} + +.session-detail-pr-card__diff-stats { + font-family: var(--font-mono); + font-size: 10px; + flex-shrink: 0; +} + +.session-detail-pr-card__diff-label { + font-size: 9.5px; + font-weight: 600; + padding: 1px 6px; + border-radius: 3px; + background: var(--color-bg-subtle); + border: 1px solid var(--color-border-subtle); + color: var(--color-text-muted); + flex-shrink: 0; +} + +/* PR detail strips — inline blockers + CI chips */ +.session-detail-pr-card__details { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 0 14px 10px; +} + +.session-detail-pr-sep { + width: 1px; + height: 14px; + background: var(--color-border-subtle); + flex-shrink: 0; +} + +/* Blocker chips */ +.session-detail-blocker-chip { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10.5px; + padding: 2px 8px; + border-radius: 3px; +} + +.session-detail-blocker-chip--fail { + color: var(--color-status-error, var(--color-accent-red)); + background: color-mix(in srgb, var(--color-status-error, var(--color-accent-red)) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--color-status-error, var(--color-accent-red)) 18%, transparent); +} + +.session-detail-blocker-chip--warn { + color: var(--color-status-attention, var(--color-accent-yellow)); + background: color-mix(in srgb, var(--color-status-attention, var(--color-accent-yellow)) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--color-status-attention, var(--color-accent-yellow)) 18%, transparent); +} + +.session-detail-blocker-chip--muted { + color: var(--color-text-muted); + background: var(--color-bg-subtle); + border: 1px solid var(--color-border-subtle); +} + +.session-detail-blocker-chip__note { + font-size: 9px; + opacity: 0.7; +} + +/* CI inline chips */ +.session-detail-ci-chip { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 10px; + font-family: var(--font-mono); + padding: 2px 7px; + border-radius: 3px; +} + +.session-detail-ci-chip--pass { + color: var(--color-accent-green); + border: 1px solid color-mix(in srgb, var(--color-accent-green) 20%, transparent); + background: color-mix(in srgb, var(--color-accent-green) 6%, transparent); +} + +.session-detail-ci-chip--fail { + color: var(--color-accent-red, var(--color-status-error)); + border: 1px solid color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 20%, transparent); + background: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 6%, transparent); +} + +.session-detail-ci-chip--pending { + color: var(--color-accent-yellow, var(--color-status-attention)); + border: 1px solid color-mix(in srgb, var(--color-accent-yellow, var(--color-status-attention)) 20%, transparent); + background: color-mix(in srgb, var(--color-accent-yellow, var(--color-status-attention)) 6%, transparent); +} + +.session-detail-ci-chip--queued { + color: var(--color-text-muted); + border: 1px solid var(--color-border-subtle); + background: transparent; +} + +/* Merge-ready banner (compact, inline) */ +.session-detail-merge-banner { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 2px 10px; + border-radius: 4px; + border: 1px solid color-mix(in srgb, var(--color-accent-green) 25%, transparent); + background: color-mix(in srgb, var(--color-accent-green) 8%, transparent); + font-size: 11px; + font-weight: 600; + color: var(--color-accent-green); +} + +/* Collapsible comments strip */ +.session-detail-comments-strip { + border-top: 1px solid var(--color-border-subtle); +} + +.session-detail-comments-strip summary { + list-style: none; + cursor: pointer; +} + +.session-detail-comments-strip summary::-webkit-details-marker { + display: none; +} + +.session-detail-comments-strip__toggle { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 14px; + font-size: 11px; + transition: background 80ms; +} + +.session-detail-comments-strip__toggle:hover { + background: var(--color-bg-subtle); +} + +.session-detail-comments-strip__chevron { + width: 10px; + height: 10px; + flex-shrink: 0; + color: var(--color-text-muted); + transition: transform 150ms; +} + +.session-detail-comments-strip[open] .session-detail-comments-strip__chevron { + transform: rotate(90deg); +} + +.session-detail-comments-strip__label { + font-weight: 600; + color: var(--color-text-secondary); +} + +.session-detail-comments-strip__count { + font-size: 10px; + font-weight: 700; + padding: 1px 6px; + border-radius: 3px; + color: var(--color-accent-red, var(--color-status-error)); + background: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 12%, transparent); +} + +.session-detail-comments-strip__hint { + margin-left: auto; + font-size: 10px; + color: var(--color-text-muted); +} + +.session-detail-comments-strip__body { + padding: 0 14px 10px; +} + +/* Individual comment row */ +.session-detail-comment__row { + display: flex; + align-items: center; + gap: 7px; + padding: 4px 8px; + font-size: 12px; + border-radius: 4px; + transition: background 80ms; +} + +.session-detail-comment__row:hover { + background: var(--color-bg-subtle); +} + +.session-detail-comment summary { + list-style: none; + cursor: pointer; +} + +.session-detail-comment summary::-webkit-details-marker { + display: none; +} + +.session-detail-comment__chevron { + width: 11px; + height: 11px; + flex-shrink: 0; + color: var(--color-text-muted); + transition: transform 150ms; +} + +.session-detail-comment[open] .session-detail-comment__chevron { + transform: rotate(90deg); +} + +.session-detail-comment__title { + font-size: 12px; + font-weight: 500; + color: var(--color-text-secondary); +} + +.session-detail-comment__author { + font-size: 10.5px; + color: var(--color-text-muted); +} + +.session-detail-comment__view { + margin-left: auto; + font-size: 10px; + color: var(--color-accent-blue, var(--color-accent)); + text-decoration: none; + flex-shrink: 0; +} + +.session-detail-comment__view:hover { + text-decoration: underline; +} + +.session-detail-comment__body { + margin-left: 18px; + padding: 2px 8px 10px; +} + +.session-detail-comment__file { + font-family: var(--font-mono); + font-size: 10px; + color: var(--color-text-muted); + margin-bottom: 4px; +} + +.session-detail-comment__text { + border-left: 2px solid var(--color-border-default); + padding-left: 10px; + font-size: 11.5px; + line-height: 1.55; + color: var(--color-text-secondary); + margin-bottom: 6px; +} + +.session-detail-comment__fix-btn { + padding: 3px 10px; + border-radius: 4px; + border: 1px solid var(--color-accent-amber-border, color-mix(in srgb, var(--color-accent) 30%, transparent)); + background: var(--color-accent-amber-dim, color-mix(in srgb, var(--color-accent) 12%, transparent)); + color: var(--color-accent-amber, var(--color-accent)); + font-family: var(--font-sans, inherit); + font-size: 10.5px; + font-weight: 600; + cursor: pointer; + transition: background 100ms, transform 100ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.session-detail-comment__fix-btn:hover { + background: color-mix(in srgb, var(--color-accent-amber, var(--color-accent)) 18%, transparent); +} + +.session-detail-comment__fix-btn:active { + transform: scale(0.97); +} + +.session-detail-comment__fix-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.session-detail-comment__fix-btn--sent { + border-color: color-mix(in srgb, var(--color-accent-green) 30%, transparent); + background: color-mix(in srgb, var(--color-accent-green) 12%, transparent); + color: var(--color-accent-green); +} + +.session-detail-comment__fix-btn--error { + border-color: color-mix(in srgb, var(--color-accent-red) 30%, transparent); + background: color-mix(in srgb, var(--color-accent-red) 12%, transparent); + color: var(--color-accent-red); +} + +/* Identity strip action buttons */ +.session-detail-action-btn { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 11px; + border-radius: 5px; + border: 1px solid var(--color-border-default); + background: transparent; + color: var(--color-text-secondary); + font-family: inherit; + font-size: 11.5px; + font-weight: 500; + cursor: pointer; + transition: background 100ms, border-color 100ms, color 100ms, transform 100ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.session-detail-action-btn:hover { + background: var(--color-bg-subtle); + border-color: var(--color-border-strong); + color: var(--color-text-primary); +} + +.session-detail-action-btn:active { + transform: scale(0.97); +} + +.session-detail-action-btn--danger { + color: var(--color-accent-red, var(--color-status-error)); + border-color: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 22%, var(--color-border-default)); +} + +.session-detail-action-btn--danger:hover { + background: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 8%, transparent); + border-color: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 40%, var(--color-border-default)); + color: var(--color-accent-red, var(--color-status-error)); +} + +/* Diff stat colors (for PR card and pills) */ +.session-detail-diff--add { + color: var(--color-accent-green); +} + +.session-detail-diff--del { + color: var(--color-accent-red, var(--color-status-error)); +} + /* ── Project sidebar ────────────────────────────────────────────────── */ .project-sidebar nav::-webkit-scrollbar { @@ -1543,311 +3130,287 @@ 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 { - border-right: 1px solid var(--color-border-subtle); - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-bg-elevated) 92%, black 8%) 0%, - color-mix(in srgb, var(--color-bg-base) 90%, black 10%) 100% - ); + --sidebar-bg: #f0ede9; + --sidebar-border: rgba(0, 0, 0, 0.06); + --sidebar-row-hover: rgba(120, 100, 80, 0.05); + --sidebar-row-active: rgba(120, 100, 80, 0.09); + border-right: 1px solid var(--sidebar-border); + background: var(--sidebar-bg); + color: var(--color-text-primary); + overflow: hidden; + width: 224px; +} + +.dark .project-sidebar { + --sidebar-bg: #0e0d0c; + --sidebar-border: rgba(255, 240, 220, 0.07); + --sidebar-row-hover: rgba(255, 240, 220, 0.05); + --sidebar-row-active: rgba(255, 240, 220, 0.07); } .project-sidebar--collapsed { - align-items: center; + width: 0; + border-right-color: transparent; + opacity: 0; + pointer-events: none; } -.project-sidebar__header { - border-bottom: 1px solid var(--color-border-subtle); - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-accent) 4%, transparent) 0%, - transparent 100% - ); -} - -.project-sidebar__eyebrow { - font-size: 10px; - letter-spacing: 0.16em; - text-transform: uppercase; - color: var(--color-accent); -} - -.project-sidebar__title-row { +.project-sidebar__compact-hdr { display: flex; - align-items: flex-start; + align-items: center; justify-content: space-between; - gap: 12px; - margin-top: 6px; + padding: 10px 12px 8px; + border-bottom: 1px solid var(--sidebar-border); + flex-shrink: 0; } -.project-sidebar__title { - font-size: 13px; - line-height: 1.1; - letter-spacing: -0.02em; - font-weight: 600; - color: var(--color-text-primary); -} - -.project-sidebar__subtitle { - margin-top: 3px; - max-width: 18ch; - font-size: 10px; - line-height: 1.4; +.project-sidebar__sect-label { + font-size: 11px; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; color: var(--color-text-muted); } -.project-sidebar__badge { - display: inline-flex; - min-width: 22px; - height: 22px; +.project-sidebar__add-btn { + display: flex; align-items: center; justify-content: center; - border: 1px solid var(--color-border-default); - background: color-mix(in srgb, var(--color-bg-base) 42%, transparent); - font-size: 10px; - font-weight: 600; - color: var(--color-text-primary); - font-variant-numeric: tabular-nums; + width: 20px; + height: 20px; + border-radius: 4px; + color: var(--color-text-muted); + background: transparent; + border: none; + cursor: pointer; + transition: background 100ms, color 100ms; + flex-shrink: 0; } -.project-sidebar__summary { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); +.project-sidebar__add-btn svg { + width: 12px; + height: 12px; +} + +.project-sidebar__add-btn:hover { + background: var(--sidebar-row-hover); + color: var(--color-text-secondary); +} + +/* ── Project tree ──────────────────────────────────────────────────── */ + +.project-sidebar__tree::-webkit-scrollbar { + width: 3px; +} +.project-sidebar__tree::-webkit-scrollbar-track { + background: transparent; +} +.project-sidebar__tree::-webkit-scrollbar-thumb { + background: var(--color-border-default, var(--color-border-subtle)); + border-radius: 2px; +} + +/* Project accordion item */ +.project-sidebar__project { + border-bottom: 1px solid var(--sidebar-border); +} +.project-sidebar__project:last-child { + border-bottom: none; +} + +/* Project toggle row */ +.project-sidebar__proj-toggle { + width: 100%; + display: flex; + align-items: center; gap: 6px; - margin-top: 10px; + padding: 6px 12px; + background: none; + border: none; + cursor: pointer; + color: var(--color-text-primary); + font-family: var(--font-sans); + font-size: 12px; + font-weight: 500; + text-align: left; + transition: background 80ms; } -.project-sidebar__metric { +.project-sidebar__proj-toggle:hover { + background: var(--sidebar-row-hover); +} + +.project-sidebar__proj-toggle--active { + color: var(--color-accent-amber); +} + +/* Project chevron: rotates 90deg when open */ +.project-sidebar__proj-chevron { + flex-shrink: 0; + color: var(--color-text-muted); + transition: transform 180ms cubic-bezier(0.23, 1, 0.32, 1); +} + +.project-sidebar__proj-chevron--open { + transform: rotate(90deg); +} + +/* Project name */ +.project-sidebar__proj-name { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Project badge (session count) */ +.project-sidebar__proj-badge { + flex-shrink: 0; + font-size: 10px; + font-family: var(--font-mono); + font-weight: 500; + padding: 1px 5px; + border-radius: 3px; + background: var(--color-bg-subtle); + border: 1px solid var(--color-border-subtle); + color: var(--color-text-muted); + line-height: 1.4; +} + +.project-sidebar__proj-badge--active { + background: var(--color-accent-amber-dim); + border-color: var(--color-accent-amber-border); + color: var(--color-accent-amber); +} + +/* Session list */ +.project-sidebar__sessions { display: flex; flex-direction: column; - gap: 2px; - padding: 6px 6px 5px; - border: 1px solid var(--color-border-subtle); - background: color-mix(in srgb, var(--color-bg-base) 40%, transparent); + gap: 1px; + padding: 2px 0 4px; } -.project-sidebar__metric-value { - font-size: 13px; - line-height: 1; - letter-spacing: -0.03em; - font-weight: 600; - color: var(--color-text-primary); - font-variant-numeric: tabular-nums; -} - -.project-sidebar__metric-label { - font-size: 9px; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--color-text-muted); -} - -.project-sidebar__divider { - border-top: 1px solid var(--color-border-subtle); -} - -.project-sidebar__item { - position: relative; - border: 1px solid transparent; - background: transparent; -} - -.project-sidebar__item:hover { - background: var(--color-hover-overlay); - border-color: var(--color-border-subtle); -} - -.project-sidebar__item--active { - border-color: color-mix(in srgb, var(--color-accent) 24%, var(--color-border-default)); - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--color-accent) 16%, transparent), - transparent 90% - ); -} - -.project-sidebar__count { - border: 1px solid var(--color-border-subtle); - background: color-mix(in srgb, var(--color-bg-base) 40%, transparent); -} - -.project-sidebar__children { - position: relative; - margin-top: 4px; - border-left: 1px solid color-mix(in srgb, var(--color-border-subtle) 82%, transparent); -} - -.project-sidebar__session { - position: relative; - margin: 2px 0 2px 8px; - border: 1px solid transparent; - background: transparent; -} - -.project-sidebar__session:hover { - border-color: var(--color-border-subtle); - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--color-hover-overlay) 90%, transparent), - transparent 100% - ); -} - -.project-sidebar__session--active { - border-color: color-mix(in srgb, var(--color-accent) 18%, var(--color-border-subtle)); - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--color-accent) 12%, transparent), - transparent 88% - ); - box-shadow: inset 2px 0 0 var(--color-accent); -} - -.project-sidebar__session::before { - content: ""; - position: absolute; - left: -9px; - top: 50%; - width: 8px; - height: 1px; - background: var(--color-border-subtle); - transform: translateY(-50%); -} - -.project-sidebar__session-tone { - flex-shrink: 0; - padding: 2px 6px; - border: 1px solid var(--color-border-subtle); - background: color-mix(in srgb, var(--color-bg-base) 40%, transparent); - font-size: 9px; - font-weight: 700; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--color-text-muted); -} - -.project-sidebar__session-id { - opacity: 0; - color: var(--color-text-tertiary); - transition: - opacity 0.15s ease, - color 0.15s ease; -} - -.project-sidebar__session:hover .project-sidebar__session-id, -.project-sidebar__session--active .project-sidebar__session-id { - opacity: 0.75; -} - -.project-sidebar__session--active .project-sidebar__session-tone { - border-color: color-mix(in srgb, var(--color-accent) 24%, var(--color-border-subtle)); - background: color-mix(in srgb, var(--color-accent) 10%, transparent); - color: var(--color-accent); -} - -.project-sidebar__collapse-btn, -.project-sidebar__collapsed-toggle { - display: inline-flex; +/* Session row: dot + label + status */ +.project-sidebar__sess-row { + display: flex; align-items: center; - justify-content: center; - gap: 8px; + gap: 7px; + padding: 4px 12px 4px 26px; width: 100%; - border: 1px solid var(--color-border-subtle); - background: color-mix(in srgb, var(--color-bg-base) 40%, transparent); + border: 0; + background: transparent; + cursor: pointer; + transition: background 80ms; + text-align: left; +} + +.project-sidebar__sess-row:hover { + background: var(--sidebar-row-hover); +} + +.project-sidebar__sess-row--active { + background: transparent; +} + +/* Session label */ +.project-sidebar__sess-label { + flex: 1; + font-size: 11.5px; color: var(--color-text-secondary); - font-size: 11px; - font-weight: 600; - transition: - border-color 0.12s ease, - background 0.12s ease, - color 0.12s ease; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -.project-sidebar__collapse-btn { - padding: 9px 10px; -} - -.project-sidebar__collapsed-toggle { - width: 36px; - height: 36px; -} - -.project-sidebar__collapse-btn:hover, -.project-sidebar__collapsed-toggle:hover { - border-color: var(--color-border-default); - background: var(--color-hover-overlay); +.project-sidebar__sess-label--active { color: var(--color-text-primary); + font-weight: 500; } -.project-sidebar__collapsed-project { - position: relative; +/* Session status text */ +.project-sidebar__sess-status { + font-size: 10px; + font-family: var(--font-mono); + color: var(--color-text-muted); + flex-shrink: 0; +} + +/* Empty state inside project */ +.project-sidebar__empty { + padding: 18px 12px; + text-align: center; + color: var(--color-text-muted); + font-size: 11px; +} + +.project-sidebar__footer { + margin-top: auto; + display: flex; + align-items: center; + gap: 8px; + border-top: 1px solid var(--sidebar-border); + padding: 8px 12px; + flex-shrink: 0; +} + +.project-sidebar__theme-toggle { display: inline-flex; - width: 36px; - height: 36px; + height: 27px; + width: 27px; + padding: 0; align-items: center; justify-content: center; - border: 1px solid var(--color-border-subtle); - border-radius: 8px; - background: var(--color-bg-surface); - cursor: pointer; + gap: 0; + border: 1px solid var(--color-border-default); + border-radius: 5px; + background: transparent; + color: var(--color-text-muted); transition: - border-color 0.15s ease, - background 0.15s ease, - box-shadow 0.15s ease; + background 100ms ease-out, + color 100ms ease-out, + border-color 100ms ease-out; } -.project-sidebar__collapsed-project:hover { - border-color: var(--color-border-default); - background: var(--color-bg-elevated); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); -} - -.project-sidebar__collapsed-project--active { - border-color: color-mix(in srgb, var(--color-accent) 35%, var(--color-border-default)); - background: color-mix(in srgb, var(--color-accent) 8%, var(--color-bg-elevated)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-accent) 12%, transparent); -} - -.project-sidebar__avatar { - font-size: 13px; - font-weight: 600; - line-height: 1; +.project-sidebar__theme-toggle:hover { + background: var(--sidebar-row-hover); color: var(--color-text-secondary); - user-select: none; + border-color: var(--color-border-strong); } -.project-sidebar__collapsed-project--active .project-sidebar__avatar { - color: var(--color-accent); +.project-sidebar__theme-toggle svg { + width: 14px; + height: 14px; + flex-shrink: 0; } -.project-sidebar__health-indicator { - position: absolute; - top: -2px; - right: -2px; - width: 7px; - height: 7px; - border-radius: 50%; - border: 1.5px solid var(--color-bg-surface); - box-sizing: content-box; +.project-sidebar__theme-label { + font-size: 11px; + color: var(--color-text-muted); } /* ── Kanban board ────────────────────────────────────────────────────── */ .kanban-board { - display: flex; + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 8px; - height: calc(100vh - 280px); - height: calc(100dvh - 280px); + height: calc(100vh - 240px); + height: calc(100dvh - 240px); overflow-x: auto; padding-bottom: 8px; } .kanban-board-wrap { position: relative; - margin-top: 12px; + margin-top: 14px; + overflow-x: auto; } .board-section-head { @@ -1862,7 +3425,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); } @@ -1898,95 +3461,75 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .kanban-column { - flex: 1 0 260px; - min-width: 260px; - max-width: 420px; display: flex; + min-width: 0; flex-direction: column; - border: 1px solid var(--color-border-default); - border-radius: 0; + border: 1px solid var(--color-border-subtle); + border-radius: 7px; background: var(--color-column-bg); - padding: 4px; - box-shadow: - inset 0 1px 0 color-mix(in srgb, white 2%, transparent), - 0 16px 34px rgba(0, 0, 0, 0.05); + overflow: hidden; } -.kanban-column[data-level="merge"] { - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-status-ready) 4%, var(--color-column-bg)) 0%, - var(--color-column-bg) 100% - ); -} - -.dark .kanban-column { - border-color: rgba(170, 195, 230, 0.14); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.02), - 0 18px 42px rgba(0, 0, 0, 0.24); -} - -.dark .kanban-column[data-level="merge"] { - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-status-ready) 7%, var(--color-column-bg)) 0%, - var(--color-column-bg) 100% - ); -} .kanban-column__header { - margin-bottom: 14px; - padding: 2px 2px 0; + flex-shrink: 0; + padding: 8px 10px 7px; + border-bottom: 1px solid var(--color-border-subtle); } .kanban-column__title-row { display: flex; align-items: center; - gap: 10px; + gap: 6px; } .kanban-column__dot { - width: 8px; - height: 8px; + width: 6px; + height: 6px; border-radius: 999px; } .kanban-column__title { - font-size: 20px; - font-weight: 600; - letter-spacing: normal; - text-transform: none; - color: var(--color-text-primary); + font-size: 10.5px; + font-weight: 500; + letter-spacing: 0.05em; + text-transform: uppercase; + font-family: var(--font-mono); + color: var(--color-text-muted); } .kanban-column__count { margin-left: auto; - min-width: 28px; - padding: 4px 8px; + padding: 0 4px; border: 1px solid var(--color-border-subtle); - border-radius: 0; - background: color-mix(in srgb, var(--color-bg-base) 72%, transparent); - font-size: 11px; - font-weight: 700; - line-height: 1; - color: var(--color-text-secondary); - font-variant-numeric: tabular-nums; - text-align: center; + border-radius: 3px; + background: var(--color-bg-subtle); + font-family: var(--font-mono); + font-size: 10px; + line-height: 1.6; + color: var(--color-text-muted); } .kanban-column__caption { - display: block; - margin-top: 7px; - font-size: 12px; - line-height: 1.5; - color: var(--color-text-muted); + display: none; } .kanban-column-body { flex: 1; overflow-y: auto; - padding: 2px; + padding: 6px; +} + +.kanban-column-body > div { + display: flex; + flex-direction: column; + gap: 5px; +} + +.kanban-column__stack { + display: flex; + flex-direction: column; + gap: 5px; } .kanban-column-body::-webkit-scrollbar { @@ -1997,7 +3540,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 ────────────────────────────────── */ @@ -2010,22 +3553,32 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { padding: 8px 0; background: none; border: none; + border-bottom: 1px solid var(--color-border-subtle); cursor: pointer; - font-size: 12px; - font-weight: 700; - letter-spacing: 0.16em; - text-transform: uppercase; color: var(--color-text-muted); transition: color 0.15s ease; } +.done-bar__label { + font-size: 10.5px; + font-weight: 500; + letter-spacing: 0.05em; + text-transform: uppercase; + font-family: var(--font-mono); + color: var(--color-text-muted); +} + .done-bar__toggle:hover { + color: var(--color-text-primary); +} + +.done-bar__toggle:hover .done-bar__label { color: var(--color-text-secondary); } .done-bar__chevron { - width: 14px; - height: 14px; + width: 12px; + height: 12px; flex-shrink: 0; transition: transform 0.15s ease; } @@ -2035,83 +3588,146 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .done-bar__count { + margin-left: auto; flex-shrink: 0; - min-width: 18px; - height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; + font-family: var(--font-mono); font-size: 10px; - font-weight: 700; - border-radius: 999px; - background: var(--color-border-subtle); color: var(--color-text-muted); - padding: 0 5px; -} - -.done-bar__rule { - flex: 1; - height: 1px; - background: var(--color-border-subtle); } .done-bar__cards { display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 8px; - margin-top: 10px; + margin-top: 12px; +} + +/* ── Done card (compact grid card) ──────────────────────────────────── */ + +.done-card { + background: var(--color-bg-card); + border: 1px solid var(--color-border-subtle); + border-radius: 6px; + padding: 8px 10px; + opacity: 0.75; + transition: opacity 0.15s; + display: flex; + flex-direction: column; + gap: 5px; +} + +.done-card:hover { + opacity: 1; +} + +.done-card__title { + font-size: 11.5px; + font-weight: 500; + color: var(--color-text-secondary); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.done-card__meta { + display: flex; + align-items: center; + gap: 6px; +} + +.done-card__badge { + font-size: 9.5px; + font-weight: 600; + padding: 1px 6px; + border-radius: 4px; + white-space: nowrap; + flex-shrink: 0; +} + +.done-card__badge--merged { + background: var(--color-tint-neutral); + color: var(--color-text-secondary); +} + +.done-card__badge--terminated { + background: var(--color-tint-red); + color: var(--color-accent-red); +} + +.done-card__pr { + font-size: 9.5px; + font-family: var(--font-mono); + color: var(--color-text-muted); + display: flex; + align-items: center; + gap: 3px; + text-decoration: none; + flex-shrink: 0; +} + +.done-card__pr:hover { + color: var(--color-text-secondary); +} + +.done-card__age { + font-size: 9.5px; + color: var(--color-text-muted); + margin-left: auto; + white-space: nowrap; + flex-shrink: 0; +} + +.done-card__restore { + padding: 2px 7px; + font-size: 10px; + font-weight: 600; + border: 1px solid var(--color-border-default); + background: transparent; + color: var(--color-text-muted); + border-radius: 4px; + cursor: pointer; + white-space: nowrap; + transition: border-color 0.15s, color 0.15s; + flex-shrink: 0; +} + +.done-card__restore:hover { + border-color: var(--color-accent); + color: var(--color-accent); } /* ── Session card zone glow (hover) ──────────────────────────────────── */ .kanban-column__empty { - display: flex; - min-height: 132px; - align-items: center; - justify-content: center; - border: 1px dashed color-mix(in srgb, var(--color-border-default) 82%, transparent); - border-radius: 0; - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--color-bg-base) 28%, transparent) 0%, - transparent 100% - ); + display: none; } .kanban-column__empty-label { - font-size: 12px; - letter-spacing: 0.08em; - text-transform: none; - color: var(--color-text-tertiary); + display: none; } /* Card glow effects removed for Linear-clean look */ - @media (max-width: 960px) { .dashboard-hero__content { - padding: 12px 14px; + padding: 16px 28px 10px; } .dashboard-hero__primary { - width: 100%; - flex-direction: column; + width: auto; + flex-direction: row; gap: 10px; } .dashboard-hero__meta { - width: 100%; - align-items: flex-start; - justify-content: space-between; - } - - .dashboard-stat-cards { - width: 100%; + justify-content: flex-end; } .kanban-board { height: auto; - min-height: calc(100vh - 280px); + min-height: calc(100vh - 240px); } .session-card--fixed { @@ -2129,15 +3745,11 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { @media (max-width: 640px) { .dashboard-title { - font-size: 26px; - } - - .dashboard-stat-cards { - gap: 6px; + font-size: 19px; } .kanban-column { - min-width: min(86vw, 320px); + min-width: min(86vw, 280px); } .session-card--fixed { @@ -2228,9 +3840,6 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { /* -- Dashboard main -- */ .dashboard-main { - padding-left: max(12px, env(safe-area-inset-left, 0px)); - padding-right: max(12px, env(safe-area-inset-right, 0px)); - padding-top: 12px; padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px)); } @@ -2238,15 +3847,16 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .dashboard-hero { margin-bottom: 8px; border: none; + border-bottom: none; background: transparent; box-shadow: none; } .dashboard-hero__content { display: grid; - grid-template-columns: 36px minmax(0, 1fr) 36px; + grid-template-columns: 44px minmax(0, 1fr) 44px; align-items: center; - padding: 0; + padding: 0 12px; gap: 8px; min-height: 0; } @@ -2257,10 +3867,12 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .dashboard-hero__heading { + display: block; min-width: 0; } .dashboard-hero__copy { + display: block; min-width: 0; } @@ -2276,47 +3888,36 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { display: none; } - /* Hide stat cards on mobile — replaced by the action strip */ - .dashboard-stat-cards { - display: none; - } - - /* Keep stat cards visible on pages without an action-strip replacement */ - .dashboard-stat-cards--persist-mobile { - display: flex; - } - .dashboard-hero__meta { display: flex; align-items: center; justify-content: flex-end; } - .dashboard-hero__meta > div { + .dashboard-topbar-meta { gap: 0; } - .dashboard-hero__meta button { - width: 36px; - height: 36px; - border: none; - background: none; - color: var(--color-text-secondary); - box-shadow: none; - padding: 0; + .dashboard-topbar-summary, + .dashboard-topbar-link { + display: none; } .mobile-menu-toggle { - width: 36px; - height: 36px; - border: none; - background: none; + width: 44px; + min-width: 44px; + height: 44px; + min-height: 44px; + padding: 0; + border: 1px solid var(--color-border-default); + background: color-mix(in srgb, var(--color-bg-surface) 92%, transparent); color: var(--color-text-secondary); + border-radius: 10px; } .mobile-menu-toggle:hover { - border: none; - background: none; + border-color: var(--color-border-strong); + background: var(--color-hover-overlay); color: var(--color-text-primary); } @@ -2335,8 +3936,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); padding: 8px max(12px, env(safe-area-inset-right, 0px)) - calc(8px + env(safe-area-inset-bottom, 0px)) - max(12px, env(safe-area-inset-left, 0px)); + calc(8px + env(safe-area-inset-bottom, 0px)) max(12px, env(safe-area-inset-left, 0px)); } .mobile-bottom-nav__item { @@ -2405,13 +4005,13 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .mobile-filter-row { display: flex; - align-items: center; gap: 8px; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; margin-bottom: 16px; - padding-bottom: 2px; + padding: 0 0 2px; + overflow-x: auto; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + min-width: 0; } .mobile-filter-row::-webkit-scrollbar { @@ -2423,12 +4023,14 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { align-items: center; justify-content: center; min-height: 32px; - padding: 0 12px; - border: 1px solid var(--color-border-default); - background: color-mix(in srgb, var(--color-bg-surface) 88%, transparent); + min-width: 0; + padding: 6px 14px; + border: 1px solid var(--color-border-subtle); + border-radius: 16px; + background: var(--color-bg-surface); color: var(--color-text-secondary); font-size: 11px; - font-weight: 600; + font-weight: 500; white-space: nowrap; cursor: pointer; transition: @@ -2438,8 +4040,8 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .mobile-filter-chip[data-active="true"] { - border-color: color-mix(in srgb, var(--color-accent) 28%, var(--color-border-default)); - background: color-mix(in srgb, var(--color-accent) 10%, transparent); + border-color: color-mix(in srgb, var(--color-accent) 30%, transparent); + background: color-mix(in srgb, var(--color-accent) 12%, transparent); color: var(--color-accent); } @@ -2503,6 +4105,28 @@ 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-respond); + } + .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-respond); + } + .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; @@ -2708,7 +4332,9 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } /* -- iOS auto-zoom prevention on input focus -- */ - input, textarea, select { + input, + textarea, + select { font-size: 16px; } @@ -2946,6 +4572,38 @@ 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-respond); + } + .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-respond); + } + .mobile-session-row__dot[data-level="merge"] { + background: var(--color-status-ready); + } + .accordion-header__label { font-size: 16px; font-weight: 600; @@ -3007,22 +4665,23 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .mobile-pr-list { display: flex; flex-direction: column; - border: 1px solid color-mix(in srgb, var(--color-border-default) 82%, transparent); - background: var(--color-bg-surface); + gap: 0; } .mobile-pr-card { - display: flex; - flex-direction: column; - gap: 5px; - padding: 11px 12px; - border-top: 1px solid var(--color-border-subtle); + display: block; + padding: 10px 0; + border-bottom: 1px solid color-mix(in srgb, var(--color-border-subtle) 92%, transparent); + background: transparent; color: var(--color-text-primary); text-decoration: none; + transition: + opacity 0.14s ease, + background 0.14s ease; } - .mobile-pr-card:first-child { - border-top: none; + .mobile-pr-card:active { + background: color-mix(in srgb, var(--color-bg-elevated) 42%, transparent); } .mobile-pr-card__line { @@ -3030,42 +4689,61 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { align-items: center; gap: 8px; min-width: 0; + margin-bottom: 4px; } .mobile-pr-card__number { flex-shrink: 0; font-family: var(--font-mono); - font-size: 11px; - font-weight: 700; + font-size: 12px; + font-weight: 500; color: var(--color-accent); } .mobile-pr-card__title { flex: 1; min-width: 0; + font-size: 13px; + font-weight: 500; + line-height: 1.35; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - } - - .mobile-pr-card__size { - flex-shrink: 0; - padding: 1px 6px; - border: 1px solid var(--color-border-default); - background: var(--color-bg-subtle); - font-size: 10px; - font-weight: 700; - color: var(--color-text-secondary); } .mobile-pr-card__meta { display: flex; - flex-wrap: wrap; - gap: 8px; + align-items: center; + gap: 10px; + min-width: 0; + font-size: 11px; + color: var(--color-text-secondary); + } + + .mobile-pr-card__metric-value { + display: inline-flex; + align-items: center; + gap: 4px; + flex-shrink: 0; + font-size: 11px; + font-weight: 500; + color: var(--color-text-secondary); + } + + .mobile-pr-card__review { + min-width: 0; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .mobile-pr-card__diff { + font-family: var(--font-mono); font-size: 10px; - color: var(--color-text-muted); + margin-left: auto; + color: var(--color-text-secondary); + flex-shrink: 0; } .mx-auto.max-w-\[900px\] > h2 { @@ -3075,6 +4753,483 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { color: var(--color-text-secondary); letter-spacing: 0.12em; } + + /* ── Mobile Priority Feed (Dashboard) ───────────────────────────────── */ + + .mobile-feed { + display: flex; + flex-direction: column; + gap: 8px; + padding-bottom: 8px; + } + + .mobile-feed-card { + display: flex; + gap: 12px; + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 6px; + padding: 12px; + text-decoration: none; + color: var(--color-text-primary); + box-shadow: var(--card-shadow); + transition: + background 0.12s ease, + border-color 0.12s ease, + box-shadow 0.12s ease; + } + + .mobile-feed-card:active { + background: var(--card-expanded-bg); + border-color: var(--color-border-strong); + box-shadow: var(--card-shadow-hover); + } + + .mobile-feed-card__strip { + width: 3px; + border-radius: 2px; + flex-shrink: 0; + align-self: stretch; + } + + .mobile-feed-card__strip[data-level="respond"] { + background: var(--color-status-respond); + } + .mobile-feed-card__strip[data-level="merge"] { + background: var(--color-status-ready); + } + .mobile-feed-card__strip[data-level="review"] { + background: var(--color-accent-orange); + } + .mobile-feed-card__strip[data-level="pending"] { + background: var(--color-status-attention); + } + .mobile-feed-card__strip[data-level="working"] { + background: var(--color-status-working); + } + + .mobile-feed-card__content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 6px; + } + + .mobile-feed-card__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + + .mobile-feed-card__id { + display: flex; + align-items: center; + gap: 6px; + font-family: var(--font-mono); + font-size: 11px; + color: var(--color-text-muted); + } + + .mobile-feed-card__time { + font-family: var(--font-mono); + font-size: 10px; + color: var(--color-text-tertiary); + flex-shrink: 0; + } + + .mobile-feed-card__title { + font-size: 13px; + font-weight: 500; + line-height: 1.4; + color: var(--color-text-primary); + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + } + + .mobile-feed-card__meta { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + } + + .mobile-feed-card__branch { + font-family: var(--font-mono); + font-size: 10px; + color: var(--color-text-secondary); + background: color-mix(in srgb, var(--color-bg-subtle) 88%, var(--color-bg-surface)); + border: 1px solid color-mix(in srgb, var(--color-border-subtle) 88%, transparent); + padding: 2px 6px; + border-radius: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 160px; + } + + .mobile-feed-card__pr { + font-family: var(--font-mono); + font-size: 11px; + color: var(--color-accent); + text-decoration: none; + } + + .mobile-feed-card__diff { + font-family: var(--font-mono); + font-size: 10px; + } + + .mobile-feed-card__pills { + display: flex; + gap: 6px; + flex-wrap: wrap; + } + + .mobile-feed-card__pill { + display: inline-block; + font-size: 10px; + font-family: var(--font-mono); + padding: 2px 6px; + border: 1px solid transparent; + border-radius: 4px; + font-weight: 500; + } + + .mobile-feed-card__pill--green { + background: color-mix(in srgb, var(--color-accent-green) 12%, transparent); + border-color: color-mix(in srgb, var(--color-accent-green) 22%, transparent); + color: var(--color-accent-green); + } + .mobile-feed-card__pill--amber { + background: color-mix(in srgb, var(--color-status-attention) 12%, transparent); + border-color: color-mix(in srgb, var(--color-status-attention) 22%, transparent); + color: var(--color-status-attention); + } + .mobile-feed-card__pill--red { + background: color-mix(in srgb, var(--color-accent-red) 12%, transparent); + border-color: color-mix(in srgb, var(--color-accent-red) 22%, transparent); + color: var(--color-accent-red); + } + .mobile-feed-card__pill--accent { + background: color-mix(in srgb, var(--color-accent) 12%, transparent); + border-color: color-mix(in srgb, var(--color-accent) 22%, transparent); + color: var(--color-accent); + } + + .mobile-feed-empty { + text-align: center; + padding: 32px 16px; + color: var(--color-text-muted); + font-size: 13px; + } + + /* ── Mobile Session Detail: Terminal First ──────────────────────────── */ + + .session-detail--terminal-first { + position: relative; + height: 100vh; + height: 100dvh; + overflow: hidden; + background: var(--color-bg-base); + } + + .session-detail__floating-header { + position: absolute; + top: 0; + left: 0; + right: 0; + display: flex; + align-items: center; + gap: 10px; + padding: max(env(safe-area-inset-top, 8px), 8px) 16px 8px; + background: color-mix(in srgb, var(--color-bg-base) 84%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--color-border-subtle) 88%, transparent); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + z-index: 10; + } + + .session-detail__back { + color: var(--color-accent); + font-size: 18px; + text-decoration: none; + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + } + + .session-detail__status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + } + + .session-detail__session-id { + font-family: var(--font-mono); + font-size: 13px; + color: var(--color-text-primary); + } + + .session-detail__status-pill { + font-size: 10px; + font-family: var(--font-mono); + padding: 2px 8px; + border: 1px solid color-mix(in srgb, currentColor 20%, transparent); + border-radius: 10px; + } + + .session-detail__time { + margin-left: auto; + font-family: var(--font-mono); + font-size: 11px; + color: var(--color-text-tertiary); + flex-shrink: 0; + } + + .session-detail__terminal-full { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding-top: calc(max(env(safe-area-inset-top, 8px), 8px) + 40px); + padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px)); + } + + .session-detail__terminal-full--with-sheet { + padding-bottom: calc(120px + env(safe-area-inset-bottom, 0px)); + } + + .session-detail__terminal-full .direct-terminal, + .session-detail__terminal-full .direct-terminal__container, + .session-detail__terminal-full .terminal-exited-placeholder, + .session-detail__terminal-full .session-detail-terminal-placeholder { + height: 100% !important; + min-height: 0; + border-radius: 0; + border: none; + } + + .session-detail__bottom-sheet { + position: absolute; + bottom: calc(60px + env(safe-area-inset-bottom, 0px)); + left: 0; + right: 0; + background: color-mix(in srgb, var(--color-bg-surface) 96%, var(--color-bg-base)); + border-top: 1px solid color-mix(in srgb, var(--color-border-default) 88%, transparent); + border-radius: 12px 12px 0 0; + padding: 8px 16px 12px; + box-shadow: 0 -10px 30px rgba(0, 0, 0, 0.22); + z-index: 10; + } + + .session-detail__sheet-handle { + width: 36px; + height: 4px; + background: color-mix(in srgb, var(--color-text-tertiary) 72%, transparent); + border-radius: 2px; + margin: 0 auto 10px; + } + + .session-detail__sheet-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + } + + .session-detail__sheet-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--color-text-secondary); + } + + .session-detail__sheet-pr { + font-family: var(--font-mono); + font-size: 12px; + color: var(--color-accent); + text-decoration: none; + } + + .session-detail__sheet-ci-dot { + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; + } + + /* ── Mobile PR Dense Rows enhancements ─────────────────────────────── */ + + .mobile-pr-mobile-layout { + display: flex; + flex-direction: column; + gap: 10px; + padding-bottom: 8px; + } + + .mobile-pr-group { + display: flex; + flex-direction: column; + gap: 0; + } + + .mobile-pr-section-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + font-family: var(--font-sans); + font-size: 10px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--color-text-tertiary); + padding: 12px 0 6px; + } + + .mobile-pr-card--muted { + opacity: 0.72; + } + + .mobile-pr-card__ci-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + flex-shrink: 0; + vertical-align: middle; + margin-right: 4px; + } + + .mobile-pr-filter-tabs { + display: flex; + gap: 0; + overflow-x: auto; + padding: 0; + margin-bottom: 0; + scrollbar-width: none; + border-bottom: 1px solid color-mix(in srgb, var(--color-border-subtle) 92%, transparent); + } + + .mobile-pr-filter-tabs::-webkit-scrollbar { + display: none; + } + + .mobile-pr-filter-tab { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + flex-shrink: 0; + min-height: 38px; + min-width: 0; + padding: 10px 12px; + font-size: 12px; + font-weight: 500; + color: var(--color-text-tertiary); + background: transparent; + border: none; + cursor: pointer; + white-space: nowrap; + transition: color 0.12s ease; + position: relative; + } + + .mobile-pr-filter-tab[data-active="true"] { + color: var(--color-accent); + } + + .mobile-pr-filter-tab[data-active="true"]::after { + content: ""; + position: absolute; + bottom: 0; + left: 8px; + right: 8px; + height: 2px; + background: var(--color-accent); + border-radius: 1px; + } + + .mobile-pr-filter-tab__count { + font-family: var(--font-mono); + font-size: 10px; + opacity: 0.7; + } + + @media (max-width: 360px) { + .mobile-pr-filter-tabs { + gap: 6px; + padding-right: 10px; + scroll-padding-right: 10px; + } + + .mobile-pr-filter-tab { + padding: 6px 12px; + } + } + + .mobile-pr-empty { + padding: 22px 0; + color: var(--color-text-muted); + font-size: 12px; + text-align: center; + } + + .mobile-pr-page-header { + padding: 8px 0 0; + margin-bottom: 2px; + } + + .mobile-pr-page-header__top { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 4px; + } + + .mobile-pr-page-header__title-row { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + } + + .mobile-pr-page-header__title { + font-family: var(--font-mono); + font-size: 15px; + font-weight: 500; + letter-spacing: -0.02em; + color: var(--color-text-primary); + } + + .mobile-pr-page-header__meta { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; + } + + .mobile-pr-page-header__count { + font-family: var(--font-mono); + font-size: 12px; + color: var(--color-text-secondary); + } + + .mobile-pr-page-header__subtitle { + font-size: 11px; + color: var(--color-text-tertiary); + } } /* ── Phone breakpoint — 480px ────────────────────────────────────────── */ @@ -3097,7 +5252,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .kanban-column__title { - font-size: 16px; + font-size: 10px; } .session-page-header h1 { @@ -3105,7 +5260,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } /* Session detail terminal full-width */ - .session-detail-page .mx-auto { + .session-detail-layout { padding-left: 8px; padding-right: 8px; } @@ -3171,7 +5326,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); @@ -3216,7 +5371,7 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { .bottom-sheet-backdrop { position: fixed; inset: 0; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.42); z-index: calc(var(--z-overlay) + 20); } @@ -3226,51 +5381,130 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { left: 0; right: 0; background: var(--color-bg-surface); - border-top: 1px solid var(--color-border-default); + border: 1px solid var(--color-border-subtle); border-bottom: none; - padding: 16px 20px calc(12px + env(safe-area-inset-bottom, 0px)); + border-radius: 12px 12px 0 0; + box-shadow: 0 -14px 36px rgba(0, 0, 0, 0.28); + padding: 10px 16px calc(18px + env(safe-area-inset-bottom, 0px)); z-index: calc(var(--z-overlay) + 21); transform: translateY(0); animation: sheet-in 0.25s ease forwards; } .bottom-sheet__handle { - width: 36px; + width: 42px; height: 4px; - background: var(--color-border-default); - border-radius: 2px; - margin: 0 auto 16px; + background: color-mix(in srgb, var(--color-text-tertiary) 55%, transparent); + border-radius: 999px; + margin: 0 auto 12px; } .bottom-sheet__header { - margin-bottom: 16px; + margin-bottom: 14px; +} + +.bottom-sheet__preview-card { + display: flex; + gap: 12px; + background: var(--card-bg); + border: 1px solid var(--card-border); + border-radius: 6px; + padding: 12px; + box-shadow: var(--card-shadow); + margin-bottom: 12px; +} + +.bottom-sheet__preview-strip { + width: 3px; + border-radius: 2px; + flex-shrink: 0; + align-self: stretch; + background: var(--color-status-attention); +} + +.bottom-sheet__preview-strip[data-level="respond"] { + background: var(--color-status-respond); +} + +.bottom-sheet__preview-strip[data-level="merge"] { + background: var(--color-status-ready); +} + +.bottom-sheet__preview-strip[data-level="review"] { + background: var(--color-accent-orange); +} + +.bottom-sheet__preview-strip[data-level="pending"] { + background: var(--color-status-attention); +} + +.bottom-sheet__preview-strip[data-level="working"] { + background: var(--color-status-working); +} + +.bottom-sheet__preview-strip[data-level="done"] { + background: var(--color-text-tertiary); +} + +.bottom-sheet__preview-content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 6px; +} + +.bottom-sheet__preview-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.bottom-sheet__preview-id { + font-family: var(--font-mono); + font-size: 11px; + color: var(--color-text-muted); +} + +.bottom-sheet__preview-time { + font-family: var(--font-mono); + font-size: 10px; + color: var(--color-text-tertiary); + flex-shrink: 0; } .bottom-sheet__title { - font-size: 15px; - font-weight: 600; + font-family: var(--font-sans); + font-size: 17px; + line-height: 1.3; + font-weight: 500; + letter-spacing: -0.015em; color: var(--color-text-primary); - margin: 0 0 4px; + margin: 0; } .bottom-sheet__subtitle { font-size: 12px; + line-height: 1.45; color: var(--color-text-secondary); margin: 0; } .bottom-sheet__session-info { - background: var(--color-bg-subtle); - border: 1px solid var(--color-border-subtle); - padding: 10px 12px; - margin-bottom: 20px; + background: color-mix(in srgb, var(--color-bg-subtle) 92%, var(--color-bg-surface)); + border: 1px solid color-mix(in srgb, var(--color-border-subtle) 90%, transparent); + border-radius: 0; + padding: 14px; + margin-bottom: 12px; + box-shadow: none; } .bottom-sheet__session-name { - font-size: 13px; + font-size: 15px; font-weight: 500; color: var(--color-text-primary); - margin-bottom: 4px; + margin-bottom: 10px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -3282,39 +5516,94 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { flex-wrap: wrap; } +.bottom-sheet__preview-meta { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.bottom-sheet__preview-branch { + font-family: var(--font-mono); + font-size: 10px; + color: var(--color-text-secondary); + background: color-mix(in srgb, var(--color-bg-subtle) 88%, var(--color-bg-surface)); + border: 1px solid color-mix(in srgb, var(--color-border-subtle) 88%, transparent); + padding: 2px 6px; + border-radius: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 150px; +} + +.bottom-sheet__preview-pr { + font-family: var(--font-mono); + font-size: 11px; + color: var(--color-accent); +} + +.bottom-sheet__preview-diff { + font-family: var(--font-mono); + font-size: 10px; +} + +.bottom-sheet__preview-diff-add { + color: var(--color-accent-green); +} + +.bottom-sheet__preview-diff-del { + color: var(--color-accent-red); +} + +.bottom-sheet__preview-pills { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + .bottom-sheet__tag { display: inline-flex; align-items: center; - min-height: 24px; - padding: 0 8px; - border: 1px solid var(--color-border-default); + min-height: 22px; + padding: 2px 6px; + border: 1px solid transparent; + border-radius: 4px; background: var(--color-bg-surface); font-size: 10px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; + font-family: var(--font-mono); + font-weight: 500; + letter-spacing: 0; + text-transform: none; color: var(--color-text-secondary); } .bottom-sheet__tag--accent { - color: var(--color-accent); - border-color: color-mix(in srgb, var(--color-accent) 24%, var(--color-border-default)); + background: color-mix(in srgb, var(--color-status-attention) 12%, transparent); + border-color: color-mix(in srgb, var(--color-status-attention) 22%, transparent); + color: var(--color-status-attention); +} + +.bottom-sheet__tag--neutral { + background: color-mix(in srgb, var(--color-accent-green) 12%, transparent); + border-color: color-mix(in srgb, var(--color-accent-green) 22%, transparent); + color: var(--color-accent-green); } .bottom-sheet__tag--mono { font-family: var(--font-mono); text-transform: none; - letter-spacing: 0; + letter-spacing: 0.01em; } .bottom-sheet__summary { - margin: 10px 0 0; + margin: 0 0 12px; font-size: 12px; line-height: 1.5; color: var(--color-text-secondary); display: -webkit-box; -webkit-box-orient: vertical; - -webkit-line-clamp: 1; + -webkit-line-clamp: 2; overflow: hidden; } @@ -3330,11 +5619,18 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { align-items: center; justify-content: center; gap: 8px; - font-size: 14px; + padding: 0 14px; + border-radius: 6px; + font-size: 13px; font-weight: 500; cursor: pointer; border: 1px solid var(--color-border-default); - transition: opacity 0.15s ease; + transition: + background 120ms ease, + border-color 120ms ease, + color 120ms ease, + transform 120ms cubic-bezier(0.23, 1, 0.32, 1), + opacity 120ms ease; } .bottom-sheet__btn-icon { @@ -3344,44 +5640,47 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } .bottom-sheet__btn:active { - opacity: 0.75; + transform: scale(0.98); } .bottom-sheet__btn--cancel { - background: transparent; + background: var(--color-bg-elevated); color: var(--color-text-secondary); } .bottom-sheet__btn--cancel:hover { background: var(--color-bg-subtle); + color: var(--color-text-primary); } .bottom-sheet__btn--danger { - background: var(--color-status-error); - border-color: var(--color-status-error); + background: var(--color-accent-red, var(--color-status-error)); + border-color: var(--color-accent-red, var(--color-status-error)); color: #ffffff; } .bottom-sheet__btn--danger:hover { - opacity: 0.9; + background: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 92%, black 8%); + border-color: color-mix(in srgb, var(--color-accent-red, var(--color-status-error)) 92%, black 8%); } .bottom-sheet__btn--primary { display: inline-flex; align-items: center; justify-content: center; - background: var(--color-accent); - border-color: var(--color-accent); + background: var(--color-accent-blue, var(--color-accent)); + border-color: var(--color-accent-blue, var(--color-accent)); color: #ffffff; text-decoration: none; } .bottom-sheet__btn--primary:hover { - opacity: 0.92; + background: var(--color-accent-hover); + border-color: var(--color-accent-hover); } .bottom-sheet__btn--secondary { - background: transparent; + background: var(--color-bg-elevated); color: var(--color-text-primary); } @@ -3399,7 +5698,9 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { z-index: var(--z-connection-bar, 500); height: 0; overflow: hidden; - transition: height 0.2s ease, opacity 0.3s ease; + transition: + height 0.2s ease, + opacity 0.3s ease; display: flex; align-items: center; justify-content: center; @@ -3413,7 +5714,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; } @@ -3425,6 +5726,23 @@ html.light .xterm .xterm-viewport:hover::-webkit-scrollbar-thumb:active { } @keyframes connection-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.4; } + 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.test.ts b/packages/web/src/app/layout.test.ts new file mode 100644 index 000000000..5c904aa36 --- /dev/null +++ b/packages/web/src/app/layout.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@/lib/project-name", () => ({ + getProjectName: () => "Agent Orchestrator", +})); + +vi.mock("next/font/google", () => ({ + Geist: () => ({ variable: "--font-geist-sans" }), + JetBrains_Mono: () => ({ variable: "--font-jetbrains-mono" }), +})); + +describe("app layout metadata", () => { + it("exports the themed mobile viewport colors", async () => { + const { viewport } = await import("./layout"); + + expect(viewport.themeColor).toEqual([ + { media: "(prefers-color-scheme: light)", color: "#f5f3f0" }, + { media: "(prefers-color-scheme: dark)", color: "#121110" }, + ]); + }); + + it("builds metadata with the project-aware title and apple web app settings", async () => { + const { generateMetadata } = await import("./layout"); + + await expect(generateMetadata()).resolves.toMatchObject({ + title: { + template: "%s | Agent Orchestrator", + default: "ao | Agent Orchestrator", + }, + appleWebApp: { + capable: true, + statusBarStyle: "black-translucent", + title: "ao | Agent Orchestrator", + }, + }); + }); +}); diff --git a/packages/web/src/app/layout.tsx b/packages/web/src/app/layout.tsx index 0c12d5c1a..144110739 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.test.ts b/packages/web/src/app/manifest.test.ts new file mode 100644 index 000000000..bd6e7e62f --- /dev/null +++ b/packages/web/src/app/manifest.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@/lib/project-name", () => ({ + getProjectName: () => "Agent Orchestrator", +})); + +describe("app manifest", () => { + it("builds the PWA manifest with project-aware naming and icons", async () => { + const { default: manifest } = await import("./manifest"); + + expect(manifest()).toMatchObject({ + name: "ao | Agent Orchestrator", + short_name: "ao", + start_url: "/", + display: "standalone", + 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" }, + { src: "/icon-512", sizes: "512x512", type: "image/png", purpose: "any" }, + ], + }); + }); +}); 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/app/sessions/[id]/page.tsx b/packages/web/src/app/sessions/[id]/page.tsx index aceb30352..6a5faa35e 100644 --- a/packages/web/src/app/sessions/[id]/page.tsx +++ b/packages/web/src/app/sessions/[id]/page.tsx @@ -59,6 +59,8 @@ export default function SessionPage() { const [session, setSession] = useState(null); const [zoneCounts, setZoneCounts] = useState(null); const [projectOrchestratorId, setProjectOrchestratorId] = useState(undefined); + const [projects, setProjects] = useState([]); + const [sidebarSessions, setSidebarSessions] = useState([]); const [loading, setLoading] = useState(true); const [routeError, setRouteError] = useState(null); const [sessionMissing, setSessionMissing] = useState(false); @@ -85,6 +87,7 @@ export default function SessionPage() { .then((res) => res.ok ? res.json() : null) .then((data: { projects?: ProjectInfo[] } | null) => { if (data?.projects) { + setProjects(data.projects); setPrefixByProject( new Map(data.projects.map((p) => [p.id, p.sessionPrefix ?? p.id])), ); @@ -185,6 +188,17 @@ export default function SessionPage() { } }, []); + const fetchSidebarSessions = useCallback(async () => { + try { + const res = await fetch("/api/sessions"); + if (!res.ok) return; + const body = (await res.json()) as { sessions?: DashboardSession[] } | null; + setSidebarSessions(body?.sessions ?? []); + } catch { + // non-critical + } + }, []); + useEffect(() => { if (!sessionIsOrchestrator) { setZoneCounts(null); @@ -194,19 +208,21 @@ export default function SessionPage() { // Initial fetch — session first, zone counts after (avoids blocking on slow /api/sessions) useEffect(() => { fetchSession(); + fetchSidebarSessions(); // Delay zone counts so the heavy /api/sessions call doesn't contend with session load const t = setTimeout(fetchProjectSessions, 2000); return () => clearTimeout(t); - }, [fetchSession, fetchProjectSessions]); + }, [fetchSession, fetchProjectSessions, fetchSidebarSessions]); // Poll every 5s useEffect(() => { const interval = setInterval(() => { fetchSession(); fetchProjectSessions(); + fetchSidebarSessions(); }, 5000); return () => clearInterval(interval); - }, [fetchSession, fetchProjectSessions]); + }, [fetchSession, fetchProjectSessions, fetchSidebarSessions]); if (loading) { return ( @@ -235,6 +251,8 @@ export default function SessionPage() { isOrchestrator={sessionIsOrchestrator} orchestratorZones={zoneCounts ?? undefined} projectOrchestratorId={projectOrchestratorId} + projects={projects} + sidebarSessions={sidebarSessions} /> ); } 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 0ae427fd4..0ea5302df 100644 --- a/packages/web/src/components/AttentionZone.tsx +++ b/packages/web/src/components/AttentionZone.tsx @@ -32,39 +32,32 @@ 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: "Waiting on external 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 +111,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 +153,7 @@ function AttentionZoneView({
) : compactMobile ? (
-
No sessions
+
{config.emptyMessage}
) : null}
@@ -171,16 +165,15 @@ function AttentionZoneView({
-
+
{config.label} {sessions.length}
-

{config.caption}

{sessions.length > 0 ? ( -
+
{sessions.map((session) => ( ))}
- ) : ( -
- No sessions -
- )} + ) : null}
); @@ -247,7 +236,7 @@ function MobileSessionRow({