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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) hide show
  1. package/dist/components/error-boundary.js +1 -1
  2. package/dist/components/event-list-view.d.ts +16 -1
  3. package/dist/components/event-list-view.d.ts.map +1 -1
  4. package/dist/components/event-list-view.js +196 -85
  5. package/dist/components/hook-actions.js +1 -1
  6. package/dist/components/index.d.ts +4 -0
  7. package/dist/components/index.d.ts.map +1 -1
  8. package/dist/components/index.js +5 -1
  9. package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
  10. package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
  11. package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
  12. package/dist/components/new-trace-viewer/components/event-list.d.ts +10 -0
  13. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
  14. package/dist/components/new-trace-viewer/components/event-list.js +46 -0
  15. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts +18 -0
  16. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.d.ts.map +1 -0
  17. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +54 -0
  18. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts +20 -0
  19. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.d.ts.map +1 -0
  20. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +81 -0
  21. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +25 -0
  22. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -0
  23. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +101 -0
  24. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts +20 -0
  25. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.d.ts.map +1 -0
  26. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +114 -0
  27. package/dist/components/new-trace-viewer/components/span-markers.d.ts +46 -0
  28. package/dist/components/new-trace-viewer/components/span-markers.d.ts.map +1 -0
  29. package/dist/components/new-trace-viewer/components/span-markers.js +85 -0
  30. package/dist/components/new-trace-viewer/components/split-pane.d.ts +15 -0
  31. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
  32. package/dist/components/new-trace-viewer/components/split-pane.js +105 -0
  33. package/dist/components/new-trace-viewer/components/timeline.d.ts +36 -0
  34. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
  35. package/dist/components/new-trace-viewer/components/timeline.js +254 -0
  36. package/dist/components/new-trace-viewer/components/timeline.module.css +46 -0
  37. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts +2 -0
  38. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -0
  39. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +19 -0
  40. package/dist/components/new-trace-viewer/components/use-row-window.d.ts +34 -0
  41. package/dist/components/new-trace-viewer/components/use-row-window.d.ts.map +1 -0
  42. package/dist/components/new-trace-viewer/components/use-row-window.js +107 -0
  43. package/dist/components/new-trace-viewer/context.d.ts +15 -0
  44. package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
  45. package/dist/components/new-trace-viewer/context.js +47 -0
  46. package/dist/components/new-trace-viewer/icons.d.ts +6 -0
  47. package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
  48. package/dist/components/new-trace-viewer/icons.js +15 -0
  49. package/dist/components/new-trace-viewer/search.d.ts +19 -0
  50. package/dist/components/new-trace-viewer/search.d.ts.map +1 -0
  51. package/dist/components/new-trace-viewer/search.js +134 -0
  52. package/dist/components/new-trace-viewer/trace-viewer.d.ts +11 -0
  53. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
  54. package/dist/components/new-trace-viewer/trace-viewer.js +462 -0
  55. package/dist/components/new-trace-viewer/types.d.ts +3 -0
  56. package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
  57. package/dist/components/new-trace-viewer/types.js +2 -0
  58. package/dist/components/new-trace-viewer/utils.d.ts +61 -0
  59. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
  60. package/dist/components/new-trace-viewer/utils.js +345 -0
  61. package/dist/components/run-trace-view.js +1 -1
  62. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  63. package/dist/components/sidebar/attribute-panel.js +129 -102
  64. package/dist/components/sidebar/attributes-block.d.ts +28 -0
  65. package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
  66. package/dist/components/sidebar/attributes-block.js +81 -0
  67. package/dist/components/sidebar/conversation-view.js +1 -1
  68. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  69. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  70. package/dist/components/sidebar/copyable-data-block.js +16 -15
  71. package/dist/components/sidebar/detail-card.d.ts +5 -6
  72. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  73. package/dist/components/sidebar/detail-card.js +32 -16
  74. package/dist/components/sidebar/entity-detail-panel.d.ts +1 -3
  75. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  76. package/dist/components/sidebar/entity-detail-panel.js +17 -35
  77. package/dist/components/sidebar/events-list.d.ts +3 -1
  78. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  79. package/dist/components/sidebar/events-list.js +36 -22
  80. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  81. package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
  82. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
  83. package/dist/components/sidebar/sidebar-data-context.js +19 -0
  84. package/dist/components/sidebar/span-detail-merge.d.ts +16 -0
  85. package/dist/components/sidebar/span-detail-merge.d.ts.map +1 -0
  86. package/dist/components/sidebar/span-detail-merge.js +53 -0
  87. package/dist/components/stream-viewer.d.ts +1 -1
  88. package/dist/components/stream-viewer.d.ts.map +1 -1
  89. package/dist/components/stream-viewer.js +3 -27
  90. package/dist/components/trace-viewer/components/map.js +1 -1
  91. package/dist/components/trace-viewer/components/markers.js +1 -1
  92. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  93. package/dist/components/trace-viewer/components/node.js +2 -1
  94. package/dist/components/trace-viewer/components/search-input.js +1 -1
  95. package/dist/components/trace-viewer/components/search.js +1 -1
  96. package/dist/components/trace-viewer/components/span-content.js +1 -1
  97. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  98. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  99. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  100. package/dist/components/trace-viewer/components/ui.js +1 -1
  101. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  102. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  103. package/dist/components/trace-viewer/context.js +1 -1
  104. package/dist/components/trace-viewer/index.js +1 -1
  105. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  106. package/dist/components/trace-viewer/trace-viewer.module.css +4 -4
  107. package/dist/components/trace-viewer/types.js +1 -1
  108. package/dist/components/trace-viewer/util/constants.js +1 -1
  109. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  110. package/dist/components/trace-viewer/util/timing.d.ts +3 -3
  111. package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
  112. package/dist/components/trace-viewer/util/timing.js +8 -11
  113. package/dist/components/trace-viewer/util/tree.js +1 -1
  114. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  115. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  116. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  117. package/dist/components/trace-viewer/worker.js +1 -1
  118. package/dist/components/trace-viewer-new.d.ts +14 -0
  119. package/dist/components/trace-viewer-new.d.ts.map +1 -0
  120. package/dist/components/trace-viewer-new.js +21 -0
  121. package/dist/components/ui/alert.js +1 -1
  122. package/dist/components/ui/button.d.ts +10 -0
  123. package/dist/components/ui/button.d.ts.map +1 -0
  124. package/dist/components/ui/button.js +46 -0
  125. package/dist/components/ui/card.js +1 -1
  126. package/dist/components/ui/context-card.d.ts +47 -0
  127. package/dist/components/ui/context-card.d.ts.map +1 -0
  128. package/dist/components/ui/context-card.js +471 -0
  129. package/dist/components/ui/data-inspector.d.ts +22 -0
  130. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  131. package/dist/components/ui/data-inspector.js +103 -13
  132. package/dist/components/ui/decrypt-button.js +1 -1
  133. package/dist/components/ui/error-card.js +1 -1
  134. package/dist/components/ui/error-stack-block.d.ts +17 -10
  135. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  136. package/dist/components/ui/error-stack-block.js +49 -30
  137. package/dist/components/ui/icon-button.d.ts +19 -0
  138. package/dist/components/ui/icon-button.d.ts.map +1 -0
  139. package/dist/components/ui/icon-button.js +29 -0
  140. package/dist/components/ui/inspector-theme.d.ts +9 -10
  141. package/dist/components/ui/inspector-theme.d.ts.map +1 -1
  142. package/dist/components/ui/inspector-theme.js +39 -72
  143. package/dist/components/ui/kbd.d.ts +6 -0
  144. package/dist/components/ui/kbd.d.ts.map +1 -0
  145. package/dist/components/ui/kbd.js +6 -0
  146. package/dist/components/ui/load-more-button.js +1 -1
  147. package/dist/components/ui/menu-dropdown.js +1 -1
  148. package/dist/components/ui/skeleton.js +1 -1
  149. package/dist/components/ui/spinner.js +1 -1
  150. package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
  151. package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
  152. package/dist/components/ui/timestamp-tooltip.js +80 -132
  153. package/dist/components/ui/tooltip.d.ts +10 -0
  154. package/dist/components/ui/tooltip.d.ts.map +1 -0
  155. package/dist/components/ui/tooltip.js +12 -0
  156. package/dist/components/workflow-trace-view.d.ts +1 -3
  157. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  158. package/dist/components/workflow-trace-view.js +11 -13
  159. package/dist/components/workflow-traces/event-colors.d.ts +1 -0
  160. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  161. package/dist/components/workflow-traces/event-colors.js +12 -1
  162. package/dist/components/workflow-traces/trace-colors.js +1 -1
  163. package/dist/components/workflow-traces/trace-span-construction.d.ts +2 -2
  164. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  165. package/dist/components/workflow-traces/trace-span-construction.js +19 -5
  166. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  167. package/dist/hooks/use-dark-mode.js +1 -1
  168. package/dist/hooks/use-intersection-observer.d.ts +17 -0
  169. package/dist/hooks/use-intersection-observer.d.ts.map +1 -0
  170. package/dist/hooks/use-intersection-observer.js +29 -0
  171. package/dist/hooks/use-load-more-on-scroll.d.ts +21 -0
  172. package/dist/hooks/use-load-more-on-scroll.d.ts.map +1 -0
  173. package/dist/hooks/use-load-more-on-scroll.js +28 -0
  174. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  175. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  176. package/dist/hooks/use-reduced-motion.js +29 -0
  177. package/dist/index.d.ts +3 -1
  178. package/dist/index.d.ts.map +1 -1
  179. package/dist/index.js +3 -2
  180. package/dist/lib/event-analysis.js +1 -1
  181. package/dist/lib/event-materialization.js +1 -1
  182. package/dist/lib/exact-event-search-id.d.ts +25 -0
  183. package/dist/lib/exact-event-search-id.d.ts.map +1 -0
  184. package/dist/lib/exact-event-search-id.js +39 -0
  185. package/dist/lib/hydration.d.ts +6 -0
  186. package/dist/lib/hydration.d.ts.map +1 -1
  187. package/dist/lib/hydration.js +154 -2
  188. package/dist/lib/stream-display.d.ts +18 -0
  189. package/dist/lib/stream-display.d.ts.map +1 -0
  190. package/dist/lib/stream-display.js +30 -0
  191. package/dist/lib/toast.js +1 -1
  192. package/dist/lib/trace-builder.d.ts +15 -0
  193. package/dist/lib/trace-builder.d.ts.map +1 -1
  194. package/dist/lib/trace-builder.js +29 -3
  195. package/dist/lib/utils.d.ts +20 -9
  196. package/dist/lib/utils.d.ts.map +1 -1
  197. package/dist/lib/utils.js +74 -31
  198. package/dist/lib/zstd-browser-decoder.d.ts +7 -0
  199. package/dist/lib/zstd-browser-decoder.d.ts.map +1 -0
  200. package/dist/lib/zstd-browser-decoder.js +41 -0
  201. package/dist/styles.css +484 -1
  202. package/package.json +10 -5
  203. package/src/components/event-list-view.tsx +279 -104
  204. package/src/components/index.ts +7 -0
  205. package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
  206. package/src/components/new-trace-viewer/components/event-list.tsx +136 -0
  207. package/src/components/new-trace-viewer/components/middle-truncate/copy-selection.ts +114 -0
  208. package/src/components/new-trace-viewer/components/middle-truncate/middle-truncate.tsx +157 -0
  209. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +132 -0
  210. package/src/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.ts +167 -0
  211. package/src/components/new-trace-viewer/components/span-markers.tsx +173 -0
  212. package/src/components/new-trace-viewer/components/split-pane.tsx +198 -0
  213. package/src/components/new-trace-viewer/components/timeline.module.css +46 -0
  214. package/src/components/new-trace-viewer/components/timeline.tsx +744 -0
  215. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +94 -0
  216. package/src/components/new-trace-viewer/components/use-row-window.ts +140 -0
  217. package/src/components/new-trace-viewer/context.tsx +90 -0
  218. package/src/components/new-trace-viewer/icons.tsx +79 -0
  219. package/src/components/new-trace-viewer/search.ts +178 -0
  220. package/src/components/new-trace-viewer/trace-viewer.tsx +843 -0
  221. package/src/components/new-trace-viewer/types.ts +2 -0
  222. package/src/components/new-trace-viewer/utils.test.ts +182 -0
  223. package/src/components/new-trace-viewer/utils.ts +519 -0
  224. package/src/components/sidebar/attribute-panel.tsx +194 -240
  225. package/src/components/sidebar/attributes-block.tsx +173 -0
  226. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  227. package/src/components/sidebar/detail-card.tsx +119 -59
  228. package/src/components/sidebar/entity-detail-panel.tsx +55 -111
  229. package/src/components/sidebar/events-list.tsx +85 -96
  230. package/src/components/sidebar/sidebar-data-context.tsx +62 -0
  231. package/src/components/sidebar/span-detail-merge.ts +65 -0
  232. package/src/components/stream-viewer.tsx +18 -40
  233. package/src/components/trace-viewer/components/node.tsx +3 -2
  234. package/src/components/trace-viewer/trace-viewer.module.css +4 -4
  235. package/src/components/trace-viewer/util/timing.ts +7 -11
  236. package/src/components/trace-viewer-new.tsx +55 -0
  237. package/src/components/ui/button.tsx +68 -0
  238. package/src/components/ui/context-card.tsx +784 -0
  239. package/src/components/ui/data-inspector.tsx +244 -22
  240. package/src/components/ui/error-stack-block.tsx +84 -55
  241. package/src/components/ui/icon-button.tsx +53 -0
  242. package/src/components/ui/inspector-theme.ts +39 -81
  243. package/src/components/ui/kbd.tsx +21 -0
  244. package/src/components/ui/timestamp-tooltip.tsx +132 -194
  245. package/src/components/ui/tooltip.tsx +47 -0
  246. package/src/components/workflow-trace-view.tsx +19 -15
  247. package/src/components/workflow-traces/event-colors.ts +12 -0
  248. package/src/components/workflow-traces/trace-span-construction.ts +24 -5
  249. package/src/hooks/use-intersection-observer.ts +57 -0
  250. package/src/hooks/use-load-more-on-scroll.ts +49 -0
  251. package/src/hooks/use-reduced-motion.ts +34 -0
  252. package/src/index.ts +9 -0
  253. package/src/lib/exact-event-search-id.ts +67 -0
  254. package/src/lib/hydration.ts +166 -1
  255. package/src/lib/stream-display.ts +48 -0
  256. package/src/lib/trace-builder.ts +34 -2
  257. package/src/lib/utils.ts +83 -31
  258. package/src/lib/zstd-browser-decoder.ts +42 -0
  259. package/src/styles.css +484 -1
  260. package/dist/components/error-boundary.js.map +0 -1
  261. package/dist/components/event-list-view.js.map +0 -1
  262. package/dist/components/hook-actions.js.map +0 -1
  263. package/dist/components/index.js.map +0 -1
  264. package/dist/components/run-trace-view.js.map +0 -1
  265. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  266. package/dist/components/sidebar/conversation-view.js.map +0 -1
  267. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  268. package/dist/components/sidebar/detail-card.js.map +0 -1
  269. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  270. package/dist/components/sidebar/events-list.js.map +0 -1
  271. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  272. package/dist/components/stream-viewer.js.map +0 -1
  273. package/dist/components/trace-viewer/components/map.js.map +0 -1
  274. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  275. package/dist/components/trace-viewer/components/node.js.map +0 -1
  276. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  277. package/dist/components/trace-viewer/components/search.js.map +0 -1
  278. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  279. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  280. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  281. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  282. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  283. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  284. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  285. package/dist/components/trace-viewer/context.js.map +0 -1
  286. package/dist/components/trace-viewer/index.js.map +0 -1
  287. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  288. package/dist/components/trace-viewer/types.js.map +0 -1
  289. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  290. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  291. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  292. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  293. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  294. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  295. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  296. package/dist/components/trace-viewer/worker.js.map +0 -1
  297. package/dist/components/ui/alert.js.map +0 -1
  298. package/dist/components/ui/card.js.map +0 -1
  299. package/dist/components/ui/data-inspector.js.map +0 -1
  300. package/dist/components/ui/decrypt-button.js.map +0 -1
  301. package/dist/components/ui/error-card.js.map +0 -1
  302. package/dist/components/ui/error-stack-block.js.map +0 -1
  303. package/dist/components/ui/inspector-theme.js.map +0 -1
  304. package/dist/components/ui/load-more-button.js.map +0 -1
  305. package/dist/components/ui/menu-dropdown.js.map +0 -1
  306. package/dist/components/ui/skeleton.js.map +0 -1
  307. package/dist/components/ui/spinner.js.map +0 -1
  308. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  309. package/dist/components/workflow-trace-view.js.map +0 -1
  310. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  311. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  312. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  313. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  314. package/dist/hooks/use-dark-mode.js.map +0 -1
  315. package/dist/index.js.map +0 -1
  316. package/dist/lib/event-analysis.js.map +0 -1
  317. package/dist/lib/event-materialization.js.map +0 -1
  318. package/dist/lib/hydration.js.map +0 -1
  319. package/dist/lib/toast.js.map +0 -1
  320. package/dist/lib/trace-builder.js.map +0 -1
  321. package/dist/lib/utils.js.map +0 -1
