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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/dist/components/error-boundary.js +1 -1
  2. package/dist/components/event-list-view.d.ts +16 -1
  3. package/dist/components/event-list-view.d.ts.map +1 -1
  4. package/dist/components/event-list-view.js +196 -85
  5. package/dist/components/hook-actions.js +1 -1
  6. package/dist/components/index.d.ts +4 -0
  7. package/dist/components/index.d.ts.map +1 -1
  8. package/dist/components/index.js +5 -1
  9. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  10. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  11. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  12. package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
  13. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/event-list.js +46 -0
  15. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  16. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  17. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  18. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  19. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  20. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  21. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  22. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  23. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  24. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  25. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  26. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  27. package/dist/components/new-trace-viewer/components/span-markers.d.ts +46 -0
  28. package/dist/components/new-trace-viewer/components/span-markers.d.ts.map +1 -0
  29. package/dist/components/new-trace-viewer/components/span-markers.js +85 -0
  30. package/dist/components/new-trace-viewer/components/split-pane.d.ts +15 -0
  31. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  32. package/dist/components/new-trace-viewer/components/split-pane.js +105 -0
  33. package/dist/components/new-trace-viewer/components/timeline.d.ts +36 -0
  34. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  35. package/dist/components/new-trace-viewer/components/timeline.js +254 -0
  36. package/dist/components/new-trace-viewer/components/timeline.module.css +46 -0
  37. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
  38. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
  39. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +19 -0
  40. package/dist/components/new-trace-viewer/components/use-row-window.d.ts +34 -0
  41. package/dist/components/new-trace-viewer/components/use-row-window.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/components/use-row-window.js +107 -0
  43. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  44. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  45. package/dist/components/new-trace-viewer/context.js +47 -0
  46. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  47. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  48. package/dist/components/new-trace-viewer/icons.js +15 -0
  49. package/dist/components/new-trace-viewer/search.d.ts +19 -0
  50. package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
  51. package/dist/components/new-trace-viewer/search.js +134 -0
  52. package/dist/components/new-trace-viewer/trace-viewer.d.ts +11 -0
  53. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/trace-viewer.js +462 -0
  55. package/dist/components/new-trace-viewer/types.d.ts +3 -0
  56. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  57. package/dist/components/new-trace-viewer/types.js +2 -0
  58. package/dist/components/new-trace-viewer/utils.d.ts +61 -0
  59. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  60. package/dist/components/new-trace-viewer/utils.js +345 -0
  61. package/dist/components/run-trace-view.js +1 -1
  62. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  63. package/dist/components/sidebar/attribute-panel.js +129 -102
  64. package/dist/components/sidebar/attributes-block.d.ts +28 -0
  65. package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
  66. package/dist/components/sidebar/attributes-block.js +81 -0
  67. package/dist/components/sidebar/conversation-view.js +1 -1
  68. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  69. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  70. package/dist/components/sidebar/copyable-data-block.js +16 -15
  71. package/dist/components/sidebar/detail-card.d.ts +5 -6
  72. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  73. package/dist/components/sidebar/detail-card.js +32 -16
  74. package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
  75. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  76. package/dist/components/sidebar/entity-detail-panel.js +17 -35
  77. package/dist/components/sidebar/events-list.d.ts +3 -1
  78. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  79. package/dist/components/sidebar/events-list.js +36 -22
  80. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  81. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  82. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  83. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  84. package/dist/components/sidebar/span-detail-merge.d.ts +16 -0
  85. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  86. package/dist/components/sidebar/span-detail-merge.js +53 -0
  87. package/dist/components/stream-viewer.d.ts +1 -1
  88. package/dist/components/stream-viewer.d.ts.map +1 -1
  89. package/dist/components/stream-viewer.js +3 -27
  90. package/dist/components/trace-viewer/components/map.js +1 -1
  91. package/dist/components/trace-viewer/components/markers.js +1 -1
  92. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  93. package/dist/components/trace-viewer/components/node.js +2 -1
  94. package/dist/components/trace-viewer/components/search-input.js +1 -1
  95. package/dist/components/trace-viewer/components/search.js +1 -1
  96. package/dist/components/trace-viewer/components/span-content.js +1 -1
  97. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  98. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  99. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  100. package/dist/components/trace-viewer/components/ui.js +1 -1
  101. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  102. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  103. package/dist/components/trace-viewer/context.js +1 -1
  104. package/dist/components/trace-viewer/index.js +1 -1
  105. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  106. package/dist/components/trace-viewer/trace-viewer.module.css +4 -4
  107. package/dist/components/trace-viewer/types.js +1 -1
  108. package/dist/components/trace-viewer/util/constants.js +1 -1
  109. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  110. package/dist/components/trace-viewer/util/timing.d.ts +3 -3
  111. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  112. package/dist/components/trace-viewer/util/timing.js +8 -11
  113. package/dist/components/trace-viewer/util/tree.js +1 -1
  114. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  115. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  116. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  117. package/dist/components/trace-viewer/worker.js +1 -1
  118. package/dist/components/trace-viewer-new.d.ts +14 -0
  119. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  120. package/dist/components/trace-viewer-new.js +21 -0
  121. package/dist/components/ui/alert.js +1 -1
  122. package/dist/components/ui/button.d.ts +10 -0
  123. package/dist/components/ui/button.d.ts.map +1 -0
  124. package/dist/components/ui/button.js +46 -0
  125. package/dist/components/ui/card.js +1 -1
  126. package/dist/components/ui/context-card.d.ts +47 -0
  127. package/dist/components/ui/context-card.d.ts.map +1 -0
  128. package/dist/components/ui/context-card.js +471 -0
  129. package/dist/components/ui/data-inspector.d.ts +22 -0
  130. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  131. package/dist/components/ui/data-inspector.js +103 -13
  132. package/dist/components/ui/decrypt-button.js +1 -1
  133. package/dist/components/ui/error-card.js +1 -1
  134. package/dist/components/ui/error-stack-block.d.ts +17 -10
  135. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  136. package/dist/components/ui/error-stack-block.js +49 -30
  137. package/dist/components/ui/icon-button.d.ts +19 -0
  138. package/dist/components/ui/icon-button.d.ts.map +1 -0
  139. package/dist/components/ui/icon-button.js +29 -0
  140. package/dist/components/ui/inspector-theme.d.ts +9 -10
  141. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  142. package/dist/components/ui/inspector-theme.js +39 -72
  143. package/dist/components/ui/kbd.d.ts +6 -0
  144. package/dist/components/ui/kbd.d.ts.map +1 -0
  145. package/dist/components/ui/kbd.js +6 -0
  146. package/dist/components/ui/load-more-button.js +1 -1
  147. package/dist/components/ui/menu-dropdown.js +1 -1
  148. package/dist/components/ui/skeleton.js +1 -1
  149. package/dist/components/ui/spinner.js +1 -1
  150. package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
  151. package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
  152. package/dist/components/ui/timestamp-tooltip.js +80 -132
  153. package/dist/components/ui/tooltip.d.ts +10 -0
  154. package/dist/components/ui/tooltip.d.ts.map +1 -0
  155. package/dist/components/ui/tooltip.js +12 -0
  156. package/dist/components/workflow-trace-view.d.ts +1 -3
  157. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  158. package/dist/components/workflow-trace-view.js +11 -13
  159. package/dist/components/workflow-traces/event-colors.d.ts +1 -0
  160. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  161. package/dist/components/workflow-traces/event-colors.js +12 -1
  162. package/dist/components/workflow-traces/trace-colors.js +1 -1
  163. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  164. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  165. package/dist/components/workflow-traces/trace-span-construction.js +19 -5
  166. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  167. package/dist/hooks/use-dark-mode.js +1 -1
  168. package/dist/hooks/use-intersection-observer.d.ts +17 -0
  169. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  170. package/dist/hooks/use-intersection-observer.js +29 -0
  171. package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
  172. package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
  173. package/dist/hooks/use-load-more-on-scroll.js +28 -0
  174. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  175. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  176. package/dist/hooks/use-reduced-motion.js +29 -0
  177. package/dist/index.d.ts +3 -1
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +3 -2
  180. package/dist/lib/event-analysis.js +1 -1
  181. package/dist/lib/event-materialization.js +1 -1
  182. package/dist/lib/exact-event-search-id.d.ts +25 -0
  183. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  184. package/dist/lib/exact-event-search-id.js +39 -0
  185. package/dist/lib/hydration.d.ts +6 -0
  186. package/dist/lib/hydration.d.ts.map +1 -1
  187. package/dist/lib/hydration.js +154 -2
  188. package/dist/lib/stream-display.d.ts +18 -0
  189. package/dist/lib/stream-display.d.ts.map +1 -0
  190. package/dist/lib/stream-display.js +30 -0
  191. package/dist/lib/toast.js +1 -1
  192. package/dist/lib/trace-builder.d.ts +15 -0
  193. package/dist/lib/trace-builder.d.ts.map +1 -1
  194. package/dist/lib/trace-builder.js +29 -3
  195. package/dist/lib/utils.d.ts +20 -9
  196. package/dist/lib/utils.d.ts.map +1 -1
  197. package/dist/lib/utils.js +74 -31
  198. package/dist/lib/zstd-browser-decoder.d.ts +7 -0
  199. package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
  200. package/dist/lib/zstd-browser-decoder.js +41 -0
  201. package/dist/styles.css +484 -1
  202. package/package.json +10 -5
  203. package/src/components/event-list-view.tsx +279 -104
  204. package/src/components/index.ts +7 -0
  205. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  206. package/src/components/new-trace-viewer/components/event-list.tsx +136 -0
  207. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  208. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  209. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  210. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  211. package/src/components/new-trace-viewer/components/span-markers.tsx +173 -0
  212. package/src/components/new-trace-viewer/components/split-pane.tsx +198 -0
  213. package/src/components/new-trace-viewer/components/timeline.module.css +46 -0
  214. package/src/components/new-trace-viewer/components/timeline.tsx +744 -0
  215. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +94 -0
  216. package/src/components/new-trace-viewer/components/use-row-window.ts +140 -0
  217. package/src/components/new-trace-viewer/context.tsx +90 -0
  218. package/src/components/new-trace-viewer/icons.tsx +79 -0
  219. package/src/components/new-trace-viewer/search.ts +178 -0
  220. package/src/components/new-trace-viewer/trace-viewer.tsx +843 -0
  221. package/src/components/new-trace-viewer/types.ts +2 -0
  222. package/src/components/new-trace-viewer/utils.test.ts +182 -0
  223. package/src/components/new-trace-viewer/utils.ts +519 -0
  224. package/src/components/sidebar/attribute-panel.tsx +194 -240
  225. package/src/components/sidebar/attributes-block.tsx +173 -0
  226. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  227. package/src/components/sidebar/detail-card.tsx +119 -59
  228. package/src/components/sidebar/entity-detail-panel.tsx +55 -111
  229. package/src/components/sidebar/events-list.tsx +85 -96
  230. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  231. package/src/components/sidebar/span-detail-merge.ts +65 -0
  232. package/src/components/stream-viewer.tsx +18 -40
  233. package/src/components/trace-viewer/components/node.tsx +3 -2
  234. package/src/components/trace-viewer/trace-viewer.module.css +4 -4
  235. package/src/components/trace-viewer/util/timing.ts +7 -11
  236. package/src/components/trace-viewer-new.tsx +55 -0
  237. package/src/components/ui/button.tsx +68 -0
  238. package/src/components/ui/context-card.tsx +784 -0
  239. package/src/components/ui/data-inspector.tsx +244 -22
  240. package/src/components/ui/error-stack-block.tsx +84 -55
  241. package/src/components/ui/icon-button.tsx +53 -0
  242. package/src/components/ui/inspector-theme.ts +39 -81
  243. package/src/components/ui/kbd.tsx +21 -0
  244. package/src/components/ui/timestamp-tooltip.tsx +132 -194
  245. package/src/components/ui/tooltip.tsx +47 -0
  246. package/src/components/workflow-trace-view.tsx +19 -15
  247. package/src/components/workflow-traces/event-colors.ts +12 -0
  248. package/src/components/workflow-traces/trace-span-construction.ts +24 -5
  249. package/src/hooks/use-intersection-observer.ts +57 -0
  250. package/src/hooks/use-load-more-on-scroll.ts +49 -0
  251. package/src/hooks/use-reduced-motion.ts +34 -0
  252. package/src/index.ts +9 -0
  253. package/src/lib/exact-event-search-id.ts +67 -0
  254. package/src/lib/hydration.ts +166 -1
  255. package/src/lib/stream-display.ts +48 -0
  256. package/src/lib/trace-builder.ts +34 -2
  257. package/src/lib/utils.ts +83 -31
  258. package/src/lib/zstd-browser-decoder.ts +42 -0
  259. package/src/styles.css +484 -1
  260. package/dist/components/error-boundary.js.map +0 -1
  261. package/dist/components/event-list-view.js.map +0 -1
  262. package/dist/components/hook-actions.js.map +0 -1
  263. package/dist/components/index.js.map +0 -1
  264. package/dist/components/run-trace-view.js.map +0 -1
  265. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  266. package/dist/components/sidebar/conversation-view.js.map +0 -1
  267. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  268. package/dist/components/sidebar/detail-card.js.map +0 -1
  269. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  270. package/dist/components/sidebar/events-list.js.map +0 -1
  271. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  272. package/dist/components/stream-viewer.js.map +0 -1
  273. package/dist/components/trace-viewer/components/map.js.map +0 -1
  274. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  275. package/dist/components/trace-viewer/components/node.js.map +0 -1
  276. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  277. package/dist/components/trace-viewer/components/search.js.map +0 -1
  278. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  279. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  280. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  281. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  282. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  283. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  284. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  285. package/dist/components/trace-viewer/context.js.map +0 -1
  286. package/dist/components/trace-viewer/index.js.map +0 -1
  287. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  288. package/dist/components/trace-viewer/types.js.map +0 -1
  289. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  290. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  291. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  292. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  293. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  294. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  295. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  296. package/dist/components/trace-viewer/worker.js.map +0 -1
  297. package/dist/components/ui/alert.js.map +0 -1
  298. package/dist/components/ui/card.js.map +0 -1
  299. package/dist/components/ui/data-inspector.js.map +0 -1
  300. package/dist/components/ui/decrypt-button.js.map +0 -1
  301. package/dist/components/ui/error-card.js.map +0 -1
  302. package/dist/components/ui/error-stack-block.js.map +0 -1
  303. package/dist/components/ui/inspector-theme.js.map +0 -1
  304. package/dist/components/ui/load-more-button.js.map +0 -1
  305. package/dist/components/ui/menu-dropdown.js.map +0 -1
  306. package/dist/components/ui/skeleton.js.map +0 -1
  307. package/dist/components/ui/spinner.js.map +0 -1
  308. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  309. package/dist/components/workflow-trace-view.js.map +0 -1
  310. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  311. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  312. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  313. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  314. package/dist/hooks/use-dark-mode.js.map +0 -1
  315. package/dist/index.js.map +0 -1
  316. package/dist/lib/event-analysis.js.map +0 -1
  317. package/dist/lib/event-materialization.js.map +0 -1
  318. package/dist/lib/hydration.js.map +0 -1
  319. package/dist/lib/toast.js.map +0 -1
  320. package/dist/lib/trace-builder.js.map +0 -1
  321. package/dist/lib/utils.js.map +0 -1
