@workflow/web-shared 5.0.0-beta.2 → 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 (127) hide show
  1. package/dist/components/index.d.ts +2 -0
  2. package/dist/components/index.d.ts.map +1 -1
  3. package/dist/components/index.js +2 -0
  4. package/dist/components/index.js.map +1 -1
  5. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  6. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  7. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  8. package/dist/components/new-trace-viewer/components/copy-button.js.map +1 -0
  9. package/dist/components/new-trace-viewer/components/event-list.d.ts +8 -0
  10. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  11. package/dist/components/new-trace-viewer/components/event-list.js +35 -0
  12. package/dist/components/new-trace-viewer/components/event-list.js.map +1 -0
  13. package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
  14. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  15. package/dist/components/new-trace-viewer/components/split-pane.js +100 -0
  16. package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -0
  17. package/dist/components/new-trace-viewer/components/timeline.d.ts +32 -0
  18. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  19. package/dist/components/new-trace-viewer/components/timeline.js +134 -0
  20. package/dist/components/new-trace-viewer/components/timeline.js.map +1 -0
  21. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  22. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  23. package/dist/components/new-trace-viewer/context.js +49 -0
  24. package/dist/components/new-trace-viewer/context.js.map +1 -0
  25. package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
  26. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
  27. package/dist/components/new-trace-viewer/detail-panel.js +11 -0
  28. package/dist/components/new-trace-viewer/detail-panel.js.map +1 -0
  29. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  30. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  31. package/dist/components/new-trace-viewer/icons.js +15 -0
  32. package/dist/components/new-trace-viewer/icons.js.map +1 -0
  33. package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
  34. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  35. package/dist/components/new-trace-viewer/trace-viewer.js +329 -0
  36. package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -0
  37. package/dist/components/new-trace-viewer/types.d.ts +1 -0
  38. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  39. package/dist/components/new-trace-viewer/types.js +2 -0
  40. package/dist/components/new-trace-viewer/types.js.map +1 -0
  41. package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
  42. package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
  43. package/dist/components/new-trace-viewer/util/constants.js +2 -0
  44. package/dist/components/new-trace-viewer/util/constants.js.map +1 -0
  45. package/dist/components/new-trace-viewer/utils.d.ts +47 -0
  46. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  47. package/dist/components/new-trace-viewer/utils.js +358 -0
  48. package/dist/components/new-trace-viewer/utils.js.map +1 -0
  49. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  50. package/dist/components/sidebar/attribute-panel.js +42 -23
  51. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  52. package/dist/components/sidebar/copyable-data-block.js +1 -1
  53. package/dist/components/sidebar/copyable-data-block.js.map +1 -1
  54. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  55. package/dist/components/sidebar/detail-card.js +1 -1
  56. package/dist/components/sidebar/detail-card.js.map +1 -1
  57. package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
  58. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  59. package/dist/components/sidebar/entity-detail-panel.js +3 -25
  60. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  61. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  62. package/dist/components/sidebar/events-list.js +2 -2
  63. package/dist/components/sidebar/events-list.js.map +1 -1
  64. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  65. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  66. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  67. package/dist/components/sidebar/sidebar-data-context.js.map +1 -0
  68. package/dist/components/stream-viewer.d.ts +1 -1
  69. package/dist/components/stream-viewer.d.ts.map +1 -1
  70. package/dist/components/stream-viewer.js +2 -26
  71. package/dist/components/stream-viewer.js.map +1 -1
  72. package/dist/components/trace-viewer-new.d.ts +10 -0
  73. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  74. package/dist/components/trace-viewer-new.js +23 -0
  75. package/dist/components/trace-viewer-new.js.map +1 -0
  76. package/dist/components/ui/data-inspector.d.ts +21 -0
  77. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  78. package/dist/components/ui/data-inspector.js +99 -4
  79. package/dist/components/ui/data-inspector.js.map +1 -1
  80. package/dist/components/ui/inspector-theme.d.ts +9 -10
  81. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  82. package/dist/components/ui/inspector-theme.js +38 -71
  83. package/dist/components/ui/inspector-theme.js.map +1 -1
  84. package/dist/components/workflow-trace-view.d.ts +1 -3
  85. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  86. package/dist/components/workflow-trace-view.js +4 -4
  87. package/dist/components/workflow-trace-view.js.map +1 -1
  88. package/dist/index.d.ts +1 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js.map +1 -1
  91. package/dist/lib/stream-display.d.ts +18 -0
  92. package/dist/lib/stream-display.d.ts.map +1 -0
  93. package/dist/lib/stream-display.js +30 -0
  94. package/dist/lib/stream-display.js.map +1 -0
  95. package/dist/lib/utils.d.ts +7 -9
  96. package/dist/lib/utils.d.ts.map +1 -1
  97. package/dist/lib/utils.js +17 -22
  98. package/dist/lib/utils.js.map +1 -1
  99. package/dist/styles.css +470 -1
  100. package/package.json +3 -2
  101. package/src/components/index.ts +5 -0
  102. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  103. package/src/components/new-trace-viewer/components/event-list.tsx +109 -0
  104. package/src/components/new-trace-viewer/components/split-pane.tsx +186 -0
  105. package/src/components/new-trace-viewer/components/timeline.tsx +378 -0
  106. package/src/components/new-trace-viewer/context.tsx +94 -0
  107. package/src/components/new-trace-viewer/detail-panel.tsx +57 -0
  108. package/src/components/new-trace-viewer/icons.tsx +79 -0
  109. package/src/components/new-trace-viewer/trace-viewer.tsx +585 -0
  110. package/src/components/new-trace-viewer/types.ts +0 -0
  111. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  112. package/src/components/new-trace-viewer/utils.ts +524 -0
  113. package/src/components/sidebar/attribute-panel.tsx +43 -32
  114. package/src/components/sidebar/copyable-data-block.tsx +1 -1
  115. package/src/components/sidebar/detail-card.tsx +1 -19
  116. package/src/components/sidebar/entity-detail-panel.tsx +0 -71
  117. package/src/components/sidebar/events-list.tsx +9 -6
  118. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  119. package/src/components/stream-viewer.tsx +18 -40
  120. package/src/components/trace-viewer-new.tsx +49 -0
  121. package/src/components/ui/data-inspector.tsx +235 -4
  122. package/src/components/ui/inspector-theme.ts +39 -81
  123. package/src/components/workflow-trace-view.tsx +2 -6
  124. package/src/index.ts +1 -0
  125. package/src/lib/stream-display.ts +48 -0
  126. package/src/lib/utils.ts +18 -23
  127. 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
