@tangle-network/charts 0.21.22 → 0.21.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/figure.css CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  .tgc-noteref {
28
28
  margin-left: 2px;
29
- font-size: 12px;
29
+ font-size: 14px;
30
30
  font-weight: 400;
31
31
  color: var(--chart-ink-muted);
32
32
  text-decoration: none;
@@ -71,15 +71,15 @@
71
71
 
72
72
  .tgc-read {
73
73
  margin: 8px 0 0;
74
- font-size: 12px;
75
- line-height: 16px;
74
+ font-size: 14px;
75
+ line-height: 20px;
76
76
  color: var(--chart-ink-muted);
77
77
  }
78
78
 
79
79
  .tgc-data {
80
80
  margin: 8px 0 0;
81
- font-size: 12px;
82
- line-height: 16px;
81
+ font-size: 14px;
82
+ line-height: 20px;
83
83
  }
84
84
  .tgc-data > summary {
85
85
  cursor: pointer;
@@ -104,8 +104,8 @@
104
104
  .tgc-table {
105
105
  border-collapse: collapse;
106
106
  min-width: 100%;
107
- font-size: 12px;
108
- line-height: 16px;
107
+ font-size: 14px;
108
+ line-height: 20px;
109
109
  color: var(--chart-ink);
110
110
  /* A host page's table rules must not restyle the figure's table. */
111
111
  border: 0;
@@ -158,8 +158,8 @@
158
158
  .tgc-notes {
159
159
  margin: 16px 0 0;
160
160
  padding-left: 20px;
161
- font-size: 12px;
162
- line-height: 16px;
161
+ font-size: 14px;
162
+ line-height: 20px;
163
163
  color: var(--chart-ink-muted);
164
164
  }
165
165
  .tgc-notes li {
@@ -366,8 +366,11 @@ interface FlowPause {
366
366
  }
367
367
  /** A host verdict: `label` is the legend text, `tone` the status color. */
368
368
  interface FlowOutcome {
369
- tone: 'ok' | 'neutral' | 'failed' | 'warn';
369
+ /** `live` is a running agent: the one running hue the host uses for every live mark. */
370
+ tone: 'ok' | 'live' | 'neutral' | 'failed' | 'warn';
370
371
  label: string;
372
+ /** A hollow ring in the tone instead of a filled dot: a second outcome on the same tone (Done beside Shipped, Stopped beside a plain end). */
373
+ ring?: boolean;
371
374
  }
372
375
  /** A labelled instant on the trace clock, such as a release or an alert; drawn as a line across the rows. */
373
376
  interface FlowMarker {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-CSy-mQ31.js";
1
+ import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-KGCtujbr.js";
2
2
  //#region src/block.d.ts
3
3
  interface RenderFigureOptions {
4
4
  /** Chart uses a closed data disclosure; plot keeps only the SVG and accessible data. */
package/dist/index.js CHANGED
@@ -1136,7 +1136,7 @@ function plot$1(costed, unplaced, drawLine, o) {
1136
1136
  //#endregion
1137
1137
  //#region src/css.generated.ts
1138
1138
  /** The figure block and mark styles (src/figure.css), for documents that inline their CSS. */
1139
- const figureCss = "/* @tangle-network/charts — figure block and mark styles.\n *\n * Every colour is a --chart-* token from @tangle-network/brand tokens.css, so a\n * figure follows the page theme. A standalone document without brand tokens\n * embeds `chartTokensCss` first. Fonts fall back to Inter and JetBrains Mono.\n *\n * Each figure carries a 720 px and a 358 px render. The swap follows the\n * figure's own width (a container query), so a narrow column shows the phone\n * render even on a wide screen. */\n\n.tgc-figure {\n margin: 32px 0 0;\n padding: 0;\n max-width: 720px;\n color: var(--chart-ink);\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n\n.tgc-finding {\n margin: 0 0 8px;\n font-size: 18px;\n line-height: 24px;\n font-weight: 600;\n color: var(--chart-ink);\n}\n\n.tgc-noteref {\n margin-left: 2px;\n font-size: 12px;\n font-weight: 400;\n color: var(--chart-ink-muted);\n text-decoration: none;\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n}\n.tgc-noteref:hover,\n.tgc-noteref:focus-visible {\n color: var(--chart-accent);\n text-decoration: underline;\n}\n\n.tgc-lede {\n margin: 0 0 16px;\n font-size: 14px;\n line-height: 20px;\n color: var(--text-secondary, var(--chart-ink));\n}\n\n.tgc-plot {\n container-type: inline-size;\n}\n.tgc-wide svg,\n.tgc-narrow svg {\n display: block;\n overflow: visible;\n}\n.tgc-narrow {\n display: none;\n}\n.tgc-narrow svg {\n max-width: 100%;\n height: auto;\n}\n@container (max-width: 719.98px) {\n .tgc-wide {\n display: none;\n }\n .tgc-narrow {\n display: block;\n }\n}\n\n.tgc-read {\n margin: 8px 0 0;\n font-size: 12px;\n line-height: 16px;\n color: var(--chart-ink-muted);\n}\n\n.tgc-data {\n margin: 8px 0 0;\n font-size: 12px;\n line-height: 16px;\n}\n.tgc-data > summary {\n cursor: pointer;\n color: var(--chart-ink-muted);\n width: fit-content;\n}\n.tgc-data > summary:hover {\n color: var(--chart-ink);\n}\n.tgc-data[open] > summary {\n margin-bottom: 8px;\n}\n\n.tgc-scroll {\n overflow-x: auto;\n}\n.tgc-scroll:focus-visible {\n outline: 2px solid var(--chart-accent);\n outline-offset: 2px;\n}\n\n.tgc-table {\n border-collapse: collapse;\n min-width: 100%;\n font-size: 12px;\n line-height: 16px;\n color: var(--chart-ink);\n /* A host page's table rules must not restyle the figure's table. */\n border: 0;\n border-radius: 0;\n background: none;\n}\n.tgc-table caption {\n text-align: left;\n padding: 0 0 8px;\n color: var(--chart-ink-muted);\n}\n.tgc-table th,\n.tgc-table td {\n padding: 8px 12px 8px 0;\n border-bottom: 1px solid var(--chart-rule);\n text-align: left;\n vertical-align: top;\n font-weight: 400;\n font-size: inherit;\n color: inherit;\n background: none;\n text-transform: none;\n letter-spacing: normal;\n}\n.tgc-table thead th {\n color: var(--chart-ink-muted);\n white-space: nowrap;\n}\n.tgc-table tbody th {\n position: sticky;\n left: 0;\n background: var(--chart-canvas, var(--depth-1, transparent));\n}\n.tgc-table .tgc-n {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.tgc-table thead .tgc-n {\n font-family: inherit;\n}\n.tgc-table tbody tr:hover {\n background: var(--chart-shade);\n}\n.tgc-narrow-table .tgc-table {\n display: block;\n overflow-x: auto;\n}\n\n.tgc-notes {\n margin: 16px 0 0;\n padding-left: 20px;\n font-size: 12px;\n line-height: 16px;\n color: var(--chart-ink-muted);\n}\n.tgc-notes li {\n margin: 0 0 8px;\n padding-left: 4px;\n}\n.tgc-notes li:target {\n color: var(--chart-ink);\n}\n.tgc-backlink {\n color: var(--chart-ink-muted);\n text-decoration: none;\n}\n\n.tgc-refusals {\n margin: 8px 0 0;\n padding-left: 20px;\n font-size: 14px;\n line-height: 22px;\n color: var(--chart-ink-muted);\n}\n\n/* Marks */\n.tgc-svg {\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n.tgc-svg .tgc-num {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n}\n.tgc-ink {\n fill: var(--chart-ink);\n}\n.tgc-ink-muted {\n fill: var(--chart-ink-muted);\n}\n.tgc-bar,\n.tgc-ci,\n.tgc-pt-front {\n fill: var(--chart-accent);\n}\n.tgc-gridline {\n stroke: var(--chart-grid);\n stroke-width: 1;\n}\n.tgc-rule {\n stroke: var(--chart-rule);\n stroke-width: 1;\n}\n.tgc-axis,\n.tgc-zero {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-whisker,\n.tgc-tick {\n stroke: var(--chart-ink);\n stroke-width: 2;\n stroke-linecap: round;\n}\n.tgc-threshold {\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-dasharray: 3 3;\n}\n.tgc-front-line {\n fill: none;\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-linejoin: round;\n}\n.tgc-pt,\n.tgc-open {\n fill: none;\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-pt-est {\n fill: none;\n stroke: var(--chart-accent);\n stroke-width: 2;\n}\n.tgc-pt-ci,\n.tgc-strike {\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-leader {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-ci-exact,\n.tgc-dot {\n fill: var(--chart-muted);\n}\n.tgc-dashed {\n stroke-dasharray: 2 2;\n}\n.tgc-tier-c {\n fill: var(--chart-tier-c);\n}\n.tgc-tier-b {\n fill: var(--chart-tier-b);\n}\n.tgc-tier-a {\n fill: var(--chart-tier-a);\n}\n.tgc-tier-s {\n fill: var(--chart-tier-s);\n}\n/* Neutral tint for counts: never the accent, capped at half strength so ink keeps 6:1. */\n.tgc-tint {\n fill: color-mix(in srgb, var(--chart-muted) calc(var(--s, 0) * 50%), transparent);\n}\n\n@media print {\n .tgc-plot {\n container-type: normal;\n }\n .tgc-wide {\n display: block;\n }\n .tgc-narrow {\n display: none;\n }\n .tgc-wide svg {\n width: 100%;\n height: auto;\n }\n .tgc-figure {\n break-inside: avoid;\n }\n}\n\n/* Chart-led surfaces keep supporting text in the native disclosure. */\n.tgc-figure-chart { max-width: none; margin: 0; }\n.tgc-figure-chart .tgc-wide svg { width: 100%; height: auto; }\n.tgc-wide:has(.tgc-vertical), .tgc-narrow:has(.tgc-vertical) { overflow-x: auto; }\n.tgc-vertical { width: 100%; }\n.tgc-narrow .tgc-vertical { max-width: none; }\n.tgc-column-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-column:focus-visible { outline: none; }\n.tgc-column:focus-visible .tgc-column-hit { stroke: var(--chart-accent); }\n.tgc-vertical-bar { fill: var(--chart-accent); }\n/* Identity assets keep their approved colors on a white plate in either theme. */\n.tgc-logo-plate { fill: #fff; stroke: var(--chart-rule); }\n.tgc-column-tip { opacity: 0; pointer-events: none; }\n.tgc-tip-bg { fill: var(--chart-ink); }\n.tgc-tip-ink { fill: var(--chart-canvas); }\n\n.tgc-series-line { fill: none; stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 2; stroke-linejoin: round; }\n.tgc-series-point, .tgc-series-key { fill: var(--tgc-series, var(--chart-accent)); stroke: var(--chart-canvas); stroke-width: 2; }\n\n/* Semantic states stay tied to Brand status tokens. No-data has its own neutral mark. */\n.tgc-heat-pass { fill: color-mix(in srgb, var(--chart-pass, var(--status-running)) 19%, var(--chart-canvas)); stroke: var(--chart-pass, var(--status-running)); stroke-width: .8; }\n.tgc-heat-breach { fill: color-mix(in srgb, var(--chart-breach, var(--status-error)) 17%, var(--chart-canvas)); stroke: var(--chart-breach, var(--status-error)); stroke-width: .8; }\n.tgc-heat-no-data { fill: var(--chart-grid); stroke: var(--chart-rule); stroke-width: .8; }\n.tgc-heat-label { fill: var(--chart-ink); }\n.tgc-heat-missing { fill: var(--chart-ink-muted); }\n.tgc-wide:has(.tgc-heat-pass), .tgc-wide:has(.tgc-heat-breach) { overflow-x: auto; }\n.tgc-range-pass { fill: var(--chart-pass, var(--status-running)); }\n.tgc-range-breach { fill: var(--chart-breach, var(--status-error)); }\n.tgc-range-dot-pass { fill: var(--chart-canvas); stroke: var(--chart-pass, var(--status-running)); stroke-width: 3; }\n.tgc-range-dot-breach { fill: var(--chart-canvas); stroke: var(--chart-breach, var(--status-error)); stroke-width: 3; }\n.tgc-waterfall-bar { fill: var(--chart-accent); }\n.tgc-series-hit { fill: transparent; stroke: transparent; }\n.tgc-series-observation:focus-visible { outline: none; }\n.tgc-series-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-series-tip { opacity: 0; pointer-events: none; }\n\n.tgc-scalar-table { min-width: 620px; }\n.tgc-scalar-table tbody th { min-width: 180px; max-width: 260px; }\n\n/* Plot presentation keeps the same complete data available to assistive technology. */\n.tgc-accessible-data { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.tgc-figure-plot .tgc-wide svg { box-sizing: content-box; width: calc(100% - 48px); padding: 0 24px; }\n/* One plot text size on the 12/14/18/32 scale; numbers differ by tabular figures, not size. */\n.tgc-figure-plot svg text { font-size: 14px; }\n/* Larger tooltip text needs padding outside the original report backdrop. */\n.tgc-figure-plot .tgc-tip-bg { stroke: var(--chart-ink); stroke-width: 24px; }\n.tgc-figure-plot .tgc-phone-plot svg { min-width: var(--tgc-phone-width); }\n.tgc-figure-plot .tgc-wide, .tgc-figure-plot .tgc-narrow { overflow-x: auto; }\n\n.tgc-figure-plot .tgc-wide-fallback svg { min-width: var(--tgc-fallback-width); }\n.tgc-phone-plot:focus-visible, .tgc-wide-fallback:focus-visible { outline: 2px solid var(--chart-accent); outline-offset: 2px; }\n\n/* Recorded events and spans. Kinds differ by stroke pattern as well as token, so they read without colour. */\n.tgc-mark { stroke-width: 1.5; }\n.tgc-mark-pass { stroke: var(--chart-pass, var(--status-running)); }\n.tgc-mark-deadline { stroke: var(--chart-ink); stroke-dasharray: 5 3; }\n.tgc-mark-died { stroke: var(--chart-breach, var(--status-error)); stroke-width: 2; }\n.tgc-mark-event { stroke: var(--chart-ink-muted); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-release { stroke: var(--chart-ink-muted); }\n.tgc-mark-steer { stroke: var(--chart-accent); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-ruling { stroke: var(--chart-ink); stroke-dasharray: 6 2 1 2; }\n.tgc-mark-wake { stroke: var(--chart-ink-muted); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-head { fill: var(--chart-ink-muted); stroke: var(--chart-canvas); stroke-width: 1; }\n[data-mark=\"steer\"] .tgc-mark-head { fill: var(--chart-accent); }\n[data-mark=\"ruling\"] .tgc-mark-head { fill: var(--chart-ink); }\n/* A recorded verdict colours the rule and head: pass and fail take the status tones, unknown the muted tone. */\n.tgc-verdict-pass .tgc-mark { stroke: var(--chart-pass, var(--status-running)); }\n.tgc-verdict-pass .tgc-mark-head { fill: var(--chart-pass, var(--status-running)); }\n.tgc-verdict-fail .tgc-mark { stroke: var(--chart-breach, var(--status-error)); }\n.tgc-verdict-fail .tgc-mark-head { fill: var(--chart-breach, var(--status-error)); }\n.tgc-verdict-unknown .tgc-mark { stroke: var(--chart-muted); }\n.tgc-verdict-unknown .tgc-mark-head { fill: var(--chart-muted); }\n.tgc-mark-label { fill: var(--chart-ink); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n.tgc-mark-edge { fill: var(--chart-ink-muted); }\n.tgc-band-after-pass { fill: color-mix(in srgb, var(--chart-pass, var(--status-running)) 12%, transparent); }\n.tgc-band-idle { fill: var(--chart-shade); }\n/* A system-wide wall, such as a quota window: a breach wash across every lane, topped by a solid edge. */\n.tgc-band-blocked { fill: color-mix(in srgb, var(--chart-breach, var(--status-error)) 9%, transparent); }\n.tgc-band-wall { stroke: var(--chart-breach, var(--status-error)); stroke-width: 2; }\n/* Missing data: a neutral wash under hatch lines, never a zero-height mark. */\n.tgc-hatch-bg { fill: color-mix(in srgb, var(--chart-muted) 10%, transparent); }\n.tgc-hatch { fill: none; stroke: var(--chart-muted); stroke-width: 1; }\n.tgc-span { fill: var(--chart-accent); }\n.tgc-span-muted { fill: var(--chart-muted); }\n.tgc-span-open { stroke: var(--chart-ink); stroke-width: 1.5; stroke-dasharray: 3 2; }\n.tgc-now { stroke: var(--chart-ink); stroke-width: 1.5; }\n/* Timeline trees and attempts. Ok ticks rise above the bar and failed ticks drop below it, so they read without colour. */\n.tgc-tree { fill: none; stroke: var(--chart-muted); stroke-width: 1; }\n.tgc-caret { fill: var(--chart-ink-muted); }\n.tgc-lane-toggle { cursor: pointer; }\n.tgc-lane-toggle:focus-visible { outline: none; }\n.tgc-lane-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-lane-toggle:hover .tgc-lane-hit { fill: var(--chart-shade); }\n.tgc-lane-toggle:focus-visible .tgc-lane-hit { stroke: var(--chart-accent); }\n.tgc-tick { stroke-width: 1.5; }\n.tgc-tick-ok { stroke: var(--chart-outcome-ok); }\n.tgc-tick-quota { stroke: var(--chart-outcome-quota); }\n.tgc-tick-lost { stroke: var(--chart-outcome-lost); }\n.tgc-tick-rate-limit { stroke: var(--chart-outcome-rate-limit); }\n.tgc-tick-oom { stroke: var(--chart-outcome-oom); }\n.tgc-tick-refused { stroke: var(--chart-outcome-refused); }\n.tgc-tick-aborted { stroke: var(--chart-outcome-neutral); }\n.tgc-tick-other { stroke: var(--chart-outcome-other); }\n/* Lane labels and counts keep a canvas halo, so rules and ticks under them never strike through the text. */\n.tgc-lane-label, .tgc-count { paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n.tgc-count { fill: var(--chart-ink); }\n.tgc-count-ok { fill: var(--chart-pass, var(--status-running)); }\n.tgc-count-failed { fill: var(--chart-breach, var(--status-error)); }\n\n/* FigureView (React) picks the render by measured width; the wide plot fills its container. */\n.tgc-figure-view { max-width: none; margin: 0; }\n.tgc-view-plot svg { display: block; overflow: visible; }\n.tgc-view-wide svg { width: 100%; height: auto; }\n.tgc-view-narrow svg { max-width: 100%; height: auto; }\n\n/*\n * One fixed palette in every view (src/outcomes.ts names them), each a light-dark() pair picked by the page's\n * color-scheme. Green, red and amber are status colours and mean one thing everywhere: green is a turn or run that\n * worked (an `ok` attempt, a solved run), red a run that died, amber a run stalled or a turn rate-limited. Outcomes\n * pass the dataviz colour-vision checks as adjacent pairs in stack order (ok, quota, lost, rate-limit, oom, refused) on\n * the light surface and on #161616; `aborted` and `other` are two distinct greys. Checkers take hues no status uses\n * (blue for the current checker, then magenta, cyan and violet for older ones) and pass as adjacent series; their\n * marks also carry a vN label. Run endings reuse those colours. `scripts/palette-check.mjs` re-runs the validator. A\n * page may override any of these properties; :where() keeps them at zero specificity.\n */\n:where(:root) {\n --chart-outcome-ok: light-dark(#1baf7a, #199e70);\n --chart-outcome-quota: light-dark(#eb6834, #d95926);\n --chart-outcome-lost: light-dark(#0f9fb8, #1a9cb3);\n --chart-outcome-rate-limit: light-dark(#eda100, #c98500);\n --chart-outcome-oom: light-dark(#e87ba4, #d55181);\n --chart-outcome-refused: light-dark(#6250d6, #9085e9);\n --chart-outcome-neutral: #898781;\n --chart-outcome-other: #b5b3ac;\n --chart-checker-0: light-dark(#2a78d6, #3987e5);\n --chart-checker-1: light-dark(#b8478f, #c2408f);\n --chart-checker-2: light-dark(#0f9fb8, #1a9cb3);\n --chart-checker-3: light-dark(#6250d6, #8c7ce6);\n /* How a run ended: solved green, died red, stalled amber, live blue; not solved and cancelled are neutral. */\n --chart-ending-solved: var(--chart-outcome-ok);\n --chart-ending-died: light-dark(#d03b3b, #e05252);\n --chart-ending-stalled: var(--chart-outcome-rate-limit);\n --chart-ending-live: var(--chart-checker-0);\n --chart-ending-not-solved: light-dark(#6b7a90, #8494ab);\n --chart-ending-cancelled: var(--chart-outcome-neutral);\n --chart-ending-never-started: color-mix(in srgb, var(--chart-outcome-other) 45%, transparent);\n}\n/* A series drawn in a checker's hue; past the fourth hue it is muted and named by its label. */\n.tgc-checker-0 { --tgc-series: var(--chart-checker-0); }\n.tgc-checker-1 { --tgc-series: var(--chart-checker-1); }\n.tgc-checker-2 { --tgc-series: var(--chart-checker-2); }\n.tgc-checker-3 { --tgc-series: var(--chart-checker-3); }\n.tgc-checker-other { --tgc-series: var(--chart-muted); }\n/* Recorded verdicts: a pass is filled, a fail or an incomparable value is an outline, an undecided one dashed. */\n.tgc-series-point.tgc-point-outline { fill: var(--chart-canvas); stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 1.75; }\n.tgc-series-point.tgc-point-undecided { fill: var(--chart-canvas); stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 1.5; stroke-dasharray: 2 1.5; }\n.tgc-strip-hatch .tgc-hatch { stroke: var(--tgc-series, var(--chart-muted)); }\n/* A failing verdict without a value is a neutral ring: in its checker's hue it read as that checker passing. */\n.tgc-series-point.tgc-point-outline.tgc-strip-fail { stroke: var(--chart-ink-muted); }\n.tgc-strip-edge { fill: none; stroke: var(--tgc-series, var(--chart-muted)); stroke-width: 1.25; }\n.tgc-strip-observation:focus-visible { outline: none; }\n.tgc-strip-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-point-badge { paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace); }\n.tgc-point-label { fill: var(--chart-ink-muted); }\n.tgc-glyph-note { fill: var(--chart-ink); font-weight: 600; }\n/* Status colours appear only on these glyphs. */\n.tgc-glyph-check { fill: var(--chart-pass, var(--status-running)); font-weight: 600; }\n.tgc-glyph-cross { fill: var(--chart-breach, var(--status-error)); font-weight: 600; }\n/* Recorded targets sit under the data: a target is a soft band between solid edges, a stretch a dashed outline. */\n.tgc-target-target { fill: color-mix(in srgb, var(--chart-ink) 7%, transparent); }\n.tgc-target-stretch { fill: none; }\n.tgc-target-target-edge, .tgc-target-target.tgc-target-line { stroke: var(--chart-ink-muted); stroke-width: 1; }\n.tgc-target-stretch-edge, .tgc-target-stretch.tgc-target-line { stroke: var(--chart-ink-muted); stroke-width: 1; stroke-dasharray: 4 3; }\n.tgc-target-label { fill: var(--chart-ink-muted); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n/* Group headers: a labelled bracket above the plot and a light rule at each inner edge. */\n.tgc-group-rule { stroke: var(--chart-ink-muted); stroke-width: 1.5; }\n.tgc-group-edge { stroke: var(--chart-rule); stroke-width: 1; }\n.tgc-group-label { fill: var(--chart-ink); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n/* The verdict key: glyphs in the neutral ink, so they describe a mark's form, not a series. */\n.tgc-key-mark { stroke-width: 1.5; }\n.tgc-key-filled { fill: var(--chart-ink-muted); stroke: none; }\n.tgc-key-outline { fill: none; stroke: var(--chart-ink-muted); }\n.tgc-key-dashed { fill: none; stroke: var(--chart-ink-muted); stroke-dasharray: 2 1.5; }\n";
1139
+ const figureCss = "/* @tangle-network/charts — figure block and mark styles.\n *\n * Every colour is a --chart-* token from @tangle-network/brand tokens.css, so a\n * figure follows the page theme. A standalone document without brand tokens\n * embeds `chartTokensCss` first. Fonts fall back to Inter and JetBrains Mono.\n *\n * Each figure carries a 720 px and a 358 px render. The swap follows the\n * figure's own width (a container query), so a narrow column shows the phone\n * render even on a wide screen. */\n\n.tgc-figure {\n margin: 32px 0 0;\n padding: 0;\n max-width: 720px;\n color: var(--chart-ink);\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n\n.tgc-finding {\n margin: 0 0 8px;\n font-size: 18px;\n line-height: 24px;\n font-weight: 600;\n color: var(--chart-ink);\n}\n\n.tgc-noteref {\n margin-left: 2px;\n font-size: 14px;\n font-weight: 400;\n color: var(--chart-ink-muted);\n text-decoration: none;\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n}\n.tgc-noteref:hover,\n.tgc-noteref:focus-visible {\n color: var(--chart-accent);\n text-decoration: underline;\n}\n\n.tgc-lede {\n margin: 0 0 16px;\n font-size: 14px;\n line-height: 20px;\n color: var(--text-secondary, var(--chart-ink));\n}\n\n.tgc-plot {\n container-type: inline-size;\n}\n.tgc-wide svg,\n.tgc-narrow svg {\n display: block;\n overflow: visible;\n}\n.tgc-narrow {\n display: none;\n}\n.tgc-narrow svg {\n max-width: 100%;\n height: auto;\n}\n@container (max-width: 719.98px) {\n .tgc-wide {\n display: none;\n }\n .tgc-narrow {\n display: block;\n }\n}\n\n.tgc-read {\n margin: 8px 0 0;\n font-size: 14px;\n line-height: 20px;\n color: var(--chart-ink-muted);\n}\n\n.tgc-data {\n margin: 8px 0 0;\n font-size: 14px;\n line-height: 20px;\n}\n.tgc-data > summary {\n cursor: pointer;\n color: var(--chart-ink-muted);\n width: fit-content;\n}\n.tgc-data > summary:hover {\n color: var(--chart-ink);\n}\n.tgc-data[open] > summary {\n margin-bottom: 8px;\n}\n\n.tgc-scroll {\n overflow-x: auto;\n}\n.tgc-scroll:focus-visible {\n outline: 2px solid var(--chart-accent);\n outline-offset: 2px;\n}\n\n.tgc-table {\n border-collapse: collapse;\n min-width: 100%;\n font-size: 14px;\n line-height: 20px;\n color: var(--chart-ink);\n /* A host page's table rules must not restyle the figure's table. */\n border: 0;\n border-radius: 0;\n background: none;\n}\n.tgc-table caption {\n text-align: left;\n padding: 0 0 8px;\n color: var(--chart-ink-muted);\n}\n.tgc-table th,\n.tgc-table td {\n padding: 8px 12px 8px 0;\n border-bottom: 1px solid var(--chart-rule);\n text-align: left;\n vertical-align: top;\n font-weight: 400;\n font-size: inherit;\n color: inherit;\n background: none;\n text-transform: none;\n letter-spacing: normal;\n}\n.tgc-table thead th {\n color: var(--chart-ink-muted);\n white-space: nowrap;\n}\n.tgc-table tbody th {\n position: sticky;\n left: 0;\n background: var(--chart-canvas, var(--depth-1, transparent));\n}\n.tgc-table .tgc-n {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n.tgc-table thead .tgc-n {\n font-family: inherit;\n}\n.tgc-table tbody tr:hover {\n background: var(--chart-shade);\n}\n.tgc-narrow-table .tgc-table {\n display: block;\n overflow-x: auto;\n}\n\n.tgc-notes {\n margin: 16px 0 0;\n padding-left: 20px;\n font-size: 14px;\n line-height: 20px;\n color: var(--chart-ink-muted);\n}\n.tgc-notes li {\n margin: 0 0 8px;\n padding-left: 4px;\n}\n.tgc-notes li:target {\n color: var(--chart-ink);\n}\n.tgc-backlink {\n color: var(--chart-ink-muted);\n text-decoration: none;\n}\n\n.tgc-refusals {\n margin: 8px 0 0;\n padding-left: 20px;\n font-size: 14px;\n line-height: 22px;\n color: var(--chart-ink-muted);\n}\n\n/* Marks */\n.tgc-svg {\n font-family: var(--font-sans, Inter, ui-sans-serif, system-ui, sans-serif);\n}\n.tgc-svg .tgc-num {\n font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace);\n font-variant-numeric: tabular-nums;\n}\n.tgc-ink {\n fill: var(--chart-ink);\n}\n.tgc-ink-muted {\n fill: var(--chart-ink-muted);\n}\n.tgc-bar,\n.tgc-ci,\n.tgc-pt-front {\n fill: var(--chart-accent);\n}\n.tgc-gridline {\n stroke: var(--chart-grid);\n stroke-width: 1;\n}\n.tgc-rule {\n stroke: var(--chart-rule);\n stroke-width: 1;\n}\n.tgc-axis,\n.tgc-zero {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-whisker,\n.tgc-tick {\n stroke: var(--chart-ink);\n stroke-width: 2;\n stroke-linecap: round;\n}\n.tgc-threshold {\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-dasharray: 3 3;\n}\n.tgc-front-line {\n fill: none;\n stroke: var(--chart-ink);\n stroke-width: 1.5;\n stroke-linejoin: round;\n}\n.tgc-pt,\n.tgc-open {\n fill: none;\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-pt-est {\n fill: none;\n stroke: var(--chart-accent);\n stroke-width: 2;\n}\n.tgc-pt-ci,\n.tgc-strike {\n stroke: var(--chart-muted);\n stroke-width: 1.5;\n}\n.tgc-leader {\n stroke: var(--chart-muted);\n stroke-width: 1;\n}\n.tgc-ci-exact,\n.tgc-dot {\n fill: var(--chart-muted);\n}\n.tgc-dashed {\n stroke-dasharray: 2 2;\n}\n.tgc-tier-c {\n fill: var(--chart-tier-c);\n}\n.tgc-tier-b {\n fill: var(--chart-tier-b);\n}\n.tgc-tier-a {\n fill: var(--chart-tier-a);\n}\n.tgc-tier-s {\n fill: var(--chart-tier-s);\n}\n/* Neutral tint for counts: never the accent, capped at half strength so ink keeps 6:1. */\n.tgc-tint {\n fill: color-mix(in srgb, var(--chart-muted) calc(var(--s, 0) * 50%), transparent);\n}\n\n@media print {\n .tgc-plot {\n container-type: normal;\n }\n .tgc-wide {\n display: block;\n }\n .tgc-narrow {\n display: none;\n }\n .tgc-wide svg {\n width: 100%;\n height: auto;\n }\n .tgc-figure {\n break-inside: avoid;\n }\n}\n\n/* Chart-led surfaces keep supporting text in the native disclosure. */\n.tgc-figure-chart { max-width: none; margin: 0; }\n.tgc-figure-chart .tgc-wide svg { width: 100%; height: auto; }\n.tgc-wide:has(.tgc-vertical), .tgc-narrow:has(.tgc-vertical) { overflow-x: auto; }\n.tgc-vertical { width: 100%; }\n.tgc-narrow .tgc-vertical { max-width: none; }\n.tgc-column-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-column:focus-visible { outline: none; }\n.tgc-column:focus-visible .tgc-column-hit { stroke: var(--chart-accent); }\n.tgc-vertical-bar { fill: var(--chart-accent); }\n/* Identity assets keep their approved colors on a white plate in either theme. */\n.tgc-logo-plate { fill: #fff; stroke: var(--chart-rule); }\n.tgc-column-tip { opacity: 0; pointer-events: none; }\n.tgc-tip-bg { fill: var(--chart-ink); }\n.tgc-tip-ink { fill: var(--chart-canvas); }\n\n.tgc-series-line { fill: none; stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 2; stroke-linejoin: round; }\n.tgc-series-point, .tgc-series-key { fill: var(--tgc-series, var(--chart-accent)); stroke: var(--chart-canvas); stroke-width: 2; }\n\n/* Semantic states stay tied to Brand status tokens. No-data has its own neutral mark. */\n.tgc-heat-pass { fill: color-mix(in srgb, var(--chart-pass, var(--status-running)) 19%, var(--chart-canvas)); stroke: var(--chart-pass, var(--status-running)); stroke-width: .8; }\n.tgc-heat-breach { fill: color-mix(in srgb, var(--chart-breach, var(--status-error)) 17%, var(--chart-canvas)); stroke: var(--chart-breach, var(--status-error)); stroke-width: .8; }\n.tgc-heat-no-data { fill: var(--chart-grid); stroke: var(--chart-rule); stroke-width: .8; }\n.tgc-heat-label { fill: var(--chart-ink); }\n.tgc-heat-missing { fill: var(--chart-ink-muted); }\n.tgc-wide:has(.tgc-heat-pass), .tgc-wide:has(.tgc-heat-breach) { overflow-x: auto; }\n.tgc-range-pass { fill: var(--chart-pass, var(--status-running)); }\n.tgc-range-breach { fill: var(--chart-breach, var(--status-error)); }\n.tgc-range-dot-pass { fill: var(--chart-canvas); stroke: var(--chart-pass, var(--status-running)); stroke-width: 3; }\n.tgc-range-dot-breach { fill: var(--chart-canvas); stroke: var(--chart-breach, var(--status-error)); stroke-width: 3; }\n.tgc-waterfall-bar { fill: var(--chart-accent); }\n.tgc-series-hit { fill: transparent; stroke: transparent; }\n.tgc-series-observation:focus-visible { outline: none; }\n.tgc-series-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-series-tip { opacity: 0; pointer-events: none; }\n\n.tgc-scalar-table { min-width: 620px; }\n.tgc-scalar-table tbody th { min-width: 180px; max-width: 260px; }\n\n/* Plot presentation keeps the same complete data available to assistive technology. */\n.tgc-accessible-data { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n.tgc-figure-plot .tgc-wide svg { box-sizing: content-box; width: calc(100% - 48px); padding: 0 24px; }\n/* One plot text size on the 12/14/18/32 scale; numbers differ by tabular figures, not size. */\n.tgc-figure-plot svg text { font-size: 14px; }\n/* Larger tooltip text needs padding outside the original report backdrop. */\n.tgc-figure-plot .tgc-tip-bg { stroke: var(--chart-ink); stroke-width: 24px; }\n.tgc-figure-plot .tgc-phone-plot svg { min-width: var(--tgc-phone-width); }\n.tgc-figure-plot .tgc-wide, .tgc-figure-plot .tgc-narrow { overflow-x: auto; }\n\n.tgc-figure-plot .tgc-wide-fallback svg { min-width: var(--tgc-fallback-width); }\n.tgc-phone-plot:focus-visible, .tgc-wide-fallback:focus-visible { outline: 2px solid var(--chart-accent); outline-offset: 2px; }\n\n/* Recorded events and spans. Kinds differ by stroke pattern as well as token, so they read without colour. */\n.tgc-mark { stroke-width: 1.5; }\n.tgc-mark-pass { stroke: var(--chart-pass, var(--status-running)); }\n.tgc-mark-deadline { stroke: var(--chart-ink); stroke-dasharray: 5 3; }\n.tgc-mark-died { stroke: var(--chart-breach, var(--status-error)); stroke-width: 2; }\n.tgc-mark-event { stroke: var(--chart-ink-muted); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-release { stroke: var(--chart-ink-muted); }\n.tgc-mark-steer { stroke: var(--chart-accent); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-ruling { stroke: var(--chart-ink); stroke-dasharray: 6 2 1 2; }\n.tgc-mark-wake { stroke: var(--chart-ink-muted); stroke-dasharray: 1 3; stroke-linecap: round; }\n.tgc-mark-head { fill: var(--chart-ink-muted); stroke: var(--chart-canvas); stroke-width: 1; }\n[data-mark=\"steer\"] .tgc-mark-head { fill: var(--chart-accent); }\n[data-mark=\"ruling\"] .tgc-mark-head { fill: var(--chart-ink); }\n/* A recorded verdict colours the rule and head: pass and fail take the status tones, unknown the muted tone. */\n.tgc-verdict-pass .tgc-mark { stroke: var(--chart-pass, var(--status-running)); }\n.tgc-verdict-pass .tgc-mark-head { fill: var(--chart-pass, var(--status-running)); }\n.tgc-verdict-fail .tgc-mark { stroke: var(--chart-breach, var(--status-error)); }\n.tgc-verdict-fail .tgc-mark-head { fill: var(--chart-breach, var(--status-error)); }\n.tgc-verdict-unknown .tgc-mark { stroke: var(--chart-muted); }\n.tgc-verdict-unknown .tgc-mark-head { fill: var(--chart-muted); }\n.tgc-mark-label { fill: var(--chart-ink); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n.tgc-mark-edge { fill: var(--chart-ink-muted); }\n.tgc-band-after-pass { fill: color-mix(in srgb, var(--chart-pass, var(--status-running)) 12%, transparent); }\n.tgc-band-idle { fill: var(--chart-shade); }\n/* A system-wide wall, such as a quota window: a breach wash across every lane, topped by a solid edge. */\n.tgc-band-blocked { fill: color-mix(in srgb, var(--chart-breach, var(--status-error)) 9%, transparent); }\n.tgc-band-wall { stroke: var(--chart-breach, var(--status-error)); stroke-width: 2; }\n/* Missing data: a neutral wash under hatch lines, never a zero-height mark. */\n.tgc-hatch-bg { fill: color-mix(in srgb, var(--chart-muted) 10%, transparent); }\n.tgc-hatch { fill: none; stroke: var(--chart-muted); stroke-width: 1; }\n.tgc-span { fill: var(--chart-accent); }\n.tgc-span-muted { fill: var(--chart-muted); }\n.tgc-span-open { stroke: var(--chart-ink); stroke-width: 1.5; stroke-dasharray: 3 2; }\n.tgc-now { stroke: var(--chart-ink); stroke-width: 1.5; }\n/* Timeline trees and attempts. Ok ticks rise above the bar and failed ticks drop below it, so they read without colour. */\n.tgc-tree { fill: none; stroke: var(--chart-muted); stroke-width: 1; }\n.tgc-caret { fill: var(--chart-ink-muted); }\n.tgc-lane-toggle { cursor: pointer; }\n.tgc-lane-toggle:focus-visible { outline: none; }\n.tgc-lane-hit { fill: transparent; stroke: transparent; stroke-width: 2; }\n.tgc-lane-toggle:hover .tgc-lane-hit { fill: var(--chart-shade); }\n.tgc-lane-toggle:focus-visible .tgc-lane-hit { stroke: var(--chart-accent); }\n.tgc-tick { stroke-width: 1.5; }\n.tgc-tick-ok { stroke: var(--chart-outcome-ok); }\n.tgc-tick-quota { stroke: var(--chart-outcome-quota); }\n.tgc-tick-lost { stroke: var(--chart-outcome-lost); }\n.tgc-tick-rate-limit { stroke: var(--chart-outcome-rate-limit); }\n.tgc-tick-oom { stroke: var(--chart-outcome-oom); }\n.tgc-tick-refused { stroke: var(--chart-outcome-refused); }\n.tgc-tick-aborted { stroke: var(--chart-outcome-neutral); }\n.tgc-tick-other { stroke: var(--chart-outcome-other); }\n/* Lane labels and counts keep a canvas halo, so rules and ticks under them never strike through the text. */\n.tgc-lane-label, .tgc-count { paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n.tgc-count { fill: var(--chart-ink); }\n.tgc-count-ok { fill: var(--chart-pass, var(--status-running)); }\n.tgc-count-failed { fill: var(--chart-breach, var(--status-error)); }\n\n/* FigureView (React) picks the render by measured width; the wide plot fills its container. */\n.tgc-figure-view { max-width: none; margin: 0; }\n.tgc-view-plot svg { display: block; overflow: visible; }\n.tgc-view-wide svg { width: 100%; height: auto; }\n.tgc-view-narrow svg { max-width: 100%; height: auto; }\n\n/*\n * One fixed palette in every view (src/outcomes.ts names them), each a light-dark() pair picked by the page's\n * color-scheme. Green, red and amber are status colours and mean one thing everywhere: green is a turn or run that\n * worked (an `ok` attempt, a solved run), red a run that died, amber a run stalled or a turn rate-limited. Outcomes\n * pass the dataviz colour-vision checks as adjacent pairs in stack order (ok, quota, lost, rate-limit, oom, refused) on\n * the light surface and on #161616; `aborted` and `other` are two distinct greys. Checkers take hues no status uses\n * (blue for the current checker, then magenta, cyan and violet for older ones) and pass as adjacent series; their\n * marks also carry a vN label. Run endings reuse those colours. `scripts/palette-check.mjs` re-runs the validator. A\n * page may override any of these properties; :where() keeps them at zero specificity.\n */\n:where(:root) {\n --chart-outcome-ok: light-dark(#1baf7a, #199e70);\n --chart-outcome-quota: light-dark(#eb6834, #d95926);\n --chart-outcome-lost: light-dark(#0f9fb8, #1a9cb3);\n --chart-outcome-rate-limit: light-dark(#eda100, #c98500);\n --chart-outcome-oom: light-dark(#e87ba4, #d55181);\n --chart-outcome-refused: light-dark(#6250d6, #9085e9);\n --chart-outcome-neutral: #898781;\n --chart-outcome-other: #b5b3ac;\n --chart-checker-0: light-dark(#2a78d6, #3987e5);\n --chart-checker-1: light-dark(#b8478f, #c2408f);\n --chart-checker-2: light-dark(#0f9fb8, #1a9cb3);\n --chart-checker-3: light-dark(#6250d6, #8c7ce6);\n /* How a run ended: solved green, died red, stalled amber, live blue; not solved and cancelled are neutral. */\n --chart-ending-solved: var(--chart-outcome-ok);\n --chart-ending-died: light-dark(#d03b3b, #e05252);\n --chart-ending-stalled: var(--chart-outcome-rate-limit);\n --chart-ending-live: var(--chart-checker-0);\n --chart-ending-not-solved: light-dark(#6b7a90, #8494ab);\n --chart-ending-cancelled: var(--chart-outcome-neutral);\n --chart-ending-never-started: color-mix(in srgb, var(--chart-outcome-other) 45%, transparent);\n}\n/* A series drawn in a checker's hue; past the fourth hue it is muted and named by its label. */\n.tgc-checker-0 { --tgc-series: var(--chart-checker-0); }\n.tgc-checker-1 { --tgc-series: var(--chart-checker-1); }\n.tgc-checker-2 { --tgc-series: var(--chart-checker-2); }\n.tgc-checker-3 { --tgc-series: var(--chart-checker-3); }\n.tgc-checker-other { --tgc-series: var(--chart-muted); }\n/* Recorded verdicts: a pass is filled, a fail or an incomparable value is an outline, an undecided one dashed. */\n.tgc-series-point.tgc-point-outline { fill: var(--chart-canvas); stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 1.75; }\n.tgc-series-point.tgc-point-undecided { fill: var(--chart-canvas); stroke: var(--tgc-series, var(--chart-accent)); stroke-width: 1.5; stroke-dasharray: 2 1.5; }\n.tgc-strip-hatch .tgc-hatch { stroke: var(--tgc-series, var(--chart-muted)); }\n/* A failing verdict without a value is a neutral ring: in its checker's hue it read as that checker passing. */\n.tgc-series-point.tgc-point-outline.tgc-strip-fail { stroke: var(--chart-ink-muted); }\n.tgc-strip-edge { fill: none; stroke: var(--tgc-series, var(--chart-muted)); stroke-width: 1.25; }\n.tgc-strip-observation:focus-visible { outline: none; }\n.tgc-strip-observation:focus-visible .tgc-series-hit { stroke: var(--chart-ink); stroke-width: 2; }\n.tgc-point-badge { paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; font-family: var(--font-mono, \"JetBrains Mono\", ui-monospace, monospace); }\n.tgc-point-label { fill: var(--chart-ink-muted); }\n.tgc-glyph-note { fill: var(--chart-ink); font-weight: 600; }\n/* Status colours appear only on these glyphs. */\n.tgc-glyph-check { fill: var(--chart-pass, var(--status-running)); font-weight: 600; }\n.tgc-glyph-cross { fill: var(--chart-breach, var(--status-error)); font-weight: 600; }\n/* Recorded targets sit under the data: a target is a soft band between solid edges, a stretch a dashed outline. */\n.tgc-target-target { fill: color-mix(in srgb, var(--chart-ink) 7%, transparent); }\n.tgc-target-stretch { fill: none; }\n.tgc-target-target-edge, .tgc-target-target.tgc-target-line { stroke: var(--chart-ink-muted); stroke-width: 1; }\n.tgc-target-stretch-edge, .tgc-target-stretch.tgc-target-line { stroke: var(--chart-ink-muted); stroke-width: 1; stroke-dasharray: 4 3; }\n.tgc-target-label { fill: var(--chart-ink-muted); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n/* Group headers: a labelled bracket above the plot and a light rule at each inner edge. */\n.tgc-group-rule { stroke: var(--chart-ink-muted); stroke-width: 1.5; }\n.tgc-group-edge { stroke: var(--chart-rule); stroke-width: 1; }\n.tgc-group-label { fill: var(--chart-ink); paint-order: stroke; stroke: var(--chart-canvas); stroke-width: 3px; stroke-linejoin: round; }\n/* The verdict key: glyphs in the neutral ink, so they describe a mark's form, not a series. */\n.tgc-key-mark { stroke-width: 1.5; }\n.tgc-key-filled { fill: var(--chart-ink-muted); stroke: none; }\n.tgc-key-outline { fill: none; stroke: var(--chart-ink-muted); }\n.tgc-key-dashed { fill: none; stroke: var(--chart-ink-muted); stroke-dasharray: 2 1.5; }\n";
1140
1140
  /** --chart-* resolved per theme, for documents without brand tokens: light, dark by preference or [data-theme="dark"], light in print. */
1141
1141
  const chartTokensCss = ":root{--chart-ink:#2d2d2d;--chart-ink-muted:#686868;--chart-muted:#787878;--chart-grid:#f1f2f7;--chart-rule:#c7c6d6;--chart-shade:rgba(76, 63, 198, 0.06);--chart-accent:hsl(246 54% 51%);--chart-tier-c:#9b9ef0;--chart-tier-b:#7c7fe3;--chart-tier-a:#5b5bd6;--chart-tier-s:#272962;--chart-pass:#059669;--chart-breach:#ba1a1a;--chart-canvas:#ffffff;color-scheme:light}\n@media (prefers-color-scheme: dark){:root:not([data-theme=\"light\"]){--chart-ink:#e6e6e6;--chart-ink-muted:#a5a5a5;--chart-muted:#8e8e8e;--chart-grid:#221f33;--chart-rule:#2a293d;--chart-shade:rgba(165, 180, 252, 0.07);--chart-accent:#818CF8;--chart-tier-c:#4a4a95;--chart-tier-b:#5b5bd6;--chart-tier-a:#8b87f0;--chart-tier-s:#b1a9ff;--chart-pass:#10B981;--chart-breach:#FF4D6D;--chart-canvas:#0a0a14;color-scheme:dark}}\n:root[data-theme=\"dark\"]{--chart-ink:#e6e6e6;--chart-ink-muted:#a5a5a5;--chart-muted:#8e8e8e;--chart-grid:#221f33;--chart-rule:#2a293d;--chart-shade:rgba(165, 180, 252, 0.07);--chart-accent:#818CF8;--chart-tier-c:#4a4a95;--chart-tier-b:#5b5bd6;--chart-tier-a:#8b87f0;--chart-tier-s:#b1a9ff;--chart-pass:#10B981;--chart-breach:#FF4D6D;--chart-canvas:#0a0a14;color-scheme:dark}\n@media print{:root,:root:not([data-theme=\"light\"]),:root[data-theme=\"dark\"]{--chart-ink:#2d2d2d;--chart-ink-muted:#686868;--chart-muted:#787878;--chart-grid:#f1f2f7;--chart-rule:#c7c6d6;--chart-shade:rgba(76, 63, 198, 0.06);--chart-accent:hsl(246 54% 51%);--chart-tier-c:#9b9ef0;--chart-tier-b:#7c7fe3;--chart-tier-a:#5b5bd6;--chart-tier-s:#272962;--chart-pass:#059669;--chart-breach:#ba1a1a;--chart-canvas:#ffffff;color-scheme:light}}";
1142
1142
  //#endregion
@@ -21,6 +21,8 @@
21
21
  --fw-failed: var(--surface-danger-text);
22
22
  --fw-ok: var(--surface-success-text);
23
23
  --fw-amber: var(--surface-warning-text);
24
+ /* The running hue: a live agent's dot, outcome and pill. The host sets it to the one running ink its other pages use. */
25
+ --fw-live: var(--fw-accent);
24
26
  /* Step kinds inside an agent bar: the chart accent and a categorical hue, far from the outcome greens and reds. */
25
27
  --fw-kind-model: var(--chart-accent);
26
28
  --fw-kind-tool: var(--tone-cyan-icon);
@@ -85,10 +87,10 @@
85
87
  .fw-root .fw-title { font-size: 14px; line-height: 20px; font-weight: 600; letter-spacing: -.005em; white-space: nowrap; }
86
88
  .fw-root .fw-state { display: inline-flex; align-items: center; gap: 8px; height: 20px; padding: 0 8px; border-radius: var(--radius-pill); font-size: 14px; font-weight: 600; color: var(--fw-muted); background: var(--fw-well); flex-shrink: 0; }
87
89
  .fw-root .fw-state::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
88
- .fw-root .fw-state-live { color: var(--fw-accent); background: color-mix(in oklab, var(--fw-accent) 12%, transparent); }
90
+ .fw-root .fw-state-live { color: var(--fw-live); background: color-mix(in oklab, var(--fw-live) 12%, transparent); }
89
91
  .fw-root .fw-state-live::before { animation: fw-pulse 1.6s var(--fw-ease) infinite; }
90
- .fw-root .fw-headline { color: var(--fw-muted); display: flex; align-items: center; gap: 8px; }
91
- .fw-root .fw-done-mark { display: inline-grid; place-items: center; width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid var(--fw-ok); color: var(--fw-ok); font-size: 12px; line-height: 1; }
92
+ .fw-root .fw-headline { color: var(--fw-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
93
+ .fw-root .fw-headline::before { content: "·"; margin-right: 8px; }
92
94
  .fw-root .fw-tools { display: flex; align-items: center; gap: 8px; }
93
95
  .fw-root .fw-views { display: inline-flex; padding: 4px; border-radius: var(--radius-md); background: var(--fw-well); border: 1px solid var(--fw-border); }
94
96
  .fw-root .fw-view-button { appearance: none; border: 0; background: transparent; height: 24px; padding: 0 12px; border-radius: var(--radius-sm); font-size: 14px; color: var(--fw-muted); cursor: pointer; transition: background-color 120ms var(--fw-ease), color 120ms var(--fw-ease); }
@@ -208,6 +210,8 @@
208
210
  .fw-root .fw-agent-dot { flex: 0 0 8px; width: 8px; height: 8px; border-radius: 50%; background: var(--line, var(--fw-muted)); }
209
211
  /* The dot is outcome only (critique P0 #3): an agent with no recorded outcome gets a hollow ring, never its lane hue. */
210
212
  .fw-root .fw-row:not([data-outcome]) .fw-agent-dot:not([data-outcome]) { background: transparent; box-shadow: inset 0 0 0 1.5px var(--fw-muted); }
213
+ .fw-root :is(.fw-agent-dot, .fw-legend .fw-swatch)[data-ring] { background: transparent; box-shadow: inset 0 0 0 2px var(--line, var(--fw-muted)); }
214
+ .fw-root :is(.fw-agent-dot, .fw-legend .fw-swatch)[data-outcome="live"]:not([data-ring]) { box-shadow: 0 0 0 3px color-mix(in oklab, var(--fw-live) 22%, transparent); }
211
215
 
212
216
  /* Lanes and marks */
213
217
  .fw-root .fw-lane { position: relative; overflow: hidden; min-width: 0; }
@@ -234,9 +238,9 @@
234
238
  /* While searching, ancestors shown only for context recede and the matching names stand out. */
235
239
  .fw-root .fw-row[data-search="context"] .fw-label { opacity: 0.55; }
236
240
  .fw-root .fw-row[data-search="match"] .fw-name-text { font-weight: 600; }
237
- /* A restart: an amber notch through the bar at that moment; the bar keeps its own state. */
241
+ /* A restart: a notch in the failure ink through the bar at that moment (the run overview's ✕); the bar keeps its own state. */
238
242
  .fw-root .fw-mark { position: absolute; z-index: 2; left: calc(var(--x0) - 1px); width: 2px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2 - 4px); height: calc(var(--fw-bar-h) + 8px);
239
- border-radius: 2px; background: var(--fw-amber); box-shadow: 0 0 0 1px var(--fw-surface); pointer-events: auto; }
243
+ border-radius: 2px; background: var(--fw-failed); box-shadow: 0 0 0 1px var(--fw-surface); pointer-events: auto; }
240
244
  /* Centered by top, not a negative margin, so measured spacing stays on the grid. */
241
245
  .fw-root :is(.fw-bar, .fw-seg) { top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h); width: max(var(--w), calc(var(--on) * 3px)); border-radius: var(--radius-tag); cursor: pointer; display: flex; align-items: center; justify-content: center; overflow: hidden; }
242
246
  .fw-root .fw-bar { background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
@@ -263,10 +267,10 @@
263
267
  failure keeps its red glyph, so color only ever means state. */
264
268
  .fw-root.fw-fill-muted .fw-view-agents [data-tone] { --tone: var(--fw-h0); }
265
269
  /* With per-agent hues the slowest agent keeps its own hue and says so with the amber pill (round 27: an amber-brown bar read "muddy"). */
266
- .fw-root.fw-fill-muted .fw-view-agents .fw-row[data-slowest], .fw-root.fw-fill-muted .fw-view-agents .fw-row[data-slowest] [data-tone] { --tone: var(--fw-amber); }
267
270
  .fw-root [data-outcome="ok"], .fw-root [data-outcome="ok"] [data-tone] { --tone: var(--fw-ok); }
268
271
  .fw-root [data-outcome="failed"], .fw-root [data-outcome="failed"] [data-tone] { --tone: var(--fw-failed); }
269
272
  .fw-root [data-outcome="warn"], .fw-root [data-outcome="warn"] [data-tone] { --tone: var(--fw-amber); }
273
+ .fw-root [data-outcome="live"], .fw-root [data-outcome="live"] [data-tone] { --tone: var(--fw-live); }
270
274
  .fw-root [data-outcome="neutral"], .fw-root [data-outcome="neutral"] [data-tone] { --tone: var(--fw-muted); }
271
275
  .fw-root .fw-legend { display: flex; flex-wrap: wrap; gap: 4px 16px; margin: 8px 0 0; padding: 0; list-style: none; color: var(--fw-muted); font-size: 14px; line-height: 18px; }
272
276
  .fw-root .fw-legend li { display: inline-flex; align-items: center; gap: 8px; }
@@ -279,6 +283,7 @@
279
283
  .fw-root.fw-fill-agent .fw-kinds .fw-swatch-tool { background: light-dark(oklch(0.80 0 0), oklch(0.64 0 0)); }
280
284
  .fw-root .fw-kinds .fw-swatch-tool { width: 12px; height: 8px; border-radius: 2px; background: var(--fw-kind-tool); }
281
285
  .fw-root .fw-kinds .fw-swatch-wait { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 12%, transparent); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 18%, transparent); }
286
+ .fw-root .fw-kinds .fw-swatch-event { width: 8px; height: 8px; border-radius: 50%; background: var(--fw-muted); box-shadow: 0 0 0 2px var(--fw-surface); }
282
287
  .fw-root .fw-kinds .fw-swatch-handoff { width: 10px; height: 8px; border-left: 1.5px solid var(--fw-muted); border-bottom: 1.5px solid var(--fw-muted); border-bottom-left-radius: 3px; }
283
288
  .fw-root .fw-description { margin: 0; color: var(--fw-muted); font-size: 14px; line-height: 20px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
284
289
  .fw-root .fw-legend .fw-swatch { width: 8px; height: 8px; border-radius: 50%; background: var(--line); }
@@ -317,9 +322,9 @@
317
322
  .fw-root .fw-seg-chip { padding: 0 4px; border-radius: var(--radius-pill); background: color-mix(in oklab, currentColor 18%, transparent); font-weight: 600; font-variant-numeric: tabular-nums; }
318
323
  .fw-root .fw-seg-meta { font-variant-numeric: tabular-nums; }
319
324
  @container (max-width: 24px) { .fw-root :is(.fw-seg-label, .fw-view-agents .fw-seg .fw-glyph) { display: none; } }
320
- /* Fail and retry read as filled chips inside the bar, red and amber, as the references draw them. */
325
+ /* Fail and retry read as filled chips inside the bar: red, and a quiet grey for a retry that recovered. */
321
326
  .fw-root .fw-view-agents .fw-seg .fw-glyph { flex: none; margin-left: 6px; display: grid; place-items: center; width: 14px; height: 14px; border-radius: 50%; background: var(--fw-failed); color: light-dark(var(--fw-surface), hsl(var(--hsl-background))); }
322
- .fw-root .fw-view-agents .fw-seg .fw-glyph[data-glyph="retry"] { background: var(--fw-amber); }
327
+ .fw-root .fw-view-agents .fw-seg .fw-glyph[data-glyph="retry"] { background: var(--fw-muted); }
323
328
  .fw-root .fw-view-agents .fw-seg .fw-glyph svg { width: 9px; height: 9px; stroke-width: 2.5; }
324
329
  .fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents :is(.fw-seg-alt.fw-seg-model, .fw-seg-alt.fw-seg-tool) { filter: brightness(.88); }
325
330
  .fw-root .fw-seg-failed { background: color-mix(in oklab, var(--fw-failed) 18%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 70%, transparent); color: var(--fw-failed); }
@@ -335,7 +340,7 @@
335
340
  background: linear-gradient(var(--fw-tint, transparent) 0 0), var(--fw-surface); white-space: nowrap; pointer-events: none; }
336
341
  .fw-root .fw-duration { font-size: 14px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
337
342
  .fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
338
- .fw-root .fw-badge { height: 18px; padding: 0 8px; border-radius: var(--radius-pill); font-size: 14px; line-height: 18px; font-weight: 600; color: var(--fw-amber); background: color-mix(in oklab, var(--fw-amber) 13%, transparent); border: 1px solid color-mix(in oklab, var(--fw-amber) 28%, transparent); }
343
+ .fw-root .fw-badge { height: 20px; padding: 0 8px; border-radius: var(--radius-pill); font-size: 14px; line-height: 20px; font-weight: 500; color: var(--fw-muted); background: transparent; box-shadow: inset 0 0 0 1px var(--fw-border); }
339
344
 
340
345
  /* Handoff elbows: 1.5 px stroke, 6 px radius, a 5 px dot where the handoff leaves its sender. */
341
346
  /* Under the rows (which follow it), so end labels and badges always sit above the connectors. */
@@ -364,7 +369,7 @@
364
369
  /* The playhead: a round knob on a thin neutral track, visible at rest without outweighing the chart. */
365
370
  .fw-root.fw-root-agents .fw-slider::-webkit-slider-thumb { width: 14px; height: 14px; border-radius: 50%; background: var(--fw-text); box-shadow: 0 0 0 3px var(--fw-surface); }
366
371
  .fw-root.fw-root-agents .fw-slider::-moz-range-thumb { width: 14px; height: 14px; border-radius: 50%; background: var(--fw-text); box-shadow: 0 0 0 3px var(--fw-surface); }
367
- .fw-root .fw-card { position: absolute; z-index: 20; width: min(320px, calc(100% - 16px)); padding: 12px; border-radius: var(--radius-xl); background: var(--fw-raised); border: 1px solid var(--fw-border);
372
+ .fw-root .fw-card { position: absolute; z-index: 20; width: min(320px, calc(100% - 16px)); max-height: min(60vh, 560px); overflow-y: auto; overscroll-behavior: contain; padding: 12px; border-radius: var(--radius-xl); background: var(--fw-raised); border: 1px solid var(--fw-border);
368
373
  box-shadow: var(--shadow-dropdown); animation: fw-arrive 140ms var(--fw-ease); }
369
374
  /* A hover card never covers the next mark: only a pinned card (a dialog) takes the pointer. */
370
375
  .fw-root .fw-card[role="tooltip"] { pointer-events: none; }
@@ -377,7 +382,7 @@
377
382
  .fw-root .fw-card-name { flex: 1; min-width: 0; font-size: 14px; line-height: 20px; font-weight: 600; overflow-wrap: anywhere; }
378
383
  .fw-root .fw-pill { flex-shrink: 0; height: 20px; padding: 0 8px; border-radius: var(--radius-pill); font-size: 14px; line-height: 20px; font-weight: 600; }
379
384
  .fw-root .fw-pill-done { color: var(--fw-ok); background: color-mix(in oklab, var(--fw-ok) 13%, transparent); }
380
- .fw-root .fw-pill-running { color: var(--fw-accent); background: color-mix(in oklab, var(--fw-accent) 13%, transparent); }
385
+ .fw-root .fw-pill-running { color: var(--fw-live); background: color-mix(in oklab, var(--fw-live) 13%, transparent); }
381
386
  .fw-root .fw-pill-failed { color: var(--fw-failed); background: color-mix(in oklab, var(--fw-failed) 13%, transparent); }
382
387
  .fw-root .fw-icon-button { appearance: none; display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--fw-muted); cursor: pointer; flex-shrink: 0; }
383
388
  .fw-root .fw-icon-button:hover:not(:disabled) { background: var(--fw-hover); color: var(--fw-text); }
@@ -390,7 +395,8 @@
390
395
  /* A value of several parts wraps between parts, never inside one ("4.0M" / "cached"). */
391
396
  .fw-root .fw-card-parts { display: flex; flex-wrap: wrap; column-gap: 4px; }
392
397
  .fw-root .fw-card-parts > * { white-space: nowrap; }
393
- .fw-root .fw-card-parts > * + *::before { content: "· "; color: var(--fw-muted); }
398
+ .fw-root .fw-card-parts:not(.fw-card-stack) > * + *::before { content: "· "; color: var(--fw-muted); }
399
+ .fw-root .fw-card-stack { flex-direction: column; }
394
400
  .fw-root .fw-card-tools { display: grid; grid-template-columns: minmax(0, max-content) max-content; justify-content: start; column-gap: 16px; margin-top: 4px; color: var(--fw-muted); font-weight: 400; }
395
401
  .fw-root .fw-card-tools > span { display: contents; }
396
402
  .fw-root .fw-card-tools > span > :first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -1,4 +1,4 @@
1
- import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-CSy-mQ31.js";
1
+ import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-KGCtujbr.js";
2
2
  import { CSSProperties, ReactElement, ReactNode } from "react";
3
3
  //#region src/react/sparkline.d.ts
4
4
  type SparklineDirection = "rising" | "falling" | "flat";
@@ -2450,7 +2450,7 @@ function formatCost(usd) {
2450
2450
  /** 9.8k, 754.4k, 1.2M with the exact count left to a title. */
2451
2451
  function formatCount(n) {
2452
2452
  if (n < 1e3) return String(n);
2453
- if (n < 1e6) return `${(n / 1e3).toFixed(1)}k`;
2453
+ if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
2454
2454
  return `${(n / 1e6).toFixed(1)}M`;
2455
2455
  }
2456
2456
  /** Merge marks closer than `gapMs` so a dense row stays a readable strip of at most `cap` marks. Marks narrower
@@ -2859,7 +2859,7 @@ function agentFamilies(agents, open) {
2859
2859
  const running = members.some((row) => spanRunning(row.span));
2860
2860
  const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
2861
2861
  const failed = members.filter((row) => row.span.outcome ? row.span.outcome.tone === "failed" : !row.ok).length;
2862
- const outcome = members.every((row) => row.span.outcome && row.span.outcome.tone === members[0].span.outcome?.tone) ? members[0].span.outcome : void 0;
2862
+ const outcome = members.every((row) => row.span.outcome && row.span.outcome.tone === members[0].span.outcome?.tone && !row.span.outcome.ring === !members[0].span.outcome?.ring) ? members[0].span.outcome : void 0;
2863
2863
  out.push({
2864
2864
  costFloor: known.length < members.length || members.some((row) => row.costFloor),
2865
2865
  id,
@@ -2913,7 +2913,7 @@ function agentFamilies(agents, open) {
2913
2913
  }
2914
2914
  return out;
2915
2915
  }
2916
- /** The second line of an agent row in parts: how it ended and how much it did (failed, 3 LLM calls, 412 tool calls; 6 agents, 4 failed).
2916
+ /** The second line of an agent row in parts: how it ended and how much it did (failed, 3 model calls, 412 tool calls; 6 agents, 4 failed).
2917
2917
  * The row shows the parts in order and drops the last ones whole when the line is full, never half a word. */
2918
2918
  function agentSummary(row) {
2919
2919
  if (row.id.startsWith("family:")) {
@@ -2928,7 +2928,7 @@ function agentSummary(row) {
2928
2928
  const tools = row.counts?.tool ?? 0;
2929
2929
  return [
2930
2930
  row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done"),
2931
- calls ? `${calls} LLM ${calls === 1 ? "call" : "calls"}` : "",
2931
+ calls ? `${calls} model ${calls === 1 ? "call" : "calls"}` : "",
2932
2932
  tools ? `${tools} tool ${tools === 1 ? "call" : "calls"}` : ""
2933
2933
  ].filter(Boolean);
2934
2934
  }
@@ -3170,7 +3170,7 @@ const FitGlyph = () => svg(/* @__PURE__ */ jsx("path", { d: "M2.5 6V3.5a1 1 0 0
3170
3170
  const keepWords = (name) => name.replace(/-/g, "‑");
3171
3171
  const KIND_LABEL = {
3172
3172
  pipeline: "Agent",
3173
- model: "LLM call",
3173
+ model: "Model call",
3174
3174
  tool: "Tool call",
3175
3175
  waiting: "Waiting",
3176
3176
  handoff: "Handoff"
@@ -3203,19 +3203,19 @@ function metrics(row, clockMs, full) {
3203
3203
  ["cached", "cached"]
3204
3204
  ].flatMap(([key, unit]) => tokens[key] === void 0 ? [] : [count(tokens[key], unit)]);
3205
3205
  out.push(["Tokens", /* @__PURE__ */ jsx("span", {
3206
- className: "fw-card-parts",
3206
+ className: `fw-card-parts${parts.length > 2 ? " fw-card-stack" : ""}`,
3207
3207
  children: parts.map((part, i) => /* @__PURE__ */ jsx("span", { children: part }, i))
3208
3208
  })]);
3209
3209
  }
3210
3210
  const latency = row.modelMs;
3211
- if (latency?.length) out.push(["LLM latency", /* @__PURE__ */ jsxs("span", {
3211
+ if (latency?.length) out.push(["Model latency", /* @__PURE__ */ jsxs("span", {
3212
3212
  className: "fw-card-parts",
3213
3213
  children: [/* @__PURE__ */ jsxs("span", { children: [formatSpanDuration(percentile(latency, .5)), " median"] }), latency.length >= 10 && /* @__PURE__ */ jsxs("span", { children: [formatSpanDuration(percentile(latency, .95)), " p95"] })]
3214
3214
  })]);
3215
3215
  const cost = span.costUsd ?? row.rollupCostUsd;
3216
3216
  if (cost !== void 0) out.push(["Cost", rowCostText(row, cost)]);
3217
3217
  const counts = row.counts;
3218
- if (counts?.model) out.push(["LLM calls", counts.model.toLocaleString()]);
3218
+ if (counts?.model) out.push(["Model calls", counts.model.toLocaleString()]);
3219
3219
  if (counts?.tool) {
3220
3220
  const tools = Object.entries(counts.tools).sort(([a, x], [b, y]) => y - x || a.localeCompare(b));
3221
3221
  const rest = tools.length - TOOLS_SHOWN;
@@ -3285,6 +3285,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
3285
3285
  children: [
3286
3286
  /* @__PURE__ */ jsx("span", {
3287
3287
  className: "fw-agent-dot",
3288
+ "data-ring": outcome?.ring || void 0,
3288
3289
  "aria-hidden": true
3289
3290
  }),
3290
3291
  /* @__PURE__ */ jsx("span", {
@@ -3547,13 +3548,14 @@ const FlowRow = memo(function FlowRow(props) {
3547
3548
  className: "fw-agent-dot",
3548
3549
  "data-tone": "0",
3549
3550
  "data-outcome": span.outcome.tone,
3551
+ "data-ring": span.outcome.ring || void 0,
3550
3552
  title: span.outcome.label,
3551
3553
  "aria-hidden": true
3552
3554
  }) : agentView && !step && /* @__PURE__ */ jsx("span", {
3553
3555
  className: "fw-agent-dot",
3554
3556
  "aria-hidden": true
3555
3557
  }),
3556
- running && /* @__PURE__ */ jsx("span", {
3558
+ running && !(fillByAgent && isAgent && span.outcome) && /* @__PURE__ */ jsx("span", {
3557
3559
  className: "fw-spinner",
3558
3560
  role: "status",
3559
3561
  "aria-label": "running"
@@ -4550,9 +4552,9 @@ function summaryCells(trace, at, durationMs) {
4550
4552
  `${tokens.toLocaleString()} tokens`
4551
4553
  ],
4552
4554
  [
4553
- "LLM calls",
4555
+ "Model calls",
4554
4556
  modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
4555
- modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
4557
+ modelCalls === void 0 ? "Model output windows do not establish the number of model requests" : void 0
4556
4558
  ],
4557
4559
  [
4558
4560
  "Tool calls",
@@ -4565,6 +4567,7 @@ function summaryCells(trace, at, durationMs) {
4565
4567
  }
4566
4568
  /** One legend item per outcome label the agents carry, with its count; nothing when no agent has an outcome. */
4567
4569
  const TONE_ORDER = [
4570
+ "live",
4568
4571
  "ok",
4569
4572
  "warn",
4570
4573
  "failed",
@@ -4589,6 +4592,25 @@ function KindLegend({ rows }) {
4589
4592
  }), name] }, kind))
4590
4593
  });
4591
4594
  }
4595
+ /** The axis dots and the dashed lines they drop: one key naming what they mark (a release, or the kinds the host gave). */
4596
+ function MarkerLegend({ markers }) {
4597
+ if (!markers?.length) return null;
4598
+ const kinds = [...new Set(markers.map((marker) => marker.kind ?? "event"))];
4599
+ const name = kinds.length === 1 ? `${kinds[0][0].toUpperCase()}${kinds[0].slice(1)}` : "Event";
4600
+ return /* @__PURE__ */ jsx("ul", {
4601
+ className: "fw-legend fw-kinds",
4602
+ "aria-label": "Markers",
4603
+ children: /* @__PURE__ */ jsxs("li", { children: [
4604
+ /* @__PURE__ */ jsx("span", {
4605
+ className: "fw-swatch-event",
4606
+ "aria-hidden": true
4607
+ }),
4608
+ name,
4609
+ " ",
4610
+ /* @__PURE__ */ jsx("b", { children: markers.length })
4611
+ ] })
4612
+ });
4613
+ }
4592
4614
  function OutcomeLegend({ agents }) {
4593
4615
  const items = /* @__PURE__ */ new Map();
4594
4616
  for (const row of agents) {
@@ -4596,6 +4618,7 @@ function OutcomeLegend({ agents }) {
4596
4618
  if (!outcome) continue;
4597
4619
  const item = items.get(outcome.label) ?? {
4598
4620
  tone: outcome.tone,
4621
+ ring: outcome.ring,
4599
4622
  count: 0
4600
4623
  };
4601
4624
  item.count += 1;
@@ -4611,6 +4634,7 @@ function OutcomeLegend({ agents }) {
4611
4634
  className: "fw-swatch",
4612
4635
  "data-tone": "0",
4613
4636
  "data-outcome": item.tone,
4637
+ "data-ring": item.ring || void 0,
4614
4638
  "aria-hidden": true
4615
4639
  }),
4616
4640
  label,
@@ -4873,11 +4897,13 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4873
4897
  const preferred = card.above ? room.above : room.below;
4874
4898
  const other = card.above ? room.below : room.above;
4875
4899
  const above = box.height <= preferred || box.height > other && preferred >= other ? card.above : !card.above;
4900
+ const floor = globalThis.innerHeight - hostTop - 8 - box.height;
4901
+ const top = Math.max(Math.min(above ? card.anchorTop - 8 - box.height : card.anchorBottom + 8, floor), Math.max(8, 8 - hostTop));
4876
4902
  setCard({
4877
4903
  ...card,
4878
4904
  fitted: true,
4879
4905
  above,
4880
- top: above ? Math.max(8, card.anchorTop - 8 - box.height) : card.anchorBottom + 8
4906
+ top
4881
4907
  });
4882
4908
  }, [card, cardId]);
4883
4909
  latest.current.inspect = (target, element, x) => {
@@ -4994,51 +5020,47 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4994
5020
  className: "fw-heading",
4995
5021
  children: [/* @__PURE__ */ jsxs("div", {
4996
5022
  className: "fw-title-block",
4997
- children: [
4998
- /* @__PURE__ */ jsxs("div", {
4999
- className: "fw-title-line",
5000
- children: [
5001
- /* @__PURE__ */ jsx("span", {
5002
- className: "fw-title-glyph",
5003
- "aria-hidden": true,
5004
- children: /* @__PURE__ */ jsx(TraceGlyph, {})
5005
- }),
5006
- /* @__PURE__ */ jsx("h3", {
5007
- className: "fw-title",
5008
- children: label
5009
- }),
5010
- description && /* @__PURE__ */ jsx("p", {
5011
- className: "fw-description",
5012
- title: description,
5013
- children: description
5014
- }),
5015
- isLive && /* @__PURE__ */ jsx("span", {
5016
- className: "fw-state fw-state-live",
5017
- children: "Live"
5018
- })
5019
- ]
5020
- }),
5021
- view === "agents" && /* @__PURE__ */ jsxs("p", {
5022
- className: "fw-headline",
5023
- "aria-live": "polite",
5024
- children: [
5025
- done && /* @__PURE__ */ jsx("span", {
5026
- className: "fw-done-mark",
5027
- "aria-label": "done",
5028
- children: "✓"
5029
- }),
5030
- agents.length,
5031
- " ",
5032
- agents.length === 1 ? "agent" : "agents",
5033
- sharedDetail ? ` · ${agents.every((row) => row.span.detail === sharedDetail) ? "" : "mostly "}${sharedDetail}` : "",
5034
- done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
5035
- ]
5036
- }),
5037
- /* @__PURE__ */ jsxs("div", {
5038
- className: "fw-legends",
5039
- children: [/* @__PURE__ */ jsx(OutcomeLegend, { agents }), view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows })]
5040
- })
5041
- ]
5023
+ children: [/* @__PURE__ */ jsxs("div", {
5024
+ className: "fw-title-line",
5025
+ children: [
5026
+ /* @__PURE__ */ jsx("span", {
5027
+ className: "fw-title-glyph",
5028
+ "aria-hidden": true,
5029
+ children: /* @__PURE__ */ jsx(TraceGlyph, {})
5030
+ }),
5031
+ /* @__PURE__ */ jsx("h3", {
5032
+ className: "fw-title",
5033
+ children: label
5034
+ }),
5035
+ description && /* @__PURE__ */ jsx("p", {
5036
+ className: "fw-description",
5037
+ title: description,
5038
+ children: description
5039
+ }),
5040
+ view === "agents" && /* @__PURE__ */ jsxs("p", {
5041
+ className: "fw-headline",
5042
+ "aria-live": "polite",
5043
+ children: [
5044
+ agents.length,
5045
+ " ",
5046
+ agents.length === 1 ? "agent" : "agents",
5047
+ sharedDetail ? ` · ${agents.every((row) => row.span.detail === sharedDetail) ? "" : "mostly "}${sharedDetail}` : "",
5048
+ done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
5049
+ ]
5050
+ }),
5051
+ isLive && /* @__PURE__ */ jsx("span", {
5052
+ className: "fw-state fw-state-live",
5053
+ children: "Live"
5054
+ })
5055
+ ]
5056
+ }), /* @__PURE__ */ jsxs("div", {
5057
+ className: "fw-legends",
5058
+ children: [
5059
+ /* @__PURE__ */ jsx(OutcomeLegend, { agents }),
5060
+ view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
5061
+ /* @__PURE__ */ jsx(MarkerLegend, { markers })
5062
+ ]
5063
+ })]
5042
5064
  }), /* @__PURE__ */ jsxs("div", {
5043
5065
  className: "fw-tools",
5044
5066
  children: [/* @__PURE__ */ jsx("div", {
@@ -4,7 +4,7 @@
4
4
  .ov-plot { position: relative; min-width: 0; }
5
5
  .ov-plot svg { display: block; overflow: visible; }
6
6
  line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%, transparent); stroke-width: 1; }
7
- .ov-axis { fill: var(--chart-live-muted, #666); font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
7
+ .ov-axis { fill: var(--chart-live-muted, #666); font-size: 14px; line-height: 20px; font-family: inherit; font-variant-numeric: tabular-nums; }
8
8
  .ov-crosshair { stroke: var(--chart-live-faint, #777); stroke-dasharray: 3 3; }
9
9
  .ov-tip { position: absolute; z-index: 4; display: grid; gap: 4px; min-width: 160px; max-width: 240px; padding: 8px 12px; background: var(--chart-live-raised, #fff); border: 1px solid var(--chart-live-border, #aaa); border-radius: 8px; box-shadow: 0 10px 24px -12px #000; font-size: 14px; line-height: 20px; pointer-events: none; }
10
10
  .ov-tip-line { display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: center; color: var(--chart-live-muted, #666); }
@@ -17,12 +17,12 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
17
17
  .ov-hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; line-height: 20px; color: var(--chart-live-fg, #222); }
18
18
  .ov-hbar-track { height: 8px; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
19
19
  .ov-hbar-track > span { display: block; height: 100%; border-radius: 3px; }
20
- .ov-hbar-value { font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; text-align: right; color: var(--chart-live-fg, #222); }
20
+ .ov-hbar-value { font-size: 14px; line-height: 20px; font-family: inherit; font-variant-numeric: tabular-nums; text-align: right; color: var(--chart-live-fg, #222); }
21
21
  .ov-hbar-note { grid-column: 1 / -1; font-size: 14px; color: var(--chart-live-faint, #777); }
22
22
  .ov-meter { display: grid; gap: 4px; min-width: 0; }
23
23
  .ov-meter-track { display: block; height: 8px; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
24
24
  .ov-meter-track > span { display: block; height: 100%; border-radius: 3px; }
25
- .ov-meter-text { font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; color: var(--chart-live-muted, #666); }
25
+ .ov-meter-text { font-size: 14px; line-height: 20px; font-family: inherit; font-variant-numeric: tabular-nums; color: var(--chart-live-muted, #666); }
26
26
  .ov-spark { display: block; width: 112px; height: 24px; }
27
27
  .ov-spark-empty { font-size: 14px; color: var(--chart-live-faint, #777); }
28
28
  .ov-tip-line i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 4px; vertical-align: 0; }
@@ -88,7 +88,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
88
88
  }
89
89
  .tgc-dot-map { color: var(--chart-live-muted, #8a8a8a); }
90
90
  .tgc-dot-map canvas:focus-visible { outline: 2px solid var(--chart-accent, #7760bd); outline-offset: 2px; }
91
- .tgc-dot-map-tip { z-index: 2; max-width: 300px; padding: 6px 9px; border-radius: 6px; background: var(--chart-tip-bg, #262626); color: var(--chart-tip-ink, #eee); font: 12px/1.4 ui-sans-serif, system-ui, sans-serif; white-space: pre-line; box-shadow: 0 4px 14px rgb(0 0 0 / .3); }
91
+ .tgc-dot-map-tip { z-index: 2; max-width: 300px; padding: 6px 9px; border-radius: 6px; background: var(--chart-tip-bg, #262626); color: var(--chart-tip-ink, #eee); font: 14px/1.4 ui-sans-serif, system-ui, sans-serif; white-space: pre-line; box-shadow: 0 4px 14px rgb(0 0 0 / .3); }
92
92
  .ov-gap { width: 100%; min-width: 96px; height: 24px; }
93
93
  .ov-gap svg { display: block; overflow: visible; }
94
94
  .ov-gap-track { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 40%, transparent); stroke-width: 1; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.21.22",
3
+ "version": "0.21.24",
4
4
  "description": "Static SVG and HTML figures for benchmarks and operational timing. Draws from producer data and refuses what it cannot draw honestly.",
5
5
  "type": "module",
6
6
  "license": "MIT",