@workflow/web-shared 5.0.0-beta.3 → 5.0.0-beta.31

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 (402) hide show
  1. package/README.md +2 -10
  2. package/dist/components/error-boundary.js +1 -1
  3. package/dist/components/event-list-view.d.ts +46 -1
  4. package/dist/components/event-list-view.d.ts.map +1 -1
  5. package/dist/components/event-list-view.js +228 -90
  6. package/dist/components/hook-actions.js +1 -1
  7. package/dist/components/index.d.ts +6 -2
  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.js +3 -3
  11. package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts +34 -0
  12. package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts.map +1 -0
  13. package/dist/components/new-trace-viewer/components/detail-panel-width.js +67 -0
  14. package/dist/components/new-trace-viewer/components/detail-panel.d.ts +14 -0
  15. package/dist/components/new-trace-viewer/components/detail-panel.d.ts.map +1 -0
  16. package/dist/components/new-trace-viewer/components/detail-panel.js +122 -0
  17. package/dist/components/new-trace-viewer/components/draggable-border.d.ts +31 -0
  18. package/dist/components/new-trace-viewer/components/draggable-border.d.ts.map +1 -0
  19. package/dist/components/new-trace-viewer/components/draggable-border.js +140 -0
  20. package/dist/components/new-trace-viewer/components/event-list.d.ts +4 -2
  21. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -1
  22. package/dist/components/new-trace-viewer/components/event-list.js +22 -13
  23. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  24. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  25. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  26. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  27. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  28. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  29. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  30. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  31. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  32. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  33. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  34. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  35. package/dist/components/new-trace-viewer/components/minimap.d.ts +14 -0
  36. package/dist/components/new-trace-viewer/components/minimap.d.ts.map +1 -0
  37. package/dist/components/new-trace-viewer/components/minimap.js +337 -0
  38. package/dist/components/new-trace-viewer/components/pane-constants.d.ts +14 -0
  39. package/dist/components/new-trace-viewer/components/pane-constants.d.ts.map +1 -0
  40. package/dist/components/new-trace-viewer/components/pane-constants.js +16 -0
  41. package/dist/components/new-trace-viewer/components/span-markers.d.ts +46 -0
  42. package/dist/components/new-trace-viewer/components/span-markers.d.ts.map +1 -0
  43. package/dist/components/new-trace-viewer/components/span-markers.js +85 -0
  44. package/dist/components/new-trace-viewer/components/split-pane.d.ts +3 -7
  45. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
  46. package/dist/components/new-trace-viewer/components/split-pane.js +25 -84
  47. package/dist/components/new-trace-viewer/components/timeline.d.ts +19 -15
  48. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  49. package/dist/components/new-trace-viewer/components/timeline.js +229 -111
  50. package/dist/components/new-trace-viewer/components/timeline.module.css +46 -0
  51. package/dist/components/new-trace-viewer/components/trace-shortcut-helper.d.ts +5 -0
  52. package/dist/components/new-trace-viewer/components/trace-shortcut-helper.d.ts.map +1 -0
  53. package/dist/components/new-trace-viewer/components/trace-shortcut-helper.js +55 -0
  54. package/dist/components/new-trace-viewer/components/trace-shortcut-helper.module.css +20 -0
  55. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
  56. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
  57. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +26 -0
  58. package/dist/components/new-trace-viewer/components/use-element-width.d.ts +8 -0
  59. package/dist/components/new-trace-viewer/components/use-element-width.d.ts.map +1 -0
  60. package/dist/components/new-trace-viewer/components/use-element-width.js +25 -0
  61. package/dist/components/new-trace-viewer/components/use-row-window.d.ts +34 -0
  62. package/dist/components/new-trace-viewer/components/use-row-window.d.ts.map +1 -0
  63. package/dist/components/new-trace-viewer/components/use-row-window.js +107 -0
  64. package/dist/components/new-trace-viewer/context.d.ts +3 -1
  65. package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
  66. package/dist/components/new-trace-viewer/context.js +9 -10
  67. package/dist/components/new-trace-viewer/icons.js +3 -3
  68. package/dist/components/new-trace-viewer/search.d.ts +19 -0
  69. package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
  70. package/dist/components/new-trace-viewer/search.js +134 -0
  71. package/dist/components/new-trace-viewer/trace-viewer.d.ts +6 -3
  72. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  73. package/dist/components/new-trace-viewer/trace-viewer.js +167 -162
  74. package/dist/components/new-trace-viewer/types.d.ts +2 -0
  75. package/dist/components/new-trace-viewer/types.d.ts.map +1 -1
  76. package/dist/components/new-trace-viewer/types.js +2 -2
  77. package/dist/components/new-trace-viewer/utils.d.ts +48 -13
  78. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  79. package/dist/components/new-trace-viewer/utils.js +135 -107
  80. package/dist/components/run-trace-view.d.ts +5 -7
  81. package/dist/components/run-trace-view.d.ts.map +1 -1
  82. package/dist/components/run-trace-view.js +3 -3
  83. package/dist/components/sidebar/attribute-panel.d.ts +4 -1
  84. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  85. package/dist/components/sidebar/attribute-panel.js +133 -113
  86. package/dist/components/sidebar/attributes-block.d.ts +40 -0
  87. package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
  88. package/dist/components/sidebar/attributes-block.js +140 -0
  89. package/dist/components/sidebar/conversation-view.js +1 -1
  90. package/dist/components/sidebar/copyable-data-block.d.ts +2 -0
  91. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  92. package/dist/components/sidebar/copyable-data-block.js +17 -16
  93. package/dist/components/sidebar/entity-detail-panel.d.ts +11 -11
  94. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  95. package/dist/components/sidebar/entity-detail-panel.js +35 -69
  96. package/dist/components/sidebar/events-list.d.ts +6 -2
  97. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  98. package/dist/components/sidebar/events-list.js +76 -62
  99. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  100. package/dist/components/sidebar/sidebar-data-context.d.ts +10 -7
  101. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
  102. package/dist/components/sidebar/sidebar-data-context.js +1 -1
  103. package/dist/components/sidebar/span-detail-merge.d.ts +31 -0
  104. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  105. package/dist/components/sidebar/span-detail-merge.js +92 -0
  106. package/dist/components/sidebar/use-selected-span-detail.d.ts +16 -0
  107. package/dist/components/sidebar/use-selected-span-detail.d.ts.map +1 -0
  108. package/dist/components/sidebar/use-selected-span-detail.js +93 -0
  109. package/dist/components/stream-viewer.js +1 -1
  110. package/dist/components/trace-viewer/components/map.d.ts +2 -2
  111. package/dist/components/trace-viewer/components/map.d.ts.map +1 -1
  112. package/dist/components/trace-viewer/components/map.js +1 -1
  113. package/dist/components/trace-viewer/components/markers.js +1 -1
  114. package/dist/components/trace-viewer/components/node.d.ts +7 -7
  115. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  116. package/dist/components/trace-viewer/components/node.js +2 -1
  117. package/dist/components/trace-viewer/components/search-input.js +1 -1
  118. package/dist/components/trace-viewer/components/search.js +1 -1
  119. package/dist/components/trace-viewer/components/span-content.js +1 -1
  120. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  121. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  122. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  123. package/dist/components/trace-viewer/components/ui.js +1 -1
  124. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  125. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  126. package/dist/components/trace-viewer/context.js +1 -1
  127. package/dist/components/trace-viewer/index.js +1 -1
  128. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  129. package/dist/components/trace-viewer/trace-viewer.module.css +4 -4
  130. package/dist/components/trace-viewer/types.js +1 -1
  131. package/dist/components/trace-viewer/util/constants.js +1 -1
  132. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  133. package/dist/components/trace-viewer/util/timing.d.ts +3 -3
  134. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  135. package/dist/components/trace-viewer/util/timing.js +8 -11
  136. package/dist/components/trace-viewer/util/tree.js +1 -1
  137. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  138. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  139. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  140. package/dist/components/trace-viewer/worker.js +1 -1
  141. package/dist/components/trace-viewer-new.d.ts +5 -1
  142. package/dist/components/trace-viewer-new.d.ts.map +1 -1
  143. package/dist/components/trace-viewer-new.js +8 -10
  144. package/dist/components/ui/alert.js +2 -2
  145. package/dist/components/ui/button.d.ts +10 -0
  146. package/dist/components/ui/button.d.ts.map +1 -0
  147. package/dist/components/ui/button.js +46 -0
  148. package/dist/components/ui/card.js +2 -2
  149. package/dist/components/ui/collapsible.d.ts +46 -0
  150. package/dist/components/ui/collapsible.d.ts.map +1 -0
  151. package/dist/components/ui/collapsible.js +66 -0
  152. package/dist/components/ui/context-card.d.ts +47 -0
  153. package/dist/components/ui/context-card.d.ts.map +1 -0
  154. package/dist/components/ui/context-card.js +471 -0
  155. package/dist/components/ui/data-inspector.d.ts +7 -6
  156. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  157. package/dist/components/ui/data-inspector.js +232 -99
  158. package/dist/components/ui/data-inspector.styles.d.ts +29 -0
  159. package/dist/components/ui/data-inspector.styles.d.ts.map +1 -0
  160. package/dist/components/ui/data-inspector.styles.js +95 -0
  161. package/dist/components/ui/decrypt-button.js +1 -1
  162. package/dist/components/ui/error-card.js +2 -2
  163. package/dist/components/ui/error-stack-block.d.ts +17 -10
  164. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  165. package/dist/components/ui/error-stack-block.js +49 -30
  166. package/dist/components/ui/icon-button.d.ts +19 -0
  167. package/dist/components/ui/icon-button.d.ts.map +1 -0
  168. package/dist/components/ui/icon-button.js +29 -0
  169. package/dist/components/ui/kbd.d.ts +10 -0
  170. package/dist/components/ui/kbd.d.ts.map +1 -0
  171. package/dist/components/ui/kbd.js +25 -0
  172. package/dist/components/ui/load-more-button.js +1 -1
  173. package/dist/components/ui/menu-dropdown.js +1 -1
  174. package/dist/components/ui/skeleton.js +2 -2
  175. package/dist/components/ui/spinner.js +1 -1
  176. package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
  177. package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
  178. package/dist/components/ui/timestamp-tooltip.js +80 -132
  179. package/dist/components/ui/tooltip.d.ts +10 -0
  180. package/dist/components/ui/tooltip.d.ts.map +1 -0
  181. package/dist/components/ui/tooltip.js +12 -0
  182. package/dist/components/workflow-trace-view.d.ts +7 -8
  183. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  184. package/dist/components/workflow-trace-view.js +11 -16
  185. package/dist/components/workflow-traces/event-colors.d.ts +2 -1
  186. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  187. package/dist/components/workflow-traces/event-colors.js +15 -7
  188. package/dist/components/workflow-traces/trace-colors.d.ts.map +1 -1
  189. package/dist/components/workflow-traces/trace-colors.js +3 -4
  190. package/dist/components/workflow-traces/trace-span-construction.d.ts +7 -4
  191. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  192. package/dist/components/workflow-traces/trace-span-construction.js +87 -33
  193. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  194. package/dist/hooks/use-dark-mode.js +1 -1
  195. package/dist/hooks/use-intersection-observer.d.ts +17 -0
  196. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  197. package/dist/hooks/use-intersection-observer.js +29 -0
  198. package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
  199. package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
  200. package/dist/hooks/use-load-more-on-scroll.js +28 -0
  201. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  202. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  203. package/dist/hooks/use-reduced-motion.js +29 -0
  204. package/dist/index.d.ts +3 -2
  205. package/dist/index.d.ts.map +1 -1
  206. package/dist/index.js +4 -3
  207. package/dist/lib/cn.d.ts +10 -0
  208. package/dist/lib/cn.d.ts.map +1 -0
  209. package/dist/lib/cn.js +83 -0
  210. package/dist/lib/event-analysis.d.ts +1 -1
  211. package/dist/lib/event-analysis.d.ts.map +1 -1
  212. package/dist/lib/event-analysis.js +3 -2
  213. package/dist/lib/event-materialization.d.ts +1 -1
  214. package/dist/lib/event-materialization.d.ts.map +1 -1
  215. package/dist/lib/event-materialization.js +29 -21
  216. package/dist/lib/exact-event-search-id.d.ts +25 -0
  217. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  218. package/dist/lib/exact-event-search-id.js +39 -0
  219. package/dist/lib/hydration.d.ts +17 -2
  220. package/dist/lib/hydration.d.ts.map +1 -1
  221. package/dist/lib/hydration.js +185 -22
  222. package/dist/lib/stream-display.js +1 -1
  223. package/dist/lib/toast.js +1 -1
  224. package/dist/lib/trace-builder.d.ts +16 -4
  225. package/dist/lib/trace-builder.d.ts.map +1 -1
  226. package/dist/lib/trace-builder.js +34 -15
  227. package/dist/lib/utils.d.ts +23 -9
  228. package/dist/lib/utils.d.ts.map +1 -1
  229. package/dist/lib/utils.js +73 -33
  230. package/dist/lib/zstd-browser-decoder.d.ts +7 -0
  231. package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
  232. package/dist/lib/zstd-browser-decoder.js +41 -0
  233. package/dist/styles.css +14 -0
  234. package/package.json +10 -7
  235. package/src/components/event-list-view.tsx +375 -116
  236. package/src/components/index.ts +12 -5
  237. package/src/components/new-trace-viewer/components/copy-button.tsx +2 -2
  238. package/src/components/new-trace-viewer/components/detail-panel-width.test.ts +123 -0
  239. package/src/components/new-trace-viewer/components/detail-panel-width.ts +76 -0
  240. package/src/components/new-trace-viewer/components/detail-panel.tsx +273 -0
  241. package/src/components/new-trace-viewer/components/draggable-border.tsx +216 -0
  242. package/src/components/new-trace-viewer/components/event-list.tsx +57 -32
  243. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  244. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  245. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  246. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  247. package/src/components/new-trace-viewer/components/minimap.tsx +559 -0
  248. package/src/components/new-trace-viewer/components/pane-constants.ts +19 -0
  249. package/src/components/new-trace-viewer/components/span-markers.tsx +173 -0
  250. package/src/components/new-trace-viewer/components/split-pane.tsx +80 -135
  251. package/src/components/new-trace-viewer/components/timeline.module.css +46 -0
  252. package/src/components/new-trace-viewer/components/timeline.tsx +603 -239
  253. package/src/components/new-trace-viewer/components/trace-shortcut-helper.module.css +20 -0
  254. package/src/components/new-trace-viewer/components/trace-shortcut-helper.tsx +116 -0
  255. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +114 -0
  256. package/src/components/new-trace-viewer/components/use-element-width.ts +29 -0
  257. package/src/components/new-trace-viewer/components/use-row-window.ts +140 -0
  258. package/src/components/new-trace-viewer/context.tsx +13 -14
  259. package/src/components/new-trace-viewer/icons.tsx +2 -2
  260. package/src/components/new-trace-viewer/search.ts +178 -0
  261. package/src/components/new-trace-viewer/trace-viewer.tsx +336 -293
  262. package/src/components/new-trace-viewer/types.ts +2 -0
  263. package/src/components/new-trace-viewer/utils.test.ts +247 -0
  264. package/src/components/new-trace-viewer/utils.ts +203 -134
  265. package/src/components/run-trace-view.tsx +10 -14
  266. package/src/components/sidebar/attribute-panel.tsx +276 -275
  267. package/src/components/sidebar/attributes-block.tsx +326 -0
  268. package/src/components/sidebar/copyable-data-block.tsx +50 -28
  269. package/src/components/sidebar/entity-detail-panel.tsx +89 -123
  270. package/src/components/sidebar/events-list.tsx +196 -178
  271. package/src/components/sidebar/sidebar-data-context.tsx +11 -11
  272. package/src/components/sidebar/span-detail-merge.ts +134 -0
  273. package/src/components/sidebar/use-selected-span-detail.ts +146 -0
  274. package/src/components/trace-viewer/components/node.tsx +3 -2
  275. package/src/components/trace-viewer/trace-viewer.module.css +4 -4
  276. package/src/components/trace-viewer/util/timing.ts +7 -11
  277. package/src/components/trace-viewer-new.tsx +21 -15
  278. package/src/components/ui/alert.tsx +1 -1
  279. package/src/components/ui/button.tsx +68 -0
  280. package/src/components/ui/card.tsx +1 -1
  281. package/src/components/ui/collapsible.tsx +219 -0
  282. package/src/components/ui/context-card.tsx +784 -0
  283. package/src/components/ui/data-inspector.styles.ts +96 -0
  284. package/src/components/ui/data-inspector.tsx +501 -204
  285. package/src/components/ui/error-card.tsx +1 -1
  286. package/src/components/ui/error-stack-block.tsx +84 -55
  287. package/src/components/ui/icon-button.tsx +53 -0
  288. package/src/components/ui/kbd.tsx +43 -0
  289. package/src/components/ui/skeleton.tsx +1 -1
  290. package/src/components/ui/timestamp-tooltip.tsx +132 -194
  291. package/src/components/ui/tooltip.tsx +47 -0
  292. package/src/components/workflow-trace-view.tsx +31 -36
  293. package/src/components/workflow-traces/event-colors.ts +15 -11
  294. package/src/components/workflow-traces/trace-colors.ts +6 -6
  295. package/src/components/workflow-traces/trace-span-construction.test.ts +85 -0
  296. package/src/components/workflow-traces/trace-span-construction.ts +118 -35
  297. package/src/hooks/use-intersection-observer.ts +57 -0
  298. package/src/hooks/use-load-more-on-scroll.ts +49 -0
  299. package/src/hooks/use-reduced-motion.ts +34 -0
  300. package/src/index.ts +9 -0
  301. package/src/lib/cn.ts +93 -0
  302. package/src/lib/event-analysis.ts +6 -4
  303. package/src/lib/event-materialization.ts +35 -21
  304. package/src/lib/exact-event-search-id.ts +67 -0
  305. package/src/lib/hydration.ts +203 -29
  306. package/src/lib/trace-builder.ts +44 -19
  307. package/src/lib/utils.test.ts +33 -0
  308. package/src/lib/utils.ts +82 -35
  309. package/src/lib/zstd-browser-decoder.ts +42 -0
  310. package/src/styles.css +14 -0
  311. package/dist/components/error-boundary.js.map +0 -1
  312. package/dist/components/event-list-view.js.map +0 -1
  313. package/dist/components/hook-actions.js.map +0 -1
  314. package/dist/components/index.js.map +0 -1
  315. package/dist/components/new-trace-viewer/components/copy-button.js.map +0 -1
  316. package/dist/components/new-trace-viewer/components/event-list.js.map +0 -1
  317. package/dist/components/new-trace-viewer/components/split-pane.js.map +0 -1
  318. package/dist/components/new-trace-viewer/components/timeline.js.map +0 -1
  319. package/dist/components/new-trace-viewer/context.js.map +0 -1
  320. package/dist/components/new-trace-viewer/detail-panel.d.ts +0 -10
  321. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +0 -1
  322. package/dist/components/new-trace-viewer/detail-panel.js +0 -11
  323. package/dist/components/new-trace-viewer/detail-panel.js.map +0 -1
  324. package/dist/components/new-trace-viewer/icons.js.map +0 -1
  325. package/dist/components/new-trace-viewer/trace-viewer.js.map +0 -1
  326. package/dist/components/new-trace-viewer/types.js.map +0 -1
  327. package/dist/components/new-trace-viewer/util/constants.d.ts +0 -1
  328. package/dist/components/new-trace-viewer/util/constants.d.ts.map +0 -1
  329. package/dist/components/new-trace-viewer/util/constants.js +0 -2
  330. package/dist/components/new-trace-viewer/util/constants.js.map +0 -1
  331. package/dist/components/new-trace-viewer/utils.js.map +0 -1
  332. package/dist/components/run-trace-view.js.map +0 -1
  333. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  334. package/dist/components/sidebar/conversation-view.js.map +0 -1
  335. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  336. package/dist/components/sidebar/detail-card.d.ts +0 -14
  337. package/dist/components/sidebar/detail-card.d.ts.map +0 -1
  338. package/dist/components/sidebar/detail-card.js +0 -20
  339. package/dist/components/sidebar/detail-card.js.map +0 -1
  340. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  341. package/dist/components/sidebar/events-list.js.map +0 -1
  342. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  343. package/dist/components/sidebar/sidebar-data-context.js.map +0 -1
  344. package/dist/components/stream-viewer.js.map +0 -1
  345. package/dist/components/trace-viewer/components/map.js.map +0 -1
  346. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  347. package/dist/components/trace-viewer/components/node.js.map +0 -1
  348. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  349. package/dist/components/trace-viewer/components/search.js.map +0 -1
  350. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  351. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  352. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  353. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  354. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  355. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  356. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  357. package/dist/components/trace-viewer/context.js.map +0 -1
  358. package/dist/components/trace-viewer/index.js.map +0 -1
  359. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  360. package/dist/components/trace-viewer/types.js.map +0 -1
  361. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  362. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  363. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  364. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  365. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  366. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  367. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  368. package/dist/components/trace-viewer/worker.js.map +0 -1
  369. package/dist/components/trace-viewer-new.js.map +0 -1
  370. package/dist/components/ui/alert.js.map +0 -1
  371. package/dist/components/ui/card.js.map +0 -1
  372. package/dist/components/ui/data-inspector.js.map +0 -1
  373. package/dist/components/ui/decrypt-button.js.map +0 -1
  374. package/dist/components/ui/error-card.js.map +0 -1
  375. package/dist/components/ui/error-stack-block.js.map +0 -1
  376. package/dist/components/ui/inspector-theme.d.ts +0 -95
  377. package/dist/components/ui/inspector-theme.d.ts.map +0 -1
  378. package/dist/components/ui/inspector-theme.js +0 -82
  379. package/dist/components/ui/inspector-theme.js.map +0 -1
  380. package/dist/components/ui/load-more-button.js.map +0 -1
  381. package/dist/components/ui/menu-dropdown.js.map +0 -1
  382. package/dist/components/ui/skeleton.js.map +0 -1
  383. package/dist/components/ui/spinner.js.map +0 -1
  384. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  385. package/dist/components/workflow-trace-view.js.map +0 -1
  386. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  387. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  388. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  389. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  390. package/dist/hooks/use-dark-mode.js.map +0 -1
  391. package/dist/index.js.map +0 -1
  392. package/dist/lib/event-analysis.js.map +0 -1
  393. package/dist/lib/event-materialization.js.map +0 -1
  394. package/dist/lib/hydration.js.map +0 -1
  395. package/dist/lib/stream-display.js.map +0 -1
  396. package/dist/lib/toast.js.map +0 -1
  397. package/dist/lib/trace-builder.js.map +0 -1
  398. package/dist/lib/utils.js.map +0 -1
  399. package/src/components/new-trace-viewer/detail-panel.tsx +0 -57
  400. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  401. package/src/components/sidebar/detail-card.tsx +0 -65
  402. package/src/components/ui/inspector-theme.ts +0 -110
