@workflow/web-shared 5.0.0-beta.4 → 5.0.0-beta.40

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 (517) hide show
  1. package/README.md +5 -13
  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 +235 -97
  6. package/dist/components/hook-actions.js +1 -1
  7. package/dist/components/index.d.ts +8 -5
  8. package/dist/components/index.d.ts.map +1 -1
  9. package/dist/components/index.js +6 -4
  10. package/dist/components/sidebar/attribute-panel.d.ts +4 -1
  11. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  12. package/dist/components/sidebar/attribute-panel.js +173 -119
  13. package/dist/components/sidebar/attributes-block.d.ts +40 -0
  14. package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
  15. package/dist/components/sidebar/attributes-block.js +140 -0
  16. package/dist/components/sidebar/conversation-view.js +1 -1
  17. package/dist/components/sidebar/copyable-data-block.d.ts +2 -0
  18. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  19. package/dist/components/sidebar/copyable-data-block.js +17 -16
  20. package/dist/components/sidebar/entity-detail-panel.d.ts +11 -11
  21. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  22. package/dist/components/sidebar/entity-detail-panel.js +37 -71
  23. package/dist/components/sidebar/events-list.d.ts +6 -2
  24. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  25. package/dist/components/sidebar/events-list.js +77 -63
  26. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  27. package/dist/components/sidebar/sidebar-data-context.d.ts +10 -7
  28. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
  29. package/dist/components/sidebar/sidebar-data-context.js +1 -1
  30. package/dist/components/sidebar/span-detail-merge.d.ts +31 -0
  31. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  32. package/dist/components/sidebar/span-detail-merge.js +92 -0
  33. package/dist/components/sidebar/use-selected-span-detail.d.ts +16 -0
  34. package/dist/components/sidebar/use-selected-span-detail.d.ts.map +1 -0
  35. package/dist/components/sidebar/use-selected-span-detail.js +93 -0
  36. package/dist/components/stream-viewer-skeleton.d.ts +2 -0
  37. package/dist/components/stream-viewer-skeleton.d.ts.map +1 -0
  38. package/dist/components/stream-viewer-skeleton.js +12 -0
  39. package/dist/components/stream-viewer.d.ts +1 -1
  40. package/dist/components/stream-viewer.d.ts.map +1 -1
  41. package/dist/components/stream-viewer.js +9 -26
  42. package/dist/components/trace-viewer/components/copy-button.d.ts.map +1 -0
  43. package/dist/components/trace-viewer/components/copy-button.js +27 -0
  44. package/dist/components/trace-viewer/components/detail-panel-width.d.ts +34 -0
  45. package/dist/components/trace-viewer/components/detail-panel-width.d.ts.map +1 -0
  46. package/dist/components/trace-viewer/components/detail-panel-width.js +67 -0
  47. package/dist/components/trace-viewer/components/detail-panel.d.ts +14 -0
  48. package/dist/components/trace-viewer/components/detail-panel.d.ts.map +1 -0
  49. package/dist/components/trace-viewer/components/detail-panel.js +122 -0
  50. package/dist/components/trace-viewer/components/draggable-border.d.ts +31 -0
  51. package/dist/components/trace-viewer/components/draggable-border.d.ts.map +1 -0
  52. package/dist/components/trace-viewer/components/draggable-border.js +140 -0
  53. package/dist/components/trace-viewer/components/event-list.d.ts +10 -0
  54. package/dist/components/trace-viewer/components/event-list.d.ts.map +1 -0
  55. package/dist/components/trace-viewer/components/event-list.js +47 -0
  56. package/dist/components/trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  57. package/dist/components/trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  58. package/dist/components/trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  59. package/dist/components/trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  60. package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/truncate.d.ts +1 -1
  61. package/dist/components/trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  62. package/dist/components/trace-viewer/components/middle-truncate/truncate.js +101 -0
  63. package/dist/components/trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  64. package/dist/components/trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  65. package/dist/components/trace-viewer/components/minimap.d.ts +14 -0
  66. package/dist/components/trace-viewer/components/minimap.d.ts.map +1 -0
  67. package/dist/components/trace-viewer/components/minimap.js +337 -0
  68. package/dist/components/trace-viewer/components/pane-constants.d.ts +14 -0
  69. package/dist/components/trace-viewer/components/pane-constants.d.ts.map +1 -0
  70. package/dist/components/trace-viewer/components/pane-constants.js +16 -0
  71. package/dist/components/trace-viewer/components/span-markers.d.ts +46 -0
  72. package/dist/components/trace-viewer/components/span-markers.d.ts.map +1 -0
  73. package/dist/components/trace-viewer/components/span-markers.js +85 -0
  74. package/dist/components/trace-viewer/components/split-pane.d.ts +10 -0
  75. package/dist/components/trace-viewer/components/split-pane.d.ts.map +1 -0
  76. package/dist/components/trace-viewer/components/split-pane.js +41 -0
  77. package/dist/components/trace-viewer/components/timeline.d.ts +42 -0
  78. package/dist/components/trace-viewer/components/timeline.d.ts.map +1 -0
  79. package/dist/components/trace-viewer/components/timeline.js +321 -0
  80. package/dist/components/trace-viewer/components/timeline.module.css +46 -0
  81. package/dist/components/trace-viewer/components/trace-shortcut-helper.d.ts +5 -0
  82. package/dist/components/trace-viewer/components/trace-shortcut-helper.d.ts.map +1 -0
  83. package/dist/components/trace-viewer/components/trace-shortcut-helper.js +55 -0
  84. package/dist/components/trace-viewer/components/trace-shortcut-helper.module.css +20 -0
  85. package/dist/components/trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
  86. package/dist/components/trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
  87. package/dist/components/trace-viewer/components/trace-viewer-skeleton.js +26 -0
  88. package/dist/components/trace-viewer/components/use-element-width.d.ts +8 -0
  89. package/dist/components/trace-viewer/components/use-element-width.d.ts.map +1 -0
  90. package/dist/components/trace-viewer/components/use-element-width.js +25 -0
  91. package/dist/components/trace-viewer/components/use-row-window.d.ts +34 -0
  92. package/dist/components/trace-viewer/components/use-row-window.d.ts.map +1 -0
  93. package/dist/components/trace-viewer/components/use-row-window.js +107 -0
  94. package/dist/components/trace-viewer/context.d.ts +14 -182
  95. package/dist/components/trace-viewer/context.d.ts.map +1 -1
  96. package/dist/components/trace-viewer/context.js +44 -351
  97. package/dist/components/trace-viewer/icons.d.ts.map +1 -0
  98. package/dist/components/trace-viewer/icons.js +15 -0
  99. package/dist/components/trace-viewer/search.d.ts +19 -0
  100. package/dist/components/trace-viewer/search.d.ts.map +1 -0
  101. package/dist/components/trace-viewer/search.js +134 -0
  102. package/dist/components/trace-viewer/trace-viewer.d.ts +7 -28
  103. package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
  104. package/dist/components/trace-viewer/trace-viewer.js +306 -356
  105. package/dist/components/trace-viewer/types.d.ts +2 -211
  106. package/dist/components/trace-viewer/types.d.ts.map +1 -1
  107. package/dist/components/trace-viewer/types.js +1 -1
  108. package/dist/components/trace-viewer/util/timing.d.ts +2 -2
  109. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  110. package/dist/components/trace-viewer/util/timing.js +3 -3
  111. package/dist/components/trace-viewer/utils.d.ts +111 -0
  112. package/dist/components/trace-viewer/utils.d.ts.map +1 -0
  113. package/dist/components/trace-viewer/utils.js +430 -0
  114. package/dist/components/{trace-viewer-new.d.ts → trace-viewer.d.ts} +7 -3
  115. package/dist/components/trace-viewer.d.ts.map +1 -0
  116. package/dist/components/trace-viewer.js +21 -0
  117. package/dist/components/ui/alert.js +3 -3
  118. package/dist/components/ui/button.d.ts +10 -0
  119. package/dist/components/ui/button.d.ts.map +1 -0
  120. package/dist/components/ui/button.js +46 -0
  121. package/dist/components/ui/card.d.ts.map +1 -1
  122. package/dist/components/ui/card.js +4 -4
  123. package/dist/components/ui/collapsible.d.ts +46 -0
  124. package/dist/components/ui/collapsible.d.ts.map +1 -0
  125. package/dist/components/ui/collapsible.js +66 -0
  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 +7 -6
  130. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  131. package/dist/components/ui/data-inspector.js +232 -99
  132. package/dist/components/ui/data-inspector.styles.d.ts +29 -0
  133. package/dist/components/ui/data-inspector.styles.d.ts.map +1 -0
  134. package/dist/components/ui/data-inspector.styles.js +95 -0
  135. package/dist/components/ui/decrypt-button.js +1 -1
  136. package/dist/components/ui/error-card.js +3 -3
  137. package/dist/components/ui/error-stack-block.d.ts +17 -10
  138. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  139. package/dist/components/ui/error-stack-block.js +49 -30
  140. package/dist/components/ui/icon-button.d.ts +19 -0
  141. package/dist/components/ui/icon-button.d.ts.map +1 -0
  142. package/dist/components/ui/icon-button.js +29 -0
  143. package/dist/components/ui/kbd.d.ts +10 -0
  144. package/dist/components/ui/kbd.d.ts.map +1 -0
  145. package/dist/components/ui/kbd.js +25 -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 +2 -2
  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-traces/event-colors.d.ts +2 -1
  157. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  158. package/dist/components/workflow-traces/event-colors.js +15 -7
  159. package/dist/components/workflow-traces/trace-span-construction.d.ts +6 -3
  160. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  161. package/dist/components/workflow-traces/trace-span-construction.js +71 -30
  162. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  163. package/dist/hooks/use-dark-mode.js +1 -1
  164. package/dist/hooks/use-intersection-observer.d.ts +17 -0
  165. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  166. package/dist/hooks/use-intersection-observer.js +29 -0
  167. package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
  168. package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
  169. package/dist/hooks/use-load-more-on-scroll.js +28 -0
  170. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  171. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  172. package/dist/hooks/use-reduced-motion.js +29 -0
  173. package/dist/index.d.ts +4 -3
  174. package/dist/index.d.ts.map +1 -1
  175. package/dist/index.js +4 -3
  176. package/dist/lib/cn.d.ts +10 -0
  177. package/dist/lib/cn.d.ts.map +1 -0
  178. package/dist/lib/cn.js +83 -0
  179. package/dist/lib/event-analysis.d.ts +1 -1
  180. package/dist/lib/event-analysis.d.ts.map +1 -1
  181. package/dist/lib/event-analysis.js +3 -2
  182. package/dist/lib/event-materialization.d.ts +1 -1
  183. package/dist/lib/event-materialization.d.ts.map +1 -1
  184. package/dist/lib/event-materialization.js +29 -21
  185. package/dist/lib/exact-event-search-id.d.ts +25 -0
  186. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  187. package/dist/lib/exact-event-search-id.js +39 -0
  188. package/dist/lib/hydration.d.ts +17 -2
  189. package/dist/lib/hydration.d.ts.map +1 -1
  190. package/dist/lib/hydration.js +194 -23
  191. package/dist/lib/stream-display.js +1 -1
  192. package/dist/lib/toast.js +1 -1
  193. package/dist/lib/trace-builder.d.ts +17 -5
  194. package/dist/lib/trace-builder.d.ts.map +1 -1
  195. package/dist/lib/trace-builder.js +32 -14
  196. package/dist/lib/trace-types.d.ts +54 -0
  197. package/dist/lib/trace-types.d.ts.map +1 -0
  198. package/dist/lib/trace-types.js +2 -0
  199. package/dist/lib/utils.d.ts +14 -2
  200. package/dist/lib/utils.d.ts.map +1 -1
  201. package/dist/lib/utils.js +46 -6
  202. package/dist/lib/zstd-browser-decoder.d.ts +7 -0
  203. package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
  204. package/dist/lib/zstd-browser-decoder.js +41 -0
  205. package/dist/styles.css +210 -139
  206. package/package.json +11 -8
  207. package/src/components/event-list-view.tsx +385 -123
  208. package/src/components/index.ts +14 -8
  209. package/src/components/sidebar/attribute-panel.tsx +331 -282
  210. package/src/components/sidebar/attributes-block.tsx +326 -0
  211. package/src/components/sidebar/copyable-data-block.tsx +50 -28
  212. package/src/components/sidebar/entity-detail-panel.tsx +92 -126
  213. package/src/components/sidebar/events-list.tsx +197 -179
  214. package/src/components/sidebar/sidebar-data-context.tsx +11 -11
  215. package/src/components/sidebar/span-detail-merge.ts +134 -0
  216. package/src/components/sidebar/use-selected-span-detail.ts +146 -0
  217. package/src/components/stream-viewer-skeleton.tsx +34 -0
  218. package/src/components/stream-viewer.tsx +34 -106
  219. package/src/components/{new-trace-viewer → trace-viewer}/components/copy-button.tsx +2 -2
  220. package/src/components/trace-viewer/components/detail-panel-width.test.ts +123 -0
  221. package/src/components/trace-viewer/components/detail-panel-width.ts +76 -0
  222. package/src/components/trace-viewer/components/detail-panel.tsx +273 -0
  223. package/src/components/trace-viewer/components/draggable-border.tsx +216 -0
  224. package/src/components/trace-viewer/components/event-list.tsx +144 -0
  225. package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/middle-truncate.tsx +8 -8
  226. package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/truncate.ts +1 -1
  227. package/src/components/trace-viewer/components/minimap.tsx +556 -0
  228. package/src/components/trace-viewer/components/pane-constants.ts +19 -0
  229. package/src/components/trace-viewer/components/span-markers.tsx +173 -0
  230. package/src/components/trace-viewer/components/split-pane.tsx +141 -0
  231. package/src/components/trace-viewer/components/timeline.module.css +46 -0
  232. package/src/components/trace-viewer/components/timeline.tsx +850 -0
  233. package/src/components/trace-viewer/components/trace-shortcut-helper.module.css +20 -0
  234. package/src/components/trace-viewer/components/trace-shortcut-helper.tsx +116 -0
  235. package/src/components/trace-viewer/components/trace-viewer-skeleton.tsx +114 -0
  236. package/src/components/trace-viewer/components/use-element-width.ts +29 -0
  237. package/src/components/trace-viewer/components/use-row-window.ts +140 -0
  238. package/src/components/trace-viewer/context.tsx +63 -656
  239. package/src/components/{new-trace-viewer → trace-viewer}/icons.tsx +2 -2
  240. package/src/components/trace-viewer/search.ts +178 -0
  241. package/src/components/trace-viewer/trace-viewer.tsx +563 -531
  242. package/src/components/trace-viewer/types.ts +2 -234
  243. package/src/components/trace-viewer/util/timing.ts +2 -2
  244. package/src/components/trace-viewer/utils.test.ts +458 -0
  245. package/src/components/{new-trace-viewer → trace-viewer}/utils.ts +271 -50
  246. package/src/components/{trace-viewer-new.tsx → trace-viewer.tsx} +24 -18
  247. package/src/components/ui/alert.tsx +2 -2
  248. package/src/components/ui/button.tsx +68 -0
  249. package/src/components/ui/card.tsx +3 -10
  250. package/src/components/ui/collapsible.tsx +219 -0
  251. package/src/components/ui/context-card.tsx +784 -0
  252. package/src/components/ui/data-inspector.styles.ts +96 -0
  253. package/src/components/ui/data-inspector.tsx +501 -204
  254. package/src/components/ui/error-card.tsx +4 -4
  255. package/src/components/ui/error-stack-block.tsx +84 -55
  256. package/src/components/ui/icon-button.tsx +53 -0
  257. package/src/components/ui/kbd.tsx +43 -0
  258. package/src/components/ui/skeleton.tsx +1 -1
  259. package/src/components/ui/timestamp-tooltip.tsx +132 -194
  260. package/src/components/ui/tooltip.tsx +47 -0
  261. package/src/components/workflow-traces/event-colors.ts +15 -11
  262. package/src/components/workflow-traces/trace-span-construction.test.ts +85 -0
  263. package/src/components/workflow-traces/trace-span-construction.ts +97 -32
  264. package/src/hooks/use-intersection-observer.ts +57 -0
  265. package/src/hooks/use-load-more-on-scroll.ts +49 -0
  266. package/src/hooks/use-reduced-motion.ts +34 -0
  267. package/src/index.ts +10 -4
  268. package/src/lib/cn.ts +93 -0
  269. package/src/lib/event-analysis.ts +6 -4
  270. package/src/lib/event-materialization.ts +35 -21
  271. package/src/lib/exact-event-search-id.ts +67 -0
  272. package/src/lib/hydration.ts +210 -30
  273. package/src/lib/trace-builder.ts +43 -19
  274. package/src/lib/trace-types.ts +56 -0
  275. package/src/lib/utils.test.ts +33 -0
  276. package/src/lib/utils.ts +53 -6
  277. package/src/lib/zstd-browser-decoder.ts +42 -0
  278. package/src/styles.css +210 -139
  279. package/dist/components/error-boundary.js.map +0 -1
  280. package/dist/components/event-list-view.js.map +0 -1
  281. package/dist/components/hook-actions.js.map +0 -1
  282. package/dist/components/index.js.map +0 -1
  283. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +0 -1
  284. package/dist/components/new-trace-viewer/components/copy-button.js +0 -27
  285. package/dist/components/new-trace-viewer/components/copy-button.js.map +0 -1
  286. package/dist/components/new-trace-viewer/components/event-list.d.ts +0 -8
  287. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +0 -1
  288. package/dist/components/new-trace-viewer/components/event-list.js +0 -38
  289. package/dist/components/new-trace-viewer/components/event-list.js.map +0 -1
  290. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +0 -1
  291. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +0 -54
  292. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +0 -1
  293. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +0 -1
  294. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +0 -81
  295. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +0 -1
  296. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +0 -1
  297. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +0 -101
  298. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +0 -1
  299. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +0 -1
  300. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +0 -114
  301. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +0 -1
  302. package/dist/components/new-trace-viewer/components/split-pane.d.ts +0 -14
  303. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +0 -1
  304. package/dist/components/new-trace-viewer/components/split-pane.js +0 -99
  305. package/dist/components/new-trace-viewer/components/split-pane.js.map +0 -1
  306. package/dist/components/new-trace-viewer/components/timeline.d.ts +0 -31
  307. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +0 -1
  308. package/dist/components/new-trace-viewer/components/timeline.js +0 -138
  309. package/dist/components/new-trace-viewer/components/timeline.js.map +0 -1
  310. package/dist/components/new-trace-viewer/context.d.ts +0 -15
  311. package/dist/components/new-trace-viewer/context.d.ts.map +0 -1
  312. package/dist/components/new-trace-viewer/context.js +0 -47
  313. package/dist/components/new-trace-viewer/context.js.map +0 -1
  314. package/dist/components/new-trace-viewer/detail-panel.d.ts +0 -10
  315. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +0 -1
  316. package/dist/components/new-trace-viewer/detail-panel.js +0 -12
  317. package/dist/components/new-trace-viewer/detail-panel.js.map +0 -1
  318. package/dist/components/new-trace-viewer/icons.d.ts.map +0 -1
  319. package/dist/components/new-trace-viewer/icons.js +0 -15
  320. package/dist/components/new-trace-viewer/icons.js.map +0 -1
  321. package/dist/components/new-trace-viewer/trace-viewer.d.ts +0 -8
  322. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +0 -1
  323. package/dist/components/new-trace-viewer/trace-viewer.js +0 -349
  324. package/dist/components/new-trace-viewer/trace-viewer.js.map +0 -1
  325. package/dist/components/new-trace-viewer/types.d.ts +0 -1
  326. package/dist/components/new-trace-viewer/types.d.ts.map +0 -1
  327. package/dist/components/new-trace-viewer/types.js +0 -2
  328. package/dist/components/new-trace-viewer/types.js.map +0 -1
  329. package/dist/components/new-trace-viewer/util/constants.d.ts +0 -1
  330. package/dist/components/new-trace-viewer/util/constants.d.ts.map +0 -1
  331. package/dist/components/new-trace-viewer/util/constants.js +0 -2
  332. package/dist/components/new-trace-viewer/util/constants.js.map +0 -1
  333. package/dist/components/new-trace-viewer/utils.d.ts +0 -42
  334. package/dist/components/new-trace-viewer/utils.d.ts.map +0 -1
  335. package/dist/components/new-trace-viewer/utils.js +0 -305
  336. package/dist/components/new-trace-viewer/utils.js.map +0 -1
  337. package/dist/components/run-trace-view.d.ts +0 -25
  338. package/dist/components/run-trace-view.d.ts.map +0 -1
  339. package/dist/components/run-trace-view.js +0 -11
  340. package/dist/components/run-trace-view.js.map +0 -1
  341. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  342. package/dist/components/sidebar/conversation-view.js.map +0 -1
  343. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  344. package/dist/components/sidebar/detail-card.d.ts +0 -14
  345. package/dist/components/sidebar/detail-card.d.ts.map +0 -1
  346. package/dist/components/sidebar/detail-card.js +0 -20
  347. package/dist/components/sidebar/detail-card.js.map +0 -1
  348. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  349. package/dist/components/sidebar/events-list.js.map +0 -1
  350. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  351. package/dist/components/sidebar/sidebar-data-context.js.map +0 -1
  352. package/dist/components/stream-viewer.js.map +0 -1
  353. package/dist/components/trace-viewer/components/map.d.ts +0 -8
  354. package/dist/components/trace-viewer/components/map.d.ts.map +0 -1
  355. package/dist/components/trace-viewer/components/map.js +0 -162
  356. package/dist/components/trace-viewer/components/map.js.map +0 -1
  357. package/dist/components/trace-viewer/components/markers.d.ts +0 -23
  358. package/dist/components/trace-viewer/components/markers.d.ts.map +0 -1
  359. package/dist/components/trace-viewer/components/markers.js +0 -416
  360. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  361. package/dist/components/trace-viewer/components/node.d.ts +0 -35
  362. package/dist/components/trace-viewer/components/node.d.ts.map +0 -1
  363. package/dist/components/trace-viewer/components/node.js +0 -285
  364. package/dist/components/trace-viewer/components/node.js.map +0 -1
  365. package/dist/components/trace-viewer/components/search-input.d.ts +0 -9
  366. package/dist/components/trace-viewer/components/search-input.d.ts.map +0 -1
  367. package/dist/components/trace-viewer/components/search-input.js +0 -9
  368. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  369. package/dist/components/trace-viewer/components/search.d.ts +0 -3
  370. package/dist/components/trace-viewer/components/search.d.ts.map +0 -1
  371. package/dist/components/trace-viewer/components/search.js +0 -25
  372. package/dist/components/trace-viewer/components/search.js.map +0 -1
  373. package/dist/components/trace-viewer/components/span-content.d.ts +0 -19
  374. package/dist/components/trace-viewer/components/span-content.d.ts.map +0 -1
  375. package/dist/components/trace-viewer/components/span-content.js +0 -137
  376. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  377. package/dist/components/trace-viewer/components/span-detail-panel.d.ts +0 -10
  378. package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +0 -1
  379. package/dist/components/trace-viewer/components/span-detail-panel.js +0 -285
  380. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  381. package/dist/components/trace-viewer/components/span-segments.d.ts +0 -50
  382. package/dist/components/trace-viewer/components/span-segments.d.ts.map +0 -1
  383. package/dist/components/trace-viewer/components/span-segments.js +0 -445
  384. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  385. package/dist/components/trace-viewer/components/span-strategies.d.ts +0 -46
  386. package/dist/components/trace-viewer/components/span-strategies.d.ts.map +0 -1
  387. package/dist/components/trace-viewer/components/span-strategies.js +0 -108
  388. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  389. package/dist/components/trace-viewer/components/ui.d.ts +0 -28
  390. package/dist/components/trace-viewer/components/ui.d.ts.map +0 -1
  391. package/dist/components/trace-viewer/components/ui.js +0 -39
  392. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  393. package/dist/components/trace-viewer/components/zoom-button.d.ts +0 -3
  394. package/dist/components/trace-viewer/components/zoom-button.d.ts.map +0 -1
  395. package/dist/components/trace-viewer/components/zoom-button.js +0 -29
  396. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  397. package/dist/components/trace-viewer/components/zoom-icons.d.ts +0 -11
  398. package/dist/components/trace-viewer/components/zoom-icons.d.ts.map +0 -1
  399. package/dist/components/trace-viewer/components/zoom-icons.js +0 -11
  400. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  401. package/dist/components/trace-viewer/context.js.map +0 -1
  402. package/dist/components/trace-viewer/index.d.ts +0 -5
  403. package/dist/components/trace-viewer/index.d.ts.map +0 -1
  404. package/dist/components/trace-viewer/index.js +0 -4
  405. package/dist/components/trace-viewer/index.js.map +0 -1
  406. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  407. package/dist/components/trace-viewer/trace-viewer.module.css +0 -1465
  408. package/dist/components/trace-viewer/types.js.map +0 -1
  409. package/dist/components/trace-viewer/util/constants.d.ts +0 -9
  410. package/dist/components/trace-viewer/util/constants.d.ts.map +0 -1
  411. package/dist/components/trace-viewer/util/constants.js +0 -9
  412. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  413. package/dist/components/trace-viewer/util/scrollbar-width.d.ts +0 -2
  414. package/dist/components/trace-viewer/util/scrollbar-width.d.ts.map +0 -1
  415. package/dist/components/trace-viewer/util/scrollbar-width.js +0 -14
  416. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  417. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  418. package/dist/components/trace-viewer/util/tree.d.ts +0 -13
  419. package/dist/components/trace-viewer/util/tree.d.ts.map +0 -1
  420. package/dist/components/trace-viewer/util/tree.js +0 -227
  421. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  422. package/dist/components/trace-viewer/util/use-immediate-style.d.ts +0 -15
  423. package/dist/components/trace-viewer/util/use-immediate-style.d.ts.map +0 -1
  424. package/dist/components/trace-viewer/util/use-immediate-style.js +0 -23
  425. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  426. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +0 -8
  427. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +0 -1
  428. package/dist/components/trace-viewer/util/use-streaming-spans.js +0 -345
  429. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  430. package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts +0 -6
  431. package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts.map +0 -1
  432. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +0 -39
  433. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  434. package/dist/components/trace-viewer/worker.d.ts +0 -2
  435. package/dist/components/trace-viewer/worker.d.ts.map +0 -1
  436. package/dist/components/trace-viewer/worker.js +0 -109
  437. package/dist/components/trace-viewer/worker.js.map +0 -1
  438. package/dist/components/trace-viewer-new.d.ts.map +0 -1
  439. package/dist/components/trace-viewer-new.js +0 -23
  440. package/dist/components/trace-viewer-new.js.map +0 -1
  441. package/dist/components/ui/alert.js.map +0 -1
  442. package/dist/components/ui/card.js.map +0 -1
  443. package/dist/components/ui/data-inspector.js.map +0 -1
  444. package/dist/components/ui/decrypt-button.js.map +0 -1
  445. package/dist/components/ui/error-card.js.map +0 -1
  446. package/dist/components/ui/error-stack-block.js.map +0 -1
  447. package/dist/components/ui/inspector-theme.d.ts +0 -95
  448. package/dist/components/ui/inspector-theme.d.ts.map +0 -1
  449. package/dist/components/ui/inspector-theme.js +0 -82
  450. package/dist/components/ui/inspector-theme.js.map +0 -1
  451. package/dist/components/ui/load-more-button.js.map +0 -1
  452. package/dist/components/ui/menu-dropdown.js.map +0 -1
  453. package/dist/components/ui/skeleton.js.map +0 -1
  454. package/dist/components/ui/spinner.js.map +0 -1
  455. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  456. package/dist/components/workflow-trace-view.d.ts +0 -40
  457. package/dist/components/workflow-trace-view.d.ts.map +0 -1
  458. package/dist/components/workflow-trace-view.js +0 -695
  459. package/dist/components/workflow-trace-view.js.map +0 -1
  460. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  461. package/dist/components/workflow-traces/trace-colors.d.ts +0 -15
  462. package/dist/components/workflow-traces/trace-colors.d.ts.map +0 -1
  463. package/dist/components/workflow-traces/trace-colors.js +0 -87
  464. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  465. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  466. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  467. package/dist/hooks/use-dark-mode.js.map +0 -1
  468. package/dist/index.js.map +0 -1
  469. package/dist/lib/event-analysis.js.map +0 -1
  470. package/dist/lib/event-materialization.js.map +0 -1
  471. package/dist/lib/hydration.js.map +0 -1
  472. package/dist/lib/stream-display.js.map +0 -1
  473. package/dist/lib/toast.js.map +0 -1
  474. package/dist/lib/trace-builder.js.map +0 -1
  475. package/dist/lib/utils.js.map +0 -1
  476. package/src/components/new-trace-viewer/components/event-list.tsx +0 -116
  477. package/src/components/new-trace-viewer/components/split-pane.tsx +0 -185
  478. package/src/components/new-trace-viewer/components/timeline.tsx +0 -382
  479. package/src/components/new-trace-viewer/context.tsx +0 -92
  480. package/src/components/new-trace-viewer/detail-panel.tsx +0 -58
  481. package/src/components/new-trace-viewer/trace-viewer.tsx +0 -645
  482. package/src/components/new-trace-viewer/types.ts +0 -0
  483. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  484. package/src/components/run-trace-view.tsx +0 -84
  485. package/src/components/sidebar/detail-card.tsx +0 -65
  486. package/src/components/trace-viewer/components/map.tsx +0 -226
  487. package/src/components/trace-viewer/components/markers.tsx +0 -612
  488. package/src/components/trace-viewer/components/node.tsx +0 -505
  489. package/src/components/trace-viewer/components/search-input.tsx +0 -52
  490. package/src/components/trace-viewer/components/search.tsx +0 -47
  491. package/src/components/trace-viewer/components/span-content.tsx +0 -247
  492. package/src/components/trace-viewer/components/span-detail-panel.tsx +0 -655
  493. package/src/components/trace-viewer/components/span-segments.ts +0 -585
  494. package/src/components/trace-viewer/components/span-strategies.ts +0 -205
  495. package/src/components/trace-viewer/components/ui.tsx +0 -156
  496. package/src/components/trace-viewer/components/zoom-button.tsx +0 -61
  497. package/src/components/trace-viewer/components/zoom-icons.tsx +0 -65
  498. package/src/components/trace-viewer/index.tsx +0 -4
  499. package/src/components/trace-viewer/trace-viewer.module.css +0 -1465
  500. package/src/components/trace-viewer/util/constants.ts +0 -8
  501. package/src/components/trace-viewer/util/scrollbar-width.ts +0 -13
  502. package/src/components/trace-viewer/util/tree.ts +0 -277
  503. package/src/components/trace-viewer/util/use-immediate-style.ts +0 -38
  504. package/src/components/trace-viewer/util/use-streaming-spans.ts +0 -426
  505. package/src/components/trace-viewer/util/use-trackpad-zoom.tsx +0 -51
  506. package/src/components/trace-viewer/worker.ts +0 -131
  507. package/src/components/ui/inspector-theme.ts +0 -110
  508. package/src/components/workflow-trace-view.tsx +0 -1171
  509. package/src/components/workflow-traces/trace-colors.ts +0 -112
  510. /package/dist/components/{new-trace-viewer → trace-viewer}/components/copy-button.d.ts +0 -0
  511. /package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/copy-selection.d.ts +0 -0
  512. /package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/middle-truncate.d.ts +0 -0
  513. /package/dist/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/use-middle-truncate.d.ts +0 -0
  514. /package/dist/components/{new-trace-viewer → trace-viewer}/icons.d.ts +0 -0
  515. /package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/copy-selection.ts +0 -0
  516. /package/src/components/{new-trace-viewer → trace-viewer}/components/middle-truncate/use-middle-truncate.ts +0 -0
  517. /package/src/{components/trace-viewer/modules.d.ts → modules.d.ts} +0 -0
