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

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
@@ -0,0 +1,2 @@
1
+ export type { Span, SpanEvent, Trace } from '../trace-viewer/types';
2
+ export type { TraceWithMeta } from '../../lib/trace-builder';
@@ -0,0 +1,182 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { Span, SpanEvent } from './types';
3
+ import {
4
+ computeOffscreenMarkers,
5
+ computeSpanMarkers,
6
+ computeSpanSegments,
7
+ } from './utils';
8
+
9
+ /** Build a high-res timestamp tuple ([seconds, nanoseconds]) for a given ms. */
10
+ function ts(ms: number): [number, number] {
11
+ return [Math.floor(ms / 1000), (ms % 1000) * 1e6];
12
+ }
13
+
14
+ function hookSpan(opts: {
15
+ startMs: number;
16
+ endMs: number;
17
+ receivesMs: number[];
18
+ disposedMs?: number;
19
+ attrSetMs?: number[];
20
+ }): Span {
21
+ const events: SpanEvent[] = [
22
+ { name: 'hook_created', timestamp: ts(opts.startMs), attributes: {} },
23
+ ...opts.receivesMs.map((m) => ({
24
+ name: 'hook_received',
25
+ timestamp: ts(m),
26
+ attributes: {},
27
+ })),
28
+ ...(opts.attrSetMs ?? []).map((m) => ({
29
+ name: 'attr_set',
30
+ timestamp: ts(m),
31
+ attributes: {},
32
+ })),
33
+ ...(opts.disposedMs !== undefined
34
+ ? [
35
+ {
36
+ name: 'hook_disposed',
37
+ timestamp: ts(opts.disposedMs),
38
+ attributes: {},
39
+ } satisfies SpanEvent,
40
+ ]
41
+ : []),
42
+ ];
43
+
44
+ return {
45
+ name: 'hook',
46
+ kind: 0,
47
+ resource: 'hook',
48
+ library: { name: 'workflow' },
49
+ spanId: 'hook-1',
50
+ status: { code: 1 },
51
+ traceFlags: 0,
52
+ attributes: {},
53
+ links: [],
54
+ events,
55
+ startTime: ts(opts.startMs),
56
+ endTime: ts(opts.endMs),
57
+ duration: ts(opts.endMs - opts.startMs),
58
+ };
59
+ }
60
+
61
+ describe('computeSpanSegments (run)', () => {
62
+ function runSpan(status: string): Span {
63
+ return {
64
+ name: 'run',
65
+ kind: 0,
66
+ resource: 'run',
67
+ library: { name: 'workflow' },
68
+ spanId: 'run-1',
69
+ status: { code: 1 },
70
+ traceFlags: 0,
71
+ attributes: { data: { status } },
72
+ links: [],
73
+ events: [{ name: 'run_created', timestamp: ts(0), attributes: {} }],
74
+ startTime: ts(0),
75
+ endTime: ts(100_000),
76
+ duration: ts(100_000),
77
+ };
78
+ }
79
+
80
+ it('maps pending runs to a pending segment (not running)', () => {
81
+ expect(computeSpanSegments(runSpan('pending'))).toEqual([
82
+ { startFraction: 0, endFraction: 1, status: 'pending' },
83
+ ]);
84
+ });
85
+
86
+ it('maps running runs to a running segment', () => {
87
+ expect(computeSpanSegments(runSpan('running'))).toEqual([
88
+ { startFraction: 0, endFraction: 1, status: 'running' },
89
+ ]);
90
+ });
91
+ });
92
+
93
+ describe('computeSpanSegments (hook)', () => {
94
+ it('renders a single waiting segment for a hook resumed many times but not disposed', () => {
95
+ const span = hookSpan({
96
+ startMs: 0,
97
+ endMs: 100_000,
98
+ receivesMs: [1_000, 50_000, 99_000],
99
+ });
100
+
101
+ // A hook resumed N times still re-suspends after every resumption, so the
102
+ // bar must stay "waiting" for its whole life — not flip to a filled
103
+ // "received" segment after the first receive (which hid resumptions 2..N).
104
+ expect(computeSpanSegments(span)).toEqual([
105
+ { startFraction: 0, endFraction: 1, status: 'waiting' },
106
+ ]);
107
+ });
108
+
109
+ it('ends the waiting segment at disposal and appends a succeeded tail', () => {
110
+ const span = hookSpan({
111
+ startMs: 0,
112
+ endMs: 100_000,
113
+ receivesMs: [1_000, 50_000],
114
+ disposedMs: 80_000,
115
+ });
116
+
117
+ expect(computeSpanSegments(span)).toEqual([
118
+ { startFraction: 0, endFraction: 0.8, status: 'waiting' },
119
+ { startFraction: 0.8, endFraction: 1, status: 'succeeded' },
120
+ ]);
121
+ });
122
+
123
+ it('treats a never-resolved hook as fully waiting', () => {
124
+ const span = hookSpan({ startMs: 0, endMs: 100_000, receivesMs: [] });
125
+
126
+ expect(computeSpanSegments(span)).toEqual([
127
+ { startFraction: 0, endFraction: 1, status: 'waiting' },
128
+ ]);
129
+ });
130
+ });
131
+
132
+ describe('computeSpanMarkers', () => {
133
+ it('emits one marker per resumption, including those at the temporal edges', () => {
134
+ const span = hookSpan({
135
+ startMs: 0,
136
+ endMs: 100_000,
137
+ receivesMs: [1_000, 50_000, 99_000],
138
+ });
139
+
140
+ const markers = computeSpanMarkers(span);
141
+ expect(markers.map((m) => m.timeMs)).toEqual([1_000, 50_000, 99_000]);
142
+ });
143
+
144
+ it('merges hook_received and attr_set events, sorted by time', () => {
145
+ const span = hookSpan({
146
+ startMs: 0,
147
+ endMs: 100_000,
148
+ receivesMs: [50_000],
149
+ attrSetMs: [10_000, 70_000],
150
+ });
151
+
152
+ expect(computeSpanMarkers(span).map((m) => m.timeMs)).toEqual([
153
+ 10_000, 50_000, 70_000,
154
+ ]);
155
+ });
156
+
157
+ it('returns no markers when the span has no marker events', () => {
158
+ const span = hookSpan({ startMs: 0, endMs: 100_000, receivesMs: [] });
159
+ expect(computeSpanMarkers(span)).toEqual([]);
160
+ });
161
+ });
162
+
163
+ describe('computeOffscreenMarkers', () => {
164
+ const mk = (timeMs: number) => ({ timeMs });
165
+
166
+ it('partitions markers by side with the nearest one per side', () => {
167
+ const markers = [mk(5), mk(8), mk(50), mk(92), mk(99)];
168
+ // Visible window [10, 90]: 5 & 8 off left (nearest 8), 92 & 99 off right
169
+ // (nearest 92), 50 in view.
170
+ expect(computeOffscreenMarkers(markers, 10, 90)).toEqual({
171
+ left: { count: 2, nearestMs: 8 },
172
+ right: { count: 2, nearestMs: 92 },
173
+ });
174
+ });
175
+
176
+ it('returns null for a side with nothing off-screen', () => {
177
+ expect(computeOffscreenMarkers([mk(20), mk(50)], 10, 90)).toEqual({
178
+ left: null,
179
+ right: null,
180
+ });
181
+ });
182
+ });
@@ -0,0 +1,519 @@
1
+ import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
2
+ import type { Span, SpanEvent } from './types';
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Root bounds
6
+ // ---------------------------------------------------------------------------
7
+
8
+ export interface RootBounds {
9
+ startTime: number;
10
+ endTime: number;
11
+ duration: number;
12
+ }
13
+
14
+ export function computeRootBounds(spans: Span[]): RootBounds {
15
+ let minStart = Number.POSITIVE_INFINITY;
16
+ let maxEnd = Number.NEGATIVE_INFINITY;
17
+
18
+ for (const span of spans) {
19
+ const s = getHighResInMs(span.startTime);
20
+ const e = getHighResInMs(span.endTime);
21
+ if (s < minStart) minStart = s;
22
+ if (e > maxEnd) maxEnd = e;
23
+ }
24
+
25
+ if (!Number.isFinite(minStart) || !Number.isFinite(maxEnd)) {
26
+ return { startTime: 0, endTime: 1, duration: 1 };
27
+ }
28
+
29
+ const duration = Math.max(maxEnd - minStart, 1);
30
+ return { startTime: minStart, endTime: maxEnd, duration };
31
+ }
32
+
33
+ export function getSpanDurationMs(span: Span): number {
34
+ return Math.max(
35
+ 0,
36
+ getHighResInMs(span.endTime) - getHighResInMs(span.startTime)
37
+ );
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Time markers
42
+ // ---------------------------------------------------------------------------
43
+
44
+ export interface TimeMarker {
45
+ position: number;
46
+ label: string;
47
+ value: number;
48
+ }
49
+
50
+ const NICE_INTERVALS = [
51
+ 1, 2, 5, 10, 20, 50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000,
52
+ 50_000, 100_000, 200_000, 500_000, 1_000_000, 2_000_000, 5_000_000,
53
+ 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000,
54
+ 1_000_000_000, 2_000_000_000, 5_000_000_000,
55
+ ];
56
+
57
+ const MAX_MARKERS = 8;
58
+
59
+ function pickInterval(viewDuration: number, maxTicks: number): number {
60
+ for (const interval of NICE_INTERVALS) {
61
+ if (viewDuration / interval <= maxTicks) return interval;
62
+ }
63
+ return NICE_INTERVALS[NICE_INTERVALS.length - 1];
64
+ }
65
+
66
+ export function computeTimeMarkers(
67
+ viewDuration: number,
68
+ offset: number
69
+ ): TimeMarker[] {
70
+ if (viewDuration <= 0) return [];
71
+
72
+ const maxTicks = 6;
73
+ const interval = pickInterval(viewDuration, maxTicks);
74
+
75
+ const firstTick = Math.ceil(offset / interval) * interval;
76
+ const markers: TimeMarker[] = [];
77
+
78
+ for (let t = firstTick; t <= offset + viewDuration; t += interval) {
79
+ const position = (t - offset) / viewDuration;
80
+ if (position < -0.01 || position > 1.01) continue;
81
+ markers.push({
82
+ position: Math.min(Math.max(position, 0), 1),
83
+ label: formatDuration(Math.abs(t), true),
84
+ value: t,
85
+ });
86
+ if (markers.length >= MAX_MARKERS) break;
87
+ }
88
+
89
+ return markers;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Span gaps — time deltas between consecutive spans (Alt-key overlay)
94
+ // ---------------------------------------------------------------------------
95
+
96
+ export interface SpanGap {
97
+ gapMs: number;
98
+ leftFrac: number;
99
+ rightFrac: number;
100
+ rowIndex: number;
101
+ }
102
+
103
+ export function computeSpanGaps(
104
+ spans: Span[],
105
+ viewStart: number,
106
+ viewEnd: number
107
+ ): SpanGap[] {
108
+ const range = viewEnd - viewStart;
109
+ if (range <= 0) return [];
110
+
111
+ const gaps: SpanGap[] = [];
112
+ for (let i = 0; i < spans.length - 1; i++) {
113
+ const endTime = getHighResInMs(spans[i].endTime);
114
+ const startTime = getHighResInMs(spans[i + 1].startTime);
115
+ const gapMs = startTime - endTime;
116
+ if (gapMs <= 0) continue;
117
+
118
+ const leftFrac = Math.min(Math.max((endTime - viewStart) / range, 0), 1);
119
+ const rightFrac = Math.min(Math.max((startTime - viewStart) / range, 0), 1);
120
+ if (rightFrac - leftFrac < 0.001) continue;
121
+
122
+ gaps.push({ gapMs, leftFrac, rightFrac, rowIndex: i });
123
+ }
124
+ return gaps;
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Resource colors
129
+ // ---------------------------------------------------------------------------
130
+
131
+ export const RESOURCE_COLORS: Record<
132
+ string,
133
+ {
134
+ bg: string;
135
+ border: string;
136
+ errorBg?: string;
137
+ errorBorder?: string;
138
+ }
139
+ > = {
140
+ run: {
141
+ bg: 'var(--ds-blue-200)',
142
+ border: 'var(--ds-blue-500)',
143
+ errorBg: 'var(--ds-red-200)',
144
+ errorBorder: 'var(--ds-red-500)',
145
+ },
146
+ step: {
147
+ bg: 'var(--ds-green-200)',
148
+ border: 'var(--ds-green-500)',
149
+ errorBg: 'var(--ds-red-200)',
150
+ errorBorder: 'var(--ds-red-500)',
151
+ },
152
+ hook: {
153
+ bg: 'var(--ds-amber-200)',
154
+ border: 'var(--ds-amber-500)',
155
+ errorBg: 'var(--ds-red-200)',
156
+ errorBorder: 'var(--ds-red-500)',
157
+ },
158
+ sleep: {
159
+ bg: 'var(--ds-purple-200)',
160
+ border: 'var(--ds-purple-500)',
161
+ errorBg: 'var(--ds-red-200)',
162
+ errorBorder: 'var(--ds-red-500)',
163
+ },
164
+ default: {
165
+ bg: 'var(--ds-gray-200)',
166
+ border: 'var(--ds-gray-500)',
167
+ errorBg: 'var(--ds-red-200)',
168
+ errorBorder: 'var(--ds-red-500)',
169
+ },
170
+ };
171
+
172
+ export function getResourceColor(resource: string): {
173
+ bg: string;
174
+ border: string;
175
+ errorBg?: string;
176
+ errorBorder?: string;
177
+ } {
178
+ return RESOURCE_COLORS[resource] ?? RESOURCE_COLORS.default;
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Span segments — split a timeline bar into colored sections by event state
183
+ // ---------------------------------------------------------------------------
184
+
185
+ export type SegmentStatus =
186
+ | 'queued'
187
+ | 'pending'
188
+ | 'running'
189
+ | 'completed'
190
+ | 'failed'
191
+ | 'retrying'
192
+ | 'succeeded'
193
+ | 'waiting'
194
+ | 'sleeping'
195
+ | 'received';
196
+
197
+ export interface Segment {
198
+ startFraction: number;
199
+ endFraction: number;
200
+ status: SegmentStatus;
201
+ }
202
+
203
+ function clampFraction(value: number): number {
204
+ return Math.max(0, Math.min(1, value));
205
+ }
206
+
207
+ function timeToFraction(
208
+ time: number,
209
+ spanStart: number,
210
+ spanDuration: number
211
+ ): number {
212
+ if (spanDuration <= 0) return 0;
213
+ return clampFraction((time - spanStart) / spanDuration);
214
+ }
215
+
216
+ interface EventMark {
217
+ time: number;
218
+ type: string;
219
+ }
220
+
221
+ function sortedEventMarks(
222
+ events: SpanEvent[],
223
+ relevantNames: string[]
224
+ ): EventMark[] {
225
+ return events
226
+ .filter((e) => relevantNames.includes(e.name))
227
+ .map((e) => ({ time: getHighResInMs(e.timestamp), type: e.name }))
228
+ .sort((a, b) => a.time - b.time);
229
+ }
230
+
231
+ function computeStepSegmentsFromSpan(
232
+ startMs: number,
233
+ duration: number,
234
+ events: SpanEvent[]
235
+ ): Segment[] {
236
+ const segments: Segment[] = [];
237
+ if (duration <= 0) return segments;
238
+
239
+ const marks = sortedEventMarks(events, [
240
+ 'step_started',
241
+ 'step_retrying',
242
+ 'step_failed',
243
+ 'step_completed',
244
+ ]);
245
+
246
+ if (marks.length === 0) {
247
+ segments.push({ startFraction: 0, endFraction: 1, status: 'queued' });
248
+ return segments;
249
+ }
250
+
251
+ const firstFraction = timeToFraction(marks[0].time, startMs, duration);
252
+ if (firstFraction > 0.001) {
253
+ segments.push({
254
+ startFraction: 0,
255
+ endFraction: firstFraction,
256
+ status: 'queued',
257
+ });
258
+ }
259
+
260
+ for (let i = 0; i < marks.length; i++) {
261
+ const mark = marks[i];
262
+ const markFrac = timeToFraction(mark.time, startMs, duration);
263
+ const nextMark = marks[i + 1];
264
+ const nextFrac = nextMark
265
+ ? timeToFraction(nextMark.time, startMs, duration)
266
+ : 1;
267
+
268
+ if (mark.type === 'step_started') {
269
+ if (i === marks.length - 1) {
270
+ segments.push({
271
+ startFraction: markFrac,
272
+ endFraction: 1,
273
+ status: 'succeeded',
274
+ });
275
+ } else {
276
+ const nextType = nextMark.type;
277
+ const attemptStatus: SegmentStatus =
278
+ nextType === 'step_retrying' || nextType === 'step_failed'
279
+ ? 'failed'
280
+ : nextType === 'step_completed'
281
+ ? 'succeeded'
282
+ : 'retrying';
283
+ segments.push({
284
+ startFraction: markFrac,
285
+ endFraction: nextFrac,
286
+ status: attemptStatus,
287
+ });
288
+ }
289
+ } else if (mark.type === 'step_retrying') {
290
+ segments.push({
291
+ startFraction: markFrac,
292
+ endFraction: nextFrac,
293
+ status: 'retrying',
294
+ });
295
+ } else if (mark.type === 'step_failed') {
296
+ if (markFrac < 0.999) {
297
+ segments.push({
298
+ startFraction: markFrac,
299
+ endFraction: 1,
300
+ status: 'failed',
301
+ });
302
+ }
303
+ }
304
+ }
305
+
306
+ return segments;
307
+ }
308
+
309
+ function computeHookSegmentsFromSpan(
310
+ startMs: number,
311
+ duration: number,
312
+ events: SpanEvent[]
313
+ ): Segment[] {
314
+ const segments: Segment[] = [];
315
+ if (duration <= 0) return segments;
316
+
317
+ const disposed = sortedEventMarks(events, ['hook_disposed'])[0];
318
+ const disposedFrac = disposed
319
+ ? timeToFraction(disposed.time, startMs, duration)
320
+ : null;
321
+
322
+ const waitingEnd = disposedFrac ?? 1;
323
+ if (waitingEnd > 0.001) {
324
+ segments.push({
325
+ startFraction: 0,
326
+ endFraction: waitingEnd,
327
+ status: 'waiting',
328
+ });
329
+ }
330
+
331
+ if (disposedFrac !== null && disposedFrac < 0.999) {
332
+ segments.push({
333
+ startFraction: disposedFrac,
334
+ endFraction: 1,
335
+ status: 'succeeded',
336
+ });
337
+ }
338
+
339
+ return segments;
340
+ }
341
+
342
+ function computeSleepSegmentsFromSpan(
343
+ _startMs: number,
344
+ duration: number,
345
+ _events: SpanEvent[]
346
+ ): Segment[] {
347
+ if (duration <= 0) return [];
348
+ return [{ startFraction: 0, endFraction: 1, status: 'sleeping' }];
349
+ }
350
+
351
+ function runSegmentStatus(runStatus: string | undefined): SegmentStatus {
352
+ if (runStatus === 'failed') return 'failed';
353
+ if (runStatus === 'pending') return 'pending';
354
+ if (runStatus === 'running') return 'running';
355
+ return 'completed';
356
+ }
357
+
358
+ function computeRunSegmentsFromSpan(
359
+ startMs: number,
360
+ duration: number,
361
+ activeStartMs: number | undefined,
362
+ events: SpanEvent[],
363
+ attributes: Record<string, unknown>
364
+ ): Segment[] {
365
+ const segments: Segment[] = [];
366
+ if (duration <= 0) return segments;
367
+
368
+ const sorted = [...events]
369
+ .map((e) => ({ name: e.name, time: getHighResInMs(e.timestamp) }))
370
+ .sort((a, b) => a.time - b.time);
371
+
372
+ const runData = attributes?.data as Record<string, unknown> | undefined;
373
+ const runStatus = runData?.status as string | undefined;
374
+
375
+ const hasRunCreated = sorted.some((e) => e.name === 'run_created');
376
+
377
+ if (!hasRunCreated) {
378
+ return computeV1RunSegments(startMs, duration, activeStartMs, runStatus);
379
+ }
380
+
381
+ const failedEvent = sorted.find((e) => e.name === 'run_failed');
382
+
383
+ let cursor = 0;
384
+ if (activeStartMs !== undefined && activeStartMs > startMs) {
385
+ const queuedFrac = timeToFraction(activeStartMs, startMs, duration);
386
+ if (queuedFrac > 0.001) {
387
+ segments.push({
388
+ startFraction: 0,
389
+ endFraction: queuedFrac,
390
+ status: 'queued',
391
+ });
392
+ cursor = queuedFrac;
393
+ }
394
+ }
395
+
396
+ segments.push({
397
+ startFraction: cursor,
398
+ endFraction: 1,
399
+ status: failedEvent ? 'failed' : runSegmentStatus(runStatus),
400
+ });
401
+
402
+ return segments;
403
+ }
404
+
405
+ function computeV1RunSegments(
406
+ startMs: number,
407
+ duration: number,
408
+ activeStartMs: number | undefined,
409
+ runStatus: string | undefined
410
+ ): Segment[] {
411
+ const segments: Segment[] = [];
412
+
413
+ let cursor = 0;
414
+ if (activeStartMs !== undefined && activeStartMs > startMs) {
415
+ const queuedFrac = timeToFraction(activeStartMs, startMs, duration);
416
+ if (queuedFrac > 0.001) {
417
+ segments.push({
418
+ startFraction: 0,
419
+ endFraction: queuedFrac,
420
+ status: 'queued',
421
+ });
422
+ cursor = queuedFrac;
423
+ }
424
+ }
425
+
426
+ segments.push({
427
+ startFraction: cursor,
428
+ endFraction: 1,
429
+ status: runSegmentStatus(runStatus),
430
+ });
431
+
432
+ return segments;
433
+ }
434
+
435
+ /**
436
+ * Compute event-based segments for a span. Dispatches on `span.resource`
437
+ * to the appropriate resource-type-specific segment builder.
438
+ * Returns an empty array for generic (non-workflow) spans.
439
+ */
440
+ export function computeSpanSegments(span: Span): Segment[] {
441
+ const startMs = getHighResInMs(span.startTime);
442
+ const duration = getSpanDurationMs(span);
443
+ const activeStartMs = span.activeStartTime
444
+ ? getHighResInMs(span.activeStartTime)
445
+ : undefined;
446
+
447
+ switch (span.resource) {
448
+ case 'step':
449
+ return computeStepSegmentsFromSpan(startMs, duration, span.events);
450
+ case 'hook':
451
+ return computeHookSegmentsFromSpan(startMs, duration, span.events);
452
+ case 'sleep':
453
+ return computeSleepSegmentsFromSpan(startMs, duration, span.events);
454
+ case 'run':
455
+ return computeRunSegmentsFromSpan(
456
+ startMs,
457
+ duration,
458
+ activeStartMs,
459
+ span.events,
460
+ span.attributes
461
+ );
462
+ default:
463
+ return [];
464
+ }
465
+ }
466
+
467
+ // ---------------------------------------------------------------------------
468
+ // Span markers — point-in-time events rendered as ticks on top of a bar
469
+ // ---------------------------------------------------------------------------
470
+
471
+ export interface SpanMarker {
472
+ timeMs: number;
473
+ }
474
+
475
+ // `hook_received` = a resumption; `attr_set` = attributes written mid-span.
476
+ const MARKER_EVENT_NAMES = ['hook_received', 'attr_set'];
477
+
478
+ export function computeSpanMarkers(span: Span): SpanMarker[] {
479
+ return sortedEventMarks(span.events, MARKER_EVENT_NAMES).map((mark) => ({
480
+ timeMs: mark.time,
481
+ }));
482
+ }
483
+
484
+ export interface OffscreenSide {
485
+ count: number;
486
+ /** Nearest off-screen marker — the one a reveal jumps to. */
487
+ nearestMs: number;
488
+ }
489
+
490
+ export interface OffscreenMarkers {
491
+ left: OffscreenSide | null;
492
+ right: OffscreenSide | null;
493
+ }
494
+
495
+ /** Partition markers outside `[visibleStartMs, visibleEndMs]` by side. */
496
+ export function computeOffscreenMarkers(
497
+ markers: SpanMarker[],
498
+ visibleStartMs: number,
499
+ visibleEndMs: number
500
+ ): OffscreenMarkers {
501
+ let leftCount = 0;
502
+ let rightCount = 0;
503
+ let nearestLeft = Number.NEGATIVE_INFINITY;
504
+ let nearestRight = Number.POSITIVE_INFINITY;
505
+ for (const { timeMs } of markers) {
506
+ if (timeMs < visibleStartMs) {
507
+ leftCount++;
508
+ if (timeMs > nearestLeft) nearestLeft = timeMs;
509
+ } else if (timeMs > visibleEndMs) {
510
+ rightCount++;
511
+ if (timeMs < nearestRight) nearestRight = timeMs;
512
+ }
513
+ }
514
+ return {
515
+ left: leftCount > 0 ? { count: leftCount, nearestMs: nearestLeft } : null,
516
+ right:
517
+ rightCount > 0 ? { count: rightCount, nearestMs: nearestRight } : null,
518
+ };
519
+ }