@@ -1,215 +1,472 @@
1
1
  'use client';
2
2
 
3
- import type { ReactNode } from 'react';
4
- import { memo, useEffect, useMemo, useRef, useState } from 'react';
5
- import { cn } from '../../../lib/utils';
6
- import type { Span } from '../../trace-viewer/types';
7
- import { formatDuration, getHighResInMs } from '../../trace-viewer/util/timing';
8
- import type { SegmentStatus, TimeCompression } from '../utils';
3
+ import { ArrowLeft, ArrowRight } from 'lucide-react';
4
+ import type { CSSProperties, ReactNode } from 'react';
9
5
  import {
10
- computeCompressedTimeMarkers,
6
+ Fragment,
7
+ memo,
8
+ useCallback,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from 'react';
14
+ import { cn } from '../../../lib/cn';
15
+ import {
16
+ formatDurationPrecise,
17
+ getHighResInMs,
18
+ } from '../../trace-viewer/util/timing';
19
+ import { isSpanDimmedBySearch, type SpanSearchResult } from '../search';
20
+ import type { Span } from '../types';
21
+ import type {
22
+ OffscreenMarkers,
23
+ Segment,
24
+ SegmentStatus,
25
+ TimeMarker,
26
+ } from '../utils';
27
+ import {
28
+ computeOffscreenMarkers,
11
29
  computeSpanGaps,
30
+ computeSpanMarkers,
12
31
  computeSpanSegments,
13
- computeTimeMarkers,
14
32
  getResourceColor,
33
+ getSpanDurationMs,
34
+ isSpanErrored,
15
35
  } from '../utils';
36
+ import {
37
+ cullCollidingMarkers,
38
+ MarkerLayer,
39
+ OffscreenMarkerIndicator,
40
+ projectMarkers,
41
+ } from './span-markers';
42
+ import styles from './timeline.module.css';
43
+ import { ROW_HEIGHT_PX, useRowWindow } from './use-row-window';
16
44
 
17
- const QUEUED_BACKGROUND =
18
- 'radial-gradient(circle, var(--ds-gray-400) 2px, transparent 2px) center / 20px 20px space no-repeat, var(--ds-gray-500)';
45
+ // ---------------------------------------------------------------------------
46
+ // Constants
47
+ // ---------------------------------------------------------------------------
19
48
 
20
- const HATCHED_BACKGROUND =
21
- 'repeating-linear-gradient(-45deg, var(--ds-gray-400) 0px, var(--ds-gray-400) 3px, var(--ds-gray-500) 3px, var(--ds-gray-500) 6px)';
49
+ const TINY_BAR_BOX_SIZE_PX = 24;
50
+ const TINY_BAR_WIDTH_PX = 4;
51
+ export const TIMELINE_PADDING_PX = 16;
22
52
 
23
- const SEGMENT_CONFIG: Record<
24
- SegmentStatus,
25
- { className?: string; style?: React.CSSProperties }
26
- > = {
27
- queued: { style: { background: QUEUED_BACKGROUND } },
28
- retrying: {
29
- className: 'box-border bg-gray-500',
30
- },
31
- waiting: { style: { background: HATCHED_BACKGROUND } },
32
- running: { className: 'bg-blue-700' },
33
- failed: { className: 'bg-red-700' },
34
- succeeded: { className: 'bg-green-700' },
35
- sleeping: { className: 'bg-amber-700' },
36
- received: { className: 'bg-blue-700' },
53
+ const SEGMENT_CLASSES: Record<SegmentStatus, string> = {
54
+ queued: 'bg-gray-400 border border-gray-500',
55
+ pending: 'bg-gray-200 border border-gray-500',
56
+ retrying: 'bg-gray-400 border border-gray-500',
57
+ waiting: 'bg-gray-200 border border-gray-500',
58
+ running: 'bg-blue-200 border border-blue-500',
59
+ completed: 'bg-blue-200 border border-blue-500',
60
+ failed: 'bg-red-200 border border-red-500',
61
+ succeeded: 'bg-green-200 border border-green-500',
62
+ sleeping: 'bg-gray-400 border border-gray-500',
63
+ received: 'bg-blue-200 border border-blue-500',
37
64
  };
38
65
 
39
- const FIXED_BAR_WIDTH_PX = 4;
40
- const SEGMENT_GAP_PX = 1;
41
- // Keep this in sync with the rendered row height in the timeline/event list.
42
- const ROW_HEIGHT = 34;
43
- const CONTAINER_PAD_Y = 8;
44
- const END_CAP_HEIGHT = 8;
66
+ const TIMELINE_INSET_STYLE: CSSProperties = {
67
+ left: TIMELINE_PADDING_PX,
68
+ right: TIMELINE_PADDING_PX,
69
+ };
45
70
 
46
- const DeltaIndicator = memo(function DeltaIndicator({
47
- leftFrac,
48
- rightFrac,
49
- label,
50
- rowIndex,
51
- }: {
52
- leftFrac: number;
53
- rightFrac: number;
54
- label: string;
55
- rowIndex: number;
56
- }) {
57
- const centerY = CONTAINER_PAD_Y + (rowIndex + 1) * ROW_HEIGHT;
71
+ const STRIPED_SEGMENT_STATUSES: ReadonlySet<SegmentStatus> = new Set([
72
+ 'pending',
73
+ 'running',
74
+ 'received',
75
+ ]);
58
76
 
77
+ function AnimatedStripes({ status }: { status: SegmentStatus }): ReactNode {
59
78
  return (
60
79
  <div
61
- className="absolute pointer-events-none"
62
- style={{
63
- left: `${leftFrac * 100}%`,
64
- width: `${(rightFrac - leftFrac) * 100}%`,
65
- top: centerY - END_CAP_HEIGHT / 2,
66
- height: END_CAP_HEIGHT,
67
- }}
68
- >
69
- <div className="absolute left-0 top-0 w-px h-full bg-amber-800" />
70
- <div className="absolute left-0 right-0 top-1/2 h-px bg-amber-800" />
71
- <div className="absolute right-0 top-0 w-px h-full bg-amber-800" />
72
- <span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-label-12 leading-none whitespace-nowrap rounded-xs px-1 py-0.5 text-gray-100 bg-amber-800">
73
- {label}
74
- </span>
75
- </div>
80
+ aria-hidden
81
+ className={
82
+ status === 'pending' ? styles.pendingStripes : styles.runningStripes
83
+ }
84
+ />
76
85
  );
77
- });
86
+ }
78
87
 
79
- const TimelineBar = memo(function TimelineBar({
80
- span,
81
- compression,
82
- containerWidth,
83
- isSelected,
84
- onClick,
85
- }: {
86
- span: Span;
87
- compression: TimeCompression;
88
- containerWidth: number;
89
- isSelected: boolean;
90
- onClick: () => void;
91
- }): ReactNode {
92
- const startTime = getHighResInMs(span.startTime);
93
- const endTime = getHighResInMs(span.endTime);
94
- const totalDurationMs = endTime - startTime;
88
+ // ---------------------------------------------------------------------------
89
+ // Bar geometry
90
+ // ---------------------------------------------------------------------------
95
91
 
96
- const leftFrac = compression.toVisual(startTime);
97
- const rightFrac = compression.toVisual(endTime);
98
- const widthFrac = rightFrac - leftFrac;
92
+ type BarMode =
93
+ | { kind: 'arrow'; direction: 'left' | 'right' }
94
+ | { kind: 'tiny' }
95
+ | { kind: 'full' };
99
96
 
100
- const leftPct = leftFrac * 100;
101
- const widthPct = widthFrac * 100;
97
+ interface BarGeometry {
98
+ mode: BarMode;
99
+ leftPct: number;
100
+ widthPct: number;
101
+ visibleStartMs: number;
102
+ visibleEndMs: number;
103
+ visiblePixelWidth: number;
104
+ }
102
105
 
103
- const pixelWidth = widthFrac * containerWidth;
104
- const isCompressed = containerWidth > 0 && pixelWidth < FIXED_BAR_WIDTH_PX;
105
- const [isRowHovered, setIsRowHovered] = useState(false);
106
+ /**
107
+ * Compute the bar's geometry inside the timeline viewport. Percentages are
108
+ * always in [0, 100] so we never emit CSS values that exceed browser layout
109
+ * limits at extreme zoom.
110
+ */
111
+ function computeBarGeometry(
112
+ startMs: number,
113
+ endMs: number,
114
+ viewStart: number,
115
+ viewEnd: number,
116
+ containerWidth: number
117
+ ): BarGeometry {
118
+ const viewDuration = viewEnd - viewStart;
119
+ const visibleStartMs = Math.max(startMs, viewStart);
120
+ const visibleEndMs = Math.min(endMs, viewEnd);
121
+ const visibleDurationMs = Math.max(0, visibleEndMs - visibleStartMs);
122
+ const widthFrac = viewDuration > 0 ? visibleDurationMs / viewDuration : 0;
123
+ const visiblePixelWidth = widthFrac * containerWidth;
106
124
 
107
- const segments = useMemo(() => computeSpanSegments(span), [span]);
108
- const finalSegment = segments[segments.length - 1];
125
+ const isTiny = containerWidth > 0 && visiblePixelWidth < TINY_BAR_BOX_SIZE_PX;
126
+ const extendsOffLeft = startMs < viewStart;
127
+ const extendsOffRight = endMs > viewEnd;
109
128
 
110
- const workflowStatus = (span.attributes.data as Record<string, unknown>)
111
- ?.status as string | undefined;
112
- const isErrored = span.status.code === 2 || workflowStatus === 'failed';
113
- const colors = getResourceColor(span.resource);
114
- const fallbackColor = isErrored
115
- ? (colors.errorBar ?? 'var(--ds-red-700)')
116
- : colors.bar;
117
- const compressedSegmentStatus = isErrored
118
- ? 'failed'
119
- : span.resource === 'hook'
120
- ? 'received'
121
- : finalSegment?.status;
122
- const compressedSegmentStyle =
123
- compressedSegmentStatus === 'queued'
124
- ? { background: 'var(--ds-gray-500)' }
125
- : compressedSegmentStatus
126
- ? SEGMENT_CONFIG[compressedSegmentStatus].style
127
- : undefined;
128
- const compressedSegmentClassName = compressedSegmentStatus
129
- ? SEGMENT_CONFIG[compressedSegmentStatus].className
130
- : undefined;
131
-
132
- const hasCompressedStatus = Boolean(
133
- compressedSegmentClassName || compressedSegmentStyle
134
- );
135
- const renderDurationLabel = (label: string) => (
129
+ const mode: BarMode =
130
+ isTiny && (extendsOffLeft || extendsOffRight)
131
+ ? { kind: 'arrow', direction: extendsOffRight ? 'right' : 'left' }
132
+ : isTiny
133
+ ? { kind: 'tiny' }
134
+ : { kind: 'full' };
135
+
136
+ return {
137
+ mode,
138
+ leftPct:
139
+ viewDuration > 0
140
+ ? ((visibleStartMs - viewStart) / viewDuration) * 100
141
+ : 0,
142
+ widthPct: widthFrac * 100,
143
+ visibleStartMs,
144
+ visibleEndMs,
145
+ visiblePixelWidth,
146
+ };
147
+ }
148
+
149
+ function getBarPositionStyle(geometry: BarGeometry): {
150
+ left: string;
151
+ width: string;
152
+ } {
153
+ switch (geometry.mode.kind) {
154
+ case 'arrow':
155
+ return {
156
+ left:
157
+ geometry.mode.direction === 'right'
158
+ ? `calc(100% - ${TINY_BAR_BOX_SIZE_PX}px)`
159
+ : '0px',
160
+ width: `${TINY_BAR_BOX_SIZE_PX}px`,
161
+ };
162
+ case 'tiny':
163
+ return {
164
+ left: `min(${geometry.leftPct}%, calc(100% - ${TINY_BAR_WIDTH_PX}px))`,
165
+ width: `${TINY_BAR_WIDTH_PX}px`,
166
+ };
167
+ case 'full':
168
+ return {
169
+ left: `${geometry.leftPct}%`,
170
+ width: `max(${geometry.widthPct}%, 4px)`,
171
+ };
172
+ }
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Segment projection
177
+ // ---------------------------------------------------------------------------
178
+
179
+ interface VisibleSegment {
180
+ status: SegmentStatus;
181
+ leftPct: number;
182
+ widthPct: number;
183
+ pixelWidth: number;
184
+ fullDurationMs: number;
185
+ }
186
+
187
+ /**
188
+ * Project status segments onto the visible portion of a bar. Segments fully
189
+ * outside the visible window are dropped; segments crossing the edge are
190
+ * clipped to [0%, 100%] of the visible bar.
191
+ */
192
+ function projectSegments(
193
+ segments: Segment[],
194
+ spanStartMs: number,
195
+ spanDurationMs: number,
196
+ geometry: BarGeometry
197
+ ): VisibleSegment[] {
198
+ const visibleDurationMs = geometry.visibleEndMs - geometry.visibleStartMs;
199
+ if (visibleDurationMs <= 0) return [];
200
+
201
+ return segments.flatMap((seg) => {
202
+ const segStartMs = spanStartMs + seg.startFraction * spanDurationMs;
203
+ const segEndMs = spanStartMs + seg.endFraction * spanDurationMs;
204
+ const startMs = Math.max(segStartMs, geometry.visibleStartMs);
205
+ const endMs = Math.min(segEndMs, geometry.visibleEndMs);
206
+ if (endMs <= startMs) return [];
207
+
208
+ const widthFrac = (endMs - startMs) / visibleDurationMs;
209
+ return [
210
+ {
211
+ status: seg.status,
212
+ leftPct:
213
+ ((startMs - geometry.visibleStartMs) / visibleDurationMs) * 100,
214
+ widthPct: widthFrac * 100,
215
+ pixelWidth: widthFrac * geometry.visiblePixelWidth,
216
+ fullDurationMs: (seg.endFraction - seg.startFraction) * spanDurationMs,
217
+ },
218
+ ];
219
+ });
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Small render helpers
224
+ // ---------------------------------------------------------------------------
225
+
226
+ function DurationLabel({
227
+ label,
228
+ className,
229
+ }: {
230
+ label: string;
231
+ className?: string;
232
+ }): ReactNode {
233
+ return (
136
234
  <span
137
- className="pointer-events-none absolute inset-0 flex items-center justify-start overflow-hidden px-1 text-[10px] font-mono font-medium leading-none whitespace-nowrap text-left text-white tabular-nums"
138
- style={{ textShadow: '0 1px 1px rgba(0, 0, 0, 0.45)' }}
235
+ className={cn(
236
+ 'pointer-events-none absolute inset-0 flex items-center justify-start overflow-hidden px-1 text-[10px] font-mono font-medium leading-none whitespace-nowrap text-left text-gray-1000 tabular-nums opacity-0 group-hover/timeline-row:opacity-100',
237
+ className
238
+ )}
139
239
  >
140
240
  {label}
141
241
  </span>
142
242
  );
143
- const getMinDurationLabelWidthPx = (label: string) =>
144
- Math.max(40, label.length * 6 + 12);
145
- const totalDurationLabel = formatDuration(totalDurationMs);
146
- const showBarDurationLabel =
147
- isRowHovered &&
148
- pixelWidth >= getMinDurationLabelWidthPx(totalDurationLabel);
149
-
150
- const barContent = isCompressed ? (
243
+ }
244
+
245
+ function BoundaryArrow({
246
+ direction,
247
+ }: {
248
+ direction: 'left' | 'right';
249
+ }): ReactNode {
250
+ const Icon = direction === 'right' ? ArrowRight : ArrowLeft;
251
+ return (
252
+ <div className="flex h-6 w-6 items-center justify-center rounded-[0.25rem]">
253
+ <Icon className="size-3 text-gray-900" />
254
+ </div>
255
+ );
256
+ }
257
+
258
+ function PlainBar({
259
+ bg,
260
+ border,
261
+ label,
262
+ }: {
263
+ bg: string;
264
+ border: string;
265
+ label: string | null;
266
+ }): ReactNode {
267
+ return (
151
268
  <div
152
- className={cn(
153
- 'relative h-4 rounded-[0.25rem] top-[3px]',
154
- compressedSegmentClassName
155
- )}
269
+ className="relative h-6 w-full min-w-1 rounded-[0.25rem] border"
270
+ style={{ background: bg, borderColor: border }}
271
+ >
272
+ {label ? <DurationLabel label={label} /> : null}
273
+ </div>
274
+ );
275
+ }
276
+
277
+ function LeadInConnector({
278
+ leftPct,
279
+ widthPct,
280
+ label,
281
+ }: {
282
+ leftPct: number;
283
+ widthPct: number;
284
+ label: string | null;
285
+ }): ReactNode {
286
+ return (
287
+ <div
288
+ className="absolute top-1/2 h-6 -translate-y-1/2"
156
289
  style={{
157
- width: '100%',
158
- background: hasCompressedStatus ? undefined : fallbackColor,
159
- ...compressedSegmentStyle,
290
+ left: `calc(${leftPct}% + 0.5px)`,
291
+ width: `calc(${widthPct}% - 1px)`,
160
292
  }}
161
293
  >
162
- {showBarDurationLabel ? renderDurationLabel(totalDurationLabel) : null}
294
+ <div className="absolute left-0 top-1/2 h-4 w-px -translate-y-1/2 bg-gray-500" />
295
+ <div className="absolute inset-x-0 top-1/2 h-px -translate-y-1/2 bg-gray-500" />
296
+ {label ? <DurationLabel label={label} /> : null}
163
297
  </div>
164
- ) : segments.length > 0 ? (
165
- <div className="relative w-full h-4 top-[3px]">
298
+ );
299
+ }
300
+
301
+ function SegmentBar({
302
+ segments,
303
+ showLabels = true,
304
+ }: {
305
+ segments: VisibleSegment[];
306
+ /**
307
+ * When false, segment duration labels are suppressed. Used when a separate
308
+ * top layer (e.g. the resumption-marker duration overlay) renders the label
309
+ * above the markers instead, so we don't draw it twice.
310
+ */
311
+ showLabels?: boolean;
312
+ }): ReactNode {
313
+ return (
314
+ <div className="relative h-6 w-full">
166
315
  {segments.map((seg, i) => {
167
- const segPixelWidth =
168
- (seg.endFraction - seg.startFraction) * pixelWidth;
169
- const segDurationLabel = formatDuration(
170
- (seg.endFraction - seg.startFraction) * totalDurationMs
171
- );
172
- const showSegmentDurationLabel =
173
- isRowHovered &&
174
- segPixelWidth >= getMinDurationLabelWidthPx(segDurationLabel);
175
- const segStyle =
176
- seg.status === 'queued' && segPixelWidth < 20
177
- ? { background: 'var(--ds-gray-500)' }
178
- : SEGMENT_CONFIG[seg.status].style;
316
+ if (seg.status === 'queued') {
317
+ const leadInLabel = formatDurationPrecise(seg.fullDurationMs);
318
+ const showLeadInLabel =
319
+ showLabels &&
320
+ seg.pixelWidth >= Math.max(40, leadInLabel.length * 6 + 12);
321
+ const isFullWidthQueued = segments.length === 1;
322
+ return (
323
+ <Fragment key={i}>
324
+ <LeadInConnector
325
+ leftPct={seg.leftPct}
326
+ widthPct={seg.widthPct}
327
+ label={showLeadInLabel ? leadInLabel : null}
328
+ />
329
+ {isFullWidthQueued ? (
330
+ <div
331
+ className="absolute top-1/2 h-4 w-px -translate-y-1/2 bg-gray-500"
332
+ style={{
333
+ left: `calc(${seg.leftPct + seg.widthPct}% - 1.5px)`,
334
+ }}
335
+ />
336
+ ) : null}
337
+ </Fragment>
338
+ );
339
+ }
340
+
341
+ const label = formatDurationPrecise(seg.fullDurationMs);
342
+ // Only render the label when there's enough room for it without clipping.
343
+ const showLabel =
344
+ showLabels && seg.pixelWidth >= Math.max(40, label.length * 6 + 12);
179
345
 
180
346
  return (
181
347
  <div
182
- key={`${seg.status}-${i}`}
348
+ key={i}
183
349
  className={cn(
184
- 'absolute h-full rounded-[0.25rem]',
185
- SEGMENT_CONFIG[seg.status].className
350
+ 'absolute h-full overflow-hidden rounded-[0.25rem]',
351
+ SEGMENT_CLASSES[seg.status]
186
352
  )}
187
353
  style={{
188
- left: `calc(${seg.startFraction * 100}% + ${SEGMENT_GAP_PX / 2}px)`,
189
- width: `calc(${(seg.endFraction - seg.startFraction) * 100}% - ${SEGMENT_GAP_PX}px)`,
354
+ // 1px gap between adjacent segments, distributed equally.
355
+ left: `calc(${seg.leftPct}% + 0.5px)`,
356
+ width: `calc(${seg.widthPct}% - 1px)`,
190
357
  minWidth: 1,
191
- ...segStyle,
192
358
  }}
193
359
  >
194
- {showSegmentDurationLabel
195
- ? renderDurationLabel(segDurationLabel)
196
- : null}
360
+ {STRIPED_SEGMENT_STATUSES.has(seg.status) ? (
361
+ <AnimatedStripes status={seg.status} />
362
+ ) : null}
363
+ {showLabel ? <DurationLabel label={label} /> : null}
197
364
  </div>
198
365
  );
199
366
  })}
200
367
  </div>
201
- ) : (
202
- <div
203
- className="relative h-4 rounded-[0.25rem] top-[3px]"
204
- style={{
205
- width: '100%',
206
- minWidth: 4,
207
- background: fallbackColor,
208
- }}
209
- >
210
- {showBarDurationLabel ? renderDurationLabel(totalDurationLabel) : null}
211
- </div>
212
368
  );
369
+ }
370
+
371
+ // ---------------------------------------------------------------------------
372
+ // TimelineBar
373
+ // ---------------------------------------------------------------------------
374
+
375
+ const TimelineBar = memo(function TimelineBar({
376
+ span,
377
+ viewStart,
378
+ viewDuration,
379
+ containerWidth,
380
+ isSelected,
381
+ isDimmed,
382
+ onSelect,
383
+ onRevealTime,
384
+ }: {
385
+ span: Span;
386
+ viewStart: number;
387
+ viewDuration: number;
388
+ containerWidth: number;
389
+ isSelected: boolean;
390
+ isDimmed?: boolean;
391
+ onSelect: (spanId: string) => void;
392
+ onRevealTime?: (timeMs: number) => void;
393
+ }): ReactNode {
394
+ const startMs = getHighResInMs(span.startTime);
395
+ const endMs = getHighResInMs(span.endTime);
396
+ const totalDurationMs = getSpanDurationMs(span);
397
+
398
+ const geometry = useMemo(
399
+ () =>
400
+ computeBarGeometry(
401
+ startMs,
402
+ endMs,
403
+ viewStart,
404
+ viewStart + viewDuration,
405
+ containerWidth
406
+ ),
407
+ [startMs, endMs, viewStart, viewDuration, containerWidth]
408
+ );
409
+
410
+ const baseSegments = useMemo(() => computeSpanSegments(span), [span]);
411
+ const segments = useMemo(
412
+ () =>
413
+ geometry.mode.kind === 'full'
414
+ ? projectSegments(baseSegments, startMs, totalDurationMs, geometry)
415
+ : [],
416
+ [geometry, baseSegments, startMs, totalDurationMs]
417
+ );
418
+
419
+ const baseMarkers = useMemo(() => computeSpanMarkers(span), [span]);
420
+ const markers = useMemo(
421
+ () =>
422
+ geometry.mode.kind === 'full'
423
+ ? cullCollidingMarkers(
424
+ projectMarkers(
425
+ baseMarkers,
426
+ geometry.visibleStartMs,
427
+ geometry.visibleEndMs
428
+ ),
429
+ geometry.visiblePixelWidth
430
+ )
431
+ : [],
432
+ [geometry, baseMarkers]
433
+ );
434
+
435
+ // Markers that fall outside the visible window (scrolled off while zoomed in)
436
+ // — surfaced as edge indicators so they aren't silently lost.
437
+ const offscreen = useMemo<OffscreenMarkers>(
438
+ () =>
439
+ geometry.mode.kind === 'full'
440
+ ? computeOffscreenMarkers(
441
+ baseMarkers,
442
+ geometry.visibleStartMs,
443
+ geometry.visibleEndMs
444
+ )
445
+ : { left: null, right: null },
446
+ [geometry, baseMarkers]
447
+ );
448
+
449
+ // Markers (visible or off-screen) move the duration label into the overlay,
450
+ // so the in-bar segment label is suppressed.
451
+ const hasMarkers =
452
+ markers.length > 0 || offscreen.left !== null || offscreen.right !== null;
453
+
454
+ const isErrored = isSpanErrored(span);
455
+ const colors = getResourceColor(span.resource);
456
+ const fallbackBg = isErrored
457
+ ? (colors.errorBg ?? 'var(--ds-red-200)')
458
+ : colors.bg;
459
+ const fallbackBorder = isErrored
460
+ ? (colors.errorBorder ?? 'var(--ds-red-500)')
461
+ : colors.border;
462
+
463
+ const totalLabel = formatDurationPrecise(totalDurationMs);
464
+ const showTotalLabel =
465
+ geometry.visiblePixelWidth >= Math.max(40, totalLabel.length * 6 + 12);
466
+
467
+ const handleClick = useCallback(() => {
468
+ onSelect(span.spanId);
469
+ }, [onSelect, span.spanId]);
213
470
 
214
471
  return (
215
472
  <div
@@ -218,24 +475,65 @@ const TimelineBar = memo(function TimelineBar({
218
475
  aria-expanded={isSelected}
219
476
  aria-level={1}
220
477
  className={cn(
221
- 'h-[34px] relative flex items-center hover:bg-gray-100 aria-selected:bg-gray-100 rounded-sm aria-selected:hover:bg-gray-200'
478
+ 'group/timeline-row h-10 relative flex items-center hover:bg-gray-100 aria-selected:bg-gray-100 aria-selected:hover:bg-gray-200 transition-opacity',
479
+ isDimmed && 'opacity-35'
222
480
  )}
223
- onMouseEnter={() => setIsRowHovered(true)}
224
- onMouseLeave={() => setIsRowHovered(false)}
225
- onClick={onClick}
481
+ onClick={handleClick}
226
482
  >
227
- <div
228
- className="absolute top-1.5 h-[22px] rounded-sm"
229
- style={{
230
- left: isCompressed
231
- ? `min(${leftPct}%, calc(100% - ${FIXED_BAR_WIDTH_PX}px))`
232
- : `${leftPct}%`,
233
- width: isCompressed
234
- ? `${FIXED_BAR_WIDTH_PX}px`
235
- : `max(${widthPct}%, 4px)`,
236
- }}
237
- >
238
- {barContent}
483
+ <div className="absolute inset-y-0" style={TIMELINE_INSET_STYLE}>
484
+ <div
485
+ className="absolute top-1/2 h-6 -translate-y-1/2 overflow-hidden rounded-[0.25rem]"
486
+ style={getBarPositionStyle(geometry)}
487
+ >
488
+ {geometry.mode.kind === 'arrow' ? (
489
+ <BoundaryArrow direction={geometry.mode.direction} />
490
+ ) : geometry.mode.kind === 'tiny' ? (
491
+ <div
492
+ className="h-6 rounded-[0.25rem] border"
493
+ style={{ background: fallbackBg, borderColor: fallbackBorder }}
494
+ />
495
+ ) : segments.length > 0 ? (
496
+ <SegmentBar segments={segments} showLabels={!hasMarkers} />
497
+ ) : (
498
+ <PlainBar
499
+ bg={fallbackBg}
500
+ border={fallbackBorder}
501
+ label={showTotalLabel ? totalLabel : null}
502
+ />
503
+ )}
504
+ </div>
505
+ {/* Overlay (not clipped): ticks, off-screen indicators, then the duration label on top. */}
506
+ {hasMarkers ? (
507
+ <div
508
+ className="pointer-events-none absolute top-1/2 h-6 -translate-y-1/2"
509
+ style={getBarPositionStyle(geometry)}
510
+ >
511
+ {markers.length > 0 ? <MarkerLayer markers={markers} /> : null}
512
+ {offscreen.left ? (
513
+ <OffscreenMarkerIndicator
514
+ direction="left"
515
+ count={offscreen.left.count}
516
+ targetMs={offscreen.left.nearestMs}
517
+ onReveal={onRevealTime}
518
+ />
519
+ ) : null}
520
+ {offscreen.right ? (
521
+ <OffscreenMarkerIndicator
522
+ direction="right"
523
+ count={offscreen.right.count}
524
+ targetMs={offscreen.right.nearestMs}
525
+ onReveal={onRevealTime}
526
+ />
527
+ ) : null}
528
+ {showTotalLabel ? (
529
+ <DurationLabel
530
+ label={totalLabel}
531
+ // Shift clear of the left edge indicator so it isn't covered.
532
+ className={offscreen.left ? 'pl-10' : undefined}
533
+ />
534
+ ) : null}
535
+ </div>
536
+ ) : null}
239
537
  </div>
240
538
  </div>
241
539
  );
@@ -243,44 +541,66 @@ const TimelineBar = memo(function TimelineBar({
243
541
 
244
542
  export { TimelineBar };
245
543
 
544
+ // ---------------------------------------------------------------------------
545
+ // DeltaIndicator (Alt-key gap overlay)
546
+ // ---------------------------------------------------------------------------
547
+
548
+ const DELTA_CAP_HEIGHT_PX = 8;
549
+ // Vertical offset to sit the indicator inside the gap between row N and N+1,
550
+ // aligned with where the bar starts in the next row (rows center a 24px bar
551
+ // inside 40px, so bars start ~8px from the top of the row).
552
+ const DELTA_ROW_OFFSET_PX = 8;
553
+
554
+ const DeltaIndicator = memo(function DeltaIndicator({
555
+ leftFrac,
556
+ rightFrac,
557
+ label,
558
+ rowIndex,
559
+ }: {
560
+ leftFrac: number;
561
+ rightFrac: number;
562
+ label: string;
563
+ rowIndex: number;
564
+ }) {
565
+ const centerY = DELTA_ROW_OFFSET_PX + (rowIndex + 1) * ROW_HEIGHT_PX;
566
+
567
+ return (
568
+ <div
569
+ className="absolute pointer-events-none"
570
+ style={{
571
+ left: `${leftFrac * 100}%`,
572
+ width: `${(rightFrac - leftFrac) * 100}%`,
573
+ top: centerY - DELTA_CAP_HEIGHT_PX / 2,
574
+ height: DELTA_CAP_HEIGHT_PX,
575
+ }}
576
+ >
577
+ <div className="absolute left-0 top-0 w-px h-full bg-amber-800" />
578
+ <div className="absolute left-0 right-0 top-1/2 h-px bg-amber-800" />
579
+ <div className="absolute right-0 top-0 w-px h-full bg-amber-800" />
580
+ <span className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-label-12 leading-none whitespace-nowrap rounded-xs px-1 py-0.5 text-gray-100 bg-amber-800">
581
+ {label}
582
+ </span>
583
+ </div>
584
+ );
585
+ });
586
+
587
+ // ---------------------------------------------------------------------------
588
+ // TimelineHeader
589
+ // ---------------------------------------------------------------------------
590
+
246
591
  export function TimelineHeader({
247
- viewStart,
248
- viewDuration,
249
- rootStart,
250
- compression,
251
- isZoomed,
252
- onResetZoom,
592
+ markers,
253
593
  hoverInfo,
254
594
  }: {
255
- viewStart: number;
256
- viewDuration: number;
257
- rootStart: number;
258
- compression: TimeCompression;
259
- isZoomed: boolean;
260
- onResetZoom: () => void;
595
+ markers: TimeMarker[];
261
596
  hoverInfo?: { fraction: number; label: string } | null;
262
597
  }): ReactNode {
263
- const viewEnd = viewStart + viewDuration;
264
-
265
- const markers = useMemo(
266
- () =>
267
- compression.isCompressed
268
- ? computeCompressedTimeMarkers(
269
- compression,
270
- viewStart,
271
- viewEnd,
272
- rootStart
273
- )
274
- : computeTimeMarkers(viewDuration, viewStart - rootStart),
275
- [compression, viewStart, viewEnd, viewDuration, rootStart]
276
- );
277
-
278
598
  return (
279
- <div className="relative bg-background-100 border-b border-gray-alpha-400 h-10 min-h-10 flex items-end px-2 pb-1">
599
+ <div className="relative bg-background-100 border-b border-gray-alpha-400 h-10 min-h-10 flex items-end px-4 pb-1">
280
600
  <div className="relative h-full flex-1">
281
- {markers.map((m, i) => (
601
+ {markers.map((m) => (
282
602
  <span
283
- key={i}
603
+ key={String(m.value)}
284
604
  className="absolute bottom-1 font-mono text-xs font-normal leading-4 text-gray-900 whitespace-nowrap"
285
605
  style={{ left: `${m.position * 100}%` }}
286
606
  >
@@ -296,36 +616,46 @@ export function TimelineHeader({
296
616
  </span>
297
617
  )}
298
618
  </div>
299
- {isZoomed && (
300
- <button
301
- type="button"
302
- className="absolute right-2 top-1/2 -translate-y-1/2 z-[5] flex items-center py-0.5 px-2 border border-gray-alpha-400 rounded-md bg-background-100 font-sans text-[11px] font-medium text-gray-900 cursor-pointer whitespace-nowrap transition-[color,border-color] duration-[120ms] ease-in-out hover:text-gray-1000 hover:border-gray-600"
303
- onClick={onResetZoom}
304
- >
305
- Reset zoom
306
- </button>
307
- )}
308
619
  </div>
309
620
  );
310
621
  }
311
622
 
623
+ // ---------------------------------------------------------------------------
624
+ // Timeline
625
+ // ---------------------------------------------------------------------------
626
+
312
627
  export function Timeline({
313
628
  spans,
314
- compression,
629
+ viewStart,
630
+ viewEnd,
631
+ markers,
315
632
  selectedId,
633
+ searchResult,
316
634
  onSelect,
635
+ onRevealTime,
317
636
  hoverFraction,
318
637
  altHeld = false,
319
638
  }: {
320
639
  spans: Span[];
321
- compression: TimeCompression;
640
+ viewStart: number;
641
+ viewEnd: number;
642
+ markers: TimeMarker[];
322
643
  selectedId: string | null;
644
+ searchResult: SpanSearchResult;
323
645
  onSelect: (spanId: string) => void;
646
+ onRevealTime?: (timeMs: number) => void;
324
647
  hoverFraction?: number | null;
325
648
  altHeld?: boolean;
326
649
  }): ReactNode {
327
650
  const containerRef = useRef<HTMLDivElement>(null);
328
651
  const [containerWidth, setContainerWidth] = useState(0);
652
+ const viewDuration = viewEnd - viewStart;
653
+ const timelineWidth = Math.max(0, containerWidth - TIMELINE_PADDING_PX * 2);
654
+ const { start, end } = useRowWindow(
655
+ containerRef,
656
+ spans.length,
657
+ ROW_HEIGHT_PX
658
+ );
329
659
 
330
660
  useEffect(() => {
331
661
  const el = containerRef.current;
@@ -338,36 +668,70 @@ export function Timeline({
338
668
  }, []);
339
669
 
340
670
  const gaps = useMemo(
341
- () => computeSpanGaps(spans, compression),
342
- [spans, compression]
671
+ () => computeSpanGaps(spans, viewStart, viewEnd),
672
+ [spans, viewStart, viewEnd]
343
673
  );
344
674
 
345
675
  return (
346
- <div ref={containerRef} className="relative py-2">
676
+ <div
677
+ ref={containerRef}
678
+ className="relative h-full overflow-hidden"
679
+ style={{ minHeight: spans.length * ROW_HEIGHT_PX }}
680
+ >
681
+ <div
682
+ aria-hidden
683
+ className="absolute inset-y-0 pointer-events-none"
684
+ style={TIMELINE_INSET_STYLE}
685
+ >
686
+ {markers.map((marker) =>
687
+ // Skip the "0s" origin marker since the left edge already implies it.
688
+ Math.abs(marker.value) > 0.000001 ? (
689
+ <div
690
+ key={String(marker.value)}
691
+ className="absolute top-0 bottom-0 w-px bg-gray-alpha-300"
692
+ style={{ left: `${marker.position * 100}%` }}
693
+ />
694
+ ) : null
695
+ )}
696
+ </div>
347
697
  {hoverFraction != null && (
348
698
  <div
349
- className="absolute top-0 bottom-0 w-px bg-gray-alpha-400 pointer-events-none z-10"
350
- style={{ left: `${hoverFraction * 100}%` }}
351
- />
699
+ className="absolute inset-y-0 pointer-events-none z-10"
700
+ style={TIMELINE_INSET_STYLE}
701
+ >
702
+ <div
703
+ className="absolute top-0 bottom-0 w-px bg-gray-alpha-500"
704
+ style={{ left: `${hoverFraction * 100}%` }}
705
+ />
706
+ </div>
352
707
  )}
353
- {spans.map((span) => (
354
- <TimelineBar
355
- key={span.spanId}
356
- span={span}
357
- compression={compression}
358
- containerWidth={containerWidth}
359
- isSelected={selectedId === span.spanId}
360
- onClick={() => onSelect(span.spanId)}
361
- />
362
- ))}
708
+ <div style={{ transform: `translateY(${start * ROW_HEIGHT_PX}px)` }}>
709
+ {spans.slice(start, end).map((span) => (
710
+ <TimelineBar
711
+ key={span.spanId}
712
+ span={span}
713
+ viewStart={viewStart}
714
+ viewDuration={viewDuration}
715
+ containerWidth={timelineWidth}
716
+ isSelected={selectedId === span.spanId}
717
+ isDimmed={isSpanDimmedBySearch(span.spanId, searchResult)}
718
+ onSelect={onSelect}
719
+ onRevealTime={onRevealTime}
720
+ />
721
+ ))}
722
+ </div>
363
723
  {altHeld && (
364
- <div aria-hidden className="absolute inset-0 pointer-events-none">
724
+ <div
725
+ aria-hidden
726
+ className="absolute inset-y-0 pointer-events-none"
727
+ style={TIMELINE_INSET_STYLE}
728
+ >
365
729
  {gaps.map((gap) => (
366
730
  <DeltaIndicator
367
731
  key={gap.rowIndex}
368
732
  leftFrac={gap.leftFrac}
369
733
  rightFrac={gap.rightFrac}
370
- label={formatDuration(gap.gapMs, true)}
734
+ label={formatDurationPrecise(gap.gapMs)}
371
735
  rowIndex={gap.rowIndex}
372
736
  />
373
737
  ))}