@tangle-network/charts 0.21.17 → 0.21.19

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.
@@ -259,7 +259,8 @@
259
259
  /* Muted agents (the default): one quiet tone for every agent bar, its steps and handoffs; the slowest agent alone takes amber, and
260
260
  failure keeps its red glyph, so color only ever means state. */
261
261
  .fw-root.fw-fill-muted .fw-view-agents [data-tone] { --tone: var(--fw-h0); }
262
- .fw-root:is(.fw-fill-muted, .fw-fill-agent) .fw-view-agents .fw-row[data-slowest], .fw-root:is(.fw-fill-muted, .fw-fill-agent) .fw-view-agents .fw-row[data-slowest] [data-tone] { --tone: var(--fw-amber); }
262
+ /* 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"). */
263
+ .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); }
263
264
  .fw-root [data-outcome="ok"], .fw-root [data-outcome="ok"] [data-tone] { --tone: var(--fw-ok); }
264
265
  .fw-root [data-outcome="failed"], .fw-root [data-outcome="failed"] [data-tone] { --tone: var(--fw-failed); }
265
266
  .fw-root [data-outcome="warn"], .fw-root [data-outcome="warn"] [data-tone] { --tone: var(--fw-amber); }
@@ -291,14 +292,30 @@
291
292
  .fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents .fw-seg-model { background: var(--fw-kind-model); box-shadow: none; }
292
293
  .fw-root:is(.fw-fill-muted, .fw-fill-outcome) .fw-view-agents .fw-seg-tool { background: var(--fw-kind-tool); box-shadow: none; }
293
294
  /* Per-agent hues at full saturation (blind round 26: muted per-agent bars "blur together", "wash out on white"): kind is a darker or
294
- lighter step of the agent's own saturated hue, every solid segment at least 3:1 against the card; waiting stays the only pale fill. */
295
- .fw-root.fw-fill-agent .fw-view-agents .fw-seg-model { background: light-dark(color-mix(in oklab, var(--tone) 75%, var(--fw-text)), var(--tone)); box-shadow: none; }
296
- .fw-root.fw-fill-agent .fw-view-agents .fw-seg-tool { background: light-dark(var(--tone), color-mix(in oklab, var(--tone) 72%, var(--fw-text))); box-shadow: none; }
295
+ lighter step of the agent's own saturated hue; waiting stays the only pale fill. Light theme sits one step softer (round 27: the
296
+ saturated light bars read "heavy"). */
297
+ .fw-root.fw-fill-agent .fw-view-agents .fw-seg-model { background: light-dark(color-mix(in oklab, var(--tone) 88%, var(--fw-text)), var(--tone)); box-shadow: none; }
298
+ .fw-root.fw-fill-agent .fw-view-agents .fw-seg-tool { background: light-dark(color-mix(in oklab, var(--tone) 84%, var(--fw-surface)), color-mix(in oklab, var(--tone) 72%, var(--fw-text))); box-shadow: none; }
297
299
  /* A failed turn is still the agent's work: it keeps the agent's saturated hue, and the red cross on the ending turn says it failed. */
