@tangle-network/charts 2.0.6 → 2.0.11
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 +2 -2
- package/dist/{flow-trace-CF4Q07cA.d.ts → flow-trace-VOGEAdl8.d.ts} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/react/flow-waterfall.css +2 -2
- package/dist/react/index.d.ts +11 -3
- package/dist/react/index.js +292 -142
- package/dist/react/live.css +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -337,9 +337,9 @@ const trace = otlpFlowTrace(spans, { price: catalogPrice(await (await fetch(cata
|
|
|
337
337
|
<FlowWaterfall trace={trace} live={running} defaultView="agents" renderIcon={brandMark} />;
|
|
338
338
|
```
|
|
339
339
|
|
|
340
|
-
`defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. `detail` becomes the agent's subtitle. The card is a table of what the row recorded, every value with its unit: model, duration, tokens in, out and cached (the span's `promptTokens`, `completionTokens` and `cachedTokens`, else the sum of its children's), LLM latency (median and p95 of its model calls), cost, LLM calls, tool calls by tool (the name before " · "), failed steps, and retries and restarts (`retryOf`, `marks` of kind `restart`); a value nobody recorded is left out. `facts` (`[{ label, value }]`)
|
|
340
|
+
`defaultView="agents"` opens the agent timeline: one row per agent with its model, thinking and tool marks, hatched waiting while it delegates, elbow connectors to each agent it spawned and back, retry and failure glyphs, and a headline with the measured speedup over sequential (agent busy time over wall time). Expanding an agent opens the span view with that agent unfolded. The span view virtualizes rows (5,000 spans scroll at 58+ fps in headless Chromium), pins ancestors while their children scroll, and labels each bar with its duration and recorded cost. Hover shows a span card; click pins it and zooms the axis to a span too narrow to read. `onSelect(span)` hears each opened row and pinned bar, so a host can open its own drill-down; `markers={[{ at, label, tone }]}` draws labelled instants, such as releases, across the rows. Give an agent span an `outcome` (`{ tone, label }`) and its lane takes the outcome color and the header a legend. `detail` becomes the agent's subtitle. The card is a table of what the row recorded, every value with its unit: model, duration, tokens in, out and cached (the span's `promptTokens`, `completionTokens` and `cachedTokens`, else the sum of its children's), LLM latency (median and p95 of its model calls), cost, LLM calls, tool calls by tool (the name before " · "), failed steps, and retries and restarts (`retryOf`, `marks` of kind `restart`); a value nobody recorded is left out. `facts` (`[{ label, value }]`) supplies authoritative host rows: a matching label replaces the inferred metric, case-insensitively, including explicit unknown or zero values; additional labels append. The last explicit value wins for a repeated label. Agent lane summaries also use `Model calls` and `Tool calls` facts verbatim (`Model calls: 413`), including unknowns and zero; no count is parsed from a formatted fact. Model/Provider identity and Ran for/Cost end labels also honor facts verbatim; full values remain in the card and label title when phone metadata clips. Opaque duration/cost facts do not compete in numeric superlatives. Family lanes summarize agent and failure counts. Synthetic family cards show `Not recorded` for model identity, model/tool counts, model latency, failures or retries when any member supplies that authoritative fact: formatted counts and percentiles cannot be aggregated. Family tokens become unknown if a member supplies a Tokens fact without its own numeric usage. Cost rollups use full numeric span prices when a member supplies a Cost fact; partial known prices retain their lower bound, and an entirely opaque price is Not recorded. Strings and percentiles are never parsed or summed. The card opens at the pointer, above or below its row, whichever side fits the viewport. `onTimeChange` reports where the reader scrubbed (null when live), and markers double as an event track (click, N or P to jump). A partly priced trace shows its floor and coverage (`costCoverage`), and `billedUsd` shows the real charge beside the list price. Ctrl/Cmd+wheel or `+`/`-`/`0` zoom, Shift+wheel pans, the scrub track under the controls shows the whole trace, and arrow keys walk the tree.
|
|
341
341
|
|
|
342
|
-
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked
|
|
342
|
+
Live views append spans as they arrive: `traces watch <id|run-dir> --format jsonl --spans` emits a `traces.stream.span` event per new or changed span, and `otlpFlowTrace` keeps the last span per `span_id`. Running agent spans grow to the clock between deltas without React renders. `activityAt` supplies trace-relative event times and `medianEventGapMs` preserves producer cadence before tail truncation; without it, child-step activity supplies the samples. Silence beyond five minutes and three median event gaps hatches the tail and names the quiet duration. The Errors filter expands matching agents and steps; its rug locates each error, marking unreported times separately. Containers below 600px use stacked 52px rows and a Key disclosure. Search supports Cmd/Ctrl+F and keeps ancestor context. Token totals are optional: omit unknown counts, retain measured zero, and guard optional token fields when reading a trace. Missing, partial and nonfinite usage displays a dash, including during playback. Missing cost and authoritative LLM counts stay Unknown; a cost priced from a catalog is labelled list price. Invalid intervals retain a per-row `—` timing mark. The static phase waterfall remains a separate chart for disjoint phases.
|
|
343
343
|
|
|
344
344
|
`scripts/flow-waterfall-demo.mjs` serves the demo routes (reference-shaped fixtures, recorded runs, a synthetic load trace and OTLP files from `FW_DATA_DIR`); `scripts/flow-waterfall-capture.mjs` captures frames, hover cards, fps, first paint and recordings, and with `FW_LIVE_RELAY` records a live session through `scripts/flow-waterfall-live-relay.mjs` with every span's event-to-pixel time.
|
|
345
345
|
|
|
@@ -343,7 +343,7 @@ interface FlowSpan {
|
|
|
343
343
|
handoffTo?: string;
|
|
344
344
|
/** The host's verdict on an agent span, such as shipped or crashed: colors its lane and joins the legend. */
|
|
345
345
|
outcome?: FlowOutcome;
|
|
346
|
-
/**
|
|
346
|
+
/** Authoritative host rows: replace same-label inferred card metrics and lane model/count/duration/cost labels, case-insensitively; unknowns stay unknown. */
|
|
347
347
|
facts?: readonly {
|
|
348
348
|
label: string;
|
|
349
349
|
value: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-
|
|
1
|
+
import { A as BreakdownInput, B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, H as Tier, I as Interval, L as IntervalMethod, M as CostBasis, N as Estimate, O as YTarget, P as Exclusion, R as MatrixCell, S as ChartMark, T as MarkerShape, U as TierCounts, V as Setup, W as isRefusal, _ as checkerColor, a as TimelineLane, b as BandKind, c as TimelineTick, d as ENDING_COLORS, f as ENDING_ORDER, g as TickOutcome, h as RunEnding, i as FlowTrace, j as ComparisonRow, k as YTick, l as timeline, m as OUTCOME_ORDER, n as FlowOutcome, o as TimelineOptions, p as OUTCOME_COLORS, r as FlowSpan, s as TimelineSpan, t as FlowMarker, u as CHECKER_HUES, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph, z as RateRow } from "./flow-trace-VOGEAdl8.js";
|
|
2
2
|
//#region src/block.d.ts
|
|
3
3
|
interface RenderFigureOptions {
|
|
4
4
|
/** Chart uses a closed data disclosure; plot keeps only the SVG and accessible data. */
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
left: min(calc(var(--x0) + max(var(--w), 3px) + 4px), calc(100% - var(--fw-end-w, 0px) - 4px)); padding: 0 4px; border-radius: var(--radius-tag);
|
|
272
272
|
background: linear-gradient(var(--fw-tint, transparent) 0 0), var(--fw-surface); white-space: nowrap; pointer-events: none; }
|
|
273
273
|
.fw-root .fw-outcome-end { display: inline-flex; pointer-events: auto; }
|
|
274
|
-
.fw-root .fw-duration { font-size: 14px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
274
|
+
.fw-root .fw-duration { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; color: var(--fw-muted); font-variant-numeric: tabular-nums; }
|
|
275
275
|
.fw-root .fw-k-failed ~ .fw-end .fw-duration { color: var(--fw-failed); }
|
|
276
276
|
.fw-root .fw-badge { display: inline-flex; flex: none; height: 18px; padding: 0 6px; border-radius: var(--radius-pill); font-size: 12px; line-height: 18px; font-weight: 500; color: var(--fw-muted); background: transparent; box-shadow: inset 0 0 0 1px var(--fw-border); }
|
|
277
277
|
|
|
@@ -416,7 +416,7 @@
|
|
|
416
416
|
.fw-root .fw-view-agents :is(.fw-subtitle, .fw-seg-label) { display: none; }
|
|
417
417
|
.fw-root .fw-view-agents .fw-label { padding-left: calc(8px + var(--depth) * 12px); }
|
|
418
418
|
.fw-root .fw-lane { grid-column: 2; }
|
|
419
|
-
.fw-root .fw-end { top: 20px; height: 18px; left: auto; right: 4px; }
|
|
419
|
+
.fw-root .fw-end { top: 20px; height: 18px; left: auto; right: 4px; max-width: calc(100% - 144px); }
|
|
420
420
|
.fw-root .fw-time { display: block; grid-column: 2; position: absolute; top: -12px; left: var(--fw-progress); transform: translateX(calc(-1 * var(--fw-progress))); width: max-content; max-width: 100%; background: var(--fw-surface); }
|
|
421
421
|
.fw-root .fw-playback { position: relative; padding: 12px 8px 8px; }
|
|
422
422
|
.fw-root .fw-overview { margin-top: 12px; grid-template-columns: 0 minmax(0, 1fr); column-gap: 0; }
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, n as FlowOutcome, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-
|
|
1
|
+
import { B as Refusal, C as MarkKind, D as YScale, E as XCategory, F as Figure, O as YTarget, S as ChartMark, T as MarkerShape, a as TimelineLane, b as BandKind, c as TimelineTick, g as TickOutcome, i as FlowTrace, k as YTick, n as FlowOutcome, o as TimelineOptions, r as FlowSpan, s as TimelineSpan, t as FlowMarker, v as PointFields, w as MarkVerdict, x as ChartBand, y as PointGlyph } from "../flow-trace-VOGEAdl8.js";
|
|
2
2
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
3
3
|
//#region src/react/sparkline.d.ts
|
|
4
4
|
type SparklineDirection = "rising" | "falling" | "flat";
|
|
@@ -223,7 +223,11 @@ interface MeterSegment {
|
|
|
223
223
|
}
|
|
224
224
|
/** A used-of-total meter with its numbers. With `segments` it is a part-of-whole bar: the remainder of `total` that no
|
|
225
225
|
* segment accounts for is drawn hatched and labelled `untraced`, and a missing total hatches the whole track. */
|
|
226
|
-
export declare function Meter({ used, total, format, tone, text, segments, untraced }: {
|
|
226
|
+
export declare function Meter({ used, total, format, tone, text, segments, steps, untraced }: {
|
|
227
|
+
steps?: {
|
|
228
|
+
label: string;
|
|
229
|
+
state: 'pass' | 'fail' | 'undecided' | 'unrun';
|
|
230
|
+
}[];
|
|
227
231
|
used?: number | null | undefined;
|
|
228
232
|
total: number | null | undefined;
|
|
229
233
|
format?: (v: number) => string;
|
|
@@ -260,7 +264,11 @@ export declare function Events({ at, from, to, label, color, ticks, axis, time }
|
|
|
260
264
|
* state; hovering a span names it, its state and its span. Times are epoch ms; `axis` labels the time axis.
|
|
261
265
|
* `lanes` give a row several spans (the same shape as the static `timeline`); a span with `end: null` is open and runs
|
|
262
266
|
* to `now`. `marks` draw recorded events across every lane, and states in `hatch` are drawn hatched (no data). */
|
|
263
|
-
export declare function Gantt({ rows, lanes: laneInput, colors, axis, span, marks, now, hatch }: {
|
|
267
|
+
export declare function Gantt({ rows, lanes: laneInput, colors, axis, span, marks, now, hatch, selectedId, onSelect, bestMark, maxHeight }: {
|
|
268
|
+
selectedId?: string;
|
|
269
|
+
onSelect?: (id: string) => void;
|
|
270
|
+
bestMark?: string;
|
|
271
|
+
maxHeight?: number;
|
|
264
272
|
rows?: {
|
|
265
273
|
label: string;
|
|
266
274
|
start: number;
|
package/dist/react/index.js
CHANGED
|
@@ -1385,7 +1385,19 @@ const SEGMENT_TONES = [
|
|
|
1385
1385
|
];
|
|
1386
1386
|
/** A used-of-total meter with its numbers. With `segments` it is a part-of-whole bar: the remainder of `total` that no
|
|
1387
1387
|
* segment accounts for is drawn hatched and labelled `untraced`, and a missing total hatches the whole track. */
|
|
1388
|
-
function Meter({ used, total, format = formatCompact, tone, text, segments, untraced = "untraced" }) {
|
|
1388
|
+
function Meter({ used, total, format = formatCompact, tone, text, segments, steps, untraced = "untraced" }) {
|
|
1389
|
+
if (steps) {
|
|
1390
|
+
const current = steps.findIndex((step) => step.state !== "pass");
|
|
1391
|
+
return /* @__PURE__ */ jsx("ol", {
|
|
1392
|
+
className: "ov-meter-steps",
|
|
1393
|
+
"aria-label": text ?? "Check steps",
|
|
1394
|
+
children: steps.map((step, i) => /* @__PURE__ */ jsxs("li", {
|
|
1395
|
+
"data-state": step.state,
|
|
1396
|
+
"aria-current": i === current ? "step" : void 0,
|
|
1397
|
+
children: [/* @__PURE__ */ jsx("strong", { children: i + 1 }), /* @__PURE__ */ jsx("span", { children: step.label })]
|
|
1398
|
+
}, i))
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1389
1401
|
if (segments) return /* @__PURE__ */ jsx(SegmentMeter, {
|
|
1390
1402
|
segments,
|
|
1391
1403
|
total,
|
|
@@ -1586,9 +1598,11 @@ function Events({ at, from, to, label, color = LIVE_SERIES[0], ticks = 7, axis =
|
|
|
1586
1598
|
* state; hovering a span names it, its state and its span. Times are epoch ms; `axis` labels the time axis.
|
|
1587
1599
|
* `lanes` give a row several spans (the same shape as the static `timeline`); a span with `end: null` is open and runs
|
|
1588
1600
|
* to `now`. `marks` draw recorded events across every lane, and states in `hatch` are drawn hatched (no data). */
|
|
1589
|
-
function Gantt({ rows, lanes: laneInput, colors, axis, span, marks = [], now, hatch = ["missing", "untraced"] }) {
|
|
1601
|
+
function Gantt({ rows, lanes: laneInput, colors, axis, span, marks = [], now, hatch = ["missing", "untraced"], selectedId, onSelect, bestMark, maxHeight }) {
|
|
1590
1602
|
const [ref, width, em] = useWidth();
|
|
1591
1603
|
const [tip, setTip] = useState(null);
|
|
1604
|
+
const scroller = useRef(null);
|
|
1605
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
1592
1606
|
const lanes = laneInput ?? (rows ?? []).map((row, i) => ({
|
|
1593
1607
|
id: `${row.label}-${i}`,
|
|
1594
1608
|
label: row.label,
|
|
@@ -1612,9 +1626,21 @@ function Gantt({ rows, lanes: laneInput, colors, axis, span, marks = [], now, ha
|
|
|
1612
1626
|
children: "No agent has started."
|
|
1613
1627
|
});
|
|
1614
1628
|
const ends = (s) => s.end ?? now;
|
|
1615
|
-
const
|
|
1616
|
-
const
|
|
1617
|
-
const
|
|
1629
|
+
const phone = width < 600;
|
|
1630
|
+
const rowH = Math.round((phone ? 2.8 : 2.2) * em);
|
|
1631
|
+
const labelW = phone ? 8 : Math.min(Math.round(16 * em), Math.round(width * .28));
|
|
1632
|
+
const top = maxHeight ? 4 : marks.length ? 48 : 8;
|
|
1633
|
+
const fractions = phone ? [
|
|
1634
|
+
0,
|
|
1635
|
+
.5,
|
|
1636
|
+
1
|
|
1637
|
+
] : [
|
|
1638
|
+
0,
|
|
1639
|
+
.25,
|
|
1640
|
+
.5,
|
|
1641
|
+
.75,
|
|
1642
|
+
1
|
|
1643
|
+
];
|
|
1618
1644
|
const bottom = Math.round(1.8 * em);
|
|
1619
1645
|
const height = top + lanes.length * rowH + bottom;
|
|
1620
1646
|
const times = [
|
|
@@ -1629,131 +1655,233 @@ function Gantt({ rows, lanes: laneInput, colors, axis, span, marks = [], now, ha
|
|
|
1629
1655
|
const fallback = colors.unknown ?? "var(--chart-live-faint, var(--chart-muted))";
|
|
1630
1656
|
const hatched = new Set(hatch);
|
|
1631
1657
|
const plotBottom = top + lanes.length * rowH;
|
|
1658
|
+
const first = maxHeight && lanes.length > 100 ? Math.max(0, Math.floor((scrollTop - top) / rowH) - 6) : 0;
|
|
1659
|
+
const visible = maxHeight && lanes.length > 100 ? lanes.slice(first, first + Math.ceil(maxHeight / rowH) + 14) : lanes;
|
|
1660
|
+
const select = (id) => onSelect?.(id);
|
|
1661
|
+
const focusLane = (index) => {
|
|
1662
|
+
const at = Math.max(0, Math.min(lanes.length - 1, index));
|
|
1663
|
+
const position = at === 0 ? 0 : top + at * rowH - rowH;
|
|
1664
|
+
if (scroller.current) scroller.current.scrollTop = position;
|
|
1665
|
+
setScrollTop(Math.max(0, position));
|
|
1666
|
+
requestAnimationFrame(() => scroller.current?.querySelector(`[data-lane-index="${at}"]`)?.focus());
|
|
1667
|
+
};
|
|
1668
|
+
const tipY = (y) => Math.max(0, y - (maxHeight ? scrollTop - 52 : 0) - em);
|
|
1669
|
+
const tipX = (x) => Math.max(0, Math.min(x + 8, width - Math.min(240, width)));
|
|
1632
1670
|
return /* @__PURE__ */ jsxs("div", {
|
|
1633
1671
|
ref,
|
|
1634
|
-
className: "ov-plot",
|
|
1672
|
+
className: "ov-plot ov-gantt",
|
|
1673
|
+
"data-featured": bestMark ? true : void 0,
|
|
1635
1674
|
onMouseLeave: () => setTip(null),
|
|
1636
1675
|
children: [
|
|
1637
|
-
width > 0 && /* @__PURE__ */ jsxs("svg", {
|
|
1676
|
+
maxHeight && width > 0 && /* @__PURE__ */ jsxs("svg", {
|
|
1638
1677
|
width,
|
|
1639
|
-
height,
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
[
|
|
1644
|
-
|
|
1645
|
-
.
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1678
|
+
height: 52,
|
|
1679
|
+
"aria-label": "Releases and time axis",
|
|
1680
|
+
children: [marks.map((mark, i) => /* @__PURE__ */ jsxs("g", {
|
|
1681
|
+
"data-best": mark.label === bestMark || void 0,
|
|
1682
|
+
children: [/* @__PURE__ */ jsx(Mark, {
|
|
1683
|
+
mark,
|
|
1684
|
+
x: x(mark.at),
|
|
1685
|
+
top: 10,
|
|
1686
|
+
bottom: 28,
|
|
1687
|
+
row: 0,
|
|
1688
|
+
em,
|
|
1689
|
+
flip: x(mark.at) > width * .62
|
|
1690
|
+
}), mark.label === bestMark && /* @__PURE__ */ jsx("circle", {
|
|
1691
|
+
cx: x(mark.at),
|
|
1692
|
+
cy: 14,
|
|
1693
|
+
r: 8,
|
|
1694
|
+
fill: "none",
|
|
1695
|
+
stroke: "var(--accent-text)",
|
|
1696
|
+
strokeWidth: 2
|
|
1697
|
+
})]
|
|
1698
|
+
}, i)), fractions.map((f) => /* @__PURE__ */ jsx("text", {
|
|
1699
|
+
x: labelW + f * plotW,
|
|
1700
|
+
y: 46,
|
|
1701
|
+
className: "ov-axis",
|
|
1702
|
+
textAnchor: f === 0 ? "start" : f === 1 ? "end" : "middle",
|
|
1703
|
+
children: axis(t0 + f * (t1 - t0))
|
|
1704
|
+
}, f))]
|
|
1705
|
+
}),
|
|
1706
|
+
/* @__PURE__ */ jsx("div", {
|
|
1707
|
+
ref: scroller,
|
|
1708
|
+
className: "ov-gantt-scroll",
|
|
1709
|
+
style: {
|
|
1710
|
+
maxHeight,
|
|
1711
|
+
overflow: maxHeight ? "auto" : void 0
|
|
1712
|
+
},
|
|
1713
|
+
onScroll: (event) => {
|
|
1714
|
+
setScrollTop(event.currentTarget.scrollTop);
|
|
1715
|
+
setTip(null);
|
|
1716
|
+
},
|
|
1717
|
+
children: width > 0 && /* @__PURE__ */ jsxs("svg", {
|
|
1718
|
+
width,
|
|
1719
|
+
height,
|
|
1720
|
+
role: onSelect ? "group" : "img",
|
|
1721
|
+
"aria-label": `${lanes.length} ${laneInput ? "lanes" : "agents"} over time`,
|
|
1722
|
+
children: [
|
|
1723
|
+
fractions.map((f) => /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsx("line", {
|
|
1724
|
+
x1: labelW + f * plotW,
|
|
1725
|
+
x2: labelW + f * plotW,
|
|
1726
|
+
y1: top,
|
|
1727
|
+
y2: plotBottom,
|
|
1728
|
+
className: "ov-gridline"
|
|
1729
|
+
}), !maxHeight && /* @__PURE__ */ jsx("text", {
|
|
1730
|
+
x: labelW + f * plotW,
|
|
1731
|
+
y: height - em * .35,
|
|
1732
|
+
className: "ov-axis",
|
|
1733
|
+
textAnchor: f === 0 ? "start" : f === 1 ? "end" : "middle",
|
|
1734
|
+
children: axis(t0 + f * (t1 - t0))
|
|
1735
|
+
})] }, f)),
|
|
1736
|
+
visible.map((lane, offset) => {
|
|
1737
|
+
const i = first + offset, y = top + i * rowH;
|
|
1738
|
+
return /* @__PURE__ */ jsxs("g", {
|
|
1739
|
+
"data-lane": lane.id,
|
|
1740
|
+
"data-lane-index": i,
|
|
1741
|
+
role: onSelect ? "button" : void 0,
|
|
1742
|
+
tabIndex: onSelect ? 0 : void 0,
|
|
1743
|
+
"aria-label": lane.label,
|
|
1744
|
+
"aria-pressed": onSelect ? selectedId === lane.id : void 0,
|
|
1745
|
+
onClick: () => select(lane.id),
|
|
1746
|
+
onKeyDown: (event) => {
|
|
1747
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1748
|
+
event.preventDefault();
|
|
1749
|
+
select(lane.id);
|
|
1750
|
+
}
|
|
1751
|
+
const next = {
|
|
1752
|
+
ArrowDown: i + 1,
|
|
1753
|
+
ArrowUp: i - 1,
|
|
1754
|
+
Home: 0,
|
|
1755
|
+
End: lanes.length - 1
|
|
1756
|
+
}[event.key];
|
|
1757
|
+
if (next !== void 0) {
|
|
1758
|
+
event.preventDefault();
|
|
1759
|
+
focusLane(next);
|
|
1760
|
+
}
|
|
1761
|
+
},
|
|
1762
|
+
children: [
|
|
1763
|
+
selectedId === lane.id && /* @__PURE__ */ jsx("rect", {
|
|
1764
|
+
x: 0,
|
|
1765
|
+
y,
|
|
1766
|
+
width,
|
|
1767
|
+
height: rowH,
|
|
1768
|
+
fill: "var(--ix-sel-bg)",
|
|
1769
|
+
rx: 4
|
|
1770
|
+
}),
|
|
1771
|
+
/* @__PURE__ */ jsx("rect", {
|
|
1772
|
+
x: 0,
|
|
1773
|
+
y,
|
|
1774
|
+
width,
|
|
1775
|
+
height: rowH,
|
|
1776
|
+
fill: "transparent",
|
|
1703
1777
|
onMouseEnter: () => setTip({
|
|
1704
|
-
x:
|
|
1705
|
-
y:
|
|
1778
|
+
x: tipX(x(ends(lane.spans.at(-1) ?? { end: t1 }))),
|
|
1779
|
+
y: tipY(y),
|
|
1706
1780
|
title: lane.label,
|
|
1707
|
-
lines: [{
|
|
1708
|
-
label:
|
|
1781
|
+
lines: lane.spans.flatMap((s) => [{
|
|
1782
|
+
label: s.end === null ? `${s.state} (open)` : s.state,
|
|
1709
1783
|
value: span(s.start, ends(s)),
|
|
1710
|
-
color: hatched.has(s.state) ? void 0 :
|
|
1784
|
+
color: hatched.has(s.state) ? void 0 : colors[s.state] ?? fallback
|
|
1711
1785
|
}, ...s.note ? [{
|
|
1712
1786
|
label: s.note,
|
|
1713
1787
|
value: ""
|
|
1714
|
-
}] : []]
|
|
1715
|
-
}),
|
|
1716
|
-
children: hatched.has(s.state) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1717
|
-
x: bx,
|
|
1718
|
-
y: by,
|
|
1719
|
-
width: bw,
|
|
1720
|
-
height: bh,
|
|
1721
|
-
className: "ov-hatch-bg"
|
|
1722
|
-
}), /* @__PURE__ */ jsx("path", {
|
|
1723
|
-
d: hatchPath(bx, by, bw, bh, 4),
|
|
1724
|
-
className: "ov-hatch"
|
|
1725
|
-
})] }) : /* @__PURE__ */ jsx("rect", {
|
|
1726
|
-
x: bx,
|
|
1727
|
-
y: by,
|
|
1728
|
-
width: bw,
|
|
1729
|
-
height: bh,
|
|
1730
|
-
rx: 2,
|
|
1731
|
-
fill: color,
|
|
1732
|
-
className: open ? "ov-gantt-open" : void 0
|
|
1788
|
+
}] : []])
|
|
1733
1789
|
})
|
|
1734
|
-
},
|
|
1790
|
+
}),
|
|
1791
|
+
/* @__PURE__ */ jsxs("text", {
|
|
1792
|
+
x: phone ? 8 : labelW - em * .5,
|
|
1793
|
+
y: phone ? y + 13 : y + rowH / 2,
|
|
1794
|
+
dominantBaseline: "middle",
|
|
1795
|
+
textAnchor: phone ? "start" : "end",
|
|
1796
|
+
className: "ov-axis ov-gantt-label",
|
|
1797
|
+
children: [/* @__PURE__ */ jsx("title", { children: lane.label }), lane.label.length > 26 ? `${lane.label.slice(0, 25)}…` : lane.label]
|
|
1798
|
+
}),
|
|
1799
|
+
lane.spans.map((s, j) => {
|
|
1800
|
+
const color = colors[s.state] ?? fallback;
|
|
1801
|
+
const bx = x(s.start), bw = Math.max(2, x(ends(s)) - bx), by = y + (phone ? 24 : rowH * .25), bh = rowH * .5;
|
|
1802
|
+
const open = s.end === null;
|
|
1803
|
+
return /* @__PURE__ */ jsx("g", {
|
|
1804
|
+
"data-state": s.state,
|
|
1805
|
+
"data-open": open ? "true" : void 0,
|
|
1806
|
+
"data-hatched": hatched.has(s.state) ? "true" : void 0,
|
|
1807
|
+
onMouseEnter: () => setTip({
|
|
1808
|
+
x: tipX(bx + bw),
|
|
1809
|
+
y: tipY(y),
|
|
1810
|
+
title: lane.label,
|
|
1811
|
+
lines: [{
|
|
1812
|
+
label: open ? `${s.state} (open)` : s.state,
|
|
1813
|
+
value: span(s.start, ends(s)),
|
|
1814
|
+
color: hatched.has(s.state) ? void 0 : color
|
|
1815
|
+
}, ...s.note ? [{
|
|
1816
|
+
label: s.note,
|
|
1817
|
+
value: ""
|
|
1818
|
+
}] : []]
|
|
1819
|
+
}),
|
|
1820
|
+
children: hatched.has(s.state) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("rect", {
|
|
1821
|
+
x: bx,
|
|
1822
|
+
y: by,
|
|
1823
|
+
width: bw,
|
|
1824
|
+
height: bh,
|
|
1825
|
+
className: "ov-hatch-bg"
|
|
1826
|
+
}), /* @__PURE__ */ jsx("path", {
|
|
1827
|
+
d: hatchPath(bx, by, bw, bh, 4),
|
|
1828
|
+
className: "ov-hatch"
|
|
1829
|
+
})] }) : /* @__PURE__ */ jsx("rect", {
|
|
1830
|
+
x: bx,
|
|
1831
|
+
y: by,
|
|
1832
|
+
width: bw,
|
|
1833
|
+
height: bh,
|
|
1834
|
+
rx: 2,
|
|
1835
|
+
fill: color,
|
|
1836
|
+
className: open ? "ov-gantt-open" : void 0
|
|
1837
|
+
})
|
|
1838
|
+
}, j);
|
|
1839
|
+
})
|
|
1840
|
+
]
|
|
1841
|
+
}, lane.id);
|
|
1842
|
+
}),
|
|
1843
|
+
now !== void 0 && /* @__PURE__ */ jsx("line", {
|
|
1844
|
+
x1: x(now),
|
|
1845
|
+
x2: x(now),
|
|
1846
|
+
y1: top,
|
|
1847
|
+
y2: plotBottom,
|
|
1848
|
+
className: "ov-now",
|
|
1849
|
+
"data-now": "true"
|
|
1850
|
+
}),
|
|
1851
|
+
[...marks].sort((a, b) => a.at - b.at).map((mark, i) => /* @__PURE__ */ jsxs("g", {
|
|
1852
|
+
className: mark.verdict ? `ov-verdict-${mark.verdict}` : void 0,
|
|
1853
|
+
"data-best": mark.label === bestMark || void 0,
|
|
1854
|
+
style: maxHeight ? { pointerEvents: "none" } : void 0,
|
|
1855
|
+
children: [
|
|
1856
|
+
maxHeight && /* @__PURE__ */ jsx("line", {
|
|
1857
|
+
x1: x(mark.at),
|
|
1858
|
+
x2: x(mark.at),
|
|
1859
|
+
y1: top,
|
|
1860
|
+
y2: plotBottom,
|
|
1861
|
+
className: `ov-mark ov-mark-${mark.kind}`,
|
|
1862
|
+
style: { opacity: mark.label === bestMark ? .8 : .3 }
|
|
1863
|
+
}),
|
|
1864
|
+
!maxHeight && /* @__PURE__ */ jsx(Mark, {
|
|
1865
|
+
mark,
|
|
1866
|
+
x: x(mark.at),
|
|
1867
|
+
top: 4,
|
|
1868
|
+
bottom: plotBottom,
|
|
1869
|
+
row: i % 2,
|
|
1870
|
+
em,
|
|
1871
|
+
flip: x(mark.at) > labelW + plotW * .62
|
|
1872
|
+
}),
|
|
1873
|
+
!maxHeight && mark.label === bestMark && /* @__PURE__ */ jsx("circle", {
|
|
1874
|
+
cx: x(mark.at),
|
|
1875
|
+
cy: 4 + Math.max(3, em / 4),
|
|
1876
|
+
r: 8,
|
|
1877
|
+
fill: "none",
|
|
1878
|
+
stroke: "var(--accent-text)",
|
|
1879
|
+
strokeWidth: 2
|
|
1735
1880
|
})
|
|
1736
1881
|
]
|
|
1737
|
-
},
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
x1: x(now),
|
|
1741
|
-
x2: x(now),
|
|
1742
|
-
y1: top,
|
|
1743
|
-
y2: plotBottom,
|
|
1744
|
-
className: "ov-now",
|
|
1745
|
-
"data-now": "true"
|
|
1746
|
-
}),
|
|
1747
|
-
[...marks].sort((a, b) => a.at - b.at).map((mark, i) => /* @__PURE__ */ jsx(Mark, {
|
|
1748
|
-
mark,
|
|
1749
|
-
x: x(mark.at),
|
|
1750
|
-
top: 4,
|
|
1751
|
-
bottom: plotBottom,
|
|
1752
|
-
row: i % 3,
|
|
1753
|
-
em,
|
|
1754
|
-
flip: x(mark.at) > labelW + plotW * .62
|
|
1755
|
-
}, `mark-${i}`))
|
|
1756
|
-
]
|
|
1882
|
+
}, `mark-${i}`))
|
|
1883
|
+
]
|
|
1884
|
+
})
|
|
1757
1885
|
}),
|
|
1758
1886
|
/* @__PURE__ */ jsx(Tooltip, { tip }),
|
|
1759
1887
|
/* @__PURE__ */ jsx(EventList, {
|
|
@@ -6500,6 +6628,7 @@ const toolOf = (name) => name?.split(" · ")[0];
|
|
|
6500
6628
|
const toneKey = (tone) => tone > 0 && tone <= 5 ? tone : 0;
|
|
6501
6629
|
const spanFailed = (span) => span.status === "failed";
|
|
6502
6630
|
const spanRunning = (span) => span.status === "running";
|
|
6631
|
+
const spanFact = (span, label) => new Map((span.facts ?? []).map((fact) => [fact.label.toLowerCase(), fact.value])).get(label.toLowerCase());
|
|
6503
6632
|
const ordered = (span) => Number.isFinite(span.startMs) && Number.isFinite(span.endMs) && span.endMs >= span.startMs;
|
|
6504
6633
|
/** 542ms, 6.9s, 23s, 1m 22s, 2h 05m: the precision a reader compares at each scale. */
|
|
6505
6634
|
function formatSpanDuration(ms) {
|
|
@@ -6570,9 +6699,10 @@ function flowWaterfallRows(trace, resolutionMs) {
|
|
|
6570
6699
|
tone: state === "running" ? "live" : state === "done" ? "ok" : state === "needs-you" ? "warn" : state === "failed" ? "failed" : "neutral",
|
|
6571
6700
|
...state === "unknown" ? { ring: true } : {}
|
|
6572
6701
|
};
|
|
6573
|
-
const span =
|
|
6702
|
+
const span = {
|
|
6574
6703
|
...recorded,
|
|
6575
|
-
outcome
|
|
6704
|
+
outcome: recorded.outcome ?? outcome,
|
|
6705
|
+
detail: spanFact(recorded, recorded.kind === "tool" ? "Provider" : "Model") ?? recorded.detail
|
|
6576
6706
|
};
|
|
6577
6707
|
return {
|
|
6578
6708
|
span,
|
|
@@ -6825,12 +6955,14 @@ function agentCallouts(agents, clockMs) {
|
|
|
6825
6955
|
for (const row of agents) {
|
|
6826
6956
|
if (row.durationLabel === "—" || row.span.startMs > clockMs || row.kind !== "pipeline" || row.hasAgents || row.id.startsWith("family:") || row.id.startsWith("run:")) continue;
|
|
6827
6957
|
const ms = (spanRunning(row.span) ? Math.max(row.span.endMs, clockMs) : Math.min(row.span.endMs, clockMs)) - row.span.startMs;
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6958
|
+
if (spanFact(row.span, "Ran for") === void 0) {
|
|
6959
|
+
timed += 1;
|
|
6960
|
+
if (ms > slowestMs) {
|
|
6961
|
+
slowest = row;
|
|
6962
|
+
slowestMs = ms;
|
|
6963
|
+
}
|
|
6832
6964
|
}
|
|
6833
|
-
const cost = row.span.costUsd ?? row.rollupCostUsd;
|
|
6965
|
+
const cost = spanFact(row.span, "Cost") === void 0 ? row.span.costUsd ?? row.rollupCostUsd : void 0;
|
|
6834
6966
|
if (typeof cost !== "number" || !Number.isFinite(cost)) continue;
|
|
6835
6967
|
priced += 1;
|
|
6836
6968
|
if (cost > top) {
|
|
@@ -6949,9 +7081,24 @@ function agentFamilies(agents, open) {
|
|
|
6949
7081
|
const startMs = Math.min(...members.map((row) => row.span.startMs));
|
|
6950
7082
|
const endMs = Math.max(...members.map((row) => row.span.endMs));
|
|
6951
7083
|
const running = members.some((row) => spanRunning(row.span));
|
|
6952
|
-
const known = members.map((row) => row.span.costUsd ?? row.rollupCostUsd).filter((cost) => cost !== void 0);
|
|
7084
|
+
const known = members.map((row) => row.span.costUsd ?? (spanFact(row.span, "Cost") === void 0 ? row.rollupCostUsd : void 0)).filter((cost) => cost !== void 0 && Number.isFinite(cost));
|
|
6953
7085
|
const failed = members.filter((row) => row.span.outcome ? row.span.outcome.tone === "failed" : !row.ok).length;
|
|
6954
7086
|
const outcome = members.every((row) => row.span.outcome && row.span.outcome.tone === members[0].span.outcome?.tone && !row.span.outcome.ring === !members[0].span.outcome?.ring) ? members[0].span.outcome : void 0;
|
|
7087
|
+
const facts = members.flatMap((row) => (row.span.facts ?? []).filter(({ label }) => [
|
|
7088
|
+
"model",
|
|
7089
|
+
"model calls",
|
|
7090
|
+
"tool calls",
|
|
7091
|
+
"model latency",
|
|
7092
|
+
"failures",
|
|
7093
|
+
"retries"
|
|
7094
|
+
].includes(label.toLowerCase()) || label.toLowerCase() === "tokens" && ![
|
|
7095
|
+
row.span.promptTokens,
|
|
7096
|
+
row.span.completionTokens,
|
|
7097
|
+
row.span.cachedTokens
|
|
7098
|
+
].some(Number.isFinite) || label.toLowerCase() === "cost" && known.length === 0)).map(({ label }) => ({
|
|
7099
|
+
label,
|
|
7100
|
+
value: "Not recorded"
|
|
7101
|
+
}));
|
|
6955
7102
|
out.push({
|
|
6956
7103
|
costFloor: known.length < members.length || members.some((row) => row.costFloor),
|
|
6957
7104
|
id,
|
|
@@ -6992,7 +7139,8 @@ function agentFamilies(agents, open) {
|
|
|
6992
7139
|
activityAt: members.flatMap((row) => row.span.activityAt ?? row.own.flatMap((mark) => [mark.startMs, mark.endMs])),
|
|
6993
7140
|
status: running ? "running" : failed ? "failed" : "done",
|
|
6994
7141
|
...outcome ? { outcome } : {},
|
|
6995
|
-
detail: members[0].span.detail,
|
|
7142
|
+
detail: members.every((row) => row.span.detail === members[0].span.detail) ? members[0].span.detail : void 0,
|
|
7143
|
+
facts,
|
|
6996
7144
|
meta: {
|
|
6997
7145
|
failedAgents: failed,
|
|
6998
7146
|
costPartial: known.length < members.length,
|
|
@@ -7025,13 +7173,17 @@ function agentSummary(row) {
|
|
|
7025
7173
|
const failed = row.own.filter((mark) => mark.kind === "failed").length;
|
|
7026
7174
|
return [failed ? `${failed} failed` : "all done"];
|
|
7027
7175
|
}
|
|
7028
|
-
const
|
|
7029
|
-
const
|
|
7030
|
-
|
|
7031
|
-
row.
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
|
|
7176
|
+
const facts = new Map((row.span.facts ?? []).map(({ label, value }) => [label.toLowerCase(), value]));
|
|
7177
|
+
const calls = [[
|
|
7178
|
+
"Model calls",
|
|
7179
|
+
row.counts?.model,
|
|
7180
|
+
"model"
|
|
7181
|
+
], [
|
|
7182
|
+
"Tool calls",
|
|
7183
|
+
row.counts?.tool,
|
|
7184
|
+
"tool"
|
|
7185
|
+
]].map(([label, count, kind]) => facts.has(label.toLowerCase()) ? `${label}: ${facts.get(label.toLowerCase())}` : count ? `${count} ${kind} ${count === 1 ? "call" : "calls"}` : "");
|
|
7186
|
+
return [row.span.outcome?.label ?? (spanRunning(row.span) ? "running" : !row.ok ? "failed" : row.kind === "pipeline" ? "" : "done"), ...calls].filter(Boolean);
|
|
7035
7187
|
}
|
|
7036
7188
|
/**
|
|
7037
7189
|
* The agent view's rows: families folded until opened, and an opened agent's own steps (its turns and tool calls, not its
|
|
@@ -7115,7 +7267,7 @@ function drawnRow(row, visible) {
|
|
|
7115
7267
|
} : row;
|
|
7116
7268
|
}
|
|
7117
7269
|
/** A row's cost as printed: "≥" when it is a floor. */
|
|
7118
|
-
const rowCostText = (row, cost) => `${row.costFloor ? "≥" : ""}${formatCost(cost)}
|
|
7270
|
+
const rowCostText = (row, cost) => spanFact(row.span, "Cost") ?? (cost === void 0 ? "" : `${row.costFloor ? "≥" : ""}${formatCost(cost)}`);
|
|
7119
7271
|
/** Quiet is elapsed silence beyond both five minutes and three median event gaps. */
|
|
7120
7272
|
function quietActivity(row, now) {
|
|
7121
7273
|
const times = [...row.span.activityAt ?? row.own.flatMap((mark) => [mark.startMs, mark.endMs])].filter(Number.isFinite).sort((a, b) => a - b);
|
|
@@ -7346,8 +7498,8 @@ function metrics(row, clockMs, full) {
|
|
|
7346
7498
|
const restarts = span.marks?.filter((mark) => mark.kind === "restart").length ?? 0;
|
|
7347
7499
|
if (counts?.retried || restarts) out.push(["Retries", [counts?.retried ? plural(counts.retried, "step retried", "steps retried") : "", restarts ? plural(restarts, "restart") : ""].filter(Boolean).join(" · ")]);
|
|
7348
7500
|
if (full && row.hasChildren) out.push(["Steps", row.descendants.toLocaleString()]);
|
|
7349
|
-
|
|
7350
|
-
return out;
|
|
7501
|
+
const facts = (span.facts ?? []).map(({ label, value }) => [label, value]);
|
|
7502
|
+
return [...new Map([...out, ...facts].map((row) => [row[0].toLowerCase(), row])).values()];
|
|
7351
7503
|
}
|
|
7352
7504
|
function Table({ rows }) {
|
|
7353
7505
|
return /* @__PURE__ */ jsx("dl", {
|
|
@@ -7358,7 +7510,7 @@ function Table({ rows }) {
|
|
|
7358
7510
|
/** The span card: what ran, for how long, on what tokens and at what recorded cost, then its input and output. */
|
|
7359
7511
|
function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
|
|
7360
7512
|
const span = row.span;
|
|
7361
|
-
const failure = span
|
|
7513
|
+
const failure = spanFact(span, "Failure");
|
|
7362
7514
|
const state = runStatus(span.status ?? "unknown");
|
|
7363
7515
|
const meta = statusMeta(state);
|
|
7364
7516
|
const status = meta.label;
|
|
@@ -7572,11 +7724,8 @@ const open = (row, clockMs) => spanRunning(row.span) || row.span.startMs <= cloc
|
|
|
7572
7724
|
function endLabel(row, clockMs) {
|
|
7573
7725
|
const span = row.span;
|
|
7574
7726
|
if (row.durationLabel === "—") return "—";
|
|
7575
|
-
if (row.approx && span.endMs === span.startMs && !spanRunning(span)) return
|
|
7576
|
-
|
|
7577
|
-
const duration = spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel;
|
|
7578
|
-
const cost = span.costUsd ?? (row.rollupCostUsd ? row.rollupCostUsd : void 0);
|
|
7579
|
-
return cost === void 0 ? duration : `${duration} · ${rowCostText(row, cost)}`;
|
|
7727
|
+
if (row.approx && span.endMs === span.startMs && !spanRunning(span)) return rowCostText(row, span.costUsd);
|
|
7728
|
+
return [spanFact(span, "Ran for") ?? (!spanRunning(span) && clockMs < span.endMs ? `${formatSpanDuration(Math.max(0, clockMs - span.startMs))}${row.approx ? "~" : ""}` : spanRunning(span) ? `${formatSpanDuration(Math.max(span.endMs, clockMs) - span.startMs)}${row.approx ? "~" : ""}` : row.durationLabel), !spanRunning(span) && clockMs < span.endMs && spanFact(span, "Cost") === void 0 ? "" : rowCostText(row, span.costUsd ?? row.rollupCostUsd)].filter(Boolean).join(" · ");
|
|
7580
7729
|
}
|
|
7581
7730
|
/** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
|
|
7582
7731
|
const FlowRow = memo(function FlowRow(props) {
|
|
@@ -7826,6 +7975,7 @@ const FlowRow = memo(function FlowRow(props) {
|
|
|
7826
7975
|
})
|
|
7827
7976
|
}), /* @__PURE__ */ jsx("span", {
|
|
7828
7977
|
className: "fw-duration",
|
|
7978
|
+
title: label,
|
|
7829
7979
|
children: label
|
|
7830
7980
|
})]
|
|
7831
7981
|
})
|
|
@@ -9301,7 +9451,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
|
|
|
9301
9451
|
const errorEnds = Array.from({ length: errors ?? 0 }, (_, index) => Number.isFinite(errorSpans[index]?.endMs) ? errorSpans[index].endMs : void 0);
|
|
9302
9452
|
const done = !isLive && !trace.spans.some(spanRunning);
|
|
9303
9453
|
const shown = (row) => row && drawnRow(row, visible);
|
|
9304
|
-
const cardRow = shown(card ? byId.get(card.target) : void 0);
|
|
9454
|
+
const cardRow = shown(card ? byId.get(card.target) ?? visible.find((row) => row.id === card.target) : void 0);
|
|
9305
9455
|
const cardAgent = cardRow && (() => {
|
|
9306
9456
|
for (let cursor = cardRow; cursor; cursor = cursor.parentId ? byId.get(cursor.parentId) : void 0) if (cursor.kind === "pipeline") return shown(cursor);
|
|
9307
9457
|
})();
|
package/dist/react/live.css
CHANGED
|
@@ -97,3 +97,28 @@ line.ov-gridline { stroke: color-mix(in srgb, var(--chart-live-faint, #777) 22%,
|
|
|
97
97
|
.ov-gap-stretch { fill: none; stroke: var(--chart-live-pass, #2f9e6e); stroke-width: 1; stroke-dasharray: 3 3; }
|
|
98
98
|
.ov-gap-span { stroke-width: 2; stroke-dasharray: 2 4; stroke-linecap: round; }
|
|
99
99
|
.ov-gap-value { cursor: default; }
|
|
100
|
+
|
|
101
|
+
.ov-gantt { font: 14px/20px var(--font-sans, sans-serif); }
|
|
102
|
+
.ov-gantt .ov-axis { fill: var(--chart-live-muted, var(--text-muted, #666)); font-size: 13px; }
|
|
103
|
+
.ov-gantt .ov-mark-label { fill: var(--chart-live-fg, var(--text-primary, currentColor)); font-size: 13px; }
|
|
104
|
+
.ov-gantt .ov-tip { background: var(--chart-live-raised, var(--bg-elevated, #fff)); border-color: var(--chart-live-border, var(--border-color, #aaa)); max-width: min(240px, 100%); box-sizing: border-box; }
|
|
105
|
+
.ov-gantt-scroll { overscroll-behavior: contain; }
|
|
106
|
+
.ov-gantt [role='button'] { cursor: pointer; outline: none; }
|
|
107
|
+
.ov-gantt [role='button']:hover > rect:first-child { fill: var(--ix-hover-bg); }
|
|
108
|
+
.ov-gantt [role='button']:focus-visible > rect:first-child { stroke: var(--accent-text); stroke-width: 2; }
|
|
109
|
+
.ov-gantt [data-state] rect { transition: width var(--motion-stream), fill var(--motion-control); }
|
|
110
|
+
.ov-gantt [data-open='true'] rect { animation: ov-gantt-live var(--motion-ambient) infinite; }
|
|
111
|
+
.ov-gantt[data-featured] [data-mark] text { display: none; }
|
|
112
|
+
.ov-gantt[data-featured] [data-best] text { display: block; }
|
|
113
|
+
.ov-gantt[data-featured] .ov-events { display: none; }
|
|
114
|
+
@keyframes ov-gantt-live { 50% { opacity: .55; } }
|
|
115
|
+
@media (prefers-reduced-motion: reduce) { .ov-gantt [data-state] rect { transition: none; animation: none; } }
|
|
116
|
+
|
|
117
|
+
.ov-meter-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 6px; padding: 0; margin: 0; list-style: none; }
|
|
118
|
+
.ov-meter-steps li { display: flex; flex-direction: column; justify-content: space-between; gap: 12px; min-height: 104px; padding: 16px; border-radius: var(--radius-lg); background: var(--bg-elevated); }
|
|
119
|
+
.ov-meter-steps strong { font-size: 26px; line-height: 30px; }
|
|
120
|
+
.ov-meter-steps [data-state='pass'] { background: var(--surface-success-bg); }
|
|
121
|
+
.ov-meter-steps [aria-current='step'] { background: var(--surface-warning-bg); color: var(--surface-warning-text); }
|
|
122
|
+
|
|
123
|
+
.ov-meter-steps span { overflow-wrap: anywhere; }
|
|
124
|
+
@media (max-width: 720px) { .ov-meter-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); } .ov-meter-steps li { padding: 12px; min-height: 94px; } }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
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",
|