@workflow/web-shared 4.1.0-beta.52 → 4.1.0-beta.54

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 (145) hide show
  1. package/dist/components/error-boundary.d.ts +15 -20
  2. package/dist/components/error-boundary.d.ts.map +1 -1
  3. package/dist/components/error-boundary.js +17 -31
  4. package/dist/components/error-boundary.js.map +1 -1
  5. package/dist/components/event-list-view.d.ts +7 -6
  6. package/dist/components/event-list-view.d.ts.map +1 -1
  7. package/dist/components/event-list-view.js +492 -109
  8. package/dist/components/event-list-view.js.map +1 -1
  9. package/dist/components/index.d.ts +1 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/components/index.js +1 -0
  12. package/dist/components/index.js.map +1 -1
  13. package/dist/components/run-trace-view.d.ts +2 -1
  14. package/dist/components/run-trace-view.d.ts.map +1 -1
  15. package/dist/components/run-trace-view.js +2 -2
  16. package/dist/components/run-trace-view.js.map +1 -1
  17. package/dist/components/sidebar/attribute-panel.d.ts +2 -1
  18. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  19. package/dist/components/sidebar/attribute-panel.js +53 -142
  20. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  21. package/dist/components/sidebar/conversation-view.d.ts.map +1 -1
  22. package/dist/components/sidebar/conversation-view.js +3 -17
  23. package/dist/components/sidebar/conversation-view.js.map +1 -1
  24. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  25. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  26. package/dist/components/sidebar/entity-detail-panel.js +63 -10
  27. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  28. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  29. package/dist/components/sidebar/events-list.js +4 -8
  30. package/dist/components/sidebar/events-list.js.map +1 -1
  31. package/dist/components/sidebar/resolve-hook-modal.d.ts +3 -0
  32. package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -1
  33. package/dist/components/sidebar/resolve-hook-modal.js +152 -3
  34. package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
  35. package/dist/components/stream-viewer.d.ts +7 -5
  36. package/dist/components/stream-viewer.d.ts.map +1 -1
  37. package/dist/components/stream-viewer.js +54 -22
  38. package/dist/components/stream-viewer.js.map +1 -1
  39. package/dist/components/trace-viewer/components/markers.d.ts +2 -1
  40. package/dist/components/trace-viewer/components/markers.d.ts.map +1 -1
  41. package/dist/components/trace-viewer/components/markers.js +59 -20
  42. package/dist/components/trace-viewer/components/markers.js.map +1 -1
  43. package/dist/components/trace-viewer/components/node.d.ts +5 -1
  44. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  45. package/dist/components/trace-viewer/components/node.js +250 -68
  46. package/dist/components/trace-viewer/components/node.js.map +1 -1
  47. package/dist/components/trace-viewer/components/span-content.d.ts +19 -0
  48. package/dist/components/trace-viewer/components/span-content.d.ts.map +1 -0
  49. package/dist/components/trace-viewer/components/span-content.js +137 -0
  50. package/dist/components/trace-viewer/components/span-content.js.map +1 -0
  51. package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -1
  52. package/dist/components/trace-viewer/components/span-detail-panel.js +3 -2
  53. package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
  54. package/dist/components/trace-viewer/components/span-segments.d.ts +50 -0
  55. package/dist/components/trace-viewer/components/span-segments.d.ts.map +1 -0
  56. package/dist/components/trace-viewer/components/span-segments.js +392 -0
  57. package/dist/components/trace-viewer/components/span-segments.js.map +1 -0
  58. package/dist/components/trace-viewer/components/span-strategies.d.ts +46 -0
  59. package/dist/components/trace-viewer/components/span-strategies.d.ts.map +1 -0
  60. package/dist/components/trace-viewer/components/span-strategies.js +108 -0
  61. package/dist/components/trace-viewer/components/span-strategies.js.map +1 -0
  62. package/dist/components/trace-viewer/context.d.ts +7 -6
  63. package/dist/components/trace-viewer/context.d.ts.map +1 -1
  64. package/dist/components/trace-viewer/context.js +47 -18
  65. package/dist/components/trace-viewer/context.js.map +1 -1
  66. package/dist/components/trace-viewer/trace-viewer.d.ts +5 -1
  67. package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
  68. package/dist/components/trace-viewer/trace-viewer.js +87 -11
  69. package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
  70. package/dist/components/trace-viewer/trace-viewer.module.css +179 -6
  71. package/dist/components/trace-viewer/util/timing.d.ts +5 -0
  72. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  73. package/dist/components/trace-viewer/util/timing.js +12 -0
  74. package/dist/components/trace-viewer/util/timing.js.map +1 -1
  75. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +1 -1
  76. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -1
  77. package/dist/components/trace-viewer/util/use-streaming-spans.js +29 -17
  78. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
  79. package/dist/components/trace-viewer/worker.js +3 -1
  80. package/dist/components/trace-viewer/worker.js.map +1 -1
  81. package/dist/components/ui/alert.js +3 -3
  82. package/dist/components/ui/alert.js.map +1 -1
  83. package/dist/components/ui/card.d.ts.map +1 -1
  84. package/dist/components/ui/card.js +2 -2
  85. package/dist/components/ui/card.js.map +1 -1
  86. package/dist/components/ui/data-inspector.d.ts +17 -0
  87. package/dist/components/ui/data-inspector.d.ts.map +1 -0
  88. package/dist/components/ui/data-inspector.js +184 -0
  89. package/dist/components/ui/data-inspector.js.map +1 -0
  90. package/dist/components/ui/error-card.d.ts.map +1 -1
  91. package/dist/components/ui/error-card.js +4 -1
  92. package/dist/components/ui/error-card.js.map +1 -1
  93. package/dist/components/ui/inspector-theme.d.ts +39 -24
  94. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  95. package/dist/components/ui/inspector-theme.js +90 -38
  96. package/dist/components/ui/inspector-theme.js.map +1 -1
  97. package/dist/components/ui/skeleton.d.ts +1 -1
  98. package/dist/components/ui/skeleton.d.ts.map +1 -1
  99. package/dist/components/ui/skeleton.js +2 -2
  100. package/dist/components/ui/skeleton.js.map +1 -1
  101. package/dist/components/workflow-trace-view.d.ts +3 -1
  102. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  103. package/dist/components/workflow-trace-view.js +435 -21
  104. package/dist/components/workflow-trace-view.js.map +1 -1
  105. package/dist/components/workflow-traces/trace-span-construction.d.ts +1 -1
  106. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  107. package/dist/components/workflow-traces/trace-span-construction.js +2 -2
  108. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  109. package/dist/lib/hydration.d.ts.map +1 -1
  110. package/dist/lib/hydration.js +17 -3
  111. package/dist/lib/hydration.js.map +1 -1
  112. package/dist/styles.css +186 -0
  113. package/package.json +8 -7
  114. package/src/components/error-boundary.tsx +29 -40
  115. package/src/components/event-list-view.tsx +1000 -287
  116. package/src/components/index.ts +1 -0
  117. package/src/components/run-trace-view.tsx +3 -0
  118. package/src/components/sidebar/attribute-panel.tsx +58 -258
  119. package/src/components/sidebar/conversation-view.tsx +30 -27
  120. package/src/components/sidebar/entity-detail-panel.tsx +86 -20
  121. package/src/components/sidebar/events-list.tsx +4 -11
  122. package/src/components/sidebar/resolve-hook-modal.tsx +206 -47
  123. package/src/components/stream-viewer.tsx +138 -61
  124. package/src/components/trace-viewer/components/markers.tsx +69 -21
  125. package/src/components/trace-viewer/components/node.tsx +346 -100
  126. package/src/components/trace-viewer/components/span-content.tsx +247 -0
  127. package/src/components/trace-viewer/components/span-detail-panel.tsx +7 -2
  128. package/src/components/trace-viewer/components/span-segments.ts +516 -0
  129. package/src/components/trace-viewer/components/span-strategies.ts +205 -0
  130. package/src/components/trace-viewer/context.tsx +92 -40
  131. package/src/components/trace-viewer/trace-viewer.module.css +179 -6
  132. package/src/components/trace-viewer/trace-viewer.tsx +115 -11
  133. package/src/components/trace-viewer/util/timing.ts +13 -0
  134. package/src/components/trace-viewer/util/use-streaming-spans.ts +28 -17
  135. package/src/components/trace-viewer/worker.ts +4 -1
  136. package/src/components/ui/alert.tsx +3 -3
  137. package/src/components/ui/card.tsx +3 -5
  138. package/src/components/ui/data-inspector.tsx +318 -0
  139. package/src/components/ui/error-card.tsx +17 -6
  140. package/src/components/ui/inspector-theme.ts +127 -39
  141. package/src/components/ui/skeleton.tsx +3 -1
  142. package/src/components/workflow-trace-view.tsx +625 -26
  143. package/src/components/workflow-traces/trace-span-construction.ts +3 -2
  144. package/src/lib/hydration.ts +17 -8
  145. package/src/styles.css +186 -0
