@tangle-network/charts 0.20.0 → 0.20.1

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.
@@ -382,6 +382,8 @@ interface FlowTrace {
382
382
  costUsd?: number;
383
383
  /** `list`: priced from a model catalog's list prices, not a bill; the summary says so. */
384
384
  costBasis?: 'billed' | 'list';
385
+ /** The list cost is at most this: the input counts include cached tokens priced as fresh input. */
386
+ costUpperBound?: boolean;
385
387
  /** How many model calls `costUsd` covers; when `priced < total` the total is a lower bound and shown as one. */
386
388
  costCoverage?: {
387
389
  priced: number;
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-Sr8GtLm0.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-DqCMrnTs.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
@@ -3337,6 +3337,13 @@ function unitDotLayout(groups, width, height, o = {}) {
3337
3337
  }
3338
3338
  //#endregion
3339
3339
  //#region src/otlp-trace.ts
3340
+ /** Attributes that carry cached input apart from fresh input (OpenTelemetry GenAI and Anthropic-style names). */
3341
+ const CACHE_KEYS = [
3342
+ "gen_ai.usage.cache_read_input_tokens",
3343
+ "gen_ai.usage.cache_creation_input_tokens",
3344
+ "gen_ai.usage.input_tokens.cached",
3345
+ "tangle.llm.cache_read_tokens"
3346
+ ];
3340
3347
  /** Price model calls from an OpenAI-compatible model catalog (`data[].pricing.prompt|completion`, USD per token), such as
3341
3348
  * the Router's `/v1/models`. Read the catalog at runtime; never copy its numbers. */
3342
3349
  function catalogPrice(catalog) {
@@ -3475,6 +3482,7 @@ function otlpFlowTrace(input, options = {}) {
3475
3482
  let cost = 0;
3476
3483
  let unpriced = 0;
3477
3484
  let priced = 0;
3485
+ let cacheSplit = false;
3478
3486
  const offset = (iso) => time(iso) - origin;
3479
3487
  const isSpawn = (span) => span.kind === "TOOL" && (attrs(span)["traces.agent.spawn"] === true || /spawn/i.test(str(attrs(span)["gen_ai.tool.name"]) ?? span.name));
3480
3488
  const groupOfSpan = (span) => groupOf.get(rootOf(span).span_id);
@@ -3541,6 +3549,7 @@ function otlpFlowTrace(input, options = {}) {
3541
3549
  if (listPrice !== void 0) {
3542
3550
  cost += listPrice;
3543
3551
  priced++;
3552
+ if (CACHE_KEYS.some((key) => a[key] !== void 0)) cacheSplit = true;
3544
3553
  } else if (kind === "model" && !aggregate && (prompt || completion)) unpriced++;
3545
3554
  out.push({
3546
3555
  id: span.span_id,
@@ -3574,7 +3583,8 @@ function otlpFlowTrace(input, options = {}) {
3574
3583
  costCoverage: {
3575
3584
  priced,
3576
3585
  total: priced + unpriced
3577
- }
3586
+ },
3587
+ ...cacheSplit ? {} : { costUpperBound: true }
3578
3588
  } : {}
3579
3589
  };
3580
3590
  }
@@ -62,12 +62,12 @@
62
62
  .fw-root [data-tone="3"] { --tone: var(--fw-h3); }
63
63
  .fw-root [data-tone="4"] { --tone: var(--fw-h4); }
64
64
  .fw-root [data-tone="5"] { --tone: var(--fw-h5); }
65
- /* Light fills carry enough of the hue to tell agents apart on white (round 8: 24% read as "pale mint on gray"); the outline is
65
+ /* Light fills carry enough of the hue to tell agents apart on white (24% and 42% both read as "pastel, washed out"); the outline is
66
66
  the full hue, which passes 3:1 against the surface, so every mark keeps its edge. */
67
67
  .fw-root [data-tone] {
68
- --fill: light-dark(color-mix(in oklab, var(--tone) 42%, #fff), color-mix(in oklab, var(--tone) 86%, #000));
68
+ --fill: light-dark(color-mix(in oklab, var(--tone) 58%, #fff), color-mix(in oklab, var(--tone) 86%, #000));
69
69
  --line: light-dark(var(--tone), var(--tone));
70
- --dim: light-dark(color-mix(in oklab, var(--tone) 22%, #fff), color-mix(in oklab, var(--tone) 40%, #17171d));
70
+ --dim: light-dark(color-mix(in oklab, var(--tone) 30%, #fff), color-mix(in oklab, var(--tone) 40%, #17171d));
71
71
  --dim-line: light-dark(color-mix(in oklab, var(--tone) 70%, #fff), color-mix(in oklab, var(--tone) 55%, #17171d));
72
72
  }
73
73
 
@@ -231,6 +231,13 @@
231
231
  .fw-root [data-outcome="neutral"], .fw-root [data-outcome="neutral"] [data-tone] { --tone: var(--fw-muted); }
232
232
  .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: 12px; line-height: 16px; }
233
233
  .fw-root .fw-legend li { display: inline-flex; align-items: center; gap: 8px; }
234
+ .fw-root .fw-kinds { margin: 0; padding: 8px 24px 12px; justify-content: flex-end; }
235
+ .fw-root .fw-kinds .fw-swatch-model { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 40%, var(--fw-surface)); }
236
+ .fw-root .fw-kinds .fw-swatch-tool { width: 12px; height: 8px; border-radius: 2px; background: color-mix(in oklab, var(--fw-text) 16%, var(--fw-surface)); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 30%, transparent); }
237
+ .fw-root .fw-kinds .fw-swatch-wait { width: 12px; height: 8px; border-radius: 2px; background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 4px); box-shadow: inset 0 0 0 1px var(--fw-hatch); }
238
+ .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; }
239
+ .fw-root .fw-kinds .fw-swatch-failed { width: 12px; height: 8px; border-radius: 2px; box-shadow: inset 0 0 0 1px var(--fw-failed); }
240
+ .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; }
234
241
  .fw-root .fw-legend .fw-swatch { width: 12px; height: 8px; border-radius: 2px; background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
235
242
  .fw-root .fw-legend b { color: var(--fw-text); font-weight: 600; font-variant-numeric: tabular-nums; }
236
243
  .fw-root .fw-legend .fw-swatch-step { display: inline-grid; place-items: center; width: 12px; height: 12px; border-radius: 3px; background: var(--fw-well); color: var(--fw-failed); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 50%, transparent); }
@@ -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-Sr8GtLm0.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-DqCMrnTs.js";
2
2
  import { CSSProperties, ReactElement, ReactNode } from "react";
3
3
  //#region src/react/sparkline.d.ts
4
4
  type SparklineDirection = "rising" | "falling" | "flat";
@@ -175,6 +175,7 @@ export declare function HBars({ rows, format, color, max, limit }: {
175
175
  note?: string;
176
176
  color?: string;
177
177
  href?: string;
178
+ title?: string;
178
179
  }[];
179
180
  format?: (v: number) => string;
180
181
  color?: string;
@@ -376,6 +377,8 @@ interface FlowWaterfallProps {
376
377
  /** What a bar's color means: the agent (default), whose hue its steps and handoffs share, with the outcome on the row dot, legend and
377
378
  * crash glyph; or its `outcome`. */
378
379
  agentFill?: 'outcome' | 'agent';
380
+ /** One line beside the title saying what the run was for, such as its goal. */
381
+ description?: string;
379
382
  /** The row the host has selected, such as the agent whose transcript is open: it lifts and, in the agent view, its peers dim.
380
383
  * A pinned card alone only highlights its row, so the rest of the run stays readable around it. */
381
384
  selectedId?: string;
@@ -384,6 +387,6 @@ interface FlowWaterfallProps {
384
387
  }
385
388
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
386
389
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
387
- export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect }: FlowWaterfallProps): import("react").JSX.Element;
390
+ export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill, selectedId, onDeselect, description }: FlowWaterfallProps): import("react").JSX.Element;
388
391
  //#endregion
389
392
  export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, LineSeries, MarkKind, MarkVerdict, MarkerShape, MeterSegment, PointFields, PointGlyph, Refusal, SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries, TickOutcome, TimelineLane, TimelineOptions, TimelineSpan, TimelineTick, TimelineViewProps, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow, XCategory, YScale, YTarget, YTick };
@@ -1001,10 +1001,10 @@ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limi
1001
1001
  return /* @__PURE__ */ jsxs("ol", {
1002
1002
  className: "ov-hbars",
1003
1003
  children: [rows.slice(0, limit).map((row) => {
1004
+ const title = row.title ?? `${row.label}: ${format(row.value)}`;
1004
1005
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [
1005
1006
  /* @__PURE__ */ jsx("span", {
1006
1007
  className: "ov-hbar-label",
1007
- title: row.label,
1008
1008
  children: row.label
1009
1009
  }),
1010
1010
  /* @__PURE__ */ jsx("span", {
@@ -1025,8 +1025,12 @@ function HBars({ rows, format = formatCompact, color = LIVE_SERIES[0], max, limi
1025
1025
  ] });
1026
1026
  return /* @__PURE__ */ jsx("li", { children: row.href ? /* @__PURE__ */ jsx("a", {
1027
1027
  href: row.href,
1028
+ title,
1028
1029
  children: content
1029
- }) : /* @__PURE__ */ jsx("div", { children: content }) }, row.label);
1030
+ }) : /* @__PURE__ */ jsx("div", {
1031
+ title,
1032
+ children: content
1033
+ }) }, row.label);
1030
1034
  }), rest.length > 0 && /* @__PURE__ */ jsxs("li", {
1031
1035
  className: "ov-hbars-more",
1032
1036
  children: [
@@ -2575,11 +2579,13 @@ function costSummary(costUsd, trace) {
2575
2579
  };
2576
2580
  const coverage = trace.costCoverage;
2577
2581
  const partial = coverage !== void 0 && coverage.priced < coverage.total;
2582
+ const upper = !partial && trace.costUpperBound === true;
2578
2583
  return {
2579
- value: `${partial ? "≥" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
2584
+ value: `${partial ? "≥" : upper ? "≤" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
2580
2585
  title: [
2581
2586
  partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
2582
2587
  list ? "at the model catalog list price; not a bill" : "",
2588
+ upper ? "cached input is not split out, so it is priced as fresh input: at most this" : "",
2583
2589
  trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
2584
2590
  ].filter(Boolean).join("; "),
2585
2591
  label
@@ -2684,6 +2690,8 @@ const ZoomOutGlyph = () => svg(/* @__PURE__ */ jsxs(Fragment, { children: [/* @_
2684
2690
  const FitGlyph = () => svg(/* @__PURE__ */ jsx("path", { d: "M2.5 6V3.5a1 1 0 0 1 1-1H6M10 2.5h2.5a1 1 0 0 1 1 1V6M13.5 10v2.5a1 1 0 0 1-1 1H10M6 13.5H3.5a1 1 0 0 1-1-1V10" }));
2685
2691
  //#endregion
2686
2692
  //#region src/react/flow-waterfall-card.tsx
2693
+ /** Names wrap between words, never at their hyphens ("memscale-engineer-" / "2" read as two names). */
2694
+ const keepWords = (name) => name.replace(/-/g, "‑");
2687
2695
  const KIND_LABEL = {
2688
2696
  pipeline: "Agent",
2689
2697
  model: "LLM call",
@@ -2753,7 +2761,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2753
2761
  }),
2754
2762
  /* @__PURE__ */ jsx("span", {
2755
2763
  className: "fw-card-who",
2756
- children: who.name
2764
+ children: keepWords(who.name)
2757
2765
  }),
2758
2766
  outcome ? /* @__PURE__ */ jsx("span", {
2759
2767
  className: "fw-pill fw-pill-outcome",
@@ -2840,7 +2848,7 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2840
2848
  className: "fw-card-title",
2841
2849
  children: [/* @__PURE__ */ jsx("div", {
2842
2850
  className: "fw-card-name",
2843
- children: span.name
2851
+ children: keepWords(span.name)
2844
2852
  }), span.detail && /* @__PURE__ */ jsx("div", {
2845
2853
  className: "fw-muted",
2846
2854
  children: span.detail
@@ -2898,7 +2906,7 @@ const at = (startMs, endMs) => ({
2898
2906
  "--e": Number.isFinite(endMs) ? endMs : OPEN_END
2899
2907
  });
2900
2908
  function Segment({ mark, events, agentView }) {
2901
- const glyph = mark.kind === "failed" ? /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
2909
+ const glyph = mark.kind === "failed" ? agentView ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
2902
2910
  return /* @__PURE__ */ jsx("span", {
2903
2911
  className: `fw-seg fw-seg-${mark.kind}`,
2904
2912
  "data-tone": toneKey(mark.tone),
@@ -3924,6 +3932,26 @@ const TONE_ORDER = [
3924
3932
  "failed",
3925
3933
  "neutral"
3926
3934
  ];
3935
+ /** What each mark means, as the agent view draws it; a key only for kinds the run has. */
3936
+ function KindLegend({ rows }) {
3937
+ const has = (kind) => rows.some((row) => row.own.some((mark) => mark.kind === kind) || row.span.kind === kind);
3938
+ const shown = [
3939
+ ["model", "Model call"],
3940
+ ["tool", "Tool call"],
3941
+ ["waiting", "Waiting"],
3942
+ ["handoff", "Handoff"],
3943
+ ["failed", "Failed turn"]
3944
+ ].filter(([kind]) => kind === "waiting" ? rows.some((row) => row.kind === "pipeline") : kind === "handoff" ? rows.some((row) => row.span.handoffTo || row.hasAgents) : has(kind));
3945
+ if (shown.length === 0) return null;
3946
+ return /* @__PURE__ */ jsx("ul", {
3947
+ className: "fw-legend fw-kinds",
3948
+ "aria-label": "Key",
3949
+ children: shown.map(([kind, name]) => /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("span", {
3950
+ className: `fw-swatch-${kind === "waiting" ? "wait" : kind}`,
3951
+ "aria-hidden": true
3952
+ }), name] }, kind))
3953
+ });
3954
+ }
3927
3955
  function OutcomeLegend({ agents }) {
3928
3956
  const items = /* @__PURE__ */ new Map();
3929
3957
  for (const row of agents) {
@@ -3964,7 +3992,7 @@ function OutcomeLegend({ agents }) {
3964
3992
  }
3965
3993
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
3966
3994
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
3967
- function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect }) {
3995
+ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description }) {
3968
3996
  const root = useRef(null);
3969
3997
  const search = useRef(null);
3970
3998
  const slider = useRef(null);
@@ -4305,6 +4333,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4305
4333
  className: "fw-title",
4306
4334
  children: label
4307
4335
  }),
4336
+ description && /* @__PURE__ */ jsx("p", {
4337
+ className: "fw-description",
4338
+ title: description,
4339
+ children: description
4340
+ }),
4308
4341
  isLive && /* @__PURE__ */ jsx("span", {
4309
4342
  className: "fw-state fw-state-live",
4310
4343
  children: "Live"
@@ -4450,6 +4483,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4450
4483
  compact: view === "agents"
4451
4484
  })
4452
4485
  }),
4486
+ view === "agents" && /* @__PURE__ */ jsx(KindLegend, { rows }),
4453
4487
  view === "spans" && playback
4454
4488
  ]
4455
4489
  });
@@ -1,30 +1,31 @@
1
- /* Shared live charts. Consumers may set --chart-live-* tokens on an ancestor. */
2
- .ov-empty { margin: 0; color: var(--chart-live-faint, #777); font-size: .9em; }
1
+ /* Shared live charts. Consumers may set --chart-live-* tokens on an ancestor. Sizes are px on the 12/14 type scale and a
2
+ 4 px grid, so a host's font size never rescales them. */
3
+ .ov-empty { margin: 0; color: var(--chart-live-faint, #777); font-size: 12px; }
3
4
  .ov-plot { position: relative; min-width: 0; }
4
5
  .ov-plot svg { display: block; overflow: visible; }
5
6
  line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%, transparent); stroke-width: 1; }
6
- .ov-axis { fill: var(--chart-live-muted, #666); font: .8em var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
7
+ .ov-axis { fill: var(--chart-live-muted, #666); font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
7
8
  .ov-crosshair { stroke: var(--chart-live-faint, #777); stroke-dasharray: 3 3; }
8
- .ov-tip { position: absolute; z-index: 4; display: grid; gap: .25em; min-width: 12em; max-width: 18em; padding: .6em .75em; 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: .85em; pointer-events: none; }
9
- .ov-tip-line { display: grid; grid-template-columns: auto 1fr auto; gap: .6em; align-items: center; color: var(--chart-live-muted, #666); }
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: 12px; line-height: 16px; pointer-events: none; }
10
+ .ov-tip-line { display: grid; grid-template-columns: auto 1fr auto; gap: 8px; align-items: center; color: var(--chart-live-muted, #666); }
10
11
  .ov-tip-line b { color: var(--chart-live-fg, #222); font-variant-numeric: tabular-nums; }
11
- .ov-hbars { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, max-content) minmax(33%, 1fr) max-content; gap: .25em .7em; }
12
+ .ov-hbars { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: minmax(0, max-content) minmax(33%, 1fr) max-content; gap: 4px 12px; }
12
13
  .ov-hbars li { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
13
- .ov-hbars li > a, .ov-hbars li > div { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; row-gap: .15em; align-items: center; padding: .3em .4em; border-radius: 6px; color: inherit; text-decoration: none; }
14
- .ov-hbars-more { grid-column: 1 / -1; padding: .1em .4em; font-size: .8em; color: var(--chart-live-faint, #777); }
14
+ .ov-hbars li > a, .ov-hbars li > div { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; row-gap: 0; align-items: center; padding: 4px; border-radius: 6px; color: inherit; text-decoration: none; }
15
+ .ov-hbars-more { grid-column: 1 / -1; padding: 0 4px; font-size: 12px; color: var(--chart-live-faint, #777); }
15
16
  .ov-hbars li > a:hover, .ov-hbars li > div:hover { background: var(--chart-live-hover, #eee); text-decoration: none; }
16
- .ov-hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .95em; color: var(--chart-live-fg, #222); }
17
- .ov-hbar-track { height: .7em; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
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
+ .ov-hbar-track { height: 8px; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
18
19
  .ov-hbar-track > span { display: block; height: 100%; border-radius: 3px; }
19
- .ov-hbar-value { font: .9em var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; text-align: right; color: var(--chart-live-fg, #222); }
20
- .ov-hbar-note { grid-column: 1 / -1; font-size: .78em; color: var(--chart-live-faint, #777); }
21
- .ov-meter { display: grid; gap: .3em; min-width: 0; }
22
- .ov-meter-track { display: block; height: .7em; border-radius: 3px; background: color-mix(in srgb, var(--chart-live-faint, #777) 14%, transparent); overflow: hidden; }
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); }
21
+ .ov-hbar-note { grid-column: 1 / -1; font-size: 12px; color: var(--chart-live-faint, #777); }
22
+ .ov-meter { display: grid; gap: 4px; min-width: 0; }
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; }
23
24
  .ov-meter-track > span { display: block; height: 100%; border-radius: 3px; }
24
- .ov-meter-text { font: .85em var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; color: var(--chart-live-muted, #666); }
25
- .ov-spark { display: block; width: 8em; height: 1.6em; }
26
- .ov-spark-empty { font-size: .8em; color: var(--chart-live-faint, #777); }
27
- .ov-tip-line i { display: inline-block; width: .7em; height: .7em; border-radius: 3px; margin-right: .4em; vertical-align: -.05em; }
25
+ .ov-meter-text { font: 12px/16px var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; color: var(--chart-live-muted, #666); }
26
+ .ov-spark { display: block; width: 112px; height: 24px; }
27
+ .ov-spark-empty { font-size: 12px; color: var(--chart-live-faint, #777); }
28
+ .ov-tip-line i { display: inline-block; width: 8px; height: 8px; border-radius: 2px; margin-right: 4px; vertical-align: 0; }
28
29
  /* Recorded events and spans. Colours come from Brand tokens; kinds also differ by stroke pattern and glyph. */
29
30
  .ov-mark { stroke-width: 1.5; fill: none; }
30
31
  .ov-mark-pass { stroke: var(--chart-live-pass, var(--chart-pass, var(--status-running))); }
@@ -44,7 +45,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
44
45
  .ov-verdict-fail .ov-mark-head { fill: var(--chart-live-danger, var(--chart-breach, var(--status-error))); }
45
46
  .ov-verdict-unknown .ov-mark { stroke: var(--chart-live-faint, var(--chart-muted)); }
46
47
  .ov-verdict-unknown .ov-mark-head { fill: var(--chart-live-faint, var(--chart-muted)); }
47
- .ov-mark-label { fill: var(--chart-live-fg, var(--chart-ink)); font-size: .75em; paint-order: stroke; stroke: var(--chart-live-surface, var(--chart-canvas)); stroke-width: 3px; stroke-linejoin: round; }
48
+ .ov-mark-label { fill: var(--chart-live-fg, var(--chart-ink)); font-size: 12px; paint-order: stroke; stroke: var(--chart-live-surface, var(--chart-canvas)); stroke-width: 3px; stroke-linejoin: round; }
48
49
  .ov-band-after-pass { fill: color-mix(in srgb, var(--chart-live-pass, var(--chart-pass, var(--status-running))) 12%, transparent); }
49
50
  .ov-band-idle { fill: var(--chart-live-hover, var(--chart-shade)); }
50
51
  .ov-band-blocked { fill: color-mix(in srgb, var(--chart-live-danger, var(--chart-breach, var(--status-error))) 9%, transparent); }
@@ -56,10 +57,10 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
56
57
  .ov-hatched { background: repeating-linear-gradient(135deg, var(--chart-live-faint, var(--chart-muted)) 0 1px, transparent 1px 5px), color-mix(in srgb, var(--chart-live-faint, var(--chart-muted)) 10%, transparent); }
57
58
  .ov-now { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-width: 1.5; }
58
59
  .ov-gantt-open { stroke: var(--chart-live-fg, var(--chart-ink)); stroke-width: 1.5; stroke-dasharray: 3 2; }
59
- .ov-events, .ov-key, .ov-meter-key { list-style: none; margin: .4em 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .2em 1em; font-size: .8em; color: var(--chart-live-muted, var(--chart-ink-muted)); }
60
- .ov-key li, .ov-events li, .ov-meter-key li { display: inline-flex; align-items: center; gap: .4em; }
60
+ .ov-events, .ov-key, .ov-meter-key { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 12px; line-height: 16px; color: var(--chart-live-muted, var(--chart-ink-muted)); }
61
+ .ov-key li, .ov-events li, .ov-meter-key li { display: inline-flex; align-items: center; gap: 8px; }
61
62
  .ov-meter-key b { font-weight: 500; color: var(--chart-live-fg, var(--chart-ink)); font-family: var(--chart-live-font-mono, monospace); font-variant-numeric: tabular-nums; }
62
- .ov-meter-key i, .ov-event-swatch { display: inline-block; width: .8em; height: .8em; border-radius: 2px; }
63
+ .ov-meter-key i, .ov-event-swatch { display: inline-block; width: 12px; height: 12px; border-radius: 2px; }
63
64
  [data-segmented] > .ov-meter-track { display: flex; }
64
65
  [data-segmented] > .ov-meter-track > span { flex: none; border-radius: 0; }
65
66
  .ov-event-pass { border-left: 2px solid var(--chart-live-pass, var(--chart-pass, var(--status-running))); border-radius: 0; width: 0; }
@@ -72,7 +73,7 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
72
73
  .ov-event-after-pass { background: color-mix(in srgb, var(--chart-live-pass, var(--chart-pass, var(--status-running))) 25%, transparent); }
73
74
  .ov-event-idle { background: var(--chart-live-hover, var(--chart-shade)); }
74
75
  .ov-event-missing { background: repeating-linear-gradient(135deg, var(--chart-live-faint, var(--chart-muted)) 0 1px, transparent 1px 4px); }
75
- .ov-refusal { margin: 0; color: var(--chart-live-muted, var(--chart-ink-muted)); font-size: .9em; }
76
+ .ov-refusal { margin: 0; color: var(--chart-live-muted, var(--chart-ink-muted)); font-size: 12px; }
76
77
 
77
78
  /*
78
79
  * DotMap: thousands of dots on one canvas. A categorical lens names at most three categories in colour, the first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
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",