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

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 (194) hide show
  1. package/README.md +1 -1
  2. package/dist/components/error-boundary.js.map +1 -1
  3. package/dist/components/event-list-view.d.ts +3 -1
  4. package/dist/components/event-list-view.d.ts.map +1 -1
  5. package/dist/components/event-list-view.js +53 -53
  6. package/dist/components/event-list-view.js.map +1 -1
  7. package/dist/components/hook-actions.js.map +1 -1
  8. package/dist/components/index.d.ts +3 -1
  9. package/dist/components/index.d.ts.map +1 -1
  10. package/dist/components/index.js +3 -1
  11. package/dist/components/index.js.map +1 -1
  12. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  13. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  15. package/dist/components/new-trace-viewer/components/copy-button.js.map +1 -0
  16. package/dist/components/new-trace-viewer/components/event-list.d.ts +8 -0
  17. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  18. package/dist/components/new-trace-viewer/components/event-list.js +35 -0
  19. package/dist/components/new-trace-viewer/components/event-list.js.map +1 -0
  20. package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
  21. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  22. package/dist/components/new-trace-viewer/components/split-pane.js +100 -0
  23. package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -0
  24. package/dist/components/new-trace-viewer/components/timeline.d.ts +32 -0
  25. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  26. package/dist/components/new-trace-viewer/components/timeline.js +134 -0
  27. package/dist/components/new-trace-viewer/components/timeline.js.map +1 -0
  28. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  29. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  30. package/dist/components/new-trace-viewer/context.js +49 -0
  31. package/dist/components/new-trace-viewer/context.js.map +1 -0
  32. package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
  33. package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
  34. package/dist/components/new-trace-viewer/detail-panel.js +11 -0
  35. package/dist/components/new-trace-viewer/detail-panel.js.map +1 -0
  36. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  37. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  38. package/dist/components/new-trace-viewer/icons.js +15 -0
  39. package/dist/components/new-trace-viewer/icons.js.map +1 -0
  40. package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
  41. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/trace-viewer.js +329 -0
  43. package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -0
  44. package/dist/components/new-trace-viewer/types.d.ts +1 -0
  45. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  46. package/dist/components/new-trace-viewer/types.js +2 -0
  47. package/dist/components/new-trace-viewer/types.js.map +1 -0
  48. package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
  49. package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
  50. package/dist/components/new-trace-viewer/util/constants.js +2 -0
  51. package/dist/components/new-trace-viewer/util/constants.js.map +1 -0
  52. package/dist/components/new-trace-viewer/utils.d.ts +47 -0
  53. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/utils.js +358 -0
  55. package/dist/components/new-trace-viewer/utils.js.map +1 -0
  56. package/dist/components/run-trace-view.d.ts +2 -1
  57. package/dist/components/run-trace-view.d.ts.map +1 -1
  58. package/dist/components/run-trace-view.js +2 -2
  59. package/dist/components/run-trace-view.js.map +1 -1
  60. package/dist/components/sidebar/attribute-panel.d.ts +7 -1
  61. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  62. package/dist/components/sidebar/attribute-panel.js +76 -47
  63. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  64. package/dist/components/sidebar/conversation-view.js.map +1 -1
  65. package/dist/components/sidebar/copyable-data-block.js +1 -1
  66. package/dist/components/sidebar/copyable-data-block.js.map +1 -1
  67. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  68. package/dist/components/sidebar/detail-card.js +1 -1
  69. package/dist/components/sidebar/detail-card.js.map +1 -1
  70. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  71. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  72. package/dist/components/sidebar/entity-detail-panel.js +16 -36
  73. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  74. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  75. package/dist/components/sidebar/events-list.js +2 -2
  76. package/dist/components/sidebar/events-list.js.map +1 -1
  77. package/dist/components/sidebar/resolve-hook-modal.js.map +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/sidebar/sidebar-data-context.js.map +1 -0
  82. package/dist/components/stream-viewer.d.ts +1 -1
  83. package/dist/components/stream-viewer.d.ts.map +1 -1
  84. package/dist/components/stream-viewer.js +2 -26
  85. package/dist/components/stream-viewer.js.map +1 -1
  86. package/dist/components/trace-viewer/components/map.js.map +1 -1
  87. package/dist/components/trace-viewer/components/markers.js.map +1 -1
  88. package/dist/components/trace-viewer/components/node.js.map +1 -1
  89. package/dist/components/trace-viewer/components/search-input.js.map +1 -1
  90. package/dist/components/trace-viewer/components/search.js.map +1 -1
  91. package/dist/components/trace-viewer/components/span-content.js.map +1 -1
  92. package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
  93. package/dist/components/trace-viewer/components/span-segments.js.map +1 -1
  94. package/dist/components/trace-viewer/components/span-strategies.js.map +1 -1
  95. package/dist/components/trace-viewer/components/ui.js.map +1 -1
  96. package/dist/components/trace-viewer/components/zoom-button.js.map +1 -1
  97. package/dist/components/trace-viewer/components/zoom-icons.js.map +1 -1
  98. package/dist/components/trace-viewer/context.js.map +1 -1
  99. package/dist/components/trace-viewer/index.js.map +1 -1
  100. package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
  101. package/dist/components/trace-viewer/types.js.map +1 -1
  102. package/dist/components/trace-viewer/util/constants.js.map +1 -1
  103. package/dist/components/trace-viewer/util/scrollbar-width.js.map +1 -1
  104. package/dist/components/trace-viewer/util/timing.js.map +1 -1
  105. package/dist/components/trace-viewer/util/tree.js.map +1 -1
  106. package/dist/components/trace-viewer/util/use-immediate-style.js.map +1 -1
  107. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
  108. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +1 -1
  109. package/dist/components/trace-viewer/worker.js.map +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/trace-viewer-new.js.map +1 -0
  114. package/dist/components/ui/alert.js.map +1 -1
  115. package/dist/components/ui/card.js.map +1 -1
  116. package/dist/components/ui/data-inspector.d.ts +38 -1
  117. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  118. package/dist/components/ui/data-inspector.js +211 -21
  119. package/dist/components/ui/data-inspector.js.map +1 -1
  120. package/dist/components/ui/decrypt-button.js.map +1 -1
  121. package/dist/components/ui/error-card.js.map +1 -1
  122. package/dist/components/ui/error-stack-block.js.map +1 -1
  123. package/dist/components/ui/inspector-theme.d.ts +9 -10
  124. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  125. package/dist/components/ui/inspector-theme.js +38 -71
  126. package/dist/components/ui/inspector-theme.js.map +1 -1
  127. package/dist/components/ui/load-more-button.js.map +1 -1
  128. package/dist/components/ui/menu-dropdown.js.map +1 -1
  129. package/dist/components/ui/skeleton.js.map +1 -1
  130. package/dist/components/ui/spinner.js.map +1 -1
  131. package/dist/components/ui/timestamp-tooltip.js.map +1 -1
  132. package/dist/components/workflow-trace-view.d.ts +3 -1
  133. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  134. package/dist/components/workflow-trace-view.js +9 -4
  135. package/dist/components/workflow-trace-view.js.map +1 -1
  136. package/dist/components/workflow-traces/event-colors.js.map +1 -1
  137. package/dist/components/workflow-traces/trace-colors.js.map +1 -1
  138. package/dist/components/workflow-traces/trace-span-construction.js +1 -1
  139. package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
  140. package/dist/components/workflow-traces/trace-time-utils.js.map +1 -1
  141. package/dist/hooks/use-dark-mode.js.map +1 -1
  142. package/dist/index.d.ts +2 -1
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +1 -1
  145. package/dist/index.js.map +1 -1
  146. package/dist/lib/event-analysis.js.map +1 -1
  147. package/dist/lib/event-materialization.js.map +1 -1
  148. package/dist/lib/hydration.d.ts +8 -1
  149. package/dist/lib/hydration.d.ts.map +1 -1
  150. package/dist/lib/hydration.js +32 -2
  151. package/dist/lib/hydration.js.map +1 -1
  152. package/dist/lib/stream-display.d.ts +18 -0
  153. package/dist/lib/stream-display.d.ts.map +1 -0
  154. package/dist/lib/stream-display.js +30 -0
  155. package/dist/lib/stream-display.js.map +1 -0
  156. package/dist/lib/toast.js.map +1 -1
  157. package/dist/lib/trace-builder.js.map +1 -1
  158. package/dist/lib/utils.d.ts +7 -9
  159. package/dist/lib/utils.d.ts.map +1 -1
  160. package/dist/lib/utils.js +17 -22
  161. package/dist/lib/utils.js.map +1 -1
  162. package/dist/styles.css +470 -1
  163. package/package.json +4 -3
  164. package/src/components/event-list-view.tsx +181 -173
  165. package/src/components/index.ts +11 -1
  166. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  167. package/src/components/new-trace-viewer/components/event-list.tsx +109 -0
  168. package/src/components/new-trace-viewer/components/split-pane.tsx +186 -0
  169. package/src/components/new-trace-viewer/components/timeline.tsx +378 -0
  170. package/src/components/new-trace-viewer/context.tsx +94 -0
  171. package/src/components/new-trace-viewer/detail-panel.tsx +57 -0
  172. package/src/components/new-trace-viewer/icons.tsx +79 -0
  173. package/src/components/new-trace-viewer/trace-viewer.tsx +585 -0
  174. package/src/components/new-trace-viewer/types.ts +0 -0
  175. package/src/components/new-trace-viewer/util/constants.ts +0 -0
  176. package/src/components/new-trace-viewer/utils.ts +524 -0
  177. package/src/components/run-trace-view.tsx +3 -0
  178. package/src/components/sidebar/attribute-panel.tsx +200 -138
  179. package/src/components/sidebar/copyable-data-block.tsx +1 -1
  180. package/src/components/sidebar/detail-card.tsx +1 -19
  181. package/src/components/sidebar/entity-detail-panel.tsx +91 -147
  182. package/src/components/sidebar/events-list.tsx +9 -6
  183. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  184. package/src/components/stream-viewer.tsx +18 -40
  185. package/src/components/trace-viewer-new.tsx +49 -0
  186. package/src/components/ui/data-inspector.tsx +436 -31
  187. package/src/components/ui/inspector-theme.ts +39 -81
  188. package/src/components/workflow-trace-view.tsx +12 -2
  189. package/src/components/workflow-traces/trace-span-construction.ts +1 -1
  190. package/src/index.ts +2 -0
  191. package/src/lib/hydration.ts +37 -0
  192. package/src/lib/stream-display.ts +48 -0
  193. package/src/lib/utils.ts +18 -23
  194. package/src/styles.css +470 -1
