@tangle-network/charts 0.19.0 → 0.20.0

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.
@@ -1,4 +1,4 @@
1
- import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-CVXBpZzt.js";
1
+ import { G as hatchPath, J as markerPath, b as isRefusal, h as eventsGap, t as timeline, u as MARK_HEADS, y as markName } from "../timeline-B7c-cede.js";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Fragment as Fragment$1, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  //#region src/react/sparkline.tsx
@@ -2500,6 +2500,29 @@ function slowestRow(rows, clockMs) {
2500
2500
  }
2501
2501
  return best;
2502
2502
  }
2503
+ /** The agents a reader should look at first: the longest one by the clock, and the costliest when two or more are priced. */
2504
+ function agentCallouts(agents, clockMs) {
2505
+ let slowest, slowestMs = -1, costliest, top = -1, priced = 0;
2506
+ for (const row of agents) {
2507
+ if (row.durationLabel === "—" || row.span.startMs > clockMs) continue;
2508
+ const ms = (spanRunning(row.span) ? Math.max(row.span.endMs, clockMs) : Math.min(row.span.endMs, clockMs)) - row.span.startMs;
2509
+ if (ms > slowestMs) {
2510
+ slowest = row;
2511
+ slowestMs = ms;
2512
+ }
2513
+ const cost = row.span.costUsd ?? row.rollupCostUsd;
2514
+ if (cost === void 0) continue;
2515
+ priced += 1;
2516
+ if (cost > top) {
2517
+ costliest = row;
2518
+ top = cost;
2519
+ }
2520
+ }
2521
+ return {
2522
+ slowestId: agents.length > 1 ? slowest?.id : void 0,
2523
+ costliestId: priced > 1 && top > 0 ? costliest?.id : void 0
2524
+ };
2525
+ }
2503
2526
  /** Busy time per agent row: the union of its own work. Their sum over wall time is the parallel speedup. */
2504
2527
  function busyMs(row, clockMs) {
2505
2528
  let total = 0, end = -Infinity;
@@ -2537,6 +2560,31 @@ function totalsAt(trace, clockMs) {
2537
2560
  errors
2538
2561
  };
2539
2562
  }
2563
+ /** The cost a reader can rely on: a lower bound when some calls are unpriced, a dash only when nothing is priced. */
2564
+ function costSummary(costUsd, trace) {
2565
+ const list = trace.costBasis === "list";
2566
+ const label = list ? "Cost · list price" : "Cost";
2567
+ if (costUsd === void 0) return trace.billedUsd !== void 0 ? {
2568
+ value: `${formatCost(trace.billedUsd)} billed`,
2569
+ title: "No list price for these calls; billed is the seat charge",
2570
+ label
2571
+ } : {
2572
+ value: "—",
2573
+ title: "No cost source: the harness recorded no cost and no price catalog was given",
2574
+ label
2575
+ };
2576
+ const coverage = trace.costCoverage;
2577
+ const partial = coverage !== void 0 && coverage.priced < coverage.total;
2578
+ return {
2579
+ value: `${partial ? "≥" : list ? "≈" : ""}${formatCost(costUsd)}${trace.billedUsd !== void 0 ? ` · ${formatCost(trace.billedUsd)} billed` : ""}`,
2580
+ title: [
2581
+ partial ? `${coverage.priced} of ${coverage.total} model calls priced` : coverage ? `all ${coverage.total} model calls priced` : "",
2582
+ list ? "at the model catalog list price; not a bill" : "",
2583
+ trace.billedUsd !== void 0 ? `billed ${formatCost(trace.billedUsd)} (the seat charge)` : ""
2584
+ ].filter(Boolean).join("; "),
2585
+ label
2586
+ };
2587
+ }
2540
2588
  //#endregion
2541
2589
  //#region src/react/flow-waterfall-icons.tsx
