@tangle-network/charts 0.21.4 → 0.21.5

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.
@@ -123,13 +123,18 @@
123
123
  .fw-root .fw-gridline { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--fw-grid); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
124
124
  .fw-root .fw-cursor-layer { z-index: 3; }
125
125
  .fw-root .fw-marker-layer { position: absolute; top: 0; bottom: 0; left: var(--fw-label-col); right: 0; overflow: hidden; pointer-events: none; z-index: 2; }
126
- .fw-root .fw-marker { --fw-marker: var(--fw-muted); position: absolute; top: 0; bottom: 0; width: 0; border-left: 1px dashed var(--fw-marker); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
126
+ /* An event's line is drawn behind the rows, with the gridlines, so bars and labels cover it; its label stays on top. */
127
+ .fw-root .fw-marker { --fw-marker: var(--fw-muted); position: absolute; top: 0; bottom: 0; width: 0; left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
128
+ .fw-root .fw-marker-line { --fw-marker: var(--fw-muted); position: absolute; top: 0; bottom: 0; border-left: 1px dashed var(--fw-marker); left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); }
129
+ .fw-root .fw-marker-line[data-tone="ok"] { --fw-marker: var(--fw-ok); }
130
+ .fw-root .fw-marker-line[data-tone="failed"] { --fw-marker: var(--fw-failed); }
127
131
  .fw-root .fw-marker[data-tone="ok"] { --fw-marker: var(--fw-ok); }
128
132
  .fw-root .fw-marker[data-tone="failed"] { --fw-marker: var(--fw-failed); }
129
133
  /* A pause: the run stood still. A hatched band across every row with its reason on top, never blank space. */
130
134
  .fw-root .fw-pause { position: absolute; top: 0; bottom: 0; left: calc(var(--fw-pad) + (var(--s) - var(--fw-v0)) * var(--fw-k) * 1px); width: calc(max(0, var(--e) - var(--s)) * var(--fw-k) * 1px);
131
135
  background: repeating-linear-gradient(-45deg, color-mix(in oklab, var(--fw-text) 9%, transparent) 0 1px, transparent 1px 6px), color-mix(in oklab, var(--fw-text) 4%, transparent);
132
136
  border-inline: 1px dashed color-mix(in oklab, var(--fw-text) 24%, transparent); }
137
+ .fw-root .fw-pause-label b { font-weight: 600; color: var(--fw-text); }
133
138
  .fw-root .fw-pause-label { position: sticky; top: 4px; display: block; width: max-content; max-width: calc(100% - 8px); margin: 4px auto 0; padding: 0 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
134
139
  border-radius: 4px; font-size: 12px; line-height: 20px; color: var(--fw-muted); background: var(--fw-surface); box-shadow: inset 0 0 0 1px var(--fw-border); }
135
140
  /* The label alone takes the pointer, for its reason; the band stays click-through to the rows. */
@@ -231,6 +236,10 @@
231
236
  .fw-root .fw-view-agents .fw-bar.fw-k-pipeline.fw-has-marks { background: repeating-linear-gradient(-45deg, color-mix(in oklab, var(--tone) 28%, transparent) 0 1px, transparent 1px 5px), color-mix(in oklab, var(--tone) 9%, transparent); }
232
237
  .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); }
233
238
  /* A host outcome re-tones the whole lane: dot, bar and marks. */
239
+ /* Muted agents (the default): one quiet tone for every agent bar, its steps and handoffs; the slowest agent alone takes amber, and
240
+ failure keeps its red glyph, so color only ever means state. */
241
+ .fw-root.fw-fill-muted .fw-view-agents [data-tone] { --tone: var(--fw-h0); }
242
+ .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); }
234
243
  .fw-root [data-outcome="ok"], .fw-root [data-outcome="ok"] [data-tone] { --tone: var(--fw-ok); }
235
244
  .fw-root [data-outcome="failed"], .fw-root [data-outcome="failed"] [data-tone] { --tone: var(--fw-failed); }
236
245
  .fw-root [data-outcome="warn"], .fw-root [data-outcome="warn"] [data-tone] { --tone: var(--fw-amber); }
@@ -260,7 +269,10 @@
260
269
  .fw-root .fw-glyph { display: inline-flex; color: currentColor; }
261
270
  .fw-root .fw-glyph svg { width: 11px; height: 11px; stroke-width: 2; }
