@workflow/web-shared 5.0.0-beta.2 → 5.0.0-beta.20

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 (321) hide show
  1. package/dist/components/error-boundary.js +1 -1
  2. package/dist/components/event-list-view.d.ts +16 -1
  3. package/dist/components/event-list-view.d.ts.map +1 -1
  4. package/dist/components/event-list-view.js +196 -85
  5. package/dist/components/hook-actions.js +1 -1
  6. package/dist/components/index.d.ts +4 -0
  7. package/dist/components/index.d.ts.map +1 -1
  8. package/dist/components/index.js +5 -1
  9. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  10. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  11. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  12. package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
  13. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/event-list.js +46 -0
  15. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  16. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  17. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  18. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  19. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  20. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  21. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  22. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  23. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  24. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  25. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  26. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  27. package/dist/components/new-trace-viewer/components/span-markers.d.ts +46 -0
  28. package/dist/components/new-trace-viewer/components/span-markers.d.ts.map +1 -0
  29. package/dist/components/new-trace-viewer/components/span-markers.js +85 -0
  30. package/dist/components/new-trace-viewer/components/split-pane.d.ts +15 -0
  31. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  32. package/dist/components/new-trace-viewer/components/split-pane.js +105 -0
  33. package/dist/components/new-trace-viewer/components/timeline.d.ts +36 -0
  34. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  35. package/dist/components/new-trace-viewer/components/timeline.js +254 -0
  36. package/dist/components/new-trace-viewer/components/timeline.module.css +46 -0
  37. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
  38. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
  39. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +19 -0
  40. package/dist/components/new-trace-viewer/components/use-row-window.d.ts +34 -0
  41. package/dist/components/new-trace-viewer/components/use-row-window.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/components/use-row-window.js +107 -0
  43. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  44. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  45. package/dist/components/new-trace-viewer/context.js +47 -0
  46. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  47. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  48. package/dist/components/new-trace-viewer/icons.js +15 -0
  49. package/dist/components/new-trace-viewer/search.d.ts +19 -0
  50. package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
  51. package/dist/components/new-trace-viewer/search.js +134 -0
  52. package/dist/components/new-trace-viewer/trace-viewer.d.ts +11 -0
  53. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/trace-viewer.js +462 -0
  55. package/dist/components/new-trace-viewer/types.d.ts +3 -0
  56. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  57. package/dist/components/new-trace-viewer/types.js +2 -0
  58. package/dist/components/new-trace-viewer/utils.d.ts +61 -0
  59. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  60. package/dist/components/new-trace-viewer/utils.js +345 -0
  61. package/dist/components/run-trace-view.js +1 -1
  62. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  63. package/dist/components/sidebar/attribute-panel.js +129 -102
  64. package/dist/components/sidebar/attributes-block.d.ts +28 -0
  65. package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
  66. package/dist/components/sidebar/attributes-block.js +81 -0
  67. package/dist/components/sidebar/conversation-view.js +1 -1
  68. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  69. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  70. package/dist/components/sidebar/copyable-data-block.js +16 -15
  71. package/dist/components/sidebar/detail-card.d.ts +5 -6
  72. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  73. package/dist/components/sidebar/detail-card.js +32 -16
  74. package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
  75. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  76. package/dist/components/sidebar/entity-detail-panel.js +17 -35
  77. package/dist/components/sidebar/events-list.d.ts +3 -1
  78. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  79. package/dist/components/sidebar/events-list.js +36 -22
  80. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  81. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  82. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  83. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  84. package/dist/components/sidebar/span-detail-merge.d.ts +16 -0
  85. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  86. package/dist/components/sidebar/span-detail-merge.js +53 -0
  87. package/dist/components/stream-viewer.d.ts +1 -1
  88. package/dist/components/stream-viewer.d.ts.map +1 -1
  89. package/dist/components/stream-viewer.js +3 -27
  90. package/dist/components/trace-viewer/components/map.js +1 -1
  91. package/dist/components/trace-viewer/components/markers.js +1 -1
  92. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  93. package/dist/components/trace-viewer/components/node.js +2 -1
  94. package/dist/components/trace-viewer/components/search-input.js +1 -1
  95. package/dist/components/trace-viewer/components/search.js +1 -1
  96. package/dist/components/trace-viewer/components/span-content.js +1 -1
  97. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  98. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  99. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  100. package/dist/components/trace-viewer/components/ui.js +1 -1
  101. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  102. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  103. package/dist/components/trace-viewer/context.js +1 -1
  104. package/dist/components/trace-viewer/index.js +1 -1
  105. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  106. package/dist/components/trace-viewer/trace-viewer.module.css +4 -4
  107. package/dist/components/trace-viewer/types.js +1 -1
  108. package/dist/components/trace-viewer/util/constants.js +1 -1
  109. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  110. package/dist/components/trace-viewer/util/timing.d.ts +3 -3
  111. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  112. package/dist/components/trace-viewer/util/timing.js +8 -11
  113. package/dist/components/trace-viewer/util/tree.js +1 -1
  114. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  115. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  116. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  117. package/dist/components/trace-viewer/worker.js +1 -1
  118. package/dist/components/trace-viewer-new.d.ts +14 -0
  119. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  120. package/dist/components/trace-viewer-new.js +21 -0
  121. package/dist/components/ui/alert.js +1 -1
  122. package/dist/components/ui/button.d.ts +10 -0
  123. package/dist/components/ui/button.d.ts.map +1 -0
  124. package/dist/components/ui/button.js +46 -0
  125. package/dist/components/ui/card.js +1 -1
  126. package/dist/components/ui/context-card.d.ts +47 -0
  127. package/dist/components/ui/context-card.d.ts.map +1 -0
  128. package/dist/components/ui/context-card.js +471 -0
  129. package/dist/components/ui/data-inspector.d.ts +22 -0
  130. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  131. package/dist/components/ui/data-inspector.js +103 -13
  132. package/dist/components/ui/decrypt-button.js +1 -1
  133. package/dist/components/ui/error-card.js +1 -1
  134. package/dist/components/ui/error-stack-block.d.ts +17 -10
  135. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  136. package/dist/components/ui/error-stack-block.js +49 -30
  137. package/dist/components/ui/icon-button.d.ts +19 -0
  138. package/dist/components/ui/icon-button.d.ts.map +1 -0
  139. package/dist/components/ui/icon-button.js +29 -0
  140. package/dist/components/ui/inspector-theme.d.ts +9 -10
  141. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  142. package/dist/components/ui/inspector-theme.js +39 -72
  143. package/dist/components/ui/kbd.d.ts +6 -0
  144. package/dist/components/ui/kbd.d.ts.map +1 -0
  145. package/dist/components/ui/kbd.js +6 -0
  146. package/dist/components/ui/load-more-button.js +1 -1
  147. package/dist/components/ui/menu-dropdown.js +1 -1
  148. package/dist/components/ui/skeleton.js +1 -1
  149. package/dist/components/ui/spinner.js +1 -1
  150. package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
  151. package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
  152. package/dist/components/ui/timestamp-tooltip.js +80 -132
  153. package/dist/components/ui/tooltip.d.ts +10 -0
  154. package/dist/components/ui/tooltip.d.ts.map +1 -0
  155. package/dist/components/ui/tooltip.js +12 -0
  156. package/dist/components/workflow-trace-view.d.ts +1 -3
  157. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  158. package/dist/components/workflow-trace-view.js +11 -13
  159. package/dist/components/workflow-traces/event-colors.d.ts +1 -0
  160. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  161. package/dist/components/workflow-traces/event-colors.js +12 -1
  162. package/dist/components/workflow-traces/trace-colors.js +1 -1
  163. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  164. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  165. package/dist/components/workflow-traces/trace-span-construction.js +19 -5
  166. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  167. package/dist/hooks/use-dark-mode.js +1 -1
  168. package/dist/hooks/use-intersection-observer.d.ts +17 -0
  169. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  170. package/dist/hooks/use-intersection-observer.js +29 -0
  171. package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
  172. package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
  173. package/dist/hooks/use-load-more-on-scroll.js +28 -0
  174. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  175. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  176. package/dist/hooks/use-reduced-motion.js +29 -0
  177. package/dist/index.d.ts +3 -1
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +3 -2
  180. package/dist/lib/event-analysis.js +1 -1
  181. package/dist/lib/event-materialization.js +1 -1
  182. package/dist/lib/exact-event-search-id.d.ts +25 -0
  183. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  184. package/dist/lib/exact-event-search-id.js +39 -0
  185. package/dist/lib/hydration.d.ts +6 -0
  186. package/dist/lib/hydration.d.ts.map +1 -1
  187. package/dist/lib/hydration.js +154 -2
  188. package/dist/lib/stream-display.d.ts +18 -0
  189. package/dist/lib/stream-display.d.ts.map +1 -0
  190. package/dist/lib/stream-display.js +30 -0
  191. package/dist/lib/toast.js +1 -1
  192. package/dist/lib/trace-builder.d.ts +15 -0
  193. package/dist/lib/trace-builder.d.ts.map +1 -1
  194. package/dist/lib/trace-builder.js +29 -3
  195. package/dist/lib/utils.d.ts +20 -9
  196. package/dist/lib/utils.d.ts.map +1 -1
  197. package/dist/lib/utils.js +74 -31
  198. package/dist/lib/zstd-browser-decoder.d.ts +7 -0
  199. package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
  200. package/dist/lib/zstd-browser-decoder.js +41 -0
  201. package/dist/styles.css +484 -1
  202. package/package.json +10 -5
  203. package/src/components/event-list-view.tsx +279 -104
  204. package/src/components/index.ts +7 -0
  205. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  206. package/src/components/new-trace-viewer/components/event-list.tsx +136 -0
  207. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  208. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  209. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  210. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  211. package/src/components/new-trace-viewer/components/span-markers.tsx +173 -0
  212. package/src/components/new-trace-viewer/components/split-pane.tsx +198 -0
  213. package/src/components/new-trace-viewer/components/timeline.module.css +46 -0
  214. package/src/components/new-trace-viewer/components/timeline.tsx +744 -0
  215. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +94 -0
  216. package/src/components/new-trace-viewer/components/use-row-window.ts +140 -0
  217. package/src/components/new-trace-viewer/context.tsx +90 -0
  218. package/src/components/new-trace-viewer/icons.tsx +79 -0
  219. package/src/components/new-trace-viewer/search.ts +178 -0
  220. package/src/components/new-trace-viewer/trace-viewer.tsx +843 -0
  221. package/src/components/new-trace-viewer/types.ts +2 -0
  222. package/src/components/new-trace-viewer/utils.test.ts +182 -0
  223. package/src/components/new-trace-viewer/utils.ts +519 -0
  224. package/src/components/sidebar/attribute-panel.tsx +194 -240
  225. package/src/components/sidebar/attributes-block.tsx +173 -0
  226. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  227. package/src/components/sidebar/detail-card.tsx +119 -59
  228. package/src/components/sidebar/entity-detail-panel.tsx +55 -111
  229. package/src/components/sidebar/events-list.tsx +85 -96
  230. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  231. package/src/components/sidebar/span-detail-merge.ts +65 -0
  232. package/src/components/stream-viewer.tsx +18 -40
  233. package/src/components/trace-viewer/components/node.tsx +3 -2
  234. package/src/components/trace-viewer/trace-viewer.module.css +4 -4
  235. package/src/components/trace-viewer/util/timing.ts +7 -11
  236. package/src/components/trace-viewer-new.tsx +55 -0
  237. package/src/components/ui/button.tsx +68 -0
  238. package/src/components/ui/context-card.tsx +784 -0
  239. package/src/components/ui/data-inspector.tsx +244 -22
  240. package/src/components/ui/error-stack-block.tsx +84 -55
  241. package/src/components/ui/icon-button.tsx +53 -0
  242. package/src/components/ui/inspector-theme.ts +39 -81
  243. package/src/components/ui/kbd.tsx +21 -0
  244. package/src/components/ui/timestamp-tooltip.tsx +132 -194
  245. package/src/components/ui/tooltip.tsx +47 -0
  246. package/src/components/workflow-trace-view.tsx +19 -15
  247. package/src/components/workflow-traces/event-colors.ts +12 -0
  248. package/src/components/workflow-traces/trace-span-construction.ts +24 -5
  249. package/src/hooks/use-intersection-observer.ts +57 -0
  250. package/src/hooks/use-load-more-on-scroll.ts +49 -0
  251. package/src/hooks/use-reduced-motion.ts +34 -0
  252. package/src/index.ts +9 -0
  253. package/src/lib/exact-event-search-id.ts +67 -0
  254. package/src/lib/hydration.ts +166 -1
  255. package/src/lib/stream-display.ts +48 -0
  256. package/src/lib/trace-builder.ts +34 -2
  257. package/src/lib/utils.ts +83 -31
  258. package/src/lib/zstd-browser-decoder.ts +42 -0
  259. package/src/styles.css +484 -1
  260. package/dist/components/error-boundary.js.map +0 -1
  261. package/dist/components/event-list-view.js.map +0 -1
  262. package/dist/components/hook-actions.js.map +0 -1
  263. package/dist/components/index.js.map +0 -1
  264. package/dist/components/run-trace-view.js.map +0 -1
  265. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  266. package/dist/components/sidebar/conversation-view.js.map +0 -1
  267. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  268. package/dist/components/sidebar/detail-card.js.map +0 -1
  269. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  270. package/dist/components/sidebar/events-list.js.map +0 -1
  271. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  272. package/dist/components/stream-viewer.js.map +0 -1
  273. package/dist/components/trace-viewer/components/map.js.map +0 -1
  274. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  275. package/dist/components/trace-viewer/components/node.js.map +0 -1
  276. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  277. package/dist/components/trace-viewer/components/search.js.map +0 -1
  278. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  279. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  280. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  281. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  282. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  283. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  284. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  285. package/dist/components/trace-viewer/context.js.map +0 -1
  286. package/dist/components/trace-viewer/index.js.map +0 -1
  287. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  288. package/dist/components/trace-viewer/types.js.map +0 -1
  289. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  290. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  291. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  292. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  293. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  294. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  295. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  296. package/dist/components/trace-viewer/worker.js.map +0 -1
  297. package/dist/components/ui/alert.js.map +0 -1
  298. package/dist/components/ui/card.js.map +0 -1
  299. package/dist/components/ui/data-inspector.js.map +0 -1
  300. package/dist/components/ui/decrypt-button.js.map +0 -1
  301. package/dist/components/ui/error-card.js.map +0 -1
  302. package/dist/components/ui/error-stack-block.js.map +0 -1
  303. package/dist/components/ui/inspector-theme.js.map +0 -1
  304. package/dist/components/ui/load-more-button.js.map +0 -1
  305. package/dist/components/ui/menu-dropdown.js.map +0 -1
  306. package/dist/components/ui/skeleton.js.map +0 -1
  307. package/dist/components/ui/spinner.js.map +0 -1
  308. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  309. package/dist/components/workflow-trace-view.js.map +0 -1
  310. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  311. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  312. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  313. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  314. package/dist/hooks/use-dark-mode.js.map +0 -1
  315. package/dist/index.js.map +0 -1
  316. package/dist/lib/event-analysis.js.map +0 -1
  317. package/dist/lib/event-materialization.js.map +0 -1
  318. package/dist/lib/hydration.js.map +0 -1
  319. package/dist/lib/toast.js.map +0 -1
  320. package/dist/lib/trace-builder.js.map +0 -1
  321. package/dist/lib/utils.js.map +0 -1
