@workflow/web-shared 5.0.0-beta.0 → 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 (275) 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 +242 -142
  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 +1 -1
  75. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  76. package/dist/components/sidebar/events-list.js +46 -32
  77. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  78. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  79. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  80. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  81. package/dist/components/stream-viewer.d.ts +1 -1
  82. package/dist/components/stream-viewer.d.ts.map +1 -1
  83. package/dist/components/stream-viewer.js +3 -27
  84. package/dist/components/trace-viewer/components/map.js +1 -1
  85. package/dist/components/trace-viewer/components/markers.js +1 -1
  86. package/dist/components/trace-viewer/components/node.js +1 -1
  87. package/dist/components/trace-viewer/components/search-input.js +1 -1
  88. package/dist/components/trace-viewer/components/search.js +1 -1
  89. package/dist/components/trace-viewer/components/span-content.js +1 -1
  90. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  91. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  92. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  93. package/dist/components/trace-viewer/components/ui.js +1 -1
  94. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  95. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  96. package/dist/components/trace-viewer/context.js +1 -1
  97. package/dist/components/trace-viewer/index.js +1 -1
  98. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  99. package/dist/components/trace-viewer/types.js +1 -1
  100. package/dist/components/trace-viewer/util/constants.js +1 -1
  101. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  102. package/dist/components/trace-viewer/util/timing.d.ts +1 -1
  103. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  104. package/dist/components/trace-viewer/util/timing.js +6 -9
  105. package/dist/components/trace-viewer/util/tree.js +1 -1
  106. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  107. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  108. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  109. package/dist/components/trace-viewer/worker.js +1 -1
  110. package/dist/components/trace-viewer-new.d.ts +10 -0
  111. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  112. package/dist/components/trace-viewer-new.js +23 -0
  113. package/dist/components/ui/alert.js +1 -1
  114. package/dist/components/ui/button.d.ts +10 -0
  115. package/dist/components/ui/button.d.ts.map +1 -0
  116. package/dist/components/ui/button.js +46 -0
  117. package/dist/components/ui/card.js +1 -1
  118. package/dist/components/ui/data-inspector.d.ts +39 -1
  119. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  120. package/dist/components/ui/data-inspector.js +207 -22
  121. package/dist/components/ui/decrypt-button.js +1 -1
  122. package/dist/components/ui/error-card.js +1 -1
  123. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  124. package/dist/components/ui/error-stack-block.js +27 -19
  125. package/dist/components/ui/icon-button.d.ts +13 -0
  126. package/dist/components/ui/icon-button.d.ts.map +1 -0
  127. package/dist/components/ui/icon-button.js +29 -0
  128. package/dist/components/ui/inspector-theme.d.ts +9 -10
  129. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  130. package/dist/components/ui/inspector-theme.js +39 -72
  131. package/dist/components/ui/load-more-button.js +1 -1
  132. package/dist/components/ui/menu-dropdown.js +1 -1
  133. package/dist/components/ui/skeleton.js +1 -1
  134. package/dist/components/ui/spinner.js +1 -1
  135. package/dist/components/ui/timestamp-tooltip.js +1 -1
  136. package/dist/components/workflow-trace-view.d.ts +3 -1
  137. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  138. package/dist/components/workflow-trace-view.js +10 -5
  139. package/dist/components/workflow-traces/event-colors.js +1 -1
  140. package/dist/components/workflow-traces/trace-colors.js +1 -1
  141. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  142. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  143. package/dist/components/workflow-traces/trace-span-construction.js +19 -6
  144. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  145. package/dist/hooks/use-dark-mode.js +1 -1
  146. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  147. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  148. package/dist/hooks/use-reduced-motion.js +29 -0
  149. package/dist/index.d.ts +3 -1
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +3 -2
  152. package/dist/lib/event-analysis.js +1 -1
  153. package/dist/lib/event-materialization.js +1 -1
  154. package/dist/lib/exact-event-search-id.d.ts +25 -0
  155. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  156. package/dist/lib/exact-event-search-id.js +39 -0
  157. package/dist/lib/hydration.d.ts +15 -2
  158. package/dist/lib/hydration.d.ts.map +1 -1
  159. package/dist/lib/hydration.js +168 -16
  160. package/dist/lib/stream-display.d.ts +18 -0
  161. package/dist/lib/stream-display.d.ts.map +1 -0
  162. package/dist/lib/stream-display.js +30 -0
  163. package/dist/lib/toast.js +1 -1
  164. package/dist/lib/trace-builder.d.ts.map +1 -1
  165. package/dist/lib/trace-builder.js +3 -2
  166. package/dist/lib/utils.d.ts +9 -9
  167. package/dist/lib/utils.d.ts.map +1 -1
  168. package/dist/lib/utils.js +27 -32
  169. package/dist/styles.css +475 -1
  170. package/package.json +6 -4
  171. package/src/components/event-list-view.tsx +447 -282
  172. package/src/components/index.ts +12 -1
  173. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  174. package/src/components/new-trace-viewer/components/event-list.tsx +127 -0
  175. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  176. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  177. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  178. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  179. package/src/components/new-trace-viewer/components/split-pane.tsx +185 -0
  180. package/src/components/new-trace-viewer/components/timeline.tsx +552 -0
  181. package/src/components/new-trace-viewer/context.tsx +92 -0
  182. package/src/components/new-trace-viewer/detail-panel.tsx +55 -0
  183. package/src/components/new-trace-viewer/icons.tsx +79 -0
  184. package/src/components/new-trace-viewer/search.ts +178 -0
  185. package/src/components/new-trace-viewer/trace-viewer.tsx +694 -0
  186. package/src/components/new-trace-viewer/types.ts +0 -0
  187. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  188. package/src/components/new-trace-viewer/utils.ts +482 -0
  189. package/src/components/run-trace-view.tsx +3 -0
  190. package/src/components/sidebar/attribute-panel.tsx +240 -196
  191. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  192. package/src/components/sidebar/detail-card.tsx +119 -59
  193. package/src/components/sidebar/entity-detail-panel.tsx +92 -148
  194. package/src/components/sidebar/events-list.tsx +93 -108
  195. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  196. package/src/components/stream-viewer.tsx +18 -40
  197. package/src/components/trace-viewer/util/timing.ts +5 -9
  198. package/src/components/trace-viewer-new.tsx +49 -0
  199. package/src/components/ui/button.tsx +68 -0
  200. package/src/components/ui/data-inspector.tsx +427 -31
  201. package/src/components/ui/error-stack-block.tsx +39 -30
  202. package/src/components/ui/icon-button.tsx +57 -0
  203. package/src/components/ui/inspector-theme.ts +39 -81
  204. package/src/components/workflow-trace-view.tsx +12 -2
  205. package/src/components/workflow-traces/trace-span-construction.ts +24 -6
  206. package/src/hooks/use-reduced-motion.ts +34 -0
  207. package/src/index.ts +9 -0
  208. package/src/lib/exact-event-search-id.ts +67 -0
  209. package/src/lib/hydration.ts +182 -14
  210. package/src/lib/stream-display.ts +48 -0
  211. package/src/lib/trace-builder.ts +2 -1
  212. package/src/lib/utils.ts +27 -32
  213. package/src/styles.css +475 -1
  214. package/dist/components/error-boundary.js.map +0 -1
  215. package/dist/components/event-list-view.js.map +0 -1
  216. package/dist/components/hook-actions.js.map +0 -1
  217. package/dist/components/index.js.map +0 -1
  218. package/dist/components/run-trace-view.js.map +0 -1
  219. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  220. package/dist/components/sidebar/conversation-view.js.map +0 -1
  221. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  222. package/dist/components/sidebar/detail-card.js.map +0 -1
  223. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  224. package/dist/components/sidebar/events-list.js.map +0 -1
  225. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  226. package/dist/components/stream-viewer.js.map +0 -1
  227. package/dist/components/trace-viewer/components/map.js.map +0 -1
  228. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  229. package/dist/components/trace-viewer/components/node.js.map +0 -1
  230. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  231. package/dist/components/trace-viewer/components/search.js.map +0 -1
  232. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  233. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  234. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  235. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  236. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  237. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  238. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  239. package/dist/components/trace-viewer/context.js.map +0 -1
  240. package/dist/components/trace-viewer/index.js.map +0 -1
  241. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  242. package/dist/components/trace-viewer/types.js.map +0 -1
  243. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  244. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  245. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  246. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  247. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  248. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  249. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  250. package/dist/components/trace-viewer/worker.js.map +0 -1
  251. package/dist/components/ui/alert.js.map +0 -1
  252. package/dist/components/ui/card.js.map +0 -1
  253. package/dist/components/ui/data-inspector.js.map +0 -1
  254. package/dist/components/ui/decrypt-button.js.map +0 -1
  255. package/dist/components/ui/error-card.js.map +0 -1
  256. package/dist/components/ui/error-stack-block.js.map +0 -1
  257. package/dist/components/ui/inspector-theme.js.map +0 -1
  258. package/dist/components/ui/load-more-button.js.map +0 -1
  259. package/dist/components/ui/menu-dropdown.js.map +0 -1
  260. package/dist/components/ui/skeleton.js.map +0 -1
  261. package/dist/components/ui/spinner.js.map +0 -1
  262. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  263. package/dist/components/workflow-trace-view.js.map +0 -1
  264. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  265. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  266. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  267. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  268. package/dist/hooks/use-dark-mode.js.map +0 -1
  269. package/dist/index.js.map +0 -1
  270. package/dist/lib/event-analysis.js.map +0 -1
  271. package/dist/lib/event-materialization.js.map +0 -1
  272. package/dist/lib/hydration.js.map +0 -1
  273. package/dist/lib/toast.js.map +0 -1
  274. package/dist/lib/trace-builder.js.map +0 -1
  275. package/dist/lib/utils.js.map +0 -1