298
- .fw-root.fw-fill-agent .fw-view-agents .fw-seg-failed { background: light-dark(var(--tone), color-mix(in oklab, var(--tone) 72%, var(--fw-text))); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 55%, transparent); }
299
- .fw-root .fw-view-agents .fw-seg-tool { container-type: inline-size; justify-content: flex-start; }
300
- .fw-root .fw-seg-name { padding: 0 6px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 12px; line-height: 16px; font-weight: 600; color: light-dark(var(--fw-surface), hsl(var(--hsl-background))); pointer-events: none; }
301
- @container (max-width: 56px) { .fw-root .fw-seg-name { display: none; } }
300
+ .fw-root.fw-fill-agent .fw-view-agents .fw-seg-failed { background: light-dark(color-mix(in oklab, var(--tone) 84%, var(--fw-surface)), color-mix(in oklab, var(--tone) 72%, var(--fw-text))); box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--fw-failed) 55%, transparent); }
301
+ .fw-root .fw-view-agents .fw-seg { container-type: inline-size; justify-content: flex-start; font-size: 12px; line-height: 16px; white-space: nowrap; color: var(--fw-seg-ink); }
302
+ /* Label ink holds 4.5:1 on its fill: light text on the darker model step, a near-black of the agent's hue on the lighter tool step
303
+ (round 28: white on the pale light-theme fills failed); on dark the steps are bright, so ink is the page's dark. */
304
+ .fw-root .fw-view-agents .fw-seg-model { --fw-seg-ink: light-dark(var(--fw-surface), hsl(var(--hsl-background))); }
305
+ .fw-root .fw-view-agents :is(.fw-seg-tool, .fw-seg-failed) { --fw-seg-ink: light-dark(color-mix(in oklab, var(--tone) 18%, black), hsl(var(--hsl-background))); }
306
+ .fw-root .fw-view-agents .fw-seg > * { pointer-events: none; }
307
+ /* One line, 6 px in from each end; a part that does not fit whole wraps to the hidden second line, so trailing parts drop first. */
308
+ .fw-root .fw-seg-label { flex: 1; min-width: 0; height: 16px; margin: 0 6px; display: flex; flex-wrap: wrap; align-items: center; column-gap: 4px; overflow: hidden; }
309
+ .fw-root .fw-seg-label > * { flex: none; height: 16px; display: inline-flex; align-items: center; }
310
+ .fw-root .fw-seg-icon svg { width: 12px; height: 12px; stroke-width: 1.75; }
311
+ .fw-root .fw-seg-name { font-weight: 600; }
312
+ .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; }
313
+ .fw-root .fw-seg-meta { font-variant-numeric: tabular-nums; }
314
+ @container (max-width: 24px) { .fw-root :is(.fw-seg-label, .fw-view-agents .fw-seg .fw-glyph) { display: none; } }
315
+ /* Fail and retry read as filled chips inside the bar, red and amber, as the references draw them. */
316
+ .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))); }
317
+ .fw-root .fw-view-agents .fw-seg .fw-glyph[data-glyph="retry"] { background: var(--fw-amber); }
318
+ .fw-root .fw-view-agents .fw-seg .fw-glyph svg { width: 9px; height: 9px; stroke-width: 2.5; }
302
319
  .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); }
303
320
  .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); }
304
321
  /* In the agent view a failed turn draws like any turn; the agent's own failure shows once, by its name and its end label. */
@@ -320,15 +337,15 @@
320
337
  .fw-root .fw-links { position: absolute; top: 0; bottom: 0; left: var(--fw-label-col); right: 0; overflow: hidden; pointer-events: none; z-index: 0; }
321
338
  .fw-root .fw-link {
322
339
  --x: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px);
323
- position: absolute; width: 8px; border: 0 solid var(--line); border-bottom-width: 1.5px; translate: 0 var(--fw-bar-shift);
340
+ position: absolute; width: 12px; border: 0 solid var(--line); border-bottom-width: 1.5px; translate: 0 var(--fw-bar-shift);
324
341
  opacity: clamp(0, (var(--fw-t) - var(--s)) * 1000, 1);
325
342
  }
326
343
  .fw-root .fw-view-spans .fw-link { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .7); }
327
344
  /* Agent-view connectors rest faint, one curved elbow per spawn in the agent's hue (they show who started whom), and come to
328
345
  full strength for the agent under the pointer, pinned or focused. */
329
- .fw-root .fw-view-agents .fw-link:not(.fw-link-hot) { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .6); }
346
+ .fw-root .fw-view-agents .fw-link:not(.fw-link-hot) { opacity: calc(clamp(0, (var(--fw-t) - var(--s)) * 1000, 1) * .8); }
330
347
  .fw-root .fw-link { transition: opacity 160ms var(--fw-ease); }