262
271
  .fw-root .fw-seg-model .fw-glyph, .fw-root .fw-k-model .fw-glyph { color: light-dark(var(--tone), #fff); }
263
- .fw-root .fw-end { z-index: 2; display: inline-flex; align-items: center; gap: 8px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h); left: calc(var(--x0) + max(var(--w), 3px) + 8px); white-space: nowrap; pointer-events: none; }
272
+ /* End labels sit on the surface, so connectors and event lines stop at them, and never run past the lane: a label that would
273
+ be cut at the right edge slides back over its bar's tail instead. */
274
+ .fw-root .fw-end { z-index: 2; display: inline-flex; align-items: center; gap: 8px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2); height: var(--fw-bar-h);
275
+ left: min(calc(var(--x0) + max(var(--w), 3px) + 4px), calc(100% - var(--fw-end-w, 0px) - 4px)); padding: 0 4px; border-radius: 4px; background: var(--fw-surface); white-space: nowrap; pointer-events: none; }
264
276
  .fw-root .fw-duration { font-size: 12px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
265
277
  .fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
266
278
  .fw-root .fw-badge { height: 18px; padding: 0 8px; border-radius: 999px; font-size: 12px; line-height: 16px; 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); }
@@ -399,9 +399,10 @@ interface FlowWaterfallProps {
399
399
  markers?: FlowMarker[];
400
400
  /** The time the reader moved to, in trace ms; `null` when following live or showing the whole trace. */
401
401
  onTimeChange?: (t: number | null) => void;
402
- /** 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
403
- * crash glyph; or its `outcome`. */
404
- agentFill?: 'outcome' | 'agent';
402
+ /** What an agent bar's color means. `muted` (default): every agent one quiet tone, with color spent on state only: the outcome on
403
+ * the row dot, red for failure, amber for the slowest. `agent`: each agent its own hue, shared by its steps and handoffs. `outcome`: the
404
+ * bar takes its outcome's color. */
405
+ agentFill?: 'muted' | 'outcome' | 'agent';
405
406
  /** One line beside the title saying what the run was for, such as its goal. */
406
407
  description?: string;
407
408
  /** The row the host has selected, such as the agent whose transcript is open: it lifts and, in the agent view, its peers dim.
@@ -1,4 +1,4 @@
1
- import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-B7c-cede.js";
1
+ import { G as hatchPath, J as markerPath, T as duration, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-B7c-cede.js";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Fragment as Fragment$1, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  //#region src/react/sparkline.tsx
@@ -2724,6 +2724,13 @@ const roleOf = (name) => name.replace(/[-\s]\d+$/, "");
2724
2724
  * Agents sharing a role fold into one family row until opened: a 17-agent run reads as its few roles, and the bar of a
2725
2725
  * family carries every member's work in each member's hue. Members follow their opened family, marked with `family`.
2726
2726
  */
2727
+ /** The families open by default: all of them when every agent fits in `rows`, so a reader sees each agent and its steps (the
2728
+ * blind reviews preferred agent rows to `role ×N`); none on bigger runs. `flipped` holds the families the reader toggled. */
2729
+ function defaultOpenFamilies(agents, rows, flipped) {
2730
+ const unfold = agents.length <= rows;
2731
+ const ids = agentFamilies(agents, /* @__PURE__ */ new Set()).filter((row) => row.id.startsWith("family:")).map((row) => row.id);
2732
+ return new Set(ids.filter((id) => unfold !== flipped.has(id)));
2733
+ }
2727
2734
  function agentFamilies(agents, open) {
2728
2735
  const groups = /* @__PURE__ */ new Map();
2729
2736
  for (const row of agents) {
@@ -3272,6 +3279,7 @@ const at = (startMs, endMs) => ({
3272
3279
  "--s": startMs,
3273
3280
  "--e": Number.isFinite(endMs) ? endMs : OPEN_END
3274
3281
  });
3282
+ const endLabelWidth = (label, slowest, costliest) => label.length * 7 + 8 + (slowest ? 74 : 0) + (costliest ? 84 : 0);
3275
3283
  function Segment({ mark, events, agentView, ending }) {
3276
3284
  const glyph = mark.kind === "failed" ? agentView && !ending ? null : /* @__PURE__ */ jsx(FailGlyph, {}) : mark.retry ? /* @__PURE__ */ jsx(RetryGlyph, {}) : null;
3277
3285
  return /* @__PURE__ */ jsx("span", {
@@ -3483,7 +3491,10 @@ const FlowRow = memo(function FlowRow(props) {
3483
3491
  }, `${mark.id}-${i}`)),
3484
3492
  /* @__PURE__ */ jsxs("span", {
3485
3493
  className: "fw-end",
3486
- style: at(span.startMs, running ? OPEN_END : span.endMs),
3494
+ style: {
3495
+ ...at(span.startMs, running ? OPEN_END : span.endMs),
3496
+ "--fw-end-w": `${endLabelWidth(label, slowest, costliest)}px`
3497
+ },
3487
3498
  children: [
3488
3499
  /* @__PURE__ */ jsx("span", {
3489
3500
  className: "fw-duration",
@@ -3952,13 +3963,17 @@ function FlowBody(props) {
3952
3963
  children: [axis, /* @__PURE__ */ jsxs("div", {
3953
3964
  className: "fw-viewport",
3954
3965
  children: [
3955
- /* @__PURE__ */ jsx("div", {
3966
+ /* @__PURE__ */ jsxs("div", {
3956
3967
  className: "fw-gridlayer",
3957
3968
  "aria-hidden": true,
3958
- children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3969
+ children: [ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3959
3970
  className: "fw-gridline",
3960
3971
  style: { "--s": tick }
3961
- }, tick))
3972
+ }, tick)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
3973
+ className: "fw-marker-line",
3974
+ "data-tone": marker.tone ?? "neutral",
3975
+ style: { "--s": marker.at }
3976
+ }, `l-${marker.at}-${marker.label}`))]
3962
3977
  }),
3963
3978
  /* @__PURE__ */ jsxs("div", {
3964
3979
  ref: scroller,
@@ -4012,12 +4027,16 @@ function FlowBody(props) {
4012
4027
  "--e": pause.endMs
4013
4028
  },
4014
4029
  title: pause.label,
4015
- children: /* @__PURE__ */ jsx("span", {
4030
+ children: /* @__PURE__ */ jsxs("span", {
4016
4031
  className: "fw-pause-label",
4017
4032
  title: pause.detail,
4018
4033
  tabIndex: pause.detail ? 0 : void 0,
4019
4034
  "aria-label": pause.detail ? `${pause.label}: ${pause.detail}` : void 0,
4020
- children: pause.label
4035
+ children: [
4036
+ /* @__PURE__ */ jsx("b", { children: duration(pause.endMs - pause.startMs) }),
4037
+ " ",
4038
+ pause.label
4039
+ ]
4021
4040
  })
4022
4041
  }, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
4023
4042
  className: "fw-marker",
@@ -4298,6 +4317,7 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
4298
4317
  //#endregion
4299
4318
  //#region src/react/flow-waterfall.tsx
4300
4319
  const LANE_PAD = 12;
4320
+ const AGENT_ROWS = 20;
4301
4321
  const END_LABEL_GUTTER = {
4302
4322
  spans: 120,
4303
4323
  agents: 176
@@ -4408,7 +4428,7 @@ function OutcomeLegend({ agents }) {
4408
4428
  }
4409
4429
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
4410
4430
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
4411
- function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect, description, summary: showSummary = true }) {
4431
+ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "muted", selectedId, onDeselect, description, summary: showSummary = true }) {
4412
4432
  const root = useRef(null);
4413
4433
  const search = useRef(null);
4414
4434
  const slider = useRef(null);
@@ -4482,7 +4502,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4482
4502
  return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
4483
4503
  }, [rows]);
4484
4504
  const sharedDetail = agents.length > 1 && agents.every((row) => row.span.detail && row.span.detail === agents[0]?.span.detail) ? agents[0]?.span.detail : void 0;
4485
- const [openFamilies, setOpenFamilies] = useState(/* @__PURE__ */ new Set());
4505
+ const [flippedFamilies, setFlippedFamilies] = useState(/* @__PURE__ */ new Set());
4506
+ const openFamilies = useMemo(() => defaultOpenFamilies(agents, AGENT_ROWS, flippedFamilies), [agents, flippedFamilies]);
4486
4507
  const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
4487
4508
  const visible = useMemo(() => view === "agents" ? agentViewRows(agents, rows, openFamilies, openSteps) : visibleFlowWaterfallRows(rows, query, collapsed), [
4488
4509
  view,
@@ -4499,7 +4520,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4499
4520
  visible,
4500
4521
  clockMs
4501
4522
  ]);
4502
- const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
4523
+ const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * AGENT_ROWS : 640);
4503
4524
  const resized = useRef(false);
4504
4525
  useLayoutEffect(() => {
4505
4526
  if (view !== "agents" || resized.current || !root.current) return;
@@ -4600,7 +4621,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4600
4621
  const toggleNow = (row) => {
4601
4622
  const flip = (open) => new Set(open.has(row.id) ? [...open].filter((id) => id !== row.id) : [...open, row.id]);
4602
4623
  if (row.id.startsWith("family:")) {
4603
- setOpenFamilies(flip);
4624
+ setFlippedFamilies(flip);
4604
4625
  return;
4605
4626
  }
4606
4627
  onSelect?.(row.span);
@@ -4737,7 +4758,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4737
4758
  ref: root,
4738
4759
  "data-flow-waterfall": true,
4739
4760
  "aria-label": label,
4740
- className: `fw-root fw-root-${view}`,
4761
+ className: `fw-root fw-root-${view} fw-fill-${agentFill}`,
4741
4762
  "data-selection": selectedId ? "" : void 0,
4742
4763
  style: {
4743
4764
  "--fw-name-w": `${nameWidth}px`,
@@ -4879,7 +4900,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4879
4900
  trackPx,
4880
4901
  showCursor,
4881
4902
  markers,
4882
- fillByAgent: agentFill === "agent",
4903
+ fillByAgent: agentFill !== "outcome",
4883
4904
  zoomed: zoom !== null,
4884
4905
  renderIcon,
4885
4906
  emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "0.21.4",
3
+ "version": "0.21.5",
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",