2542
2590
  const svg = (children, label) => /* @__PURE__ */ jsx("svg", {
@@ -2646,7 +2694,7 @@ const KIND_LABEL = {
2646
2694
  const unknown = /* @__PURE__ */ jsx("span", {
2647
2695
  className: "fw-unknown",
2648
2696
  title: "Not recorded by the harness",
2649
- children: "Unknown"
2697
+ children: "—"
2650
2698
  });
2651
2699
  const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
2652
2700
  title: n.toLocaleString(),
@@ -2655,23 +2703,29 @@ const count = (n) => n === void 0 ? unknown : /* @__PURE__ */ jsx("span", {
2655
2703
  /** The span card: what ran, for how long, at what recorded cost, on what input. */
2656
2704
  function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderIcon, compact }) {
2657
2705
  const span = row.span;
2658
- const running = spanRunning(span);
2659
- const end = running ? Math.max(span.endMs, clockMs) : span.endMs;
2660
- const status = running ? "Running" : row.ok ? "Done" : "Failed";
2706
+ const status = spanRunning(span) ? "Running" : row.ok ? "Done" : "Failed";
2661
2707
  const tokens = span.kind === "model" || span.kind === "pipeline" || span.promptTokens !== void 0;
2662
2708
  const owner = agent && agent.id !== row.id ? agent : void 0;
2663
- const duration = Number.isFinite(end - span.startMs) && end >= span.startMs ? formatSpanDuration(end - span.startMs) : "—";
2709
+ const lasted = (s) => {
2710
+ const stop = spanRunning(s) ? Math.max(s.endMs, clockMs) : s.endMs;
2711
+ return Number.isFinite(stop - s.startMs) && stop >= s.startMs ? formatSpanDuration(stop - s.startMs) : "—";
2712
+ };
2713
+ const duration = lasted(span);
2664
2714
  if (compact) {
2665
2715
  const who = owner ?? row;
2666
- const outcome = who.span.outcome;
2667
- const tokenCount = span.promptTokens !== void 0 || span.completionTokens !== void 0 ? (span.promptTokens ?? 0) + (span.completionTokens ?? 0) : void 0;
2668
- const cells = [[span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind], duration]];
2669
- for (const fact of (span.facts ?? who.span.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
2670
- cells.push(["Started", `+${formatSpanDuration(Math.max(0, span.startMs))}`]);
2716
+ const subject = who.span;
2717
+ const outcome = subject.outcome;
2718
+ const tokenCount = subject.promptTokens !== void 0 || subject.completionTokens !== void 0 ? (subject.promptTokens ?? 0) + (subject.completionTokens ?? 0) : void 0;
2719
+ const cost = subject.costUsd ?? who.rollupCostUsd;
2720
+ const cells = [[subject.kind === "model" ? "Thinking" : KIND_LABEL[subject.kind], lasted(subject)]];
2721
+ for (const fact of (subject.facts ?? []).slice(0, 3)) cells.push([fact.label, fact.value]);
2722
+ cells.push(["Started", `+${formatSpanDuration(Math.max(0, subject.startMs))}`]);
2671
2723
  if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
2672
- if (span.costUsd !== void 0) cells.push(["Cost", formatCost(span.costUsd)]);
2724
+ if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
2673
2725
  cells.splice(6);
2674
- const sentence = [span.output, span.id === who.id ? void 0 : span.name].find((text) => text && text !== who.name);
2726
+ const kind = span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind];
2727
+ const step = owner ? `${kind}${span.name && span.name !== kind && span.name.toLowerCase() !== span.kind ? ` ${span.name}` : ""} · ${duration}${row.ok ? "" : " · failed"}` : void 0;
2728
+ const sentence = !owner && span.output && span.output !== who.name ? span.output : void 0;
2675
2729
  return /* @__PURE__ */ jsxs("div", {
2676
2730
  id,
2677
2731
  role: pinned ? "dialog" : "tooltip",
@@ -2732,6 +2786,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2732
2786
  className: "fw-card-cells",
2733
2787
  children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
2734
2788
  }),
2789
+ step && /* @__PURE__ */ jsx("p", {
2790
+ className: "fw-card-step",
2791
+ children: step
2792
+ }),
2735
2793
  sentence && /* @__PURE__ */ jsx("p", {
2736
2794
  className: "fw-card-result",
2737
2795
  children: sentence
@@ -2807,9 +2865,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2807
2865
  children: [
2808
2866
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
2809
2867
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
2810
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: span.costUsd === void 0 ? unknown : formatCost(span.costUsd) })] }),
2811
- tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
2812
- tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
2868
+ span.costUsd !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: formatCost(span.costUsd) })] }),
2869
+ tokens && span.promptTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
2870
+ tokens && span.completionTokens !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens out" }), /* @__PURE__ */ jsx("dd", { children: count(span.completionTokens) })] }),
2871
+ (span.facts ?? []).slice(0, 3).map((fact) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: fact.label }), /* @__PURE__ */ jsx("dd", { children: fact.value })] }, fact.label)),
2813
2872
  row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
2814
2873
  ]
2815
2874
  }),
@@ -2868,7 +2927,7 @@ function endLabel(row, clockMs) {
2868
2927
  }