331
- .fw-root .fw-link-down { left: calc(var(--x) - 8px); border-left-width: 1.5px; border-bottom-left-radius: 12px; }
348
+ .fw-root .fw-link-down { left: calc(var(--x) - 12px); border-left-width: 1.5px; border-bottom-left-radius: 12px; }
332
349
  .fw-root .fw-link-up { left: var(--x); border-right-width: 1.5px; border-bottom-right-radius: 12px; }
333
350
  .fw-root .fw-link::before { content: ""; position: absolute; top: -3.5px; width: 5px; height: 5px; border-radius: 50%; background: var(--line); box-shadow: 0 0 0 1.5px var(--fw-surface); }
334
351
  .fw-root .fw-link-down::before { left: -3.25px; }
@@ -391,6 +391,10 @@ interface FlowSegment {
391
391
  name?: string;
392
392
  /** The tool every merged step shares (the name before " · "); undefined once the merged steps differ. */
393
393
  tool?: string;
394
+ /** What the merged steps cost, when any of them recorded a cost. */
395
+ costUsd?: number;
396
+ /** A failed mark that was a model call, so it draws the model's icon and no tool name. */
397
+ modelCall?: boolean;
394
398
  }
395
399
  /** A row's direct steps: model and tool calls (failed ones included), the failed and retrying ones, and tool calls by tool. */
396
400
  interface StepCounts {
@@ -2476,6 +2476,7 @@ function mergeSegments(marks, gapMs, cap = 240) {
2476
2476
  last.endMs = Math.max(last.endMs, mark.endMs);
2477
2477
  last.count += mark.count;
2478
2478
  if (last.tool !== mark.tool) last.tool = void 0;
2479
+ if (mark.costUsd !== void 0) last.costUsd = (last.costUsd ?? 0) + mark.costUsd;
2479
2480
  last.kind = share.model >= share.tool ? "model" : "tool";
2480
2481
  } else out.push({ ...mark });
2481
2482
  }
@@ -2656,7 +2657,9 @@ function segmentOf(row) {
2656
2657
  count: 1,
2657
2658
  retry: row.retry,
2658
2659
  name: row.name,
2659
- tool: toolOf(row.name)
2660
+ tool: toolOf(row.name),
2661
+ ...row.span.costUsd === void 0 ? {} : { costUsd: row.span.costUsd },
2662
+ ...!row.ok && row.kind === "model" ? { modelCall: true } : {}
2660
2663
  };
2661
2664
  }
2662
2665
  /** The rows a search term matches by themselves, before ancestor context is added. */
@@ -3374,25 +3377,54 @@ const at = (startMs, endMs) => ({
3374
3377
  });
3375
3378
  const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ? 80 : 0) + (costliest ? 90 : 0);