@@ -3,27 +3,26 @@
3
3
  import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
4
  import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
5
5
  import type { ModelMessage } from 'ai';
6
- import { Lock } from 'lucide-react';
6
+ import { format } from 'date-fns';
7
7
  import type { KeyboardEvent, ReactNode } from 'react';
8
8
  import { useCallback, useContext, useMemo, useState } from 'react';
9
9
  import { isEncryptedMarker, isExpiredMarker } from '../../lib/hydration';
10
- import { useToast } from '../../lib/toast';
11
10
  import { extractConversation, isDoStreamStep } from '../../lib/utils';
11
+ import { CopyButton } from '../new-trace-viewer/components/copy-button';
12
+ import { MiddleTruncate } from '../new-trace-viewer/components/middle-truncate/middle-truncate';
13
+ import { ContextCardProvider } from '../ui/context-card';
12
14
  import {
13
15
  DecryptClickContext,
14
16
  RunClickContext,
15
17
  StreamClickContext,
16
18
  } from '../ui/data-inspector';
17
19
  import { ErrorCard } from '../ui/error-card';
18
- import {
19
- ErrorStackBlock,
20
- isStructuredErrorWithStack,
21
- } from '../ui/error-stack-block';
20
+ import { ErrorStackBlock, isStructuredError } from '../ui/error-stack-block';
22
21
  import { Skeleton } from '../ui/skeleton';
