From b6ac037358625aa2c6ff3aca0f96bde2571d9e8f Mon Sep 17 00:00:00 2001 From: jtenniswood Date: Mon, 17 Aug 2026 12:01:37 +0100 Subject: [PATCH 01/12] feat(studio): adopt the enterprise design system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring Studio's visual language in line with the Stacklok console: the same token palette, the same shell grammar, and the same composer. Behaviour is untouched — every handler, effect, and protocol path is the one that was there before. Design tokens (app/globals.css) - Replace Studio's ad-hoc --ink/--line/--panel/--paper with the console's full token set, including a .dark block, and re-export them through @theme inline so they are reachable as Tailwind utilities. - Point the surviving component CSS at those tokens, which is what earns the whole app dark mode rather than only the migrated components. - Map every remaining literal colour onto a semantic token, using color-mix() tints so a tile re-derives on a theme flip instead of staying light. The only literals left are the modal scrim and its shadow, which are meant to be dark in both themes. - Drop 100 rules belonging to components this commit replaces. Shell - components/shell/task-sidebar.tsx: the console's left rail — fixed w-64, full-bleed rows, 3px brand-ink left accent, and the grid-slot trick that swaps timestamp for overflow affordance without reflowing the row. - components/shell/navbar.tsx: h-16 bg-sidebar bar whose seam lines up with the rail's wordmark block, plus the md-and-below Sheet drawer. - components/user-menu/*: the profile pill and dropdown, carrying workspace identity, the six configuration panels, and the theme switch. The panels moved here from the top bar: six always-visible config buttons is a settings tray, not a navigation surface. Composer - components/chat/composer.tsx: the console's three-box composer, where the toolbar is pulled up behind the input box so the two borders read as one outline. Keeps Studio's stop control (the console queues instead) and its CSV attach/drag path, and adds an IME guard so committing a candidate no longer sends the turn. Also fixes two pre-existing bugs: an undefined var(--bg) that rendered schedule buttons transparent, and the composer hint line that claimed Enter-to-send while offering no visible affordance for it. Primitives are copied from the prototype rather than re-derived, so the two products cannot drift. components.json is configured for the same style/baseColor, so `npx shadcn add ` lands matching output. Co-Authored-By: Claude Opus 5 --- studio/app/globals.css | 537 ++++++----- studio/app/layout.tsx | 11 +- studio/app/page.tsx | 158 ++-- studio/components.json | 20 + studio/components/chat/composer.tsx | 271 ++++++ studio/components/shell/navbar.tsx | 124 +++ studio/components/shell/task-sidebar.tsx | 160 ++++ studio/components/theme-provider.tsx | 24 + studio/components/ui/avatar.tsx | 53 ++ studio/components/ui/button.tsx | 68 ++ studio/components/ui/chevron-indicator.tsx | 16 + studio/components/ui/dropdown-menu.tsx | 257 ++++++ studio/components/ui/sheet.tsx | 140 +++ studio/components/user-menu/index.tsx | 151 ++++ .../components/user-menu/theme-menu-items.tsx | 41 + studio/components/user-menu/user-avatar.tsx | 30 + .../components/user-menu/user-menu-button.tsx | 46 + studio/lib/utils.ts | 14 + studio/package-lock.json | 852 +++++++++++++++++- studio/package.json | 12 +- 20 files changed, 2669 insertions(+), 316 deletions(-) create mode 100644 studio/components.json create mode 100644 studio/components/chat/composer.tsx create mode 100644 studio/components/shell/navbar.tsx create mode 100644 studio/components/shell/task-sidebar.tsx create mode 100644 studio/components/theme-provider.tsx create mode 100644 studio/components/ui/avatar.tsx create mode 100644 studio/components/ui/button.tsx create mode 100644 studio/components/ui/chevron-indicator.tsx create mode 100644 studio/components/ui/dropdown-menu.tsx create mode 100644 studio/components/ui/sheet.tsx create mode 100644 studio/components/user-menu/index.tsx create mode 100644 studio/components/user-menu/theme-menu-items.tsx create mode 100644 studio/components/user-menu/user-avatar.tsx create mode 100644 studio/components/user-menu/user-menu-button.tsx create mode 100644 studio/lib/utils.ts diff --git a/studio/app/globals.css b/studio/app/globals.css index 6824117d56..0d6fa811c2 100644 --- a/studio/app/globals.css +++ b/studio/app/globals.css @@ -1,343 +1,430 @@ @import "tailwindcss"; +@import "tw-animate-css"; +@custom-variant dark (&:is(.dark *)); + +/* --------------------------------------------------------------------------- + Design tokens — the enterprise design system's palette, shared with the + Stacklok console so Studio and the console read as one product. + + Tokens hold COMPLETE CSS colour values, never bare HSL triplets, so an + operator override can be hex, hsl, or oklch without touching the consumers. + + Studio's earlier ad-hoc names (--ink / --line / --panel / --paper) are gone: + the component CSS below now consumes these tokens directly, which is what + gives the whole app dark mode rather than only the migrated components. + --------------------------------------------------------------------------- */ :root { - --brand: #036a49; - --brand-hover: #02543a; - --brand-soft: #e7f2ed; - --brand-ink: #18442e; - --ink: hsl(240 10% 3.9%); - --muted: hsl(240 3.8% 46.1%); - --line: hsl(240 5.9% 90%); - --panel: hsl(240 4.8% 95.9%); + --background: hsl(0 0% 100%); + --foreground: hsl(240 10% 3.9%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(240 10% 3.9%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(240 10% 3.9%); + --primary: hsl(240 5.9% 10%); + --primary-foreground: hsl(0 0% 98%); + --secondary: hsl(240 4.8% 95.9%); + --secondary-foreground: hsl(240 5.9% 10%); + --muted: hsl(240 4.8% 95.9%); + --muted-foreground: hsl(240 3.8% 46.1%); + --accent: hsl(240 4.8% 95.9%); + --accent-foreground: hsl(240 5.9% 10%); + --destructive: hsl(0 72% 51%); + --destructive-foreground: hsl(0 0% 98%); + --destructive-strong: hsl(0 74% 40%); + --border: hsl(240 5.9% 90%); + --input: hsl(240 5.9% 90%); + --ring: hsl(240 5% 65%); + --radius: 0.5rem; --sidebar: hsl(240 4.8% 98.5%); - --paper: #fff; - --danger: #b42318; - --warning: #a15c07; - --radius: 8px; + --brand: hsl(161 94% 21%); /* #036a49 — Stacklok brand green */ + --brand-foreground: hsl(0 0% 100%); + --avatar-background: oklch(0.696 0 0 / 89.8%); + --nav-background: #18442e; + --nav-border: #265b41; + --nav-button-active-bg: #398560; + --nav-button-active-text: #ffffff; + + /* Status, display, and link tokens. */ + --success: hsl(142 71% 29%); + --warning: hsl(35 92% 42%); + --info: hsl(211 92% 43%); + --title: hsl(240 5% 18%); + --logo: hsl(0 0% 28%); + --brand-label: hsl(161 94% 21%); + --link: hsl(211 92% 43%); + --brand-ink: hsl(161 94% 21%); + --input-icon: hsl(240 3.8% 46.1%); + --badge-outline-border: hsl(240 5.9% 90%); + --badge-outline-text: hsl(240 10% 3.9%); + --btn-primary: hsl(161 94% 21%); + --btn-primary-hover: #02543a; + + /* Studio-specific: the brand tint behind agent avatars, active task rows, + and the approval affordances. Not in the console's palette because the + console has no conversation surface. */ + --brand-soft: #e7f2ed; +} + +.dark { + --background: hsl(240 6% 10%); + --foreground: hsl(0 0% 98%); + --card: hsl(240 6.1% 12.9%); /* cards are elevated in dark */ + --card-foreground: hsl(0 0% 98%); + --popover: hsl(240 6.3% 12.5%); + --popover-foreground: hsl(0 0% 98%); + --primary: hsl(210 34.5% 98%); + --primary-foreground: hsl(0 0% 9.1%); + --secondary: hsl(240 4% 15.9%); + --secondary-foreground: hsl(0 0% 98%); + --muted: hsl(240 6.3% 12.5%); + --muted-foreground: hsl(0 0% 63%); + --accent: hsl(240 4% 15.9%); + --accent-foreground: hsl(0 0% 98%); + --destructive: hsl(358.7 100% 69.6%); + --destructive-foreground: hsl(0 0% 98%); + --destructive-strong: hsl(358.7 78% 55%); + --border: hsl(0 0% 15.5%); + --input: hsl(0 0% 20.4%); + --ring: hsl(0 0% 45.2%); + --sidebar: hsl(240 6.3% 12.5%); + --brand-foreground: hsl(0 0% 100%); + --avatar-background: oklch(0.696 0 0 / 89.8%); + --nav-background: #0f2e1e; + --nav-border: #1a4430; + --nav-button-active-bg: #2d6b4a; + --nav-button-active-text: #f0f0f0; + + --success: hsl(142 61% 55%); + --warning: hsl(35 92% 62%); + --info: hsl(211 92% 66%); + --title: hsl(0 0% 74%); + --logo: hsl(0 0% 58%); + --brand-label: hsl(158 64% 52%); + --link: hsl(211 92% 66%); + --brand-ink: hsl(142 50% 60%); + --input-icon: hsl(0 0% 100%); + --badge-outline-border: hsl(0 0% 32%); + --badge-outline-text: hsl(0 0% 80%); + --btn-primary: #024c34; + --btn-primary-hover: #013826; + + --brand-soft: hsl(161 28% 15%); +} + +/* Maps CSS tokens → Tailwind utility classes: bg-background, text-foreground, + border-border, bg-nav-background, text-brand-ink, and so on. */ +@theme inline { + --font-sans: var(--font-inter); + --font-serif: var(--font-merriweather); + --font-mono: var(--font-geist-mono); + + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-destructive-strong: var(--destructive-strong); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-sidebar: var(--sidebar); + --color-avatar-background: var(--avatar-background); + --color-nav-background: var(--nav-background); + --color-nav-border: var(--nav-border); + --color-nav-button-active-bg: var(--nav-button-active-bg); + --color-nav-button-active-text: var(--nav-button-active-text); + --color-brand: var(--brand); + --color-brand-foreground: var(--brand-foreground); + --color-brand-soft: var(--brand-soft); + + --color-success: var(--success); + --color-warning: var(--warning); + --color-info: var(--info); + --color-title: var(--title); + --color-logo: var(--logo); + --color-brand-label: var(--brand-label); + --color-link: var(--link); + --color-brand-ink: var(--brand-ink); + --color-input-icon: var(--input-icon); + --color-badge-outline-border: var(--badge-outline-border); + --color-badge-outline-text: var(--badge-outline-text); + --color-btn-primary: var(--btn-primary); + --color-btn-primary-hover: var(--btn-primary-hover); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + font-family: var(--font-inter), sans-serif; + } +} + +@layer utilities { + .text-page-title { + font-family: var(--font-merriweather), serif; + font-size: 34px; + line-height: 42px; + font-weight: 300; + letter-spacing: -0.85px; + } + + /* The composer's toolbar scrolls horizontally on narrow viewports; the design + it comes from hides that scrollbar but never shipped the utility, so it + lives here rather than as a dangling class name. */ + .hide-scrollbar { + scrollbar-width: none; + -ms-overflow-style: none; + } + .hide-scrollbar::-webkit-scrollbar { + display: none; + } } +/* --------------------------------------------------------------------------- + Studio component CSS. Migrated regions (app shell, navigation, task list, + composer, profile menu) are Tailwind utilities in components/ and are NOT + styled here; what remains below dresses the conversation surface and the + configuration dialogs, and now reads its colour from the tokens above. + --------------------------------------------------------------------------- */ * { box-sizing: border-box; } -html, body { min-height: 100%; margin: 0; background: var(--paper); color: var(--ink); } +html, body { min-height: 100%; margin: 0; background: var(--background); color: var(--foreground); } body { font-family: var(--font-inter), Arial, sans-serif; } button, textarea, input { font: inherit; } button { color: inherit; } -button:focus-visible, textarea:focus-visible, input:focus-visible { outline: 2px solid rgba(3, 106, 73, .35); outline-offset: 2px; } +button:focus-visible, textarea:focus-visible, input:focus-visible { outline: 2px solid color-mix(in oklab, var(--brand) 8%, transparent); outline-offset: 2px; } -.studio-shell { display: flex; height: 100vh; overflow: hidden; background: var(--paper); } -.sidebar { width: 256px; flex: 0 0 256px; display: flex; flex-direction: column; padding: 0 12px 12px; background: var(--sidebar); border-right: 1px solid var(--line); } -.brand-row { height: 64px; flex: 0 0 64px; display: flex; align-items: center; gap: 10px; padding: 0 8px; border-bottom: 1px solid var(--line); margin: 0 -12px 12px; padding-inline: 20px; } -.brand-mark, .assistant-avatar, .empty-symbol { display: grid; place-items: center; color: #fff; background: var(--brand); font-family: var(--font-merriweather), Georgia, serif; font-weight: 700; } -.brand-mark { width: 30px; height: 30px; border-radius: 6px; font-size: 15px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.16); } -.brand-name { color: var(--brand-ink); font-size: 15px; font-weight: 700; letter-spacing: -.02em; } -.brand-name span { color: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; } -.icon-button { min-width: 34px; height: 34px; display: grid; place-items: center; border: 0; border-radius: 6px; background: transparent; cursor: pointer; font-weight: 650; } -.icon-button:hover { background: var(--panel); } -.sidebar-close { display: none; margin-left: auto; font-size: 22px; } -.new-task { width: 100%; height: 38px; display: flex; align-items: center; gap: 8px; margin: 0 0 20px; padding: 0 11px; border: 1px solid var(--brand); border-radius: 6px; background: var(--brand); color: #fff; cursor: pointer; font-size: 12px; font-weight: 650; box-shadow: 0 1px 2px rgba(3,106,73,.18); } -.new-task:hover { background: var(--brand-hover); border-color: var(--brand-hover); } -.new-task span { font-size: 18px; font-weight: 350; } -.new-task kbd { margin-left: auto; color: rgba(255,255,255,.7); background: transparent; font-size: 9px; } -.task-section-label { padding: 0 9px 8px; color: var(--muted); text-transform: uppercase; font-size: 10px; font-weight: 700; letter-spacing: .1em; } -.task-list { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; margin-inline: -12px; } -.task-row { width: 100%; min-height: 54px; display: flex; align-items: flex-start; gap: 9px; padding: 9px 15px 9px 17px; border: 0; border-left: 3px solid transparent; border-radius: 0; background: transparent; text-align: left; cursor: pointer; } -.task-row:hover { background: var(--panel); } -.task-row.active { border-left-color: var(--brand); background: #edf3f0; color: var(--brand-ink); } -.task-icon { margin-top: 2px; color: var(--muted); font-size: 15px; } -.task-row.active .task-icon { color: var(--brand); } -.task-copy { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; } -.task-copy strong { overflow: hidden; font-size: 12px; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; } -.task-copy small { color: var(--muted); font-size: 10px; } -.task-more { color: #a1a1aa; letter-spacing: 1px; font-size: 10px; } -.sidebar-footer { margin-inline: -12px; padding: 10px 12px 0; border-top: 1px solid var(--line); } -.repo-card { width: 100%; display: flex; align-items: center; gap: 9px; padding: 8px; border: 0; border-radius: 6px; background: transparent; text-align: left; cursor: pointer; } -.repo-card:hover { background: var(--panel); } -.repo-icon { width: 30px; height: 30px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 6px; background: #fff; color: var(--brand); font-size: 13px; } -.repo-card strong, .repo-card small { display: block; } -.repo-card strong { font-size: 11px; } -.repo-card small { margin-top: 3px; color: var(--muted); font-size: 9px; } -.chevron { margin-left: auto; color: #a1a1aa; font-size: 18px; } -.connection-row { display: flex; align-items: center; gap: 7px; padding: 7px 11px 1px; color: var(--muted); font-size: 10px; } -.status-dot { width: 7px; height: 7px; border-radius: 50%; background: #a1a1aa; } -.status-dot.online { background: var(--brand); box-shadow: 0 0 0 3px rgba(3,106,73,.1); } -.status-dot.offline { background: var(--danger); } +.studio-shell { display: flex; height: 100vh; overflow: hidden; background: var(--background); } +.assistant-avatar, .empty-symbol { display: grid; place-items: center; color: var(--brand-foreground); background: var(--brand); font-family: var(--font-merriweather), Georgia, serif; font-weight: 700; } -.workspace-panel { min-width: 0; flex: 1; display: flex; flex-direction: column; background: var(--paper); } -.topbar { height: 64px; flex: 0 0 64px; z-index: 3; display: flex; align-items: center; justify-content: space-between; padding: 0 20px 0 24px; border-bottom: 1px solid var(--line); background: rgba(255,255,255,.96); backdrop-filter: blur(10px); } -.topbar-left, .topbar-actions, .task-heading { display: flex; align-items: center; } -.task-heading { gap: 10px; } -.task-heading h1 { max-width: 460px; overflow: hidden; margin: 0; font-size: 14px; font-weight: 650; letter-spacing: -.01em; text-overflow: ellipsis; white-space: nowrap; } -.live-pill { height: 20px; padding: 0 7px; border-radius: 999px; background: var(--panel); color: var(--muted); font-size: 9px; font-weight: 700; letter-spacing: .06em; line-height: 20px; text-transform: uppercase; } -.live-pill.running { background: var(--brand-soft); color: var(--brand); } -.routing-live-pill { height: 22px; display: flex; align-items: center; gap: 5px; padding: 0 8px; border: 1px solid #d9ddf4; border-radius: 999px; background: #f5f6ff; color: #4854a6; cursor: pointer; font-size: 9px; font-weight: 700; white-space: nowrap; } -.routing-live-pill:hover { border-color: #aeb5df; background: #eef0ff; } -.routing-live-pill.disabled { border-color: var(--line); background: var(--panel); color: var(--muted); } -.topbar-actions { gap: 6px; } -.topbar-button { height: 34px; display: flex; align-items: center; gap: 6px; padding: 0 10px; border: 1px solid transparent; border-radius: 6px; background: transparent; cursor: pointer; font-size: 11px; font-weight: 600; } -.topbar-button:hover { border-color: var(--line); background: var(--sidebar); } -.menu-button { display: none; } +.workspace-panel { min-width: 0; flex: 1; display: flex; flex-direction: column; background: var(--background); } -.conversation { min-height: 0; flex: 1; overflow-y: auto; scrollbar-gutter: stable; background: linear-gradient(#fff, #fcfdfc); } -.routing-summary { width: min(800px, calc(100% - 42px)); display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 18px auto -13px; padding: 10px 12px; border: 1px solid #d9ddf4; border-radius: var(--radius); background: #fafaff; } +.conversation { min-height: 0; flex: 1; overflow-y: auto; scrollbar-gutter: stable; background: var(--background); } +.routing-summary { width: min(800px, calc(100% - 42px)); display: flex; align-items: center; justify-content: space-between; gap: 16px; margin: 18px auto -13px; padding: 10px 12px; border: 1px solid color-mix(in oklab, var(--info) 28%, transparent); border-radius: var(--radius); background: color-mix(in oklab, var(--info) 6%, transparent); } .routing-summary > div:first-child { display: flex; align-items: center; gap: 9px; } -.routing-summary-icon { width: 25px; height: 25px; display: grid; place-items: center; border-radius: 6px; background: #eef0ff; color: #4854a6; font-size: 14px; } +.routing-summary-icon { width: 25px; height: 25px; display: grid; place-items: center; border-radius: 6px; background: color-mix(in oklab, var(--info) 14%, transparent); color: var(--info); font-size: 14px; } .routing-summary p, .routing-summary strong, .routing-summary small { display: block; margin: 0; } .routing-summary strong { font-size: 10.5px; } -.routing-summary small { margin-top: 2px; color: var(--muted); font-size: 9px; } +.routing-summary small { margin-top: 2px; color: var(--muted-foreground); font-size: 9px; } .routing-summary-counts { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 5px; } -.routing-summary-counts span { display: flex; align-items: center; gap: 5px; padding: 4px 7px; border: 1px solid #e4e6f6; border-radius: 999px; background: #fff; color: #606784; font-size: 9px; } -.routing-summary-counts b { color: #4854a6; font-weight: 700; } +.routing-summary-counts span { display: flex; align-items: center; gap: 5px; padding: 4px 7px; border: 1px solid color-mix(in oklab, var(--info) 20%, transparent); border-radius: 999px; background: var(--card); color: var(--muted-foreground); font-size: 9px; } +.routing-summary-counts b { color: var(--info); font-weight: 700; } .message-stack { width: min(800px, calc(100% - 42px)); margin: 0 auto; padding: 34px 0 42px; } .message { display: flex; gap: 13px; margin-bottom: 32px; } .message.user { justify-content: flex-end; } -.message.user .message-body { max-width: 78%; padding: 11px 14px; border: 1px solid #cfe3da; border-radius: 8px 8px 2px 8px; background: #f0f7f4; } +.message.user .message-body { max-width: 78%; padding: 11px 14px; border: 1px solid color-mix(in oklab, var(--brand) 28%, transparent); border-radius: 8px 8px 2px 8px; background: color-mix(in oklab, var(--brand) 8%, transparent); } .message-attachments { display: grid; gap: 6px; margin-top: 10px; } -.message-attachment { min-width: 230px; display: flex; align-items: center; gap: 9px; padding: 8px 9px; border: 1px solid #c8dfd4; border-radius: 6px; background: rgba(255,255,255,.72); } -.message-attachment > b, .csv-badge { height: 24px; display: grid; place-items: center; padding: 0 6px; border-radius: 4px; background: var(--brand); color: #fff; font-size: 8px; letter-spacing: .06em; } +.message-attachment { min-width: 230px; display: flex; align-items: center; gap: 9px; padding: 8px 9px; border: 1px solid color-mix(in oklab, var(--brand) 32%, transparent); border-radius: 6px; background: color-mix(in oklab, var(--card) 72%, transparent); } +.message-attachment > b { height: 24px; display: grid; place-items: center; padding: 0 6px; border-radius: 4px; background: var(--brand); color: var(--brand-foreground); font-size: 8px; letter-spacing: .06em; } .message-attachment span, .message-attachment strong, .message-attachment small { min-width: 0; display: block; } .message-attachment strong { overflow: hidden; font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; } -.message-attachment small { margin-top: 2px; color: var(--muted); font-size: 8.5px; } -.assistant-avatar { width: 30px; height: 30px; flex: 0 0 30px; border-radius: 6px; font-size: 14px; box-shadow: 0 1px 2px rgba(3,106,73,.2); } +.message-attachment small { margin-top: 2px; color: var(--muted-foreground); font-size: 8.5px; } +.assistant-avatar { width: 30px; height: 30px; flex: 0 0 30px; border-radius: 6px; font-size: 14px; box-shadow: 0 1px 2px color-mix(in oklab, var(--brand) 5%, transparent); } .message-body { min-width: 0; max-width: calc(100% - 43px); } -.message-meta { margin: 1px 0 8px; color: var(--muted); font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; } +.message-meta { margin: 1px 0 8px; color: var(--muted-foreground); font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; } .user .message-meta { display: none; } .message-text { overflow-wrap: anywhere; font-size: 14px; line-height: 1.65; letter-spacing: -.003em; white-space: pre-wrap; } /* A turn that died in the provider. Must not be mistakable for a normal reply. */ -.message-failed { padding: 9px 11px; border: 1px solid #e0b4a8; border-radius: 6px; background: #fdf3f0; color: #8a3b2a; font-size: 13px; } +.message-failed { padding: 9px 11px; border: 1px solid color-mix(in oklab, var(--destructive) 35%, transparent); border-radius: 6px; background: color-mix(in oklab, var(--destructive) 8%, transparent); color: var(--destructive); font-size: 13px; } .event-notices { display: grid; gap: 5px; margin-top: 8px; } -.event-notices p { margin: 0; padding: 7px 9px; border-left: 3px solid #c69a45; background: #fff9ec; color: #72551e; font-size: 11px; } -.thinking { height: 30px; display: flex; align-items: center; gap: 4px; color: var(--muted); font-size: 11px; } +.event-notices p { margin: 0; padding: 7px 9px; border-left: 3px solid color-mix(in oklab, var(--warning) 60%, transparent); background: var(--card)9ec; color: var(--warning); font-size: 11px; } +.thinking { height: 30px; display: flex; align-items: center; gap: 4px; color: var(--muted-foreground); font-size: 11px; } .thinking i { width: 5px; height: 5px; border-radius: 50%; background: var(--brand); animation: pulse 1.3s infinite ease-in-out; } .thinking i:nth-child(2) { animation-delay: .15s; } .thinking i:nth-child(3) { animation-delay: .3s; } .thinking span { margin-left: 5px; } @keyframes pulse { 0%,70%,100% { opacity:.25; transform:translateY(0) } 35% { opacity:1; transform:translateY(-2px) } } -.activity-list { margin-top: 15px; overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.025); } -.tool-card { width: 100%; display: flex; align-items: flex-start; gap: 10px; padding: 10px 11px; border: 0; border-bottom: 1px solid var(--line); background: transparent; text-align: left; cursor: default; } +.activity-list { margin-top: 15px; overflow: hidden; border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); box-shadow: 0 1px 2px color-mix(in oklab, var(--foreground) 3%, transparent); } +.tool-card { width: 100%; display: flex; align-items: flex-start; gap: 10px; padding: 10px 11px; border: 0; border-bottom: 1px solid var(--border); background: transparent; text-align: left; cursor: default; } .tool-card:last-child { border-bottom: 0; } .tool-card:hover { background: var(--sidebar); cursor: pointer; } .tool-card.expanded { background: var(--sidebar); } .tool-status { width: 20px; height: 20px; flex: 0 0 20px; display: grid; place-items: center; border-radius: 5px; background: var(--brand-soft); color: var(--brand); font-size: 11px; font-weight: 800; } -.tool-card.running .tool-status { background: #fff3d6; color: var(--warning); } -.tool-card.error .tool-status { background: #fee4e2; color: var(--danger); } +.tool-card.running .tool-status { background: var(--card)3d6; color: var(--warning); } +.tool-card.error .tool-status { background: color-mix(in oklab, var(--destructive) 15%, transparent); color: var(--destructive); } .tool-status i { width: 8px; height: 8px; border: 1.5px solid var(--warning); border-top-color: transparent; border-radius: 50%; animation: spin .8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .tool-main { min-width: 0; flex: 1; display: block; } .tool-main strong, .tool-main small { display: block; } .tool-title-row { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; } -.route-badge { max-width: 330px; overflow: hidden; padding: 3px 6px; border: 1px solid #d9ddf4; border-radius: 999px; background: #f5f6ff; color: #4854a6; font: 8.5px/1.2 var(--font-geist-mono), monospace; text-overflow: ellipsis; white-space: nowrap; } -.route-badge.unrouted { border-color: #e5e7eb; background: #f7f7f8; color: #71717a; } +.route-badge { max-width: 330px; overflow: hidden; padding: 3px 6px; border: 1px solid color-mix(in oklab, var(--info) 28%, transparent); border-radius: 999px; background: color-mix(in oklab, var(--info) 10%, transparent); color: var(--info); font: 8.5px/1.2 var(--font-geist-mono), monospace; text-overflow: ellipsis; white-space: nowrap; } +.route-badge.unrouted { border-color: var(--border); background: var(--secondary); color: var(--muted-foreground); } .tool-main strong { font-size: 11.5px; font-weight: 650; } -.tool-main small { overflow: hidden; margin-top: 3px; color: var(--muted); font: 10px/1.4 var(--font-geist-mono), monospace; text-overflow: ellipsis; white-space: nowrap; } +.tool-main small { overflow: hidden; margin-top: 3px; color: var(--muted-foreground); font: 10px/1.4 var(--font-geist-mono), monospace; text-overflow: ellipsis; white-space: nowrap; } .tool-details { display: grid; gap: 10px; width: 100%; margin-top: 11px; } .tool-details > span { display: block; } .route-details { display: grid; gap: 6px; } -.route-details > span { display: grid; grid-template-columns: minmax(90px, .6fr) minmax(160px, 1fr); gap: 3px 10px; padding: 9px 10px; border: 1px solid #d9ddf4; border-radius: 5px; background: #fff; } -.route-details > span.unrouted { border-color: var(--line); } +.route-details > span { display: grid; grid-template-columns: minmax(90px, .6fr) minmax(160px, 1fr); gap: 3px 10px; padding: 9px 10px; border: 1px solid color-mix(in oklab, var(--info) 28%, transparent); border-radius: 5px; background: var(--card); } +.route-details > span.unrouted { border-color: var(--border); } .route-details strong { overflow: hidden; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; } -.route-details code { overflow: hidden; color: #4854a6; font: 9px/1.4 var(--font-geist-mono), monospace; text-overflow: ellipsis; white-space: nowrap; } -.route-details small { grid-column: 1 / -1; margin: 2px 0 0; color: var(--muted); font: 9px/1.45 var(--font-inter), sans-serif; white-space: normal; } -.tool-details b { display: block; margin-bottom: 5px; color: var(--muted); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; } -.tool-main pre { width: 100%; max-height: none; overflow-x: auto; margin: 0; padding: 11px; border: 1px solid var(--line); border-radius: 5px; background: #fff; color: var(--ink); font: 10px/1.55 var(--font-geist-mono), monospace; white-space: pre-wrap; word-break: break-word; } -.tool-toggle { color: #a1a1aa; font-size: 12px; transform-origin: center; transition: transform .16s ease, color .16s ease; } +.route-details code { overflow: hidden; color: var(--info); font: 9px/1.4 var(--font-geist-mono), monospace; text-overflow: ellipsis; white-space: nowrap; } +.route-details small { grid-column: 1 / -1; margin: 2px 0 0; color: var(--muted-foreground); font: 9px/1.45 var(--font-inter), sans-serif; white-space: normal; } +.tool-details b { display: block; margin-bottom: 5px; color: var(--muted-foreground); font-size: 9px; letter-spacing: .08em; text-transform: uppercase; } +.tool-main pre { width: 100%; max-height: none; overflow-x: auto; margin: 0; padding: 11px; border: 1px solid var(--border); border-radius: 5px; background: var(--card); color: var(--foreground); font: 10px/1.55 var(--font-geist-mono), monospace; white-space: pre-wrap; word-break: break-word; } +.tool-toggle { color: var(--muted-foreground); font-size: 12px; transform-origin: center; transition: transform .16s ease, color .16s ease; } .tool-card.expanded .tool-toggle { color: var(--brand); transform: rotate(180deg); } -.approval-card { margin-top: 15px; padding: 13px; border: 1px solid #f0c36a; border-radius: var(--radius); background: #fffbeb; } +.approval-card { margin-top: 15px; padding: 13px; border: 1px solid color-mix(in oklab, var(--warning) 45%, transparent); border-radius: var(--radius); background: var(--card)beb; } .approval-title { display: flex; align-items: flex-start; gap: 10px; } -.approval-title > span { width: 22px; height: 22px; display: grid; place-items: center; border-radius: 50%; background: #ffedbd; color: var(--warning); font-weight: 800; } +.approval-title > span { width: 22px; height: 22px; display: grid; place-items: center; border-radius: 50%; background: color-mix(in oklab, var(--warning) 22%, transparent); color: var(--warning); font-weight: 800; } .approval-title strong { font-size: 12px; } -.approval-title p, .approval-reason { margin: 3px 0 0; color: #725d34; font-size: 10.5px; } -.approval-card code { display: block; overflow: auto; margin: 11px 0 7px; padding: 8px; border: 1px solid #ead9a8; border-radius: 5px; background: #fff; font: 10px/1.45 var(--font-geist-mono), monospace; } +.approval-title p, .approval-reason { margin: 3px 0 0; color: var(--warning); font-size: 10.5px; } +.approval-card code { display: block; overflow: auto; margin: 11px 0 7px; padding: 8px; border: 1px solid color-mix(in oklab, var(--warning) 35%, transparent); border-radius: 5px; background: var(--card); font: 10px/1.45 var(--font-geist-mono), monospace; } .approval-actions { display: flex; justify-content: flex-end; gap: 6px; margin-top: 12px; } -.approval-actions button { height: 30px; padding: 0 10px; border: 1px solid var(--line); border-radius: 5px; background: #fff; cursor: pointer; font-size: 10.5px; } -.approval-actions button.primary { border-color: var(--brand); background: var(--brand); color: #fff; } +.approval-actions button { height: 30px; padding: 0 10px; border: 1px solid var(--border); border-radius: 5px; background: var(--card); cursor: pointer; font-size: 10.5px; } +.approval-actions button.primary { border-color: var(--brand); background: var(--brand); color: var(--brand-foreground); } .empty-state { width: min(720px, calc(100% - 42px)); min-height: calc(100vh - 250px); display: flex; flex-direction: column; align-items: center; justify-content: center; margin: 0 auto; padding: 50px 0; text-align: center; } -.empty-symbol { width: 48px; height: 48px; margin-bottom: 20px; border-radius: 8px; font-size: 24px; box-shadow: 0 5px 18px rgba(3,106,73,.16); } +.empty-symbol { width: 48px; height: 48px; margin-bottom: 20px; border-radius: 8px; font-size: 24px; box-shadow: 0 5px 18px color-mix(in oklab, var(--brand) 4%, transparent); } .empty-state h2 { margin: 0 0 10px; font: 300 30px/1.25 var(--font-merriweather), Georgia, serif; letter-spacing: -.025em; } -.empty-state > p { max-width: 520px; margin: 0; color: var(--muted); font-size: 12.5px; line-height: 1.55; } +.empty-state > p { max-width: 520px; margin: 0; color: var(--muted-foreground); font-size: 12.5px; line-height: 1.55; } .suggestions { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 28px; } -.suggestions button { display: flex; justify-content: space-between; padding: 12px 13px; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; text-align: left; cursor: pointer; font-size: 11.5px; box-shadow: 0 1px 2px rgba(0,0,0,.02); } -.suggestions button:hover { border-color: #a8c9ba; background: #f7fbf9; } +.suggestions button { display: flex; justify-content: space-between; padding: 12px 13px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); text-align: left; cursor: pointer; font-size: 11.5px; box-shadow: 0 1px 2px color-mix(in oklab, var(--foreground) 3%, transparent); } +.suggestions button:hover { border-color: color-mix(in oklab, var(--brand) 40%, transparent); background: color-mix(in oklab, var(--brand) 6%, transparent); } .suggestions span { color: var(--brand); } -.composer-wrap { z-index: 2; flex: 0 0 auto; padding: 0 26px 14px; background: linear-gradient(0deg, #fff 75%, rgba(255,255,255,0)); } -.composer { width: min(800px, 100%); margin: 0 auto; padding: 12px 12px 9px; border: 1px solid #cfd4d1; border-radius: var(--radius); background: #fff; box-shadow: 0 6px 24px rgba(24,68,46,.08), 0 1px 2px rgba(0,0,0,.04); } -.composer:focus-within { border-color: #72a68f; box-shadow: 0 0 0 3px rgba(3,106,73,.08), 0 6px 24px rgba(24,68,46,.08); } -.composer.dragging-file { position: relative; border-color: var(--brand); background: #f7fbf9; box-shadow: 0 0 0 3px rgba(3,106,73,.1), 0 6px 24px rgba(24,68,46,.08); } -.csv-file-input { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; } -.csv-attachment { display: flex; align-items: center; gap: 9px; margin: -1px 0 9px; padding: 8px 9px; border: 1px solid #cfe3da; border-radius: 6px; background: #f4faf7; } -.csv-badge { flex: 0 0 auto; } -.csv-copy { min-width: 0; flex: 1; } -.csv-copy strong, .csv-copy small { display: block; } -.csv-copy strong { overflow: hidden; font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; } -.csv-copy small { margin-top: 2px; color: var(--muted); font-size: 8.5px; } -.csv-attachment button { width: 24px; height: 24px; flex: 0 0 24px; border: 0; border-radius: 4px; background: transparent; color: var(--muted); cursor: pointer; font-size: 17px; } -.csv-attachment button:hover { background: #e4f1eb; color: var(--brand); } -.csv-drop-hint { position: absolute; inset: 0; z-index: 3; display: grid; place-items: center; border-radius: inherit; background: rgba(247,251,249,.94); color: var(--brand); font-size: 12px; font-weight: 700; pointer-events: none; } -.composer textarea { width: 100%; min-height: 32px; max-height: 150px; resize: none; padding: 2px 4px 7px; border: 0; outline: 0; background: transparent; color: var(--ink); font-size: 13.5px; line-height: 1.5; } -.composer textarea::placeholder { color: #a1a1aa; } -.composer-controls { display: flex; align-items: center; justify-content: space-between; } -.composer-left { display: flex; align-items: center; gap: 4px; } -.composer-icon { width: 29px; height: 29px; border: 0; border-radius: 5px; background: transparent; cursor: pointer; font-size: 20px; font-weight: 300; } -.composer-icon:hover, .mode-button:hover { background: var(--panel); } -.mode-button { height: 29px; display: flex; align-items: center; gap: 5px; padding: 0 7px; border: 0; border-radius: 5px; background: transparent; color: #52525b; cursor: pointer; font-size: 10.5px; } -.mode-button.selected { background: var(--brand-soft); color: var(--brand); } -.mode-button b { color: #a1a1aa; font-size: 9px; font-weight: 500; } -.model-control { position: relative; } -.model-popover { position: absolute; bottom: 38px; left: 0; z-index: 8; width: 245px; padding: 12px; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; box-shadow: 0 12px 35px rgba(24,24,27,.14); } -.model-popover small { color: var(--muted); font-size: 9px; letter-spacing: .08em; } -.model-popover strong { display: block; margin: 5px 0; font-size: 12px; } -.model-popover p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.45; } -.model-popover code { font-family: var(--font-geist-mono), monospace; } -.send-button { width: 31px; height: 31px; display: grid; place-items: center; border: 0; border-radius: 6px; background: var(--brand); color: #fff; cursor: pointer; font-size: 16px; } -.send-button:hover { background: var(--brand-hover); } -.send-button:disabled { background: #d4d4d8; color: #fafafa; cursor: default; } -.send-button.stop { background: var(--danger); font-size: 10px; } -.composer-hint { margin: 7px auto 0; color: #a1a1aa; text-align: center; font-size: 9.5px; } -.error-banner { width: min(800px, 100%); display: flex; align-items: center; gap: 8px; margin: 0 auto 8px; padding: 8px 10px; border: 1px solid #f6b5ad; border-radius: 6px; background: #fff1f0; color: var(--danger); font-size: 10.5px; } -.error-banner > span { width: 17px; height: 17px; display: grid; place-items: center; border-radius: 50%; background: #fee4e2; font-weight: 800; } +.composer-wrap { z-index: 2; flex: 0 0 auto; padding: 0 26px 14px; background: linear-gradient(0deg, var(--background) 75%, transparent); } +.error-banner { width: min(800px, 100%); display: flex; align-items: center; gap: 8px; margin: 0 auto 8px; padding: 8px 10px; border: 1px solid color-mix(in oklab, var(--destructive) 35%, transparent); border-radius: 6px; background: var(--card)1f0; color: var(--destructive); font-size: 10.5px; } +.error-banner > span { width: 17px; height: 17px; display: grid; place-items: center; border-radius: 50%; background: color-mix(in oklab, var(--destructive) 15%, transparent); font-weight: 800; } .error-banner p { flex: 1; margin: 0; } -.error-banner button { border: 0; background: transparent; color: var(--danger); cursor: pointer; font-size: 17px; } -.error-banner .retry-button { height: 26px; padding: 0 9px; border: 1px solid #f6b5ad; border-radius: 5px; background: #fff; font-size: 10px; font-weight: 700; } -.error-banner .retry-button:hover { background: #fff8f7; } +.error-banner button { border: 0; background: transparent; color: var(--destructive); cursor: pointer; font-size: 17px; } +.error-banner .retry-button { height: 26px; padding: 0 9px; border: 1px solid color-mix(in oklab, var(--destructive) 35%, transparent); border-radius: 5px; background: var(--card); font-size: 10px; font-weight: 700; } +.error-banner .retry-button:hover { background: var(--card)8f7; } .error-banner .retry-button:disabled { opacity: .5; cursor: default; } -.sidebar-scrim { display: none; } .modal-backdrop { position: fixed; inset: 0; z-index: 50; display: grid; place-items: center; padding: 20px; background: rgba(9,30,20,.5); backdrop-filter: blur(3px); } -.credential-modal { width: min(440px, 100%); padding: 22px; border: 1px solid var(--line); border-radius: var(--radius); background: #fff; box-shadow: 0 24px 70px rgba(9,30,20,.24); animation: modal-in .18s ease-out; } +.credential-modal { width: min(440px, 100%); padding: 22px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--card); box-shadow: 0 24px 70px rgba(9,30,20,.24); animation: modal-in .18s ease-out; } @keyframes modal-in { from { opacity:0; transform:translateY(8px) scale(.985) } to { opacity:1; transform:none } } .modal-topline { display: flex; align-items: center; justify-content: space-between; } -.modal-topline button { width: 31px; height: 31px; border: 0; border-radius: 6px; background: var(--panel); color: var(--muted); cursor: pointer; font-size: 20px; } +.modal-topline button { width: 31px; height: 31px; border: 0; border-radius: 6px; background: var(--secondary); color: var(--muted-foreground); cursor: pointer; font-size: 20px; } .openrouter-mark, .mcp-mark, .router-mark, .skills-mark, .memory-mark { width: 40px; height: 40px; display: grid; place-items: center; border-radius: 6px; font-weight: 800; } -.memory-mark { background: #e9f3ec; color: #35704b; font-size: 20px; } -.schedule-mark { background: #e7eaf4; color: #3f4a70; font-size: 20px; } +.memory-mark { background: color-mix(in oklab, var(--success) 12%, transparent); color: var(--success); font-size: 20px; } +.schedule-mark { background: color-mix(in oklab, var(--info) 22%, transparent); color: var(--info); font-size: 20px; } /* .transport-note is a flex row, so an inline would otherwise become its own flex item and collapse into a narrow column. */ .transport-note p { margin: 0; } .transport-note code { white-space: nowrap; } .schedule-list { display: grid; gap: 8px; margin: 12px 0 4px; padding: 0; max-height: 340px; overflow-y: auto; list-style: none; } -.schedule-list li { display: grid; gap: 5px; padding: 10px 11px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); } +.schedule-list li { display: grid; gap: 5px; padding: 10px 11px; border: 1px solid var(--border); border-radius: 6px; background: var(--secondary); } .schedule-list li.paused { opacity: .72; } .schedule-row-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } .schedule-row-head strong { font-size: 11px; font-family: var(--mono, ui-monospace, monospace); } .schedule-state { padding: 2px 7px; border-radius: 999px; font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; } -.schedule-state.armed { background: #e9f3ec; color: #35704b; } -.schedule-state.paused { background: #f1f1f0; color: #6b6b68; } -.schedule-state.running { background: #fdf1dc; color: #96631a; } -.schedule-trigger { color: var(--muted); font-size: 9.5px; font-family: var(--mono, ui-monospace, monospace); } -.schedule-prompt { color: var(--muted); font-size: 10px; line-height: 1.45; } +.schedule-state.armed { background: color-mix(in oklab, var(--success) 12%, transparent); color: var(--success); } +.schedule-state.paused { background: var(--secondary); color: var(--muted-foreground); } +.schedule-state.running { background: color-mix(in oklab, var(--warning) 14%, transparent); color: var(--warning); } +.schedule-trigger { color: var(--muted-foreground); font-size: 9.5px; font-family: var(--mono, ui-monospace, monospace); } +.schedule-prompt { color: var(--muted-foreground); font-size: 10px; line-height: 1.45; } .schedule-badges { display: flex; gap: 6px; margin-top: 1px; } -.schedule-badges span { padding: 2px 6px; border: 1px solid var(--line); border-radius: 4px; color: var(--muted); font-size: 9px; } -.schedule-badges .badge-write { border-color: #e8c9a8; background: #fdf1dc; color: #96631a; font-weight: 700; } -.schedule-badges .badge-read { border-color: #cfe2d5; background: #f1f8f3; color: #35704b; } +.schedule-badges span { padding: 2px 6px; border: 1px solid var(--border); border-radius: 4px; color: var(--muted-foreground); font-size: 9px; } +.schedule-badges .badge-write { border-color: color-mix(in oklab, var(--warning) 35%, transparent); background: color-mix(in oklab, var(--warning) 14%, transparent); color: var(--warning); font-weight: 700; } +.schedule-badges .badge-read { border-color: color-mix(in oklab, var(--success) 28%, transparent); background: color-mix(in oklab, var(--success) 8%, transparent); color: var(--success); } .schedule-actions, .schedule-confirm > div { display: flex; gap: 6px; margin-top: 4px; } -.schedule-actions button, .schedule-confirm button { padding: 4px 9px; border: 1px solid var(--line); border-radius: 5px; background: var(--bg); color: inherit; font-size: 10px; cursor: pointer; } -.schedule-actions button:hover:not(:disabled), .schedule-confirm button:hover:not(:disabled) { background: var(--panel-hover, #f4f4f2); } +.schedule-actions button, .schedule-confirm button { padding: 4px 9px; border: 1px solid var(--border); border-radius: 5px; background: var(--card); color: inherit; font-size: 10px; cursor: pointer; } +.schedule-actions button:hover:not(:disabled), .schedule-confirm button:hover:not(:disabled) { background: var(--panel-hover, var(--secondary)); } .schedule-actions button:disabled, .schedule-confirm button:disabled { opacity: .5; cursor: default; } -.schedule-confirm { display: grid; gap: 5px; margin-top: 4px; padding: 8px 9px; border: 1px dashed #e0b4a8; border-radius: 5px; background: #fdf3f0; } -.schedule-confirm > span { color: #8a3b2a; font-size: 10px; line-height: 1.45; } -.schedule-confirm .schedule-danger { border-color: #d99a8a; background: #f7ddd6; color: #8a3b2a; font-weight: 700; } +.schedule-confirm { display: grid; gap: 5px; margin-top: 4px; padding: 8px 9px; border: 1px dashed color-mix(in oklab, var(--destructive) 35%, transparent); border-radius: 5px; background: color-mix(in oklab, var(--destructive) 8%, transparent); } +.schedule-confirm > span { color: var(--destructive); font-size: 10px; line-height: 1.45; } +.schedule-confirm .schedule-danger { border-color: color-mix(in oklab, var(--destructive) 38%, transparent); background: color-mix(in oklab, var(--destructive) 12%, transparent); color: var(--destructive); font-weight: 700; } .memory-heading { display: flex; align-items: baseline; gap: 7px; margin: 20px 0 0; font: 600 11px/1.3 inherit; letter-spacing: .02em; text-transform: uppercase; } -.memory-heading small { color: var(--muted); font-size: 9.5px; font-weight: 400; letter-spacing: 0; text-transform: none; } +.memory-heading small { color: var(--muted-foreground); font-size: 9.5px; font-weight: 400; letter-spacing: 0; text-transform: none; } .memory-heading + .skills-list, .memory-heading + .skills-empty { margin-top: 8px; } -.openrouter-mark { background: var(--brand-ink); color: #fff; font-size: 10px; letter-spacing: -.04em; } +.openrouter-mark { background: var(--brand-ink); color: var(--brand-foreground); font-size: 10px; letter-spacing: -.04em; } .mcp-mark { background: var(--brand-soft); color: var(--brand); font-size: 20px; } -.router-mark { background: #eef1ff; color: #4854a6; font-size: 20px; } -.skills-mark { background: #fdf1dc; color: #96631a; font-size: 20px; } +.router-mark { background: color-mix(in oklab, var(--info) 12%, transparent); color: var(--info); font-size: 20px; } +.skills-mark { background: color-mix(in oklab, var(--warning) 14%, transparent); color: var(--warning); font-size: 20px; } .credential-modal h2 { margin: 20px 0 8px; font: 300 28px/1.25 var(--font-merriweather), Georgia, serif; letter-spacing: -.025em; } -.credential-modal > p { margin: 0 0 22px; color: var(--muted); font-size: 12px; line-height: 1.55; } -.credential-modal label { display: block; margin-bottom: 7px; color: #52525b; font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; } -.credential-modal input { width: 100%; height: 42px; padding: 0 11px; border: 1px solid #d4d4d8; border-radius: 6px; background: #fff; color: var(--ink); font: 12px var(--font-geist-mono), monospace; box-shadow: inset 0 1px 2px rgba(0,0,0,.02); } -.credential-modal input:focus { border-color: #72a68f; outline: 2px solid rgba(3,106,73,.12); } -.key-safety { display: flex; align-items: flex-start; gap: 8px; margin: 12px 1px 17px; color: var(--muted); } +.credential-modal > p { margin: 0 0 22px; color: var(--muted-foreground); font-size: 12px; line-height: 1.55; } +.credential-modal label { display: block; margin-bottom: 7px; color: var(--muted-foreground); font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; } +.credential-modal input { width: 100%; height: 42px; padding: 0 11px; border: 1px solid var(--muted); border-radius: 6px; background: var(--card); color: var(--foreground); font: 12px var(--font-geist-mono), monospace; box-shadow: inset 0 1px 2px color-mix(in oklab, var(--foreground) 3%, transparent); } +.credential-modal input:focus { border-color: color-mix(in oklab, var(--brand) 55%, transparent); outline: 2px solid color-mix(in oklab, var(--brand) 3%, transparent); } +.key-safety { display: flex; align-items: flex-start; gap: 8px; margin: 12px 1px 17px; color: var(--muted-foreground); } .key-safety > span { width: 17px; height: 17px; flex: 0 0 17px; display: grid; place-items: center; border-radius: 50%; background: var(--brand-soft); color: var(--brand); font-size: 10px; font-weight: 800; } .key-safety p { margin: 0; font-size: 10px; line-height: 1.5; } -.connect-button { width: 100%; height: 40px; border: 1px solid var(--brand); border-radius: 6px; background: var(--brand); color: #fff; cursor: pointer; font-size: 11.5px; font-weight: 650; transition: .15s ease; } -.connect-button:hover { border-color: var(--brand-hover); background: var(--brand-hover); } -.connect-button:disabled { border-color: #d4d4d8; background: #d4d4d8; color: #fafafa; cursor: default; } +.connect-button { width: 100%; height: 40px; border: 1px solid var(--brand); border-radius: 6px; background: var(--brand); color: var(--brand-foreground); cursor: pointer; font-size: 11.5px; font-weight: 650; transition: .15s ease; } +.connect-button:hover { border-color: var(--btn-primary-hover); background: var(--btn-primary-hover); } +.connect-button:disabled { border-color: var(--muted); background: var(--muted); color: var(--muted-foreground); cursor: default; } .connect-button.success { background: var(--brand); } -.credential-error { margin: -7px 0 10px; padding: 8px 9px; border-radius: 5px; background: #fff1f0; color: var(--danger); font-size: 10px; } +.credential-error { margin: -7px 0 10px; padding: 8px 9px; border-radius: 5px; background: var(--card)1f0; color: var(--destructive); font-size: 10px; } .mcp-modal .connect-button { margin-top: 14px; } -.mcp-modal .secondary-connect { margin-top: 10px; border-color: var(--line); background: #fff; color: var(--brand); } -.mcp-modal .secondary-connect:hover { border-color: #a8c9ba; background: var(--brand-soft); } -.mcp-modal .secondary-connect:disabled { border-color: var(--line); background: var(--panel); color: #a1a1aa; } +.mcp-modal .secondary-connect { margin-top: 10px; border-color: var(--border); background: var(--card); color: var(--brand); } +.mcp-modal .secondary-connect:hover { border-color: color-mix(in oklab, var(--brand) 40%, transparent); background: var(--brand-soft); } +.mcp-modal .secondary-connect:disabled { border-color: var(--border); background: var(--secondary); color: var(--muted-foreground); } .credential-modal > a { display: block; margin-top: 13px; color: var(--brand); text-align: center; text-decoration: none; font-size: 10px; } .credential-modal > a:hover { text-decoration: underline; } .field-row { display: flex; gap: 8px; } .field-row > div:first-child { width: 115px; flex: 0 0 115px; } .field-row .url-field { min-width: 0; flex: 1; } -.credential-modal label .optional { float: right; color: #a1a1aa; font-size: 8px; font-weight: 550; letter-spacing: .04em; } +.credential-modal label .optional { float: right; color: var(--muted-foreground); font-size: 8px; font-weight: 550; letter-spacing: .04em; } .credential-modal form > label:not(:first-child) { margin-top: 13px; } -.checkbox-row { display: flex !important; align-items: flex-start; gap: 9px; margin: 13px 1px 0 !important; letter-spacing: 0 !important; text-transform: none !important; cursor: pointer; } -.checkbox-row > input { appearance: none; width: 17px !important; height: 17px !important; flex: 0 0 17px; display: grid; place-items: center; margin: 1px 0 0; padding: 0 !important; border-radius: 4px !important; } -.checkbox-row > input:checked { border-color: var(--brand); background: var(--brand); } -.checkbox-row > input:checked::after { content: "✓"; color: #fff; font-size: 10px; } -.checkbox-row span, .checkbox-row strong, .checkbox-row small { display: block; } -.checkbox-row strong { font-size: 10.5px; letter-spacing: 0; text-transform: none; } -.checkbox-row small { margin-top: 3px; color: var(--muted); font-size: 9.5px; font-weight: 400; line-height: 1.4; letter-spacing: 0; text-transform: none; } -.checkbox-row.disabled { opacity: .48; cursor: not-allowed; } -.checkbox-row code { font-family: var(--font-geist-mono), monospace; } -.transport-note { display: flex; align-items: flex-start; gap: 7px; margin-top: 13px; color: var(--muted); font-size: 9.5px; line-height: 1.45; } -.transport-note > span { width: 16px; height: 16px; flex: 0 0 16px; display: grid; place-items: center; border: 1px solid #d4d4d8; border-radius: 50%; font: italic 10px var(--font-merriweather), Georgia, serif; } -.input-hint { margin: 5px 2px 0; color: var(--muted); font-size: 9px; line-height: 1.4; } -.input-hint code { padding: 1px 3px; border-radius: 3px; background: var(--panel); font-family: var(--font-geist-mono), monospace; } -.gateway-status { display: flex; align-items: flex-start; gap: 8px; margin: 12px 0; padding: 9px 10px; border: 1px solid #cce6d5; border-radius: 6px; background: #f2fbf5; color: #207a3d; } +.transport-note { display: flex; align-items: flex-start; gap: 7px; margin-top: 13px; color: var(--muted-foreground); font-size: 9.5px; line-height: 1.45; } +.transport-note > span { width: 16px; height: 16px; flex: 0 0 16px; display: grid; place-items: center; border: 1px solid var(--muted); border-radius: 50%; font: italic 10px var(--font-merriweather), Georgia, serif; } +.input-hint { margin: 5px 2px 0; color: var(--muted-foreground); font-size: 9px; line-height: 1.4; } +.input-hint code { padding: 1px 3px; border-radius: 3px; background: var(--secondary); font-family: var(--font-geist-mono), monospace; } +.gateway-status { display: flex; align-items: flex-start; gap: 8px; margin: 12px 0; padding: 9px 10px; border: 1px solid color-mix(in oklab, var(--success) 30%, transparent); border-radius: 6px; background: color-mix(in oklab, var(--success) 7%, transparent); color: var(--success); } .gateway-status > span { line-height: 1.2; } .gateway-status p { display: grid; gap: 2px; margin: 0; min-width: 0; } .gateway-status strong { font-size: 10px; } -.gateway-status small { overflow: hidden; color: #4b6b56; font-size: 9px; text-overflow: ellipsis; white-space: nowrap; } +.gateway-status small { overflow: hidden; color: var(--muted-foreground); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; } .router-modal { width: min(720px, 100%); max-height: calc(100vh - 32px); overflow: auto; } .router-modal h2 { margin-top: 14px; } -.router-loading { padding: 28px 8px; color: var(--muted); text-align: center; font-size: 11px; } +.router-loading { padding: 28px 8px; color: var(--muted-foreground); text-align: center; font-size: 11px; } .skills-list { display: grid; gap: 6px; margin: 12px 0 4px; padding: 0; max-height: 320px; overflow-y: auto; list-style: none; } -.skills-list li { display: grid; gap: 3px; padding: 9px 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--panel); } +.skills-list li { display: grid; gap: 3px; padding: 9px 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--secondary); } .skills-list strong { font-size: 11px; font-family: var(--mono, ui-monospace, monospace); } -.skills-list small { color: var(--muted); font-size: 10px; line-height: 1.45; } -.skills-empty { display: grid; gap: 5px; margin: 12px 0 4px; padding: 20px 14px; border: 1px dashed var(--line); border-radius: 6px; text-align: center; } +.skills-list small { color: var(--muted-foreground); font-size: 10px; line-height: 1.45; } +.skills-empty { display: grid; gap: 5px; margin: 12px 0 4px; padding: 20px 14px; border: 1px dashed var(--border); border-radius: 6px; text-align: center; } .skills-empty strong { font-size: 11px; } -.skills-empty p { margin: 0; color: var(--muted); font-size: 10px; line-height: 1.5; } -.skills-path { display: block; margin-top: 3px; overflow-x: auto; color: var(--muted); font-size: 9px; white-space: nowrap; } -.router-managed { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 14px; padding: 10px 11px; border: 1px solid #cce6d5; border-radius: 7px; background: #f2fbf5; color: #207a3d; } +.skills-empty p { margin: 0; color: var(--muted-foreground); font-size: 10px; line-height: 1.5; } +.skills-path { display: block; margin-top: 3px; overflow-x: auto; color: var(--muted-foreground); font-size: 9px; white-space: nowrap; } +.router-managed { display: flex; align-items: flex-start; gap: 8px; margin: 0 0 14px; padding: 10px 11px; border: 1px solid color-mix(in oklab, var(--success) 30%, transparent); border-radius: 7px; background: color-mix(in oklab, var(--success) 7%, transparent); color: var(--success); } .router-managed > span { line-height: 1.2; } .router-managed p, .router-managed strong, .router-managed small { display: block; margin: 0; } .router-managed strong { font-size: 10.5px; } -.router-managed small { margin-top: 3px; color: #4b6b56; font-size: 9.5px; line-height: 1.45; } +.router-managed small { margin-top: 3px; color: var(--muted-foreground); font-size: 9.5px; line-height: 1.45; } .router-managed-fields { min-width: 0; margin: 0; padding: 0; border: 0; } .router-managed-fields:disabled { opacity: .68; } -.router-switch { display: flex !important; align-items: flex-start; gap: 10px; margin: 0 0 16px !important; padding: 11px; border: 1px solid var(--line); border-radius: 7px; background: var(--panel); letter-spacing: 0 !important; text-transform: none !important; cursor: pointer; } -.router-switch input { appearance: none; width: 34px !important; height: 20px !important; flex: 0 0 34px; margin: 0; padding: 0 !important; border: 0 !important; border-radius: 10px !important; background: #c8cbc9; transition: .15s ease; } -.router-switch input::after { content: ""; display: block; width: 16px; height: 16px; margin: 2px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.2); transition: transform .15s ease; } +.router-switch { display: flex !important; align-items: flex-start; gap: 10px; margin: 0 0 16px !important; padding: 11px; border: 1px solid var(--border); border-radius: 7px; background: var(--secondary); letter-spacing: 0 !important; text-transform: none !important; cursor: pointer; } +.router-switch input { appearance: none; width: 34px !important; height: 20px !important; flex: 0 0 34px; margin: 0; padding: 0 !important; border: 0 !important; border-radius: 10px !important; background: var(--border); transition: .15s ease; } +.router-switch input::after { content: ""; display: block; width: 16px; height: 16px; margin: 2px; border-radius: 50%; background: var(--card); box-shadow: 0 1px 3px color-mix(in oklab, var(--foreground) 3%, transparent); transition: transform .15s ease; } .router-switch input:checked { background: var(--brand); } .router-switch input:checked::after { transform: translateX(14px); } .router-switch span, .router-switch strong, .router-switch small { display: block; } -.router-switch strong { color: var(--ink); font-size: 10.5px; } -.router-switch small { margin-top: 3px; color: var(--muted); font-size: 9.5px; font-weight: 400; line-height: 1.4; } +.router-switch strong { color: var(--foreground); font-size: 10.5px; } +.router-switch small { margin-top: 3px; color: var(--muted-foreground); font-size: 9.5px; font-weight: 400; line-height: 1.4; } .router-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } -.router-modal select { width: 100%; height: 42px; padding: 0 11px; border: 1px solid #d4d4d8; border-radius: 6px; background: #fff; color: var(--ink); font: 12px var(--font-geist-mono), monospace; } -.router-section-heading { display: flex; align-items: center; justify-content: space-between; margin: 18px 0 8px; color: #52525b; font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; } -.router-section-heading button { padding: 5px 8px; border: 1px solid var(--line); border-radius: 5px; background: #fff; color: var(--brand); cursor: pointer; font-size: 9.5px; font-weight: 700; letter-spacing: 0; text-transform: none; } +.router-modal select { width: 100%; height: 42px; padding: 0 11px; border: 1px solid var(--muted); border-radius: 6px; background: var(--card); color: var(--foreground); font: 12px var(--font-geist-mono), monospace; } +.router-section-heading { display: flex; align-items: center; justify-content: space-between; margin: 18px 0 8px; color: var(--muted-foreground); font-size: 10px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase; } +.router-section-heading button { padding: 5px 8px; border: 1px solid var(--border); border-radius: 5px; background: var(--card); color: var(--brand); cursor: pointer; font-size: 9.5px; font-weight: 700; letter-spacing: 0; text-transform: none; } .router-section-heading button:disabled { opacity: .45; cursor: default; } .router-categories { display: grid; gap: 8px; } -.router-category { position: relative; min-width: 0; margin: 0; padding: 12px; border: 1px solid var(--line); border-radius: 7px; background: #fcfcfb; } -.router-category legend { padding: 0 5px; color: var(--muted); font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } +.router-category { position: relative; min-width: 0; margin: 0; padding: 12px; border: 1px solid var(--border); border-radius: 7px; background: var(--card); } +.router-category legend { padding: 0 5px; color: var(--muted-foreground); font-size: 9px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; } .router-category > label { margin-top: 10px !important; } -.router-category textarea { width: 100%; min-height: 66px; resize: vertical; padding: 9px 11px; border: 1px solid #d4d4d8; border-radius: 6px; color: var(--ink); font: 11px/1.45 var(--font-inter), sans-serif; } -.router-category textarea:focus, .router-modal select:focus { border-color: #72a68f; outline: 2px solid rgba(3,106,73,.12); } -.router-remove { position: absolute; top: 5px; right: 7px; width: 25px; height: 25px; border: 0; border-radius: 4px; background: transparent; color: #a1a1aa; cursor: pointer; font-size: 17px; } -.router-remove:hover { background: #fff1f0; color: var(--danger); } +.router-category textarea { width: 100%; min-height: 66px; resize: vertical; padding: 9px 11px; border: 1px solid var(--muted); border-radius: 6px; color: var(--foreground); font: 11px/1.45 var(--font-inter), sans-serif; } +.router-category textarea:focus, .router-modal select:focus { border-color: color-mix(in oklab, var(--brand) 55%, transparent); outline: 2px solid color-mix(in oklab, var(--brand) 3%, transparent); } +.router-remove { position: absolute; top: 5px; right: 7px; width: 25px; height: 25px; border: 0; border-radius: 4px; background: transparent; color: var(--muted-foreground); cursor: pointer; font-size: 17px; } +.router-remove:hover { background: var(--card)1f0; color: var(--destructive); } .router-remove:disabled { opacity: .25; cursor: default; } .router-safety { margin-bottom: 12px; } .router-modal .connect-button { margin-top: 3px; } @media (max-width: 820px) { - .sidebar { position: fixed; inset: 0 auto 0 0; z-index: 20; transform: translateX(-105%); box-shadow: 10px 0 35px rgba(9,30,20,.14); transition: transform .22s ease; } .sidebar.open { transform: translateX(0); } - .sidebar-close, .menu-button { display: grid; } - .sidebar-scrim { position: fixed; inset: 0; z-index: 19; display: block; border: 0; background: rgba(9,30,20,.28); } - .topbar { padding: 0 10px; } - .topbar-left { gap: 4px; } - .task-heading h1 { max-width: 43vw; } - .desktop-label { display: none; } .message-stack { width: calc(100% - 28px); } .composer-wrap { padding: 0 12px 10px; } .empty-state { width: calc(100% - 28px); } @@ -351,16 +438,12 @@ button:focus-visible, textarea:focus-visible, input:focus-visible { outline: 2px .mcp-modal { max-height: calc(100vh - 20px); overflow: auto; } .router-modal { max-height: calc(100vh - 20px); } .router-grid { grid-template-columns: 1fr; } - .routing-live-pill { display: none; } .routing-summary { align-items: flex-start; flex-direction: column; } .routing-summary-counts { justify-content: flex-start; } .message-stack { padding-top: 24px; } .message { gap: 9px; margin-bottom: 27px; } .assistant-avatar { width: 26px; height: 26px; flex-basis: 26px; } .message-body { max-width: calc(100% - 35px); } - .composer-hint { display: none; } - .mode-button { padding: 0 5px; } - .topbar-actions .topbar-button:nth-child(2) { display: none; } .approval-actions { flex-wrap: wrap; } .approval-actions button { flex: 1; } .empty-state h2 { font-size: 25px; } diff --git a/studio/app/layout.tsx b/studio/app/layout.tsx index 3a052098ed..45a2f1f38a 100644 --- a/studio/app/layout.tsx +++ b/studio/app/layout.tsx @@ -1,5 +1,8 @@ import type { Metadata } from "next"; import { Geist_Mono, Inter, Merriweather } from "next/font/google"; + +import { ThemeProvider } from "@/components/theme-provider"; + import "./globals.css"; const inter = Inter({ @@ -33,11 +36,13 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + // suppressHydrationWarning: next-themes stamps the resolved theme class onto + // before paint, so the server markup deliberately differs here. + - {children} + {children} ); diff --git a/studio/app/page.tsx b/studio/app/page.tsx index 15c7df8d6d..102ff86f47 100644 --- a/studio/app/page.tsx +++ b/studio/app/page.tsx @@ -1,7 +1,11 @@ "use client"; -import { ChangeEvent, DragEvent, FormEvent, KeyboardEvent, useEffect, useId, useMemo, useRef, useState } from "react"; +import { ChangeEvent, DragEvent, FormEvent, useEffect, useId, useMemo, useRef, useState } from "react"; import { decodeScheduleRows, parseMecatlEvent, type MecatlEvent, type ScheduleRow } from "../lib/protocol"; +import { Composer } from "@/components/chat/composer"; +import { Navbar } from "@/components/shell/navbar"; +import { TaskSidebar } from "@/components/shell/task-sidebar"; +import type { SettingsPanel } from "@/components/user-menu"; type ToolActivity = { id: string; @@ -209,8 +213,8 @@ export default function Home() { const [draggingCsv, setDraggingCsv] = useState(false); const [running, setRunning] = useState(false); const [connected, setConnected] = useState<"checking" | "online" | "offline">("checking"); - const [sidebarOpen, setSidebarOpen] = useState(false); - const [modelMenu, setModelMenu] = useState(false); + // The mobile nav drawer and the model popover are owned by Navbar and Composer + // respectively — local to the component that renders the trigger. const [credentialsOpen, setCredentialsOpen] = useState(false); const [controllerMode, setControllerMode] = useState<"managed" | "external">("managed"); const [providerName, setProviderName] = useState("offline mock"); @@ -444,7 +448,6 @@ export default function Home() { setTasks((current) => [task, ...current]); setActiveId(task.id); setCsvAttachment(null); - setSidebarOpen(false); setError(""); requestAnimationFrame(() => textareaRef.current?.focus()); }; @@ -660,7 +663,7 @@ export default function Home() { void selectCsv(event.target.files?.[0]); }; - const onCsvDrop = (event: DragEvent) => { + const onCsvDrop = (event: DragEvent) => { event.preventDefault(); setDraggingCsv(false); void selectCsv(event.dataTransfer.files?.[0]); @@ -1042,61 +1045,63 @@ export default function Home() { setMcpOpen(false); }; - const onPromptKey = (event: KeyboardEvent) => { - if (event.key === "Enter" && !event.shiftKey) { - event.preventDefault(); - void sendPrompt(); + // One entry point for the six configuration dialogs, so the profile menu does + // not need to know which of them have a loader to prime first. + const openSettingsPanel = (panel: SettingsPanel) => { + switch (panel) { + case "provider": + setCredentialsOpen(true); + return; + case "router": + openRouterSettings(); + return; + case "mcp": + setMcpState("idle"); + setMcpError(""); + setMcpOpen(true); + return; + case "skills": + openSkills(); + return; + case "memory": + openMemory(); + return; + case "schedules": + openSchedules(); + return; } }; return (
- - - {sidebarOpen && -

{active?.title || "New task"}

{running ? "Working" : "Local"}{routerStatus && }
- -
- - - - - - - - -
- +
{routingSummary.total > 0 &&
@@ -1141,35 +1146,26 @@ export default function Home() {
{error &&
!

{error}

} -
{ event.preventDefault(); setDraggingCsv(true); }} - onDragOver={(event) => event.preventDefault()} - onDragLeave={(event) => { if (event.currentTarget === event.target) setDraggingCsv(false); }} - onDrop={onCsvDrop} - > - - {csvAttachment &&
- CSV - {csvAttachment.name}{csvAttachment.rows} rows · {csvAttachment.columns} columns · {formatBytes(csvAttachment.size)} - -
} - {draggingCsv && } -