@@ -0,0 +1,79 @@
1
+ const WorkflowIcon = () => {
2
+ return (
3
+ <svg
4
+ data-testid="geist-icon"
5
+ height="16"
6
+ stroke-linejoin="round"
7
+ style={{ color: 'currentColor' }}
8
+ viewBox="0 0 16 16"
9
+ width="16"
10
+ >
11
+ <path
12
+ d="M5.75 7L6.4209 7.33594L4.85254 10.4717C5.38789 10.771 5.75 11.3431 5.75 12V14C5.75 14.9665 4.96648 15.75 4 15.75H2C1.03352 15.75 0.25 14.9665 0.25 14V12C0.25 11.0335 1.03352 10.25 2 10.25H3.28711L5.0791 6.66504L5.75 7ZM14 10.25C14.9665 10.25 15.75 11.0335 15.75 12V14C15.75 14.9665 14.9665 15.75 14 15.75H12C11.1185 15.75 10.3909 15.098 10.2695 14.25H7V12.75H10.25V12C10.25 11.0335 11.0335 10.25 12 10.25H14ZM2 11.75C1.86192 11.75 1.75 11.8619 1.75 12V14C1.75 14.1381 1.86192 14.25 2 14.25H4C4.13808 14.25 4.25 14.1381 4.25 14V12C4.25 11.8619 4.13808 11.75 4 11.75H2ZM12 11.75C11.8619 11.75 11.75 11.8619 11.75 12V14C11.75 14.1381 11.8619 14.25 12 14.25H14C14.1381 14.25 14.25 14.1381 14.25 14V12C14.25 11.8619 14.1381 11.75 14 11.75H12ZM9 0.25C9.96649 0.25 10.75 1.03351 10.75 2V4C10.75 4.44642 10.5808 4.85197 10.3057 5.16113L12.2041 8.6416L10.8877 9.36035L8.91895 5.75H7C6.03351 5.75 5.25 4.96649 5.25 4V2C5.25 1.03351 6.03351 0.25 7 0.25H9ZM7 1.75C6.86193 1.75 6.75 1.86193 6.75 2V4C6.75 4.13807 6.86193 4.25 7 4.25H9C9.13807 4.25 9.25 4.13807 9.25 4V2C9.25 1.86193 9.13807 1.75 9 1.75H7Z"
13
+ fill="currentColor"
14
+ ></path>
15
+ </svg>
16
+ );
17
+ };
18
+
19
+ const WebhookIcon = () => {
20
+ return (
21
+ <svg
22
+ data-testid="geist-icon"
23
+ height="16"
24
+ stroke-linejoin="round"
25
+ viewBox="0 0 16 16"
26
+ width="16"
27
+ style={{ color: 'currentColor' }}
28
+ >
29
+ <path
30
+ fillRule="evenodd"
31
+ clipRule="evenodd"
32
+ d="M5.8349 3.24994C5.14454 4.44567 5.55423 5.97464 6.74996 6.665L7.39465 7.03721L7.02726 7.68466L5.14599 11C5.21289 11.1531 5.25 11.3222 5.25 11.5C5.25 12.1904 4.69036 12.75 4 12.75C3.30964 12.75 2.75 12.1904 2.75 11.5C2.75 10.8634 3.22584 10.338 3.84123 10.26L5.38944 7.5316C3.95909 6.30274 3.55857 4.19264 4.53586 2.49994C5.64043 0.586765 8.08679 -0.0687357 9.99996 1.03583C11.836 2.09584 12.5137 4.39156 11.5892 6.26591L10.4949 4.33754C10.4418 3.53274 10.0009 2.76843 9.24996 2.33487C8.05423 1.64452 6.52525 2.0542 5.8349 3.24994ZM9.146 4.99996C9.21289 4.84686 9.25 4.67776 9.25 4.5C9.25 3.80964 8.69036 3.25 8 3.25C7.30964 3.25 6.75 3.80964 6.75 4.5C6.75 5.13659 7.22586 5.66203 7.84127 5.74002L9.72266 9.05556L10.0956 9.71274L10.75 9.33493C11.9457 8.64458 13.4747 9.05427 14.165 10.25C14.8554 11.4457 14.4457 12.9747 13.25 13.6651C12.5047 14.0953 11.63 14.0983 10.9096 13.75H8.69252C9.85895 15.4685 12.1727 16.0191 14 14.9641C15.9131 13.8595 16.5686 11.4132 15.4641 9.5C14.4886 7.81038 12.4665 7.10169 10.6895 7.71998L9.146 4.99996ZM13.25 11.5C13.25 12.1904 12.6904 12.75 12 12.75C11.591 12.75 11.228 12.5536 10.9999 12.25H7.9298C7.57875 14.1006 5.95279 15.5 4 15.5C1.79086 15.5 0 13.7091 0 11.5C0 9.40018 1.61801 7.67826 3.67529 7.51299L2.58098 9.44147C1.92792 9.89251 1.5 10.6463 1.5 11.5C1.5 12.8807 2.61929 14 4 14C5.38071 14 6.5 12.8807 6.5 11.5V10.75H7.25H10.9999C11.228 10.4464 11.591 10.25 12 10.25C12.6904 10.25 13.25 10.8096 13.25 11.5Z"
33
+ fill="currentColor"
34
+ ></path>
35
+ </svg>
36
+ );
37
+ };
38
+
39
+ const SleepIcon = () => {
40
+ return (
41
+ <svg
42
+ data-testid="geist-icon"
43
+ height="16"
44
+ strokeLinejoin="round"
45
+ viewBox="0 0 16 16"
46
+ width="16"
47
+ style={{ color: 'currentColor' }}
48
+ >
49
+ <path
50
+ fillRule="evenodd"
51
+ clipRule="evenodd"
52
+ d="M7.25 1.25V2.03971C5.87928 2.18571 4.62678 2.72736 3.6089 3.54824L3.03033 2.96967L2.5 2.43934L1.43934 3.5L1.96967 4.03033L2.54824 4.6089C1.57979 5.80976 1 7.33717 1 9C1 12.866 4.13401 16 8 16C11.866 16 15 12.866 15 9C15 7.33717 14.4202 5.80976 13.4518 4.6089L14.0303 4.03033L14.5607 3.5L13.5 2.43934L12.9697 2.96967L12.3911 3.54824C11.3732 2.72736 10.1207 2.18571 8.75 2.03971V1.25H9.25H10V-0.25H9.25H8.75H7.25H6.75H6V1.25H6.75H7.25ZM2.5 9C2.5 5.96243 4.96243 3.5 8 3.5C11.0376 3.5 13.5 5.96243 13.5 9C13.5 12.0376 11.0376 14.5 8 14.5C4.96243 14.5 2.5 12.0376 2.5 9ZM8.75 6.75V6H7.25V6.75V9V9.75H8.75V9V6.75Z"
53
+ fill="currentColor"
54
+ ></path>
55
+ </svg>
56
+ );
57
+ };
58
+
59
+ const StepForwardIcon = () => {
60
+ return (
61
+ <svg
62
+ data-testid="geist-icon"
63
+ height="16"
64
+ strokeLinejoin="round"
65
+ viewBox="0 0 16 16"
66
+ width="16"
67
+ style={{ color: 'currentColor' }}
68
+ >
69
+ <path
70
+ fillRule="evenodd"
71
+ clipRule="evenodd"
72
+ d="M4.21969 12.5303L4.75002 13.0607L5.81068 12L5.28035 11.4697L1.81068 7.99999L5.28035 4.53032L5.81068 3.99999L4.75002 2.93933L4.21969 3.46966L0.39647 7.29289C0.00594562 7.68341 0.00594562 8.31658 0.39647 8.7071L4.21969 12.5303ZM11.7804 12.5303L11.25 13.0607L10.1894 12L10.7197 11.4697L14.1894 7.99999L10.7197 4.53032L10.1894 3.99999L11.25 2.93933L11.7804 3.46966L15.6036 7.29289C15.9941 7.68341 15.9941 8.31658 15.6036 8.7071L11.7804 12.5303Z"
73
+ fill="currentColor"
74
+ ></path>
75
+ </svg>
76
+ );
77
+ };
78
+
79
+ export { WorkflowIcon, WebhookIcon, SleepIcon, StepForwardIcon };
@@ -0,0 +1,585 @@
1
+ 'use client';
2
+
3
+ import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
+ import { Search, X } from 'lucide-react';
5
+ import {
6
+ type ReactNode,
7
+ useCallback,
8
+ useEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
13
+ import { ErrorBoundary } from '../error-boundary';
14
+ import {
15
+ EntityDetailPanel,
16
+ type SelectedSpanInfo,
17
+ } from '../sidebar/entity-detail-panel';
18
+ import { useSidebarDataOptional } from '../sidebar/sidebar-data-context';
19
+ import type { Trace } from '../trace-viewer/types';
20
+ import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
21
+ import { CopyButton } from './components/copy-button';
22
+ import { SplitPane } from './components/split-pane';
23
+ import EventList from './components/event-list';
24
+ import { Timeline, TimelineHeader } from './components/timeline';
25
+ import { ActiveSpanProvider, useActiveSpan } from './context';
26
+ import { DetailPanel } from './detail-panel';
27
+ import { buildTimeCompression, computeRootBounds } from './utils';
28
+
29
+ interface NewTraceViewerProps {
30
+ trace: Trace;
31
+ }
32
+
33
+ const MIN_VIEWPORT_MS = 0.001;
34
+ const TIMELINE_PADDING = 16;
35
+
36
+ interface Viewport {
37
+ start: number;
38
+ end: number;
39
+ }
40
+
41
+ function useAnimatedViewport(initial: Viewport) {
42
+ const [viewport, setViewportState] = useState<Viewport>(initial);
43
+ const animRef = useRef<{
44
+ raf: number;
45
+ from: Viewport;
46
+ to: Viewport;
47
+ start: number;
48
+ } | null>(null);
49
+ const currentRef = useRef(initial);
50
+ currentRef.current = viewport;
51
+
52
+ const cancel = useCallback(() => {
53
+ if (animRef.current) {
54
+ cancelAnimationFrame(animRef.current.raf);
55
+ animRef.current = null;
56
+ }
57
+ }, []);
58
+
59
+ const animateTo = useCallback(
60
+ (target: Viewport) => {
61
+ cancel();
62
+ const from = currentRef.current;
63
+ const anim = { raf: 0, from, to: target, start: performance.now() };
64
+
65
+ const tick = () => {
66
+ const t = Math.min((performance.now() - anim.start) / 150, 1);
67
+ const e = 1 - (1 - t) * (1 - t);
68
+ setViewportState({
69
+ start: anim.from.start + (anim.to.start - anim.from.start) * e,
70
+ end: anim.from.end + (anim.to.end - anim.from.end) * e,
71
+ });
72
+ if (t < 1) anim.raf = requestAnimationFrame(tick);
73
+ else animRef.current = null;
74
+ };
75
+
76
+ animRef.current = anim;
77
+ anim.raf = requestAnimationFrame(tick);
78
+ },
79
+ [cancel]
80
+ );
81
+
82
+ const setViewport = useCallback(
83
+ (update: Viewport | ((prev: Viewport) => Viewport)) => {
84
+ cancel();
85
+ if (typeof update === 'function') {
86
+ setViewportState((prev) => {
87
+ const next = update(prev);
88
+ currentRef.current = next;
89
+ return next;
90
+ });
91
+ } else {
92
+ currentRef.current = update;
93
+ setViewportState(update);
94
+ }
95
+ },
96
+ [cancel]
97
+ );
98
+
99
+ useEffect(() => cancel, [cancel]);
100
+
101
+ return { viewport, setViewport, animateTo };
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Hook: bridge ActiveSpanContext + SidebarDataContext → SelectedSpanInfo
106
+ // ---------------------------------------------------------------------------
107
+
108
+ function useSelectedSpanInfo(): SelectedSpanInfo | null {
109
+ const { activeSpan } = useActiveSpan();
110
+ const sidebar = useSidebarDataOptional();
111
+
112
+ return useMemo(() => {
113
+ if (!activeSpan || !sidebar) return null;
114
+
115
+ const correlationId = activeSpan.spanId;
116
+ const rawEvents = correlationId
117
+ ? sidebar.events.filter((e) => e.correlationId === correlationId)
118
+ : [];
119
+
120
+ return {
121
+ data: activeSpan.attributes?.data,
122
+ resource: activeSpan.attributes?.resource as string | undefined,
123
+ spanId: activeSpan.spanId,
124
+ rawEvents,
125
+ };
126
+ }, [activeSpan, sidebar?.events]);
127
+ }
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Root component
131
+ // ---------------------------------------------------------------------------
132
+
133
+ export function NewTraceViewer({ trace }: NewTraceViewerProps): ReactNode {
134
+ return (
135
+ <ActiveSpanProvider spans={trace.spans}>
136
+ <NewTraceViewerContent trace={trace} />
137
+ </ActiveSpanProvider>
138
+ );
139
+ }
140
+
141
+ function NewTraceViewerContent({ trace }: NewTraceViewerProps): ReactNode {
142
+ const { activeSpan, activeSpanId, setActiveSpan, clearActiveSpan } =
143
+ useActiveSpan();
144
+
145
+ const sidebar = useSidebarDataOptional();
146
+ const selectedSpan = useSelectedSpanInfo();
147
+
148
+ const [searchQuery, setSearchQuery] = useState('');
149
+
150
+ const filteredSpans = useMemo(() => {
151
+ const q = searchQuery.trim().toLowerCase();
152
+ if (!q) return trace.spans;
153
+ return trace.spans.filter(
154
+ (s) =>
155
+ s.name.toLowerCase().includes(q) || s.resource.toLowerCase().includes(q)
156
+ );
157
+ }, [trace.spans, searchQuery]);
158
+
159
+ const root = useMemo(() => computeRootBounds(trace.spans), [trace.spans]);
160
+
161
+ const { viewport, setViewport, animateTo } = useAnimatedViewport({
162
+ start: root.startTime,
163
+ end: root.startTime + root.duration,
164
+ });
165
+
166
+ const prevRootRef = useRef<Viewport>({
167
+ start: root.startTime,
168
+ end: root.startTime + root.duration,
169
+ });
170
+
171
+ useEffect(() => {
172
+ const prevRoot = prevRootRef.current;
173
+ const newStart = root.startTime;
174
+ const newEnd = root.startTime + root.duration;
175
+
176
+ setViewport((prev) => {
177
+ const wasAtFullExtent =
178
+ Math.abs(prev.start - prevRoot.start) < 0.01 &&
179
+ Math.abs(prev.end - prevRoot.end) < 0.01;
180
+
181
+ if (wasAtFullExtent) {
182
+ return { start: newStart, end: newEnd };
183
+ }
184
+ return prev;
185
+ });
186
+
187
+ prevRootRef.current = { start: newStart, end: newEnd };
188
+ }, [root.startTime, root.duration]);
189
+
190
+ const viewDuration = viewport.end - viewport.start;
191
+
192
+ const compression = useMemo(
193
+ () => buildTimeCompression(trace.spans, viewport.start, viewport.end),
194
+ [trace.spans, viewport.start, viewport.end]
195
+ );
196
+
197
+ const isZoomed =
198
+ viewport.start > root.startTime + 0.01 ||
199
+ viewport.end < root.startTime + root.duration - 0.01;
200
+
201
+ const resetZoom = useCallback(() => {
202
+ animateTo({ start: root.startTime, end: root.startTime + root.duration });
203
+ }, [animateTo, root.startTime, root.duration]);
204
+
205
+ const handleSelectSpan = useCallback(
206
+ (spanId: string) => {
207
+ if (spanId === activeSpanId) {
208
+ clearActiveSpan();
209
+ return;
210
+ }
211
+ setActiveSpan(spanId);
212
+
213
+ const span = trace.spans.find((s) => s.spanId === spanId);
214
+ if (!span) return;
215
+
216
+ const spanStart = getHighResInMs(span.startTime);
217
+ const spanEnd = getHighResInMs(span.endTime);
218
+ const spanDuration = spanEnd - spanStart;
219
+
220
+ const rootS = root.startTime;
221
+ const rootE = root.startTime + root.duration;
222
+ const rootD = root.duration;
223
+
224
+ if (spanDuration > rootD * 0.8) {
225
+ animateTo({ start: rootS, end: rootE });
226
+ return;
227
+ }
228
+
229
+ const padding = Math.max(spanDuration * 0.2, MIN_VIEWPORT_MS / 2);
230
+ let newStart = spanStart - padding;
231
+ let newEnd = spanEnd + padding;
232
+
233
+ if (newEnd - newStart < MIN_VIEWPORT_MS) {
234
+ const center = (spanStart + spanEnd) / 2;
235
+ newStart = center - MIN_VIEWPORT_MS / 2;
236
+ newEnd = center + MIN_VIEWPORT_MS / 2;
237
+ }
238
+
239
+ if (newStart < rootS) {
240
+ const duration = newEnd - newStart;
241
+ newStart = rootS;
242
+ newEnd = Math.min(rootE, rootS + duration);
243
+ }
244
+ if (newEnd > rootE) {
245
+ const duration = newEnd - newStart;
246
+ newEnd = rootE;
247
+ newStart = Math.max(rootS, rootE - duration);
248
+ }
249
+
250
+ animateTo({ start: newStart, end: newEnd });
251
+ },
252
+ [
253
+ animateTo,
254
+ setActiveSpan,
255
+ clearActiveSpan,
256
+ activeSpanId,
257
+ trace.spans,
258
+ root.startTime,
259
+ root.duration,
260
+ ]
261
+ );
262
+
263
+ const [altHeld, setAltHeld] = useState(false);
264
+
265
+ useEffect(() => {
266
+ const onKeyDown = (e: KeyboardEvent): void => {
267
+ if (e.key === 'Escape') {
268
+ clearActiveSpan();
269
+ }
270
+ if (e.key === 'Alt') {
271
+ e.preventDefault();
272
+ setAltHeld(true);
273
+ }
274
+ };
275
+ const onKeyUp = (e: KeyboardEvent): void => {
276
+ if (e.key === 'Alt') setAltHeld(false);
277
+ };
278
+ const onBlur = (): void => setAltHeld(false);
279
+
280
+ window.addEventListener('keydown', onKeyDown);
281
+ window.addEventListener('keyup', onKeyUp);
282
+ window.addEventListener('blur', onBlur);
283
+ return () => {
284
+ window.removeEventListener('keydown', onKeyDown);
285
+ window.removeEventListener('keyup', onKeyUp);
286
+ window.removeEventListener('blur', onBlur);
287
+ };
288
+ }, [clearActiveSpan]);
289
+
290
+ const timelineRef = useRef<HTMLDivElement>(null);
291
+ const [hoverFraction, setHoverFraction] = useState<number | null>(null);
292
+
293
+ const hoverInfo = useMemo(() => {
294
+ if (hoverFraction == null) return null;
295
+ const absTime = compression.fromVisual(hoverFraction);
296
+ const offset = absTime - root.startTime;
297
+ return { fraction: hoverFraction, label: formatDuration(offset, true) };
298
+ }, [hoverFraction, compression, root.startTime]);
299
+
300
+ const handleTimelineMouseMove = useCallback(
301
+ (e: React.MouseEvent<HTMLDivElement>) => {
302
+ const el = timelineRef.current;
303
+ if (!el) return;
304
+ const rect = el.getBoundingClientRect();
305
+ const padding = 8;
306
+ const contentWidth = rect.width - padding * 2;
307
+ if (contentWidth <= 0) return;
308
+ const fraction = Math.max(
309
+ 0,
310
+ Math.min(1, (e.clientX - rect.left - padding) / contentWidth)
311
+ );
312
+ setHoverFraction(fraction);
313
+ },
314
+ []
315
+ );
316
+
317
+ const handleTimelineMouseLeave = useCallback(() => {
318
+ setHoverFraction(null);
319
+ }, []);
320
+
321
+ useEffect(() => {
322
+ const el = timelineRef.current;
323
+ if (!el) return;
324
+
325
+ const rootS = root.startTime;
326
+ const rootE = root.startTime + root.duration;
327
+ const rootD = root.duration;
328
+ if (rootD <= 0) return;
329
+
330
+ const onWheel = (e: WheelEvent): void => {
331
+ const isZoomGesture = e.ctrlKey || e.metaKey;
332
+ const hasDeltaX = Math.abs(e.deltaX) > Math.abs(e.deltaY);
333
+
334
+ if (!isZoomGesture && !hasDeltaX) return;
335
+ e.preventDefault();
336
+
337
+ const rect = el.getBoundingClientRect();
338
+ const contentWidth = rect.width - TIMELINE_PADDING * 2;
339
+ if (contentWidth <= 0) return;
340
+
341
+ if (isZoomGesture) {
342
+ let dy = e.deltaY;
343
+ if (e.deltaMode === 1) dy *= 16;
344
+
345
+ const cursorFraction = Math.max(
346
+ 0,
347
+ Math.min(1, (e.clientX - rect.left - TIMELINE_PADDING) / contentWidth)
348
+ );
349
+ const scaleFactor = Math.pow(2, dy / 200);
350
+
351
+ setViewport((prev) => {
352
+ const prevDuration = prev.end - prev.start;
353
+ const cursorTime = prev.start + cursorFraction * prevDuration;
354
+ const newDuration = Math.max(
355
+ MIN_VIEWPORT_MS,
356
+ Math.min(rootD, prevDuration * scaleFactor)
357
+ );
358
+
359
+ let newStart = cursorTime - cursorFraction * newDuration;
360
+ let newEnd = newStart + newDuration;
361
+
362
+ if (newStart < rootS) {
363
+ newStart = rootS;
364
+ newEnd = rootS + newDuration;
365
+ }
366
+ if (newEnd > rootE) {
367
+ newEnd = rootE;
368
+ newStart = Math.max(rootS, rootE - newDuration);
369
+ }
370
+
371
+ return { start: newStart, end: newEnd };
372
+ });
373
+ } else {
374
+ let dx = e.deltaX;
375
+ if (e.deltaMode === 1) dx *= 16;
376
+
377
+ setViewport((prev) => {
378
+ const prevDuration = prev.end - prev.start;
379
+ const panAmount = (dx / contentWidth) * prevDuration;
380
+
381
+ let newStart = prev.start + panAmount;
382
+ let newEnd = prev.end + panAmount;
383
+
384
+ if (newStart < rootS) {
385
+ newStart = rootS;
386
+ newEnd = rootS + prevDuration;
387
+ }
388
+ if (newEnd > rootE) {
389
+ newEnd = rootE;
390
+ newStart = Math.max(rootS, rootE - prevDuration);
391
+ }
392
+
393
+ return { start: newStart, end: newEnd };
394
+ });
395
+ }
396
+ };
397
+
398
+ el.addEventListener('wheel', onWheel, { passive: false });
399
+ return () => el.removeEventListener('wheel', onWheel);
400
+ }, [root.startTime, root.duration]);
401
+
402
+ // Derive the selected span name and metadata for the panel header
403
+ const selectedSpanName = useMemo(() => {
404
+ if (!selectedSpan?.data) return 'Details';
405
+ const data = selectedSpan.data as Record<string, unknown>;
406
+ const stepName = data.stepName as string | undefined;
407
+ const workflowName = data.workflowName as string | undefined;
408
+ return (
409
+ (stepName ? parseStepName(stepName)?.shortName : undefined) ??
410
+ (workflowName ? parseWorkflowName(workflowName)?.shortName : undefined) ??
411
+ stepName ??
412
+ workflowName ??
413
+ (data.hookId as string) ??
414
+ 'Details'
415
+ );
416
+ }, [selectedSpan?.data]);
417
+
418
+ const selectedResource = selectedSpan?.resource as string | undefined;
419
+ const selectedResourceId = useMemo(() => {
420
+ if (!selectedSpan?.data) return undefined;
421
+ const data = selectedSpan.data as Record<string, unknown>;
422
+ return (
423
+ (data.stepId as string) ??
424
+ (data.runId as string) ??
425
+ (data.hookId as string) ??
426
+ selectedSpan.spanId
427
+ );
428
+ }, [selectedSpan?.data, selectedSpan?.spanId]);
429
+
430
+ return (
431
+ <div
432
+ data-pane="pane-root"
433
+ data-has-detail={activeSpan ? '' : undefined}
434
+ className="grid w-full h-full max-h-full grid-cols-[minmax(100px,1fr)] data-[has-detail]:grid-cols-[minmax(100px,1fr)_clamp(280px,420px,100%)]"
435
+ >
436
+ <div
437
+ id="trace-parent"
438
+ className="grid grid-rows-[1fr] h-full min-h-0 overflow-hidden relative bg-background-100"
439
+ >
440
+ <SplitPane
441
+ startHeader={
442
+ <div className="bg-background-100 border-b border-gray-alpha-400 h-10 min-h-10 flex items-center px-2 gap-1.5">
443
+ <Search className="w-3.5 h-3.5 shrink-0 text-gray-800" />
444
+ <input
445
+ id="trace-viewer-search"
446
+ name="trace-viewer-search"
447
+ type="text"
448
+ value={searchQuery}
449
+ onChange={(e) => setSearchQuery(e.target.value)}
450
+ placeholder="Search spans..."
451
+ aria-label="Search spans"
452
+ className="flex-1 min-w-0 bg-transparent text-sm text-gray-1000 placeholder:text-gray-800 outline-none"
453
+ />
454
+ {searchQuery && (
455
+ <button
456
+ type="button"
457
+ onClick={() => setSearchQuery('')}
458
+ className="shrink-0 p-0.5 rounded-sm text-gray-800 hover:text-gray-1000 hover:bg-gray-200 transition-colors"
459
+ >
460
+ <X className="w-3 h-3" />
461
+ </button>
462
+ )}
463
+ </div>
464
+ }
465
+ endHeader={
466
+ <TimelineHeader
467
+ viewStart={viewport.start}
468
+ viewDuration={viewDuration}
469
+ rootStart={root.startTime}
470
+ compression={compression}
471
+ isZoomed={isZoomed}
472
+ onResetZoom={resetZoom}
473
+ hoverInfo={hoverInfo}
474
+ />
475
+ }
476
+ >
477
+ <div className="block overflow-visible">
478
+ <EventList
479
+ spans={filteredSpans}
480
+ activeSpanId={activeSpanId}
481
+ onSelectSpan={handleSelectSpan}
482
+ />
483
+ </div>
484
+ <div
485
+ ref={timelineRef}
486
+ className="block min-h-0 overflow-visible relative px-2"
487
+ onDoubleClick={resetZoom}
488
+ onMouseMove={handleTimelineMouseMove}
489
+ onMouseLeave={handleTimelineMouseLeave}
490
+ >
491
+ <Timeline
492
+ spans={filteredSpans}
493
+ compression={compression}
494
+ selectedId={activeSpanId}
495
+ onSelect={handleSelectSpan}
496
+ hoverFraction={hoverFraction}
497
+ altHeld={altHeld}
498
+ />
499
+ </div>
500
+ </SplitPane>
501
+ </div>
502
+
503
+ {/* Detail panel */}
504
+ {activeSpan && sidebar ? (
505
+ <aside className="flex flex-col h-full max-h-full bg-background-100 border-l border-gray-alpha-400 overflow-auto">
506
+ {/* Panel header */}
507
+ <div className="flex-shrink-0 px-4 pt-4 pb-3">
508
+ <div className="flex items-start justify-between gap-2">
509
+ <div className="min-w-0 flex-1">
510
+ <span className="text-[15px] font-semibold text-gray-1000 truncate block">
511
+ {selectedSpanName}
512
+ </span>
513
+ {selectedResourceId && (
514
+ <div className="mt-1 flex items-center gap-2">
515
+ {selectedResource && (
516
+ <span
517
+ className={`inline-flex items-center rounded-md px-1.5 py-0.5 text-[11px] font-medium leading-none shrink-0 ${
518
+ selectedResource === 'step'
519
+ ? 'bg-green-200 text-green-900'
520
+ : selectedResource === 'run'
521
+ ? 'bg-blue-200 text-blue-900'
522
+ : selectedResource === 'hook'
523
+ ? 'bg-yellow-200 text-yellow-900'
524
+ : 'bg-gray-200 text-gray-900'
525
+ }`}
526
+ >
527
+ {selectedResource.charAt(0).toUpperCase() +
528
+ selectedResource.slice(1)}
529
+ </span>
530
+ )}
531
+ <div
532
+ className="flex items-center gap-1 text-[13px] font-mono text-gray-700 min-w-0"
533
+ title={selectedResourceId}
534
+ >
535
+ <span className="truncate">{selectedResourceId}</span>
536
+ <CopyButton
537
+ copyText={selectedResourceId}
538
+ ariaLabel="Copy ID"
539
+ className="shrink-0"
540
+ />
541
+ </div>
542
+ </div>
543
+ )}
544
+ </div>
545
+ <button
546
+ type="button"
547
+ className="p-1 rounded-md text-gray-900 hover:text-gray-1000 hover:bg-gray-alpha-200 transition-colors shrink-0"
548
+ onClick={clearActiveSpan}
549
+ >
550
+ <X className="w-4 h-4" />
551
+ </button>
552
+ </div>
553
+ </div>
554
+ {/* Panel body */}
555
+ <div className="flex-1 overflow-y-auto">
556
+ <ErrorBoundary>
557
+ <EntityDetailPanel
558
+ run={sidebar.run}
559
+ onStreamClick={sidebar.onStreamClick}
560
+ onRunClick={sidebar.onRunClick}
561
+ spanDetailData={sidebar.spanDetailData}
562
+ spanDetailError={sidebar.spanDetailError}
563
+ spanDetailLoading={sidebar.spanDetailLoading}
564
+ onSpanSelect={sidebar.onSpanSelect}
565
+ onWakeUpSleep={sidebar.onWakeUpSleep}
566
+ onLoadEventData={sidebar.onLoadEventData}
567
+ onResolveHook={sidebar.onResolveHook}
568
+ encryptionKey={sidebar.encryptionKey}
569
+ onDecrypt={sidebar.onDecrypt}
570
+ isDecrypting={sidebar.isDecrypting}
571
+ selectedSpan={selectedSpan}
572
+ />
573
+ </ErrorBoundary>
574
+ </div>
575
+ </aside>
576
+ ) : activeSpan ? (
577
+ <DetailPanel
578
+ span={activeSpan}
579
+ rootStart={root.startTime}
580
+ onClose={clearActiveSpan}
581
+ />
582
+ ) : null}
583
+ </div>
584
+ );
585
+ }
File without changes