@tangle-network/charts 0.21.11 → 0.21.13
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/dist/react/flow-waterfall.css +2 -4
- package/dist/react/index.d.ts +21 -2
- package/dist/react/index.js +54 -11
- package/package.json +1 -1
|
@@ -222,6 +222,8 @@
|
|
|
222
222
|
.fw-root .fw-settle .fw-end { animation: fw-land 260ms var(--fw-ease); }
|
|
223
223
|
/* A live bar grows with the clock instead of stepping each tick. */
|
|
224
224
|
.fw-root .fw-row[data-running] .fw-bar { transition: width 120ms linear; }
|
|
225
|
+
/* The chain that gates the trace's end: an accent edge under its bars, so cutting latency there is visibly what moves the end. */
|
|
226
|
+
.fw-root .fw-row[data-critical] .fw-bar { box-shadow: inset 0 -2px 0 var(--fw-accent); }
|
|
225
227
|
/* A restart: an amber notch through the bar at that moment; the bar keeps its own state. */
|
|
226
228
|
.fw-root .fw-mark { position: absolute; z-index: 2; left: calc(var(--x0) - 1px); width: 2px; top: calc(50% + var(--fw-bar-shift) - var(--fw-bar-h) / 2 - 4px); height: calc(var(--fw-bar-h) + 8px);
|
|
227
229
|
border-radius: 2px; background: var(--fw-amber); box-shadow: 0 0 0 1px var(--fw-surface); opacity: var(--on); pointer-events: auto; }
|
|
@@ -321,10 +323,6 @@
|
|
|
321
323
|
|
|
322
324
|
/* Span card */
|
|
323
325
|
.fw-root .fw-card-anchor { display: contents; }
|
|
324
|
-
/* Agent view: the card docks under the lanes instead of floating over the plot (judges: "covers the data it describes"). */
|
|
325
|
-
/* Two classes on the root so these win over the base .fw-root .fw-card rule that follows. */
|
|
326
|
-
.fw-root.fw-root-agents .fw-card-anchor { display: block; padding: 0 24px 12px; }
|
|
327
|
-
.fw-root.fw-root-agents .fw-card, .fw-root.fw-root-agents .fw-card-above .fw-card { position: relative; left: auto !important; top: auto !important; transform: none; animation-name: fw-arrive; width: auto; max-width: 560px; }
|
|
328
326
|
.fw-root .fw-seg-alt { filter: brightness(.78); }
|
|
329
327
|
/* Agent view: the scrubber track shows how far the run has played, so at rest it reads full, not empty. */
|
|
330
328
|
.fw-root.fw-root-agents .fw-slider { background: linear-gradient(90deg, color-mix(in oklab, var(--fw-text) 32%, transparent) var(--fw-progress), var(--fw-well) var(--fw-progress)) 0 50% / 100% 4px no-repeat; }
|
package/dist/react/index.d.ts
CHANGED
|
@@ -376,6 +376,12 @@ export declare function DotMap({ positions, radii, colors, palette, extent, rect
|
|
|
376
376
|
//#region src/react/flow-waterfall-row.d.ts
|
|
377
377
|
type FlowView = 'spans' | 'agents';
|
|
378
378
|
//#endregion
|
|
379
|
+
//#region src/react/flow-waterfall-time.d.ts
|
|
380
|
+
interface ViewWindow {
|
|
381
|
+
startMs: number;
|
|
382
|
+
endMs: number;
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
379
385
|
//#region src/react/flow-waterfall.d.ts
|
|
380
386
|
interface FlowWaterfallProps {
|
|
381
387
|
trace: FlowTrace;
|
|
@@ -412,9 +418,22 @@ interface FlowWaterfallProps {
|
|
|
412
418
|
onDeselect?: () => void;
|
|
413
419
|
/** Show the stat strip (default); a page with its own stat row passes false. */
|
|
414
420
|
summary?: boolean;
|
|
421
|
+
/** Mark the steps on the chain that gates the trace's end. */
|
|
422
|
+
criticalPath?: boolean;
|
|
423
|
+
/** Controlled search text; pass with `onQueryChange` to keep it in the host's state, such as the URL. */
|
|
424
|
+
query?: string;
|
|
425
|
+
onQueryChange?: (query: string) => void;
|
|
426
|
+
/** Controlled folded span ids; pass with `onCollapsedChange`. Overrides `defaultCollapsed`. */
|
|
427
|
+
collapsed?: ReadonlySet<string>;
|
|
428
|
+
onCollapsedChange?: (collapsed: ReadonlySet<string>) => void;
|
|
429
|
+
/** Controlled time window, `null` for the whole trace; pass with `onZoomChange`. */
|
|
430
|
+
zoom?: ViewWindow | null;
|
|
431
|
+
onZoomChange?: (zoom: ViewWindow | null) => void;
|
|
432
|
+
/** Replaces the built-in search match (name, model, kind, status), such as to search the host's attributes. */
|
|
433
|
+
matchSpan?: (span: FlowSpan, query: string) => boolean;
|
|
415
434
|
}
|
|
416
435
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
417
436
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
418
|
-
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary }: FlowWaterfallProps): import("react").JSX.Element;
|
|
437
|
+
export declare function FlowWaterfall({ trace, live, startedAt, label, defaultCollapsed, defaultView, renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary, criticalPath, query: queryProp, onQueryChange, collapsed: collapsedProp, onCollapsedChange, zoom: zoomProp, onZoomChange, matchSpan }: FlowWaterfallProps): import("react").JSX.Element;
|
|
419
438
|
//#endregion
|
|
420
|
-
export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, 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 };
|
|
439
|
+
export type { BandKind, ChartBand, ChartMark, DotMapLabel, DotMapProps, DotMapRect, Figure, FigureAtWidth, FigureViewProps, FlowWaterfallProps, 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 };
|
package/dist/react/index.js
CHANGED
|
@@ -2591,13 +2591,13 @@ function segmentOf(row) {
|
|
|
2591
2591
|
};
|
|
2592
2592
|
}
|
|
2593
2593
|
/** Search keeps ancestor context and deliberately reveals matches inside collapsed branches. */
|
|
2594
|
-
function visibleFlowWaterfallRows(rows, query, collapsed) {
|
|
2594
|
+
function visibleFlowWaterfallRows(rows, query, collapsed, matchSpan) {
|
|
2595
2595
|
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
2596
2596
|
const term = query.trim().toLowerCase();
|
|
2597
2597
|
if (term) {
|
|
2598
2598
|
const matching = /* @__PURE__ */ new Set();
|
|
2599
2599
|
for (const row of rows) {
|
|
2600
|
-
if (!`${row.name} ${row.span.detail ?? ""} ${row.kind} ${row.ok ? "" : "failed"} ${row.span.status ?? ""}`.toLowerCase().includes(term)) continue;
|
|
2600
|
+
if (matchSpan ? !matchSpan(row.span, query.trim()) : !`${row.name} ${row.span.detail ?? ""} ${row.kind} ${row.ok ? "" : "failed"} ${row.span.status ?? ""}`.toLowerCase().includes(term)) continue;
|
|
2601
2601
|
let cursor = row;
|
|
2602
2602
|
while (cursor && !matching.has(cursor.id)) {
|
|
2603
2603
|
matching.add(cursor.id);
|
|
@@ -2628,6 +2628,28 @@ function slowestRow(rows, clockMs) {
|
|
|
2628
2628
|
}
|
|
2629
2629
|
return best;
|
|
2630
2630
|
}
|
|
2631
|
+
/** The chain of steps that gates the trace's end: from each root, descend into the latest-ending child until a leaf; the root whose
|
|
2632
|
+
* chain ends last wins. Cutting latency anywhere else leaves the trace's end unchanged. */
|
|
2633
|
+
function criticalPathIds(rows) {
|
|
2634
|
+
const children = /* @__PURE__ */ new Map();
|
|
2635
|
+
for (const row of rows) if (row.parentId) children.set(row.parentId, [...children.get(row.parentId) ?? [], row]);
|
|
2636
|
+
let best;
|
|
2637
|
+
for (const root of rows) {
|
|
2638
|
+
if (root.parentId) continue;
|
|
2639
|
+
const chain = [];
|
|
2640
|
+
let end = root.span.endMs;
|
|
2641
|
+
for (let cursor = root; cursor;) {
|
|
2642
|
+
chain.push(cursor.id);
|
|
2643
|
+
end = Math.max(end, cursor.span.endMs);
|
|
2644
|
+
cursor = children.get(cursor.id)?.reduce((latest, child) => !latest || child.span.endMs > latest.span.endMs ? child : latest, void 0);
|
|
2645
|
+
}
|
|
2646
|
+
if (!best || end > best.end) best = {
|
|
2647
|
+
end,
|
|
2648
|
+
chain
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
return new Set(best?.chain);
|
|
2652
|
+
}
|
|
2631
2653
|
/** The agents a reader should look at first: the longest one by the clock, and the costliest when two or more are priced. */
|
|
2632
2654
|
function agentCallouts(agents, clockMs) {
|
|
2633
2655
|
let slowest, slowestMs = -1, costliest, top = -1, priced = 0, timed = 0;
|
|
@@ -3324,7 +3346,7 @@ function endLabel(row, clockMs) {
|
|
|
3324
3346
|
}
|
|
3325
3347
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
3326
3348
|
const FlowRow = memo(function FlowRow(props) {
|
|
3327
|
-
const { row, view, top, collapsed, searching, slowest, costliest, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
3349
|
+
const { row, view, top, collapsed, searching, slowest, costliest, critical, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
|
|
3328
3350
|
const span = row.span;
|
|
3329
3351
|
const running = spanRunning(span);
|
|
3330
3352
|
const agentView = view === "agents";
|
|
@@ -3345,6 +3367,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3345
3367
|
"data-span-id": row.id,
|
|
3346
3368
|
"data-span-kind": kind,
|
|
3347
3369
|
"data-slowest": slowest || void 0,
|
|
3370
|
+
"data-critical": critical || void 0,
|
|
3348
3371
|
"data-running": running || void 0,
|
|
3349
3372
|
"aria-level": agentView ? void 0 : row.depth + 1,
|
|
3350
3373
|
"aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
|
|
@@ -3477,7 +3500,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3477
3500
|
role: "gridcell",
|
|
3478
3501
|
className: "fw-lane",
|
|
3479
3502
|
"data-waterfall-track": true,
|
|
3480
|
-
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}`,
|
|
3503
|
+
"aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}${critical ? ", critical path" : ""}`,
|
|
3481
3504
|
children: invalid ? /* @__PURE__ */ jsx("span", {
|
|
3482
3505
|
className: "fw-invalid",
|
|
3483
3506
|
children: "—"
|
|
@@ -3539,7 +3562,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
3539
3562
|
] })
|
|
3540
3563
|
})]
|
|
3541
3564
|
});
|
|
3542
|
-
}, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
3565
|
+
}, (a, b) => a.row === b.row && a.view === b.view && a.top === b.top && a.collapsed === b.collapsed && a.searching === b.searching && a.slowest === b.slowest && a.costliest === b.costliest && a.critical === b.critical && a.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.motion === b.motion && a.renderIcon === b.renderIcon && a.fillByAgent === b.fillByAgent && a.sharedDetail === b.sharedDetail && a.expanded === b.expanded && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
|
|
3543
3566
|
/** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
|
|
3544
3567
|
function LinkMark({ link, rowHeight, hot }) {
|
|
3545
3568
|
const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
|
|
@@ -3997,6 +4020,7 @@ function FlowBody(props) {
|
|
|
3997
4020
|
searching,
|
|
3998
4021
|
slowest: item.id === slowestId,
|
|
3999
4022
|
costliest: item.id === props.costliestId,
|
|
4023
|
+
critical: props.criticalIds?.has(item.id),
|
|
4000
4024
|
active: item.id === activeId,
|
|
4001
4025
|
focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
|
|
4002
4026
|
selected: item.id === selectedId,
|
|
@@ -4372,6 +4396,22 @@ const NAME_WIDTH = {
|
|
|
4372
4396
|
spans: 320,
|
|
4373
4397
|
agents: 240
|
|
4374
4398
|
};
|
|
4399
|
+
/** State the host may own: its value wins when given, and every change is reported either way. */
|
|
4400
|
+
function useHostable(value, onChange, initial) {
|
|
4401
|
+
const [own, setOwn] = useState(initial);
|
|
4402
|
+
const current = value === void 0 ? own : value;
|
|
4403
|
+
const latest = useRef(current);
|
|
4404
|
+
latest.current = current;
|
|
4405
|
+
const report = useRef(onChange);
|
|
4406
|
+
report.current = onChange;
|
|
4407
|
+
const controlled = value !== void 0;
|
|
4408
|
+
return [current, useCallback((next) => {
|
|
4409
|
+
const resolved = typeof next === "function" ? next(latest.current) : next;
|
|
4410
|
+
latest.current = resolved;
|
|
4411
|
+
if (!controlled) setOwn(resolved);
|
|
4412
|
+
report.current?.(resolved);
|
|
4413
|
+
}, [controlled])];
|
|
4414
|
+
}
|
|
4375
4415
|
/** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
|
|
4376
4416
|
function summaryCells(trace, at, durationMs) {
|
|
4377
4417
|
const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
|
|
@@ -4474,7 +4514,7 @@ function OutcomeLegend({ agents }) {
|
|
|
4474
4514
|
}
|
|
4475
4515
|
/** Shared trace inspector: the agent timeline and the span waterfall over one clock.
|
|
4476
4516
|
* Data remains producer-owned; playback, zoom and folding change presentation only. */
|
|
4477
|
-
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary = true }) {
|
|
4517
|
+
function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill: fillProp, selectedId, onDeselect, description, summary: showSummary = true, criticalPath = false, query: queryProp, onQueryChange, collapsed: collapsedProp, onCollapsedChange, zoom: zoomProp, onZoomChange, matchSpan }) {
|
|
4478
4518
|
const root = useRef(null);
|
|
4479
4519
|
const search = useRef(null);
|
|
4480
4520
|
const slider = useRef(null);
|
|
@@ -4483,12 +4523,12 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4483
4523
|
const searchId = useId();
|
|
4484
4524
|
const cardId = useId();
|
|
4485
4525
|
const [view, setView] = useState(defaultView);
|
|
4486
|
-
const [query, setQuery] =
|
|
4487
|
-
const [collapsed, setCollapsed] =
|
|
4526
|
+
const [query, setQuery] = useHostable(queryProp, onQueryChange, "");
|
|
4527
|
+
const [collapsed, setCollapsed] = useHostable(collapsedProp, onCollapsedChange, () => new Set(defaultCollapsed ? trace.spans.flatMap((span) => span.parentId ? [span.parentId] : []) : []));
|
|
4488
4528
|
const [speed, setSpeed] = useState(1);
|
|
4489
4529
|
const [playing, setPlaying] = useState(false);
|
|
4490
4530
|
const [position, setPosition] = useState(null);
|
|
4491
|
-
const [zoom, setZoom] =
|
|
4531
|
+
const [zoom, setZoom] = useHostable(zoomProp, onZoomChange, null);
|
|
4492
4532
|
const [nameWidth, setNameWidth] = useState(NAME_WIDTH[defaultView]);
|
|
4493
4533
|
const [laneWidth, setLaneWidth] = useState(0);
|
|
4494
4534
|
const [card, setCard] = useState(null);
|
|
@@ -4544,6 +4584,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4544
4584
|
resolution
|
|
4545
4585
|
]);
|
|
4546
4586
|
const byId = useMemo(() => new Map(rows.map((row) => [row.id, row])), [rows]);
|
|
4587
|
+
const critical = useMemo(() => criticalPathIds(rows), [rows]);
|
|
4547
4588
|
const agents = useMemo(() => {
|
|
4548
4589
|
const pipelines = rows.filter((row) => row.kind === "pipeline");
|
|
4549
4590
|
return pipelines.length ? pipelines : rows.filter((row) => !row.parentId);
|
|
@@ -4552,14 +4593,15 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4552
4593
|
const [flippedFamilies, setFlippedFamilies] = useState(/* @__PURE__ */ new Set());
|
|
4553
4594
|
const openFamilies = useMemo(() => defaultOpenFamilies(agents, UNFOLD_ROWS, flippedFamilies), [agents, flippedFamilies]);
|
|
4554
4595
|
const [openSteps, setOpenSteps] = useState(/* @__PURE__ */ new Set());
|
|
4555
|
-
const visible = useMemo(() => view === "agents" ? agentViewRows(agents, rows, openFamilies, openSteps) : visibleFlowWaterfallRows(rows, query, collapsed), [
|
|
4596
|
+
const visible = useMemo(() => view === "agents" ? agentViewRows(agents, rows, openFamilies, openSteps) : visibleFlowWaterfallRows(rows, query, collapsed, matchSpan), [
|
|
4556
4597
|
view,
|
|
4557
4598
|
agents,
|
|
4558
4599
|
rows,
|
|
4559
4600
|
openFamilies,
|
|
4560
4601
|
openSteps,
|
|
4561
4602
|
query,
|
|
4562
|
-
collapsed
|
|
4603
|
+
collapsed,
|
|
4604
|
+
matchSpan
|
|
4563
4605
|
]);
|
|
4564
4606
|
const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
|
|
4565
4607
|
const callouts = useMemo(() => view === "agents" ? agentCallouts(visible, clockMs) : {}, [
|
|
@@ -4940,6 +4982,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
4940
4982
|
holdHeight: isLive,
|
|
4941
4983
|
slowestId: slowest?.id ?? callouts.slowestId,
|
|
4942
4984
|
costliestId: callouts.costliestId,
|
|
4985
|
+
criticalIds: criticalPath && view === "spans" ? critical : void 0,
|
|
4943
4986
|
activeId,
|
|
4944
4987
|
selectedId: selection,
|
|
4945
4988
|
focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.13",
|
|
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",
|