23
- import { Spinner } from '../ui/spinner';
24
22
  import { TimestampTooltip } from '../ui/timestamp-tooltip';
23
+ import { RunAttributesCard } from './attributes-block';
25
24
  import { ConversationView } from './conversation-view';
26
- import { CopyableDataBlock } from './copyable-data-block';
25
+ import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
27
26
  import { DetailCard } from './detail-card';
28
27
 
29
28
  /**
@@ -151,7 +150,7 @@ function ConversationWithTabs({
151
150
  );
152
151
 
153
152
  return (
154
- <DetailCard summary={`Input (${conversation.length} messages)`}>
153
+ <DetailCard summary="Input">
155
154
  <TabbedContainer
156
155
  tabs={conversationTabs}
157
156
  activeTab={activeTab}
@@ -180,51 +179,8 @@ function ConversationWithTabs({
180
179
  * Render a value with the shared DataInspector (ObjectInspector with
181
180
  * custom theming, nodeRenderer for StreamRef/ClassInstanceRef, etc.)
182
181
  */
183
- /**
184
- * Inline display for an encrypted field — no expand, just a flat label
185
- * with the lucide Lock icon matching the title bar Decrypt button.
186
- */
187
182
  function EncryptedFieldBlock() {
188
- const ctx = useContext(DecryptClickContext);
189
- if (ctx) {
190
- return (
191
- <button
192
- type="button"
193
- onClick={ctx.onDecrypt}
194
- disabled={ctx.isDecrypting}
195
- className="flex w-full items-center justify-center gap-1.5 rounded-md border px-3 py-2 text-xs cursor-pointer transition-colors"
196
- style={{
197
- borderColor: 'var(--ds-gray-400)',
198
- backgroundColor: 'var(--ds-gray-100)',
199
- color: 'var(--ds-gray-700)',
200
- opacity: ctx.isDecrypting ? 0.6 : 1,
201
- }}
202
- title="Click to decrypt"
203
- >
204
- {ctx.isDecrypting ? (
205
- <Spinner size={12} />
206
- ) : (
207
- <Lock className="h-3 w-3" />
208
- )}
209
- <span className="font-medium">
210
- {ctx.isDecrypting ? 'Decrypting…' : 'Decrypt'}
211
- </span>
212
- </button>
213
- );
214
- }
215
- return (
216
- <div
217
- className="flex w-full items-center justify-center gap-1.5 rounded-md border px-3 py-2 text-xs"
218
- style={{
219
- borderColor: 'var(--ds-gray-300)',
220
- backgroundColor: 'var(--ds-gray-100)',
221
- color: 'var(--ds-gray-700)',
222
- }}
223
- >
224
- <Lock className="h-3 w-3" />
225
- <span className="font-medium">Encrypted</span>
226
- </div>
227
- );
183
+ return <EncryptedDataBlock />;
228
184
  }