2869
2928
  /** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
2870
2929
  const FlowRow = memo(function FlowRow(props) {
2871
- const { row, view, top, collapsed, searching, slowest, active, focusable, selected, sticky, clockMs, renderIcon } = props;
2930
+ const { row, view, top, collapsed, searching, slowest, costliest, active, focusable, selected, sticky, clockMs, renderIcon, fillByAgent, motion } = props;
2872
2931
  const span = row.span;
2873
2932
  const running = spanRunning(span);
2874
2933
  const agentView = view === "agents";
@@ -2884,18 +2943,26 @@ const FlowRow = memo(function FlowRow(props) {
2884
2943
  "data-span-id": row.id,
2885
2944
  "data-span-kind": kind,
2886
2945
  "data-slowest": slowest || void 0,
2946
+ "data-running": running || void 0,
2887
2947
  "aria-level": agentView ? void 0 : row.depth + 1,
2888
2948
  "aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
2889
2949
  "aria-selected": selected,
2890
2950
  tabIndex: focusable ? 0 : -1,
2891
2951
  "data-tone": toneKey(row.tone),
2892
- "data-outcome": span.outcome?.tone,
2893
- className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}`,
2894
- style: { "--row-y": `${top}px` },
2952
+ "data-outcome": fillByAgent ? void 0 : span.outcome?.tone,
2953
+ className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}${motion?.enter !== void 0 ? " fw-enter" : ""}${motion?.settle ? " fw-settle" : ""}`,
2954
+ style: {
2955
+ "--row-y": `${top}px`,
2956
+ "--fw-enter-delay": `${motion?.enter ?? 0}ms`
2957
+ },
2895
2958
  onFocus: (event) => {
2896
2959
  if (event.target === event.currentTarget) props.onActivate(row);
2897
2960
  },
2898
- onClick: () => props.onActivate(row),
2961
+ onClick: (event) => {
2962
+ props.onActivate(row);
2963
+ const bar = agentView ? event.currentTarget.querySelector(".fw-bar") : null;
2964
+ if (bar) props.onPin(row.id, bar);
2965
+ },
2899
2966
  children: [/* @__PURE__ */ jsxs("div", {
2900
2967
  role: "gridcell",
2901
2968
  className: "fw-label",
@@ -2921,10 +2988,6 @@ const FlowRow = memo(function FlowRow(props) {
2921
2988
  className: "fw-toggle-space",
2922
2989
  "aria-hidden": true
2923
2990
  })),
2924
- agentView && /* @__PURE__ */ jsx("span", {
2925
- className: "fw-agent-dot",
2926
- "aria-hidden": true
2927
- }),
2928
2991
  !agentView && /* @__PURE__ */ jsx("span", {
2929
2992
  className: "fw-icon-tile",
2930
2993
  "aria-hidden": true,
@@ -2936,14 +2999,27 @@ const FlowRow = memo(function FlowRow(props) {
2936
2999
  children: [
2937
3000
  /* @__PURE__ */ jsxs("span", {
2938
3001
  className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
2939
- children: [running && /* @__PURE__ */ jsx("span", {
2940
- className: "fw-spinner",
2941
- role: "status",
2942
- "aria-label": "running"
2943
- }), /* @__PURE__ */ jsx("span", {
2944
- className: "fw-name-text",
2945
- children: row.name
2946
- })]
3002
+ children: [
3003
+ fillByAgent && isAgent && span.outcome ? /* @__PURE__ */ jsx("span", {
3004
+ className: "fw-agent-dot",
3005
+ "data-tone": "0",
3006
+ "data-outcome": span.outcome.tone,
3007
+ title: span.outcome.label,
3008
+ "aria-hidden": true
3009
+ }) : agentView && /* @__PURE__ */ jsx("span", {
3010
+ className: "fw-agent-dot",
3011
+ "aria-hidden": true
3012
+ }),
3013
+ running && /* @__PURE__ */ jsx("span", {
3014
+ className: "fw-spinner",
3015
+ role: "status",
3016
+ "aria-label": "running"
3017
+ }),
3018
+ /* @__PURE__ */ jsx("span", {
3019
+ className: "fw-name-text",
3020
+ children: row.name
3021
+ })
3022
+ ]
2947
3023
  }),
2948
3024
  agentView && span.detail && /* @__PURE__ */ jsx("span", {
2949
3025
  className: "fw-subtitle",
@@ -2970,11 +3046,19 @@ const FlowRow = memo(function FlowRow(props) {
2970
3046
  role: "gridcell",
2971
3047
  className: "fw-lane",
2972
3048
  "data-waterfall-track": true,
2973
- "aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? ", slowest step" : ""}`,
3049
+ "aria-label": `${KIND_LABEL[span.kind]}, ${label}${slowest ? `, slowest ${agentView ? "agent" : "step"}` : ""}${costliest ? ", costliest agent" : ""}`,
2974
3050
  children: invalid ? /* @__PURE__ */ jsx("span", {
2975
3051
  className: "fw-invalid",
2976
3052
  children: "—"
2977
3053
  }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3054
+ span.marks?.map((mark) => /* @__PURE__ */ jsx("span", {
3055
+ className: "fw-mark",
3056
+ "data-mark": mark.kind,
3057
+ style: at(mark.at, mark.at),
3058
+ title: mark.label,
3059
+ "aria-label": mark.label,
3060
+ role: "img"
3061
+ }, `m-${mark.at}-${mark.label}`)),
2978
3062
  /* @__PURE__ */ jsxs("span", {
2979
3063
  className: `fw-bar fw-k-${isAgent ? "pipeline" : kind}${row.approx ? " fw-approx" : ""}${marks.length ? " fw-has-marks" : ""}${row.hasAgents ? " fw-has-agents" : ""}`,
2980
3064
  "data-tone": toneKey(row.tone),
@@ -2998,21 +3082,28 @@ const FlowRow = memo(function FlowRow(props) {
2998
3082
  events: props,
2999
3083
  agentView
3000
3084
  }, `${mark.id}-${i}`)),
3001
- !agentView && /* @__PURE__ */ jsxs("span", {
3085
+ /* @__PURE__ */ jsxs("span", {
3002
3086
  className: "fw-end",
3003
- style: at(span.startMs, running ? 0x9184e72a000 : span.endMs),
3004
- children: [/* @__PURE__ */ jsx("span", {
3005
- className: "fw-duration",
3006
- children: label
3007
- }), slowest && /* @__PURE__ */ jsx("span", {
3008
- className: "fw-badge",
3009
- children: "Slowest"
3010
- })]
3087
+ style: at(span.startMs, running ? OPEN_END : span.endMs),
3088
+ children: [
3089
+ /* @__PURE__ */ jsx("span", {
3090
+ className: "fw-duration",
3091
+ children: label
3092
+ }),
3093
+ slowest && /* @__PURE__ */ jsx("span", {
3094
+ className: "fw-badge",
3095
+ children: "Slowest"
3096
+ }),
3097
+ costliest && /* @__PURE__ */ jsx("span", {
3098
+ className: "fw-badge",
3099
+ children: "Costliest"
3100
+ })
3101
+ ]
3011
3102
  })
3012
3103
  ] })
3013
3104
  })]
3014
3105
  });
3015
- }, (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.active === b.active && a.focusable === b.focusable && a.selected === b.selected && a.sticky === b.sticky && a.renderIcon === b.renderIcon && (a.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3106
+ }, (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.clockMs === b.clockMs || !open(a.row, a.clockMs) && !open(b.row, b.clockMs)));
3016
3107
  /** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
3017
3108
  function LinkMark({ link, rowHeight, hot }) {
3018
3109
  const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
@@ -3029,6 +3120,48 @@ function LinkMark({ link, rowHeight, hot }) {
3029
3120
  });
3030
3121
  }
3031
3122
  //#endregion
3123
+ //#region src/react/flow-waterfall-motion.ts
3124
+ const HOLD_MS = 700;
3125
+ const STAGGER_MS = 20;
3126
+ const STAGGER_CAP_MS = 200;
3127
+ /**
3128
+ * Grow, unfold and settle. Rows that arrived since the last change (a live span, an unfolded branch, the first paint) grow
3129
+ * from their start in a short stagger; a row that stops running settles. Motion is keyed to the row set, not to what is
3130
+ * mounted, so scrolling a virtualized tree never replays it; an entry expires after its animation so a remount stays still.
3131
+ */
3132
+ function useRowMotion(rows) {
3133
+ const known = useRef(null);
3134
+ const running = useRef(/* @__PURE__ */ new Set());
3135
+ const active = useRef(/* @__PURE__ */ new Map());
3136
+ const [expiry, setExpiry] = useState(0);
3137
+ const motion = useMemo(() => {
3138
+ const now = Date.now();
3139
+ for (const [id, entry] of active.current) if (entry.until <= now) active.current.delete(id);
3140
+ const previous = known.current;
3141
+ let order = 0;
3142
+ for (const row of rows) {
3143
+ const isRunning = spanRunning(row.span);
3144
+ if (!previous || !previous.has(row.id)) active.current.set(row.id, {
3145
+ enter: Math.min(order++ * STAGGER_MS, STAGGER_CAP_MS),
3146
+ until: now + HOLD_MS + STAGGER_CAP_MS
3147
+ });
3148
+ else if (running.current.has(row.id) && !isRunning) active.current.set(row.id, {
3149
+ settle: true,
3150
+ until: now + HOLD_MS
3151
+ });
3152
+ }
3153
+ known.current = new Set(rows.map((row) => row.id));
3154
+ running.current = new Set(rows.filter((row) => spanRunning(row.span)).map((row) => row.id));
3155
+ return new Map(active.current);
3156
+ }, [rows, expiry]);
3157
+ useEffect(() => {
3158
+ if (motion.size === 0) return;
3159
+ const timer = setTimeout(() => setExpiry((value) => value + 1), 920);
3160
+ return () => clearTimeout(timer);
3161
+ }, [motion]);
3162
+ return motion;
3163
+ }
3164
+ //#endregion
3032
3165
  //#region src/react/flow-waterfall-time.ts
3033
3166
  /** Time-axis geometry for the trace inspector: clock-aligned steps, labels and the zoom window. */
3034
3167
  const STEPS = [
@@ -3108,7 +3241,7 @@ function clampWindow(view, limitMs) {
3108
3241
  //#region src/react/flow-waterfall-body.tsx
3109
3242
  const ROW_HEIGHT = {
3110
3243
  spans: 36,
3111
- agents: 52
3244
+ agents: 40
3112
3245
  };
3113
3246
  const OVERSCAN = 8;
3114
3247
  /** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
@@ -3181,6 +3314,7 @@ function FlowBody(props) {
3181
3314
  view
3182
3315
  ]);
3183
3316
  const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
3317
+ const motion = useRowMotion(rows);
3184
3318
  const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
3185
3319
  const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
3186
3320
  const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
@@ -3310,7 +3444,7 @@ function FlowBody(props) {
3310
3444
  handle.addEventListener("pointerup", up);
3311
3445
  };
3312
3446
  const axis = /* @__PURE__ */ jsxs("div", {
3313
- className: `fw-axis fw-axis-${view === "agents" ? "bottom" : "top"}`,
3447
+ className: "fw-axis fw-axis-top",
3314
3448
  children: [/* @__PURE__ */ jsxs("div", {
3315
3449
  className: "fw-axis-label",
3316
3450
  children: [view === "spans" && /* @__PURE__ */ jsx(Fragment, { children: props.onExpandAll && /* @__PURE__ */ jsxs("span", {
@@ -3349,14 +3483,27 @@ function FlowBody(props) {
3349
3483
  ref: props.onTrack,
3350
3484
  onPointerDown: scrub,
3351
3485
  title: "Drag to scrub",
3352
- children: [ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3353
- className: "fw-tick",
3354
- style: { "--s": tick },
3355
- children: formatClock(tick)
3356
- }, tick)), showCursor && /* @__PURE__ */ jsx("span", {
3357
- className: "fw-cursor-handle",
3358
- "aria-hidden": true
3359
- })]
3486
+ children: [
3487
+ ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3488
+ className: "fw-tick",
3489
+ style: { "--s": tick },
3490
+ children: formatClock(tick)
3491
+ }, tick)),
3492
+ props.markers?.map((marker) => /* @__PURE__ */ jsx("button", {
3493
+ type: "button",
3494
+ className: "fw-event",
3495
+ "data-tone": marker.tone ?? "neutral",
3496
+ style: { "--s": marker.at },
3497
+ title: marker.kind ? `${marker.kind}: ${marker.label}` : marker.label,
3498
+ "aria-label": `Jump to ${marker.kind ? `${marker.kind} ` : ""}${marker.label}`,
3499
+ onPointerDown: (event) => event.stopPropagation(),
3500
+ onClick: () => props.onScrub(marker.at)
3501
+ }, `e-${marker.at}-${marker.label}`)),
3502
+ showCursor && /* @__PURE__ */ jsx("span", {
3503
+ className: "fw-cursor-handle",
3504
+ "aria-hidden": true
3505
+ })
3506
+ ]
3360
3507
  })]
3361
3508
  });
3362
3509
  const row = (item, i, isSticky = false) => /* @__PURE__ */ jsx(FlowRow, {
@@ -3366,12 +3513,15 @@ function FlowBody(props) {
3366
3513
  collapsed: collapsed.has(item.id),
3367
3514
  searching,
3368
3515
  slowest: item.id === slowestId,
3516
+ costliest: item.id === props.costliestId,
3369
3517
  active: item.id === activeId,
3370
3518
  focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
3371
3519
  selected: item.id === selectedId,
3372
3520
  sticky: isSticky,
3521
+ motion: isSticky ? void 0 : motion.get(item.id),
3373
3522
  clockMs,
3374
3523
  renderIcon: props.renderIcon,
3524
+ fillByAgent: props.fillByAgent,
3375
3525
  onToggle: props.onToggle,
3376
3526
  onInspect: props.onInspect,
3377
3527
  onLeave: props.onLeave,
@@ -3381,83 +3531,91 @@ function FlowBody(props) {
3381
3531
  return /* @__PURE__ */ jsxs("div", {
3382
3532
  className: `fw-body fw-view-${view}${zoomed ? " fw-zoomed" : ""}`,
3383
3533
  ref: surface,
3384
- children: [
3385
- view === "spans" && axis,
3386
- /* @__PURE__ */ jsxs("div", {
3387
- className: "fw-viewport",
3388
- children: [
3389
- /* @__PURE__ */ jsx("div", {
3390
- className: "fw-gridlayer",
3391
- "aria-hidden": true,
3392
- children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3393
- className: "fw-gridline",
3394
- style: { "--s": tick }
3395
- }, tick))
3396
- }),
3397
- /* @__PURE__ */ jsxs("div", {
3398
- ref: scroller,
3399
- role: "treegrid",
3400
- "aria-label": "Trace timeline",
3401
- "aria-rowcount": rows.length,
3402
- "aria-multiselectable": false,
3403
- tabIndex: -1,
3404
- className: "fw-scroller",
3534
+ children: [axis, /* @__PURE__ */ jsxs("div", {
3535
+ className: "fw-viewport",
3536
+ children: [
3537
+ /* @__PURE__ */ jsx("div", {
3538
+ className: "fw-gridlayer",
3539
+ "aria-hidden": true,
3540
+ children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3541
+ className: "fw-gridline",
3542
+ style: { "--s": tick }
3543
+ }, tick))
3544
+ }),
3545
+ /* @__PURE__ */ jsxs("div", {
3546
+ ref: scroller,
3547
+ role: "treegrid",
3548
+ "aria-label": "Trace timeline",
3549
+ "aria-rowcount": rows.length,
3550
+ "aria-multiselectable": false,
3551
+ tabIndex: -1,
3552
+ className: "fw-scroller",
3553
+ "data-primary-content": "",
3554
+ style: {
3555
+ height,
3556
+ maxHeight
3557
+ },
3558
+ onKeyDown: keydown,
3559
+ onScroll: (event) => {
3560
+ const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
3561
+ if (next !== chunk) setChunk(next);
3562
+ },
3563
+ children: [/* @__PURE__ */ jsxs("div", {
3564
+ className: "fw-canvas",
3565
+ style: { height: rows.length * rowHeight },
3566
+ children: [/* @__PURE__ */ jsx("div", {
3567
+ className: "fw-links",
3568
+ "aria-hidden": true,
3569
+ children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
3570
+ link,
3571
+ rowHeight,
3572
+ hot: props.focusId !== void 0 && (rows[link.from]?.id === props.focusId || rows[link.to]?.id === props.focusId)
3573
+ }, link.id))
3574
+ }), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
3575
+ }), rows.length === 0 && /* @__PURE__ */ jsx("p", {
3576
+ role: "status",
3577
+ className: "fw-empty",
3578
+ children: props.emptyText
3579
+ })]
3580
+ }),
3581
+ view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
3582
+ scroller,
3583
+ rows,
3584
+ index,
3585
+ rowHeight,
3586
+ render: row
3587
+ }),
3588
+ props.markers?.length || props.pauses?.length ? /* @__PURE__ */ jsxs("div", {
3589
+ className: "fw-marker-layer",
3590
+ children: [props.pauses?.map((pause) => /* @__PURE__ */ jsx("span", {
3591
+ className: "fw-pause",
3405
3592
  style: {
3406
- height,
3407
- maxHeight
3593
+ "--s": pause.startMs,
3594
+ "--e": pause.endMs
3408
3595
  },
3409
- onKeyDown: keydown,
3410
- onScroll: (event) => {
3411
- const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
3412
- if (next !== chunk) setChunk(next);
3413
- },
3414
- children: [/* @__PURE__ */ jsxs("div", {
3415
- className: "fw-canvas",
3416
- style: { height: rows.length * rowHeight },
3417
- children: [/* @__PURE__ */ jsx("div", {
3418
- className: "fw-links",
3419
- "aria-hidden": true,
3420
- children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
3421
- link,
3422
- rowHeight,
3423
- hot: props.focusId !== void 0 && (rows[link.from]?.id === props.focusId || rows[link.to]?.id === props.focusId)
3424
- }, link.id))
3425
- }), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
3426
- }), rows.length === 0 && /* @__PURE__ */ jsx("p", {
3427
- role: "status",
3428
- className: "fw-empty",
3429
- children: props.emptyText
3430
- })]
3431
- }),
3432
- view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
3433
- scroller,
3434
- rows,
3435
- index,
3436
- rowHeight,
3437
- render: row
3438
- }),
3439
- props.markers?.length ? /* @__PURE__ */ jsx("div", {
3440
- className: "fw-marker-layer",
3441
- children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
3442
- className: "fw-marker",
3443
- "data-tone": marker.tone ?? "neutral",
3444
- style: { "--s": marker.at },
3445
- title: marker.label,
3446
- children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
3447
- className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
3448
- children: marker.label
3449
- })
3450
- }, `${marker.at}-${marker.label}`))
3451
- }) : null,
3452
- showCursor && /* @__PURE__ */ jsx("div", {
3453
- className: "fw-cursor-layer",
3454
- "aria-hidden": true,
3455
- children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
3456
- })
3457
- ]
3458
- }),
3459
- view === "agents" && axis
3460
- ]
3596
+ title: pause.label,
3597
+ children: /* @__PURE__ */ jsx("span", {
3598
+ className: "fw-pause-label",
3599
+ children: pause.label
3600
+ })
3601
+ }, `p-${pause.startMs}`)), props.markers?.map((marker) => /* @__PURE__ */ jsx("span", {
3602
+ className: "fw-marker",
3603
+ "data-tone": marker.tone ?? "neutral",
3604
+ style: { "--s": marker.at },
3605
+ title: marker.label,
3606
+ children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
3607
+ className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
3608
+ children: marker.label
3609
+ })
3610
+ }, `${marker.at}-${marker.label}`))]
3611
+ }) : null,
3612
+ showCursor && /* @__PURE__ */ jsx("div", {
3613
+ className: "fw-cursor-layer",
3614
+ "aria-hidden": true,
3615
+ children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
3616
+ })
3617
+ ]
3618
+ })]
3461
3619
  });
