@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
@@ -15,15 +15,26 @@ export {
15
15
  } from './hook-actions';
16
16
  export { RunTraceView } from './run-trace-view';
17
17
  export { ConversationView } from './sidebar/conversation-view';
18
+ export {
19
+ SidebarDataProvider,
20
+ type SidebarDataContextValue,
21
+ } from './sidebar/sidebar-data-context';
18
22
  export type {
19
23
  SelectedSpanInfo,
20
24
  SpanSelectionInfo,
21
25
  } from './sidebar/entity-detail-panel';
22
26
  export { type StreamChunk, StreamViewer } from './stream-viewer';
23
27
  export type { Span, SpanEvent } from './trace-viewer/types';
24
- export { DataInspector, type DataInspectorProps } from './ui/data-inspector';
28
+ export {
29
+ DataInspector,
30
+ type DataInspectorProps,
31
+ DecryptClickContext,
32
+ type DecryptClickContextValue,
33
+ } from './ui/data-inspector';
25
34
  export { DecryptButton } from './ui/decrypt-button';
35
+ export { IconButton } from './ui/icon-button';
26
36
  export { LoadMoreButton } from './ui/load-more-button';
27
37
  export { MenuDropdown, type MenuDropdownOption } from './ui/menu-dropdown';
28
38
  export { Spinner } from './ui/spinner';
29
39
  export { WorkflowTraceViewer } from './workflow-trace-view';