229
185
 
230
186
  /**
@@ -269,7 +225,9 @@ type AttributeKey =
269
225
  | 'workflowCoreVersion'
270
226
  | 'receivedCount'
271
227
  | 'lastReceivedAt'
272
- | 'disposedAt';
228
+ | 'disposedAt'
229
+ | 'isSystem'
230
+ | 'errorCode';
273
231
 
274
232
  const attributeOrder: AttributeKey[] = [
275
233
  'workflowName',
@@ -305,6 +263,7 @@ const attributeOrder: AttributeKey[] = [
305
263
  'eventData',
306
264
  'input',
307
265
  'output',
266
+ 'attributes',
308
267
  'resumeAt',
309
268
  ];
310
269
 
@@ -318,8 +277,31 @@ const sortByAttributeOrder = (a: string, b: string): number => {
318
277
  * Display names for attributes that should render differently from their key.
319
278
  */
320
279
  const attributeDisplayNames: Partial<Record<AttributeKey, string>> = {
280
+ moduleSpecifier: 'Module',
281
+ workflowName: 'Workflow Name',
282
+ stepName: 'Step Name',
283
+ stepId: 'Step ID',
284
+ hookId: 'Hook ID',
285
+ attempt: 'Attempts',
286
+ eventId: 'Event ID',
287
+ runId: 'Run ID',
288
+ token: 'Token',
289
+ eventType: 'Event Type',
290
+ errorCode: 'Error Code',
291
+ correlationId: 'Correlation ID',
292
+ deploymentId: 'Deployment ID',
293
+ specVersion: 'Spec Version',
321
294
  workflowCoreVersion: '@workflow/core version',
322
- receivedCount: 'times resolved',
295
+ createdAt: 'Created',
296
+ startedAt: 'Started',
297
+ updatedAt: 'Updated',
298
+ completedAt: 'Completed',
299
+ expiredAt: 'Expired',
300
+ retryAfter: 'Retry After',
301
+ resumeAt: 'Resume',
302
+ lastReceivedAt: 'Last Received',
303
+ disposedAt: 'Disposed',
304
+ receivedCount: 'Times Resolved',
323
305
  };
