@workflow/web-shared 4.1.0-beta.47 → 4.1.0-beta.49

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 (301) hide show
  1. package/README.md +26 -52
  2. package/dist/components/error-boundary.d.ts.map +1 -0
  3. package/dist/{error-boundary.js → components/error-boundary.js} +1 -1
  4. package/dist/components/error-boundary.js.map +1 -0
  5. package/dist/{event-list-view.d.ts → components/event-list-view.d.ts} +2 -3
  6. package/dist/components/event-list-view.d.ts.map +1 -0
  7. package/dist/{event-list-view.js → components/event-list-view.js} +9 -17
  8. package/dist/components/event-list-view.js.map +1 -0
  9. package/dist/{hook-actions.d.ts → components/hook-actions.d.ts} +2 -3
  10. package/dist/components/hook-actions.d.ts.map +1 -0
  11. package/dist/{hook-actions.js → components/hook-actions.js} +3 -4
  12. package/dist/components/hook-actions.js.map +1 -0
  13. package/dist/components/index.d.ts +10 -0
  14. package/dist/components/index.d.ts.map +1 -0
  15. package/dist/components/index.js +8 -0
  16. package/dist/components/index.js.map +1 -0
  17. package/dist/components/run-trace-view.d.ts +22 -0
  18. package/dist/components/run-trace-view.d.ts.map +1 -0
  19. package/dist/components/run-trace-view.js +11 -0
  20. package/dist/components/run-trace-view.js.map +1 -0
  21. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -0
  22. package/dist/{sidebar → components/sidebar}/attribute-panel.js +3 -3
  23. package/dist/components/sidebar/attribute-panel.js.map +1 -0
  24. package/dist/components/sidebar/conversation-view.d.ts.map +1 -0
  25. package/dist/components/sidebar/conversation-view.js.map +1 -0
  26. package/dist/components/sidebar/detail-card.d.ts.map +1 -0
  27. package/dist/components/sidebar/detail-card.js.map +1 -0
  28. package/dist/components/sidebar/entity-detail-panel.d.ts +32 -0
  29. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -0
  30. package/dist/{sidebar → components/sidebar}/entity-detail-panel.js +61 -49
  31. package/dist/components/sidebar/entity-detail-panel.js.map +1 -0
  32. package/dist/components/sidebar/events-list.d.ts +8 -0
  33. package/dist/components/sidebar/events-list.d.ts.map +1 -0
  34. package/dist/components/sidebar/events-list.js +16 -0
  35. package/dist/components/sidebar/events-list.js.map +1 -0
  36. package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -0
  37. package/dist/components/sidebar/resolve-hook-modal.js.map +1 -0
  38. package/dist/components/stream-viewer.d.ts +18 -0
  39. package/dist/components/stream-viewer.d.ts.map +1 -0
  40. package/dist/{stream-viewer.js → components/stream-viewer.js} +1 -59
  41. package/dist/components/stream-viewer.js.map +1 -0
  42. package/dist/components/trace-viewer/components/map.d.ts.map +1 -0
  43. package/dist/components/trace-viewer/components/map.js.map +1 -0
  44. package/dist/components/trace-viewer/components/markers.d.ts.map +1 -0
  45. package/dist/components/trace-viewer/components/markers.js.map +1 -0
  46. package/dist/components/trace-viewer/components/node.d.ts.map +1 -0
  47. package/dist/components/trace-viewer/components/node.js.map +1 -0
  48. package/dist/components/trace-viewer/components/search-input.d.ts.map +1 -0
  49. package/dist/components/trace-viewer/components/search-input.js.map +1 -0
  50. package/dist/components/trace-viewer/components/search.d.ts.map +1 -0
  51. package/dist/components/trace-viewer/components/search.js.map +1 -0
  52. package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -0
  53. package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -0
  54. package/dist/components/trace-viewer/components/ui.d.ts.map +1 -0
  55. package/dist/components/trace-viewer/components/ui.js.map +1 -0
  56. package/dist/components/trace-viewer/components/zoom-button.d.ts.map +1 -0
  57. package/dist/components/trace-viewer/components/zoom-button.js.map +1 -0
  58. package/dist/components/trace-viewer/components/zoom-icons.d.ts.map +1 -0
  59. package/dist/components/trace-viewer/components/zoom-icons.js.map +1 -0
  60. package/dist/components/trace-viewer/context.d.ts.map +1 -0
  61. package/dist/components/trace-viewer/context.js.map +1 -0
  62. package/dist/components/trace-viewer/index.d.ts.map +1 -0
  63. package/dist/components/trace-viewer/index.js.map +1 -0
  64. package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -0
  65. package/dist/components/trace-viewer/trace-viewer.js.map +1 -0
  66. package/dist/components/trace-viewer/types.d.ts.map +1 -0
  67. package/dist/components/trace-viewer/types.js.map +1 -0
  68. package/dist/components/trace-viewer/util/constants.d.ts.map +1 -0
  69. package/dist/components/trace-viewer/util/constants.js.map +1 -0
  70. package/dist/components/trace-viewer/util/scrollbar-width.d.ts.map +1 -0
  71. package/dist/components/trace-viewer/util/scrollbar-width.js.map +1 -0
  72. package/dist/{trace-viewer → components/trace-viewer}/util/timing.d.ts +1 -1
  73. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -0
  74. package/dist/{trace-viewer → components/trace-viewer}/util/timing.js +1 -1
  75. package/dist/components/trace-viewer/util/timing.js.map +1 -0
  76. package/dist/components/trace-viewer/util/tree.d.ts.map +1 -0
  77. package/dist/components/trace-viewer/util/tree.js.map +1 -0
  78. package/dist/components/trace-viewer/util/use-immediate-style.d.ts.map +1 -0
  79. package/dist/components/trace-viewer/util/use-immediate-style.js.map +1 -0
  80. package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -0
  81. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -0
  82. package/dist/components/trace-viewer/util/use-trackpad-zoom.d.ts.map +1 -0
  83. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +1 -0
  84. package/dist/components/trace-viewer/worker.d.ts.map +1 -0
  85. package/dist/components/trace-viewer/worker.js.map +1 -0
  86. package/dist/components/workflow-trace-view.d.ts +24 -0
  87. package/dist/components/workflow-trace-view.d.ts.map +1 -0
  88. package/dist/components/workflow-trace-view.js +152 -0
  89. package/dist/components/workflow-trace-view.js.map +1 -0
  90. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -0
  91. package/dist/components/workflow-traces/event-colors.js.map +1 -0
  92. package/dist/components/workflow-traces/trace-colors.d.ts.map +1 -0
  93. package/dist/components/workflow-traces/trace-colors.js.map +1 -0
  94. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -0
  95. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -0
  96. package/dist/components/workflow-traces/trace-time-utils.d.ts.map +1 -0
  97. package/dist/components/workflow-traces/trace-time-utils.js.map +1 -0
  98. package/dist/index.d.ts +3 -13
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +2 -9
  101. package/dist/index.js.map +1 -1
  102. package/package.json +15 -11
  103. package/src/components/error-boundary.tsx +79 -0
  104. package/src/components/event-list-view.tsx +429 -0
  105. package/src/components/hook-actions.tsx +167 -0
  106. package/src/components/index.d.ts +1 -0
  107. package/src/components/index.ts +23 -0
  108. package/src/components/run-trace-view.tsx +75 -0
  109. package/src/components/sidebar/attribute-panel.tsx +938 -0
  110. package/src/components/sidebar/conversation-view.tsx +235 -0
  111. package/src/components/sidebar/detail-card.tsx +43 -0
  112. package/src/components/sidebar/entity-detail-panel.tsx +338 -0
  113. package/src/components/sidebar/events-list.tsx +119 -0
  114. package/src/components/sidebar/resolve-hook-modal.tsx +219 -0
  115. package/src/components/stream-viewer.tsx +143 -0
  116. package/src/components/trace-viewer/components/map.tsx +226 -0
  117. package/src/components/trace-viewer/components/markers.tsx +564 -0
  118. package/src/components/trace-viewer/components/node.tsx +259 -0
  119. package/src/components/trace-viewer/components/search-input.tsx +52 -0
  120. package/src/components/trace-viewer/components/search.tsx +47 -0
  121. package/src/components/trace-viewer/components/span-detail-panel.tsx +650 -0
  122. package/src/components/trace-viewer/components/ui.tsx +156 -0
  123. package/src/components/trace-viewer/components/zoom-button.tsx +61 -0
  124. package/src/components/trace-viewer/components/zoom-icons.tsx +65 -0
  125. package/src/components/trace-viewer/context.tsx +633 -0
  126. package/src/components/trace-viewer/index.tsx +4 -0
  127. package/src/components/trace-viewer/modules.d.ts +16 -0
  128. package/src/components/trace-viewer/trace-viewer.module.css +1292 -0
  129. package/src/components/trace-viewer/trace-viewer.tsx +448 -0
  130. package/src/components/trace-viewer/types.ts +234 -0
  131. package/src/components/trace-viewer/util/constants.ts +8 -0
  132. package/src/components/trace-viewer/util/scrollbar-width.ts +13 -0
  133. package/src/components/trace-viewer/util/timing.ts +33 -0
  134. package/src/components/trace-viewer/util/tree.ts +277 -0
  135. package/src/components/trace-viewer/util/use-immediate-style.ts +38 -0
  136. package/src/components/trace-viewer/util/use-streaming-spans.ts +415 -0
  137. package/src/components/trace-viewer/util/use-trackpad-zoom.tsx +51 -0
  138. package/src/components/trace-viewer/worker.ts +128 -0
  139. package/src/components/ui/alert.tsx +59 -0
  140. package/src/components/ui/card.tsx +88 -0
  141. package/src/components/ui/error-card.tsx +65 -0
  142. package/src/components/ui/skeleton.tsx +15 -0
  143. package/src/components/workflow-trace-view.tsx +306 -0
  144. package/src/components/workflow-traces/event-colors.ts +94 -0
  145. package/src/components/workflow-traces/trace-colors.ts +112 -0
  146. package/src/components/workflow-traces/trace-span-construction.ts +299 -0
  147. package/src/components/workflow-traces/trace-time-utils.ts +50 -0
  148. package/src/hooks/use-dark-mode.ts +34 -0
  149. package/src/index.d.ts +1 -0
  150. package/src/index.ts +29 -0
  151. package/src/lib/event-analysis.ts +231 -0
  152. package/src/lib/utils.ts +166 -0
  153. package/dist/api/workflow-api-client.d.ts +0 -543
  154. package/dist/api/workflow-api-client.d.ts.map +0 -1
  155. package/dist/api/workflow-api-client.js +0 -953
  156. package/dist/api/workflow-api-client.js.map +0 -1
  157. package/dist/api/workflow-server-actions.d.ts +0 -230
  158. package/dist/api/workflow-server-actions.d.ts.map +0 -1
  159. package/dist/api/workflow-server-actions.js +0 -861
  160. package/dist/api/workflow-server-actions.js.map +0 -1
  161. package/dist/error-boundary.d.ts.map +0 -1
  162. package/dist/error-boundary.js.map +0 -1
  163. package/dist/event-list-view.d.ts.map +0 -1
  164. package/dist/event-list-view.js.map +0 -1
  165. package/dist/hook-actions.d.ts.map +0 -1
  166. package/dist/hook-actions.js.map +0 -1
  167. package/dist/run-trace-view.d.ts +0 -8
  168. package/dist/run-trace-view.d.ts.map +0 -1
  169. package/dist/run-trace-view.js +0 -15
  170. package/dist/run-trace-view.js.map +0 -1
  171. package/dist/sidebar/attribute-panel.d.ts.map +0 -1
  172. package/dist/sidebar/attribute-panel.js.map +0 -1
  173. package/dist/sidebar/conversation-view.d.ts.map +0 -1
  174. package/dist/sidebar/conversation-view.js.map +0 -1
  175. package/dist/sidebar/detail-card.d.ts.map +0 -1
  176. package/dist/sidebar/detail-card.js.map +0 -1
  177. package/dist/sidebar/entity-detail-panel.d.ts +0 -12
  178. package/dist/sidebar/entity-detail-panel.d.ts.map +0 -1
  179. package/dist/sidebar/entity-detail-panel.js.map +0 -1
  180. package/dist/sidebar/events-list.d.ts +0 -9
  181. package/dist/sidebar/events-list.d.ts.map +0 -1
  182. package/dist/sidebar/events-list.js +0 -36
  183. package/dist/sidebar/events-list.js.map +0 -1
  184. package/dist/sidebar/resolve-hook-modal.d.ts.map +0 -1
  185. package/dist/sidebar/resolve-hook-modal.js.map +0 -1
  186. package/dist/stream-viewer.d.ts +0 -13
  187. package/dist/stream-viewer.d.ts.map +0 -1
  188. package/dist/stream-viewer.js.map +0 -1
  189. package/dist/trace-viewer/components/map.d.ts.map +0 -1
  190. package/dist/trace-viewer/components/map.js.map +0 -1
  191. package/dist/trace-viewer/components/markers.d.ts.map +0 -1
  192. package/dist/trace-viewer/components/markers.js.map +0 -1
  193. package/dist/trace-viewer/components/node.d.ts.map +0 -1
  194. package/dist/trace-viewer/components/node.js.map +0 -1
  195. package/dist/trace-viewer/components/search-input.d.ts.map +0 -1
  196. package/dist/trace-viewer/components/search-input.js.map +0 -1
  197. package/dist/trace-viewer/components/search.d.ts.map +0 -1
  198. package/dist/trace-viewer/components/search.js.map +0 -1
  199. package/dist/trace-viewer/components/span-detail-panel.d.ts.map +0 -1
  200. package/dist/trace-viewer/components/span-detail-panel.js.map +0 -1
  201. package/dist/trace-viewer/components/ui.d.ts.map +0 -1
  202. package/dist/trace-viewer/components/ui.js.map +0 -1
  203. package/dist/trace-viewer/components/zoom-button.d.ts.map +0 -1
  204. package/dist/trace-viewer/components/zoom-button.js.map +0 -1
  205. package/dist/trace-viewer/components/zoom-icons.d.ts.map +0 -1
  206. package/dist/trace-viewer/components/zoom-icons.js.map +0 -1
  207. package/dist/trace-viewer/context.d.ts.map +0 -1
  208. package/dist/trace-viewer/context.js.map +0 -1
  209. package/dist/trace-viewer/index.d.ts.map +0 -1
  210. package/dist/trace-viewer/index.js.map +0 -1
  211. package/dist/trace-viewer/trace-viewer.d.ts.map +0 -1
  212. package/dist/trace-viewer/trace-viewer.js.map +0 -1
  213. package/dist/trace-viewer/types.d.ts.map +0 -1
  214. package/dist/trace-viewer/types.js.map +0 -1
  215. package/dist/trace-viewer/util/constants.d.ts.map +0 -1
  216. package/dist/trace-viewer/util/constants.js.map +0 -1
  217. package/dist/trace-viewer/util/scrollbar-width.d.ts.map +0 -1
  218. package/dist/trace-viewer/util/scrollbar-width.js.map +0 -1
  219. package/dist/trace-viewer/util/timing.d.ts.map +0 -1
  220. package/dist/trace-viewer/util/timing.js.map +0 -1
  221. package/dist/trace-viewer/util/tree.d.ts.map +0 -1
  222. package/dist/trace-viewer/util/tree.js.map +0 -1
  223. package/dist/trace-viewer/util/use-immediate-style.d.ts.map +0 -1
  224. package/dist/trace-viewer/util/use-immediate-style.js.map +0 -1
  225. package/dist/trace-viewer/util/use-streaming-spans.d.ts.map +0 -1
  226. package/dist/trace-viewer/util/use-streaming-spans.js.map +0 -1
  227. package/dist/trace-viewer/util/use-trackpad-zoom.d.ts.map +0 -1
  228. package/dist/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  229. package/dist/trace-viewer/worker.d.ts.map +0 -1
  230. package/dist/trace-viewer/worker.js.map +0 -1
  231. package/dist/workflow-trace-view.d.ts +0 -14
  232. package/dist/workflow-trace-view.d.ts.map +0 -1
  233. package/dist/workflow-trace-view.js +0 -135
  234. package/dist/workflow-trace-view.js.map +0 -1
  235. package/dist/workflow-traces/event-colors.d.ts.map +0 -1
  236. package/dist/workflow-traces/event-colors.js.map +0 -1
  237. package/dist/workflow-traces/trace-colors.d.ts.map +0 -1
  238. package/dist/workflow-traces/trace-colors.js.map +0 -1
  239. package/dist/workflow-traces/trace-span-construction.d.ts.map +0 -1
  240. package/dist/workflow-traces/trace-span-construction.js.map +0 -1
  241. package/dist/workflow-traces/trace-time-utils.d.ts.map +0 -1
  242. package/dist/workflow-traces/trace-time-utils.js.map +0 -1
  243. package/server/README.md +0 -1
  244. package/server/package.json +0 -4
  245. /package/dist/{error-boundary.d.ts → components/error-boundary.d.ts} +0 -0
  246. /package/dist/{sidebar → components/sidebar}/attribute-panel.d.ts +0 -0
  247. /package/dist/{sidebar → components/sidebar}/conversation-view.d.ts +0 -0
  248. /package/dist/{sidebar → components/sidebar}/conversation-view.js +0 -0
  249. /package/dist/{sidebar → components/sidebar}/detail-card.d.ts +0 -0
  250. /package/dist/{sidebar → components/sidebar}/detail-card.js +0 -0
  251. /package/dist/{sidebar → components/sidebar}/resolve-hook-modal.d.ts +0 -0
  252. /package/dist/{sidebar → components/sidebar}/resolve-hook-modal.js +0 -0
  253. /package/dist/{trace-viewer → components/trace-viewer}/components/map.d.ts +0 -0
  254. /package/dist/{trace-viewer → components/trace-viewer}/components/map.js +0 -0
  255. /package/dist/{trace-viewer → components/trace-viewer}/components/markers.d.ts +0 -0
  256. /package/dist/{trace-viewer → components/trace-viewer}/components/markers.js +0 -0
  257. /package/dist/{trace-viewer → components/trace-viewer}/components/node.d.ts +0 -0
  258. /package/dist/{trace-viewer → components/trace-viewer}/components/node.js +0 -0
  259. /package/dist/{trace-viewer → components/trace-viewer}/components/search-input.d.ts +0 -0
  260. /package/dist/{trace-viewer → components/trace-viewer}/components/search-input.js +0 -0
  261. /package/dist/{trace-viewer → components/trace-viewer}/components/search.d.ts +0 -0
  262. /package/dist/{trace-viewer → components/trace-viewer}/components/search.js +0 -0
  263. /package/dist/{trace-viewer → components/trace-viewer}/components/span-detail-panel.d.ts +0 -0
  264. /package/dist/{trace-viewer → components/trace-viewer}/components/span-detail-panel.js +0 -0
  265. /package/dist/{trace-viewer → components/trace-viewer}/components/ui.d.ts +0 -0
  266. /package/dist/{trace-viewer → components/trace-viewer}/components/ui.js +0 -0
  267. /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-button.d.ts +0 -0
  268. /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-button.js +0 -0
  269. /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-icons.d.ts +0 -0
  270. /package/dist/{trace-viewer → components/trace-viewer}/components/zoom-icons.js +0 -0
  271. /package/dist/{trace-viewer → components/trace-viewer}/context.d.ts +0 -0
  272. /package/dist/{trace-viewer → components/trace-viewer}/context.js +0 -0
  273. /package/dist/{trace-viewer → components/trace-viewer}/index.d.ts +0 -0
  274. /package/dist/{trace-viewer → components/trace-viewer}/index.js +0 -0
  275. /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.d.ts +0 -0
  276. /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.js +0 -0
  277. /package/dist/{trace-viewer → components/trace-viewer}/trace-viewer.module.css +0 -0
  278. /package/dist/{trace-viewer → components/trace-viewer}/types.d.ts +0 -0
  279. /package/dist/{trace-viewer → components/trace-viewer}/types.js +0 -0
  280. /package/dist/{trace-viewer → components/trace-viewer}/util/constants.d.ts +0 -0
  281. /package/dist/{trace-viewer → components/trace-viewer}/util/constants.js +0 -0
  282. /package/dist/{trace-viewer → components/trace-viewer}/util/scrollbar-width.d.ts +0 -0
  283. /package/dist/{trace-viewer → components/trace-viewer}/util/scrollbar-width.js +0 -0
  284. /package/dist/{trace-viewer → components/trace-viewer}/util/tree.d.ts +0 -0
  285. /package/dist/{trace-viewer → components/trace-viewer}/util/tree.js +0 -0
  286. /package/dist/{trace-viewer → components/trace-viewer}/util/use-immediate-style.d.ts +0 -0
  287. /package/dist/{trace-viewer → components/trace-viewer}/util/use-immediate-style.js +0 -0
  288. /package/dist/{trace-viewer → components/trace-viewer}/util/use-streaming-spans.d.ts +0 -0
  289. /package/dist/{trace-viewer → components/trace-viewer}/util/use-streaming-spans.js +0 -0
  290. /package/dist/{trace-viewer → components/trace-viewer}/util/use-trackpad-zoom.d.ts +0 -0
  291. /package/dist/{trace-viewer → components/trace-viewer}/util/use-trackpad-zoom.js +0 -0
  292. /package/dist/{trace-viewer → components/trace-viewer}/worker.d.ts +0 -0
  293. /package/dist/{trace-viewer → components/trace-viewer}/worker.js +0 -0
  294. /package/dist/{workflow-traces → components/workflow-traces}/event-colors.d.ts +0 -0
  295. /package/dist/{workflow-traces → components/workflow-traces}/event-colors.js +0 -0
  296. /package/dist/{workflow-traces → components/workflow-traces}/trace-colors.d.ts +0 -0
  297. /package/dist/{workflow-traces → components/workflow-traces}/trace-colors.js +0 -0
  298. /package/dist/{workflow-traces → components/workflow-traces}/trace-span-construction.d.ts +0 -0
  299. /package/dist/{workflow-traces → components/workflow-traces}/trace-span-construction.js +0 -0
  300. /package/dist/{workflow-traces → components/workflow-traces}/trace-time-utils.d.ts +0 -0
  301. /package/dist/{workflow-traces → components/workflow-traces}/trace-time-utils.js +0 -0