@@ -17,6 +17,11 @@ import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
17
17
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
18
18
  import { createPortal } from 'react-dom';
19
19
  import { useToast } from '../lib/toast';
20
+ import {
21
+ buildTrace,
22
+ filterSpanRawEvents,
23
+ type TraceWithMeta,
24
+ } from '../lib/trace-builder';
20
25
  import { ErrorBoundary } from './error-boundary';
21
26
  import {
22
27
  EntityDetailPanel,
@@ -36,7 +41,6 @@ import {
36
41
  getCustomSpanClassName,
37
42
  getCustomSpanEventClassName,
38
43
  } from './workflow-traces/trace-colors';
39
- import { buildTrace, type TraceWithMeta } from '../lib/trace-builder';
40
44
 
41
45
  /**
42
46
  * While a run is live, continuously grow root.duration and rescale so the
@@ -776,7 +780,6 @@ export const WorkflowTraceViewer = ({
776
780
  encryptionKey,
777
781
  onDecrypt,
778
782
  isDecrypting = false,
779
- hasEncryptedData = false,
780
783
  }: {
781
784
  run: WorkflowRun;
782
785
  events: Event[];
@@ -819,8 +822,6 @@ export const WorkflowTraceViewer = ({
819
822
  onDecrypt?: () => void;
820
823
  /** Whether the encryption key is currently being fetched */
821
824
  isDecrypting?: boolean;
822
- /** Run-level hint: the run contains encrypted data (from probe). */
823
- hasEncryptedData?: boolean;
824
825
  }) => {
825
826
  const toast = useToast();
826
827
  const [selectedSpan, setSelectedSpan] = useState<SelectedSpanInfo | null>(
@@ -865,12 +866,14 @@ export const WorkflowTraceViewer = ({
865
866
  const handleSelectionChange = useCallback(
866
867
  (info: SelectedSpanInfo | null) => {
867
868
  if (info) {
868
- // Filter raw events by the selected span's correlationId (stepId/hookId)
869
- // This bypasses the trace worker pipeline entirely.
870
- const correlationId = info.spanId;
871
- const rawEvents = correlationId
872
- ? events.filter((e) => e.correlationId === correlationId)
873
- : [];
869
+ // Filter raw events for the selected span: child spans match on
870
+ // correlationId (stepId/hookId), the run root span gets run-level
871
+ // events. This bypasses the trace worker pipeline entirely.
872
+ const rawEvents = filterSpanRawEvents(
873
+ events,
874
+ info.resource,
875
+ info.spanId
876
+ );
874
877
  setSelectedSpan({ ...info, rawEvents });
875
878
  } else {
876
879
  setSelectedSpan(null);
@@ -884,8 +887,10 @@ export const WorkflowTraceViewer = ({
884
887
  const correlationId = selectedSpan?.spanId;
885
888
  if (!correlationId) return;
886
889
 
887
- const nextRawEvents = events.filter(
888
- (e) => e.correlationId === correlationId
890
+ const nextRawEvents = filterSpanRawEvents(
891
+ events,
892
+ selectedSpan?.resource,
893
+ correlationId
889
894
  );
890
895
  setSelectedSpan((prev) => {
891
896
  if (!prev || prev.spanId !== correlationId) {
@@ -907,7 +912,7 @@ export const WorkflowTraceViewer = ({
907
912
  rawEvents: nextRawEvents,
908
913
  };
909
914
  });
910
- }, [events, selectedSpan?.spanId]);
915
+ }, [events, selectedSpan?.spanId, selectedSpan?.resource]);
911
916
 
912
917
  // Reset selected span when navigating to a different run
913
918
  useEffect(() => {
@@ -977,7 +982,7 @@ export const WorkflowTraceViewer = ({
977
982
  }
978
983
 
979
984
  return (
980
- <div className="relative w-full h-full flex">
985
+ <div className="relative w-full h-full flex flex-row">
981
986
  {/* Timeline (takes remaining space) */}
982
987
  <div className="flex-1 min-w-0 relative">
983
988
  <TraceViewerContextProvider
@@ -1164,7 +1169,6 @@ export const WorkflowTraceViewer = ({
1164
1169
  onDecrypt={onDecrypt}
1165
1170
  isDecrypting={isDecrypting}
1166
1171
  selectedSpan={selectedSpan}
1167
- hasEncryptedData={hasEncryptedData}
1168
1172
  />
1169
1173
  </ErrorBoundary>
1170
1174
  </div>
@@ -22,6 +22,7 @@ export interface EventColorPalette {
22
22
  * - Red for failures (step_failed, run_failed)
23
23
  * - Orange/yellow for retries (step_retrying)
24
24
  * - Purple for webhook-related events
25
+ * - Teal for attribute changes (attr_set)
25
26
  * - Blue otherwise (default)
26
27
  */
27
28
  export function getEventColor(
@@ -64,6 +65,17 @@ export function getEventColor(
64
65
  };
65
66
  }
66
67
 
68
+ // Attribute changes - Teal
69
+ if (eventType === 'attr_set') {
70
+ return {
71
+ color: 'var(--ds-teal-900)',
72
+ background: 'var(--ds-teal-200)',
73
+ border: 'var(--ds-teal-500)',
74
+ text: 'var(--ds-teal-900)',
75
+ secondary: 'var(--ds-gray-900)',
76
+ };
77
+ }
78
+
67
79
  // Default - Blue
68
80
  return {
69
81
  color: 'var(--ds-blue-600)',
@@ -26,6 +26,7 @@ const MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([
26
26
  'run_failed',
27
27
  'wait_created',
28
28
  'wait_completed',
29
+ 'attr_set',
29
30
  ]);
30
31
 
31
32
  /**
@@ -59,7 +60,7 @@ export const waitEventsToWaitEntity = (
59
60
  waitId: string;
60
61
  runId: string;
61
62
  createdAt: Date;
62
- resumeAt: Date;
63
+ resumeAt?: Date;
63
64
  completedAt?: Date;
64
65
  } | null => {
65
66
  const startEvent = events.find((event) => event.eventType === 'wait_created');
@@ -73,7 +74,9 @@ export const waitEventsToWaitEntity = (
73
74
  waitId: startEvent.correlationId,
74
75
  runId: startEvent.runId,
75
76
  createdAt: startEvent.createdAt,
76
- resumeAt: startEvent.eventData?.resumeAt,
77
+ resumeAt: startEvent.eventData?.resumeAt
78
+ ? new Date(startEvent.eventData.resumeAt)
79
+ : undefined,
77
80
  completedAt: completedEvent?.createdAt,
78
81
  };
79
82
  };
@@ -81,13 +84,29 @@ export const waitEventsToWaitEntity = (
81
84
  /**
82
85
  * Converts a workflow Wait to an OpenTelemetry Span
83
86
  */
84
- export function waitToSpan(events: Event[], maxEndTime: Date): Span | null {
87
+ export function waitToSpan(
88
+ events: Event[],
89
+ maxEndTime: Date,
90
+ fallbackEndTime = maxEndTime
91
+ ): Span | null {
85
92
  const wait = waitEventsToWaitEntity(events);
86
93
  if (!wait) {
87
94
  return null;
88
95
  }
89
96
  const startTime = wait.createdAt;
90
- const endTime = wait.completedAt ?? maxEndTime;
97
+ const startMs = startTime.getTime();
98
+ let endTime = wait.completedAt;
99
+ if (!endTime) {
100
+ const fallbackCap =
101
+ wait.resumeAt && wait.resumeAt.getTime() < fallbackEndTime.getTime()
102
+ ? wait.resumeAt
103
+ : fallbackEndTime;
104
+ endTime =
105
+ maxEndTime.getTime() > startMs &&
106
+ maxEndTime.getTime() < fallbackCap.getTime()
107
+ ? maxEndTime
108
+ : fallbackCap;
109
+ }
91
110
  const start = dateToOtelTime(startTime);
92
111
  const end = dateToOtelTime(endTime);
93
112
  const duration = calculateDuration(startTime, endTime);
@@ -296,7 +315,7 @@ export function hookToSpan(hookEvents: Event[], maxEndTime: Date): Span | null {
296
315
 
297
316
  return {
298
317
  spanId: String(hook.hookId),
299
- name: String(hook.hookId),
318
+ name: hook.token ?? String(hook.hookId),
300
319
  kind: 1, // INTERNAL span kind
301
320
  resource: 'hook',
302
321
  library: WORKFLOW_LIBRARY,
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import type { RefObject } from 'react';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ interface UseIntersectionObserverArgs
7
+ extends Omit<IntersectionObserverInit, 'root'> {
8
+ /**
9
+ * Scroll container the target is observed within. Defaults to the viewport.
10
+ * Passed as a ref so it resolves after commit (when the node exists).
11
+ */
12
+ rootRef?: RefObject<Element | null>;
13
+ freezeOnceVisible?: boolean;
14
+ }
15
+
16
+ /**
17
+ * Returns the latest IntersectionObserverEntry for `elementRef`.
18
+ * Ported from vercel/front's `@vercel/hooks` `useIntersectionObserver`, with
19
+ * `root` threaded as a ref so we can observe inside a custom scroll container.
20
+ */
21
+ export function useIntersectionObserver(
22
+ elementRef: RefObject<Element | null>,
23
+ {
24
+ threshold = 0,
25
+ rootRef,
26
+ rootMargin = '0%',
27
+ freezeOnceVisible = false,
28
+ }: UseIntersectionObserverArgs = {}
29
+ ): IntersectionObserverEntry | undefined {
30
+ const [entry, setEntry] = useState<IntersectionObserverEntry>();
31
+
32
+ const frozen = entry?.isIntersecting && freezeOnceVisible;
33
+
34
+ useEffect(() => {
35
+ const node = elementRef?.current;
36
+
37
+ const hasIOSupport = !!window.IntersectionObserver;
38
+
39
+ if (!hasIOSupport || frozen || !node) return;
40
+
41
+ const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
42
+ setEntry(entry);
43
+ };
44
+
45
+ const observer = new IntersectionObserver(updateEntry, {
46
+ threshold,
47
+ root: rootRef?.current ?? null,
48
+ rootMargin,
49
+ });
50
+
51
+ observer.observe(node);
52
+
53
+ return () => observer.disconnect();
54
+ }, [elementRef, threshold, rootRef, rootMargin, frozen]);
55
+
56
+ return entry;
57
+ }
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+
3
+ import type { RefObject } from 'react';
4
+ import { useEffect, useRef } from 'react';
5
+ import { useIntersectionObserver } from './use-intersection-observer';
6
+
7
+ interface UseLoadMoreOnScrollOptions {
8
+ hasMore: boolean;
9
+ isLoadingMore: boolean;
10
+ /** Scroll container the sentinel lives in. Defaults to the viewport. */
11
+ rootRef?: RefObject<Element | null>;
12
+ rootMargin?: string;
13
+ }
14
+
15
+ /**
16
+ * Triggers `loadMore` when a sentinel element scrolls into view.
17
+ * Returns a ref to attach to an invisible sentinel `<div>` placed after the
18
+ * list content. Ported from vercel/front's `@vercel/hooks`
19
+ * `useLoadMoreOnScroll`.
20
+ *
21
+ * Because it keys off `isIntersecting`/`isLoadingMore`, it self-continues:
22
+ * if the sentinel is still in view once a page finishes loading, the next
23
+ * page is requested automatically.
24
+ */
25
+ export function useLoadMoreOnScroll(
26
+ loadMore: () => void,
27
+ {
28
+ hasMore,
29
+ isLoadingMore,
30
+ rootRef,
31
+ rootMargin = '400px',
32
+ }: UseLoadMoreOnScrollOptions
33
+ ) {
34
+ const sentinelRef = useRef<HTMLDivElement>(null);
35
+ const entry = useIntersectionObserver(sentinelRef, { rootRef, rootMargin });
36
+
37
+ const loadMoreRef = useRef(loadMore);
38
+ useEffect(() => {
39
+ loadMoreRef.current = loadMore;
40
+ }, [loadMore]);
41
+
42
+ useEffect(() => {
43
+ if (entry?.isIntersecting && hasMore && !isLoadingMore) {
44
+ loadMoreRef.current();
45
+ }
46
+ }, [entry?.isIntersecting, hasMore, isLoadingMore]);
47
+
48
+ return sentinelRef;
49
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+
5
+ const QUERY = '(prefers-reduced-motion: reduce)';
6
+
7
+ /**
8
+ * Hook that detects whether the user has requested reduced motion and reacts
9
+ * to changes. Mirrors the `prefers-reduced-motion: reduce` media query so
10
+ * components can skip or shorten animations for users with motion
11
+ * sensitivities (e.g. vestibular disorders).
12
+ *
13
+ * @returns `true` if the user prefers reduced motion, `false` otherwise
14
+ */
15
+ export const useReducedMotion = (): boolean => {
16
+ const [reduced, setReduced] = useState(() => {
17
+ if (typeof window === 'undefined' || !window.matchMedia) return false;
18
+ return window.matchMedia(QUERY).matches;
19
+ });
20
+
21
+ useEffect(() => {
22
+ if (typeof window === 'undefined' || !window.matchMedia) return;
23
+
24
+ const media = window.matchMedia(QUERY);
25
+ const onChange = (): void => setReduced(media.matches);
26
+
27
+ media.addEventListener('change', onChange);
28
+ onChange();
29
+
30
+ return () => media.removeEventListener('change', onChange);
31
+ }, []);
32
+
33
+ return reduced;
34
+ };
package/src/index.ts CHANGED
@@ -14,6 +14,13 @@ export {
14
14
  waitEventsToWaitEntity,
15
15
  } from './components/workflow-traces/trace-span-construction';
16
16
  export type { EventAnalysis } from './lib/event-analysis';
17
+ export {
18
+ parseExactWorkflowSearchId,
19
+ looksLikeWorkflowIdSearchInput,
20
+ type ExactWorkflowSearchId,
21
+ type ExactWorkflowSearchIdKind,
22
+ type ExactIdSearchResult,
23
+ } from './lib/exact-event-search-id';
17
24
  export {
18
25
  analyzeEvents,
19
26
  hasPendingHooksFromEvents,
@@ -50,12 +57,14 @@ export {
50
57
  STREAM_REF_TYPE,
51
58
  truncateId,
52
59
  } from './lib/hydration';
60
+ export type { DecodedStreamChunkSource } from './lib/stream-display';
53
61
  export type { ToastAdapter } from './lib/toast';
54
62
  export { ToastProvider, useToast } from './lib/toast';
55
63
  export type { StreamStep } from './lib/utils';
56
64
  export {
57
65
  extractConversation,
58
66
  formatDuration,
67
+ formatDurationPrecise,
59
68
  identifyStreamSteps,
60
69
  isDoStreamStep,
61
70
  } from './lib/utils';
@@ -0,0 +1,67 @@
1
+ import type { Event } from '@workflow/world';
2
+
3
+ const WORKFLOW_ULID_BODY = '[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}';
4
+
5
+ const STEP_ID_PATTERN = new RegExp(`^step_(${WORKFLOW_ULID_BODY})$`, 'i');
6
+ const WAIT_ID_PATTERN = new RegExp(`^wait_(${WORKFLOW_ULID_BODY})$`, 'i');
7
+ const HOOK_ID_PATTERN = new RegExp(`^hook_(${WORKFLOW_ULID_BODY})$`, 'i');
8
+ const EVENT_ID_PATTERN = new RegExp(`^evnt_(${WORKFLOW_ULID_BODY})$`, 'i');
9
+
10
+ const WORKFLOW_ID_PREFIX_PATTERN = /^(step_|wait_|hook_|evnt_|wrun_)/i;
11
+
12
+ export type ExactWorkflowSearchIdKind = 'step' | 'wait' | 'hook' | 'event';
13
+
14
+ export type ExactWorkflowSearchId = {
15
+ kind: ExactWorkflowSearchIdKind;
16
+ id: string;
17
+ };
18
+
19
+ export type ExactIdSearchResult =
20
+ | { status: 'ok'; events: Event[]; truncated?: boolean }
21
+ | { status: 'not_found' }
22
+ | { status: 'error'; message: string };
23
+
24
+ function matchPrefixedId(
25
+ pattern: RegExp,
26
+ prefix: 'step' | 'wait' | 'hook' | 'evnt',
27
+ kind: ExactWorkflowSearchIdKind,
28
+ query: string
29
+ ): ExactWorkflowSearchId | null {
30
+ const match = query.match(pattern);
31
+ if (!match) {
32
+ return null;
33
+ }
34
+ return { kind, id: `${prefix}_${match[1].toUpperCase()}` };
35
+ }
36
+
37
+ /**
38
+ * Returns a parsed workflow ID when `query` is a full step, wait, hook, or event ID.
39
+ * Partial IDs and run IDs (`wrun_`) are ignored. ULID bodies are matched case-insensitively
40
+ * and normalized to uppercase in the returned ID.
41
+ */
42
+ export function parseExactWorkflowSearchId(
43
+ query: string
44
+ ): ExactWorkflowSearchId | null {
45
+ const trimmed = query.trim();
46
+ if (!trimmed) {
47
+ return null;
48
+ }
49
+
50
+ return (
51
+ matchPrefixedId(STEP_ID_PATTERN, 'step', 'step', trimmed) ??
52
+ matchPrefixedId(WAIT_ID_PATTERN, 'wait', 'wait', trimmed) ??
53
+ matchPrefixedId(HOOK_ID_PATTERN, 'hook', 'hook', trimmed) ??
54
+ matchPrefixedId(EVENT_ID_PATTERN, 'evnt', 'event', trimmed)
55
+ );
56
+ }
57
+
58
+ /** True when input looks like the user is attempting an ID search (including partial). */
59
+ export function looksLikeWorkflowIdSearchInput(query: string): boolean {
60
+ const trimmed = query.trim();
61
+ if (!WORKFLOW_ID_PREFIX_PATTERN.test(trimmed)) {
62
+ return false;
63
+ }
64
+ // Distinguish IDs (contain digits) from event-type strings like step_started.
65
+ // Assumes workflow event types do not include digits in their names.
66
+ return /\d/.test(trimmed);
67
+ }
@@ -58,12 +58,60 @@ function base64ToArrayBuffer(base64: string): ArrayBuffer {
58
58
  // Web revivers (browser-safe, no Buffer dependency)
59
59
  // ---------------------------------------------------------------------------
60
60
 
61
+ /**
62
+ * Build a reviver for one of the built-in `Error` subclasses (e.g.
63
+ * `TypeError`, `RangeError`). The constructor for the named subclass is
64
+ * resolved off `globalThis` at call time so the produced instance has the
65
+ * correct prototype chain in the consumer realm. Falls back to a generic
66
+ * `Error` (with `name` set) if the global isn't available, which keeps the
67
+ * o11y UI rendering even on exotic browsers.
68
+ *
69
+ * `cause` is passed through `ErrorOptions` to the constructor when present,
70
+ * matching `getCommonRevivers` in `@workflow/core` so the resulting `cause`
71
+ * property has the same semantics (non-enumerable, set by the engine) as a
72
+ * freshly thrown Error in the consumer realm. The `'cause' in value` check
73
+ * preserves the distinction between "no cause" and "cause is undefined".
74
+ */
75
+ function makeWebErrorSubclassReviver(
76
+ name:
77
+ | 'EvalError'
78
+ | 'RangeError'
79
+ | 'ReferenceError'
80
+ | 'SyntaxError'
81
+ | 'TypeError'
82
+ | 'URIError'
83
+ ) {
84
+ return (value: { message: string; stack?: string; cause?: unknown }) => {
85
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
86
+ const Ctor = (globalThis as Record<string, any>)[name] as
87
+ | ErrorConstructor
88
+ | undefined;
89
+ let error: Error;
90
+ if (typeof Ctor === 'function') {
91
+ error = new Ctor(value.message, opts);
92
+ } else {
93
+ // Fallback path: no built-in subclass available (exotic env). Construct
94
+ // a plain Error with the right `name` and copy `cause` manually since
95
+ // the base Error constructor is what we actually called.
96
+ error = Object.assign(new Error(value.message, opts), { name });
97
+ }
98
+ if (value.stack !== undefined) error.stack = value.stack;
99
+ return error;
100
+ };
101
+ }
102
+
61
103
  /**
62
104
  * Get the web-specific revivers for hydrating serialized data.
63
105
  *
64
106
  * Uses `atob()` for base64 decoding (no Node.js Buffer dependency).
65
107
  * All types are revived as real instances (Date, Map, Set, URL,
66
108
  * URLSearchParams, Headers, Error, etc.).
109
+ *
110
+ * NOTE: this set must mirror the keys in `SerializableSpecial` (see
111
+ * `@workflow/core/serialization/types`). Any reducer key added on the
112
+ * serialization side that isn't covered here will cause `devalue.unflatten`
113
+ * to throw `Unknown type X`, which `hydrateResourceIO` swallows and
114
+ * surfaces as a "Failed to load resource details" banner in the o11y UI.
67
115
  */
68
116
  export function getWebRevivers(): Revivers {
69
117
  function reviveArrayBuffer(value: string): ArrayBuffer {
@@ -83,10 +131,120 @@ export function getWebRevivers(): Revivers {
83
131
  BigUint64Array: (value: string) =>
84
132
  new BigUint64Array(reviveArrayBuffer(value)),
85
133
  Date: (value) => new Date(value),
134
+
135
+ // Error family. The reducer side (see
136
+ // `packages/core/src/serialization/reducers/common.ts`) emits a tagged
137
+ // entry for each built-in Error subclass plus the workflow-specific
138
+ // `FatalError` / `RetryableError` / `HookConflictError` /
139
+ // `RuntimeDecryptionError` and `AggregateError`. Without
140
+ // matching revivers here, `devalue.unflatten` throws "Unknown type X"
141
+ // — which surfaces in the web o11y UI as "Failed to load resource
142
+ // details: Unknown type FatalError".
86
143
  Error: (value) => {
144
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
145
+ const error = new Error(value.message, opts);
146
+ error.name = value.name;
147
+ if (value.stack !== undefined) error.stack = value.stack;
148
+ return error;
149
+ },
150
+ EvalError: makeWebErrorSubclassReviver('EvalError'),
151
+ RangeError: makeWebErrorSubclassReviver('RangeError'),
152
+ ReferenceError: makeWebErrorSubclassReviver('ReferenceError'),
153
+ SyntaxError: makeWebErrorSubclassReviver('SyntaxError'),
154
+ TypeError: makeWebErrorSubclassReviver('TypeError'),
155
+ URIError: makeWebErrorSubclassReviver('URIError'),
156
+ AggregateError: (value) => {
157
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
158
+ const Ctor = (
159
+ globalThis as { AggregateError?: AggregateErrorConstructor }
160
+ ).AggregateError;
161
+ const error =
162
+ typeof Ctor === 'function'
163
+ ? new Ctor(value.errors, value.message, opts)
164
+ : Object.assign(new Error(value.message, opts), {
165
+ name: 'AggregateError',
166
+ errors: value.errors,
167
+ });
168
+ if (value.stack !== undefined) error.stack = value.stack;
169
+ return error;
170
+ },
171
+ // `FatalError` and `RetryableError` are not built-in browser globals,
172
+ // so we can't resolve a constructor from globalThis. The web o11y UI
173
+ // doesn't need `instanceof FatalError` to pass (no user code runs
174
+ // here) — it just needs `name`, `message`, `stack`, and any extra
175
+ // enumerable fields to render. Construct a plain `Error` with `name`
176
+ // set; ObjectInspector reads `constructor.name` for the displayed
177
+ // class label, but we don't have the real class, so we emit a tagged
178
+ // Error whose `name` field carries the class identity. This matches
179
+ // how the existing base `Error` reviver presents unknown subclasses.
180
+ FatalError: (value) => {
181
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
182
+ const error = new Error(value.message, opts);
183
+ error.name = 'FatalError';
184
+ if (value.stack !== undefined) error.stack = value.stack;
185
+ return error;
186
+ },
187
+ HookConflictError: (value) => {
188
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
189
+ const error = new Error(value.message, opts) as Error & {
190
+ token?: string;
191
+ conflictingRunId?: string;
192
+ };
193
+ error.name = 'HookConflictError';
194
+ error.token = value.token;
195
+ if (value.conflictingRunId !== undefined) {
196
+ error.conflictingRunId = value.conflictingRunId;
197
+ }
198
+ if (value.stack !== undefined) error.stack = value.stack;
199
+ return error;
200
+ },
201
+ RetryableError: (value) => {
202
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
203
+ const error = new Error(value.message, opts) as Error & {
204
+ retryAfter?: Date;
205
+ };
206
+ error.name = 'RetryableError';
207
+ if (value.stack !== undefined) error.stack = value.stack;
208
+ // `retryAfter` is serialized as an epoch ms number (see the runtime
209
+ // RetryableError reducer for the rationale around realm-safety).
210
+ // Rehydrate as a Date so o11y consumers can render it directly.
211
+ // Guard against payloads from older runtime versions that predate
212
+ // the field — without this check, `new Date(undefined)` would
213
+ // produce an Invalid Date rather than omitting the property.
214
+ if (value.retryAfter != null) {
215
+ error.retryAfter = new Date(value.retryAfter);
216
+ }
217
+ return error;
218
+ },
219
+ RuntimeDecryptionError: (value) => {
220
+ const opts = 'cause' in value ? { cause: value.cause } : undefined;
221
+ const error = new Error(value.message, opts) as Error & {
222
+ context?: unknown;
223
+ };
224
+ error.name = 'RuntimeDecryptionError';
225
+ if (value.context !== undefined) {
226
+ error.context = value.context;
227
+ }
228
+ if (value.stack !== undefined) error.stack = value.stack;
229
+ return error;
230
+ },
231
+ DOMException: (value) => {
232
+ // Modern browsers and Node 18+ expose `DOMException` on globalThis.
233
+ // `AbortController.abort()` with no argument synthesizes one as the
234
+ // signal's reason, so this is a common payload for any aborted step.
235
+ const G = globalThis as { DOMException?: typeof DOMException };
236
+ if (typeof G.DOMException === 'function') {
237
+ const e = new G.DOMException(value.message, value.name);
238
+ if (value.stack !== undefined) e.stack = value.stack;
239
+ if ('cause' in value) (e as { cause?: unknown }).cause = value.cause;
240
+ return e;
241
+ }
87
242
  const error = new Error(value.message);
88
243
  error.name = value.name;
89
- error.stack = value.stack;
244
+ if (value.stack !== undefined) error.stack = value.stack;
245
+ if ('cause' in value) {
246
+ (error as Error & { cause?: unknown }).cause = value.cause;
247
+ }
90
248
  return error;
91
249
  },
92
250
  Float32Array: (value: string) => new Float32Array(reviveArrayBuffer(value)),
@@ -318,6 +476,13 @@ export async function hydrateResourceIOWithKey<T>(
318
476
  '@workflow/core/serialization-format'
319
477
  );
320
478
  const { importKey } = await import('@workflow/core/encryption');
479
+ // Payloads may be zstd-compressed (the Web DecompressionStream has no zstd);
480
+ // register the WASM-backed browser decoder before hydrating. Idempotent and
481
+ // lazy — the WASM is only compiled when a zstd payload is actually decoded.
482
+ const { ensureZstdDecoderRegistered } = await import(
483
+ './zstd-browser-decoder.js'
484
+ );
485
+ ensureZstdDecoderRegistered();
321
486
  const cryptoKey = await importKey(key);
322
487
  const revivers = getRevivers();
323
488
 
@@ -0,0 +1,48 @@
1
+ export interface DecodedStreamChunkSource {
2
+ type: string;
3
+ encoding: 'utf-8';
4
+ rawSummary: string;
5
+ }
6
+
7
+ export interface FormattedStreamChunkDisplay {
8
+ text: string;
9
+ decodedFrom?: DecodedStreamChunkSource;
10
+ }
11
+
12
+ export function summarizeArrayBufferView(value: ArrayBufferView): string {
13
+ const ta = value as unknown as {
14
+ length: number;
15
+ constructor: { name: string };
16
+ } & ArrayLike<number>;
17
+ const name = ta.constructor.name;
18
+ const preview = Array.from(
19
+ { length: Math.min(ta.length, 8) },
20
+ (_, i) => ta[i]
21
+ );
22
+ const suffix = ta.length > 8 ? ', …' : '';
23
+ return `${name}(${ta.length}) [${preview.join(', ')}${suffix}]`;
24
+ }
25
+
26
+ /**
27
+ * Decode a typed array as UTF-8 text when valid, otherwise return a compact
28
+ * raw-byte summary. Used by `DataInspector`'s `collapseRefs` pipeline so
29
+ * hydrated `Uint8Array` chunks (e.g. AI SDK text deltas) render as readable
30
+ * text while still exposing the underlying byte layout.
31
+ */
32
+ export function formatArrayBufferViewForDisplay(
33
+ value: ArrayBufferView
34
+ ): FormattedStreamChunkDisplay {
35
+ try {
36
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(value);
37
+ return {
38
+ text,
39
+ decodedFrom: {
40
+ type: value.constructor.name,
41
+ encoding: 'utf-8',
42
+ rawSummary: summarizeArrayBufferView(value),
43
+ },
44
+ };
45
+ } catch {
46
+ return { text: summarizeArrayBufferView(value) };
47
+ }
48
+ }