324
306
 
325
307
  /**
@@ -359,15 +341,7 @@ const parseDateValue = (value: unknown): Date | null => {
359
341
  };
360
342
 
361
343
  const formatLocalMillisecondTime = (date: Date): string =>
362
- date.toLocaleString(undefined, {
363
- year: 'numeric',
364
- month: 'numeric',
365
- day: 'numeric',
366
- hour: 'numeric',
367
- minute: 'numeric',
368
- second: 'numeric',
369
- fractionalSecondDigits: 3,
370
- });
344
+ format(date, 'MMM dd HH:mm:ss.SS OO').toUpperCase();
371
345
 
372
346
  export const localMillisecondTime = (value: unknown): string => {
373
347
  const date = parseDateValue(value);
@@ -375,7 +349,6 @@ export const localMillisecondTime = (value: unknown): string => {
375
349
  return '-';
376
350
  }
377
351
 
378
- // e.g. 12/17/2025, 9:08:55.182 AM
379
352
  return formatLocalMillisecondTime(date);
380
353
  };
381
354
 
@@ -406,22 +379,21 @@ const attributeToDisplayFn: Record<
406
379
  (value: unknown, context?: DisplayContext) => null | string | ReactNode
407
380
  > = {
408
381
  // Names that need pretty-printing
409
- workflowName: (value: unknown) =>
410
- parseWorkflowName(String(value))?.shortName ?? '?',
382
+ workflowName: (_value: unknown) => null,
411
383
  moduleSpecifier: (value: unknown) => getModuleSpecifierFromName(value),
412
- stepName: (value: unknown) =>
413
- parseStepName(String(value))?.shortName ?? String(value),
384
+ stepName: (_value: unknown) => null,
414
385
  // IDs
415
- runId: (value: unknown) => String(value),
386
+ runId: (_value: unknown) => null,
416
387
  stepId: (value: unknown) => String(value),
417
388
  hookId: (value: unknown) => String(value),
418
389
  eventId: (value: unknown) => String(value),
419
390
  // Run/step details
420
- status: (value: unknown) => String(value),
391
+ status: (_value: unknown) => null,
421
392
  attempt: (value: unknown) => String(value),
422
393
  // Hook details
423
394
  token: (value: unknown) => String(value),
424
395
  isWebhook: (value: unknown) => String(value),
396
+ isSystem: (value: unknown) => String(value),
425
397
  receivedCount: (value: unknown) => String(value),
426
398
  lastReceivedAt: localMillisecondTimeOrNull,
427
399
  disposedAt: localMillisecondTimeOrNull,
@@ -437,23 +409,36 @@ const attributeToDisplayFn: Record<
437
409
  projectId: (_value: unknown) => null,
438
410
  environment: (_value: unknown) => null,
439
411
  executionContext: (_value: unknown) => null,
412
+ // Attributes — string-string metadata attached to the run.
413
+ // Rendered as key-value rows in its own collapsible DetailCard;
414
+ // if empty/missing, hidden by the hasDisplayContent gate.
415
+ attributes: (value: unknown) => {
416
+ if (!hasDisplayContent(value)) return null;
417
+ return <RunAttributesCard attributes={value as Record<string, string>} />;
418
+ },
440
419
  // Dates — wrapped with TimestampTooltip showing UTC/local + relative time
441
420
  createdAt: timestampWithTooltipOrNull,
442
421
  startedAt: timestampWithTooltipOrNull,
443
- updatedAt: timestampWithTooltipOrNull,
422
+ updatedAt: (_value: unknown) => null,
444
423
  completedAt: timestampWithTooltipOrNull,
445
- expiredAt: timestampWithTooltipOrNull,
424
+ expiredAt: (_value: unknown) => null,
446
425
  retryAfter: timestampWithTooltipOrNull,
447
426
  resumeAt: timestampWithTooltipOrNull,
448
427
  // Resolved attributes, won't actually use this function
449
428
  metadata: (value: unknown) => {
450
429
  if (!hasDisplayContent(value)) return null;
451
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
430
+ if (isEncryptedMarker(value)) return <EncryptedDataBlock />;
452
431
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
453
432
  return JsonBlock(value);
454
433
  },
455
434
  input: (value: unknown, context?: DisplayContext) => {
456
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
435
+ if (isEncryptedMarker(value)) {
436
+ return (
437
+ <DetailCard summary="Input">
438
+ <EncryptedFieldBlock />
439
+ </DetailCard>
440
+ );
441
+ }
457
442
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
458
443
  // Check if input has args + closure vars structure
459
444
  if (value && typeof value === 'object' && 'args' in value) {
@@ -462,8 +447,6 @@ const attributeToDisplayFn: Record<
462
447
  closureVars?: Record<string, unknown>;
463
448
  thisVal?: unknown;
464
449
  };
465
- const argCount = Array.isArray(args) ? args.length : 0;
466
- const argLabel = argCount === 1 ? 'argument' : 'arguments';
467
450
  const hasClosureVars = hasDisplayContent(closureVars);
468
451
  const hasThisVal = hasDisplayContent(thisVal);
469
452
  const hasArgs = hasDisplayContent(args);
@@ -492,22 +475,12 @@ const attributeToDisplayFn: Record<
492
475
 
493
476
  // Don't render an empty "Input (0 arguments)" card when no input exists.
494
477
  if (!hasArgs && !hasClosureVars && !hasThisVal) {
495
- return (
496
- <DetailCard
497
- summary="Input (no data)"
498
- disabled
499
- summaryClassName="text-base py-2"
500
- />
501
- );
478
+ return <DetailCard summary="Input (no data)" disabled />;
502
479
  }
503
480
 
504
481
  return (
505
482
  <>
506
- <DetailCard
507
- summary={`Input (${argCount} ${argLabel})`}
508
- summaryClassName="text-base py-2"
509
- contentClassName="mt-0"
510
- >
483
+ <DetailCard summary="Input">
511
484
  {Array.isArray(args)
512
485
  ? args.map((v, i) => (
513
486
  <div className="mt-2 first:mt-0" key={i}>
@@ -522,30 +495,18 @@ const attributeToDisplayFn: Record<
522
495
  </DetailCard>
523
496
  )}
524
497
  {hasThisVal && (
525
- <DetailCard summary="this">{JsonBlock(thisVal)}</DetailCard>
498
+ <DetailCard summary="Context">{JsonBlock(thisVal)}</DetailCard>
526
499
  )}
527
500
  </>
528
501
  );
529
502
  }
530
503
 
531
504
  // Fallback: treat as plain array or object
532
- const argCount = Array.isArray(value) ? value.length : 0;
533
- const argLabel = argCount === 1 ? 'argument' : 'arguments';
534
505
  if (!hasDisplayContent(value)) {
535
- return (
536
- <DetailCard
537
- summary="Input (no data)"
538
- disabled
539
- summaryClassName="text-base py-2"
540
- />
541
- );
506
+ return <DetailCard summary="Input (no data)" disabled />;
542
507
  }
543
508
  return (
544
- <DetailCard
545
- summary={`Input (${argCount} ${argLabel})`}
546
- summaryClassName="text-base py-2"
547
- contentClassName="mt-0"
548
- >
509
+ <DetailCard summary="Input">
549
510
  {Array.isArray(value)
550
511
  ? value.map((v, i) => (
551
512
  <div className="mt-2 first:mt-0" key={i}>
@@ -557,53 +518,63 @@ const attributeToDisplayFn: Record<
557
518
  );
558
519
  },
559
520
  output: (value: unknown) => {
521
+ if (isEncryptedMarker(value)) {
522
+ return (
523
+ <DetailCard summary="Output">
524
+ <EncryptedFieldBlock />
525
+ </DetailCard>
526
+ );
527
+ }
560
528
  if (!hasDisplayContent(value)) return null;
561
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
562
529
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
563
- return (
564
- <DetailCard
565
- summary="Output"
566
- summaryClassName="text-base py-2"
567
- contentClassName="mt-0"
568
- >
569
- {JsonBlock(value)}
570
- </DetailCard>
571
- );
530
+ return <DetailCard summary="Output">{JsonBlock(value)}</DetailCard>;
572
531
  },
573
532
  error: (value: unknown) => {
574
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
533
+ if (isEncryptedMarker(value)) {
534
+ return (
535
+ <DetailCard summary="Error" defaultOpen>
536
+ <EncryptedFieldBlock />
537
+ </DetailCard>
538
+ );
539
+ }
575
540
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
576
541
  if (!hasDisplayContent(value)) return null;
577
542
 
578
- // If the error object has a `stack` field, render it as readable
579
- // pre-formatted text. Otherwise fall back to the raw JSON viewer.
580
- if (isStructuredErrorWithStack(value)) {
543
+ // Structured workflow errors may be persisted as either `{ message }` or
544
+ // `{ message, stack }`. Render both with the dedicated error block.
545
+ if (isStructuredError(value)) {
581
546
  return (
582
- <DetailCard
583
- summary="Error"
584
- summaryClassName="text-base py-2"
585
- contentClassName="mt-0"
586
- >
547
+ <DetailCard summary="Error" defaultOpen>
587
548
  <ErrorStackBlock value={value} />
588
549
  </DetailCard>
589
550
  );
590
551
  }
591
552
 
592
553
  return (
593
- <DetailCard
594
- summary="Error"
595
- summaryClassName="text-base py-2"
596
- contentClassName="mt-0"
597
- >
554
+ <DetailCard summary="Error" defaultOpen>
598
555
  {JsonBlock(value)}
599
556
  </DetailCard>
600
557
  );
601
558
  },
602
559
  eventData: (value: unknown) => {
603
- if (isEncryptedMarker(value)) return <EncryptedFieldBlock />;
560
+ if (isEncryptedMarker(value)) {
561
+ return (
562
+ <DetailCard summary="Event Data" defaultOpen>
563
+ <EncryptedFieldBlock />
564
+ </DetailCard>
565
+ );
566
+ }
604
567
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
605
568
  if (!hasDisplayContent(value)) return null;
606
- return <DetailCard summary="Event Data">{JsonBlock(value)}</DetailCard>;
569
+ return (
570
+ <DetailCard summary="Event Data" defaultOpen>
571
+ {JsonBlock(value)}
572
+ </DetailCard>
573
+ );
574
+ },
575
+ errorCode: (value: unknown) => {
576
+ if (typeof value !== 'string' || value.length === 0) return null;
577
+ return String(value);
607
578
  },
608
579
  };
609
580
 
@@ -612,9 +583,20 @@ const resolvableAttributes = [
612
583
  'output',
613
584
  'error',
614
585
  'metadata',
586
+ 'attributes',
615
587
  'eventData',
616
588
  ];
617
589
 
590
+ // Attributes whose displayFn renders its own section header via DetailCard,
591
+ // so the outer AttributeBlock should not duplicate the label.
592
+ const selfHeaderedAttributes = new Set([
593
+ 'input',
594
+ 'output',
595
+ 'error',
596
+ 'attributes',
597
+ 'eventData',
598
+ ]);
599
+
618
600
  const ExpiredDataMessage = () => (
619
601
  <div
620
602
  className="text-copy-12 rounded-md border p-4 my-2"
@@ -628,6 +610,14 @@ const ExpiredDataMessage = () => (
628
610
  </div>
629
611
  );
630
612
 
613
+ const copyableBasicAttributes = new Set<AttributeKey>([
614
+ 'stepId',
615
+ 'hookId',
616
+ 'eventId',
617
+ 'deploymentId',
618
+ 'moduleSpecifier',
619
+ ]);
620
+
631
621
  export const AttributeBlock = ({
632
622
  attribute,
633
623
  value,
@@ -641,24 +631,26 @@ export const AttributeBlock = ({
641
631
  inline?: boolean;
642
632
  context?: DisplayContext;
643
633
  }) => {
634
+ const decryptCtx = useContext(DecryptClickContext);
644
635
  const isExpandableLoadingTarget =
645
636
  attribute === 'input' ||
646
637
  attribute === 'output' ||
647
638
  attribute === 'eventData';
648
639
  if (isLoading && isExpandableLoadingTarget && !hasDisplayContent(value)) {
649
- return (
650
- <div
651
- className={`my-2 flex flex-col ${attribute === 'input' || attribute === 'output' ? 'gap-2 my-3.5' : 'gap-0'}`}
652
- >
653
- <span
654
- className={`${attribute === 'input' || attribute === 'output' ? 'text-base' : 'text-xs'} font-medium first-letter:uppercase`}
655
- style={{ color: 'var(--ds-gray-700)' }}
656
- >
657
- {attribute}
658
- </span>
659
- <Skeleton className="h-9 w-full rounded-md" />
660
- </div>
661
- );
640
+ const label =
641
+ attribute === 'eventData'
642
+ ? 'Event Data'
643
+ : attribute === 'output'
644
+ ? 'Output'
645
+ : 'Input';
646
+ if (decryptCtx?.hasEncryptedData) {
647
+ return (
648
+ <DetailCard summary={label} defaultOpen={attribute === 'eventData'}>
649
+ <EncryptedFieldBlock />
650
+ </DetailCard>
651
+ );
652
+ }
653
+ return <DetailCard summary={label} />;
662
654
  }
663
655
 
664
656
  const displayFn =
@@ -687,6 +679,10 @@ export const AttributeBlock = ({
687
679
  );
688
680
  }
689
681
 
682
+ if (selfHeaderedAttributes.has(attribute)) {
683
+ return <>{displayValue}</>;
684
+ }
685
+
690
686
  return (
691
687
  <div className="relative">
692
688
  {typeof isLoading === 'boolean' && isLoading && (
@@ -697,14 +693,8 @@ export const AttributeBlock = ({
697
693
  />
698
694
  </div>
699
695
  )}
700
- <div
701
- key={attribute}
702
- className={`my-2 flex flex-col ${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'gap-2 my-3.5' : 'gap-0'}`}
703
- >
704
- <span
705
- className={`${attribute === 'input' || attribute === 'output' || attribute === 'error' ? 'text-base' : 'text-xs'} font-medium first-letter:uppercase`}
706
- style={{ color: 'var(--ds-gray-700)' }}
707
- >
696
+ <div key={attribute} className="my-2 flex flex-col gap-0">
697
+ <span className="text-label-14 text-gray-1000 font-medium first-letter:uppercase">
708
698
  {attribute}
709
699
  </span>
710
700
  <span className="text-xs" style={{ color: 'var(--ds-gray-1000)' }}>
@@ -743,7 +733,6 @@ export const AttributePanel = ({
743
733
  /** Resource type of the selected span — used to show targeted loading skeletons. */
