diff --git a/.github/workflows/shell-e2e.yml b/.github/workflows/shell-e2e.yml index 9332d09e0..6b93496e9 100644 --- a/.github/workflows/shell-e2e.yml +++ b/.github/workflows/shell-e2e.yml @@ -52,10 +52,15 @@ jobs: - name: Install Node.js uses: actions/setup-node@v5 with: - node-version: '20' + node-version: '22' cache: 'npm' cache-dependency-path: shell/tests/e2e/workers/harness/package-lock.json + # shell/build.rs builds the injectable UI (shell/ui) via pnpm. + # Version comes from the repo-root package.json `packageManager` field. + - name: Setup pnpm + uses: pnpm/action-setup@v5 + - name: Install iii engine (latest from main) run: | curl -fsSL --retry 3 --retry-connrefused --retry-delay 5 \ diff --git a/console/DESIGN.md b/console/DESIGN.md index c82d0fd6e..c60b9b3de 100644 --- a/console/DESIGN.md +++ b/console/DESIGN.md @@ -1,118 +1,155 @@ --- -version: alpha +version: beta name: iii Schematic -description: A minimal, blueprint-style design system for the iii engine. Engineering-document aesthetic in warm cream and ink, with a single vivid orange accent and an all-monospace voice. +description: A minimal, engineering-document design system for the iii engine. The system draws no lines — hierarchy comes entirely from layered surfaces and alpha-gray fills; warm cream paper in light, neutral grays from black in dark, with a single rationed accent (burnt orange on cream, electric blue on dark), one 6px corner radius everywhere, and a mono voice for technical data. The only sanctioned strokes are the focus ring and the very subtle `edge` frame around the floating workspace panels. colors: bg: "#f2f0ed" - panel: "#e9e6e2" + sidebar: "#edeae5" + panel: "lab(98.26% 0 0)" + panel-raised: "#f7f5f2" paper: "#f2f0ed" paper-2: "#ebe8e3" + surface: "rgba(20, 16, 8, 0.055)" + surface-hover: "rgba(20, 16, 8, 0.085)" + surface-selected: "rgba(184, 66, 15, 0.12)" + surface-active: "rgba(20, 16, 8, 0.12)" ink: "#0a0a0a" ink-2: "#1a1a1a" ink-soft: "#1a1a1a" ink-faint: "#6b6865" ink-ghost: "#a3a09c" + ink-disabled: "#b8b4ae" mute: "#6b6865" mute-2: "#a3a09c" - rule: "#d8d5d0" - rule-2: "#e6e3df" - accent: "#ff5a1f" - accent-dark: "#3ea8ff" - bg-dark: "#111110" - panel-dark: "#1a1916" - ink-dark: "#f2f0ed" - ink-faint-dark: "#9c9893" - ink-ghost-dark: "#5d5a55" - rule-dark: "#2a2926" - rule-2-dark: "#1f1e1c" - alert: "#c43e1c" + rule: "transparent" + rule-2: "transparent" + rule-strong: "transparent" + rule-focus: "rgba(184, 66, 15, 0.6)" + edge: "rgba(20, 16, 8, 0.08)" + accent: "#b8420f" + accent-fg: "#f2f0ed" + accent-hover: "#a53a0c" + accent-muted: "rgba(184, 66, 15, 0.1)" + accent-border: "rgba(184, 66, 15, 0.35)" + alert: "#ff0026" + alert-muted: "rgba(255, 0, 38, 0.08)" warn: "#a87a00" + warn-muted: "rgba(168, 122, 0, 0.12)" + ok: "#356f3d" + ok-muted: "rgba(53, 111, 61, 0.12)" + bg-dark: "#0a0a0a" + sidebar-dark: "#0e0e0e" + panel-dark: "#111111" + panel-raised-dark: "#171717" + paper-2-dark: "#171717" + surface-dark: "rgba(255, 255, 255, 0.055)" + surface-hover-dark: "rgba(255, 255, 255, 0.085)" + surface-selected-dark: "rgba(40, 168, 247, 0.14)" + surface-active-dark: "rgba(255, 255, 255, 0.12)" + ink-dark: "#ededed" + ink-faint-dark: "#a6a6a6" + ink-ghost-dark: "#6f6f6f" + ink-disabled-dark: "#4d4d4d" + rule-dark: "transparent" + rule-2-dark: "transparent" + rule-strong-dark: "transparent" + rule-focus-dark: "rgba(40, 168, 247, 0.7)" + edge-dark: "rgba(255, 255, 255, 0.07)" + accent-dark: "#28a8f7" + accent-hover-dark: "#46b6fa" + accent-muted-dark: "rgba(40, 168, 247, 0.12)" + accent-border-dark: "rgba(40, 168, 247, 0.35)" + alert-dark: "#f05d68" + warn-dark: "#f5a524" + ok-dark: "#36c98f" typography: logo: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 18px fontWeight: 600 lineHeight: 1 letterSpacing: -0.02em display-hero: - fontFamily: Chivo Mono + fontFamily: Geist fontSize: 72px fontWeight: 600 lineHeight: 1.02 letterSpacing: -0.02em display-foot: - fontFamily: Chivo Mono + fontFamily: Geist fontSize: 48px fontWeight: 600 lineHeight: 1.05 letterSpacing: -0.03em headline-section: - fontFamily: Chivo Mono + fontFamily: Geist fontSize: 28px fontWeight: 500 lineHeight: 1.25 letterSpacing: -0.01em headline-card: - fontFamily: Chivo Mono + fontFamily: Geist fontSize: 20px fontWeight: 500 lineHeight: 1.1 letterSpacing: -0.02em title-cell: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 16px fontWeight: 600 lineHeight: 1.3 letterSpacing: -0.01em body-md: - fontFamily: Chivo Mono + fontFamily: Geist fontSize: 14px fontWeight: 400 lineHeight: 1.7 body-sm: - fontFamily: Chivo Mono + fontFamily: Geist fontSize: 13px fontWeight: 400 lineHeight: 1.7 code-md: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 13px fontWeight: 400 lineHeight: 1.65 code-sm: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 12.5px fontWeight: 400 lineHeight: 1.55 label-caps-lg: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 12px fontWeight: 500 lineHeight: 1 letterSpacing: 0.18em label-caps-md: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 12px fontWeight: 500 lineHeight: 1 letterSpacing: 0.14em label-caps-sm: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 11px fontWeight: 500 lineHeight: 1 letterSpacing: 0.06em micro: - fontFamily: Chivo Mono + fontFamily: Geist Mono fontSize: 9px fontWeight: 400 lineHeight: 1 letterSpacing: 0.04em rounded: none: 0px - sm: 0px - md: 0px - lg: 0px + xs: 6px + sm: 6px + md: 6px + lg: 6px + xl: 6px full: 9999px spacing: base: 16px @@ -147,35 +184,35 @@ components: backgroundColor: "{colors.ink}" textColor: "{colors.bg}" typography: "{typography.body-sm}" - rounded: "{rounded.none}" + rounded: "{rounded.md}" padding: 12px 20px button-primary-hover: - backgroundColor: "{colors.bg}" - textColor: "{colors.ink}" + backgroundColor: "{colors.ink}" + opacity: 0.9 button-ghost: - backgroundColor: "{colors.bg}" - textColor: "{colors.ink}" + backgroundColor: transparent + textColor: "{colors.ink-faint}" typography: "{typography.body-sm}" - rounded: "{rounded.none}" + rounded: "{rounded.sm}" padding: 12px 20px button-ghost-hover: - backgroundColor: "{colors.ink}" - textColor: "{colors.bg}" + backgroundColor: "{colors.surface-hover}" + textColor: "{colors.ink}" button-pill: - backgroundColor: "{colors.bg}" + backgroundColor: "{colors.surface}" textColor: "{colors.ink}" typography: "{typography.body-sm}" - rounded: "{rounded.none}" + rounded: "{rounded.sm}" padding: 6px 12px button-pill-hover: - backgroundColor: "{colors.ink}" - textColor: "{colors.bg}" + backgroundColor: "{colors.surface-hover}" button-icon: - backgroundColor: "{colors.bg}" + backgroundColor: transparent textColor: "{colors.ink-faint}" - rounded: "{rounded.none}" + rounded: "{rounded.sm}" size: 30px button-icon-hover: + backgroundColor: "{colors.surface-hover}" textColor: "{colors.ink}" nav-link: typography: "{typography.body-sm}" @@ -184,68 +221,78 @@ components: nav-link-hover: textColor: "{colors.ink}" card: - backgroundColor: "{colors.bg}" - rounded: "{rounded.none}" - padding: 28px + backgroundColor: "{colors.surface}" + rounded: "{rounded.md}" + padding: 20px card-focus: - backgroundColor: "{colors.panel}" + backgroundColor: "{colors.panel-raised}" card-head: - backgroundColor: "{colors.panel}" + backgroundColor: "{colors.panel-raised}" typography: "{typography.label-caps-lg}" textColor: "{colors.ink-faint}" padding: 10px 14px input: - backgroundColor: "{colors.bg}" + backgroundColor: "{colors.surface}" textColor: "{colors.ink}" typography: "{typography.body-sm}" - rounded: "{rounded.none}" - padding: 10px 2px + rounded: "{rounded.sm}" + padding: 10px 12px input-placeholder: textColor: "{colors.ink-ghost}" + input-hover: + backgroundColor: "{colors.surface-hover}" input-focus: - backgroundColor: "{colors.bg}" - textColor: "{colors.ink}" + borderColor: "{colors.rule-focus}" + ring: "3px {colors.accent-muted}" badge-numeric: backgroundColor: "{colors.accent}" - textColor: "{colors.bg}" + textColor: "{colors.accent-fg}" typography: "{typography.label-caps-sm}" - rounded: "{rounded.none}" + rounded: "{rounded.xs}" padding: 0 4px height: 16px status-dot: backgroundColor: "{colors.accent}" rounded: "{rounded.full}" size: 6px - rule-line: - backgroundColor: "{colors.rule}" - height: 1px code-block: backgroundColor: "{colors.bg}" typography: "{typography.code-sm}" textColor: "{colors.ink}" + rounded: "{rounded.sm}" padding: 18px 20px terminal-button: - backgroundColor: "{colors.bg}" + backgroundColor: "{colors.surface}" textColor: "{colors.ink}" typography: "{typography.body-sm}" - rounded: "{rounded.none}" + rounded: "{rounded.sm}" padding: 10px 14px terminal-prompt: textColor: "{colors.accent}" + toggle-track: + backgroundColor: "{colors.surface}" + rounded: "{rounded.sm}" + padding: 2px toggle-active: - backgroundColor: "{colors.ink}" - textColor: "{colors.bg}" + backgroundColor: "{colors.accent-muted}" + textColor: "{colors.ink}" + rounded: "{rounded.xs}" toggle-inactive: - backgroundColor: "{colors.bg}" + backgroundColor: transparent textColor: "{colors.mute}" --- # iii Schematic — design system -This document is a self-contained, portable spec for the iii Schematic UI: a -warm-cream, ink-on-paper, monospace web UI built like an engineering drafting -sheet. Everything needed to reproduce the system in another project lives -inside this file — there are no links to repository sources. +This document is a self-contained, portable spec for the iii Schematic UI: +an engineering-document web UI built from layered surfaces and alpha-gray +fills — warm cream paper in light, neutral grays from black in dark — with +Geist for UI text, Geist Mono for technical data, one 6px corner radius +everywhere, and a single rationed accent. The system draws no lines: the +only sanctioned strokes are the focus ring and the subtle `edge` frame +around the floating workspace panels. Everything needed to reproduce the +system in another project lives inside this file — there are no links to +repository sources. The YAML frontmatter above is the machine-readable token spec. The sections below translate it into implementation-ready CSS and React. They assume: @@ -263,18 +310,24 @@ canonical components in §10 as-is. ## 0. Setup -### Font +### Fonts -Load Chivo Mono in `index.html`. Weights 400/500/600 cover the entire scale -(body, label-caps, headlines, display). +Two families, loaded via `@fontsource` at the top of the global stylesheet: +**Geist** (the UI sans — navigation, buttons, body copy, headings) and +**Geist Mono** (the technical voice — trace names, IDs, timestamps, metrics, +code). Chivo Mono stays in the mono fallback stack for legacy glyph parity. +Weights 400/500/600 cover the entire scale. -```html - - - +```css +@import "@fontsource/geist/400.css"; +@import "@fontsource/geist/500.css"; +@import "@fontsource/geist/600.css"; +@import "@fontsource/geist-mono/400.css"; +@import "@fontsource/geist-mono/500.css"; +@import "@fontsource/geist-mono/600.css"; +@import "@fontsource/chivo-mono/400.css"; +@import "@fontsource/chivo-mono/500.css"; +@import "@fontsource/chivo-mono/600.css"; ``` ### HTML shell @@ -299,37 +352,90 @@ The full design-token stylesheet — drop this in as your global CSS entrypoint: @import "tailwindcss"; @theme { - --font-sans: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, - Consolas, "Liberation Mono", "Courier New", monospace; - --font-mono: "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, + --font-sans: + "Geist", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: + "Geist Mono", "Chivo Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - /* paper / surface ramp (3-step) */ + /* ── Surface ramp ───────────────────────────────────────────────────── + Layered surfaces are the ONLY structural tool — the system draws no + lines: bg (canvas) → sidebar → panel → panel-raised, then the + component fills surface → surface-hover → surface-selected → + surface-active. The base layers are solid; the component fills are + alpha grays so one step reads identically over any base layer. + `paper-2` is the legacy alias for panel-raised. */ --color-bg: #f2f0ed; - --color-panel: #e9e6e2; + --color-sidebar: #edeae5; + --color-panel: lab(98.26% 0 0); + --color-panel-raised: #f7f5f2; --color-paper-2: #ebe8e3; + --color-surface: rgba(20, 16, 8, 0.055); + --color-surface-hover: rgba(20, 16, 8, 0.085); + --color-surface-selected: rgba(184, 66, 15, 0.12); + --color-surface-active: rgba(20, 16, 8, 0.12); - /* ink ramp (3-step) */ + /* ── Ink ramp ──────────────────────────────────────────────────────── */ --color-ink: #0a0a0a; --color-ink-faint: #6b6865; --color-ink-ghost: #a3a09c; - - /* structural lines */ - --color-rule: #d8d5d0; - --color-rule-2: #e6e3df; - - /* accent (single hero — hot orange) */ - --color-accent: #ff5a1f; + --color-ink-disabled: #b8b4ae; + + /* ── Border ramp ────────────────────────────────────────────────────── + The system draws no lines: rule/rule-2/rule-strong resolve to + transparent in both themes (legacy `border-rule*` utilities become + inert 1px transparent borders, so layout never shifts). Hierarchy is + carried by the surface fills above. The single exception is + rule-focus — the focus indicator on inputs and controls, which must + stay visible for accessibility. */ + --color-rule: transparent; + --color-rule-2: transparent; + --color-rule-strong: transparent; + --color-rule-focus: rgba(184, 66, 15, 0.6); + + /* The one structural stroke the system keeps: a VERY subtle edge on the + main workspace panels (the tab columns), so the floating panels + read against the canvas. Never used inside a panel. */ + --color-edge: rgba(20, 16, 8, 0.08); + + /* accent (single hero — burnt orange on cream, blue on dark) */ + --color-accent: #b8420f; --color-accent-fg: #f2f0ed; + --color-accent-hover: #a53a0c; + --color-accent-muted: rgba(184, 66, 15, 0.1); + --color-accent-border: rgba(184, 66, 15, 0.35); - /* status */ - --color-alert: #c43e1c; + /* status (each with a muted fill for tinted backgrounds) */ + --color-alert: #ff0026; + --color-alert-muted: rgba(255, 0, 38, 0.08); --color-warn: #a87a00; - - /* radii — only two are allowed */ + --color-warn-muted: rgba(168, 122, 0, 0.12); + --color-ok: #356f3d; + --color-ok-muted: rgba(53, 111, 61, 0.12); + + /* ── Radii ──────────────────────────────────────────────────────────── + One radius everywhere: every step of the Tailwind scale resolves to + 6px, so badges, buttons, cards, panels, and modals share the same + corner. Only `none` (main columns' outer frame edge cases) and `full` + (dots, round action buttons) differ. */ --radius-none: 0px; + --radius-xs: 6px; + --radius-sm: 6px; + --radius-md: 6px; + --radius-lg: 6px; + --radius-xl: 6px; --radius-full: 9999px; + /* ── Elevation (the only two sanctioned shadows) ────────────────────── */ + --shadow-raised: + 0 1px 0 rgba(255, 255, 255, 0.025) inset, 0 8px 24px rgba(0, 0, 0, 0.18); + --shadow-floating: + 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 12px 32px rgba(0, 0, 0, 0.28); + + /* ── Motion ─────────────────────────────────────────────────────────── */ + --ease-glide: cubic-bezier(0.2, 0.8, 0.2, 1); + /* spacing scale (carried from the YAML) */ --spacing-gutter: 24px; --spacing-section-x: 36px; @@ -338,18 +444,41 @@ The full design-token stylesheet — drop this in as your global CSS entrypoint: --spacing-content-max: 1216px; } -/* dark theme: invert paper/ink and swap accent to electric blue */ [data-theme="dark"] { - --color-bg: #111110; - --color-panel: #1a1916; - --color-paper-2: #1f1e1c; - --color-ink: #f2f0ed; - --color-ink-faint: #9c9893; - --color-ink-ghost: #5d5a55; - --color-rule: #2a2926; - --color-rule-2: #1f1e1c; - --color-accent: #3ea8ff; - --color-accent-fg: #111110; + /* Neutral grays derived from black — no blue cast in the base ramp. The + component fills (surface*) are white-alpha so a step reads identically + over any base layer; the ONLY chromatic surface is surface-selected, + the blue selection tint. Borders are gone: rule/rule-2/rule-strong + resolve to transparent, and hierarchy is carried entirely by fills. */ + --color-bg: #0a0a0a; + --color-sidebar: #0e0e0e; + --color-panel: #111111; + --color-panel-raised: #171717; + --color-paper-2: #171717; + --color-surface: rgba(255, 255, 255, 0.055); + --color-surface-hover: rgba(255, 255, 255, 0.085); + --color-surface-selected: rgba(40, 168, 247, 0.14); + --color-surface-active: rgba(255, 255, 255, 0.12); + --color-ink: #ededed; + --color-ink-faint: #a6a6a6; + --color-ink-ghost: #6f6f6f; + --color-ink-disabled: #4d4d4d; + --color-rule: transparent; + --color-rule-2: transparent; + --color-rule-strong: transparent; + --color-rule-focus: rgba(40, 168, 247, 0.7); + --color-edge: rgba(255, 255, 255, 0.07); + --color-accent: #28a8f7; + --color-accent-fg: #070909; + --color-accent-hover: #46b6fa; + --color-accent-muted: rgba(40, 168, 247, 0.12); + --color-accent-border: rgba(40, 168, 247, 0.35); + --color-alert: #f05d68; + --color-alert-muted: rgba(240, 93, 104, 0.12); + --color-warn: #f5a524; + --color-warn-muted: rgba(245, 165, 36, 0.12); + --color-ok: #36c98f; + --color-ok-muted: rgba(54, 201, 143, 0.12); } @layer base { @@ -388,11 +517,13 @@ The full design-token stylesheet — drop this in as your global CSS entrypoint: ::-webkit-scrollbar-track { background: transparent; } + /* rule is transparent (no-lines system) — the thumb needs its own + visible alpha gray. */ ::-webkit-scrollbar-thumb { - background: var(--color-rule); + background: color-mix(in oklab, var(--color-ink) 22%, transparent); } ::-webkit-scrollbar-thumb:hover { - background: var(--color-ink-ghost); + background: color-mix(in oklab, var(--color-ink) 38%, transparent); } } @@ -428,12 +559,27 @@ The full design-token stylesheet — drop this in as your global CSS entrypoint: animation: wiggle 3s ease-in-out infinite; } -/* the one sanctioned shadow stack — used only for the "deal" stack - animation on language cards */ +/* transient shadow stack — used only for the "deal" stack animation on + language cards (default elevation goes through shadow-raised / + shadow-floating from the theme) */ @utility deal-shadow { box-shadow: -2px 0 0 var(--color-rule), -16px 4px 36px -10px rgba(0, 0, 0, 0.22); } + +/* Function/tool-call card chrome — an accent-tinted raised surface (a soft + gradient wash over panel-raised, no border) so calls read as special + without any outline. Derived from the accent token, so it tints blue in + dark mode and burnt orange in light mode. */ +@utility fcall-chrome { + background: + linear-gradient( + 180deg, + color-mix(in oklab, var(--color-accent) 7%, transparent), + color-mix(in oklab, var(--color-accent) 3%, transparent) + ), + var(--color-panel-raised); +} ``` ### `cn` helper @@ -454,19 +600,32 @@ export function cn(...inputs: ClassValue[]) { ## 1. Philosophy — "iii Schematic" -The app should feel like an **engineering document**, not a SaaS dashboard. - -- The page is a **drafting sheet**: cream paper, hairline ink rules, - monospace voice, an unflinching commitment to lowercase. Nothing is - rounded; everything sits on a 1px grid. -- Color is rationed: the palette is essentially **black-on-cream**, broken - only by a single hot orange used for state, focus, and emphasis. +The app should feel like an **engineering document**, not a SaaS dashboard — +technical, precise, dense without feeling cramped, structured without +looking boxed. + +- The page is built from **layered surfaces**: hierarchy comes from a + one-step background difference, full stop. The system draws **no lines** + — no outlines on controls, no dividers between rows or regions. Exactly + two strokes are sanctioned: the focus indicator (`rule-focus` + border/ring) on a focused control, and the very subtle `edge` frame + around the floating workspace panels. +- Dark mode is a **first-class layered system** — neutral grays derived + from black (`#0a0a0a → #171717`, no blue cast) with white-alpha component + fills, not an inverted paper ramp. +- One corner radius: **6px everywhere** (every Tailwind radius step + resolves to it); the shapes stay disciplined, not soft or + consumer-playful. Lowercase voice throughout. +- Color is rationed: the palette is essentially **ink-on-surface**, broken + by a single accent (burnt orange on cream, electric blue on dark) reserved + for selected, focused, active, and live states. - The personality is **technical but unintimidating** — the same energy as a well-kept lab notebook or a hand-drawn architecture diagram. It must feel built by engineers, for engineers, and for the agents working alongside them. - Density is deliberate: spec sheets, code, traces, and console panels - coexist on the same surface without a hierarchy contest. + coexist on the same surface without a hierarchy contest. Trace + visualizations stay the most colorful area of the UI. ### Voice @@ -478,21 +637,27 @@ The app should feel like an **engineering document**, not a SaaS dashboard. - The wordmark is pronounced *"three eye"* — every "i" stays lowercase. > If you removed all the type, the page should still read as a structured -> document. Lines establish hierarchy before color does. +> document. Surfaces establish hierarchy; color marks state. Lines never +> appear. --- -## 2. Typography — single typeface (Chivo Mono) +## 2. Typography — two families (Geist + Geist Mono) -Chivo Mono is wired to **both** `--font-sans` and `--font-mono` in the theme -(see §0), so every default text node is monospaced. There is no secondary -face in this design language. +**Geist** (`--font-sans`) carries the UI: navigation, conversation titles, +buttons, inputs, chat content, empty states, headings, labels. **Geist +Mono** (`--font-mono`) carries everything technical: trace names, worker +names, function names, IDs, timestamps, metrics, span labels, filter +expressions, code-like values. -**Rule:** every UI surface uses Chivo Mono (the default). Don't reach for a -sans-serif or a second monospace stack — variety comes from weight, scale, -case, and letter-spacing, not family. +**Rule:** if a human wrote it, it's sans; if the machine produced it (or a +machine will parse it), it's mono. Don't add a third family — variety comes +from weight, scale, case, and letter-spacing, not more fonts. Chat and +configuration surfaces remap incidental `.font-mono` chrome back to Geist +(see the `.chat-surface` / `.configuration-surface` rules in `index.css`); +function-trigger cards run Geist Mono throughout. -Decorative ligatures are explicitly disabled in `@layer base` +Decorative ligatures are explicitly disabled on mono surfaces (`liga 0, clig 0, calt 0, dlig 0`) to preserve the schematic feel. ### Size scale @@ -527,56 +692,85 @@ Decorative ligatures are explicitly disabled in `@layer base` ## 3. Color tokens All tokens live in the `@theme` block in §0. Use the Tailwind utility -(`bg-bg`, `text-ink-faint`, `text-accent`, `border-rule`, …) — **never** the +(`bg-bg`, `text-ink-faint`, `text-accent`, `bg-surface`, …) — **never** the raw CSS variable. -### Paper / surface (3-step neutral scale) - -| Token | Use | -| --------- | ---------------------------------------------------- | -| `bg` | Page (warm cream paper) — every default surface | -| `panel` | Header strips, focused cards, code-block chrome | -| `paper-2` | Nested separation when `panel` would be too heavy | - -### Ink (3-step contrast) - -| Token | Use | -| ----------- | -------------------------------------------------- | -| `ink` | Primary type, wordmark, primary buttons, hairlines | -| `ink-faint` | Body in muted contexts, captions, inactive nav | -| `ink-ghost` | Line numbers, placeholders, timestamps | - -### Rules (the structural lines) - -| Token | Use | -| -------- | ------------------------------------------------------------ | -| `rule` | Default 1px borders — defines every container | -| `rule-2` | Nested separators (e.g. card head → body) when `rule` is too loud | - -### Accent — hot orange (single hero) - -`accent`, `accent-fg`. Reserved for: the active state, the live pulse, the -keyword/return value in a code block, the `iii` highlight in a sentence, the -focus underline on the email row, the orange `$` prompt. **Never** for body -text or large fills. +### Surface ramp (the depth axis) + +The base layers (`bg` → `panel-raised`) are solid neutral tones; the +component fills (`surface*`) are **alpha grays**, so one step reads +identically over any base layer. `surface-selected` is the only chromatic +surface — the accent-tinted selection fill (blue in dark, burnt orange in +light). + +| Token | Use | +| ------------------ | ---------------------------------------------------------- | +| `bg` | Application canvas — the deepest layer | +| `sidebar` | Left navigation/sidebar (one step off the canvas) | +| `panel` | Main chat and traces columns | +| `panel-raised` | Composer, trace details, popovers, active tool cards | +| `paper-2` | Legacy alias for `panel-raised` (kept for existing code) | +| `surface` | Inputs, controls, pills, chips, secondary cards | +| `surface-hover` | Hover state on rows, items, and ghost controls | +| `surface-selected` | Selected conversation, trace row, or list item (accent tint) | +| `surface-active` | Strong active/pressed state | + +### Ink (4-step contrast) + +| Token | Use | +| -------------- | -------------------------------------------------- | +| `ink` | Primary type, wordmark, primary buttons | +| `ink-faint` | Body in muted contexts, captions, inactive nav | +| `ink-ghost` | Line numbers, placeholders, timestamps | +| `ink-disabled` | Disabled labels (paired with reduced opacity) | + +### Rules (there are none) + +`rule`, `rule-2`, and `rule-strong` resolve to `transparent` in both +themes. They exist only so legacy `border-rule*` / `divide-rule*` +utilities stay inert (1px transparent — layout never shifts) instead of +breaking. Never design with them. + +| Token | Value | Meaning | +| ------------- | ------------ | --------------------------------------------- | +| `rule` | transparent | Legacy default border — draws nothing | +| `rule-2` | transparent | Legacy subtle divider — draws nothing | +| `rule-strong` | transparent | Legacy emphasis border — draws nothing | +| `rule-focus` | accent ~60–70% alpha | The interactive stroke: the focus indicator on inputs and controls | +| `edge` | ink ~7–8% alpha | The structural stroke: the VERY subtle frame around the floating workspace panels (the tab columns) — never used inside a panel | + +### Accent (single hero — burnt orange on cream, electric blue on dark) + +`accent`, `accent-fg`, `accent-hover`, `accent-muted` (10–12%-alpha fill), +`accent-border` (35%-alpha, legacy — prefer `accent-muted` fills). Reserved +for: selected navigation and segmented controls (`accent-muted` fill), +focused inputs (`rule-focus`), active filters, live state, the selected +conversation/trace (`surface-selected` fill), the primary technical action, +the `$` prompt. **Never** for body text, large fills, or outlines. ### Status -`alert`, `warn` — desaturated on purpose. Status is expressed through **text -color + a small icon/stripe**, not full-color backgrounds. +`alert`, `warn`, `ok` — each with a `-muted` 8–12%-alpha fill for tinted +backgrounds. Status is expressed through **text color + a small icon/dot on +a muted tinted fill**, never a full-saturation background and never a +stripe or outline. | Token | Use | | -------- | ----------------------------------------- | -| `accent` | live / running / success | +| `accent` | live / running / focused / selected | +| `ok` | success, completed calls, diff additions | | `alert` | error states (traces, status panels) | -| `warn` | warning states | +| `warn` | warning states, pending approval | ### Dark theme -Override the same tokens inside a `[data-theme="dark"]` block (see §0). The -ramp inverts (`#111110 → #1a1916 → #1f1e1c` for paper; `#f2f0ed → #9c9893 → -#5d5a55` for ink) and the accent swaps to electric blue (`#3ea8ff`). Same -structural logic — never collapses into pure black. +Override the same tokens inside a `[data-theme="dark"]` block (see §0). Dark +is a neutral gray ramp derived from black (`#0a0a0a → #0e0e0e → #111111 → +#171717` — no blue cast), white-alpha component fills +(`rgba(255,255,255,0.055 → 0.12)`), neutral light ink (`#ededed → #a6a6a6 → +#6f6f6f`), and the accent swapped to electric blue (`#28a8f7`) — which also +tints `surface-selected`. Same structural logic; the blue lives only in +state, never in the grays. To follow the OS, set the attribute on load: @@ -587,102 +781,141 @@ document.documentElement.dataset.theme = isDark ? 'dark' : 'light' --- -## 4. The "lines define structure" rule (key composition pattern) +## 4. The "surfaces, not borders" rule (key composition pattern) + +> **A one-step surface difference defines a region. The system draws no +> lines — exactly two strokes are sanctioned: the focus ring, and the +> `edge` frame around the floating workspace panels.** + +Structure comes from the layered surface ramp (§3): a new region means a new +background step, never an outline and never a divider. Controls are +alpha-gray fills; rows separate by their hover/selected fills; regions +separate by base-layer steps; overlays separate by `panel-raised` + +`shadow-floating`. + +Two strokes are allowed, each with one job: -> **Borders define every container. Add a border before reaching for a -> background fill.** +1. **Focus** — a focused field swaps its (transparent) border to + `rule-focus` and gains a soft 3px accent ring; keyboard focus on + buttons uses the same `ring-rule-focus`. +2. **Panel edges** — the main workspace panels (each + workspace-tab column) float on the canvas as + `rounded-sm border border-edge bg-panel` with 6px gutters; the `edge` + stroke is a VERY subtle ink-alpha frame that keeps a panel readable + against the canvas. It is never used inside a panel — interior + hierarchy stays fill-only. -This is the inverse of a surface-stacking system: the schematic is held -together by 1px ink rules. A new line means a new container. Cards, cells, -panels, code blocks, and inputs are all defined by `border border-rule` — not -by background steps. +Nothing else in the chrome may draw a line. (Data visualizations are +exempt: charts may draw connector and grid lines with explicit alpha-ink +fills, e.g. `bg-ink/15` elbows and `bg-ink/8` time-grid guides in the +trace timeline.) ### Pattern ```tsx -
+{title ? ( -+{title}) : null} @@ -1108,8 +1363,9 @@ export function TerminalRow({ ### `Trace` Header strip + a list of trace rows with a `StatusDot`, op label, duration, -and a label-caps status. A waterfall of horizontal bars follows: `rule-2` -background, `ink` fill, `alert` fill for error spans. +and a label-caps status. A waterfall of horizontal bars follows: `surface` +track, `ink` fill, `alert` fill for error spans. Rows separate by spacing — +no dividers. > `TraceStatus` is the consumer's own domain type. The shape assumed here is > `{ id, op, durationMs, status, startMs?, spanMs? }`. Replace with whatever @@ -1140,9 +1396,9 @@ interface TraceProps { const statusTone: Record< TraceStatus, - { dot: 'accent' | 'warn' | 'alert'; label: string; bar: string } + { dot: 'ok' | 'warn' | 'alert'; label: string; bar: string } > = { - ok: { dot: 'accent', label: 'text-accent', bar: 'bg-ink' }, + ok: { dot: 'ok', label: 'text-ok', bar: 'bg-ink' }, warn: { dot: 'warn', label: 'text-warn', bar: 'bg-warn' }, err: { dot: 'alert', label: 'text-alert', bar: 'bg-alert' }, } @@ -1154,11 +1410,11 @@ export function Trace({ title, rows, totalMs, className }: TraceProps) { ...rows.map((r) => (r.startMs ?? 0) + (r.spanMs ?? r.durationMs)), ) return ( --++{title}-+
{rows.map((row) => { const tone = statusTone[row.status] const start = ((row.startMs ?? 0) / span) * 100 @@ -1181,7 +1437,7 @@ export function Trace({ title, rows, totalMs, className }: TraceProps) { > {row.status} -
++{title ? ({title} @@ -1232,8 +1489,8 @@ export function Cell({ title, children, className }: CellProps) { 400px-wide ticker card with a name + version row, description, a `panel`-tinted command block, and a footer with a kind tag and check icon. -Focused state switches body fill from `bg` to `panel` and grows a -`border-l-2 border-l-accent` rail on the left edge. +Focused state switches the body fill from `surface` to `surface-selected` +(the accent tint) — no rail, no outline. ```tsx import * as React from 'react' @@ -1261,12 +1518,12 @@ export function WorkerCard({ return (- + {name}@@ -1277,10 +1534,10 @@ export function WorkerCard({{description}-+{command}-