@@ -1,16 +1,15 @@
1
1
  'use client';
2
2
 
3
- import type { Event } from '@workflow/world';
4
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
- import { isExpiredMarker } from '../../lib/hydration';
3
+ import { EVENT_DATA_REF_FIELDS, type Event } from '@workflow/world';
4
+ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
+ import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
6
6
  import { ErrorCard } from '../ui/error-card';
7
7
  import {
8
8
  ErrorStackBlock,
9
9
  isStructuredErrorWithStack,
10
10
  } from '../ui/error-stack-block';
11
11
  import { Skeleton } from '../ui/skeleton';
12
- import { localMillisecondTime } from './attribute-panel';
13
- import { CopyableDataBlock } from './copyable-data-block';
12
+ import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
14
13
  import { DetailCard } from './detail-card';
15
14
 
16
15
  /**
@@ -55,62 +54,74 @@ function EventItem({
55
54
  const [isLoading, setIsLoading] = useState(false);
56
55
  const [loadError, setLoadError] = useState<string | null>(null);
57
56
  const wasExpandedRef = useRef(false);
57
+ /** Mirrors whether we have a fetched payload; avoids stale `loadedData` in load closure. */
58
+ const loadedDataRef = useRef<unknown | null>(null);
58
59
 
59
60
  // Check if the event already has eventData from the store
