diff --git a/CLAUDE.md b/CLAUDE.md index 5c13967..67a00ed 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,18 @@ scarf/scarf/ Xcode project root (PBXFileSystemSynchronizedRootGroup - **Sandbox disabled**: App reads `~/.hermes/` directly. - **Swift 6 concurrency**: `@MainActor` default. Services use `nonisolated` + async/await. +## Design System (ScarfDesign) + +All app UI uses the typed token bundle in [scarf/Packages/ScarfDesign/](scarf/Packages/ScarfDesign/) — both the `scarf` and `scarf mobile` targets `import ScarfDesign`. Reach for these tokens before inventing new colors, fonts, or spacings. + +- **Colors**: `ScarfColor.accent`, `.foregroundPrimary/Muted/Faint`, `.backgroundPrimary/Secondary/Tertiary`, `.border/.borderStrong`, `.success/.danger/.warning/.info`, `.Tool.bash/edit/search/web/think`. All resolve from `ScarfBrand.xcassets` and adapt light/dark automatically. +- **Typography**: `.scarfStyle(.title2)`, `.scarfStyle(.body)`, `.scarfStyle(.captionUppercase)`, etc. Use these instead of `.font(.system(...))`. Eleven preset styles cover the type scale. +- **Spacing / radius / shadow**: `ScarfSpace.s1...s10` (4/8/12/16/20/24/32/40), `ScarfRadius.sm/md/lg/xl/xxl/pill`, `.scarfShadow(.sm/.md/.lg/.xl)`. Hardcoded `.padding(12)` or `cornerRadius: 8` is a code smell — convert. +- **Components**: `ScarfPageHeader("Title", subtitle: "...") { trailing }`, `ScarfCard { ... }`, `ScarfBadge("text", kind: .success)`, `ScarfTextField`, `ScarfSectionHeader`, `ScarfDivider`, `ScarfPrimaryButton/SecondaryButton/GhostButton/DestructiveButton` (apply with `.buttonStyle(...)`). +- **App icon + accent**: `Assets.xcassets/AppIcon.appiconset/` is the rust set; `Assets.xcassets/AccentColor.colorset` resolves `Color.accentColor` to rust so any unmigrated SwiftUI control still tints correctly. +- **Reference**: full screen mockups live at `design/static-site/ui-kit/*.jsx` (open `design/static-site/index.html` in a browser). The `ScarfChatView.ChatRootView` reference component in the package is a 3-pane chat redesign target — usable for previews but not yet swapped into the live chat (the existing `RichChatView` machinery still owns the real ACP pipeline). +- **Don't**: introduce purple/violet tones (we shifted to rust); use yellow `#F0AD4E` for success (that's `.warning` — `.success` is green); bypass the type scale with `.font(.system(size: 13.5))`; ship terminal/syntax-highlight palettes through ScarfColor (those are content semantics, keep them inline). + ## Key Paths - Hermes home: `~/.hermes/` diff --git a/design/Assets.xcassets/AppIcon-iOS.appiconset/Contents.json b/design/Assets.xcassets/AppIcon-iOS.appiconset/Contents.json new file mode 100644 index 0000000..ce12503 --- /dev/null +++ b/design/Assets.xcassets/AppIcon-iOS.appiconset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "filename" : "Scarf-AppIcon-iOS-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { "author" : "xcode", "version" : 1 } +} diff --git a/design/Assets.xcassets/AppIcon-iOS.appiconset/Scarf-AppIcon-iOS-1024.png b/design/Assets.xcassets/AppIcon-iOS.appiconset/Scarf-AppIcon-iOS-1024.png new file mode 100644 index 0000000..e16b745 Binary files /dev/null and b/design/Assets.xcassets/AppIcon-iOS.appiconset/Scarf-AppIcon-iOS-1024.png differ diff --git a/design/Assets.xcassets/Contents.json b/design/Assets.xcassets/Contents.json new file mode 100644 index 0000000..d8b757a --- /dev/null +++ b/design/Assets.xcassets/Contents.json @@ -0,0 +1,3 @@ +{ + "info" : { "author" : "xcode", "version" : 1 } +} diff --git a/design/static-site/README.md b/design/static-site/README.md new file mode 100644 index 0000000..cc02cb5 --- /dev/null +++ b/design/static-site/README.md @@ -0,0 +1,52 @@ +# Scarf Design System — static site + +A self-contained, offline-friendly site that browses every artifact in the +Scarf design system. Open `index.html` directly in any browser — no server, +no build step. + +## What's here + +``` +static-site/ +├── index.html ← landing page, links into everything +├── colors_and_type.css ← shared design tokens (referenced everywhere) +│ +├── ui-kit/ ← interactive macOS UI kit +│ ├── index.html ← click-thru of every screen in the app +│ └── *.jsx ← React components (Sidebar, Chat, Dashboard…) +│ +├── tokens/ ← design-system cards +│ ├── _preview.css ← shared card styling +│ ├── colors-*.html ← brand / neutrals / semantic / tool-kinds +│ ├── type-*.html ← display / body / mono +│ ├── spacing-*.html ← scale / radii / shadows +│ ├── components-*.html ← buttons / forms / sidebar / cards / chat / composer / tool-call +│ ├── iconography.html +│ └── brand-mark.html +│ +└── assets/ ← icons, brand artwork +``` + +## How to use it + +- **Browse offline**: double-click `index.html`. Everything renders locally; + the only network dependency is Google Fonts (Inter + JetBrains Mono). +- **Host as a site**: drop the whole folder onto any static host (Netlify, + GitHub Pages, S3, your own nginx). Nothing needs building. +- **Embed in a doc**: link individual cards directly, e.g. + `static-site/tokens/colors-brand.html`. +- **Show the macOS app**: `static-site/ui-kit/index.html` runs the full + React-based interactive kit (single self-contained file — works from + `file://`, no server needed). The traffic-light corner makes it look like + the real app. Source components live alongside as `*.jsx` for editing — + re-bundle into `index.html` when you change them. + +## Notes + +- The kit's `index.html` is a self-contained bundle — React, Babel, Lucide + and every component are inlined, so it works from `file://` with no + network. The original split-file source is preserved as + `ui-kit/index.source.html` next to the `.jsx` files for editing. +- The font import in `colors_and_type.css` (`fonts.googleapis.com`) is the + only other network call. Replace with locally-served WOFF2 if you need + airgapped use. diff --git a/design/static-site/assets/scarf-app-icon-1024.png b/design/static-site/assets/scarf-app-icon-1024.png new file mode 100644 index 0000000..e16b745 Binary files /dev/null and b/design/static-site/assets/scarf-app-icon-1024.png differ diff --git a/design/static-site/assets/scarf-app-icon-128.png b/design/static-site/assets/scarf-app-icon-128.png new file mode 100644 index 0000000..73107e2 Binary files /dev/null and b/design/static-site/assets/scarf-app-icon-128.png differ diff --git a/design/static-site/assets/scarf-app-icon-256.png b/design/static-site/assets/scarf-app-icon-256.png new file mode 100644 index 0000000..f0bceae Binary files /dev/null and b/design/static-site/assets/scarf-app-icon-256.png differ diff --git a/design/static-site/assets/scarf-app-icon-512.png b/design/static-site/assets/scarf-app-icon-512.png new file mode 100644 index 0000000..2b21938 Binary files /dev/null and b/design/static-site/assets/scarf-app-icon-512.png differ diff --git a/design/static-site/assets/scarf-icon-flame-1024.png b/design/static-site/assets/scarf-icon-flame-1024.png new file mode 100644 index 0000000..182e9f4 Binary files /dev/null and b/design/static-site/assets/scarf-icon-flame-1024.png differ diff --git a/design/static-site/assets/scarf-icon-flame-256.png b/design/static-site/assets/scarf-icon-flame-256.png new file mode 100644 index 0000000..b32ddec Binary files /dev/null and b/design/static-site/assets/scarf-icon-flame-256.png differ diff --git a/design/static-site/assets/scarf-icon-geo-1024.png b/design/static-site/assets/scarf-icon-geo-1024.png new file mode 100644 index 0000000..a2e75e1 Binary files /dev/null and b/design/static-site/assets/scarf-icon-geo-1024.png differ diff --git a/design/static-site/assets/scarf-icon-geo-256.png b/design/static-site/assets/scarf-icon-geo-256.png new file mode 100644 index 0000000..829a251 Binary files /dev/null and b/design/static-site/assets/scarf-icon-geo-256.png differ diff --git a/design/static-site/assets/scarf-icon-horizon-1024.png b/design/static-site/assets/scarf-icon-horizon-1024.png new file mode 100644 index 0000000..0bfe8f3 Binary files /dev/null and b/design/static-site/assets/scarf-icon-horizon-1024.png differ diff --git a/design/static-site/assets/scarf-icon-horizon-256.png b/design/static-site/assets/scarf-icon-horizon-256.png new file mode 100644 index 0000000..0aff12e Binary files /dev/null and b/design/static-site/assets/scarf-icon-horizon-256.png differ diff --git a/design/static-site/assets/scarf-icon.png b/design/static-site/assets/scarf-icon.png new file mode 100644 index 0000000..2eaf0bb Binary files /dev/null and b/design/static-site/assets/scarf-icon.png differ diff --git a/design/static-site/colors_and_type.css b/design/static-site/colors_and_type.css new file mode 100644 index 0000000..f55e07d --- /dev/null +++ b/design/static-site/colors_and_type.css @@ -0,0 +1,193 @@ +/* Scarf Design System — colors + type tokens. v2 (amber→rust) + * + * Light/dark via [data-theme="dark"] override on a parent. Default light. + * + * v2 changes: brand shifted from purple to a tri-stop amber→rust gradient. + * Neutrals warmed (yellow undertone). Semantic green/blue/red/orange preserved + * — those still mean success/info/danger and remain the tool-kind colors in chat. + */ + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap'); + +:root { + /* ───── Brand — amber → rust ───── */ + --brand-50: #FBF1E8; + --brand-100: #F6E0CB; + --brand-200: #EFC59E; /* highlight stop in tri-gradient */ + --brand-300: #E89360; /* gradient start */ + --brand-400: #D87844; + --brand-500: #C25A2A; /* primary accent — Scarf Rust */ + --brand-600: #A6481E; + --brand-700: #7A2E14; /* gradient end */ + --brand-800: #5C220F; + --brand-900: #3B1608; + + /* ───── Neutrals (warm, slight amber tint) ───── */ + --gray-0: #FFFFFF; + --gray-50: #FBF9F6; + --gray-100: #F4F1EC; + --gray-200: #EAE5DD; + --gray-300: #D8D1C5; + --gray-400: #B5ABA0; + --gray-500: #8C857B; + --gray-600: #6A645B; + --gray-700: #4A463F; + --gray-800: #2D2A25; + --gray-900: #1A1814; + --gray-950: #100E0B; + + /* ───── Semantic palette ───── */ + --green-500: #2AA876; + --green-600: #1F7F5A; + --green-100: #D8F0E5; + --red-500: #D9534F; + --red-600: #B83C38; + --red-100: #F8DAD8; + --orange-500: #F0AD4E; /* reasoning / warning — distinct from brand rust */ + --orange-100: #FCEAD0; + --blue-500: #3498DB; + --blue-100: #D8ECF8; + --indigo-500: #5B6CD9; + --purple-tool-500: #8E5BC9; + + /* ───── Surfaces (light) ───── */ + --fg: var(--gray-900); + --fg-muted: var(--gray-600); + --fg-faint: var(--gray-500); + --bg: var(--gray-50); + --bg-card: var(--gray-0); + --bg-quaternary: rgba(45, 42, 37, 0.04); + --bg-tertiary: rgba(45, 42, 37, 0.07); + --border: rgba(45, 42, 37, 0.08); + --border-strong: rgba(45, 42, 37, 0.14); + + /* ───── Brand tokens (semantic) ───── */ + --accent: var(--brand-500); + --accent-hover: var(--brand-600); + --accent-active: var(--brand-700); + --accent-tint: rgba(194, 90, 42, 0.10); + --accent-tint-strong: rgba(194, 90, 42, 0.18); + --on-accent: #FFFFFF; + + /* ───── Type stacks ───── */ + --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", "Segoe UI", Roboto, sans-serif; + --font-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", "Segoe UI", sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace; + + /* ───── Type scale ───── */ + --text-caption2: 10px; + --text-caption: 12px; + --text-footnote: 13px; + --text-body: 14px; + --text-callout: 15px; + --text-subhead: 16px; + --text-headline: 17px; + --text-title3: 20px; + --text-title2: 22px; + --text-title1: 28px; + --text-largeTitle: 34px; + + --leading-tight: 1.2; + --leading-snug: 1.35; + --leading-normal: 1.5; + --leading-relaxed: 1.6; + + --weight-regular: 400; + --weight-medium: 500; + --weight-semibold: 600; + --weight-bold: 700; + + /* ───── Radii / spacing / shadow ───── */ + --r-sm: 4px; + --r-md: 6px; + --r-lg: 8px; + --r-xl: 12px; + --r-2xl: 14px; + --r-pill: 999px; + + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + --space-10: 40px; + + --shadow-sm: 0 1px 2px rgba(45, 42, 37, 0.05); + --shadow-md: 0 1px 2px rgba(45, 42, 37, 0.04), 0 4px 12px rgba(45, 42, 37, 0.04); + --shadow-lg: 0 2px 4px rgba(45, 42, 37, 0.06), 0 8px 24px rgba(45, 42, 37, 0.07); + --shadow-xl: 0 4px 8px rgba(45, 42, 37, 0.08), 0 16px 40px rgba(45, 42, 37, 0.10); + --shadow-focus: 0 0 0 3px rgba(194, 90, 42, 0.28); + + --gradient-brand: linear-gradient(135deg, #E89360 0%, #C25A2A 50%, #7A2E14 100%); + --gradient-brand-soft: linear-gradient(135deg, #F6E0CB 0%, #EFC59E 100%); + + --ease-smooth: cubic-bezier(0.32, 0.72, 0, 1); + --dur-fast: 120ms; + --dur-base: 200ms; + --dur-slow: 300ms; +} + +[data-theme="dark"] { + --fg: #EDE8E0; + --fg-muted: #A39C92; + --fg-faint: #756F66; + --bg: #15130F; + --bg-card: #1F1C18; + --bg-quaternary: rgba(255, 248, 235, 0.05); + --bg-tertiary: rgba(255, 248, 235, 0.08); + --border: rgba(255, 248, 235, 0.08); + --border-strong: rgba(255, 248, 235, 0.14); + + --accent: #E89360; + --accent-hover: #F0A879; + --accent-active: #D87844; + --accent-tint: rgba(232, 147, 96, 0.14); + --accent-tint-strong: rgba(232, 147, 96, 0.24); + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35); + --shadow-md: 0 1px 2px rgba(0, 0, 0, 0.35), 0 4px 12px rgba(0, 0, 0, 0.35); + --shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.45), 0 8px 24px rgba(0, 0, 0, 0.45); +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --fg: #EDE8E0; + --fg-muted: #A39C92; + --fg-faint: #756F66; + --bg: #15130F; + --bg-card: #1F1C18; + --bg-quaternary: rgba(255, 248, 235, 0.05); + --bg-tertiary: rgba(255, 248, 235, 0.08); + --border: rgba(255, 248, 235, 0.08); + --border-strong: rgba(255, 248, 235, 0.14); + + --accent: #E89360; + --accent-hover: #F0A879; + --accent-active: #D87844; + --accent-tint: rgba(232, 147, 96, 0.14); + --accent-tint-strong: rgba(232, 147, 96, 0.24); + } +} + +/* ───── Semantic type rules ───── */ +body, .scarf-body { + font-family: var(--font-sans); + font-size: var(--text-body); + line-height: var(--leading-normal); + color: var(--fg); + background: var(--bg); + -webkit-font-smoothing: antialiased; +} + +.scarf-h1 { font-family: var(--font-display); font-size: var(--text-largeTitle); font-weight: 600; line-height: 1.2; letter-spacing: -0.02em; } +.scarf-h2 { font-family: var(--font-display); font-size: var(--text-title1); font-weight: 600; line-height: 1.2; letter-spacing: -0.015em; } +.scarf-h3 { font-family: var(--font-display); font-size: var(--text-title2); font-weight: 600; line-height: 1.35; letter-spacing: -0.01em; } +.scarf-headline { font-family: var(--font-sans); font-size: var(--text-headline); font-weight: 600; line-height: 1.35; } +.scarf-subhead { font-family: var(--font-sans); font-size: var(--text-subhead); font-weight: 500; line-height: 1.35; } +.scarf-body-text { font-family: var(--font-sans); font-size: var(--text-body); line-height: 1.5; } +.scarf-caption { font-family: var(--font-sans); font-size: var(--text-caption); line-height: 1.5; color: var(--fg-muted); } +.scarf-caption-strong { font-family: var(--font-sans); font-size: var(--text-caption); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: var(--fg-muted); } +.scarf-mono { font-family: var(--font-mono); font-size: 0.92em; } +.scarf-code { font-family: var(--font-mono); font-size: 0.9em; background: var(--bg-quaternary); padding: 1px 5px; border-radius: var(--r-sm); color: var(--fg); } diff --git a/design/static-site/index.html b/design/static-site/index.html new file mode 100644 index 0000000..8359017 --- /dev/null +++ b/design/static-site/index.html @@ -0,0 +1,382 @@ + + + + + + Scarf Design System + + + + + +
+ +
+ +
+

Scarf Design System

+

A native macOS & iOS companion for the Hermes AI agent — calm, confident, and rust-warm. This site documents the palette, type, components, and screens.

+
+
+ + +
UI Kit
+ +
+

Interactive macOS app

+

Click through every screen — Dashboard, Sessions, Insights, Projects, Chat, Settings, Tools, MCP servers, Cron, Logs, Memory, Activity, Health and more. Faithful to the real Scarf macOS app, with a working sidebar and the rust palette throughout.

+ + Open the kit + + +
+
+ +
+
+ + +
Tokens & components
+

Foundations

+

Each tile opens a single design-system card. They're sized for ~700px wide and render one concept at a time.

+ +
+ +
Color
+

Brand — amber → rust

+

The 9-step rust ramp. Primary accent is #C25A2A.

+
+
+
+
+
+
+
+
+
+ + +
Color
+

Warm neutrals

+

Slight amber undertone — never cool grey. 11 steps for surfaces and text.

+
+
+
+
+
+
+
+
+
+ + +
Color
+

Semantic palette

+

Success, danger, warning, info — preserved from system conventions.

+
+
+
+
+
+
+
+ + +
Color
+

Tool-kind palette

+

Bash, edit, search, web, think — the per-tool decorations in chat.

+
+
+
+
+
+
+
+
+ + +
Type
+

Display scale

+

Large titles & headlines — SF Pro Display, tight tracking.

+
+ + +
Type
+

Body scale

+

14px base, the working text of the app.

+
+ + +
Type
+

Mono

+

SF Mono — for transcripts, paths, command output.

+
+ + +
Layout
+

Spacing scale

+

4 / 8 / 12 / 16 / 20 / 24 / 32 / 40 — that's the whole grid.

+
+ + +
Layout
+

Radii

+

4 / 6 / 8 / 12 / 14 / pill — tuned for native macOS controls.

+
+ + +
Layout
+

Shadows

+

Four elevation tiers, all on a warm-black tint.

+
+ + +
Brand
+

Iconography

+

Lucide icons at 16/18/20/24, 1.6px stroke, currentColor.

+
+ + +
Brand
+

App mark

+

The flowing-silk icon — preferred backgrounds & minimum sizes.

+
+
+ +

Components

+

Composable pieces lifted directly from the macOS app's surfaces.

+ +
+ +
Component
+

Buttons

+

Primary / secondary / ghost / destructive — three sizes each.

+
+ +
Component
+

Forms

+

Text fields, toggles, selects — with focus & error states.

+
+ +
Component
+

Sidebar

+

Section headers, items, active state, count pills.

+
+ +
Component
+

Stat cards

+

Number-forward dashboard tiles.

+
+ +
Component
+

Status cards

+

Connection / health / run cards with semantic dots.

+
+ +
Component
+

Chat bubbles

+

User & agent rich messages, avatars, timestamps.

+
+ +
Component
+

Composer

+

Multiline input with attachments & tool toggles.

+
+ +
Component
+

Tool-call card

+

Inline transcript card showing what the agent did.

+
+
+ + +
+ + diff --git a/design/static-site/tokens/_preview.css b/design/static-site/tokens/_preview.css new file mode 100644 index 0000000..de2cd19 --- /dev/null +++ b/design/static-site/tokens/_preview.css @@ -0,0 +1,44 @@ +/* Shared styling for design-system preview cards. + Each card is sized for ~700px wide and renders one focused concept. */ +@import url('../colors_and_type.css'); + +* { box-sizing: border-box; } +html, body { margin: 0; padding: 0; } +body { + background: var(--bg); + color: var(--fg); + font-family: var(--font-sans); + font-size: var(--text-body); + line-height: var(--leading-normal); + -webkit-font-smoothing: antialiased; +} + +.card-root { + padding: 20px 24px; + min-height: 110px; + display: flex; + flex-direction: column; + gap: 12px; +} +.row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } +.col { display: flex; flex-direction: column; gap: 8px; } +.label { font-size: 11px; color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; } +.mono { font-family: var(--font-mono); font-size: 11px; color: var(--fg-muted); } + +/* swatches */ +.swatch { + width: 92px; + height: 64px; + border-radius: 8px; + border: 1px solid var(--border); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 6px 8px; + position: relative; + overflow: hidden; +} +.swatch .name { font-size: 10px; font-weight: 600; } +.swatch .hex { font-family: var(--font-mono); font-size: 10px; opacity: 0.85; } +.swatch.dark-text { color: var(--gray-900); } +.swatch.light-text { color: #fff; } diff --git a/design/static-site/tokens/brand-mark.html b/design/static-site/tokens/brand-mark.html new file mode 100644 index 0000000..18bf651 --- /dev/null +++ b/design/static-site/tokens/brand-mark.html @@ -0,0 +1,13 @@ +Brand mark + + +
+ Scarf icon +
+
Scarf
+
A native macOS GUI for the Hermes AI agent. Full visibility into what an autonomous agent is doing, when, and what it creates.
+
brand: white silk on lavender → magenta gradient
+
+
+ diff --git a/design/static-site/tokens/colors-brand.html b/design/static-site/tokens/colors-brand.html new file mode 100644 index 0000000..5c58fd8 --- /dev/null +++ b/design/static-site/tokens/colors-brand.html @@ -0,0 +1,17 @@ +Primary palette + + +
+
Brand · Scarf Purple
+
+
50
#F5F0FA
+
100
#EADDF3
+
200
#D4B8E8
+
300
#B288D9
+
500 ★
#8B5BB8
+
600
#7848A0
+
800
#4D2C68
+
+
★ var(--accent) · used for primary buttons, focused borders, active sidebar items
+
+ diff --git a/design/static-site/tokens/colors-neutrals.html b/design/static-site/tokens/colors-neutrals.html new file mode 100644 index 0000000..d3997c7 --- /dev/null +++ b/design/static-site/tokens/colors-neutrals.html @@ -0,0 +1,21 @@ +Neutral palette + + +
+
Neutrals · warm-cool gray scale
+
+
0
#FFFFFF
+
50
#FAFAFB
+
100
#F3F2F5
+
200
#E8E6EC
+
300
#D6D3DC
+
400
#B5B1BD
+
500
#8C8893
+
600
#6A666F
+
700
#4A464E
+
800
#2E2C32
+
900
#1A181E
+
+
slight violet tint — bg=50, bg-card=0, fg=900, fg-muted=600
+
+ diff --git a/design/static-site/tokens/colors-semantic.html b/design/static-site/tokens/colors-semantic.html new file mode 100644 index 0000000..f49cf06 --- /dev/null +++ b/design/static-site/tokens/colors-semantic.html @@ -0,0 +1,19 @@ +Semantic colors + + +
+
Semantic · status & feedback
+
+
success
#2AA876
+
danger
#D9534F
+
warning
#F0AD4E
+
info
#3498DB
+
+
+ ● Running + ● Error + ● Reasoning + ● Model +
+
+ diff --git a/design/static-site/tokens/colors-tool-kinds.html b/design/static-site/tokens/colors-tool-kinds.html new file mode 100644 index 0000000..c7e99e0 --- /dev/null +++ b/design/static-site/tokens/colors-tool-kinds.html @@ -0,0 +1,16 @@ +Tool-kind colors + + +
+
Tool-kind colors · agent activity
+
+
read
green
+
edit
blue
+
execute
orange
+
fetch
purple
+
browser
indigo
+
other
gray
+
+
preserved verbatim from ToolCallCard.swift — semantic to the product
+
+ diff --git a/design/static-site/tokens/components-buttons.html b/design/static-site/tokens/components-buttons.html new file mode 100644 index 0000000..7c0fe5f --- /dev/null +++ b/design/static-site/tokens/components-buttons.html @@ -0,0 +1,31 @@ +Buttons + + + +
+
Buttons
+
+ + + + + +
+
+ + + +
+
+ diff --git a/design/static-site/tokens/components-chat-bubbles.html b/design/static-site/tokens/components-chat-bubbles.html new file mode 100644 index 0000000..186ba26 --- /dev/null +++ b/design/static-site/tokens/components-chat-bubbles.html @@ -0,0 +1,15 @@ +Chat bubbles + + +
+
+
What's the status of the cron job?
+
+
9:42 AM
+
+
▾ Reasoning (127 tokens)
+ The daily-summary job ran 14 minutes ago and completed successfully. +
+
284 tokens · stop · 9:42 AM
+
+ diff --git a/design/static-site/tokens/components-composer.html b/design/static-site/tokens/components-composer.html new file mode 100644 index 0000000..6520cd3 --- /dev/null +++ b/design/static-site/tokens/components-composer.html @@ -0,0 +1,12 @@ +Composer + + +
+
+
+
Message Hermes…
+
+
+
Rich Chat composer · /-menu opens above on slash, Shift+Enter for newline
+
+ diff --git a/design/static-site/tokens/components-forms.html b/design/static-site/tokens/components-forms.html new file mode 100644 index 0000000..40b583a --- /dev/null +++ b/design/static-site/tokens/components-forms.html @@ -0,0 +1,26 @@ +Form inputs + + + +
+
+
+
+
+
+
Auto-update
+
Pause cron
+
Verified
+
Local
+
+
+ diff --git a/design/static-site/tokens/components-sidebar.html b/design/static-site/tokens/components-sidebar.html new file mode 100644 index 0000000..8865f32 --- /dev/null +++ b/design/static-site/tokens/components-sidebar.html @@ -0,0 +1,25 @@ +Sidebar + + + +
+
+
Monitor
+
Dashboard
+
📊Insights
+
💬Sessions
+
Interact
+
Chat
+
Memory
+
Skills
+
+
+ diff --git a/design/static-site/tokens/components-stat-cards.html b/design/static-site/tokens/components-stat-cards.html new file mode 100644 index 0000000..0266dca --- /dev/null +++ b/design/static-site/tokens/components-stat-cards.html @@ -0,0 +1,18 @@ +Stat cards + + + +
+
+
847
Sessions
+
12,394
Messages
+
3,221
Tool Calls
+
2.4M
Tokens
+
$42.18
Cost
+
+
+ diff --git a/design/static-site/tokens/components-status-cards.html b/design/static-site/tokens/components-status-cards.html new file mode 100644 index 0000000..c1f7228 --- /dev/null +++ b/design/static-site/tokens/components-status-cards.html @@ -0,0 +1,19 @@ +Status cards + + + +
+
+
Hermes
Running
+
⌬ Model
claude-sonnet-4.5
+
☁ Provider
Anthropic
+
Gateway
Connected · 3
+
+
Status cards · 4 across at standard width
+
+ diff --git a/design/static-site/tokens/components-tool-call.html b/design/static-site/tokens/components-tool-call.html new file mode 100644 index 0000000..f75205e --- /dev/null +++ b/design/static-site/tokens/components-tool-call.html @@ -0,0 +1,31 @@ +Tool call card + + +
+
+
+ 📖 + read_file + ~/.hermes/config.yaml + + +
+
+
+ + execute + { "cmd": "hermes status" } + + +
+
+
+ + write_file + cron/jobs.json +
+ +
+ +
+ diff --git a/design/static-site/tokens/iconography.html b/design/static-site/tokens/iconography.html new file mode 100644 index 0000000..e9d3cf4 --- /dev/null +++ b/design/static-site/tokens/iconography.html @@ -0,0 +1,24 @@ +Iconography + + + + +
+
Iconography · Lucide (web sub for SF Symbols)
+
+
Dashboard
+
Insights
+
Sessions
+
Model
+
Provider
+
Templates
+
Projects
+
Tools
+
Diagnostics
+
+ +
+ diff --git a/design/static-site/tokens/spacing-radii.html b/design/static-site/tokens/spacing-radii.html new file mode 100644 index 0000000..dc21ec4 --- /dev/null +++ b/design/static-site/tokens/spacing-radii.html @@ -0,0 +1,14 @@ +Radii + + +
+
Radii · 4 / 6 / 8 / 12 / 14
+
+
4 · chips, code
+
6 · tool cards
+
8 · cards, btns
+
12 · bubbles
+
14 · windows
+
+
+ diff --git a/design/static-site/tokens/spacing-scale.html b/design/static-site/tokens/spacing-scale.html new file mode 100644 index 0000000..9ae83ab --- /dev/null +++ b/design/static-site/tokens/spacing-scale.html @@ -0,0 +1,16 @@ +Spacing scale + + +
+
Spacing · 4-base scale
+
+
4 · 1 · inline gaps
+
8 · 2 · button padding y
+
12 · 3 · card padding
+
16 · 4 · view padding
+
20 · 5 · section gap
+
24 · 6 · header gap
+
32 · 8 · page-level
+
+
+ diff --git a/design/static-site/tokens/spacing-shadows.html b/design/static-site/tokens/spacing-shadows.html new file mode 100644 index 0000000..1f62be0 --- /dev/null +++ b/design/static-site/tokens/spacing-shadows.html @@ -0,0 +1,13 @@ +Shadows + + +
+
Shadows · two-layer Apple style
+
+
sm · subtle lift
+
md · cards
+
lg · hover
+
xl · sheet
+
+
+ diff --git a/design/static-site/tokens/type-body.html b/design/static-site/tokens/type-body.html new file mode 100644 index 0000000..619848f --- /dev/null +++ b/design/static-site/tokens/type-body.html @@ -0,0 +1,11 @@ +Type · body + + +
+
Body · sentence case, calm and direct
+
Hermes actually knows what project it's in
+
Every project-scoped chat gets a Scarf-managed block auto-injected into the project's AGENTS.md before the session starts.
+
Ask the agent "what project am I in?" and it answers with the project name, directory, template id, and registered cron jobs.
+
headline 17 · subhead 15 · body 14 · caption 12 — same rhythm as SwiftUI's text styles
+
+ diff --git a/design/static-site/tokens/type-display.html b/design/static-site/tokens/type-display.html new file mode 100644 index 0000000..e4da0bd --- /dev/null +++ b/design/static-site/tokens/type-display.html @@ -0,0 +1,11 @@ +Type · display + + +
+
Display · SF Pro Display / Inter
+
Make the complex simple
+
Recent sessions
+
Activity patterns
+
largeTitle 34 / title1 28 / title2 22 — used for view titles only
+
+ diff --git a/design/static-site/tokens/type-mono.html b/design/static-site/tokens/type-mono.html new file mode 100644 index 0000000..edfec39 --- /dev/null +++ b/design/static-site/tokens/type-mono.html @@ -0,0 +1,15 @@ +Type · mono + + +
+
Mono · SF Mono / JetBrains Mono
+
claude-haiku-4-5
+
~/.hermes/state.db · 14.2 MB
+
{ "tokens": 2384, "model": "claude-haiku-4-5" }
+
+ v2.3.0 + 2,847 tokens + $0.0421 +
+
+ diff --git a/design/static-site/ui-kit/Activity.jsx b/design/static-site/ui-kit/Activity.jsx new file mode 100644 index 0000000..8d2e778 --- /dev/null +++ b/design/static-site/ui-kit/Activity.jsx @@ -0,0 +1,98 @@ +// Activity — chronological feed of everything that happened recently across +// all projects, sessions, cron, and tools. Day-grouped, filterable. + +const ACTIVITY_GROUPS = [ + { day: 'Today', items: [ + { time: '09:42', icon: 'message-square', tone: 'accent', title: 'Sera — chat session resumed', sub: 'Forge · 14 turns · refactored CronRunner', proj: 'sera' }, + { time: '09:30', icon: 'clock', tone: 'green', title: 'incident-triage ran', sub: 'cron · ok in 4.2s · 0 issues created', proj: '—' }, + { time: '09:00', icon: 'clock', tone: 'green', title: 'daily-summary ran', sub: 'cron · ok in 36s · posted to #standup', proj: '—' }, + { time: '08:42', icon: 'git-pull-request', tone: 'blue', title: 'PR #284 opened', sub: 'sera · "Switch to AbortController for cron timeouts"', proj: 'sera' }, + { time: '08:14', icon: 'shield', tone: 'amber', title: 'Approval: execute git push origin main', sub: 'sera · approved by Aurora · 3.2s wait', proj: 'sera' }, + ]}, + { day: 'Yesterday', items: [ + { time: '17:22', icon: 'check-circle', tone: 'green', title: 'release-notes generated', sub: 'cron · ok in 1m 03s · draft saved', proj: '—' }, + { time: '15:08', icon: 'plug', tone: 'accent', title: 'MCP server connected — Figma', sub: '6 tools, 2 prompts available', proj: '—' }, + { time: '14:31', icon: 'message-square', tone: 'accent', title: 'Hermes — onboarding draft', sub: '8 turns · drafted welcome email', proj: 'hermes' }, + { time: '11:02', icon: 'alert-triangle', tone: 'red', title: 'Tool denied — rm -rf node_modules', sub: 'sera · matched deny rule "rm -rf"', proj: 'sera' }, + { time: '09:00', icon: 'clock', tone: 'green', title: 'daily-summary ran', sub: 'cron · ok in 41s', proj: '—' }, + ]}, + { day: 'Mon, Apr 21', items: [ + { time: '16:48', icon: 'user-plus', tone: 'accent', title: 'New personality — Atlas', sub: 'Created by Aurora · long-form writing model', proj: '—' }, + { time: '14:00', icon: 'database', tone: 'blue', title: 'Postgres (prod, ro) reconfigured', sub: 'switched to read replica', proj: '—' }, + { time: '09:00', icon: 'clock', tone: 'red', title: 'daily-summary failed', sub: 'cron · github 502 bad gateway · retried ok at 09:14', proj: '—' }, + ]}, +]; + +const ACT_TONES = { + accent: { bg: 'var(--accent-tint)', fg: 'var(--accent)' }, + green: { bg: 'var(--green-100)', fg: 'var(--green-600)' }, + blue: { bg: 'var(--blue-100)', fg: 'var(--blue-500)' }, + amber: { bg: 'var(--orange-100)', fg: 'var(--orange-500)' }, + red: { bg: 'var(--red-100)', fg: 'var(--red-500)' }, +}; + +function Activity() { + const [filter, setFilter] = React.useState('all'); + React.useEffect(() => { requestAnimationFrame(() => window.lucide && window.lucide.createIcons()); }); + + return ( +
+ Filter} + right={ + + } /> +
+ {ACTIVITY_GROUPS.map(g => ( +
+
{g.day}
+
+ {g.items.map((it, i) => )} +
+
+ ))} +
+
+ ); +} + +function ActivityRow({ it, last }) { + const tone = ACT_TONES[it.tone]; + const [hover, setHover] = React.useState(false); + return ( +
setHover(true)} onMouseLeave={() => setHover(false)} style={{ + display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', + borderBottom: last ? 'none' : '0.5px solid var(--border)', + background: hover ? 'var(--bg-quaternary)' : 'transparent', cursor: 'pointer', + }}> + {it.time} +
+ +
+
+
{it.title}
+
{it.sub}
+
+ {it.proj !== '—' && {it.proj}} + +
+ ); +} + +window.Activity = Activity; diff --git a/design/static-site/ui-kit/Chat.jsx b/design/static-site/ui-kit/Chat.jsx new file mode 100644 index 0000000..535ffe3 --- /dev/null +++ b/design/static-site/ui-kit/Chat.jsx @@ -0,0 +1,787 @@ +// Chat — three-pane: session list / transcript / inspector. +// Inspector defaults to ToolCall details for the focused tool call; falls +// back to session-level metadata. Transcript supports reasoning, multi-step +// tool calls, file diffs, and a slash-command palette in the composer. + +const TOOL_TONES = { + read: { color: 'var(--green-500)', tint: 'var(--green-100)', icon: 'book-open', label: 'Read' }, + edit: { color: 'var(--blue-500)', tint: 'var(--blue-100)', icon: 'file-edit', label: 'Edit' }, + execute: { color: 'var(--orange-500)', tint: 'var(--orange-100)', icon: 'terminal', label: 'Execute' }, + fetch: { color: 'var(--purple-tool-500)', tint: '#EFE0F8', icon: 'globe', label: 'Fetch' }, + browser: { color: 'var(--indigo-500)', tint: '#E0E5F8', icon: 'compass', label: 'Browser' }, + search: { color: 'var(--accent)', tint: 'var(--accent-tint)',icon: 'search', label: 'Search' }, +}; + +// ─────────────── Top-level Chat ─────────────── +function Chat() { + const [active, setActive] = React.useState('s1'); + const [focused, setFocused] = React.useState({ kind: 'tool', id: 'tc-2' }); // inspector subject + const [composerOpen, setComposerOpen] = React.useState(false); // slash menu + + React.useEffect(() => { + requestAnimationFrame(() => window.lucide && window.lucide.createIcons()); + }); + + const sessions = [ + { id: 's1', title: 'Cron diagnostics', project: 'scarf', preview: 'The daily-summary job ran 14 minutes ago…', time: '14m', model: 'sonnet-4.5', unread: 0, pinned: true, status: 'live' }, + { id: 's2', title: 'Release notes draft', project: 'hermes-blog', preview: 'Pulled the merged PRs from this week…', time: '42m', model: 'haiku-4.5', unread: 2, status: 'idle' }, + { id: 's3', title: 'PR review summary', project: 'hermes-blog', preview: 'Three PRs are ready for review.', time: '2h', model: 'sonnet-4.5', status: 'idle' }, + { id: 's4', title: 'Function calling models', project: '—', preview: 'Sonnet handles structured tool use…', time: '3h', model: 'haiku-4.5', status: 'idle' }, + { id: 's5', title: 'Memory layout question', project: 'scarf', preview: 'The shared memory keys live at…', time: 'yesterday', model: 'sonnet-4.5', status: 'idle' }, + { id: 's6', title: 'Catalog publish flow', project: 'hermes-blog', preview: 'Walked through the .scarftemplate bundle…', time: 'yesterday', model: 'sonnet-4.5', status: 'idle' }, + { id: 's7', title: 'SSH tunnel debug', project: 'scarf-remote', preview: 'Connection drops after ~90s of idle…', time: 'Mon', model: 'sonnet-4.5', status: 'error' }, + ]; + + return ( +
+ + + +
+ ); +} + +// ─────────────── Pane 1 — session list ─────────────── +function ChatList({ sessions, active, setActive }) { + const [filter, setFilter] = React.useState('all'); + return ( +
+
+
Chats
+ + New +
+ +
+ +
+ +
+ Today + {sessions.slice(0, 4).map(s => setActive(s.id)} />)} + Earlier + {sessions.slice(4).map(s => setActive(s.id)} />)} +
+ +
+ + {sessions.length} chats + 1.2 MB · state.db +
+
+ ); +} + +function SessionGroupHeader({ children }) { + return ( +
{children}
+ ); +} + +function SessionRow({ s, active, onClick }) { + const [hover, setHover] = React.useState(false); + const statusColor = s.status === 'live' ? 'var(--green-500)' : s.status === 'error' ? 'var(--red-500)' : 'var(--gray-400)'; + return ( +
setHover(true)} onMouseLeave={() => setHover(false)} + style={{ + padding: '8px 10px', borderRadius: 7, cursor: 'pointer', marginBottom: 1, + background: active ? 'var(--accent-tint)' : (hover ? 'var(--bg-quaternary)' : 'transparent'), + position: 'relative', + }}> +
+ {s.status === 'live' + ? + : } + {s.pinned && } +
{s.title}
+
{s.time}
+
+
+ {s.project !== '—' && {s.project}} +
{s.preview}
+ {s.unread > 0 && {s.unread}} +
+
+ ); +} + +// ─────────────── Pane 2 — transcript ─────────────── +function Transcript({ focused, setFocused, composerOpen, setComposerOpen }) { + return ( +
+ +
+ + Today · 9:42 AM + + What's the status of the daily-summary cron job? I need to know if it's healthy before I push the new schedule changes. + + + + + +