3376
3379
  function Segment({ mark, events, agentView, ending }) {
3377
- const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
3378
- const named = agentView && mark.kind === "tool" && (mark.count === 1 ? mark.name : mark.tool && `${mark.tool} ×${mark.count}`);
3379
- return /* @__PURE__ */ jsx("span", {
3380
+ const glyph = mark.kind === "failed" ? agentView && !ending ? null : "fail" : mark.retry ? "retry" : null;
3381
+ const model = mark.kind === "model" || mark.modelCall;
3382
+ const name = model ? void 0 : mark.count === 1 ? mark.name : mark.tool;
3383
+ const duration = Number.isFinite(mark.endMs) ? formatSpanDuration(mark.endMs - mark.startMs) : "";
3384
+ const meta = [duration, mark.costUsd ? formatCost(mark.costUsd) : ""].filter(Boolean).join(" · ");
3385
+ return /* @__PURE__ */ jsxs("span", {
3380
3386
  className: `fw-seg fw-seg-${mark.kind}${mark.alt ? " fw-seg-alt" : ""}`,
3381
3387
  "data-tone": toneKey(mark.tone),
3382
3388
  style: at(mark.startMs, mark.endMs),
3383
- title: mark.name ? `${mark.name}${mark.count > 1 ? ` +${mark.count - 1} more` : ""}` : void 0,
3389
+ title: mark.name ? `${mark.name}${mark.count > 1 ? ` +${mark.count - 1} more` : ""}${meta ? ` · ${meta}` : ""}` : void 0,
3384
3390
  onPointerEnter: agentView ? (event) => events.onInspect(mark.id, event.currentTarget, event.clientX) : void 0,
3385
3391
  onClick: agentView ? (event) => {
3386
3392
  event.stopPropagation();
3387
3393
  events.onPin(mark.id, event.currentTarget, event.clientX);
3388
3394
  } : void 0,
3389
- children: glyph ? /* @__PURE__ */ jsx("span", {
3395
+ children: [glyph && /* @__PURE__ */ jsx("span", {
3390
3396
  className: "fw-glyph",
3391
- children: glyph
3392
- }) : named && /* @__PURE__ */ jsx("span", {
3393
- className: "fw-seg-name",
3394
- children: named
3395
- })
3397
+ "data-glyph": glyph,
3398
+ children: glyph === "fail" ? /* @__PURE__ */ jsx(FailGlyph, {}) : /* @__PURE__ */ jsx(RetryGlyph, {})
3399
+ }), agentView && /* @__PURE__ */ jsxs("span", {
3400
+ className: "fw-seg-label",
3401
+ children: [
3402
+ /* @__PURE__ */ jsx("span", {
3403
+ className: "fw-seg-icon",
3404
+ "aria-hidden": true,
3405
+ children: /* @__PURE__ */ jsx(KindGlyph, {
3406
+ kind: model ? "model" : "tool",
3407
+ name: mark.tool ?? mark.name
3408
+ })
3409
+ }),
3410
+ name && /* @__PURE__ */ jsx("span", {
3411
+ className: "fw-seg-name",
3412
+ children: name
3413
+ }),
3414
+ mark.count > 1 && /* @__PURE__ */ jsxs("span", {
3415
+ className: "fw-seg-chip",
3416
+ children: ["×", mark.count]
3417
+ }),
3418
+ duration && /* @__PURE__ */ jsx("span", {
3419
+ className: "fw-seg-meta",
3420
+ children: duration
3421
+ }),
3422
+ mark.costUsd ? /* @__PURE__ */ jsx("span", {
3423
+ className: "fw-seg-meta",
3424
+ children: formatCost(mark.costUsd)
3425
+ }) : null
3426
+ ]
3427
+ })]
3396
3428
  });
3397
3429
  }
3398
3430
  /** A span the clock is inside: it grows live, or playback has not reached its end. */
@@ -3418,7 +3450,7 @@ const FlowRow = memo(function FlowRow(props) {
3418
3450
  const kind = !row.ok ? "failed" : row.kind;
3419
3451
  const marks = isAgent || row.hasChildren || row.id.startsWith("run:") ? folded && !agentView ? row.segments : row.own : [];
3420
3452
  const label = endLabel(row, clockMs);
3421
- const summary = [...span.detail && span.detail !== props.sharedDetail ? [span.detail] : [], ...agentSummary(row)];
3453
+ const summary = [...span.detail && span.detail !== props.sharedDetail ? [span.detail] : [], ...agentSummary(row).filter((part) => part !== props.sharedOutcome)];
3422
3454
  const inspect = (event) => props.onInspect(row.id, event.currentTarget, event.clientX);
3423
3455
  const family = agentView && row.id.startsWith("family:");
3424
3456
  const step = agentView && row.stepOf !== void 0;
@@ -3554,6 +3586,11 @@ const FlowRow = memo(function FlowRow(props) {
3554
3586
  "aria-label": `${row.descendants} hidden steps`,
3555
3587
  children: ["+", row.descendants]
3556
3588
  }),
3589
+ family && !props.expanded && /* @__PURE__ */ jsxs("span", {
3590
+ className: "fw-count",
3591
+ "aria-label": `${row.descendants} agents folded`,
3592
+ children: ["+", row.descendants]
3593
+ }),
3557
3594
  !row.ok && !agentView && /* @__PURE__ */ jsx("span", {
3558
3595
  className: "fw-alert",
3559
3596
  children: /* @__PURE__ */ jsx(AlertGlyph, { label: "failed" })
@@ -3622,7 +3659,7 @@ const FlowRow = memo(function FlowRow(props) {
3622
3659
  ] })
3623
3660
  })]
3624
3661
  });