60
61
  const existingData =
61
62
  'eventData' in event && event.eventData != null ? event.eventData : null;
62
- const displayData = existingData ?? loadedData;
63
+ const mergedDisplay = loadedData ?? existingData;
63
64
  const canHaveData = DATA_EVENT_TYPES.has(event.eventType);
64
65
 
65
- const loadEventData = useCallback(async () => {
66
- if (!onLoadEventData || !event.correlationId || !event.eventId) return;
66
+ const loadEventData = useCallback(
67
+ async (options?: { force?: boolean }) => {
68
+ if (!onLoadEventData || !event.correlationId || !event.eventId) return;
69
+ if (!options?.force && loadedDataRef.current !== null) {
70
+ return;
71
+ }
67
72
 
68
- try {
69
- setIsLoading(true);
70
- setLoadError(null);
71
- const data = await onLoadEventData(event.correlationId, event.eventId);
72
- setLoadedData(data);
73
- } catch (err) {
74
- setLoadError(err instanceof Error ? err.message : String(err));
75
- } finally {
76
- setIsLoading(false);
77
- }
78
- }, [onLoadEventData, event.correlationId, event.eventId]);
73
+ try {
74
+ setIsLoading(true);
75
+ setLoadError(null);
76
+ const data = await onLoadEventData(event.correlationId, event.eventId);
77
+ loadedDataRef.current = data;
78
+ setLoadedData(data);
79
+ } catch (err) {
80
+ setLoadError(err instanceof Error ? err.message : String(err));
81
+ } finally {
82
+ setIsLoading(false);
83
+ }
84
+ },
85
+ [onLoadEventData, event.correlationId, event.eventId]
86
+ );
79
87
 