+ }
@@ -318,8 +318,29 @@ const sortByAttributeOrder = (a: string, b: string): number => {
318
318
  * Display names for attributes that should render differently from their key.
319
319
  */
320
320
  const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
321
+ moduleSpecifier: 'Module',
322
+ workflowName: 'Workflow Name',
323
+ stepName: 'Step Name',
324
+ stepId: 'Step ID',
325
+ hookId: 'Hook ID',
326
+ attempt: 'Attempts',
327
+ eventId: 'Event ID',
328
+ runId: 'Run ID',
329
+ eventType: 'Event Type',
330
+ correlationId: 'Correlation ID',
331
+ deploymentId: 'Deployment ID',
332
+ specVersion: 'Spec Version',
321
333
  workflowCoreVersion: '@workflow/core version',
322
- receivedCount: 'times resolved',
334
+ createdAt: 'Created',
335
+ startedAt: 'Started',
336
+ updatedAt: 'Updated',
337
+ completedAt: 'Completed',
338
+ expiredAt: 'Expired',
339
+ retryAfter: 'Retry After',
340
+ resumeAt: 'Resume',
341
+ lastReceivedAt: 'Last Received',
342
+ disposedAt: 'Disposed',
343
+ receivedCount: 'Times Resolved',
323
344
  };