3462
3620
  }
3463
3621
  /** Ancestors of the first visible row stay pinned while their children scroll beneath them. This layer follows
@@ -3721,13 +3879,51 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
3721
3879
  const LANE_PAD = 12;
3722
3880
  const END_LABEL_GUTTER = {
3723
3881
  spans: 120,
3724
- agents: 16
3882
+ agents: 176
3725
3883
  };
3726
3884
  const NAME_WIDTH = {
3727
3885
  spans: 320,
3728
3886
  agents: 240
3729
3887
  };
3888
+ /** The summary strip: as of the playhead while scrubbing, the producer's totals otherwise. */
3889
+ function summaryCells(trace, at, durationMs) {
3890
+ const cost = costSummary(at ? at.costUsd : trace.costUsd, trace);
3891
+ const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
3892
+ const modelCalls = at ? at.modelCalls : trace.modelCalls;
3893
+ const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
3894
+ return {
3895
+ cost,
3896
+ tokens,
3897
+ errors,
3898
+ summary: [
3899
+ ["Duration", formatSpanDuration(durationMs)],
3900
+ [
3901
+ cost.label,
3902
+ cost.value,
3903
+ cost.title
3904
+ ],
3905
+ [
3906
+ "Tokens",
3907
+ formatCount(tokens),
3908
+ `${tokens.toLocaleString()} tokens`
3909
+ ],
3910
+ [
3911
+ "LLM calls",
3912
+ modelCalls === void 0 ? "—" : modelCalls.toLocaleString(),
3913
+ modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
3914
+ ],
3915
+ ["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
3916
+ ["Errors", errors.toLocaleString()]
3917
+ ]
3918
+ };
3919
+ }
3730
3920
  /** One legend item per outcome label the agents carry, with its count; nothing when no agent has an outcome. */
3921
+ const TONE_ORDER = [
3922
+ "ok",
3923
+ "warn",
3924
+ "failed",
3925
+ "neutral"
3926
+ ];
3731
3927
  function OutcomeLegend({ agents }) {
3732
3928
  const items = /* @__PURE__ */ new Map();
3733
3929
  for (const row of agents) {
@@ -3745,7 +3941,7 @@ function OutcomeLegend({ agents }) {
3745
3941
  return /* @__PURE__ */ jsxs("ul", {
3746
3942
  className: "fw-legend",
3747
3943
  "aria-label": "Outcomes",
3748
- children: [[...items].map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
3944
+ children: [[...items].sort(([a, x], [b, y]) => TONE_ORDER.indexOf(x.tone) - TONE_ORDER.indexOf(y.tone) || a.localeCompare(b)).map(([label, item]) => /* @__PURE__ */ jsxs("li", { children: [
3749
3945
  /* @__PURE__ */ jsx("span", {
3750
3946
  className: "fw-swatch",
3751
3947
  "data-tone": "0",
@@ -3766,10 +3962,9 @@ function OutcomeLegend({ agents }) {
3766
3962
  ] })]
3767
3963
  });
3768
3964
  }
3769
- const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
3770
3965
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
3771
3966
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
3772
- function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers }) {
3967
+ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defaultCollapsed = false, defaultView = "spans", renderIcon, maxHeight, onSelect, markers, onTimeChange, agentFill = "agent", selectedId, onDeselect }) {
3773
3968
  const root = useRef(null);
3774
3969
  const search = useRef(null);
3775
3970
  const slider = useRef(null);
@@ -3850,6 +4045,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3850
4045
  collapsed
3851
4046
  ]);
3852
4047
  const slowest = view === "spans" ? slowestRow(rows, clockMs) : void 0;
4048
+ const callouts = useMemo(() => view === "agents" ? agentCallouts(agents, clockMs) : {}, [
4049
+ view,
4050
+ agents,
4051
+ clockMs
4052
+ ]);
3853
4053
  const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
3854
4054
  const resized = useRef(false);
3855
4055
  useLayoutEffect(() => {
@@ -3880,7 +4080,17 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3880
4080
  if (event.key === "+" || event.key === "=") zoomBy(.5);
3881
4081
  else if (event.key === "-" || event.key === "_") zoomBy(2);
3882
4082
  else if (event.key === "0") setZoom(null);
3883
- else if (event.key === "Escape" && card) closeCard();
4083
+ else if (event.key === "Escape" && (card || selectedId)) {
4084
+ if (card) closeCard();
4085
+ onDeselect?.();
4086
+ } else if ((event.key === "n" || event.key === "p") && markers?.length) {
4087
+ const ats = markers.map((marker) => marker.at).sort((a, b) => a - b);
4088
+ const next = event.key === "n" ? ats.find((at) => at > clockMs + 1) : [...ats].reverse().find((at) => at < clockMs - 1);
4089
+ if (next !== void 0) {
4090
+ event.preventDefault();
4091
+ scrubTo(next);
4092
+ }
4093
+ }
3884
4094
  };
3885
4095
  document.addEventListener("keydown", keydown);
3886
4096
  return () => document.removeEventListener("keydown", keydown);
@@ -3907,7 +4117,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3907
4117
  const width = current.endMs - current.startMs;
3908
4118
  const end = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
3909
4119
  if (view !== "spans" || !Number.isFinite(end) || (end - span.startMs) / Math.max(1, width) > .25) return;
3910
- const pad = Math.max((end - span.startMs) * .15, 500);
4120
+ const pad = Math.max((end - span.startMs) * .15, 500, (width / 8 - (end - span.startMs)) / 2);
3911
4121
  const target = clampWindow({
3912
4122
  startMs: span.startMs - pad,
3913
4123
  endMs: end + pad
@@ -3998,6 +4208,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3998
4208
  }
3999
4209
  };
4000
4210
  const pin = useCallback((target, element) => latest.current.pin(target, element), []);
4211
+ const selection = selectedId ?? (card?.pinned ? card.target : void 0);
4001
4212
  const closeCard = () => {
4002
4213
  const target = card?.target;
4003
4214
  setCard(null);
@@ -4007,6 +4218,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4007
4218
  setPlaying(false);
4008
4219
  setPosition(Math.min(ms, clock.limitMs()));
4009
4220
  }, [clock]);
4221
+ const timeListener = useRef(onTimeChange);
4222
+ timeListener.current = onTimeChange;
4223
+ useEffect(() => {
4224
+ timeListener.current?.(position);
4225
+ }, [position]);
4010
4226
  const togglePlay = () => {
4011
4227
  if (playing) {
4012
4228
  setPosition(clock.positionMs());
@@ -4017,31 +4233,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4017
4233
  setPlaying(true);
4018
4234
  };
4019
4235
  const scrubbing = position !== null || playing;
4020
- const at = scrubbing ? totalsAt(trace, clockMs) : void 0;
4021
- const costUsd = at ? at.costUsd : trace.costUsd;
4022
- const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
4023
- const modelCalls = at ? at.modelCalls : trace.modelCalls;
4024
- const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
4025
- const summary = [
4026
- ["Duration", formatSpanDuration(scrubbing ? clockMs : limit)],
4027
- [
4028
- trace.costBasis === "list" ? "Cost · list price" : "Cost",
4029
- costUsd === void 0 ? "Unknown" : formatCost(costUsd),
4030
- costUsd === void 0 ? "The harness recorded no cost for this trace" : trace.costBasis === "list" ? "Input and output tokens at the model catalog list price; not a bill" : void 0
4031
- ],
4032
- [
4033
- "Tokens",
4034
- formatCount(tokens),
4035
- `${tokens.toLocaleString()} tokens`
4036
- ],
4037
- [
4038
- "LLM calls",
4039
- modelCalls === void 0 ? "Unknown" : modelCalls.toLocaleString(),
4040
- modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
4041
- ],
4042
- ["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
4043
- ["Errors", errors.toLocaleString()]
4044
- ];
4236
+ const { errors, summary } = summaryCells(trace, scrubbing ? totalsAt(trace, clockMs) : void 0, scrubbing ? clockMs : limit);
4045
4237
  const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
4046
4238
  const speedup = limit > 0 ? busy / limit : 0;
4047
4239
  const done = !isLive && !trace.spans.some(spanRunning);
@@ -4059,11 +4251,37 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4059
4251
  count: 1,
4060
4252
  retry: false
4061
4253
  }]), [rows]);
4254
+ const playback = /* @__PURE__ */ jsx(PlaybackBar, {
4255
+ view,
4256
+ playing,
4257
+ limit,
4258
+ clockMs,
4259
+ speed,
4260
+ zoom,
4261
+ marks: overviewMarks,
4262
+ slider,
4263
+ timeOutput,
4264
+ followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
4265
+ onTogglePlay: togglePlay,
4266
+ onRestart: () => {
4267
+ setPosition(0);
4268
+ setPlaying(false);
4269
+ },
4270
+ onSpeed: setSpeed,
4271
+ onScrub: scrubTo,
4272
+ onFollow: () => {
4273
+ setPosition(null);
4274
+ setPlaying(false);
4275
+ },
4276
+ onZoom: zoomBy,
4277
+ onFit: () => setZoom(null)
4278
+ });
4062
4279
  return /* @__PURE__ */ jsxs("section", {
4063
4280
  ref: root,
4064
4281
  "data-flow-waterfall": true,
4065
4282
  "aria-label": label,
4066
4283
  className: `fw-root fw-root-${view}`,
4284
+ "data-selection": selectedId ? "" : void 0,
4067
4285
  style: {
4068
4286
  "--fw-name-w": `${nameWidth}px`,
4069
4287
  "--fw-pad": `${LANE_PAD}px`,
@@ -4093,11 +4311,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4093
4311
  })
4094
4312
  ]
4095
4313
  }),