80
88
  const handleExpand = useCallback(async () => {
81
- if (existingData || loadedData !== null || isLoading) return;
89
+ if (isLoading) return;
82
90
  wasExpandedRef.current = true;
83
91
  await loadEventData();
84
- }, [existingData, loadedData, isLoading, loadEventData]);
92
+ }, [isLoading, loadEventData]);
85
93
 
86
94
  // When the encryption key changes and this event was previously expanded,
87
95
  // re-load the data so it gets decrypted
88
- useEffect(() => {
89
- if (encryptionKey && wasExpandedRef.current && loadedData !== null) {
90
- setLoadedData(null); // clear stale data
91
- loadEventData();
92
- }
93
- // eslint-disable-next-line react-hooks/exhaustive-deps
94
- }, [encryptionKey]);
96
+ useLayoutEffect(() => {
97
+ if (!encryptionKey || !wasExpandedRef.current) return;
98
+ loadedDataRef.current = null;
99
+ setLoadedData(null);
100
+ void loadEventData({ force: true });
101
+ }, [encryptionKey, loadEventData]);
95
102
 
96
103
  const createdAt = new Date(event.createdAt);
104
+ const createdAtTime = createdAt.toLocaleTimeString(undefined, {
105
+ hour: 'numeric',
106
+ minute: 'numeric',
107
+ second: 'numeric',
108
+ });
109
+
110
+ const displayPayload = isLoading ? loadedData : mergedDisplay;
97
111
 