+ The daily-summary job ran 14 minutes ago and completed + successfully in 14.2 s, using 1,847 tokens. Next run is scheduled for tomorrow at 09:00 — safe to ship the schedule changes. +

+ +
+ + Show me what it produced. + + + +

The latest summary covers April 24, 2026. Highlights:

+
    +
  • 3 PRs merged across hermes and scarf
  • +
  • 2 cron failures auto-recovered (gateway timeouts)
  • +
  • Token spend down 8% week-over-week
  • +
+ +
+ + +
+ + +
+ ); +} + +function TranscriptHeader() { + return ( +
+
+
+ +
Cron diagnostics
+ live +
+
+ + + scarf + + · + claude-sonnet-4.5 + · + 14 messages + · + 12,847 tok + · + $0.0421 +
+
+ Branch + Share + +
+ ); +} + +function DateMarker({ children }) { + return ( +
+
+ {children} +
+
+ ); +} + +const msgPara = { fontSize: 14, lineHeight: 1.55, color: 'var(--fg)', margin: '6px 0' }; +const inlineCode = { fontFamily: 'var(--font-mono)', fontSize: 12.5, + background: 'var(--bg-quaternary)', padding: '1px 5px', borderRadius: 4 }; + +function UserMsg({ time, children }) { + return ( +
+
{children}
+
+ + {time} +
+
+ ); +} + +function AssistantMsg({ time, tokens, model, inProgress, durationMs, children }) { + return ( +
+
+
+ +
+
+
{children}
+
+ {inProgress && + + thinking… + } + {model} + · + {tokens} tok + · + {(durationMs / 1000).toFixed(1)}s + · + {time} +
+
+
+
+ ); +} + +function MsgFooter() { + const Btnn = ({ icon, label }) => { + const [hover, setHover] = React.useState(false); + return ( + + ); + }; + return ( +
+ + + + +
+ +
+ ); +} + +// ─────────────── Reasoning disclosure ─────────────── +function Reasoning({ tokens, preview, children }) { + const [open, setOpen] = React.useState(false); + return ( +
+
setOpen(!open)} style={{ + cursor: 'pointer', fontSize: 11, fontWeight: 600, + display: 'flex', alignItems: 'center', gap: 5, color: '#A8741F', + }}> + + Reasoning + · {tokens} tok + + +
+ {!open && preview && ( +
{preview}
+ )} + {open && ( +
+ The user wants the status of a specific cron job named "daily-summary". + I should check the cron registry first, then look at the most recent execution + via hermes cron status. If exit_code is 0, + the job is healthy and the schedule push is safe. +
+ )} +
+ ); +} + +// ─────────────── ToolCall card ─────────────── +function ToolCall({ id, kind, name, arg, duration, expanded: initial, diff, focus, setFocus }) { + const [open, setOpen] = React.useState(initial || false); + const t = TOOL_TONES[kind] || TOOL_TONES.read; + const isFocused = focus.kind === 'tool' && focus.id === id; + + return ( +
+
{ setOpen(!open); setFocus({ kind: 'tool', id }); }} style={{ + background: isFocused ? t.tint : 'var(--bg-quaternary)', + border: `0.5px solid ${isFocused ? t.color : 'var(--border)'}`, + outline: isFocused ? `1px solid ${t.color}` : 'none', outlineOffset: '-1px', + borderRadius: 7, padding: '6px 10px', + display: 'flex', alignItems: 'center', gap: 9, + fontSize: 12, cursor: 'pointer', transition: 'all 120ms', + }}> +
+ + {t.label} +
+ {name} + {arg} + {duration} + + +
+ {open && ( + diff + ? + : + )} +
+ ); +} + +function ToolOutput({ kind }) { + if (kind === 'execute') { + return ( +
+
$ hermes cron status daily-summary
+
+ last_run: 2026-04-25T09:28:14Z
+ duration: 14.2s
+ exit_code: 0
+ tokens_used: 1,847
+ next_run: 2026-04-26T09:00:00Z +
+
+ ); + } + // read + return ( +
+
1 {
+
2 "name": "daily-summary",
+
3 "schedule": "0 9 * * *",
+
4 "enabled": true
+
5 }
+
+ ); +} + +function DiffPreview() { + return ( +
+
3 "schedule": "0 9 * * *",
+
+ - + "timezone": "UTC", +
+
+ + + "timezone": "America/New_York", +
+
5 "enabled": true
+
+ ); +} + +// ─────────────── Suggested replies ─────────────── +function SuggestedReplies({ items }) { + return ( +
+ {items.map(s => ( + + ))} +
+ ); +} + +// ─────────────── Composer ─────────────── +const SLASH_COMMANDS = [ + { cmd: 'compress', desc: 'Compress conversation context', icon: 'minimize-2' }, + { cmd: 'clear', desc: 'Clear and start fresh', icon: 'trash-2' }, + { cmd: 'model', desc: 'Switch model', icon: 'cpu' }, + { cmd: 'project', desc: 'Change project', icon: 'folder' }, + { cmd: 'memory', desc: 'Edit AGENTS.md', icon: 'database' }, + { cmd: 'cost', desc: 'Show token / cost report', icon: 'circle-dollar-sign' }, +]; + +function Composer({ open, setOpen }) { + const [text, setText] = React.useState(''); + const onChange = e => { + const v = e.currentTarget.innerText; + setText(v); + setOpen(v.trim().startsWith('/')); + }; + return ( +
+ {open && ( +
+
+ Slash commands +
+ {SLASH_COMMANDS.map((c, i) => ( +
+ + /{c.cmd} + {c.desc} + {i === 0 && } +
+ ))} +
+ )} + +
+ {/* Attached context chips */} +
+ + + +
+ + {/* Input */} +
+ + {/* Footer row */} +
+ + + + + + + +
+ + + ↵ send · ⇧↵ newline + + +
+
+
+ ); +} + +function ContextChip({ icon, label, tone, muted }) { + return ( +
+ {label} +
+ ); +} + +function ComposerChip({ icon, label }) { + const [hover, setHover] = React.useState(false); + return ( + + ); +} + +// ─────────────── Pane 3 — Inspector ─────────────── +function Inspector({ focused }) { + const [tab, setTab] = React.useState('details'); + // Find the focused tool call. For demo, hard-code tc-2 details. + const FOCUS_DATA = { + 'tc-1': { kind: 'read', name: 'read_file', arg: '~/.scarf/cron/jobs.json', + duration: '86 ms', startedAt: '09:42:18.214', tokens: 412 }, + 'tc-2': { kind: 'execute', name: 'execute', arg: 'hermes cron status daily-summary', + duration: '1.4 s', startedAt: '09:42:18.302', tokens: 86, + cwd: '~/.scarf', exitCode: 0 }, + 'tc-3': { kind: 'read', name: 'read_file', arg: '~/.scarf/cron/output/daily-summary.md', + duration: '42 ms', startedAt: '09:43:01.190', tokens: 1284 }, + 'tc-4': { kind: 'edit', name: 'apply_patch', arg: '~/.scarf/cron/jobs.json', + duration: '120 ms', startedAt: '09:43:03.910', tokens: 88, linesAdded: 1, linesRemoved: 1 }, + }; + const data = FOCUS_DATA[focused.id] || FOCUS_DATA['tc-2']; + const t = TOOL_TONES[data.kind]; + + return ( + + ); +} + +function InspectorDetails({ data, t }) { + return ( +
+
+
+ +
+
Completed
+
Exit 0 · No errors
+
+
+
+ +
+
+
{data.arg}
+
+
+ +
+
+ + + + {data.exitCode != null && } + {data.cwd && } + {data.linesAdded != null && ( + + +{data.linesAdded} + / + −{data.linesRemoved} + + } /> + )} +
+
+ +
+
+
+
+ + Allowed by scarf-default profile +
+
+ + No human approval required +
+
+
+
+
+ ); +} + +function InspectorOutput({ data, t }) { + return ( +
+
⌘C}> +
+
$ hermes cron status daily-summary
+
+ last_run: 2026-04-25T09:28:14Z
+ duration: 14.2s
+ exit_code: 0
+ tokens_used: 1,847
+ next_run: 2026-04-26T09:00:00Z
+ schedule: 0 9 * * *
+ timezone: America/New_York +
+
+
+
+
+
+ (empty) +
+
+
+
+ ); +} + +function InspectorRaw({ data }) { + return ( +
+{`{ + "id": "${data.kind === 'execute' ? 'tc-2' : 'tc-x'}", + "type": "tool_use", + "name": "${data.name}", + "input": { + "command": "hermes cron status daily-summary", + "cwd": "~/.scarf" + }, + "result": { + "exit_code": 0, + "duration_ms": 1402, + "stdout_bytes": 287 + } +}`} +
+ ); +} + +function KV({ k, v, mono, color }) { + return ( +
+ {k} + {v} +
+ ); +} + +window.Chat = Chat; diff --git a/design/static-site/ui-kit/Common.jsx b/design/static-site/ui-kit/Common.jsx new file mode 100644 index 0000000..5803da6 --- /dev/null +++ b/design/static-site/ui-kit/Common.jsx @@ -0,0 +1,550 @@ +// Scarf v2 shared components — calmer density, full state matrices. +// Exports to window: Btn, IconBtn, Pill, Dot, Card, StatCard, Section, ContentHeader, +// Field, TextInput, NumberInput, TextArea, Toggle, Checkbox, Radio, RadioGroup, +// Segmented, Select, SettingsGroup, SettingsRow, Tabs, Menu, MenuItem, Divider, +// EmptyState, KbdKey, HelpIcon, Tooltip, Avatar, ProgressBar, Spinner. + +const SF = "var(--font-sans)"; + +// ─────────────── ContentHeader ─────────────── +function ContentHeader({ title, subtitle, actions, right, breadcrumb }) { + return ( +
+ {breadcrumb && ( +
{breadcrumb}
+ )} +
+
+
{title}
+ {subtitle &&
{subtitle}
} +
+ {right} + {actions &&
{actions}
} +
+
+ ); +} + +// ─────────────── Buttons ─────────────── +function Btn({ kind = 'secondary', size = 'md', icon, iconRight, children, onClick, disabled, loading, fullWidth, type = 'button' }) { + const sizes = { + sm: { padding: '5px 11px', fontSize: 12, gap: 5, iconSize: 13 }, + md: { padding: '7px 14px', fontSize: 13, gap: 6, iconSize: 14 }, + lg: { padding: '10px 18px', fontSize: 14, gap: 7, iconSize: 16 }, + }; + const kinds = { + primary: { background: 'var(--accent)', color: 'var(--on-accent)', border: '1px solid transparent', shadow: '0 1px 0 rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.18)' }, + secondary: { background: 'var(--bg-card)', color: 'var(--fg)', border: '1px solid var(--border-strong)', shadow: 'var(--shadow-sm)' }, + ghost: { background: 'transparent', color: 'var(--fg)', border: '1px solid transparent' }, + danger: { background: 'var(--bg-card)', color: 'var(--red-600)', border: '1px solid var(--red-500)' }, + 'danger-solid': { background: 'var(--red-500)', color: '#fff', border: '1px solid transparent' }, + accent: { background: 'var(--accent-tint)', color: 'var(--accent-active)', border: '1px solid transparent' }, + }; + const s = sizes[size]; + const k = kinds[kind]; + const [hover, setHover] = React.useState(false); + + const hoverStyle = !disabled && hover ? { + primary: { background: 'var(--accent-hover)' }, + secondary: { background: 'var(--gray-50)', borderColor: 'var(--accent)' }, + ghost: { background: 'var(--bg-quaternary)' }, + danger: { background: 'var(--red-100)' }, + 'danger-solid': { background: 'var(--red-600)' }, + accent: { background: 'var(--accent-tint-strong)' }, + }[kind] : {}; + + return ( + + ); +} + +function IconBtn({ icon, onClick, size = 28, tooltip, active, disabled }) { + const [hover, setHover] = React.useState(false); + return ( + + ); +} + +function Spinner({ size = 14, color = 'currentColor' }) { + return ( + + ); +} + +// ─────────────── Pills / Dots ─────────────── +function Pill({ tone = 'gray', dot, icon, children, size = 'md' }) { + const tones = { + gray: { bg: 'var(--bg-quaternary)', fg: 'var(--fg-muted)', dotc: 'var(--gray-500)' }, + green: { bg: 'var(--green-100)', fg: 'var(--green-600)', dotc: 'var(--green-500)' }, + red: { bg: 'var(--red-100)', fg: 'var(--red-600)', dotc: 'var(--red-500)' }, + orange: { bg: 'var(--orange-100)', fg: '#A8741F', dotc: 'var(--orange-500)' }, + blue: { bg: 'var(--blue-100)', fg: '#1F70A8', dotc: 'var(--blue-500)' }, + accent: { bg: 'var(--accent-tint)', fg: 'var(--accent-active)', dotc: 'var(--accent)' }, + amber: { bg: 'var(--orange-100)', fg: '#A8741F', dotc: 'var(--orange-500)' }, + purple: { bg: '#EFE0F8', fg: '#5E4080', dotc: '#7E5BA9' }, + idle: { bg: 'var(--bg-quaternary)', fg: 'var(--fg-faint)', dotc: 'var(--gray-400)' }, + }; + const t = tones[tone]; + const sizes = { sm: { p: '2px 7px', f: 10 }, md: { p: '3px 9px', f: 11 }, lg: { p: '4px 11px', f: 12 } }; + const sz = sizes[size]; + return ( + + {dot && } + {icon && } + {children} + + ); +} + +function Dot({ tone = 'gray', size = 8 }) { + const tones = { gray: 'var(--gray-400)', green: 'var(--green-500)', red: 'var(--red-500)', + orange: 'var(--orange-500)', blue: 'var(--blue-500)', accent: 'var(--accent)' }; + return ; +} + +// ─────────────── Cards / Sections ─────────────── +function Card({ children, padding = 18, style = {}, onClick, interactive }) { + return ( +
{children}
+ ); +} + +function StatCard({ label, value, sub, accent, icon }) { + return ( + +
+ {icon && } + {label} +
+
{value}
+ {sub &&
{sub}
} +
+ ); +} + +function Section({ title, hint, right, children, gap = 12 }) { + return ( +
+
+
{title}
+ {hint &&
{hint}
} +
{right}
+
+ {children} +
+ ); +} + +function Divider({ vertical, label }) { + if (vertical) return
; + if (label) return ( +
+
+ {label} +
+
+ ); + return
; +} + +// ─────────────── Form fields ─────────────── +function Field({ label, hint, error, help, children, required, inline }) { + return ( + + ); +} + +function HelpIcon({ text }) { + return ( + + + + ); +} + +function inputStyle(invalid) { + return { + fontFamily: SF, fontSize: 13, padding: '7px 11px', + border: `1px solid ${invalid ? 'var(--red-500)' : 'var(--border-strong)'}`, + borderRadius: 7, background: 'var(--bg-card)', color: 'var(--fg)', + outline: 'none', transition: 'all 120ms', width: '100%', boxSizing: 'border-box', + }; +} + +function TextInput({ value, onChange, placeholder, mono, invalid, leftIcon, rightSlot, type = 'text' }) { + const [v, setV] = React.useState(value ?? ''); + React.useEffect(() => setV(value ?? ''), [value]); + const ref = React.useRef(); + return ( +
+ {leftIcon && } + { setV(e.target.value); onChange && onChange(e.target.value); }} + placeholder={placeholder} + style={{ ...inputStyle(invalid), + fontFamily: mono ? 'var(--font-mono)' : SF, + paddingLeft: leftIcon ? 32 : 11, + paddingRight: rightSlot ? 36 : 11, + }} + onFocus={e => { if (!invalid) { e.target.style.borderColor = 'var(--accent)'; e.target.style.boxShadow = 'var(--shadow-focus)'; }}} + onBlur={e => { e.target.style.borderColor = invalid ? 'var(--red-500)' : 'var(--border-strong)'; e.target.style.boxShadow = 'none'; }} + /> + {rightSlot &&
{rightSlot}
} +
+ ); +} + +function TextArea({ value, onChange, placeholder, rows = 3, invalid, mono }) { + const [v, setV] = React.useState(value ?? ''); + React.useEffect(() => setV(value ?? ''), [value]); + return ( +