@workflow/web-shared 5.0.0-beta.0 → 5.0.0-beta.10

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 (275) hide show
  1. package/README.md +1 -1
  2. package/dist/components/error-boundary.js +1 -1
  3. package/dist/components/event-list-view.d.ts +18 -1
  4. package/dist/components/event-list-view.d.ts.map +1 -1
  5. package/dist/components/event-list-view.js +242 -142
  6. package/dist/components/hook-actions.js +1 -1
  7. package/dist/components/index.d.ts +4 -1
  8. package/dist/components/index.d.ts.map +1 -1
  9. package/dist/components/index.js +5 -2
  10. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  11. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  12. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  13. package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
  14. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  15. package/dist/components/new-trace-viewer/components/event-list.js +39 -0
  16. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  17. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  18. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  19. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  20. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  21. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  22. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  23. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  24. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  25. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  26. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  27. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  28. package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
  29. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  30. package/dist/components/new-trace-viewer/components/split-pane.js +99 -0
  31. package/dist/components/new-trace-viewer/components/timeline.d.ts +34 -0
  32. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  33. package/dist/components/new-trace-viewer/components/timeline.js +223 -0
  34. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  35. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  36. package/dist/components/new-trace-viewer/context.js +47 -0
  37. package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
  38. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
  39. package/dist/components/new-trace-viewer/detail-panel.js +13 -0
  40. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  41. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/icons.js +15 -0
  43. package/dist/components/new-trace-viewer/search.d.ts +19 -0
  44. package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
  45. package/dist/components/new-trace-viewer/search.js +134 -0
  46. package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
  47. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  48. package/dist/components/new-trace-viewer/trace-viewer.js +387 -0
  49. package/dist/components/new-trace-viewer/types.d.ts +1 -0
  50. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  51. package/dist/components/new-trace-viewer/types.js +2 -0
  52. package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
  53. package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/util/constants.js +2 -0
  55. package/dist/components/new-trace-viewer/utils.d.ts +46 -0
  56. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  57. package/dist/components/new-trace-viewer/utils.js +330 -0
  58. package/dist/components/run-trace-view.d.ts +2 -1
  59. package/dist/components/run-trace-view.d.ts.map +1 -1
  60. package/dist/components/run-trace-view.js +3 -3
  61. package/dist/components/sidebar/attribute-panel.d.ts +7 -1
  62. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  63. package/dist/components/sidebar/attribute-panel.js +131 -70
  64. package/dist/components/sidebar/conversation-view.js +1 -1
  65. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  66. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  67. package/dist/components/sidebar/copyable-data-block.js +16 -15
  68. package/dist/components/sidebar/detail-card.d.ts +5 -6
  69. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  70. package/dist/components/sidebar/detail-card.js +32 -16
  71. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  72. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  73. package/dist/components/sidebar/entity-detail-panel.js +20 -37
  74. package/dist/components/sidebar/events-list.d.ts +1 -1
  75. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  76. package/dist/components/sidebar/events-list.js +46 -32
  77. package/dist/components/sidebar/resolve-hook-modal.js +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/stream-viewer.d.ts +1 -1
  82. package/dist/components/stream-viewer.d.ts.map +1 -1
  83. package/dist/components/stream-viewer.js +3 -27
  84. package/dist/components/trace-viewer/components/map.js +1 -1
  85. package/dist/components/trace-viewer/components/markers.js +1 -1
  86. package/dist/components/trace-viewer/components/node.js +1 -1
  87. package/dist/components/trace-viewer/components/search-input.js +1 -1
  88. package/dist/components/trace-viewer/components/search.js +1 -1
  89. package/dist/components/trace-viewer/components/span-content.js +1 -1
  90. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  91. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  92. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  93. package/dist/components/trace-viewer/components/ui.js +1 -1
  94. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  95. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  96. package/dist/components/trace-viewer/context.js +1 -1
  97. package/dist/components/trace-viewer/index.js +1 -1
  98. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  99. package/dist/components/trace-viewer/types.js +1 -1
  100. package/dist/components/trace-viewer/util/constants.js +1 -1
  101. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  102. package/dist/components/trace-viewer/util/timing.d.ts +1 -1
  103. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  104. package/dist/components/trace-viewer/util/timing.js +6 -9
  105. package/dist/components/trace-viewer/util/tree.js +1 -1
  106. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  107. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  108. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  109. package/dist/components/trace-viewer/worker.js +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/ui/alert.js +1 -1
  114. package/dist/components/ui/button.d.ts +10 -0
  115. package/dist/components/ui/button.d.ts.map +1 -0
  116. package/dist/components/ui/button.js +46 -0
  117. package/dist/components/ui/card.js +1 -1
  118. package/dist/components/ui/data-inspector.d.ts +39 -1
  119. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  120. package/dist/components/ui/data-inspector.js +207 -22
  121. package/dist/components/ui/decrypt-button.js +1 -1
  122. package/dist/components/ui/error-card.js +1 -1
  123. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  124. package/dist/components/ui/error-stack-block.js +27 -19
  125. package/dist/components/ui/icon-button.d.ts +13 -0
  126. package/dist/components/ui/icon-button.d.ts.map +1 -0
  127. package/dist/components/ui/icon-button.js +29 -0
  128. package/dist/components/ui/inspector-theme.d.ts +9 -10
  129. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  130. package/dist/components/ui/inspector-theme.js +39 -72
  131. package/dist/components/ui/load-more-button.js +1 -1
  132. package/dist/components/ui/menu-dropdown.js +1 -1
  133. package/dist/components/ui/skeleton.js +1 -1
  134. package/dist/components/ui/spinner.js +1 -1
  135. package/dist/components/ui/timestamp-tooltip.js +1 -1
  136. package/dist/components/workflow-trace-view.d.ts +3 -1
  137. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  138. package/dist/components/workflow-trace-view.js +10 -5
  139. package/dist/components/workflow-traces/event-colors.js +1 -1
  140. package/dist/components/workflow-traces/trace-colors.js +1 -1
  141. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  142. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  143. package/dist/components/workflow-traces/trace-span-construction.js +19 -6
  144. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  145. package/dist/hooks/use-dark-mode.js +1 -1
  146. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  147. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  148. package/dist/hooks/use-reduced-motion.js +29 -0
  149. package/dist/index.d.ts +3 -1
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +3 -2
  152. package/dist/lib/event-analysis.js +1 -1
  153. package/dist/lib/event-materialization.js +1 -1
  154. package/dist/lib/exact-event-search-id.d.ts +25 -0
  155. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  156. package/dist/lib/exact-event-search-id.js +39 -0
  157. package/dist/lib/hydration.d.ts +15 -2
  158. package/dist/lib/hydration.d.ts.map +1 -1
  159. package/dist/lib/hydration.js +168 -16
  160. package/dist/lib/stream-display.d.ts +18 -0
  161. package/dist/lib/stream-display.d.ts.map +1 -0
  162. package/dist/lib/stream-display.js +30 -0
  163. package/dist/lib/toast.js +1 -1
  164. package/dist/lib/trace-builder.d.ts.map +1 -1
  165. package/dist/lib/trace-builder.js +3 -2
  166. package/dist/lib/utils.d.ts +9 -9
  167. package/dist/lib/utils.d.ts.map +1 -1
  168. package/dist/lib/utils.js +27 -32
  169. package/dist/styles.css +475 -1
  170. package/package.json +6 -4
  171. package/src/components/event-list-view.tsx +447 -282
  172. package/src/components/index.ts +12 -1
  173. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  174. package/src/components/new-trace-viewer/components/event-list.tsx +127 -0
  175. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  176. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  177. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  178. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  179. package/src/components/new-trace-viewer/components/split-pane.tsx +185 -0
  180. package/src/components/new-trace-viewer/components/timeline.tsx +552 -0
  181. package/src/components/new-trace-viewer/context.tsx +92 -0
  182. package/src/components/new-trace-viewer/detail-panel.tsx +55 -0
  183. package/src/components/new-trace-viewer/icons.tsx +79 -0
  184. package/src/components/new-trace-viewer/search.ts +178 -0
  185. package/src/components/new-trace-viewer/trace-viewer.tsx +694 -0
  186. package/src/components/new-trace-viewer/types.ts +0 -0
  187. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  188. package/src/components/new-trace-viewer/utils.ts +482 -0
  189. package/src/components/run-trace-view.tsx +3 -0
  190. package/src/components/sidebar/attribute-panel.tsx +240 -196
  191. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  192. package/src/components/sidebar/detail-card.tsx +119 -59
  193. package/src/components/sidebar/entity-detail-panel.tsx +92 -148
  194. package/src/components/sidebar/events-list.tsx +93 -108
  195. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  196. package/src/components/stream-viewer.tsx +18 -40
  197. package/src/components/trace-viewer/util/timing.ts +5 -9
  198. package/src/components/trace-viewer-new.tsx +49 -0
  199. package/src/components/ui/button.tsx +68 -0
  200. package/src/components/ui/data-inspector.tsx +427 -31
  201. package/src/components/ui/error-stack-block.tsx +39 -30
  202. package/src/components/ui/icon-button.tsx +57 -0
  203. package/src/components/ui/inspector-theme.ts +39 -81
  204. package/src/components/workflow-trace-view.tsx +12 -2
  205. package/src/components/workflow-traces/trace-span-construction.ts +24 -6
  206. package/src/hooks/use-reduced-motion.ts +34 -0
  207. package/src/index.ts +9 -0
  208. package/src/lib/exact-event-search-id.ts +67 -0
  209. package/src/lib/hydration.ts +182 -14
  210. package/src/lib/stream-display.ts +48 -0
  211. package/src/lib/trace-builder.ts +2 -1
  212. package/src/lib/utils.ts +27 -32
  213. package/src/styles.css +475 -1
  214. package/dist/components/error-boundary.js.map +0 -1
  215. package/dist/components/event-list-view.js.map +0 -1
  216. package/dist/components/hook-actions.js.map +0 -1
  217. package/dist/components/index.js.map +0 -1
  218. package/dist/components/run-trace-view.js.map +0 -1
  219. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  220. package/dist/components/sidebar/conversation-view.js.map +0 -1
  221. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  222. package/dist/components/sidebar/detail-card.js.map +0 -1
  223. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  224. package/dist/components/sidebar/events-list.js.map +0 -1
  225. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  226. package/dist/components/stream-viewer.js.map +0 -1
  227. package/dist/components/trace-viewer/components/map.js.map +0 -1
  228. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  229. package/dist/components/trace-viewer/components/node.js.map +0 -1
  230. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  231. package/dist/components/trace-viewer/components/search.js.map +0 -1
  232. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  233. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  234. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  235. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  236. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  237. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  238. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  239. package/dist/components/trace-viewer/context.js.map +0 -1
  240. package/dist/components/trace-viewer/index.js.map +0 -1
  241. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  242. package/dist/components/trace-viewer/types.js.map +0 -1
  243. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  244. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  245. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  246. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  247. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  248. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  249. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  250. package/dist/components/trace-viewer/worker.js.map +0 -1
  251. package/dist/components/ui/alert.js.map +0 -1
  252. package/dist/components/ui/card.js.map +0 -1
  253. package/dist/components/ui/data-inspector.js.map +0 -1
  254. package/dist/components/ui/decrypt-button.js.map +0 -1
  255. package/dist/components/ui/error-card.js.map +0 -1
  256. package/dist/components/ui/error-stack-block.js.map +0 -1
  257. package/dist/components/ui/inspector-theme.js.map +0 -1
  258. package/dist/components/ui/load-more-button.js.map +0 -1
  259. package/dist/components/ui/menu-dropdown.js.map +0 -1
  260. package/dist/components/ui/skeleton.js.map +0 -1
  261. package/dist/components/ui/spinner.js.map +0 -1
  262. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  263. package/dist/components/workflow-trace-view.js.map +0 -1
  264. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  265. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  266. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  267. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  268. package/dist/hooks/use-dark-mode.js.map +0 -1
  269. package/dist/index.js.map +0 -1
  270. package/dist/lib/event-analysis.js.map +0 -1
  271. package/dist/lib/event-materialization.js.map +0 -1
  272. package/dist/lib/hydration.js.map +0 -1
  273. package/dist/lib/toast.js.map +0 -1
  274. package/dist/lib/trace-builder.js.map +0 -1
  275. package/dist/lib/utils.js.map +0 -1
