@workflow/web-shared 5.0.0-beta.1 → 5.0.0-beta.3

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.
Files changed (194) hide show
  1. package/README.md +1 -1
  2. package/dist/components/error-boundary.js.map +1 -1
  3. package/dist/components/event-list-view.d.ts +3 -1
  4. package/dist/components/event-list-view.d.ts.map +1 -1
  5. package/dist/components/event-list-view.js +53 -53
  6. package/dist/components/event-list-view.js.map +1 -1
  7. package/dist/components/hook-actions.js.map +1 -1
  8. package/dist/components/index.d.ts +3 -1
  9. package/dist/components/index.d.ts.map +1 -1
  10. package/dist/components/index.js +3 -1
  11. package/dist/components/index.js.map +1 -1
  12. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  13. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  15. package/dist/components/new-trace-viewer/components/copy-button.js.map +1 -0
  16. package/dist/components/new-trace-viewer/components/event-list.d.ts +8 -0
  17. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  18. package/dist/components/new-trace-viewer/components/event-list.js +35 -0
  19. package/dist/components/new-trace-viewer/components/event-list.js.map +1 -0
  20. package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
  21. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  22. package/dist/components/new-trace-viewer/components/split-pane.js +100 -0
  23. package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -0
  24. package/dist/components/new-trace-viewer/components/timeline.d.ts +32 -0
  25. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  26. package/dist/components/new-trace-viewer/components/timeline.js +134 -0
  27. package/dist/components/new-trace-viewer/components/timeline.js.map +1 -0
  28. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  29. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  30. package/dist/components/new-trace-viewer/context.js +49 -0
  31. package/dist/components/new-trace-viewer/context.js.map +1 -0
  32. package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
  33. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
  34. package/dist/components/new-trace-viewer/detail-panel.js +11 -0
  35. package/dist/components/new-trace-viewer/detail-panel.js.map +1 -0
  36. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  37. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  38. package/dist/components/new-trace-viewer/icons.js +15 -0
  39. package/dist/components/new-trace-viewer/icons.js.map +1 -0
  40. package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
  41. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/trace-viewer.js +329 -0
  43. package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -0
  44. package/dist/components/new-trace-viewer/types.d.ts +1 -0
  45. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  46. package/dist/components/new-trace-viewer/types.js +2 -0
  47. package/dist/components/new-trace-viewer/types.js.map +1 -0
  48. package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
  49. package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
  50. package/dist/components/new-trace-viewer/util/constants.js +2 -0
  51. package/dist/components/new-trace-viewer/util/constants.js.map +1 -0
  52. package/dist/components/new-trace-viewer/utils.d.ts +47 -0
  53. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/utils.js +358 -0
  55. package/dist/components/new-trace-viewer/utils.js.map +1 -0
  56. package/dist/components/run-trace-view.d.ts +2 -1
  57. package/dist/components/run-trace-view.d.ts.map +1 -1
  58. package/dist/components/run-trace-view.js +2 -2
  59. package/dist/components/run-trace-view.js.map +1 -1
  60. package/dist/components/sidebar/attribute-panel.d.ts +7 -1
  61. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  62. package/dist/components/sidebar/attribute-panel.js +76 -47
  63. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  64. package/dist/components/sidebar/conversation-view.js.map +1 -1
  65. package/dist/components/sidebar/copyable-data-block.js +1 -1
  66. package/dist/components/sidebar/copyable-data-block.js.map +1 -1
  67. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  68. package/dist/components/sidebar/detail-card.js +1 -1
  69. package/dist/components/sidebar/detail-card.js.map +1 -1
  70. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  71. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  72. package/dist/components/sidebar/entity-detail-panel.js +16 -36
  73. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  74. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  75. package/dist/components/sidebar/events-list.js +2 -2
  76. package/dist/components/sidebar/events-list.js.map +1 -1
  77. package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
  78. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  79. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  80. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  81. package/dist/components/sidebar/sidebar-data-context.js.map +1 -0
  82. package/dist/components/stream-viewer.d.ts +1 -1
  83. package/dist/components/stream-viewer.d.ts.map +1 -1
  84. package/dist/components/stream-viewer.js +2 -26
  85. package/dist/components/stream-viewer.js.map +1 -1
  86. package/dist/components/trace-viewer/components/map.js.map +1 -1
  87. package/dist/components/trace-viewer/components/markers.js.map +1 -1
  88. package/dist/components/trace-viewer/components/node.js.map +1 -1
  89. package/dist/components/trace-viewer/components/search-input.js.map +1 -1
  90. package/dist/components/trace-viewer/components/search.js.map +1 -1
  91. package/dist/components/trace-viewer/components/span-content.js.map +1 -1
  92. package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
  93. package/dist/components/trace-viewer/components/span-segments.js.map +1 -1
  94. package/dist/components/trace-viewer/components/span-strategies.js.map +1 -1
  95. package/dist/components/trace-viewer/components/ui.js.map +1 -1
  96. package/dist/components/trace-viewer/components/zoom-button.js.map +1 -1
  97. package/dist/components/trace-viewer/components/zoom-icons.js.map +1 -1
  98. package/dist/components/trace-viewer/context.js.map +1 -1
  99. package/dist/components/trace-viewer/index.js.map +1 -1
  100. package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
  101. package/dist/components/trace-viewer/types.js.map +1 -1
  102. package/dist/components/trace-viewer/util/constants.js.map +1 -1
  103. package/dist/components/trace-viewer/util/scrollbar-width.js.map +1 -1
  104. package/dist/components/trace-viewer/util/timing.js.map +1 -1
  105. package/dist/components/trace-viewer/util/tree.js.map +1 -1
  106. package/dist/components/trace-viewer/util/use-immediate-style.js.map +1 -1
  107. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
  108. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +1 -1
  109. package/dist/components/trace-viewer/worker.js.map +1 -1
  110. package/dist/components/trace-viewer-new.d.ts +10 -0
  111. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  112. package/dist/components/trace-viewer-new.js +23 -0
  113. package/dist/components/trace-viewer-new.js.map +1 -0
  114. package/dist/components/ui/alert.js.map +1 -1
  115. package/dist/components/ui/card.js.map +1 -1
  116. package/dist/components/ui/data-inspector.d.ts +38 -1
  117. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  118. package/dist/components/ui/data-inspector.js +211 -21
  119. package/dist/components/ui/data-inspector.js.map +1 -1
  120. package/dist/components/ui/decrypt-button.js.map +1 -1
  121. package/dist/components/ui/error-card.js.map +1 -1
  122. package/dist/components/ui/error-stack-block.js.map +1 -1
  123. package/dist/components/ui/inspector-theme.d.ts +9 -10
  124. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  125. package/dist/components/ui/inspector-theme.js +38 -71
  126. package/dist/components/ui/inspector-theme.js.map +1 -1
  127. package/dist/components/ui/load-more-button.js.map +1 -1
  128. package/dist/components/ui/menu-dropdown.js.map +1 -1
  129. package/dist/components/ui/skeleton.js.map +1 -1
  130. package/dist/components/ui/spinner.js.map +1 -1
  131. package/dist/components/ui/timestamp-tooltip.js.map +1 -1
  132. package/dist/components/workflow-trace-view.d.ts +3 -1
  133. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  134. package/dist/components/workflow-trace-view.js +9 -4
  135. package/dist/components/workflow-trace-view.js.map +1 -1
  136. package/dist/components/workflow-traces/event-colors.js.map +1 -1
  137. package/dist/components/workflow-traces/trace-colors.js.map +1 -1
  138. package/dist/components/workflow-traces/trace-span-construction.js +1 -1
  139. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  140. package/dist/components/workflow-traces/trace-time-utils.js.map +1 -1
  141. package/dist/hooks/use-dark-mode.js.map +1 -1
  142. package/dist/index.d.ts +2 -1
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +1 -1
  145. package/dist/index.js.map +1 -1
  146. package/dist/lib/event-analysis.js.map +1 -1
  147. package/dist/lib/event-materialization.js.map +1 -1
  148. package/dist/lib/hydration.d.ts +8 -1
  149. package/dist/lib/hydration.d.ts.map +1 -1
  150. package/dist/lib/hydration.js +32 -2
  151. package/dist/lib/hydration.js.map +1 -1
  152. package/dist/lib/stream-display.d.ts +18 -0
  153. package/dist/lib/stream-display.d.ts.map +1 -0
  154. package/dist/lib/stream-display.js +30 -0
  155. package/dist/lib/stream-display.js.map +1 -0
  156. package/dist/lib/toast.js.map +1 -1
  157. package/dist/lib/trace-builder.js.map +1 -1
  158. package/dist/lib/utils.d.ts +7 -9
  159. package/dist/lib/utils.d.ts.map +1 -1
  160. package/dist/lib/utils.js +17 -22
  161. package/dist/lib/utils.js.map +1 -1
  162. package/dist/styles.css +470 -1
  163. package/package.json +4 -3
  164. package/src/components/event-list-view.tsx +181 -173
  165. package/src/components/index.ts +11 -1
  166. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  167. package/src/components/new-trace-viewer/components/event-list.tsx +109 -0
  168. package/src/components/new-trace-viewer/components/split-pane.tsx +186 -0
  169. package/src/components/new-trace-viewer/components/timeline.tsx +378 -0
  170. package/src/components/new-trace-viewer/context.tsx +94 -0
  171. package/src/components/new-trace-viewer/detail-panel.tsx +57 -0
  172. package/src/components/new-trace-viewer/icons.tsx +79 -0
  173. package/src/components/new-trace-viewer/trace-viewer.tsx +585 -0
  174. package/src/components/new-trace-viewer/types.ts +0 -0
  175. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  176. package/src/components/new-trace-viewer/utils.ts +524 -0
  177. package/src/components/run-trace-view.tsx +3 -0
  178. package/src/components/sidebar/attribute-panel.tsx +200 -138
  179. package/src/components/sidebar/copyable-data-block.tsx +1 -1
  180. package/src/components/sidebar/detail-card.tsx +1 -19
  181. package/src/components/sidebar/entity-detail-panel.tsx +91 -147
  182. package/src/components/sidebar/events-list.tsx +9 -6
  183. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  184. package/src/components/stream-viewer.tsx +18 -40
  185. package/src/components/trace-viewer-new.tsx +49 -0
  186. package/src/components/ui/data-inspector.tsx +436 -31
  187. package/src/components/ui/inspector-theme.ts +39 -81
  188. package/src/components/workflow-trace-view.tsx +12 -2
  189. package/src/components/workflow-traces/trace-span-construction.ts +1 -1
  190. package/src/index.ts +2 -0
  191. package/src/lib/hydration.ts +37 -0
  192. package/src/lib/stream-display.ts +48 -0
  193. package/src/lib/utils.ts +18 -23
  194. package/src/styles.css +470 -1