@@ -1,597 +1,629 @@
1
1
  'use client';
2
2
 
3
- import { clsx } from 'clsx';
4
- import type { CSSProperties, MouseEventHandler, ReactNode } from 'react';
5
- import { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
6
- import { MiniMap } from './components/map';
7
- import { CursorMarker, EventMarkers, Markers } from './components/markers';
8
- import { SpanNodes } from './components/node';
9
- import { SearchBar } from './components/search';
10
- import { SpanDetailPanel } from './components/span-detail-panel';
11
- import { ZoomButton } from './components/zoom-button';
12
- import { TraceViewerContextProvider, useTraceViewer } from './context';
13
- import styles from './trace-viewer.module.css';
14
- import type { GetQuickLinks, Trace } from './types';
3
+ import { RotateCcw, Search, ZoomIn, ZoomOut } from 'lucide-react';
15
4
  import {
16
- MAP_HEIGHT,
17
- MARKER_HEIGHT,
18
- MARKER_NOTCH_HEIGHT,
19
- ROW_HEIGHT,
20
- ROW_PADDING,
21
- SEARCH_GAP,
22
- SEARCH_HEIGHT,
23
- TIMELINE_PADDING,
24
- } from './util/constants';
25
- import { parseTrace } from './util/tree';
26
- import { useStreamingSpans } from './util/use-streaming-spans';
5
+ type ReactNode,
6
+ useCallback,
7
+ useDeferredValue,
8
+ useEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
13
+ import { useLoadMoreOnScroll } from '../../hooks/use-load-more-on-scroll';
14
+ import { useReducedMotion } from '../../hooks/use-reduced-motion';
15
+ import { IconButton } from '../ui/icon-button';
16
+ import { Kbd } from '../ui/kbd';
17
+ import { Spinner } from '../ui/spinner';
18
+ import { TooltipProvider } from '../ui/tooltip';
19
+ import { TraceDetailPanel } from './components/detail-panel';
20
+ import EventList from './components/event-list';
21
+ import { Minimap } from './components/minimap';
22
+ import { SplitPane } from './components/split-pane';
23
+ import {
24
+ TIMELINE_PADDING_PX,
25
+ Timeline,
26
+ TimelineHeader,
27
+ type TimelineHover,
28
+ } from './components/timeline';
29
+ import { TraceShortcutHelper } from './components/trace-shortcut-helper';
30
+ import { ROW_HEIGHT_PX, scrollRowIntoView } from './components/use-row-window';
31
+ import { ActiveSpanProvider, useActiveSpan } from './context';
32
+ import { searchSpans } from './search';
33
+ import type { TraceWithMeta } from './types';
34
+ import { formatDurationPrecise, getHighResInMs } from './util/timing';
35
+ import {
36
+ clampViewportToRoot,
37
+ computeRootBounds,
38
+ computeTimeMarkers,
39
+ type ViewportRange,
40
+ wheelDeltaToPixels,
41
+ wheelZoomScaleFactor,
42
+ } from './utils';
27
43
 
28
44
  interface TraceViewerProps {
29
- trace?: Trace;
30
- className?: string;
31
- scrollLock?: boolean;
32
- height?: string | number;
33
- withPanel?: boolean;
34
- getQuickLinks?: GetQuickLinks;
35
- highlightedSpans?: string[];
36
- /** Render all spans immediately (no progressive streaming). */
37
- eagerRender?: boolean;
38
- /** Whether the trace is live (for continuous tick updates). */
39
- isLive?: boolean;
45
+ trace: TraceWithMeta;
46
+ onLoadMore?: () => void | Promise<void>;
47
+ hasMore?: boolean;
48
+ isLoadingMore?: boolean;
40
49
  }
41
50
 
42
- export function TraceViewerProvider({
43
- getQuickLinks,
44
- children,
45
- }: Pick<TraceViewerProps, 'getQuickLinks'> & {
46
- children: ReactNode;
47
- }): ReactNode {
48
- return (
49
- <TraceViewerContextProvider getQuickLinks={getQuickLinks}>
50
- {children}
51
- </TraceViewerContextProvider>
51
+ const MIN_VIEWPORT_MS = 0.001;
52
+
53
+ const ZOOM_DEBOUNCE_MS = 150;
54
+
55
+ function useAnimatedViewport(initial: ViewportRange) {
56
+ const [viewport, setViewportState] = useState<ViewportRange>(initial);
57
+ const animRef = useRef<{
58
+ raf: number;
59
+ from: ViewportRange;
60
+ to: ViewportRange;
61
+ start: number;
62
+ } | null>(null);
63
+ const currentRef = useRef(initial);
64
+ currentRef.current = viewport;
65
+ const reducedMotion = useReducedMotion();
66
+
67
+ const cancel = useCallback(() => {
68
+ if (animRef.current) {
69
+ cancelAnimationFrame(animRef.current.raf);
70
+ animRef.current = null;
71
+ }
72
+ }, []);
73
+
74
+ const animateTo = useCallback(
75
+ (target: ViewportRange) => {
76
+ cancel();
77
+
78
+ if (reducedMotion) {
79
+ currentRef.current = target;
80
+ setViewportState(target);
81
+ return;
82
+ }
83
+
84
+ const from = currentRef.current;
85
+ const anim = { raf: 0, from, to: target, start: performance.now() };
86
+
87
+ const tick = () => {
88
+ const t = Math.min((performance.now() - anim.start) / 240, 1);
89
+ const e = t < 0.5 ? 8 * t * t * t * t : 1 - (-2 * t + 2) ** 4 / 2;
90
+ setViewportState({
91
+ start: anim.from.start + (anim.to.start - anim.from.start) * e,
92
+ end: anim.from.end + (anim.to.end - anim.from.end) * e,
93
+ });
94
+ if (t < 1) anim.raf = requestAnimationFrame(tick);
95
+ else animRef.current = null;
96
+ };
97
+
98
+ animRef.current = anim;
99
+ anim.raf = requestAnimationFrame(tick);
100
+ },
101
+ [cancel, reducedMotion]
102
+ );
103
+
104
+ const setViewport = useCallback(
105
+ (update: ViewportRange | ((prev: ViewportRange) => ViewportRange)) => {
106
+ cancel();
107
+ if (typeof update === 'function') {
108
+ setViewportState((prev) => {
109
+ const next = update(prev);
110
+ currentRef.current = next;
111
+ return next;
112
+ });
113
+ } else {
114
+ currentRef.current = update;
115
+ setViewportState(update);
116
+ }
117
+ },
118
+ [cancel]
52
119
  );
120
+
121
+ useEffect(() => cancel, [cancel]);
122
+
123
+ return { viewport, setViewport, animateTo };
53
124
  }
54
125
 
55
- interface LastClickRef {
56
- x: number;
57
- y: number;
58
- t: number;
59
- spanId: string;
126
+ // ---------------------------------------------------------------------------
127
+ // Root component
128
+ // ---------------------------------------------------------------------------
129
+
130
+ export function TraceViewer({
131
+ trace,
132
+ onLoadMore,
133
+ hasMore,
134
+ isLoadingMore,
135
+ }: TraceViewerProps): ReactNode {
136
+ return (
137
+ <TooltipProvider delayDuration={300}>
138
+ <ActiveSpanProvider spans={trace.spans}>
139
+ <TraceViewerContent
140
+ trace={trace}
141
+ onLoadMore={onLoadMore}
142
+ hasMore={hasMore}
143
+ isLoadingMore={isLoadingMore}
144
+ />
145
+ </ActiveSpanProvider>
146
+ </TooltipProvider>
147
+ );
60
148
  }
61
149
 
62
- const skeletonTrace: Trace = {
63
- traceId: 'skeleton',
64
- spans: [
65
- {
66
- parentSpanId: '',
67
- spanId: 'root',
68
- name: 'root span',
69
- kind: 1,
70
- resource: 'vercel.runtime',
71
- startTime: [5000, 0],
72
- endTime: [6000, 0],
73
- duration: [1000, 0],
74
- library: {
75
- name: 'vercel-site',
76
- },
77
- status: {
78
- code: 1,
79
- },
80
- attributes: {
81
- 'vercel.ownerId': 'team_abc',
82
- },
83
- traceFlags: 1,
84
- events: [],
85
- links: [],
86
- },
87
- ],
88
- resources: [
89
- {
90
- name: 'vercel.runtime',
91
- attributes: {},
92
- },
93
- ],
94
- rootSpanId: 'root',
95
- };
96
-
97
- export function TraceViewerTimeline({
98
- trace = skeletonTrace,
99
- className = '',
100
- scrollLock = false,
101
- height,
102
- withPanel = false,
103
- highlightedSpans,
104
- eagerRender = false,
105
- isLive = false,
106
- footer,
107
- knownDurationMs,
108
- hasMoreData = false,
109
- }: Omit<TraceViewerProps, 'getQuickLinks'> & {
110
- footer?: ReactNode;
111
- /** Duration in ms from trace start to the latest known event. Used to render the unknown-time overlay. */
112
- knownDurationMs?: number;
113
- /** Whether more data pages are expected. Controls the unknown-data overlay visibility. */
114
- hasMoreData?: boolean;
115
- }): ReactNode {
116
- const isSkeleton = trace === skeletonTrace;
117
- const { state, dispatch } = useTraceViewer();
118
- const { timelineRef, scrollSnapshotRef } = state;
119
- const memoCache = state.memoCacheRef.current;
120
- const hideSearchBar = (highlightedSpans?.length ?? 0) > 0;
121
-
122
- const hasInitializedRef = useRef(false);
123
- const prevSpanKeyRef = useRef('');
124
- const prevRootRef = useRef<ReturnType<typeof parseTrace>['root'] | null>(
125
- null
150
+ function TraceViewerContent({
151
+ trace,
152
+ onLoadMore,
153
+ hasMore,
154
+ isLoadingMore,
155
+ }: TraceViewerProps): ReactNode {
156
+ const { activeSpanId, setActiveSpan, clearActiveSpan } = useActiveSpan();
157
+
158
+ const reducedMotion = useReducedMotion();
159
+
160
+ const [searchQuery, setSearchQuery] = useState('');
161
+ const deferredSearchQuery = useDeferredValue(searchQuery);
162
+
163
+ const paneRootRef = useRef<HTMLDivElement>(null);
164
+
165
+ const searchResult = useMemo(
166
+ () => searchSpans(trace.spans, deferredSearchQuery),
167
+ [trace.spans, deferredSearchQuery]
126
168
  );
127
- const prevSpanMapRef = useRef<ReturnType<typeof parseTrace>['map']>({});
128
- const prevSizeRef = useRef<{ width: number; height: number } | null>(null);
169
+
170
+ const root = useMemo(() => computeRootBounds(trace.spans), [trace.spans]);
171
+
172
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
173
+ const loadMore = useCallback(() => {
174
+ void onLoadMore?.();
175
+ }, [onLoadMore]);
176
+ const loadMoreSentinelRef = useLoadMoreOnScroll(loadMore, {
177
+ hasMore: Boolean(onLoadMore && hasMore),
178
+ isLoadingMore: Boolean(isLoadingMore),
179
+ rootRef: scrollContainerRef,
180
+ });
181
+
182
+ const { viewport, setViewport, animateTo } = useAnimatedViewport({
183
+ start: root.startTime,
184
+ end: root.startTime + root.duration,
185
+ });
186
+
187
+ const prevRootRef = useRef<ViewportRange>({
188
+ start: root.startTime,
189
+ end: root.startTime + root.duration,
190
+ });
129
191
 
130
192
  useEffect(() => {
131
- const { root: newRoot, map: newSpanMap } = parseTrace(trace);
132
- const isInitial = !hasInitializedRef.current;
133
- hasInitializedRef.current = true;
134
-
135
- // Build a structural key from span IDs + lightweight event signatures.
136
- // When only timing changes (same spans and boundary events), we can skip the
137
- // worker restart. When events change (step completed, etc.) we need a
138
- // full update because the VisibleSpan objects rendered by React hold
139
- // copies of the events from the worker — in-place mutation won't reach them.
140
- const spanKey = Object.keys(newSpanMap)
141
- .sort()
142
- .map((id) => {
143
- const events = newSpanMap[id].events;
144
- const count = events?.length ?? 0;
145
- const first = count ? events?.[0] : undefined;
146
- const last = count ? events?.[count - 1] : undefined;
147
- const eventSignature = `${first?.event.name ?? 'none'}@${first?.timestamp ?? 0}|${last?.event.name ?? 'none'}@${last?.timestamp ?? 0}`;
148
- return `${id}:${count}:${eventSignature}`;
149
- })
150
- .join(',');
151
-
152
- if (
153
- !isInitial &&
154
- spanKey === prevSpanKeyRef.current &&
155
- prevRootRef.current
156
- ) {
157
- // Same span set, same event counts — only timing changed.
158
- // Mutate in-place so the worker effect (which depends on root/spanMap
159
- // references) does NOT restart.
160
- const oldRoot = prevRootRef.current;
161
- const oldSpanMap = prevSpanMapRef.current;
162
- let didChange = false;
163
-
164
- if (
165
- oldRoot.endTime !== newRoot.endTime ||
166
- oldRoot.duration !== newRoot.duration
167
- ) {
168
- oldRoot.endTime = newRoot.endTime;
169
- oldRoot.duration = newRoot.duration;
170
- didChange = true;
171
- }
193
+ const prevRoot = prevRootRef.current;
194
+ const newStart = root.startTime;
195
+ const newEnd = root.startTime + root.duration;
172
196
 
173
- for (const [id, newNode] of Object.entries(newSpanMap)) {
174
- const oldNode = oldSpanMap[id];
175
- if (oldNode) {
176
- if (
177
- oldNode.endTime !== newNode.endTime ||
178
- oldNode.duration !== newNode.duration
179
- ) {
180
- oldNode.endTime = newNode.endTime;
181
- oldNode.duration = newNode.duration;
182
- didChange = true;
183
- }
184
- Object.assign(oldNode.span, newNode.span);
185
- }
186
- }
197
+ setViewport((prev) => {
198
+ const wasAtFullExtent =
199
+ Math.abs(prev.start - prevRoot.start) < 0.01 &&
200
+ Math.abs(prev.end - prevRoot.end) < 0.01;
187
201
 
188
- if (!didChange) {
189
- return;
202
+ if (wasAtFullExtent) {
203
+ return { start: newStart, end: newEnd };
190
204
  }
205
+ return prev;
206
+ });
191
207
 
192
- // Trigger re-render (scale/markers) without restarting the worker.
193
- // useLiveTick handles continuous scale recalculation for live runs.
194
- dispatch({ type: 'forceRender' });
195
- return;
196
- }
208
+ prevRootRef.current = { start: newStart, end: newEnd };
209
+ }, [root.startTime, root.duration, setViewport]);
197
210
 
198
- // Structure changed or initial load — full dispatch (worker computes new rows)
199
- prevSpanKeyRef.current = spanKey;
200
- prevRootRef.current = newRoot;
201
- prevSpanMapRef.current = newSpanMap;
202
- dispatch({
203
- type: isInitial ? 'setRoot' : 'updateRoot',
204
- root: newRoot,
205
- spanMap: newSpanMap,
206
- resources: trace.resources || [],
207
- });
208
- }, [dispatch, trace]);
211
+ const viewDuration = viewport.end - viewport.start;
212
+
213
+ // Keep a ref to the live viewport so zoom callbacks can read the current
214
+ // range without being recreated on every pan (which would bust TimelineBar's
215
+ // memo and re-render every row each animation frame).
216
+ const viewportRef = useRef(viewport);
217
+ viewportRef.current = viewport;
209
218
 
210
- const { rows, spans, events, scale } = useStreamingSpans(
211
- highlightedSpans,
212
- eagerRender
219
+ const timeMarkers = useMemo(
220
+ () => computeTimeMarkers(viewDuration, viewport.start - root.startTime),
221
+ [viewDuration, viewport.start, root.startTime]
213
222
  );
214
223
 
215
- const ref = useRef<HTMLDivElement>(null);
216
- useLayoutEffect(() => {
217
- const $el = ref.current;
218
- if (!$el) return;
219
-
220
- const onResize = (): void => {
221
- const padding = 2 * TIMELINE_PADDING;
222
- const rect = $el.getBoundingClientRect();
223
- const nextWidth = rect.width - padding;
224
- const nextHeight = rect.height;
225
- const prevSize = prevSizeRef.current;
226
-
227
- if (
228
- prevSize &&
229
- prevSize.width === nextWidth &&
230
- prevSize.height === nextHeight
231
- ) {
232
- return;
233
- }
234
- prevSizeRef.current = { width: nextWidth, height: nextHeight };
224
+ const resetZoom = useCallback(() => {
225
+ animateTo({ start: root.startTime, end: root.startTime + root.duration });
226
+ }, [animateTo, root.startTime, root.duration]);
235
227
 
236
- dispatch({
237
- type: 'setSize',
238
- width: nextWidth,
239
- height: nextHeight,
240
- });
241
- };
228
+ const clampToRoot = useCallback(
229
+ (next: ViewportRange): ViewportRange =>
230
+ clampViewportToRoot(next, root.startTime, root.endTime, MIN_VIEWPORT_MS),
231
+ [root.startTime, root.endTime]
232
+ );
242
233
 
243
- onResize();
234
+ // Pan (keeping the current zoom) so `timeMs` is centered in view — used by the
235
+ // off-screen marker indicators to scroll their marker into view.
236
+ const handleRevealTime = useCallback(
237
+ (timeMs: number) => {
238
+ const { start, end } = viewportRef.current;
239
+ const duration = end - start;
240
+ animateTo(
241
+ clampToRoot({
242
+ start: timeMs - duration / 2,
243
+ end: timeMs + duration / 2,
244
+ })
245
+ );
246
+ },
247
+ [animateTo, clampToRoot]
248
+ );
244
249
 
245
- const observer = new ResizeObserver(onResize);
246
- observer.observe($el);
247
- window.addEventListener('resize', onResize);
250
+ const ZOOM_FACTOR = 0.5;
251
+
252
+ const zoomBy = useCallback(
253
+ (factor: number) => {
254
+ const { start, end } = viewportRef.current;
255
+ const center = (start + end) / 2;
256
+ const newDuration = Math.max(MIN_VIEWPORT_MS, (end - start) * factor);
257
+ animateTo(
258
+ clampToRoot({
259
+ start: center - newDuration / 2,
260
+ end: center + newDuration / 2,
261
+ })
262
+ );
263
+ },
264
+ [animateTo, clampToRoot]
265
+ );
248
266
 
249
- return () => {
250
- observer.disconnect();
251
- window.removeEventListener('resize', onResize);
252
- };
253
- }, [dispatch]);
267
+ const zoomIn = useCallback(() => zoomBy(ZOOM_FACTOR), [zoomBy]);
268
+ const zoomOut = useCallback(() => zoomBy(1 / ZOOM_FACTOR), [zoomBy]);
269
+ const isAtMinZoom = viewDuration >= root.duration;
270
+ const isAtMaxZoom = viewDuration <= MIN_VIEWPORT_MS;
254
271
 
255
- const lastClickRef = useRef<LastClickRef>({
256
- t: 0,
257
- x: -1,
258
- y: -1,
259
- spanId: '',
260
- });
261
- const onClick: MouseEventHandler = useCallback(
262
- (event) => {
263
- // NOTE(wits): We manually implement double-click logic here so that we can support double
264
- // clicking a span even if the first click moves the span that the user is clicking on due
265
- // to the panel opening. If we used a regular double click listener we would need to delay
266
- // the opening of the panel artificially. This implementation allows us to always be
267
- // FAST while also avoiding a FRUSTRATING situation.
268
- const prev = lastClickRef.current;
269
- const t = Date.now();
270
- const { clientX: x, clientY: y } = event;
271
- const d = Math.sqrt((x - prev.x) ** 2 + (y - prev.y) ** 2);
272
- // double click
273
- if (t - prev.t <= 500 && d <= 8) {
274
- event.stopPropagation();
275
- event.preventDefault();
276
- if (!prev.spanId) {
277
- dispatch({
278
- type: 'resetScale',
279
- });
280
- return;
281
- }
282
- dispatch({
283
- type: 'select',
284
- id: prev.spanId,
285
- });
286
- dispatch({
287
- type: 'scaleToNode',
288
- id: prev.spanId,
289
- });
272
+ const focusViewportOnSpan = useCallback(
273
+ (spanId: string) => {
274
+ const span = trace.spans.find((s) => s.spanId === spanId);
275
+ if (!span) return;
276
+
277
+ const spanStart = getHighResInMs(span.startTime);
278
+ const spanEnd = getHighResInMs(span.endTime);
279
+ const spanDuration = spanEnd - spanStart;
280
+
281
+ if (spanDuration > root.duration * 0.8) {
282
+ animateTo({ start: root.startTime, end: root.endTime });
290
283
  return;
291
284
  }
292
- const target = event.target as HTMLElement;
293
- if (!target.closest(`.${String(styles.timeline)}`)) return;
294
- const $button = target.closest<HTMLButtonElement>('[data-span-id]');
295
- const spanId = $button?.dataset.spanId || '';
296
- lastClickRef.current = {
297
- x,
298
- y,
299
- t,
300
- spanId,
301
- };
302
- if (!spanId) return;
303
- dispatch({
304
- type: 'toggleSelection',
305
- id: spanId,
285
+
286
+ const padding = Math.max(spanDuration * 0.2, MIN_VIEWPORT_MS / 2);
287
+ let newStart = spanStart - padding;
288
+ let newEnd = spanEnd + padding;
289
+
290
+ if (newEnd - newStart < MIN_VIEWPORT_MS) {
291
+ const center = (spanStart + spanEnd) / 2;
292
+ newStart = center - MIN_VIEWPORT_MS / 2;
293
+ newEnd = center + MIN_VIEWPORT_MS / 2;
294
+ }
295
+
296
+ animateTo(clampToRoot({ start: newStart, end: newEnd }));
297
+ },
298
+ [
299
+ animateTo,
300
+ trace.spans,
301
+ root.startTime,
302
+ root.endTime,
303
+ root.duration,
304
+ clampToRoot,
305
+ ]
306
+ );
307
+
308
+ // Bring a row into view when keyboard/button navigation lands on a span that
309
+ // sits outside the shared scroll container's visible area. The list is
310
+ // windowed, so an off-screen row has no DOM node to `scrollIntoView` —
311
+ // `scrollRowIntoView` computes the target offset from the span's index.
312
+ const scrollSpanIntoView = useCallback(
313
+ (spanId: string) => {
314
+ const index = trace.spans.findIndex((s) => s.spanId === spanId);
315
+ if (index === -1) return;
316
+
317
+ const list =
318
+ scrollContainerRef.current?.querySelector<HTMLElement>('#event-list') ??
319
+ null;
320
+ scrollRowIntoView(list, index, ROW_HEIGHT_PX, {
321
+ behavior: reducedMotion ? 'auto' : 'smooth',
306
322
  });
307
- event.stopPropagation();
308
323
  },
309
- [dispatch]
324
+ [trace.spans, reducedMotion]
310
325
  );
311
326
 
312
- // Zoom helper apply the scroll snapshot once after a zoom operation so
313
- // the anchor point stays at the same screen position, then clear it.
314
- // Without clearing, live runs (where scale changes every frame via
315
- // detectBaseScale) would continuously reset scrollLeft and prevent the
316
- // user from scrolling horizontally.
317
- useLayoutEffect(() => {
318
- const $timeline = timelineRef.current;
319
- if (!$timeline) return;
320
-
321
- const snapshot = scrollSnapshotRef.current;
322
- if (snapshot) {
323
- $timeline.scrollLeft = snapshot.anchorT * scale - snapshot.anchorX;
324
- scrollSnapshotRef.current = undefined;
327
+ const zoomTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
328
+ const cancelPendingZoom = useCallback(() => {
329
+ if (zoomTimerRef.current !== null) {
330
+ clearTimeout(zoomTimerRef.current);
331
+ zoomTimerRef.current = null;
325
332
  }
326
- }, [scrollSnapshotRef, timelineRef, scale]);
327
-
328
- // Selection helper
329
- useEffect(() => {
330
- const spanId = state.selected?.span.spanId;
331
- if (!spanId) return;
332
-
333
- const timeout = setTimeout(() => {
334
- const $timeline = state.timelineRef.current;
335
- const $span = $timeline?.querySelector(`[data-span-id="${spanId}"]`);
336
- if (!$timeline || !$span) return;
337
-
338
- const viewRect = $timeline.getBoundingClientRect();
339
- const spanRect = $span.getBoundingClientRect();
340
-
341
- // If the selected span is narrower than the timeline, scroll it into view
342
- if (
343
- spanRect.width < viewRect.width &&
344
- (spanRect.left < viewRect.left ||
345
- spanRect.right > viewRect.right ||
346
- spanRect.top < viewRect.top ||
347
- spanRect.bottom > viewRect.bottom)
348
- ) {
349
- $span.scrollIntoView({
350
- block: 'nearest',
351
- inline: 'center',
352
- behavior: 'smooth',
353
- });
333
+ }, []);
334
+ useEffect(() => cancelPendingZoom, [cancelPendingZoom]);
335
+
336
+ const handleClearActiveSpan = useCallback(() => {
337
+ cancelPendingZoom();
338
+ clearActiveSpan();
339
+ }, [cancelPendingZoom, clearActiveSpan]);
340
+
341
+ const handleSelectSpan = useCallback(
342
+ (spanId: string) => {
343
+ cancelPendingZoom();
344
+ if (spanId === activeSpanId) {
345
+ clearActiveSpan();
346
+ return;
354
347
  }
355
- }, 500);
348
+ setActiveSpan(spanId);
349
+ focusViewportOnSpan(spanId);
350
+ },
351
+ [
352
+ cancelPendingZoom,
353
+ activeSpanId,
354
+ clearActiveSpan,
355
+ setActiveSpan,
356
+ focusViewportOnSpan,
357
+ ]
358
+ );
356
359
 
357
- return () => {
358
- clearTimeout(timeout);
359
- };
360
- }, [state.selected, state.timelineRef]);
360
+ const navigateToSpan = useCallback(
361
+ (spanId: string) => {
362
+ setActiveSpan(spanId);
363
+ scrollSpanIntoView(spanId);
364
+ cancelPendingZoom();
365
+ zoomTimerRef.current = setTimeout(() => {
366
+ zoomTimerRef.current = null;
367
+ focusViewportOnSpan(spanId);
368
+ }, ZOOM_DEBOUNCE_MS);
369
+ },
370
+ [setActiveSpan, scrollSpanIntoView, cancelPendingZoom, focusViewportOnSpan]
371
+ );
372
+
373
+ const [altHeld, setAltHeld] = useState(false);
361
374
 
362
- // Global keyboard shortcuts
363
375
  useEffect(() => {
364
376
  const onKeyDown = (e: KeyboardEvent): void => {
365
377
  if (e.key === 'Escape') {
366
- dispatch({
367
- type: 'escape',
368
- });
378
+ handleClearActiveSpan();
379
+ } else if (e.key === 'Alt') {
380
+ setAltHeld(true);
369
381
  }
370
382
  };
371
- window.addEventListener('keydown', onKeyDown);
383
+ const onKeyUp = (e: KeyboardEvent): void => {
384
+ if (e.key === 'Alt') setAltHeld(false);
385
+ };
386
+ const onBlur = (): void => setAltHeld(false);
372
387
 
388
+ window.addEventListener('keydown', onKeyDown);
389
+ window.addEventListener('keyup', onKeyUp);
390
+ window.addEventListener('blur', onBlur);
373
391
  return () => {
374
392
  window.removeEventListener('keydown', onKeyDown);
393
+ window.removeEventListener('keyup', onKeyUp);
394
+ window.removeEventListener('blur', onBlur);
375
395
  };
376
- }, [dispatch]);
377
-
378
- // Scroll locking
379
- useEffect(() => {
380
- if (!scrollLock) return;
381
-
382
- const $html = document.documentElement;
383
- const $body = document.body;
396
+ }, [handleClearActiveSpan]);
397
+
398
+ const timelineRef = useRef<HTMLDivElement>(null);
399
+ const [hover, setHover] = useState<TimelineHover | null>(null);
400
+
401
+ const hoverInfo = useMemo(() => {
402
+ if (hover == null) return null;
403
+ const absTime = viewport.start + hover.fraction * viewDuration;
404
+ const offset = absTime - root.startTime;
405
+ return { fraction: hover.fraction, label: formatDurationPrecise(offset) };
406
+ }, [hover, viewport.start, viewDuration, root.startTime]);
407
+
408
+ const handleTimelineMouseMove = useCallback(
409
+ (e: React.MouseEvent<HTMLDivElement>) => {
410
+ const el = timelineRef.current;
411
+ if (!el) return;
412
+ const rect = el.getBoundingClientRect();
413
+ const contentWidth = rect.width - TIMELINE_PADDING_PX * 2;
414
+ if (contentWidth <= 0) return;
415
+ const fraction = Math.max(
416
+ 0,
417
+ Math.min(
418
+ 1,
419
+ (e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
420
+ )
421
+ );
422
+ setHover({
423
+ fraction,
424
+ rowIndex: Math.floor((e.clientY - rect.top) / ROW_HEIGHT_PX),
425
+ });
426
+ },
427
+ []
428
+ );
384
429
 
385
- $html.style.overflow = 'clip';
386
- $body.style.overflow = 'clip';
430
+ const handleTimelineMouseLeave = useCallback(() => {
431
+ setHover(null);
432
+ }, []);
387
433
 
388
- const onScroll = (event?: Event): void => {
389
- if (event?.cancelable) {
390
- event.preventDefault();
434
+ useEffect(() => {
435
+ const el = timelineRef.current;
436
+ if (!el || root.duration <= 0) return;
437
+
438
+ const onWheel = (e: WheelEvent): void => {
439
+ const isZoomGesture = e.ctrlKey || e.metaKey;
440
+ const hasDeltaX = Math.abs(e.deltaX) > Math.abs(e.deltaY);
441
+
442
+ if (!isZoomGesture && !hasDeltaX) return;
443
+ e.preventDefault();
444
+
445
+ const rect = el.getBoundingClientRect();
446
+ const contentWidth = rect.width - TIMELINE_PADDING_PX * 2;
447
+ if (contentWidth <= 0) return;
448
+
449
+ if (isZoomGesture) {
450
+ const cursorFraction = Math.max(
451
+ 0,
452
+ Math.min(
453
+ 1,
454
+ (e.clientX - rect.left - TIMELINE_PADDING_PX) / contentWidth
455
+ )
456
+ );
457
+ const scaleFactor = wheelZoomScaleFactor(e);
458
+
459
+ setViewport((prev) => {
460
+ const prevDuration = prev.end - prev.start;
461
+ const cursorTime = prev.start + cursorFraction * prevDuration;
462
+ // Clamp the duration before anchoring so the cursor keeps its
463
+ // fraction even when the zoom hits the min/max bounds.
464
+ const newDuration = Math.max(
465
+ MIN_VIEWPORT_MS,
466
+ Math.min(root.duration, prevDuration * scaleFactor)
467
+ );
468
+ return clampToRoot({
469
+ start: cursorTime - cursorFraction * newDuration,
470
+ end: cursorTime + (1 - cursorFraction) * newDuration,
471
+ });
472
+ });
391
473
  } else {
392
- window.scrollTo({
393
- left: 0,
394
- top: 0,
395
- behavior: 'instant',
474
+ const dx = wheelDeltaToPixels(e.deltaX, e.deltaMode);
475
+ setViewport((prev) => {
476
+ const panAmount = (dx / contentWidth) * (prev.end - prev.start);
477
+ return clampToRoot({
478
+ start: prev.start + panAmount,
479
+ end: prev.end + panAmount,
480
+ });
396
481
  });
397
482
  }
398
483
  };
399
- onScroll();
400
- window.addEventListener('scroll', onScroll, {
401
- passive: false,
402
- });
403
-
404
- return () => {
405
- $html.style.overflow = '';
406
- $body.style.overflow = '';
407
- window.removeEventListener('scroll', onScroll);
408
- };
409
- }, [scrollLock]);
410
-
411
- const timelineHeight = Math.max(
412
- state.timelineHeight - state.scrollbarWidth,
413
- MARKER_HEIGHT +
414
- ROW_PADDING +
415
- rows.length * (ROW_HEIGHT + ROW_PADDING) -
416
- ROW_PADDING +
417
- // When there are enough spans to be near the bottom edge, add some extra padding
418
- // to avoid overlapping with the zoom buttons, etc.
419
- 84
420
- );
421
484
 
422
- const inert = Boolean(state.isMobile && state.selected);
485
+ el.addEventListener('wheel', onWheel, { passive: false });
486
+ return () => el.removeEventListener('wheel', onWheel);
487
+ }, [root.duration, setViewport, clampToRoot]);
423
488
 
424
489
  return (
425
490
  <div
426
- className={clsx(
427
- styles.traceViewer,
428
- isSkeleton && styles.skeleton,
429
- className
430
- )}
431
- onClickCapture={onClick}
432
- ref={ref}
433
- style={
434
- {
435
- height,
436
- '--timeline-padding': `${TIMELINE_PADDING}px`,
437
- '--row-height': `${ROW_HEIGHT}px`,
438
- '--row-padding': `${ROW_PADDING}px`,
439
- '--search-height': `${!hideSearchBar ? SEARCH_HEIGHT : 0}px`,
440
- '--search-gap': `${!hideSearchBar ? SEARCH_GAP : 2}px`,
441
- '--map-height': `${MAP_HEIGHT}px`,
442
- '--timeline-width': `${state.timelineWidth}px`,
443
- '--timeline-height': `${state.timelineHeight}px`,
444
- '--timeline-scroll-width': `${Math.round(state.root.duration * state.scale)}px`,
445
- '--panel-width': `${state.panelWidth}px`,
446
- '--panel-height': `${state.panelHeight}px`,
447
- '--height': `${state.height}px`,
448
- '--scrollbar-width': `${state.scrollbarWidth}px`,
449
- '--marker-height': `${MARKER_HEIGHT}px`,
450
- '--marker-notch-height': `${MARKER_NOTCH_HEIGHT}px`,
451
- } as CSSProperties
452
- }
491
+ ref={paneRootRef}
492
+ data-pane="pane-root"
493
+ className="relative flex w-full h-full max-h-full"
453
494
  >
454
- {!hideSearchBar ? <SearchBar /> : null}
455
- <MiniMap rows={rows} scale={scale} timelineRef={timelineRef} />
456
- <div className={clsx(styles.traceViewerContent, inert && styles.inert)}>
457
- <div className={styles.timeline} ref={timelineRef}>
458
- <div
459
- style={{
460
- position: 'relative',
461
- width: state.timelineWidth,
462
- minHeight: state.timelineHeight - TIMELINE_PADDING * 2,
463
- padding: TIMELINE_PADDING,
464
- paddingBottom: 0,
465
- }}
466
- >
467
- <div
468
- className={styles.traceNode}
469
- style={{
470
- width: state.root.duration * scale || undefined,
471
- height: timelineHeight - TIMELINE_PADDING * 2,
472
- }}
473
- >
474
- <Markers scale={scale} isLive={isLive} />
475
- <EventMarkers events={events} root={state.root} scale={scale} />
476
- <CursorMarker
477
- dispatch={dispatch}
478
- events={events}
479
- memoCacheRef={state.memoCacheRef}
480
- root={state.root}
481
- scale={scale}
482
- scrollSnapshotRef={scrollSnapshotRef}
483
- spans={spans}
484
- timelineRef={timelineRef}
495
+ <div
496
+ id="trace-parent"
497
+ className="@container flex-1 min-w-0 grid grid-rows-[auto_1fr] h-full min-h-0 overflow-hidden relative bg-background-100"
498
+ >
499
+ <Minimap
500
+ spans={trace.spans}
501
+ root={root}
502
+ viewport={viewport}
503
+ minViewportMs={MIN_VIEWPORT_MS}
504
+ onViewportChange={setViewport}
505
+ onAnimateTo={animateTo}
506
+ />
507
+ <SplitPane
508
+ scrollContainerRef={scrollContainerRef}
509
+ startHeader={
510
+ <div className="bg-background-100 border-b border-gray-alpha-400 h-10 min-h-10 flex items-center pl-4 pr-2 gap-1.5">
511
+ <Search className="w-3.5 h-3.5 shrink-0 text-gray-800" />
512
+ <input
513
+ id="trace-viewer-search"
514
+ name="trace-viewer-search"
515
+ type="text"
516
+ value={searchQuery}
517
+ onChange={(e) => setSearchQuery(e.target.value)}
518
+ onKeyDown={(e) => {
519
+ if (e.key === 'Escape' && searchQuery) {
520
+ e.preventDefault();
521
+ e.stopPropagation();
522
+ setSearchQuery('');
523
+ }
524
+ }}
525
+ placeholder="Search spans..."
526
+ aria-label="Search spans"
527
+ className="flex-1 min-w-0 bg-transparent text-label-14 text-gray-1000 placeholder:text-gray-800 outline-none"
485
528
  />
486
- <SpanNodes
487
- cacheKey={memoCache.get('')}
488
- cache={memoCache}
489
- customSpanClassNameFunc={state.customSpanClassNameFunc}
490
- customSpanEventClassNameFunc={
491
- state.customSpanEventClassNameFunc
492
- }
493
- isLive={isLive}
494
- root={state.root}
495
- scale={scale}
496
- scrollSnapshotRef={scrollSnapshotRef}
497
- spans={spans}
498
- />
499
- {/* Horizontal "unknown time" overlay — covers the region to the
500
- right of the latest known event, indicating data beyond this
501
- point hasn't been loaded yet. */}
502
- {knownDurationMs != null &&
503
- knownDurationMs > 0 &&
504
- (hasMoreData || isLive) &&
505
- state.root.duration > 0 &&
506
- (() => {
507
- const knownPx = knownDurationMs * scale;
508
- const totalPx = state.root.duration * scale;
509
- const unknownWidth = totalPx - knownPx;
510
- // Only show if the unknown region is meaningfully wide
511
- if (unknownWidth < 4) return null;
512
- // Offset ~5% into the unknown region so it doesn't touch spans
513
- const insetPx = Math.min(unknownWidth * 0.05, 20);
514
- return (
515
- <div
516
- style={{
517
- position: 'absolute',
518
- top: 0,
519
- left: knownPx + insetPx,
520
- width: unknownWidth - insetPx,
521
- height: '100%',
522
- pointerEvents: 'none',
523
- zIndex: 1,
524
- maskImage:
525
- 'linear-gradient(to right, transparent 1%, black 3%)',
526
- WebkitMaskImage:
527
- 'linear-gradient(to right, transparent 1%, black 3%)',
528
- background:
529
- 'repeating-linear-gradient(-45deg, var(--ds-background-200) 0, var(--ds-background-200) 11px, var(--ds-gray-200) 11px, var(--ds-gray-200) 12px)',
530
- }}
531
- />
532
- );
533
- })()}
529
+ {searchQuery && (
530
+ <button
531
+ type="button"
532
+ aria-label="Clear search"
533
+ onClick={() => setSearchQuery('')}
534
+ className="-mr-2 hidden h-full max-w-full shrink-0 cursor-pointer items-center rounded-r-md border-0 bg-transparent px-2.5 font-inherit text-label-16 text-gray-900 no-underline transition-colors duration-150 ease-in hover:text-gray-1000 focus-visible:-outline-offset-1 focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--ds-focus-color)] min-[961px]:flex"
535
+ >
536
+ <Kbd variant="outline" size="search">
537
+ Esc
538
+ </Kbd>
539
+ </button>
540
+ )}
541
+ </div>
542
+ }
543
+ endHeader={
544
+ <TimelineHeader markers={timeMarkers} hoverInfo={hoverInfo} />
545
+ }
546
+ >
547
+ <div className="block overflow-visible">
548
+ <EventList
549
+ spans={trace.spans}
550
+ activeSpanId={activeSpanId}
551
+ searchResult={searchResult}
552
+ onSelectSpan={handleSelectSpan}
553
+ />
554
+ <div ref={loadMoreSentinelRef} className="flex justify-center">
555
+ {isLoadingMore ? (
556
+ <div className="flex items-center justify-center gap-2 py-3 text-label-14 text-gray-800">
557
+ <Spinner size={14} />
558
+ <span>Loading spans…</span>
559
+ </div>
560
+ ) : null}
534
561
  </div>
535
562
  </div>
536
- {footer}
537
- </div>
538
- <div className={styles.zoomButtonTraceViewer}>
539
- <ZoomButton />
540
- </div>
563
+ {/* biome-ignore lint/a11y/noStaticElementInteractions: timeline hover and wheel gestures are pointer-only annotations */}
564
+ <div
565
+ ref={timelineRef}
566
+ id="trace-timeline"
567
+ className="@container block min-h-0 overflow-visible relative"
568
+ onDoubleClick={resetZoom}
569
+ onMouseMove={handleTimelineMouseMove}
570
+ onMouseLeave={handleTimelineMouseLeave}
571
+ >
572
+ <Timeline
573
+ spans={trace.spans}
574
+ viewStart={viewport.start}
575
+ viewEnd={viewport.end}
576
+ markers={timeMarkers}
577
+ selectedId={activeSpanId}
578
+ searchResult={searchResult}
579
+ onSelect={handleSelectSpan}
580
+ onRevealTime={handleRevealTime}
581
+ hover={hover}
582
+ altHeld={altHeld}
583
+ />
584
+ </div>
585
+ <>
586
+ <TraceShortcutHelper
587
+ hasMultipleSpans={trace.spans.length > 1}
588
+ reducedMotion={reducedMotion}
589
+ />
590
+ <div className="pointer-events-auto flex items-center border border-gray-alpha-400 rounded-md bg-background-100 shadow-sm overflow-hidden divide-x divide-gray-alpha-400">
591
+ <IconButton
592
+ variant="muted"
593
+ size="small"
594
+ onClick={zoomOut}
595
+ disabled={isAtMinZoom}
596
+ aria-label="Zoom out"
597
+ >
598
+ <ZoomOut className="w-4 h-4" />
599
+ </IconButton>
600
+ <IconButton
601
+ variant="muted"
602
+ size="small"
603
+ onClick={resetZoom}
604
+ aria-label="Reset zoom"
605
+ >
606
+ <RotateCcw className="w-3.5 h-3.5" />
607
+ </IconButton>
608
+ <IconButton
609
+ variant="muted"
610
+ size="small"
611
+ onClick={zoomIn}
612
+ disabled={isAtMaxZoom}
613
+ aria-label="Zoom in"
614
+ >
615
+ <ZoomIn className="w-4 h-4" />
616
+ </IconButton>
617
+ </div>
618
+ </>
619
+ </SplitPane>
541
620
  </div>
542
- {withPanel ? (
543
- <div
544
- className={clsx(
545
- styles.spanDetailPanelTraceViewer,
546
- !state.selected && styles.hidden,
547
- state.isMobile && styles.mobile
548
- )}
549
- >
550
- <SpanDetailPanel attached />
551
- </div>
552
- ) : null}
553
- </div>
554
- );
555
- }
556
-
557
- export function TraceViewerPanel({
558
- className = '',
559
- children = null,
560
- }: {
561
- className?: string;
562
- children?: ReactNode;
563
- }): ReactNode {
564
- const { state } = useTraceViewer();
565
621
 
566
- if (!state.selected) {
567
- return children;
568
- }
569
-
570
- return (
571
- <div
572
- className={clsx(styles.spanDetailPanelTraceViewer, className)}
573
- style={
574
- {
575
- position: 'relative',
576
- '--search-height': '0',
577
- '--search-gap': '0',
578
- '--map-height': '0',
579
- '--panel-width': `100%`,
580
- '--panel-height': `100%`,
581
- '--height': `100%`,
582
- '--scrollbar-width': `${state.scrollbarWidth}px`,
583
- } as CSSProperties
584
- }
585
- >
586
- <SpanDetailPanel />
622
+ <TraceDetailPanel
623
+ containerRef={paneRootRef}
624
+ onNavigateToSpan={navigateToSpan}
625
+ onClose={handleClearActiveSpan}
626
+ />
587
627
  </div>
588
628
  );
589
629
  }
590
-
591
- export function TraceViewer(props: TraceViewerProps): ReactNode {
592
- return (
593
- <TraceViewerContextProvider getQuickLinks={props.getQuickLinks} withPanel>
594
- <TraceViewerTimeline withPanel {...props} />
595
- </TraceViewerContextProvider>
596
- );
597
- }