40
+ export { NewTraceViewer } from './trace-viewer-new';
@@ -0,0 +1,68 @@
1
+ 'use client';
2
+
3
+ import { Check, Copy } from 'lucide-react';
4
+ import { type JSX, useEffect, useRef, useState } from 'react';
5
+ import { cn } from '../../../lib/utils';
6
+
7
+ interface CopyButtonProps {
8
+ copyText: string;
9
+ ariaLabel: string;
10
+ className?: string;
11
+ }
12
+
13
+ export function CopyButton({
14
+ copyText,
15
+ ariaLabel,
16
+ className,
17
+ }: CopyButtonProps): JSX.Element {
18
+ const [copied, setCopied] = useState(false);
19
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
20
+
21
+ useEffect(() => {
22
+ return () => {
23
+ if (timeoutRef.current) {
24
+ clearTimeout(timeoutRef.current);
25
+ }
26
+ };
27
+ }, []);
28
+
29
+ return (
30
+ <button
31
+ type="button"
32
+ aria-label={ariaLabel}
33
+ className={cn(
34
+ 'cursor-pointer text-gray-800 hover:text-gray-1000 bg-transparent border-0 p-1 m-0',
35
+ className
36
+ )}
37
+ onClick={(e) => {
38
+ e.stopPropagation();
39
+ if (timeoutRef.current) {
40
+ clearTimeout(timeoutRef.current);
41
+ }
42
+ void navigator.clipboard.writeText(copyText).then(() => {
43
+ setCopied(true);
44
+ timeoutRef.current = setTimeout(() => setCopied(false), 1000);
45
+ });
46
+ }}
47
+ >
48
+ <div className="relative w-3 h-3">
49
+ <div
50
+ className={cn(
51
+ 'absolute inset-0 flex items-center justify-center transition-all duration-150 ease-out',
52
+ copied ? 'scale-100 opacity-100' : 'scale-0 opacity-0'
53
+ )}
54
+ >
55
+ <Check className="w-3 h-3" />
56
+ </div>
57
+ <div
58
+ className={cn(
59
+ 'absolute inset-0 flex items-center justify-center transition-all duration-150 ease-out',
60
+ copied ? 'scale-0 opacity-0' : 'scale-100 opacity-100'
61
+ )}
62
+ >
63
+ <Copy className="w-3 h-3" />
64
+ </div>
65
+ </div>
66
+ </button>
67
+ );
68
+ }
@@ -0,0 +1,127 @@
1
+ import { Circle } from 'lucide-react';
2
+ import { cn } from '../../../lib/utils';
3
+ import type { Span } from '../../trace-viewer/types';
4
+ import { formatDuration } from '../../trace-viewer/util/timing';
5
+ import {
6
+ SleepIcon,
7
+ StepForwardIcon,
8
+ WebhookIcon,
9
+ WorkflowIcon,
10
+ } from '../icons';
11
+ import { isSpanDimmedBySearch, type SpanSearchResult } from '../search';
12
+ import { getSpanDurationMs } from '../utils';
13
+ import { MiddleTruncate } from './middle-truncate/middle-truncate';
14
+
15
+ interface EventStyle {
16
+ icon: React.ComponentType<{ className?: string }>;
17
+ className: string;
18
+ }
19
+
20
+ const eventStyles: Record<string, EventStyle> = {
21
+ run: { icon: WorkflowIcon, className: 'text-blue-900' },
22
+ step: { icon: StepForwardIcon, className: 'text-green-900' },
23
+ hook: { icon: WebhookIcon, className: 'text-gray-900' },
24
+ sleep: { icon: SleepIcon, className: 'text-gray-900' },
25
+ };
26
+
27
+ const defaultStyle: EventStyle = {
28
+ icon: Circle,
29
+ className: 'text-gray-900',
30
+ };
31
+
32
+ const ROW_HEIGHT_CLASS = 'h-10';
33
+
34
+ function getEventStyle(resource: string, isErrored: boolean): EventStyle {
35
+ const style = eventStyles[resource] ?? defaultStyle;
36
+ return {
37
+ icon: style.icon,
38
+ className: cn(isErrored ? 'text-red-900' : style.className),
39
+ };
40
+ }
41
+
42
+ const EventRow = ({
43
+ span,
44
+ isSelected,
45
+ isDimmed,
46
+ onSelectSpan,
47
+ }: {
48
+ span: Span;
49
+ isSelected: boolean;
50
+ isDimmed?: boolean;
51
+ onSelectSpan: (spanId: string) => void;
52
+ }) => {
53
+ const durationMs = getSpanDurationMs(span);
54
+ const isErrored =
55
+ (span.attributes.data as Record<string, unknown>).status === 'failed';
56
+ const { icon: Icon, className: tagClassName } = getEventStyle(
57
+ span.resource,
58
+ isErrored
59
+ );
60
+
61
+ return (
62
+ <li
63
+ className={cn(
64
+ 'relative overflow-clip group transition-opacity',
65
+ ROW_HEIGHT_CLASS,
66
+ isDimmed && 'opacity-35'
67
+ )}
68
+ role="treeitem"
69
+ aria-selected={isSelected}
70
+ aria-expanded={isSelected}
71
+ aria-level={1}
72
+ onClick={() => onSelectSpan(span.spanId)}
73
+ >
74
+ <div className="h-full hover:bg-gray-100 group-aria-selected:bg-gray-100 group-aria-selected:hover:bg-gray-200">
75
+ <div className="flex h-full min-w-0 items-center pl-4 pr-2">
76
+ <div className="flex min-w-0 flex-1 items-center gap-2">
77
+ <span className={cn('shrink-0', tagClassName)}>
78
+ <Icon className="w-4 h-4" />
79
+ </span>
80
+ <span className="min-w-0 text-label-14">
81
+ <MiddleTruncate value={span.name} />
82
+ </span>
83
+ </div>
84
+ <div className="ml-2 shrink-0">
85
+ <span className="text-label-14 text-gray-900 tabular-nums">
86
+ {formatDuration(durationMs)}
87
+ </span>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ </li>
92
+ );
93
+ };
94
+
95
+ const EventList = ({
96
+ spans,
97
+ activeSpanId,
98
+ searchResult,
99
+ onSelectSpan,
100
+ }: {
101
+ spans: Span[];
102
+ activeSpanId: string | null;
103
+ searchResult: SpanSearchResult;
104
+ onSelectSpan: (spanId: string) => void;
105
+ }) => {
106
+ return (
107
+ <ul
108
+ id="event-list"
109
+ role="tree"
110
+ className="block min-h-0 overflow-visible divide-y divide-gray-alpha-400 border-b border-gray-alpha-400"
111
+ >
112
+ {spans.map((span) => {
113
+ return (
114
+ <EventRow
115
+ key={span.spanId}
116
+ span={span}
117
+ isSelected={span.spanId === activeSpanId}
118
+ isDimmed={isSpanDimmedBySearch(span.spanId, searchResult)}
119
+ onSelectSpan={onSelectSpan}
120
+ />
121
+ );
122
+ })}
123
+ </ul>
124
+ );
125
+ };
126
+
127
+ export default EventList;
@@ -0,0 +1,114 @@
1
+ import { ELLIPSIS, toGraphemes } from './truncate';
2
+
3
+ interface GetMiddleTruncateCopyTextOptions {
4
+ prefixText: string;
5
+ selectionEnd: number;
6
+ selectionStart: number;
7
+ suffixText: string;
8
+ value: string;
9
+ }
10
+
11
+ interface GetMiddleTruncateCopyTextFromSelectionTextOptions {
12
+ prefixText: string;
13
+ selectionText: string;
14
+ suffixText: string;
15
+ value: string;
16
+ }
17
+
18
+ function getMiddleTruncateCopyText({
19
+ prefixText,
20
+ selectionEnd,
21
+ selectionStart,
22
+ suffixText,
23
+ value,
24
+ }: GetMiddleTruncateCopyTextOptions): string | null {
25
+ const visibleText = prefixText + ELLIPSIS + suffixText;
26
+
27
+ if (
28
+ selectionStart < 0 ||
29
+ selectionEnd > visibleText.length ||
30
+ selectionStart >= selectionEnd
31
+ ) {
32
+ return null;
33
+ }
34
+
35
+ if (selectionStart === 0 && selectionEnd === visibleText.length) {
36
+ return value;
37
+ }
38
+
39
+ const ellipsisStart = prefixText.length;
40
+ const ellipsisEnd = ellipsisStart + ELLIPSIS.length;
41
+
42
+ if (selectionStart > ellipsisStart || selectionEnd < ellipsisEnd) {
43
+ return null;
44
+ }
45
+
46
+ const originalGraphemes = toGraphemes(value);
47
+ const selectedPrefixGraphemeCount = toGraphemes(
48
+ visibleText.slice(0, selectionStart)
49
+ ).length;
50
+ const selectedSuffixGraphemeCount = toGraphemes(
51
+ visibleText.slice(ellipsisEnd, selectionEnd)
52
+ ).length;
53
+ const suffixStart = originalGraphemes.length - toGraphemes(suffixText).length;
54
+
55
+ return originalGraphemes
56
+ .slice(
57
+ selectedPrefixGraphemeCount,
58
+ suffixStart + selectedSuffixGraphemeCount
59
+ )
60
+ .join('');
61
+ }
62
+
63
+ function getMiddleTruncateCopyTextFromSelectionText({
64
+ prefixText,
65
+ selectionText,
66
+ suffixText,
67
+ value,
68
+ }: GetMiddleTruncateCopyTextFromSelectionTextOptions): string | null {
69
+ const visibleText = prefixText + ELLIPSIS + suffixText;
70
+ const trimmedSelectionText = selectionText.trim();
71
+
72
+ if (!trimmedSelectionText) {
73
+ return null;
74
+ }
75
+
76
+ const leading = selectionText.slice(
77
+ 0,
78
+ selectionText.length - selectionText.trimStart().length
79
+ );
80
+ const trailing = selectionText.slice(selectionText.trimEnd().length);
81
+
82
+ if (trimmedSelectionText === visibleText) {
83
+ return leading + value + trailing;
84
+ }
85
+
86
+ if (!trimmedSelectionText.includes(ELLIPSIS)) {
87
+ return null;
88
+ }
89
+
90
+ const selectionStart = visibleText.indexOf(trimmedSelectionText);
91
+ if (selectionStart === -1) {
92
+ return null;
93
+ }
94
+
95
+ const selectionEnd = selectionStart + trimmedSelectionText.length;
96
+ const mappedText = getMiddleTruncateCopyText({
97
+ prefixText,
98
+ selectionEnd,
99
+ selectionStart,
100
+ suffixText,
101
+ value,
102
+ });
103
+
104
+ return mappedText === null ? null : leading + mappedText + trailing;
105
+ }
106
+
107
+ export {
108
+ getMiddleTruncateCopyText,
109
+ getMiddleTruncateCopyTextFromSelectionText,
110
+ };
111
+ export type {
112
+ GetMiddleTruncateCopyTextFromSelectionTextOptions,
113
+ GetMiddleTruncateCopyTextOptions,
114
+ };
@@ -0,0 +1,157 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useRef } from 'react';
4
+ import type { ClipboardEvent, ComponentPropsWithoutRef, JSX } from 'react';
5
+ import { cn } from '../../../../lib/utils';
6
+ import {
7
+ getMiddleTruncateCopyText,
8
+ getMiddleTruncateCopyTextFromSelectionText,
9
+ } from './copy-selection';
10
+ import { ELLIPSIS } from './truncate';
11
+ import { useMiddleTruncate } from './use-middle-truncate';
12
+
13
+ /** Props for the {@link MiddleTruncate} component. */
14
+ interface MiddleTruncateProps
15
+ extends Omit<ComponentPropsWithoutRef<'span'>, 'children'> {
16
+ /** The full text string to display, truncated from the middle when it overflows. */
17
+ value: string;
18
+ }
19
+
20
+ function getRangeOffsets(
21
+ container: HTMLElement,
22
+ range: Range
23
+ ): { end: number; start: number } | null {
24
+ if (
25
+ !container.contains(range.startContainer) ||
26
+ !container.contains(range.endContainer)
27
+ ) {
28
+ return null;
29
+ }
30
+
31
+ const startRange = document.createRange();
32
+ startRange.selectNodeContents(container);
33
+ startRange.setEnd(range.startContainer, range.startOffset);
34
+
35
+ const endRange = document.createRange();
36
+ endRange.selectNodeContents(container);
37
+ endRange.setEnd(range.endContainer, range.endOffset);
38
+
39
+ return {
40
+ end: endRange.toString().length,
41
+ start: startRange.toString().length,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * @gdoc
47
+ *
48
+ * Truncates text from the middle with an ellipsis when it overflows, preserving the beginning and end (e.g. file paths, URLs). Copy behavior restores the full untruncated text to the clipboard.
49
+ * Renders a `<span>` element.
50
+ *
51
+ * Documentation: [Geist Middle Truncate](https://vercel.com/geist/middle-truncate)
52
+ *
53
+ * @param value - Full text string to display.
54
+ */
55
+ function MiddleTruncate({
56
+ value,
57
+ className,
58
+ onCopy: onCopyProp,
59
+ ...props
60
+ }: MiddleTruncateProps): JSX.Element {
61
+ const { ref, measureRef, displayText, isTruncated, prefixText, suffixText } =
62
+ useMiddleTruncate(value);
63
+ const visibleRef = useRef<HTMLSpanElement | null>(null);
64
+
65
+ const handleCopy = useCallback(
66
+ (e: ClipboardEvent<HTMLSpanElement>) => {
67
+ onCopyProp?.(e);
68
+ if (e.defaultPrevented || !isTruncated) return;
69
+
70
+ const selection = window.getSelection();
71
+ if (!selection || selection.rangeCount === 0) return;
72
+ const selectionText = selection.toString();
73
+ if (!selectionText) return;
74
+
75
+ // Only intercept when the selection is fully within this element.
76
+ // If the user selected across elements (e.g. a table row), let
77
+ // the browser's default behavior preserve surrounding context.
78
+ const range = selection.getRangeAt(0);
79
+ const visibleEl = visibleRef.current;
80
+ let copyText: string | null = null;
81
+
82
+ if (
83
+ e.currentTarget.contains(range.startContainer) &&
84
+ e.currentTarget.contains(range.endContainer) &&
85
+ visibleEl
86
+ ) {
87
+ const offsets = getRangeOffsets(visibleEl, range);
88
+ if (offsets) {
89
+ copyText = getMiddleTruncateCopyText({
90
+ prefixText,
91
+ selectionEnd: offsets.end,
92
+ selectionStart: offsets.start,
93
+ suffixText,
94
+ value,
95
+ });
96
+ }
97
+ }
98
+
99
+ copyText ??= getMiddleTruncateCopyTextFromSelectionText({
100
+ prefixText,
101
+ selectionText,
102
+ suffixText,
103
+ value,
104
+ });
105
+
106
+ if (copyText === null) return;
107
+
108
+ e.preventDefault();
109
+ e.clipboardData.setData('text/plain', copyText);
110
+ },
111
+ [onCopyProp, isTruncated, prefixText, suffixText, value]
112
+ );
113
+
114
+ return (
115
+ <span
116
+ title={isTruncated ? value : undefined}
117
+ {...props}
118
+ ref={ref}
119
+ className={cn(
120
+ 'relative inline-grid min-w-0 max-w-full overflow-hidden whitespace-nowrap',
121
+ className
122
+ )}
123
+ onCopy={handleCopy}
124
+ >
125
+ {isTruncated && <span className="sr-only select-none">{value}</span>}
126
+ <span
127
+ aria-hidden="true"
128
+ className="pointer-events-none col-start-1 row-start-1 invisible select-none whitespace-nowrap"
129
+ >
130
+ {value}
131
+ </span>
132
+ <span
133
+ aria-hidden={isTruncated || undefined}
134
+ className="col-start-1 row-start-1 min-w-0 overflow-hidden"
135
+ ref={visibleRef}
136
+ >
137
+ {isTruncated ? (
138
+ <>
139
+ <span>{prefixText}</span>
140
+ <span>{ELLIPSIS}</span>
141
+ <span>{suffixText}</span>
142
+ </>
143
+ ) : (
144
+ displayText
145
+ )}
146
+ </span>
147
+ <span
148
+ aria-hidden="true"
149
+ className="pointer-events-none absolute left-0 top-0 inline-block invisible select-none whitespace-nowrap"
150
+ ref={measureRef}
151
+ />
152
+ </span>
153
+ );
154
+ }
155
+
156
+ export { MiddleTruncate };
157
+ export type { MiddleTruncateProps };
@@ -0,0 +1,132 @@
1
+ const ELLIPSIS = '...';
2
+ const MIN_START = 3;
3
+ const MIN_END = 3;
4
+ const MIN_KEPT = MIN_START + MIN_END;
5
+ const graphemeSegmenter =
6
+ typeof Intl !== 'undefined' && 'Segmenter' in Intl
7
+ ? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
8
+ : null;
9
+
10
+ type MeasureFn = (text: string) => number;
11
+
12
+ /**
13
+ * Splits a string into grapheme clusters using Intl.Segmenter.
14
+ * Falls back to Array.from for environments without Intl.Segmenter.
15
+ */
16
+ function toGraphemes(text: string): string[] {
17
+ if (graphemeSegmenter) {
18
+ return [...graphemeSegmenter.segment(text)].map((s) => s.segment);
19
+ }
20
+ return Array.from(text);
21
+ }
22
+
23
+ interface TruncateResult {
24
+ prefixText: string;
25
+ prefixGraphemeCount: number;
26
+ suffixText: string;
27
+ suffixGraphemeCount: number;
28
+ text: string;
29
+ truncated: boolean;
30
+ }
31
+
32
+ function buildCandidate(graphemes: string[], kept: number): TruncateResult {
33
+ if (kept <= 0) {
34
+ return {
35
+ prefixText: '',
36
+ prefixGraphemeCount: 0,
37
+ suffixText: '',
38
+ suffixGraphemeCount: 0,
39
+ text: ELLIPSIS,
40
+ truncated: true,
41
+ };
42
+ }
43
+
44
+ const suffixGraphemeCount =
45
+ kept >= MIN_KEPT
46
+ ? Math.max(MIN_END, Math.floor(kept / 2))
47
+ : Math.floor(kept / 2);
48
+ const prefixGraphemeCount = kept - suffixGraphemeCount;
49
+ const prefixText = graphemes.slice(0, prefixGraphemeCount).join('');
50
+ const suffixText =
51
+ suffixGraphemeCount > 0
52
+ ? graphemes.slice(-suffixGraphemeCount).join('')
53
+ : '';
54
+
55
+ return {
56
+ prefixText,
57
+ prefixGraphemeCount,
58
+ suffixText,
59
+ suffixGraphemeCount,
60
+ text: prefixText + ELLIPSIS + suffixText,
61
+ truncated: true,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Middle-truncates a string so `prefix...suffix` fits within `availableWidth`.
67
+ * Uses binary search over grapheme clusters with an injectable measure function.
68
+ */
69
+ function middleTruncate(
70
+ graphemes: string[],
71
+ availableWidth: number,
72
+ measure: MeasureFn,
73
+ fullWidth?: number
74
+ ): TruncateResult {
75
+ const fullText = graphemes.join('');
76
+ const resolvedFullWidth = fullWidth ?? measure(fullText);
77
+
78
+ if (availableWidth <= 0 || graphemes.length === 0) {
79
+ return {
80
+ prefixText: fullText,
81
+ prefixGraphemeCount: graphemes.length,
82
+ suffixText: '',
83
+ suffixGraphemeCount: 0,
84
+ text: fullText,
85
+ truncated: false,
86
+ };
87
+ }
88
+
89
+ if (resolvedFullWidth <= availableWidth) {
90
+ return {
91
+ prefixText: fullText,
92
+ prefixGraphemeCount: graphemes.length,
93
+ suffixText: '',
94
+ suffixGraphemeCount: 0,
95
+ text: fullText,
96
+ truncated: false,
97
+ };
98
+ }
99
+
100
+ // Binary search for the maximum number of kept graphemes
101
+ let lo = 0;
102
+ let hi = graphemes.length - 1;
103
+ let best = -1;
104
+
105
+ while (lo <= hi) {
106
+ const mid = (lo + hi) >>> 1;
107
+ const candidate = buildCandidate(graphemes, mid);
108
+
109
+ if (measure(candidate.text) <= availableWidth) {
110
+ best = mid;
111
+ lo = mid + 1;
112
+ } else {
113
+ hi = mid - 1;
114
+ }
115
+ }
116
+
117
+ if (best === -1) {
118
+ return {
119
+ prefixText: '',
120
+ prefixGraphemeCount: 0,
121
+ suffixText: '',
122
+ suffixGraphemeCount: 0,
123
+ text: '',
124
+ truncated: true,
125
+ };
126
+ }
127
+
128
+ return buildCandidate(graphemes, best);
129
+ }
130
+
131
+ export { middleTruncate, toGraphemes, ELLIPSIS, MIN_START, MIN_END };
132
+ export type { MeasureFn, TruncateResult };