Files
deepseek-harness/examples/desktop/src/renderer/style.css
T
ZiyaZhang 47949244c8 feat(desktop): trace signal detection + tri-view badges
Adds a small signal-detection layer over trace payloads and surfaces
three kinds of semantic signals inline in the trace tri-view (Timeline
/ Graph / turn footer chips) so a researcher can spot repetitive
tool-call loops, redundant identical calls, and plan-completion beats
without hand-scanning the raw log:

- loop-detected  — three or more repeats of the same tool.method
                   signature inside a window, badge in Timeline gutter
                   + colored ring on the Graph node
- redundant-call — identical (tool, arg-hash) call twice without any
                   state change in between, badge + subdued styling
                   so it stays advisory not alarmist
- plan-* chips   — plan/subplan step-complete beats surfaced above the
                   assistant body in the turn footer, wired through
                   the finishTurnContainer tail

Pure rendering; no wire-format changes needed on the runtime side —
signal detection is fixture-driven off the same payload the tri-view
already consumes. Companion RFC L-2 in docs/upstream-ledger.md asks
the runtime to emit these signals natively rather than deriving them
in the shell, so once L-2 lands the shell will consume upstream signals
and this detector becomes a fallback.

Files:
  src/renderer/trace-signal-detect.js       new  (loop/redundant/plan)
  src/renderer/trace-tri-view.js            +24  (wire detector output)
  src/renderer/trace-timeline.js            +39  (badge in gutter)
  src/renderer/trace-graph.js               +30  (colored ring)
  src/renderer/renderer.js                  +81  (finishTurnContainer chips)
  src/renderer/index.html                    +1  (one script tag)
  src/renderer/style.css                    +58  (badge/ring/chip rules)
  docs/upstream-ledger.md                  +121  (append L-2 RFC)
  scripts/qa-trace-signals-fixture.mjs     new  (headless SVG proof)
  scripts/qa-trace-signals-shoot.mjs       new  (CDP live shoot)
  docs/trace-signals-shoot/*.html          new  (3 fixture-driven shots)
  test/trace-signal-detect.test.js         new  (detector unit tests)
  test/trace-signal-overlay.test.js        new  (renderer overlay tests)

Test suite: 1668/1668 pass (17 new). Fixture-driven signals-01-timeline-
loop.html regenerated byte-identical from the merged HEAD.
2026-07-19 00:57:19 -07:00

11582 lines
399 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* DSH Desktop — visual system.
*
* Design language: reference design desktop / Apple macOS. Light by default; dark by
* explicit `data-theme="dark"` on the root, or by `prefers-color-scheme:
* dark` for users on system dark mode. Structure and spacing come first;
* colour is a thin, brand-neutral layer over these tokens. Swap the accent
* HSL and every hover/focus/badge follows without further edits.
*
* The selector families that follow (msg / tool-block / card / widget-* /
* mission-* / market-* / plugins-* / devtools-* / visibility-* / panels-c-*
* / playground-* / onboarding-* / adaptive-layout buckets) are load-bearing —
* renderer.js and the sibling scripts reach into the DOM by exact class /
* id. The visuals may change; the names must not. New surfaces (sidebar-brand,
* nav-item, debug-popover, empty-welcome, prompt-chip, page-title/page-sub,
* icon-btn) come from the reference design IA restructure.
*/
:root {
color-scheme: light dark;
/* Neutrals */
--bg: #ffffff;
--surface: #ffffff;
--bg-elev: #f7f7f8; /* legacy alias — panels used it as "one step up" */
--bg-elev-2: #f2f2f4; /* legacy alias — hover / secondary surface */
--surface-2: #f7f7f8;
--surface-vibrancy: rgba(245, 245, 247, 0.78);
--surface-hover: rgba(0, 0, 0, 0.045);
--surface-active: rgba(0, 0, 0, 0.07);
--text: #1d1d1f;
--text-secondary: #6b6b70;
--text-tertiary: #98989d;
--muted: #6b6b70;
--border: rgba(0, 0, 0, 0.09);
--border-strong: rgba(0, 0, 0, 0.16);
--divider: rgba(0, 0, 0, 0.06);
/* Accent — the single hue used for CTAs, selection, live-state. */
--accent: #2563eb;
--accent-strong: #1d4ed8;
--accent-soft: rgba(37, 99, 235, 0.12);
/* Semantic */
--ok: #16a34a;
--ok-soft: rgba(22, 163, 74, 0.12);
--warn: #d97706;
--warn-soft: rgba(217, 119, 6, 0.14);
--err: #dc2626;
--error: #dc2626; /* legacy alias */
--err-soft: rgba(220, 38, 38, 0.12);
--tool: #b45309; /* legacy alias — tool-family accents */
/* Bubble backgrounds still referenced by older msg rules. */
--user-bubble: #f0f2f5;
--assistant-bubble: transparent;
/* Category tints (icon backgrounds, family accents). */
--tint-blue: #2563eb;
--tint-purple: #7c3aed;
--tint-teal: #0d9488;
--tint-orange: #ea580c;
--tint-pink: #db2777;
--tint-slate: #475569;
/* Radii */
--radius-sm: 6px;
--radius: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
/* Spacing (8px grid; halves for micro adjustments) */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
/* Type */
--font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
"Segoe UI", Roboto, "Noto Sans SC", "Hiragino Sans GB",
"Microsoft YaHei", sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
"Liberation Mono", monospace;
--font-mono: var(--mono);
--fs-hero: 32px;
--fs-page: 28px;
--fs-section: 17px;
--fs-body: 14px;
--fs-sm: 13px;
--fs-xs: 12px;
--fs-micro: 11px;
/* Shadows */
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 1px rgba(0, 0, 0, 0.02);
--shadow-2: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-pop: 0 8px 24px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.06);
/* Layout constants. `--sidebar-w` widened from 244→260 so session titles
* with the `smoke-tr…`/`smoke-ki…` prefixes stop truncating at the fifth
* character; `--stream-w` is the shared max-width used by chat bubbles,
* cards, the empty-welcome block, and now the composer so everything reads
* as one centered column. */
--sidebar-w: 260px;
--stream-w: 760px;
--header-h: 56px;
/* D35 (drift cycle 18): chart series palette. widgets.js reads these
* via `var(--chart-series-0..3)` so the single palette is themable and
* reusable across chart consumers (mission-topo, growth widgets, etc.)
* without touching module code. Series 0 mirrors --accent for
* continuity with app-wide "primary series" cues. */
--chart-series-0: var(--accent);
--chart-series-1: #8ad0a1;
--chart-series-2: #e0b57b;
--chart-series-3: #c88ad0;
}
/* Dark theme — honoured on `[data-theme="dark"]` and, when unset, whenever
* the system reports dark preference. Users can force light with
* `[data-theme="light"]` on <html>. */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #101013;
--surface: #17171a;
--bg-elev: #17171a;
--bg-elev-2: #1c1c20;
--surface-2: #1c1c20;
--surface-vibrancy: rgba(28, 28, 32, 0.82);
--surface-hover: rgba(255, 255, 255, 0.05);
--surface-active: rgba(255, 255, 255, 0.08);
--text: #f2f2f4;
--text-secondary: #a1a1a8;
--text-tertiary: #6c6c75;
--muted: #a1a1a8;
--border: rgba(255, 255, 255, 0.09);
--border-strong: rgba(255, 255, 255, 0.18);
--divider: rgba(255, 255, 255, 0.06);
--accent: #4f8bff;
--accent-strong: #6ea3ff;
--accent-soft: rgba(79, 139, 255, 0.18);
--user-bubble: #22262f;
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
--shadow-2: 0 2px 8px rgba(0, 0, 0, 0.5);
--shadow-pop: 0 12px 32px rgba(0, 0, 0, 0.6);
}
}
:root[data-theme="dark"] {
--bg: #101013;
--surface: #17171a;
--bg-elev: #17171a;
--bg-elev-2: #1c1c20;
--surface-2: #1c1c20;
--surface-vibrancy: rgba(28, 28, 32, 0.82);
--surface-hover: rgba(255, 255, 255, 0.05);
--surface-active: rgba(255, 255, 255, 0.08);
--text: #f2f2f4;
--text-secondary: #a1a1a8;
--text-tertiary: #6c6c75;
--muted: #a1a1a8;
--border: rgba(255, 255, 255, 0.09);
--border-strong: rgba(255, 255, 255, 0.18);
--divider: rgba(255, 255, 255, 0.06);
--accent: #4f8bff;
--accent-strong: #6ea3ff;
--accent-soft: rgba(79, 139, 255, 0.18);
--user-bubble: #22262f;
}
* { box-sizing: border-box; }
/* The app is a fixed viewport grid; the document itself must never scroll.
* Without this, any sidebar section that outgrows 100vh (e.g. a long History
* list) pushes the page into a document scroll below the statusbar. */
html, body { margin: 0; height: 100%; overflow: hidden; }
body {
background: var(--bg); color: var(--text); font-family: var(--font);
font-size: 14px; line-height: 1.5;
-webkit-font-smoothing: antialiased;
}
.app { display: grid; grid-template-columns: 260px 1fr; height: 100vh; }
.sidebar {
background: var(--bg-elev); border-right: 1px solid var(--border);
display: flex; flex-direction: column;
}
.sidebar-head { padding: 12px; border-bottom: 1px solid var(--border); }
.sidebar-foot { padding: 12px; border-top: 1px solid var(--border); }
.sessions { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }
.sessions li {
padding: 8px 10px; border-radius: 6px; cursor: pointer;
color: var(--muted); font-size: 13px;
display: flex; align-items: center; gap: 6px;
}
.sessions li .title {
flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sessions li .live-dot {
width: 6px; height: 6px; border-radius: 50%; background: var(--ok);
flex: 0 0 auto;
animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: 0.35 } }
.sessions li:hover { background: var(--bg-elev-2); color: var(--text); }
.sessions li.active { background: var(--bg-elev-2); color: var(--text); }
.sessions li .indent {
flex: 0 0 auto; color: var(--border);
font-family: var(--mono); font-size: 11px; letter-spacing: 0;
white-space: pre; user-select: none;
}
.sessions li.child .title { color: var(--muted); }
.sessions li.orphan .title::after {
content: ' (orphan)'; color: var(--tool); font-size: 10px;
}
.fork-marker {
align-self: flex-start;
max-width: 780px; width: 100%;
border: 1px dashed var(--accent); border-radius: 6px;
background: rgba(110, 168, 255, 0.05);
padding: 6px 10px; font-size: 12px;
}
.fork-marker .head {
color: var(--accent); font-weight: 600; cursor: pointer; user-select: none;
}
.fork-marker .head::before { content: '⑂ '; }
.fork-marker ul { margin: 6px 0 0; padding-left: 20px; list-style: disc; color: var(--muted); }
.fork-marker ul li { cursor: pointer; padding: 2px 0; }
.fork-marker ul li:hover { color: var(--text); }
.fork-marker ul li .live-dot {
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
background: var(--ok); margin-right: 4px; vertical-align: middle;
animation: pulse 1.4s ease-in-out infinite;
}
.context-card {
align-self: flex-start; max-width: 780px;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
padding: 6px 10px; font-size: 12.5px;
}
.context-card summary {
cursor: pointer; color: var(--muted); list-style: none;
display: flex; align-items: center; gap: 6px;
}
.context-card summary::-webkit-details-marker { display: none; }
/* Round-visual N3 (2026-07-16): replaced a data:image/svg+xml paperclip
* glyph (was refused by our CSP `default-src 'self'`, since we deliberately
* do NOT allow `img-src data:`). A single typographic glyph via `content:`
* has zero CSP surface and preserves the muted-stroke feel — the paperclip
* spirit was "here's an attachment"; the down-arrow is the standard
* typographic marker for "expand for content". */
.context-card summary::before {
content: '\2304'; /* ⌄ down-arrow marker (typographic, not emoji) */
color: var(--muted);
font-size: 12px; line-height: 1;
width: 12px; height: 12px;
display: inline-flex; align-items: center; justify-content: center;
flex: 0 0 auto;
transition: transform 120ms ease;
}
.context-card[open] summary::before { transform: rotate(180deg); }
.context-card summary .src {
color: var(--accent); font-family: var(--mono); font-size: 11px;
}
.context-card summary .hint {
color: var(--muted); overflow: hidden; white-space: nowrap;
text-overflow: ellipsis; flex: 1;
}
.context-card[open] summary::before { /* N3: rotation lives on line 250 now */ }
.context-card .body {
margin-top: 6px; padding: 6px 8px; background: var(--bg);
border-radius: 4px; font-family: var(--mono); font-size: 12px;
white-space: pre-wrap; word-wrap: break-word; color: var(--text);
}
.compact-divider {
align-self: stretch; text-align: center;
color: var(--muted); font-size: 11px; letter-spacing: 0.1em;
margin: 8px 0;
position: relative;
}
.compact-divider::before, .compact-divider::after {
content: ''; position: absolute; top: 50%; width: 30%;
border-top: 1px dashed var(--border);
}
.compact-divider::before { left: 8%; }
.compact-divider::after { right: 8%; }
.compact-divider .tokens { color: var(--tool); margin-left: 6px; }
.msg.assistant { position: relative; }
.msg .fork-here {
position: absolute; top: 6px; right: 8px;
opacity: 0; transition: opacity 0.15s;
font-size: 11px; padding: 2px 6px;
background: var(--bg-elev-2); border: 1px solid var(--border);
color: var(--muted); border-radius: 4px; cursor: pointer;
}
.msg.assistant:hover .fork-here { opacity: 1; }
.msg .fork-here:hover { color: var(--accent); border-color: var(--accent); }
.msg .fork-here.mock::after { content: ' (mock)'; color: var(--tool); font-size: 9px; }
.main { display: flex; flex-direction: column; min-width: 0; min-height: 0; overflow-y: auto; }
.header {
padding: 10px 16px; border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 12px;
}
.title { font-weight: 600; }
.debug { margin-left: auto; display: flex; gap: 8px; }
.stream {
flex: 1; overflow-y: auto; padding: 16px 24px; display: flex; flex-direction: column; gap: 12px;
/* task #164: min-height:0 allows the stream to actually scroll inside its
* parent column-flex; without it a tall body pushes .stream past 100% of
* viewport instead of scrolling internally. */
min-height: 0;
}
/* task #164: prevent flex-column from collapsing children with no internal
* overflow (approval / exit_plan_mode / steer cards, reasoning blocks, tool
* rows). Flex column defaults to shrinking children to fit; that squashes
* fixed-height interaction cards to ~16px whenever the stream body is taller
* than the viewport. Every stream child renders at its intrinsic height and
* the stream itself scrolls (established by min-height:0 above). */
.stream > * { flex-shrink: 0; }
.msg { max-width: 780px; padding: 10px 14px; border-radius: 10px; white-space: pre-wrap; word-wrap: break-word; }
.msg.user { background: var(--user-bubble); align-self: flex-end; }
.msg.assistant { background: var(--assistant-bubble); border: 1px solid var(--border); }
.msg.reasoning { font-style: italic; color: var(--muted); background: transparent; border-left: 2px solid var(--border); border-radius: 0; padding-left: 12px; }
/* task #34 (2026-07-17 user 实机 + team-lead 正面参照): role labels are
* lightweight — small typographic dot glyph + Titlecase word, inline with
* body. The old block-level uppercase caps card ("USER"/"HUMAN") was flagged
* as adding no information increment. Keeps `.role` selector for existing
* tests + DOM queries; the inner `.role-glyph` + `.role-label` carry the new
* two-part shape. LangSmith detail-pane grammar (System/User Titlecase word
* with a small leading icon, body 13px) is the positive reference. */
.msg .role {
display: flex; align-items: baseline; gap: 6px;
font-size: 11px; color: var(--muted);
margin-bottom: 4px;
letter-spacing: 0;
text-transform: none;
}
.msg .role .role-glyph { font-family: var(--mono); color: var(--muted); }
.msg .role .role-label { font-weight: 500; color: var(--muted); }
.tool-block {
/* task #159: LangSmith-row shape. The old ~780px centered card with orange
* mono heading was 老板 "太丑了" 反馈的直接靶子; the row is now full-width,
* left-aligned, single-line summary ~28px tall. Left color edge stays
* (family band), but the accent-orange mono name is retired — neutral
* text with a per-family tint via .tool-family-name. Args + result are
* still full disclosure on click, capped at 320px per density-spec §2 L1. */
background: var(--bg-elev); border: 1px solid var(--border);
border-left-width: 2px;
border-radius: 4px;
padding: 4px 10px 4px 10px;
font-family: var(--mono); font-size: 12.5px;
max-width: none; width: 100%;
min-height: 28px;
margin: 2px 0;
}
.tool-block summary {
cursor: pointer; color: var(--text); list-style: none;
display: flex; align-items: center; gap: 8px;
min-height: 20px;
}
.tool-block summary::-webkit-details-marker { display: none; }
/* Round-visual N3: chevron via pure-CSS border triangle (right-pointing,
* rotates 90deg when `[open]`). Replaces a `data:image/svg+xml` background
* that CSP `default-src 'self'` refused. Border-triangle also carries
* currentColor via `border-left-color`, so the chevron tints with the row
* text automatically — the SVG-with-baked-hex approach couldn't. */
.tool-block summary::before {
content: '';
width: 0; height: 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 5px solid var(--muted);
transition: transform 120ms ease;
flex: 0 0 auto;
}
.tool-block[open] summary::before { transform: rotate(90deg); }
.tool-block .args, .tool-block .result {
margin-top: 6px; padding: 6px 8px; background: var(--bg); border-radius: 4px;
white-space: pre-wrap; word-wrap: break-word;
max-height: 320px; overflow: auto; /* density-spec §2 L1 rule */
}
.tool-block .label { color: var(--muted); font-size: 11px; }
.card {
max-width: 780px; background: var(--bg-elev); border: 1px solid var(--accent);
border-radius: 10px; padding: 12px 14px;
}
.card h4 { margin: 0 0 4px 0; font-size: 13px; }
.card .actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.card.disabled { opacity: 0.45; border-color: var(--border); }
.card.disabled button { pointer-events: none; }
.system {
color: var(--muted); font-size: 12px; font-style: italic;
padding: 4px 0; text-align: center;
}
.composer {
border-top: 1px solid var(--border); padding: 10px 16px;
display: flex; gap: 10px; align-items: flex-end;
}
.composer textarea {
flex: 1; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
color: var(--text); padding: 8px 10px; resize: none; font: inherit; font-family: var(--font);
}
.composer textarea:focus { outline: none; border-color: var(--accent); }
/* Legacy rule once stacked Cancel above Send in a right-hand column; the
* composer-bar layout wants a single trailing row (Cancel · Send) aligned
* with the meta chips. Kept as a row so no later section needs to fight it. */
.composer-actions { display: flex; flex-direction: row; align-items: center; gap: 6px; }
button {
border: 1px solid var(--border); background: var(--bg-elev); color: var(--text);
padding: 6px 12px; border-radius: 6px; cursor: pointer; font: inherit; font-size: 13px;
}
button:hover:not(:disabled) { background: var(--bg-elev-2); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
button.primary { background: var(--accent); color: #0a0d14; border-color: var(--accent); font-weight: 600; }
button.primary:hover:not(:disabled) { filter: brightness(1.1); background: var(--accent); }
button.ghost { background: transparent; }
.statusbar {
padding: 6px 16px; border-top: 1px solid var(--border);
display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted);
}
.statusbar .spacer { flex: 1; }
.statusbar .muted { color: var(--muted); }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.dot.running { background: var(--ok); }
.dot.starting, .dot.respawning { background: var(--tool); }
.dot.crashed, .dot.dead { background: var(--error); }
.field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.field select {
background: var(--bg-elev-2); border: 1px solid var(--border); color: var(--text);
padding: 6px 8px; border-radius: 6px;
}
/* --- widget renderer ---------------------------------------------------- */
.widget {
background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
padding: 10px 12px; margin-top: 4px; display: flex; flex-direction: column; gap: 10px;
font-family: var(--font); font-size: 13px; color: var(--text);
max-height: 420px; overflow: auto;
}
.widget-body { display: flex; flex-direction: column; gap: 8px; }
.widget-caption { color: var(--muted); font-size: 11.5px; }
.widget-unsupported { color: var(--muted); font-style: italic; font-size: 12px; }
/* table */
.widget-table table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.widget-table th, .widget-table td {
padding: 5px 8px; border-bottom: 1px solid var(--border);
text-align: left; vertical-align: top;
}
.widget-table th { color: var(--muted); font-weight: 600; text-transform: uppercase; font-size: 10.5px; letter-spacing: 0.04em; }
.widget-table tbody tr:last-child td { border-bottom: none; }
.widget-table tbody tr:hover { background: var(--bg-elev); }
/* chart */
.widget-chart-svg { width: 100%; height: auto; display: block; }
.widget-chart-legend { display: flex; gap: 14px; font-size: 11.5px; color: var(--muted); flex-wrap: wrap; }
.widget-chart-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.widget-chart-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }
/* options */
.widget-options-q { font-weight: 600; font-size: 13.5px; margin-bottom: 2px; }
.widget-options-list { display: flex; flex-direction: column; gap: 4px; }
.widget-option {
border: 1px solid var(--border); background: var(--bg-elev); color: var(--text);
border-radius: 6px; padding: 8px 10px; text-align: left; cursor: pointer;
font: inherit; font-size: 12.5px; display: flex; flex-direction: column; gap: 2px;
}
.widget-option:hover { background: var(--bg-elev-2); border-color: var(--accent); }
.widget-option-label { font-weight: 500; }
.widget-option-hint { font-size: 11px; color: var(--muted); }
/* kv */
.widget-kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; margin: 0; font-size: 12.5px; }
.widget-kv dt { color: var(--muted); font-weight: 500; }
.widget-kv dd { margin: 0; }
/* action row */
.widget-actions { display: flex; gap: 6px; flex-wrap: wrap; border-top: 1px dashed var(--border); padding-top: 8px; }
.widget-actions button { font-size: 12px; padding: 4px 10px; }
.widget-actions button.danger { border-color: var(--error); color: var(--error); background: transparent; }
/* --- adaptive layout (heuristic buckets) -------------------------------- *
* Body carries one of .layout-chat / .layout-code-review / .layout-artifact
* / .layout-monitor at all times. The variables below are the levers each
* bucket tweaks — main-column width, chat gutter, chat font size, tool
* block open-by-default, right-rail width. Nothing here alters existing
* selectors, so other layout work continues untouched.
*/
:root {
--layout-stream-max: 780px; /* max-width of chat bubbles / tool blocks */
--layout-stream-pad: 16px; /* horizontal padding of .stream */
--layout-stream-gap: 12px; /* vertical gap between stream items */
--layout-chat-fs: 14px; /* body font-size in stream */
--layout-rail-w: 0px; /* right-rail width (artifact bucket only) */
--layout-tool-open: initial; /* used to force <details> open by default */
--layout-scroll-behavior: auto;
--layout-transition: 220ms ease;
}
body { transition: background var(--layout-transition); }
body .app { transition: grid-template-columns var(--layout-transition); }
body .stream {
transition: padding var(--layout-transition), font-size var(--layout-transition);
scroll-behavior: var(--layout-scroll-behavior);
font-size: var(--layout-chat-fs);
padding-left: var(--layout-stream-pad);
padding-right: var(--layout-stream-pad);
gap: var(--layout-stream-gap);
}
/* Widen bubbles / tool blocks against the current bucket's max. Rules
* are scoped by body class so unrelated layouts don't inherit. */
body.layout-code-review .msg,
body.layout-code-review .tool-block,
body.layout-code-review .widget,
body.layout-code-review .context-card { max-width: var(--layout-stream-max); }
body.layout-code-review {
--layout-stream-max: 1080px;
--layout-stream-pad: 20px;
}
body.layout-code-review .tool-block[open] .args,
body.layout-code-review .tool-block[open] .result {
font-size: 12.5px;
}
/* Force tool blocks open by default so diffs / patch results are visible
* without a click. A user's manual toggle still wins (details keeps its
* own open state on the element). We rely on `[open]` scoping already
* present in existing rules. */
body.layout-code-review .tool-block:not([data-collapsed]) {
/* Hack: <details> is only open when the `open` attribute is set. We
* cannot force that via CSS, so layout-controller.js also sets `open`
* on any newly-appended .tool-block while this class is active. */
}
body.layout-artifact {
--layout-rail-w: 320px;
--layout-stream-max: 720px; /* narrower main so the rail fits */
}
/* Reserve a right-rail column via a subtle right-padding on .main; the
* artifact card can slot into it, or another module can drop a preview
* webview there. Keeps DOM structure identical to the chat layout. */
body.layout-artifact .main {
padding-right: var(--layout-rail-w);
transition: padding var(--layout-transition);
position: relative;
}
body.layout-artifact .layout-rail-slot {
position: absolute; top: 48px; right: 0;
width: var(--layout-rail-w); bottom: 32px;
border-left: 1px solid var(--border);
background: var(--bg-elev);
overflow-y: auto;
}
body.layout-monitor {
--layout-stream-pad: 10px;
--layout-stream-gap: 6px;
--layout-chat-fs: 12.5px;
--layout-scroll-behavior: smooth;
}
body.layout-monitor .msg { padding: 6px 10px; border-radius: 6px; }
body.layout-monitor .tool-block { padding: 4px 10px; }
/* Layout indicator (header widget). Low-key text button, dropdown pinned
* beneath. No background fanfare — the point is glanceable, not shouty. */
/* The indicator sits in .header-actions next to ghost/small buttons; its
* metrics must match theirs exactly (same padding/font/radius/height) or the
* row reads misaligned. No extra margin — the flex gap owns spacing. */
.layout-indicator { position: relative; display: inline-flex; }
.layout-indicator-btn {
font-family: var(--font);
/* Neighbours in .header-actions are `ghost small` (4px 8px, --fs-xs) —
* match that tier exactly, not the full-size ghost tier, or this button
* reads taller than Quick chat / Debug beside it. */
font-size: var(--fs-xs); font-weight: 500;
padding: 4px 8px; border-radius: var(--radius-sm);
background: transparent; color: var(--text); border: 1px solid var(--border);
display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
cursor: pointer;
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.layout-indicator-btn:hover { background: var(--surface-hover); }
.layout-indicator-btn:hover { color: var(--text); border-color: var(--accent); }
.layout-indicator-btn.locked { border-color: var(--tool); color: var(--tool); }
.layout-indicator-dropdown {
position: absolute; top: calc(100% + 4px); right: 0;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
padding: 4px; z-index: 10; display: flex; flex-direction: column; gap: 2px;
min-width: 140px; box-shadow: 0 4px 12px rgba(0,0,0,0.35);
}
.layout-indicator-item {
background: transparent; color: var(--text); border: none; text-align: left;
padding: 4px 8px; border-radius: 4px; font-size: 12px; cursor: pointer;
}
.layout-indicator-item:hover { background: var(--bg-elev-2); }
.layout-indicator-item.active { color: var(--accent); }
.layout-indicator-auto { border-top: 1px solid var(--border); margin-top: 2px; padding-top: 6px; color: var(--muted); }
/* Transient toast fired on every layout switch. Anchored top-right under the
* header. The visible-difference tell for layouts whose stream-side effects
* only kick in once diffs/artifacts/etc show up. */
.layout-toast {
position: fixed; top: 62px; right: 20px; z-index: 30;
padding: 6px 12px; border-radius: 6px;
background: var(--bg-elev-2, var(--bg-elev)); color: var(--text);
border: 1px solid var(--accent);
font-size: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.4);
opacity: 0; transform: translateY(-4px);
transition: opacity 180ms ease, transform 180ms ease;
pointer-events: none;
}
.layout-toast.show { opacity: 1; transform: translateY(0); }
/* Artifact-rail empty-state placeholder. Renders inside .layout-rail-slot
* whenever the artifact bucket is active AND no artifact card exists yet,
* so switching to `artifact` has an immediate visual tell. */
.layout-rail-empty {
padding: 24px 16px; color: var(--muted); text-align: center;
display: flex; flex-direction: column; gap: 6px; align-items: center;
}
.layout-rail-empty .layout-rail-icon { font-size: 24px; opacity: 0.6; }
.layout-rail-empty .layout-rail-title { font-size: 12px; color: var(--text); font-weight: 500; }
.layout-rail-empty .layout-rail-sub { font-size: 11px; line-height: 1.5; }
.layout-rail-empty code { font-size: 10.5px; background: var(--bg); padding: 1px 4px; border-radius: 3px; }
/* Faint bucket-specific stream tint so an empty stream still shows a diff
* between chat and code-review. Kept below tool-block/msg colors so no
* existing surface is affected. */
body.layout-code-review .stream {
background: linear-gradient(to bottom, transparent, color-mix(in oklab, var(--accent) 3%, transparent));
}
body.layout-monitor .stream {
background: color-mix(in oklab, var(--muted) 4%, transparent);
}
/* --- artifact card (density-spec §2 L0 row form, 2026-07-18) ------------ */
/* Each artifact renders as a compact ~28px row (native <details>) with an
* L1 body that expands inline on click. Consecutive rows in the stream
* fuse into a visual group via `:has()` (see block below). No hero button:
* the L0 action is a tiny `open ↗` link; the ghost "Open in browser"
* button lives in the L1 body. */
.artifact-card {
align-self: flex-start; max-width: 780px; width: 100%;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
padding: 0;
/* <details> gets a UA margin (~12px in some Chromiums) that would
* fight the group's zero-gap layout. Zero it explicitly. */
margin: 0;
transition: border-color 0.4s, background 0.4s;
}
.artifact-card.artifact-flash {
border-color: var(--accent);
background: color-mix(in oklab, var(--accent) 8%, var(--bg-elev));
}
.artifact-row {
display: flex; align-items: center; gap: 8px;
padding: 4px 12px; min-height: 28px;
cursor: pointer; list-style: none;
font-size: 12px; color: var(--text);
}
/* Hide the native <summary> marker; the row itself is the affordance. */
.artifact-row::-webkit-details-marker { display: none; }
.artifact-row::marker { content: ''; }
.artifact-card:hover { background: var(--bg-elev-2); }
.artifact-icon {
display: inline-flex; align-items: center; justify-content: center;
width: 14px; height: 14px; flex: 0 0 auto; color: var(--muted);
}
.artifact-icon svg { display: block; }
.artifact-name {
font-family: var(--mono); font-size: 12px; color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
flex: 1 1 auto; min-width: 0;
}
.artifact-kind {
flex: 0 0 auto;
text-transform: uppercase; letter-spacing: 0.05em;
font-size: 10px; padding: 1px 6px; border-radius: 3px;
background: var(--bg-elev-2); color: var(--muted);
}
.artifact-version {
flex: 0 0 auto;
color: var(--accent); font-family: var(--mono); font-size: 11px;
}
.artifact-live-dot {
flex: 0 0 auto;
width: 6px; height: 6px; border-radius: 50%; background: var(--ok);
animation: pulse 1.4s ease-in-out infinite;
}
.artifact-open-link {
flex: 0 0 auto;
font-size: 11px; color: var(--muted); text-decoration: none;
padding: 2px 4px; border-radius: 3px;
}
.artifact-open-link:hover { color: var(--accent); background: var(--accent-soft); }
.artifact-open-link[aria-disabled="true"] { pointer-events: none; opacity: 0.6; }
.artifact-open-link.is-busy { color: var(--accent); }
/* L1 body — inline expansion below the row. */
.artifact-body-l1 {
padding: 8px 12px 12px 34px; /* 34 = 12 + 14 icon + 8 gap */
display: flex; flex-direction: column; gap: 8px;
font-size: 11px; color: var(--muted);
border-top: 1px solid var(--border);
}
.artifact-body-path { display: flex; align-items: baseline; gap: 8px; }
.artifact-body-path-label {
text-transform: uppercase; letter-spacing: 0.05em; font-size: 10px;
color: var(--text-tertiary);
}
.artifact-body-path-val {
font-family: var(--mono); font-size: 11px; color: var(--text);
background: var(--bg-elev-2); padding: 1px 6px; border-radius: 3px;
overflow-wrap: anywhere;
}
.artifact-body-actions { display: flex; gap: 8px; }
.artifact-open { font-size: 12px; padding: 4px 10px; }
/* Auto-group: consecutive artifact cards get wrapped in an
* `.artifact-group` container by the renderer so the group owns its own
* zero-gap layout (the stream's 12px flex-gap would otherwise dominate).
* Cards inside a group share a single seam — the first keeps its top
* corners rounded, the last keeps bottom, middles are square. The
* cascade order here matters: reset all radii and top-border inside a
* group, then re-add radii to the true first-of-type / last-of-type. */
.artifact-group {
align-self: flex-start; max-width: 780px; width: 100%;
display: flex; flex-direction: column; gap: 0;
}
.artifact-group > .artifact-card {
max-width: none; width: 100%;
border-radius: 0;
border-top: none;
/* Override the shared card-family `margin: var(--space-3) 0` rule
* (further down the sheet) so grouped cards actually sit flush. */
margin: 0;
}
.artifact-group > .artifact-card:first-child {
border-top: 1px solid var(--border);
border-top-left-radius: 8px; border-top-right-radius: 8px;
}
.artifact-group > .artifact-card:last-child {
border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;
}
/* Also fuse via `:has()` for the future where cards might sit as direct
* stream children (single-shot cases the wrapper doesn't touch). */
.artifact-card + .artifact-card {
border-top-left-radius: 0; border-top-right-radius: 0;
}
.artifact-card:has(+ .artifact-card) {
border-bottom-left-radius: 0; border-bottom-right-radius: 0;
}
/* --- tab nav (Chat / Plugins) ------------------------------------------- */
.tab-nav {
display: flex; border-bottom: 1px solid var(--border);
background: var(--bg);
}
.tab-btn {
flex: 1; background: transparent; border: none; color: var(--muted);
padding: 10px 8px; border-radius: 0; border-bottom: 2px solid transparent;
cursor: pointer; font: inherit; font-size: 13px;
}
.tab-btn:hover:not(:disabled) { color: var(--text); background: var(--bg-elev-2); }
.tab-btn.active {
color: var(--accent); border-bottom-color: var(--accent);
background: var(--bg-elev);
}
.tab-panel { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.tab-panel[hidden] { display: none; }
.main .pane { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.main .pane[hidden] { display: none; }
.sidebar { min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
/* Injected sections (History etc.) scroll inside their tab panel, never grow
* the sidebar. panels-c-controller mounts the History <div> inside the
* chat-tab panel (not as a direct child of `.sidebar`), so the selector has
* to reach two levels deep. */
.sidebar > .tab-panel > .panels-c-history {
flex: 1 1 auto; min-height: 0; overflow-y: auto;
}
.sidebar-foot { display: flex; flex-direction: column; gap: 8px; }
.plugins-sidebar-note {
padding: 10px 12px; font-size: 12px; color: var(--muted);
}
.plugins-sidebar-note.dirty { color: var(--tool); }
/* --- Plugins pane ------------------------------------------------------- */
.plugins-body {
flex: 1; overflow-y: auto; padding: 16px 24px;
display: flex; flex-direction: column; gap: 12px;
}
.plugins-meta {
padding: 8px 10px; background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 6px; font-size: 12px; color: var(--muted);
}
.plugins-meta code {
background: var(--bg); padding: 1px 5px; border-radius: 3px;
font-family: var(--mono); font-size: 11.5px; color: var(--text);
}
.plugins-meta .ok { color: var(--ok); }
.plugins-meta .error { color: var(--error); }
.plugins-meta .muted { color: var(--muted); }
.plugins-table {
width: 100%; border-collapse: collapse; font-size: 13px;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 6px;
}
.plugins-table th, .plugins-table td {
padding: 8px 10px; border-bottom: 1px solid var(--border);
text-align: left; vertical-align: middle;
}
.plugins-table th {
color: var(--muted); font-weight: 600; text-transform: uppercase;
font-size: 10.5px; letter-spacing: 0.04em; background: var(--bg);
}
.plugins-table tbody tr:last-child td { border-bottom: none; }
.plugins-table tbody tr:hover { background: var(--bg-elev-2); }
.plugins-table tbody tr.disabled { opacity: 0.55; }
.plugins-table td.mono { font-family: var(--mono); font-size: 12px; }
.plugins-table td.muted { color: var(--muted); }
.plugins-table td.source-cell {
font-family: var(--mono); font-size: 11px; text-transform: uppercase;
color: var(--muted); letter-spacing: 0.05em;
}
.plugins-table td.source-cell.user { color: var(--accent); }
.plugins-table td.runtime-cell {
font-family: var(--mono); font-size: 11px; text-transform: uppercase;
color: var(--muted); letter-spacing: 0.05em;
}
.plugins-table td.runtime-cell.muted { opacity: 0.6; }
.plugins-table .runtime-state { display: inline-block; padding: 1px 6px; border-radius: 4px; }
.plugins-table .runtime-active { color: #4ec9a0; }
.plugins-table .runtime-loading { color: #dcdcaa; }
.plugins-table .runtime-pending { color: var(--muted); }
.plugins-table .runtime-failed { color: #f28b82; font-weight: 600; }
.plugins-table .runtime-disposed, .plugins-table .runtime-unloading { color: var(--muted); }
.plugins-table .runtime-absent { color: var(--muted); font-style: italic; }
.plugins-table .runtime-mismatch { color: #e8b64c; font-weight: 700; margin-right: 4px; }
.plugins-table tr.runtime-mismatch-row { background: rgba(232, 182, 76, 0.06); }
.plugins-table tr.runtime-extras-head td {
font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--muted); padding-top: 12px; border-top: 1px dashed var(--border);
}
.plugins-table tr.runtime-extras-row { opacity: 0.75; }
.plugins-table td.empty {
text-align: center; color: var(--muted); font-style: italic; padding: 24px 10px;
}
.plugins-table input[type="checkbox"] { cursor: pointer; }
/* --- onboarding overlay ------------------------------------------------- */
.onboarding {
position: fixed; inset: 0; z-index: 100;
background: rgba(6, 8, 14, 0.85);
display: flex; align-items: center; justify-content: center;
padding: 40px 20px;
}
.onboarding[hidden] { display: none; }
.onboarding.committing { pointer-events: none; opacity: 0.6; }
.onboarding-card {
max-width: 640px; width: 100%;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 12px;
padding: 28px 32px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.4);
}
.onboarding-progress {
display: flex; gap: 6px; margin-bottom: 20px;
}
.onboarding-progress .dot {
width: 22px; height: 4px; border-radius: 2px; background: var(--border);
}
.onboarding-progress .dot.on { background: var(--accent); }
.onboarding-step h2 { margin: 0 0 6px 0; font-size: 20px; }
.onboarding-step p.muted { margin: 0 0 22px 0; color: var(--muted); font-size: 13px; }
.onboarding-cards {
display: grid; grid-template-columns: 1fr; gap: 10px; margin-bottom: 20px;
}
.onboarding-choice {
background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
padding: 14px 16px; text-align: left; cursor: pointer;
color: var(--text); font: inherit;
transition: border-color 0.15s, background 0.15s;
display: flex; flex-direction: column; gap: 4px;
}
.onboarding-choice:hover { border-color: var(--accent); background: var(--bg-elev-2); }
.onboarding-choice.selected {
border-color: var(--accent); background: rgba(110, 168, 255, 0.08);
}
.onboarding-choice .lbl { font-weight: 600; font-size: 14px; }
.onboarding-choice .hint { color: var(--muted); font-size: 12px; }
.onboarding-actions { display: flex; align-items: center; }
.onboarding-actions .spacer { flex: 1; }
/* --- plugins validation diagnostics ------------------------------------- */
.plugins-diagnostics {
padding: 8px 12px; margin-bottom: 12px;
border-radius: 6px; font-size: 12px;
border: 1px solid var(--border);
background: var(--bg-elev);
}
.plugins-diagnostics.ok {
border-color: var(--ok); color: var(--ok);
}
.plugins-diagnostics.warn {
border-color: var(--tool); color: var(--tool);
}
.plugins-diagnostics.error {
border-color: var(--error); color: var(--error);
}
.plugins-diagnostics .muted { color: var(--muted); }
/* --- plugins effect-heuristics info bar (A3) ---------------------------- */
/* Compact summary that sits above the diagnostics strip. Purely informational
* — not a warning surface — so it uses the elevated-bg treatment without an
* accent border. Fields are separated by dot-bullets and hover-tooltip'd. */
.plugins-summary {
display: flex; flex-wrap: wrap; align-items: center;
gap: 6px 14px;
padding: 6px 12px; margin-bottom: 8px;
border-radius: 6px; font-size: 12px;
border: 1px solid var(--border);
background: var(--bg-elev);
color: var(--text);
}
.plugins-summary .field { display: inline-flex; align-items: center; gap: 4px; }
.plugins-summary .field .label { color: var(--muted); }
.plugins-summary .field .value { font-variant-numeric: tabular-nums; }
.plugins-summary .field.warn { color: var(--tool); }
.plugins-summary .field.warn .label { color: var(--tool); opacity: 0.85; }
.plugins-summary .conflict-item { font-family: var(--mono); }
.plugins-table tr.has-error { border-left: 3px solid var(--error); }
.plugins-table tr.has-warn { border-left: 3px solid var(--tool); }
.plugins-table tr.plugin-diagnostic td {
border-top: none !important; border-bottom: 1px solid var(--border);
font-size: 11.5px; padding: 4px 12px 8px 40px;
font-family: var(--mono);
}
.plugins-table tr.plugin-diagnostic.error td { color: var(--error); }
.plugins-table tr.plugin-diagnostic.warn td { color: var(--tool); }
/* --- Mission Control ----------------------------------------------------- *
* Long-running task management view. Three subviews (tree/topo/board) share
* the same top-bar (summary chips + cross-session ticker). Palette reuses
* the shell's existing variables so light-mode/dark-mode toggles (if they
* ever ship) don't require a second pass; only the accent categoricals
* (--mission-cat-*) are new, defined off the accent+tool+ok+error base.
*/
:root {
--mission-cat-1: var(--accent); /* running / primary */
--mission-cat-2: var(--ok); /* finished / success */
--mission-cat-3: var(--tool); /* pending / advisory */
--mission-cat-4: var(--error); /* orphan / error */
--mission-cat-neutral: var(--muted); /* idle */
}
.mission-sidebar-note {
padding: 10px 12px; font-size: 12px; color: var(--muted);
}
.mission-body {
flex: 1; overflow-y: auto; padding: 12px 16px;
display: flex; flex-direction: column; gap: 12px; min-height: 0;
}
.mission-topbar {
display: flex; gap: 12px; align-items: stretch;
border: 1px solid var(--border); border-radius: 8px;
background: var(--bg-elev); padding: 10px 12px;
}
.mission-summary {
display: flex; gap: 12px; flex-wrap: wrap; align-items: center;
border-right: 1px solid var(--border); padding-right: 14px;
}
.mission-chip {
display: flex; flex-direction: column; align-items: flex-start;
min-width: 56px;
}
.mission-chip-value {
font-size: 18px; font-weight: 600; color: var(--text);
font-family: var(--mono);
}
.mission-chip-label {
font-size: 10.5px; color: var(--muted); text-transform: uppercase;
letter-spacing: 0.05em;
}
.mission-chip.running .mission-chip-value { color: var(--mission-cat-2); }
.mission-chip.todos .mission-chip-value { color: var(--mission-cat-3); }
.mission-ticker {
flex: 1; min-width: 0; overflow: hidden;
display: flex; gap: 12px; align-items: center;
font-size: 12px; color: var(--muted);
padding-left: 4px;
white-space: nowrap; text-overflow: ellipsis;
}
.mission-ticker-hint { color: var(--muted); font-style: italic; }
.mission-ticker-item {
display: inline-flex; align-items: center; gap: 6px;
flex-shrink: 0;
}
.mission-ticker-badge {
border: 1px solid var(--border); background: var(--bg);
border-radius: 3px; padding: 1px 6px;
color: var(--accent); font-family: var(--mono); font-size: 10.5px;
cursor: pointer;
}
.mission-ticker-badge:hover { border-color: var(--accent); }
.mission-ticker-text {
color: var(--muted); overflow: hidden; text-overflow: ellipsis;
max-width: 240px;
}
.mission-subview-tabs {
display: flex; gap: 4px; border-bottom: 1px solid var(--border);
}
.mission-subview-tab {
border: none; background: transparent; color: var(--muted);
padding: 6px 14px; border-radius: 0;
border-bottom: 2px solid transparent;
cursor: pointer; font: inherit; font-size: 13px;
}
.mission-subview-tab:hover:not(:disabled) { color: var(--text); }
.mission-subview-tab.active {
color: var(--accent); border-bottom-color: var(--accent);
}
.mission-panels {
flex: 1; min-height: 0;
display: flex; flex-direction: column;
}
.mission-panel { flex: 1; min-height: 0; overflow-y: auto; }
.mission-panel[hidden] { display: none; }
.mission-empty {
color: var(--muted); font-style: italic; font-size: 13px;
padding: 24px; text-align: center;
}
/* --- tree subview ---------- */
.mission-tree {
display: flex; flex-direction: column; gap: 2px;
padding: 8px;
}
.mission-tree-row {
position: relative;
padding: 8px 10px 6px 10px;
border-radius: 6px;
border: 1px solid transparent;
background: var(--bg-elev);
display: grid;
grid-template-columns: auto auto 1fr auto auto;
grid-template-rows: auto auto;
align-items: center;
column-gap: 8px;
font-size: 13px;
}
.mission-tree-row.running { border-color: color-mix(in oklab, var(--mission-cat-1) 40%, var(--border)); }
.mission-tree-row.orphan { border-left: 3px solid var(--mission-cat-4); }
.mission-tree-row:hover { background: var(--bg-elev-2); }
.mission-tree-indent {
color: var(--border); font-family: var(--mono); font-size: 11px;
white-space: pre; user-select: none; grid-row: 1;
}
.mission-tree-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--mission-cat-neutral); flex-shrink: 0; grid-row: 1;
}
.mission-tree-dot.running {
background: var(--mission-cat-2); box-shadow: 0 0 0 3px color-mix(in oklab, var(--mission-cat-2) 22%, transparent);
animation: pulse 1.4s ease-in-out infinite;
}
.mission-tree-dot.finished { background: var(--mission-cat-1); }
.mission-tree-dot.idle { background: var(--mission-cat-neutral); }
.mission-tree-title {
color: var(--text); font-weight: 500; cursor: pointer; grid-row: 1;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
min-width: 0;
}
.mission-tree-title:hover { color: var(--accent); }
.mission-tree-collapse {
background: transparent; border: none; color: var(--muted);
padding: 0 4px; cursor: pointer; font-size: 12px; grid-row: 1;
}
.mission-tree-collapse:hover { color: var(--text); }
.mission-tree-metrics {
display: flex; gap: 8px; align-items: center; grid-row: 1;
font-size: 11px; color: var(--muted); font-family: var(--mono);
}
.mission-tree-rel { color: var(--muted); }
.mission-tree-count { padding: 1px 6px; border-radius: 3px; background: var(--bg); }
.mission-tree-count.tools { color: var(--tool); }
.mission-tree-count.todos { color: var(--mission-cat-1); }
.mission-tree-sub {
grid-column: 1 / -1; grid-row: 2;
color: var(--muted); font-size: 11.5px;
padding-left: 20px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* --- topology subview ---------- */
.mission-topo {
position: relative; width: 100%; height: 100%; min-height: 400px;
overflow: hidden;
}
.mission-topo-svg { display: block; background: var(--bg); border-radius: 6px; }
.mission-topo-edge {
fill: none; stroke: var(--border); stroke-width: 1.5;
opacity: 0.7;
}
.mission-topo-edge.active {
stroke: var(--mission-cat-1);
stroke-dasharray: 5 4;
animation: mission-edge-flow 1.2s linear infinite;
opacity: 1;
}
@keyframes mission-edge-flow {
to { stroke-dashoffset: -18; }
}
.mission-topo-node {
cursor: pointer;
}
/* Fill/stroke come from JS inline attributes (family palette). CSS only
* covers the neutral fallback + hover accent + transitions. */
.mission-topo-node .dot {
stroke-width: 2;
transition: stroke 120ms ease, filter 120ms ease;
}
.mission-topo-node:not([data-family]) .dot {
fill: var(--bg-elev-2);
stroke: var(--muted);
}
.mission-topo-node.running .dot {
filter: saturate(1.1);
}
.mission-topo-node .halo {
fill: none; stroke-width: 1;
opacity: 0.55;
animation: mission-halo 1.6s ease-in-out infinite;
}
@keyframes mission-halo {
0%,100% { r: 12; opacity: 0.6; }
50% { r: 18; opacity: 0.15; }
}
.mission-topo-node:hover .dot { stroke: var(--accent); }
.mission-topo-node .label {
fill: var(--text); font-size: 11px; font-family: var(--font);
pointer-events: none; user-select: none;
}
.mission-topo-tip {
position: absolute; z-index: 5;
background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 6px; padding: 8px 10px;
font-size: 12px; color: var(--text);
box-shadow: 0 4px 12px rgba(0,0,0,0.35);
pointer-events: none;
max-width: 260px;
}
.mission-topo-tip-title { font-weight: 600; margin-bottom: 2px; }
.mission-topo-tip-meta { color: var(--muted); font-size: 11px; }
.mission-topo-tip-sub { color: var(--muted); font-size: 11.5px; margin-top: 4px; font-style: italic; }
/* --- board subview ---------- */
.mission-board { padding: 8px; }
.mission-board-grid {
display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px; align-items: start;
}
.mission-board-column {
background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 8px; padding: 8px; min-height: 200px;
display: flex; flex-direction: column; gap: 8px;
}
.mission-board-column-head {
font-size: 11.5px; font-weight: 600; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--muted);
padding: 4px 6px 6px; border-bottom: 1px solid var(--border);
}
.mission-board-column.pending .mission-board-column-head { color: var(--mission-cat-3); }
.mission-board-column.in_progress .mission-board-column-head { color: var(--mission-cat-1); }
.mission-board-column.completed .mission-board-column-head { color: var(--mission-cat-2); }
.mission-board-column-body {
display: flex; flex-direction: column; gap: 6px;
}
.mission-board-column-empty {
color: var(--muted); text-align: center; font-size: 12px;
padding: 12px 0;
}
.mission-board-card {
background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
padding: 8px 10px; display: flex; flex-direction: column; gap: 6px;
font-size: 12.5px;
}
.mission-board-card.pending { border-left: 3px solid var(--mission-cat-3); }
.mission-board-card.in_progress { border-left: 3px solid var(--mission-cat-1); }
.mission-board-card.completed { border-left: 3px solid var(--mission-cat-2); opacity: 0.8; }
.mission-board-card.completed .mission-board-card-content { text-decoration: line-through; color: var(--muted); }
.mission-board-card-content {
color: var(--text); line-height: 1.4;
overflow-wrap: anywhere;
}
.mission-board-card-badge {
align-self: flex-start; font-family: var(--mono); font-size: 10.5px;
padding: 1px 6px; border-radius: 3px;
background: var(--bg-elev); color: var(--accent);
border: 1px solid var(--border); cursor: pointer;
}
.mission-board-card-badge:hover { border-color: var(--accent); }
/* --- Board empty-state preview -----------------------------------------
* QA round-3 §5.1: the single-sentence empty state didn't teach the user
* what the view fills in with. We wrap the copy in the same .mission-empty
* shell tree/topo use, then stack a ghost mini-kanban below — three column
* headers with one dashed placeholder card each. Dashed borders + reduced
* opacity keep the preview visually distinct from real cards. */
.mission-board-empty {
align-items: center; text-align: center; gap: 10px;
}
.mission-board-preview {
margin-top: 16px;
display: grid; grid-template-columns: repeat(3, minmax(120px, 160px));
gap: 12px; width: 100%; max-width: 520px;
pointer-events: none;
}
.mission-board-preview-column {
display: flex; flex-direction: column; gap: 6px;
padding: 8px; border-radius: 8px;
background: var(--bg); border: 1px dashed var(--border);
}
.mission-board-preview-head {
font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em;
color: var(--muted); text-align: left;
padding: 2px 4px 4px; border-bottom: 1px dashed var(--border);
}
.mission-board-preview-column.pending .mission-board-preview-head { color: var(--mission-cat-3); }
.mission-board-preview-column.in_progress .mission-board-preview-head { color: var(--mission-cat-1); }
.mission-board-preview-column.completed .mission-board-preview-head { color: var(--mission-cat-2); }
.mission-board-preview-card {
padding: 8px 10px; border-radius: 6px;
background: transparent;
border: 1px dashed var(--border);
color: var(--muted); font-size: 12px; line-height: 1.35;
text-align: left; opacity: 0.78;
}
.mission-board-preview-card.pending { border-left: 3px dashed var(--mission-cat-3); }
.mission-board-preview-card.in_progress { border-left: 3px dashed var(--mission-cat-1); }
.mission-board-preview-card.completed { border-left: 3px dashed var(--mission-cat-2); }
/* fix/demo-labels P4: the "preview" chip is a grid child alongside the three
preview columns; span the full row so it reads as a caption above the
grid rather than getting stretched into a column slot. */
.mission-board-preview-chip {
grid-column: 1 / -1;
justify-self: start;
margin: 0;
}
/* --- playground pane ---------------------------------------------------- */
.pane[data-pane="playground"] { display: flex; flex-direction: column; }
.playground-banner {
background: rgba(214, 181, 110, 0.10); border-bottom: 1px solid var(--tool);
color: var(--tool); padding: 8px 16px; font-size: 12.5px;
}
.playground-banner em { font-style: normal; font-weight: 600; }
.playground-body {
flex: 1; display: grid; grid-template-columns: 1fr 280px;
overflow: hidden; min-height: 0;
}
.playground-stream {
overflow-y: auto; padding: 12px 16px; min-height: 0;
}
.playground-side {
border-left: 1px solid var(--border); background: var(--bg-elev);
padding: 12px; overflow-y: auto;
}
.playground-meta { font-size: 12px; margin-bottom: 8px; }
.playground-composer {
border-top: 1px solid var(--border);
padding: 10px 12px;
display: flex; gap: 8px; align-items: flex-end;
}
.playground-composer textarea {
flex: 1; resize: vertical; background: var(--bg); color: var(--text);
border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px;
font: inherit;
}
/* Bubbles in the playground stream mirror the chat stream palette but the
* simplified variant only needs a handful of types. */
.playground-stream .bubble {
padding: 8px 12px; border-radius: 8px; margin: 6px 0; max-width: 80ch;
white-space: pre-wrap; word-wrap: break-word; font-size: 13px;
border: 1px solid var(--border); background: var(--bg-elev);
}
.playground-stream .bubble-user {
background: var(--user-bubble); border-color: rgba(255, 255, 255, 0.06);
margin-left: auto;
}
.playground-stream .bubble-assistant { background: var(--assistant-bubble); }
.playground-stream .bubble-tool-call { color: var(--tool); font-family: var(--mono); font-size: 12px; }
.playground-stream .bubble-tool-result { color: var(--ok); font-family: var(--mono); font-size: 12px; }
.playground-stream .bubble-meta {
background: transparent; border: none; color: var(--muted); font-size: 11.5px;
padding: 2px 0; font-style: italic;
}
.playground-stream .bubble-error {
background: rgba(242, 122, 122, 0.10); border-color: var(--error); color: var(--error);
}
/* Compare drawer sits above the playground stream (position:absolute). */
.playground-compare-drawer {
position: absolute; inset: 60px 0 0 260px; z-index: 20;
background: var(--bg); border-top: 1px solid var(--border);
display: flex; flex-direction: column;
}
.playground-compare-drawer[hidden] { display: none; }
.playground-compare-drawer .drawer-head {
display: flex; gap: 10px; align-items: center;
padding: 10px 16px; border-bottom: 1px solid var(--border);
}
.playground-compare-drawer .drawer-head .title { font-weight: 600; }
.playground-compare-drawer .drawer-head select {
background: var(--bg-elev); color: var(--text); border: 1px solid var(--border);
border-radius: 4px; padding: 4px 8px; font: inherit;
}
.playground-compare-drawer .drawer-body {
flex: 1; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden;
}
.playground-compare-drawer .compare-col {
border-right: 1px solid var(--border); display: flex; flex-direction: column;
overflow: hidden;
}
.playground-compare-drawer .compare-col:last-child { border-right: none; }
.playground-compare-drawer .col-title {
padding: 6px 12px; font-size: 11px; text-transform: uppercase;
letter-spacing: 0.05em; color: var(--muted); background: var(--bg-elev);
border-bottom: 1px solid var(--border);
}
.playground-compare-drawer .stream.compact { overflow-y: auto; padding: 8px 12px; font-size: 12px; }
/* ============================================================================
* Tool-render-intent cards (§ P0 render batch A)
* Wired at src/renderer/renderer.js `tool/result` dispatch. Card kinds map
* to `ToolResultView` in packages/core/tools/src/presentation.ts:
* - card === 'terminal' → .card-terminal (bash)
* - card === 'diff' → .card-diff (fs edit/write)
* Per-tool family band is added by appendToolCall via .family-* classes
* on the enclosing .tool-block (a left-border tint + a header icon).
* ==========================================================================*/
/* Family band: coloured 2px left border on the tool-block + a monochrome
* glyph column. Density-spec §2 L0: 2px status/type edge. New tool names
* fall through to plain .tool-block styling (no family class means no
* override). Width bumped to 2px to match the density spec exactly. */
.tool-block.family-bash { border-left: 2px solid var(--tool); }
.tool-block.family-fs { border-left: 2px solid var(--accent); }
.tool-block.family-web { border-left: 2px solid #7fc4a1; }
.tool-block.family-skill { border-left: 2px solid #c48ad0; }
.tool-block.family-subagent { border-left: 2px solid #d0a97b; }
.tool-block.family-todo { border-left: 2px solid var(--ok); }
.tool-block.family-cordis { border-left: 2px solid var(--muted); }
.tool-block.family-code { border-left: 2px solid #6ea8ff; }
.tool-family-icon {
display: inline-block; width: 1em; text-align: center;
color: var(--muted); font-size: 11px;
flex: 0 0 auto;
}
.tool-family-name {
color: var(--text);
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.tool-arg-gist {
color: var(--muted);
font-family: var(--mono);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
flex: 1 1 auto;
}
/* ×N fold badge when ≥3 same-name tool calls collapse into one row. */
.tool-run-count {
padding: 0 6px;
border-radius: 3px;
border: 1px solid var(--border);
color: var(--muted);
font-size: 10.5px;
flex: 0 0 auto;
}
/* Duration pill — small right-aligned chip inside .tool-block summary,
* populated by tool-cards.applyToolDuration when tool/result carries
* `durationMs` (audit doc §2.3 GAP T1). Keeps the summary line the same
* height; a reader can spot slow tools without opening the block. Flip
* the summary to full-width flex so `margin-left: auto` on the pill
* pushes it to the tool-block right edge instead of hugging the name. */
.tool-block summary { display: flex; }
.tool-duration {
margin-left: auto;
padding: 1px 6px;
border-radius: 3px;
border: 1px solid var(--border);
color: var(--muted);
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.02em;
font-variant-numeric: tabular-nums;
}
/* Terminal card (bash presentResult) --------------------------------------- */
.card-terminal {
background: #0a0c11; border: 1px solid var(--border); border-radius: 6px;
padding: 8px 10px; overflow: hidden;
}
.card-terminal-output {
margin: 0; padding: 0; color: #d6d9e2; font-family: var(--mono);
font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word;
max-height: 320px; overflow-y: auto;
}
.card-terminal-footer {
margin-top: 6px; display: flex; gap: 6px; align-items: center;
}
.card-badge {
display: inline-block; padding: 1px 6px; border-radius: 3px;
font-family: var(--mono); font-size: 10px; letter-spacing: 0.02em;
border: 1px solid var(--border); color: var(--muted);
}
.card-badge.ok { color: var(--ok); border-color: color-mix(in oklab, var(--ok) 40%, transparent); }
.card-badge.err { color: var(--error); border-color: color-mix(in oklab, var(--error) 40%, transparent); }
.card-badge.sig { color: var(--tool); border-color: color-mix(in oklab, var(--tool) 40%, transparent); }
/* Diff card (fs edit/write presentResult) --------------------------------- */
.card-diff {
border: 1px solid var(--border); border-radius: 6px; overflow: hidden;
background: var(--bg);
}
.card-diff-title {
padding: 6px 10px; background: var(--bg-elev-2);
color: var(--muted); font-size: 11px; font-family: var(--mono);
border-bottom: 1px solid var(--border);
}
.card-diff-file { border-bottom: 1px solid var(--border); }
.card-diff-file:last-child { border-bottom: none; }
.card-diff-file-header {
display: flex; align-items: center; justify-content: space-between;
padding: 4px 10px; background: var(--bg-elev);
font-family: var(--mono); font-size: 11px; color: var(--muted);
}
.card-diff-file-path { color: var(--text); }
.card-diff-file-stats { display: flex; gap: 6px; align-items: center; }
.card-diff-file-count { color: var(--muted); font-size: 10.5px; }
.card-diff-body {
font-family: var(--mono); font-size: 12px; line-height: 1.5;
max-height: 360px; overflow-y: auto;
}
.card-diff-line { display: flex; padding: 0 8px; }
.card-diff-line.add { background: color-mix(in oklab, var(--ok) 12%, transparent); }
.card-diff-line.del { background: color-mix(in oklab, var(--error) 12%, transparent); }
.card-diff-sigil {
flex: 0 0 auto; width: 1.2em; text-align: center;
color: var(--muted); user-select: none;
}
.card-diff-line.add .card-diff-sigil { color: var(--ok); }
.card-diff-line.del .card-diff-sigil { color: var(--error); }
.card-diff-text {
flex: 1 1 auto; white-space: pre; overflow-x: auto; color: var(--text);
}
.card-diff-empty { padding: 8px 10px; color: var(--muted); font-style: italic; font-size: 12px; }
/* Ticket #139 (strategy §1.5) — CodeSandbox layout for the diff card.
* Multi-file diffs get a left file-tree column; single-file stays inline.
* Every hunk is a <details> collapsed by default (first hunk opens); a tail
* reveal button materialises when hunk count exceeds HUNK_CAP. Bench-flat
* layout — no shadows, no scrim, hits the 老板 "简单 UI、绝不全塞满" note. */
.card-diff-body-wrap {
display: flex; flex-direction: column;
}
.card-diff-body-wrap.has-tree {
flex-direction: row; align-items: stretch;
}
.card-diff-tree {
flex: 0 0 220px; max-width: 240px; min-width: 160px;
border-right: 1px solid var(--border);
background: var(--bg-elev);
display: flex; flex-direction: column;
overflow-y: auto; max-height: 420px;
}
.card-diff-tree-header {
padding: 6px 10px; color: var(--muted); font-size: 11px;
border-bottom: 1px solid var(--border);
letter-spacing: 0.03em; text-transform: uppercase;
font-family: var(--mono);
}
.card-diff-tree-row {
display: flex; align-items: center; justify-content: space-between; gap: 8px;
padding: 6px 10px; text-align: left;
background: transparent; border: none; border-left: 2px solid transparent;
color: var(--text); font-family: var(--mono); font-size: 12px;
cursor: pointer;
}
.card-diff-tree-row:hover { background: var(--bg-elev-2); }
.card-diff-tree-row.active {
background: var(--bg); border-left-color: var(--accent);
}
.card-diff-tree-path {
flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
color: var(--text);
}
.card-diff-tree-count {
flex: 0 0 auto; color: var(--muted); font-size: 10.5px;
}
.card-diff-pane {
flex: 1 1 auto; min-width: 0; overflow: hidden;
}
/* Hunk fold — each hunk is a <details>. Summary is the "@@ … @@" header. */
.card-diff-hunk {
border-top: 1px dashed var(--border);
}
.card-diff-hunk:first-child { border-top: none; }
.card-diff-hunk-summary {
cursor: pointer; user-select: none;
padding: 4px 10px; color: var(--muted);
font-family: var(--mono); font-size: 11px;
background: var(--bg-elev);
list-style: none;
}
.card-diff-hunk-summary::-webkit-details-marker { display: none; }
.card-diff-hunk-summary::before {
content: '▸'; display: inline-block; width: 1em;
transition: transform 0.12s ease;
}
.card-diff-hunk[open] > .card-diff-hunk-summary::before {
transform: rotate(90deg);
}
.card-diff-hunk-body {
border-top: 1px solid var(--border);
}
.card-diff-hunk-more {
display: block; width: 100%;
padding: 6px 10px; background: var(--bg-elev);
border: none; border-top: 1px dashed var(--border);
color: var(--muted); font-family: var(--mono); font-size: 11px;
cursor: pointer; text-align: left;
}
.card-diff-hunk-more:hover { color: var(--accent); background: var(--bg-elev-2); }
/* Ticket #139 — universal "{ }" JSON badge on every tool card summary.
* Sits at the right edge alongside the duration pill. Both use margin-left:
* auto; because the badge is added first (at appendToolCall) and the pill
* last (at tool/result), the badge floats leftward-of the pill. */
.tool-json-badge {
margin-left: auto;
padding: 1px 6px;
border: 1px solid var(--border); border-radius: 3px;
background: transparent; color: var(--muted);
font-family: var(--mono); font-size: 10px; letter-spacing: 0.02em;
cursor: pointer;
}
.tool-json-badge:hover { color: var(--accent); border-color: var(--accent); }
/* When both are present, we want the pill to hug the right edge and the
* badge to sit just left of it. First-child auto pushes the row right; a
* fixed gap between siblings keeps them from touching. */
.tool-block summary > .tool-json-badge + .tool-duration { margin-left: 6px; }
/* Ticket #139 — right-side JSON drawer. Non-modal on purpose (strategy §1.5
* / 老板 "抽屉 pin 时可保持打开切消息"): fixed panel that occupies ~40% of
* the viewport, main flow stays interactive. Slides in with a transform on
* .open; hidden by translate off-screen otherwise. */
.tool-json-drawer {
position: fixed; top: 0; right: 0; height: 100vh; width: min(560px, 42vw);
background: var(--bg); border-left: 1px solid var(--border);
box-shadow: -6px 0 16px rgba(0,0,0,0.18);
transform: translateX(100%); transition: transform 0.18s ease;
z-index: 40; display: flex; flex-direction: column;
pointer-events: auto;
}
.tool-json-drawer.open { transform: translateX(0); }
.tool-json-drawer[aria-hidden="true"] { pointer-events: none; }
.tool-json-drawer[aria-hidden="false"] { pointer-events: auto; }
.tool-json-drawer-head {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 14px; border-bottom: 1px solid var(--border);
background: var(--bg-elev);
}
.tool-json-drawer-title {
font-family: var(--mono); font-size: 13px; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tool-json-drawer-body {
flex: 1 1 auto; overflow-y: auto; padding: 8px 14px 14px;
display: flex; flex-direction: column; gap: 12px;
}
.tool-json-section > summary {
cursor: pointer; user-select: none;
padding: 4px 0; color: var(--muted);
font-family: var(--mono); font-size: 11px;
letter-spacing: 0.02em;
}
.tool-json-section > summary::-webkit-details-marker { display: none; }
.tool-json-section > summary::before {
content: '▸'; display: inline-block; width: 1em;
transition: transform 0.12s ease;
}
.tool-json-section[open] > summary::before { transform: rotate(90deg); }
.tool-json-note { color: var(--muted); font-size: 10.5px; letter-spacing: 0; text-transform: none; }
.tool-json-pane {
margin: 4px 0 0; padding: 8px 10px;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 4px;
color: var(--text); font-family: var(--mono); font-size: 11.5px;
line-height: 1.5; white-space: pre-wrap; word-wrap: break-word;
max-height: 40vh; overflow-y: auto;
}
/* Code-dispatch sub-call list (fan-out under a run_code tool block) -------- */
.card-code-dispatch {
border-top: 1px dashed var(--border); margin-top: 4px; padding-top: 4px;
}
.card-code-dispatch-header {
color: var(--muted); font-size: 11px; letter-spacing: 0.03em;
text-transform: uppercase; margin-bottom: 2px;
}
.card-code-dispatch-row {
display: flex; align-items: center; gap: 6px; padding: 2px 0;
font-family: var(--mono); font-size: 12px;
}
.card-code-dispatch-branch { color: var(--border); }
.card-code-dispatch-name { color: var(--accent); }
.card-code-dispatch-summary { color: var(--muted); }
.card-code-dispatch-dot { width: 1em; text-align: center; }
.card-code-dispatch-row.ok .card-code-dispatch-dot { color: var(--ok); }
.card-code-dispatch-row.err .card-code-dispatch-dot { color: var(--error); }
/* --- visibility batch B (todo, prompt/blocked, turn/end, subagent finished, sandbox+preset badges, approval audit) --- */
/* Owned by visibility-controller.js; scoped to .visibility-* / .todo-* / .pb-* / .sas-* / .sandbox-* / .preset-* classes so the block adds surface without overriding anything above. */
.visibility-card {
margin: 8px 0; padding: 10px 12px;
background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 6px;
}
.todo-card { border-left: 3px solid var(--accent); }
.todo-head {
display: flex; align-items: baseline; gap: 10px;
border-bottom: 1px solid var(--border); padding-bottom: 6px; margin-bottom: 6px;
}
.todo-title { font-weight: 600; }
.todo-summary { color: var(--muted); font-size: 12px; }
.todo-items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.todo-item {
display: flex; align-items: baseline; gap: 8px;
padding: 2px 0;
}
.todo-item .todo-icon {
width: 16px; text-align: center; font-family: var(--mono);
color: var(--muted);
}
.todo-item.status-in_progress .todo-icon { color: var(--tool); }
.todo-item.status-completed .todo-icon { color: var(--ok); }
.todo-item.status-completed .todo-text { text-decoration: line-through; color: var(--muted); }
.todo-item .todo-text { flex: 1; }
.todo-warn { margin-top: 6px; color: var(--tool); font-size: 12px; }
.todo-empty { padding: 4px 0; }
.prompt-blocked-card {
border-left: 3px solid var(--error);
background: color-mix(in oklab, var(--error) 10%, var(--bg-elev));
}
/* .pb-head/.pb-title/.pb-reason/.pb-body were the multi-part pb-* rules for
* the old prompt-blocked card layout; the compact .prompt-blocked-row
* variant (.pb-row-* below) supersedes them. Only .pb-pre stays because
* that <pre> is reused across both shapes. */
.pb-pre {
margin: 6px 0 0; padding: 8px 10px;
background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
font-family: var(--mono); font-size: 12px; white-space: pre-wrap;
}
/* prompt/blocked row (viz-coverage-matrix §5 P0-1 fix — compact single-row
* form, red-edge, full-width left-aligned, L1 <details> body). Replaces
* the earlier .prompt-blocked-card multi-part shape while keeping the
* .prompt-blocked-card block above as a legacy shim (test/mock callers may
* still target it — no behaviour change). .pb-pre stays because the new
* row shape still uses it for the blocked-prompt <pre>. */
details.prompt-blocked-row {
border-left: 3px solid var(--error);
background: color-mix(in oklab, var(--error) 6%, var(--bg-elev));
padding: 6px 10px;
}
details.prompt-blocked-row > summary.pb-row-head {
display: flex; align-items: baseline; gap: 6px;
cursor: pointer; list-style: none;
color: var(--text); font-size: 13px;
}
details.prompt-blocked-row > summary.pb-row-head::-webkit-details-marker { display: none; }
.pb-row-icon { color: var(--error); font-weight: 600; }
.pb-row-label { font-weight: 600; color: var(--error); }
.pb-row-sep { color: var(--muted); }
.pb-row-reason { color: var(--text); overflow: hidden; text-overflow: ellipsis; }
.pb-row-body {
margin-top: 6px; padding-top: 6px;
border-top: 1px dashed color-mix(in oklab, var(--error) 40%, var(--border));
display: flex; flex-direction: column; gap: 6px;
}
.pb-row-reason-full { color: var(--text); font-size: 12px; }
.pb-row-body-label { color: var(--muted); font-size: 11px; font-family: var(--mono); }
/* approval note pinned inside the tool card (viz-coverage-matrix §5 P0-2 —
* "auto-approved · <preset> 灰字小行" per task brief). Sits inside a
* .tool-block right after its <summary>, so a reader sees the auto-decision
* inline with the tool even when the card is folded. */
.tool-approval-note {
margin: 2px 12px 4px;
padding: 2px 6px;
color: var(--muted);
font-size: 11px; font-family: var(--mono);
border-left: 2px solid var(--border);
}
.tool-approval-note.tone-ok { color: var(--ok); border-left-color: var(--ok); }
.tool-approval-note.tone-warn { color: var(--tool); border-left-color: var(--tool); }
.tool-approval-note.tone-error { color: var(--error); border-left-color: var(--error); }
/* sandbox/preset switch divider (viz-coverage-matrix §5 P0-3 P0-4 — same
* visual grammar as .compact-divider but scoped to permission/sandbox
* events, so the point-in-time of a security-context change is anchored
* in the main stream, not only in the header badge). */
.mode-divider {
align-self: stretch; text-align: center;
color: var(--muted); font-size: 11px; letter-spacing: 0.08em;
margin: 6px 0; padding: 2px 0;
font-family: var(--mono);
}
.mode-divider-sandbox .mode-divider-label { color: var(--accent); }
.mode-divider-preset .mode-divider-label { color: var(--tool); }
.visibility-turnend {
margin: 4px 0 8px;
padding: 4px 10px 4px 12px; border-left: 3px solid var(--border);
font-size: 12px; font-family: var(--mono);
}
.visibility-turnend-error { color: var(--error); border-left-color: var(--error); }
.visibility-turnend-warn { color: var(--tool); border-left-color: var(--tool); }
.visibility-turnend-info { color: var(--muted); }
.visibility-subagent-summary {
margin: 4px 0 8px;
padding: 4px 10px; font-size: 12px;
color: var(--muted); border-left: 2px solid var(--border);
}
.sas-label { color: var(--accent); margin-right: 4px; font-family: var(--mono); }
.sas-brief { color: var(--text); }
.sas-full { display: inline-block; margin-left: 8px; vertical-align: middle; }
.sas-full summary { color: var(--muted); cursor: pointer; }
.sas-pre {
display: block; margin: 6px 0 0; padding: 8px 10px;
background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
font-family: var(--mono); font-size: 12px; white-space: pre-wrap;
}
.visibility-badge {
display: inline-flex; align-items: center;
padding: 2px 8px; margin-right: 6px;
border: 1px solid var(--border); border-radius: 10px;
background: var(--bg-elev-2); color: var(--muted);
font-size: 11px; font-family: var(--mono);
transition: background 220ms ease, color 220ms ease;
}
.sandbox-badge.sandbox-read-only { color: var(--ok); border-color: var(--ok); }
.sandbox-badge.sandbox-workspace-write { color: var(--accent);border-color: var(--accent); }
.sandbox-badge.sandbox-danger-full-access { color: var(--error); border-color: var(--error); }
.preset-badge { color: var(--tool); border-color: var(--tool); }
.visibility-badge.flash { animation: vb-flash 900ms ease; }
@keyframes vb-flash {
0% { background: var(--bg-elev-2); }
20% { background: var(--bg-elev); }
100% { background: var(--bg-elev-2); }
}
.visibility-audit {
margin: 4px 0; padding: 3px 10px;
font-size: 12px; font-family: var(--mono);
color: var(--muted); border-left: 2px solid var(--border);
}
.visibility-audit-ok { color: var(--ok); border-left-color: var(--ok); }
.visibility-audit-error { color: var(--error); border-left-color: var(--error); }
.visibility-audit-warn { color: var(--tool); border-left-color: var(--tool); }
/* -- Devtools drawer ------------------------------------------------------ */
/* Right-anchored, off by default. Sits above the app grid so opening it does
not reflow the chat pane. Toggle via header "devtools" button or ⌥D. */
.devtools-toggle.active { color: var(--accent); border-color: var(--accent); }
.devtools-drawer {
position: fixed; top: 0; right: 0; height: 100vh;
width: 480px; max-width: 90vw;
display: flex; flex-direction: column;
background: var(--bg-elev); border-left: 1px solid var(--border);
z-index: 40;
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.35);
font-family: var(--font);
}
.devtools-head {
display: flex; align-items: center; gap: 10px;
padding: 10px 12px; border-bottom: 1px solid var(--border);
}
.devtools-title { flex: 1; font-weight: 600; }
.devtools-count { font-family: var(--mono); font-size: 11px; }
.devtools-close {
padding: 2px 8px; font-size: 16px; line-height: 1;
}
.devtools-presets {
display: flex; flex-wrap: wrap; gap: 6px;
padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.devtools-preset {
padding: 3px 10px; border-radius: 12px;
background: var(--bg-elev-2); color: var(--muted);
border: 1px solid var(--border);
font-size: 12px; cursor: pointer;
}
.devtools-preset:hover { color: var(--text); }
.devtools-preset.active {
background: var(--accent); color: var(--bg); border-color: var(--accent);
}
.devtools-search-row {
display: flex; align-items: center; gap: 8px;
padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.devtools-search {
flex: 1;
background: var(--bg); color: var(--text);
border: 1px solid var(--border); border-radius: 4px;
padding: 4px 8px; font-family: var(--font); font-size: 13px;
}
.devtools-clear { font-size: 11px; padding: 3px 8px; }
.devtools-autoscroll {
display: inline-flex; align-items: center; gap: 4px;
font-size: 11px; color: var(--muted);
white-space: nowrap;
}
.devtools-chips {
display: flex; flex-wrap: wrap; gap: 4px;
padding: 6px 12px; border-bottom: 1px solid var(--border);
max-height: 96px; overflow-y: auto;
}
.devtools-chips-empty { padding: 2px 4px; font-size: 11px; }
.devtools-chip {
padding: 2px 8px; border-radius: 10px;
background: var(--bg-elev-2); color: var(--muted);
border: 1px solid var(--border);
font-family: var(--mono); font-size: 10px; cursor: pointer;
}
.devtools-chip:hover { color: var(--text); }
.devtools-chip.active {
background: var(--tool); color: var(--bg); border-color: var(--tool);
}
.devtools-chip-reset {
font-size: 10px; padding: 2px 8px; margin-left: 2px;
}
.devtools-list {
flex: 1; overflow-y: auto;
padding: 8px 6px;
}
.devtools-list-empty {
padding: 20px 12px; text-align: center; font-size: 12px;
}
/* t159 row grammar (mirrors .trace-event-row): 2px family edge on the left,
* fixed monochrome glyph column, then type/session/seq, then a spacer that
* pushes the surface pill + time + `{ }` badge to the right edge. */
.devtools-row {
margin: 2px 0; border-left: 2px solid var(--border);
background: var(--bg);
}
.devtools-row[open] { background: var(--bg-elev-2); }
.devtools-row-summary {
display: flex; align-items: baseline; gap: 8px;
padding: 4px 8px; cursor: pointer;
font-family: var(--mono); font-size: 11px;
list-style: none;
}
.devtools-row-summary::-webkit-details-marker { display: none; }
.devtools-row-glyph {
width: 12px; text-align: center; color: var(--muted);
font-family: var(--mono); flex-shrink: 0;
}
.devtools-row-type { color: var(--text); font-weight: 600; }
.devtools-row-session { font-size: 10px; }
.devtools-row-seq { font-size: 10px; flex: 1; } /* spacer: pushes trailing metrics right */
.devtools-row-time { color: var(--muted); font-size: 10px; margin-left: auto; }
.devtools-row-raw-badge { margin-left: 4px; }
.devtools-row-body {
margin: 0; padding: 6px 12px 10px;
display: flex; flex-direction: column; gap: 4px;
max-height: 320px; overflow-y: auto;
}
.devtools-row-l2-head {
display: flex; align-items: baseline; justify-content: space-between;
font-family: var(--mono); font-size: 10px;
}
.devtools-row-l2-label { text-transform: uppercase; letter-spacing: 0.5px; }
.devtools-row-copy {
padding: 1px 8px; border-radius: 4px;
background: var(--bg); color: var(--muted);
border: 1px solid var(--border);
font-family: var(--mono); font-size: 10px; cursor: pointer;
}
.devtools-row-copy:hover { color: var(--accent); border-color: var(--accent); }
.devtools-row-pre {
margin: 0; padding: 6px 8px;
background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
font-family: var(--mono); font-size: 11px;
color: var(--muted); white-space: pre-wrap; word-break: break-word;
}
/* Family edge — mirrors .trace-event-row-* colours from the trace panes
* (renderer.js renderTraceEventRow). Overrides the generic --border edge
* so family reads at a glance; the surface classification is orthogonal
* (encoded via the surface pill + row background wash below). */
.devtools-row-family-assistant { border-left-color: color-mix(in srgb, var(--accent) 70%, transparent); }
.devtools-row-family-tool { border-left-color: color-mix(in srgb, var(--tool) 70%, transparent); }
.devtools-row-family-context { border-left-color: #7fc4a1; }
.devtools-row-family-hook { border-left-color: #d0a97b; }
.devtools-row-family-approval { border-left-color: color-mix(in srgb, var(--accent) 50%, #d0a97b); }
.devtools-row-family-bash { border-left-color: color-mix(in srgb, var(--ok) 60%, transparent); }
.devtools-row-family-meta { border-left-color: var(--border); }
.devtools-row-family-generic { border-left-color: var(--border); }
.devtools-type-hook { color: var(--tool); }
.devtools-type-approval { color: var(--accent); }
.devtools-type-request { color: var(--muted); }
.devtools-type-bash { color: var(--ok); }
.devtools-type-tool { color: var(--user-bubble); }
.devtools-type-assistant { color: var(--accent); }
.devtools-type-flow { color: var(--muted); }
.devtools-type-compact { color: var(--tool); }
.devtools-type-generic { color: var(--text); }
/* --- Surface filter chips + row surface encoding (ticket #128) ------------ *
* Three-way partition on every event: current / shadowed / log-only. The
* left-border colour on each row matches the chip colour so the two read as
* one system. Colours are tuned against both the demo dark chrome and the
* default light theme via color-mix on --accent / neutrals. */
.devtools-surfaces {
display: flex; align-items: center; gap: 6px;
padding: 4px 8px 2px; flex-wrap: wrap;
}
.devtools-surfaces-label {
font-family: var(--mono); font-size: 10px;
text-transform: uppercase; letter-spacing: 0.5px;
margin-right: 2px;
}
.devtools-surface {
display: inline-flex; align-items: center; gap: 6px;
padding: 2px 8px; border-radius: 999px;
border: 1px solid var(--border);
background: var(--bg);
color: var(--muted);
font-family: var(--mono); font-size: 11px;
cursor: pointer;
}
.devtools-surface:hover { color: var(--text); }
.devtools-surface .devtools-surface-badge {
font-size: 10px; padding: 0 5px; border-radius: 999px;
background: color-mix(in oklab, currentColor 15%, transparent);
min-width: 16px; text-align: center;
}
.devtools-surface-current {
color: color-mix(in oklab, var(--ok) 70%, var(--text));
border-color: color-mix(in oklab, var(--ok) 30%, var(--border));
}
.devtools-surface-shadowed {
color: color-mix(in oklab, var(--warn) 70%, var(--text));
border-color: color-mix(in oklab, var(--warn) 30%, var(--border));
}
.devtools-surface-log-only {
color: color-mix(in oklab, var(--muted) 60%, var(--text));
border-color: var(--border);
}
.devtools-surface.active {
background: color-mix(in oklab, currentColor 12%, var(--bg));
border-color: currentColor;
}
/* Surface encoding on the row itself is a subtle background wash — the
* 2px left edge is owned by family (see .devtools-row-family-*), so the
* two axes never fight for the same pixel. The surface pill in the summary
* carries the labeled colour; the wash is a cross-check for skim reading. */
.devtools-row-surface-current { background: color-mix(in oklab, var(--ok) 3%, var(--bg)); }
.devtools-row-surface-shadowed { background: color-mix(in oklab, var(--warn) 5%, var(--bg)); }
.devtools-row-surface-log-only { background: color-mix(in oklab, var(--muted) 4%, var(--bg)); }
.devtools-row-surface-current[open] { background: color-mix(in oklab, var(--ok) 6%, var(--bg-elev-2)); }
.devtools-row-surface-shadowed[open] { background: color-mix(in oklab, var(--warn) 9%, var(--bg-elev-2)); }
.devtools-row-surface-log-only[open] { background: color-mix(in oklab, var(--muted) 7%, var(--bg-elev-2)); }
/* Small pill in the row summary — same colour family, denser than the chip. */
.devtools-row-surface {
font-family: var(--mono); font-size: 9px;
text-transform: uppercase; letter-spacing: 0.5px;
padding: 1px 6px; border-radius: 999px;
border: 1px solid currentColor;
background: color-mix(in oklab, currentColor 8%, transparent);
margin-left: 4px;
}
/* --- Plugins tab · Browse (marketplace) --------------------------------- *
* reference-style row list (2026-07-16 redesign lane): big hero title + subtitle
* + search, an Installed icon strip, then a vertical list of one-line rows
* (icon left, name/desc middle, install button right). Only CSS variables
* are used so the redesign lane's theme swap re-skins for free. */
.plugins-subnav {
display: flex; gap: 4px; padding: 2px; margin-bottom: 4px;
background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 8px; align-self: flex-start;
}
.plugins-subnav-btn {
padding: 6px 14px; border: 0; background: transparent;
border-radius: 6px; color: var(--muted);
font-size: 12.5px; font-weight: 500; cursor: pointer;
transition: background 120ms ease, color 120ms ease;
}
.plugins-subnav-btn:hover { color: var(--text); }
.plugins-subnav-btn.active {
background: var(--bg-elev-2); color: var(--text);
box-shadow: 0 0 0 1px var(--border);
}
.plugins-subpanel { display: flex; flex-direction: column; gap: 16px; }
.plugins-subpanel[hidden] { display: none; }
/* Hero: big title + one-paragraph subtitle + search input. The whole block
* is intentionally airy so it reads as a landing page — reference design's plugin store
* cadence — rather than a dense settings pane. */
.market-hero {
display: flex; flex-direction: column; gap: 8px;
padding: 4px 2px 8px 2px;
}
.market-hero-title {
margin: 0; font-size: 22px; font-weight: 700; line-height: 1.2;
color: var(--text); letter-spacing: -0.01em;
}
.market-hero-sub {
margin: 0; font-size: 13px; line-height: 1.5;
color: var(--muted); max-width: 68ch;
}
.market-hero-sub em {
font-style: normal; color: var(--text);
border-bottom: 1px dashed var(--border);
}
.market-search {
display: flex; gap: 12px; align-items: center;
margin-top: 6px;
}
.market-search input[type="search"] {
flex: 1; min-width: 0;
padding: 8px 12px;
background: var(--bg); color: var(--text);
border: 1px solid var(--border); border-radius: 8px;
font-size: 13px; font-family: inherit;
transition: border-color 120ms ease, box-shadow 120ms ease;
}
.market-search input[type="search"]:focus {
outline: none; border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 20%, transparent);
}
.market-search input[type="search"]::placeholder { color: var(--muted); }
.market-meta {
font-size: 11.5px; color: var(--muted); white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.market-meta code {
background: var(--bg-elev-2); padding: 1px 5px; border-radius: 3px;
font-family: var(--mono); font-size: 11px; color: var(--text);
}
.market-meta .warn { color: var(--tool); }
/* Installed strip: a small row of square avatars for currently-installed
* plugins, click-to-scroll to the row-list entry. Nothing here is a form
* control — the strip is a compact reference, not an editor. */
.market-installed-strip {
display: flex; flex-direction: column; gap: 8px;
padding: 4px 2px;
}
.market-installed-header {
display: flex; align-items: baseline; gap: 10px;
}
.market-installed-label {
font-size: 12px; font-weight: 600; color: var(--text);
text-transform: uppercase; letter-spacing: 0.06em;
}
.market-installed-count {
font-size: 11.5px; font-variant-numeric: tabular-nums;
}
.market-installed-icons {
display: flex; flex-wrap: wrap; gap: 6px;
}
.market-installed-icon {
width: 32px; height: 32px; padding: 0;
display: inline-flex; align-items: center; justify-content: center;
background: var(--bg-elev); color: var(--accent);
border: 1px solid var(--border); border-radius: 6px;
font-family: var(--mono); font-size: 11px; font-weight: 600;
cursor: pointer;
transition: transform 120ms ease, border-color 120ms ease;
}
.market-installed-icon:hover {
transform: translateY(-1px); border-color: var(--accent);
}
.market-installed-icon.is-disabled {
color: var(--tool); border-color: var(--tool);
opacity: 0.7;
}
.market-divider {
height: 1px; background: var(--border); margin: 0;
}
/* Row list: vertical stack of one-line-ish cards. Each row is a flex
* container with fixed-width icon, flexible body, fixed-width action
* cluster. min-height keeps them visually uniform even when a description
* is single-line. */
.market-list {
list-style: none; margin: 0; padding: 0;
display: flex; flex-direction: column; gap: 6px;
}
.market-row {
display: flex; gap: 14px; align-items: center;
padding: 12px 14px; min-height: 64px;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 10px;
transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
}
.market-row:hover { border-color: var(--accent); }
.market-row.is-installed {
border-color: color-mix(in oklab, var(--ok) 60%, var(--border));
background: color-mix(in oklab, var(--ok) 4%, var(--bg-elev));
}
.market-row.is-disabled {
border-color: color-mix(in oklab, var(--tool) 60%, var(--border));
background: color-mix(in oklab, var(--tool) 4%, var(--bg-elev));
}
.market-row.is-highlight {
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in oklab, var(--accent) 30%, transparent);
}
.market-row-icon {
flex: 0 0 auto;
width: 40px; height: 40px; border-radius: 8px;
display: flex; align-items: center; justify-content: center;
background: var(--bg); color: var(--accent);
font-family: var(--mono); font-size: 13px; font-weight: 700;
border: 1px solid var(--border);
}
.market-row-body {
flex: 1; min-width: 0;
display: flex; flex-direction: column; gap: 3px;
}
.market-row-topline {
display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline;
}
.market-row-title {
margin: 0; font-size: 14px; font-weight: 600; color: var(--text);
}
.market-row-package code {
background: var(--bg); padding: 1px 6px; border-radius: 3px;
font-family: var(--mono); font-size: 11px; color: var(--muted);
border: 1px solid var(--border);
}
.market-row-author {
font-size: 11.5px;
}
.market-row-desc {
margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--muted);
overflow: hidden; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.market-row-perms {
display: inline-flex; flex-wrap: wrap; gap: 4px; margin-top: 2px;
}
.market-perm {
padding: 1px 7px; border-radius: 10px;
font-family: var(--mono); font-size: 10.5px;
border: 1px solid var(--border); background: var(--bg);
}
.market-perm.perm-net { color: var(--tool); border-color: var(--tool); }
.market-perm.perm-fs { color: var(--accent); border-color: var(--accent); }
.market-perm.perm-subprocess { color: var(--error); border-color: var(--error); }
.market-perm.perm-other { color: var(--muted); }
.market-row-actions {
flex: 0 0 auto;
display: flex; flex-direction: column; align-items: stretch;
gap: 4px; min-width: 110px;
}
.market-primary { min-width: 110px; }
.market-primary.is-disabled { background: var(--tool); border-color: var(--tool); }
.market-primary:disabled.is-installed,
.market-primary:disabled {
cursor: default;
background: transparent; color: var(--ok); border: 1px solid var(--ok);
}
.market-uninstall {
font-size: 11px; padding: 4px 8px;
}
.market-basehint {
font-size: 11px; font-style: italic; text-align: center;
}
.market-empty, .market-error {
list-style: none;
padding: 32px; text-align: center; color: var(--muted); font-style: italic;
background: var(--bg-elev); border: 1px dashed var(--border); border-radius: 8px;
}
.market-error { color: var(--error); font-style: normal; }
/* -- P1 batch C: panels-c-controller.js styles ---------------------------- */
/* web_search result-list card */
.card-web-search { display: flex; flex-direction: column; gap: 4px; }
.card-web-search-head { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; }
.card-web-search-empty { font-style: italic; }
.card-web-search-raw {
margin: 4px 0 0; padding: 6px 8px; background: var(--bg-elev-2);
color: var(--muted); font-size: 11px; white-space: pre-wrap; border-radius: 4px;
}
.card-web-search-list { list-style: decimal inside; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.card-web-search-item { padding: 4px 0; border-bottom: 1px solid var(--bg-elev-2); }
.card-web-search-item:last-child { border-bottom: none; }
.card-web-search-title { display: inline; color: var(--accent); text-decoration: none; font-weight: 500; }
.card-web-search-title:hover { text-decoration: underline; cursor: pointer; }
.card-web-search-url { display: block; font-size: 11px; margin-top: 2px; word-break: break-all; }
.card-web-search-snippet { display: block; font-size: 12px; margin-top: 3px; color: var(--text); }
/* skill loaded card */
.card-skill { display: flex; flex-direction: column; }
.card-skill-summary {
cursor: pointer; padding: 6px 8px; background: var(--bg-elev-2); border-radius: 4px;
display: flex; align-items: center; gap: 8px; user-select: none;
}
.card-skill-icon { font-size: 14px; }
.card-skill-label { flex: 1; font-weight: 500; }
.card-skill-size { font-size: 11px; }
.card-skill-body {
margin: 6px 0 0; padding: 8px; background: var(--bg-elev-2); color: var(--text);
font-size: 12px; white-space: pre-wrap; word-break: break-word; max-height: 320px;
overflow-y: auto; border-radius: 4px;
}
/* workflow orchestration skeleton */
.card-workflow { display: flex; flex-direction: column; gap: 6px; }
.card-workflow-head { display: flex; align-items: center; gap: 8px; }
.card-workflow-title { font-weight: 500; }
.card-workflow-chip {
font-size: 10px; padding: 2px 6px; border-radius: 999px; border: 1px solid var(--bg-elev-2);
background: transparent; text-transform: uppercase; letter-spacing: 0.4px;
}
.card-workflow-phases { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.card-workflow-phase { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 12px; }
.card-workflow-phase-dot { width: 14px; text-align: center; color: var(--muted); }
.card-workflow-phase.status-done .card-workflow-phase-dot { color: var(--ok); }
.card-workflow-phase.status-running .card-workflow-phase-dot { color: var(--tool); }
.card-workflow-phase.status-failed .card-workflow-phase-dot { color: var(--error); }
.card-workflow-empty { font-size: 12px; font-style: italic; }
/* Background task drawer (dock above composer) */
.panels-c-tasks-drawer {
border-top: 1px solid var(--bg-elev-2);
background: var(--bg-elev-1);
max-height: 200px; overflow-y: auto;
display: flex; flex-direction: column;
}
.panels-c-tasks-head {
display: flex; align-items: center; gap: 8px; padding: 6px 10px;
background: var(--bg-elev-2); font-size: 11px;
}
.panels-c-tasks-title { font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; }
.panels-c-tasks-count {
min-width: 20px; text-align: center; padding: 1px 6px; border-radius: 999px;
background: var(--bg-elev-1); font-variant-numeric: tabular-nums;
}
.panels-c-tasks-toggle { margin-left: auto; padding: 2px 8px; font-size: 11px; }
.panels-c-tasks-body { padding: 6px 10px; display: flex; flex-direction: column; gap: 4px; }
.panels-c-tasks-body.collapsed { display: none; }
.panels-c-task-row { display: flex; align-items: center; gap: 8px; padding: 4px 6px; border-radius: 3px; font-size: 12px; }
.panels-c-task-row.status-running { background: var(--bg-elev-2); border-left: 2px solid var(--tool); padding-left: 4px; }
.panels-c-task-row.status-done { background: var(--bg-elev-2); border-left: 2px solid var(--ok); padding-left: 4px; }
.panels-c-task-row.status-failed { background: var(--bg-elev-2); border-left: 2px solid var(--error); padding-left: 4px; }
.panels-c-task-row.status-killed { background: var(--bg-elev-2); border-left: 2px solid var(--muted); padding-left: 4px; }
.panels-c-task-status { width: 14px; text-align: center; font-family: monospace; }
.panels-c-task-id { color: var(--muted); font-family: monospace; font-size: 11px; }
.panels-c-task-name { font-weight: 500; }
.panels-c-task-summary { flex: 1; color: var(--muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* --- context meter + Compact-now button (task #49) ------------------- */
/* Sits between the status text and the model badge; a mini progress bar +
* textual label + a compact-now button. Level colour is driven by
* .level-* classes so the JS side just toggles a class name. */
.ctx-meter {
display: inline-flex; align-items: center; gap: 6px;
font-size: 11.5px; color: var(--muted);
margin-left: 8px;
}
.ctx-meter-bar {
display: inline-block; position: relative;
width: 90px; height: 6px; border-radius: 3px;
background: var(--bg-elev-2); overflow: hidden;
}
.ctx-meter-fill {
position: absolute; top: 0; left: 0; bottom: 0;
width: 0%; background: var(--muted);
transition: width 0.2s linear, background-color 0.2s;
}
.ctx-meter.level-nominal .ctx-meter-fill { background: var(--ok); }
.ctx-meter.level-warn .ctx-meter-fill { background: var(--tool); }
.ctx-meter.level-high .ctx-meter-fill { background: var(--tool); filter: saturate(1.4); }
.ctx-meter.level-critical .ctx-meter-fill { background: var(--error); }
.ctx-meter-label { font-family: var(--mono); font-size: 11px; }
.ctx-meter.level-critical .ctx-meter-label { color: var(--error); }
#ctx-compact-btn {
font-size: 11px; padding: 2px 8px; border-radius: 4px;
background: var(--bg-elev-2); border: 1px solid var(--border);
color: var(--muted); cursor: pointer;
}
#ctx-compact-btn:not(:disabled):hover { color: var(--text); }
#ctx-compact-btn:disabled { cursor: not-allowed; }
/* --- recall card (history_read / history_search) ---------------------- */
/* Same family as .context-card (which uses a paperclip glyph) so the
* "context surface" cards share a visual language; the leading glyph is
* what distinguishes reads (magnifying-glass) from writes (paperclip). The
* details/summary shape lets the recalled content stay folded by default —
* reads can be long. */
.recall-card {
align-self: flex-start; max-width: 780px;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
padding: 6px 10px; font-size: 12.5px;
}
.recall-card summary {
cursor: pointer; color: var(--muted); list-style: none;
display: flex; align-items: center; gap: 6px;
}
.recall-card summary::-webkit-details-marker { display: none; }
.recall-card summary::before {
/* Round-visual N3: was a search-glass SVG data-URI (CSP-refused). Uses
* a typographic magnifier glyph via `content:` instead — same intent
* (recall = search-through-history), zero image surface. `err` variant
* below swaps the color; the glyph stays. */
content: '\2315'; /* ⌕ telephone recorder (renders as a small magnifier) */
color: var(--muted);
font-size: 12px; line-height: 1;
width: 12px; height: 12px;
display: inline-flex; align-items: center; justify-content: center;
flex: 0 0 auto;
}
.recall-card .tool-name {
color: var(--accent); font-family: var(--mono); font-size: 11px;
}
.recall-card .query {
color: var(--muted); overflow: hidden; white-space: nowrap;
text-overflow: ellipsis; flex: 1;
font-family: var(--mono); font-size: 11px;
}
.recall-card .body {
margin-top: 6px; padding: 6px 8px; background: var(--bg);
border-radius: 4px; font-family: var(--mono); font-size: 12px;
white-space: pre-wrap; word-wrap: break-word; color: var(--text);
max-height: 320px; overflow: auto;
}
.recall-card.pending .body { color: var(--muted); font-style: italic; }
.recall-card.err summary::before {
color: var(--err, #dc2626);
}
.recall-card.err .query { color: var(--error); }
/* --- compact summary as an expandable card --------------------------- */
/* Upgrade of `.compact-divider` from a static break to an interactive one:
* still centred with dashes-on-either-side visual weight, but clicking the
* label opens a details block showing the full summary text + shadowed
* event count. Legacy `.compact-divider` styling above remains as the
* fallback for compact/start / compact/end anchors. */
.compact-card {
align-self: stretch; margin: 8px 0;
color: var(--muted); font-size: 11px;
}
.compact-card summary {
cursor: pointer; list-style: none;
text-align: center; letter-spacing: 0.1em;
position: relative; padding: 4px 0;
}
.compact-card summary::-webkit-details-marker { display: none; }
.compact-card summary::before, .compact-card summary::after {
content: ''; position: absolute; top: 50%; width: 28%;
border-top: 1px dashed var(--border);
}
.compact-card summary::before { left: 8%; }
.compact-card summary::after { right: 8%; }
.compact-card summary .tokens { color: var(--tool); margin-left: 6px; }
.compact-card summary .events { color: var(--muted); margin-left: 6px; opacity: 0.7; }
/* Auto vs manual badge — a small pill on the left of the divider label.
Auto = the runtime saved the turn (pre-step safety valve). Manual = the
user or a plugin asked for compaction. Same 11px monospace family the
surrounding meta uses, but with a coloured background so the two paths
read as distinct at a glance. Sits inside the summary so the click
affordance to open the card still covers the whole line. */
.compact-card summary .compact-badge {
display: inline-block;
margin-right: 8px; padding: 1px 6px;
border-radius: 6px;
font-family: var(--mono); font-size: 10px;
letter-spacing: 0.05em; text-transform: uppercase;
border: 1px solid transparent;
vertical-align: baseline;
}
.compact-card summary .compact-badge-auto {
background: color-mix(in oklab, var(--muted) 12%, var(--bg));
color: color-mix(in oklab, var(--muted) 90%, var(--text));
border-color: color-mix(in oklab, var(--muted) 30%, var(--border));
}
.compact-card summary .compact-badge-manual {
background: color-mix(in oklab, var(--accent) 14%, var(--bg));
color: color-mix(in oklab, var(--accent) 80%, var(--text));
border-color: color-mix(in oklab, var(--accent) 40%, var(--border));
}
.compact-card .body {
margin: 6px 12% 0; padding: 8px 10px;
background: var(--bg); border: 1px solid var(--border); border-radius: 6px;
font-family: var(--mono); font-size: 12px;
white-space: pre-wrap; word-wrap: break-word; color: var(--text);
max-height: 320px; overflow: auto;
text-align: left; letter-spacing: normal;
}
.compact-card .meta {
margin: 6px 12% 0; font-size: 11px; color: var(--muted);
text-align: right; letter-spacing: normal;
}
/* Shadowed-events expander (task #103 P0-1). Compact cards can open a
secondary <details> that reveals the raw events the summary superseded —
DSH's key differentiator (kernel intent doc §2.1). The visual grammar
here is deliberately "read-only ghost": dashed left border, muted
foreground, reduced opacity, no hover highlight, no cursor:pointer on
rows. A reader must feel this is history the model no longer sees but
the log still holds, not something to click, fork, or copy from.
Nested inside .compact-card so it inherits the divider indentation. */
.compact-card .shadowed-expander {
margin: 6px 12% 0; text-align: left; letter-spacing: normal;
}
.compact-card .shadowed-expander-summary {
cursor: pointer; list-style: none;
padding: 4px 8px;
font-size: 11px; color: var(--muted);
border-radius: 4px;
border: 1px dashed var(--border);
background: color-mix(in oklab, var(--muted) 4%, transparent);
display: inline-block;
}
.compact-card .shadowed-expander-summary::-webkit-details-marker {
display: none;
}
.compact-card .shadowed-expander-summary::before {
content: '▸ '; margin-right: 2px;
transition: transform 0.12s ease;
display: inline-block;
}
.compact-card details[open] > .shadowed-expander-summary::before {
content: '▾ ';
}
.compact-card .shadowed-expander-body {
margin-top: 8px;
padding: 8px 0 8px 12px;
border-left: 2px dashed color-mix(in oklab, var(--muted) 40%, var(--border));
opacity: 0.78;
}
.compact-card .shadowed-event {
margin-bottom: 8px; padding: 6px 8px;
background: color-mix(in oklab, var(--muted) 3%, var(--bg));
border-radius: 4px;
font-family: var(--mono); font-size: 11px;
color: var(--muted);
/* No cursor:pointer, no hover — ghost affordance. */
}
.compact-card .shadowed-event-hdr {
display: flex; gap: 8px; align-items: baseline;
margin-bottom: 4px;
}
.compact-card .shadowed-event-seq {
color: color-mix(in oklab, var(--accent) 40%, var(--muted));
font-weight: 600;
}
.compact-card .shadowed-event-type {
color: var(--muted); opacity: 0.85;
}
.compact-card .shadowed-event-body {
white-space: pre-wrap; word-wrap: break-word;
color: color-mix(in oklab, var(--text) 55%, var(--muted));
max-height: 160px; overflow: auto;
}
.compact-card .shadowed-expander-fallback,
.compact-card .shadowed-expander-error {
padding: 6px 8px; font-size: 11px; color: var(--muted); font-style: italic;
}
/* ============================================================================
* Pull Requests page + Quick chat overlay
* ==========================================================================*/
/* --- PR toolbar (search + filter chips) --------------------------------- */
.pr-toolbar {
display: flex; align-items: center; gap: 12px;
padding: 12px 20px; border-bottom: 1px solid var(--border);
}
.pr-search-wrap {
flex: 1; display: flex; align-items: center; gap: 8px;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 8px;
padding: 6px 10px; color: var(--muted);
}
.pr-search-wrap:focus-within { border-color: var(--accent); }
.pr-search {
flex: 1; background: transparent; border: none; outline: none;
color: var(--text); font: inherit; font-size: 13px;
}
.pr-search::placeholder { color: var(--muted); }
/* Filters lead the toolbar (see index.html §pr-toolbar). Order-agnostic
* styling — the visual weight difference between the active chip and the
* others is the load-bearing signal (QA round-3 §8.2). Active gets:
* · brighter fill (accent-tinted background, not just border colour)
* · text bumped from muted → text and font-weight 600
* · a live count pill on the right so scope + volume read at a glance */
.pr-filters {
display: flex; align-items: center; gap: 4px;
padding: 3px; border-radius: 999px;
background: var(--bg-elev); border: 1px solid var(--border);
}
.pr-filter-chip {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 12px; font-size: 12.5px; font-weight: 500;
background: transparent; color: var(--muted);
border: 1px solid transparent; border-radius: 999px;
cursor: pointer;
transition: color 120ms ease, background-color 120ms ease, border-color 120ms ease;
}
.pr-filter-chip:hover { color: var(--text); background: color-mix(in oklab, var(--accent) 8%, transparent); }
.pr-filter-chip.active {
color: var(--text); font-weight: 600;
background: color-mix(in oklab, var(--accent) 18%, var(--bg));
border-color: color-mix(in oklab, var(--accent) 40%, var(--border));
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04);
}
.pr-filter-chip:focus-visible {
outline: 2px solid var(--accent); outline-offset: 2px;
}
.pr-filter-count {
display: inline-block; min-width: 18px; padding: 0 6px;
font-size: 11px; font-family: var(--mono); line-height: 16px; text-align: center;
color: var(--muted); background: var(--bg); border-radius: 999px;
}
.pr-filter-chip.active .pr-filter-count {
color: var(--text);
background: color-mix(in oklab, var(--accent) 26%, var(--bg));
}
/* --- PR list body ------------------------------------------------------- */
.pr-body { flex: 1; overflow-y: auto; padding: 16px 20px 24px; }
.pr-meta { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.pr-hint-banner {
padding: 8px 12px; border: 1px dashed var(--tool); border-radius: 6px;
background: rgba(214, 181, 110, 0.06); color: var(--muted); font-size: 12.5px;
}
.pr-hint-badge {
display: inline-block; padding: 1px 8px; margin-right: 6px;
border: 1px solid var(--tool); color: var(--tool); border-radius: 999px;
font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
}
.pr-hint-banner code {
background: var(--bg); padding: 1px 6px; border-radius: 4px; font-family: var(--mono);
color: var(--text);
}
.pr-group-head {
font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
color: var(--muted); font-weight: 600;
margin: 16px 0 6px; padding-left: 4px;
}
.pr-group-head:first-child { margin-top: 4px; }
.pr-group-head-closed { color: var(--muted); opacity: 0.72; }
.pr-group {
display: flex; flex-direction: column;
background: var(--bg-elev); border: 1px solid var(--border); border-radius: 10px;
overflow: hidden;
}
.pr-empty {
padding: 32px 12px; text-align: center;
display: flex; flex-direction: column; gap: 6px; align-items: center;
color: var(--muted);
}
.pr-empty-title { font-size: 14px; color: var(--text); }
.pr-empty-hint { font-size: 12.5px; }
/* --- PR row ------------------------------------------------------------- */
.pr-row {
display: grid;
grid-template-columns: 44px 1fr auto;
align-items: center; gap: 12px;
width: 100%; text-align: left;
padding: 10px 14px;
background: transparent; color: var(--text);
border: none; border-bottom: 1px solid var(--border);
cursor: pointer;
font: inherit;
}
.pr-row:last-child { border-bottom: none; }
.pr-row:hover { background: var(--bg-elev-2); }
.pr-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pr-row-lead { display: flex; align-items: center; gap: 6px; color: var(--muted); }
.pr-icon { color: var(--muted); }
.pr-state-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--muted); flex: 0 0 auto;
}
.pr-state-open { background: var(--ok); }
.pr-state-draft { background: var(--muted); }
.pr-state-conflict { background: var(--error); }
.pr-state-merged { background: #a184ff; }
.pr-state-closed { background: var(--error); opacity: 0.7; }
.pr-row-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pr-row-titleline {
display: flex; align-items: baseline; gap: 8px;
min-width: 0;
}
.pr-title {
font-weight: 600; font-size: 13.5px; color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
flex: 1; min-width: 0;
}
.pr-num { color: var(--muted); font-family: var(--mono); font-size: 12px; flex: 0 0 auto; }
.pr-row-subline {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
color: var(--muted); font-size: 12px;
}
.pr-repo { font-family: var(--mono); }
.pr-branch { font-family: var(--mono); color: var(--accent); }
.pr-branch-sep { color: var(--muted); }
.pr-base { font-family: var(--mono); }
.pr-dotsep { color: var(--border); }
.pr-author { }
.pr-row-trail {
display: flex; align-items: center; gap: 12px; flex: 0 0 auto;
}
.pr-diff { display: flex; gap: 8px; font-family: var(--mono); font-size: 11.5px; }
.pr-diff-adds { color: var(--ok); }
.pr-diff-dels { color: var(--error); }
.pr-rel { font-size: 12px; min-width: 42px; text-align: right; }
.pr-ask {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 10px 3px 8px; border-radius: 999px; font-size: 12px;
border: 1px solid var(--border);
}
.pr-ask:hover { border-color: var(--accent); color: var(--accent); background: transparent; }
.pr-ask svg { display: inline-block; vertical-align: middle; }
.pr-sidebar-note { padding: 10px 12px; font-size: 12px; line-height: 1.45; }
.pr-sidebar-note code {
background: var(--bg); padding: 1px 4px; border-radius: 3px;
font-family: var(--mono); font-size: 11.5px;
}
/* Merged PR titles read a shade more muted so the "recently merged/closed"
* group visually recedes below Open. */
.pr-row-merged .pr-title,
.pr-row-closed .pr-title { color: var(--muted); font-weight: 500; }
/* --- Quick chat overlay -------------------------------------------------
* reference-style Cmd+Shift+Space palette. Tokens throughout so light and dark
* come from the same rules; scrim uses a very low-alpha wash + backdrop blur
* (macOS Spotlight feel) rather than a hardcoded dark curtain. */
/* `display: flex` would defeat the [hidden] attribute's UA default of
* display:none — without this rule the invisible scrim covers the whole
* window at z-index 900 and silently eats every click in the app. */
.quickchat-scrim[hidden] { display: none; }
.quickchat-scrim {
position: fixed; inset: 0; z-index: 900;
background: rgba(0, 0, 0, 0.18);
display: flex; align-items: flex-start; justify-content: center;
padding-top: 12vh;
opacity: 0; transition: opacity 120ms ease;
backdrop-filter: saturate(160%) blur(10px);
-webkit-backdrop-filter: saturate(160%) blur(10px);
}
:root[data-theme="dark"] .quickchat-scrim { background: rgba(0, 0, 0, 0.45); }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .quickchat-scrim { background: rgba(0, 0, 0, 0.45); }
}
.quickchat-scrim.quickchat-open { opacity: 1; }
.quickchat-card {
width: min(600px, 92vw);
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-pop);
padding: 12px 12px 10px;
display: flex; flex-direction: column; gap: 8px;
transform: translateY(6px);
transition: transform 120ms ease;
}
.quickchat-scrim.quickchat-open .quickchat-card { transform: translateY(0); }
.quickchat-head {
display: flex; align-items: center; gap: 8px;
padding: 2px 6px 8px; border-bottom: 1px solid var(--divider);
}
.quickchat-brand-logo {
width: 16px; height: 16px;
border-radius: 4px;
display: block; flex: 0 0 auto;
}
.quickchat-title { font-weight: 600; font-size: 13px; color: var(--text); }
.quickchat-hint { font-size: 11.5px; color: var(--text-secondary); }
.quickchat-close {
margin-left: auto; font-size: 15px; line-height: 1;
padding: 0 8px; height: 24px;
background: transparent; border: 1px solid transparent;
border-radius: var(--radius-sm); color: var(--text-secondary);
}
.quickchat-close:hover { background: var(--surface-hover); color: var(--text); }
.quickchat-recent {
display: flex; flex-direction: column; gap: 1px;
max-height: 240px; overflow-y: auto;
}
.quickchat-recent-head {
font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em;
padding: 6px 8px 4px; color: var(--text-tertiary);
}
.quickchat-recent-empty {
padding: 10px 8px; font-size: 12.5px; color: var(--text-secondary);
}
.quickchat-recent-row {
display: flex; align-items: center; gap: 8px;
padding: 7px 8px; border-radius: var(--radius-sm);
background: transparent; border: none; color: var(--text); text-align: left;
cursor: pointer; font: inherit; font-size: 13px;
}
.quickchat-recent-row:hover { background: var(--surface-hover); }
.quickchat-recent-row:focus-visible {
outline: 2px solid var(--accent); outline-offset: -2px;
}
.quickchat-recent-title {
flex: 1; min-width: 0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
color: var(--text);
}
/* Smart-title flagged this row as a placeholder ("未命名 · rel-time"). Same
* italic-muted treatment the sidebar uses so both surfaces read the same. */
.quickchat-recent-title.quickchat-recent-title-untitled {
color: var(--text-secondary, var(--muted));
font-style: italic;
}
.quickchat-recent-id {
font-family: var(--mono); font-size: 11px; color: var(--text-tertiary);
}
.quickchat-recent-dot {
width: 6px; height: 6px; border-radius: 50%; background: var(--ok);
animation: pulse 1.4s ease-in-out infinite;
}
.quickchat-composer {
display: flex; align-items: flex-end; gap: 8px;
padding-top: 8px; border-top: 1px solid var(--divider);
}
.quickchat-input {
flex: 1; resize: none;
background: var(--surface-2); border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text); font: inherit; font-size: 13px;
padding: 9px 12px; line-height: 1.45;
transition: border-color 120ms ease, background 120ms ease;
}
.quickchat-input::placeholder { color: var(--text-tertiary); }
.quickchat-input:focus {
outline: none; border-color: var(--accent); background: var(--surface);
}
.quickchat-send {
width: 32px; height: 32px; padding: 0;
border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
}
/* Shared page header primitives — used by Mission Control, Plugins, Playground
* and the new PR page. Kept as a *thin* base so the ui-redesign lane can layer
* a proper theme on top without fighting these rules. */
.header-lead { display: flex; flex-direction: column; gap: 2px; }
.header-lead .page-title { font-size: 15px; font-weight: 600; }
.header-lead .page-sub { font-size: 12.5px; }
/* ==========================================================================
* reference design IA overrides — appended last so they win the cascade for new nodes.
* This section rebases the shell (sidebar-brand, nav-item, sidebar-section-head,
* icon-btn, debug-popover, empty-welcome, prompt-chip, page-title/page-sub,
* .msg light-mode bubbles, composer / statusbar polish, sessions relative
* time) onto the light-first tokens above.
* ========================================================================== */
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
font-size: var(--fs-body);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
::selection { background: var(--accent-soft); color: var(--text); }
.app { grid-template-columns: var(--sidebar-w) 1fr; }
/* --- sidebar: macOS vibrancy, brand row, section nav, foot ---------------- */
.sidebar {
background: var(--surface-vibrancy);
backdrop-filter: saturate(180%) blur(24px);
-webkit-backdrop-filter: saturate(180%) blur(24px);
border-right: 1px solid var(--border);
}
/* Brand row: 22px logo + DSH wordmark + search icon. Left padding reserves
* the macOS traffic-light region so the row stays draggable there. The logo
* is a plain <img>; a 6px radius (matching --radius-sm) softens the raster
* corners into the surrounding UI without hiding the whale's own rounded
* frame. Search is a no-drag island so a click actually lands on the button. */
.sidebar .sidebar-brand {
display: flex; align-items: center; gap: var(--space-2);
padding: var(--space-3) var(--space-3) var(--space-3) 78px;
-webkit-app-region: drag;
}
.sidebar .brand-logo {
width: 22px; height: 22px;
border-radius: var(--radius-sm);
flex: 0 0 auto;
display: block;
}
.sidebar .brand-name {
font-size: var(--fs-section);
font-weight: 600;
letter-spacing: -0.01em;
color: var(--text);
flex: 1; min-width: 0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar .brand-search { -webkit-app-region: no-drag; }
/* Section nav: icon + text rows, active row uses the subtle system tint. */
.sidebar .sidebar-nav {
display: flex; flex-direction: column; gap: 2px;
padding: var(--space-1) var(--space-2) var(--space-3);
}
.sidebar .nav-item {
display: flex; align-items: center; gap: var(--space-3);
width: 100%; padding: 7px var(--space-2);
border: 0; background: transparent;
color: var(--text);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: var(--fs-body);
text-align: left;
transition: background 120ms ease;
}
.sidebar .nav-item:hover { background: var(--surface-hover); }
.sidebar .nav-item.active { background: var(--surface-active); font-weight: 500; }
.sidebar .nav-item.active svg { color: var(--accent); }
.sidebar .nav-item svg { color: var(--text-secondary); flex-shrink: 0; }
.sidebar .sidebar-section-head {
display: flex; align-items: center; justify-content: space-between;
padding: var(--space-3) var(--space-4) var(--space-1);
}
.sidebar .section-label {
font-size: var(--fs-xs); color: var(--text-tertiary);
text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600;
}
.icon-btn {
display: inline-flex; align-items: center; justify-content: center;
padding: 4px; min-width: 24px; min-height: 24px;
background: transparent; color: var(--text-secondary);
border: 0; border-radius: var(--radius-sm);
cursor: pointer;
transition: background 120ms ease, color 120ms ease;
}
.icon-btn:hover:not(:disabled) { background: var(--surface-hover); color: var(--text); }
.icon-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.sidebar > .tab-panel {
display: flex; flex-direction: column; min-height: 0; flex: 1;
}
.sidebar > .tab-panel[hidden] { display: none; }
.sidebar-foot {
margin-top: auto;
padding: var(--space-3);
border-top: 1px solid var(--divider);
display: flex; flex-direction: column; gap: var(--space-2);
}
.sidebar-foot .field {
display: flex; flex-direction: column; gap: 4px;
font-size: var(--fs-xs); color: var(--text-secondary);
}
.sidebar-foot select {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 6px 8px;
font-size: var(--fs-sm);
color: var(--text);
cursor: pointer;
}
.sidebar-foot select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
/* --- sessions list: unified "Recent" — row-dot + inline title + rel-time -- */
.sessions {
padding: 0 var(--space-2) var(--space-2);
/* Independent scroll so long history doesn't push the profile selector or
the primary nav out of reach. min-height: 0 lets flex actually clip. */
overflow-y: auto;
min-height: 0;
}
.sessions li {
color: var(--text);
padding: 6px var(--space-2);
border-radius: var(--radius-sm);
min-height: 32px;
gap: var(--space-2);
align-items: center;
transition: background 120ms ease;
display: flex;
cursor: pointer;
}
.sessions li:hover { background: var(--surface-hover); color: var(--text); }
.sessions li.active {
background: var(--accent-soft);
color: var(--text);
}
/* Row layout: state dot · title (grows) · rel-time (right-aligned muted).
The title lives in a flex-1 wrapper so a very long title truncates while
the rel-time chip stays pinned to the row's right edge. */
.sessions li .title-and-sub {
flex: 1 1 auto; min-width: 0;
display: flex; align-items: center; gap: var(--space-2);
}
.sessions li .title-and-sub .title {
color: inherit;
font-size: var(--fs-sm);
line-height: 1.2;
flex: 1 1 auto; min-width: 0;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sessions li .title-and-sub .title-untitled {
color: var(--text-secondary);
font-style: italic;
}
.sessions li .title-and-sub .rel-time {
color: var(--text-tertiary);
font-size: var(--fs-micro);
line-height: 1.2;
flex: 0 0 auto;
}
/* Per-row state dot family: green for a running live session (a turn is in
flight right now), muted ring for a persisted-only resume row, an invisible
spacer for a plain idle-live row so all rows line up on the same left edge
regardless of state. */
.sessions li .row-dot {
flex: 0 0 auto;
width: 8px; height: 8px;
border-radius: 50%;
background: transparent;
}
.sessions li .row-dot-live {
background: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.sessions li .row-dot-resume {
background: transparent;
border: 1.5px solid var(--text-tertiary);
width: 8px; height: 8px;
box-sizing: border-box;
}
.sessions li .row-dot-idle {
/* invisible spacer — keeps title left-edge aligned across states */
background: transparent;
}
/* Legacy .live-dot kept as an alias so any lingering reference (mocks,
fork-marker rendering) still paints. Prefer .row-dot-live in new code. */
.sessions li .live-dot {
background: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
animation: none;
}
.sessions .session-group-label {
padding: var(--space-3) var(--space-2) 4px;
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
list-style: none;
}
.sessions .session-untitled-group {
padding: 4px var(--space-2);
color: var(--text-secondary);
font-style: italic;
font-size: var(--fs-xs);
cursor: default;
}
.sessions .session-untitled-group:hover { background: transparent; }
/* Sessions head: label · Show-all toggle · new-session button. The toggle
is a compact ghost affordance next to the "+" so it never fights the
section-label. Hidden entirely when the list is short. */
.sidebar-section-head #sessions-toggle {
font-size: var(--fs-micro);
padding: 2px 6px;
color: var(--text-tertiary);
text-transform: none;
letter-spacing: 0;
margin-left: auto;
margin-right: var(--space-1);
}
/* --- main area: page-header lockups + light background -------------------- */
.main {
background: var(--bg);
position: relative;
}
.pane { min-height: 0; }
.header {
padding: var(--space-4) var(--space-6) var(--space-3);
border-bottom: 1px solid var(--divider);
min-height: var(--header-h);
/* Header is a column-flex child of .pane; without flex-shrink:0 the pane
* squeezes it down to min-height and two-line subtitles bleed into the
* content below (user-visible on Growth/Session Tree). Size to content. */
flex-shrink: 0;
background: var(--bg);
display: flex;
align-items: flex-start;
gap: var(--space-4);
}
.header .title {
font-size: var(--fs-section);
font-weight: 600;
color: var(--text);
padding-top: 4px;
}
.header .header-lead {
flex: 1;
min-width: 0;
gap: 2px;
}
.header .header-lead .page-title {
font-size: var(--fs-page);
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text);
line-height: 1.2;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.header .header-lead .page-sub {
font-size: var(--fs-sm);
color: var(--text-secondary);
line-height: 1.4;
max-width: 640px;
}
.header .header-lead .page-title .muted.small { font-weight: 400; font-size: var(--fs-sm); }
.header .header-actions {
display: flex; align-items: center; gap: var(--space-2); flex-shrink: 0;
}
/* --- debug popover -------------------------------------------------------- */
.debug-popover { position: relative; }
/* Fresh-eyes P0 (2026-07-18, review-fresh-eyes.md #4 + team-lead follow-up):
the Debug popover should only exist when the app is running in QA mode
(DSH_QA=1 → location.hash 'qa' → body[data-qa="1"] set by renderer.js).
Production launches must not surface the mock-card / fixture-load / inject
buttons — a first-time researcher clicking "Debug" was landing on a wall
of unlabelled fixture verbs. The renderer sets the body flag once during
boot; the popover is hidden here until that flag flips on. */
body:not([data-qa="1"]) .debug-popover { display: none; }
.debug-toggle { display: inline-flex; align-items: center; gap: 6px; }
.debug-toggle svg { color: var(--text-tertiary); }
.debug-popover .debug {
display: none;
position: absolute;
right: 0;
top: calc(100% + 6px);
min-width: 240px;
max-height: 60vh;
overflow-y: auto;
padding: var(--space-2);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-pop);
z-index: 30;
flex-direction: column;
gap: 2px;
}
.debug-popover[data-open="true"] .debug { display: flex; }
.debug-popover .debug .debug-group-label {
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
padding: 6px 6px 2px;
}
.debug-popover .debug button {
justify-content: flex-start;
width: 100%;
text-align: left;
padding: 6px 8px;
}
/* --- stream + messages (light theme rebase, keep max-width tokens) -------- */
.stream {
padding: var(--space-6) var(--space-6) var(--space-4);
}
.msg { line-height: 1.55; }
.msg.user {
background: var(--user-bubble);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
color: var(--text);
max-width: 720px;
margin-left: auto;
border: none;
}
.msg.assistant {
background: transparent;
padding: var(--space-2) 0;
color: var(--text);
border: none;
}
.msg.reasoning {
color: var(--text-secondary);
font-style: italic;
font-size: var(--fs-sm);
padding: var(--space-2) var(--space-3);
border-left: 2px solid var(--divider);
}
.system {
color: var(--text-tertiary);
font-size: var(--fs-xs);
font-family: var(--mono);
opacity: 0.75;
}
/* --- empty welcome card + prompt chips ------------------------------------ */
.empty-welcome {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: var(--space-12) var(--space-6);
max-width: 760px;
margin: auto;
min-height: 60vh;
}
/* Rec 29 revision (2026-07-17): tight-viewport variant so the whole
empty-state fits inside a ≤700px stream area without scrolling. The
4-card launcher block itself is ~180px; the badge+title+sub column
above it eats ~330px with default padding, which pushes the bottom of
the fourth card off a 600px viewport. Trim vertical padding + inter-
block margins here (only at ≤700px viewport height) rather than on
the base rule so the standard 900+px window keeps its generous
whitespace. Numbers: 48→24 top/bottom padding (-48), 60vh floor drops
out of the way (content is 400+), sub margin 24→12 (-12), badge
margin 16→8 (-8). Net -68px, enough to bring the 4-card bottom edge
inside a 600px viewport. */
@media (max-height: 700px) {
.empty-welcome {
padding-top: var(--space-6);
padding-bottom: var(--space-6);
min-height: 0;
}
.empty-welcome-badge { margin-bottom: var(--space-2); }
.empty-welcome-sub { margin-bottom: var(--space-3); }
}
.empty-welcome-badge {
display: inline-flex;
align-items: center; justify-content: center;
width: 56px; height: 56px;
border-radius: var(--radius-lg);
background: var(--accent-soft);
color: var(--accent);
margin-bottom: var(--space-4);
}
/* Brand variant of the welcome badge: the logo IS the badge, so we drop the
* tinted plate and let the raster's own rounded frame speak. Same footprint
* as the SVG variant so the layout is unchanged. */
.empty-welcome-badge--brand {
background: transparent;
padding: 0;
border-radius: var(--radius-lg);
overflow: hidden;
}
.empty-welcome-badge--brand img { display: block; width: 56px; height: 56px; }
.empty-welcome-title {
font-size: var(--fs-hero);
font-weight: 700;
letter-spacing: -0.02em;
color: var(--text);
margin: 0 0 var(--space-2);
}
.empty-welcome-sub {
font-size: var(--fs-body);
color: var(--text-secondary);
margin: 0 0 var(--space-6);
max-width: 540px;
line-height: 1.5;
}
.empty-welcome-prompts {
display: grid;
/* Two columns at any reasonable width. The four cards teach the four
surfaces (chat / fork / vibe / mission), so a 2x2 grid reads as a
matrix of first moves rather than a random ribbon. */
grid-template-columns: repeat(2, minmax(240px, 1fr));
gap: var(--space-3);
width: 100%; max-width: 720px;
}
@media (max-width: 640px) {
.empty-welcome-prompts { grid-template-columns: 1fr; }
}
.prompt-chip {
display: flex;
flex-direction: column;
gap: 6px;
padding: var(--space-3) var(--space-4);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
text-align: left;
transition: background 120ms ease, border-color 120ms ease, box-shadow 120ms ease, transform 120ms ease;
}
/* Icon-and-body variant used by the differentiator cards on the welcome
screen. Reads as a two-column card: a tinted glyph on the left, title +
one-line description on the right. Base `.prompt-chip` (title-only) is
kept so older call sites still render sanely. */
.prompt-chip.prompt-chip--action {
flex-direction: row;
align-items: flex-start;
gap: var(--space-3);
padding: var(--space-4);
min-height: 88px;
}
.prompt-chip:hover {
border-color: var(--border-strong);
box-shadow: var(--shadow-2);
transform: translateY(-1px);
}
.prompt-chip:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* §7 "one accent per view": the empty-state chip icons were four hues
(blue/purple/teal/orange) painted on solid tint backgrounds. Collapsed
to the single view accent — --accent glyph on --accent-soft tile — to
match the launcher-icon grammar at line 7259 (rec 29 revision, drift
#218). The .tint-* class variants stay defined below so surfaces that
legitimately need per-kind hues (session badges, kind-palette waiver)
can opt back in explicitly; nothing in the current empty-state HTML
sets a tint-* class on .prompt-chip-icon anymore. */
.prompt-chip-icon {
width: 36px; height: 36px;
border-radius: var(--radius-sm);
display: inline-flex; align-items: center; justify-content: center;
color: var(--accent);
background: var(--accent-soft);
flex: 0 0 auto;
}
.prompt-chip-icon.tint-blue { background: var(--tint-blue); color: white; }
.prompt-chip-icon.tint-purple { background: var(--tint-purple); color: white; }
.prompt-chip-icon.tint-teal { background: var(--tint-teal); color: white; }
.prompt-chip-icon.tint-orange { background: var(--tint-orange); color: white; }
.prompt-chip-icon.tint-pink { background: var(--tint-pink); color: white; }
.prompt-chip-icon.tint-slate { background: var(--tint-slate); color: white; }
.prompt-chip-body {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.prompt-chip-title {
font-size: var(--fs-body);
font-weight: 600;
color: var(--text);
}
.prompt-chip-hint {
font-size: var(--fs-xs);
color: var(--text-secondary);
line-height: 1.45;
}
/* --- composer (rounded shell with focus ring) ----------------------------- */
.composer {
padding: var(--space-3) var(--space-6);
border-top: 1px solid var(--divider);
background: var(--bg);
}
.composer textarea {
border: 1px solid var(--border);
background: var(--surface-2);
color: var(--text);
border-radius: var(--radius-lg);
padding: var(--space-3) var(--space-4);
font: inherit;
font-size: var(--fs-body);
line-height: 1.5;
resize: vertical;
width: 100%;
outline: none;
transition: border-color 120ms ease, box-shadow 120ms ease;
}
.composer textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.composer textarea::placeholder { color: var(--text-tertiary); }
.composer-actions {
display: flex; align-items: center; justify-content: flex-end;
gap: var(--space-2);
padding-top: var(--space-2);
}
/* --- buttons (unified primary + ghost, reference design weight) ---------------------- */
button.primary,
button.ghost,
button.small {
font-family: var(--font);
font-size: var(--fs-sm);
padding: 6px 12px;
border-radius: var(--radius-sm);
border: 1px solid transparent;
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
font-weight: 500;
display: inline-flex; align-items: center; gap: 6px;
white-space: nowrap;
}
button.primary {
background: var(--accent); color: white; border-color: var(--accent);
}
button.primary:hover:not(:disabled) {
background: var(--accent-strong); border-color: var(--accent-strong);
}
button.primary:disabled {
background: var(--surface-hover); color: var(--text-tertiary);
border-color: transparent; cursor: not-allowed;
}
button.ghost {
background: transparent; color: var(--text);
border-color: var(--border);
}
button.ghost:hover:not(:disabled) { background: var(--surface-hover); }
button.ghost:disabled { color: var(--text-tertiary); opacity: 0.6; cursor: not-allowed; }
button.small { padding: 4px 8px; font-size: var(--fs-xs); }
/* Status bar reads slightly quieter on a light backdrop. */
.statusbar {
padding: 6px var(--space-6);
background: var(--surface-2);
border-top: 1px solid var(--divider);
color: var(--text-secondary);
font-size: var(--fs-xs);
}
.statusbar .dot { background: var(--text-tertiary); }
.statusbar .dot.running,
.statusbar .dot.starting { background: var(--warn); }
.statusbar .dot.crashed { background: var(--err); }
.statusbar .dot.idle,
.statusbar .dot.ready { background: var(--ok); }
/* Context meter uses semantic tokens for its level classes. */
.ctx-meter-bar { background: var(--surface-hover); }
.ctx-meter.level-nominal .ctx-meter-fill { background: var(--ok); }
.ctx-meter.level-warn .ctx-meter-fill,
.ctx-meter.level-high .ctx-meter-fill { background: var(--warn); }
.ctx-meter.level-critical .ctx-meter-fill { background: var(--err); }
/* --- cards / tool blocks: unify on the surface token, gentle shadow ------- */
.tool-block,
.card,
.card-terminal, .card-diff, .card-code-dispatch,
.card-web-search, .card-skill, .card-workflow,
.artifact-card, .visibility-card, .todo-card,
.recall-card, .compact-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-1);
}
/* History is sidebar chrome, not a chat card: it must stay a flat, full-width
* list section (reference-style), never a floating boxed card that shrinks with
* every inherited inset. Kept OUT of the max-width/margin:auto bucket below —
* that grouping is chat-stream cards only. */
.panels-c-history {
background: transparent;
border: 0; border-radius: 0; box-shadow: none;
margin: 0; width: 100%; max-width: none;
}
/* Centered-card ban (density-spec §7, user re-flagged 2026-07-17): every
* in-stream card renders as a full-width, left-aligned row that expands
* INLINE. No `margin: * auto` and no `max-width` narrowing here — the row
* width is the stream width. Per-card padding stays; centered layouts are
* reserved for empty-state page chrome (.empty-welcome / .empty-launcher /
* .context-page-empty-*) and the .msg.user right-aligned bubble. */
.card, .tool-block, .artifact-card, .card-terminal, .card-diff,
.card-code-dispatch, .card-web-search, .card-skill, .card-workflow,
.visibility-card, .todo-card, .recall-card, .compact-card {
margin: var(--space-3) 0;
max-width: none;
}
/* Approval / question cards get a subtle accent bar so the user knows to act. */
.card.card-approval, .card.card-question {
border-left: 3px solid var(--accent);
}
/* "demo" corner badge for anything injected by the Debug flyout. Mock
helpers all use `mock-…` for callId / interruptId, so an attribute
selector is enough — no JS change needed to tag them. The badge only
appears when the DOM node carries such an id. */
.tool-block[data-call-id^="mock-"],
.card[data-interrupt-id^="mock-"],
.recall-card[data-call-id^="mock-"],
.artifact-card[data-call-id^="mock-"],
.card[data-mock="true"] {
position: relative;
}
.tool-block[data-call-id^="mock-"]::before,
.card[data-interrupt-id^="mock-"]::before,
.recall-card[data-call-id^="mock-"]::before,
.artifact-card[data-call-id^="mock-"]::before,
.card[data-mock="true"]::before {
content: "demo";
position: absolute;
top: 8px; right: 10px;
padding: 2px 8px;
border-radius: 999px;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--text-secondary);
background: var(--surface-2);
border: 1px solid var(--border);
pointer-events: none;
z-index: 2;
}
.card .card-icon {
width: 32px; height: 32px;
border-radius: var(--radius-sm);
background: var(--accent-soft); color: var(--accent);
display: inline-flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
/* --- plugins / market / mission page shells re-inherit page-title/page-sub */
.plugins-body, .mission-body {
padding: var(--space-4) var(--space-6);
overflow-y: auto;
gap: var(--space-4);
display: flex; flex-direction: column;
flex: 1;
}
/* --- market card polish (reference design row list) ---------------------------------- */
.market-row {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
transition: box-shadow 120ms ease, border-color 120ms ease, transform 120ms ease;
}
.market-row:hover {
border-color: var(--border-strong);
box-shadow: var(--shadow-2);
transform: translateY(-1px);
}
/* --- devtools drawer: light theme rebase (frames + hover) ----------------- */
.devtools-drawer {
background: var(--surface);
border-left: 1px solid var(--border);
box-shadow: var(--shadow-pop);
}
/* --- focus ring for keyboard users --------------------------------------- */
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible {
outline: none;
box-shadow: 0 0 0 3px var(--accent-soft);
border-color: var(--accent);
}
/* ==========================================================================
* Composer shell (reference-style)
* Single rounded frame that groups the textarea + meta chips + send button.
* Chips read the environment (cwd/permission mode/model) so the user always
* knows the exact state before pressing Enter. `session/set_config` promotes
* the model dropdown from cosmetic to functional; older daemons receive a
* MethodNotFound and the field greys.
* ========================================================================== */
.composer.composer-shell {
padding: var(--space-3) var(--space-6) var(--space-4);
border-top: 1px solid var(--divider);
background: var(--bg);
/* The composer sits inside a `<footer>` that spans the pane. Without an
* inner max-width the frame stretches to the pane edge, so the send button
* ends up far right of the assistant column. Match the stream token so the
* composer, message bubbles, cards, and welcome block all read as one
* centered rail. */
display: flex; justify-content: center;
}
.composer.composer-shell .composer-frame {
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
display: flex; flex-direction: column;
width: 100%;
max-width: var(--stream-w);
transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
}
.composer.composer-shell .composer-frame:focus-within {
border-color: var(--accent);
background: var(--surface);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.composer.composer-shell textarea {
border: none;
background: transparent;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
padding: var(--space-3) var(--space-4);
font: inherit;
font-size: var(--fs-body);
line-height: 1.5;
color: var(--text);
resize: vertical;
min-height: 60px;
width: 100%;
outline: none;
box-shadow: none;
}
.composer.composer-shell textarea::placeholder { color: var(--text-tertiary); }
.composer.composer-shell textarea:focus { border: none; box-shadow: none; }
.composer-bar {
display: flex; align-items: center; justify-content: space-between;
gap: var(--space-3);
padding: 4px 6px 4px 8px;
border-top: 1px solid var(--divider);
}
.composer-meta {
display: flex; align-items: center; gap: 6px;
min-width: 0; flex: 1;
}
.composer.composer-shell .composer-actions {
display: flex; align-items: center;
gap: var(--space-2); padding: 0;
}
.composer-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 8px;
border-radius: 999px;
background: transparent;
color: var(--text-secondary);
font-size: 11.5px;
line-height: 1.4;
border: 1px solid transparent;
max-width: 240px;
transition: background 120ms ease, color 120ms ease;
cursor: default;
}
.composer-chip:hover { background: var(--surface-hover); color: var(--text); }
.composer-chip svg { flex: 0 0 auto; }
.composer-chip-label {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.composer-chip-suffix {
padding: 1px 6px;
border-radius: 999px;
background: var(--warn-soft);
color: var(--warn);
font-size: 10px; font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
}
/* Preflight (2026-07-18) NO_ADAPTER guard: inline advisory below the
composer chip row when the picked model isn't wired to the active
profile. Muted body colour + warn accent border so it reads as
guidance, not a red banner. Full-width row per §7 spacing (12px
inner padding, 8px gap to composer). */
.composer-model-warn {
margin: 8px 4px 0 4px;
padding: 8px 12px;
border-left: 2px solid var(--warn);
background: var(--warn-soft);
color: var(--text-secondary);
font-size: 12px;
line-height: 1.5;
border-radius: 4px;
}
.composer-model-warn a {
color: var(--text);
text-decoration: underline;
cursor: pointer;
}
.composer-model-warn a:hover { color: var(--accent); }
.composer-chip--mode {
color: var(--text-secondary);
}
.composer-chip--mode[data-mode="yolo"] { color: var(--err); }
.composer-chip--mode[data-mode="yolo"] svg { color: var(--err); }
.composer-chip--mode[data-mode="auto-edit"] { color: var(--warn); }
.composer-chip--mode[data-mode="ask-first"] { color: var(--text-secondary); }
.composer-chip--select {
padding-right: 4px;
cursor: pointer;
/* Round-visual N3: chip carries `position: relative` so the ::after
* chevron (see below) can absolute-position against it. */
position: relative;
}
.composer-chip--select:hover { background: var(--surface-hover); color: var(--text); }
.composer-select {
appearance: none; -webkit-appearance: none;
background: transparent;
border: none;
color: inherit;
font: inherit;
font-size: 11.5px;
padding: 0 14px 0 2px;
cursor: pointer;
outline: none;
}
.composer-chip--select::after {
content: '';
position: absolute;
right: 4px;
top: 50%;
width: 0; height: 0;
margin-top: -2px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid var(--text-tertiary, #787774);
pointer-events: none;
}
.composer-select:focus-visible {
outline: 2px solid var(--accent); outline-offset: 2px;
border-radius: 4px;
}
.composer-select:disabled {
color: var(--text-tertiary);
cursor: not-allowed;
}
/* Fresh-eyes P0 (2026-07-18, review-fresh-eyes.md #7): the send button was
a 30×30 disc with a 1.8-stroke inner glyph — visible under scrutiny, but
a new user reading the composer for the first time did not recognize it
as a send button (the reviewer thought there wasn't one). Bumped to 34×34
so it reads as a control at a glance, and the disabled state (empty
textarea, in-flight turn) now paints muted so "why can't I send?" reads
from the button itself rather than requiring the user to guess. */
.composer-send {
width: 34px; height: 34px; padding: 0;
border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.composer-send svg { display: block; }
.composer-send:disabled {
/* Overrides button.primary:disabled from the general button rule with a
softer visible state — button is legible even when idle, so the user
doesn't wonder whether it's a real button. */
background: var(--surface-hover);
color: var(--text-tertiary);
border-color: var(--border);
opacity: 1;
}
/* Scrollbars — thin, macOS-esque, invisible until hover. */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.15);
border-radius: 5px;
border: 2px solid transparent;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background-color: rgba(0, 0, 0, 0.3); background-clip: content-box; }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.15); background-clip: content-box; }
}
/* -- Next-action verb states on widget actions -----------------------------
* Broken buttons: disabled + red-tinted border so widget authors see the
* mistake without clicking. Record-only: subtler grey, still clickable but
* clearly log-only.
*/
.widget-actions button.widget-action-broken {
opacity: 0.55;
border-color: var(--err, var(--error, #b04d4d));
color: var(--err, var(--error, #b04d4d));
background: transparent;
cursor: not-allowed;
}
.widget-actions button.widget-action-record {
opacity: 0.75;
border-style: dashed;
font-style: italic;
}
.widget-broken {
outline: 1px dashed var(--err, var(--error, #b04d4d));
outline-offset: -1px;
border-radius: 6px;
}
.widget-broken-banner {
padding: 4px 8px;
font-size: 11.5px;
color: var(--err, var(--error, #b04d4d));
background: var(--err-soft, rgba(176, 77, 77, 0.08));
border-bottom: 1px solid var(--err, var(--error, #b04d4d));
}
/* -- Next-action suggestion chips (above the composer) --------------------
* A single row of pill buttons + a ✕. Hidden entirely when empty so the
* composer moves back down flush. Verb-agnostic — a broken chip disables
* itself, a record-only chip renders subtler.
*/
.next-action-chips {
display: flex; flex-wrap: wrap; gap: 6px;
padding: 6px 12px 4px;
align-items: center;
}
.next-action-chips[hidden] { display: none; }
.next-action-chip {
display: inline-flex; align-items: stretch;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface, transparent);
font-size: 12px;
overflow: hidden;
transition: background 120ms ease, border-color 120ms ease;
}
.next-action-chip:hover { background: var(--surface-hover, var(--surface, transparent)); }
.next-action-chip-btn {
border: 0; background: transparent;
color: var(--text);
padding: 3px 10px;
cursor: pointer;
font: inherit;
line-height: 1.4;
}
.next-action-chip-btn:disabled { cursor: not-allowed; opacity: 0.55; }
.next-action-chip-btn.next-action-chip-broken {
color: var(--err, var(--error, #b04d4d));
}
.next-action-chip-btn.next-action-chip-record {
color: var(--text-secondary, var(--muted));
font-style: italic;
}
.next-action-chip-dismiss {
border: 0; background: transparent;
color: var(--text-tertiary, var(--muted));
padding: 0 8px;
cursor: pointer;
font: inherit;
font-size: 14px;
line-height: 1;
border-left: 1px solid var(--border);
}
.next-action-chip-dismiss:hover { color: var(--err, var(--error, #b04d4d)); }
/* Generic guard: any element carrying [hidden] must actually disappear even
* when a later rule sets display:flex/grid. Attribute selector + !important
* outranks class-level display without needing per-component rules. This is
* the app-wide fix for the "invisible overlay eats clicks" family. */
[hidden] { display: none !important; }
/* ─────────────────────────────────────────────────────────────────────────
* Session Tree page (docs/product-ia-design.md §2).
*
* Two-column layout: compact tree navigator on the left, session preview on
* the right. The visual language is the same restrained token set the rest
* of the shell uses — no new colours, no shadows above --shadow-1, all
* icons inline SVG with currentColor so dark mode is free.
*
* Edge kinds are encoded on data-edge (fork | subagent | root). The
* connector column draws its stem with a 1px currentColor rule at 40%
* opacity so it fades behind the row content without adding a new token.
* ───────────────────────────────────────────────────────────────────── */
.pane[data-pane="tree"] .tree-body {
display: grid;
grid-template-columns: minmax(280px, 360px) 1fr;
gap: 0;
height: calc(100vh - 96px);
min-height: 0;
border-top: 1px solid var(--divider);
}
.tree-nav {
border-right: 1px solid var(--divider);
display: flex;
flex-direction: column;
min-height: 0;
background: var(--bg);
}
.tree-nav-head {
padding: 12px 16px 8px;
border-bottom: 1px solid var(--divider);
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.tree-nav-legend {
display: flex;
gap: 12px;
font-size: 12px;
color: var(--text-tertiary);
}
.tree-legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
}
.tree-legend-edge {
display: inline-block;
width: 16px;
height: 1px;
background: currentColor;
opacity: 0.55;
}
.tree-legend-edge--subagent {
height: 0;
border-top: 1px dashed currentColor;
background: transparent;
}
.tree-nav-meta {
font-size: 12px;
}
.tree-list {
list-style: none;
margin: 0;
padding: 8px 0;
overflow-y: auto;
flex: 1 1 auto;
min-height: 0;
}
.tree-empty {
padding: 24px 16px;
font-size: 13px;
text-align: center;
}
/* Row. Task #198 rewrite (2026-07-17): the old grid-template
* [connector | dot | body | actions] set --depth via padding-left, then a
* :first-child .tree-row-connector span sat in column 1. When a row had NO
* connector (depth 0 OR the removed span variant), the grid still allocated
* the 12px column and the body slid right — that's the "text floating to
* the right end of the row" the user flagged. Fix: 3-column grid
* [dot | body | actions], no reserved connector column. Vertical rails +
* horizontal ticks come from nested-<ul.tree-children> geometry instead,
* so a row that has NO children never reserves space for a connector.
*
* A .tree-row LI now also HOSTS its nested-<ul.tree-children> as a DIRECT
* child so the CSS border-left rail geometrically hangs off the parent row
* (like git-branchless / gh CLI trees). We use `display: grid` on the LI
* but push the nested UL onto its own row via `grid-column: 1 / -1` so
* it does not become a 4th grid item that squishes body+actions.
*
* §7 pass (2026-07-17): all paddings / margins / gaps snap to the 4px
* grid {4,8,12,16,24,32}; rails switch off --accent (loud, competes with
* the selected-marker) to --border-strong (structural line, one accent
* per view rule); no hard-coded hex fallbacks (tokens only).
*/
.tree-row {
display: grid;
grid-template-columns: 12px 1fr auto;
grid-auto-rows: min-content;
align-items: center;
gap: 8px;
padding: 4px 12px;
min-height: 28px;
cursor: pointer;
position: relative;
color: var(--text);
transition: background-color 120ms ease;
}
.tree-row:hover { background: var(--surface-hover); }
.tree-row:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.tree-row.is-selected {
background: var(--accent-soft);
color: var(--text);
}
.tree-row.is-selected::before {
content: '';
position: absolute;
left: 0;
top: 4px;
bottom: 4px;
width: 2px;
background: var(--accent);
border-radius: 1px;
z-index: 1;
}
.tree-row.is-compare {
background: var(--surface-2);
box-shadow: inset 0 0 0 1px var(--border);
}
.tree-row.is-compare-candidate {
outline: 1px dashed var(--accent);
outline-offset: -2px;
animation: tree-compare-blink 1200ms ease-in-out 3;
}
@keyframes tree-compare-blink {
0%, 100% { outline-color: var(--accent); }
50% { outline-color: transparent; }
}
/* Nested-<ul> children — the vertical rail is a border-left on this
* container so every child row sits under a real drawn line, and a
* per-row ::before draws the horizontal tick into the row. Result: the
* legend's "solid = fork, dashed = subagent" corresponds 1:1 to a line
* on screen. Indent (16px) is what pushes the child's dot/body right,
* so children are LEFT-aligned to a fixed column, not right-aligned to
* the row edge (the old bug).
*
* Placement inside the parent .tree-row LI: because the LI is a grid,
* force the UL to span the full grid width on a fresh row so it does
* NOT become a 4th grid item and squish dot/body/actions.
*
* §7 pass: rails use --border-strong (structural, quiet) rather than
* --accent (which is reserved for the selected-row bar — spec's "one
* accent per view" rule). Every spacing value is on the 4px grid.
*/
.tree-children {
grid-column: 1 / -1;
list-style: none;
margin: 0 0 0 16px;
padding: 0;
position: relative;
border-left: 1px solid var(--border-strong);
}
.tree-children[data-rail-kind="subagent"] {
border-left-style: dashed;
}
/* Horizontal tick from the rail into each child row. Sits BEFORE the
* child's padding-left so the dot/body slot still starts at the
* child's content edge, keeping alignment consistent with root rows. */
.tree-children > .tree-row::before {
content: '';
position: absolute;
left: -1px; /* meet the rail */
top: 50%;
width: 12px;
height: 1px;
background: var(--border-strong);
transform: translateY(-50%);
z-index: 0;
}
.tree-children[data-rail-kind="subagent"] > .tree-row::before {
background: transparent;
border-top: 1px dashed var(--border-strong);
height: 0;
}
/* Child row needs a left pad to clear the tick + rail. Also swap the
* left-inset selected bar for a tick-flavour marker so it doesn't fight
* the rail geometry. */
.tree-children > .tree-row { padding-left: 16px; }
.tree-children > .tree-row.is-selected::before {
content: '';
left: -1px;
width: 12px;
height: 1px;
top: 50%;
bottom: auto;
transform: translateY(-50%);
border-radius: 0;
}
/* Status dot: subtle grey by default; green pulse for running; amber for
* awaiting approval so the tree surfaces "who's blocking on me" at a
* glance. Uses the same accent-soft ring pattern the header meter uses. */
.tree-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-tertiary);
display: inline-block;
flex: 0 0 auto;
}
.tree-status-dot[data-status="running"] {
background: #22c55e;
box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15);
animation: tree-dot-pulse 1600ms ease-in-out infinite;
}
.tree-status-dot[data-status="awaiting"] {
background: #f59e0b;
box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.18);
}
.tree-status-dot[data-status="done"] { background: var(--accent); }
@keyframes tree-dot-pulse {
0%, 100% { box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15); }
50% { box-shadow: 0 0 0 5px rgba(34, 197, 94, 0.06); }
}
.tree-row-body {
min-width: 0;
display: flex;
flex-direction: row;
align-items: baseline;
gap: 8px;
overflow: hidden;
}
.tree-row-title {
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 0 1 auto;
min-width: 0;
}
.tree-row-rel {
font-size: 11px;
color: var(--text-tertiary);
flex: 0 0 auto;
}
.tree-row-actions {
display: flex;
gap: 4px;
opacity: 0;
/* pointer-events:none while hidden — the geometry (24×24 buttons) is
* still in the row, but a stray click on transparent chrome shouldn't
* fire fork/compare on an unhovered row. */
pointer-events: none;
transition: opacity 120ms ease;
}
.tree-row:hover .tree-row-actions,
.tree-row:focus-within .tree-row-actions {
opacity: 1;
pointer-events: auto;
}
.tree-row-action {
width: 24px;
height: 24px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
color: var(--text-tertiary);
background: transparent;
border: 0;
cursor: pointer;
}
.tree-row-action:hover {
color: var(--text);
background: var(--surface-2);
}
.tree-row-action:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
/* Preview column. Wide card layout so metadata reads like a spec sheet.
* We keep the timeline low-density: one row of markers, coloured by
* bucket, no vertical baselines. */
.tree-preview {
padding: 20px 24px;
overflow-y: auto;
min-height: 0;
min-width: 0;
}
.tree-preview-empty {
max-width: 480px;
margin: 48px auto;
padding: 24px;
text-align: center;
}
.tree-preview-empty-title {
font-size: 13px;
font-weight: 600;
margin-bottom: 4px;
}
.tree-preview-empty-body {
font-size: 13px;
line-height: 1.5;
}
.tree-preview-card {
max-width: 720px;
display: flex;
flex-direction: column;
gap: 20px;
}
.tree-preview-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.tree-preview-lead { min-width: 0; }
.tree-preview-title {
font-size: 16px;
font-weight: 600;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-preview-sub {
font-size: 12px;
margin-top: 2px;
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.tree-preview-sep { color: var(--text-tertiary); }
.tree-preview-status {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--surface);
font-size: 12px;
color: var(--text-secondary);
flex: 0 0 auto;
}
.tree-preview-meta {
margin: 0;
padding: 12px 16px;
background: var(--surface);
border: 1px solid var(--divider);
border-radius: var(--radius-md);
display: grid;
gap: 8px;
}
.tree-preview-meta-row {
display: grid;
grid-template-columns: 80px 1fr;
align-items: baseline;
gap: 12px;
font-size: 13px;
}
.tree-preview-meta-row dt {
color: var(--text-tertiary);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.tree-preview-meta-row dd {
margin: 0;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-context-bar {
display: inline-block;
width: 120px;
height: 4px;
background: var(--surface-2);
border-radius: 2px;
vertical-align: middle;
overflow: hidden;
margin-right: 8px;
}
.tree-context-fill {
display: block;
height: 100%;
background: var(--accent);
width: 0%;
transition: width 240ms ease;
}
.tree-context-label { font-size: 12px; vertical-align: middle; }
.tree-preview-timeline {
border: 1px solid var(--divider);
border-radius: var(--radius-md);
padding: 12px 16px;
background: var(--surface);
}
.tree-preview-timeline-head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 12px;
}
.tree-preview-section-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-tertiary);
}
.tree-preview-timeline-hint { font-size: 11px; }
.tree-preview-timeline-track {
position: relative;
height: 20px;
background: var(--surface-2);
border-radius: 2px;
overflow: hidden;
}
.tree-preview-timeline-empty {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
}
.tree-preview-timeline-legend {
display: flex;
gap: 12px;
margin-top: 8px;
font-size: 11px;
color: var(--text-tertiary);
}
.tree-tl-legend {
display: inline-flex;
align-items: center;
gap: 4px;
}
.tree-tl-marker {
position: absolute;
top: 4px;
bottom: 4px;
width: 3px;
border-radius: 1px;
background: var(--text-tertiary);
transform: translateX(-1.5px);
}
.tree-tl-marker--turn { background: var(--accent); }
.tree-tl-marker--tool { background: var(--tool); }
.tree-tl-marker--compact { background: var(--tint-purple); }
.tree-tl-marker--fork {
background: transparent;
border-left: 1px dashed var(--err);
width: 0;
border-radius: 0;
}
/* Legend markers reuse the position-independent look. */
.tree-tl-legend .tree-tl-marker {
position: static;
transform: none;
height: 8px;
width: 8px;
border-radius: 2px;
display: inline-block;
border-left: 0;
}
.tree-tl-legend .tree-tl-marker--fork {
background: var(--err);
width: 8px;
height: 8px;
border-left: 0;
}
.tree-preview-actions {
display: flex;
gap: 8px;
align-items: center;
}
/* Compare mode: two mini-timelines side by side. Reuses the same
* .tree-preview-timeline styling on each column so a diff on the CSS
* layer stays cheap. */
.tree-preview-compare {
max-width: 960px;
display: flex;
flex-direction: column;
gap: 16px;
}
.tree-preview-compare-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.tree-preview-compare-body {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.tree-preview-compare-col {
border: 1px solid var(--divider);
border-radius: var(--radius-md);
padding: 12px 14px;
background: var(--surface);
display: flex;
flex-direction: column;
gap: 6px;
}
.tree-preview-compare-col-title {
font-size: 13px;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tree-preview-compare-col-sub { font-size: 11px; }
.tree-preview-compare-timeline {
position: relative;
height: 20px;
background: var(--surface-2);
border-radius: 2px;
overflow: hidden;
margin-top: 4px;
}
.tree-preview-compare-hint {
font-size: 12px;
padding: 0 2px;
}
/* Sidebar note for the tree tab-panel. */
.tree-sidebar-note {
padding: 12px 16px;
font-size: 12px;
line-height: 1.5;
}
/* Session Tree round-3 fixes: noise collapse + guided empty state + untitled
* muted styling. Untitled leaves render in italic muted so a real title
* (regular weight, full-body colour) still catches the eye first. */
.tree-row-title.is-untitled {
color: var(--text-secondary);
font-style: italic;
font-weight: 400;
}
/* "Earlier sessions (N)" collapse — a single row at the bottom of the tree.
* The button spans the row; expanded state reveals a nested list underneath.
* Uses the same grid geometry as .tree-row so the vertical rhythm survives. */
.tree-row.tree-row-earlier {
display: block;
padding: 0;
cursor: default;
border-top: 1px solid var(--divider);
margin-top: 8px;
}
.tree-row.tree-row-earlier:hover { background: transparent; }
.tree-earlier-head {
all: unset;
box-sizing: border-box;
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 12px;
cursor: pointer;
color: var(--text-secondary);
font-size: 12px;
font-weight: 500;
border-radius: 0;
transition: background-color 120ms ease, color 120ms ease;
}
.tree-earlier-head:hover {
background: var(--surface-hover, var(--surface-2));
color: var(--text);
}
.tree-earlier-head:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.tree-earlier-caret {
display: inline-flex;
align-items: center;
color: var(--text-tertiary);
transition: transform 160ms ease;
}
.tree-row-earlier[data-earlier="expanded"] .tree-earlier-caret {
transform: rotate(90deg);
}
.tree-earlier-label { flex: 0 0 auto; }
.tree-earlier-hint {
margin-left: auto;
font-size: 11px;
font-weight: 400;
}
.tree-list-earlier {
list-style: none;
margin: 0;
padding: 0 0 8px 0;
background: var(--surface-2);
}
/* Extra indent for leaves nested under the "Earlier sessions" header so the
* hierarchy reads even though buildSessionTree stored them at depth 0. */
.tree-list-earlier .tree-row {
padding-left: 32px;
}
/* And an "Earlier sessions" leaf never has children, so kill any stray
* .tree-children spacing under it (defensive; the tree also never emits
* these under the earlier group). */
.tree-list-earlier .tree-children { display: none; }
/* Guided empty state — appears when the tree literally has no sessions AND
* when every root collapsed into the "Earlier sessions" group. The demo
* button sits below the copy so new users have a one-click preview.
* §7: paddings/gaps on the {4,8,12,16,24} grid; typography uses the same
* 13/12 ladder as the tree row so no fresh size is introduced. */
.tree-empty {
padding: 24px 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.tree-empty-title {
color: var(--text);
font-size: 13px;
font-weight: 600;
}
.tree-empty-body {
font-size: 12px;
line-height: 1.5;
max-width: 34ch;
}
.tree-empty-actions {
margin-top: 8px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
/* --- Plugins · 「创造」 zone (2026-07-16) ------------------------------- *
* DSH-native block above the Installed/Browse subnav. Two big cards:
* Vibe (agent writes plugin) + Playground (isolated try-before-apply).
* They're the discoverable entry for what makes DSH's plugin page different
* from a static settings pane. Header buttons #plugins-vibe/#plugins-playground
* are hidden and act only as the actual handler bus. */
.plugins-create-zone {
display: flex;
flex-direction: column;
gap: 12px;
margin: 4px 0 20px 0;
}
.create-zone-head {
display: flex;
flex-direction: column;
gap: 2px;
}
.create-zone-title {
margin: 0;
font-size: 15px;
font-weight: 600;
letter-spacing: -0.005em;
color: var(--text);
}
.create-zone-sub {
margin: 0;
font-size: 12.5px;
line-height: 1.5;
}
.create-zone-cards {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
@media (max-width: 720px) {
.create-zone-cards { grid-template-columns: 1fr; }
}
.create-card {
all: unset;
box-sizing: border-box;
display: grid;
grid-template-columns: 44px 1fr auto;
align-items: start;
gap: 14px;
padding: 18px 18px 16px 18px;
background: var(--surface-2, var(--bg-elev));
border: 1px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: background 140ms ease, border-color 140ms ease,
box-shadow 140ms ease, transform 140ms ease;
color: var(--text);
}
.create-card:hover {
background: var(--bg-elev-2, var(--surface-2));
border-color: color-mix(in oklab, var(--accent) 30%, var(--border));
box-shadow: 0 1px 3px rgba(0,0,0,0.03);
}
.create-card:focus-visible {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 20%, transparent);
}
.create-card:active { transform: translateY(1px); }
.create-card[data-disabled] {
cursor: not-allowed;
opacity: 0.55;
}
.create-card[data-disabled]:hover {
background: var(--surface-2, var(--bg-elev));
border-color: var(--border);
box-shadow: none;
}
.create-card-glyph {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: 10px;
background: color-mix(in oklab, var(--accent) 8%, var(--bg));
color: var(--accent);
}
.create-card-body {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
/* At narrow widths (≤ 800px) the 44px + 1fr + auto grid squeezes the
* body column below its content min because the CTA chip's `auto`
* track doesn't compress. Nowrap-ellipsis the single-line title and
* sub; let the hint wrap normally within the (now truly flexible) 1fr. */
.create-card-title {
font-size: 15px;
font-weight: 600;
line-height: 1.25;
color: var(--text);
letter-spacing: -0.005em;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.create-card-sub {
font-size: 12.5px;
font-weight: 500;
color: var(--accent);
line-height: 1.35;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.create-card-hint {
font-size: 12px;
line-height: 1.55;
margin-top: 2px;
max-width: 42ch;
min-width: 0;
overflow-wrap: anywhere;
}
.create-card-cta {
align-self: center;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 10px 5px 12px;
border-radius: 999px;
background: var(--bg);
border: 1px solid var(--border);
font-size: 12px;
font-weight: 500;
color: var(--text-secondary, var(--muted));
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.create-card:hover .create-card-cta {
color: var(--accent);
border-color: color-mix(in oklab, var(--accent) 45%, var(--border));
}
/* Hide the CTA chip only for hard-disabled states (runtime absent, unavailable).
* "gate" is not a disable — the card is actionable via a profile switch, so
* the CTA chip stays visible and reads "Switch profile" (NEW-6.1 fix,
* 2026-07-16). Without this split the Vibe card in a non-vibe profile
* looked text-only next to Playground's `Open` chip. */
.create-card[data-disabled="runtime"] .create-card-cta,
.create-card[data-disabled="unavailable"] .create-card-cta { display: none; }
.create-card[data-disabled="gate"] { cursor: pointer; opacity: 1; }
.create-card[data-disabled="gate"] .create-card-cta {
background: color-mix(in oklab, var(--accent) 8%, var(--bg));
border-color: color-mix(in oklab, var(--accent) 40%, var(--border));
color: var(--accent);
}
.create-card[data-disabled="gate"]:hover {
background: var(--bg-elev-2, var(--surface-2));
border-color: color-mix(in oklab, var(--accent) 30%, var(--border));
}
/* --- Mission Control · empty-state suggestion bar (2026-07-16) ---------- *
* Inline banner between the top summary/ticker row and the subview tabs.
* Shows only when state.sessions.size === 0; toggled by mission-controller
* renderAll(). Doc §5 moves the "用示例数据预览" mock entry off the header
* Debug popover onto this bar so first-time users see the point of the page
* without hunting for a debug button. */
.mission-suggest-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 14px 16px;
margin: 4px 0 12px 0;
background: color-mix(in oklab, var(--accent) 5%, var(--bg));
border: 1px solid color-mix(in oklab, var(--accent) 22%, var(--border));
border-radius: 10px;
}
.mission-suggest-bar[hidden] { display: none; }
.mission-suggest-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.mission-suggest-title {
font-size: 13px;
font-weight: 600;
color: var(--text);
}
.mission-suggest-hint {
font-size: 12px;
line-height: 1.55;
color: var(--text-secondary, var(--muted));
max-width: 62ch;
}
.mission-suggest-btn {
flex: none;
display: inline-flex;
align-items: center;
gap: 6px;
}
/* --- Growth page (2026-07-16) ------------------------------------------ *
* Auditable-facts companion log. Identity strip → activity heatmap card →
* Growth Log (diary + timeline). Every one of these tokens follows the app
* minimalist palette; the heatmap uses a validated 5-stop sequential ramp
* built from --accent (dataviz-skill sequential palette) so a theme swap
* re-skins the whole map. */
.growth-body {
display: flex;
flex-direction: column;
gap: 20px;
padding: 4px 0;
}
/* Identity strip */
.growth-identity {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 20px;
padding: 20px 22px;
background: var(--surface-2, var(--bg-elev));
border: 1px solid var(--border);
border-radius: 12px;
}
.growth-identity-logo {
display: flex;
align-items: center;
justify-content: center;
width: 52px;
height: 52px;
border-radius: 12px;
background: color-mix(in oklab, var(--accent) 10%, var(--bg));
color: var(--accent);
}
.growth-identity-name-primary {
font-size: 20px;
font-weight: 700;
letter-spacing: -0.01em;
color: var(--text);
}
.growth-identity-name-secondary {
font-size: 12.5px;
margin-top: 2px;
}
.growth-identity-stats {
display: flex;
gap: 32px;
align-items: baseline;
}
@media (max-width: 720px) {
.growth-identity { grid-template-columns: auto 1fr; }
.growth-identity-stats { grid-column: 1 / -1; margin-top: 8px; gap: 24px; }
}
.growth-stat-tile { text-align: right; min-width: 56px; }
.growth-stat-value {
font-size: 22px;
font-weight: 700;
color: var(--text);
font-variant-numeric: tabular-nums;
letter-spacing: -0.01em;
}
.growth-stat-tile.active .growth-stat-value { color: var(--accent); }
.growth-stat-label { font-size: 11.5px; margin-top: 2px; }
/* Cards */
.growth-card {
display: flex;
flex-direction: column;
gap: 12px;
padding: 18px 20px;
background: var(--surface-2, var(--bg-elev));
border: 1px solid var(--border);
border-radius: 12px;
}
.growth-card-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.growth-card-title {
margin: 0;
font-size: 14px;
font-weight: 600;
color: var(--text);
letter-spacing: -0.005em;
}
/* Heatmap — sequential palette from --accent (dataviz-skill sequential rule).
* Cells are GitHub-contribution sized (11px + 2px gap). The heatmap block
* hugs its content and lives inside a horizontally-scrollable card body so
* the whole card doesn't stretch cells to fill the viewport. */
.growth-heatmap {
position: relative;
width: max-content;
max-width: 100%;
overflow-x: auto;
}
.growth-heatmap-months {
display: grid;
grid-template-columns: repeat(52, 11px);
column-gap: 2px;
font-size: 10.5px;
color: var(--text-tertiary, var(--muted));
margin-bottom: 6px;
width: max-content;
}
.growth-heatmap-month { height: 12px; white-space: nowrap; overflow: visible; }
.growth-heatmap-grid {
display: flex;
flex-direction: column;
gap: 2px;
width: max-content;
}
.growth-heatmap-row {
display: grid;
grid-template-columns: repeat(52, 11px);
gap: 2px;
}
.growth-heatmap-cell {
width: 11px;
height: 11px;
border-radius: 2px;
background: var(--growth-hm-empty);
transition: outline-color 100ms ease;
outline: 1px solid transparent;
}
/* Empty-state overlay lives INSIDE the heatmap block, centered above the
* grid — never in the card head. */
.growth-heatmap-empty-overlay {
position: absolute;
inset: 18px 0 0 0;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
font-size: 12px;
color: var(--text-secondary, var(--muted));
padding: 0 12px;
text-align: center;
}
.growth-heatmap-cell:hover {
outline-color: color-mix(in oklab, var(--accent) 55%, transparent);
}
.growth-heatmap-cell[data-level="empty"] { background: var(--growth-hm-empty); }
.growth-heatmap-cell[data-level="low"] { background: var(--growth-hm-low); }
.growth-heatmap-cell[data-level="med"] { background: var(--growth-hm-med); }
.growth-heatmap-cell[data-level="high"] { background: var(--growth-hm-high); }
.growth-heatmap-cell[data-level="peak"] { background: var(--growth-hm-peak); }
:root {
--growth-hm-empty: color-mix(in oklab, var(--border) 55%, var(--bg));
--growth-hm-low: color-mix(in oklab, var(--accent) 20%, var(--bg));
--growth-hm-med: color-mix(in oklab, var(--accent) 45%, var(--bg));
--growth-hm-high: color-mix(in oklab, var(--accent) 70%, var(--bg));
--growth-hm-peak: var(--accent);
}
.growth-heatmap-legend {
display: flex;
align-items: center;
gap: 4px;
justify-content: flex-end;
margin-top: 6px;
}
.growth-heatmap-legend-cell {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 2px;
}
.growth-heatmap-legend-cell[data-level="empty"] { background: var(--growth-hm-empty); }
.growth-heatmap-legend-cell[data-level="low"] { background: var(--growth-hm-low); }
.growth-heatmap-legend-cell[data-level="med"] { background: var(--growth-hm-med); }
.growth-heatmap-legend-cell[data-level="high"] { background: var(--growth-hm-high); }
.growth-heatmap-legend-cell[data-level="peak"] { background: var(--growth-hm-peak); }
/* Growth Log: timeline-dominant. Diary card sits on the right, sticky. */
.growth-log {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
gap: 16px;
align-items: start;
}
@media (max-width: 860px) {
.growth-log { grid-template-columns: 1fr; }
}
.growth-diary {
position: sticky;
top: 8px;
align-self: start;
padding: 18px 18px 16px 18px;
background: var(--surface-2, var(--bg-elev));
border: 1px solid var(--border);
border-radius: 12px;
min-height: 120px;
order: 2;
}
.growth-timeline { order: 1; }
.growth-diary-empty { font-size: 12.5px; line-height: 1.6; }
.growth-diary-card { display: flex; flex-direction: column; gap: 8px; }
.growth-diary-when { letter-spacing: 0.02em; text-transform: uppercase; font-size: 10.5px; }
.growth-diary-title {
margin: 0;
font-size: 15px;
font-weight: 600;
color: var(--text);
letter-spacing: -0.005em;
}
.growth-diary-detail {
margin: 0;
font-size: 13px;
line-height: 1.6;
color: var(--text);
}
.growth-diary-evidence {
font-size: 12px;
line-height: 1.5;
padding-top: 4px;
border-top: 1px solid var(--divider);
margin-top: 4px;
}
.growth-diary-link {
all: unset;
cursor: pointer;
color: var(--accent);
border-bottom: 1px dashed color-mix(in oklab, var(--accent) 40%, transparent);
font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
font-size: 11.5px;
}
.growth-diary-link:hover { border-bottom-color: var(--accent); }
.growth-diary-ask { align-self: flex-start; margin-top: 6px; }
.growth-timeline {
display: flex;
flex-direction: column;
gap: 4px;
position: relative;
}
.growth-timeline-empty {
padding: 24px 12px;
text-align: left;
font-size: 12.5px;
line-height: 1.6;
}
.growth-timeline-node {
all: unset;
box-sizing: border-box;
display: grid;
grid-template-columns: 32px 1fr;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 8px;
cursor: pointer;
transition: background 120ms ease;
position: relative;
}
.growth-timeline-node::before {
content: '';
position: absolute;
left: 27px;
top: 0;
bottom: 0;
width: 1px;
background: var(--divider);
}
.growth-timeline-node:first-child::before { top: 50%; }
.growth-timeline-node:last-child::before { bottom: 50%; }
.growth-timeline-node:hover { background: var(--surface-2, var(--bg-elev)); }
.growth-timeline-node.is-selected {
background: color-mix(in oklab, var(--accent) 8%, var(--bg));
}
.growth-timeline-node.is-selected .growth-timeline-title {
color: var(--accent);
}
.growth-timeline-glyph {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 999px;
background: var(--surface-2, var(--bg-elev));
border: 1px solid var(--border);
color: var(--text-secondary, var(--muted));
position: relative;
z-index: 1;
}
.growth-timeline-node.is-selected .growth-timeline-glyph {
color: var(--accent);
border-color: color-mix(in oklab, var(--accent) 40%, var(--border));
background: color-mix(in oklab, var(--accent) 12%, var(--bg));
}
.growth-timeline-body {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
.growth-timeline-title {
font-size: 13px;
font-weight: 500;
color: var(--text);
letter-spacing: -0.002em;
}
.growth-timeline-meta { font-size: 11.5px; }
/* Empty state */
.growth-empty {
padding: 20px 20px 24px;
background: var(--surface-2, var(--bg-elev));
border: 1px dashed var(--border);
border-radius: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.growth-empty-title { font-size: 13px; font-weight: 600; color: var(--text); }
.growth-empty-body { font-size: 12px; line-height: 1.55; max-width: 62ch; }
.growth-empty-actions { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; }
/* Mode chip toggle in the header */
.growth-mode-chip.active {
color: var(--text);
background: color-mix(in oklab, var(--accent) 8%, var(--bg));
border-color: color-mix(in oklab, var(--accent) 30%, var(--border));
}
/* Topology empty state: two-line explainer replaces the old lane-per-root
* wall of overlapping labels. */
.mission-empty { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 48px 24px; text-align: center; }
.mission-empty-title { font-size: 15px; font-weight: 600; color: var(--text); }
.mission-empty-sub { font-size: 13px; color: var(--text-secondary, var(--muted)); max-width: 420px; }
/* Topology visual layer (Augur-style family coloring + legend). Inline
* fill/stroke on nodes and edges are set from JS — this block only handles
* layout, motion, hover affordances, and the legend row underneath. */
.mission-topo-legend {
display: flex;
flex-wrap: nowrap;
gap: 6px 12px;
align-items: center;
font-size: 11.5px;
color: var(--text-secondary, var(--muted));
}
/* --- Legend overlay: rendered inside the SVG as a <foreignObject> so it
* always sits within the visible plot area (NEW-4.b, 2026-07-16).
* Was an HTML sibling below the SVG — got pushed below the fold at
* 900 px viewports. */
.mission-topo-legend-fo { overflow: visible; pointer-events: auto; }
.mission-topo-legend-overlay {
height: 100%;
padding: 6px 10px;
background: color-mix(in oklab, var(--bg) 92%, transparent);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
white-space: nowrap;
}
.mission-topo-legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
cursor: pointer;
max-width: 160px;
padding: 2px 6px;
border-radius: 6px;
transition: background 100ms ease;
flex: none;
}
.mission-topo-legend-item:hover {
background: color-mix(in oklab, var(--accent) 6%, transparent);
}
.mission-topo-legend-swatch {
width: 10px;
height: 10px;
border-radius: 3px;
flex: none;
}
.mission-topo-legend-label {
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 110px;
}
.mission-topo-legend-count { font-variant-numeric: tabular-nums; }
/* --- Chat runtime-error banner (A-P0-2 fix, 2026-07-16) -----------------
* When the daemon reports a runtime error (e.g. capability mismatch), the
* previous shell rendered the raw string inline in the message stream —
* which meant the first-run empty state read as "this software is broken".
* The banner sits above the stream, keeps the empty-welcome cards intact,
* shows a plain-language classification + hint, and folds the raw message
* behind Details so operators can still copy it. */
.chat-runtime-banner {
margin: 8px 12px 0;
padding: 10px 12px;
border: 1px solid color-mix(in oklab, var(--warn, #d97706) 45%, var(--border));
background: color-mix(in oklab, var(--warn, #d97706) 6%, var(--bg));
border-radius: 8px;
font-size: 13px;
color: var(--text);
}
.chat-runtime-banner-head {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
}
.chat-runtime-banner-icon {
font-size: 14px;
line-height: 1;
}
.chat-runtime-banner-title { flex: 1; }
.chat-runtime-banner-dismiss {
min-width: 22px;
padding: 0 6px;
font-size: 15px;
line-height: 1;
}
.chat-runtime-banner-hint {
margin-top: 6px;
color: var(--muted);
font-size: 12.5px;
line-height: 1.5;
}
/* Missing-key switch row (2026-07-18): renders the "Switch to keyless demo"
* button under the hint so the user has a one-click recovery path instead
* of a red-wall dead-end. Kept compact with a modest top margin — the row
* lives INSIDE the hint block so line-height inherits, and the button is
* the existing .ghost.small chip idiom (spec §7 chip-tier waiver). */
.chat-runtime-banner-switch {
margin-top: 8px;
display: inline-flex;
gap: 8px;
}
.chat-runtime-banner-details {
margin-top: 8px;
font-size: 12px;
}
.chat-runtime-banner-details summary {
cursor: pointer;
color: var(--muted);
user-select: none;
}
.chat-runtime-banner-details pre {
margin: 6px 0 0;
padding: 6px 8px;
background: color-mix(in oklab, var(--muted) 8%, var(--bg));
border-radius: 6px;
font-size: 11.5px;
white-space: pre-wrap;
word-break: break-word;
color: var(--muted);
}
/* A-P1-3: shared in-app confirmation dialog. Native <dialog> so the browser
* handles ESC / focus trap / backdrop click, and the ::backdrop pseudo lets
* us match the quickchat-scrim wash without extra JS. Sits on top so any
* destructive-action confirm gets a first-class look instead of the native
* confirm() box that clashes with the shell. */
.app-dialog {
border: 1px solid var(--border);
border-radius: var(--radius-lg, 12px);
background: var(--surface);
box-shadow: var(--shadow-pop, 0 20px 48px rgba(0, 0, 0, 0.24));
color: var(--text);
padding: 0;
max-width: min(440px, 92vw);
}
.app-dialog::backdrop {
background: rgba(0, 0, 0, 0.18);
backdrop-filter: saturate(160%) blur(10px);
-webkit-backdrop-filter: saturate(160%) blur(10px);
}
:root[data-theme="dark"] .app-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) .app-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }
}
.app-dialog-form {
padding: 18px 20px 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.app-dialog-title {
margin: 0;
font-size: 15px;
font-weight: 600;
color: var(--text);
}
.app-dialog-body {
margin: 0;
font-size: 13px;
line-height: 1.5;
}
.app-dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 6px;
}
/* --- §2 interaction cards (batch 6) ---------------------------------------
* Blocking cards (approval / form / exit_plan_mode) share the same shell:
* thick border + a top status strip whose colour signals the state.
* .interact-card-waiting → yellow (等你)
* .interact-card-confirmed → green (已确认)
* .interact-card-skipped → grey (已跳过)
* Non-blocking steer cards (.card.steer) wear a lighter border and no strip.
*/
.card.interact-card {
border-width: 2px;
padding-top: 0;
overflow: hidden;
position: relative;
}
.interact-card-status {
display: block;
margin: 0 -14px 10px -14px;
padding: 6px 14px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
border-bottom: 1px solid var(--border);
}
.interact-card-waiting {
border-color: var(--warn, #e0b04a);
}
.interact-card-waiting > .interact-card-status {
background: color-mix(in oklab, var(--warn, #e0b04a) 18%, transparent);
color: var(--warn, #e0b04a);
border-bottom-color: color-mix(in oklab, var(--warn, #e0b04a) 40%, transparent);
}
.interact-card-confirmed {
border-color: var(--ok);
}
.interact-card-confirmed > .interact-card-status {
background: color-mix(in oklab, var(--ok) 18%, transparent);
color: var(--ok);
border-bottom-color: color-mix(in oklab, var(--ok) 40%, transparent);
}
.interact-card-skipped {
border-color: var(--muted);
opacity: 0.85;
}
.interact-card-skipped > .interact-card-status {
background: color-mix(in oklab, var(--muted) 18%, transparent);
color: var(--muted);
border-bottom-color: color-mix(in oklab, var(--muted) 40%, transparent);
}
/* exit_plan_mode two-column layout: card + right-rail preview. On narrow
* viewports the rail falls under the card (stack). */
.exit-plan-mode-wrap {
display: grid;
grid-template-columns: minmax(0, 1fr) 260px;
gap: 10px;
align-items: start;
max-width: 1060px;
}
@media (max-width: 900px) {
.exit-plan-mode-wrap { grid-template-columns: 1fr; }
}
.card.exit-plan-mode { max-width: none; }
.exit-plan-mode-section-label {
font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em;
margin: 10px 0 4px 0; font-weight: 600;
}
.exit-plan-mode-plan,
.exit-plan-mode-comment {
width: 100%;
background: var(--bg); color: var(--text);
border: 1px solid var(--border); border-radius: 6px;
padding: 8px 10px;
font: inherit; font-family: var(--mono); font-size: 12.5px;
resize: vertical;
box-sizing: border-box;
}
.exit-plan-mode-plan:focus,
.exit-plan-mode-comment:focus { outline: none; border-color: var(--accent); }
.plan-diff-rail {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 12px;
font-size: 12px;
}
.plan-diff-rail-label {
font-size: 10.5px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.06em;
font-weight: 600; margin-bottom: 6px;
}
.plan-diff-rail-body {
font-family: var(--mono);
font-size: 11.5px;
display: flex; flex-direction: column; gap: 3px;
}
.plan-diff-line { display: flex; gap: 6px; align-items: baseline; }
.plan-diff-line .plan-diff-sigil {
width: 12px; text-align: center;
color: var(--muted); font-weight: 700;
flex-shrink: 0;
}
.plan-diff-line-add .plan-diff-sigil { color: var(--ok); }
.plan-diff-line-add {
background: color-mix(in oklab, var(--ok) 8%, transparent);
border-radius: 3px;
padding: 1px 4px;
}
.plan-diff-line .plan-diff-text { color: var(--text); word-break: break-word; }
.plan-diff-rail-empty { color: var(--muted); font-style: italic; font-size: 11.5px; }
/* --- §2.2 steer card (non-blocking) --------------------------------------- */
.card.steer {
border: 1px dashed color-mix(in oklab, var(--accent) 45%, transparent);
background: var(--bg-elev);
position: relative;
padding-top: 10px;
}
.card.steer .steer-title {
font-size: 13px;
margin: 0 0 4px 0;
padding-right: 22px; /* room for × */
}
.card.steer .steer-body {
color: var(--muted);
font-size: 12.5px;
margin-bottom: 6px;
}
.card.steer .steer-dismiss {
position: absolute; top: 6px; right: 8px;
width: 22px; height: 22px;
padding: 0; line-height: 20px;
border: none;
background: transparent;
color: var(--muted);
font-size: 18px;
cursor: pointer;
border-radius: 4px;
}
.card.steer .steer-dismiss:hover { color: var(--text); background: var(--bg-elev-2); }
.card.steer .steer-suggestion { font-size: 12px; }
/* steer chip in the chat stream (dropped when the user accepts a suggestion).
* Pill visual is kept (small badge feel), but per the centered-card ban the
* chip anchors to the stream's left edge — not the middle — so it reads as
* a stream row, not a floating decoration. */
.steer-chip {
color: var(--accent);
font-style: normal;
font-size: 12px;
display: inline-flex; align-items: center; gap: 6px;
padding: 3px 10px;
border: 1px solid color-mix(in oklab, var(--accent) 40%, transparent);
border-radius: 999px;
background: color-mix(in oklab, var(--accent) 10%, transparent);
align-self: flex-start;
width: fit-content;
margin: 6px 0;
}
.steer-chip-label { letter-spacing: 0.02em; }
.steer-chip-label::before {
content: "steer";
display: none; /* label already carries the "steer:" prefix; kept for
theme override hooks — do NOT re-introduce an emoji. */
}
/* --- §2.3 template trigger cards ------------------------------------------
* Trigger cards sit inline in the chat stream, styled as a badge-tagged
* widget container. Colour hint follows the trigger family without stealing
* emphasis from real content: soft border, muted badge on the top-left.
*/
.card.trigger-card {
border: 1px solid color-mix(in oklab, var(--accent) 25%, transparent);
background: var(--bg-elev);
padding: 10px 12px;
}
.card.trigger-card .trigger-badge {
display: inline-block;
font-size: 10.5px; font-weight: 700;
letter-spacing: 0.08em; text-transform: uppercase;
color: var(--accent);
margin-bottom: 6px;
}
.card.trigger-t2-error-recovery {
border-color: color-mix(in oklab, var(--error) 40%, transparent);
}
.card.trigger-t2-error-recovery .trigger-badge { color: var(--error); }
.card.trigger-t4-artifact-preview {
border-color: color-mix(in oklab, var(--tool) 40%, transparent);
}
.card.trigger-t4-artifact-preview .trigger-badge { color: var(--tool); }
.card.trigger-t5-context-warning {
border-color: color-mix(in oklab, var(--warn, #e0b04a) 55%, transparent);
}
.card.trigger-t5-context-warning .trigger-badge { color: var(--warn, #e0b04a); }
/* -----------------------------------------------------------------------------
* Growth v2 (Ticket #140)
* Compact-window history + rubric/error write-back. Kept visually kin with
* the surrounding cards (`.pane header .header`, `.growth-card`) so the tab
* doesn't feel like a bolt-on. The layout is a single ordered list of rows;
* each row folds via a chevron button (novice mode leaves them all folded,
* researcher mode leaves them all open).
* ---------------------------------------------------------------------------*/
.growth-v2-body {
display: flex;
flex-direction: column;
gap: 12px;
padding: 12px 16px 20px;
min-height: 0;
overflow: auto;
}
.growth-v2-seed-note {
padding: 6px 10px;
border: 1px dashed var(--border);
border-radius: 6px;
background: color-mix(in oklab, var(--bg) 92%, var(--muted) 8%);
}
.growth-v2-empty {
padding: 40px 12px;
text-align: center;
color: var(--muted);
border: 1px dashed var(--border);
border-radius: 8px;
}
.growth-v2-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.growth-v2-row {
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.growth-v2-row.expanded {
border-color: color-mix(in oklab, var(--accent) 45%, var(--border));
box-shadow: 0 1px 6px color-mix(in oklab, var(--accent) 12%, transparent);
}
.growth-v2-head {
display: grid;
grid-template-columns: 88px 1fr auto auto auto;
gap: 14px;
align-items: center;
}
.growth-v2-comp-tile {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 6px 4px;
border-radius: 8px;
background: color-mix(in oklab, var(--accent) 6%, var(--bg));
border: 1px solid color-mix(in oklab, var(--accent) 25%, var(--border));
min-height: 60px;
}
.growth-v2-comp-tile.empty {
background: transparent;
border-color: var(--border);
opacity: 0.35;
}
.growth-v2-comp-pct {
font-family: var(--mono);
font-size: 20px;
font-weight: 700;
color: color-mix(in oklab, var(--accent) 70%, var(--text));
letter-spacing: -0.5px;
line-height: 1.05;
}
.growth-v2-comp-label {
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 1px;
}
.growth-v2-comp-sub {
font-family: var(--mono);
font-size: 10.5px;
margin-top: 2px;
}
.growth-v2-meta { min-width: 0; }
.growth-v2-meta-top {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.growth-v2-time {
font-family: var(--mono);
font-size: 12px;
color: var(--text);
white-space: nowrap;
}
.growth-v2-trigger {
font-size: 11.5px;
padding: 2px 6px;
border-radius: 999px;
border: 1px solid var(--border);
color: var(--muted);
white-space: nowrap;
}
.growth-v2-range { white-space: nowrap; }
.growth-v2-passrate-chip {
display: flex;
flex-direction: column;
align-items: center;
min-width: 56px;
padding: 4px 8px;
border-radius: 6px;
border: 1px solid color-mix(in oklab, var(--ok) 25%, var(--border));
background: color-mix(in oklab, var(--ok) 8%, var(--bg));
}
.growth-v2-passrate-chip.empty { visibility: hidden; }
.growth-v2-passrate-num {
font-family: var(--mono);
font-size: 16px;
font-weight: 700;
color: color-mix(in oklab, var(--ok) 65%, var(--text));
line-height: 1.05;
}
.growth-v2-passrate-label {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.growth-v2-arc {
border: 1px solid var(--border);
border-radius: 8px;
padding: 8px 12px 6px;
background: color-mix(in oklab, var(--accent) 3%, var(--bg));
margin-bottom: 4px;
}
.growth-v2-arc-svg {
display: block;
width: 100%;
height: 72px;
overflow: visible;
}
.growth-v2-arc-line {
fill: none;
stroke: color-mix(in oklab, var(--ok) 60%, var(--text));
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.growth-v2-arc-dot {
fill: color-mix(in oklab, var(--ok) 60%, var(--bg));
stroke: color-mix(in oklab, var(--ok) 65%, var(--text));
stroke-width: 1.5;
}
.growth-v2-arc-dot.seed {
fill: var(--bg);
stroke: var(--muted);
}
.growth-v2-arc-ref-baseline,
.growth-v2-arc-ref-ceiling {
stroke: var(--border);
stroke-width: 1;
stroke-dasharray: 3 3;
}
.growth-v2-arc-label {
font-family: var(--mono);
font-size: 11px;
fill: var(--text);
}
.growth-v2-arc-label.seed { fill: var(--muted); }
.growth-v2-arc-caption {
margin-top: 4px;
text-align: right;
}
.growth-v2-badge {
font-family: var(--mono);
font-size: 11.5px;
padding: 2px 6px;
border-radius: 999px;
border: 1px solid var(--border);
color: var(--text);
background: color-mix(in oklab, var(--accent) 8%, var(--bg));
white-space: nowrap;
}
.growth-v2-badge.muted { color: var(--muted); background: transparent; }
.growth-v2-chevron {
min-width: 24px;
height: 24px;
padding: 0;
border-radius: 4px;
font-size: 14px;
}
.growth-v2-summary {
color: var(--text);
font-size: 13px;
line-height: 1.5;
max-height: 3.6em;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.growth-v2-row.expanded .growth-v2-summary {
max-height: unset;
-webkit-line-clamp: unset;
display: block;
overflow: visible;
}
.growth-v2-expanded {
display: flex;
flex-direction: column;
gap: 10px;
padding-top: 6px;
border-top: 1px dashed var(--border);
}
.growth-v2-eval {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: baseline;
padding: 6px 8px;
border-radius: 6px;
background: color-mix(in oklab, var(--ok) 8%, var(--bg));
border: 1px solid color-mix(in oklab, var(--ok) 25%, var(--border));
}
.growth-v2-eval-name {
font-family: var(--mono);
font-size: 12px;
color: var(--text);
}
.growth-v2-eval-arc {
font-family: var(--mono);
font-size: 14px;
font-weight: 600;
color: color-mix(in oklab, var(--ok) 65%, var(--text));
}
.growth-v2-collection {
border: 1px solid var(--border);
border-radius: 6px;
padding: 8px 10px;
display: flex;
flex-direction: column;
gap: 6px;
background: var(--bg);
}
.growth-v2-collection-head {
display: flex;
align-items: center;
justify-content: space-between;
}
.growth-v2-collection-title {
margin: 0;
font-size: 12.5px;
font-weight: 600;
color: var(--text);
}
.growth-v2-add {
font-family: var(--mono);
font-size: 11.5px;
}
.growth-v2-collection-error .growth-v2-add {
color: color-mix(in oklab, red 60%, var(--text));
}
.growth-v2-collection-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.growth-v2-collection-item {
padding: 6px 8px;
border-radius: 4px;
background: color-mix(in oklab, var(--bg) 96%, var(--muted) 4%);
}
.growth-v2-item-primary { font-size: 12.5px; color: var(--text); }
.growth-v2-item-secondary { margin-top: 2px; }
.growth-v2-item-tag {
display: inline-block;
margin-top: 4px;
padding: 1px 6px;
border-radius: 999px;
border: 1px solid var(--border);
font-family: var(--mono);
font-size: 11px;
}
.growth-v2-devrow {
display: flex;
flex-wrap: wrap;
gap: 10px;
padding: 6px 8px;
border-radius: 4px;
background: color-mix(in oklab, var(--bg) 93%, var(--muted) 7%);
font-family: var(--mono);
font-size: 11.5px;
}
.growth-v2-dev-span::after { content: '·'; margin-left: 10px; color: var(--muted); }
.growth-v2-dev-span:last-child::after { content: ''; margin-left: 0; }
.growth-v2-mode-chip { min-width: 72px; }
.growth-v2-mode-chip.active {
background: color-mix(in oklab, var(--accent) 12%, var(--bg));
border-color: color-mix(in oklab, var(--accent) 45%, var(--border));
color: var(--text);
}
.growth-v2-form-host {
border: 1px solid color-mix(in oklab, var(--accent) 40%, var(--border));
border-radius: 8px;
padding: 12px;
background: color-mix(in oklab, var(--accent) 6%, var(--bg));
}
.growth-v2-form {
display: flex;
flex-direction: column;
gap: 10px;
}
.growth-v2-form-title {
margin: 0;
font-size: 13px;
font-weight: 600;
}
.growth-v2-form-row {
display: flex;
flex-direction: column;
gap: 4px;
}
.growth-v2-form-row textarea,
.growth-v2-form-row input[type='text'] {
padding: 6px 8px;
border-radius: 4px;
border: 1px solid var(--border);
background: var(--bg);
color: var(--text);
font-family: inherit;
font-size: 12.5px;
}
.growth-v2-form-row textarea { min-height: 60px; resize: vertical; }
.growth-v2-form-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.growth-v2-form-error {
color: color-mix(in oklab, red 60%, var(--text));
}
/* ============================================================================
* Compact card tabs + Context Rail drawer — task #137 (demo 批 2)
* §1.7 three tabs (压前原文 / 压后摘要 / 策略与账) + §1.2 vertical timeline
/* ============================================================================
* Compact card tabs + Context Rail drawer — task #137 (demo 批 2)
* §1.7 three tabs (Before / Summary / Policy & accounting) + §1.2 vertical timeline
* drawer, both driven from window.__dshCompactCard / window.__dshContextRail.
* ==========================================================================*/
.compact-card-tabs {
margin: 6px 12% 0;
text-align: left;
letter-spacing: normal;
color: var(--text);
}
.compact-card-tabstrip {
display: flex; gap: 4px;
border-bottom: 1px solid var(--border);
margin-bottom: 6px;
}
.compact-card-tab {
background: transparent; border: none;
padding: 6px 10px; cursor: pointer;
font: inherit; font-size: 12px;
color: var(--muted);
border-bottom: 2px solid transparent;
margin-bottom: -1px;
}
.compact-card-tab:hover { color: var(--text); }
.compact-card-tab[aria-selected="true"] {
color: var(--text);
border-bottom-color: var(--accent);
}
.compact-card-tabpanel { padding: 8px 2px; font-size: 12px; }
.compact-card-tabpanel[hidden] { display: none; }
.compact-card-tab-summary {
padding: 8px 10px;
background: var(--bg); border: 1px solid var(--border);
border-radius: 6px;
font-family: var(--mono);
white-space: pre-wrap; word-wrap: break-word;
max-height: 320px; overflow: auto;
color: var(--text);
}
.compact-card-tab-empty {
padding: 8px 10px; color: var(--muted);
font-style: italic;
border: 1px dashed var(--border); border-radius: 6px;
background: color-mix(in oklab, var(--muted) 4%, transparent);
}
.compact-card-tab-meta {
display: grid;
grid-template-columns: max-content 1fr;
gap: 4px 12px; margin: 0;
}
.compact-card-tab-meta dt { color: var(--muted); font-size: 11px; padding-top: 2px; }
.compact-card-tab-meta dd {
margin: 0; font-family: var(--mono); font-size: 12px;
color: var(--text); word-break: break-word;
}
.compact-card .compact-card-tabs { text-align: left; letter-spacing: normal; }
/* -- Diff tab (rec 32; §8.3 ruling replaces Before-only view) --------------
Two-column layout: shadowed events (left) vs summary text (right), with
a compression-ratio header row above. Left column dispatches on data
shape via buildDiffModel: preview → row list, wire seqs → shadowed
expander delegated inline, range/empty → note. Right column shows the
summary text with matching typography to the Summary tab so a reader
can quickly A/B by eye. */
.compact-diff-header {
font-family: var(--mono);
font-size: 11px;
color: var(--muted);
padding: 4px 8px 8px;
border-bottom: 1px dashed var(--border);
margin-bottom: 8px;
}
.compact-diff-cols {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.compact-diff-col {
min-width: 0; /* let flex-basis 0 children shrink; matches #164 lesson */
border: 1px solid var(--border);
border-radius: 6px;
background: var(--bg);
display: flex; flex-direction: column;
overflow: hidden;
}
.compact-diff-coltitle {
padding: 4px 8px;
font-size: 11px;
color: var(--muted);
background: color-mix(in oklab, var(--muted) 4%, transparent);
border-bottom: 1px solid var(--border);
letter-spacing: 0.02em;
}
.compact-diff-colbody {
padding: 6px 8px;
/* D21 (drift cycle 13/14): removed max-height/overflow — the outer
* `.compact-card-tab-summary` container already scrolls at 320px,
* and stacking a second overflow:auto here produced a double
* scrollbar that clipped the right edge of every row. Let the col
* body flow at natural height; the ancestor supplies the scroll. */
font-family: var(--mono);
font-size: 12px;
color: var(--text);
}
.compact-diff-row {
display: grid;
/* D23 (drift cycle 13/14): the seq column was fixed 48px but when the
* card width falls below ~440px the 1fr gist column collapsed to 0px
* and the row went "seq · type · " with empty text. `minmax` on the
* gist column reserves a minimum readable width so the row wraps to
* a second line instead of ellipsing to nothing. */
grid-template-columns: 48px max-content minmax(120px, 1fr);
gap: 6px;
padding: 2px 0;
align-items: baseline;
border-bottom: 1px dashed color-mix(in oklab, var(--border) 60%, transparent);
}
.compact-diff-row:last-child { border-bottom: 0; }
/* D22 (drift cycle 13/14): shadowed events with no body → empty gist
* column reads as a ghost row. Guard by dimming the row and prefixing
* the empty gist with a placeholder so the reader knows the row is
* intentional (a seq without a preview payload) rather than a render
* bug. Buildup lives in JS: renderer sets `data-empty="1"` on such
* rows; this rule styles the marker. */
.compact-diff-row[data-empty="1"] { opacity: 0.65; }
.compact-diff-row[data-empty="1"] .compact-diff-gist::before {
content: '(no preview)';
color: var(--muted); font-style: italic; font-size: 11px;
}
.compact-diff-seq {
color: var(--muted); font-size: 11px;
}
.compact-diff-type {
color: color-mix(in oklab, var(--accent) 70%, var(--text));
font-size: 11px;
}
.compact-diff-gist {
color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.compact-diff-summary {
white-space: pre-wrap; word-wrap: break-word;
color: var(--text);
}
/* Context Rail drawer (§1.2) */
.context-rail-drawer {
position: absolute; top: 0; right: 0; bottom: 0;
width: 320px;
background: var(--bg-elev);
border-left: 1px solid var(--border);
z-index: 5;
display: flex; flex-direction: column;
box-shadow: -6px 0 16px -12px rgba(0, 0, 0, 0.35);
}
.context-rail-drawer[hidden] { display: none; }
.pane[data-pane="chat"] { position: relative; }
.context-rail-drawer-head {
display: flex; align-items: baseline; gap: 8px;
padding: 10px 12px; border-bottom: 1px solid var(--border);
}
.context-rail-drawer-title { font-weight: 600; font-size: 13px; color: var(--text); }
.context-rail-drawer-sub { flex: 1; font-size: 11px; color: var(--muted); }
.context-rail-drawer-close { padding: 2px 8px; font-size: 14px; line-height: 1; }
.context-rail-drawer-body { flex: 1; overflow-y: auto; padding: 10px 12px; }
.context-rail-empty {
color: var(--muted); font-size: 12px; font-style: italic;
padding: 24px 12px; text-align: center;
}
.context-rail-timeline { display: flex; flex-direction: column; gap: 4px; position: relative; }
.context-rail-timeline::before {
content: ''; position: absolute; left: 6px; top: 6px; bottom: 6px;
width: 1px;
background: color-mix(in oklab, var(--muted) 30%, var(--border));
pointer-events: none;
}
.context-rail-dot {
display: grid; grid-template-columns: 16px 1fr max-content;
align-items: center; gap: 8px;
padding: 4px 6px;
border: none; background: transparent;
cursor: pointer; text-align: left; font: inherit;
color: var(--text); border-radius: 4px;
}
.context-rail-dot:hover { background: color-mix(in oklab, var(--muted) 8%, transparent); }
.context-rail-dot-glyph {
width: 10px; height: 10px; border-radius: 50%; justify-self: center;
border: 1px solid var(--border);
}
.context-rail-dot--inject .context-rail-dot-glyph { background: color-mix(in oklab, purple 55%, var(--bg)); border-color: color-mix(in oklab, purple 40%, var(--border)); }
.context-rail-dot--compact .context-rail-dot-glyph { background: color-mix(in oklab, var(--warn) 55%, var(--bg)); border-color: color-mix(in oklab, var(--warn) 45%, var(--border)); }
.context-rail-dot--recall .context-rail-dot-glyph { background: color-mix(in oklab, blue 55%, var(--bg)); border-color: color-mix(in oklab, blue 45%, var(--border)); }
.context-rail-dot--steering .context-rail-dot-glyph { background: color-mix(in oklab, var(--muted) 50%, var(--bg)); }
.context-rail-dot--workflow .context-rail-dot-glyph { background: color-mix(in oklab, var(--ok) 55%, var(--bg)); border-color: color-mix(in oklab, var(--ok) 45%, var(--border)); }
.context-rail-dot-label {
font-size: 12px; color: var(--text);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.context-rail-dot-span { font-size: 11px; color: var(--muted); font-family: var(--mono); }
/* ----- Batch 3 (task #138): workflow + subagent cards ------------------ */
/* Landing area: the Context Rail drawer body (batch 2 canvas). The wrapper
below labels the section so a reader can tell the workflow view from the
default rail projection. */
.context-rail-batch3-mount { display: flex; flex-direction: column; gap: 8px; padding: 8px 0; }
.context-rail-batch3-header {
font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.06em;
padding: 0 2px 4px; border-bottom: 1px dashed var(--border);
}
.context-rail-dot--subagent .context-rail-dot-glyph {
background: color-mix(in oklab, deeppink 55%, var(--bg));
border-color: color-mix(in oklab, deeppink 45%, var(--border));
}
/* Workflow card */
.workflow-card {
border: 1px solid var(--border); border-radius: 8px;
background: var(--bg-elev); padding: 10px 12px;
display: flex; flex-direction: column; gap: 8px;
}
.workflow-card-head {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
font-size: 12px;
}
.workflow-card-title { font-weight: 600; }
.workflow-card-kind {
padding: 1px 6px; border-radius: 999px; font-size: 10px;
background: color-mix(in oklab, var(--ok) 15%, var(--bg-elev));
color: color-mix(in oklab, var(--ok) 65%, var(--text));
border: 1px solid color-mix(in oklab, var(--ok) 30%, var(--border));
text-transform: uppercase; letter-spacing: 0.06em;
}
.workflow-card-progress { color: var(--muted); font-family: var(--mono); font-size: 11px; }
.workflow-card-chip--mock {
padding: 1px 6px; border-radius: 4px; font-size: 10px;
background: color-mix(in oklab, var(--warn) 12%, var(--bg-elev));
color: color-mix(in oklab, var(--warn) 65%, var(--text));
border: 1px dashed color-mix(in oklab, var(--warn) 45%, var(--border));
}
.workflow-body { display: flex; flex-direction: column; gap: 4px; }
.workflow-body-empty { color: var(--muted); font-style: italic; padding: 4px 2px; }
.workflow-step {
display: grid; grid-template-columns: 20px 1fr auto; align-items: baseline;
gap: 6px; padding: 4px 6px; border-radius: 4px;
font-size: 12px;
}
.workflow-step.is-clickable { cursor: pointer; }
.workflow-step.is-clickable:hover { background: color-mix(in oklab, var(--muted) 15%, transparent); }
.workflow-step.is-active { background: color-mix(in oklab, blue 15%, transparent); }
.workflow-step-glyph { font-family: var(--mono); text-align: center; }
.workflow-step-label { color: var(--text); }
.workflow-step-meta { color: var(--muted); font-family: var(--mono); font-size: 11px; text-align: right; }
.workflow-step.status-done .workflow-step-glyph { color: color-mix(in oklab, var(--ok) 65%, var(--text)); }
.workflow-step.status-running .workflow-step-glyph { color: color-mix(in oklab, blue 65%, var(--text)); }
.workflow-step.status-failed .workflow-step-glyph { color: color-mix(in oklab, red 65%, var(--text)); }
.workflow-step.status-pending .workflow-step-glyph { color: var(--muted); }
/* Fan-out */
.workflow-body-fanout { align-items: stretch; }
.workflow-fanout-root {
align-self: flex-start; padding: 4px 10px; border-radius: 4px;
background: color-mix(in oklab, var(--muted) 15%, transparent);
font-family: var(--mono); font-size: 12px;
}
.workflow-fanout-arms { display: flex; flex-direction: column; gap: 4px; padding-left: 16px; border-left: 2px dashed var(--border); margin-left: 12px; }
.workflow-fanout-arm {
display: grid; grid-template-columns: 20px 1fr auto; gap: 6px; align-items: baseline;
padding: 4px 6px; border-radius: 4px; font-size: 12px;
}
.workflow-fanout-arm.is-clickable { cursor: pointer; }
.workflow-fanout-arm.is-clickable:hover { background: color-mix(in oklab, var(--muted) 12%, transparent); }
.workflow-fanout-arm.status-done .workflow-step-glyph { color: color-mix(in oklab, var(--ok) 65%, var(--text)); }
.workflow-fanout-arm.status-running .workflow-step-glyph { color: color-mix(in oklab, blue 65%, var(--text)); }
.workflow-fanout-arm.status-pending .workflow-step-glyph { color: var(--muted); }
/* DAG mini-map */
.workflow-body-dag { display: flex; flex-direction: column; gap: 8px; overflow-x: auto; }
.workflow-dag-layer { display: flex; flex-wrap: wrap; gap: 6px; }
.workflow-dag-node {
display: inline-flex; align-items: center; gap: 4px;
padding: 3px 8px; border-radius: 4px; font-size: 11px;
border: 1px solid var(--border); background: var(--bg-elev);
font-family: var(--mono);
}
.workflow-dag-node.is-clickable { cursor: pointer; }
.workflow-dag-node.status-done { border-color: color-mix(in oklab, var(--ok) 40%, var(--border)); }
.workflow-dag-node.status-running { border-color: color-mix(in oklab, blue 40%, var(--border)); }
.workflow-dag-node.status-failed { border-color: color-mix(in oklab, red 40%, var(--border)); }
.workflow-dag-node.is-active { background: color-mix(in oklab, blue 15%, var(--bg-elev)); }
/* Iter */
.workflow-iter-predicate {
font-family: var(--mono); font-size: 11px; color: var(--muted);
padding: 2px 6px; background: color-mix(in oklab, var(--muted) 10%, transparent);
border-radius: 4px; align-self: flex-start;
}
.workflow-iter-iters { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 2px; }
/* Branch */
.workflow-body-branch { display: flex; flex-direction: column; gap: 6px; }
.workflow-branch-step {
border-left: 3px solid var(--border); padding: 4px 8px;
display: flex; flex-direction: column; gap: 2px;
}
.workflow-branch-step.status-done { border-left-color: color-mix(in oklab, var(--ok) 55%, var(--border)); }
.workflow-branch-step.status-running { border-left-color: color-mix(in oklab, blue 55%, var(--border)); }
.workflow-branch-step.status-failed { border-left-color: color-mix(in oklab, red 55%, var(--border)); }
.workflow-branch-head { display: flex; gap: 6px; align-items: baseline; font-size: 12px; }
.workflow-branch-tag {
margin-left: auto; padding: 1px 5px; border-radius: 3px;
font-size: 10px; font-family: var(--mono);
background: color-mix(in oklab, var(--muted) 15%, transparent);
}
.workflow-branch-output {
font-family: var(--mono); font-size: 11px; color: var(--muted);
padding-left: 20px;
}
/* Replay bar */
.workflow-replay-bar {
display: flex; align-items: center; gap: 6px; padding-top: 6px;
border-top: 1px dashed var(--border); margin-top: 2px;
}
.workflow-replay-pos {
flex: 1; font-family: var(--mono); font-size: 11px; color: var(--muted);
text-align: center;
}
/* Subagent card */
.subagent-card {
border: 1px solid var(--border); border-radius: 8px;
background: var(--bg-elev); padding: 10px 12px;
display: flex; flex-direction: column; gap: 6px;
}
.subagent-card-head {
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
font-size: 12px;
}
.subagent-card-lineage { font-family: var(--mono); font-weight: 600; }
.subagent-card-provider { color: var(--muted); font-size: 11px; font-family: var(--mono); }
.subagent-card-status {
padding: 1px 6px; border-radius: 999px; font-size: 10px;
text-transform: uppercase; letter-spacing: 0.06em;
border: 1px solid var(--border);
}
.subagent-card-status.status-done { background: color-mix(in oklab, var(--ok) 15%, var(--bg-elev)); color: color-mix(in oklab, var(--ok) 60%, var(--text)); border-color: color-mix(in oklab, var(--ok) 40%, var(--border)); }
.subagent-card-status.status-running { background: color-mix(in oklab, blue 15%, var(--bg-elev)); color: color-mix(in oklab, blue 60%, var(--text)); border-color: color-mix(in oklab, blue 40%, var(--border)); }
.subagent-card-status.status-failed { background: color-mix(in oklab, red 15%, var(--bg-elev)); color: color-mix(in oklab, red 60%, var(--text)); border-color: color-mix(in oklab, red 40%, var(--border)); }
.subagent-card-steps, .subagent-card-duration { color: var(--muted); font-family: var(--mono); font-size: 11px; }
.subagent-card-section { border-top: 1px dashed var(--border); padding-top: 4px; }
.subagent-card-section > summary {
cursor: pointer; font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.06em; padding: 2px 0;
}
.subagent-card-prompt-body {
margin: 4px 0; padding: 6px 10px; border-left: 3px solid var(--border);
font-size: 12px; color: var(--text); background: color-mix(in oklab, var(--muted) 8%, transparent);
}
.subagent-card-steps-list { list-style: none; padding: 0; margin: 4px 0; display: flex; flex-direction: column; gap: 2px; font-size: 12px; }
.subagent-card-step { display: flex; gap: 8px; padding: 2px 0; }
.subagent-card-step-name { font-family: var(--mono); color: color-mix(in oklab, blue 60%, var(--text)); }
.subagent-card-step-args { font-family: var(--mono); font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; }
.subagent-card-return-json, .subagent-card-return-raw {
margin: 4px 0; padding: 6px 8px; border: 1px solid var(--border); border-radius: 4px;
background: color-mix(in oklab, var(--muted) 5%, var(--bg));
font-family: var(--mono); font-size: 11px; white-space: pre-wrap; overflow-x: auto; max-height: 240px;
}
/* Prose preview for lastAssistantMessage — plain paragraph so a natural-
* language wrap-up reads as prose, not a raw dump (viz-coverage-matrix §5
* P0-5 fix: the pre-fix path styled this as a <pre>). */
.subagent-card-return-prose {
margin: 4px 0; padding: 6px 10px; border-left: 3px solid var(--border);
color: var(--text); font-size: 12px; white-space: pre-wrap;
background: color-mix(in oklab, var(--muted) 4%, transparent);
max-height: 240px; overflow-y: auto;
}
.subagent-card-return-empty {
margin: 4px 0; padding: 4px 8px;
color: var(--muted); font-size: 11px; font-style: italic;
}
/* Subagent rail (parallel pills row) */
.subagent-rail { display: flex; gap: 4px; padding: 4px 0; flex-wrap: wrap; }
.subagent-rail-pill {
padding: 2px 8px; border-radius: 999px; font-family: var(--mono); font-size: 11px;
border: 1px solid var(--border); background: var(--bg-elev); cursor: pointer;
}
.subagent-rail-pill.status-done { border-color: color-mix(in oklab, var(--ok) 40%, var(--border)); }
.subagent-rail-pill.status-running { border-color: color-mix(in oklab, blue 40%, var(--border)); }
.subagent-rail-pill.status-failed { border-color: color-mix(in oklab, red 40%, var(--border)); }
.subagent-rail-empty { color: var(--muted); font-size: 11px; font-style: italic; padding: 4px 2px; }
* Trace + inject cards task #136 (demo 1)
* §1.1 (trace-per-step) and §1.3 (inject cards, 8 families). Kept at end
* of the file so parallel lanes (§1.7 / §1.2) don't run into hunks here.
* ==========================================================================*/
/* --- trace card (§1.1) --- */
.trace-card {
align-self: flex-start;
max-width: 780px;
margin: 4px 0;
padding: 4px 10px;
background: transparent;
border: 1px dashed var(--border);
border-radius: 6px;
font-size: 12px;
color: var(--muted);
}
.trace-card summary {
cursor: pointer; list-style: none;
display: flex; align-items: center; gap: 8px;
}
.trace-card summary::-webkit-details-marker { display: none; }
.trace-card summary::before {
content: ''; font-size: 10px; color: var(--muted);
transition: transform 0.15s;
}
.trace-card[open] summary::before { transform: rotate(90deg); }
.trace-label {
color: var(--text); font-family: var(--mono); font-size: 12px;
flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.trace-duration {
color: var(--muted); font-family: var(--mono); font-size: 11px;
flex: 0 0 auto;
}
/* Task #38: right-side subtree-fold glyph on trace-card summary.
* Density-layering-spec §7 positive-reference lock: "Tree rows fold their
* subtree via a right-side on parent rows." Rotates with [open] to
* mirror the left-side native marker; hover state ≥24px hit area. */
.trace-card-fold-glyph {
flex: 0 0 auto;
color: var(--muted);
font-size: 12px;
line-height: 1;
min-width: 24px; min-height: 24px;
display: inline-flex; align-items: center; justify-content: center;
border-radius: 3px;
cursor: pointer;
transition: transform 0.15s, color 0.15s, background 0.15s;
}
.trace-card-fold-glyph:hover {
color: var(--text);
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.trace-card[open] > summary > .trace-card-fold-glyph {
transform: rotate(0deg);
color: var(--text);
}
.trace-card:not([open]) > summary > .trace-card-fold-glyph {
transform: rotate(-90deg);
}
.trace-body { margin-top: 6px; display: flex; flex-direction: column; gap: 6px; }
.trace-pane {
background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 4px; padding: 6px 8px;
}
.trace-pane-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.trace-pane-name {
font-weight: 600; font-size: 11px; text-transform: uppercase;
color: var(--text); letter-spacing: 0.05em;
}
.trace-pane-count {
font-family: var(--mono); font-size: 11px; color: var(--accent);
background: var(--bg); padding: 0 4px; border-radius: 3px;
}
.trace-pane-sub { color: var(--muted); font-size: 11px; flex: 1; }
.trace-pane-empty { color: var(--muted); font-size: 11px; font-style: italic; }
.trace-pane-list { display: flex; flex-direction: column; gap: 2px; }
.trace-event-row {
padding: 0; font-size: 11px; border-radius: 3px;
border: 1px solid transparent;
}
.trace-event-row > summary {
display: flex; align-items: baseline; gap: 8px;
padding: 2px 6px 2px 18px; list-style: none;
cursor: pointer; position: relative;
}
.trace-event-row > summary::-webkit-details-marker { display: none; }
.trace-event-row > summary::before {
content: ''; position: absolute; left: 6px; top: 2px;
color: var(--muted); font-size: 9px; transition: transform 120ms ease;
}
.trace-event-row[open] > summary::before { transform: rotate(90deg); }
.trace-event-row:hover > summary { background: var(--bg); }
.trace-event-row[open] { border-color: var(--border); background: var(--bg); }
.trace-event-run > summary { font-weight: 600; }
.trace-event-run .trace-event-type { color: var(--warn); }
.trace-event-seq { color: var(--muted); font-family: var(--mono); flex: 0 0 90px; }
.trace-event-type { color: var(--accent); font-family: var(--mono); flex: 0 0 180px; }
.trace-event-preview {
color: var(--text); overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; flex: 1;
}
.trace-event-payload {
padding: 6px 8px 8px 22px; border-top: 1px dashed var(--border);
background: var(--bg-elev);
}
.trace-event-payload-body {
margin: 0; padding: 6px 8px;
background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
font-family: var(--mono); font-size: 10.5px; line-height: 1.4;
color: var(--text); max-height: 320px; overflow: auto;
white-space: pre-wrap; word-break: break-word;
}
.trace-run-body { padding: 6px 8px 6px 22px; }
.trace-run-heading {
font-size: 10.5px; margin-bottom: 4px; letter-spacing: 0.02em;
}
/* task #157 L1 groups — every event opens with a named-field block, then
* the raw JSON floor. `.trace-event-l1` sits above `.trace-event-payload-
* body`; heavier groups (system prompt, tools, messagePrefix) fold under
* their own details/summary so a 5k-token header doesn't blow out the row. */
.trace-event-l1 {
padding: 6px 8px 4px 22px; display: flex; flex-wrap: wrap;
gap: 6px 8px; align-items: center; font-size: 11px;
}
.trace-event-l1 + .trace-event-payload-body { margin-top: 4px; }
.trace-header-l1-label {
font-weight: 600; color: var(--muted); font-family: var(--mono);
font-size: 10.5px; letter-spacing: 0.04em; text-transform: uppercase;
margin-right: 4px;
}
.trace-header-l1-meta { font-size: 10.5px; margin-left: 6px; }
.trace-meta-chip {
display: inline-flex; align-items: center;
padding: 1px 6px; border-radius: 10px;
background: var(--bg-elev); border: 1px solid var(--border);
font-family: var(--mono); font-size: 10.5px; line-height: 1.5;
}
.trace-meta-chip.absent { opacity: 0.55; }
.trace-meta-key { color: var(--muted); margin-right: 4px; }
.trace-meta-value { color: var(--text); }
.trace-header-config {
display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: center;
width: 100%;
}
.trace-header-system, .trace-header-tools, .trace-header-prefix {
width: 100%; border: 1px solid var(--border); border-radius: 4px;
background: var(--bg-elev);
}
.trace-header-system > summary,
.trace-header-tools > summary,
.trace-header-prefix > summary {
display: flex; align-items: baseline; gap: 8px;
padding: 4px 8px; cursor: pointer; list-style: none;
font-size: 11px;
}
.trace-header-system > summary::-webkit-details-marker,
.trace-header-tools > summary::-webkit-details-marker,
.trace-header-prefix > summary::-webkit-details-marker { display: none; }
.trace-header-system > summary::before,
.trace-header-tools > summary::before,
.trace-header-prefix > summary::before {
content: '▸'; color: var(--muted); font-size: 9px;
transition: transform 120ms ease;
}
.trace-header-system[open] > summary::before,
.trace-header-tools[open] > summary::before,
.trace-header-prefix[open] > summary::before { transform: rotate(90deg); }
.trace-header-system-body,
.trace-header-prefix-body {
margin: 0; padding: 8px 10px;
border-top: 1px dashed var(--border); background: var(--bg);
font-family: var(--mono); font-size: 10.5px; line-height: 1.45;
color: var(--text); max-height: 260px; overflow: auto;
white-space: pre-wrap; word-break: break-word;
}
.trace-header-tools-list {
padding: 4px 8px 8px; display: flex; flex-direction: column; gap: 2px;
border-top: 1px dashed var(--border);
}
.trace-header-tool {
border-radius: 3px; padding: 0;
}
.trace-header-tool > summary {
display: flex; align-items: baseline; gap: 8px; cursor: pointer;
list-style: none; padding: 2px 6px;
}
.trace-header-tool > summary::-webkit-details-marker { display: none; }
.trace-header-tool > summary::before {
content: '▸'; color: var(--muted); font-size: 9px;
transition: transform 120ms ease; margin-right: 2px;
}
.trace-header-tool[open] > summary::before { transform: rotate(90deg); }
.trace-header-tool:hover > summary { background: var(--bg); }
.trace-header-tool-name {
font-family: var(--mono); color: var(--accent); flex: 0 0 120px;
}
.trace-header-tool-desc {
color: var(--muted); overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; flex: 1;
}
.trace-header-tool-body {
margin: 0; padding: 6px 8px;
border-top: 1px dashed var(--border); background: var(--bg);
font-family: var(--mono); font-size: 10.5px; line-height: 1.4;
color: var(--text); max-height: 220px; overflow: auto;
white-space: pre-wrap; word-break: break-word;
}
/* task #157 usage table — five-row breakdown of an assistant/message's
* `data.usage`, folded under a details/summary that carries the compact
* badge (↑in ↓out ⚡cached). Same table used on the step meta strip for
* the step-total usage. */
.trace-usage-table {
width: 100%; border: 1px solid var(--border); border-radius: 4px;
background: var(--bg-elev);
}
.trace-usage-table > summary {
display: flex; align-items: baseline; gap: 10px; cursor: pointer;
list-style: none; padding: 4px 8px; font-size: 11px;
}
.trace-usage-table > summary::-webkit-details-marker { display: none; }
.trace-usage-table > summary::before {
content: '▸'; color: var(--muted); font-size: 9px;
transition: transform 120ms ease;
}
.trace-usage-table[open] > summary::before { transform: rotate(90deg); }
.trace-usage-table-label {
font-weight: 600; color: var(--muted); font-family: var(--mono);
letter-spacing: 0.04em; text-transform: uppercase; font-size: 10.5px;
}
.trace-usage-table-badge {
color: var(--text); font-family: var(--mono); font-size: 11px;
}
.trace-usage-table-body {
border-top: 1px dashed var(--border); padding: 6px 10px;
display: grid; grid-template-columns: 160px 1fr; gap: 2px 12px;
background: var(--bg);
}
.trace-usage-row { display: contents; }
.trace-usage-row.absent .trace-usage-value { opacity: 0.55; font-style: italic; }
.trace-usage-key { color: var(--muted); font-family: var(--mono); font-size: 10.5px; }
.trace-usage-value { color: var(--text); font-family: var(--mono); font-size: 10.5px; }
/* step meta strip on the trace card — the row of chips + optional usage
* table shown right below the summary line so the researcher's first
* glance sees turn/step/seq/duration + rolled-up usage without opening
* any pane. */
.trace-step-meta {
display: flex; flex-direction: column; gap: 4px;
padding: 4px 10px 6px; border-bottom: 1px dashed var(--border);
background: var(--bg-elev);
}
.trace-step-chips { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.trace-usage-badge {
font-family: var(--mono); font-size: 11px; color: var(--text);
background: var(--bg-elev); padding: 1px 6px; border-radius: 3px;
border: 1px solid var(--border);
}
.trace-card > summary > .trace-usage-badge { margin-left: 8px; }
/* tool row's L1: a single "open in side drawer" button that reuses the
* batch-4 JSON drawer so a reader gets call+result on one screen without
* chasing the pair by callId. */
.trace-tool-l1 { padding-left: 22px; }
.trace-tool-drawer-open {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 8px; border: 1px solid var(--border); border-radius: 3px;
background: var(--bg); color: var(--text);
font-family: var(--mono); font-size: 10.5px; cursor: pointer;
}
.trace-tool-drawer-open:hover { border-color: var(--accent); color: var(--accent); }
/* --- inject card (§1.3, eight families) --- */
.inject-card {
align-self: flex-start; max-width: 780px;
margin: 2px 0; padding: 6px 10px;
background: var(--bg-elev); border: 1px solid var(--border);
border-radius: 8px; border-left: 3px solid var(--border);
font-size: 12.5px;
}
.inject-card summary {
cursor: pointer; list-style: none;
display: flex; align-items: center; gap: 8px; color: var(--muted);
}
.inject-card summary::-webkit-details-marker { display: none; }
.inject-icon { font-size: 13px; flex: 0 0 auto; }
.inject-hint {
color: var(--text); overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; flex: 1;
}
.inject-body {
margin-top: 6px; padding: 6px 8px; background: var(--bg); border-radius: 4px;
}
.inject-members { display: flex; flex-direction: column; gap: 6px; }
.inject-member { padding: 4px; border-radius: 3px; }
.inject-member + .inject-member { border-top: 1px solid var(--border); padding-top: 6px; }
.inject-member-meta {
display: flex; gap: 8px; font-family: var(--mono);
font-size: 11px; color: var(--muted); margin-bottom: 2px;
}
.inject-member-src { color: var(--accent); }
.inject-member-payload {
color: var(--text); white-space: pre-wrap; word-wrap: break-word;
font-family: var(--mono); font-size: 12px;
}
.inject-card[data-tone="neutral"] { border-left-color: var(--muted); }
.inject-card[data-tone="plugin"] { border-left-color: var(--accent); }
.inject-card[data-tone="info"] { border-left-color: #4b8fd0; }
.inject-card[data-tone="warn"] { border-left-color: #d0a04b; background: color-mix(in srgb, var(--bg-elev) 92%, #d0a04b); }
.inject-card[data-tone="compact"] { border-left-color: #d07f4b; }
.inject-card[data-tone="danger"] { border-left-color: var(--error); background: color-mix(in srgb, var(--bg-elev) 94%, var(--error)); }
.inject-card[data-tone="muted"] { border-left-color: var(--border); }
.inject-card[data-tone="accent"] { border-left-color: #6bb26b; }
.inject-card--run > summary { font-weight: 500; }
/* ==========================================================================
* task #159 + LangSmith study §6 rec 1/2/4/7 + density-spec §2/§4 conformance.
* Turns each `.trace-event-row` into a LangSmith-flavour tree row: monochrome
* glyph column, name + preview, right-aligned duration bar sized to
* `(evt.time-stepStart)/stepDuration`, 2px left color edge by event class.
* Also: streaming-first placeholder, `.trace-event-l2` head + copy button,
* flat request/header L1 rows (no L1-in-L1 <details> nesting).
* ==========================================================================*/
/* Rework the trace-event-row summary layout so name/preview don't fight the
* new glyph column + duration-bar track. Row height stays ~24px. The 2px
* left-edge color is applied by run-type class further down. */
.trace-event-row > summary {
padding-left: 22px; /* room for chevron + glyph */
}
.trace-event-glyph {
position: absolute; left: 12px; top: 2px;
color: var(--muted); font-family: var(--mono);
font-size: 11px; line-height: 1;
width: 8px; text-align: center;
pointer-events: none;
}
.trace-event-row > summary { padding-left: 26px; }
.trace-event-row > summary::before { left: 6px; }
/* Right-side duration bar track: fixed-width right-aligned column so bars
* across many rows line up along one shared baseline (LangSmith study §6
* rec 1). Neutral fill — status color travels on the left border, not
* here, so the eye reads the bar as duration only.
*
* Task #215 (老板追加 "树状结构上每个节点需要时间的信息"):
* widened to 160px so a real start→end span is legible in the tree, and
* bar height bumped to 8px so a span reads as a segment, not a hair. The
* span variant (`.trace-event-bar-span`) uses `margin-left` for offset
* — this is the LangSmith waterfall grammar inside the tree card. */
.trace-event-bar-track {
margin-left: auto;
flex: 0 0 160px;
height: 8px;
border: 1px solid var(--border);
border-radius: 2px;
background: var(--bg-elev);
overflow: hidden;
position: relative;
}
.trace-event-bar {
display: block; height: 100%;
background: color-mix(in srgb, var(--muted) 60%, transparent);
}
/* Span variant: real start→end waterfall segment inside the shared track. */
.trace-event-bar-span {
background: color-mix(in srgb, var(--tool) 55%, transparent);
min-width: 2px;
}
/* Tool rows in a step tend to be the ones with paired spans; carry the
* tool family tint on the segment to reinforce the row's 2px edge. */
.trace-event-row-tool .trace-event-bar-span { background: color-mix(in srgb, var(--tool) 65%, transparent); }
.trace-event-row-assistant .trace-event-bar-span { background: color-mix(in srgb, var(--accent) 55%, transparent); }
/* 2px left color edge by run-type. Density-spec §2 rule "Color only encodes
* status (ok/error/running) and the 2px run-type edge". */
.trace-event-row {
border-left: 2px solid transparent;
}
.trace-event-row-assistant { border-left-color: color-mix(in srgb, var(--accent) 70%, transparent); }
.trace-event-row-tool { border-left-color: color-mix(in srgb, var(--tool) 70%, transparent); }
.trace-event-row-context { border-left-color: #7fc4a1; }
.trace-event-row-hook { border-left-color: #d0a97b; }
.trace-event-row-meta { border-left-color: var(--border); }
/* Raw-JSON badge on a trace-event row is smaller/quieter than on a tool
* block — density-spec §2 keeps L0 rows terse. */
.trace-event-raw-badge {
padding: 0 4px; font-size: 10px; line-height: 1.2;
margin-left: 6px;
}
/* Streaming-first placeholder — an in-flight step's transient card. Uses
* the same visual shape as the final card so the swap on step/end is
* invisible to the layout; the ⋯ indicator on the summary line signals
* that the step is still running. Density-spec §2 rule: "Running acts:
* pulse on the 2px edge, not a spinner emoji." */
.trace-card-streaming {
border-left: 2px solid var(--accent);
animation: dsh-trace-streaming-pulse 1.4s ease-in-out infinite;
}
.trace-streaming-indicator {
color: var(--muted); font-family: var(--mono); margin-left: 8px;
font-size: 11px;
}
.trace-body-streaming {
padding: 6px 12px; color: var(--muted); font-size: 11px; font-style: italic;
}
@keyframes dsh-trace-streaming-pulse {
0%, 100% { border-left-color: var(--accent); }
50% { border-left-color: color-mix(in srgb, var(--accent) 35%, transparent); }
}
/* L2 head + copy button — density-spec §2.3 "Copy affordance (copy JSON) on
* every L2 surface." */
.trace-event-l2 { padding: 0 8px 4px 22px; }
.trace-event-l2-head {
display: flex; align-items: center; gap: 8px;
font-size: 10px; color: var(--muted);
padding: 4px 0 2px;
}
.trace-event-l2-label {
font-family: var(--mono); letter-spacing: 0.04em; text-transform: uppercase;
}
.trace-event-copy {
margin-left: auto; background: transparent; color: var(--muted);
border: 1px solid var(--border); border-radius: 3px;
padding: 0 6px; font-family: var(--mono); font-size: 10px;
cursor: pointer; line-height: 1.5;
}
.trace-event-copy:hover { color: var(--accent); border-color: var(--accent); }
/* Retain the payload-body cap (already exists at 320px above) — do not
* duplicate it, but tighten the padding so the head + body read as one. */
.trace-event-l2 > .trace-event-payload-body { margin-top: 0; }
/* --- L1-not-nested-in-L1 (density-spec §2 L1 rule) ---------------------- */
/* `.trace-header-tools` and `.trace-header-prefix` are now plain divs. */
.trace-header-tools, .trace-header-prefix {
width: 100%;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg-elev);
padding: 4px 8px;
max-height: 320px;
overflow: auto;
}
.trace-header-tools-head,
.trace-header-prefix {
display: flex; align-items: baseline; gap: 8px;
}
.trace-header-tools-list {
padding-top: 4px;
border-top: 1px dashed var(--border);
margin-top: 4px;
display: flex; flex-direction: column; gap: 2px;
}
/* A tool row inside the request/header L1 panel is now a single flat row —
* no nested <details>. Name + one-line description + `{ }` badge. */
.trace-header-tool {
display: flex; align-items: baseline; gap: 8px;
padding: 2px 6px;
border-radius: 3px;
min-height: 20px;
}
.trace-header-tool:hover { background: var(--bg); }
.trace-header-tool-name {
font-family: var(--mono); color: var(--accent);
flex: 0 0 140px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.trace-header-tool-desc {
color: var(--muted); overflow: hidden; text-overflow: ellipsis;
white-space: nowrap; flex: 1;
font-size: 11px;
}
.trace-header-l1-badge {
margin-left: auto; flex: 0 0 auto;
}
/* Retain the system prompt as a foldable — it is a scalar text field, not
* an L1 group of its own, so a details/summary here is not L1-in-L1. */
.trace-header-system { max-height: 320px; overflow: hidden; }
.trace-header-system > .trace-header-system-body { max-height: 260px; }
/* task #157 usage-badge wording ("cache Nk" is a plain word, not an emoji) */
.trace-usage-badge {
margin-left: 8px;
padding: 1px 6px;
border-radius: 3px;
border: 1px solid var(--border);
color: var(--muted);
font-family: var(--mono);
font-size: 10.5px;
line-height: 1.4;
}
/* task #159 fold ×N chip on a tool-run row (≥3 consecutive same-name tool
* calls collapse into one row per density-spec §2 "Repetition folds"). */
.tool-block[data-run-count] .tool-run-count { display: inline-block; }
/* task #158 meta fold — per-event `data.meta` kv rows, rendered as a
* <details> under the L1 block. Compact, muted, one line per key. */
.trace-event-meta {
margin: 4px 22px 4px;
border: 1px dashed var(--border);
border-radius: 3px;
background: var(--bg-elev);
max-height: 260px; overflow: auto;
}
.trace-event-meta > summary {
display: flex; align-items: baseline; gap: 8px;
padding: 3px 8px;
cursor: pointer;
font-family: var(--mono); font-size: 11px;
}
.trace-event-meta-label { color: var(--muted); font-weight: 500; }
.trace-event-meta-count { font-size: 10px; }
.trace-event-meta-body {
padding: 4px 8px;
border-top: 1px dashed var(--border);
display: flex; flex-direction: column; gap: 2px;
}
.trace-event-meta-row {
display: flex; align-items: baseline; gap: 8px;
padding: 1px 4px;
font-family: var(--mono); font-size: 11px;
}
.trace-event-meta-key {
color: var(--muted);
flex: 0 0 120px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.trace-event-meta-value {
color: var(--text);
flex: 1;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* -- assistant-turn container (#162 rec 22-bis, design-confirm §1) ---------
* A <section.assistant-turn> groups the children of one assistant step —
* bubble (soon `.text-block`), reasoning frames, tool blocks, recall cards
* — inside `.turn-body`, plus a `<footer.turn-footer>` terminator row and
* an inline `<details.turn-trace-drawer>` for the L2/L3 trace card.
*
* pi §2.3-bis readability rules manifest in CSS as:
* R1 uniform indent — .turn-body children share the same left edge.
* R2 fixed glyph col — .turn-glyph is 1em wide, monospaced-aligned.
* R3 call+result gap — .tool-row + .tool-result-row sit tighter than
* the between-child gap (visually paired).
* R6 footer=divider — the top `.turn-rule` is a thin start-edge; the
* bottom `.turn-footer` is the terminator; no
* horizontal rules between children.
*
* Colour comes from tokens (border, surface, muted, accent) — no hard
* hex here. The container is presentation-only; renderer.js owns lifecycle.
*/
.assistant-turn {
display: block;
border: 1px solid transparent; /* no card outline by default */
border-left: 2px solid var(--border);
padding: 0 0 0 12px;
margin: 8px 0;
}
.assistant-turn[data-turn-status="streaming"] {
border-left-color: var(--accent-soft);
}
.assistant-turn > .turn-rule {
height: 1px; background: transparent; margin: 0 0 6px 0;
}
.assistant-turn > .turn-body {
display: flex; flex-direction: column;
gap: 8px; /* between-child spacing */
}
/* R3: call+result rows sit tighter (halve the gap between adjacent
* .tool-row and .tool-result-row via first-of-type sibling combinator). */
.assistant-turn > .turn-body > .tool-row + .tool-result-row {
margin-top: -4px; /* pulls result up under call */
}
/* task #162 rec 22-bis phase 2 (pi §2.3): retired-in-turn assistant bubble.
* When `.msg.assistant` lands inside `.turn-body`, the outer element gains
* `.in-turn` and its body child becomes `.text-block.turn-child`. Drop the
* bubble chrome (background, border, padding, max-width, radius) so the
* text reads as a peer of tool rows / reasoning / footer rather than a
* chat-app card. Fork-here anchor still hovers to the right of the row.
* Stream-root landings (history-replay of pre-container events, quick-chat
* overlay) keep the legacy `.msg.assistant` bubble shape untouched — the
* `.in-turn` guard is what confines this retirement to container-hosted
* bubbles. */
.msg.assistant.in-turn {
background: transparent;
border: 0;
padding: 0;
max-width: none;
border-radius: 0;
position: relative;
}
.msg.assistant.in-turn > .role {
display: none;
}
.turn-child.text-block {
font: inherit;
color: var(--text);
white-space: pre-wrap;
word-wrap: break-word;
line-height: 1.55;
padding: 0;
margin: 0;
}
/* R2: fixed-width glyph column, single character, mono-aligned. */
.turn-glyph {
display: inline-block;
width: 1em; min-width: 1em;
color: var(--muted);
font-family: var(--mono);
}
.tool-row[data-sealed="1"] > .turn-glyph,
.tool-result-row > .turn-glyph { color: var(--text); }
.tool-result-row[data-error="1"] > .turn-glyph { color: var(--error, #dc2626); }
/* tool row + result row shapes (renderer's TurnBuilder emits these
* classes; the existing .tool-block card is a distinct render path that
* still lives inside .turn-body). */
.turn-child.tool-row,
.turn-child.tool-result-row {
display: flex; align-items: baseline; gap: 6px;
font-family: var(--mono); font-size: 12.5px;
color: var(--text);
}
.turn-child.tool-row .tool-row-name { color: var(--text); }
.turn-child.tool-row .tool-row-args { color: var(--muted); }
.turn-child.tool-result-row .tool-result-summary { color: var(--muted); }
.turn-child.tool-result-row .tool-result-duration {
color: var(--muted); font-size: 11px; margin-left: auto;
}
/* Rec 21: reasoning-block ("thinking" fold, pi-agent-ui-study §2.2).
* A first-class inline block inside the assistant-turn body. Collapsed
* by default — the L0 row shows glyph + "thinking" label + last ~40
* chars of the buffer; the L1 body is the full reasoning text.
*
* Visual grammar per density-layering-spec §2: single line, monospace,
* muted (dim italic) so it reads as ambient context, not primary text.
* The row is a real <button> so keyboard/focus semantics come for free;
* unset button chrome (no border/background/padding).
*
* The [data-collapsed] attribute drives the body's `hidden` state from
* JS; keep the CSS in sync so a still-rendering fold looks right even
* before the click handler wires up.
*
* Rule #8 (2026-07-17): this CSS was missing in the initial rec-21
* landing — the module class names went unstyled and the block rendered
* as a native <button> next to raw text. Never merge a new render path
* without checking that its class selectors actually resolve. */
.turn-child.reasoning-block {
margin: 2px 0;
color: color-mix(in oklab, var(--muted) 70%, var(--text));
font-style: italic;
}
.turn-child.reasoning-block > .reasoning-row {
all: unset;
display: flex; align-items: baseline; gap: 6px;
width: 100%;
cursor: pointer;
font-family: var(--mono); font-size: 12px;
color: inherit;
}
.turn-child.reasoning-block > .reasoning-row:focus-visible {
outline: 1px dashed var(--accent);
outline-offset: 2px;
}
.turn-child.reasoning-block .reasoning-glyph {
color: var(--muted); width: 1em; font-family: var(--mono); font-style: normal;
transition: transform 120ms ease-out;
display: inline-block;
}
.turn-child.reasoning-block[data-collapsed="0"] .reasoning-glyph {
transform: rotate(90deg);
}
.turn-child.reasoning-block .reasoning-label {
color: var(--muted); font-style: normal;
}
.turn-child.reasoning-block .reasoning-sep { color: var(--muted); opacity: 0.6; font-style: normal; }
.turn-child.reasoning-block .reasoning-preview {
color: color-mix(in oklab, var(--muted) 60%, var(--text));
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 auto; min-width: 0;
}
.turn-child.reasoning-block .reasoning-body {
margin: 4px 0 6px 20px; /* indent under the glyph column */
padding: 6px 8px;
border-left: 2px solid color-mix(in oklab, var(--accent) 25%, transparent);
background: color-mix(in oklab, var(--bg-elevated, var(--bg)) 92%, var(--accent) 8%);
border-radius: 3px;
font-family: var(--mono); font-size: 12px;
color: color-mix(in oklab, var(--muted) 50%, var(--text));
white-space: pre-wrap; word-wrap: break-word;
font-style: normal;
}
/* Rec 22: partial-tool-row (in-flight tool-call-delta stream). Density
* spec §4 running state = 2px edge pulse (NOT a spinner glyph); the row
* text keeps the pi §2.3 "name(partial-args…)" shape and grows in place.
* Class-based so the sealed downgrade (tool/call event arrives → row is
* removed and full tool card renders below) needs no style transition. */
.turn-child.tool-row.partial-tool-row {
border-left: 2px solid var(--accent);
padding-left: 6px;
margin-left: -8px; /* keep the row's baseline aligned with sealed rows */
animation: partial-tool-row-pulse 1.4s ease-in-out infinite;
}
@keyframes partial-tool-row-pulse {
0%, 100% { border-left-color: var(--accent); }
50% { border-left-color: color-mix(in oklab, var(--accent) 30%, transparent); }
}
/* D25 (drift cycle 13/14): honour prefers-reduced-motion. Pulse
* animation is an ambient running-state cue, not information the user
* can't get elsewhere (the row's `data-tool-call-delta="1"` marker +
* `name(partial-args…)` shape already communicate in-flight state).
* Users with motion sensitivity see a static edge instead of a
* flashing one. */
@media (prefers-reduced-motion: reduce) {
.turn-child.tool-row.partial-tool-row {
animation: none;
}
}
.turn-child.tool-row.partial-tool-row .tool-row-args {
color: color-mix(in oklab, var(--muted) 70%, var(--text));
}
/* Honesty chip on the partial-tool-row: labels the fact that the
* `tool-call-delta` wire shape hasn't been verified against a real
* DeepSeek response yet (only fixture captures drive this branch). The
* chip is removed once a real chunk is observed and the render code is
* confirmed to match. Sits at the right end, muted, non-interactive. */
.turn-child.tool-row.partial-tool-row .tool-row-chip-unverified {
margin-left: auto;
padding: 1px 6px;
font-size: 10px;
font-family: var(--mono);
color: var(--muted);
border: 1px solid color-mix(in oklab, var(--muted) 45%, transparent);
border-radius: 3px;
white-space: nowrap;
letter-spacing: 0.01em;
}
/* Rec 31: inline subagent trace. Sits under the parent's spawn tool row
* inside the assistant-turn body so a subagent reads as a peer step,
* not a floating side card. Collapsed by default; full subagent-card
* body reveals on toggle. */
.subagent-trace {
display: block;
margin: 4px 0 4px 16px; /* indent one level under the spawn row */
border-left: 2px solid color-mix(in oklab, var(--accent) 40%, transparent);
padding-left: 8px;
}
.subagent-trace > .subagent-trace-summary {
cursor: pointer;
display: flex; gap: 6px; align-items: baseline;
font-family: var(--mono); font-size: 12px;
color: var(--text);
list-style: none;
padding: 2px 0;
}
.subagent-trace > .subagent-trace-summary::-webkit-details-marker { display: none; }
.subagent-trace-glyph { color: var(--muted); font-family: var(--mono); width: 1em; }
.subagent-trace[open] .subagent-trace-glyph { color: var(--text); }
.subagent-trace-label { color: var(--muted); }
.subagent-trace-lineage { color: color-mix(in oklab, var(--accent) 60%, var(--text)); }
.subagent-trace-meta { color: var(--muted); font-size: 11px; margin-left: auto; }
.subagent-trace-body { padding: 6px 0 4px 4px; }
/* Ticket #15 A (2026-07-17): live subagent card while the child is running.
* Uses the same trace shell as the sealed inline card but paints a pulsing
* accent left border so a reader can tell "child activity is streaming"
* without expanding. When subagent.finished lands the card swaps in place
* for the sealed inline trace and this class is dropped. */
.subagent-trace--running {
border-left-color: color-mix(in oklab, var(--accent) 80%, transparent);
animation: dsh-subagent-pulse 1.6s ease-in-out infinite;
}
.subagent-trace--running > .subagent-trace-summary .subagent-trace-glyph {
color: color-mix(in oklab, var(--accent) 70%, var(--text));
}
@keyframes dsh-subagent-pulse {
0% { border-left-color: color-mix(in oklab, var(--accent) 80%, transparent); }
50% { border-left-color: color-mix(in oklab, var(--accent) 40%, transparent); }
100% { border-left-color: color-mix(in oklab, var(--accent) 80%, transparent); }
}
.subagent-live-body {
padding: 4px 0 4px 4px;
display: flex; flex-direction: column; gap: 2px;
font-family: var(--mono); font-size: 11px;
color: var(--muted);
}
.subagent-live-head {
color: var(--muted);
font-size: 11px;
text-transform: uppercase; letter-spacing: 0.06em;
padding-bottom: 2px;
border-bottom: 1px dashed color-mix(in oklab, var(--border) 60%, transparent);
margin-bottom: 4px;
}
.subagent-live-row {
display: flex; gap: 6px; align-items: baseline;
padding: 1px 0;
color: var(--text);
}
.subagent-live-row[data-event-type="assistant/chunk"] { color: var(--muted); }
.subagent-live-row[data-event-type="tool/call"] { color: color-mix(in oklab, var(--accent) 55%, var(--text)); }
.subagent-live-row[data-event-type="tool/result"] { color: color-mix(in oklab, var(--ok) 45%, var(--text)); }
.subagent-live-glyph {
width: 1em; color: var(--muted); font-family: var(--mono);
}
.subagent-live-label {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
flex: 1 1 auto;
}
/* Ticket #15 B (2026-07-17): envelope:'raw' inject card. Semantically
* distinct from the tagged inject-card family (envelope='context') because
* the caller — not the kernel — owns the framing. Reads as a dashed-border
* card with a `raw · <kind>` badge so a reviewer notices the difference
* at a glance. meta.kind === 'workspace-instructions' unlocks a typed
* sub-header (version pill + changes list); unknown kinds render generically
* without losing envelope+meta — the L2 <pre> keeps the full JSON. */
.raw-inject-card {
display: block;
margin: 4px 0;
padding: 4px 8px;
border: 1px dashed color-mix(in oklab, var(--muted) 55%, transparent);
border-left: 3px solid color-mix(in oklab, var(--accent) 60%, transparent);
border-radius: 3px;
background: color-mix(in oklab, var(--bg-elev) 92%, transparent);
font-size: 12px;
}
.raw-inject-card > .raw-inject-summary {
cursor: pointer;
display: flex; gap: 6px; align-items: baseline;
list-style: none;
padding: 2px 0;
}
.raw-inject-card > .raw-inject-summary::-webkit-details-marker { display: none; }
.raw-inject-icon { color: var(--muted); font-family: var(--mono); width: 1em; }
.raw-inject-badge {
font-family: var(--mono); font-size: 11px;
color: color-mix(in oklab, var(--accent) 60%, var(--text));
background: color-mix(in oklab, var(--accent) 12%, var(--bg-elev));
border: 1px solid color-mix(in oklab, var(--accent) 30%, var(--border));
border-radius: 2px;
padding: 0 4px;
letter-spacing: 0.02em;
}
.raw-inject-hint {
color: var(--muted); font-size: 11px; margin-left: 4px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
flex: 1 1 auto;
}
.raw-inject-body { padding: 4px 0 2px 0; }
.raw-inject-typed-header {
display: flex; gap: 6px; align-items: baseline;
padding: 2px 0;
font-size: 11px;
}
.raw-inject-kind {
font-family: var(--mono); font-weight: 600;
color: color-mix(in oklab, var(--accent) 55%, var(--text));
}
.raw-inject-version {
font-family: var(--mono); font-size: 11px;
color: var(--muted);
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: 2px;
padding: 0 4px;
}
.raw-inject-changes {
list-style: none;
padding: 0;
margin: 4px 0;
display: flex; flex-direction: column; gap: 2px;
font-family: var(--mono); font-size: 11px;
}
.raw-inject-change-row {
display: flex; gap: 6px; align-items: baseline;
padding: 1px 0;
}
.raw-inject-change-action {
min-width: 4em;
color: var(--muted);
text-transform: uppercase; letter-spacing: 0.04em;
font-size: 11px;
}
.raw-inject-change-action.action-add { color: color-mix(in oklab, var(--ok) 55%, var(--text)); }
.raw-inject-change-action.action-remove { color: color-mix(in oklab, var(--error) 55%, var(--text)); }
.raw-inject-change-path { color: var(--text); }
.raw-inject-content {
white-space: pre-wrap;
padding: 4px 0;
color: var(--text);
font-size: 12px;
border-top: 1px dashed color-mix(in oklab, var(--border) 60%, transparent);
margin-top: 4px;
}
.raw-inject-l2 { margin-top: 4px; }
.raw-inject-l2 > summary {
cursor: pointer;
font-size: 11px;
color: var(--muted);
list-style: none;
}
.raw-inject-l2 > summary::-webkit-details-marker { display: none; }
.raw-inject-l2[open] > summary { color: var(--text); }
.raw-inject-json {
font-family: var(--mono);
font-size: 11px;
color: var(--muted);
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: 3px;
padding: 6px;
max-height: 240px;
overflow: auto;
}
/* Text block (follow-up commit will migrate bubble → this shape). */
.turn-child.text-block {
white-space: pre-wrap; word-wrap: break-word;
color: var(--text);
}
/* R6: footer terminator row. Full-width, left-aligned, muted separator. */
.assistant-turn > .turn-footer {
display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
padding: 6px 0 4px 0;
margin-top: 8px;
border-top: 1px dashed var(--border);
font-family: var(--mono); font-size: 11px;
color: var(--muted);
}
.turn-footer-field { color: var(--text); }
.turn-footer-sep { color: var(--muted); opacity: 0.6; }
/* Trace drawer sits at the end of the footer row. */
.turn-trace-drawer {
flex-basis: 100%;
margin-top: 4px;
border-top: 1px dashed var(--border);
padding-top: 4px;
}
.turn-trace-drawer > .turn-trace-drawer-summary {
cursor: pointer; color: var(--muted); font-size: 11px;
list-style: none;
}
.turn-trace-drawer > .turn-trace-drawer-summary::-webkit-details-marker {
display: none;
}
.turn-trace-drawer[open] > .turn-trace-drawer-summary { color: var(--text); }
/* ============================================================
* Lane-nav (task #189 / IA §2 Alt A): three-group nav + Runtimes
* page + Settings page + empty-state four-card launcher + keys
* resource table. §7 visual-craft baseline applied 2026-07-17:
* only 4px-grid spacing (var(--space-N)), only radius/font-size
* tokens, only --ok/--err/--accent status colors, max 3 font
* sizes per view.
* ============================================================ */
/* ---- Sidebar: three-group container --------------------------------
`.sidebar-nav` already uses gap:2px + padding:{1,2,3}, so the group
inherits the same rhythm — the visual grouping is done with header
labels and a top-border on the admin group only, not by opening
larger internal gaps that would drift from the base sidebar. */
.sidebar .sidebar-nav .nav-group {
display: flex; flex-direction: column;
gap: 2px;
margin-bottom: var(--space-2);
}
.sidebar .sidebar-nav .nav-group:last-of-type {
margin-bottom: 0;
}
.sidebar .sidebar-nav .nav-group-header {
/* Matches .sidebar-section-head token grammar (11px uppercase /
0.06em / weight 600 / text-tertiary) so the eye reads the group
labels as siblings of Recent's "SESSIONS" caption. */
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
padding: var(--space-3) var(--space-2) var(--space-1);
cursor: default;
}
.sidebar .sidebar-nav .nav-group--admin {
margin-top: auto;
border-top: 1px solid var(--border);
padding-top: var(--space-2);
}
/* Pending-lane placeholder — dim the row until the real destination
lands. Hover still triggers the tab-click handler which surfaces the
"still landing" system notice (see renderer.js pending intercept). */
.sidebar .nav-item[data-lane="pending"] {
opacity: 0.55;
cursor: default;
}
.sidebar .nav-item[data-lane="pending"]:hover {
background: var(--surface-hover);
opacity: 0.72;
}
.sidebar .nav-item[data-lane="pending"] svg {
color: var(--text-tertiary);
}
/* "· demo" chip — same fs-micro token as group headers; injected by
renderer.js decorateNav() when the row's destination is fixture-only. */
.sidebar .nav-item .nav-item-demo-chip {
margin-left: auto;
padding: 0 var(--space-1);
font-size: var(--fs-micro);
line-height: 16px;
color: var(--text-tertiary);
background: var(--surface-hover);
border-radius: var(--radius-sm);
text-transform: lowercase;
letter-spacing: 0.02em;
border: 1px solid var(--border);
}
.sidebar .nav-item.active .nav-item-demo-chip {
color: var(--text-secondary);
}
/* Page-header demo chip. Sits next to page-title / in header-actions to
mark whole surfaces whose data is fixture-tier and whose write path
depends on an upstream seam. Sibling of the workflow-card-chip--mock
family (same "not on wire yet" semantics) but sized for a page header
rather than a card head. Sticks to muted/dashed instead of coloured
so it stays "visible but not loud" per viz-coverage-matrix §7. */
.demo-tier-chip {
display: inline-flex; align-items: center; gap: 4px;
padding: 1px 8px;
border-radius: 999px;
font-size: var(--fs-micro);
line-height: 16px;
color: var(--text-tertiary);
background: var(--surface-hover);
border: 1px dashed var(--border);
letter-spacing: 0.02em;
text-transform: lowercase;
white-space: nowrap;
vertical-align: middle;
}
.demo-tier-chip[data-tier="preview"] {
/* Preview variant (mission empty state) — leaves it uppercase and drops
the dash for a slightly quieter tone since the ghost cards already
read as illustrations. */
text-transform: uppercase;
letter-spacing: 0.06em;
border-style: dotted;
}
/* ---- Runtimes page ------------------------------------------------
Font ladder: fs-body (row name), fs-sm (values), fs-micro (labels).
Radius: --radius-sm on inputs/chips, --radius-lg on section cards.
Status: --ok / --err / --text-tertiary only. */
.runtimes-body {
display: flex; flex-direction: column; gap: var(--space-4);
padding: var(--space-4);
overflow-y: auto;
min-height: 0;
}
.runtimes-legend {
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
background: var(--surface-hover);
font-size: var(--fs-micro);
}
.runtimes-list {
display: flex; flex-direction: column; gap: 1px;
background: var(--border);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
}
.runtime-row {
background: var(--surface);
}
.runtime-row-head {
display: grid;
grid-template-columns: 12px 1fr auto auto auto;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) var(--space-3);
cursor: pointer;
list-style: none;
min-height: 32px;
}
.runtime-row-head::-webkit-details-marker { display: none; }
.runtime-row-head:hover { background: var(--surface-hover); }
.runtime-row--live .runtime-row-head { background: var(--surface-hover); }
.runtime-row-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--text-tertiary);
}
.runtime-row-dot--live { background: var(--ok); }
.runtime-row-dot--idle { background: var(--text-tertiary); }
.runtime-row-name { font-weight: 500; font-size: var(--fs-sm); }
.runtime-row-id { font-family: var(--font-mono, monospace); font-size: var(--fs-micro); }
.runtime-row-adapters {
display: inline-flex; gap: var(--space-1);
}
.runtime-adapter-dot {
padding: 0 var(--space-2);
border-radius: var(--radius-sm);
font-size: var(--fs-micro);
line-height: 18px;
border: 1px solid var(--border);
color: var(--text-secondary);
background: var(--surface);
}
.runtime-adapter-dot--on {
color: var(--ok);
border-color: color-mix(in oklab, var(--ok) 40%, transparent);
background: var(--ok-soft);
}
.runtime-adapter-dot--off { color: var(--text-tertiary); }
.runtime-row-model {
font-family: var(--font-mono, monospace);
font-size: var(--fs-micro);
}
.runtime-row-body {
padding: var(--space-3) var(--space-4) var(--space-4);
border-top: 1px solid var(--border);
background: var(--surface);
}
.runtime-row-section-head {
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
margin-bottom: var(--space-2);
}
.runtime-caps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: var(--space-2) var(--space-3);
margin-bottom: var(--space-3);
}
.runtime-cap-cell {
display: flex; align-items: center; gap: var(--space-2);
font-size: var(--fs-sm);
}
.runtime-cap-dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--text-tertiary);
flex-shrink: 0;
}
.runtime-cap-dot--on { background: var(--ok); }
.runtime-cap-dot--off { background: var(--err); }
.runtime-cap-dot--unknown {
background: transparent;
border: 1px dashed var(--text-tertiary);
}
.runtime-row-meta {
display: grid;
grid-template-columns: max-content 1fr;
gap: var(--space-1) var(--space-3);
margin: 0;
}
.runtime-row-meta dt {
color: var(--text-tertiary);
font-size: var(--fs-micro);
text-transform: lowercase;
}
.runtime-row-meta dd {
margin: 0;
font-size: var(--fs-sm);
}
.runtimes-isolated {
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: var(--space-3);
background: var(--surface);
}
.runtimes-isolated-empty {
font-size: var(--fs-sm);
padding: var(--space-2);
}
.runtimes-isolated-list {
list-style: none; padding: 0; margin: 0;
display: flex; flex-direction: column; gap: var(--space-2);
}
.runtimes-isolated-row {
display: grid;
grid-template-columns: 12px 1fr auto auto auto;
align-items: center;
gap: var(--space-3);
padding: var(--space-2);
min-height: 32px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
}
.runtimes-isolated-socket { font-size: var(--fs-micro); }
.runtimes-isolated-ttl { font-size: var(--fs-micro); }
.runtimes-isolated-actions { display: inline-flex; gap: var(--space-1); }
.runtimes-empty {
padding: var(--space-4);
text-align: center;
background: var(--surface);
border: 1px dashed var(--border);
border-radius: var(--radius-lg);
}
.runtimes-sidebar-note,
.settings-sidebar-note {
padding: var(--space-3);
font-size: var(--fs-sm);
line-height: 1.5;
}
/* ---- Settings page ------------------------------------------------
Font ladder: fs-section (section titles), fs-sm (body), fs-micro
(chips/labels/meta). Radius: --radius-sm on inputs/chips,
--radius-lg on section cards. */
.settings-body {
display: flex; flex-direction: column; gap: var(--space-4);
padding: var(--space-4);
overflow-y: auto;
min-height: 0;
}
.settings-section {
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--surface);
padding: var(--space-4);
}
.settings-section-head { margin-bottom: var(--space-3); }
.settings-section-title {
font-size: var(--fs-section);
font-weight: 600;
line-height: 1.2;
margin: 0 0 var(--space-1);
}
.settings-section-sub {
font-size: var(--fs-sm);
line-height: 1.5;
}
.settings-price-table {
width: 100%;
border-collapse: collapse;
font-size: var(--fs-sm);
}
.settings-price-table thead th {
text-align: left;
padding: var(--space-2);
font-weight: 600;
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
border-bottom: 1px solid var(--border);
}
.settings-price-table tbody tr {
border-bottom: 1px solid var(--border);
}
.settings-price-table tbody tr:last-child { border-bottom: 0; }
.settings-price-table td {
padding: var(--space-2);
}
.settings-price-model {
font-family: var(--font-mono, monospace);
font-size: var(--fs-sm);
}
.settings-price-cell {
position: relative;
}
.settings-price-input {
width: 88px;
padding: var(--space-1) var(--space-2);
font-family: var(--font-mono, monospace);
font-size: var(--fs-sm);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface);
color: var(--text);
}
.settings-price-input:focus-visible {
outline: 0;
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent-soft);
}
.settings-price-marker {
display: inline-block;
margin-left: var(--space-2);
padding: 0 var(--space-1);
font-size: var(--fs-micro);
line-height: 16px;
color: var(--text-secondary);
background: var(--surface-hover);
border-radius: var(--radius-sm);
}
.settings-price-row--override .settings-price-input {
border-color: var(--accent);
background: var(--accent-soft);
}
.settings-price-actions { text-align: right; }
/* Legacy list fallback (kept for the `<ul data-settings-keys-list>`
escape-hatch renderer path in settings-page.js). Primary path is the
resource table below. */
.settings-keys-list {
list-style: none; margin: 0; padding: 0;
display: flex; flex-direction: column; gap: var(--space-1);
}
.settings-key-row {
display: grid;
grid-template-columns: 12px 1fr auto auto;
align-items: center;
gap: var(--space-3);
padding: var(--space-2);
min-height: 32px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
}
.settings-key-dot {
width: 8px; height: 8px; border-radius: 50%;
flex-shrink: 0;
}
.settings-key-dot--on { background: var(--ok); }
.settings-key-dot--off { background: var(--text-tertiary); }
.settings-key-name { font-size: var(--fs-sm); }
.settings-key-tier {
padding: 0 var(--space-2);
border-radius: var(--radius-sm);
font-size: var(--fs-micro);
line-height: 18px;
text-transform: uppercase;
letter-spacing: 0.06em;
border: 1px solid var(--border);
color: var(--text-secondary);
background: var(--surface-hover);
}
.settings-key-tier--personal {
color: var(--tint-purple);
border-color: color-mix(in oklab, var(--tint-purple) 35%, transparent);
background: color-mix(in oklab, var(--tint-purple) 6%, transparent);
}
.settings-key-tier--service {
color: var(--tint-teal);
border-color: color-mix(in oklab, var(--tint-teal) 35%, transparent);
background: color-mix(in oklab, var(--tint-teal) 6%, transparent);
}
.settings-key-status { font-size: var(--fs-micro); }
/* ---- Rec 30 (2026-07-17): API keys resource table ----------------
Columns: Name / Tier / Description / Presence / Last used — same
shape as LangSmith Settings > API Keys. Values are never shown;
presence is a dot + label.
Font ladder shared with .settings-price-table (fs-sm body, fs-micro
thead/meta) so the two tables in the Settings pane read as one
system. Row min-height 32px matches spec §4 click-target rule. */
.settings-keys-table {
width: 100%;
border-collapse: collapse;
font-size: var(--fs-sm);
margin-top: var(--space-1);
}
.settings-keys-table thead th {
text-align: left;
padding: var(--space-2);
font-weight: 600;
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-transform: uppercase;
letter-spacing: 0.06em;
border-bottom: 1px solid var(--border);
}
.settings-keys-table tbody tr {
border-bottom: 1px solid var(--border);
}
.settings-keys-table tbody tr:last-child { border-bottom: 0; }
.settings-keys-table td {
padding: var(--space-2);
vertical-align: middle;
}
.settings-keys-table .settings-key-name { font-size: var(--fs-sm); }
.settings-keys-table .settings-key-desc {
color: var(--text-secondary);
font-size: var(--fs-sm);
line-height: 1.4;
}
.settings-keys-table .settings-key-presence {
display: flex; align-items: center; gap: var(--space-2);
white-space: nowrap;
font-size: var(--fs-micro);
}
.settings-keys-table .settings-key-lastused {
font-family: var(--font-mono, monospace);
font-size: var(--fs-micro);
white-space: nowrap;
}
/* =========================================================================
* Rubrics page + Annotation panel (tasks #188 / #191)
* Catalog-first evaluator library with per-group tiles, an L2 detail drawer,
* plus a right-side annotation drawer that owns the 5-dim per-turn cards +
* task-tag picker + JSONL export preview.
* ------------------------------------------------------------------------- */
/*
* §7 visual-craft baseline compliance (density-layering-spec §7):
* - Spacing on 4px grid, only {4, 8, 12, 16, 24, 32}.
* - Typography 3-size ladder: 14px body / 13px body-condensed / 11px meta.
* Mono used only for wire data (session ids, snippets, scores, JSON).
* - Line-height 1.5 body, 1.2 heading.
* - Click targets ≥24px on every interactive control.
* - Colors: token-only. Status semantics for ok/warn/err verdict; single
* accent for focus/selection.
* - Shadows: use shared --shadow-pop token, not raw rgba.
*/
.rubrics-body {
overflow-y: auto;
padding: 24px 32px 32px 32px;
gap: 24px;
display: flex;
flex-direction: column;
}
.rubrics-catalog { display: flex; flex-direction: column; gap: 24px; }
.rubric-group {
border-bottom: 1px solid var(--divider);
padding-bottom: 24px;
}
.rubric-group:last-child { border-bottom: none; }
.rubric-group-head {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 8px 12px;
align-items: baseline;
margin-bottom: 12px;
}
.rubric-group-title {
font-weight: 600;
font-size: 13px;
line-height: 1.2;
letter-spacing: 0.02em;
color: var(--text);
}
.rubric-group-hint {
grid-column: 2 / 3;
font-size: 11px;
color: var(--text-secondary);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.rubric-group-count {
grid-column: 3 / 4;
font-family: var(--mono);
font-size: 11px;
color: var(--text-secondary);
font-variant-numeric: tabular-nums;
}
.rubric-group-empty {
grid-column: 1 / -1;
padding: 16px;
border: 1px dashed var(--border);
border-radius: 4px;
text-align: center;
font-size: 11px;
color: var(--text-secondary);
}
.rubric-group-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
gap: 12px;
}
.rubric-tile {
display: flex;
flex-direction: column;
gap: 8px;
align-items: stretch;
text-align: left;
padding: 12px;
min-height: 96px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease;
}
.rubric-tile:hover { background: var(--surface-hover); border-color: var(--border-strong); }
.rubric-tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rubric-tile-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
}
.rubric-tile-name { font-weight: 600; font-size: 13px; line-height: 1.2; color: var(--text); }
.rubric-tpl-badge {
display: inline-flex;
align-items: center;
min-height: 16px;
padding: 0 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
background: var(--surface-2);
border: 1px solid var(--border);
white-space: nowrap;
}
.rubric-tile-desc { font-size: 11px; line-height: 1.5; color: var(--text-secondary); }
.rubric-tile-preview {
font-family: var(--mono);
font-size: 11px;
line-height: 1.5;
color: var(--text-secondary);
padding: 8px;
background: var(--surface-2);
border-radius: 4px;
}
.rubric-tile-foot {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
min-height: 24px;
}
.rubric-tile-attach {
display: inline-flex;
align-items: center;
min-height: 20px;
padding: 0 8px;
border: 1px solid var(--accent-soft);
color: var(--accent);
background: var(--accent-soft);
border-radius: 4px;
font-size: 11px;
}
.rubric-fallback {
border-top: 1px dashed var(--border);
padding-top: 16px;
}
.rubric-fallback-head { margin-bottom: 8px; }
.rubric-fallback-title {
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
font-size: 11px;
color: var(--text-secondary);
}
.rubric-fallback-cards {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
@media (max-width: 640px) {
.rubric-fallback-cards { grid-template-columns: 1fr; }
}
.rubric-fallback-card {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
text-align: left;
padding: 16px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
cursor: pointer;
}
.rubric-fallback-card:hover { background: var(--surface-hover); }
.rubric-fallback-icon {
font-family: var(--mono);
font-size: 13px;
color: var(--accent);
width: 24px; height: 24px;
display: flex; align-items: center; justify-content: center;
border-radius: 4px;
background: var(--accent-soft);
}
.rubric-fallback-card-title { font-weight: 600; font-size: 13px; color: var(--text); }
.rubric-fallback-card-desc { font-size: 11px; line-height: 1.5; color: var(--text-secondary); }
/* Rubric Create-from-scratch form — LangSmith "Creating new feedback
config" parity (see docs/design-refs/langsmith-live/round-6/15-*.png).
Inline card rather than an overlay so the fallback CTA context stays
visible; roughly 320px wide (matches the LangSmith popover width). */
.rubric-create-form {
margin-top: 16px;
padding: 16px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
max-width: 360px;
display: flex;
flex-direction: column;
gap: 12px;
}
.rubric-create-head {
display: flex;
align-items: center;
gap: 8px;
}
.rubric-create-back {
font-size: 13px;
padding: 2px 8px;
}
.rubric-create-title { font-weight: 600; font-size: 13px; color: var(--text); }
.rubric-create-body {
display: flex;
flex-direction: column;
gap: 8px;
}
.rubric-create-label {
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-secondary);
font-size: 11px;
margin-top: 4px;
}
.rubric-create-input,
.rubric-create-select {
width: 100%;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface-2);
color: var(--text);
font-size: 12px;
font-family: inherit;
box-sizing: border-box;
}
.rubric-create-input:focus-visible,
.rubric-create-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.rubric-create-tag-row {
display: flex;
align-items: center;
gap: 8px;
}
.rubric-create-color-row {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.rubric-create-color-dot {
width: 16px; height: 16px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
padding: 0;
}
.rubric-create-color-dot.active { border-color: var(--accent); }
.rubric-create-color-dot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rubric-create-type-fields {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 0;
border-top: 1px dashed var(--border);
margin-top: 4px;
}
.rubric-create-hint { margin-top: 4px; }
.rubric-create-actions {
display: flex;
align-items: center;
gap: 8px;
border-top: 1px solid var(--border);
padding-top: 12px;
}
.rubric-create-save {
min-width: 80px;
}
/* Dim type badge — sits next to the dim label in the annotation drawer
so the reader knows which primitive they're scoring against. Chip-
sized, all-caps, sits inline with the label. */
.annotation-dim-type-badge {
margin-left: 6px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-family: var(--mono);
}
.annotation-dim-type-badge[data-dim-type="continuous"] {
background: var(--accent-soft);
color: var(--accent);
}
.annotation-dim-type-badge[data-dim-type="categorical"] {
background: var(--warn-soft);
color: var(--warn);
}
.annotation-dim-type-badge[data-dim-type="boolean"] {
background: var(--ok-soft);
color: var(--ok);
}
/* Categorical/Boolean score buttons — same visual grammar as the
continuous button row so the drawer feels cohesive across primitives.
Wider than the 1-5 continuous buttons so enum text has room to breathe. */
.annotation-dim-score-cat,
.annotation-dim-score-bool {
min-width: 48px;
padding: 0 10px;
}
.annotation-dim-numeric {
align-items: center;
gap: 8px;
}
.annotation-dim-input {
width: 96px;
padding: 4px 6px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface-2);
color: var(--text);
font-size: 12px;
font-family: var(--mono);
}
.annotation-dim-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.annotation-dim-range { font-family: var(--mono); }
/* Rubric picker in the drawer header — a compact select + a running
mix badge that reads "3 continuous · 1 categorical · 1 boolean". */
.annotation-rubric-picker {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 0;
border-top: 1px dashed var(--border);
border-bottom: 1px dashed var(--border);
margin: 6px 0 4px;
}
.annotation-rubric-label {
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 600;
}
.annotation-rubric-select {
flex: 1;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface-2);
color: var(--text);
font-size: 12px;
}
.annotation-rubric-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.annotation-rubric-mix {
font-family: var(--mono);
}
/* Rubric detail drawer — right-anchored, non-modal by design. */
.rubric-detail-backdrop {
position: fixed; inset: 0;
background: transparent;
pointer-events: none;
z-index: 20;
}
.rubric-detail-drawer {
position: fixed;
top: 48px; bottom: 0; right: 0;
width: min(520px, 60vw);
background: var(--surface);
border-left: 1px solid var(--border);
box-shadow: var(--shadow-pop);
z-index: 30;
transform: translateX(100%);
transition: transform 200ms ease;
display: flex;
flex-direction: column;
}
.rubric-detail-drawer.open { transform: translateX(0); }
.rubric-detail-drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--divider);
min-height: 48px;
}
.rubric-detail-drawer-title { font-weight: 600; font-size: 13px; line-height: 1.2; color: var(--text); }
.rubric-detail-drawer-body {
overflow-y: auto;
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.rubric-detail-head { display: flex; flex-direction: column; gap: 4px; }
.rubric-detail-name { font-weight: 600; font-size: 14px; line-height: 1.2; color: var(--text); }
.rubric-detail-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.rubric-detail-desc { font-size: 13px; line-height: 1.5; color: var(--text); }
.rubric-detail-section-title {
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 11px;
line-height: 1.2;
margin-top: 8px;
}
.rubric-checklist {
margin: 0;
padding-left: 16px;
display: flex;
flex-direction: column;
gap: 4px;
font-size: 13px;
line-height: 1.5;
}
.rubric-checklist-item { color: var(--text); }
.rubric-detail-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
padding: 4px 0;
}
.rubric-raw-view {
font-family: var(--mono);
font-size: 11px;
line-height: 1.5;
padding: 12px;
background: var(--surface-2);
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
}
.rubric-raw-editor {
width: 100%;
font-family: var(--mono);
font-size: 11px;
line-height: 1.5;
padding: 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
color: var(--text);
resize: vertical;
}
/* ---- Annotation drawer -------------------------------------------------- */
.annotation-drawer {
position: fixed;
top: 48px; bottom: 0; right: 0;
width: min(560px, 60vw);
background: var(--surface);
border-left: 1px solid var(--border);
box-shadow: var(--shadow-pop);
z-index: 40;
transform: translateX(100%);
transition: transform 200ms ease;
display: flex;
flex-direction: column;
}
.annotation-drawer.open { transform: translateX(0); }
.annotation-drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--divider);
min-height: 48px;
}
.annotation-drawer-title { font-weight: 600; font-size: 13px; line-height: 1.2; color: var(--text); }
.annotation-drawer-body {
overflow-y: auto;
flex: 1;
padding: 16px;
}
/* ---------------------------------------------------------------------------
* Bench page (#187) — researcher experiment platform.
* Everything here is scoped under .pane[data-pane="bench"] semantically; we
* use bench- prefixed classes so no rule bleeds into peer panes.
* -------------------------------------------------------------------------*/
.pane[data-pane="bench"] {
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
/* toolbar (sub-tabs + spacer + row count) */
.bench-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 20px 12px 20px;
border-bottom: 1px solid var(--border);
}
.bench-toolbar-spacer { flex: 1; }
.bench-toolbar-meta { white-space: nowrap; color: var(--text-secondary); font-size: var(--fs-xs); }
/* Chips are ≥24px tall (§7 click-target rule): 4px×2 padding + 16px line-box = 24px. */
.bench-subtab-chip {
padding: 4px 12px;
min-height: 24px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text-secondary);
border-radius: 999px;
font-size: var(--fs-xs);
cursor: pointer;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.bench-subtab-chip:hover { background: var(--surface-hover); }
.bench-subtab-chip.active {
background: var(--accent-soft);
color: var(--accent-strong);
border-color: var(--accent);
}
/* body: list on top, detail underneath (charts-above-table pattern applied
* to the whole page — the L0 table is the "chart-strip", L1 is the detail).
* We use grid rather than flex to give the detail elastic height when a
* cell is drilled. */
.bench-body {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
padding: 12px 20px 24px 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.annotation-drawer-foot {
border-top: 1px solid var(--divider);
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
background: var(--surface-2);
min-height: 32px;
}
.annotation-head-block {
display: flex;
flex-direction: column;
gap: 12px;
padding-bottom: 12px;
border-bottom: 1px dashed var(--border);
}
.annotation-session-id { font-family: var(--mono); font-size: 11px; color: var(--text-secondary); }
.annotation-tag-picker {
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.annotation-tag-picker label { font-size: 11px; color: var(--text-secondary); }
.annotation-tag-picker select {
min-height: 24px;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
color: var(--text);
font-size: 13px;
line-height: 1.2;
}
.annotation-overall { display: flex; flex-direction: column; gap: 8px; }
.annotation-overall-label {
color: var(--text-secondary);
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.annotation-verdict-row { display: flex; gap: 8px; }
.annotation-verdict-btn {
min-height: 24px;
padding: 4px 12px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
color: var(--text);
cursor: pointer;
font-size: 11px;
line-height: 1.2;
text-transform: uppercase;
letter-spacing: 0.06em;
}
.annotation-verdict-btn:hover { background: var(--surface-hover); }
.annotation-verdict-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.annotation-verdict-btn.active[data-verdict="bad"] { background: var(--err-soft); color: var(--err); border-color: var(--err); }
.annotation-verdict-btn.active[data-verdict="ok"] { background: var(--warn-soft); color: var(--warn); border-color: var(--warn); }
.annotation-verdict-btn.active[data-verdict="good"] { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }
.annotation-turns { display: flex; flex-direction: column; gap: 12px; }
.annotation-empty {
padding: 16px;
border: 1px dashed var(--border);
border-radius: 4px;
text-align: center;
font-size: 11px;
color: var(--text-secondary);
}
.annotation-turn-card {
border: 1px solid var(--border);
border-radius: 4px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 8px;
background: var(--surface);
}
.annotation-turn-card.focused { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.annotation-turn-head {
display: flex;
align-items: baseline;
gap: 8px;
}
.annotation-turn-idx { font-weight: 600; font-family: var(--mono); font-size: 11px; color: var(--text); }
.annotation-turn-snippet {
color: var(--text-secondary);
font-size: 11px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.annotation-turn-body {
display: grid;
grid-template-columns: minmax(140px, 1fr) 2fr;
gap: 12px;
}
.annotation-prior-feedback {
background: var(--surface-2);
padding: 8px 12px;
border-radius: 4px;
border-left: 2px solid var(--accent);
align-self: start;
}
.annotation-prior-label {
font-weight: 600;
margin-bottom: 4px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 11px;
color: var(--text-secondary);
}
.annotation-prior-text {
margin: 0;
font-size: 11px;
line-height: 1.5;
color: var(--text);
font-style: italic;
}
.annotation-dims { display: flex; flex-direction: column; gap: 4px; }
.annotation-dim-row {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
min-height: 32px;
transition: background 100ms ease;
}
.annotation-dim-row.focused { background: var(--accent-soft); }
.annotation-dim-label { font-size: 11px; color: var(--text); }
.annotation-dim-scores { display: flex; gap: 4px; }
.annotation-dim-score {
width: 24px; height: 24px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--surface);
color: var(--text-secondary);
cursor: pointer;
font-family: var(--mono);
font-size: 11px;
line-height: 1;
}
.annotation-dim-score:hover { background: var(--surface-hover); }
.annotation-dim-score:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.annotation-dim-score.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.annotation-keymap {
display: flex;
flex-wrap: wrap;
gap: 4px;
align-items: center;
font-size: 11px;
color: var(--text-secondary);
}
.annotation-keymap kbd {
display: inline-block;
padding: 0 4px;
min-width: 16px;
text-align: center;
border: 1px solid var(--border);
border-bottom-width: 2px;
border-radius: 4px;
background: var(--surface);
font-family: var(--mono);
font-size: 11px;
line-height: 1.2;
color: var(--text);
}
.annotation-completeness .chip {
display: inline-flex;
align-items: center;
min-height: 20px;
padding: 0 8px;
border-radius: 4px;
border: 1px solid var(--border);
background: var(--surface);
font-size: 11px;
color: var(--text-secondary);
}
.annotation-completeness .chip.ok { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }
/* Row chip on Recent list — "rated N/M". */
.annotation-row-chip {
display: inline-flex;
align-items: center;
min-height: 16px;
margin-left: 8px;
padding: 0 8px;
border-radius: 4px;
background: var(--surface-2);
border: 1px solid var(--border);
color: var(--text-secondary);
font-family: var(--mono);
font-size: 11px;
line-height: 1.2;
}
/* ---- Export drawer ----------------------------------------------------- */
.export-drawer {
position: fixed;
top: 48px; bottom: 0; right: 0;
width: min(560px, 60vw);
background: var(--surface);
border-left: 1px solid var(--border);
box-shadow: var(--shadow-pop);
z-index: 45;
transform: translateX(100%);
transition: transform 200ms ease;
display: flex;
flex-direction: column;
}
.export-drawer.open { transform: translateX(0); }
.export-drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border-bottom: 1px solid var(--divider);
min-height: 48px;
}
.export-drawer-title { font-weight: 600; font-size: 13px; line-height: 1.2; color: var(--text); }
.export-drawer-body {
overflow-y: auto;
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.export-format-row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; font-size: 11px; color: var(--text); }
.export-format-row label { display: inline-flex; align-items: center; gap: 4px; min-height: 24px; cursor: pointer; }
.export-format-row input[type="radio"] { min-width: 16px; min-height: 16px; margin: 0; accent-color: var(--accent); }
.export-session-list {
display: flex;
flex-direction: column;
gap: 4px;
border: 1px solid var(--border);
border-radius: 4px;
padding: 8px;
max-height: 200px;
overflow-y: auto;
}
.export-session-row {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 8px;
align-items: center;
padding: 4px 8px;
border-radius: 4px;
min-height: 24px;
cursor: pointer;
font-size: 11px;
color: var(--text);
}
.export-session-row:hover { background: var(--surface-hover); }
.export-session-row.annotated { border-left: 2px solid var(--ok); padding-left: 6px; }
.export-session-row input[type="checkbox"] { min-width: 16px; min-height: 16px; margin: 0; accent-color: var(--accent); }
.export-stats { font-family: var(--mono); font-size: 11px; color: var(--text-secondary); }
.export-preview {
font-family: var(--mono);
font-size: 11px;
line-height: 1.5;
padding: 12px;
background: var(--surface-2);
border-radius: 4px;
max-height: 200px;
overflow: auto;
white-space: pre-wrap;
word-break: break-all;
color: var(--text);
}
.export-actions { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.rubrics-sidebar-note {
padding: 8px 12px;
font-size: 11px;
line-height: 1.5;
color: var(--text-secondary);
}
* Hub page (#186 + #190)
*
* The extensibility-first asset catalog. Plugins are the hero
* section; the six other kinds paint below at the same density.
* All rows are single-line-height L0; the follow-up preview /
* editor / versions panels open below the row on demand.
* ============================================================ */
/* ============================================================
* §7 visual-craft baseline (density-layering-spec.md, af9ff14):
* - Spacing tokens ONLY {4,8,12,16,24,32}. Row padding 8px 12px,
* in-card gap 8px, section gap 24px, page pad 16px 24px.
* - Typography ladder ≤3 sizes: page title 20/600 (inherited from
* .page-title), body/label 13-14, meta/chips 11-12. No 10px.
* - Mono ONLY for wire data — filenames, versions, JSON, code.
* Unicode-symbol glyphs use the default sans stack.
* - Click targets ≥24px on every clickable; :hover only on
* clickables. One accent (--accent) per view; status = ok/error.
* ============================================================ */
.pane[data-pane="hub"] { display: flex; flex-direction: column; min-height: 0; }
.hub-scroll { overflow-y: auto; padding: 16px 24px 32px; display: flex; flex-direction: column; gap: 16px; }
.hub-page-head { display: flex; align-items: flex-start; gap: 16px; }
.hub-page-lead { flex: 1; }
.hub-page-actions { display: flex; gap: 8px; }
.hub-sections { display: flex; flex-direction: column; gap: 24px; }
.hub-section { border: 1px solid var(--border); border-radius: 6px; background: var(--bg); }
.hub-section-head {
display: flex; align-items: center; gap: 8px;
padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.hub-section-glyph {
width: 24px; height: 24px; border-radius: 4px;
display: inline-flex; align-items: center; justify-content: center;
background: var(--bg-elev-2); color: var(--text-secondary);
font-size: 12px; font-weight: 600;
}
.hub-section-lead { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.hub-section-title { font-weight: 600; font-size: 13px; display: flex; align-items: center; gap: 8px; }
.hub-section-sub { font-size: var(--fs-xs); }
.hub-section-actions { display: flex; gap: 8px; }
.hub-count-chip {
display: inline-block; min-width: 24px; text-align: center;
padding: 0 8px; line-height: 16px; border-radius: 999px;
background: var(--bg-elev-2); color: var(--text-secondary);
font-size: 11px; font-weight: 500;
}
.hub-section-empty { opacity: 0.9; }
.hub-empty { padding: 12px; font-size: var(--fs-xs); }
.hub-rows { list-style: none; margin: 0; padding: 0; }
.hub-row { border-bottom: 1px solid var(--border); }
.hub-row:last-child { border-bottom: none; }
.hub-row-line {
display: flex; align-items: center; gap: 8px;
padding: 8px 12px; min-height: 32px;
}
.hub-row-line:hover { background: var(--bg-elev-2); }
.hub-row-glyph {
width: 16px; text-align: center;
color: var(--text-tertiary); font-size: 12px;
}
.hub-row-name {
font-weight: 500; font-size: 13px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
max-width: 240px;
}
.hub-row-chips { display: flex; gap: 4px; flex: 1; flex-wrap: wrap; align-items: center; }
.hub-row-actions { display: flex; gap: 4px; }
.hub-row-actions button { padding: 4px 8px; font-size: 11px; min-height: 24px; }
.hub-chip {
display: inline-flex; align-items: center;
padding: 0 8px; height: 18px; border-radius: 3px;
font-size: 11px; font-weight: 500;
background: var(--bg-elev-2); color: var(--text-secondary);
border: 1px solid transparent;
}
.hub-chip-kind { text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; }
.hub-chip-version { color: var(--text-tertiary); font-family: var(--mono, monospace); }
.hub-chip-user { color: var(--accent); border-color: var(--accent-soft); }
.hub-chip-rows { color: var(--text-secondary); }
.hub-chip-lang { color: var(--text-secondary); }
.hub-chip-status.hub-chip-ok { color: var(--ok); }
.hub-chip-status.hub-chip-error { color: var(--error); }
.hub-chip-runtime.hub-chip-active { color: var(--ok); }
.hub-chip-runtime.hub-chip-failed { color: var(--error); }
.hub-chip-runtime.hub-chip-pending { color: var(--text-secondary); }
.hub-chip-runtime.hub-chip-unknown { color: var(--text-tertiary); }
.hub-chip-column { color: var(--text-secondary); }
.hub-row-follow { padding: 12px; background: var(--bg-elev); border-top: 1px dashed var(--border); }
.hub-preview-body {
margin: 0; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border);
border-radius: 4px; max-height: 260px; overflow: auto;
font-size: 12px; line-height: 1.5;
white-space: pre-wrap; word-break: break-word;
}
.hub-preview-meta { font-size: var(--fs-xs); margin-bottom: 8px; }
.hub-preview-title { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
.hub-dataset-rows { display: flex; flex-direction: column; gap: 8px; }
.hub-dataset-row {
display: grid; grid-template-columns: 64px minmax(0, 1fr);
gap: 4px 12px; padding: 8px 12px;
background: var(--bg); border: 1px solid var(--border); border-radius: 4px;
}
.hub-dataset-row > .muted { grid-row: span 3; align-self: center; font-size: 11px; }
.hub-preview-cell { display: flex; gap: 8px; font-size: 12px; align-items: baseline; }
.hub-preview-cell-key { color: var(--text-tertiary); width: 120px; flex-shrink: 0; font-family: var(--mono, monospace); font-size: 11px; }
.hub-preview-cell-val { color: var(--text); font-family: var(--mono, monospace); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.hub-editor { display: flex; flex-direction: column; gap: 8px; }
.hub-editor-head { display: flex; align-items: center; gap: 8px; }
.hub-editor-title { flex: 1; font-weight: 600; font-size: 13px; }
.hub-editor-actions { display: flex; gap: 8px; }
.hub-editor-body {
width: 100%; min-height: 240px;
padding: 8px 12px; background: var(--bg); color: var(--text);
border: 1px solid var(--border); border-radius: 4px;
font-family: var(--mono, monospace); font-size: 12px; line-height: 1.5;
resize: vertical;
}
.hub-editor-note { font-size: var(--fs-xs); }
.hub-inline-note { padding: 8px 12px; font-size: var(--fs-xs); }
.hub-error { padding: 8px 12px; color: var(--error); font-size: var(--fs-xs); }
.hub-versions-title { font-size: var(--fs-xs); margin-bottom: 8px; }
.hub-versions-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.hub-version-row {
display: flex; align-items: center; gap: 8px;
padding: 4px 8px; min-height: 24px;
background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
}
.hub-version-label { font-family: var(--mono, monospace); font-size: 11px; }
.hub-version-mtime { font-size: 11px; margin-left: auto; }
.hub-version-preview-target { margin-top: 8px; }
.hub-sdk-legend { border-top: 1px solid var(--border); padding: 12px 4px 0; }
.hub-sdk-title { font-size: var(--fs-xs); margin-bottom: 8px; }
.hub-sdk-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; font-size: 11px; }
.hub-sdk-list code { font-family: var(--mono, monospace); background: var(--bg-elev-2); padding: 0 4px; border-radius: 3px; }
.hub-legend-badge {
display: inline-block; margin-left: 8px; padding: 0 8px; border-radius: 3px;
font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px;
line-height: 16px;
}
.hub-legend-badge.wire { color: var(--ok); background: var(--ok-soft); }
.hub-legend-badge.file { color: var(--text-tertiary); background: var(--bg-elev-2); }
/* --- Script-run modal --------------------------------------- */
.hub-run-modal {
position: fixed; inset: 0; z-index: 1000;
display: flex; align-items: center; justify-content: center;
}
.hub-run-scrim { position: absolute; inset: 0; background: rgba(0,0,0,0.5); }
.hub-run-card {
position: relative; z-index: 1;
width: 640px; max-width: 92vw; max-height: 88vh;
display: flex; flex-direction: column;
background: var(--bg); border: 1px solid var(--border-strong); border-radius: 8px;
box-shadow: 0 8px 32px rgba(0,0,0,0.24);
overflow: hidden;
}
.hub-run-head {
display: flex; align-items: center; gap: 8px;
padding: 8px 12px; border-bottom: 1px solid var(--border);
}
.hub-run-title { flex: 1; font-weight: 600; font-size: 13px; }
.hub-run-title code { font-family: var(--mono, monospace); background: var(--bg-elev-2); padding: 0 4px; border-radius: 3px; }
.hub-run-body { padding: 12px; display: flex; flex-direction: column; gap: 8px; overflow-y: auto; }
.hub-run-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); }
.hub-run-tab {
padding: 8px 12px; background: transparent; border: none; cursor: pointer;
color: var(--text-secondary); font-size: 12px; min-height: 32px;
border-bottom: 2px solid transparent;
}
.hub-run-tab:hover { color: var(--text); background: var(--bg-elev-2); }
.hub-run-tab.active { color: var(--text); border-bottom-color: var(--accent); }
.hub-run-tab-body { padding: 8px 0; }
.hub-run-tab-body select, .hub-run-tab-body input, .hub-run-tab-body textarea {
width: 100%; padding: 8px; background: var(--bg); color: var(--text);
border: 1px solid var(--border); border-radius: 3px; font-size: 12px;
}
.hub-run-security-note { font-size: 11px; padding: 8px 12px; background: var(--bg-elev-2); border-radius: 3px; }
.hub-run-actions { display: flex; gap: 8px; justify-content: flex-end; }
.hub-run-output {
padding: 12px; border-top: 1px solid var(--border);
display: flex; flex-direction: column; gap: 8px;
background: var(--bg-elev);
}
.hub-run-status { font-size: 12px; font-weight: 500; color: var(--text-secondary); }
.hub-run-stdout, .hub-run-stderr {
margin: 0; padding: 8px 12px; max-height: 200px; overflow: auto;
background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
font-family: var(--mono, monospace); font-size: 11px; line-height: 1.5;
white-space: pre-wrap; word-break: break-word;
}
.hub-run-stderr { color: var(--error); }
.hub-run-summary { padding: 8px 12px; background: var(--ok-soft); border-radius: 3px; font-size: 12px; }
.hub-run-summary strong { color: var(--ok); }
.hub-run-summary code { font-family: var(--mono, monospace); font-size: 11px; }
/* task #201 / trace-viz §4d: turn-flow glyph — inline "shape of this turn"
chip that lives in the turn footer, left of the fused pill row.
Palette: llm=violet, tool=teal, subagent=amber, compact=orange, error=red.
Waiver (dataviz color rule): these five kinds are not a categorical
data palette — they're semantic status tokens (loop-step type), so
picking distinct saturated hues per kind is the intended read. Neutrals
would flatten the very signal the glyph is supposed to carry. Color
sourced from the neutral-token set + status hues already used by
compact-badge / subagent-rail / error variants elsewhere in the shell. */
/* Kind tokens — semantic status hues (not a categorical data palette), so
these are named per-kind rather than reused from the neutral swatch. Kept
here rather than in :root because they are used by exactly one component;
dark theme override lives immediately below. Spec §7 "tokens only": kind
fills below reference these vars — no hex leaks out of this block. */
.turn-flow-glyph {
--glyph-llm: #7c6cf4;
--glyph-tool: #0aa19c;
--glyph-subagent: #d68a26;
--glyph-compact: #d4711a;
--glyph-error: var(--err, #dc2626);
--glyph-line: var(--muted, #6b6b70);
--glyph-stroke: var(--surface, #ffffff);
display: inline-block;
/* Baseline-align inside the footer flex row (`align-items: baseline`).
`text-bottom` puts the glyph bottom on the text bottom of the 11px mono
footer copy, which reads as "sits on the same baseline as the numbers
to the right" without introducing a new alignment mode. */
vertical-align: text-bottom;
margin-right: 8px;
cursor: pointer;
flex: 0 0 auto;
border-radius: 4px; /* 4px grid */
transition: background-color 120ms ease;
}
.turn-flow-glyph:hover {
/* Visible :hover on the one clickable in the footer row (§7 rule:
everything clickable has hover; nothing non-clickable does). Uses the
shell's canonical subtle-hover token so it matches every other row. */
background-color: var(--surface-hover, rgba(0, 0, 0, 0.045));
}
.turn-flow-glyph:focus-visible {
outline: 2px solid var(--accent, #2563eb);
outline-offset: 4px; /* 4px grid */
}
:root[data-theme="dark"] .turn-flow-glyph {
--glyph-llm: #a190ff;
--glyph-tool: #22c9c4;
--glyph-subagent: #f0a63c;
--glyph-compact: #ee8a34;
--glyph-error: var(--err, #ef4444);
--glyph-line: var(--muted, #a1a1a8);
--glyph-stroke: var(--surface, #17171a);
}
.turn-flow-glyph-line {
stroke: var(--glyph-line);
stroke-width: 1;
stroke-linecap: round;
opacity: 0.55;
}
.turn-flow-glyph-dot {
stroke: var(--glyph-stroke);
stroke-width: 0.5;
}
.turn-flow-glyph-dot.kind-llm { fill: var(--glyph-llm); }
.turn-flow-glyph-dot.kind-tool { fill: var(--glyph-tool); }
.turn-flow-glyph-dot.kind-subagent { fill: var(--glyph-subagent); }
.turn-flow-glyph-dot.kind-compact { fill: var(--glyph-compact); }
.turn-flow-glyph-dot.kind-error { fill: var(--glyph-error); }
.bench-list { flex: 0 0 auto; }
.bench-detail { flex: 1 1 auto; min-width: 0; }
/* L0 experiments table
*
* At narrow widths (≤ 800px) the fixed pixel + 1fr grid columns can't
* shrink below their content min, so rows overflow their card. Rather
* than repack the grid (chip/name/latency stay readable at their
* current widths), the outer card becomes an overflow-x: auto scroller
* and each row is pinned to a readable min-width — researcher-table
* horizontal-scroll idiom. Y clipping preserves the border-radius edge. */
.bench-table {
display: flex;
flex-direction: column;
border: 1px solid var(--border);
border-radius: 8px;
overflow-x: auto;
overflow-y: hidden;
background: var(--surface);
}
.bench-row {
display: grid;
grid-template-columns: 60px 1.8fr 84px 80px 1fr 1fr 84px 100px;
align-items: center;
gap: 12px;
padding: 8px 12px;
min-height: 32px;
min-width: 620px;
border-bottom: 1px solid var(--border);
font-size: var(--fs-xs);
text-align: left;
}
.bench-row:last-child { border-bottom: 0; }
.bench-row-head {
background: var(--bg-elev);
color: var(--text-secondary);
font-weight: 500;
font-size: var(--fs-micro);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.bench-row-data {
cursor: pointer;
background: transparent;
border: 0;
border-bottom: 1px solid var(--border);
color: inherit;
font: inherit;
transition: background 0.1s;
}
.bench-row-data:hover { background: var(--surface-hover); }
.bench-row-data.selected {
background: var(--accent-soft);
outline: 1px solid var(--accent);
outline-offset: -1px;
}
.bench-row-name { font-weight: 500; color: var(--text); }
.bench-col-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Numeric columns are right-aligned + tabular-nums (§7 alignment rule). */
.bench-col-metric,
.bench-col-lat,
.bench-col-progress {
font-variant-numeric: tabular-nums;
text-align: right;
justify-self: end;
}
.bench-col-created { font-size: var(--fs-micro); color: var(--text-secondary); }
.bench-kind-chip {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: var(--fs-micro);
font-weight: 500;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--text);
background: var(--bg-elev-2);
border: 1px solid var(--border);
}
.bench-kind-matrix { background: var(--accent-soft); color: var(--accent-strong); border-color: var(--accent); }
.bench-kind-ab { background: var(--warn-soft); color: var(--warn); border-color: var(--warn); }
.bench-kind-rep { background: var(--ok-soft); color: var(--ok); border-color: var(--ok); }
.bench-status {
display: inline-block;
padding: 2px 6px;
border-radius: 4px;
font-size: var(--fs-micro);
text-transform: capitalize;
}
.bench-status-running { color: var(--warn); }
.bench-status-running::before {
content: '● ';
color: var(--warn);
animation: bench-pulse 1.6s ease-in-out infinite;
}
.bench-status-done { color: var(--ok); }
.bench-status-done::before { content: '● '; }
.bench-status-queued { color: var(--text-tertiary); }
.bench-status-queued::before { content: '○ '; }
.bench-status-error { color: var(--err); }
.bench-status-error::before { content: '! '; font-weight: 700; }
@keyframes bench-pulse {
0%,100% { opacity: 1 }
50% { opacity: 0.45 }
}
/* Latency tri-tint (§5.4 panel-quantile) — status-only color via tokens. */
.bench-lat {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-variant-numeric: tabular-nums;
font-size: var(--fs-xs);
}
.bench-lat-fast { background: var(--ok-soft); color: var(--ok); }
.bench-lat-normal { background: var(--bg-elev-2); color: var(--text-secondary); }
.bench-lat-slow { background: var(--warn-soft); color: var(--warn); }
.bench-lat-neutral { color: var(--text-secondary); }
/* empty state */
.bench-empty {
padding: 32px 24px;
text-align: center;
border: 1px dashed var(--border);
border-radius: 8px;
background: var(--surface);
}
.bench-empty-title { font-weight: 500; font-size: var(--fs-body); margin-bottom: 8px; }
.bench-empty-body { max-width: 460px; margin: 0 auto 16px auto; line-height: 1.5; color: var(--text-secondary); font-size: var(--fs-xs); }
.bench-empty-actions { display: flex; gap: 8px; justify-content: center; }
/* L1 detail — the pane below the list */
.bench-detail-hint {
padding: 24px;
text-align: center;
border: 1px dashed var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-size: var(--fs-xs);
}
.bench-detail-header {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid var(--border);
}
.bench-detail-title { display: flex; align-items: center; gap: 12px; flex: 1; min-width: 0; }
.bench-detail-name { font-weight: 500; font-size: var(--fs-body); }
.bench-detail-actions { display: flex; gap: 8px; }
.bench-detail-sdk {
padding: 8px 0 12px 0;
font-size: var(--fs-micro);
color: var(--text-secondary);
}
.bench-sdk-legend {
display: inline-block;
padding: 2px 8px;
background: var(--bg-elev-2);
border-radius: 4px;
border: 1px dashed var(--border);
color: var(--text-secondary);
}
.bench-inline-banner {
padding: 8px 12px;
margin-bottom: 12px;
background: var(--accent-soft);
border: 1px solid var(--accent);
border-radius: 6px;
color: var(--text);
font-size: var(--fs-xs);
line-height: 1.5;
}
/* charts-above-table strip: three mini-charts in a row */
.bench-charts {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.bench-chart {
border: 1px solid var(--border);
border-radius: 6px;
padding: 12px;
background: var(--surface);
min-height: 108px;
}
.bench-chart-title {
font-size: var(--fs-micro);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 8px;
}
.bench-chart-empty { padding: 16px 0; text-align: center; color: var(--text-tertiary); font-size: var(--fs-xs); }
.bench-bars-wrap { display: flex; flex-direction: column; gap: 8px; }
.bench-bars {
display: flex;
gap: 12px;
align-items: flex-end;
padding: 4px 0 0 0;
}
.bench-bar-group {
flex: 1 1 0;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.bench-bar-pair {
display: flex;
align-items: flex-end;
gap: 4px;
justify-content: center;
}
.bench-bar {
width: 12px;
min-height: 2px;
border-radius: 2px 2px 0 0;
}
.bench-bar-primary { background: var(--accent); }
.bench-bar-secondary { background: var(--accent-soft); border: 1px solid var(--accent); }
.bench-bar-label {
font-size: var(--fs-micro);
color: var(--text-tertiary);
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
font-variant-numeric: tabular-nums;
}
.bench-bars-legend {
display: flex;
align-items: center;
gap: 8px;
font-size: var(--fs-micro);
color: var(--text-secondary);
}
.bench-legend-swatch {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 2px;
margin-right: 4px;
vertical-align: middle;
}
.bench-legend-primary { background: var(--accent); }
.bench-legend-secondary { background: var(--accent-soft); border: 1px solid var(--accent); }
/* SVG charts — token-only fills, no raw hex. */
.bench-svg { width: 100%; height: 68px; }
.bench-svg-axis { stroke: var(--border-strong); stroke-width: 1; }
.bench-svg-tick { fill: var(--text-tertiary); font-size: 8px; font-variant-numeric: tabular-nums; }
.bench-hist-bar { fill: var(--accent); }
.bench-box-box { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1; }
.bench-box-median { stroke: var(--accent-strong); stroke-width: 2; }
.bench-stack-resolved { fill: var(--ok); }
.bench-stack-unresolved { fill: var(--warn); fill-opacity: 0.65; }
/* Kind A: matrix grid */
.bench-grid-wrap { margin: 12px 0 4px 0; overflow-x: auto; }
.bench-grid {
display: grid;
gap: 0;
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
min-width: 480px;
}
.bench-grid-h {
padding: 8px 12px;
min-height: 32px;
background: var(--bg-elev);
color: var(--text-secondary);
font-size: var(--fs-micro);
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.03em;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
text-align: right; /* metrics columns are numeric */
}
.bench-grid-h:first-child { text-align: left; }
.bench-grid-h:last-child { border-right: 0; }
.bench-grid-h-row {
background: var(--surface);
color: var(--text);
text-transform: none;
letter-spacing: 0;
font-weight: 500;
font-size: var(--fs-xs);
text-align: left;
}
.bench-grid-corner { background: var(--bg-elev-2); }
.bench-grid-row { display: contents; }
/* Data cells: right-align numeric run of [n/N ✓ score] the way DSBench does. */
.bench-grid-cell {
padding: 8px 12px;
min-height: 32px;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
font-size: var(--fs-xs);
font-variant-numeric: tabular-nums;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
background: var(--surface);
cursor: pointer;
border-top: 0;
border-left: 0;
color: var(--text);
text-align: right;
}
.bench-grid-cell:last-child { border-right: 0; }
.bench-grid-cell:hover { background: var(--surface-hover); }
.bench-cell-empty,
.bench-cell-queued,
.bench-cell-running {
cursor: default;
color: var(--text-tertiary);
justify-content: center;
}
.bench-cell-running { color: var(--warn); }
.bench-cell-nn { font-weight: 500; }
.bench-cell-glyph { color: var(--accent-strong); }
.bench-cell-score { color: var(--text-secondary); font-size: var(--fs-micro); }
.bench-cell-tint-fast { background: var(--ok-soft); }
.bench-cell-tint-slow { background: var(--err-soft); }
.bench-cell-tint-normal { }
.bench-grid-total {
padding: 8px 12px;
border-right: 1px solid var(--border);
background: var(--bg-elev);
font-size: var(--fs-micro);
font-variant-numeric: tabular-nums;
text-align: right;
}
.bench-grid-total:last-child { border-right: 0; }
.bench-total-line { display: flex; justify-content: space-between; gap: 8px; }
.bench-total-line + .bench-total-line { margin-top: 4px; }
.bench-grid-total-lead { background: var(--bg-elev); font-size: var(--fs-micro); text-align: left; color: var(--text-secondary); }
/* Kind B: A/B table */
.bench-ab { margin: 12px 0; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--surface); }
.bench-ab-head,
.bench-ab-row {
display: grid;
grid-template-columns: 1.6fr 1fr 1fr 1fr 1fr 1fr;
gap: 12px;
padding: 8px 12px;
min-height: 32px;
border-bottom: 1px solid var(--border);
font-size: var(--fs-xs);
align-items: center;
text-align: left;
}
.bench-ab-row {
cursor: pointer;
background: transparent;
border-left: 0;
border-right: 0;
border-top: 0;
color: inherit;
font: inherit;
}
.bench-ab-row:hover { background: var(--surface-hover); }
.bench-ab-head {
background: var(--bg-elev);
color: var(--text-secondary);
font-size: var(--fs-micro);
text-transform: uppercase;
letter-spacing: 0.03em;
}
/* Numeric A/B cells (score/verdict/latency/tokens/Δ) right-align to a shared edge. */
.bench-ab-cell {
font-variant-numeric: tabular-nums;
text-align: right;
justify-self: end;
}
.bench-ab-head > .bench-ab-cell { text-align: right; }
.bench-ab-delta-up { color: var(--ok); font-weight: 500; }
.bench-ab-delta-down { color: var(--err); font-weight: 500; }
.bench-ab-delta-flat { color: var(--text-tertiary); }
.bench-ab-foot { padding: 8px 12px; border-top: 1px solid var(--border); color: var(--text-secondary); font-size: var(--fs-xs); }
/* Kind C: repetition table + list */
.bench-rep { margin: 12px 0; display: flex; flex-direction: column; gap: 12px; }
.bench-rep-table {
display: grid;
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
background: var(--surface);
}
.bench-rep-row { display: contents; }
.bench-rep-row > * {
padding: 8px 12px;
min-height: 32px;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
font-size: var(--fs-xs);
font-variant-numeric: tabular-nums;
text-align: right; /* numeric grid: right-align numbers */
background: var(--surface);
display: flex;
align-items: center;
justify-content: flex-end;
}
.bench-rep-row > *:first-child { text-align: left; justify-content: flex-start; }
.bench-rep-row > *:last-child { border-right: 0; }
.bench-rep-head > * {
background: var(--bg-elev);
color: var(--text-secondary);
font-size: var(--fs-micro);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.bench-rep-avg { font-weight: 500; background: var(--bg-elev-2); }
.bench-rep-cell-pass { color: var(--ok); }
.bench-rep-cell-fail { color: var(--err); }
.bench-rep-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.bench-rep-list-row {
display: grid;
grid-template-columns: 40px 24px 60px 80px auto;
gap: 12px;
align-items: center;
padding: 8px 12px;
min-height: 32px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: var(--fs-xs);
font-variant-numeric: tabular-nums;
background: var(--surface);
cursor: pointer;
color: inherit;
text-align: left;
}
.bench-rep-list-row:hover { background: var(--surface-hover); }
.bench-rep-list-idx { font-weight: 500; color: var(--text-secondary); }
.bench-rep-list-glyph { text-align: center; }
/* config recap */
.bench-config-recap {
margin-top: 16px;
border-top: 1px solid var(--border);
padding-top: 12px;
}
.bench-config-recap > summary { cursor: pointer; padding: 4px 0; min-height: 24px; font-size: var(--fs-xs); color: var(--text-secondary); }
.bench-config-body {
padding: 8px 0;
color: var(--text-secondary);
font-size: var(--fs-xs);
line-height: 1.5;
}
/* cell drill (L2 stub) */
.bench-drill {
margin-top: 16px;
border: 1px solid var(--accent);
border-radius: 6px;
background: var(--accent-soft);
padding: 12px;
}
.bench-drill-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; gap: 12px; flex-wrap: wrap; }
.bench-drill-title { font-weight: 500; font-size: var(--fs-xs); }
.bench-drill-actions { font-size: var(--fs-micro); color: var(--text-secondary); }
/* Only wire data uses mono (§7). `sessionId` is wire data — legitimate. */
.bench-drill-actions code {
background: var(--surface);
padding: 2px 4px;
border-radius: 4px;
font-family: var(--mono);
font-size: var(--fs-micro);
}
.bench-drill-body { display: flex; flex-direction: column; gap: 8px; }
.bench-drill-runs { display: flex; flex-direction: column; gap: 4px; }
.bench-drill-ab { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.bench-drill-ab-h { font-weight: 500; margin-bottom: 4px; font-size: var(--fs-xs); }
.bench-run-line {
display: flex;
gap: 12px;
align-items: center;
padding: 8px 12px;
min-height: 24px;
background: var(--surface);
border-radius: 4px;
font-size: var(--fs-xs);
font-variant-numeric: tabular-nums;
}
.bench-run-ok { border-left: 3px solid var(--ok); }
.bench-run-fail { border-left: 3px solid var(--err); }
.bench-run-tag { font-weight: 500; color: var(--text-secondary); }
.bench-run-glyph { color: var(--accent-strong); }
.bench-run-score { font-weight: 500; }
.bench-run-reason { margin-left: auto; font-style: italic; color: var(--text-secondary); }
.bench-drill-code-result {
border-top: 1px solid var(--border);
padding-top: 8px;
}
.bench-drill-code-result > summary { cursor: pointer; padding: 4px 0; min-height: 24px; font-size: var(--fs-xs); color: var(--text-secondary); }
/* Mono is reserved for wire JSON (`code_result.json` block). */
.bench-code {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
padding: 8px 12px;
font-size: var(--fs-micro);
overflow-x: auto;
font-family: var(--mono);
}
/* new-experiment kind-picker modal */
.bench-modal-scrim {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.35);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.bench-modal {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
min-width: 620px;
max-width: 90vw;
padding: 0;
box-shadow: var(--shadow-pop);
}
.bench-modal-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
border-bottom: 1px solid var(--border);
}
.bench-modal-title { font-weight: 500; font-size: var(--fs-body); }
.bench-kind-cards {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
padding: 20px;
}
.bench-kind-card {
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px;
min-height: 24px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
cursor: pointer;
text-align: left;
transition: border-color 0.12s, background 0.12s;
color: inherit;
font: inherit;
}
.bench-kind-card:hover {
border-color: var(--accent);
background: var(--accent-soft);
}
.bench-kind-card-title { font-weight: 500; font-size: var(--fs-body); }
/* Body-copy sub-label — NOT wire data — must not be mono (§7). */
.bench-kind-card-sub { font-size: var(--fs-xs); color: var(--text-secondary); }
.bench-kind-card-body { line-height: 1.5; font-size: var(--fs-xs); color: var(--text-secondary); }
.bench-modal-foot { padding: 12px 20px; border-top: 1px solid var(--border); color: var(--text-secondary); font-size: var(--fs-xs); }
/* -------------------------------------------------------------------- *
* Context page (#185, #179-A) — per-turn ledger of shadowing / inject /
* recall / compact events. Layout mirrors the growth-body two-panel
* card shell so the researcher's eye doesn't have to re-learn the
* chrome. The L0 row is the "identity + 3 counters + budget + spark"
* budget the density-spec §2 defines, kept to one line by design.
* -------------------------------------------------------------------- */
/* §7 compliance: fonts collapsed to 3-size ladder (14/12/11 = --fs-body /
* --fs-xs / --fs-micro), spacing on 4px grid (only 4/8/12/16/24/32 via
* --space-*), status/tint hex removed in favour of --ok / --warn / --err /
* --tint-* tokens, radii from --radius-sm/--radius/--radius-lg, min-height
* 24px on all click targets, hover only on rows that toggle L1. */
.context-page-body {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: var(--space-6);
padding: var(--space-1) 0;
align-items: start;
}
@media (max-width: 960px) {
.context-page-body { grid-template-columns: minmax(0, 1fr); }
}
/* Empty-state layout (#14, user field report 2026-07-17). When there are no
* turns to render, the two-column skeleton produces (a) a big empty left
* track, (b) the empty card floating in the right slot next to it, and (c)
* the SDK-support card stranded alone below. Collapsing to a single column
* and hiding the empty list container turns the pane into a stacked
* document (empty card → SDK card) that reads as page chrome. Sticky is
* removed on the legend so the two cards flow together. */
.context-page-body.is-empty {
grid-template-columns: minmax(0, 1fr);
}
.context-page-body.is-empty .context-page-legend-card {
position: static;
top: auto;
}
/* --- List of turn rows --- */
.context-page-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
min-width: 0;
}
.context-page-row {
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface-2, var(--bg-elev));
overflow: hidden;
}
.context-page-row[data-open="running"] { border-left: 2px solid var(--accent); }
.context-page-row-summary {
display: grid;
grid-template-columns: 96px minmax(0, 1fr) 56px var(--space-3) auto;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) var(--space-3);
min-height: 32px;
cursor: pointer;
list-style: none;
font-variant-numeric: tabular-nums;
}
.context-page-row-summary::-webkit-details-marker { display: none; }
.context-page-row-summary:hover { background: var(--surface-hover); }
.context-page-row-turn {
font-weight: 600;
color: var(--text);
font-size: var(--fs-xs);
}
.context-page-row-counters {
display: inline-flex;
gap: var(--space-2);
min-width: 0;
}
.context-page-counter {
display: inline-flex;
align-items: baseline;
gap: var(--space-1);
padding: 0 var(--space-2);
min-height: 20px;
line-height: 20px;
border-radius: 999px;
font-size: var(--fs-xs);
color: var(--text);
background: color-mix(in oklab, var(--text) 6%, transparent);
}
.context-page-counter.is-zero { color: var(--muted); opacity: 0.55; }
.context-page-counter--inject { background: color-mix(in oklab, var(--accent) 10%, transparent); }
.context-page-counter--compact { background: color-mix(in oklab, var(--tint-purple) 12%, transparent); }
.context-page-counter--recall { background: color-mix(in oklab, var(--tint-blue) 12%, transparent); }
.context-page-row-budget {
font-size: var(--fs-xs);
color: var(--text);
text-align: right;
font-variant-numeric: tabular-nums;
}
.context-page-row-budget.is-assumed { color: var(--muted); font-style: italic; }
.context-page-row-budget.is-high { color: var(--warn); font-weight: 600; }
.context-page-row-budget.is-critical { color: var(--err); font-weight: 700; }
.context-page-row-spark {
display: inline-block;
width: 6px;
height: 20px;
background: linear-gradient(to top,
color-mix(in oklab, var(--accent) 60%, transparent) var(--pip-h, 20%),
transparent var(--pip-h, 20%));
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
border-bottom: 1px solid var(--border);
}
.context-page-row-actions {
display: inline-flex;
gap: var(--space-2);
justify-self: end;
}
.context-page-row-actions .ghost.small {
padding: 0 var(--space-2);
min-height: 24px;
font-size: var(--fs-micro);
}
.context-page-json-btn {
font-family: var(--mono);
min-height: 24px;
min-width: 24px;
}
/* --- L1 body --- */
.context-page-row-body {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-3) var(--space-4);
padding: var(--space-3) var(--space-3) var(--space-4);
border-top: 1px solid var(--border);
background: color-mix(in oklab, var(--bg) 60%, var(--bg-elev));
}
@media (max-width: 720px) {
.context-page-row-body { grid-template-columns: 1fr; }
}
.context-page-group {
display: flex;
flex-direction: column;
gap: var(--space-2);
min-width: 0;
}
.context-page-group-title {
margin: 0;
font-size: var(--fs-micro);
font-weight: 600;
color: var(--text);
text-transform: uppercase;
letter-spacing: 0.02em;
}
.context-page-group-note {
font-size: var(--fs-micro);
line-height: 1.5;
}
/* Status chips — mirror the SDK-support legend colours */
.context-page-status-chip {
display: inline-flex;
align-items: center;
padding: 0 var(--space-2);
min-height: 20px;
border-radius: 999px;
font-size: var(--fs-micro);
font-weight: 500;
align-self: flex-start;
}
.context-page-status-chip--live {
background: var(--ok-soft);
color: var(--ok);
}
.context-page-status-chip--restart {
background: var(--warn-soft);
color: var(--warn);
}
.context-page-status-chip--pending {
background: color-mix(in oklab, var(--muted) 20%, transparent);
color: var(--muted);
}
/* Shadowing tri-state */
.context-page-tri {
display: inline-flex;
gap: var(--space-1);
padding: var(--space-1);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
align-self: flex-start;
}
.context-page-tri-btn {
padding: 0 var(--space-3);
min-height: 24px;
border-radius: var(--radius-sm);
font-size: var(--fs-xs);
}
.context-page-tri-btn:hover { background: var(--surface-hover); }
.context-page-tri-btn.is-active,
.context-page-tri-btn.is-active:hover {
background: var(--accent);
color: var(--accent-contrast, var(--surface));
}
/* Injection roster rows */
.context-page-roster {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.context-page-roster-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: var(--space-2);
align-items: center;
padding: var(--space-1) var(--space-2);
min-height: 24px;
border-radius: var(--radius-sm);
background: var(--surface-2, var(--bg-elev));
}
.context-page-roster-name { justify-self: start; text-align: left; font-size: var(--fs-xs); }
.context-page-roster-count { font-variant-numeric: tabular-nums; font-size: var(--fs-xs); color: var(--muted); }
.context-page-roster-toggle { display: inline-flex; gap: var(--space-1); align-items: center; font-size: var(--fs-micro); }
/* Recall list */
.context-page-recall-list {
display: flex;
flex-direction: column;
gap: var(--space-1);
}
.context-page-recall-row {
display: grid;
grid-template-columns: auto auto minmax(0, 1fr);
gap: var(--space-2);
align-items: center;
padding: var(--space-1) var(--space-2);
min-height: 24px;
border-radius: var(--radius-sm);
background: var(--surface-2, var(--bg-elev));
min-width: 0;
}
.context-page-recall-name { font-weight: 500; font-size: var(--fs-xs); }
.context-page-recall-count { font-variant-numeric: tabular-nums; color: var(--muted); font-size: var(--fs-xs); }
.context-page-recall-args {
font-family: var(--mono);
font-size: var(--fs-micro);
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
/* Compact policy definition list */
.context-page-policy {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--space-1) var(--space-3);
margin: 0;
font-size: var(--fs-xs);
}
.context-page-policy-key { color: var(--muted); }
.context-page-policy-val { color: var(--text); font-family: var(--mono); word-break: break-all; }
/* --- Empty state --- */
.context-page-empty {
padding: var(--space-8) var(--space-8);
border: 1px dashed var(--border);
border-radius: var(--radius-lg);
text-align: center;
background: color-mix(in oklab, var(--bg) 80%, transparent);
}
.context-page-empty-head {
font-size: var(--fs-body);
font-weight: 600;
color: var(--text);
margin-bottom: var(--space-2);
}
.context-page-empty-note {
max-width: 460px;
margin: 0 auto var(--space-4) auto;
font-size: var(--fs-xs);
line-height: 1.5;
}
.context-page-empty-actions {
display: inline-flex;
gap: var(--space-2);
flex-wrap: wrap;
justify-content: center;
}
.context-page-status {
padding: var(--space-4);
text-align: center;
font-size: var(--fs-xs);
}
/* --- SDK support legend card --- */
.context-page-legend-card {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-4);
background: var(--surface-2, var(--bg-elev));
border: 1px solid var(--border);
border-radius: var(--radius-lg);
position: sticky;
top: var(--space-2);
}
.context-page-legend-title {
margin: 0;
font-size: var(--fs-body);
font-weight: 600;
color: var(--text);
}
.context-page-legend-sub { font-size: var(--fs-micro); line-height: 1.5; }
.context-page-legend {
display: flex;
flex-direction: column;
gap: var(--space-2);
margin-top: var(--space-1);
}
.context-page-legend-row {
display: grid;
grid-template-columns: var(--space-3) 1fr auto;
grid-template-rows: auto auto;
grid-template-areas:
"dot name status"
"dot note note";
gap: var(--space-1) var(--space-2);
align-items: baseline;
padding-bottom: var(--space-2);
border-bottom: 1px solid var(--border);
}
.context-page-legend-row:last-child { border-bottom: none; padding-bottom: 0; }
.context-page-legend-dot {
grid-area: dot;
width: 8px;
height: 8px;
border-radius: 50%;
align-self: center;
margin-top: var(--space-1);
}
.context-page-legend-dot--live { background: var(--ok); }
.context-page-legend-dot--restart-required { background: var(--warn); }
.context-page-legend-dot--upstream-pending { background: var(--muted); }
.context-page-legend-name {
grid-area: name;
font-size: var(--fs-xs);
font-weight: 500;
color: var(--text);
}
.context-page-legend-status {
grid-area: status;
font-family: var(--mono);
font-size: var(--fs-micro);
color: var(--muted);
}
.context-page-legend-note {
grid-area: note;
line-height: 1.5;
font-size: var(--fs-micro);
}
/* ─── Task #203: trace tri-view (Tree | Timeline | Graph) ──────────────
* Chips + panel container that wraps the pre-existing trace card and
* lazily instantiates SVG timeline / SVG node graph siblings. Colors
* come from --tint-* + family palette vars so light/dark theme both
* behave the same as the surrounding shell. */
.trace-tri-view {
display: flex; flex-direction: column; gap: 8px;
margin-top: 6px;
}
.trace-tri-chips {
display: flex; align-items: center; gap: 6px;
flex-wrap: wrap;
}
.trace-tri-chip {
padding: 2px 10px;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
border-radius: 999px;
font: inherit; font-size: 11px;
cursor: pointer;
transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.trace-tri-chip:hover { color: var(--text); border-color: var(--border-strong); }
.trace-tri-chip.active {
color: var(--text);
border-color: var(--accent);
background: var(--accent-soft);
}
.trace-tri-export { margin-left: auto; font-size: 11px; }
/* Task #205: two-column stage — panels on the left, right-side detail
* pane on the right. When no node is selected the detail slot is hidden
* and panels take full width. When has-detail is set, the stage becomes
* a 1fr 320px grid. */
.trace-tri-stage {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 8px;
align-items: stretch;
}
.trace-tri-view.has-detail .trace-tri-stage {
grid-template-columns: minmax(0, 1fr) minmax(280px, 340px);
}
.trace-tri-panels { position: relative; min-width: 0; }
.trace-tri-panel { min-height: 60px; }
.trace-tri-panel[hidden] { display: none !important; }
.trace-tri-stub {
padding: 12px; color: var(--muted); font-size: 12px;
border: 1px dashed var(--border); border-radius: var(--radius-sm);
}
/* Detail pane — LangSmith run-detail parity: header, four tabs, panel
* body. Sits inside .trace-tri-detail (a stage grid cell). */
.trace-tri-detail {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface);
min-width: 0;
display: flex; flex-direction: column;
padding: 12px;
gap: 8px;
user-select: text; /* §205 team-lead: text must be selectable */
}
.trace-tri-detail-close {
align-self: flex-end;
font-size: 11px;
}
.trace-detail-pane { display: flex; flex-direction: column; gap: 8px; }
.trace-detail-head { display: flex; flex-direction: column; gap: 4px; }
.trace-detail-title { font-size: 13px; font-weight: 600; color: var(--text); }
.trace-detail-subtitle { font-size: 11px; }
.trace-detail-tabs {
display: flex; align-items: center; gap: 4px;
border-bottom: 1px solid var(--border);
padding-bottom: 4px;
}
.trace-detail-tab {
padding: 4px 8px;
border: 0;
background: transparent;
color: var(--muted);
font: inherit; font-size: 12px;
cursor: pointer;
border-radius: 4px;
min-height: 24px;
}
.trace-detail-tab:hover { color: var(--text); background: var(--bg-elev); }
.trace-detail-tab.active { color: var(--text); box-shadow: inset 0 -2px 0 0 var(--accent); }
.trace-detail-panels { position: relative; }
.trace-detail-panel[hidden] { display: none !important; }
.trace-detail-panel { display: flex; flex-direction: column; gap: 8px; }
.trace-detail-section-head {
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.04em;
padding-top: 4px;
}
.trace-detail-empty { font-size: 12px; padding: 4px 0; }
.trace-detail-kv { display: flex; flex-direction: column; gap: 4px; }
.trace-detail-kv-row {
display: grid;
grid-template-columns: 120px minmax(0, 1fr);
gap: 8px;
align-items: baseline;
font-size: 12px;
}
.trace-detail-kv-key { color: var(--muted); }
.trace-detail-kv-value { color: var(--text); word-break: break-all; }
.trace-detail-kv-value.absent { color: var(--muted); font-style: italic; }
.trace-detail-message-list { display: flex; flex-direction: column; gap: 8px; }
.trace-detail-message-row,
.trace-detail-output-row {
border-left: 2px solid var(--border);
padding: 4px 8px;
display: flex; flex-direction: column; gap: 4px;
font-size: 12px;
}
.trace-detail-output-row[data-kind="tool-call"] { border-left-color: color-mix(in srgb, var(--tool) 55%, transparent); }
.trace-detail-role {
/* task #34 (2026-07-17): inline titlecase two-part label (dot + word).
* Retires the mono lowercase "role" tag; matches the LangSmith
* detail-pane grammar the user pointed at ("像这样的 UI 就是我们想要
* 的") — small icon + Titlecase word inline with the message body. */
display: inline-flex; align-items: baseline; gap: 4px;
font-size: 12px; color: var(--muted);
line-height: 1.5;
}
.trace-detail-role-glyph { font-family: var(--mono); color: var(--muted); }
.trace-detail-role-label { font-weight: 500; color: var(--muted); }
.trace-detail-message-body { color: var(--text); white-space: pre-wrap; word-break: break-word; }
.trace-detail-tool-calls { display: flex; flex-direction: column; gap: 4px; margin-top: 4px; }
.trace-detail-tool-call {
background: var(--bg-elev);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 4px 8px;
display: flex; flex-direction: column; gap: 4px;
}
.trace-detail-args-summary {
cursor: pointer;
color: var(--text);
padding: 2px 0;
min-height: 24px;
}
.trace-detail-args-pre {
margin: 4px 0 0 0;
padding: 8px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 2px;
max-height: 240px;
overflow: auto;
font-size: 11px;
}
/* task #34 (2026-07-17 team-lead 正面参照): recursive collapsible JSON tree
* — each nested object gets its own `/▸` arrow (native `<details>` marker,
* no reinvented interaction — density-spec §7 uses "<details>/native
* controls over reinvented ones" as a hard rule), scalars get a leading
* typographic dot glyph, leaf values in mono. Density is per-level
* folding — never dropping. Default open depth = 1 (top only). Indentation
* is via padding-inline-start on the children container, so nested arrows
* line up. */
.trace-detail-json-tree {
display: flex; flex-direction: column;
font-size: 12px; line-height: 1.5;
gap: 2px;
}
.trace-detail-json-node { display: block; }
.trace-detail-json-branch { display: block; }
.trace-detail-json-branch > .trace-detail-json-summary {
cursor: pointer;
display: inline-flex; align-items: baseline; gap: 6px;
padding: 2px 4px;
min-height: 20px;
border-radius: 2px;
}
.trace-detail-json-branch > .trace-detail-json-summary:hover {
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.trace-detail-json-key { color: var(--text); font-weight: 500; }
.trace-detail-json-preview { font-size: 11px; }
.trace-detail-json-children {
padding-inline-start: 16px;
border-inline-start: 1px dashed color-mix(in srgb, var(--border) 60%, transparent);
margin-inline-start: 6px;
margin-block: 2px;
display: flex; flex-direction: column; gap: 2px;
}
.trace-detail-json-leaf {
display: flex; align-items: baseline; gap: 6px;
padding: 1px 4px;
min-height: 20px;
}
.trace-detail-json-dot { color: var(--muted); }
.trace-detail-json-eq { font-size: 11px; }
.trace-detail-json-value { color: var(--text); word-break: break-all; }
.trace-detail-json-value.is-string { color: color-mix(in srgb, var(--accent) 80%, var(--text)); }
.trace-detail-json-value.is-number,
.trace-detail-json-value.is-boolean { color: color-mix(in srgb, var(--accent) 60%, var(--text)); }
.trace-detail-json-value.is-null,
.trace-detail-json-value.absent { color: var(--muted); font-style: italic; }
/* Per-Output-row inline Fields subtree (LangSmith parity — 2026-07-17).
* Sits inside a `.trace-detail-output-row`, closed by default so it never
* pushes the row body around; clicking `Fields` unfolds a recursive
* buildJsonTree over the raw wire payload. Zero-drop reachability path
* for anything beyond `text` + tool args. */
.trace-detail-row-fields {
display: block;
margin-top: 4px;
padding-left: 24px; /* align tree column with role glyph baseline */
}
.trace-detail-row-fields-summary {
cursor: pointer;
display: inline-flex; align-items: baseline; gap: 6px;
font-size: 11px;
padding: 2px 4px;
min-height: 20px;
border-radius: 2px;
list-style: none;
}
.trace-detail-row-fields-summary::-webkit-details-marker { display: none; }
.trace-detail-row-fields-summary:hover {
background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.trace-detail-row-fields[open] > .trace-detail-row-fields-summary {
color: var(--text);
}
.trace-detail-row-fields-label { font-weight: 500; }
.trace-detail-row-fields-body {
margin-top: 4px;
padding-inline-start: 8px;
border-inline-start: 1px dashed color-mix(in srgb, var(--border) 60%, transparent);
margin-inline-start: 4px;
}
/* Batch C: tool_result output row error chip — status-token color, small
* pill, mono, sits inline between preview and Fields tree. */
.trace-detail-tool-result-error-chip {
align-self: flex-start;
display: inline-flex; align-items: center;
padding: 2px 6px;
min-height: 20px;
font-size: 11px;
border-radius: 2px;
color: var(--err);
background: var(--err-soft);
border: 1px solid color-mix(in srgb, var(--err) 32%, transparent);
}
.trace-detail-raw-badge { margin-left: auto; align-self: flex-start; }
.trace-detail-annotate { align-self: flex-start; }
.trace-detail-feedback-list { display: flex; flex-direction: column; gap: 8px; }
.trace-detail-feedback-row {
border-left: 2px solid color-mix(in srgb, var(--accent) 55%, transparent);
padding: 4px 8px;
display: flex; flex-direction: column; gap: 4px;
}
.trace-detail-feedback-head {
display: flex; align-items: baseline; gap: 8px;
font-size: 12px;
}
.trace-detail-feedback-scope { color: var(--muted); }
.trace-detail-feedback-overall {
color: var(--text);
font-weight: 600;
padding: 0 6px; border-radius: 999px;
background: var(--bg-elev);
font-size: 11px;
}
.trace-detail-feedback-meta { font-size: 11px; }
.trace-detail-feedback-dims {
font-size: 11px;
color: var(--text);
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.trace-detail-feedback-dim-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 6px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--surface-2);
font-family: var(--mono);
font-size: 11px;
}
.trace-detail-feedback-dim-key { color: var(--text-secondary); }
.trace-detail-feedback-dim-value { color: var(--text); font-weight: 600; }
.trace-detail-feedback-dim-value.absent { color: var(--text-tertiary); font-weight: 400; }
.trace-detail-feedback-dim-type-badge {
padding: 0 4px;
border-radius: 3px;
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
line-height: 1.4;
}
.trace-detail-feedback-dim-type-badge[data-dim-type="continuous"] {
background: var(--accent-soft);
color: var(--accent);
}
.trace-detail-feedback-dim-type-badge[data-dim-type="categorical"] {
background: var(--warn-soft);
color: var(--warn);
}
.trace-detail-feedback-dim-type-badge[data-dim-type="boolean"] {
background: var(--ok-soft);
color: var(--ok);
}
.trace-detail-feedback-note { font-size: 12px; color: var(--text); white-space: pre-wrap; }
/* ─── 2026-07-17 addendum: LangSmith run-detail 细节全抄批 ─────────────
* ID copy chip + Messages/Details switch in the pane header, scroll-
* anchor section grammar, dashed `+ Add feedback` empty state, per-block
* Fields card with Expand/Copy controls, Render dropdown, Attributes
* three-group folders, Runtime-URL links, tree-side toolbar, and the
* per-event token badge on assistant/message rows in the tree pane. */
.trace-detail-title-row {
display: flex; align-items: center; gap: 8px;
flex-wrap: wrap;
}
.trace-detail-id-chip {
display: inline-flex; align-items: baseline; gap: 4px;
padding: 2px 8px;
border: 1px solid var(--border);
background: transparent;
border-radius: 999px;
font: inherit; font-size: 11px;
color: var(--muted);
cursor: pointer;
min-height: 24px;
transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.trace-detail-id-chip:hover { color: var(--text); border-color: var(--border-strong); }
.trace-detail-id-chip.absent { opacity: 0.6; }
.trace-detail-id-chip.copied { color: var(--text); background: var(--accent-soft); border-color: var(--accent); }
.trace-detail-id-label { color: var(--muted); }
.trace-detail-id-value { color: var(--text); }
.trace-detail-id-copy { color: var(--muted); }
.trace-detail-mode-switch {
display: inline-flex; align-items: stretch; gap: 0;
margin-left: auto;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
}
.trace-detail-mode-seg {
padding: 4px 8px;
border: 0;
background: transparent;
color: var(--muted);
font: inherit; font-size: 11px;
cursor: pointer;
min-height: 24px;
}
.trace-detail-mode-seg + .trace-detail-mode-seg { border-left: 1px solid var(--border); }
.trace-detail-mode-seg:hover { color: var(--text); }
.trace-detail-mode-seg.active { color: var(--text); background: var(--bg-elev); }
.trace-detail-sections { display: flex; flex-direction: column; gap: 8px; }
.trace-detail-section { border-top: 1px solid var(--border); padding-top: 4px; }
.trace-detail-section:first-child { border-top: 0; padding-top: 0; }
.trace-detail-section > summary::-webkit-details-marker { display: none; }
.trace-detail-section > summary {
cursor: pointer;
display: flex; align-items: center; gap: 8px;
padding: 4px 0;
list-style: none;
}
.trace-detail-section-label {
font-size: 12px; font-weight: 600; color: var(--text);
letter-spacing: 0.02em;
}
.trace-detail-section-controls {
margin-left: auto;
display: inline-flex; align-items: center; gap: 4px;
}
/* Reasoning tab (differentiator surface, 2026-07-17). Prose body uses the
* same font stack as the assistant bubble so reasoning content reads like
* a first-class narrative rather than a debug dump. Chips in the section
* head carry the quant summary (tokens + delta count). */
.trace-detail-reasoning-chip {
padding: 1px 6px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--bg-elev, var(--surface));
}
.trace-detail-reasoning-body { margin-top: 6px; }
.trace-detail-reasoning-summary { padding: 2px 0; cursor: pointer; }
.trace-detail-reasoning-content {
padding: 8px 10px;
margin-top: 4px;
border-left: 2px solid color-mix(in oklab, var(--tint-purple, #7c3aed) 45%, transparent);
background: color-mix(in oklab, var(--tint-purple, #7c3aed) 6%, transparent);
white-space: pre-wrap;
line-height: 1.55;
font-size: 13px;
}
.trace-detail-subhead {
font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.04em;
padding-top: 4px;
}
.trace-detail-render-mode {
padding: 2px 6px;
border: 1px solid var(--border);
background: var(--bg-elev);
color: var(--text);
font: inherit; font-size: 11px;
border-radius: 4px;
min-height: 24px;
}
.trace-detail-attr-group {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 4px 8px;
background: var(--bg-elev);
}
.trace-detail-attr-group + .trace-detail-attr-group { margin-top: 8px; }
.trace-detail-attr-group > summary::-webkit-details-marker { display: none; }
.trace-detail-attr-group-head {
cursor: pointer;
display: flex; align-items: center; gap: 8px;
padding: 4px 0;
list-style: none;
}
.trace-detail-attr-group-label {
font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.04em;
}
.trace-detail-ext-marker { color: var(--muted); font-size: 11px; }
.trace-detail-fields-card {
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 8px;
background: var(--surface);
}
.trace-detail-fields-head {
display: flex; align-items: center; gap: 8px;
padding-bottom: 4px;
border-bottom: 1px solid var(--border);
margin-bottom: 8px;
}
.trace-detail-fields-glyph { color: var(--muted); font-size: 12px; }
.trace-detail-fields-title { font-size: 12px; font-weight: 600; color: var(--text); }
.trace-detail-fields-count { font-size: 11px; }
.trace-detail-fields-controls { margin-left: auto; display: inline-flex; gap: 4px; }
.trace-detail-field-block {
border-left: 2px solid var(--border);
padding-left: 8px;
}
.trace-detail-field-block + .trace-detail-field-block { margin-top: 8px; }
.trace-detail-field-block > summary::-webkit-details-marker { display: none; }
.trace-detail-field-block-head {
cursor: pointer;
display: flex; align-items: center; gap: 8px;
padding: 2px 0;
list-style: none;
min-height: 24px;
}
.trace-detail-field-key { color: var(--muted); font-size: 11px; }
.trace-detail-field-copy { margin-left: auto; }
.trace-detail-message-body.plain { white-space: pre; }
.trace-detail-json-panel { position: relative; display: flex; flex-direction: column; gap: 4px; }
.trace-detail-json-copy { align-self: flex-end; }
/* dashed empty-state Add-feedback button per老板2026-07-17 spec */
.trace-detail-add-feedback {
display: inline-flex; align-items: center; justify-content: center;
padding: 8px 12px;
min-height: 32px;
background: transparent;
border: 1px dashed var(--border-strong);
color: var(--muted);
border-radius: var(--radius-sm);
font: inherit; font-size: 12px;
cursor: pointer;
align-self: flex-start;
}
.trace-detail-add-feedback:hover { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
/* Inline Feedback popover (round-7 §15 team-lead 2026-07-17): the button
* pops open a compact form directly below itself. Not a floating overlay —
* flow-in, left-aligned, 4px grid; the centered-card ban still applies. */
.trace-detail-feedback-popover {
display: flex; flex-direction: column; gap: 8px;
margin-top: 8px;
padding: 12px;
background: var(--surface, var(--bg-elev));
border: 1px solid var(--border);
border-radius: var(--radius-sm);
max-width: 480px;
align-self: flex-start;
}
.trace-detail-feedback-popover-head {
display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.trace-detail-feedback-popover-title { font-size: 13px; font-weight: 600; color: var(--text); }
.trace-detail-feedback-popover-close {
min-width: 24px; min-height: 24px;
line-height: 1;
}
.trace-detail-feedback-popover-row {
display: flex; align-items: center; gap: 8px;
min-height: 24px;
}
.trace-detail-feedback-popover-row.row-note { align-items: flex-start; }
.trace-detail-feedback-popover-row.hidden { display: none; }
.trace-detail-feedback-popover-label {
flex: 0 0 auto; width: 84px;
font-size: 12px; color: var(--muted);
}
.trace-detail-feedback-popover-verdict-group { display: inline-flex; gap: 4px; }
.trace-detail-feedback-popover-verdict {
padding: 4px 8px; min-height: 24px;
border: 1px solid var(--border);
background: transparent;
color: var(--text);
border-radius: var(--radius-sm);
font: inherit; font-size: 12px;
cursor: pointer;
}
.trace-detail-feedback-popover-verdict:hover { border-color: var(--accent); }
.trace-detail-feedback-popover-verdict.active {
border-color: var(--accent); background: var(--accent-soft); color: var(--text);
}
.trace-detail-feedback-popover-verdict.verdict-bad.active { border-color: var(--err, #dc2626); background: color-mix(in oklab, var(--err, #dc2626) 12%, transparent); }
.trace-detail-feedback-popover-verdict.verdict-ok.active { border-color: var(--warn, #d97706); background: var(--warn-soft, color-mix(in oklab, var(--warn) 12%, transparent)); }
.trace-detail-feedback-popover-verdict.verdict-good.active { border-color: var(--ok, #16a34a); background: color-mix(in oklab, var(--ok, #16a34a) 12%, transparent); }
.trace-detail-feedback-popover-scope,
.trace-detail-feedback-popover-dim {
min-height: 24px;
padding: 2px 6px;
border: 1px solid var(--border);
background: transparent;
color: var(--text);
border-radius: var(--radius-sm);
font: inherit; font-size: 12px;
}
.trace-detail-feedback-popover-note {
flex: 1 1 auto; min-width: 0;
min-height: 40px;
padding: 4px 8px;
border: 1px solid var(--border);
background: var(--bg);
color: var(--text);
border-radius: var(--radius-sm);
font: inherit; font-size: 12px;
resize: vertical;
}
.trace-detail-feedback-popover-actions {
display: flex; align-items: center; gap: 8px; justify-content: flex-end;
margin-top: 4px;
}
.trace-detail-feedback-popover-submit { min-height: 28px; padding: 4px 12px; }
.trace-detail-feedback-popover-full { min-height: 28px; padding: 4px 8px; }
/* Tree-side toolbar (item 7) */
.trace-tree-toolbar {
display: flex; align-items: center; gap: 8px;
padding: 4px 0 8px 0;
border-bottom: 1px solid var(--border);
margin-bottom: 8px;
}
.trace-tree-filter {
flex: 0 1 180px; min-width: 120px;
padding: 4px 8px;
border: 1px solid var(--border);
background: var(--bg-elev);
color: var(--text);
font: inherit; font-size: 12px;
border-radius: 4px;
min-height: 24px;
}
.trace-tree-tool {
padding: 2px 8px;
min-height: 24px;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
font: inherit; font-size: 11px;
border-radius: 4px;
cursor: pointer;
transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.trace-tree-tool:hover { color: var(--text); border-color: var(--border-strong); }
.trace-tree-tool.active { color: var(--text); border-color: var(--accent); background: var(--accent-soft); }
.trace-tree-settings { margin-left: auto; }
/* When the Waterfall toggle is off, hide inline span bars in the tree
* without touching layout (rows stay identically aligned). */
.trace-card.waterfall-off .trace-event-bar-track,
.waterfall-off .trace-event-bar-track { visibility: hidden; }
/* Item 8: per-event token badge on assistant/message rows */
.trace-event-token-badge {
display: inline-flex; align-items: center;
padding: 0 6px;
min-height: 16px;
border-radius: 999px;
background: var(--bg-elev);
color: var(--text);
font-size: 11px;
border: 1px solid var(--border);
margin-left: 4px;
}
/* 2026-07-17 delta 205-Δ3: per-row model chip. Same pill idiom as the token
* badge (chip-tier waiver: 2/6 padding, 11px, 999px radius) so the "model +
* duration + tokens" trio reads as a single row-end column, LangSmith style. */
.trace-event-model {
display: inline-flex; align-items: center;
padding: 0 6px;
min-height: 16px;
border-radius: 999px;
background: var(--bg-elev);
color: var(--muted);
font-size: 11px;
border: 1px solid var(--border);
margin-left: 4px;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 2026-07-17 delta 205-Δ4: per-row duration pill. Pill-style duration
* (1.17s / 320ms) sitting alongside the token pill, so every descendant
* row has readable latency without hovering the bar tooltip. */
.trace-event-duration {
display: inline-flex; align-items: center;
padding: 0 6px;
min-height: 16px;
border-radius: 999px;
background: var(--bg-elev);
color: var(--muted);
font-size: 11px;
border: 1px solid var(--border);
margin-left: 4px;
}
/* ─ Timeline (Gantt) ─ */
.trace-timeline {
overflow-x: auto;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface);
padding: 4px;
}
.trace-timeline-svg { display: block; }
.trace-timeline-tick { stroke: var(--divider); stroke-width: 0.6; stroke-dasharray: 2 3; }
.trace-timeline-tick-label {
fill: var(--muted); font-size: 10px; font-family: var(--mono);
}
.trace-timeline-label {
fill: var(--text); font-size: 11px; font-family: var(--font);
}
.trace-timeline-row.depth-0 .trace-timeline-label { font-weight: 600; }
.trace-timeline-row { transition: filter 0.12s ease; }
.trace-timeline-row:hover { filter: brightness(1.08); }
/* Click feedback (audit D5 2026-07-17): the selected timeline row keeps
* the accent-soft background stripe across the label + bar so the reader
* can tell which bar drove the right-side detail pane at a glance. */
.trace-timeline-row.selected .trace-timeline-label { fill: var(--accent-strong, var(--accent, #4b6bcf)); font-weight: 600; }
.trace-timeline-row.selected .trace-timeline-bar {
stroke: var(--accent-strong, var(--accent, #4b6bcf));
stroke-width: 1.6;
}
.trace-timeline-bar { transition: filter 0.12s ease; }
.trace-timeline-bar.family-step { fill: var(--text-tertiary); opacity: 0.55; }
.trace-timeline-bar.family-llm { fill: var(--tint-purple); }
.trace-timeline-bar.family-tool { fill: var(--tool); }
.trace-timeline-bar.family-subagent { fill: #d0a97b; }
.trace-timeline-bar.family-compact { fill: var(--tint-orange); }
.trace-timeline-bar.family-input { fill: var(--text-secondary); }
.trace-timeline-bar.family-hook { fill: var(--tint-slate); }
.trace-timeline-bar.family-event { fill: var(--muted); }
.trace-timeline-now-cursor {
stroke: var(--err); stroke-width: 1;
}
/* ─ Graph (DAG) ─ */
.trace-graph {
overflow: hidden;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface);
padding: 4px;
user-select: none;
}
.trace-graph-svg { display: block; }
.trace-graph-edge {
stroke: var(--text-tertiary);
stroke-width: 1.2;
opacity: 0.7;
}
.trace-graph-edge.kind-fan-out {
stroke: #d0a97b;
stroke-dasharray: 3 3;
}
.trace-graph-node {
transition: filter 0.14s ease;
}
.trace-graph-node:hover { filter: brightness(1.1); }
/* Click feedback (clickability audit D2 2026-07-17): a selected graph node
* gets an accent-toned stroke halo so the reader can see which node drove
* the right-side detail pane. Timeline bars get the same treatment on the
* matching selector below. */
.trace-graph-node.selected .trace-graph-node-body {
stroke: var(--accent-strong, var(--accent, #4b6bcf));
stroke-width: 2.4;
filter: drop-shadow(0 0 3px color-mix(in oklab, var(--accent, #4b6bcf) 55%, transparent));
}
.trace-graph-node-body {
stroke: var(--border-strong); stroke-width: 1.2;
}
/* Family palette — mirrors trace-timeline bar colors so the two views
* read as one system. D31-style waiver: single-hue-per-family so the
* reader learns the palette once. */
.trace-graph-node-body.family-step { fill: var(--surface-2, var(--surface)); }
.trace-graph-node-body.family-llm { fill: color-mix(in oklab, var(--tint-purple) 30%, var(--surface)); }
.trace-graph-node-body.family-tool { fill: color-mix(in oklab, var(--tool) 30%, var(--surface)); }
.trace-graph-node-body.family-subagent { fill: color-mix(in oklab, #d0a97b 40%, var(--surface)); }
.trace-graph-node-body.family-compact { fill: color-mix(in oklab, var(--tint-orange) 30%, var(--surface)); }
.trace-graph-node-body.family-fork { fill: color-mix(in oklab, var(--accent) 25%, var(--surface)); }
.trace-graph-node-glyph {
font-weight: 600; fill: var(--text);
font-family: var(--font); font-size: 13px;
pointer-events: none;
}
.trace-graph-node-label {
font-size: 10px; fill: var(--text); font-family: var(--mono);
pointer-events: none;
}
/* Running-node pulse (respects prefers-reduced-motion). */
@keyframes traceGraphRunPulse {
0% { stroke: var(--accent); stroke-width: 1.4; }
50% { stroke: var(--accent-strong); stroke-width: 2.2; }
100% { stroke: var(--accent); stroke-width: 1.4; }
}
.trace-graph-node.status-running .trace-graph-node-body {
animation: traceGraphRunPulse 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.trace-graph-node.status-running .trace-graph-node-body { animation: none; }
}
/* Session-scope full-trace drawer entry — a small "Full trace" button in
* the Devtools drawer header that opens a wide tri-view over every step
* in the current session. */
.devtools-full-trace-btn { margin-left: 8px; }
.trace-tri-view[data-scope="session"] {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px;
background: var(--bg-elev, var(--surface));
}
/* Task #203: deep-link flash — brief accent tint when Timeline/Graph
* scrolls a stream row into view. Fades naturally so it doesn't stack
* across rapid clicks. */
@keyframes traceDeepLinkFlash {
0% { background: var(--accent-soft); }
100% { background: transparent; }
}
.trace-deep-link-flash {
animation: traceDeepLinkFlash 1.4s ease-out;
}
/* Task #203: full-session trace overlay (opened from Devtools drawer). */
.devtools-full-trace-overlay {
position: fixed; inset: 0; z-index: 40;
display: flex; align-items: center; justify-content: center;
}
.devtools-full-trace-scrim {
position: absolute; inset: 0;
background: rgba(0, 0, 0, 0.42);
}
.devtools-full-trace-card {
position: relative;
max-width: 960px; width: min(92vw, 960px);
max-height: 88vh; overflow: auto;
background: var(--bg-elev, var(--surface));
color: var(--text);
border: 1px solid var(--border-strong);
border-radius: var(--radius);
box-shadow: var(--shadow-pop);
padding: 16px 18px;
}
.devtools-full-trace-head {
display: flex; align-items: center; justify-content: space-between;
margin-bottom: 8px;
}
.devtools-full-trace-title {
font-size: var(--fs-section); font-weight: 600;
}
/* Tracing page (#225) — LangSmith-style project runs table. Density spec
* §7: 4px grid, tabular-nums right-aligned numeric columns, ~32px row
* height, three font sizes only, hover ring only on clickable rows.
* No centered cards; the table fills the pane width. */
.tracing-page-body {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px 24px;
min-height: 0;
flex: 1 1 auto;
}
.tracing-page-actions {
display: flex;
align-items: center;
gap: 8px;
}
.tracing-page-search {
display: inline-flex;
align-items: center;
}
.tracing-page-search input[type="search"] {
width: 260px;
height: 28px;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 4px);
background: var(--surface);
color: var(--text);
font-size: var(--fs-body, 13px);
line-height: 1.5;
}
.tracing-page-search input[type="search"]:focus-visible {
outline: 2px solid var(--focus-ring, var(--accent));
outline-offset: 1px;
border-color: var(--accent);
}
.tracing-page-columns {
position: relative;
}
.tracing-page-columns-menu {
position: absolute;
top: calc(100% + 4px);
right: 0;
min-width: 180px;
background: var(--bg-elev, var(--surface));
border: 1px solid var(--border-strong);
border-radius: var(--radius-sm, 4px);
box-shadow: var(--shadow-pop);
padding: 4px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 0;
}
.tracing-page-columns-menu-row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
cursor: pointer;
font-size: var(--fs-body, 13px);
border-radius: 3px;
}
.tracing-page-columns-menu-row:hover {
background: var(--surface-hover, rgba(127, 127, 127, 0.08));
}
.tracing-page-columns-menu-row input[type="checkbox"] {
margin: 0;
}
.tracing-page-columns-menu-row input[type="checkbox"]:disabled + .tracing-page-columns-menu-label {
color: var(--text-muted);
cursor: default;
}
.tracing-page-columns-menu-label {
flex: 1 1 auto;
line-height: 1.5;
}
.tracing-page-breadcrumb {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.tracing-page-breadcrumb-title {
font-size: var(--fs-body, 13px);
color: var(--text-muted);
}
.tracing-page-table-wrap {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 4px);
background: var(--surface);
}
.tracing-page-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
font-size: var(--fs-body, 13px);
color: var(--text);
}
.tracing-page-table thead th {
position: sticky;
top: 0;
z-index: 1;
background: var(--bg-elev, var(--surface));
border-bottom: 1px solid var(--border-strong);
font-weight: 600;
font-size: var(--fs-meta, 12px);
color: var(--text-muted);
text-transform: none;
letter-spacing: 0;
text-align: left;
padding: 8px 12px;
white-space: nowrap;
}
.tracing-page-table thead th.num {
text-align: right;
font-variant-numeric: tabular-nums;
}
.tracing-page-table tbody td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
line-height: 1.5;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 320px;
}
.tracing-page-table tbody td.num {
text-align: right;
font-variant-numeric: tabular-nums;
}
.tracing-page-table tbody tr.tracing-page-row {
cursor: pointer;
transition: background-color 90ms ease;
}
.tracing-page-table tbody tr.tracing-page-row:hover,
.tracing-page-table tbody tr.tracing-page-row:focus-visible {
background: var(--surface-hover, rgba(127, 127, 127, 0.08));
outline: none;
}
.tracing-page-table tbody tr.tracing-page-row:focus-visible td:first-child {
box-shadow: inset 2px 0 0 var(--focus-ring, var(--accent));
}
/* Click feedback (audit D5 2026-07-17): the row that opened the tri-view
* keeps a persistent accent-soft stripe so the reader can tell which
* trajectory drives the detail pane below the breadcrumb. Overrides the
* hover-only tint. */
.tracing-page-table tbody tr.tracing-page-row.selected,
.tracing-page-table tbody tr.tracing-page-row.selected:hover {
background: var(--accent-soft, rgba(75, 107, 207, 0.14));
}
.tracing-page-table tbody tr.tracing-page-row.selected td:first-child {
box-shadow: inset 2px 0 0 var(--accent, #4b6bcf);
}
.tracing-page-table tbody tr.tracing-page-row:last-child td {
border-bottom: none;
}
.tracing-page-empty {
padding: 32px 24px;
text-align: left;
}
.tracing-page-empty-head {
font-size: var(--fs-body, 13px);
font-weight: 600;
color: var(--text);
margin-bottom: 4px;
}
.tracing-page-empty-note code {
font-family: var(--font-mono);
font-size: 12px;
background: var(--surface-hover, rgba(127, 127, 127, 0.10));
padding: 1px 4px;
border-radius: 3px;
}
.tracing-page-detail {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
}
/* Ticket #168 step 1: shared payload-controls util (pretty⇅raw · copy ·
* download) mounted on every payload block — tool-block args, tool JSON
* drawer sections, trace-detail-pane Render=JSON panels. Cluster sits
* top-right of the host via flex (host must be relatively positioned or
* a flex container). Controls-hover surface reveal + hover-only glow
* matches the .ghost.small idiom used elsewhere. */
.payload-controls {
display: inline-flex; gap: 4px;
align-items: center;
margin-left: auto;
opacity: 0.55;
transition: opacity 120ms ease;
}
.payload-controls:hover,
.payload-controls:focus-within {
opacity: 1;
}
.tool-block:hover .payload-controls,
.tool-json-section:hover .payload-controls,
.trace-detail-json-panel:hover .payload-controls {
opacity: 1;
}
.payload-controls .ghost.small {
font-size: 11px;
padding: 2px 6px;
min-height: 20px;
border-radius: var(--radius-sm, 4px);
}
/* 2026-07-18 P0 hotfix (drawer/tool-block payload-controls overlap):
* a dedicated flex row wraps the .label + controls into `[label ······
* pretty | copy | download]`. The label carries flex:1 so it stretches
* and pushes controls to the far right; long meta lines (drawer's
* "(content · meta · isError · error · durationMs)") wrap under the
* label rather than sliding under the buttons. Replaces the earlier
* `margin-top:-18px + float:right` hack that overlapped when text grew. */
.tool-block-label-row {
display: flex;
align-items: baseline;
gap: 8px;
flex-wrap: wrap;
}
.tool-block-label-row > .label {
flex: 1 1 auto;
min-width: 0;
}
.tool-block-label-row > .payload-controls {
flex: 0 0 auto;
}
.payload-body {
margin: 0; padding: 6px 8px;
background: var(--bg);
border-radius: 4px;
font-family: var(--mono);
font-size: 11.5px;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 320px; /* density-spec §2 L1 cap */
overflow: auto;
}
.tool-block .args.args-with-controls,
.tool-block .result.result-with-controls {
/* Host has payload-controls floated at the top-right; keep the CSS
* height cap but let the inner <pre.payload-body> handle its own scroll
* so the cluster stays visible while long args scroll. */
padding: 0;
background: transparent;
overflow: visible;
max-height: none;
}
.tool-json-section-controls {
display: flex; justify-content: flex-end;
padding: 4px 4px 0 4px;
}
.tool-json-section-controls .payload-controls {
margin-top: 0;
float: none;
}
/* Ticket #168 step 2: request/header "Re-run with edited config" button.
* Lives inside the L1 header block as a small collapsed <details>.
* §7 baseline: 4px grid, 24px+ click targets, no new fonts. */
.edit-rerun-header {
margin-top: 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 4px);
background: var(--bg-elev, var(--surface));
padding: 6px 8px;
}
.edit-rerun-header-summary {
cursor: pointer;
display: inline-flex; align-items: center; gap: 6px;
min-height: 24px;
list-style: none;
}
.edit-rerun-header-summary::-webkit-details-marker { display: none; }
.edit-rerun-header-summary::before {
content: '';
width: 0; height: 0;
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 5px solid var(--muted);
transition: transform 120ms ease;
flex: 0 0 auto;
}
.edit-rerun-header[open] > .edit-rerun-header-summary::before { transform: rotate(90deg); }
.edit-rerun-header-label {
font-weight: 600;
font-size: 12px;
color: var(--text);
}
.edit-rerun-header-hint { font-size: 11px; }
.edit-rerun-header-body {
display: flex; flex-direction: column; gap: 8px;
margin-top: 8px;
}
.edit-rerun-header-grid {
display: grid;
grid-template-columns: 96px 1fr;
gap: 4px 8px;
}
.edit-rerun-header-row {
display: contents;
}
.edit-rerun-header-row.disabled .edit-rerun-header-input {
opacity: 0.55;
cursor: not-allowed;
}
.edit-rerun-header-key {
align-self: center;
color: var(--muted);
font-size: 11px;
}
.edit-rerun-header-input {
min-height: 24px;
padding: 2px 6px;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 4px);
background: var(--bg);
color: var(--text);
font-family: var(--mono);
font-size: 12px;
}
.edit-rerun-header-input:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
.edit-rerun-header-controls {
display: flex; align-items: center; gap: 8px;
flex-wrap: wrap;
}
.edit-rerun-header-status {
font-size: 11px;
flex: 1 1 auto;
}
.edit-rerun-header-submit {
min-height: 24px;
padding: 2px 12px;
border-radius: var(--radius-sm, 4px);
font-size: 12px;
}
.edit-rerun-header-note {
font-size: 11px;
color: var(--muted);
line-height: 1.4;
}
/* Ticket #168 step 3: tool row "Edit & re-run" — trigger button + inline
* JSON editor panel injected below the tool card's `.args` row.
* §7 baseline: 4px grid, ≥24px click targets, mono for wire data. */
.tool-edit-rerun-trigger {
/* Rides the summary's right-cluster alongside `.tool-json-badge` and
* `.tool-duration`. Not `margin-left: auto` (that belongs to the
* duration pill); a small left gap keeps the row tidy without stealing
* the auto-margin anchor. */
margin-left: 8px;
min-height: 24px;
padding: 1px 8px;
font-size: 11px;
font-family: var(--mono);
border-radius: 3px;
color: var(--muted);
border: 1px solid var(--border);
background: transparent;
cursor: pointer;
}
.tool-edit-rerun-trigger:hover { color: var(--text); border-color: var(--text); }
.tool-edit-rerun-trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
/* Preserve the duration pill's far-right anchor. Without this, adding the
* trigger before the pill leaves the pill exactly where it was — CSS
* margin-left: auto on the pill still wins because trigger has an explicit
* margin-left. Confirmed geometry: [name … {gist}] [{ }] [trigger] [pill]. */
.tool-block summary > .tool-edit-rerun-trigger + .tool-duration { margin-left: 6px; }
.tool-edit-rerun-panel {
margin-top: 8px;
padding: 8px 12px;
border: 1px dashed var(--border);
border-radius: var(--radius-sm, 4px);
background: var(--bg-elev, var(--surface));
display: flex; flex-direction: column; gap: 8px;
}
.tool-edit-rerun-label {
font-size: 11px;
color: var(--muted);
}
.tool-edit-rerun-textarea {
min-height: 96px;
max-height: 320px;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 4px);
background: var(--bg);
color: var(--text);
font-family: var(--mono);
font-size: 12px;
line-height: 1.5;
resize: vertical;
width: 100%;
box-sizing: border-box;
}
.tool-edit-rerun-textarea:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
.tool-edit-rerun-controls {
display: flex; align-items: center; gap: 8px;
flex-wrap: wrap;
}
.tool-edit-rerun-status {
font-size: 11px;
flex: 1 1 auto;
}
.tool-edit-rerun-submit {
min-height: 24px;
padding: 2px 12px;
border-radius: var(--radius-sm, 4px);
font-size: 12px;
}
.tool-edit-rerun-note {
font-size: 11px;
color: var(--muted);
line-height: 1.4;
}
/* Ticket #168 step 4: fork-compare drawer. Reuses the playground-compare
* drawer's grammar but repositions to a full-viewport overlay so it can
* open from any tab (edit-rerun-header + tool-edit-rerun trigger it after
* a successful fork). §7 baseline: 4px grid, mono for wire data. */
.fork-compare-drawer {
/* Override the playground-compare-drawer's sidebar-anchored inset with
* a viewport-anchored modal. `position: fixed` (not inherited absolute)
* keeps it pinned to the viewport regardless of document scroll — Bug
* D (2026-07-18): the previous `position: absolute` inherited from
* .playground-compare-drawer meant the drawer scrolled with the page,
* so on any pane taller than the viewport its content appeared
* displaced ("白面板 + 内容碎片飘到左上跟着滚动"). z-index sits above
* every other float (playground-compare-drawer=20; hub tabs=15). */
position: fixed;
inset: 40px 40px 40px 40px;
z-index: 100;
border: 1px solid var(--border);
border-radius: var(--radius-sm, 4px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
}
/* Bug D dismissal path: clicking the 40px gutter between viewport edge
* and drawer content triggers close (see fork-compare.js root click
* handler); make the gutter feel like a backdrop instead of dead space. */
.fork-compare-drawer::before {
content: '';
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.25);
z-index: -1;
pointer-events: none;
}
.fork-compare-drawer .drawer-head {
/* Give the inline badge room; keep the buttons on the far right. */
gap: 12px;
}
.fork-compare-badge {
font-size: 11px;
color: var(--muted);
padding: 2px 8px;
border-radius: 3px;
border: 1px solid var(--border);
background: var(--bg-elev, var(--surface));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 60vw;
}
.fork-compare-spacer { flex: 1 1 auto; }
.fork-compare-row {
display: grid;
grid-template-columns: 48px 140px 1fr;
gap: 8px;
padding: 4px 0;
border-bottom: 1px dashed var(--border);
font-size: 12px;
align-items: baseline;
}
.fork-compare-row:last-child { border-bottom: none; }
.fork-compare-row.fork-compare-meta {
grid-template-columns: 1fr;
font-style: italic;
padding: 6px 4px;
}
.fork-compare-seq { text-align: right; }
.fork-compare-kind { color: var(--text); }
.fork-compare-body {
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ─── 2026-07-17 trace-parity batch: Error section (conditional 5th tab)
* Rendered when detectRecordError(rec) returns non-null. Banner mirrors
* LangSmith's red-outlined shape (round-6 shot 12); the "raised on"
* row lists the wire events that flagged the error and deep-links each
* back into the stream via __dshDeepLinkToSeq. Colour tokens only,
* status semantics only (§7 baseline). */
.trace-detail-tab.is-error {
color: var(--error, #d64545);
border-color: color-mix(in srgb, var(--error, #d64545) 45%, transparent);
}
.trace-detail-tab.is-error.active {
background: color-mix(in srgb, var(--error, #d64545) 12%, transparent);
border-color: var(--error, #d64545);
}
.trace-detail-pane.has-error .trace-detail-section.section-error > summary {
color: var(--error, #d64545);
}
.trace-detail-error-banner {
border: 1px solid var(--error, #d64545);
border-radius: 8px;
background: color-mix(in srgb, var(--error, #d64545) 6%, transparent);
padding: 8px 12px;
display: grid;
grid-template-columns: 20px 1fr;
gap: 4px 8px;
align-items: baseline;
}
.trace-detail-error-glyph {
grid-row: 1 / span 2;
color: var(--error, #d64545);
font-size: 16px;
font-weight: 700;
line-height: 1.2;
align-self: start;
}
.trace-detail-error-head {
display: flex; align-items: baseline; gap: 6px;
color: var(--text);
font-size: 13px;
font-weight: 600;
}
.trace-detail-error-name { color: var(--error, #d64545); }
.trace-detail-error-code { font-size: 11px; }
.trace-detail-error-message {
color: var(--text);
font-size: 12px;
line-height: 1.5;
white-space: pre-wrap;
}
.trace-detail-error-refs {
display: flex; flex-wrap: wrap; gap: 6px;
margin-top: 4px;
}
.trace-detail-error-ref {
padding: 2px 8px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--bg-elev, var(--surface));
color: var(--text);
font-size: 11px;
cursor: pointer;
min-height: 24px;
}
.trace-detail-error-ref:hover { border-color: var(--border-strong, var(--accent)); }
.trace-detail-error-ref:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.trace-detail-error-raw-row {
display: flex; align-items: center; gap: 8px;
margin-top: 4px;
font-size: 11px;
}
.trace-detail-error-copy { margin-left: auto; }
/* Token-pill breakdown tooltip is native-title only (no third-party
* tooltip library), so no CSS is required — but we do want the pill
* itself to hint that hover reveals more. */
.trace-usage-badge[title],
.trace-event-token-badge[title] {
cursor: help;
}
/* LLM-leaf row-action "Edit & re-run" chip (task 3). Rendered on
* request/header event rows; visually a ghost chip that lights up on
* row hover so the row still reads clean at rest. Same pill idiom as
* the token badge — chip-tier waiver in effect (§7 codified). */
.trace-event-rerun-chip {
display: inline-flex; align-items: center;
padding: 0 8px;
min-height: 20px;
margin-left: 4px;
border-radius: 999px;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
font-size: 11px;
cursor: pointer;
opacity: 0;
transition: opacity 120ms ease;
}
.trace-event-row:hover > summary > .trace-event-rerun-chip,
.trace-event-row > summary:focus-within > .trace-event-rerun-chip,
.trace-event-rerun-chip:focus-visible {
opacity: 1;
}
.trace-event-rerun-chip:hover {
color: var(--text);
border-color: var(--border-strong, var(--accent));
background: var(--bg-elev, var(--surface));
}
.trace-event-rerun-chip:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* ---------------------------------------------------------------------------
* Trace-detail · MCP tool-source chip (task #49, 2026-07-17).
* Appended to the tool_call `name` KV value; the value is a plain text node
* so we need explicit left-margin on the chip to keep the two apart. Uses the
* shared `.chip-tier` grammar (density-layering-spec §7 waiver) for padding
* and font-size.
* -------------------------------------------------------------------------*/
.trace-detail-tool-source-chip {
display: inline-flex;
align-items: center;
margin-left: 8px;
padding: 0 6px;
min-height: 16px;
border: 1px solid var(--border);
border-radius: 999px;
background: var(--bg-elev, var(--surface));
color: var(--muted);
font-size: 11px;
font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
gap: 0;
}
.trace-detail-tool-source-chip.mcp {
color: color-mix(in srgb, var(--accent) 60%, var(--muted));
border-color: color-mix(in srgb, var(--accent) 30%, var(--border));
}
.trace-detail-tool-source-chip.plugin {
color: color-mix(in srgb, var(--warn, #b26a00) 60%, var(--muted));
border-color: color-mix(in srgb, var(--warn, #b26a00) 30%, var(--border));
}
.trace-detail-tool-source-chip-glyph { letter-spacing: 0.05em; }
.trace-detail-tool-source-chip-server { color: var(--text); }
/* ---------------------------------------------------------------------------
* Plugins tab · MCP-server config card (task #49, 2026-07-17).
* Anchored beneath a `dsh-mcp-client` plugin row as a full-width follow-up
* `<tr>` (same pattern as `.plugin-diagnostic`). Card lives inside a
* <details> so it folds; open by default because the whole point of the
* card is that today the row looks unconfigurable.
* -------------------------------------------------------------------------*/
.plugin-mcp-card > td {
padding: 0;
border-top: 0;
background: var(--surface-alt, var(--surface));
}
.plugin-mcp-card .mcp-config-wrap {
border-left: 3px solid var(--accent);
padding: 8px 12px;
margin: 0;
background: var(--surface);
}
.plugin-mcp-card .mcp-config-summary {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0 8px 0;
cursor: pointer;
list-style: revert; /* keep native disclosure marker */
font-size: 13px;
}
.plugin-mcp-card .mcp-config-summary:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.plugin-mcp-card .mcp-config-glyph {
padding: 2px 6px;
border: 1px solid var(--border);
border-radius: 3px;
font-size: 11px;
letter-spacing: 0.05em;
}
.plugin-mcp-card .mcp-config-label { font-weight: 500; }
.plugin-mcp-card .mcp-config-preview { font-size: 12px; }
.plugin-mcp-card .mcp-config-restart-badge {
margin-left: auto;
padding: 2px 8px;
border: 1px solid var(--warn-border, var(--border));
color: var(--warn, var(--muted));
background: var(--warn-bg, transparent);
border-radius: 999px;
font-size: 11px;
gap: 6px;
}
.plugin-mcp-card .mcp-config-body {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0 4px 0;
}
.plugin-mcp-card .mcp-config-row {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 4px 0;
}
.plugin-mcp-card .mcp-config-row.list,
.plugin-mcp-card .mcp-config-row.map {
align-items: flex-start;
}
.plugin-mcp-card .mcp-config-key {
min-width: 96px;
padding-top: 4px;
font-size: 12px;
}
.plugin-mcp-card .mcp-config-radio-group {
display: flex;
gap: 12px;
}
.plugin-mcp-card .mcp-config-radio {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
cursor: pointer;
}
.plugin-mcp-card .mcp-config-input {
flex: 1;
min-width: 200px;
min-height: 24px;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--surface);
color: var(--text);
font-size: 12px;
}
.plugin-mcp-card .mcp-config-input:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 0;
border-color: var(--accent);
}
.plugin-mcp-card .mcp-config-list-body,
.plugin-mcp-card .mcp-config-map-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.plugin-mcp-card .mcp-config-list-item,
.plugin-mcp-card .mcp-config-map-item {
display: flex;
gap: 8px;
align-items: center;
}
.plugin-mcp-card .mcp-config-list-remove,
.plugin-mcp-card .mcp-config-map-remove {
padding: 2px 8px;
min-height: 24px;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
border-radius: 3px;
cursor: pointer;
font-size: 11px;
}
.plugin-mcp-card .mcp-config-list-remove:hover,
.plugin-mcp-card .mcp-config-map-remove:hover {
color: var(--text);
border-color: var(--border-strong, var(--accent));
}
.plugin-mcp-card .mcp-config-list-add,
.plugin-mcp-card .mcp-config-map-add {
align-self: flex-start;
padding: 2px 12px;
min-height: 24px;
border: 1px dashed var(--border);
background: transparent;
color: var(--muted);
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.plugin-mcp-card .mcp-config-list-add:hover,
.plugin-mcp-card .mcp-config-map-add:hover {
color: var(--text);
border-color: var(--accent);
}
.plugin-mcp-card .mcp-config-footer {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0 4px 0;
border-top: 1px solid var(--border);
margin-top: 4px;
}
.plugin-mcp-card .mcp-config-status {
font-size: 12px;
}
.plugin-mcp-card .mcp-config-status.error {
color: var(--error, #c33);
}
.plugin-mcp-card .mcp-config-spacer { flex: 1; }
.plugin-mcp-card .mcp-config-clear,
.plugin-mcp-card .mcp-config-save {
padding: 4px 12px;
min-height: 24px;
border-radius: 3px;
border: 1px solid var(--border);
background: transparent;
color: var(--text);
font-size: 12px;
cursor: pointer;
}
.plugin-mcp-card .mcp-config-save.primary {
border-color: var(--accent);
background: var(--accent);
color: var(--on-accent, #fff);
}
.plugin-mcp-card .mcp-config-save.primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.plugin-mcp-card .mcp-config-clear:hover,
.plugin-mcp-card .mcp-config-save:hover:not(:disabled) {
filter: brightness(1.05);
}
.plugin-mcp-card .mcp-config-save:focus-visible,
.plugin-mcp-card .mcp-config-clear:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* ---------------------------------------------------------------------------
* Plugins tab · Browse subview · "Import from…" panel (task #49, 2026-07-17).
* Sits between the installed strip and the curated list. Keeps the marketplace
* hero grammar (rounded, sub-title muted) so the two sections read as siblings.
* -------------------------------------------------------------------------*/
.market-import-panel {
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 16px;
margin: 8px 0;
border: 1px solid var(--border);
border-radius: 6px;
background: var(--surface);
}
.market-import-header { display: flex; flex-direction: column; gap: 2px; }
.market-import-title { margin: 0; font-size: 14px; font-weight: 600; }
.market-import-sub { margin: 0; font-size: 12px; }
.market-import-seg {
display: inline-flex;
gap: 0;
border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
align-self: flex-start;
}
.market-import-seg-btn {
padding: 4px 12px;
min-height: 26px;
background: transparent;
color: var(--muted);
border: 0;
border-right: 1px solid var(--border);
font-size: 12px;
cursor: pointer;
}
.market-import-seg-btn:last-child { border-right: 0; }
.market-import-seg-btn.active {
background: var(--bg-elev, var(--surface));
color: var(--text);
font-weight: 500;
}
.market-import-seg-btn.disabled,
.market-import-seg-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.market-import-seg-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.market-import-form { display: flex; flex-direction: column; gap: 6px; }
.market-import-row {
display: flex;
align-items: center;
gap: 12px;
}
.market-import-label {
min-width: 80px;
font-size: 12px;
}
.market-import-input {
flex: 1;
min-height: 24px;
padding: 4px 8px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--surface);
color: var(--text);
font-size: 12px;
}
.market-import-input:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 0;
border-color: var(--accent);
}
.market-import-note {
padding: 6px 8px;
border: 1px dashed var(--border);
border-radius: 3px;
font-size: 12px;
}
.market-import-footer {
display: flex;
align-items: center;
gap: 8px;
padding-top: 4px;
border-top: 1px solid var(--border);
margin-top: 4px;
}
.market-import-status { font-size: 12px; }
.market-import-status.error { color: var(--error, #c33); }
.market-import-spacer { flex: 1; }
.market-import-submit {
padding: 4px 14px;
min-height: 26px;
border-radius: 3px;
border: 1px solid var(--accent);
background: var(--accent);
color: var(--on-accent, #fff);
font-size: 12px;
cursor: pointer;
}
.market-import-submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.market-import-submit:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* --- Field §3 P0 收尾批 (2026-07-17) ------------------------------------ */
/* FinishReason chip (audit §3 P0 #9). Same pill idiom as the token badge —
* chip-tier waiver in effect. Tone maps to LangSmith severity palette:
* ok=green success, info=neutral, warn=amber, error=red. Rendered right of
* the token badge on assistant/chunk (finish) and assistant/message rows. */
.trace-event-finish-chip {
display: inline-flex; align-items: center;
padding: 0 6px;
min-height: 16px;
border-radius: 999px;
background: var(--bg-elev);
color: var(--text);
font-size: 11px;
border: 1px solid var(--border);
margin-left: 4px;
font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
}
.trace-event-finish-chip.tone-ok {
color: var(--ok, #1a7f37);
border-color: color-mix(in srgb, var(--ok, #1a7f37) 40%, var(--border));
}
.trace-event-finish-chip.tone-info {
color: var(--muted);
}
.trace-event-finish-chip.tone-warn {
color: var(--warn, #b26a00);
border-color: color-mix(in srgb, var(--warn, #b26a00) 40%, var(--border));
}
.trace-event-finish-chip.tone-error {
color: var(--error, #b42318);
border-color: color-mix(in srgb, var(--error, #b42318) 40%, var(--border));
}
/* system-line severity tints for the fused turn/end + session.finished
* L0 lines (audit §3 P0 #4 + #10). Preserves the muted baseline for `.system`
* from the top of the file and adds a subtle left-border accent instead of
* a full color-swap — a reader still reads the line as a system line but
* spots the error/warn tier at a glance. */
.system.system-error {
color: var(--error, #b42318);
border-left: 2px solid var(--error, #b42318);
padding-left: 6px;
}
.system.system-warn {
color: var(--warn, #b26a00);
border-left: 2px solid var(--warn, #b26a00);
padding-left: 6px;
}
.system.system-info {
border-left: 2px solid var(--border);
padding-left: 6px;
}
/* ============================================================
* Expand-affordance batch (fix/expand-affordance, 2026-07-18):
* user reported that `trace · ↑… ↓…` and other click-to-expand
* rows read as static text — no fold cue. Solution is uniform:
* ▸ (collapsed) / rotate-90 (expanded), var(--muted), row-head
* left, matching the existing trace-card / tool-block / trace-
* event ▸ language (density-spec §4 + §7 hover state on every
* clickable). Full inventory + judgement per surface lives in
* docs/expand-affordance-audit.md. See also assistant-turn.js
* and renderer.js for `aria-expanded` reflection wiring.
*
* Sites added here (14 sites 缺失 → 达标):
* turn-trace-drawer-summary, pb-row-head, devtools-row-summary,
* recall-card summary (fold overlay next to ⌕ semantic glyph),
* inject-card summary, subagent-trace-summary, raw-inject-summary,
* raw-inject-l2 summary, runtime-row-head, context-page-row-summary,
* trace-detail-row-fields-summary, trace-detail-section > summary,
* trace-detail-attr-group > summary, trace-detail-field-block > summary.
*
* Language: prepend a ▸ (\25B8) via ::before, muted, 10px, 1em wide,
* rotate 90deg when [open]; hover row background = var(--surface-hover).
* The marker slots in *before* any semantic icon a summary already
* carries — reader reads "▸ [family-icon] [label]" collapsed.
* ============================================================ */
.turn-trace-drawer > .turn-trace-drawer-summary { display: inline-flex; align-items: center; gap: 6px; }
.turn-trace-drawer > .turn-trace-drawer-summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.turn-trace-drawer[open] > .turn-trace-drawer-summary::before { transform: rotate(90deg); }
.turn-trace-drawer > .turn-trace-drawer-summary:hover { background: var(--surface-hover); }
details.prompt-blocked-row > summary.pb-row-head { position: relative; }
details.prompt-blocked-row > summary.pb-row-head::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
margin-right: 4px; transition: transform 120ms ease;
}
details.prompt-blocked-row[open] > summary.pb-row-head::before { transform: rotate(90deg); }
details.prompt-blocked-row > summary.pb-row-head:hover { background: var(--surface-hover); }
.devtools-row-summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
details.devtools-row[open] > .devtools-row-summary::before { transform: rotate(90deg); }
.devtools-row-summary:hover { background: var(--surface-hover); }
.recall-card summary::after {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
margin-left: auto; transition: transform 120ms ease;
}
.recall-card[open] > summary::after { transform: rotate(90deg); }
.recall-card summary:hover { background: var(--surface-hover); }
.inject-card summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.inject-card[open] > summary::before { transform: rotate(90deg); }
.inject-card summary:hover { background: var(--surface-hover); }
/* subagent-trace: head already carries a status glyph (✓/✗/▸-running).
* Put the fold chevron at the row-tail (::after) so we don't double-glyph
* a running trace. Same grammar as recall-card. */
.subagent-trace > .subagent-trace-summary::after {
content: '\25B8'; color: var(--muted); font-size: 10px;
margin-left: auto; padding-left: 8px; flex: 0 0 auto;
transition: transform 120ms ease;
}
.subagent-trace[open] > .subagent-trace-summary::after { transform: rotate(90deg); }
.subagent-trace > .subagent-trace-summary:hover { background: var(--surface-hover); }
.raw-inject-card > .raw-inject-summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
margin-right: 4px; transition: transform 120ms ease;
}
.raw-inject-card[open] > .raw-inject-summary::before { transform: rotate(90deg); }
.raw-inject-card > .raw-inject-summary:hover { background: var(--surface-hover); }
.raw-inject-l2 > summary { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.raw-inject-l2 > summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.raw-inject-l2[open] > summary::before { transform: rotate(90deg); }
.raw-inject-l2 > summary:hover { background: var(--surface-hover); }
.runtime-row-head::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
margin-right: var(--space-1); transition: transform 120ms ease;
}
.runtime-row[open] > .runtime-row-head::before { transform: rotate(90deg); }
.context-page-row-summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
margin-right: var(--space-1); transition: transform 120ms ease;
}
.context-page-row[open] > .context-page-row-summary::before { transform: rotate(90deg); }
.trace-detail-row-fields-summary { display: inline-flex; align-items: center; gap: 4px; }
.trace-detail-row-fields-summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.trace-detail-row-fields[open] > .trace-detail-row-fields-summary::before { transform: rotate(90deg); }
.trace-detail-section > summary::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.trace-detail-section[open] > summary::before { transform: rotate(90deg); }
.trace-detail-section > summary:hover { background: var(--surface-hover); }
.trace-detail-attr-group > .trace-detail-attr-group-head::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.trace-detail-attr-group[open] > .trace-detail-attr-group-head::before { transform: rotate(90deg); }
.trace-detail-attr-group > .trace-detail-attr-group-head:hover { background: var(--surface-hover); }
.trace-detail-field-block > .trace-detail-field-block-head::before {
content: '\25B8'; color: var(--muted); font-size: 10px;
width: 1em; flex: 0 0 auto; display: inline-block;
transition: transform 120ms ease;
}
.trace-detail-field-block[open] > .trace-detail-field-block-head::before { transform: rotate(90deg); }
.trace-detail-field-block > .trace-detail-field-block-head:hover { background: var(--surface-hover); }
/* --- trace signal badges (lane-trace-signals; see docs/upstream-ledger.md L-2) --
* The detector runs renderer-side until upstream emits `trace/signal` events.
* Colors follow the palette: red=error/loop, amber=redundant, blue=plan.
* Multiple signals stack horizontally on the Timeline; the Graph uses a single
* outer ring per node (highest-priority signal wins the color).
*/
.trace-timeline-signal-badge { stroke: rgba(0,0,0,0.15); stroke-width: 0.75; }
.trace-timeline-signal-badge.sig-error { fill: #dc2626; }
.trace-timeline-signal-badge.sig-loop { fill: #ef4444; }
.trace-timeline-signal-badge.sig-redundant{ fill: #f59e0b; }
.trace-timeline-signal-badge.sig-plan { fill: #2563eb; }
.trace-timeline-signal-badge.sig-plan-restart { fill: #1d4ed8; }
.trace-timeline-signal-badge.sig-generic { fill: #6b7280; }
.trace-graph-signal-ring { stroke-width: 2.5; }
.trace-graph-signal-ring.sig-error { stroke: #dc2626; }
.trace-graph-signal-ring.sig-loop { stroke: #ef4444; }
.trace-graph-signal-ring.sig-redundant{ stroke: #f59e0b; }
.trace-graph-signal-ring.sig-plan { stroke: #2563eb; }
.trace-graph-signal-ring.sig-plan-restart { stroke: #1d4ed8; }
.trace-graph-signal-ring.sig-generic { stroke: #6b7280; }
/* Main-flow marker chips — small pill row above the assistant turn body */
.turn-signal-chip-row {
display: flex; flex-wrap: wrap; gap: 6px;
padding: 4px 0 6px 0;
align-items: center;
}
.turn-signal-chip {
font-size: 11px; line-height: 1;
padding: 3px 8px; border-radius: 10px;
border: 1px solid transparent;
background: rgba(0,0,0,0.03);
color: #1d1d1f;
cursor: pointer;
font-family: inherit;
}
.turn-signal-chip:hover { filter: brightness(0.96); }
.turn-signal-chip::before {
content: ''; display: inline-block; width: 6px; height: 6px;
border-radius: 50%; margin-right: 6px; vertical-align: 1px;
background: currentColor;
}
.turn-signal-chip.sig-error { color: #b91c1c; border-color: rgba(220,38,38,0.35); background: rgba(220,38,38,0.06); }
.turn-signal-chip.sig-loop { color: #b91c1c; border-color: rgba(239,68,68,0.35); background: rgba(239,68,68,0.06); }
.turn-signal-chip.sig-redundant { color: #92400e; border-color: rgba(245,158,11,0.4); background: rgba(245,158,11,0.08); }
.turn-signal-chip.sig-plan { color: #1d4ed8; border-color: rgba(37,99,235,0.35); background: rgba(37,99,235,0.06); }
.turn-signal-chip.sig-plan-restart{ color: #1e3a8a; border-color: rgba(29,78,216,0.4); background: rgba(29,78,216,0.08); }
/* Dark theme reads the badges/chips against the dark surface, so bump the
* saturation a touch — the palette is designed for both. */
@media (prefers-color-scheme: dark) {
.turn-signal-chip { background: rgba(255,255,255,0.05); color: #f3f4f6; }
.turn-signal-chip.sig-error, .turn-signal-chip.sig-loop { color: #fca5a5; }
.turn-signal-chip.sig-redundant { color: #fcd34d; }
.turn-signal-chip.sig-plan, .turn-signal-chip.sig-plan-restart { color: #93c5fd; }
}
/* --- onboarding overlay: first-frame FOUC guard ------------------------ */
/* index.html declares <div id="onboarding" class="onboarding" hidden> and */
/* onboarding-ui.js clears the [hidden] attribute only after a first-run */
/* check. The rule at ~L776 already reads `.onboarding[hidden] { display: */
/* none; }`, but during the first paint a later CSS override or transient */
/* style recomputation can win against non-!important `display: none` and */
/* flash the modal for one frame even when firstRun=false. `!important` */
/* here is the standard FOUC-guard for [hidden] elements with a heavy */
/* default display (this one is `display: flex`) — behaviourally identical */
/* once JS finishes booting, but robust against the first-paint race. */
.onboarding[hidden] { display: none !important; }