@@ -0,0 +1,205 @@
1
+ import type { RefObject } from 'react';
2
+ import type { RootNode, ScrollSnapshot, VisibleSpan } from '../types';
3
+ import { MARKER_HEIGHT, ROW_HEIGHT, ROW_PADDING } from '../util/constants';
4
+
5
+ /**
6
+ * The resource type of a workflow span, read from span.attributes.resource.
7
+ * Falls back to 'default' for non-workflow (generic OTEL) spans.
8
+ */
9
+ export type ResourceType = 'run' | 'step' | 'hook' | 'sleep' | 'default';
10
+
11
+ /** Minimum rendered width so very short spans are always visible */
12
+ const MIN_SPAN_WIDTH = 4;
13
+
14
+ /**
15
+ * Layout result computed by a span strategy. Controls how the span
16
+ * is sized and positioned in the timeline.
17
+ */
18
+ export interface SpanLayout {
19
+ /** The rendered width in pixels */
20
+ width: number;
21
+ /** The actual duration-based width (before min-width clamping) */
22
+ actualWidth: number;
23
+ /** The rendered height in pixels */
24
+ height: number;
25
+ /** The y position in pixels */
26
+ top: number;
27
+ /** The x position in pixels (from left) */
28
+ left: number;
29
+ /** Whether the span is considered "small" (< 64px actual width) */
30
+ isSmall: boolean;
31
+ /** Whether the span is considered "huge" (>= 500px actual width) */
32
+ isHuge: boolean;
33
+ /** Whether the span is currently hovered (with hover eligibility) */
34
+ isHovered: boolean;
35
+ /** Whether the span is expanded (hovered or selected) — controls sizing */
36
+ isExpanded: boolean;
37
+ /** Whether the span is near the right side of the visible area */
38
+ isNearRightSide: boolean;
39
+ }
40
+
41
+ /**
42
+ * Reads the resource type from a span's attributes.
43
+ */
44
+ export function getResourceType(node: VisibleSpan): ResourceType {
45
+ const resource = node.span.attributes?.resource;
46
+ if (
47
+ resource === 'run' ||
48
+ resource === 'step' ||
49
+ resource === 'hook' ||
50
+ resource === 'sleep'
51
+ ) {
52
+ return resource;
53
+ }
54
+ return 'default';
55
+ }
56
+
57
+ // ──────────────────────────────────────────────────────────────────────────
58
+ // Shared helpers
59
+ // ──────────────────────────────────────────────────────────────────────────
60
+
61
+ function computeIsSmall(actualWidth: number): boolean {
62
+ return actualWidth < 64;
63
+ }
64
+
65
+ function computeIsHuge(actualWidth: number): boolean {
66
+ return actualWidth >= 500;
67
+ }
68
+
69
+ function computeIsHovered(node: VisibleSpan, isHuge: boolean): boolean {
70
+ return node.isHovered && !isHuge && node.isHighlighted !== false;
71
+ }
72
+
73
+ function computeIsNearRightSide(
74
+ node: VisibleSpan,
75
+ root: RootNode,
76
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>,
77
+ isHovered: boolean
78
+ ): boolean {
79
+ if (!isHovered) return false;
80
+
81
+ let visibleDuration = root.duration;
82
+ let visibleEndTime = root.endTime;
83
+ const snapshot = scrollSnapshotRef.current;
84
+ if (snapshot) {
85
+ visibleDuration = snapshot.endTime - snapshot.startTime;
86
+ visibleEndTime = snapshot.endTime;
87
+ }
88
+ return visibleEndTime - node.startTime < 0.25 * visibleDuration;
89
+ }
90
+
91
+ // ──────────────────────────────────────────────────────────────────────────
92
+ // Default layout (used by step, hook, run, default — all behave the same today)
93
+ // ──────────────────────────────────────────────────────────────────────────
94
+
95
+ function computeDefaultLayout(
96
+ node: VisibleSpan,
97
+ root: RootNode,
98
+ scale: number,
99
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>
100
+ ): SpanLayout {
101
+ const left = (node.startTime - root.startTime) * scale;
102
+ let top = MARKER_HEIGHT + (ROW_HEIGHT + ROW_PADDING) * node.row;
103
+ const actualWidth = node.duration * scale;
104
+ const width = Math.max(actualWidth, MIN_SPAN_WIDTH);
105
+ let height = ROW_HEIGHT;
106
+
107
+ const isHuge = computeIsHuge(actualWidth);
108
+ const isSmall = computeIsSmall(actualWidth);
109
+ const isHovered = computeIsHovered(node, isHuge);
110
+ const isExpanded = isHovered || Boolean(node.isSelected);
111
+
112
+ if (isSmall && !isExpanded) {
113
+ height *= 0.4;
114
+ top += (ROW_HEIGHT - height) * 0.5;
115
+ }
116
+
117
+ const isNearRightSide = computeIsNearRightSide(
118
+ node,
119
+ root,
120
+ scrollSnapshotRef,
121
+ isExpanded
122
+ );
123
+
124
+ return {
125
+ width,
126
+ actualWidth,
127
+ height,
128
+ top,
129
+ left,
130
+ isSmall,
131
+ isHuge,
132
+ isHovered,
133
+ isExpanded,
134
+ isNearRightSide,
135
+ };
136
+ }
137
+
138
+ // ──────────────────────────────────────────────────────────────────────────
139
+ // Per-type layout functions
140
+ // Each returns the same result today but provides a clear extension point.
141
+ // ──────────────────────────────────────────────────────────────────────────
142
+
143
+ export function getRunLayout(
144
+ node: VisibleSpan,
145
+ root: RootNode,
146
+ scale: number,
147
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>
148
+ ): SpanLayout {
149
+ return computeDefaultLayout(node, root, scale, scrollSnapshotRef);
150
+ }
151
+
152
+ export function getStepLayout(
153
+ node: VisibleSpan,
154
+ root: RootNode,
155
+ scale: number,
156
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>
157
+ ): SpanLayout {
158
+ return computeDefaultLayout(node, root, scale, scrollSnapshotRef);
159
+ }
160
+
161
+ export function getHookLayout(
162
+ node: VisibleSpan,
163
+ root: RootNode,
164
+ scale: number,
165
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>
166
+ ): SpanLayout {
167
+ return computeDefaultLayout(node, root, scale, scrollSnapshotRef);
168
+ }
169
+
170
+ export function getSleepLayout(
171
+ node: VisibleSpan,
172
+ root: RootNode,
173
+ scale: number,
174
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>
175
+ ): SpanLayout {
176
+ return computeDefaultLayout(node, root, scale, scrollSnapshotRef);
177
+ }
178
+
179
+ // ──────────────────────────────────────────────────────────────────────────
180
+ // Dispatcher
181
+ // ──────────────────────────────────────────────────────────────────────────
182
+
183
+ /**
184
+ * Computes the layout for a span based on its resource type.
185
+ */
186
+ export function getSpanLayout(
187
+ resourceType: ResourceType,
188
+ node: VisibleSpan,
189
+ root: RootNode,
190
+ scale: number,
191
+ scrollSnapshotRef: RefObject<ScrollSnapshot | undefined>
192
+ ): SpanLayout {
193
+ switch (resourceType) {
194
+ case 'run':
195
+ return getRunLayout(node, root, scale, scrollSnapshotRef);
196
+ case 'step':
197
+ return getStepLayout(node, root, scale, scrollSnapshotRef);
198
+ case 'hook':
199
+ return getHookLayout(node, root, scale, scrollSnapshotRef);
200
+ case 'sleep':
201
+ return getSleepLayout(node, root, scale, scrollSnapshotRef);
202
+ default:
203
+ return computeDefaultLayout(node, root, scale, scrollSnapshotRef);
204
+ }
205
+ }
@@ -117,12 +117,6 @@ export interface TraceViewerState {
117
117
  * Whether the trace viewer is small enough that it should be in mobile mode
118
118
  */
119
119
  isMobile: boolean;
120
- /**
121
- * @deprecated Panel rendering has been moved outside the context.
122
- * This field is kept for backwards compatibility but is no longer
123
- * used by the workflow trace viewer.
124
- */
125
- customPanelComponent: ReactNode | null;
126
120
  /**
127
121
  * A function to provide custom class names for spans.
128
122
  */
@@ -216,6 +210,13 @@ export type TraceViewerAction =
216
210
  }
