@tangle-network/charts 0.17.1 → 0.18.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.
package/README.md CHANGED
@@ -335,7 +335,7 @@ const trace = otlpFlowTrace(spans, { price: catalogPrice(await (await fetch(cata
335
335
  <FlowWaterfall trace={trace} live={running} defaultView="agents" renderIcon={brandMark} />;
336
336
  ```
337
337
 
338
- `defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Ctrl/Cmd+wheel or `+`/`-`/`0` zoom, Shift+wheel pans, the scrub track under the controls shows the whole trace, and arrow keys walk the tree.
338
+ `defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. Ctrl/Cmd+wheel or `+`/`-`/`0` zoom, Shift+wheel pans, the scrub track under the controls shows the whole trace, and arrow keys walk the tree.
339
339
 
340
340
  Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders (CSS variables carry the clock). Search supports Cmd/Ctrl+F and keeps ancestor context. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
341
341
 
@@ -332,8 +332,15 @@ interface FlowSpan {
332
332
  retryOf?: string;
333
333
  /** For a handoff, the span that received the work; a connector joins the two rows. */
334
334
  handoffTo?: string;
335
+ /** The host's verdict on an agent span, such as shipped or crashed: colors its lane and joins the legend. */
336
+ outcome?: FlowOutcome;
335
337
  meta?: Record<string, unknown>;
336
338
  }
339
+ /** A host verdict: `label` is the legend text, `tone` the status color. */
340
+ interface FlowOutcome {
341
+ tone: 'ok' | 'neutral' | 'failed' | 'warn';
342
+ label: string;
343
+ }
337
344
  /** A labelled instant on the trace clock, such as a release or an alert; drawn as a line across the rows. */
338
345
  interface FlowMarker {
339
346
  /** Trace-relative milliseconds, on the same clock as span startMs. */
@@ -357,4 +364,4 @@ interface FlowTrace {
357
364
  errors?: number;
358
365
  }
359
366
  //#endregion
360
- export { ComparisonRow as A, Setup as B, MarkVerdict as C, YTarget as D, YScale as E, Interval as F, TierCounts as H, IntervalMethod as I, MatrixCell as L, Estimate as M, Exclusion as N, YTick as O, Figure as P, RateRow as R, MarkKind as S, XCategory as T, isRefusal as U, Tier as V, PointFields as _, TimelineOptions as a, ChartBand as b, timeline as c, ENDING_ORDER as d, OUTCOME_COLORS as f, checkerColor as g, TickOutcome as h, TimelineLane as i, CostBasis as j, BreakdownInput as k, CHECKER_HUES as l, RunEnding as m, FlowSpan as n, TimelineSpan as o, OUTCOME_ORDER as p, FlowTrace as r, TimelineTick as s, FlowMarker as t, ENDING_COLORS as u, PointGlyph as v, MarkerShape as w, ChartMark as x, BandKind as y, Refusal as z };
367
+ export { BreakdownInput as A, Refusal as B, MarkKind as C, YScale as D, XCategory as E, Figure as F, Tier as H, Interval as I, IntervalMethod as L, CostBasis as M, Estimate as N, YTarget as O, Exclusion as P, MatrixCell as R, ChartMark as S, MarkerShape as T, TierCounts as U, Setup as V, isRefusal as W, checkerColor as _, TimelineLane as a, BandKind as b, TimelineTick as c, ENDING_COLORS as d, ENDING_ORDER as f, TickOutcome as g, RunEnding as h, FlowTrace as i, ComparisonRow as j, YTick as k, timeline as l, OUTCOME_ORDER as m, FlowOutcome as n, TimelineOptions as o, OUTCOME_COLORS as p, FlowSpan as r, TimelineSpan as s, FlowMarker as t, CHECKER_HUES as u, PointFields as v, MarkVerdict as w, ChartBand as x, PointGlyph as y, RateRow as z };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as ComparisonRow, B as Setup, C as MarkVerdict, D as YTarget, E as YScale, F as Interval, H as TierCounts, I as IntervalMethod, L as MatrixCell, M as Estimate, N as Exclusion, O as YTick, P as Figure, R as RateRow, S as MarkKind, T as XCategory, U as isRefusal, V as Tier, _ as PointFields, a as TimelineOptions, b as ChartBand, c as timeline, d as ENDING_ORDER, f as OUTCOME_COLORS, g as checkerColor, h as TickOutcome, i as TimelineLane, j as CostBasis, k as BreakdownInput, l as CHECKER_HUES, m as RunEnding, n as FlowSpan, o as TimelineSpan, p as OUTCOME_ORDER, r as FlowTrace, s as TimelineTick, t as FlowMarker, u as ENDING_COLORS, v as PointGlyph, w as MarkerShape, x as ChartMark, y as BandKind, z as Refusal } from "./flow-trace-Cti_6fZP.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-BYKE-PMb.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. */
@@ -398,4 +398,4 @@ export declare function otlpFlowTrace(input: OtlpSpan[], options?: OtlpTraceOpti
398
398
  omitted: number;
399
399
  };
400
400
  //#endregion
401
- export { type BandKind, type BreakdownInput, type BreakdownOptions, CHECKER_HUES, type ChartBand, type ChartMark, type ComparisonIntervalsOptions, type ComparisonRow, type CostBasis, type CostFrontierOptions, type DotGroup, type DotGroupRect, ENDING_COLORS, ENDING_ORDER, type Estimate, type Exclusion, type Figure, type FlowMarker, type FlowSpan, type FlowTrace, type HeatmapCell, type HeatmapColumn, type HeatmapOptions, type HeatmapRow, type HeatmapStatus, type Interval, type IntervalMethod, type LatencyRangeOptions, type LatencyRangeRow, type MarkKind, type MarkVerdict, type MatrixCell, type MetricBarsOptions, type MetricRow, type MultiplePanel, OUTCOME_COLORS, OUTCOME_ORDER, type Observations, type OtlpSpan, type OtlpTraceOptions, type PhaseWaterfallOptions, type PointFields, type PointGlyph, type RankedRatesOptions, type RateIdentity, type RateRow, type Refusal, type RenderFigureOptions, type RunEnding, type Setup, type SmallMultiplesOptions, type TaskMatrixOptions, type TickOutcome, type Tier, type TierCounts, type TimePoint, type TimeSeriesOptions, type TimeSeriesRow, type TimelineLane, type TimelineOptions, type TimelineSpan, type TimelineTick, type UnitDotLayout, type UnitDotOptions, type WaterfallPhase, type XCategory, type YScale, type YTarget, type YTick, checkerColor, isRefusal, timeline };
401
+ export { type BandKind, type BreakdownInput, type BreakdownOptions, CHECKER_HUES, type ChartBand, type ChartMark, type ComparisonIntervalsOptions, type ComparisonRow, type CostBasis, type CostFrontierOptions, type DotGroup, type DotGroupRect, ENDING_COLORS, ENDING_ORDER, type Estimate, type Exclusion, type Figure, type FlowMarker, type FlowOutcome, type FlowSpan, type FlowTrace, type HeatmapCell, type HeatmapColumn, type HeatmapOptions, type HeatmapRow, type HeatmapStatus, type Interval, type IntervalMethod, type LatencyRangeOptions, type LatencyRangeRow, type MarkKind, type MarkVerdict, type MatrixCell, type MetricBarsOptions, type MetricRow, type MultiplePanel, OUTCOME_COLORS, OUTCOME_ORDER, type Observations, type OtlpSpan, type OtlpTraceOptions, type PhaseWaterfallOptions, type PointFields, type PointGlyph, type RankedRatesOptions, type RateIdentity, type RateRow, type Refusal, type RenderFigureOptions, type RunEnding, type Setup, type SmallMultiplesOptions, type TaskMatrixOptions, type TickOutcome, type Tier, type TierCounts, type TimePoint, type TimeSeriesOptions, type TimeSeriesRow, type TimelineLane, type TimelineOptions, type TimelineSpan, type TimelineTick, type UnitDotLayout, type UnitDotOptions, type WaterfallPhase, type XCategory, type YScale, type YTarget, type YTick, checkerColor, isRefusal, timeline };
@@ -122,12 +122,13 @@
122
122
  .fw-root .fw-marker[data-tone="ok"] { --fw-marker: var(--fw-ok); }
123
123
  .fw-root .fw-marker[data-tone="failed"] { --fw-marker: var(--fw-failed); }
124
124
  .fw-root .fw-marker-label { position: absolute; top: 2px; left: 3px; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 4px; border-radius: 4px; font-size: 12px; line-height: 16px; color: var(--fw-marker); background: var(--fw-surface); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-marker) 40%, transparent); }
125
+ .fw-root .fw-marker-label-left { left: auto; right: 3px; }
125
126
  .fw-root .fw-cursor { position: absolute; top: 0; bottom: 0; width: 1px; background: color-mix(in oklab, var(--fw-text) 70%, transparent); left: calc(var(--fw-pad) + (var(--fw-t) - var(--fw-v0)) * var(--fw-k) * 1px); }
126
127
  .fw-root .fw-empty { padding: 40px 16px; text-align: center; color: var(--fw-muted); }
127
128
  .fw-root .fw-sticky { position: absolute; top: 0; left: 0; right: 0; z-index: 2; background: var(--fw-surface); box-shadow: 0 1px 0 var(--fw-border), 0 6px 12px -8px rgb(0 0 0 / .35); }
128
129
 
129
130
  /* Rows */
130
- .fw-root .fw-row { position: absolute; top: 0; left: 0; right: 0; height: var(--fw-row-h); display: grid; grid-template-columns: var(--fw-label-col) minmax(0, 1fr); outline: 0; contain: layout style; }
131
+ .fw-root .fw-row { position: absolute; top: 0; left: 0; right: 0; transform: translateY(var(--row-y)); height: var(--fw-row-h); display: grid; grid-template-columns: var(--fw-label-col) minmax(0, 1fr); outline: 0; contain: layout style; }
131
132
  .fw-root .fw-row:hover { background: var(--fw-hover); }
132
133
  .fw-root .fw-row.fw-active { background: color-mix(in oklab, var(--fw-accent) 7%, transparent); }
133
134
  .fw-root .fw-row[aria-selected="true"] { background: color-mix(in oklab, var(--fw-accent) 11%, transparent); }
@@ -186,6 +187,19 @@
186
187
  .fw-root .fw-bar.fw-k-pipeline { background: linear-gradient(90deg, var(--fw-neutral-bar), color-mix(in oklab, var(--fw-neutral-bar) 55%, var(--fw-surface))); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 12%, transparent); }
187
188
  .fw-root .fw-bar.fw-k-pipeline.fw-has-marks { background: color-mix(in oklab, var(--tone) 9%, transparent); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--tone) 18%, transparent); }
188
189
  .fw-root .fw-bar.fw-k-pipeline.fw-has-agents { background: repeating-linear-gradient(-45deg, var(--fw-hatch) 0 1px, transparent 1px 5px), color-mix(in oklab, var(--fw-text) 4%, transparent); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-text) 12%, transparent); }
190
+ /* An agent bar with no child marks says who it is by its lane color, never a neutral grey. */
191
+ .fw-root .fw-view-agents .fw-bar.fw-k-pipeline:not(.fw-has-marks, .fw-has-agents) { background: var(--fill); box-shadow: inset 0 0 0 1px var(--line); }
192
+ /* A host outcome re-tones the whole lane: dot, bar and marks. */
193
+ .fw-root [data-outcome="ok"], .fw-root [data-outcome="ok"] [data-tone] { --tone: var(--fw-ok); }
194
+ .fw-root [data-outcome="failed"], .fw-root [data-outcome="failed"] [data-tone] { --tone: var(--fw-failed); }
195
+ .fw-root [data-outcome="warn"], .fw-root [data-outcome="warn"] [data-tone] { --tone: var(--fw-amber); }
196
+ .fw-root [data-outcome="neutral"], .fw-root [data-outcome="neutral"] [data-tone] { --tone: var(--fw-muted); }
197
+ .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; }
198
+ .fw-root .fw-legend li { display: inline-flex; align-items: center; gap: 8px; }
199
+ .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); }
200
+ .fw-root .fw-legend b { color: var(--fw-text); font-weight: 600; font-variant-numeric: tabular-nums; }
201
+ .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); }
202
+ .fw-root .fw-legend .fw-swatch-step svg { width: 8px; height: 8px; }
189
203
  .fw-root .fw-bar.fw-approx { opacity: calc(var(--on) * .7); }
190
204
  .fw-root .fw-seg { width: max(calc(var(--w) - 2px), calc(var(--on) * 3px)); pointer-events: none; }
191
205
  .fw-root .fw-view-agents .fw-seg { pointer-events: auto; }
@@ -315,12 +329,12 @@
315
329
  @container (max-width: 899px) {
316
330
  /* The root is the queried container, so the variables go on its children. */
317
331
  .fw-root-agents > * { --fw-label-col: 0px; --fw-bar-shift: 8px; }
318
- .fw-root-agents .fw-view-agents .fw-label { position: absolute; z-index: 2; top: 4px; left: 0; right: 0; height: 18px; padding: 0 var(--fw-pad); gap: 8px; align-items: center; pointer-events: none; overflow: hidden; }
319
332
  .fw-root-agents .fw-view-agents .fw-names { flex-direction: row; align-items: baseline; gap: 8px; min-width: 0; }
320
333
  .fw-root-agents .fw-view-agents .fw-agent-dot { align-self: center; margin-top: 0; }
321
334
  /* The label leaves the grid flow, so the lane must claim the track column itself. */
322
335
  .fw-root-agents .fw-view-agents .fw-lane { grid-column: 2; }
323
- /* Elbows pass behind the name, not through its letters. */
324
- .fw-root-agents .fw-view-agents .fw-names { padding-right: 4px; border-radius: 4px; background: var(--fw-surface); }
325
- .fw-root-agents .fw-view-agents .fw-row:hover .fw-names { background: transparent; }
336
+ /* Elbows pass behind the name, not through its letters. A transform or layout containment would make each row
337
+ its own stacking context and trap the name under the connector layer, so stacked rows place by top instead. */
338
+ .fw-root-agents .fw-view-agents .fw-row { transform: none; top: var(--row-y); contain: style; }
339
+ .fw-root-agents .fw-view-agents .fw-label { position: absolute; z-index: 3; top: 4px; left: 0; height: 18px; max-width: calc(100% - var(--fw-pad)); padding: 0 8px 0 var(--fw-pad); gap: 8px; align-items: center; pointer-events: none; overflow: hidden; border-radius: 0 4px 4px 0; background: var(--fw-surface); }
326
340
  }
@@ -1,4 +1,4 @@
1
- import { C as MarkVerdict, D as YTarget, E as YScale, O as YTick, P as Figure, S as MarkKind, T as XCategory, _ as PointFields, a as TimelineOptions, b as ChartBand, h as TickOutcome, i as TimelineLane, n as FlowSpan, o as TimelineSpan, r as FlowTrace, s as TimelineTick, t as FlowMarker, v as PointGlyph, w as MarkerShape, x as ChartMark, y as BandKind, z as Refusal } from "../flow-trace-Cti_6fZP.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-BYKE-PMb.js";
2
2
  import { CSSProperties, ReactElement, ReactNode } from "react";
3
3
  //#region src/react/sparkline.d.ts
4
4
  type SparklineDirection = "rising" | "falling" | "flat";
@@ -365,7 +365,7 @@ interface FlowWaterfallProps {
365
365
  defaultView?: FlowView;
366
366
  /** Host brand marks by span; the inspector draws kind glyphs without it. */
367
367
  renderIcon?: (span: FlowSpan) => ReactNode;
368
- /** Scroll height of the rows; the tree is virtualized beyond it. */
368
+ /** Scroll height of the rows; the tree is virtualized beyond it. Defaults to 20 agent rows, or 640 px of steps. */
369
369
  maxHeight?: number;
370
370
  /** Called when a row is opened or a bar is pinned, with that row's span; the built-in behavior still runs. */
371
371
  onSelect?: (span: FlowSpan) => void;
@@ -2874,8 +2874,9 @@ const FlowRow = memo(function FlowRow(props) {
2874
2874
  "aria-selected": selected,
2875
2875
  tabIndex: focusable ? 0 : -1,
2876
2876
  "data-tone": toneKey(row.tone),
2877
+ "data-outcome": span.outcome?.tone,
2877
2878
  className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}`,
2878
- style: { transform: `translateY(${top}px)` },
2879
+ style: { "--row-y": `${top}px` },
2879
2880
  onFocus: (event) => {
2880
2881
  if (event.target === event.currentTarget) props.onActivate(row);
2881
2882
  },
@@ -3161,6 +3162,25 @@ function FlowBody(props) {
3161
3162
  const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
3162
3163
  const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
3163
3164
  const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
3165
+ const markerLabels = useMemo(() => {
3166
+ const shown = /* @__PURE__ */ new Map();
3167
+ let freeFrom = -Infinity;
3168
+ for (const marker of [...props.markers ?? []].sort((a, b) => a.at - b.at)) {
3169
+ const x = (marker.at - win.startMs) / msPerPx;
3170
+ const width = marker.label.length * 7 + 16;
3171
+ const side = x + width > trackPx ? "left" : "right";
3172
+ const left = side === "right" ? x : x - width;
3173
+ if (x < 0 || x > trackPx || left < freeFrom) continue;
3174
+ shown.set(marker, side);
3175
+ freeFrom = left + width + 8;
3176
+ }
3177
+ return shown;
3178
+ }, [
3179
+ props.markers,
3180
+ win.startMs,
3181
+ msPerPx,
3182
+ trackPx
3183
+ ]);
3164
3184
  useEffect(() => {
3165
3185
  const element = scroller.current;
3166
3186
  if (!element) return;
@@ -3399,8 +3419,9 @@ function FlowBody(props) {
3399
3419
  className: "fw-marker",
3400
3420
  "data-tone": marker.tone ?? "neutral",
3401
3421
  style: { "--s": marker.at },
3402
- children: /* @__PURE__ */ jsx("span", {
3403
- className: "fw-marker-label",
3422
+ title: marker.label,
3423
+ children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
3424
+ className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
3404
3425
  children: marker.label
3405
3426
  })
3406
3427
  }, `${marker.at}-${marker.label}`))
@@ -3683,10 +3704,49 @@ const NAME_WIDTH = {
3683
3704
  spans: 320,
3684
3705
  agents: 240
3685
3706
  };
3707
+ /** One legend item per outcome label the agents carry, with its count; nothing when no agent has an outcome. */
3708
+ function OutcomeLegend({ agents }) {
3709
+ const items = /* @__PURE__ */ new Map();
3710
+ for (const row of agents) {
3711
+ const outcome = row.span.outcome;
3712
+ if (!outcome) continue;
3713
+ const item = items.get(outcome.label) ?? {
3714
+ tone: outcome.tone,
3715
+ count: 0
3716
+ };
3717
+ item.count += 1;
3718
+ items.set(outcome.label, item);
3719
+ }
3720
+ if (items.size === 0) return null;
3721
+ const failedSteps = agents.reduce((sum, row) => sum + row.own.reduce((n, mark) => n + (mark.kind === "failed" ? mark.count : 0), 0), 0);
3722
+ return /* @__PURE__ */ jsxs("ul", {
3723
+ className: "fw-legend",
3724
+ "aria-label": "Outcomes",
3725
+ children: [[...items].map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
3726
+ /* @__PURE__ */ jsx("span", {
3727
+ className: "fw-swatch",
3728
+ "data-tone": "0",
3729
+ "data-outcome": item.tone,
3730
+ "aria-hidden": true
3731
+ }),
3732
+ label,
3733
+ " ",
3734
+ /* @__PURE__ */ jsx("b", { children: item.count })
3735
+ ] }, label)), failedSteps > 0 && /* @__PURE__ */ jsxs("li", { children: [
3736
+ /* @__PURE__ */ jsx("span", {
3737
+ className: "fw-swatch-step",
3738
+ "aria-hidden": true,
3739
+ children: /* @__PURE__ */ jsx(FailGlyph, {})
3740
+ }),
3741
+ "Failed step ",
3742
+ /* @__PURE__ */ jsx("b", { children: failedSteps })
3743
+ ] })]
3744
+ });
3745
+ }
3686
3746
  const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
3687
3747
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
3688
3748
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
3689
- function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight = 640, onSelect, markers }) {
3749
+ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers }) {
3690
3750
  const root = useRef(null);
3691
3751
  const search = useRef(null);
3692
3752
  const slider = useRef(null);
@@ -3767,6 +3827,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3767
3827
  collapsed
3768
3828
  ]);