3625
- }, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.matched === b.matched && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3662
+ }, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.matched === b.matched && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.sharedOutcome === b.sharedOutcome && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3626
3663
  /** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
3627
3664
  function LinkMark({ link, rowHeight, hot }) {
3628
3665
  const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
@@ -4095,6 +4132,7 @@ function FlowBody(props) {
4095
4132
  renderIcon: props.renderIcon,
4096
4133
  fillByAgent: props.fillByAgent,
4097
4134
  sharedDetail: props.sharedDetail,
4135
+ sharedOutcome: props.sharedOutcome,
4098
4136
  expanded: props.expandedIds?.has(item.id),
4099
4137
  onToggle: props.onToggle,
4100
4138
  onInspect: props.onInspect,
@@ -4481,6 +4519,13 @@ function useHostable(value, onChange, initial) {
4481
4519
  }, [controlled])];
4482
4520
  }
4483
4521
  const CARD_WIDTH = 320;
4522
+ /** The value at least half of several agents share, if one does. */
4523
+ function usual(values) {
4524
+ const counts = /* @__PURE__ */ new Map();
4525
+ for (const value of values) if (value) counts.set(value, (counts.get(value) ?? 0) + 1);
4526
+ const [top, n] = [...counts].sort((a, b) => b[1] - a[1])[0] ?? [];
4527
+ return values.length > 1 && n !== void 0 && n >= 2 && n * 2 >= values.length ? top : void 0;
4528
+ }
4484
4529
  /** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
4485
4530
  function summaryCells(trace, at, durationMs) {
4486
4531
  const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
@@ -4658,8 +4703,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4658
4703
  const pipelines = rows.filter((row) => row.kind === "pipeline");
4659
4704
  return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
4660
4705
  }, [rows]);
4661
- const details = new Set(agents.flatMap((row) => row.span.detail ? [row.span.detail] : []));
4662
- const sharedDetail = agents.length > 1 && details.size === 1 ? [...details][0] : void 0;
4706
+ const sharedDetail = usual(agents.map((row) => row.span.detail));
4707
+ const sharedOutcome = usual(agents.map((row) => row.span.outcome?.label));
4663
4708
  const [flippedFamilies, setFlippedFamilies] = useState(/* @__PURE__ */ new Set());
4664
4709
  const openFamilies = useMemo(() => defaultOpenFamilies(agents, UNFOLD_ROWS, flippedFamilies), [agents, flippedFamilies]);
4665
4710
  const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
@@ -4982,7 +5027,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4982
5027
  agents.length,
4983
5028
  " ",
4984
5029
  agents.length === 1 ? "agent" : "agents",
4985
- sharedDetail ? ` · ${sharedDetail}` : "",
5030
+ sharedDetail ? ` · ${agents.every((row) => row.span.detail === sharedDetail) ? "" : "mostly "}${sharedDetail}` : "",
4986
5031
  done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
4987
5032
  ]
4988
5033
  }),
@@ -5068,6 +5113,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
5068
5113
  pauses: trace.pauses,
5069
5114
  cursorLine: position !== null || playing,
5070
5115
  sharedDetail,
5116
+ sharedOutcome,
5071
5117
  expandedIds: view === "agents" ? /* @__PURE__ */ new Set([...openFamilies, ...openSteps]) : void 0,
5072
5118
  holdHeight: isLive,
5073
5119
  slowestId: slowest?.id ?? callouts.slowestId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.21.17",
3
+ "version": "0.21.19",
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",