@workflow/web-shared 5.0.0-beta.1 → 5.0.0-beta.10

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 (274) hide show
  1. package/README.md +1 -1
  2. package/dist/components/error-boundary.js +1 -1
  3. package/dist/components/event-list-view.d.ts +18 -1
  4. package/dist/components/event-list-view.d.ts.map +1 -1
  5. package/dist/components/event-list-view.js +219 -125
  6. package/dist/components/hook-actions.js +1 -1
  7. package/dist/components/index.d.ts +4 -1
  8. package/dist/components/index.d.ts.map +1 -1
  9. package/dist/components/index.js +5 -2
  10. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  11. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  12. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  13. package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
  14. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  15. package/dist/components/new-trace-viewer/components/event-list.js +39 -0
  16. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  17. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  18. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  19. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  20. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  21. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  22. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  23. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  24. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  25. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  26. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  27. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  28. package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
  29. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  30. package/dist/components/new-trace-viewer/components/split-pane.js +99 -0
  31. package/dist/components/new-trace-viewer/components/timeline.d.ts +34 -0
  32. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  33. package/dist/components/new-trace-viewer/components/timeline.js +223 -0
  34. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  35. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  36. package/dist/components/new-trace-viewer/context.js +47 -0
  37. package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
  38. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
  39. package/dist/components/new-trace-viewer/detail-panel.js +13 -0
  40. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  41. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/icons.js +15 -0
  43. package/dist/components/new-trace-viewer/search.d.ts +19 -0
  44. package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
  45. package/dist/components/new-trace-viewer/search.js +134 -0
  46. package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
  47. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  48. package/dist/components/new-trace-viewer/trace-viewer.js +387 -0
  49. package/dist/components/new-trace-viewer/types.d.ts +1 -0
  50. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  51. package/dist/components/new-trace-viewer/types.js +2 -0
  52. package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
  53. package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/util/constants.js +2 -0
  55. package/dist/components/new-trace-viewer/utils.d.ts +46 -0
  56. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  57. package/dist/components/new-trace-viewer/utils.js +330 -0
  58. package/dist/components/run-trace-view.d.ts +2 -1
  59. package/dist/components/run-trace-view.d.ts.map +1 -1
  60. package/dist/components/run-trace-view.js +3 -3
  61. package/dist/components/sidebar/attribute-panel.d.ts +7 -1
  62. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  63. package/dist/components/sidebar/attribute-panel.js +131 -70
  64. package/dist/components/sidebar/conversation-view.js +1 -1
  65. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  66. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  67. package/dist/components/sidebar/copyable-data-block.js +16 -15
  68. package/dist/components/sidebar/detail-card.d.ts +5 -6
  69. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  70. package/dist/components/sidebar/detail-card.js +32 -16
  71. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  72. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  73. package/dist/components/sidebar/entity-detail-panel.js +20 -37
  74. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  75. package/dist/components/sidebar/events-list.js +21 -15
  76. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  77. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  78. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  79. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  80. package/dist/components/stream-viewer.d.ts +1 -1
  81. package/dist/components/stream-viewer.d.ts.map +1 -1
  82. package/dist/components/stream-viewer.js +3 -27
  83. package/dist/components/trace-viewer/components/map.js +1 -1
  84. package/dist/components/trace-viewer/components/markers.js +1 -1
  85. package/dist/components/trace-viewer/components/node.js +1 -1
  86. package/dist/components/trace-viewer/components/search-input.js +1 -1
  87. package/dist/components/trace-viewer/components/search.js +1 -1
  88. package/dist/components/trace-viewer/components/span-content.js +1 -1
  89. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  90. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  91. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  92. package/dist/components/trace-viewer/components/ui.js +1 -1
  93. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  94. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  95. package/dist/components/trace-viewer/context.js +1 -1
  96. package/dist/components/trace-viewer/index.js +1 -1
  97. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  98. package/dist/components/trace-viewer/types.js +1 -1
  99. package/dist/components/trace-viewer/util/constants.js +1 -1
  100. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  101. package/dist/components/trace-viewer/util/timing.d.ts +1 -1
  102. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  103. package/dist/components/trace-viewer/util/timing.js +6 -9
  104. package/dist/components/trace-viewer/util/tree.js +1 -1
  105. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  106. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  107. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  108. package/dist/components/trace-viewer/worker.js +1 -1
  109. package/dist/components/trace-viewer-new.d.ts +10 -0
  110. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  111. package/dist/components/trace-viewer-new.js +23 -0
  112. package/dist/components/ui/alert.js +1 -1
  113. package/dist/components/ui/button.d.ts +10 -0
  114. package/dist/components/ui/button.d.ts.map +1 -0
  115. package/dist/components/ui/button.js +46 -0
  116. package/dist/components/ui/card.js +1 -1
  117. package/dist/components/ui/data-inspector.d.ts +39 -1
  118. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  119. package/dist/components/ui/data-inspector.js +207 -22
  120. package/dist/components/ui/decrypt-button.js +1 -1
  121. package/dist/components/ui/error-card.js +1 -1
  122. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  123. package/dist/components/ui/error-stack-block.js +27 -19
  124. package/dist/components/ui/icon-button.d.ts +13 -0
  125. package/dist/components/ui/icon-button.d.ts.map +1 -0
  126. package/dist/components/ui/icon-button.js +29 -0
  127. package/dist/components/ui/inspector-theme.d.ts +9 -10
  128. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  129. package/dist/components/ui/inspector-theme.js +39 -72
  130. package/dist/components/ui/load-more-button.js +1 -1
  131. package/dist/components/ui/menu-dropdown.js +1 -1
  132. package/dist/components/ui/skeleton.js +1 -1
  133. package/dist/components/ui/spinner.js +1 -1
  134. package/dist/components/ui/timestamp-tooltip.js +1 -1
  135. package/dist/components/workflow-trace-view.d.ts +3 -1
  136. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  137. package/dist/components/workflow-trace-view.js +10 -5
  138. package/dist/components/workflow-traces/event-colors.js +1 -1
  139. package/dist/components/workflow-traces/trace-colors.js +1 -1
  140. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  141. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  142. package/dist/components/workflow-traces/trace-span-construction.js +19 -6
  143. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  144. package/dist/hooks/use-dark-mode.js +1 -1
  145. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  146. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  147. package/dist/hooks/use-reduced-motion.js +29 -0
  148. package/dist/index.d.ts +3 -1
  149. package/dist/index.d.ts.map +1 -1
  150. package/dist/index.js +3 -2
  151. package/dist/lib/event-analysis.js +1 -1
  152. package/dist/lib/event-materialization.js +1 -1
  153. package/dist/lib/exact-event-search-id.d.ts +25 -0
  154. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  155. package/dist/lib/exact-event-search-id.js +39 -0
  156. package/dist/lib/hydration.d.ts +14 -1
  157. package/dist/lib/hydration.d.ts.map +1 -1
  158. package/dist/lib/hydration.js +157 -4
  159. package/dist/lib/stream-display.d.ts +18 -0
  160. package/dist/lib/stream-display.d.ts.map +1 -0
  161. package/dist/lib/stream-display.js +30 -0
  162. package/dist/lib/toast.js +1 -1
  163. package/dist/lib/trace-builder.d.ts.map +1 -1
  164. package/dist/lib/trace-builder.js +3 -2
  165. package/dist/lib/utils.d.ts +9 -9
  166. package/dist/lib/utils.d.ts.map +1 -1
  167. package/dist/lib/utils.js +27 -32
  168. package/dist/styles.css +475 -1
  169. package/package.json +6 -4
  170. package/src/components/event-list-view.tsx +421 -262
  171. package/src/components/index.ts +12 -1
  172. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  173. package/src/components/new-trace-viewer/components/event-list.tsx +127 -0
  174. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  175. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  176. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  177. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  178. package/src/components/new-trace-viewer/components/split-pane.tsx +185 -0
  179. package/src/components/new-trace-viewer/components/timeline.tsx +552 -0
  180. package/src/components/new-trace-viewer/context.tsx +92 -0
  181. package/src/components/new-trace-viewer/detail-panel.tsx +55 -0
  182. package/src/components/new-trace-viewer/icons.tsx +79 -0
  183. package/src/components/new-trace-viewer/search.ts +178 -0
  184. package/src/components/new-trace-viewer/trace-viewer.tsx +694 -0
  185. package/src/components/new-trace-viewer/types.ts +0 -0
  186. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  187. package/src/components/new-trace-viewer/utils.ts +482 -0
  188. package/src/components/run-trace-view.tsx +3 -0
  189. package/src/components/sidebar/attribute-panel.tsx +240 -196
  190. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  191. package/src/components/sidebar/detail-card.tsx +119 -59
  192. package/src/components/sidebar/entity-detail-panel.tsx +92 -148
  193. package/src/components/sidebar/events-list.tsx +51 -76
  194. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  195. package/src/components/stream-viewer.tsx +18 -40
  196. package/src/components/trace-viewer/util/timing.ts +5 -9
  197. package/src/components/trace-viewer-new.tsx +49 -0
  198. package/src/components/ui/button.tsx +68 -0
  199. package/src/components/ui/data-inspector.tsx +427 -31
  200. package/src/components/ui/error-stack-block.tsx +39 -30
  201. package/src/components/ui/icon-button.tsx +57 -0
  202. package/src/components/ui/inspector-theme.ts +39 -81
  203. package/src/components/workflow-trace-view.tsx +12 -2
  204. package/src/components/workflow-traces/trace-span-construction.ts +24 -6
  205. package/src/hooks/use-reduced-motion.ts +34 -0
  206. package/src/index.ts +9 -0
  207. package/src/lib/exact-event-search-id.ts +67 -0
  208. package/src/lib/hydration.ts +169 -1
  209. package/src/lib/stream-display.ts +48 -0
  210. package/src/lib/trace-builder.ts +2 -1
  211. package/src/lib/utils.ts +27 -32
  212. package/src/styles.css +475 -1
  213. package/dist/components/error-boundary.js.map +0 -1
  214. package/dist/components/event-list-view.js.map +0 -1
  215. package/dist/components/hook-actions.js.map +0 -1
  216. package/dist/components/index.js.map +0 -1
  217. package/dist/components/run-trace-view.js.map +0 -1
  218. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  219. package/dist/components/sidebar/conversation-view.js.map +0 -1
  220. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  221. package/dist/components/sidebar/detail-card.js.map +0 -1
  222. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  223. package/dist/components/sidebar/events-list.js.map +0 -1
  224. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  225. package/dist/components/stream-viewer.js.map +0 -1
  226. package/dist/components/trace-viewer/components/map.js.map +0 -1
  227. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  228. package/dist/components/trace-viewer/components/node.js.map +0 -1
  229. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  230. package/dist/components/trace-viewer/components/search.js.map +0 -1
  231. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  232. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  233. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  234. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  235. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  236. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  237. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  238. package/dist/components/trace-viewer/context.js.map +0 -1
  239. package/dist/components/trace-viewer/index.js.map +0 -1
  240. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  241. package/dist/components/trace-viewer/types.js.map +0 -1
  242. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  243. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  244. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  245. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  246. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  247. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  248. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  249. package/dist/components/trace-viewer/worker.js.map +0 -1
  250. package/dist/components/ui/alert.js.map +0 -1
  251. package/dist/components/ui/card.js.map +0 -1
  252. package/dist/components/ui/data-inspector.js.map +0 -1
  253. package/dist/components/ui/decrypt-button.js.map +0 -1
  254. package/dist/components/ui/error-card.js.map +0 -1
  255. package/dist/components/ui/error-stack-block.js.map +0 -1
  256. package/dist/components/ui/inspector-theme.js.map +0 -1
  257. package/dist/components/ui/load-more-button.js.map +0 -1
  258. package/dist/components/ui/menu-dropdown.js.map +0 -1
  259. package/dist/components/ui/skeleton.js.map +0 -1
  260. package/dist/components/ui/spinner.js.map +0 -1
  261. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  262. package/dist/components/workflow-trace-view.js.map +0 -1
  263. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  264. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  265. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  266. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  267. package/dist/hooks/use-dark-mode.js.map +0 -1
  268. package/dist/index.js.map +0 -1
  269. package/dist/lib/event-analysis.js.map +0 -1
  270. package/dist/lib/event-materialization.js.map +0 -1
  271. package/dist/lib/hydration.js.map +0 -1
  272. package/dist/lib/toast.js.map +0 -1
  273. package/dist/lib/trace-builder.js.map +0 -1
  274. package/dist/lib/utils.js.map +0 -1