3769
3829
  const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
3830
+ const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
3770
3831
  const resized = useRef(false);
3771
3832
  useLayoutEffect(() => {
3772
3833
  if (view !== "agents" || resized.current || !root.current) return;
@@ -3990,47 +4051,51 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3990
4051
  className: "fw-heading",
3991
4052
  children: [/* @__PURE__ */ jsxs("div", {
3992
4053
  className: "fw-title-block",
3993
- children: [/* @__PURE__ */ jsxs("div", {
3994
- className: "fw-title-line",
3995
- children: [
3996
- /* @__PURE__ */ jsx("span", {
3997
- className: "fw-title-glyph",
3998
- "aria-hidden": true,
3999
- children: /* @__PURE__ */ jsx(TraceGlyph, {})
4000
- }),
4001
- /* @__PURE__ */ jsx("h3", {
4002
- className: "fw-title",
4003
- children: label
4004
- }),
4005
- isLive && /* @__PURE__ */ jsx("span", {
4006
- className: "fw-state fw-state-live",
4007
- children: "Live"
4008
- })
4009
- ]
4010
- }), view === "agents" && /* @__PURE__ */ jsx("p", {
4011
- className: "fw-headline",
4012
- "aria-live": "polite",
4013
- children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
4014
- /* @__PURE__ */ jsx("span", {
4015
- className: "fw-done-mark",
4016
- "aria-label": "done",
4017
- children: "✓"
4018
- }),
4019
- agents.length,
4020
- " ",
4021
- agents.length === 1 ? "agent" : "agents",
4022
- " · ",
4023
- headlineSeconds(limit),
4024
- speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
4025
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4026
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4027
- headlineSeconds(clockMs),
4028
- " · ",
4029
- tokens.toLocaleString(),
4030
- " tokens",
4031
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4032
- ] })
4033
- })]
4054
+ children: [
4055
+ /* @__PURE__ */ jsxs("div", {
4056
+ className: "fw-title-line",
4057
+ children: [
4058
+ /* @__PURE__ */ jsx("span", {
4059
+ className: "fw-title-glyph",
4060
+ "aria-hidden": true,
4061
+ children: /* @__PURE__ */ jsx(TraceGlyph, {})
4062
+ }),
4063
+ /* @__PURE__ */ jsx("h3", {
4064
+ className: "fw-title",
4065
+ children: label
4066
+ }),
4067
+ isLive && /* @__PURE__ */ jsx("span", {
4068
+ className: "fw-state fw-state-live",
4069
+ children: "Live"
4070
+ })
4071
+ ]
4072
+ }),
4073
+ view === "agents" && /* @__PURE__ */ jsx("p", {
4074
+ className: "fw-headline",
4075
+ "aria-live": "polite",
4076
+ children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
4077
+ /* @__PURE__ */ jsx("span", {
4078
+ className: "fw-done-mark",
4079
+ "aria-label": "done",
4080
+ children: "✓"
4081
+ }),
4082
+ agents.length,
4083
+ " ",
4084
+ agents.length === 1 ? "agent" : "agents",
4085
+ " · ",
4086
+ headlineSeconds(limit),
4087
+ speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
4088
+ trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4089
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4090
+ headlineSeconds(clockMs),
4091
+ " · ",
4092
+ tokens.toLocaleString(),
4093
+ " tokens",
4094
+ trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4095
+ ] })
4096
+ }),
4097
+ /* @__PURE__ */ jsx(OutcomeLegend, { agents })
4098
+ ]
4034
4099
  }), /* @__PURE__ */ jsxs("div", {
4035
4100
  className: "fw-tools",
4036
4101
  children: [/* @__PURE__ */ jsx("div", {
@@ -4098,7 +4163,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4098
4163
  byId,
4099
4164
  totalRows: rows.length,
4100
4165
  view,
4101
- maxHeight,
4166
+ maxHeight: rowsHeight,
4102
4167
  collapsed,
4103
4168
  searching: Boolean(query.trim()),
4104
4169
  slowestId: slowest?.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.17.1",
3
+ "version": "0.18.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",