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.
11582 lines
399 KiB
CSS
11582 lines
399 KiB
CSS
/* 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; }
|