217
211
  | {
218
212
  type: 'forceRender';
213
+ }
214
+ | {
215
+ /** Like setRoot but preserves scroll position and memo cache (for incremental data updates) */
216
+ type: 'updateRoot';
217
+ root: RootNode;
218
+ spanMap: Record<string, SpanNode>;
219
+ resources: Resource[];
219
220
  };
220
221
 
221
222
  export interface TraceViewerContextProps {
@@ -251,16 +252,10 @@ export const initialState: TraceViewerState = {
251
252
  getQuickLinks: () => [],
252
253
  withPanel: false,
253
254
  isMobile: false,
254
- customPanelComponent: null,
255
255
  };
256
256
 
257
257
  const getMinScale = (state: TraceViewerState): number => {
258
- return (
259
- (state.width -
260
- state.scrollbarWidth -
261
- Number(Boolean(state.selected && state.withPanel)) * state.panelWidth) /
262
- state.root.duration
263
- );
258
+ return (state.timelineWidth - state.scrollbarWidth) / state.root.duration;
264
259
  };
265
260
 
266
261
  export const TraceViewerContext = createContext<TraceViewerContextProps>({
@@ -324,11 +319,20 @@ const reducer: Reducer<TraceViewerState, TraceViewerAction> = (
324
319
  240,
325
320
  Math.min(action.width, state.width - 240)
326
321
  );
327
- return {
328
- ...state,
329
- timelineWidth: state.width - panelWidth,
330
- panelWidth,
331
- };
322
+ const timelineWidth =
323
+ state.withPanel && state.selected && !state.isMobile
324
+ ? state.width - panelWidth
325
+ : state.width;
326
+ return reducer(
327
+ {
328
+ ...state,
329
+ timelineWidth,
330
+ panelWidth,
331
+ },
332
+ {
333
+ type: 'detectBaseScale',
334
+ }
335
+ );
332
336
  }
333
337
  case 'setFilter':
334
338
  return {
@@ -343,7 +347,7 @@ const reducer: Reducer<TraceViewerState, TraceViewerAction> = (
343
347
  timelineWidth: state.width,
344
348
  },
345
349
  {
346
- type: 'minScale',
350
+ type: 'detectBaseScale',
347
351
  }
348
352
  );
349
353
  case 'select': {
@@ -352,13 +356,18 @@ const reducer: Reducer<TraceViewerState, TraceViewerAction> = (
352
356
  return state;
353
357
  }
354
358
 
355
- return {
356
- ...state,
357
- selected: node,
358
- timelineWidth:
359
- state.width -
360
- (state.withPanel && !state.isMobile ? state.panelWidth : 0),
361
- };
359
+ return reducer(
360
+ {
361
+ ...state,
362
+ selected: node,
363
+ timelineWidth:
364
+ state.width -
365
+ (state.withPanel && !state.isMobile ? state.panelWidth : 0),
366
+ },
367
+ {
368
+ type: 'detectBaseScale',
369
+ }
370
+ );
362
371
  }
363
372
  case 'escape': {
364
373
  if (state.selected) {
@@ -387,13 +396,18 @@ const reducer: Reducer<TraceViewerState, TraceViewerAction> = (
387
396
  }
388
397
  case 'detectBaseScale': {
389
398
  const baseScale =
390
- (state.width - state.scrollbarWidth) / state.root.duration;
399
+ (state.timelineWidth - state.scrollbarWidth) / state.root.duration;
391
400
 
392
- return {
393
- ...state,
394
- baseScale,
395
- scale: baseScale * state.scaleRatio,
396
- };
401
+ return reducer(
402
+ {
403
+ ...state,
404
+ baseScale,
405
+ scale: baseScale * state.scaleRatio,
406
+ },
407
+ {
408
+ type: 'minScale',
409
+ }
410
+ );
397
411
  }
398
412
  case 'setScale':
399
413
  return {
@@ -552,16 +566,43 @@ const reducer: Reducer<TraceViewerState, TraceViewerAction> = (
552
566
  }
553
567
  case 'setWithPanel': {
554
568
  if (state.withPanel === action.withPanel) return state;
555
- return {
556
- ...state,
557
- withPanel: action.withPanel,
558
- };
569
+ const timelineWidth =
570
+ action.withPanel && state.selected && !state.isMobile
571
+ ? state.width - state.panelWidth
572
+ : state.width;
573
+ return reducer(
574
+ {
575
+ ...state,
576
+ withPanel: action.withPanel,
577
+ timelineWidth,
578
+ },
579
+ {
580
+ type: 'detectBaseScale',
581
+ }
582
+ );
559
583
  }
560
584
  case 'forceRender':
561
585
  state.memoCacheRef.current.set('', {});
562
586
  return {
563
587
  ...state,
564
588
  };
589
+ case 'updateRoot':
590
+ // Incremental update: preserve scroll snapshot and only invalidate
591
+ // memo cache for spans whose data may have changed
592
+ state.memoCacheRef.current.set('', {});
593
+ return reducer(
594
+ {
595
+ ...state,
596
+ root: action.root,
597
+ spanMap: action.spanMap,
598
+ resourceMap: Object.fromEntries(
599
+ action.resources.map(({ name, attributes }) => [name, attributes])
600
+ ),
601
+ },
602
+ {
603
+ type: 'detectBaseScale',
604
+ }
605
+ );
565
606
  }
566
607
  };
567
608
 
@@ -594,7 +635,6 @@ export function TraceViewerContextProvider({
594
635
  scrollSnapshotRef,
595
636
  customSpanClassNameFunc,
596
637
  customSpanEventClassNameFunc,
597
- customPanelComponent,
598
638
  memoCacheRef,
599
639
  withPanel,
600
640
  getQuickLinks: (span) => {
@@ -624,11 +664,23 @@ export function TraceViewerContextProvider({
624
664
  );
625
665
 
626
666
  return (
627
- <TraceViewerContext.Provider value={value}>
628
- {children}
629
- </TraceViewerContext.Provider>
667
+ <CustomPanelContext.Provider value={customPanelComponent}>
668
+ <TraceViewerContext.Provider value={value}>
669
+ {children}
670
+ </TraceViewerContext.Provider>
671
+ </CustomPanelContext.Provider>
630
672
  );
631
673
  }
632
674
 
633
675
  export const useTraceViewer = (): TraceViewerContextProps =>
634
676
  useContext(TraceViewerContext);
677
+
678
+ /**
679
+ * Separate context for the custom panel component. This is intentionally
680
+ * outside the useReducer state so that the panel re-renders reactively
681
+ * when props like spanDetailData change.
682
+ */
683
+ const CustomPanelContext = createContext<ReactNode | null>(null);
684
+
685
+ export const useCustomPanelComponent = (): ReactNode | null =>
686
+ useContext(CustomPanelContext);
@@ -63,7 +63,7 @@
63
63
  top: -7px;
64
64
  }
65
65
 
66
- &:hover:not(.selected) {
66
+ &:hover:not([data-selected]) {
67
67
  filter: brightness(0.95);
68
68
  }
69
69
 
@@ -103,7 +103,7 @@
103
103
  border-radius: 4px;
104
104
  }
105
105
 
106
- &.selected {
106
+ &[data-selected] {
107
107
  border-width: 2px;
108
108
  outline: 2px solid var(--span-border);
109
109
  outline-offset: -1px;
@@ -112,6 +112,7 @@
112
112
  filter: brightness(0.95);
113
113
  }
114
114
  }
115
+
115
116
  }
116
117
 
117
118
  .spanNodeEvent {
@@ -271,11 +272,11 @@
271
272
  }
272
273
  }
273
274
 
274
- & .spanNode:hover:not(.selected) {
275
+ & .spanNode:hover:not([data-selected]) {
275
276
  filter: brightness(1.05);
276
277
  }
277
278
 
278
- & .spanNode.selected:hover {
279
+ & .spanNode[data-selected]:hover {
279
280
  filter: brightness(1.05);
280
281
  }
281
282
  }
@@ -295,11 +296,11 @@
295
296
  }
296
297
  }
297
298
 
298
- & .spanNode:hover:not(.selected) {
299
+ & .spanNode:hover:not([data-selected]) {
299
300
  filter: brightness(1.05);
300
301
  }
301
302
 
302
- & .spanNode.selected:hover {
303
+ & .spanNode[data-selected]:hover {
303
304
  filter: brightness(1.05);
304
305
  }
305
306
  }
@@ -311,6 +312,8 @@
311
312
  text-align: left;
312
313
  overflow: hidden;
313
314
  text-overflow: ellipsis;
315
+ position: relative;
316
+ z-index: 1;
314
317
  }
315
318
 
316
319
  .spanDuration {
@@ -318,6 +321,8 @@
318
321
  padding-right: 6px;
319
322
  font-variant-numeric: tabular-nums;
320
323
  color: var(--span-secondary, var(--ds-gray-alpha-700));
324
+ position: relative;
325
+ z-index: 1;
321
326
  }
322
327
 
323
328
  /* SPAN DETAIL PANEL */
@@ -796,6 +801,16 @@
796
801
  bottom: 6px;
797
802
  color: var(--ds-gray-900);
798
803
  user-select: none;
804
+ white-space: nowrap;
805
+ }
806
+
807
+ .markerClockTime {
808
+ display: block;
809
+ font-size: 9px;
810
+ color: var(--ds-gray-alpha-500);
811
+ margin-top: 1px;
812
+ letter-spacing: 0.02em;
813
+ white-space: nowrap;
799
814
  }
800
815
 
801
816
  .eventMarkersContainer {
@@ -1253,6 +1268,164 @@
1253
1268
  --span-secondary: var(--ds-gray-900);
1254
1269
  }
1255
1270
 
1271
+ /* ──────────────────────────────────────────────────────────────────────── */
1272
+ /* Span segments — colored sections overlaid inside span bars */
1273
+ /* ──────────────────────────────────────────────────────────────────────── */
1274
+
1275
+ .segmentLayer {
1276
+ position: absolute;
1277
+ inset: 0;
1278
+ z-index: 0;
1279
+ border-radius: inherit;
1280
+ pointer-events: none;
1281
+ }
1282
+
1283
+ .segment {
1284
+ position: absolute;
1285
+ top: 0;
1286
+ bottom: 0;
1287
+ pointer-events: auto;
1288
+ overflow: hidden;
1289
+ }
1290
+
1291
+ .segmentLabel {
1292
+ display: flex;
1293
+ align-items: center;
1294
+ height: 100%;
1295
+ padding: 0 4px;
1296
+ font-size: 9px;
1297
+ font-variant-numeric: tabular-nums;
1298
+ white-space: nowrap;
1299
+ color: var(--ds-gray-900);
1300
+ opacity: 0.7;
1301
+ pointer-events: none;
1302
+ user-select: none;
1303
+ }
1304
+
1305
+ /* Inline segment tags rendered next to the span name */
1306
+ .segmentTag {
1307
+ font-size: 10px;
1308
+ font-variant-numeric: tabular-nums;
1309
+ opacity: 0.6;
1310
+ white-space: nowrap;
1311
+ }
1312
+
1313
+ /* Queued: hatched — waiting to start */
1314
+ .segQueued {
1315
+ background: repeating-linear-gradient(
1316
+ 45deg,
1317
+ var(--ds-gray-alpha-200),
1318
+ var(--ds-gray-alpha-200) 6px,
1319
+ var(--ds-gray-alpha-100) 6px,
1320
+ var(--ds-gray-alpha-100) 12px
1321
+ );
1322
+ }
1323
+
1324
+ /* Running: soft blue — actively executing */
1325
+ .segRunning {
1326
+ background-color: var(--ds-blue-200);
1327
+ }
1328
+
1329
+ /* Failed: soft red — attempt ended in failure */
1330
+ .segFailed {
1331
+ background-color: var(--ds-red-200);
1332
+ }
1333
+
1334
+ /* Retrying: hatched — waiting between retry attempts */
1335
+ .segRetrying {
1336
+ background: repeating-linear-gradient(
1337
+ 45deg,
1338
+ var(--ds-gray-alpha-200),
1339
+ var(--ds-gray-alpha-200) 6px,
1340
+ var(--ds-gray-alpha-100) 6px,
1341
+ var(--ds-gray-alpha-100) 12px
1342
+ );
1343
+ }
1344
+
1345
+ /* Succeeded: soft green — final successful attempt */
1346
+ .segSucceeded {
1347
+ background-color: var(--ds-green-200);
1348
+ }
1349
+
1350
+ /* Waiting: hatched — hook waiting for payload */
1351
+ .segWaiting {
1352
+ background: repeating-linear-gradient(
1353
+ 45deg,
1354
+ var(--ds-gray-alpha-200),
1355
+ var(--ds-gray-alpha-200) 6px,
1356
+ var(--ds-gray-alpha-100) 6px,
1357
+ var(--ds-gray-alpha-100) 12px
1358
+ );
1359
+ }
1360
+
1361
+ /* Sleeping: soft amber — sleep in progress */
1362
+ .segSleeping {
1363
+ background-color: var(--ds-amber-200);
1364
+ }
1365
+
1366
+ /* Received: soft blue — hook received payload */
1367
+ .segReceived {
1368
+ background-color: var(--ds-blue-200);
1369
+ }
1370
+
1371
+ /* Boundary markers — workflow events shown as thin vertical lines instead of diamonds */
1372
+ .boundaryMarker {
1373
+ & .boundaryLine {
1374
+ position: absolute;
1375
+ left: -0.5px;
1376
+ top: 0;
1377
+ bottom: 0;
1378
+ width: 1px;
1379
+ background-color: transparent;
1380
+ z-index: 0;
1381
+ transition: background-color 0.15s ease;
1382
+ }
1383
+
1384
+ &[data-hovered="true"] .boundaryLine {
1385
+ background-color: var(--ds-gray-alpha-400);
1386
+ }
1387
+
1388
+ /* Tooltip must sit above the boundary line */
1389
+ & .hoverInfo {
1390
+ z-index: 1;
1391
+ }
1392
+ }
1393
+
1394
+ /* When segments are active, make span background transparent so segments show */
1395
+ .spanNode.hasSegments {
1396
+ background-color: transparent;
1397
+
1398
+ &.small:not(.xHover) {
1399
+ background-color: transparent;
1400
+ }
1401
+
1402
+ /* Suppress hover expansion gradient — segments handle the background */
1403
+ &.xHover {
1404
+ background: none;
1405
+ min-width: var(--span-width);
1406
+ max-width: var(--span-width);
1407
+
1408
+ &[data-right-side="true"] {
1409
+ background: none;
1410
+ }
1411
+ }
1412
+
1413
+ /* Remove duration label background bleed — segments provide the visual */
1414
+ & .spanDuration {
1415
+ background-color: transparent;
1416
+ box-shadow: none;
1417
+ }
1418
+
1419
+ /* Let segment tooltips receive pointer events */
1420
+ &::after {
1421
+ pointer-events: none;
1422
+ }
1423
+
1424
+ & .segmentLayer {
1425
+ pointer-events: auto;
1426
+ }
1427
+ }
1428
+
1256
1429
  /* Event markers */
1257
1430
 
1258
1431
  /* Failure events - Red */