@tangle-network/charts 2.0.15 → 2.0.17

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.
@@ -311,7 +311,7 @@
311
311
  .fw-root .fw-icon-button { appearance: none; display: inline-grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: var(--radius-control); background: transparent; color: var(--fw-muted); cursor: pointer; flex-shrink: 0; }
312
312
  .fw-root .fw-icon-button:hover:not(:disabled) { background: var(--fw-hover); color: var(--fw-text); }
313
313
  .fw-root .fw-card-close { width: 20px; height: 20px; margin-right: -4px; }
314
- .fw-root .fw-card-table { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 6px 16px; padding-top: 8px; border-top: 1px solid var(--fw-border); font-size: 14px; line-height: 18px; }
314
+ .fw-root .fw-card-table { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); overflow-wrap: anywhere; gap: 6px 16px; padding-top: 8px; border-top: 1px solid var(--fw-border); font-size: 14px; line-height: 18px; }
315
315
  .fw-root .fw-card-table > div { display: contents; }
316
316
  .fw-root .fw-card-table dt, .fw-root .fw-card-label { font-size: 14px; line-height: 18px; color: var(--fw-muted); }
317
317
  .fw-root .fw-card-table dd { min-width: 0; font-weight: 500; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
@@ -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.15",
3
+ "version": "2.0.17",
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",