324
345
 
325
346
  /**
@@ -406,18 +427,16 @@ const attributeToDisplayFn: Record<
406
427
  (value: unknown, context?: DisplayContext) => null | string | ReactNode
407
428
  > = {
408
429
  // Names that need pretty-printing
409
- workflowName: (value: unknown) =>
410
- parseWorkflowName(String(value))?.shortName ?? '?',
430
+ workflowName: (_value: unknown) => null,
411
431
  moduleSpecifier: (value: unknown) => getModuleSpecifierFromName(value),
412
- stepName: (value: unknown) =>
413
- parseStepName(String(value))?.shortName ?? String(value),
432
+ stepName: (_value: unknown) => null,
414
433
  // IDs
415
- runId: (value: unknown) => String(value),
416
- stepId: (value: unknown) => String(value),
434
+ runId: (_value: unknown) => null,
435
+ stepId: (_value: unknown) => null,
417
436
  hookId: (value: unknown) => String(value),
418
437
  eventId: (value: unknown) => String(value),
419
438
  // Run/step details
420
- status: (value: unknown) => String(value),
439
+ status: (_value: unknown) => null,
421
440
  attempt: (value: unknown) => String(value),
422
441
  // Hook details
423
442
  token: (value: unknown) => String(value),
@@ -440,9 +459,9 @@ const attributeToDisplayFn: Record<
440
459
  // Dates — wrapped with TimestampTooltip showing UTC/local + relative time
441
460
  createdAt: timestampWithTooltipOrNull,
442
461
  startedAt: timestampWithTooltipOrNull,
443
- updatedAt: timestampWithTooltipOrNull,
462
+ updatedAt: (_value: unknown) => null,
444
463
  completedAt: timestampWithTooltipOrNull,
445
- expiredAt: timestampWithTooltipOrNull,
464
+ expiredAt: (_value: unknown) => null,
446
465
  retryAfter: timestampWithTooltipOrNull,
447
466
  resumeAt: timestampWithTooltipOrNull,
448
467
  // Resolved attributes, won't actually use this function
@@ -496,7 +515,7 @@ const attributeToDisplayFn: Record<
496
515
  <DetailCard
497
516
  summary="Input (no data)"
498
517
  disabled
499
- summaryClassName="text-base py-2"
518
+ summaryClassName="text-label-14 font-medium py-2"
500
519
  />
501
520
  );
502
521
  }
@@ -505,7 +524,7 @@ const attributeToDisplayFn: Record<
505
524
  <>
506
525
  <DetailCard
507
526
  summary={`Input (${argCount} ${argLabel})`}
508
- summaryClassName="text-base py-2"
527
+ summaryClassName="text-label-14 font-medium py-2"
509
528
  contentClassName="mt-0"
510
529
  >
511
530
  {Array.isArray(args)
@@ -536,14 +555,14 @@ const attributeToDisplayFn: Record<
536
555
  <DetailCard
537
556
  summary="Input (no data)"
538
557
  disabled
539
- summaryClassName="text-base py-2"
558
+ summaryClassName="text-label-14 font-medium py-2"
540
559
  />
541
560
  );
542
561
  }
543
562
  return (
544
563
  <DetailCard
545
564
  summary={`Input (${argCount} ${argLabel})`}
546
- summaryClassName="text-base py-2"
565
+ summaryClassName="text-label-14 font-medium py-2"
547
566
  contentClassName="mt-0"
548
567
  >
549
568
  {Array.isArray(value)
@@ -563,7 +582,7 @@ const attributeToDisplayFn: Record<
563
582
  return (
564
583
  <DetailCard
565
584
  summary="Output"
566
- summaryClassName="text-base py-2"
585
+ summaryClassName="text-label-14 font-medium py-2"
567
586
  contentClassName="mt-0"
568
587
  >
569
588
  {JsonBlock(value)}
@@ -581,7 +600,7 @@ const attributeToDisplayFn: Record<
581
600
  return (
582
601
  <DetailCard
583
602
  summary="Error"
584
- summaryClassName="text-base py-2"
603
+ summaryClassName="text-label-14 font-medium py-2"
585
604
  contentClassName="mt-0"
586
605
  >
587
606
  <ErrorStackBlock value={value} />
@@ -592,7 +611,7 @@ const attributeToDisplayFn: Record<
592
611
  return (
593
612
  <DetailCard
594
613
  summary="Error"
595
- summaryClassName="text-base py-2"
614
+ summaryClassName="text-label-14 font-medium py-2"
596
615
  contentClassName="mt-0"
597
616
  >
598
617
  {JsonBlock(value)}
@@ -642,18 +661,13 @@ export const AttributeBlock = ({
642
661
  context?: DisplayContext;
643
662
  }) => {
644
663
  const isExpandableLoadingTarget =
645
- attribute === 'input' ||
646
- attribute === 'output' ||
647
- attribute === 'eventData';
664
+ attribute === 'input' || attribute === 'eventData';
648
665
  if (isLoading && isExpandableLoadingTarget && !hasDisplayContent(value)) {
649
666
  return (
650
667
  <div
651
- className={`my-2 flex flex-col ${attribute === 'input' || attribute === 'output' ? 'gap-2 my-3.5' : 'gap-0'}`}
668
+ className={`my-2 flex flex-col ${attribute === 'input' ? 'gap-2 my-3.5' : 'gap-0'}`}
652
669
  >
653
- <span
654
- className={`${attribute === 'input' || attribute === 'output' ? 'text-base' : 'text-xs'} font-medium first-letter:uppercase`}
655
- style={{ color: 'var(--ds-gray-700)' }}
656
- >
670
+ <span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
657
671
  {attribute}
658
672
  </span>
659
673
  <Skeleton className="h-9 w-full rounded-md" />
@@ -701,10 +715,7 @@ export const AttributeBlock = ({
701
715
  key={attribute}
702
716
  className={`my-2 flex flex-col ${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'gap-2 my-3.5' : 'gap-0'}`}
703
717
  >
704
- <span
705
- className={`${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'text-base' : 'text-xs'} font-medium first-letter:uppercase`}
706
- style={{ color: 'var(--ds-gray-700)' }}
707
- >
718
+ <span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
708
719
  {attribute}
709
720
  </span>
710
721
  <span className="text-xs" style={{ color: 'var(--ds-gray-1000)' }}>
@@ -773,9 +784,9 @@ export const AttributePanel = ({
773
784
 
774
785
  if (!isLoading) return present;
775
786
 
776
- // During loading, ensure input/output appear so their skeletons render
787
+ // During loading, ensure input appears so its skeleton renders
777
788
  // in the correct position (above the events section).
778
- const loadingDefaults = ['input', 'output'];
789
+ const loadingDefaults = ['input'];
779
790
  for (const key of loadingDefaults) {
780
791
  if (!present.includes(key)) {
781
792
  present.push(key);
@@ -848,7 +859,7 @@ export const AttributePanel = ({
848
859
  {/* Basic attributes in a vertical layout with border */}
849
860
  {visibleBasicAttributes.length > 0 && (
850
861
  <div
851
- className="mb-3 flex flex-col overflow-hidden rounded-lg border"
862
+ className="mb-3 flex flex-col overflow-hidden rounded-md border"
852
863
  style={{
853
864
  borderColor: 'var(--ds-gray-300)',
854
865
  }}
@@ -24,7 +24,7 @@ export function CopyableDataBlock({ data }: { data: unknown }) {
24
24
  const toast = useToast();
25
25
  return (
26
26
  <div
27
- className="relative overflow-x-auto rounded-md border p-3 pt-9"
27
+ className="relative overflow-x-auto rounded-md border p-3"
28
28
  style={{ borderColor: 'var(--ds-gray-300)' }}
29
29
  >
30
30
  <button