@tangle-network/charts 0.18.1 → 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,24 +2703,36 @@ 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 tokenCount = span.promptTokens !== void 0 || span.completionTokens !== void 0 ? (span.promptTokens ?? 0) + (span.completionTokens ?? 0) : void 0;
2667
- const cells = [[span.kind === "model" ? "Thinking" : KIND_LABEL[span.kind], duration]];
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))}`]);
2668
2723
  if (tokenCount !== void 0) cells.push(["Tokens", count(tokenCount)]);
2669
- if (span.costUsd !== void 0) cells.push(["Cost", formatCost(span.costUsd)]);
2724
+ if (cost !== void 0) cells.push(["Cost", formatCost(cost)]);
2725
+ cells.splice(6);
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;
2670
2729
  return /* @__PURE__ */ jsxs("div", {
2671
2730
  id,
2672
2731
  role: pinned ? "dialog" : "tooltip",
2673
2732
  "aria-label": pinned ? `${span.name} details` : void 0,
2674
2733
  className: "fw-card fw-card-compact",
2675
2734
  "data-tone": toneKey(who.tone),
2735
+ "data-outcome": outcome?.tone,
2676
2736
  style: {
2677
2737
  left,
2678
2738
  top
@@ -2695,7 +2755,12 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2695
2755
  className: "fw-card-who",
2696
2756
  children: who.name
2697
2757
  }),
2698
- /* @__PURE__ */ jsx("span", {
2758
+ outcome ? /* @__PURE__ */ jsx("span", {
2759
+ className: "fw-pill fw-pill-outcome",
2760
+ "data-tone": "0",
2761
+ "data-outcome": outcome.tone,
2762
+ children: outcome.label
2763
+ }) : /* @__PURE__ */ jsx("span", {
2699
2764
  className: `fw-pill fw-pill-${status.toLowerCase()}`,
2700
2765
  children: status
2701
2766
  }),
@@ -2713,13 +2778,21 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2713
2778
  })
2714
2779
  ]
2715
2780
  }),
2781
+ who.span.detail && /* @__PURE__ */ jsx("p", {
2782
+ className: "fw-card-detail",
2783
+ children: who.span.detail
2784
+ }),
2716
2785
  /* @__PURE__ */ jsx("dl", {
2717
2786
  className: "fw-card-cells",
2718
2787
  children: cells.map(([name, value]) => /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: name }), /* @__PURE__ */ jsx("dd", { children: value })] }, name))
2719
2788
  }),
2720
- /* @__PURE__ */ jsx("p", {
2789
+ step && /* @__PURE__ */ jsx("p", {
2790
+ className: "fw-card-step",
2791
+ children: step
2792
+ }),
2793
+ sentence && /* @__PURE__ */ jsx("p", {
2721
2794
  className: "fw-card-result",
2722
- children: span.output ?? span.name
2795
+ children: sentence
2723
2796
  })
2724
2797
  ]
2725
2798
  });
@@ -2792,9 +2865,10 @@ function SpanCard({ id, row, agent, clockMs, left, top, pinned, onClose, renderI
2792
2865
  children: [
2793
2866
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Duration" }), /* @__PURE__ */ jsx("dd", { children: duration })] }),
2794
2867
  /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Started" }), /* @__PURE__ */ jsxs("dd", { children: ["+", formatSpanDuration(Math.max(0, span.startMs))] })] }),
2795
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Cost" }), /* @__PURE__ */ jsx("dd", { children: span.costUsd === void 0 ? unknown : formatCost(span.costUsd) })] }),
2796
- tokens && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Tokens in" }), /* @__PURE__ */ jsx("dd", { children: count(span.promptTokens) })] }),
2797
- 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)),
2798
2872
  row.hasChildren && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("dt", { children: "Steps" }), /* @__PURE__ */ jsx("dd", { children: row.descendants })] })
2799
2873
  ]
2800
2874
  }),
@@ -2853,7 +2927,7 @@ function endLabel(row, clockMs) {
2853
2927
  }
2854
2928
  /** One trace row. Geometry comes from CSS variables, so a row re-renders only when its own state changes. */
2855
2929
  const FlowRow = memo(function FlowRow(props) {
2856
- 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;
2857
2931
  const span = row.span;
2858
2932
  const running = spanRunning(span);
2859
2933
  const agentView = view === "agents";
@@ -2869,18 +2943,26 @@ const FlowRow = memo(function FlowRow(props) {
2869
2943
  "data-span-id": row.id,
2870
2944
  "data-span-kind": kind,
2871
2945
  "data-slowest": slowest || void 0,
2946
+ "data-running": running || void 0,
2872
2947
  "aria-level": agentView ? void 0 : row.depth + 1,
2873
2948
  "aria-expanded": row.hasChildren && !agentView ? !folded : void 0,
2874
2949
  "aria-selected": selected,
2875
2950
  tabIndex: focusable ? 0 : -1,
2876
2951
  "data-tone": toneKey(row.tone),
2877
- "data-outcome": span.outcome?.tone,
2878
- className: `fw-row${active ? " fw-active" : ""}${sticky ? " fw-sticky-row" : ""}${isAgent ? " fw-agent-row" : ""}`,
2879
- 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
+ },
2880
2958
  onFocus: (event) => {
2881
2959
  if (event.target === event.currentTarget) props.onActivate(row);
2882
2960
  },
2883
- 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
+ },
2884
2966
  children: [/* @__PURE__ */ jsxs("div", {
2885
2967
  role: "gridcell",
2886
2968
  className: "fw-label",
@@ -2906,10 +2988,6 @@ const FlowRow = memo(function FlowRow(props) {
2906
2988
  className: "fw-toggle-space",
2907
2989
  "aria-hidden": true
2908
2990
  })),
2909
- agentView && /* @__PURE__ */ jsx("span", {
2910
- className: "fw-agent-dot",
2911
- "aria-hidden": true
2912
- }),
2913
2991
  !agentView && /* @__PURE__ */ jsx("span", {
2914
2992
  className: "fw-icon-tile",
2915
2993
  "aria-hidden": true,
@@ -2918,21 +2996,41 @@ const FlowRow = memo(function FlowRow(props) {
2918
2996
  /* @__PURE__ */ jsxs("span", {
2919
2997
  className: "fw-names",
2920
2998
  title: `${row.name}${span.detail ? ` · ${span.detail}` : ""} · ${KIND_LABEL[span.kind]}${running ? " · running" : ""}${row.ok ? "" : " · failed"}`,
2921
- children: [/* @__PURE__ */ jsxs("span", {
2922
- className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
2923
- children: [running && /* @__PURE__ */ jsx("span", {
2924
- className: "fw-spinner",
2925
- role: "status",
2926
- "aria-label": "running"
2927
- }), /* @__PURE__ */ jsx("span", {
2928
- className: "fw-name-text",
2929
- children: row.name
2930
- })]
2931
- }), agentView && renderIcon && /* @__PURE__ */ jsx("span", {
2932
- className: "fw-detail-icon",
2933
- "aria-hidden": true,
2934
- children: renderIcon(span)
2935
- })]
2999
+ children: [
3000
+ /* @__PURE__ */ jsxs("span", {
3001
+ className: `fw-span-name${isAgent || row.hasChildren ? " fw-strong" : ""}`,
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
+ ]
3023
+ }),
3024
+ agentView && span.detail && /* @__PURE__ */ jsx("span", {
3025
+ className: "fw-subtitle",
3026
+ children: span.detail
3027
+ }),
3028
+ agentView && renderIcon && /* @__PURE__ */ jsx("span", {
3029
+ className: "fw-detail-icon",
3030
+ "aria-hidden": true,
3031
+ children: renderIcon(span)
3032
+ })
3033
+ ]
2936
3034
  }),
2937
3035
  folded && /* @__PURE__ */ jsxs("span", {
2938
3036
  className: "fw-count",
@@ -2948,11 +3046,19 @@ const FlowRow = memo(function FlowRow(props) {
2948
3046
  role: "gridcell",
2949
3047
  className: "fw-lane",
2950
3048
  "data-waterfall-track": true,
2951
- "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" : ""}`,
2952
3050
  children: invalid ? /* @__PURE__ */ jsx("span", {
2953
3051
  className: "fw-invalid",
2954
3052
  children: "—"
2955
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}`)),
2956
3062
  /* @__PURE__ */ jsxs("span", {
2957
3063
  className: `fw-bar fw-k-${isAgent ? "pipeline" : kind}${row.approx ? " fw-approx" : ""}${marks.length ? " fw-has-marks" : ""}${row.hasAgents ? " fw-has-agents" : ""}`,
2958
3064
  "data-tone": toneKey(row.tone),
@@ -2976,23 +3082,30 @@ const FlowRow = memo(function FlowRow(props) {
2976
3082
  events: props,
2977
3083
  agentView
2978
3084
  }, `${mark.id}-${i}`)),
2979
- !agentView && /* @__PURE__ */ jsxs("span", {
3085
+ /* @__PURE__ */ jsxs("span", {
2980
3086
  className: "fw-end",
2981
- style: at(span.startMs, running ? 0x9184e72a000 : span.endMs),
2982
- children: [/* @__PURE__ */ jsx("span", {
2983
- className: "fw-duration",
2984
- children: label
2985
- }), slowest && /* @__PURE__ */ jsx("span", {
2986
- className: "fw-badge",
2987
- children: "Slowest"
2988
- })]
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
+ ]
2989
3102
  })
2990
3103
  ] })
2991
3104
  })]
2992
3105
  });
2993
- }, (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)));
2994
3107
  /** An elbow connector between two rows at one moment, drawn in the row's tone; appears when the clock reaches it. */
2995
- function LinkMark({ link, rowHeight }) {
3108
+ function LinkMark({ link, rowHeight, hot }) {
2996
3109
  const upper = Math.min(link.from, link.to), lower = Math.max(link.from, link.to);
2997
3110
  const style = {
2998
3111
  ...at(link.atMs, link.atMs),
@@ -3000,13 +3113,55 @@ function LinkMark({ link, rowHeight }) {
3000
3113
  height: (lower - upper) * rowHeight - 5
3001
3114
  };
3002
3115
  return /* @__PURE__ */ jsx("span", {
3003
- className: `fw-link fw-link-${link.dir}`,
3116
+ className: `fw-link fw-link-${link.dir}${hot ? " fw-link-hot" : ""}`,
3004
3117
  "data-tone": toneKey(link.tone),
3005
3118
  style,
3006
3119
  "aria-hidden": true
3007
3120
  });
3008
3121
  }
3009
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
3010
3165
  //#region src/react/flow-waterfall-time.ts
3011
3166
  /** Time-axis geometry for the trace inspector: clock-aligned steps, labels and the zoom window. */
3012
3167
  const STEPS = [
@@ -3086,7 +3241,7 @@ function clampWindow(view, limitMs) {
3086
3241
  //#region src/react/flow-waterfall-body.tsx
3087
3242
  const ROW_HEIGHT = {
3088
3243
  spans: 36,
3089
- agents: 52
3244
+ agents: 40
3090
3245
  };
3091
3246
  const OVERSCAN = 8;
3092
3247
  /** Rows re-render once per CHUNK rows scrolled; overscan covers the rows in between. */
@@ -3159,6 +3314,7 @@ function FlowBody(props) {
3159
3314
  view
3160
3315
  ]);
3161
3316
  const index = useMemo(() => new Map(rows.map((row, i) => [row.id, i])), [rows]);
3317
+ const motion = useRowMotion(rows);
3162
3318
  const msPerPx = (win.endMs - win.startMs) / Math.max(1, trackPx);
3163
3319
  const tickEnd = win.startMs + (trackPx + (view === "spans" ? 72 : 0)) * msPerPx;
3164
3320
  const ticks = clockTicks(win.startMs, tickEnd, Math.max(1, trackPx + (view === "spans" ? 72 : 0)));
@@ -3288,7 +3444,7 @@ function FlowBody(props) {
3288
3444
  handle.addEventListener("pointerup", up);
3289
3445
  };
3290
3446
  const axis = /* @__PURE__ */ jsxs("div", {
3291
- className: `fw-axis fw-axis-${view === "agents" ? "bottom" : "top"}`,
3447
+ className: "fw-axis fw-axis-top",
3292
3448
  children: [/* @__PURE__ */ jsxs("div", {
3293
3449
  className: "fw-axis-label",
3294
3450
  children: [view === "spans" && /* @__PURE__ */ jsx(Fragment, { children: props.onExpandAll && /* @__PURE__ */ jsxs("span", {
@@ -3327,14 +3483,27 @@ function FlowBody(props) {
3327
3483
  ref: props.onTrack,
3328
3484
  onPointerDown: scrub,
3329
3485
  title: "Drag to scrub",
3330
- children: [ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3331
- className: "fw-tick",
3332
- style: { "--s": tick },
3333
- children: formatClock(tick)
3334
- }, tick)), showCursor && /* @__PURE__ */ jsx("span", {
3335
- className: "fw-cursor-handle",
3336
- "aria-hidden": true
3337
- })]
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
+ ]
3338
3507
  })]
3339
3508
  });
3340
3509
  const row = (item, i, isSticky = false) => /* @__PURE__ */ jsx(FlowRow, {
@@ -3344,12 +3513,15 @@ function FlowBody(props) {
3344
3513
  collapsed: collapsed.has(item.id),
3345
3514
  searching,
3346
3515
  slowest: item.id === slowestId,
3516
+ costliest: item.id === props.costliestId,
3347
3517
  active: item.id === activeId,
3348
3518
  focusable: !isSticky && (item.id === activeId || (activeId === void 0 || !index.has(activeId)) && i === 0),
3349
3519
  selected: item.id === selectedId,
3350
3520
  sticky: isSticky,
3521
+ motion: isSticky ? void 0 : motion.get(item.id),
3351
3522
  clockMs,
3352
3523
  renderIcon: props.renderIcon,
3524
+ fillByAgent: props.fillByAgent,
3353
3525
  onToggle: props.onToggle,
3354
3526
  onInspect: props.onInspect,
3355
3527
  onLeave: props.onLeave,
@@ -3359,82 +3531,91 @@ function FlowBody(props) {
3359
3531
  return /* @__PURE__ */ jsxs("div", {
3360
3532
  className: `fw-body fw-view-${view}${zoomed ? " fw-zoomed" : ""}`,
3361
3533
  ref: surface,
3362
- children: [
3363
- view === "spans" && axis,
3364
- /* @__PURE__ */ jsxs("div", {
3365
- className: "fw-viewport",
3366
- children: [
3367
- /* @__PURE__ */ jsx("div", {
3368
- className: "fw-gridlayer",
3369
- "aria-hidden": true,
3370
- children: ticks.map((tick) => /* @__PURE__ */ jsx("span", {
3371
- className: "fw-gridline",
3372
- style: { "--s": tick }
3373
- }, tick))
3374
- }),
3375
- /* @__PURE__ */ jsxs("div", {
3376
- ref: scroller,
3377
- role: "treegrid",
3378
- "aria-label": "Trace timeline",
3379
- "aria-rowcount": rows.length,
3380
- "aria-multiselectable": false,
3381
- tabIndex: -1,
3382
- 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",
3383
3592
  style: {
3384
- height,
3385
- maxHeight
3386
- },
3387
- onKeyDown: keydown,
3388
- onScroll: (event) => {
3389
- const next = Math.floor(event.currentTarget.scrollTop / rowHeight / CHUNK);
3390
- if (next !== chunk) setChunk(next);
3593
+ "--s": pause.startMs,
3594
+ "--e": pause.endMs
3391
3595
  },
3392
- children: [/* @__PURE__ */ jsxs("div", {
3393
- className: "fw-canvas",
3394
- style: { height: rows.length * rowHeight },
3395
- children: [/* @__PURE__ */ jsx("div", {
3396
- className: "fw-links",
3397
- "aria-hidden": true,
3398
- children: links.filter((link) => Math.max(link.from, link.to) >= first && Math.min(link.from, link.to) <= last).map((link) => /* @__PURE__ */ jsx(LinkMark, {
3399
- link,
3400
- rowHeight
3401
- }, link.id))
3402
- }), rows.slice(first, last).map((item, offset) => row(item, first + offset))]
3403
- }), rows.length === 0 && /* @__PURE__ */ jsx("p", {
3404
- role: "status",
3405
- className: "fw-empty",
3406
- children: props.emptyText
3407
- })]
3408
- }),
3409
- view === "spans" && /* @__PURE__ */ jsx(StickyRows, {
3410
- scroller,
3411
- rows,
3412
- index,
3413
- rowHeight,
3414
- render: row
3415
- }),
3416
- props.markers?.length ? /* @__PURE__ */ jsx("div", {
3417
- className: "fw-marker-layer",
3418
- children: props.markers.map((marker) => /* @__PURE__ */ jsx("span", {
3419
- className: "fw-marker",
3420
- "data-tone": marker.tone ?? "neutral",
3421
- style: { "--s": marker.at },
3422
- title: marker.label,
3423
- children: markerLabels.has(marker) && /* @__PURE__ */ jsx("span", {
3424
- className: `fw-marker-label${markerLabels.get(marker) === "left" ? " fw-marker-label-left" : ""}`,
3425
- children: marker.label
3426
- })
3427
- }, `${marker.at}-${marker.label}`))
3428
- }) : null,
3429
- showCursor && /* @__PURE__ */ jsx("div", {
3430
- className: "fw-cursor-layer",
3431
- "aria-hidden": true,
3432
- children: /* @__PURE__ */ jsx("span", { className: "fw-cursor" })
3433
- })
3434
- ]
3435
- }),
3436
- view === "agents" && axis
3437
- ]
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
+ })]
3438
3619
  });
3439
3620
  }
3440
3621
  /** Ancestors of the first visible row stay pinned while their children scroll beneath them. This layer follows
@@ -3698,13 +3879,51 @@ function PlaybackBar({ view, playing, limit, clockMs, speed, zoom, marks, slider
3698
3879
  const LANE_PAD = 12;
3699
3880
  const END_LABEL_GUTTER = {
3700
3881
  spans: 120,
3701
- agents: 16
3882
+ agents: 176
3702
3883
  };
3703
3884
  const NAME_WIDTH = {
3704
3885
  spans: 320,
3705
3886
  agents: 240
3706
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
+ }
3707
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
+ ];
3708
3927
  function OutcomeLegend({ agents }) {
3709
3928
  const items = /* @__PURE__ */ new Map();
3710
3929
  for (const row of agents) {
@@ -3722,7 +3941,7 @@ function OutcomeLegend({ agents }) {
3722
3941
  return /* @__PURE__ */ jsxs("ul", {
3723
3942
  className: "fw-legend",
3724
3943
  "aria-label": "Outcomes",
3725
- 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: [
3726
3945
  /* @__PURE__ */ jsx("span", {
3727
3946
  className: "fw-swatch",
3728
3947
  "data-tone": "0",
@@ -3743,10 +3962,9 @@ function OutcomeLegend({ agents }) {
3743
3962
  ] })]
3744
3963
  });
3745
3964
  }
3746
- const headlineSeconds = (ms) => ms < 6e4 ? `${(ms / 1e3).toFixed(1)}s` : formatSpanDuration(ms);
3747
3965
  /** Shared trace inspector: the agent timeline and the span waterfall over one clock.
3748
3966
  * Data remains producer-owned; playback, zoom and folding change presentation only. */
3749
- 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 }) {
3750
3968
  const root = useRef(null);
3751
3969
  const search = useRef(null);
3752
3970
  const slider = useRef(null);
@@ -3827,6 +4045,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3827
4045
  collapsed
3828
4046
  ]);
3829
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
+ ]);
3830
4053
  const rowsHeight = maxHeight ?? (view === "agents" ? ROW_HEIGHT.agents * 20 : 640);
3831
4054
  const resized = useRef(false);
3832
4055
  useLayoutEffect(() => {
@@ -3857,7 +4080,17 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3857
4080
  if (event.key === "+" || event.key === "=") zoomBy(.5);
3858
4081
  else if (event.key === "-" || event.key === "_") zoomBy(2);
3859
4082
  else if (event.key === "0") setZoom(null);
3860
- 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
+ }
3861
4094
  };
3862
4095
  document.addEventListener("keydown", keydown);
3863
4096
  return () => document.removeEventListener("keydown", keydown);
@@ -3884,7 +4117,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3884
4117
  const width = current.endMs - current.startMs;
3885
4118
  const end = spanRunning(span) ? Math.max(span.endMs, clockMs) : span.endMs;
3886
4119
  if (view !== "spans" || !Number.isFinite(end) || (end - span.startMs) / Math.max(1, width) > .25) return;
3887
- 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);
3888
4121
  const target = clampWindow({
3889
4122
  startMs: span.startMs - pad,
3890
4123
  endMs: end + pad
@@ -3975,6 +4208,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3975
4208
  }
3976
4209
  };
3977
4210
  const pin = useCallback((target, element) => latest.current.pin(target, element), []);
4211
+ const selection = selectedId ?? (card?.pinned ? card.target : void 0);
3978
4212
  const closeCard = () => {
3979
4213
  const target = card?.target;
3980
4214
  setCard(null);
@@ -3984,6 +4218,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3984
4218
  setPlaying(false);
3985
4219
  setPosition(Math.min(ms, clock.limitMs()));
3986
4220
  }, [clock]);
4221
+ const timeListener = useRef(onTimeChange);
4222
+ timeListener.current = onTimeChange;
4223
+ useEffect(() => {
4224
+ timeListener.current?.(position);
4225
+ }, [position]);
3987
4226
  const togglePlay = () => {
3988
4227
  if (playing) {
3989
4228
  setPosition(clock.positionMs());
@@ -3994,31 +4233,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
3994
4233
  setPlaying(true);
3995
4234
  };
3996
4235
  const scrubbing = position !== null || playing;
3997
- const at = scrubbing ? totalsAt(trace, clockMs) : void 0;
3998
- const costUsd = at ? at.costUsd : trace.costUsd;
3999
- const tokens = at ? at.tokens : trace.promptTokens + trace.completionTokens;
4000
- const modelCalls = at ? at.modelCalls : trace.modelCalls;
4001
- const errors = at ? at.errors : trace.errors ?? trace.spans.filter(spanFailed).length;
4002
- const summary = [
4003
- ["Duration", formatSpanDuration(scrubbing ? clockMs : limit)],
4004
- [
4005
- trace.costBasis === "list" ? "Cost · list price" : "Cost",
4006
- costUsd === void 0 ? "Unknown" : formatCost(costUsd),
4007
- 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
4008
- ],
4009
- [
4010
- "Tokens",
4011
- formatCount(tokens),
4012
- `${tokens.toLocaleString()} tokens`
4013
- ],
4014
- [
4015
- "LLM calls",
4016
- modelCalls === void 0 ? "Unknown" : modelCalls.toLocaleString(),
4017
- modelCalls === void 0 ? "Model output windows do not establish the number of LLM requests" : void 0
4018
- ],
4019
- ["Tool calls", (at ? at.toolCalls : trace.toolCalls).toLocaleString()],
4020
- ["Errors", errors.toLocaleString()]
4021
- ];
4236
+ const { errors, summary } = summaryCells(trace, scrubbing ? totalsAt(trace, clockMs) : void 0, scrubbing ? clockMs : limit);
4022
4237
  const busy = agents.reduce((sum, row) => sum + busyMs(row, limit), 0);
4023
4238
  const speedup = limit > 0 ? busy / limit : 0;
4024
4239
  const done = !isLive && !trace.spans.some(spanRunning);
@@ -4036,11 +4251,37 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4036
4251
  count: 1,
4037
4252
  retry: false
4038
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
+ });
4039
4279
  return /* @__PURE__ */ jsxs("section", {
4040
4280
  ref: root,
4041
4281
  "data-flow-waterfall": true,
4042
4282
  "aria-label": label,
4043
4283
  className: `fw-root fw-root-${view}`,
4284
+ "data-selection": selectedId ? "" : void 0,
4044
4285
  style: {
4045
4286
  "--fw-name-w": `${nameWidth}px`,
4046
4287
  "--fw-pad": `${LANE_PAD}px`,
@@ -4070,11 +4311,11 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4070
4311
  })
4071
4312
  ]
4072
4313
  }),
4073
- view === "agents" && /* @__PURE__ */ jsx("p", {
4314
+ view === "agents" && /* @__PURE__ */ jsxs("p", {
4074
4315
  className: "fw-headline",
4075
4316
  "aria-live": "polite",
4076
- children: done ? /* @__PURE__ */ jsxs(Fragment, { children: [
4077
- /* @__PURE__ */ jsx("span", {
4317
+ children: [
4318
+ done && /* @__PURE__ */ jsx("span", {
4078
4319
  className: "fw-done-mark",
4079
4320
  "aria-label": "done",
4080
4321
  children: "✓"
@@ -4082,17 +4323,8 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4082
4323
  agents.length,
4083
4324
  " ",
4084
4325
  agents.length === 1 ? "agent" : "agents",
4085
- " · ",
4086
- headlineSeconds(limit),
4087
- speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : "",
4088
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4089
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4090
- headlineSeconds(clockMs),
4091
- " · ",
4092
- tokens.toLocaleString(),
4093
- " tokens",
4094
- trace.costUsd !== void 0 ? ` · ${formatCost(trace.costUsd)}` : ""
4095
- ] })
4326
+ done && speedup >= 1.05 ? ` · ${speedup.toFixed(1)}x parallel` : ""
4327
+ ]
4096
4328
  }),
4097
4329
  /* @__PURE__ */ jsx(OutcomeLegend, { agents })
4098
4330
  ]
@@ -4106,6 +4338,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4106
4338
  type: "button",
4107
4339
  role: "radio",
4108
4340
  "aria-checked": view === option,
4341
+ disabled: view === option,
4109
4342
  className: "fw-view-button",
4110
4343
  onClick: () => {
4111
4344
  setView(option);
@@ -4147,7 +4380,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4147
4380
  })]
4148
4381
  })]
4149
4382
  }),
4150
- view === "spans" && /* @__PURE__ */ jsx("dl", {
4383
+ /* @__PURE__ */ jsx("dl", {
4151
4384
  className: "fw-summary",
4152
4385
  children: summary.map(([name, value, title]) => /* @__PURE__ */ jsxs("div", {
4153
4386
  className: name === "Errors" && errors > 0 ? "fw-has-errors" : void 0,
@@ -4158,6 +4391,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4158
4391
  })]
4159
4392
  }, name))
4160
4393
  }),
4394
+ view === "agents" && playback,
4161
4395
  /* @__PURE__ */ jsx(FlowBody, {
4162
4396
  rows: visible,
4163
4397
  byId,
@@ -4166,14 +4400,18 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4166
4400
  maxHeight: rowsHeight,
4167
4401
  collapsed,
4168
4402
  searching: Boolean(query.trim()),
4169
- slowestId: slowest?.id,
4403
+ pauses: trace.pauses,
4404
+ slowestId: slowest?.id ?? callouts.slowestId,
4405
+ costliestId: callouts.costliestId,
4170
4406
  activeId,
4171
- selectedId: card?.pinned ? card.target : void 0,
4407
+ selectedId: selection,
4408
+ focusId: cardAgent?.id ?? (view === "agents" ? activeId : void 0),
4172
4409
  clockMs,
4173
4410
  window: win,
4174
4411
  trackPx,
4175
4412
  showCursor,
4176
4413
  markers,
4414
+ fillByAgent: agentFill === "agent",
4177
4415
  zoomed: zoom !== null,
4178
4416
  renderIcon,
4179
4417
  emptyText: rows.length === 0 ? "No spans yet. Waiting for trace events." : `No matching steps for “${query}”.`,
@@ -4212,31 +4450,7 @@ function FlowWaterfall({ trace, live, startedAt, label = "Trace waterfall", defa
4212
4450
  compact: view === "agents"
4213
4451
  })
4214
4452
  }),
4215
- /* @__PURE__ */ jsx(PlaybackBar, {
4216
- view,
4217
- playing,
4218
- limit,
4219
- clockMs,
4220
- speed,
4221
- zoom,
4222
- marks: overviewMarks,
4223
- slider,
4224
- timeOutput,
4225
- followLabel: position === null ? void 0 : isLive ? "Follow live" : "Show full trace",
4226
- onTogglePlay: togglePlay,
4227
- onRestart: () => {
4228
- setPosition(0);
4229
- setPlaying(false);
4230
- },
4231
- onSpeed: setSpeed,
4232
- onScrub: scrubTo,
4233
- onFollow: () => {
4234
- setPosition(null);
4235
- setPlaying(false);
4236
- },
4237
- onZoom: zoomBy,
4238
- onFit: () => setZoom(null)
4239
- })
4453
+ view === "spans" && playback
4240
4454
  ]
4241
4455
  });
4242
4456
  }