4096
- view === "agents" && /* @__PURE__ */ jsx("p", {
4314
+ view === "agents" && /* @__PURE__ */ jsxs("p", {
4097
4315
  className: "fw-headline",
4098
4316
  "aria-live": "polite",
4099
- children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
4100
- /* @__PURE__ */ jsx("span", {
4317
+ children: [
4318
+ done && /* @__PURE__ */ jsx("span", {
4101
4319
  className: "fw-done-mark",
4102
4320
  "aria-label": "done",
4103
4321
  children: "✓"
@@ -4105,17 +4323,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4105
4323
  agents.length,
4106
4324
  " ",
4107
4325
  agents.length === 1 ? "agent" : "agents",
4108
- " · ",
4109
- headlineSeconds(limit),
4110
- speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
4111
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4112
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4113
- headlineSeconds(clockMs),
4114
- " · ",
4115
- tokens.toLocaleString(),
4116
- " tokens",
4117
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4118
- ] })
4326
+ done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
4327
+ ]
4119
4328
  }),
4120
4329
  /* @__PURE__ */ jsx(OutcomeLegend, { agents })
4121
4330
  ]
@@ -4129,6 +4338,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4129
4338
  type: "button",
4130
4339
  role: "radio",
4131
4340
  "aria-checked": view === option,
4341
+ disabled: view === option,
4132
4342
  className: "fw-view-button",
4133
4343
  onClick: () => {
4134
4344
  setView(option);
@@ -4170,7 +4380,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4170
4380
  })]