@@ -0,0 +1,650 @@
1
+ 'use client';
2
+
3
+ import { clsx } from 'clsx';
4
+ import type { PointerEventHandler, ReactNode } from 'react';
5
+ import {
6
+ Fragment,
7
+ memo,
8
+ useCallback,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from 'react';
14
+ import { initialState, useTraceViewer } from '../context';
15
+ import styles from '../trace-viewer.module.css';
16
+ import type {
17
+ GetQuickLinks,
18
+ QuickLinkValue,
19
+ RootNode,
20
+ Span,
21
+ SpanNode,
22
+ TraceNode,
23
+ } from '../types';
24
+ import { formatDuration } from '../util/timing';
25
+ import { getSpanColorClassName } from './node';
26
+ import {
27
+ ButtonLink,
28
+ IconChevronDown,
29
+ IconCross,
30
+ IconExternalSmall,
31
+ Link,
32
+ Note,
33
+ Skeleton,
34
+ } from './ui';
35
+
36
+ const percentage = (n: number): string => `${(100 * n).toFixed(3)}%`;
37
+
38
+ interface GroupedAttribute {
39
+ prefix: string;
40
+ attributes: Attribute[];
41
+ }
42
+
43
+ interface Attribute {
44
+ suffix: string;
45
+ key: string;
46
+ value: string;
47
+ }
48
+
49
+ const sortWord = (a: string, b: string): number => {
50
+ if (a < b) {
51
+ return -1;
52
+ } else if (a > b) {
53
+ return 1;
54
+ }
55
+ return 0;
56
+ };
57
+
58
+ const getGroupedAttributes = (
59
+ attributes: Span['attributes']
60
+ ): GroupedAttribute[] => {
61
+ // Build groups of items with the same prefix
62
+ const groups: Record<string, GroupedAttribute> = {};
63
+ for (const [key, value] of Object.entries(attributes)) {
64
+ const dotIndex = key.indexOf('.');
65
+ let prefix = '';
66
+ let suffix = '';
67
+ if (dotIndex !== -1) {
68
+ prefix = key.substring(0, dotIndex);
69
+ suffix = key.substring(dotIndex + 1);
70
+ } else {
71
+ suffix = key;
72
+ }
73
+
74
+ let group = groups[prefix];
75
+ if (!group) {
76
+ group = {
77
+ prefix,
78
+ attributes: [],
79
+ };
80
+ groups[prefix] = group;
81
+ }
82
+ group.attributes.push({
83
+ key,
84
+ suffix,
85
+ value: String(value),
86
+ });
87
+ }
88
+
89
+ // List and sort everything
90
+ const result = Array.from(Object.values(groups)).sort((a, b) =>
91
+ sortWord(a.prefix, b.prefix)
92
+ );
93
+ for (const group of result) {
94
+ group.attributes.sort((a, b) => sortWord(a.suffix, b.suffix));
95
+ }
96
+ return result;
97
+ };
98
+
99
+ // biome-ignore lint/correctness/noUnusedFunctionParameters: ignored using `--suppress`
100
+ const getAncestors = (_root: RootNode, start: SpanNode | null): SpanNode[] => {
101
+ const result: SpanNode[] = [];
102
+
103
+ if (!start) return result;
104
+
105
+ for (let node: TraceNode = start; 'parent' in node; node = node.parent) {
106
+ result.unshift(node);
107
+ }
108
+
109
+ return result;
110
+ };
111
+
112
+ export const SpanDetailPanelBody = () => {
113
+ const {
114
+ state: { root, selected, resourceMap, getQuickLinks },
115
+ dispatch,
116
+ } = useTraceViewer();
117
+
118
+ const ancestors = useMemo(
119
+ () => getAncestors(root, selected),
120
+ [root, selected]
121
+ );
122
+
123
+ const attributeGroups = useMemo(
124
+ () => getGroupedAttributes(selected?.span.attributes ?? {}),
125
+ [selected]
126
+ );
127
+
128
+ const resourceAttributeGroups = useMemo(() => {
129
+ const resourceName = selected?.span.resource;
130
+ if (!resourceName) return [];
131
+ const resourceAttributes = resourceMap[resourceName];
132
+ if (!resourceAttributes) return [];
133
+ return getGroupedAttributes(resourceAttributes);
134
+ }, [resourceMap, selected]);
135
+
136
+ if (!selected) return null;
137
+
138
+ const { span } = selected;
139
+
140
+ const isEntrySpan = detectIsEntrySpan(selected);
141
+
142
+ return (
143
+ <>
144
+ {selected.isVercel ? <InfraSpanDescription node={selected} /> : null}
145
+ {selected.isInstrumentationHint ? <InstrumentationHint /> : null}
146
+ {!selected.isInstrumentationHint && ancestors.length ? (
147
+ <DetailGroup className={styles.ancestorGroup} name="Location">
148
+ {ancestors.map((node) => (
149
+ <button
150
+ className={styles.ancestorNode}
151
+ key={`${selected.span.spanId}.${node.span.spanId}`}
152
+ onClick={() =>
153
+ dispatch({
154
+ type: 'select',
155
+ id: node.span.spanId,
156
+ })
157
+ }
158
+ type="button"
159
+ >
160
+ <div className={styles.ancestorText}>
161
+ <span className={styles.ancestorName}>
162
+ {node.label || node.span.name}
163
+ </span>
164
+ <span className={styles.ancestorDuration}>
165
+ {formatDuration(node.duration)}
166
+ </span>
167
+ </div>
168
+ <span aria-hidden className={styles.ancestorLineContainer}>
169
+ <span
170
+ className={clsx(
171
+ styles.ancestorLine,
172
+ getSpanColorClassName(node)
173
+ )}
174
+ style={{
175
+ left: percentage(
176
+ (node.startTime - root.startTime) / root.duration
177
+ ),
178
+ width: percentage(node.duration / root.duration),
179
+ }}
180
+ />
181
+ </span>
182
+ </button>
183
+ ))}
184
+ </DetailGroup>
185
+ ) : null}
186
+ {isEntrySpan ? (
187
+ <EntrySpanLinks getQuickLinks={getQuickLinks} span={selected.span} />
188
+ ) : null}
189
+ {selected.events?.length ? (
190
+ <DetailGroup name="Events">
191
+ {selected.events.map((event) => (
192
+ <Fragment key={event.key}>
193
+ <dl className={styles.spanDetailPanelAttribute}>
194
+ <dt className={styles.spanDetailPanelAttributeKey}>
195
+ {event.event.name}
196
+ </dt>
197
+ <dd className={styles.spanDetailPanelAttributeValue}>
198
+ {formatDuration(event.timestamp - root.startTime)}
199
+ </dd>
200
+ </dl>
201
+ {Object.entries(event.event.attributes).map(([key, value]) => (
202
+ <DetailLine
203
+ key={key}
204
+ name={`${event.event.name}.${key}`}
205
+ value={value}
206
+ />
207
+ ))}
208
+ </Fragment>
209
+ ))}
210
+ </DetailGroup>
211
+ ) : null}
212
+ {attributeGroups.length ? (
213
+ <DetailGroup name="Attributes">
214
+ {attributeGroups.map((group) => (
215
+ <Fragment key={group.prefix}>
216
+ {group.attributes.map(({ key, value }) => (
217
+ <DetailLine key={key} name={key} value={value} />
218
+ ))}
219
+ </Fragment>
220
+ ))}
221
+ </DetailGroup>
222
+ ) : null}
223
+ {resourceAttributeGroups.length ? (
224
+ <DetailGroup name="Resource">
225
+ {resourceAttributeGroups.map((group) => (
226
+ <Fragment key={group.prefix}>
227
+ {group.attributes.map(({ key, value }) => (
228
+ <DetailLine key={key} name={key} value={value} />
229
+ ))}
230
+ </Fragment>
231
+ ))}
232
+ </DetailGroup>
233
+ ) : null}
234
+ {selected.isInstrumentationHint ? null : (
235
+ <DetailGroup name="Library">
236
+ <DetailLine name="name" value={span.library.name} />
237
+ {span.library.version ? (
238
+ <DetailLine name="version" value={span.library.version} />
239
+ ) : null}
240
+ </DetailGroup>
241
+ )}
242
+ </>
243
+ );
244
+ };
245
+
246
+ export function SpanDetailPanel({
247
+ attached = false,
248
+ }: {
249
+ attached?: boolean;
250
+ }): ReactNode {
251
+ const {
252
+ state: { selected, isMobile, customPanelComponent },
253
+ dispatch,
254
+ } = useTraceViewer();
255
+
256
+ if (!selected) return null;
257
+
258
+ const { span } = selected;
259
+
260
+ return (
261
+ <div className={clsx(styles.spanDetailPanel, isMobile && styles.mobile)}>
262
+ {attached && !isMobile ? <PanelResizer /> : null}
263
+ <div className={styles.spanDetailPanelTop}>
264
+ <div className={styles.spanDetailPanelTopInfo}>
265
+ {/* Name/ID first */}
266
+ <span className={styles.spanDetailPanelName} title={span.name}>
267
+ {span.name}
268
+ </span>
269
+ {/* Right side: duration badge, separator, close */}
270
+ <div className={styles.spanDetailPanelCorner}>
271
+ {selected.isInstrumentationHint ? null : (
272
+ <span className={styles.spanDetailPanelDuration}>
273
+ {formatDuration(selected.duration)}
274
+ </span>
275
+ )}
276
+ <div className={styles.spanDetailPanelCloseVerticalRule} />
277
+ <button
278
+ aria-label="Close Span Details"
279
+ className={styles.spanDetailPanelClose}
280
+ onClick={() =>
281
+ dispatch({
282
+ type: 'deselect',
283
+ })
284
+ }
285
+ type="button"
286
+ >
287
+ <IconCross color="gray-700" size={20} />
288
+ </button>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ <div className={clsx('flex gap-3', styles.spanDetailPanelBody)}>
293
+ {customPanelComponent || <SpanDetailPanelBody />}
294
+ </div>
295
+ </div>
296
+ );
297
+ }
298
+
299
+ const EXPANDED_STORAGE_KEY_PREFIX = 'vc-trace-span-exp:';
300
+
301
+ function DetailGroup({
302
+ name,
303
+ className = '',
304
+ children,
305
+ }: {
306
+ name: string;
307
+ className?: string;
308
+ children: ReactNode;
309
+ }): ReactNode {
310
+ const [isExpanded, setIsExpanded] = useState(() => {
311
+ if (!('localStorage' in globalThis)) return true;
312
+ return (
313
+ localStorage.getItem(`${EXPANDED_STORAGE_KEY_PREFIX}${name}`) !== '0'
314
+ );
315
+ });
316
+
317
+ const toggleExpanded = useCallback(() => {
318
+ setIsExpanded((x) => {
319
+ const willBeExpanded = !x;
320
+ localStorage.setItem(
321
+ `${EXPANDED_STORAGE_KEY_PREFIX}${name}`,
322
+ String(Number(willBeExpanded))
323
+ );
324
+ return willBeExpanded;
325
+ });
326
+ }, [name]);
327
+
328
+ return (
329
+ <div
330
+ className={clsx(
331
+ styles.detailGroup,
332
+ isExpanded ? styles.expanded : styles.collapsed,
333
+ className
334
+ )}
335
+ >
336
+ <button
337
+ className={styles.detailHeading}
338
+ onClick={toggleExpanded}
339
+ type="button"
340
+ >
341
+ <span>{name}</span>
342
+ <IconChevronDown />
343
+ </button>
344
+ {isExpanded ? children : null}
345
+ </div>
346
+ );
347
+ }
348
+
349
+ const CommonHttpVerbs = new Set([
350
+ 'OPTIONS',
351
+ 'HEAD',
352
+ 'GET',
353
+ 'PUT',
354
+ 'POST',
355
+ 'DELETE',
356
+ ]);
357
+ const KnownSpans: Record<string, string> = {
358
+ 'Resolve Deployment': `How long it took Vercel's routing layer to retrieve details for the deployment.`,
359
+ 'Resolve Cache': `How long it took Vercel's routing layer to check for a cached function invocation.`,
360
+ 'Resolve Route': `How long it took Vercel's routing layer to resolve routing rules, including redirects and rewrites.`,
361
+ 'Invoke Middleware': `How long it took this middleware to start and run user code.`,
362
+ 'Invoke Function': `How long it took this Vercel Function to start and run user code.`,
363
+ waitUntil: `How long it took to run user code inside of waitUntil.`,
364
+ 'Start VM': `How long it took to start the Virtual Machine which contains the Vercel Function.`,
365
+ 'Spawn Node.js': `How long it took the Virtual Machine to start the Node.js runtime.`,
366
+ 'Spawn Python': `How long it took the Virtual Machine to start the Python runtime.`,
367
+ 'Init User Code': `How long it took to import the user code and run any globally-scoped code.`,
368
+ };
369
+
370
+ const ColdStartSpanNames = new Set([
371
+ 'Start VM',
372
+ 'Spawn Node.js',
373
+ 'Spawn Python',
374
+ 'Init User Code',
375
+ ]);
376
+
377
+ const getRegion = (
378
+ node: SpanNode | RootNode | null | undefined
379
+ ): string | null => {
380
+ if (!node || !('span' in node)) {
381
+ return null;
382
+ }
383
+
384
+ const attr = node.span.attributes;
385
+ const region = attr['vercel.region'] || attr['vercel.function.region'];
386
+ if (region) return String(region);
387
+ return null;
388
+ };
389
+
390
+ const getNote = (node: SpanNode): ReactNode => {
391
+ const { name } = node.span;
392
+ const region = getRegion(node);
393
+ const parentRegion = getRegion(node.parent);
394
+
395
+ if (node.span.attributes['vercel.middleware.internal'] === 'true') {
396
+ return 'The Vercel Toolbar created this middleware.';
397
+ } else if (
398
+ ColdStartSpanNames.has(name) ||
399
+ (name === 'Invoke Function' &&
400
+ node.children.some((x) => ColdStartSpanNames.has(x.span.name)))
401
+ ) {
402
+ return 'This function encountered a cold start.';
403
+ } else if (region && parentRegion && region !== parentRegion) {
404
+ return 'This request was routed across regions.';
405
+ }
406
+
407
+ return null;
408
+ };
409
+
410
+ const detectIsEntrySpan = (node: SpanNode): boolean => {
411
+ if (!node.isVercel) return false;
412
+ const { name } = node.span;
413
+ const spaceIndex = name.indexOf(' ');
414
+ if (spaceIndex === -1) return false;
415
+ const firstWord = name.substring(0, spaceIndex);
416
+ return CommonHttpVerbs.has(firstWord);
417
+ };
418
+
419
+ function InfraSpanDescription({ node }: { node: SpanNode }): ReactNode {
420
+ const { name } = node.span;
421
+ let description = KnownSpans[name];
422
+
423
+ if (!description && detectIsEntrySpan(node)) {
424
+ description = `How long it took for Vercel to process the request and send the response.`;
425
+ }
426
+
427
+ if (!description) return null;
428
+
429
+ const note = getNote(node);
430
+
431
+ return (
432
+ <DetailGroup name="What is this?">
433
+ <div className={styles.infraSpanDescription}>
434
+ <span>{description}</span>
435
+ {note ? <Note>{note}</Note> : note}
436
+ </div>
437
+ </DetailGroup>
438
+ );
439
+ }
440
+
441
+ const EntrySpanLinks = memo(function EntrySpanLinks({
442
+ getQuickLinks,
443
+ span,
444
+ }: {
445
+ getQuickLinks: GetQuickLinks;
446
+ span: Span;
447
+ }): ReactNode {
448
+ const links = getQuickLinks(span);
449
+
450
+ if (!links.length) return null;
451
+
452
+ return (
453
+ <DetailGroup name="Links">
454
+ {links.map(({ key, value }) => (
455
+ <EntrySpanLink key={key} link={value} name={key} />
456
+ ))}
457
+ </DetailGroup>
458
+ );
459
+ });
460
+
461
+ interface ResolvablePromise<T> extends Promise<T> {
462
+ __resolvedValue?: T;
463
+ __rejectedValue?: unknown;
464
+ }
465
+
466
+ function usePromise<T>(promise: ResolvablePromise<T>): T | undefined {
467
+ const [_, setLatest] = useState({});
468
+ const fulfilled =
469
+ promise.__resolvedValue !== undefined ||
470
+ promise.__rejectedValue !== undefined;
471
+ useEffect(() => {
472
+ if (fulfilled) return;
473
+
474
+ promise
475
+ .then((value) => {
476
+ promise.__resolvedValue = value;
477
+ })
478
+ .catch((err) => {
479
+ promise.__rejectedValue = err;
480
+ })
481
+ .finally(() => setLatest({}));
482
+ }, [promise, fulfilled]);
483
+
484
+ return promise.__resolvedValue;
485
+ }
486
+
487
+ function EntrySpanLink({
488
+ name,
489
+ link: linkPromise,
490
+ }: {
491
+ name: string;
492
+ link: Promise<QuickLinkValue>;
493
+ }): ReactNode {
494
+ const link = usePromise(linkPromise);
495
+
496
+ return (
497
+ <Link
498
+ as={link ? undefined : 'span'}
499
+ className={clsx(
500
+ styles.spanDetailPanelAttribute,
501
+ styles.spanDetailPanelQuickLink
502
+ )}
503
+ href={link?.href || ''}
504
+ >
505
+ <span className={styles.spanDetailPanelAttributeKey}>{name}</span>
506
+ <span
507
+ className={clsx(
508
+ styles.spanDetailPanelAttributeValue,
509
+ styles.spanDetailPanelQuickLinkLabel
510
+ )}
511
+ >
512
+ {link ? (
513
+ <>
514
+ {link.label}
515
+ {link.icon || <IconExternalSmall color="gray-900" />}
516
+ </>
517
+ ) : (
518
+ <>
519
+ <Skeleton height={18} width={32 + name.length * 8} />
520
+ <Skeleton height={18} rounded width={18} />
521
+ </>
522
+ )}
523
+ </span>
524
+ </Link>
525
+ );
526
+ }
527
+
528
+ function InstrumentationHint(): ReactNode {
529
+ return (
530
+ <DetailGroup name="What is this?">
531
+ <div className={styles.infraSpanDescription}>
532
+ <span>{`You can visualize your application's spans alongside Vercel's spans in this viewer.`}</span>
533
+ <ButtonLink
534
+ href="https://vercel.com/docs/session-tracing#adding-custom-spans"
535
+ size="small"
536
+ target="_blank"
537
+ >
538
+ Get Started
539
+ </ButtonLink>
540
+ </div>
541
+ </DetailGroup>
542
+ );
543
+ }
544
+
545
+ function DetailLine({
546
+ name,
547
+ value,
548
+ }: {
549
+ name: string;
550
+ value: unknown;
551
+ }): ReactNode {
552
+ return (
553
+ <dl className={styles.spanDetailPanelAttribute}>
554
+ <dt className={styles.spanDetailPanelAttributeKey}>{name}</dt>
555
+ <dd
556
+ className={styles.spanDetailPanelAttributeValue}
557
+ title={String(value)}
558
+ >
559
+ {String(value)}
560
+ </dd>
561
+ </dl>
562
+ );
563
+ }
564
+
565
+ interface ResizeGestureRef {
566
+ start: {
567
+ x: number;
568
+ width: number;
569
+ };
570
+ x: number;
571
+ width: number;
572
+ isHeld: boolean;
573
+ }
574
+
575
+ /**
576
+ * Resizer component for the trace viewer panel.
577
+ */
578
+ export function PanelResizer(): ReactNode {
579
+ const {
580
+ state: { panelWidth },
581
+ dispatch,
582
+ } = useTraceViewer();
583
+ const gestureRef = useRef<ResizeGestureRef>({
584
+ start: {
585
+ x: 0,
586
+ width: panelWidth,
587
+ },
588
+ x: 0,
589
+ width: panelWidth,
590
+ isHeld: false,
591
+ });
592
+ gestureRef.current.width = panelWidth;
593
+
594
+ const onPointerDown = useCallback<PointerEventHandler>((event) => {
595
+ if (event.pointerType !== 'mouse') return;
596
+ event.preventDefault();
597
+ const g = gestureRef.current;
598
+ g.start.x = event.clientX;
599
+ g.x = event.clientX;
600
+ g.start.width = g.width;
601
+ g.isHeld = true;
602
+ }, []);
603
+
604
+ const onDoubleClick = useCallback(() => {
605
+ dispatch({
606
+ type: 'setPanelWidth',
607
+ width: initialState.panelWidth,
608
+ });
609
+ }, [dispatch]);
610
+
611
+ useEffect(() => {
612
+ let nextFrame = 0;
613
+ const onFrame = (): void => {
614
+ const g = gestureRef.current;
615
+ const width = g.start.width + g.start.x - g.x;
616
+ dispatch({
617
+ type: 'setPanelWidth',
618
+ width,
619
+ });
620
+ };
621
+
622
+ const onPointerMove = (event: PointerEvent): void => {
623
+ if (!gestureRef.current.isHeld) return;
624
+ event.preventDefault();
625
+ gestureRef.current.x = event.clientX;
626
+ cancelAnimationFrame(nextFrame);
627
+ nextFrame = requestAnimationFrame(onFrame);
628
+ };
629
+ const onPointerUp = (): void => {
630
+ gestureRef.current.isHeld = false;
631
+ };
632
+
633
+ window.addEventListener('pointermove', onPointerMove);
634
+ window.addEventListener('pointerup', onPointerUp);
635
+
636
+ return () => {
637
+ window.removeEventListener('pointermove', onPointerMove);
638
+ window.removeEventListener('pointerup', onPointerUp);
639
+ };
640
+ }, [dispatch]);
641
+
642
+ return (
643
+ <div
644
+ aria-hidden
645
+ className={styles.spanDetailPanelResizer}
646
+ onDoubleClick={onDoubleClick}
647
+ onPointerDown={onPointerDown}
648
+ />
649
+ );
650
+ }