744
734
  resource?: string;
745
735
  }) => {
746
- const toast = useToast();
747
736
  // Extract workflowCoreVersion from executionContext for display
748
737
  const displayData = useMemo(() => {
749
738
  const result = { ...data };
@@ -773,7 +762,9 @@ export const AttributePanel = ({
773
762
 
774
763
  if (!isLoading) return present;
775
764
 
776
- // During loading, ensure input/output appear so their skeletons render
765
+ if (resource === 'sleep') return present;
766
+
767
+ // During loading, ensure sections appear so their skeletons render
777
768
  // in the correct position (above the events section).
778
769
  const loadingDefaults = ['input', 'output'];
779
770
  for (const key of loadingDefaults) {
@@ -782,7 +773,7 @@ export const AttributePanel = ({
782
773
  }
783
774
  }
784
775
  return present.sort(sortByAttributeOrder);
785
- }, [displayData, isLoading]);
776
+ }, [displayData, isLoading, resource]);
786
777
 
787
778
  // Filter out attributes that return null
788
779
  const visibleBasicAttributes = basicAttributes.filter((attribute) => {
@@ -827,96 +818,59 @@ export const AttributePanel = ({
827
818
  }),
828
819
  [displayData.stepName]
829
820
  );
830
- const handleCopyModuleSpecifier = useCallback((value: string) => {
831
- navigator.clipboard
832
- .writeText(value)
833
- .then(() => {
834
- toast.success('moduleSpecifier copied');
835
- })
836
- .catch(() => {
837
- toast.error('Failed to copy moduleSpecifier');
838
- });
839
- }, []);
821
+ const outerDecryptCtx = useContext(DecryptClickContext);
822
+ const decryptValue = onDecrypt
823
+ ? {
824
+ onDecrypt,
825
+ isDecrypting,
826
+ hasEncryptedData: outerDecryptCtx?.hasEncryptedData,
827
+ }
828
+ : outerDecryptCtx;
840
829
 
841
830
  return (
842
- <RunClickContext.Provider value={onRunClick}>
843
- <StreamClickContext.Provider value={onStreamClick}>
844
- <DecryptClickContext.Provider
845
- value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
846
- >
847
- <div>
848
- {/* Basic attributes in a vertical layout with border */}
831
+ <ContextCardProvider>
832
+ <RunClickContext.Provider value={onRunClick}>
833
+ <StreamClickContext.Provider value={onStreamClick}>
834
+ <DecryptClickContext.Provider value={decryptValue}>
849
835
  {visibleBasicAttributes.length > 0 && (
850
- <div
851
- className="mb-3 flex flex-col overflow-hidden rounded-lg border"
852
- style={{
853
- borderColor: 'var(--ds-gray-300)',
854
- }}
855
- >
856
- {orderedBasicAttributes.map((attribute, index) => {
836
+ <div className="flex flex-col overflow-hidden divide-y divide-gray-alpha-400 mb-3">
837
+ {orderedBasicAttributes.map((attribute) => {
857
838
  const displayValue = attributeToDisplayFn[
858
839
  attribute as keyof typeof attributeToDisplayFn
859
840
  ]?.(displayData[attribute as keyof typeof displayData]);
860
- const isModuleSpecifier = attribute === 'moduleSpecifier';
861
- const moduleSpecifierValue =
862
- typeof displayValue === 'string'
863
- ? displayValue
864
- : String(
865
- displayValue ?? displayData.moduleSpecifier ?? ''
866
- );
867
- const shouldCapitalizeLabel =
868
- attribute !== 'workflowCoreVersion';
869
- const showResumeAtSkeleton =
870
- isLoading && resource === 'sleep' && !displayData.resumeAt;
871
- const showDivider =
872
- index < orderedBasicAttributes.length - 1 ||
873
- showResumeAtSkeleton;
841
+ const isCopyableBasicAttribute =
842
+ copyableBasicAttributes.has(attribute as AttributeKey) &&
843
+ typeof displayValue === 'string';
874
844
 
875
845
  return (
876
- <div key={attribute} className="py-1">
877
- <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
878
- <span
879
- className={
880
- shouldCapitalizeLabel
881
- ? 'text-[14px] first-letter:uppercase'
882
- : 'text-[14px]'
883
- }
884
- style={{ color: 'var(--ds-gray-700)' }}
846
+ <div
847
+ className="flex items-center justify-between py-2"
848
+ key={attribute}
849
+ >
850
+ <span className="text-label-14 text-gray-900">
851
+ {getAttributeDisplayName(attribute)}
852
+ </span>
853
+ {isCopyableBasicAttribute ? (
854
+ <div
855
+ className="flex min-w-0 max-w-[70%] items-center justify-end gap-1 text-[13px] font-mono text-gray-1000"
856
+ title={displayValue}
885
857
  >
886
- {getAttributeDisplayName(attribute)}
858
+ <MiddleTruncate
859
+ value={displayValue}
860
+ className="text-right"
861
+ style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}
862
+ />
863
+ <CopyButton
864
+ copyText={displayValue}
865
+ ariaLabel={`Copy ${getAttributeDisplayName(attribute)}`}
866
+ className="shrink-0 -mr-1"
867
+ />
868
+ </div>
869
+ ) : (
870
+ <span className="text-right text-label-13 font-mono">
871
+ {displayValue}
887
872
  </span>
888
- {isModuleSpecifier ? (
889
- <button
890
- type="button"
891
- className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
892
- style={{
893
- color: 'var(--ds-gray-1000)',
894
- background: 'transparent',
895
- border: 'none',
896
- padding: 0,
897
- }}
898
- title={moduleSpecifierValue}
899
- onClick={() =>
900
- handleCopyModuleSpecifier(moduleSpecifierValue)
901
- }
902
- >
903
- {moduleSpecifierValue}
904
- </button>
905
- ) : (
906
- <span
907
- className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
908
- style={{ color: 'var(--ds-gray-1000)' }}
909
- >
910
- {displayValue}
911
- </span>
912
- )}
913
- </div>
914
- {showDivider ? (
915
- <div
916
- className="mx-2.5 border-b"
917
- style={{ borderColor: 'var(--ds-gray-300)' }}
918
- />
919
- ) : null}
873
+ )}
920
874
  </div>
921
875
  );
922
876
  })}
@@ -943,7 +897,7 @@ export const AttributePanel = ({
943
897
  />
944
898
  ) : hasExpired ? (
945
899
  <ExpiredDataMessage />
946
- ) : (
900
+ ) : resolvedAttributes.length > 0 ? (
947
901
  <>
948
902
  {resolvedAttributes.map((attribute) => (
949
903
  <AttributeBlock
@@ -955,10 +909,10 @@ export const AttributePanel = ({
955
909
  />
956
910
  ))}
957
911
  </>
958
- )}
959
- </div>
960
- </DecryptClickContext.Provider>
961
- </StreamClickContext.Provider>
962
- </RunClickContext.Provider>
912
+ ) : null}
913
+ </DecryptClickContext.Provider>
914
+ </StreamClickContext.Provider>
915
+ </RunClickContext.Provider>
916
+ </ContextCardProvider>
963
917
  );
964
918
  };