@tangle-network/charts 2.0.16 → 2.0.18

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
@@ -4,6 +4,8 @@ Benchmark figures as static SVG and HTML tables, plus optional React timing prim
4
4
 
5
5
  The React entry also exports `DayBars`, `HBars`, `Histogram`, `Lines`, `Meter`, `Spark`, and `Gantt` for live operational pages. Import `@tangle-network/charts/react/live.css` for their layout and set `--chart-live-*` theme tokens on an ancestor. Data, state colors, destination links, labels, and interpretation remain with the caller. `DayBars` accepts an `onOpen(index)` action with keyboard activation.
6
6
 
7
+ `VersionGraph` and `layoutVersionGraph` from `@tangle-network/charts/react` render version histories with lane and merge geometry, controlled selection, filtering, keyboard navigation and bounded rows. Import `react/live.css` and Brand tokens as above. Producers supply commit identities, parents, lane labels and scored chips; Charts preserves those facts and owns drawing, arrival motion and reduced-motion behavior.
8
+
7
9
  `FlowWaterfall` from `@tangle-network/charts/react` is the shared live trace inspector: an agent timeline (one row per agent, elbow handoffs, hatched waiting) and a virtualized span waterfall over one clock. It owns hierarchy layout, folding, search, playback, zoom, span cards and running-span growth. Producers own identity, `parentId`, explicit `status` and observed totals. Import `@tangle-network/charts/react/flow-waterfall.css`; its `fw-*` classes and six-field summary support hosts without a Tailwind build. Every color, radius and shadow in it is a Brand role (text, card, hairline, well, chart grid, status inks, the categorical tones for agents), so the host loads `@tangle-network/brand` `tokens.css` (or `legacy-light.css`) and sets its theme; the stylesheet has no color of its own. The React-free root exports the `FlowSpan` and `FlowTrace` types.
8
10
 
9
11
  `FlowWaterfall` accepts optional `graph` and `latestEvents` React nodes. Supplying `graph` adds Graph to its existing Agents/Steps view control, including `defaultView="graph"`. The trace clock and controlled selection survive switches. Graph hides waterfall-only controls; `latestEvents` stays mounted in the header across all views.
@@ -1,5 +1,5 @@
1
1
  import { D as statusMeta, O as createLucideIcon, T as RunStatusIcon } from "./flow-waterfall-model-DvrgPLGS.js";
2
- import { a as LIVE_AGENT_CARD, c as AgentMark, d as Wrench, n as AGENT_CARD, o as buildAgentTopology, s as layoutAgents, t as LatestEvents } from "./latest-events-iiWwpIck.js";
2
+ import { a as LIVE_AGENT_CARD, c as AgentMark, d as Wrench, n as AGENT_CARD, o as buildAgentTopology, s as layoutAgents, t as LatestEvents } from "./latest-events-CmbyqEIe.js";
3
3
  import { C as fmtCost, E as fmtTokenTotal, T as fmtEventTime, _ as syncAmbient, b as useMotionAllowed, g as ambientStyle, h as agentQuietActivity, i as SELECTION_OUTLINE, n as RESTING_SHADOW, o as useColorMode, r as edgeColor, t as RESTING_BORDER, u as shortModel, v as tokenMilliseconds, x as useRunNow, y as useAttention } from "./graph-style-fealnLLn.js";
4
4
  import { t as MOTION_CONTROL } from "./motion-DILryczn.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -116,13 +116,10 @@ function AgentCardView({ agent, selected, direction, now: fixed, onOpen, parentL
116
116
  /* @__PURE__ */ jsxs("div", {
117
117
  className: "flex min-w-0 items-center gap-2",
118
118
  children: [
119
- /* @__PURE__ */ jsx("span", {
120
- className: "grayscale hover:grayscale-0",
121
- children: /* @__PURE__ */ jsx(AgentMark, {
122
- agent,
123
- layout: "tree",
124
- size: 18
125
- })
119
+ /* @__PURE__ */ jsx(AgentMark, {
120
+ agent,
121
+ layout: "tree",
122
+ size: 24
126
123
  }),
127
124
  /* @__PURE__ */ jsxs("span", {
128
125
  className: "min-w-0 flex-1 truncate text-sm font-semibold leading-[18px]",
@@ -132,35 +129,50 @@ function AgentCardView({ agent, selected, direction, now: fixed, onOpen, parentL
132
129
  }), agent.label]
133
130
  }),
134
131
  /* @__PURE__ */ jsx("span", {
135
- className: "text-xs text-muted-foreground tabular-nums",
132
+ className: "text-[13px] text-muted-foreground tabular-nums",
136
133
  title: agent.costUsd == null ? "Cost not reported" : "Cost",
137
134
  children: fmtCost(agent.costUsd ?? void 0) ?? "—"
138
135
  })
139
136
  ]
140
137
  }),
141
138
  /* @__PURE__ */ jsx("p", {
142
- className: "m-0 line-clamp-2 min-h-4 text-xs text-foreground leading-4",
143
- children: (agent.status === "failed" ? agent.result ?? agent.turn : agent.turn) || "—"
139
+ "data-agent-identity": "",
140
+ className: "m-0 truncate text-[13px] leading-[18px] text-muted-foreground",
141
+ title: [agent.harness, agent.model].filter(Boolean).join(" · "),
142
+ children: [agent.harness, agent.model ? shortModel(agent.model) : null].filter(Boolean).join(" · ") || "Identity not recorded"
143
+ }),
144
+ agent.status === "failed" && /* @__PURE__ */ jsx("p", {
145
+ className: "m-0 line-clamp-2 text-[13px] leading-4",
146
+ children: agent.result ?? agent.turn ?? "Failure cause not recorded"
144
147
  }),
145
148
  /* @__PURE__ */ jsxs("div", {
146
- className: "mt-auto flex items-center justify-between gap-1 text-xs",
149
+ className: "mt-auto flex items-center justify-between gap-1 text-[13px]",
147
150
  style: { color: quiet ? "var(--surface-warning-text)" : s.color },
148
- children: [/* @__PURE__ */ jsxs("span", {
149
- className: "flex items-center gap-1",
150
- children: [/* @__PURE__ */ jsx(StatusGlyph, {
151
- status: agent.status,
152
- size: 16
153
- }), quiet?.label ?? s.label]
154
- }), agent.status === "needs-you" && /* @__PURE__ */ jsx("button", {
155
- type: "button",
156
- className: "ag-live-respond nodrag nopan",
157
- disabled: !onOpen,
158
- onClick: (event) => {
159
- event.stopPropagation();
160
- onOpen?.();
161
- },
162
- children: "Respond"
163
- })]
151
+ children: [
152
+ /* @__PURE__ */ jsxs("span", {
153
+ className: "flex items-center gap-1",
154
+ children: [/* @__PURE__ */ jsx(StatusGlyph, {
155
+ status: agent.status,
156
+ size: 16
157
+ }), quiet?.label ?? s.label]
158
+ }),
159
+ /* @__PURE__ */ jsx("span", {
160
+ "data-agent-tokens": "",
161
+ title: agent.tokens == null ? "Tokens not recorded" : "Tokens",
162
+ className: "text-muted-foreground tabular-nums whitespace-nowrap",
163
+ children: fmtTokenTotal(agent.tokens) ?? "—"
164
+ }),
165
+ agent.status === "needs-you" && /* @__PURE__ */ jsx("button", {
166
+ type: "button",
167
+ className: "ag-live-respond nodrag nopan",
168
+ disabled: !onOpen,
169
+ onClick: (event) => {
170
+ event.stopPropagation();
171
+ onOpen?.();
172
+ },
173
+ children: "Respond"
174
+ })
175
+ ]
164
176
  })
165
177
  ]