@@ -3,16 +3,30 @@
3
3
  import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
4
  import type { Event, WorkflowRun } from '@workflow/world';
5
5
  import { Check, ChevronRight, Copy } from 'lucide-react';
6
- import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
6
+ import type {
7
+ KeyboardEvent as ReactKeyboardEvent,
8
+ MouseEvent as ReactMouseEvent,
9
+ ReactNode,
10
+ } from 'react';
7
11
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
12
  import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
13
+ import {
14
+ type ExactIdSearchResult,
15
+ type ExactWorkflowSearchIdKind,
16
+ looksLikeWorkflowIdSearchInput,
17
+ parseExactWorkflowSearchId,
18
+ } from '../lib/exact-event-search-id';
9
19
  import { isEncryptedMarker } from '../lib/hydration';
10
- import { DecryptButton } from './ui/decrypt-button';
20
+ import { useToast } from '../lib/toast';
11
21
  import { formatDuration } from '../lib/utils';
22
+ import { AttrSetEventBlock } from './sidebar/attributes-block';
23
+ import { ContextCardProvider } from './ui/context-card';
12
24
  import { DataInspector, DecryptClickContext } from './ui/data-inspector';
25
+ import { DecryptButton } from './ui/decrypt-button';
13
26
  import {
14
27
  ErrorStackBlock,
15
- isStructuredErrorWithStack,
28
+ isStructuredError,
29
+ type StructuredErrorRecord,
16
30
  } from './ui/error-stack-block';