4171
4381
  })]
4172
4382
  }),
4173
- view === "spans" && /* @__PURE__ */ jsx("dl", {
4383
+ /* @__PURE__ */ jsx("dl", {
4174
4384
  className: "fw-summary",
4175
4385
  children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
4176
4386
  className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
@@ -4181,6 +4391,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4181
4391
  })]
4182
4392
  }, name))
4183
4393
  }),
4394
+ view === "agents" && playback,
4184
4395
  /* @__PURE__ */ jsx(FlowBody, {
4185
4396
  rows: visible,
4186
4397
  byId,
@@ -4189,15 +4400,18 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4189
4400
  maxHeight: rowsHeight,
4190
4401
  collapsed,
4191
4402
  searching: Boolean(query.trim()),
4192
- slowestId: slowest?.id,
4403
+ pauses: trace.pauses,
4404
+ slowestId: slowest?.id ?? callouts.slowestId,
4405
+ costliestId: callouts.costliestId,
4193
4406
  activeId,
4194
- selectedId: card?.pinned ? card.target : void 0,
4407
+ selectedId: selection,
4195
4408
  focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
4196
4409
  clockMs,
4197
4410
  window: win,
4198
4411
  trackPx,
4199
4412
  showCursor,
4200
4413
  markers,
4414
+ fillByAgent: agentFill === "agent",
4201
4415
  zoomed: zoom !== null,
4202
4416
  renderIcon,
4203
4417
  emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
@@ -4236,31 +4450,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4236
4450
  compact: view === "agents"
4237
4451
  })
4238
4452
  }),
4239
- /* @__PURE__ */ jsx(PlaybackBar, {
4240
- view,
4241
- playing,
4242
- limit,
4243
- clockMs,
4244
- speed,
4245
- zoom,
4246
- marks: overviewMarks,
4247
- slider,
4248
- timeOutput,
4249
- followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
4250
- onTogglePlay: togglePlay,
4251
- onRestart: () => {
4252
- setPosition(0);
4253
- setPlaying(false);
4254
- },
4255
- onSpeed: setSpeed,
4256
- onScrub: scrubTo,
4257
- onFollow: () => {
4258
- setPosition(null);
4259
- setPlaying(false);
4260
- },
4261
- onZoom: zoomBy,
4262
- onFit: () => setZoom(null)
4263
- })
4453
+ view === "spans" && playback
4264
4454
  ]
4265
4455
  });
4266
4456
  }