98
112
  return (
99
113
  <DetailCard
100
- summaryClassName="text-base py-2"
114
+ variant="card"
115
+ summaryClassName="px-3 py-2"
101
116
  summary={
102
- <>
103
- <span
104
- className="font-medium"
105
- style={{ color: 'var(--ds-gray-1000)' }}
106
- >
117
+ <div className="flex w-full items-center justify-between gap-3">
118
+ <span className="text-gray-1000 text-label-12 font-mono">
107
119
  {event.eventType}
108
- </span>{' '}
109
- -{' '}
110
- <span style={{ color: 'var(--ds-gray-700)' }}>
111
- {localMillisecondTime(createdAt.getTime())}
112
120
  </span>
113
- </>
121
+ <span className="shrink-0 text-label-13 text-gray-900">
122
+ {createdAtTime}
123
+ </span>
124
+ </div>
114
125
  }
115
126
  onToggle={
116
127
  canHaveData
@@ -121,44 +132,17 @@ function EventItem({
121
132
  }
122
133
  >
123
134
  {/* Event attributes */}
124
- <div
125
- className="flex flex-col divide-y rounded-md border overflow-hidden"
126
- style={{
127
- borderColor: 'var(--ds-gray-300)',
128
- backgroundColor: 'var(--ds-gray-100)',
129
- }}
130
- >
131
- <div
132
- className="flex min-h-[32px] items-center justify-between gap-4 px-2.5 py-1.5"
133
- style={{ borderColor: 'var(--ds-gray-300)' }}
134
- >
135
- <span className="text-[14px]" style={{ color: 'var(--ds-gray-700)' }}>
136
- eventId
137
- </span>
138
- <span
139
- className="max-w-[70%] truncate text-right text-[13px] font-mono"
140
- style={{ color: 'var(--ds-gray-1000)' }}
141
- title={event.eventId}
142
- >
135
+ <div className="flex flex-col bg-background-200 [&:has(+_*)]:border-b [&:has(+_*)]:border-gray-alpha-400">
136
+ <div className="flex items-center justify-between gap-2 py-2 px-3">
137
+ <span className="text-label-12 text-gray-900">Event ID</span>
138
+ <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
143
139
  {event.eventId}
144
140
  </span>
145
141
  </div>
146
142
  {event.correlationId && (
147
- <div
148
- className="flex min-h-[32px] items-center justify-between gap-4 px-2.5 py-1.5"
149
- style={{ borderColor: 'var(--ds-gray-300)' }}
150
- >
151
- <span
152
- className="text-[14px]"
153
- style={{ color: 'var(--ds-gray-700)' }}
154
- >
155
- correlationId
156
- </span>
157
- <span
158
- className="max-w-[70%] truncate text-right text-[13px] font-mono"
159
- style={{ color: 'var(--ds-gray-1000)' }}
160
- title={event.correlationId}
161
- >
143
+ <div className="flex items-center justify-between gap-2 py-2 px-3">
144
+ <span className="text-label-12 text-gray-900">Correlation ID</span>
145
+ <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
162
146
  {event.correlationId}
163
147
  </span>
164
148
  </div>
@@ -167,12 +151,7 @@ function EventItem({
167
151
 
168
152
  {/* Loading state */}
169
153
  {isLoading && (
170
- <div
171
- className="mt-2 rounded-md border p-3"
172
- style={{
173
- borderColor: 'var(--ds-gray-300)',
174
- }}
175
- >
154
+ <div className="p-3">
176
155
  <Skeleton className="h-4 w-[35%]" />
177
156
  <Skeleton className="mt-2 h-4 w-[90%]" />
178
157
  <Skeleton className="mt-2 h-4 w-[75%]" />
@@ -189,9 +168,9 @@ function EventItem({
189
168
  )}
190
169
 
191
170
  {/* Event data */}
192
- {displayData != null && (
193
- <div className="mt-2">
194
- <EventDataBlock eventType={event.eventType} data={displayData} />
171
+ {displayPayload != null && (
172
+ <div className="[&>div]:border-none [&>div]:rounded-none">
173
+ <EventDataBlock eventType={event.eventType} data={displayPayload} />
195
174
  </div>
196
175
  )}
197
176
  </DetailCard>
@@ -199,17 +178,17 @@ function EventItem({
199
178
  }
200
179
 
201
180
  /**
202
- * Check if an eventData object has only expired marker values in its serialized
203
- * sub-fields (result, input, output, metadata, payload). Non-serialized fields
204
- * like `resumeAt` or `reason` are ignored.
181
+ * Check if an eventData object has only expired marker values in ref/payload
182
+ * fields for this event type (see {@link EVENT_DATA_REF_FIELDS}). Other keys
183
+ * (e.g. `resumeAt`, `stepName`) are ignored.
205
184
  */
206
- function hasOnlyExpiredFields(data: unknown): boolean {
185
+ function hasOnlyExpiredFields(data: unknown, eventType: string): boolean {
207
186
  if (data === null || typeof data !== 'object' || Array.isArray(data)) {
208
187
  return false;
209
188
  }
210
189
  const record = data as Record<string, unknown>;
211
- const serializedKeys = ['result', 'input', 'output', 'metadata', 'payload'];
212
- const presentKeys = serializedKeys.filter((k) => k in record);
190
+ const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
191
+ const presentKeys = refKeys.filter((k) => k in record);
213
192
  return (
214
193
  presentKeys.length > 0 &&
215
194
  presentKeys.every((k) => isExpiredMarker(record[k]))
@@ -230,7 +209,7 @@ function EventDataBlock({
230
209
  // Expired data — show a simple message instead of the raw stub.
231
210
  // Check both the top-level eventData and nested sub-fields (result, input, etc.)
232
211
  // since the server stubs each ref field independently.
233
- if (isExpiredMarker(data) || hasOnlyExpiredFields(data)) {
212
+ if (isExpiredMarker(data) || hasOnlyExpiredFields(data, eventType)) {
234
213
  return (
235
214
  <div
236
215
  className="flex items-center gap-1.5 rounded-md border px-3 py-2 text-xs"
@@ -245,6 +224,10 @@ function EventDataBlock({
245
224
  );
246
225
  }
247
226
 
227
+ if (hasEncryptedFields({ eventType, eventData: data })) {
228
+ return <EncryptedDataBlock />;
229
+ }
230
+
248
231
  // For error events (step_failed, step_retrying), the eventData has the shape
249
232
  // { error: StructuredError, stack?: string, ... }. Check both the top-level
250
233
  // value and the nested `error` field for a stack trace.
@@ -298,26 +281,28 @@ export function EventsList({
298
281
  [events]
299
282
  );
300
283
 
284
+ const hasEvents = sortedEvents.length > 0 && !error;
285
+
286
+ if (!hasEvents && !isLoading) {
287
+ return <DetailCard summary="Events" disabled />;
288
+ }
289
+
301
290
  return (
302
- <div className="mt-2" style={{ color: 'var(--ds-gray-1000)' }}>
303
- <h3
304
- className="text-heading-16 font-medium mt-4 mb-2"
305
- style={{ color: 'var(--ds-gray-1000)' }}
306
- >
307
- Events {!isLoading && `(${sortedEvents.length})`}
308
- </h3>
291
+ <DetailCard summary="Events" contentClassName="mb-0" defaultOpen>
309
292
  {isLoading ? (
310
- <div className="flex flex-col gap-4">
311
- <Skeleton className="h-9 w-full rounded-md" />
312
- <Skeleton className="h-9 w-full rounded-md" />
313
- <Skeleton className="h-9 w-full rounded-md" />
293
+ <div className="flex flex-col -mx-4">
294
+ {[0, 1, 2].map((i) => (
295
+ <div
296
+ key={i}
297
+ className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
298
+ >
299
+ <Skeleton className="h-4 w-32 rounded" />
300
+ <Skeleton className="h-3 w-16 rounded" />
301
+ </div>
302
+ ))}
314
303
  </div>
315
- ) : null}
316
- {!isLoading && !error && sortedEvents.length === 0 && (
317
- <div className="text-sm">No events found</div>
318
- )}
319
- {sortedEvents.length > 0 && !error ? (
320
- <div className="flex flex-col gap-4">
304
+ ) : (
305
+ <div className="flex flex-col -mx-4">
321
306
  {sortedEvents.map((event) => (
322
307
  <EventItem
323
308
  key={event.eventId}
@@ -327,7 +312,7 @@ export function EventsList({
327
312
  />
328
313
  ))}
329
314
  </div>
330
- ) : null}
331
- </div>
315
+ )}
316
+ </DetailCard>
332
317
  );
333
318
  }
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, type ReactNode } from 'react';
4
+ import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
5
+ import type { SpanSelectionInfo } from './entity-detail-panel';
6
+
7
+ export interface SidebarDataContextValue {
8
+ run: WorkflowRun;
9
+ events: Event[];
10
+ spanDetailData: WorkflowRun | Step | Hook | Event | null;
11
+ spanDetailError?: Error | null;
12
+ spanDetailLoading?: boolean;
13
+ onSpanSelect: (info: SpanSelectionInfo) => void;
14
+ onStreamClick?: (streamId: string) => void;
15
+ onRunClick?: (runId: string) => void;
16
+ onWakeUpSleep?: (
17
+ runId: string,
18
+ correlationId: string
19
+ ) => Promise<{ stoppedCount: number }>;
20
+ onLoadEventData?: (
21
+ correlationId: string,
22
+ eventId: string
23
+ ) => Promise<unknown | null>;
24
+ onResolveHook?: (
25
+ hookToken: string,
26
+ payload: unknown,
27
+ hook?: Hook
28
+ ) => Promise<void>;
29
+ encryptionKey?: Uint8Array;
30
+ onDecrypt?: () => void;
31
+ isDecrypting?: boolean;
32
+ hasEncryptedData?: boolean;
33
+ }
34
+
35
+ const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);
36
+ SidebarDataContext.displayName = 'SidebarDataContext';
37
+
38
+ export function SidebarDataProvider({
39
+ value,
40
+ children,
41
+ }: {
42
+ value: SidebarDataContextValue;
43
+ children: ReactNode;
44
+ }) {
45
+ return (
46
+ <SidebarDataContext.Provider value={value}>
47
+ {children}
48
+ </SidebarDataContext.Provider>
49
+ );
50
+ }
51
+
52
+ export function useSidebarData(): SidebarDataContextValue {
53
+ const ctx = useContext(SidebarDataContext);
54
+ if (!ctx) {
55
+ throw new Error('useSidebarData must be used within a SidebarDataProvider');
56
+ }
57
+ return ctx;
58
+ }
59
+
60
+ export function useSidebarDataOptional(): SidebarDataContextValue | null {
61
+ return useContext(SidebarDataContext);
62
+ }
@@ -5,37 +5,13 @@ import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
5
5
  import { DataInspector } from './ui/data-inspector';
6
6
  import { Skeleton } from './ui/skeleton';
7
7
 
8
- // ──────────────────────────────────────────────────────────────────────────
9
- // Helpers
10
- // ──────────────────────────────────────────────────────────────────────────
11
-
12
- function deserializeChunkText(text: string): string {
13
- try {
14
- const parsed = JSON.parse(text);
15
- if (typeof parsed === 'string') {
16
- return parsed;
17
- }
18
- return JSON.stringify(parsed, null, 2);
19
- } catch {
20
- return text;
21
- }
22
- }
23
-
24
- function parseChunkData(text: string): unknown {
25
- try {
26
- return JSON.parse(text);
27
- } catch {
28
- return text;
29
- }
30
- }
31
-
32
8
  // ──────────────────────────────────────────────────────────────────────────
33
9
  // Types
34
10
  // ──────────────────────────────────────────────────────────────────────────
35
11
 
36
12
  export interface StreamChunk {
37
13
  id: number;
38
- text: string;
14
+ value: unknown;
39
15
  }
40
16
 
41
17
  type Chunk = StreamChunk;
@@ -62,8 +38,6 @@ const ChunkRow = React.memo(function ChunkRow({
62
38
  chunk: Chunk;
63
39
  index: number;
64
40
  }) {
65
- const parsed = parseChunkData(chunk.text);
66
-
67
41
  return (
68
42
  <div
69
43
  className="text-[11px] rounded-md border p-3"
@@ -72,22 +46,26 @@ const ChunkRow = React.memo(function ChunkRow({
72
46
  backgroundColor: 'var(--ds-gray-100)',
73
47
  }}
74
48
  >
75
- <span
76
- className="select-none mr-2"
77
- style={{ color: 'var(--ds-gray-500)' }}
78
- >
79
- [{index}]
80
- </span>
81
- {typeof parsed === 'string' ? (
49
+ <div className="flex items-start gap-2">
82
50
  <span
83
- className="whitespace-pre-wrap break-words"
84
- style={{ color: 'var(--ds-gray-1000)' }}
51
+ className="select-none pt-px"
52
+ style={{ color: 'var(--ds-gray-500)' }}
85
53
  >
86
- {deserializeChunkText(parsed)}
54
+ [{index}]
87
55
  </span>
88
- ) : (
89
- <DataInspector data={parsed} expandLevel={1} />
90
- )}
56
+ <div className="min-w-0 flex-1">
57
+ {typeof chunk.value === 'string' ? (
58
+ <span
59
+ className="whitespace-pre-wrap break-words"
60
+ style={{ color: 'var(--ds-gray-1000)' }}
61
+ >
62
+ {chunk.value}
63
+ </span>
64
+ ) : (
65
+ <DataInspector data={chunk.value} expandLevel={1} />
66
+ )}
67
+ </div>
68
+ </div>
91
69
  </div>
92
70
  );
93
71
  });
@@ -14,22 +14,18 @@ export function getMsInHighRes(ms: number): [number, number] {
14
14
  }
15
15
 
16
16
  /**
17
- * Formats a duration for timeline display (compact single-unit format).
17
+ * Formats a duration for timeline display (compact format).
18
18
  * @deprecated Use formatDuration(ms, true) instead.
19
19
  */
20
20
  export const formatDurationForTimeline = (ms: number): string =>
21
21
  formatDuration(ms, true);
22
22
 
23
- const timeSelectionFormatter = new Intl.NumberFormat(undefined, {
24
- minimumFractionDigits: 2,
25
- maximumFractionDigits: 2,
26
- });
27
-
28
23
  export function formatTimeSelection(ms: number): string {
29
- if (ms >= 1000) {
30
- return `${timeSelectionFormatter.format(ms / 1000)}s`;
24
+ const roundedMs = Math.round(ms);
25
+ if (roundedMs < 1000) {
26
+ return `${roundedMs}ms`;
31
27
  }
32
- return `${timeSelectionFormatter.format(ms)}ms`;
28
+ return `${Math.round(ms / 1000)}s`;
33
29
  }
34
30
 
35
31
  /**
@@ -0,0 +1,49 @@
1
+ import { useMemo } from 'react';
2
+ import type { WorkflowRun } from '@workflow/core/runtime';
3
+ import type { Event } from '@workflow/world';
4
+ import { buildTrace, type TraceWithMeta } from '../lib/trace-builder';
5
+ import { NewTraceViewer as NewTraceViewerComponent } from './new-trace-viewer/trace-viewer';
6
+ import {
7
+ SidebarDataProvider,
8
+ type SidebarDataContextValue,
9
+ } from './sidebar/sidebar-data-context';
10
+ import type { Trace } from './trace-viewer/types';
11
+
12
+ const NewTraceViewer = ({
13
+ run,
14
+ events,
15
+ sidebarData,
16
+ }: {
17
+ run: WorkflowRun;
18
+ events: Event[];
19
+ sidebarData: SidebarDataContextValue;
20
+ }) => {
21
+ // Build trace only when actual data changes — no timer-driven rebuilds.
22
+ // Active span widths are animated imperatively by useLiveTick at 60fps.
23
+ const traceWithMeta: TraceWithMeta | undefined = useMemo(() => {
24
+ if (!run?.runId) {
25
+ return undefined;
26
+ }
27
+ return buildTrace(run, events, new Date());
28
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- `new Date()` is intentionally not a dep; useLiveTick handles live growth
29
+ }, [run, events]);
30
+ const trace = traceWithMeta;
31
+
32
+ if (!trace) {
33
+ return (
34
+ <div className="relative w-full h-full flex items-center justify-center">
35
+ <div className="text-gray-500 text-sm">Loading trace…</div>
36
+ </div>
37
+ );
38
+ }
39
+
40
+ return (
41
+ <SidebarDataProvider value={sidebarData}>
42
+ <div className="relative w-full h-full flex">
43
+ <NewTraceViewerComponent trace={trace as Trace} />
44
+ </div>
45
+ </SidebarDataProvider>
46
+ );
47
+ };
48
+
49
+ export { NewTraceViewer };
@@ -0,0 +1,68 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ const buttonVariants = cva(
6
+ [
7
+ 'outline-none m-0 border-0 align-baseline no-underline group/trigger',
8
+ 'relative cursor-pointer select-none transform translate-z-0',
9
+ 'inline-flex max-w-full items-center justify-center gap-x-1 whitespace-nowrap rounded-md font-medium',
10
+ '!text-gray-100',
11
+ 'bg-gray-1000',
12
+ 'transition-[border-color,background,color,transform,box-shadow] duration-150 ease-in-out',
13
+ // Geist invert hover: literal fallbacks (no token dep, so it resolves in any
14
+ // consuming app), darkened via ancestor theme class without a registered variant.
15
+ 'hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_22%))]',
16
+ '[.dark-theme_&]:hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_80%))]',
17
+ '[[data-theme=dark]_&]:hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_80%))]',
18
+ // Geist focus ring as arbitrary properties (no bare `outline`/ambiguous
19
+ // arbitrary-color utilities, which differ between Tailwind v3 and v4).
20
+ 'focus-visible:[outline:2px_solid_var(--ds-focus-color)] focus-visible:[outline-offset:2px]',
21
+ // disabled styles
22
+ 'disabled:cursor-not-allowed aria-disabled:cursor-not-allowed',
23
+ 'disabled:bg-gray-100 disabled:!text-gray-700 disabled:hover:bg-gray-100',
24
+ 'aria-disabled:bg-gray-100 aria-disabled:text-gray-700 aria-disabled:hover:bg-gray-100',
25
+ ],
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: '',
30
+ secondary:
31
+ 'border border-gray-alpha-400 [--themed-bg:_var(--ds-background-100)] [--themed-fg:_var(--ds-gray-1000)] [--themed-hover-bg:_var(--ds-gray-alpha-200)]',
32
+ ghost:
33
+ '[--themed-bg:_transparent] [--themed-fg:_var(--ds-gray-1000)] [--themed-hover-bg:_var(--ds-gray-alpha-100)]',
34
+ },
35
+ size: {
36
+ default: 'h-10 px-4 text-[14px]',
37
+ sm: 'h-8 px-3 text-[14px]',
38
+ xs: 'h-6 px-1.5 py-0.5 text-button-12 rounded-[4px]',
39
+ icon: 'h-8 w-8',
40
+ },
41
+ },
42
+ defaultVariants: {
43
+ variant: 'default',
44
+ size: 'default',
45
+ },
46
+ }
47
+ );
48
+
49
+ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
50
+ VariantProps<typeof buttonVariants>;
51
+
52
+ export function Button({
53
+ className,
54
+ variant,
55
+ size,
56
+ type = 'button',
57
+ ...props
58
+ }: ButtonProps) {
59
+ return (
60
+ <button
61
+ type={type}
62
+ className={cn(buttonVariants({ variant, size, className }))}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ export { buttonVariants };