17
31
  import { LoadMoreButton } from './ui/load-more-button';
18
32
  import { MenuDropdown } from './ui/menu-dropdown';
@@ -84,6 +98,10 @@ function getStatusDotColor(eventType: string): string {
84
98
  if (eventType === 'step_retrying') {
85
99
  return 'var(--ds-amber-700)';
86
100
  }
101
+ // Attribute changes → teal
102
+ if (eventType === 'attr_set') {
103
+ return 'var(--ds-teal-900)';
104
+ }
87
105
  // Completed/succeeded → green
88
106
  if (
89
107
  eventType === 'step_completed' ||
@@ -142,7 +160,7 @@ function buildNameMaps(
142
160
  return { correlationNameMap, workflowName };
143
161
  }
144
162
 
145
- interface DurationInfo {
163
+ export interface DurationInfo {
146
164
  /** Time from created → started (ms) */
147
165
  queued?: number;
148
166
  /** Time from started → completed/failed/cancelled (ms) */
@@ -154,19 +172,30 @@ interface DurationInfo {
154
172
  * created ↔ started (queued) and started ↔ completed/failed/cancelled (ran).
155
173
  * Also computes run-level durations under the key '__run__'.
156
174
  */
157
- function buildDurationMap(events: Event[]): Map<string, DurationInfo> {
175
+ export function buildDurationMap(events: Event[]): Map<string, DurationInfo> {
176
+ // Process events in chronological order so the result doesn't depend on
177
+ // the caller's sort direction. Retried steps emit multiple `step_started`
178
+ // events for the same correlationId; the queued duration must be measured
179
+ // against the first one, not the last.
180
+ const chronological = [...events].sort(
181
+ (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
182
+ );
183
+
158
184
  const createdTimes = new Map<string, number>();
185
+ const firstStartedTimes = new Map<string, number>();
159
186
  const startedTimes = new Map<string, number>();
160
187
  const durations = new Map<string, DurationInfo>();
161
188
 
162
- for (const event of events) {
189
+ for (const event of chronological) {
163
190
  const ts = new Date(event.createdAt).getTime();
164
191
  const key = event.correlationId ?? '__run__';
165
192
  const type: string = event.eventType;
166
193
 
167
194
  // Track created times (first event for each correlation)
168
195
  if (type === 'step_created' || type === 'run_created') {
169
- createdTimes.set(key, ts);
196
+ if (!createdTimes.has(key)) {
197
+ createdTimes.set(key, ts);
198
+ }
170
199
  }
171
200
 
172
201
  // Track started times & compute queued duration
@@ -176,16 +205,21 @@ function buildDurationMap(events: Event[]): Map<string, DurationInfo> {
176
205
  type === 'workflow_started'
177
206
  ) {
178
207
  startedTimes.set(key, ts);
179
- // If no explicit created event was seen, use the started time as created
180
- if (!createdTimes.has(key)) {
181
- createdTimes.set(key, ts);
182
- }
183
- const createdAt = createdTimes.get(key);
184
- const info = durations.get(key) ?? {};
185
- if (createdAt !== undefined) {
186
- info.queued = ts - createdAt;
208
+ // The queued duration is anchored on the first start event only
209
+ // subsequent step_started events come from retries.
210
+ if (!firstStartedTimes.has(key)) {
211
+ firstStartedTimes.set(key, ts);
212
+ // If no explicit created event was seen, use the started time as created
213
+ if (!createdTimes.has(key)) {
214
+ createdTimes.set(key, ts);
215
+ }
216
+ const createdAt = createdTimes.get(key);
217
+ const info = durations.get(key) ?? {};
218
+ if (createdAt !== undefined) {
219
+ info.queued = ts - createdAt;
220
+ }
221
+ durations.set(key, info);
187
222
  }
188
- durations.set(key, info);
189
223
  }
190
224
 
191
225
  // Compute ran duration on terminal events
@@ -230,7 +264,10 @@ function isRunLevel(eventType: string): boolean {
230
264
  eventType === 'run_cancelled' ||
231
265
  eventType === 'workflow_started' ||
232
266
  eventType === 'workflow_completed' ||
233
- eventType === 'workflow_failed'
267
+ eventType === 'workflow_failed' ||
268
+ // attr_set carries a dedup correlationId rather than a child entity ID,
269
+ // so it groups and labels with the run itself.
270
+ eventType === 'attr_set'
234
271
  );
235
272
  }
236
273
 
@@ -492,20 +529,20 @@ function deepParseJson(value: unknown): unknown {
492
529
  }
493
530
 
494
531
  /**
495
- * Extracts a structured error with a stack trace from event data, if present.
532
+ * Extracts a structured error from event data, if present.
496
533
  * Returns the error object to render with ErrorStackBlock, or null if not applicable.
497
534
  */
498
535
  function extractStructuredError(
499
536
  data: unknown,
500
537
  eventType?: string
501
- ): (Record<string, unknown> & { stack: string }) | null {
538
+ ): StructuredErrorRecord | null {
502
539
  if (!eventType || !ERROR_EVENT_TYPES.has(eventType)) return null;
503
540
  if (data == null || typeof data !== 'object') return null;
504
541
  const record = data as Record<string, unknown>;
505
542
  // Check the nested `error` field first (the StructuredError)
506
- if (isStructuredErrorWithStack(record.error)) return record.error;
507
- // Some error formats put the stack at the top level of eventData
508
- if (isStructuredErrorWithStack(record)) return record;
543
+ if (isStructuredError(record.error)) return record.error;
544
+ // Some error formats put the message/stack at the top level of eventData.
545
+ if (isStructuredError(record)) return record;
509
546
  return null;
510
547
  }
511
548
 
@@ -566,6 +603,12 @@ function PayloadBlock({
566
603
  );
567
604
  }
568
605
 
606
+ // Attribute changes — render the changed keys and the writer instead of
607
+ // the raw JSON payload.
608
+ if (eventType === 'attr_set') {
609
+ return <AttrSetEventBlock data={cleaned} />;
610
+ }
611
+
569
612
  return (
570
613
  <div className="relative group/payload">
571
614
  <div
@@ -705,6 +748,12 @@ interface EventsListProps {
705
748
  isDecrypting?: boolean;
706
749
  /** Run-level hint: the run contains encrypted data (from probe). */
707
750
  hasEncryptedData?: boolean;
751
+ /** Fetch events for an exact correlation or event ID. */
752
+ onExactIdSearch?: (
753
+ id: string,
754
+ kind: ExactWorkflowSearchIdKind,
755
+ signal?: AbortSignal
756
+ ) => Promise<ExactIdSearchResult>;
708
757
  }
709
758
 
710
759
  function EventRow({
@@ -727,6 +776,7 @@ function EventRow({
727
776
  onCacheEventData,
728
777
  encryptionKey,
729
778
  onEncryptedDataDetected,
779
+ suppressGroupDimming = false,
730
780
  }: {
731
781
  event: Event;
732
782
  index: number;
@@ -747,6 +797,8 @@ function EventRow({
747
797
  onCacheEventData: (eventId: string, data: unknown) => void;
748
798
  encryptionKey?: Uint8Array;
749
799
  onEncryptedDataDetected?: () => void;
800
+ /** Exact-ID search results should not dim unrelated rows. */
801
+ suppressGroupDimming?: boolean;
750
802
  }) {
751
803
  const [isLoading, setIsLoading] = useState(false);
752
804
  const [loadedEventData, setLoadedEventData] = useState<unknown | null>(
@@ -788,7 +840,7 @@ function EventRow({
788
840
 
789
841
  const hasActive = activeGroupKey !== undefined;
790
842
  const isRelated = rowGroupKey !== undefined && rowGroupKey === activeGroupKey;
791
- const isDimmed = hasActive && !isRelated;
843
+ const isDimmed = hasActive && !isRelated && !suppressGroupDimming;
792
844
  const isPulsing = hasActive && isRelated;
793
845
 
794
846
  // Gutter state derived from selectedGroupRange
@@ -1125,7 +1177,7 @@ function EventRow({
1125
1177
  // Main component
1126
1178
  // ──────────────────────────────────────────────────────────────────────────
1127
1179
 
1128
- export function EventListView({
1180
+ function EventListViewInner({
1129
1181
  events,
1130
1182
  run,
1131
1183
  onLoadEventData,
@@ -1139,7 +1191,9 @@ export function EventListView({
1139
1191
  onDecrypt,
1140
1192
  isDecrypting = false,
1141
1193
  hasEncryptedData: hasEncryptedDataProp = false,
1194
+ onExactIdSearch,
1142
1195
  }: EventsListProps) {
1196
+ const toast = useToast();
1143
1197
  const [internalSortOrder, setInternalSortOrder] = useState<'asc' | 'desc'>(
1144
1198
  'asc'
1145
1199
  );
@@ -1155,25 +1209,42 @@ export function EventListView({
1155
1209
  [onSortOrderChange]
1156
1210
  );
1157
1211
 
1212
+ const [searchQuery, setSearchQuery] = useState('');
1213
+ const [searchResults, setSearchResults] = useState<Event[] | null>(null);
1214
+ const [searchResultsTruncated, setSearchResultsTruncated] = useState(false);
1215
+ const [searchError, setSearchError] = useState<string | null>(null);
1216
+ const [searchLoading, setSearchLoading] = useState(false);
1217
+ const [searchNotFound, setSearchNotFound] = useState(false);
1218
+ const searchRequestRef = useRef(0);
1219
+ const virtuosoRef = useRef<VirtuosoHandle>(null);
1220
+
1221
+ const parsedSearchId = useMemo(
1222
+ () => parseExactWorkflowSearchId(searchQuery),
1223
+ [searchQuery]
1224
+ );
1225
+ const isExactSearchActive = searchResults !== null;
1226
+
1158
1227
  const sortedEvents = useMemo(() => {
1159
- if (!events || events.length === 0) return [];
1228
+ const sourceEvents = isExactSearchActive ? searchResults : (events ?? []);
1229
+ if (sourceEvents.length === 0) return [];
1160
1230
  const dir = effectiveSortOrder === 'desc' ? -1 : 1;
1161
- return [...events].sort(
1231
+ return [...sourceEvents].sort(
1162
1232
  (a, b) =>
1163
1233
  dir *
1164
1234
  (new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime())
1165
1235
  );
1166
- }, [events, effectiveSortOrder]);
1236
+ }, [events, effectiveSortOrder, isExactSearchActive, searchResults]);
1167
1237
 
1168
1238
  // Detect encrypted fields across all loaded events (inline eventData).
1169
1239
  const hasEncryptedInlineData = useMemo(() => {
1170
- if (!events) return false;
1171
- for (const event of events) {
1240
+ const sourceEvents = isExactSearchActive ? searchResults : events;
1241
+ if (!sourceEvents) return false;
1242
+ for (const event of sourceEvents) {
1172
1243
  const ed = (event as Record<string, unknown>).eventData;
1173
1244
  if (hasEncryptedValues(ed)) return true;
1174
1245
  }
1175
1246
  return false;
1176
- }, [events]);
1247
+ }, [events, isExactSearchActive, searchResults]);
1177
1248
 
1178
1249
  // Tracks whether any expanded row's lazy-loaded data contained encrypted markers.
1179
1250
  // Set to true by EventRow via onEncryptedDataDetected; never reset (sticky).
@@ -1187,8 +1258,12 @@ export function EventListView({
1187
1258
  hasEncryptedDataProp || hasEncryptedInlineData || foundEncryptedInLazyData;
1188
1259
 
1189
1260
  const { correlationNameMap, workflowName } = useMemo(
1190
- () => buildNameMaps(events ?? null, run ?? null),
1191
- [events, run]
1261
+ () =>
1262
+ buildNameMaps(
1263
+ isExactSearchActive ? searchResults : (events ?? null),
1264
+ run ?? null
1265
+ ),
1266
+ [events, isExactSearchActive, run, searchResults]
1192
1267
  );
1193
1268
 
1194
1269
  const durationMap = useMemo(
@@ -1278,68 +1353,139 @@ export function EventListView({
1278
1353
  return first >= 0 ? { first, last } : null;
1279
1354
  }, [activeGroupKey, sortedEvents]);
1280
1355
 
1281
- const [searchQuery, setSearchQuery] = useState('');
1282
- const virtuosoRef = useRef<VirtuosoHandle>(null);
1283
-
1284
- const searchIndex = useMemo(() => {
1285
- const entries: {
1286
- fields: string[];
1287
- groupKey?: string;
1288
- eventId: string;
1289
- index: number;
1290
- }[] = [];
1291
- for (let i = 0; i < sortedEvents.length; i++) {
1292
- const ev = sortedEvents[i];
1293
- const isRun = isRunLevel(ev.eventType);
1294
- const name = isRun
1295
- ? (workflowName ?? '')
1296
- : ev.correlationId
1297
- ? (correlationNameMap.get(ev.correlationId) ?? '')
1298
- : '';
1299
- entries.push({
1300
- fields: [
1301
- ev.eventId,
1302
- ev.correlationId ?? '',
1303
- ev.eventType,
1304
- formatEventType(ev.eventType),
1305
- name,
1306
- ].map((f) => f.toLowerCase()),
1307
- groupKey: ev.correlationId ?? (isRun ? '__run__' : undefined),
1308
- eventId: ev.eventId,
1309
- index: i,
1310
- });
1311
- }
1312
- return entries;
1313
- }, [sortedEvents, correlationNameMap, workflowName]);
1314
-
1315
1356
  useEffect(() => {
1316
- const q = searchQuery.trim().toLowerCase();
1317
- if (!q) {
1357
+ const trimmed = searchQuery.trim();
1358
+ if (!trimmed) {
1359
+ searchRequestRef.current += 1;
1360
+ setSearchResults(null);
1361
+ setSearchResultsTruncated(false);
1362
+ setSearchError(null);
1363
+ setSearchLoading(false);
1364
+ setSearchNotFound(false);
1318
1365
  setSelectedGroupKey(undefined);
1319
1366
  return;
1320
1367
  }
1321
- let bestMatch: (typeof searchIndex)[number] | null = null;
1322
- let bestScore = 0;
1323
- for (const entry of searchIndex) {
1324
- for (const field of entry.fields) {
1325
- if (field && field.includes(q)) {
1326
- const score = q.length / field.length;
1327
- if (score > bestScore) {
1328
- bestScore = score;
1329
- bestMatch = entry;
1368
+
1369
+ const parsed = parseExactWorkflowSearchId(trimmed);
1370
+ if (!parsed || !onExactIdSearch) {
1371
+ setSearchResults(null);
1372
+ setSearchLoading(false);
1373
+ setSearchNotFound(false);
1374
+ return;
1375
+ }
1376
+
1377
+ const requestId = ++searchRequestRef.current;
1378
+ setSearchLoading(true);
1379
+ setSearchNotFound(false);
1380
+ setSearchError(null);
1381
+
1382
+ const abortController = new AbortController();
1383
+
1384
+ const timer = setTimeout(() => {
1385
+ void (async () => {
1386
+ try {
1387
+ const results = await onExactIdSearch(
1388
+ parsed.id,
1389
+ parsed.kind,
1390
+ abortController.signal
1391
+ );
1392
+ if (
1393
+ abortController.signal.aborted ||
1394
+ searchRequestRef.current !== requestId
1395
+ ) {
1396
+ return;
1397
+ }
1398
+
1399
+ if (results.status === 'error') {
1400
+ setSearchResults([]);
1401
+ setSearchResultsTruncated(false);
1402
+ setSearchNotFound(false);
1403
+ setSearchError(results.message);
1404
+ setSelectedGroupKey(undefined);
1405
+ return;
1406
+ }
1407
+
1408
+ if (
1409
+ results.status === 'not_found' ||
1410
+ (results.status === 'ok' && results.events.length === 0)
1411
+ ) {
1412
+ setSearchResults([]);
1413
+ setSearchResultsTruncated(false);
1414
+ setSearchNotFound(true);
1415
+ setSearchError(null);
1416
+ setSelectedGroupKey(undefined);
1417
+ return;
1418
+ }
1419
+
1420
+ setSearchResults(results.events);
1421
+ setSearchResultsTruncated(Boolean(results.truncated));
1422
+ setSearchNotFound(false);
1423
+ setSearchError(null);
1424
+ setSelectedGroupKey(
1425
+ parsed.kind === 'event'
1426
+ ? (() => {
1427
+ const first = results.events[0];
1428
+ if (!first) return undefined;
1429
+ return isRunLevel(first.eventType)
1430
+ ? '__run__'
1431
+ : (first.correlationId ?? undefined);
1432
+ })()
1433
+ : parsed.id
1434
+ );
1435
+ virtuosoRef.current?.scrollToIndex({
1436
+ index: 0,
1437
+ align: 'start',
1438
+ behavior: 'smooth',
1439
+ });
1440
+ } catch {
1441
+ if (
1442
+ abortController.signal.aborted ||
1443
+ searchRequestRef.current !== requestId
1444
+ ) {
1445
+ return;
1446
+ }
1447
+ setSearchResults([]);
1448
+ setSearchResultsTruncated(false);
1449
+ setSearchNotFound(false);
1450
+ setSearchError('Failed to search events. Try again.');
1451
+ setSelectedGroupKey(undefined);
1452
+ } finally {
1453
+ if (
1454
+ searchRequestRef.current === requestId &&
1455
+ !abortController.signal.aborted
1456
+ ) {
1457
+ setSearchLoading(false);
1330
1458
  }
1331
1459
  }
1460
+ })();
1461
+ }, 300);
1462
+
1463
+ return () => {
1464
+ clearTimeout(timer);
1465
+ abortController.abort();
1466
+ };
1467
+ }, [searchQuery, onExactIdSearch]);
1468
+
1469
+ const handleSearchKeyDown = useCallback(
1470
+ (event: ReactKeyboardEvent<HTMLInputElement>) => {
1471
+ if (event.key !== 'Enter') {
1472
+ return;
1473
+ }
1474
+
1475
+ const trimmed = searchQuery.trim();
1476
+ if (
1477
+ !trimmed ||
1478
+ parseExactWorkflowSearchId(trimmed) ||
1479
+ !onExactIdSearch ||
1480
+ !looksLikeWorkflowIdSearchInput(trimmed)
1481
+ ) {
1482
+ return;
1332
1483
  }
1333
- }
1334
- if (bestMatch) {
1335
- setSelectedGroupKey(bestMatch.groupKey);
1336
- virtuosoRef.current?.scrollToIndex({
1337
- index: bestMatch.index,
1338
- align: 'center',
1339
- behavior: 'smooth',
1340
- });
1341
- }
1342
- }, [searchQuery, searchIndex]);
1484
+
1485
+ toast.info('Enter a full step ID, wait ID, hook ID, or event ID');
1486
+ },
1487
+ [searchQuery, onExactIdSearch, toast]
1488
+ );
1343
1489
 
1344
1490
  // Track whether we've ever had events to distinguish initial load from refetch
1345
1491
  const hasHadEventsRef = useRef(false);
@@ -1385,17 +1531,6 @@ export function EventListView({
1385
1531
  );
1386
1532
  }
1387
1533
 
1388
- if (!isLoading && (!events || events.length === 0)) {
1389
- return (
1390
- <div
1391
- className="flex items-center justify-center h-full text-sm"
1392
- style={{ color: 'var(--ds-gray-700)' }}
1393
- >
1394
- No events found
1395
- </div>
1396
- );
1397
- }
1398
-
1399
1534
  return (
1400
1535
  <DecryptClickContext.Provider
1401
1536
  value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
@@ -1460,9 +1595,16 @@ export function EventListView({
1460
1595
  </div>
1461
1596
  <input
1462
1597
  type="search"
1463
- placeholder="Search by name, event type, or ID…"
1598
+ placeholder="Search by step ID, wait ID, hook ID, or event ID…"
1464
1599
  value={searchQuery}
1465
1600
  onChange={(e) => setSearchQuery(e.target.value)}
1601
+ onKeyDown={handleSearchKeyDown}
1602
+ disabled={!onExactIdSearch}
1603
+ title={
1604
+ onExactIdSearch
1605
+ ? undefined
1606
+ : 'Exact ID search is unavailable in this view.'
1607
+ }
1466
1608
  style={{
1467
1609
  marginLeft: -16,
1468
1610
  paddingInline: 12,
@@ -1473,6 +1615,8 @@ export function EventListView({
1473
1615
  outline: 'none',
1474
1616
  height: 40,
1475
1617
  width: '100%',
1618
+ opacity: onExactIdSearch ? 1 : 0.5,
1619
+ cursor: onExactIdSearch ? 'text' : 'not-allowed',
1476
1620
  }}
1477
1621
  />
1478
1622
  </label>
@@ -1519,8 +1663,21 @@ export function EventListView({
1519
1663
  </div>
1520
1664
 
1521
1665
  {/* Virtualized event rows or refetching skeleton */}
1522
- {isRefetching ? (
1666
+ {isRefetching || searchLoading ? (
1523
1667
  <RowsSkeleton />
1668
+ ) : sortedEvents.length === 0 ? (
1669
+ <div
1670
+ className="flex flex-1 items-center justify-center px-6 text-center text-sm"
1671
+ style={{ color: 'var(--ds-gray-700)' }}
1672
+ >
1673
+ {searchNotFound && searchQuery.trim()
1674
+ ? `No events found for ${searchQuery.trim()}`
1675
+ : searchError
1676
+ ? searchError
1677
+ : parsedSearchId && searchQuery.trim() && !onExactIdSearch
1678
+ ? 'Exact ID search is unavailable in this view.'
1679
+ : 'No events found'}
1680
+ </div>
1524
1681
  ) : (
1525
1682
  <Virtuoso
1526
1683
  ref={virtuosoRef}
@@ -1528,7 +1685,11 @@ export function EventListView({
1528
1685
  overscan={20}
1529
1686
  defaultItemHeight={40}
1530
1687
  endReached={() => {
1531
- if (!hasMoreEvents || isLoadingMoreEvents) {
1688
+ if (
1689
+ isExactSearchActive ||
1690
+ !hasMoreEvents ||
1691
+ isLoadingMoreEvents
1692
+ ) {
1532
1693
  return;
1533
1694
  }
1534
1695
  void onLoadMoreEvents?.();
@@ -1558,6 +1719,7 @@ export function EventListView({
1558
1719
  onCacheEventData={cacheEventData}
1559
1720
  encryptionKey={encryptionKey}
1560
1721
  onEncryptedDataDetected={handleEncryptedDataDetected}
1722
+ suppressGroupDimming={isExactSearchActive}
1561
1723
  />
1562
1724
  );
1563
1725
  }}
@@ -1575,10 +1737,15 @@ export function EventListView({
1575
1737
  }}
1576
1738
  >
1577
1739
  <span>
1578
- {sortedEvents.length} event
1579
- {sortedEvents.length !== 1 ? 's' : ''} loaded
1740
+ {isExactSearchActive
1741
+ ? searchError
1742
+ ? searchError
1743
+ : searchNotFound
1744
+ ? `No events found for ${searchQuery.trim()}`
1745
+ : `${sortedEvents.length} event${sortedEvents.length !== 1 ? 's' : ''} for ${searchQuery.trim()}${searchResultsTruncated ? ' (results may be truncated)' : ''}`
1746
+ : `${sortedEvents.length} event${sortedEvents.length !== 1 ? 's' : ''} loaded`}
1580
1747
  </span>
1581
- {hasMoreEvents && (
1748
+ {!isExactSearchActive && hasMoreEvents && (
1582
1749
  <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
1583
1750
  <div className="pointer-events-auto">
1584
1751
  <LoadMoreButton
@@ -1593,3 +1760,11 @@ export function EventListView({
1593
1760
  </DecryptClickContext.Provider>
1594
1761
  );
1595
1762
  }
1763
+
1764
+ export function EventListView(props: EventsListProps) {
1765
+ return (
1766
+ <ContextCardProvider>
1767
+ <EventListViewInner {...props} />
1768
+ </ContextCardProvider>
1769
+ );
1770
+ }
@@ -15,6 +15,10 @@ export {
15
15
  } from './hook-actions';
16
16
  export { RunTraceView } from './run-trace-view';
17
17
  export { ConversationView } from './sidebar/conversation-view';
18
+ export {
19
+ SidebarDataProvider,
20
+ type SidebarDataContextValue,
21
+ } from './sidebar/sidebar-data-context';
18
22
  export type {
19
23
  SelectedSpanInfo,
20
24
  SpanSelectionInfo,
@@ -28,7 +32,10 @@ export {
28
32
  type DecryptClickContextValue,
29
33
  } from './ui/data-inspector';
30
34
  export { DecryptButton } from './ui/decrypt-button';
35
+ export { IconButton } from './ui/icon-button';
31
36
  export { LoadMoreButton } from './ui/load-more-button';
32
37
  export { MenuDropdown, type MenuDropdownOption } from './ui/menu-dropdown';
33
38
  export { Spinner } from './ui/spinner';
34
39
  export { WorkflowTraceViewer } from './workflow-trace-view';
40
+ export { NewTraceViewer } from './trace-viewer-new';
41
+ export { TraceViewerSkeleton } from './new-trace-viewer/components/trace-viewer-skeleton';
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import { Check, Copy } from 'lucide-react';
4
+ import { type JSX, useEffect, useRef, useState } from 'react';
5
+ import { cn } from '../../../lib/utils';
6
+
7
+ interface CopyButtonProps {
8
+ copyText: string;
9
+ ariaLabel: string;
10
+ className?: string;
11
+ }
12
+
13
+ export function CopyButton({
14
+ copyText,
15
+ ariaLabel,
16
+ className,
17
+ }: CopyButtonProps): JSX.Element {
18
+ const [copied, setCopied] = useState(false);
19
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
20
+
21
+ useEffect(() => {
22
+ return () => {
23
+ if (timeoutRef.current) {
24
+ clearTimeout(timeoutRef.current);
25
+ }
26
+ };
27
+ }, []);
28
+
29
+ return (
30
+ <button
31
+ type="button"
32
+ aria-label={ariaLabel}
33
+ className={cn(
34
+ 'cursor-pointer text-gray-800 hover:text-gray-1000 bg-transparent border-0 p-1 m-0',
35
+ className
36
+ )}
37
+ onClick={(e) => {
38
+ e.stopPropagation();
39
+ if (timeoutRef.current) {
40
+ clearTimeout(timeoutRef.current);
41
+ }
42
+ void navigator.clipboard.writeText(copyText).then(() => {
43
+ setCopied(true);
44
+ timeoutRef.current = setTimeout(() => setCopied(false), 1000);
45
+ });
46
+ }}
47
+ >
48
+ <div className="relative w-3 h-3">
49
+ <div
50
+ className={cn(
51
+ 'absolute inset-0 flex items-center justify-center transition-all duration-150 ease-out',
52
+ copied ? 'scale-100 opacity-100' : 'scale-0 opacity-0'
53
+ )}
54
+ >
55
+ <Check className="w-3 h-3" />
56
+ </div>
57
+ <div
58
+ className={cn(
59
+ 'absolute inset-0 flex items-center justify-center transition-all duration-150 ease-out',
60
+ copied ? 'scale-0 opacity-0' : 'scale-100 opacity-100'
61
+ )}
62
+ >
63
+ <Copy className="w-3 h-3" />
64
+ </div>
65
+ </div>
66
+ </button>
67
+ );
68
+ }