166
178
  });
@@ -174,14 +186,14 @@ function AgentCard({ data }) {
174
186
  function AgentFrameBox({ data }) {
175
187
  const { frame, direction } = data;
176
188
  return /* @__PURE__ */ jsxs("div", {
177
- className: "relative h-full w-full rounded-2xl border",
189
+ className: "ag-team-frame relative h-full w-full rounded-2xl border",
178
190
  style: {
179
191
  background: "color-mix(in srgb, hsl(var(--muted-foreground)) 7%, transparent)",
180
192
  borderColor: "hsl(var(--foreground) / 0.08)"
181
193
  },
182
194
  children: [/* @__PURE__ */ jsx(Handles, { direction }), /* @__PURE__ */ jsxs("button", {
183
195
  type: "button",
184
- className: "nodrag nopan absolute right-3 top-2 flex h-16 w-8 flex-col items-center justify-center gap-1 rounded text-sm text-muted-foreground hover:bg-muted focus-visible:outline",
196
+ className: `nodrag nopan absolute top-2 flex h-16 items-center justify-center gap-2 rounded text-sm text-muted-foreground hover:bg-muted focus-visible:outline ${data.groupParents ? "right-3 w-8 flex-col" : "left-3 right-3 justify-start"}`,
185
197
  "aria-label": `${frame.collapsed ? "Expand" : "Collapse"} ${data.label} team`,
186
198
  "aria-expanded": !frame.collapsed,
187
199
  title: `${data.label} · ${frame.group.memberIds.length} agents`,
@@ -189,10 +201,14 @@ function AgentFrameBox({ data }) {
189
201
  event.stopPropagation();
190
202
  data.onToggle();
191
203
  },
192
- children: [/* @__PURE__ */ jsx(ChevronDown, {
193
- size: 16,
194
- className: frame.collapsed ? "-rotate-90" : void 0
195
- }), /* @__PURE__ */ jsx("span", { children: frame.group.memberIds.length })]
204
+ children: [
205
+ /* @__PURE__ */ jsx(ChevronDown, {
206
+ size: 16,
207
+ className: frame.collapsed ? "-rotate-90" : void 0
208
+ }),
209
+ !data.groupParents && /* @__PURE__ */ jsxs("strong", { children: [data.label, " team"] }),
210
+ /* @__PURE__ */ jsx("span", { children: frame.group.memberIds.length })
211
+ ]
196
212
  })]
197
213
  });
198
214
  }
@@ -528,7 +544,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
528
544
  const columnRef = useRef(null);
529
545
  if (canvas && !narrow && columnRef.current?.key !== columnKey) columnRef.current = {
530
546
  key: columnKey,
531
- columns: Math.max(1, Math.min(6, Math.floor((canvas.width - 32) / (card.width + 10))))
547
+ columns: Math.max(1, Math.min(6, Math.floor((canvas.width - 32 - (!live && direction === "LR" ? card.width + 70 : 0)) / (card.width + 10))))
532
548
  };
533
549
  const columns = columnRef.current?.columns ?? 1;
534
550
  const layoutKey = JSON.stringify([
@@ -542,7 +558,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
542
558
  useEffect(() => {
543
559
  if (narrow) return;
544
560
  let active = true;
545
- layoutAgents(topology, direction, columns, card, collapsed).then((layout) => {
561
+ layoutAgents(topology, direction, columns, card, collapsed, live).then((layout) => {
546
562
  if (active) setPlaced({
547
563
  key: layoutKey,
548
564
  layout
@@ -583,6 +599,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
583
599
  data: {
584
600
  frame,
585
601
  direction,
602
+ groupParents: live,
586
603
  label: byId.get(frame.group.parentId)?.label ?? "Agent",
587
604
  onToggle: () => {
588
605
  if (!frame.collapsed && selectedId && frame.group.memberIds.includes(selectedId)) onSelect?.(frame.group.parentId);
@@ -599,7 +616,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
599
616
  const agent = byId.get(id);
600
617
  const position = layout.positions.get(id);
601
618
  if (!agent || !position) continue;
602
- const selected = selectedId === id, owned = layout.frames.find((frame) => frame.group.parentId === id);
619
+ const selected = selectedId === id, owned = live && layout.frames.find((frame) => frame.group.parentId === id);
603
620
  const width = owned ? owned.width - 64 : card.width, height = owned ? 64 : card.height;
604
621
  const measured = rfRef.current?.getInternalNode(id)?.measured;
605
622
  cards.push({
@@ -646,7 +663,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
646
663
  const out = [];
647
664
  const seen = /* @__PURE__ */ new Set();
648
665
  for (const s of topology.spawns) {
649
- const source = topology.groups.find((group) => group.parentId === s.from)?.id ?? s.from, target = topology.groupOf.get(s.to) ?? s.to;
666
+ const source = live && topology.groups.find((group) => group.parentId === s.from)?.id || s.from, target = topology.groupOf.get(s.to) ?? s.to;
650
667
  if (source === target) continue;
651
668
  const id = `spawn:${source}:${target}`;
652
669
  if (seen.has(id)) continue;
@@ -720,7 +737,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
720
737
  }, [narrow]);
721
738
  const layoutIdentity = layout ? `${layoutKey}:${layout.columns}` : null;
722
739
  const cameraKey = `${direction}:${topology.key}`;
723
- const minZoom = layout && canvas ? Math.max(.02, Math.min(.2, (canvas.width - 32) / layout.width, (canvas.height - 32) / layout.height)) : .2;
740
+ const minZoom = !live ? 1 : layout && canvas ? Math.max(.02, Math.min(.2, (canvas.width - 32) / layout.width, (canvas.height - 32) / layout.height)) : .2;
724
741
  useEffect(() => {
725
742
  const inst = rfRef.current;
726
743
  if (!ready || !inst || !layoutIdentity || !canvas) return;
@@ -888,7 +905,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
888
905
  wrapperRef.current = el;
889
906
  measureCanvas(el);
890
907
  },
891
- className: `agent-graph ag-live relative flex flex-col ${className ?? ""}`,
908
+ className: `agent-graph ag-live${live ? "" : " ag-topology"} relative flex flex-col ${className ?? ""}`,
892
909
  tabIndex: -1,
893
910
  onKeyDownCapture: (event) => {
894
911
  if (event.target instanceof HTMLElement && event.target.closest("[data-agent-metrics]")) return;
@@ -949,7 +966,7 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
949
966
  select(agent.id);
950
967
  }
951
968
  },
952
- children: live || agent.status === "succeeded" || agent.status === "stopped" ? /* @__PURE__ */ jsx(LiveAgentCardView, {
969
+ children: live ? /* @__PURE__ */ jsx(LiveAgentCardView, {
953
970
  agent,
954
971
  parentLabel: byId.get(parentOf.get(agent.id) ?? "")?.label,
955
972
  selected: selectedId === agent.id,
@@ -1060,7 +1077,9 @@ function AgentGraphCanvas({ nodes, edges, selectedId, onSelect, direction = "TB"
1060
1077
  position: "bottom-right",
1061
1078
  fitViewOptions: {
1062
1079
  padding: `${FIT_PADDING}px`,
1063
- maxZoom: 1
1080
+ minZoom,
1081
+ maxZoom: 1,
1082
+ nodes: live ? void 0 : topology.agentIds.slice(0, 2).map((id) => ({ id }))
1064
1083
  }
1065
1084
  })
1066
1085
  ]
@@ -67,8 +67,8 @@ interface AgentGraphEdge {
67
67
  type AgentGraphDirection = "TB" | "LR";
68
68
  /** Every agent card's size. Fixed, so a status or turn update never moves a card. */
69
69
  declare const AGENT_CARD: {
70
- readonly width: 240;
71
- readonly height: 88;
70
+ readonly width: 232;
71
+ readonly height: 120;
72
72
  };
73
73
  /** A live card: room for the header, a two-line thinking line and the last three events. Fixed for the same reason. */
74
74
  declare const LIVE_AGENT_CARD: {
@@ -125,7 +125,7 @@ interface AgentLayout {
125
125
  height: number;
126
126
  }
127
127
  /** Lay the run out with every grid `columns` wide. */
128
- declare function layoutAgents(topology: AgentTopology, direction: AgentGraphDirection, columns: number, card?: CardSize, collapsed?: ReadonlySet<string>): Promise<AgentLayout>;
128
+ declare function layoutAgents(topology: AgentTopology, direction: AgentGraphDirection, columns: number, card?: CardSize, collapsed?: ReadonlySet<string>, groupParents?: boolean): Promise<AgentLayout>;
129
129
  //#endregion
130
130
  //#region src/react/agent-graph/format.d.ts
131
131
  /**
@@ -85,8 +85,8 @@ function ToolMark({ tool, label, size }) {
85
85
  //#region src/react/agent-graph/agent-graph-layout.ts
86
86
  /** Every agent card's size. Fixed, so a status or turn update never moves a card. */
87
87
  const AGENT_CARD = {
88
- width: 240,
89
- height: 88
88
+ width: 232,
89
+ height: 120
90
90
  };
91
91
  /** A live card: room for the header, a two-line thinking line and the last three events. Fixed for the same reason. */
92
92
  const LIVE_AGENT_CARD = {
@@ -160,35 +160,29 @@ function gridSize(members, columns, card, collapsed = false) {
160
160
  };
161
161
  }
162
162
  /** The boxes the layouter places: each ungrouped agent, and each grid as one block. */
163
- function layoutUnits(topology, columns, card, collapsed) {
164
- const units = [];
165
- const ownerGroup = new Map(topology.groups.map((group) => [group.parentId, group.id]));
166
- for (const id of topology.agentIds) if (!topology.groupOf.has(id) && !ownerGroup.has(id)) units.push({
163
+ function layoutUnits(topology, columns, card, collapsed, groupParents) {
164
+ const ownerGroup = new Map(groupParents ? topology.groups.map((group) => [group.parentId, group.id]) : []);
165
+ const units = topology.agentIds.filter((id) => !topology.groupOf.has(id) && !ownerGroup.has(id)).map((id) => ({
167
166
  id,
168
- width: card.width,
169
- height: card.height
170
- });
171
- for (const g of topology.groups) units.push({
172
- id: g.id,
173
- ...gridSize(g.memberIds.length, columns, card, collapsed.has(g.id))
174
- });
167
+ ...card
168
+ }));
169
+ units.push(...topology.groups.map((group) => ({
170
+ id: group.id,
171
+ ...gridSize(group.memberIds.length, columns, card, collapsed.has(group.id))
172
+ })));
175
173
  const unitOf = (id) => topology.groupOf.get(id) ?? ownerGroup.get(id) ?? id;
176
174
  const seen = /* @__PURE__ */ new Set();
177
- const links = [];
178
- for (const s of topology.spawns) {
179
- const from = unitOf(s.from);
180
- const to = unitOf(s.to);
181
- const key = `${from}\u0000${to}`;
182
- if (from === to || seen.has(key)) continue;
183
- seen.add(key);
184
- links.push({
185
- from,
186
- to
187
- });
188
- }
189
175
  return {
190
176
  units,
191
- links
177
+ links: topology.spawns.flatMap((spawn) => {
178
+ const from = unitOf(spawn.from), to = unitOf(spawn.to), key = `${from}\u0000${to}`;
179
+ if (from === to || seen.has(key)) return [];
180
+ seen.add(key);
181
+ return [{
182
+ from,
183
+ to
184
+ }];
185
+ })
192
186
  };
193
187
  }
194
188
  async function placeWithElk(units, links, direction) {
@@ -271,8 +265,8 @@ function placeByDepth(units, links, direction) {
271
265
  return at;
272
266
  }
273
267
  /** Lay the run out with every grid `columns` wide. */
274
- async function layoutAgents(topology, direction, columns, card = AGENT_CARD, collapsed = /* @__PURE__ */ new Set()) {
275
- const { units, links } = layoutUnits(topology, columns, card, collapsed);
268
+ async function layoutAgents(topology, direction, columns, card = AGENT_CARD, collapsed = /* @__PURE__ */ new Set(), groupParents = true) {
269
+ const { units, links } = layoutUnits(topology, columns, card, collapsed, groupParents);
276
270
  const placed = await placeWithElk(units, links, direction) ?? placeByDepth(units, links, direction);
277
271
  const positions = /* @__PURE__ */ new Map();
278
272
  const frames = [];
@@ -301,9 +295,12 @@ async function layoutAgents(topology, direction, columns, card = AGENT_CARD, col
301
295
  ...size,
302
296
  collapsed: closed
303
297
  });
304
- positions.set(g.parentId, {
298
+ positions.set(g.parentId, groupParents ? {
305
299
  x: origin.x + FRAME_PAD,
306
300
  y: origin.y + 8
301
+ } : {
302
+ ...positions.get(g.parentId),
303
+ y: origin.y
307
304
  });
308
305
  if (closed) continue;
309
306
  const cols = Math.max(1, Math.min(columns, g.memberIds.length));
@@ -1,4 +1,4 @@
1
- import { A as AGENT_CARD, B as AgentLeafGroup, C as shortModel, D as fmtEventTime, E as fmtDuration, F as AgentGraphEdgeKind, G as LIVE_AGENT_CARD, H as CardSize, I as AgentGraphEvent, K as buildAgentTopology, L as AgentGraphNode, M as AgentGraphActivity, N as AgentGraphDirection, O as fmtTokenTotal, P as AgentGraphEdge, R as AgentGraphStatus, S as parseActionPath, T as fmtCost, U as FRAME_CAPTION, V as AgentTopology, W as GRID_MIN_LEAVES, _ as useRunNow, a as RESTING_SHADOW, b as actionPathLabel, c as motionAllowed, d as agentQuietActivity, f as ambientStyle, g as useMotionAllowed, h as useAttention, i as RESTING_BORDER, j as AgentFrame, k as fmtTokens, l as useColorMode, m as tokenMilliseconds, n as GraphErrorBoundary, o as edgeColor, p as syncAmbient, q as layoutAgents, r as retryImport, s as SELECTION_OUTLINE, t as loadElk, u as activityTiming, v as ActionPathParts, w as clampPreview, x as humanizeIdentifier, y as LeadCase, z as AgentLayout } from "../../elk-loader-CrLO557o.js";
1
+ import { A as AGENT_CARD, B as AgentLeafGroup, C as shortModel, D as fmtEventTime, E as fmtDuration, F as AgentGraphEdgeKind, G as LIVE_AGENT_CARD, H as CardSize, I as AgentGraphEvent, K as buildAgentTopology, L as AgentGraphNode, M as AgentGraphActivity, N as AgentGraphDirection, O as fmtTokenTotal, P as AgentGraphEdge, R as AgentGraphStatus, S as parseActionPath, T as fmtCost, U as FRAME_CAPTION, V as AgentTopology, W as GRID_MIN_LEAVES, _ as useRunNow, a as RESTING_SHADOW, b as actionPathLabel, c as motionAllowed, d as agentQuietActivity, f as ambientStyle, g as useMotionAllowed, h as useAttention, i as RESTING_BORDER, j as AgentFrame, k as fmtTokens, l as useColorMode, m as tokenMilliseconds, n as GraphErrorBoundary, o as edgeColor, p as syncAmbient, q as layoutAgents, r as retryImport, s as SELECTION_OUTLINE, t as loadElk, u as activityTiming, v as ActionPathParts, w as clampPreview, x as humanizeIdentifier, y as LeadCase, z as AgentLayout } from "../../elk-loader-uOcDmUG_.js";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/react/agent-graph/agent-hub-layout.d.ts
4
4
  /** A tool or service an agent calls: drawn as an outer node with its brand mark. */
@@ -1,5 +1,5 @@
1
1
  import { D as statusMeta, E as runStatus, O as createLucideIcon, T as RunStatusIcon } from "../../flow-waterfall-model-DvrgPLGS.js";
2
- import { a as LIVE_AGENT_CARD, c as AgentMark, i as GRID_MIN_LEAVES, l as GraphMarksProvider, n as AGENT_CARD, o as buildAgentTopology, r as FRAME_CAPTION, s as layoutAgents, t as LatestEvents, u as ToolMark } from "../../latest-events-iiWwpIck.js";
2
+ import { a as LIVE_AGENT_CARD, c as AgentMark, i as GRID_MIN_LEAVES, l as GraphMarksProvider, n as AGENT_CARD, o as buildAgentTopology, r as FRAME_CAPTION, s as layoutAgents, t as LatestEvents, u as ToolMark } from "../../latest-events-CmbyqEIe.js";
3
3
  import { C as fmtCost, D as fmtTokens, E as fmtTokenTotal, S as clampPreview, T as fmtEventTime, _ as syncAmbient, a as motionAllowed, b as useMotionAllowed, c as humanizeIdentifier, d as loadElk, f as GraphErrorBoundary, g as ambientStyle, h as agentQuietActivity, i as SELECTION_OUTLINE, l as parseActionPath, m as activityTiming, n as RESTING_SHADOW, o as useColorMode, p as retryImport, r as edgeColor, s as actionPathLabel, t as RESTING_BORDER, u as shortModel, v as tokenMilliseconds, w as fmtDuration, x as useRunNow, y as useAttention } from "../../graph-style-fealnLLn.js";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { Suspense, lazy, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
@@ -1122,7 +1122,7 @@ function AgentHubCanvas(props) {
1122
1122
  * taking the pane down.
1123
1123
  */
1124
1124
  function lazyCanvas() {
1125
- return lazy(() => retryImport(() => import("../../AgentGraphCanvas-B74GN1o6.js").then((m) => ({ default: m.AgentGraphCanvas }))));
1125
+ return lazy(() => retryImport(() => import("../../AgentGraphCanvas-C5Ee8Z6q.js").then((m) => ({ default: m.AgentGraphCanvas }))));
1126
1126
  }
1127
1127
  /** The agents as a list, for when the canvas cannot load. */
1128
1128
  function AgentListFallback({ nodes, selectedId, onSelect, className, onRetry }) {
@@ -1131,12 +1131,12 @@ function AgentListFallback({ nodes, selectedId, onSelect, className, onRetry })
1131
1131
  children: [/* @__PURE__ */ jsxs("div", {
1132
1132
  className: "mb-1 flex items-center justify-between gap-2",
1133
1133
  children: [/* @__PURE__ */ jsx("p", {
1134
- className: "text-muted-foreground text-xs",
1134
+ className: "text-muted-foreground text-[13px]",
1135
1135
  children: "Couldn't draw the graph — showing the agents."
1136
1136
  }), /* @__PURE__ */ jsx("button", {
1137
1137
  type: "button",
1138
1138
  onClick: onRetry,
1139
- className: "shrink-0 rounded border border-border px-1.5 py-0.5 text-muted-foreground text-xs transition hover:text-foreground",
1139
+ className: "shrink-0 rounded border border-border px-1.5 py-0.5 text-muted-foreground text-[13px] transition hover:text-foreground",
1140
1140
  children: "Retry"
1141
1141
  })]
1142
1142
  }), /* @__PURE__ */ jsx("ul", {
@@ -1145,7 +1145,7 @@ function AgentListFallback({ nodes, selectedId, onSelect, className, onRetry })
1145
1145
  type: "button",
1146
1146
  "aria-pressed": selectedId === n.id,
1147
1147
  onClick: () => onSelect?.(n.id),
1148
- className: "flex w-full items-center justify-between gap-2 rounded px-2 py-1 text-left text-foreground text-xs hover:bg-muted aria-pressed:bg-muted",
1148
+ className: "flex w-full items-center justify-between gap-2 rounded px-2 py-1 text-left text-foreground text-[13px] hover:bg-muted aria-pressed:bg-muted",
1149
1149
  children: [/* @__PURE__ */ jsx("span", {
1150
1150
  className: "truncate",
1151
1151
  children: n.label
@@ -1174,7 +1174,7 @@ function AgentGraphTree(props) {
1174
1174
  }),
1175
1175
  children: /* @__PURE__ */ jsx(Suspense, {
1176
1176
  fallback: /* @__PURE__ */ jsx("div", {
1177
- className: `agent-graph flex items-center justify-center rounded-lg border border-border border-dashed text-muted-foreground text-xs ${props.className ?? ""}`,
1177
+ className: `agent-graph flex items-center justify-center rounded-lg border border-border border-dashed text-muted-foreground text-[13px] ${props.className ?? ""}`,
1178
1178
  children: "Loading graph…"
1179
1179
  }),
1180
1180
  children: /* @__PURE__ */ jsx(Canvas, { ...props })
@@ -1,4 +1,4 @@
1
- import { C as shortModel, D as fmtEventTime, E as fmtDuration, O as fmtTokenTotal, S as parseActionPath, T as fmtCost, _ as useRunNow, a as RESTING_SHADOW, b as actionPathLabel, c as motionAllowed, d as agentQuietActivity, f as ambientStyle, g as useMotionAllowed, h as useAttention, i as RESTING_BORDER, k as fmtTokens, l as useColorMode, m as tokenMilliseconds, n as GraphErrorBoundary, o as edgeColor, p as syncAmbient, r as retryImport, s as SELECTION_OUTLINE, t as loadElk, u as activityTiming, v as ActionPathParts, w as clampPreview, x as humanizeIdentifier, y as LeadCase } from "../../elk-loader-CrLO557o.js";
1
+ import { C as shortModel, D as fmtEventTime, E as fmtDuration, O as fmtTokenTotal, S as parseActionPath, T as fmtCost, _ as useRunNow, a as RESTING_SHADOW, b as actionPathLabel, c as motionAllowed, d as agentQuietActivity, f as ambientStyle, g as useMotionAllowed, h as useAttention, i as RESTING_BORDER, k as fmtTokens, l as useColorMode, m as tokenMilliseconds, n as GraphErrorBoundary, o as edgeColor, p as syncAmbient, r as retryImport, s as SELECTION_OUTLINE, t as loadElk, u as activityTiming, v as ActionPathParts, w as clampPreview, x as humanizeIdentifier, y as LeadCase } from "../../elk-loader-uOcDmUG_.js";
2
2
  //#region src/react/agent-graph/motion.d.ts
3
3
  /** Brand owns complete duration/easing roles. Keep properties explicit. */
4
4
  export declare const MOTION_CONTROL = "[transition:color_var(--motion-control),background-color_var(--motion-control),border-color_var(--motion-control),box-shadow_var(--motion-control),transform_var(--motion-control),scale_var(--motion-control),opacity_var(--motion-control)] motion-reduce:transition-none";
@@ -23,7 +23,7 @@
23
23
  --radius-full: 999px;
24
24
  }
25
25
  }
26
- @layer base {
26
+ @layer utilities.agent-graph-base {
27
27
  @scope (.agent-graph, .ag-root, .ag-latest, [data-agent-metrics]) {
28
28
  *, :scope:is(*), ::after, :scope:is(::after), ::before, :scope:is(::before), ::backdrop, :scope:is(::backdrop), ::file-selector-button, :scope:is(::file-selector-button) {
29
29
  box-sizing: border-box;
@@ -173,7 +173,7 @@
173
173
  }
174
174
  }
175
175
  }
176
- @layer utilities {
176
+ @layer utilities.agent-graph-utilities {
177
177
  @scope (.agent-graph, .ag-root, .ag-latest, [data-agent-metrics]) {
178
178
  .pointer-events-auto, :scope:is(.pointer-events-auto) {
179
179
  pointer-events: auto;
@@ -210,6 +210,9 @@
210
210
  .right-3, :scope:is(.right-3) {
211
211
  right: calc(var(--spacing) * 3);
212
212
  }
213
+ .left-3, :scope:is(.left-3) {
214
+ left: calc(var(--spacing) * 3);
215
+ }
213
216
  .container, :scope:is(.container) {
214
217
  width: 100%;
215
218
  @media (width >= 40rem) {
@@ -279,9 +282,6 @@
279
282
  .min-h-0, :scope:is(.min-h-0) {
280
283
  min-height: 0px;
281
284
  }
282
- .min-h-4, :scope:is(.min-h-4) {
283
- min-height: calc(var(--spacing) * 4);
284
- }
285
285
  .min-h-10, :scope:is(.min-h-10) {
286
286
  min-height: calc(var(--spacing) * 10);
287
287
  }
@@ -336,6 +336,9 @@
336
336
  .justify-center, :scope:is(.justify-center) {
337
337
  justify-content: center;
338
338
  }
339
+ .justify-start, :scope:is(.justify-start) {
340
+ justify-content: flex-start;
341
+ }
339
342
  .gap-0\.5, :scope:is(.gap-0\.5) {
340
343
  gap: calc(var(--spacing) * 0.5);
341
344
  }
@@ -466,6 +469,9 @@
466
469
  .text-\[10px\], :scope:is(.text-\[10px\]) {
467
470
  font-size: 10px;
468
471
  }
472
+ .text-\[13px\], :scope:is(.text-\[13px\]) {
473
+ font-size: 13px;
474
+ }
469
475
  .leading-4, :scope:is(.leading-4) {
470
476
  --tw-leading: calc(var(--spacing) * 4);
471
477
  line-height: calc(var(--spacing) * 4);
@@ -1568,6 +1574,26 @@ svg.react-flow__connectionline {
1568
1574
  min-width: 44px;
1569
1575
  }
1570
1576
  }
1577
+ .ag-topology {
1578
+ --ag-surface: hsl(var(--hsl-raised));
1579
+ --font-size-xs: 13px;
1580
+ --text-xs: 13px;
1581
+ }
1582
+ .ag-topology :is(.text-xs, .ag-parent-label) {
1583
+ font-size: 13px;
1584
+ }
1585
+ .ag-topology .ag-team-frame {
1586
+ background: hsl(var(--hsl-card)) !important;
1587
+ }
1588
+ .ag-topology .ag-agent-card {
1589
+ background: var(--ag-surface);
1590
+ }
1591
+ .ag-topology .ag-agent-card[data-selected] {
1592
+ background: hsl(var(--hsl-raised-2));
1593
+ }
1594
+ .ag-topology .react-flow__node-agentFrame .react-flow__handle-left {
1595
+ top: 40px;
1596
+ }
1571
1597
  @property --tw-rotate-x {
1572
1598
  syntax: "*";
1573
1599
  inherits: false;
@@ -538,4 +538,65 @@ export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCo
538
538
  /** Generic tool identity by name: a terminal, a file, a pencil, a lens, a globe or a branch, never a vendor mark. */
539
539
  export declare function toolGlyph(name: string | undefined): import("react").JSX.Element | undefined;
540
540
  //#endregion
541
- export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, FlowWaterfallRow, ViewWindow as FlowWaterfallWindow, GapBand, GapProps, 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 };
541
+ //#region src/react/version-graph.d.ts
542
+ /** `muted` chips sit beside the title; any other tone gets the second line. */
543
+ /** A status tone from `statusOf`, or a non-status chip: `accent` for a series, `info` and `muted` for plain facts. */
544
+ type VersionGraphTone = 'pass' | 'live' | 'attention' | 'fail' | 'neutral' | 'accent' | 'info' | 'muted';
545
+ interface VersionGraphChip {
546
+ readonly label: string;
547
+ readonly tone?: VersionGraphTone;
548
+ readonly title?: string;
549
+ }
550
+ interface VersionGraphNode {
551
+ readonly id: string;
552
+ /** Parents in the model; one that is not in it (or is hidden) is skipped, and a hidden one passes its own on. */
553
+ readonly parents: readonly string[];
554
+ readonly lane: string;
555
+ readonly at: string | null;
556
+ readonly title: string;
557
+ readonly detail?: string | null;
558
+ /** A style hook and a filter key: `write`, `integrate`, `profile`, `version`, `authored`, `ghost`. */
559
+ readonly kind: string;
560
+ readonly chips?: readonly VersionGraphChip[];
561
+ }
562
+ interface VersionGraphLane {
563
+ readonly id: string;
564
+ readonly label: string;
565
+ readonly detail?: string | null;
566
+ }
567
+ /** Nodes newest first, every child above its parents. */
568
+ interface VersionGraphModel {
569
+ readonly lanes: readonly VersionGraphLane[];
570
+ readonly nodes: readonly VersionGraphNode[];
571
+ }
572
+ /**
573
+ * Rows in model order, one column per lane that holds a shown node, and one edge from each shown node to each nearest
574
+ * shown ancestor: a hidden node (an integration when integrations are hidden, a commit of a lane filtered out) passes
575
+ * its parents on, so a lane's line stays connected.
576
+ */
577
+ export declare function layoutVersionGraph(model: VersionGraphModel, shown?: (node: VersionGraphNode) => boolean): {
578
+ rows: {
579
+ node: VersionGraphNode;
580
+ row: number;
581
+ column: number;
582
+ }[];
583
+ edges: {
584
+ from: string;
585
+ to: string;
586
+ fromRow: number;
587
+ fromColumn: number;
588
+ toRow: number;
589
+ toColumn: number;
590
+ }[];
591
+ lanes: VersionGraphLane[];
592
+ };
593
+ export declare function VersionGraph({ model, selected, onSelect, label, shown, maxHeight }: {
594
+ model: VersionGraphModel;
595
+ selected: string | null;
596
+ onSelect: (id: string) => void;
597
+ label: string;
598
+ shown?: (node: VersionGraphNode) => boolean;
599
+ maxHeight?: number;
600
+ }): import("react").JSX.Element;
601
+ //#endregion
602
+ export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, FlowWaterfallRow, ViewWindow as FlowWaterfallWindow, GapBand, GapProps, 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, VersionGraphChip, VersionGraphLane, VersionGraphModel, VersionGraphNode, VersionGraphTone, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow, XCategory, YScale, YTarget, YTick };
@@ -3354,7 +3354,7 @@ function useRowMotion(rows, element) {
3354
3354
  const previous = known.current;
3355
3355
  let order = 0;
3356
3356
  for (const row of rows) {
3357
- const isRunning = spanRunning(row.span);
3357
+ const isRunning = !!row.span && spanRunning(row.span);
3358
3358
  if (!previous || !previous.has(row.id)) active.current.set(row.id, {
3359
3359
  enter: Math.min(order++ * stagger, cap),
3360
3360
  until: now + hold + cap
@@ -3365,7 +3365,7 @@ function useRowMotion(rows, element) {
3365
3365
  });
3366
3366
  }
3367
3367
  known.current = new Set(rows.map((row) => row.id));
3368
- running.current = new Set(rows.filter((row) => spanRunning(row.span)).map((row) => row.id));
3368
+ running.current = new Set(rows.filter((row) => row.span && spanRunning(row.span)).map((row) => row.id));
3369
3369
  return new Map(active.current);
3370
3370
  }, [
3371
3371
  rows,
@@ -5062,4 +5062,239 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
5062
5062
  });
5063
5063
  }
5064
5064
  //#endregion
5065
- export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, DOT_MAP_CATEGORIES, DayBars, DotMap, Events, FigureView, FlowWaterfall, Gantt, Gap, HBars, Histogram, LIVE_SERIES, Lines, Meter, Spark, Sparkline, StackedBarChart, TimelineView, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, criticalPathIds, flowWaterfallRows, formatCompact, formatSparklineValue, formatWaterfallDuration, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, toolGlyph, waterfallSpanGeometry, waterfallTicks };
5065
+ //#region src/react/version-graph.tsx
5066
+ /** Recorded version lanes; callers supply their domain model, filters and selection. */
5067
+ /**
5068
+ * Rows in model order, one column per lane that holds a shown node, and one edge from each shown node to each nearest
5069
+ * shown ancestor: a hidden node (an integration when integrations are hidden, a commit of a lane filtered out) passes
5070
+ * its parents on, so a lane's line stays connected.
5071
+ */
5072
+ function layoutVersionGraph(model, shown = () => true) {
5073
+ const byId = new Map(model.nodes.map((node) => [node.id, node]));
5074
+ const visible = model.nodes.filter(shown);
5075
+ const visibleIds = new Set(visible.map((node) => node.id));
5076
+ const lanes = model.lanes.filter((lane) => visible.some((node) => node.lane === lane.id));
5077
+ for (const node of visible) if (!lanes.some((lane) => lane.id === node.lane)) lanes.push({
5078
+ id: node.lane,
5079
+ label: node.lane
5080
+ });
5081
+ const column = new Map(lanes.map((lane, i) => [lane.id, i]));
5082
+ const rowOf = new Map(visible.map((node, i) => [node.id, i]));
5083
+ const nearest = (id, seen) => {
5084
+ if (seen.has(id)) return [];
5085
+ seen.add(id);
5086
+ if (visibleIds.has(id)) return [id];
5087
+ const node = byId.get(id);
5088
+ return node ? node.parents.flatMap((parent) => nearest(parent, seen)) : [];
5089
+ };
5090
+ const edges = [];
5091
+ for (const node of visible) {
5092
+ const seen = /* @__PURE__ */ new Set([node.id]);
5093
+ const targets = [...new Set(node.parents.flatMap((parent) => nearest(parent, seen)))];
5094
+ for (const target of targets) {
5095
+ const toRow = rowOf.get(target);
5096
+ if (toRow <= rowOf.get(node.id)) continue;
5097
+ edges.push({
5098
+ from: node.id,
5099
+ to: target,
5100
+ fromRow: rowOf.get(node.id),
5101
+ fromColumn: column.get(node.lane),
5102
+ toRow,
5103
+ toColumn: column.get(byId.get(target).lane)
5104
+ });
5105
+ }
5106
+ }
5107
+ return {
5108
+ rows: visible.map((node, i) => ({
5109
+ node,
5110
+ row: i,
5111
+ column: column.get(node.lane)
5112
+ })),
5113
+ edges,
5114
+ lanes
5115
+ };
5116
+ }
5117
+ const WIDE = {
5118
+ column: 16,
5119
+ gutter: 14
5120
+ };
5121
+ const NARROW = {
5122
+ column: 5,
5123
+ gutter: 4
5124
+ };
5125
+ const LINE = 30;
5126
+ const TALL = 54;
5127
+ const BEND = 22;
5128
+ const COLORS = 8;
5129
+ const tall = (node) => (node.chips ?? []).some((chip) => chip.tone && chip.tone !== "muted");
5130
+ function VersionGraph({ model, selected, onSelect, label, shown, maxHeight = 600 }) {
5131
+ const laid = useMemo(() => layoutVersionGraph(model, shown), [model, shown]);
5132
+ const tops = useMemo(() => {
5133
+ const out = [];
5134
+ let y = 0;
5135
+ for (const row of laid.rows) {
5136
+ out.push(y);
5137
+ y += tall(row.node) ? TALL : LINE;
5138
+ }
5139
+ out.push(y);
5140
+ return out;
5141
+ }, [laid]);
5142
+ const height = tops.at(-1) ?? 0;
5143
+ const [measure, measuredWidth] = useWidth();
5144
+ const { column: COLUMN, gutter: GUTTER } = measuredWidth < 700 ? NARROW : WIDE;
5145
+ const width = GUTTER + laid.lanes.length * COLUMN;
5146
+ const x = (column) => GUTTER / 2 + column * COLUMN + COLUMN / 2;
5147
+ const y = (row) => tops[row] + LINE / 2;
5148
+ const path = (fromRow, fromColumn, toRow, toColumn) => {
5149
+ const [x1, y1, x2, y2] = [
5150
+ x(fromColumn),
5151
+ y(fromRow),
5152
+ x(toColumn),
5153
+ y(toRow)
5154
+ ];
5155
+ if (x1 === x2) return `M${x1},${y1} V${y2}`;
5156
+ const bend = Math.min(BEND, y2 - y1);
5157
+ return `M${x1},${y1} C${x1},${y1 + bend * .7} ${x2},${y1 + bend * .3} ${x2},${y1 + bend} V${y2}`;
5158
+ };
5159
+ const pane = useRef(null);
5160
+ const focusTarget = useRef(null);
5161
+ const arrived = useRef(/* @__PURE__ */ new Set());
5162
+ const motion = useRowMotion(model.nodes, pane.current);
5163
+ const [scrollTop, setScrollTop] = useState(0);
5164
+ const first = Math.max(0, tops.findIndex((top) => top >= scrollTop) - 7);
5165
+ const after = tops.findIndex((top) => top > scrollTop + maxHeight);
5166
+ const end = after < 0 ? laid.rows.length : Math.min(laid.rows.length, after + 7);
5167
+ const visible = laid.rows.slice(first, end);
5168
+ const focusRow = (index) => {
5169
+ const at = Math.max(0, Math.min(laid.rows.length - 1, index)), top = tops[at] ?? 0;
5170
+ focusTarget.current = laid.rows[at]?.node.id ?? null;
5171
+ if (pane.current) pane.current.scrollTop = top;
5172
+ setScrollTop(top);
5173
+ const target = pane.current?.querySelector(`[data-row="${at}"]`);
5174
+ if (target) {
5175
+ focusTarget.current = null;
5176
+ target.focus();
5177
+ }
5178
+ };
5179
+ useEffect(() => {
5180
+ const at = laid.rows.findIndex((row) => row.node.id === selected), element = pane.current;
5181
+ if (at < 0 || !element) return;
5182
+ const top = tops[at], bottom = tops[at + 1];
5183
+ if (top < element.scrollTop) element.scrollTop = Math.max(0, top - 8);
5184
+ else if (bottom > element.scrollTop + element.clientHeight) element.scrollTop = bottom - element.clientHeight + 8;
5185
+ setScrollTop(element.scrollTop);
5186
+ }, [selected]);
5187
+ const allLanes = useMemo(() => layoutVersionGraph(model).lanes, [model]);
5188
+ const colorOf = new Map(allLanes.map((item, i) => [item.id, i % COLORS]));
5189
+ return /* @__PURE__ */ jsx("div", {
5190
+ ref: measure,
5191
+ className: "tgc-version-graph",
5192
+ "data-version-graph": label,
5193
+ children: laid.rows.length === 0 ? /* @__PURE__ */ jsx("p", {
5194
+ className: "ov-empty",
5195
+ children: "Nothing to show with these filters."
5196
+ }) : /* @__PURE__ */ jsx("div", {
5197
+ ref: pane,
5198
+ className: "vg-scroll",
5199
+ style: {
5200
+ maxHeight,
5201
+ overflow: "auto"
5202
+ },
5203
+ onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
5204
+ onKeyDown: (event) => {
5205
+ const at = Number(event.target.closest("[data-row]")?.getAttribute("data-row"));
5206
+ const next = event.key === "Home" ? 0 : event.key === "End" ? laid.rows.length - 1 : event.key === "ArrowDown" ? at + 1 : event.key === "ArrowUp" ? at - 1 : null;
5207
+ if (next !== null) {
5208
+ event.preventDefault();
5209
+ focusRow(next);
5210
+ }
5211
+ },
5212
+ children: /* @__PURE__ */ jsxs("div", {
5213
+ className: "vg-host",
5214
+ style: { height },
5215
+ children: [/* @__PURE__ */ jsxs("svg", {
5216
+ className: "vg-lines",
5217
+ width,
5218
+ height,
5219
+ "aria-hidden": "true",
5220
+ children: [laid.edges.filter((edge) => y(edge.fromRow) <= scrollTop + maxHeight + TALL && y(edge.toRow) >= scrollTop - TALL).map((edge) => /* @__PURE__ */ jsx("path", {
5221
+ "data-from": edge.from,
5222
+ "data-to": edge.to,
5223
+ className: `vg-c${colorOf.get(laid.lanes[edge.toColumn].id)} ${selected === edge.from || selected === edge.to ? "hot" : ""}`,
5224
+ d: path(edge.fromRow, edge.fromColumn, edge.toRow, edge.toColumn)
5225
+ }, `${edge.from}>${edge.to}`)), visible.map((row) => /* @__PURE__ */ jsx("circle", {
5226
+ className: `vg-dot vg-c${colorOf.get(row.node.lane)} kind-${row.node.kind} ${selected === row.node.id ? "on" : ""}`,
5227
+ cx: x(row.column),
5228
+ cy: y(row.row),
5229
+ r: row.node.kind === "integrate" ? 3 : 5
5230
+ }, row.node.id))]
5231
+ }), /* @__PURE__ */ jsx("ol", {
5232
+ className: "vg-rows",
5233
+ "aria-label": label,
5234
+ style: {
5235
+ paddingLeft: width,
5236
+ paddingTop: tops[first]
5237
+ },
5238
+ children: visible.map((row) => {
5239
+ const node = row.node;
5240
+ const on = selected === node.id;
5241
+ const arriving = motion.get(node.id)?.enter !== void 0 && !arrived.current.has(node.id);
5242
+ const loud = (node.chips ?? []).filter((chip) => chip.tone && chip.tone !== "muted");
5243
+ const quiet = (node.chips ?? []).filter((chip) => !chip.tone || chip.tone === "muted");
5244
+ return /* @__PURE__ */ jsx("li", {
5245
+ "aria-posinset": row.row + 1,
5246
+ "aria-setsize": laid.rows.length,
5247
+ style: { height: tops[row.row + 1] - tops[row.row] },
5248
+ children: /* @__PURE__ */ jsxs("button", {
5249
+ ref: (element) => {
5250
+ if (!element) return;
5251
+ if (arriving) arrived.current.add(node.id);
5252
+ if (focusTarget.current === node.id) {
5253
+ focusTarget.current = null;
5254
+ element.focus();
5255
+ }
5256
+ },
5257
+ type: "button",
5258
+ className: `vg-row kind-${node.kind} ${on ? "on" : ""}`,
5259
+ "aria-pressed": on,
5260
+ disabled: node.kind === "ghost",
5261
+ "data-node": node.id,
5262
+ "data-row": row.row,
5263
+ "data-arriving": arriving || void 0,
5264
+ onClick: () => onSelect(node.id),
5265
+ children: [/* @__PURE__ */ jsxs("span", {
5266
+ className: "vg-line",
5267
+ children: [
5268
+ /* @__PURE__ */ jsx("b", {
5269
+ className: "vg-title",
5270
+ title: node.title,
5271
+ children: node.title
5272
+ }),
5273
+ node.detail && /* @__PURE__ */ jsx("span", {
5274
+ className: "vg-detail",
5275
+ children: node.detail
5276
+ }),
5277
+ quiet.map((chip, i) => /* @__PURE__ */ jsx("span", {
5278
+ className: "vg-chip tone-muted",
5279
+ title: chip.title,
5280
+ children: chip.label
5281
+ }, i))
5282
+ ]
5283
+ }), loud.length > 0 && /* @__PURE__ */ jsx("span", {
5284
+ className: "vg-line vg-tags",
5285
+ children: loud.map((chip, i) => /* @__PURE__ */ jsx("span", {
5286
+ className: `vg-chip tone-${chip.tone}`,
5287
+ title: chip.title,
5288
+ children: chip.label
5289
+ }, i))
5290
+ })]
5291
+ })
5292
+ }, node.id);
5293
+ })
5294
+ })]
5295
+ })
5296
+ })
5297
+ });
5298
+ }
5299
+ //#endregion
5300
+ export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, DOT_MAP_CATEGORIES, DayBars, DotMap, Events, FigureView, FlowWaterfall, Gantt, Gap, HBars, Histogram, LIVE_SERIES, Lines, Meter, Spark, Sparkline, StackedBarChart, TimelineView, VersionGraph, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, criticalPathIds, flowWaterfallRows, formatCompact, formatSparklineValue, formatWaterfallDuration, layoutVersionGraph, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, toolGlyph, waterfallSpanGeometry, waterfallTicks };
@@ -129,3 +129,42 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
129
129
 
130
130
  .ov-meter-steps span { overflow-wrap: anywhere; }
131
131
  @media (max-width: 720px) { .ov-meter-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); } .ov-meter-steps li { padding: 12px; min-height: 94px; } }
132
+
133
+ /* Recorded version history shares chart/Brand roles with the live timeline. */
134
+ .tgc-version-graph { min-width: 0; font: 14px/1.4 var(--font-sans, sans-serif); color: var(--chart-live-fg, var(--text-primary));
135
+ --vg-c0: var(--chart-live-accent, var(--accent-text)); --vg-c1: light-dark(#2a7fa8, #6cc0e8); --vg-c2: light-dark(#7b4fc0, #b99af0); --vg-c3: light-dark(#1f7f88, #5fc2c9); --vg-c4: light-dark(#a8418f, #e58ccf); --vg-c5: light-dark(#5a6ad8, #8fa4ff); --vg-c6: light-dark(#45507a, #c0c4dc); --vg-c7: var(--chart-live-muted, var(--text-muted));
136
+ }
137
+ .tgc-version-graph .vg-host { position: relative; min-width: max-content; }
138
+ .tgc-version-graph .vg-lines { position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible; }
139
+ .tgc-version-graph .vg-lines path { fill: none; stroke: currentColor; stroke-width: 2; opacity: .55; }
140
+ .tgc-version-graph .vg-lines path.hot { opacity: 1; stroke-width: 2.5; }
141
+ .tgc-version-graph .vg-lines .vg-dot { fill: currentColor; stroke: var(--chart-live-surface, var(--bg-surface)); stroke-width: 2; }
142
+ .tgc-version-graph .vg-lines .vg-dot.kind-profile, .tgc-version-graph .vg-lines .vg-dot.kind-authored { fill: var(--chart-live-surface, var(--bg-surface)); stroke: currentColor; stroke-width: 2.5; }
143
+ .tgc-version-graph .vg-lines .vg-dot.kind-ghost { fill: var(--chart-live-surface, var(--bg-surface)); stroke: var(--chart-live-faint, var(--text-muted)); stroke-dasharray: 2 2; }
144
+ .tgc-version-graph .vg-lines .vg-dot.on { stroke: var(--chart-live-fg, var(--text-primary)); stroke-width: 3; }
145
+ .tgc-version-graph .vg-c0 { color: var(--vg-c0); } .tgc-version-graph .vg-c1 { color: var(--vg-c1); } .tgc-version-graph .vg-c2 { color: var(--vg-c2); } .tgc-version-graph .vg-c3 { color: var(--vg-c3); }
146
+ .tgc-version-graph .vg-c4 { color: var(--vg-c4); } .tgc-version-graph .vg-c5 { color: var(--vg-c5); } .tgc-version-graph .vg-c6 { color: var(--vg-c6); } .tgc-version-graph .vg-c7 { color: var(--vg-c7); }
147
+ .tgc-version-graph .vg-rows { list-style: none; margin: 0; padding-right: 8px; }
148
+ .tgc-version-graph .vg-rows > li { display: flex; }
149
+ .tgc-version-graph .vg-row { display: flex; flex-direction: column; justify-content: flex-start; gap: 4px; width: 100%; min-width: 0; padding: 0 8px; border: 0; border-radius: 6px; background: none; color: var(--chart-live-fg, var(--text-primary)); font: inherit; text-align: left; cursor: pointer; }
150
+ .tgc-version-graph .vg-row:hover, .tgc-version-graph .vg-row:focus-visible { background: var(--chart-live-raised, var(--bg-elevated)); }
151
+ .tgc-version-graph .vg-row:focus-visible { outline: 2px solid var(--chart-live-accent, var(--accent-text)); outline-offset: -2px; }
152
+ .tgc-version-graph .vg-row.on { background: color-mix(in srgb, var(--chart-live-accent, var(--accent-text)) 13%, var(--chart-live-surface, var(--bg-surface))); box-shadow: inset 2px 0 var(--chart-live-border, var(--border-color)); }
153
+ .tgc-version-graph .vg-row:disabled { cursor: default; opacity: .8; }
154
+ .tgc-version-graph .vg-line { display: flex; align-items: center; gap: 8px; min-width: 0; height: 30px; flex: none; }
155
+ .tgc-version-graph .vg-line.vg-tags { height: 22px; flex-wrap: nowrap; overflow: hidden; }
156
+ .tgc-version-graph .vg-title { font-weight: 600; max-width: 46ch; }
157
+ .tgc-version-graph .vg-row.kind-integrate .vg-title { font-weight: 400; color: var(--chart-live-muted, var(--text-muted)); }
158
+ .tgc-version-graph .vg-row.kind-ghost .vg-title { font-weight: 400; color: var(--chart-live-faint, var(--text-muted)); }
159
+ .tgc-version-graph .vg-detail { font-size: var(--font-size-sm, 13px); color: var(--chart-live-muted, var(--text-muted)); }
160
+ .tgc-version-graph .vg-chip { display: inline-block; flex: none; margin-left: 4px; padding: 0 8px; border-radius: 4px; font: 600 var(--font-size-sm, 13px)/1.6 var(--font-sans); white-space: nowrap; background: var(--chart-live-raised, var(--bg-elevated)); color: var(--chart-live-muted, var(--text-muted)); }
161
+ .tgc-version-graph .vg-chip.tone-neutral { background: none; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--chart-live-muted, var(--text-muted)) 55%, transparent); }
162
+ .tgc-version-graph .vg-chip.tone-accent { color: var(--chart-live-accent, var(--accent-text)); background: color-mix(in srgb, var(--chart-live-accent, var(--accent-text)) 14%, transparent); }
163
+ .tgc-version-graph .vg-chip.tone-info { color: var(--chart-live-fg, var(--text-primary)); background: var(--chart-live-raised, var(--bg-elevated)); font-weight: 400; }
164
+
165
+ .tgc-version-graph :is(.vg-title,.vg-detail) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
166
+ @media (forced-colors: active) { .tgc-version-graph .vg-chip { forced-color-adjust: none; border: 1px solid CanvasText; } }
167
+
168
+ .tgc-version-graph .vg-row[data-arriving] { transition: opacity var(--motion-arrive); }
169
+ @starting-style { .tgc-version-graph .vg-row[data-arriving] { opacity: 0; } }
170
+ @media (prefers-reduced-motion: reduce) { .tgc-version-graph .vg-row[data-arriving] { transition: none; } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/charts",
3
- "version": "2.0.16",
3
+ "version": "2.0.18",
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",