@@ -0,0 +1,694 @@
1
+ 'use client';
2
+
3
+ import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
+ import {
5
+ ChevronDown,
6
+ ChevronUp,
7
+ RotateCcw,
8
+ Search,
9
+ X,
10
+ ZoomIn,
11
+ ZoomOut,
12
+ } from 'lucide-react';
13
+ import {
14
+ type ReactNode,
15
+ useCallback,
16
+ useDeferredValue,
17
+ useEffect,
18
+ useMemo,
19
+ useRef,
20
+ useState,
21
+ } from 'react';
22
+ import { useReducedMotion } from '../../hooks/use-reduced-motion';
23
+ import { ErrorBoundary } from '../error-boundary';
24
+ import {
25
+ EntityDetailPanel,
26
+ type SelectedSpanInfo,
27
+ } from '../sidebar/entity-detail-panel';
28
+ import { useSidebarDataOptional } from '../sidebar/sidebar-data-context';
29
+ import type { Trace } from '../trace-viewer/types';
30
+ import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
31
+ import { IconButton } from '../ui/icon-button';
32
+ import EventList from './components/event-list';
33
+ import { SplitPane } from './components/split-pane';
34
+ import {
35
+ TIMELINE_PADDING_PX,
36
+ Timeline,
37
+ TimelineHeader,
38
+ } from './components/timeline';
39
+ import { ActiveSpanProvider, useActiveSpan } from './context';
40
+ import { DetailPanel } from './detail-panel';
41
+ import { searchSpans } from './search';
42
+ import { computeRootBounds, computeTimeMarkers } from './utils';
43
+
44
+ interface NewTraceViewerProps {
45
+ trace: Trace;
46
+ }
47
+
48
+ const MIN_VIEWPORT_MS = 0.001;
49
+
50
+ interface Viewport {
51
+ start: number;
52
+ end: number;
53
+ }
54
+
55
+ function useAnimatedViewport(initial: Viewport) {
56
+ const [viewport, setViewportState] = useState<Viewport>(initial);
57
+ const animRef = useRef<{
58
+ raf: number;
59
+ from: Viewport;
60
+ to: Viewport;
61
+ start: number;
62
+ } | null>(null);
63
+ const currentRef = useRef(initial);
64
+ currentRef.current = viewport;
65
+ const reducedMotion = useReducedMotion();
66
+
67
+ const cancel = useCallback(() => {
68
+ if (animRef.current) {
69
+ cancelAnimationFrame(animRef.current.raf);
70
+ animRef.current = null;
71
+ }
72
+ }, []);
73
+
74
+ const animateTo = useCallback(
75
+ (target: Viewport) => {
76
+ cancel();
77
+
78
+ if (reducedMotion) {
79
+ currentRef.current = target;
80
+ setViewportState(target);
81
+ return;
82
+ }
83
+
84
+ const from = currentRef.current;
85
+ const anim = { raf: 0, from, to: target, start: performance.now() };
86
+
87
+ const tick = () => {
88
+ const t = Math.min((performance.now() - anim.start) / 150, 1);
89
+ const e = 1 - (1 - t) * (1 - t);
90
+ setViewportState({
91
+ start: anim.from.start + (anim.to.start - anim.from.start) * e,
92
+ end: anim.from.end + (anim.to.end - anim.from.end) * e,
93
+ });
94
+ if (t < 1) anim.raf = requestAnimationFrame(tick);
95
+ else animRef.current = null;
96
+ };
97
+
98
+ animRef.current = anim;
99
+ anim.raf = requestAnimationFrame(tick);
100
+ },
101
+ [cancel, reducedMotion]
102
+ );
103
+
104
+ const setViewport = useCallback(
105
+ (update: Viewport | ((prev: Viewport) => Viewport)) => {
106
+ cancel();
107
+ if (typeof update === 'function') {
108
+ setViewportState((prev) => {
109
+ const next = update(prev);
110
+ currentRef.current = next;
111
+ return next;
112
+ });
113
+ } else {
114
+ currentRef.current = update;
115
+ setViewportState(update);
116
+ }
117
+ },
118
+ [cancel]
119
+ );
120
+
121
+ useEffect(() => cancel, [cancel]);
122
+
123
+ return { viewport, setViewport, animateTo };
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Hook: bridge ActiveSpanContext + SidebarDataContext → SelectedSpanInfo
128
+ // ---------------------------------------------------------------------------
129
+
130
+ function useSelectedSpanInfo(): SelectedSpanInfo | null {
131
+ const { activeSpan } = useActiveSpan();
132
+ const sidebar = useSidebarDataOptional();
133
+
134
+ return useMemo(() => {
135
+ if (!activeSpan || !sidebar) return null;
136
+
137
+ const correlationId = activeSpan.spanId;
138
+ const rawEvents = correlationId
139
+ ? sidebar.events.filter((e) => e.correlationId === correlationId)
140
+ : [];
141
+
142
+ return {
143
+ data: activeSpan.attributes?.data,
144
+ resource: activeSpan.attributes?.resource as string | undefined,
145
+ spanId: activeSpan.spanId,
146
+ rawEvents,
147
+ };
148
+ }, [activeSpan, sidebar]);
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Root component
153
+ // ---------------------------------------------------------------------------
154
+
155
+ export function NewTraceViewer({ trace }: NewTraceViewerProps): ReactNode {
156
+ return (
157
+ <ActiveSpanProvider spans={trace.spans}>
158
+ <NewTraceViewerContent trace={trace} />
159
+ </ActiveSpanProvider>
160
+ );
161
+ }
162
+
163
+ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
164
+ const { activeSpan, activeSpanId, setActiveSpan, clearActiveSpan } =
165
+ useActiveSpan();
166
+
167
+ const sidebar = useSidebarDataOptional();
168
+ const selectedSpan = useSelectedSpanInfo();
169
+
170
+ const [searchQuery, setSearchQuery] = useState('');
171
+ const deferredSearchQuery = useDeferredValue(searchQuery);
172
+
173
+ const searchResult = useMemo(
174
+ () => searchSpans(trace.spans, deferredSearchQuery),
175
+ [trace.spans, deferredSearchQuery]
176
+ );
177
+
178
+ const root = useMemo(() => computeRootBounds(trace.spans), [trace.spans]);
179
+
180
+ const { viewport, setViewport, animateTo } = useAnimatedViewport({
181
+ start: root.startTime,
182
+ end: root.startTime + root.duration,
183
+ });
184
+
185
+ const prevRootRef = useRef<Viewport>({
186
+ start: root.startTime,
187
+ end: root.startTime + root.duration,
188
+ });
189
+
190
+ useEffect(() => {
191
+ const prevRoot = prevRootRef.current;
192
+ const newStart = root.startTime;
193
+ const newEnd = root.startTime + root.duration;
194
+
195
+ setViewport((prev) => {
196
+ const wasAtFullExtent =
197
+ Math.abs(prev.start - prevRoot.start) < 0.01 &&
198
+ Math.abs(prev.end - prevRoot.end) < 0.01;
199
+
200
+ if (wasAtFullExtent) {
201
+ return { start: newStart, end: newEnd };
202
+ }
203
+ return prev;
204
+ });
205
+
206
+ prevRootRef.current = { start: newStart, end: newEnd };
207
+ }, [root.startTime, root.duration, setViewport]);
208
+
209
+ const viewDuration = viewport.end - viewport.start;
210
+
211
+ const timeMarkers = useMemo(
212
+ () => computeTimeMarkers(viewDuration, viewport.start - root.startTime),
213
+ [viewDuration, viewport.start, root.startTime]
214
+ );
215
+
216
+ const resetZoom = useCallback(() => {
217
+ animateTo({ start: root.startTime, end: root.startTime + root.duration });
218
+ }, [animateTo, root.startTime, root.duration]);
219
+
220
+ const ZOOM_FACTOR = 0.5;
221
+
222
+ const zoomBy = useCallback(
223
+ (factor: number) => {
224
+ const rootS = root.startTime;
225
+ const rootE = root.startTime + root.duration;
226
+ const rootD = root.duration;
227
+
228
+ setViewport((prev) => {
229
+ const prevDuration = prev.end - prev.start;
230
+ const center = (prev.start + prev.end) / 2;
231
+ const newDuration = Math.max(
232
+ MIN_VIEWPORT_MS,
233
+ Math.min(rootD, prevDuration * factor)
234
+ );
235
+ let newStart = center - newDuration / 2;
236
+ let newEnd = center + newDuration / 2;
237
+
238
+ if (newStart < rootS) {
239
+ newStart = rootS;
240
+ newEnd = rootS + newDuration;
241
+ }
242
+ if (newEnd > rootE) {
243
+ newEnd = rootE;
244
+ newStart = Math.max(rootS, rootE - newDuration);
245
+ }
246
+
247
+ return { start: newStart, end: newEnd };
248
+ });
249
+ },
250
+ [setViewport, root.startTime, root.duration]
251
+ );
252
+
253
+ const zoomIn = useCallback(() => zoomBy(ZOOM_FACTOR), [zoomBy]);
254
+ const zoomOut = useCallback(() => zoomBy(1 / ZOOM_FACTOR), [zoomBy]);
255
+
256
+ const handleSelectSpan = useCallback(
257
+ (spanId: string) => {
258
+ if (spanId === activeSpanId) {
259
+ clearActiveSpan();
260
+ return;
261
+ }
262
+ setActiveSpan(spanId);
263
+
264
+ const span = trace.spans.find((s) => s.spanId === spanId);
265
+ if (!span) return;
266
+
267
+ const spanStart = getHighResInMs(span.startTime);
268
+ const spanEnd = getHighResInMs(span.endTime);
269
+ const spanDuration = spanEnd - spanStart;
270
+
271
+ const rootS = root.startTime;
272
+ const rootE = root.startTime + root.duration;
273
+ const rootD = root.duration;
274
+
275
+ if (spanDuration > rootD * 0.8) {
276
+ animateTo({ start: rootS, end: rootE });
277
+ return;
278
+ }
279
+
280
+ const padding = Math.max(spanDuration * 0.2, MIN_VIEWPORT_MS / 2);
281
+ let newStart = spanStart - padding;
282
+ let newEnd = spanEnd + padding;
283
+
284
+ if (newEnd - newStart < MIN_VIEWPORT_MS) {
285
+ const center = (spanStart + spanEnd) / 2;
286
+ newStart = center - MIN_VIEWPORT_MS / 2;
287
+ newEnd = center + MIN_VIEWPORT_MS / 2;
288
+ }
289
+
290
+ if (newStart < rootS) {
291
+ const duration = newEnd - newStart;
292
+ newStart = rootS;
293
+ newEnd = Math.min(rootE, rootS + duration);
294
+ }
295
+ if (newEnd > rootE) {
296
+ const duration = newEnd - newStart;
297
+ newEnd = rootE;
298
+ newStart = Math.max(rootS, rootE - duration);
299
+ }
300
+
301
+ animateTo({ start: newStart, end: newEnd });
302
+ },
303
+ [
304
+ animateTo,
305
+ setActiveSpan,
306
+ clearActiveSpan,
307
+ activeSpanId,
308
+ trace.spans,
309
+ root.startTime,
310
+ root.duration,
311
+ ]
312
+ );
313
+
314
+ const [altHeld, setAltHeld] = useState(false);
315
+
316
+ useEffect(() => {
317
+ const handleSidebarNavKey = (e: KeyboardEvent): void => {
318
+ const target = e.target as HTMLElement | null;
319
+ if (
320
+ target instanceof HTMLInputElement ||
321
+ target instanceof HTMLTextAreaElement ||
322
+ target?.isContentEditable
323
+ ) {
324
+ return;
325
+ }
326
+ const targetId =
327
+ e.key === 'k' ? prevSpanIdRef.current : nextSpanIdRef.current;
328
+ if (targetId) {
329
+ e.preventDefault();
330
+ handleSelectSpanRef.current(targetId);
331
+ }
332
+ };
333
+
334
+ const onKeyDown = (e: KeyboardEvent): void => {
335
+ if (e.key === 'Escape') {
336
+ clearActiveSpan();
337
+ } else if (e.key === 'Alt') {
338
+ e.preventDefault();
339
+ setAltHeld(true);
340
+ } else if (e.key === 'j' || e.key === 'k') {
341
+ handleSidebarNavKey(e);
342
+ }
343
+ };
344
+ const onKeyUp = (e: KeyboardEvent): void => {
345
+ if (e.key === 'Alt') setAltHeld(false);
346
+ };
347
+ const onBlur = (): void => setAltHeld(false);
348
+
349
+ window.addEventListener('keydown', onKeyDown);
350
+ window.addEventListener('keyup', onKeyUp);
351
+ window.addEventListener('blur', onBlur);
352
+ return () => {
353
+ window.removeEventListener('keydown', onKeyDown);
354
+ window.removeEventListener('keyup', onKeyUp);
355
+ window.removeEventListener('blur', onBlur);
356
+ };
357
+ }, [clearActiveSpan]);
358
+
359
+ const timelineRef = useRef<HTMLDivElement>(null);
360
+ const [hoverFraction, setHoverFraction] = useState<number | null>(null);
361
+
362
+ const hoverInfo = useMemo(() => {
363
+ if (hoverFraction == null) return null;
364
+ const absTime = viewport.start + hoverFraction * viewDuration;
365
+ const offset = absTime - root.startTime;
366
+ return { fraction: hoverFraction, label: formatDuration(offset, true) };
367
+ }, [hoverFraction, viewport.start, viewDuration, root.startTime]);
368
+
369
+ const handleTimelineMouseMove = useCallback(
370
+ (e: React.MouseEvent<HTMLDivElement>) => {
371
+ const el = timelineRef.current;
372
+ if (!el) return;
373
+ const rect = el.getBoundingClientRect();
374
+ const contentWidth = rect.width - TIMELINE_PADDING_PX * 2;
375
+ if (contentWidth <= 0) return;
376
+ const fraction = Math.max(
377
+ 0,
378
+ Math.min(
379
+ 1,
380
+ (e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
381
+ )
382
+ );
383
+ setHoverFraction(fraction);
384
+ },
385
+ []
386
+ );
387
+
388
+ const handleTimelineMouseLeave = useCallback(() => {
389
+ setHoverFraction(null);
390
+ }, []);
391
+
392
+ useEffect(() => {
393
+ const el = timelineRef.current;
394
+ if (!el) return;
395
+
396
+ const rootS = root.startTime;
397
+ const rootE = root.startTime + root.duration;
398
+ const rootD = root.duration;
399
+ if (rootD <= 0) return;
400
+
401
+ const onWheel = (e: WheelEvent): void => {
402
+ const isZoomGesture = e.ctrlKey || e.metaKey;
403
+ const hasDeltaX = Math.abs(e.deltaX) > Math.abs(e.deltaY);
404
+
405
+ if (!isZoomGesture && !hasDeltaX) return;
406
+ e.preventDefault();
407
+
408
+ const rect = el.getBoundingClientRect();
409
+ const contentWidth = rect.width - TIMELINE_PADDING_PX * 2;
410
+ if (contentWidth <= 0) return;
411
+
412
+ if (isZoomGesture) {
413
+ let dy = e.deltaY;
414
+ if (e.deltaMode === 1) dy *= 16;
415
+
416
+ const cursorFraction = Math.max(
417
+ 0,
418
+ Math.min(
419
+ 1,
420
+ (e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
421
+ )
422
+ );
423
+ const isMouseWheel = e.deltaMode === 1 || Math.abs(e.deltaY) >= 50;
424
+ const scaleFactor = Math.pow(2, dy / (isMouseWheel ? 200 : 60));
425
+
426
+ setViewport((prev) => {
427
+ const prevDuration = prev.end - prev.start;
428
+ const cursorTime = prev.start + cursorFraction * prevDuration;
429
+ const newDuration = Math.max(
430
+ MIN_VIEWPORT_MS,
431
+ Math.min(rootD, prevDuration * scaleFactor)
432
+ );
433
+
434
+ let newStart = cursorTime - cursorFraction * newDuration;
435
+ let newEnd = newStart + newDuration;
436
+
437
+ if (newStart < rootS) {
438
+ newStart = rootS;
439
+ newEnd = rootS + newDuration;
440
+ }
441
+ if (newEnd > rootE) {
442
+ newEnd = rootE;
443
+ newStart = Math.max(rootS, rootE - newDuration);
444
+ }
445
+
446
+ return { start: newStart, end: newEnd };
447
+ });
448
+ } else {
449
+ let dx = e.deltaX;
450
+ if (e.deltaMode === 1) dx *= 16;
451
+
452
+ setViewport((prev) => {
453
+ const prevDuration = prev.end - prev.start;
454
+ const panAmount = (dx / contentWidth) * prevDuration;
455
+
456
+ let newStart = prev.start + panAmount;
457
+ let newEnd = prev.end + panAmount;
458
+
459
+ if (newStart < rootS) {
460
+ newStart = rootS;
461
+ newEnd = rootS + prevDuration;
462
+ }
463
+ if (newEnd > rootE) {
464
+ newEnd = rootE;
465
+ newStart = Math.max(rootS, rootE - prevDuration);
466
+ }
467
+
468
+ return { start: newStart, end: newEnd };
469
+ });
470
+ }
471
+ };
472
+
473
+ el.addEventListener('wheel', onWheel, { passive: false });
474
+ return () => el.removeEventListener('wheel', onWheel);
475
+ }, [root.startTime, root.duration, setViewport]);
476
+
477
+ // Derive the selected span name and metadata for the panel header
478
+ const selectedSpanName = useMemo(() => {
479
+ if (!selectedSpan?.data) return 'Details';
480
+ const data = selectedSpan.data as Record<string, unknown>;
481
+ if (selectedSpan.resource === 'hook') {
482
+ return (data.token as string | undefined) ?? (data.hookId as string);
483
+ }
484
+
485
+ const stepName = data.stepName as string | undefined;
486
+ const workflowName = data.workflowName as string | undefined;
487
+ return (
488
+ (stepName ? parseStepName(stepName)?.shortName : undefined) ??
489
+ (workflowName ? parseWorkflowName(workflowName)?.shortName : undefined) ??
490
+ stepName ??
491
+ workflowName ??
492
+ (data.hookId as string) ??
493
+ 'Details'
494
+ );
495
+ }, [selectedSpan?.data, selectedSpan?.resource]);
496
+
497
+ const { prevSpanId, nextSpanId } = useMemo(() => {
498
+ if (!activeSpanId) return { prevSpanId: null, nextSpanId: null };
499
+ const i = trace.spans.findIndex((s) => s.spanId === activeSpanId);
500
+ if (i === -1) return { prevSpanId: null, nextSpanId: null };
501
+ return {
502
+ prevSpanId: trace.spans[i - 1]?.spanId ?? null,
503
+ nextSpanId: trace.spans[i + 1]?.spanId ?? null,
504
+ };
505
+ }, [activeSpanId, trace.spans]);
506
+
507
+ const handleSelectPrevSpan = useCallback(() => {
508
+ if (prevSpanId) handleSelectSpan(prevSpanId);
509
+ }, [prevSpanId, handleSelectSpan]);
510
+
511
+ const handleSelectNextSpan = useCallback(() => {
512
+ if (nextSpanId) handleSelectSpan(nextSpanId);
513
+ }, [nextSpanId, handleSelectSpan]);
514
+
515
+ const prevSpanIdRef = useRef(prevSpanId);
516
+ const nextSpanIdRef = useRef(nextSpanId);
517
+ const handleSelectSpanRef = useRef(handleSelectSpan);
518
+ prevSpanIdRef.current = prevSpanId;
519
+ nextSpanIdRef.current = nextSpanId;
520
+ handleSelectSpanRef.current = handleSelectSpan;
521
+
522
+ return (
523
+ <div
524
+ data-pane="pane-root"
525
+ data-has-detail={activeSpan ? '' : undefined}
526
+ className="grid w-full h-full max-h-full grid-cols-[minmax(100px,1fr)] data-[has-detail]:grid-cols-[minmax(100px,1fr)_clamp(280px,420px,100%)]"
527
+ >
528
+ <div
529
+ id="trace-parent"
530
+ className="grid grid-rows-[1fr] h-full min-h-0 overflow-hidden relative bg-background-100"
531
+ >
532
+ <SplitPane
533
+ startHeader={
534
+ <div className="bg-background-100 border-b border-gray-alpha-400 h-10 min-h-10 flex items-center pl-4 pr-2 gap-1.5">
535
+ <Search className="w-3.5 h-3.5 shrink-0 text-gray-800" />
536
+ <input
537
+ id="trace-viewer-search"
538
+ name="trace-viewer-search"
539
+ type="text"
540
+ value={searchQuery}
541
+ onChange={(e) => setSearchQuery(e.target.value)}
542
+ onKeyDown={(e) => {
543
+ if (e.key === 'Escape' && searchQuery) {
544
+ e.preventDefault();
545
+ e.stopPropagation();
546
+ setSearchQuery('');
547
+ }
548
+ }}
549
+ placeholder="Search spans..."
550
+ aria-label="Search spans"
551
+ className="flex-1 min-w-0 bg-transparent text-sm text-gray-1000 placeholder:text-gray-800 outline-none"
552
+ />
553
+ {searchQuery && (
554
+ <button
555
+ type="button"
556
+ aria-label="Clear search"
557
+ onClick={() => setSearchQuery('')}
558
+ className="-mr-2 hidden h-full max-w-full shrink-0 cursor-pointer items-center rounded-r-md border-0 bg-transparent px-2.5 font-inherit text-base text-gray-900 no-underline transition-colors duration-150 ease-in hover:text-gray-1000 focus-visible:-outline-offset-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--ds-focus-color)] min-[961px]:flex"
559
+ >
560
+ <kbd className="inline-flex h-5 min-h-5 min-w-5 items-center justify-center rounded border border-gray-alpha-400 bg-background-100 px-1 font-sans text-[13px] font-medium leading-[1.7em] text-gray-900">
561
+ Esc
562
+ </kbd>
563
+ </button>
564
+ )}
565
+ </div>
566
+ }
567
+ endHeader={
568
+ <TimelineHeader markers={timeMarkers} hoverInfo={hoverInfo} />
569
+ }
570
+ >
571
+ <div className="block overflow-visible">
572
+ <EventList
573
+ spans={trace.spans}
574
+ activeSpanId={activeSpanId}
575
+ searchResult={searchResult}
576
+ onSelectSpan={handleSelectSpan}
577
+ />
578
+ </div>
579
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: timeline hover and wheel gestures are pointer-only annotations */}
580
+ <div
581
+ ref={timelineRef}
582
+ className="block min-h-0 overflow-visible relative"
583
+ onDoubleClick={resetZoom}
584
+ onMouseMove={handleTimelineMouseMove}
585
+ onMouseLeave={handleTimelineMouseLeave}
586
+ >
587
+ <Timeline
588
+ spans={trace.spans}
589
+ viewStart={viewport.start}
590
+ viewEnd={viewport.end}
591
+ markers={timeMarkers}
592
+ selectedId={activeSpanId}
593
+ searchResult={searchResult}
594
+ onSelect={handleSelectSpan}
595
+ hoverFraction={hoverFraction}
596
+ altHeld={altHeld}
597
+ />
598
+ </div>
599
+ </SplitPane>
600
+ <div className="absolute right-3 bottom-3 z-[5] flex items-center border border-gray-alpha-400 rounded-md bg-background-100 shadow-sm overflow-hidden divide-x divide-gray-alpha-400">
601
+ <IconButton
602
+ variant="muted"
603
+ size="small"
604
+ onClick={zoomOut}
605
+ aria-label="Zoom out"
606
+ >
607
+ <ZoomOut className="w-4 h-4" />
608
+ </IconButton>
609
+ <IconButton
610
+ variant="muted"
611
+ size="small"
612
+ onClick={resetZoom}
613
+ aria-label="Reset zoom"
614
+ >
615
+ <RotateCcw className="w-3.5 h-3.5" />
616
+ </IconButton>
617
+ <IconButton
618
+ variant="muted"
619
+ size="small"
620
+ onClick={zoomIn}
621
+ aria-label="Zoom in"
622
+ >
623
+ <ZoomIn className="w-4 h-4" />
624
+ </IconButton>
625
+ </div>
626
+ </div>
627
+
628
+ {/* Detail panel */}
629
+ {activeSpan && sidebar ? (
630
+ <aside className="flex flex-col h-full max-h-full bg-background-100 border-l border-gray-alpha-400 overflow-auto">
631
+ {/* Panel header */}
632
+ <div className="flex items-center justify-between gap-2 shrink-0 px-4 pt-3 pb-3">
633
+ <span className="text-label-14 font-medium text-gray-1000 truncate block">
634
+ {selectedSpanName}
635
+ </span>
636
+ <div className="flex items-center gap-0.5 shrink-0">
637
+ <IconButton
638
+ aria-label="Navigate to previous span"
639
+ aria-keyshortcuts="K"
640
+ onClick={handleSelectPrevSpan}
641
+ disabled={!prevSpanId}
642
+ >
643
+ <ChevronUp className="w-4 h-4" />
644
+ </IconButton>
645
+ <IconButton
646
+ aria-label="Navigate to next span"
647
+ aria-keyshortcuts="J"
648
+ onClick={handleSelectNextSpan}
649
+ disabled={!nextSpanId}
650
+ >
651
+ <ChevronDown className="w-4 h-4" />
652
+ </IconButton>
653
+ <div aria-hidden className="w-px h-4 bg-gray-alpha-400 mx-1" />
654
+ <IconButton
655
+ aria-label="Close span details"
656
+ aria-keyshortcuts="Escape"
657
+ onClick={clearActiveSpan}
658
+ >
659
+ <X className="w-4 h-4" />
660
+ </IconButton>
661
+ </div>
662
+ </div>
663
+ {/* Panel body */}
664
+ <div className="flex-1 overflow-y-auto">
665
+ <ErrorBoundary>
666
+ <EntityDetailPanel
667
+ run={sidebar.run}
668
+ onStreamClick={sidebar.onStreamClick}
669
+ onRunClick={sidebar.onRunClick}
670
+ spanDetailData={sidebar.spanDetailData}
671
+ spanDetailError={sidebar.spanDetailError}
672
+ spanDetailLoading={sidebar.spanDetailLoading}
673
+ onSpanSelect={sidebar.onSpanSelect}
674
+ onWakeUpSleep={sidebar.onWakeUpSleep}
675
+ onLoadEventData={sidebar.onLoadEventData}
676
+ onResolveHook={sidebar.onResolveHook}
677
+ encryptionKey={sidebar.encryptionKey}
678
+ onDecrypt={sidebar.onDecrypt}
679
+ isDecrypting={sidebar.isDecrypting}
680
+ selectedSpan={selectedSpan}
681
+ />
682
+ </ErrorBoundary>
683
+ </div>
684
+ </aside>
685
+ ) : activeSpan ? (
686
+ <DetailPanel
687
+ span={activeSpan}
688
+ rootStart={root.startTime}
689
+ onClose={clearActiveSpan}
690
+ />
691
+ ) : null}
692
+ </div>
693
+ );
694
+ }
File without changes