@@ -0,0 +1,524 @@
1
+ import type { Span, SpanEvent } from '../trace-viewer/types';
2
+ import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Root bounds
6
+ // ---------------------------------------------------------------------------
7
+
8
+ export interface RootBounds {
9
+ startTime: number;
10
+ endTime: number;
11
+ duration: number;
12
+ }
13
+
14
+ export function computeRootBounds(spans: Span[]): RootBounds {
15
+ let minStart = Number.POSITIVE_INFINITY;
16
+ let maxEnd = Number.NEGATIVE_INFINITY;
17
+
18
+ for (const span of spans) {
19
+ const s = getHighResInMs(span.startTime);
20
+ const e = getHighResInMs(span.endTime);
21
+ if (s < minStart) minStart = s;
22
+ if (e > maxEnd) maxEnd = e;
23
+ }
24
+
25
+ if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) {
26
+ return { startTime: 0, endTime: 1, duration: 1 };
27
+ }
28
+
29
+ const duration = Math.max(maxEnd - minStart, 1);
30
+ return { startTime: minStart, endTime: maxEnd, duration };
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Time compression
35
+ // ---------------------------------------------------------------------------
36
+
37
+ export interface TimeCompression {
38
+ toVisual(time: number): number;
39
+ fromVisual(fraction: number): number;
40
+ isCompressed: boolean;
41
+ }
42
+
43
+ export function buildTimeCompression(
44
+ _spans: Span[],
45
+ viewStart: number,
46
+ viewEnd: number
47
+ ): TimeCompression {
48
+ const range = viewEnd - viewStart;
49
+
50
+ return {
51
+ isCompressed: false,
52
+ toVisual(time: number): number {
53
+ if (range <= 0) return 0;
54
+ return Math.min(Math.max((time - viewStart) / range, 0), 1);
55
+ },
56
+ fromVisual(fraction: number): number {
57
+ return viewStart + fraction * range;
58
+ },
59
+ };
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Time markers
64
+ // ---------------------------------------------------------------------------
65
+
66
+ export interface TimeMarker {
67
+ position: number;
68
+ label: string;
69
+ }
70
+
71
+ const NICE_INTERVALS = [
72
+ 1, 2, 5, 10, 20, 50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000,
73
+ 50_000, 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000,
74
+ 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000,
75
+ 1_000_000_000, 2_000_000_000, 5_000_000_000,
76
+ ];
77
+
78
+ const MAX_MARKERS = 8;
79
+
80
+ function pickInterval(viewDuration: number, maxTicks: number): number {
81
+ for (const interval of NICE_INTERVALS) {
82
+ if (viewDuration / interval <= maxTicks) return interval;
83
+ }
84
+ return NICE_INTERVALS[NICE_INTERVALS.length - 1];
85
+ }
86
+
87
+ export function computeTimeMarkers(
88
+ viewDuration: number,
89
+ offset: number
90
+ ): TimeMarker[] {
91
+ if (viewDuration <= 0) return [];
92
+
93
+ const maxTicks = 6;
94
+ const interval = pickInterval(viewDuration, maxTicks);
95
+
96
+ const firstTick = Math.ceil(offset / interval) * interval;
97
+ const markers: TimeMarker[] = [];
98
+
99
+ for (let t = firstTick; t <= offset + viewDuration; t += interval) {
100
+ const position = (t - offset) / viewDuration;
101
+ if (position < -0.01 || position > 1.01) continue;
102
+ markers.push({
103
+ position: Math.min(Math.max(position, 0), 1),
104
+ label: formatDuration(Math.abs(t), true),
105
+ });
106
+ if (markers.length >= MAX_MARKERS) break;
107
+ }
108
+
109
+ return markers;
110
+ }
111
+
112
+ export function computeCompressedTimeMarkers(
113
+ compression: TimeCompression,
114
+ viewStart: number,
115
+ viewEnd: number,
116
+ rootStart: number
117
+ ): TimeMarker[] {
118
+ const viewDuration = viewEnd - viewStart;
119
+ if (viewDuration <= 0) return [];
120
+
121
+ const maxTicks = 6;
122
+ const interval = pickInterval(viewDuration, maxTicks);
123
+ const offset = viewStart - rootStart;
124
+
125
+ const firstTick = Math.ceil(offset / interval) * interval;
126
+ const markers: TimeMarker[] = [];
127
+
128
+ for (let t = firstTick; t <= offset + viewDuration; t += interval) {
129
+ const absTime = rootStart + t;
130
+ const position = compression.toVisual(absTime);
131
+ if (position < -0.01 || position > 1.01) continue;
132
+ markers.push({
133
+ position: Math.min(Math.max(position, 0), 1),
134
+ label: formatDuration(Math.abs(t), true),
135
+ });
136
+ if (markers.length >= MAX_MARKERS) break;
137
+ }
138
+
139
+ return markers;
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Span gaps — time deltas between consecutive spans (Alt-key overlay)
144
+ // ---------------------------------------------------------------------------
145
+
146
+ export interface SpanGap {
147
+ gapMs: number;
148
+ leftFrac: number;
149
+ rightFrac: number;
150
+ rowIndex: number;
151
+ }
152
+
153
+ export function computeSpanGaps(
154
+ spans: Span[],
155
+ compression: TimeCompression
156
+ ): SpanGap[] {
157
+ const gaps: SpanGap[] = [];
158
+ for (let i = 0; i < spans.length - 1; i++) {
159
+ const endTime = getHighResInMs(spans[i].endTime);
160
+ const startTime = getHighResInMs(spans[i + 1].startTime);
161
+ const gapMs = startTime - endTime;
162
+ if (gapMs <= 0) continue;
163
+
164
+ const leftFrac = compression.toVisual(endTime);
165
+ const rightFrac = compression.toVisual(startTime);
166
+ if (rightFrac - leftFrac < 0.001) continue;
167
+
168
+ gaps.push({ gapMs, leftFrac, rightFrac, rowIndex: i });
169
+ }
170
+ return gaps;
171
+ }
172
+
173
+ // ---------------------------------------------------------------------------
174
+ // Resource colors
175
+ // ---------------------------------------------------------------------------
176
+
177
+ export const RESOURCE_COLORS: Record<
178
+ string,
179
+ { bar: string; errorBar?: string }
180
+ > = {
181
+ run: { bar: 'var(--ds-blue-700)', errorBar: 'var(--ds-red-700)' },
182
+ step: { bar: 'var(--ds-green-700)', errorBar: 'var(--ds-red-700)' },
183
+ hook: { bar: 'var(--ds-amber-700)', errorBar: 'var(--ds-red-700)' },
184
+ sleep: { bar: 'var(--ds-purple-700)', errorBar: 'var(--ds-red-700)' },
185
+ default: { bar: 'var(--ds-gray-500)', errorBar: 'var(--ds-red-700)' },
186
+ };
187
+
188
+ export function getResourceColor(resource: string): {
189
+ bar: string;
190
+ errorBar?: string;
191
+ } {
192
+ return RESOURCE_COLORS[resource] ?? RESOURCE_COLORS.default;
193
+ }
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Span segments — split a timeline bar into colored sections by event state
197
+ // ---------------------------------------------------------------------------
198
+
199
+ export type SegmentStatus =
200
+ | 'queued'
201
+ | 'running'
202
+ | 'failed'
203
+ | 'retrying'
204
+ | 'succeeded'
205
+ | 'waiting'
206
+ | 'sleeping'
207
+ | 'received';
208
+
209
+ export interface Segment {
210
+ startFraction: number;
211
+ endFraction: number;
212
+ status: SegmentStatus;
213
+ }
214
+
215
+ function clampFraction(value: number): number {
216
+ return Math.max(0, Math.min(1, value));
217
+ }
218
+
219
+ function timeToFraction(
220
+ time: number,
221
+ spanStart: number,
222
+ spanDuration: number
223
+ ): number {
224
+ if (spanDuration <= 0) return 0;
225
+ return clampFraction((time - spanStart) / spanDuration);
226
+ }
227
+
228
+ interface EventMark {
229
+ time: number;
230
+ type: string;
231
+ }
232
+
233
+ function sortedEventMarks(
234
+ events: SpanEvent[],
235
+ relevantNames: string[]
236
+ ): EventMark[] {
237
+ return events
238
+ .filter((e) => relevantNames.includes(e.name))
239
+ .map((e) => ({ time: getHighResInMs(e.timestamp), type: e.name }))
240
+ .sort((a, b) => a.time - b.time);
241
+ }
242
+
243
+ function computeStepSegmentsFromSpan(
244
+ startMs: number,
245
+ duration: number,
246
+ events: SpanEvent[]
247
+ ): Segment[] {
248
+ const segments: Segment[] = [];
249
+ if (duration <= 0) return segments;
250
+
251
+ const marks = sortedEventMarks(events, [
252
+ 'step_started',
253
+ 'step_retrying',
254
+ 'step_failed',
255
+ 'step_completed',
256
+ ]);
257
+
258
+ if (marks.length === 0) {
259
+ segments.push({ startFraction: 0, endFraction: 1, status: 'running' });
260
+ return segments;
261
+ }
262
+
263
+ const firstFraction = timeToFraction(marks[0].time, startMs, duration);
264
+ if (firstFraction > 0.001) {
265
+ segments.push({
266
+ startFraction: 0,
267
+ endFraction: firstFraction,
268
+ status: 'queued',
269
+ });
270
+ }
271
+
272
+ for (let i = 0; i < marks.length; i++) {
273
+ const mark = marks[i];
274
+ const markFrac = timeToFraction(mark.time, startMs, duration);
275
+ const nextMark = marks[i + 1];
276
+ const nextFrac = nextMark
277
+ ? timeToFraction(nextMark.time, startMs, duration)
278
+ : 1;
279
+
280
+ if (mark.type === 'step_started') {
281
+ if (i === marks.length - 1) {
282
+ segments.push({
283
+ startFraction: markFrac,
284
+ endFraction: 1,
285
+ status: 'succeeded',
286
+ });
287
+ } else {
288
+ const nextType = nextMark.type;
289
+ const attemptStatus: SegmentStatus =
290
+ nextType === 'step_retrying' || nextType === 'step_failed'
291
+ ? 'failed'
292
+ : nextType === 'step_completed'
293
+ ? 'succeeded'
294
+ : 'running';
295
+ segments.push({
296
+ startFraction: markFrac,
297
+ endFraction: nextFrac,
298
+ status: attemptStatus,
299
+ });
300
+ }
301
+ } else if (mark.type === 'step_retrying') {
302
+ segments.push({
303
+ startFraction: markFrac,
304
+ endFraction: nextFrac,
305
+ status: 'retrying',
306
+ });
307
+ } else if (mark.type === 'step_failed') {
308
+ if (markFrac < 0.999) {
309
+ segments.push({
310
+ startFraction: markFrac,
311
+ endFraction: 1,
312
+ status: 'failed',
313
+ });
314
+ }
315
+ }
316
+ }
317
+
318
+ return segments;
319
+ }
320
+
321
+ function computeHookSegmentsFromSpan(
322
+ startMs: number,
323
+ duration: number,
324
+ events: SpanEvent[]
325
+ ): Segment[] {
326
+ const segments: Segment[] = [];
327
+ if (duration <= 0) return segments;
328
+
329
+ const sorted = [...events]
330
+ .map((e) => ({ name: e.name, time: getHighResInMs(e.timestamp) }))
331
+ .sort((a, b) => a.time - b.time);
332
+
333
+ const received = sorted.find((e) => e.name === 'hook_received');
334
+ const disposed = sorted.find((e) => e.name === 'hook_disposed');
335
+
336
+ if (!received && !disposed) {
337
+ segments.push({ startFraction: 0, endFraction: 1, status: 'waiting' });
338
+ return segments;
339
+ }
340
+
341
+ const receivedFrac = received
342
+ ? timeToFraction(received.time, startMs, duration)
343
+ : null;
344
+ const disposedFrac = disposed
345
+ ? timeToFraction(disposed.time, startMs, duration)
346
+ : null;
347
+
348
+ if (receivedFrac !== null && receivedFrac > 0.001) {
349
+ segments.push({
350
+ startFraction: 0,
351
+ endFraction: receivedFrac,
352
+ status: 'waiting',
353
+ });
354
+ } else if (receivedFrac === null && disposedFrac !== null) {
355
+ segments.push({
356
+ startFraction: 0,
357
+ endFraction: disposedFrac,
358
+ status: 'waiting',
359
+ });
360
+ }
361
+
362
+ if (receivedFrac !== null) {
363
+ segments.push({
364
+ startFraction: receivedFrac,
365
+ endFraction: disposedFrac ?? 1,
366
+ status: 'received',
367
+ });
368
+ }
369
+
370
+ if (disposedFrac !== null && disposedFrac < 0.999) {
371
+ segments.push({
372
+ startFraction: disposedFrac,
373
+ endFraction: 1,
374
+ status: 'succeeded',
375
+ });
376
+ }
377
+
378
+ return segments;
379
+ }
380
+
381
+ function computeSleepSegmentsFromSpan(
382
+ startMs: number,
383
+ duration: number,
384
+ events: SpanEvent[]
385
+ ): Segment[] {
386
+ const segments: Segment[] = [];
387
+ if (duration <= 0) return segments;
388
+
389
+ const completedTime = events
390
+ .filter((e) => e.name === 'wait_completed')
391
+ .map((e) => getHighResInMs(e.timestamp))
392
+ .sort((a, b) => a - b)[0];
393
+
394
+ if (completedTime === undefined) {
395
+ segments.push({ startFraction: 0, endFraction: 1, status: 'sleeping' });
396
+ return segments;
397
+ }
398
+
399
+ const completedFrac = timeToFraction(completedTime, startMs, duration);
400
+ if (completedFrac > 0.001) {
401
+ segments.push({
402
+ startFraction: 0,
403
+ endFraction: completedFrac,
404
+ status: 'sleeping',
405
+ });
406
+ }
407
+
408
+ segments.push({
409
+ startFraction: completedFrac,
410
+ endFraction: 1,
411
+ status: 'succeeded',
412
+ });
413
+
414
+ return segments;
415
+ }
416
+
417
+ function computeRunSegmentsFromSpan(
418
+ startMs: number,
419
+ duration: number,
420
+ activeStartMs: number | undefined,
421
+ events: SpanEvent[],
422
+ attributes: Record<string, unknown>
423
+ ): Segment[] {
424
+ const segments: Segment[] = [];
425
+ if (duration <= 0) return segments;
426
+
427
+ const sorted = [...events]
428
+ .map((e) => ({ name: e.name, time: getHighResInMs(e.timestamp) }))
429
+ .sort((a, b) => a.time - b.time);
430
+
431
+ const hasRunCreated = sorted.some((e) => e.name === 'run_created');
432
+
433
+ if (!hasRunCreated) {
434
+ const runData = attributes?.data as Record<string, unknown> | undefined;
435
+ const runStatus = runData?.status as string | undefined;
436
+ return computeV1RunSegments(startMs, duration, activeStartMs, runStatus);
437
+ }
438
+
439
+ const failedEvent = sorted.find((e) => e.name === 'run_failed');
440
+
441
+ let cursor = 0;
442
+ if (activeStartMs !== undefined && activeStartMs > startMs) {
443
+ const queuedFrac = timeToFraction(activeStartMs, startMs, duration);
444
+ if (queuedFrac > 0.001) {
445
+ segments.push({
446
+ startFraction: 0,
447
+ endFraction: queuedFrac,
448
+ status: 'queued',
449
+ });
450
+ cursor = queuedFrac;
451
+ }
452
+ }
453
+
454
+ segments.push({
455
+ startFraction: cursor,
456
+ endFraction: 1,
457
+ status: failedEvent ? 'failed' : 'running',
458
+ });
459
+
460
+ return segments;
461
+ }
462
+
463
+ function computeV1RunSegments(
464
+ startMs: number,
465
+ duration: number,
466
+ activeStartMs: number | undefined,
467
+ runStatus: string | undefined
468
+ ): Segment[] {
469
+ const segments: Segment[] = [];
470
+
471
+ let cursor = 0;
472
+ if (activeStartMs !== undefined && activeStartMs > startMs) {
473
+ const queuedFrac = timeToFraction(activeStartMs, startMs, duration);
474
+ if (queuedFrac > 0.001) {
475
+ segments.push({
476
+ startFraction: 0,
477
+ endFraction: queuedFrac,
478
+ status: 'queued',
479
+ });
480
+ cursor = queuedFrac;
481
+ }
482
+ }
483
+
484
+ segments.push({
485
+ startFraction: cursor,
486
+ endFraction: 1,
487
+ status: runStatus === 'failed' ? 'failed' : 'running',
488
+ });
489
+
490
+ return segments;
491
+ }
492
+
493
+ /**
494
+ * Compute event-based segments for a span. Dispatches on `span.resource`
495
+ * to the appropriate resource-type-specific segment builder.
496
+ * Returns an empty array for generic (non-workflow) spans.
497
+ */
498
+ export function computeSpanSegments(span: Span): Segment[] {
499
+ const startMs = getHighResInMs(span.startTime);
500
+ const endMs = getHighResInMs(span.endTime);
501
+ const duration = endMs - startMs;
502
+ const activeStartMs = span.activeStartTime
503
+ ? getHighResInMs(span.activeStartTime)
504
+ : undefined;
505
+
506
+ switch (span.resource) {
507
+ case 'step':
508
+ return computeStepSegmentsFromSpan(startMs, duration, span.events);
509
+ case 'hook':
510
+ return computeHookSegmentsFromSpan(startMs, duration, span.events);
511
+ case 'sleep':
512
+ return computeSleepSegmentsFromSpan(startMs, duration, span.events);
513
+ case 'run':
514
+ return computeRunSegmentsFromSpan(
515
+ startMs,
516
+ duration,
517
+ activeStartMs,
518
+ span.events,
519
+ span.attributes
520
+ );
521
+ default:
522
+ return [];
523
+ }
524
+ }
@@ -24,6 +24,7 @@ interface RunTraceViewProps {
24
24
  ) => Promise<void>;
25
25
  onCancelRun?: (runId: string) => Promise<void>;
26
26
  onStreamClick?: (streamId: string) => void;
27
+ onRunClick?: (runId: string) => void;
27
28
  onSpanSelect?: (info: SpanSelectionInfo) => void;
28
29
  onLoadMoreSpans?: () => void | Promise<void>;
29
30
  hasMoreSpans?: boolean;
@@ -42,6 +43,7 @@ export function RunTraceView({
42
43
  onResolveHook,
43
44
  onCancelRun,
44
45
  onStreamClick,
46
+ onRunClick,
45
47
  onSpanSelect,
46
48
  onLoadMoreSpans,
47
49
  hasMoreSpans,
@@ -71,6 +73,7 @@ export function RunTraceView({
71
73
  onResolveHook={onResolveHook}
72
74
  onCancelRun={onCancelRun}
73
75
  onStreamClick={onStreamClick}
76
+ onRunClick={onRunClick}
74
77
  onSpanSelect={onSpanSelect}
75
78
  onLoadMoreSpans={onLoadMoreSpans}
76
79
  hasMoreSpans={hasMoreSpans}