From 416c71bbe3bc2315dfc61728b18d2b09c088ba7a Mon Sep 17 00:00:00 2001 From: Eric Wang Date: Wed, 29 Jul 2026 00:23:36 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat(web):=20terminal=20material=20?= =?UTF-8?q?=E2=80=94=20cctrace's=20design=20language,=20ccx's=20bones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The web UI read as a pastel chat app: 17px system sans, role-tinted bubbles, ~12 stray hues, 'Courier New' leading the mono stack, radii 3-12px, black shadows. cctrace's session UI is the taste reference: a well-kept terminal. Material swap in place — every selector and JS hook kept, values rewritten: - 13px ui-monospace body, 10/11/12/13 sub-scale, 18px page h1 - warm-tinted neutrals (light #fcfbfa, dark #131110), terracotta as the ONE accent, semantic set green/red/amber/purple/blue; provider and event tokens ride the set - bubbles -> hairline surfaces; user turns get the cctrace anchor mechanic (space above + accent-washed header row); thinking is muted italic - side-stripe purge round 2: tool blocks, outline active item, agent turns, doctor/memory/settings cards — hairline full borders now - chrome details: thin scrollbars, accent selection, :focus-visible, tinted shadow tokens, radius scale 6/4/999 - uppercase role labels exempt command names and agent types Devlog: docs/devlog/2026-07-29-web-terminal-material.org Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 5 + .../2026-07-29-web-terminal-material.org | 58 ++++ internal/web/server.go | 8 +- internal/web/static/style.css | 293 ++++++++++-------- internal/web/templates.go | 37 +-- 5 files changed, 254 insertions(+), 147 deletions(-) create mode 100644 docs/devlog/2026-07-29-web-terminal-material.org diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1b8dc..e12b8dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to ccx are documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) +## [Unreleased] + +### Changed +- **Web UI wears terminal material now** — cctrace's design language ported onto ccx's markup (every selector and JS hook kept, values rewritten). 13px `ui-monospace` body replaces 17px system sans ('Courier New' led the old mono stack); warm-tinted neutrals with terracotta as the single accent plus a five-hue semantic set (green/red/amber/purple/blue) replace ~12 stray hues; pastel role bubbles become hairline surfaces with faint washes — user turns get the cctrace anchor mechanic (space above + accent-washed header row); thinking is muted italic. Chrome details: thin scrollbars, accent selection, visible focus, tinted shadows, one radius scale. Second side-stripe purge caught what 0.11 missed (tool blocks, outline active item, agent turns, doctor/memory cards). Devlog: `docs/devlog/2026-07-29-web-terminal-material.org`. + ## [0.12.0] - 2026-07-24 Both changes answer the second field report — the first one produced by ccx tracing itself: a user audited a live session's trace and caught its two headline numbers lying. Findings 3-5 from the same report are tracked in issues #21-#23. diff --git a/docs/devlog/2026-07-29-web-terminal-material.org b/docs/devlog/2026-07-29-web-terminal-material.org new file mode 100644 index 0000000..45eaaeb --- /dev/null +++ b/docs/devlog/2026-07-29-web-terminal-material.org @@ -0,0 +1,58 @@ +* [2026-07-29] Dev Log: Web UI terminal material :WEB:DESIGN: + +** Context +Eric's verdict on the web UI: cctrace's session UI is the taste +reference ("good taste especially on the session concepts and UX, the +timeline, reply etc"); ccx's "color/font, layouts, interactions, +details are looking bad". Direction: adopt cctrace's design language, +keep ccx's single-binary Go architecture and information architecture. + +** Why +The old surface was a pastel chat app: 17px system sans body, role- +tinted bubbles (user warm / assistant green / tool yellow / error +red), ~12 stray hues (#e9a, #a80, #5a9, #fa0, #d55, #c55, #86c, +blue/violet/cyan context accents), 'Courier New' first in the mono +stack, radii from 3 to 12px, pure-black shadows. cctrace reads as a +well-kept terminal: 13px ui-monospace, one accent, a small semantic +set, hairline surfaces, quiet chrome. + +** What +- =CHANGE= Material swap in place: every selector and JS hook kept; + values rewritten. 13px ui-monospace body (Courier New gone), + 10/11/12/13 sub-scale, 18px page h1. +- =CHANGE= Palette: warm-tinted neutrals (kiln: tint neutrals toward + brand hue — light #fcfbfa ground, dark #131110), terracotta as the + ONE accent (#c65d3e light / #e08662 dark, deepened for contrast), + semantic set green/red/amber/purple/blue. All stray hues collapsed + onto tokens; provider + event tokens ride the set. +- =CHANGE= Bubbles -> hairline material. Role backgrounds are faint + washes (color-mix 6-9%) or transparent; user turns get cctrace's + anchor mechanic: extra space above + accent-washed header row in a + hairline box. Thinking is muted italic (was amber + pastel ground). +- =FIX= Side-stripe purge, round 2 (kiln permanent anti-pattern; the + 0.11 pass missed these): tool blocks (2px blue), outline active item + (2px cyan), agent turns (2px purple), doctor/memory/settings cards + (3px). Now hairline full borders, tinted where identity matters. +- =CHANGE= Chrome details from cctrace: thin scrollbars, accent + selection, :focus-visible outlines, two shadow tokens (tinted, not + black), radius scale 6/4/999. +- =FIX= Uppercase role labels exempt command names and agent types + (/init must not render /INIT). + +** How +CSS-only where possible (style.css rewritten value-by-value), plus +embedded page styles in templates.go (doctor, memory, settings, +Tailwind @theme) and two inline hexes in server.go. Favicon x +re-inked #c65d3e. + +** Decisions +| Decision | Alternatives | Rationale | DRI | Timestamp | +| Terracotta stays the accent | Copy cctrace's GitHub blue | The badness was hue-scatter + pastels, not the brand hue; one warm accent on warm-tinted neutrals keeps ccx identity and avoids clone territory | claude | 2026-07-29 | +| Warm-tinted neutrals, not GitHub blue-grays | Verbatim GitHub palette | Kiln L0: tint neutrals toward brand hue; blue-gray ground under a terracotta accent reads accidental | claude | 2026-07-29 | +| Values swapped in place, selectors untouched | Rewrite templates to cctrace's markup | Same information architecture, zero JS breakage, reviewable diff; structural ports (clamp+expander, replay bar) are separate features | claude | 2026-07-29 | + +** Notes +Not ported yet, candidates for follow-up: cctrace's msg-clamp (380px +mask + explicit expander instead of inner scroll on long outputs), +replay transport, tabular-nums pass on stat rows. Screenshots remain +host-gated (no browser in deva). diff --git a/internal/web/server.go b/internal/web/server.go index 6d880d2..bbebdbc 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -1674,11 +1674,11 @@ func handleInsights(w http.ResponseWriter, r *http.Request) { size := fmt.Sprintf("%.0fKB", float64(rpt.Size)/1024) b.WriteString(fmt.Sprintf(``, rpt.Name)) - b.WriteString(fmt.Sprintf(`%s`, + b.WriteString(fmt.Sprintf(`%s`, rpt.Name, rpt.Name)) - b.WriteString(fmt.Sprintf(`%s`, rpt.Scope)) - b.WriteString(fmt.Sprintf(`%s`, size)) - b.WriteString(fmt.Sprintf(`%s`, formatAge(rpt.CreatedAt))) + b.WriteString(fmt.Sprintf(`%s`, rpt.Scope)) + b.WriteString(fmt.Sprintf(`%s`, size)) + b.WriteString(fmt.Sprintf(`%s`, formatAge(rpt.CreatedAt))) b.WriteString(``) } b.WriteString(``) diff --git a/internal/web/static/style.css b/internal/web/static/style.css index 74d73fe..c391f8e 100644 --- a/internal/web/static/style.css +++ b/internal/web/static/style.css @@ -1,75 +1,113 @@ +/* Material: quiet terminal. One accent (terracotta — the brand), a + * small semantic set (green/red/amber/purple/blue), warm-tinted + * neutrals, hairline surfaces, 13px monospace. Emphasis comes from + * weight, spacing, and hairline boxes — never from tinted bubbles. + * Reference material: cctrace's session UI. */ :root { - --bg: #ffffff; - --bg-secondary: #f8f9fa; - --bg-tertiary: #e9ecef; - --text: #212529; - --text-muted: #6c757d; - --border: #dee2e6; - --primary: #da7756; - --primary-hover: #c5634a; - /* Context accent colors */ - --accent-project: #3b82f6; - --accent-session: #8b5cf6; - --accent-conversation: #06b6d4; - /* Provider accent colors */ - --accent-cc: #da7756; - --accent-cx: #10b981; - --accent-gx: #8b5cf6; + --bg: #fcfbfa; + --bg-secondary: #f5f3f0; + --bg-tertiary: #ebe8e4; + --hover: #f0edea; + --text: #26221e; + --text-muted: #6e675f; + --text-faint: #97908a; + --border: #dfdad4; + --primary: #c65d3e; + --primary-hover: #a94e33; + /* Semantic set — the only hues besides the accent. */ + --green: #1a7f37; + --red: #cf222e; + --amber: #9a6700; + --purple: #8250df; + --blue: #0969da; + /* Provider identity rides the semantic set. */ + --accent-cc: var(--primary); + --accent-cx: var(--green); + --accent-gx: var(--purple); + /* Legacy context accents, mapped onto the set. */ + --accent-project: var(--blue); + --accent-session: var(--purple); + --accent-conversation: var(--blue); /* Semantic event tokens — one color per event kind, used by the * rail, outline, info panel, badges, and any future surface. If * you want to rebrand an event kind, change it HERE, not in a * dozen component rules. */ - --event-prompt: var(--accent-session, #8b5cf6); - --event-command: var(--accent-conversation, #06b6d4); - --event-compact: #fa0; + --event-prompt: var(--primary); + --event-command: var(--blue); + --event-compact: var(--amber); --event-tool: var(--text-muted); - --event-subagent: var(--accent-project, #3b82f6); - --event-skill: var(--accent-session, #8b5cf6); - --event-plugin: var(--accent-conversation, #06b6d4); - --user-bg: #fff8f5; - --user-border: #da7756; - --assistant-bg: #f5faf5; - --assistant-border: #5a9; - --tool-bg: #fffbf0; - --tool-border: #d97; - --error-bg: #fff0f0; - --error-border: #d55; - --compacted-bg: #fffde0; - --compacted-border: #da0; - --shadow: 0 1px 3px rgba(0,0,0,0.08); + --event-subagent: var(--purple); + --event-skill: var(--purple); + --event-plugin: var(--blue); + /* Role tokens: hairline material, not pastel bubbles. Backgrounds + * are faint washes over the ground; borders carry the role hue for + * icons and hairlines only. */ + --user-bg: color-mix(in srgb, var(--primary) 6%, var(--bg)); + --user-border: var(--primary); + --assistant-bg: transparent; + --assistant-border: var(--green); + --tool-bg: var(--bg-secondary); + --tool-border: var(--blue); + --error-bg: color-mix(in srgb, var(--red) 7%, var(--bg)); + --error-border: var(--red); + --compacted-bg: color-mix(in srgb, var(--amber) 7%, var(--bg)); + --compacted-border: var(--amber); + --shadow: 0 1px 3px rgba(60, 40, 30, 0.08); + --shadow-float: 0 4px 16px rgba(60, 40, 30, 0.12); --radius: 6px; - --font-mono: 'Courier New', Courier, 'SF Mono', 'Consolas', 'Liberation Mono', 'Menlo', monospace; - --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + --radius-sm: 4px; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + --font-sans: var(--font-mono); + color-scheme: light; } [data-theme="dark"] { - --bg: #1a1a1f; - --bg-secondary: #222228; - --bg-tertiary: #2a2a32; - --text: #e8e8e8; - --text-muted: #888; - --border: #3a3a42; - --accent-project: #60a5fa; - --accent-session: #a78bfa; - --accent-conversation: #22d3ee; - --accent-cc: #e09070; - --accent-cx: #34d399; - --accent-gx: #a78bfa; - --user-bg: #2a2520; - --assistant-bg: #202820; - --tool-bg: #282520; - --error-bg: #2a2020; - --compacted-bg: #282820; + --bg: #131110; + --bg-secondary: #1b1917; + --bg-tertiary: #262220; + --hover: #211e1c; + --text: #d7d2cc; + --text-muted: #948d86; + --text-faint: #6c6660; + --border: #35302b; + --primary: #e08662; + --primary-hover: #e9a082; + --green: #3fb950; + --red: #f85149; + --amber: #d29922; + --purple: #a371f7; + --blue: #58a6ff; + --user-bg: color-mix(in srgb, var(--primary) 8%, var(--bg)); + --error-bg: color-mix(in srgb, var(--red) 9%, var(--bg)); + --compacted-bg: color-mix(in srgb, var(--amber) 8%, var(--bg)); + --shadow: 0 1px 3px rgba(0, 0, 0, 0.4); + --shadow-float: 0 4px 16px rgba(0, 0, 0, 0.5); + color-scheme: dark; } * { box-sizing: border-box; margin: 0; padding: 0; } +/* Chrome-quality details: quiet scrollbars, accent selection, visible + * keyboard focus. The UI should feel like a well-kept terminal. */ +:root { scrollbar-width: thin; scrollbar-color: var(--border) transparent; } +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 5px; + border: 2px solid transparent; + background-clip: padding-box; +} +::-webkit-scrollbar-thumb:hover { background-color: var(--text-faint); } +::selection { background: color-mix(in srgb, var(--primary) 25%, transparent); } +:focus-visible { outline: 1px solid var(--primary); outline-offset: 1px; } + body { - font-family: var(--font-sans); + font-family: var(--font-mono); background: var(--bg); color: var(--text); - line-height: 1.7; - font-size: 17px; + line-height: 1.55; + font-size: 13px; } code, pre, .session-id, .model-badge { @@ -112,7 +150,7 @@ code, pre, .session-id, .model-badge { border-radius: var(--radius); background: var(--bg); color: var(--text); - font-size: 14px; + font-size: 12px; } .global-search-input:focus { outline: none; border-color: var(--primary); } .search-results { @@ -127,7 +165,7 @@ code, pre, .session-id, .model-badge { overflow-y: auto; z-index: 200; display: none; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); + box-shadow: var(--shadow-float); } .search-results.active { display: block; } .search-result { @@ -142,10 +180,10 @@ code, pre, .session-id, .model-badge { .search-result:last-child { border-bottom: none; } .search-result:hover { background: var(--bg-secondary); } .search-result .result-body { flex: 1; min-width: 0; } -.search-result .result-title { font-weight: 500; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.search-result .result-title { font-weight: 500; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .search-result .result-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; } .search-result .result-snippet { font-size: 12px; color: var(--text-muted); margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.search-result .result-snippet mark { background: #ffe066; color: #000; padding: 0 2px; } +.search-result .result-snippet mark { background: color-mix(in srgb, var(--amber) 30%, var(--bg)); color: var(--text); padding: 0 2px; } .result-badge { flex-shrink: 0; width: 20px; @@ -194,7 +232,7 @@ code, pre, .session-id, .model-badge { letter-spacing: -1px; } .brand-cc { color: var(--text); } -.brand-x { color: #da7756; } +.brand-x { color: var(--primary); } .brand-sub { font-size: 13px; font-weight: 400; @@ -459,8 +497,8 @@ code, pre, .session-id, .model-badge { padding: 6px 12px; background: var(--bg-secondary); border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 4px 20px rgba(0,0,0,0.15); + border-radius: var(--radius); + box-shadow: var(--shadow-float); z-index: 100; backdrop-filter: blur(10px); max-width: calc(100vw - 280px); @@ -474,7 +512,7 @@ code, pre, .session-id, .model-badge { gap: 4px; padding: 6px 10px; border: none; - border-radius: 8px; + border-radius: var(--radius-sm); background: transparent; color: var(--text-muted); cursor: pointer; @@ -495,10 +533,10 @@ code, pre, .session-id, .model-badge { /* Live button pulse animation - animate the ◉ icon directly */ .live-btn.active { background: rgba(40,80,40,0.9); } .live-btn.active .dock-icon { - color: #4f4; + color: var(--green); animation: live-pulse 1.5s infinite; } -.live-btn.active .dock-label { color: #8f8; } +.live-btn.active .dock-label { color: var(--green); } @keyframes live-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(1.15); } @@ -515,8 +553,8 @@ code, pre, .session-id, .model-badge { margin-bottom: 8px; background: var(--bg); border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0,0,0,0.15); + border-radius: var(--radius); + box-shadow: var(--shadow-float); min-width: 100px; overflow: hidden; } @@ -539,7 +577,7 @@ code, pre, .session-id, .model-badge { } .page-header { margin-bottom: 20px; position: relative; } -.page-header h1 { font-size: 1.5rem; margin-bottom: 4px; display: inline; } +.page-header h1 { font-size: 18px; font-weight: 600; letter-spacing: -0.2px; margin-bottom: 4px; display: inline; } .page-badge { display: inline-flex; align-items: center; @@ -760,13 +798,13 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } margin-bottom: 6px; } -.card-title { font-weight: 600; font-size: 14px; word-break: break-word; flex: 1; } +.card-title { font-weight: 600; font-size: 13px; word-break: break-word; flex: 1; } .card-providers { display: flex; gap: 3px; flex-shrink: 0; margin-left: 6px; } .card-badge { background: var(--primary); color: white; padding: 2px 6px; - border-radius: 10px; + border-radius: 999px; font-size: 11px; font-weight: 600; } @@ -819,8 +857,8 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } transform: translateX(-50%); background: var(--bg); border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 20px rgba(0,0,0,0.15); + border-radius: var(--radius); + box-shadow: var(--shadow-float); padding: 12px 16px; z-index: 300; display: none; @@ -839,7 +877,7 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } background: var(--bg-secondary); padding: 8px 12px; border-radius: 6px; - font-size: 14px; + font-size: 12px; flex: 1; color: var(--text); } @@ -889,7 +927,7 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } gap: 4px; padding: 4px 10px; border: 1px solid var(--border); - border-radius: 12px; + border-radius: 999px; font-size: 11px; cursor: pointer; color: var(--text-muted); @@ -904,8 +942,8 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } } /* Search highlight */ -.search-match { background: rgba(255,220,0,0.3); } -.search-current { background: rgba(255,180,0,0.5); outline: 2px solid var(--primary); } +.search-match { background: color-mix(in srgb, var(--amber) 28%, transparent); } +.search-current { background: color-mix(in srgb, var(--amber) 45%, transparent); outline: 2px solid var(--primary); } /* Info panel (floating above info icon) */ .info-panel { @@ -914,8 +952,8 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } right: 40px; background: var(--bg); border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 8px 32px rgba(0,0,0,0.12); + border-radius: var(--radius); + box-shadow: var(--shadow-float); padding: 0; z-index: 200; display: none; @@ -1016,7 +1054,7 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } padding: 10px 20px; background: var(--bg-secondary); border: 1px solid var(--border); - border-radius: 8px; + border-radius: var(--radius); color: var(--text-muted); font-size: 13px; cursor: pointer; @@ -1106,18 +1144,18 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } overflow: hidden; } .nav-item:hover { background: var(--bg-tertiary); color: var(--text); } -.nav-item.active { background: var(--bg-tertiary); color: var(--text); border-left: 2px solid var(--accent-conversation); } +.nav-item.active { background: color-mix(in srgb, var(--primary) 10%, var(--bg-tertiary)); color: var(--text); } .nav-icon { font-size: 9px; flex-shrink: 0; width: 12px; text-align: center; } .nav-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Navigation item hierarchy */ .nav-compact { - background: color-mix(in srgb, var(--event-compact, #fa0) 12%, transparent); + background: color-mix(in srgb, var(--event-compact, var(--amber)) 12%, transparent); font-weight: 600; margin: 8px 0; } -.nav-compact .nav-icon { color: #fa0; } +.nav-compact .nav-icon { color: var(--amber); } .nav-sidechain { background: color-mix(in srgb, var(--event-subagent) 8%, transparent); @@ -1137,19 +1175,19 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } .nav-user .nav-icon { color: var(--user-border); } .nav-command { padding-left: 8px; } -.nav-command .nav-icon { color: #e9a; } +.nav-command .nav-icon { color: var(--blue); } .nav-response { padding-left: 20px; opacity: 0.9; } .nav-response .nav-icon { color: var(--assistant-border); } .nav-thinking { padding-left: 20px; opacity: 0.6; } -.nav-thinking .nav-icon { color: #a80; } +.nav-thinking .nav-icon { color: var(--amber); } .nav-tool { padding-left: 28px; opacity: 0.8; } .nav-tool .nav-icon { color: var(--tool-border); } .nav-meta { padding-left: 20px; opacity: 0.4; font-style: italic; } -.nav-meta .nav-icon { color: #999; } +.nav-meta .nav-icon { color: var(--text-faint); } /* Exchange outline groups: title + expand button + children. Two distinct click targets — clicking the title jumps to the message, @@ -1203,7 +1241,7 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } font-size: 9px; background: var(--bg-tertiary); padding: 1px 5px; - border-radius: 8px; + border-radius: 999px; color: var(--text-muted); flex-shrink: 0; } @@ -1669,7 +1707,7 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } .btn:hover { border-color: var(--text-muted); } .btn-watch { background: var(--primary); color: white; border-color: var(--primary); } .btn-watch:hover { background: var(--primary-hover); } -.btn-watch.active { background: #d55; border-color: #d55; } +.btn-watch.active { background: var(--red); border-color: var(--red); } .export-dropdown { position: relative; } .export-menu { @@ -1864,7 +1902,9 @@ body[data-ccx-provider="codex"] .cli-spinner { color: var(--accent-cx); } .turn-role { font-family: var(--font-mono); font-weight: 600; - font-size: 11px; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.05em; color: var(--text-muted); } .turn-time { @@ -1915,18 +1955,23 @@ details.turn-user[open] .turn-role { display: none; } .turn-body.raw-mode { background: var(--bg-tertiary); border-radius: var(--radius); } .turn-body.raw-mode pre.raw-content { margin: 0; padding: 8px; font-size: 10px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; } -/* User prompt - CLI style with subtle separator */ +/* User prompt — the reading anchor. Extra space above starts the + * "paragraph"; a faint accent wash on the header row guides a + * scanning eye (cctrace's user-turn mechanic). No hard border — a + * colored edge reads as UI chrome, not emphasis. */ .turn-user { - margin: 16px 0 4px 0; - padding-top: 12px; - border-top: 1px dashed var(--border); + margin: 18px 0 4px 0; } -.turn-user:first-child { border-top: none; padding-top: 0; } +.turn-user:first-child { margin-top: 0; } .turn-user .turn-header { - padding: 4px 0; + padding: 5px 10px; cursor: pointer; + background: color-mix(in srgb, var(--primary) 8%, var(--bg-secondary)); + border: 1px solid var(--border); + border-radius: var(--radius); } -.turn-user .turn-icon { color: var(--user-border); font-size: 14px; } +.turn-user .turn-icon { color: var(--user-border); font-size: 13px; } +.turn-user .turn-role { color: var(--primary); } .turn-user .turn-preview { font-weight: 500; } /* Assistant response - CLI style with ● prefix */ @@ -1967,7 +2012,7 @@ body.watching .turn:last-child .turn-header { left: 0; right: 0; height: 3px; - background: linear-gradient(90deg, #4f4, #8f8, #4f4); + background: var(--green); background-size: 200% 100%; animation: liveGradient 2s linear infinite; z-index: 200; @@ -1988,17 +2033,17 @@ body.watching .live-indicator { display: block; } .tail-spinner .cli-spinner-char { color: var(--assistant-border); font-size: 14px; } body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } -/* Agent (sidechain) - indented, purple accent */ +/* Agent (sidechain) — indented behind a 1px guide line (structure, + * not a stripe); the purple identity lives in the icon and role. */ .turn-agent { margin-left: 16px; padding-left: 12px; - padding-left: 12px; - border-left: 2px solid var(--event-subagent); + border-left: 1px solid var(--border); opacity: 0.9; } .turn-agent .turn-header { background: transparent; } .turn-agent .turn-icon { color: var(--event-subagent); } -.turn-agent .turn-role { display: inline; color: var(--event-subagent); } +.turn-agent .turn-role { display: inline; color: var(--event-subagent); text-transform: none; letter-spacing: 0; } /* Sub-agent transcript sections — inline after dispatching tool_use */ .sidechain-group { @@ -2010,7 +2055,7 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } .sidechain-header { padding: 8px 12px; cursor: pointer; - font-size: 0.875rem; + font-size: 11px; color: var(--text-muted); display: flex; align-items: center; @@ -2052,16 +2097,16 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } /* Command - CLI style */ .turn-command { margin: 12px 0 4px 0; } .turn-command .turn-header { padding: 4px 0; } -.turn-command .turn-icon { color: #e9a; } -.turn-command .turn-role { color: #e9a; font-family: var(--font-mono); font-size: 12px; } +.turn-command .turn-icon { color: var(--blue); } +.turn-command .turn-role { color: var(--blue); font-family: var(--font-mono); font-size: 12px; text-transform: none; letter-spacing: 0; } /* Meta/system - dimmed */ .turn-meta { opacity: 0.5; margin: 8px 0; } .turn-meta .turn-header { cursor: pointer; padding: 4px 0; } -.turn-meta .turn-icon { color: #999; } +.turn-meta .turn-icon { color: var(--text-faint); } /* Turn body - compact padding */ -.turn-body { padding: 4px 0 8px 20px; font-size: 14px; line-height: 1.6; } +.turn-body { padding: 4px 0 8px 20px; font-size: 13px; line-height: 1.55; } /* Block styling */ .block-text { white-space: pre-wrap; line-height: 1.5; } @@ -2120,13 +2165,14 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } .block-thinking summary { padding: 2px 0; cursor: pointer; - font-size: 13px; + font-size: 12px; display: inline-flex; align-items: center; gap: 4px; - color: #a80; + color: var(--text-muted); font-style: italic; } +.block-thinking summary:hover { color: var(--text); } .block-thinking summary::-webkit-details-marker { display: none; } .block-thinking summary::marker { display: none; } .block-thinking .block-content { @@ -2137,14 +2183,13 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } white-space: pre-wrap; } -/* Tool block - inline CLI style */ +/* Tool block — a quiet fold row; the tool name carries the identity + * color, the row itself stays chrome-free (no side-stripe). */ .block-tool { margin: 4px 0; border: none; - border-left: 2px solid var(--tool-border); border-radius: 0; background: transparent; - padding-left: 8px; } .block-tool summary { padding: 2px 0; @@ -2159,7 +2204,7 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } .block-tool summary::marker { display: none; } .block-icon { color: var(--text-muted); font-size: 11px; } -.block-thinking .block-icon { color: #a80; } +.block-thinking .block-icon { color: var(--amber); } .block-tool .block-icon { color: var(--tool-border); } .tool-preview { @@ -2187,11 +2232,11 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } } .block-thinking .block-content { - background: #fffde0; + background: transparent; white-space: pre-wrap; color: var(--text-muted); + font-style: italic; } -[data-theme="dark"] .block-thinking .block-content { background: #282820; } .tool-input { margin: 0; @@ -2303,7 +2348,7 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } .block-content:hover .copy-btn, .code-block:hover .copy-btn { opacity: 1; } .copy-btn:hover { background: var(--bg-secondary); color: var(--text); } -.copy-btn.copied { color: #0a0; } +.copy-btn.copied { color: var(--green); } /* Raw toggle */ .raw-toggle { @@ -2331,21 +2376,19 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } font-size: 10px; } .diff-old { - background: rgba(255,100,100,0.12); + background: color-mix(in srgb, var(--red) 10%, var(--bg)); padding: 8px; margin: 0; white-space: pre-wrap; - color: #a55; + color: color-mix(in srgb, var(--red) 75%, var(--text)); } .diff-new { - background: rgba(100,255,100,0.12); + background: color-mix(in srgb, var(--green) 10%, var(--bg)); padding: 8px; margin: 0; white-space: pre-wrap; - color: #595; + color: color-mix(in srgb, var(--green) 75%, var(--text)); } -[data-theme="dark"] .diff-old { background: rgba(255,100,100,0.15); color: #f88; } -[data-theme="dark"] .diff-new { background: rgba(100,255,100,0.15); color: #8f8; } /* Todo checklist */ .todo-checklist { @@ -2364,7 +2407,7 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } .todo-icon { font-size: 14px; width: 18px; text-align: center; } .todo-pending .todo-icon { color: var(--text-muted); } .todo-progress .todo-icon { color: var(--primary); } -.todo-completed .todo-icon { color: #5a9; } +.todo-completed .todo-icon { color: var(--green); } .todo-completed .todo-text { text-decoration: line-through; color: var(--text-muted); } .todo-checklist input[type="checkbox"] { margin: 0; cursor: default; } @@ -2444,7 +2487,7 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } /* KillShell tool */ .killshell-call { font-family: var(--font-mono); font-size: 12px; } -.shell-id { color: #c55; } +.shell-id { color: var(--red); } /* Settings page */ .settings-section { margin-bottom: 24px; } @@ -2564,8 +2607,8 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } .te-permalink:hover { text-decoration: underline; } .te-chip { white-space: nowrap; } .te-chip.te-edit { color: var(--primary); } -.te-chip.te-agent { color: var(--event-subagent, #8b5cf6); } -.te-chip.te-error { color: #d33; font-weight: 600; } +.te-chip.te-agent { color: var(--event-subagent); } +.te-chip.te-error { color: var(--red); font-weight: 600; } .te-chip.te-cost, .te-chip.te-time { font-family: var(--font-mono); } .te-body { padding: 6px 10px 8px; display: flex; flex-direction: column; gap: 8px; } .te-section-title { @@ -2593,7 +2636,7 @@ body.watching .tail-spinner { display: flex; align-items: center; gap: 8px; } border-radius: 3px; } .te-link:hover { border-color: var(--primary); } -.te-link-error { color: #d33; border-color: #d33; } +.te-link-error { color: var(--red); border-color: var(--red); } .te-agent-row { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; padding: 1px 0; } .te-agent-desc { color: var(--text); } .te-agent-status { color: var(--text-muted); font-family: var(--font-mono); font-size: 11px; } diff --git a/internal/web/templates.go b/internal/web/templates.go index 1562be8..78906f9 100644 --- a/internal/web/templates.go +++ b/internal/web/templates.go @@ -2542,7 +2542,7 @@ func renderSettingsPage(settings *Settings, config *GlobalConfig, configFiles [] if !pc.Enabled { status = "disabled" } - b.WriteString(fmt.Sprintf(`
`, accentColor)) + b.WriteString(fmt.Sprintf(`
`, accentColor)) b.WriteString(fmt.Sprintf(`
%s`, html.EscapeString(pc.DisplayName))) b.WriteString(fmt.Sprintf(`%s
`, status, status)) b.WriteString(fmt.Sprintf(`
%s
`, html.EscapeString(prov.home))) @@ -2684,12 +2684,13 @@ func settingsPageCSS() string { .provider-status-card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; min-width: 220px; flex: 1; + border-color: color-mix(in srgb, var(--prov-accent, var(--border)) 35%, var(--border)); } .prov-header { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } .prov-badge { font-size: 11px; padding: 1px 6px; border-radius: 3px; } -.prov-active { background: #16a34a22; color: #16a34a; } -.prov-missing { background: #eab30822; color: #eab308; } -.prov-disabled { background: #64748b22; color: #64748b; } +.prov-active { background: color-mix(in srgb, var(--green) 13%, transparent); color: var(--green); } +.prov-missing { background: color-mix(in srgb, var(--amber) 13%, transparent); color: var(--amber); } +.prov-disabled { background: color-mix(in srgb, var(--text-muted) 13%, transparent); color: var(--text-muted); } .prov-detail { font-size: 13px; color: var(--text-muted); } .prov-detail code { font-size: 12px; } .count { color: var(--text-muted); font-weight: normal; font-size: 12px; } @@ -2781,8 +2782,8 @@ func settingsPageCSS() string { .file-content .fmt a { color: var(--primary); text-decoration: none; } .file-content .fmt a:hover { text-decoration: underline; } .file-content .fmt strong { font-weight: 600; } -.agent-card { border-left: 3px solid #86c; } -.skill-card { border-left: 3px solid var(--primary); } +.agent-card { border-color: color-mix(in srgb, var(--purple) 35%, var(--border)); } +.skill-card { border-color: color-mix(in srgb, var(--primary) 35%, var(--border)); }