@@ -9,7 +9,14 @@
9
9
  */
10
10
 
11
11
  import { Lock } from 'lucide-react';
12
- import { createContext, useContext, useEffect, useRef, useState } from 'react';
12
+ import {
13
+ createContext,
14
+ useContext,
15
+ useEffect,
16
+ useMemo,
17
+ useRef,
18
+ useState,
19
+ } from 'react';
13
20
  import {
14
21
  ObjectInspector,
15
22
  ObjectLabel,
@@ -19,6 +26,11 @@ import {
19
26
  } from 'react-inspector';
20
27
  import { useDarkMode } from '../../hooks/use-dark-mode';
21
28
  import { ENCRYPTED_DISPLAY_NAME } from '../../lib/hydration';
29
+ import {
30
+ type DecodedStreamChunkSource,
31
+ type FormattedStreamChunkDisplay,
32
+ formatArrayBufferViewForDisplay,
33
+ } from '../../lib/stream-display';
22
34
  import {
23
35
  type InspectorThemeExtended,
24
36
  inspectorThemeDark,
@@ -26,6 +38,8 @@ import {
26
38
  inspectorThemeExtendedLight,
27
39
  inspectorThemeLight,
28
40
  } from './inspector-theme';
41
+ import { Button } from './button';
42
+ import { Spinner } from './spinner';
29
43
 
30
44
  // ---------------------------------------------------------------------------
31
45
  // StreamRef / ClassInstanceRef type detection
@@ -34,19 +48,62 @@ import {
34
48
 
35
49
  const STREAM_REF_TYPE = '__workflow_stream_ref__';
36
50
  const CLASS_INSTANCE_REF_TYPE = '__workflow_class_instance_ref__';
51
+ const RUN_REF_TYPE = '__workflow_run_ref__';
52
+ const BYTES_DISPLAY_TYPE = '__workflow_bytes_display__';
37
53
 
38
54
  interface StreamRef {
39
55
  __type: typeof STREAM_REF_TYPE;
40
56
  streamId: string;
41
57
  }
42
58
 
59
+ interface RunRef {
60
+ __type: typeof RUN_REF_TYPE;
61
+ runId: string;
62
+ }
63
+
64
+ interface BytesDisplay {
65
+ __type: typeof BYTES_DISPLAY_TYPE;
66
+ text: string;
67
+ decodedFrom?: DecodedStreamChunkSource;
68
+ }
69
+
70
+ function deserializeChunkText(text: string): string {
71
+ try {
72
+ const parsed = JSON.parse(text);
73
+ if (typeof parsed === 'string') {
74
+ return parsed;
75
+ }
76
+ return JSON.stringify(parsed, null, 2);
77
+ } catch {
78
+ return text;
79
+ }
80
+ }
81
+
82
+ function parseChunkData(text: string): unknown {
83
+ try {
84
+ return JSON.parse(text);
85
+ } catch {
86
+ return text;
87
+ }
88
+ }
89
+
43
90
  function isStreamRef(value: unknown): value is StreamRef {
44
- return (
45
- value !== null &&
46
- typeof value === 'object' &&
47
- '__type' in value &&
48
- (value as Record<string, unknown>).__type === STREAM_REF_TYPE
49
- );
91
+ if (value === null || typeof value !== 'object') return false;
92
+ // Check both enumerable and non-enumerable __type (opaque refs use non-enumerable)
93
+ const desc = Object.getOwnPropertyDescriptor(value, '__type');
94
+ return desc?.value === STREAM_REF_TYPE;
95
+ }
96
+
97
+ function isRunRef(value: unknown): value is RunRef {
98
+ if (value === null || typeof value !== 'object') return false;
99
+ const desc = Object.getOwnPropertyDescriptor(value, '__type');
100
+ return desc?.value === RUN_REF_TYPE;
101
+ }
102
+
103
+ export function isBytesDisplay(value: unknown): value is BytesDisplay {
104
+ if (value === null || typeof value !== 'object') return false;
105
+ const desc = Object.getOwnPropertyDescriptor(value, '__type');
106
+ return desc?.value === BYTES_DISPLAY_TYPE;
50
107
  }
51
108
 
52
109
  function isClassInstanceRef(value: unknown): value is {
@@ -75,18 +132,79 @@ export const StreamClickContext = createContext<
75
132
  ((streamId: string) => void) | undefined
76
133
  >(undefined);
77
134
 
135
+ /**
136
+ * Context for passing a decrypt handler down to DataInspector instances.
137
+ * When provided, encrypted markers become clickable buttons that trigger decryption.
138
+ */
139
+ export type DecryptClickContextValue = {
140
+ onDecrypt: () => void;
141
+ isDecrypting: boolean;
142
+ hasEncryptedData?: boolean;
143
+ };
144
+
145
+ export const DecryptClickContext = createContext<
146
+ DecryptClickContextValue | undefined
147
+ >(undefined);
148
+
149
+ export const RunClickContext = createContext<
150
+ ((runId: string) => void) | undefined
151
+ >(undefined);
152
+
153
+ function EncryptedInlineLabel() {
154
+ const ctx = useContext(DecryptClickContext);
155
+ if (ctx) {
156
+ return (
157
+ <Button
158
+ size="xs"
159
+ className="align-baseline gap-x-1"
160
+ disabled={ctx.isDecrypting}
161
+ onClick={(e) => {
162
+ e.stopPropagation();
163
+ ctx.onDecrypt();
164
+ }}
165
+ >
166
+ {ctx.isDecrypting ? (
167
+ <Spinner size={10} />
168
+ ) : (
169
+ <Lock className="h-3 w-3" />
170
+ )}
171
+ <span>Decrypt</span>
172
+ </Button>
173
+ );
174
+ }
175
+ return (
176
+ <span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
177
+ <Lock
178
+ className="h-3 w-3"
179
+ style={{
180
+ display: 'inline',
181
+ verticalAlign: 'middle',
182
+ marginRight: '3px',
183
+ marginTop: '-1px',
184
+ }}
185
+ />
186
+ Encrypted
187
+ </span>
188
+ );
189
+ }
78
190
  function StreamRefInline({ streamRef }: { streamRef: StreamRef }) {
79
191
  const onStreamClick = useContext(StreamClickContext);
192
+ const [hovered, setHovered] = useState(false);
80
193
  return (
81
194
  <button
82
195
  type="button"
83
- className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer"
196
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer underline decoration-transparent transition-colors"
84
197
  style={{
85
- backgroundColor: 'var(--ds-blue-100)',
86
- color: 'var(--ds-blue-800)',
198
+ backgroundColor: hovered ? 'var(--ds-blue-200)' : 'var(--ds-blue-100)',
199
+ color: 'var(--ds-blue-900)',
87
200
  border: '1px solid var(--ds-blue-300)',
88
201
  }}
89
- onClick={() => onStreamClick?.(streamRef.streamId)}
202
+ onMouseEnter={() => setHovered(true)}
203
+ onMouseLeave={() => setHovered(false)}
204
+ onClick={(e) => {
205
+ e.stopPropagation();
206
+ onStreamClick?.(streamRef.streamId);
207
+ }}
90
208
  title={`View stream: ${streamRef.streamId}`}
91
209
  >
92
210
  <span>📡</span>
@@ -95,6 +213,33 @@ function StreamRefInline({ streamRef }: { streamRef: StreamRef }) {
95
213
  );
96
214
  }
97
215
 
216
+ function RunRefInline({ runRef }: { runRef: RunRef }) {
217
+ const onRunClick = useContext(RunClickContext);
218
+ const [hovered, setHovered] = useState(false);
219
+ return (
220
+ <button
221
+ type="button"
222
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer underline decoration-transparent transition-colors"
223
+ style={{
224
+ backgroundColor: hovered
225
+ ? 'var(--ds-purple-200)'
226
+ : 'var(--ds-purple-100)',
227
+ color: 'var(--ds-purple-900)',
228
+ border: '1px solid var(--ds-purple-300)',
229
+ }}
230
+ onMouseEnter={() => setHovered(true)}
231
+ onMouseLeave={() => setHovered(false)}
232
+ onClick={(e) => {
233
+ e.stopPropagation();
234
+ onRunClick?.(runRef.runId);
235
+ }}
236
+ title={`View run: ${runRef.runId}`}
237
+ >
238
+ <span>{runRef.runId}</span>
239
+ </button>
240
+ );
241
+ }
242
+
98
243
  // ---------------------------------------------------------------------------
99
244
  // Extended theme context (for colors react-inspector doesn't support natively)
100
245
  // ---------------------------------------------------------------------------
@@ -103,6 +248,147 @@ const ExtendedThemeContext = createContext<InspectorThemeExtended>(
103
248
  inspectorThemeExtendedLight
104
249
  );
105
250
 
251
+ function DecodedBytesChunk({
252
+ decodedText,
253
+ source,
254
+ }: {
255
+ decodedText: string;
256
+ source: DecodedStreamChunkSource;
257
+ }) {
258
+ const [selectedView, setSelectedView] = useState<'decoded' | 'bytes'>(
259
+ 'decoded'
260
+ );
261
+ const parsed = parseChunkData(decodedText);
262
+
263
+ return (
264
+ <div className="min-w-0">
265
+ {selectedView === 'decoded' ? (
266
+ <div className="min-w-0">
267
+ {typeof parsed === 'string' ? (
268
+ <span
269
+ className="whitespace-pre-wrap break-words"
270
+ style={{ color: 'var(--ds-gray-1000)' }}
271
+ >
272
+ {deserializeChunkText(parsed)}
273
+ </span>
274
+ ) : (
275
+ <DataInspector data={parsed} expandLevel={1} />
276
+ )}
277
+ </div>
278
+ ) : (
279
+ <DecodedBytesInspector decodedText={decodedText} source={source} />
280
+ )}
281
+ <div className="mt-2 flex">
282
+ <div
283
+ className="inline-flex overflow-hidden rounded border"
284
+ style={{ borderColor: 'var(--ds-gray-400)' }}
285
+ title={`${source.type} decoded as ${source.encoding.toUpperCase()} text. Switch to Bytes to inspect the summarized raw value.`}
286
+ >
287
+ <button
288
+ type="button"
289
+ className="h-5 px-1.5 text-[10px] font-medium"
290
+ style={{
291
+ backgroundColor:
292
+ selectedView === 'decoded'
293
+ ? 'var(--ds-gray-200)'
294
+ : 'var(--ds-gray-100)',
295
+ color: 'var(--ds-gray-900)',
296
+ }}
297
+ onClick={() => setSelectedView('decoded')}
298
+ aria-pressed={selectedView === 'decoded'}
299
+ aria-label="Show decoded text"
300
+ >
301
+ Decoded
302
+ </button>
303
+ <button
304
+ type="button"
305
+ className="h-5 border-l px-1.5 text-[10px] font-medium"
306
+ style={{
307
+ borderColor: 'var(--ds-gray-400)',
308
+ backgroundColor:
309
+ selectedView === 'bytes'
310
+ ? 'var(--ds-gray-200)'
311
+ : 'var(--ds-gray-100)',
312
+ color: 'var(--ds-gray-900)',
313
+ }}
314
+ onClick={() => setSelectedView('bytes')}
315
+ aria-pressed={selectedView === 'bytes'}
316
+ aria-label="Show raw bytes summary"
317
+ >
318
+ Bytes
319
+ </button>
320
+ </div>
321
+ </div>
322
+ </div>
323
+ );
324
+ }
325
+
326
+ function DecodedBytesInspector({
327
+ decodedText,
328
+ source,
329
+ }: {
330
+ decodedText: string;
331
+ source: DecodedStreamChunkSource;
332
+ }) {
333
+ const [expanded, setExpanded] = useState(false);
334
+
335
+ return (
336
+ <div className="font-mono">
337
+ <button
338
+ type="button"
339
+ className="flex max-w-full items-start gap-1 text-left"
340
+ style={{ color: 'var(--ds-gray-1000)' }}
341
+ onClick={() => setExpanded((value) => !value)}
342
+ title={`${source.type} decoded as ${source.encoding.toUpperCase()} text`}
343
+ >
344
+ <span className="select-none" style={{ color: 'var(--ds-gray-700)' }}>
345
+ {expanded ? '▼' : '▶'}
346
+ </span>
347
+ <span className="min-w-0 break-words">{source.rawSummary}</span>
348
+ </button>
349
+ {expanded && (
350
+ <div className="mt-1 pl-5">
351
+ <span style={{ color: 'var(--ds-gray-700)' }}>decoded: </span>
352
+ <span
353
+ className="whitespace-pre-wrap break-words"
354
+ style={{ color: 'var(--ds-green-900)' }}
355
+ >
356
+ {JSON.stringify(decodedText)}
357
+ </span>
358
+ </div>
359
+ )}
360
+ </div>
361
+ );
362
+ }
363
+
364
+ function BytesDisplayLabel({
365
+ name,
366
+ display,
367
+ }: {
368
+ name?: string;
369
+ display: BytesDisplay;
370
+ }) {
371
+ return (
372
+ <div className="inline-block min-w-0 align-top">
373
+ {name != null && <ObjectName name={name} />}
374
+ {name != null && <span>: </span>}
375
+ {display.decodedFrom ? (
376
+ <DecodedBytesChunk
377
+ decodedText={display.text}
378
+ source={display.decodedFrom}
379
+ />
380
+ ) : (
381
+ <span
382
+ className="whitespace-pre-wrap break-words"
383
+ style={{ color: 'var(--ds-gray-1000)' }}
384
+ >
385
+ {display.text}
386
+ </span>
387
+ )}
388
+ </div>
389
+ );
390
+ }
391
+
106
392
  // ---------------------------------------------------------------------------
107
393
  // Custom nodeRenderer
108
394
  // ---------------------------------------------------------------------------
@@ -132,26 +418,17 @@ function NodeRenderer({
132
418
  }) {
133
419
  const extendedTheme = useContext(ExtendedThemeContext);
134
420
 
135
- // Encrypted marker → flat label with Lock icon, non-expandable
421
+ if (isBytesDisplay(data)) {
422
+ return <BytesDisplayLabel name={name} display={data} />;
423
+ }
424
+
425
+ // Encrypted marker → flat label with Lock icon, clickable when onDecrypt is available
136
426
  if (
137
427
  data !== null &&
138
428
  typeof data === 'object' &&
139
429
  data.constructor?.name === ENCRYPTED_DISPLAY_NAME
140
430
  ) {
141
- const label = (
142
- <span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
143
- <Lock
144
- className="h-3 w-3"
145
- style={{
146
- display: 'inline',
147
- verticalAlign: 'middle',
148
- marginRight: '3px',
149
- marginTop: '-1px',
150
- }}
151
- />
152
- Encrypted
153
- </span>
154
- );
431
+ const label = <EncryptedInlineLabel />;
155
432
  if (depth === 0) {
156
433
  return label;
157
434
  }
@@ -175,6 +452,17 @@ function NodeRenderer({
175
452
  );
176
453
  }
177
454
 
455
+ // RunRef → inline clickable badge linking to the target run
456
+ if (isRunRef(data)) {
457
+ return (
458
+ <span>
459
+ {name != null && <ObjectName name={name} />}
460
+ {name != null && <span>: </span>}
461
+ <RunRefInline runRef={data} />
462
+ </span>
463
+ );
464
+ }
465
+
178
466
  // ClassInstanceRef → show className as type, data as the inspectable value
179
467
  if (isClassInstanceRef(data)) {
180
468
  if (depth === 0) {
@@ -225,6 +513,76 @@ function NodeRenderer({
225
513
  // Public component
226
514
  // ---------------------------------------------------------------------------
227
515
 
516
+ /**
517
+ * Create a non-expandable wrapper that carries ref data as non-enumerable
518
+ * properties. ObjectInspector won't render children for objects with no
519
+ * enumerable keys, but our NodeRenderer can still detect them.
520
+ */
521
+ function makeOpaqueRef(ref: Record<string, unknown>): unknown {
522
+ const opaque = Object.create(null);
523
+ for (const [key, value] of Object.entries(ref)) {
524
+ Object.defineProperty(opaque, key, { value, enumerable: false });
525
+ }
526
+ return opaque;
527
+ }
528
+
529
+ function makeBytesDisplay(display: FormattedStreamChunkDisplay): unknown {
530
+ const opaque = Object.create(null);
531
+ Object.defineProperty(opaque, '__type', {
532
+ value: BYTES_DISPLAY_TYPE,
533
+ enumerable: false,
534
+ });
535
+ Object.defineProperty(opaque, 'text', {
536
+ value: display.text,
537
+ enumerable: false,
538
+ });
539
+ Object.defineProperty(opaque, 'decodedFrom', {
540
+ value: display.decodedFrom,
541
+ enumerable: false,
542
+ });
543
+ return opaque;
544
+ }
545
+
546
+ /**
547
+ * Recursively walk data and replace RunRef/StreamRef/typed array objects with
548
+ * non-expandable versions so ObjectInspector doesn't show their internals.
549
+ * Only recurses into plain objects and arrays to avoid stripping class
550
+ * instances (Date, Error, URL, Headers, etc.) that have their own rendering in
551
+ * NodeRenderer. Map and Set containers are preserved while their contents are
552
+ * prepared for display.
553
+ *
554
+ * Exported for testing the typed-array detection path used by hydrated
555
+ * AI agent stream chunks (e.g. `{ delta: new Uint8Array(...) }`).
556
+ */
557
+ export function collapseRefs(data: unknown): unknown {
558
+ if (data === null || typeof data !== 'object') return data;
559
+ if (ArrayBuffer.isView(data) && !(data instanceof DataView)) {
560
+ return makeBytesDisplay(formatArrayBufferViewForDisplay(data));
561
+ }
562
+ if (isRunRef(data) || isStreamRef(data))
563
+ return makeOpaqueRef(data as unknown as Record<string, unknown>);
564
+ if (Array.isArray(data)) return data.map(collapseRefs);
565
+ if (data instanceof Map) {
566
+ return new Map(
567
+ Array.from(data.entries(), ([key, value]) => [
568
+ collapseRefs(key),
569
+ collapseRefs(value),
570
+ ])
571
+ );
572
+ }
573
+ if (data instanceof Set) {
574
+ return new Set(Array.from(data.values(), collapseRefs));
575
+ }
576
+ // Only recurse into plain objects — leave class instances untouched
577
+ const proto = Object.getPrototypeOf(data);
578
+ if (proto !== Object.prototype && proto !== null) return data;
579
+ const result: Record<string, unknown> = {};
580
+ for (const [key, value] of Object.entries(data)) {
581
+ result[key] = collapseRefs(value);
582
+ }
583
+ return result;
584
+ }
585
+
228
586
  export interface DataInspectorProps {
229
587
  /** The data to inspect */
230
588
  data: unknown;
@@ -234,6 +592,12 @@ export interface DataInspectorProps {
234
592
  name?: string;
235
593
  /** Callback when a stream reference is clicked */
236
594
  onStreamClick?: (streamId: string) => void;
595
+ /** Callback when a run reference is clicked */
596
+ onRunClick?: (runId: string) => void;
597
+ /** Callback when an encrypted marker is clicked (triggers decryption) */
598
+ onDecrypt?: () => void;
599
+ /** Whether decryption is currently in progress */
600
+ isDecrypting?: boolean;
237
601
  }
238
602
 
239
603
  export function DataInspector({
@@ -241,8 +605,12 @@ export function DataInspector({
241
605
  expandLevel = 2,
242
606
  name,
243
607
  onStreamClick,
608
+ onRunClick,
609
+ onDecrypt,
610
+ isDecrypting = false,
244
611
  }: DataInspectorProps) {
245
- const stableData = useStableInspectorData(data);
612
+ const collapsedData = useMemo(() => collapseRefs(data), [data]);
613
+ const stableData = useStableInspectorData(collapsedData);
246
614
  const [initialExpandLevel, setInitialExpandLevel] = useState(expandLevel);
247
615
  const isDark = useDarkMode();
248
616
  const extendedTheme = isDark
@@ -269,16 +637,31 @@ export function DataInspector({
269
637
  </ExtendedThemeContext.Provider>
270
638
  );
271
639
 
272
- // Wrap in StreamClickContext if a handler is provided
640
+ let wrapped = content;
641
+
273
642
  if (onStreamClick) {
274
- return (
643
+ wrapped = (
275
644
  <StreamClickContext.Provider value={onStreamClick}>
276
- {content}
645
+ {wrapped}
277
646
  </StreamClickContext.Provider>
278
647
  );
279
648
  }
649
+ if (onRunClick) {
650
+ wrapped = (
651
+ <RunClickContext.Provider value={onRunClick}>
652
+ {wrapped}
653
+ </RunClickContext.Provider>
654
+ );
655
+ }
656
+ if (onDecrypt) {
657
+ wrapped = (
658
+ <DecryptClickContext.Provider value={{ onDecrypt, isDecrypting }}>
659
+ {wrapped}
660
+ </DecryptClickContext.Provider>
661
+ );
662
+ }
280
663
 
281
- return content;
664
+ return wrapped;
282
665
  }
283
666
 
284
667
  function useStableInspectorData<T>(next: T): T {
@@ -293,9 +676,22 @@ function isObjectLike(value: unknown): value is Record<string, unknown> {
293
676
  return typeof value === 'object' && value !== null;
294
677
  }
295
678
 
679
+ function isSameBytesDisplay(a: BytesDisplay, b: BytesDisplay): boolean {
680
+ return (
681
+ a.text === b.text &&
682
+ a.decodedFrom?.type === b.decodedFrom?.type &&
683
+ a.decodedFrom?.encoding === b.decodedFrom?.encoding &&
684
+ a.decodedFrom?.rawSummary === b.decodedFrom?.rawSummary
685
+ );
686
+ }
687
+
296
688
  function isDeepEqual(a: unknown, b: unknown, seen = new WeakMap()): boolean {
297
689
  if (Object.is(a, b)) return true;
298
690
 
691
+ if (isBytesDisplay(a) || isBytesDisplay(b)) {
692
+ return isBytesDisplay(a) && isBytesDisplay(b) && isSameBytesDisplay(a, b);
693
+ }
694
+
299
695
  if (a instanceof Date && b instanceof Date) {
300
696
  return a.getTime() === b.getTime();
301
697
  }
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
- import { AlertCircle, Copy } from 'lucide-react';
4
- import { useToast } from '../../lib/toast';
3
+ import { AlertCircle } from 'lucide-react';
4
+ import { CopyButton } from '../new-trace-viewer/components/copy-button';
5
5
 
6
6
  /**
7
7
  * Check whether `value` looks like a structured error object with a `stack`
@@ -18,6 +18,21 @@ export function isStructuredErrorWithStack(
18
18
  );
19
19
  }
20
20
 
21
+ /**
22
+ * Pull a short, single-line title out of an error message.
23
+ *
24
+ * Workflow's structured error messages are multi-line — the first line is
25
+ * the headline (`Failed to serialize step return value`) and the rest are
26
+ * `╰▶ hint:` / `╰▶ docs:` framed details. The full message belongs in the
27
+ * body of the error block; the title should just be the headline so the
28
+ * card stays scannable.
29
+ */
30
+ function deriveTitle(message: string): string {
31
+ const firstLine =
32
+ message.split('\n').find((line) => line.trim().length > 0) ?? message;
33
+ return firstLine.trim();
34
+ }
35
+
21
36
  /**
22
37
  * Renders an error with a `stack` field as a visually distinct error block.
23
38
  * Shows the error message with an alert icon at the top, separated from
@@ -28,9 +43,12 @@ export function ErrorStackBlock({
28
43
  }: {
29
44
  value: Record<string, unknown> & { stack: string };
30
45
  }) {
31
- const toast = useToast();
32
46
  const stack = value.stack;
33
47
  const message = typeof value.message === 'string' ? value.message : undefined;
48
+ // V8's `Error.stack` already starts with `Name: message`, so when the
49
+ // body shows the stack it includes the full multi-line message anyway.
50
+ // The header just needs the first line.
51
+ const title = message ? deriveTitle(message) : undefined;
34
52
  const copyText = message ? `${message}\n\n${stack}` : stack;
35
53
 
36
54
  return (
@@ -41,40 +59,31 @@ export function ErrorStackBlock({
41
59
  background: 'var(--ds-red-100)',
42
60
  }}
43
61
  >
44
- <button
45
- type="button"
46
- aria-label="Copy error"
47
- title="Copy"
48
- className="!absolute !right-2 !top-2 !flex !h-6 !w-6 !items-center !justify-center !rounded-md !border transition-transform transition-colors duration-100 hover:!bg-[var(--ds-red-200)] active:!scale-95"
49
- style={{
50
- borderColor: 'var(--ds-red-400)',
51
- background: 'var(--ds-red-100)',
52
- color: 'var(--ds-red-900)',
53
- }}
54
- onClick={() => {
55
- navigator.clipboard
56
- .writeText(copyText)
57
- .then(() => {
58
- toast.success('Copied to clipboard');
59
- })
60
- .catch(() => {
61
- toast.error('Failed to copy');
62
- });
63
- }}
64
- >
65
- <Copy size={12} />
66
- </button>
62
+ <CopyButton
63
+ copyText={copyText}
64
+ ariaLabel="Copy error"
65
+ className="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-md border border-red-400 bg-red-100 p-0 text-red-900 transition-transform transition-colors duration-100 hover:bg-red-200 active:scale-95"
66
+ />
67
67
 
68
- {message && (
68
+ {title && (
69
69
  <div
70
- className="flex items-start gap-2 px-3 py-2.5 pr-10"
70
+ className="flex items-center gap-2 px-3 py-2.5 pr-10"
71
71
  style={{
72
72
  color: 'var(--ds-red-900)',
73
73
  borderBottom: '1px solid var(--ds-red-400)',
74
74
  }}
75
75
  >
76
- <AlertCircle className="h-4 w-4 shrink-0" style={{ marginTop: 1 }} />
77
- <p className="text-xs font-semibold m-0 break-words">{message}</p>
76
+ <AlertCircle className="h-4 w-4 shrink-0" />
77
+ <p
78
+ className="text-xs font-semibold m-0 truncate"
79
+ // The full multi-line message is in the stack body below; the
80
+ // header just shows the first line, single-line, with overflow
81
+ // ellipsised so a long title doesn't push the copy button or
82
+ // wrap into the framed hint/docs lines.
83
+ title={message}
84
+ >
85
+ {title}
86
+ </p>
78
87
  </div>
79
88
  )}
80
89
  <pre