@workflow/web-shared 5.0.0-beta.6 → 5.0.0-beta.8

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 (200) hide show
  1. package/dist/components/error-boundary.js +1 -1
  2. package/dist/components/event-list-view.d.ts +12 -0
  3. package/dist/components/event-list-view.d.ts.map +1 -1
  4. package/dist/components/event-list-view.js +26 -13
  5. package/dist/components/hook-actions.js +1 -1
  6. package/dist/components/index.js +1 -1
  7. package/dist/components/new-trace-viewer/components/copy-button.js +2 -2
  8. package/dist/components/new-trace-viewer/components/event-list.js +3 -3
  9. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js +1 -1
  10. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js +1 -1
  11. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +1 -1
  12. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js +1 -1
  13. package/dist/components/new-trace-viewer/components/split-pane.js +1 -1
  14. package/dist/components/new-trace-viewer/components/timeline.d.ts +1 -1
  15. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  16. package/dist/components/new-trace-viewer/components/timeline.js +187 -103
  17. package/dist/components/new-trace-viewer/context.js +1 -1
  18. package/dist/components/new-trace-viewer/detail-panel.js +2 -2
  19. package/dist/components/new-trace-viewer/icons.js +1 -1
  20. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  21. package/dist/components/new-trace-viewer/trace-viewer.js +53 -26
  22. package/dist/components/new-trace-viewer/types.js +1 -1
  23. package/dist/components/new-trace-viewer/util/constants.js +1 -1
  24. package/dist/components/new-trace-viewer/utils.d.ts +8 -4
  25. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  26. package/dist/components/new-trace-viewer/utils.js +31 -6
  27. package/dist/components/run-trace-view.js +1 -1
  28. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  29. package/dist/components/sidebar/attribute-panel.js +99 -73
  30. package/dist/components/sidebar/conversation-view.js +1 -1
  31. package/dist/components/sidebar/copyable-data-block.d.ts +1 -0
  32. package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
  33. package/dist/components/sidebar/copyable-data-block.js +16 -15
  34. package/dist/components/sidebar/detail-card.d.ts +5 -6
  35. package/dist/components/sidebar/detail-card.d.ts.map +1 -1
  36. package/dist/components/sidebar/detail-card.js +32 -16
  37. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  38. package/dist/components/sidebar/entity-detail-panel.js +6 -3
  39. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  40. package/dist/components/sidebar/events-list.js +21 -15
  41. package/dist/components/sidebar/resolve-hook-modal.js +1 -1
  42. package/dist/components/sidebar/sidebar-data-context.js +1 -1
  43. package/dist/components/stream-viewer.js +1 -1
  44. package/dist/components/trace-viewer/components/map.js +1 -1
  45. package/dist/components/trace-viewer/components/markers.js +1 -1
  46. package/dist/components/trace-viewer/components/node.js +1 -1
  47. package/dist/components/trace-viewer/components/search-input.js +1 -1
  48. package/dist/components/trace-viewer/components/search.js +1 -1
  49. package/dist/components/trace-viewer/components/span-content.js +1 -1
  50. package/dist/components/trace-viewer/components/span-detail-panel.js +1 -1
  51. package/dist/components/trace-viewer/components/span-segments.js +1 -1
  52. package/dist/components/trace-viewer/components/span-strategies.js +1 -1
  53. package/dist/components/trace-viewer/components/ui.js +1 -1
  54. package/dist/components/trace-viewer/components/zoom-button.js +1 -1
  55. package/dist/components/trace-viewer/components/zoom-icons.js +1 -1
  56. package/dist/components/trace-viewer/context.js +1 -1
  57. package/dist/components/trace-viewer/index.js +1 -1
  58. package/dist/components/trace-viewer/trace-viewer.js +1 -1
  59. package/dist/components/trace-viewer/types.js +1 -1
  60. package/dist/components/trace-viewer/util/constants.js +1 -1
  61. package/dist/components/trace-viewer/util/scrollbar-width.js +1 -1
  62. package/dist/components/trace-viewer/util/timing.js +1 -1
  63. package/dist/components/trace-viewer/util/tree.js +1 -1
  64. package/dist/components/trace-viewer/util/use-immediate-style.js +1 -1
  65. package/dist/components/trace-viewer/util/use-streaming-spans.js +1 -1
  66. package/dist/components/trace-viewer/util/use-trackpad-zoom.js +1 -1
  67. package/dist/components/trace-viewer/worker.js +1 -1
  68. package/dist/components/trace-viewer-new.js +1 -1
  69. package/dist/components/ui/alert.js +1 -1
  70. package/dist/components/ui/button.d.ts +10 -0
  71. package/dist/components/ui/button.d.ts.map +1 -0
  72. package/dist/components/ui/button.js +39 -0
  73. package/dist/components/ui/card.js +1 -1
  74. package/dist/components/ui/data-inspector.d.ts +1 -0
  75. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  76. package/dist/components/ui/data-inspector.js +4 -9
  77. package/dist/components/ui/decrypt-button.js +1 -1
  78. package/dist/components/ui/error-card.js +1 -1
  79. package/dist/components/ui/error-stack-block.d.ts.map +1 -1
  80. package/dist/components/ui/error-stack-block.js +4 -18
  81. package/dist/components/ui/inspector-theme.js +1 -1
  82. package/dist/components/ui/load-more-button.js +1 -1
  83. package/dist/components/ui/menu-dropdown.js +1 -1
  84. package/dist/components/ui/skeleton.js +1 -1
  85. package/dist/components/ui/spinner.js +1 -1
  86. package/dist/components/ui/timestamp-tooltip.js +1 -1
  87. package/dist/components/workflow-trace-view.js +1 -1
  88. package/dist/components/workflow-traces/event-colors.js +1 -1
  89. package/dist/components/workflow-traces/trace-colors.js +1 -1
  90. package/dist/components/workflow-traces/trace-span-construction.js +1 -1
  91. package/dist/components/workflow-traces/trace-time-utils.js +1 -1
  92. package/dist/hooks/use-dark-mode.js +1 -1
  93. package/dist/hooks/use-reduced-motion.d.ts +10 -0
  94. package/dist/hooks/use-reduced-motion.d.ts.map +1 -0
  95. package/dist/hooks/use-reduced-motion.js +29 -0
  96. package/dist/index.js +1 -1
  97. package/dist/lib/event-analysis.js +1 -1
  98. package/dist/lib/event-materialization.js +1 -1
  99. package/dist/lib/hydration.js +1 -1
  100. package/dist/lib/stream-display.js +1 -1
  101. package/dist/lib/toast.js +1 -1
  102. package/dist/lib/trace-builder.js +1 -1
  103. package/dist/lib/utils.js +1 -1
  104. package/package.json +5 -5
  105. package/src/components/event-list-view.tsx +29 -13
  106. package/src/components/new-trace-viewer/components/copy-button.tsx +1 -1
  107. package/src/components/new-trace-viewer/components/event-list.tsx +2 -2
  108. package/src/components/new-trace-viewer/components/timeline.tsx +353 -192
  109. package/src/components/new-trace-viewer/detail-panel.tsx +1 -1
  110. package/src/components/new-trace-viewer/trace-viewer.tsx +93 -59
  111. package/src/components/new-trace-viewer/utils.ts +40 -8
  112. package/src/components/sidebar/attribute-panel.tsx +212 -235
  113. package/src/components/sidebar/copyable-data-block.tsx +49 -27
  114. package/src/components/sidebar/detail-card.tsx +119 -41
  115. package/src/components/sidebar/entity-detail-panel.tsx +28 -28
  116. package/src/components/sidebar/events-list.tsx +51 -79
  117. package/src/components/ui/button.tsx +61 -0
  118. package/src/components/ui/data-inspector.tsx +9 -18
  119. package/src/components/ui/error-stack-block.tsx +7 -26
  120. package/src/hooks/use-reduced-motion.ts +34 -0
  121. package/dist/components/error-boundary.js.map +0 -1
  122. package/dist/components/event-list-view.js.map +0 -1
  123. package/dist/components/hook-actions.js.map +0 -1
  124. package/dist/components/index.js.map +0 -1
  125. package/dist/components/new-trace-viewer/components/copy-button.js.map +0 -1
  126. package/dist/components/new-trace-viewer/components/event-list.js.map +0 -1
  127. package/dist/components/new-trace-viewer/components/middle-truncate/copy-selection.js.map +0 -1
  128. package/dist/components/new-trace-viewer/components/middle-truncate/middle-truncate.js.map +0 -1
  129. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js.map +0 -1
  130. package/dist/components/new-trace-viewer/components/middle-truncate/use-middle-truncate.js.map +0 -1
  131. package/dist/components/new-trace-viewer/components/split-pane.js.map +0 -1
  132. package/dist/components/new-trace-viewer/components/timeline.js.map +0 -1
  133. package/dist/components/new-trace-viewer/context.js.map +0 -1
  134. package/dist/components/new-trace-viewer/detail-panel.js.map +0 -1
  135. package/dist/components/new-trace-viewer/icons.js.map +0 -1
  136. package/dist/components/new-trace-viewer/trace-viewer.js.map +0 -1
  137. package/dist/components/new-trace-viewer/types.js.map +0 -1
  138. package/dist/components/new-trace-viewer/util/constants.js.map +0 -1
  139. package/dist/components/new-trace-viewer/utils.js.map +0 -1
  140. package/dist/components/run-trace-view.js.map +0 -1
  141. package/dist/components/sidebar/attribute-panel.js.map +0 -1
  142. package/dist/components/sidebar/conversation-view.js.map +0 -1
  143. package/dist/components/sidebar/copyable-data-block.js.map +0 -1
  144. package/dist/components/sidebar/detail-card.js.map +0 -1
  145. package/dist/components/sidebar/entity-detail-panel.js.map +0 -1
  146. package/dist/components/sidebar/events-list.js.map +0 -1
  147. package/dist/components/sidebar/resolve-hook-modal.js.map +0 -1
  148. package/dist/components/sidebar/sidebar-data-context.js.map +0 -1
  149. package/dist/components/stream-viewer.js.map +0 -1
  150. package/dist/components/trace-viewer/components/map.js.map +0 -1
  151. package/dist/components/trace-viewer/components/markers.js.map +0 -1
  152. package/dist/components/trace-viewer/components/node.js.map +0 -1
  153. package/dist/components/trace-viewer/components/search-input.js.map +0 -1
  154. package/dist/components/trace-viewer/components/search.js.map +0 -1
  155. package/dist/components/trace-viewer/components/span-content.js.map +0 -1
  156. package/dist/components/trace-viewer/components/span-detail-panel.js.map +0 -1
  157. package/dist/components/trace-viewer/components/span-segments.js.map +0 -1
  158. package/dist/components/trace-viewer/components/span-strategies.js.map +0 -1
  159. package/dist/components/trace-viewer/components/ui.js.map +0 -1
  160. package/dist/components/trace-viewer/components/zoom-button.js.map +0 -1
  161. package/dist/components/trace-viewer/components/zoom-icons.js.map +0 -1
  162. package/dist/components/trace-viewer/context.js.map +0 -1
  163. package/dist/components/trace-viewer/index.js.map +0 -1
  164. package/dist/components/trace-viewer/trace-viewer.js.map +0 -1
  165. package/dist/components/trace-viewer/types.js.map +0 -1
  166. package/dist/components/trace-viewer/util/constants.js.map +0 -1
  167. package/dist/components/trace-viewer/util/scrollbar-width.js.map +0 -1
  168. package/dist/components/trace-viewer/util/timing.js.map +0 -1
  169. package/dist/components/trace-viewer/util/tree.js.map +0 -1
  170. package/dist/components/trace-viewer/util/use-immediate-style.js.map +0 -1
  171. package/dist/components/trace-viewer/util/use-streaming-spans.js.map +0 -1
  172. package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +0 -1
  173. package/dist/components/trace-viewer/worker.js.map +0 -1
  174. package/dist/components/trace-viewer-new.js.map +0 -1
  175. package/dist/components/ui/alert.js.map +0 -1
  176. package/dist/components/ui/card.js.map +0 -1
  177. package/dist/components/ui/data-inspector.js.map +0 -1
  178. package/dist/components/ui/decrypt-button.js.map +0 -1
  179. package/dist/components/ui/error-card.js.map +0 -1
  180. package/dist/components/ui/error-stack-block.js.map +0 -1
  181. package/dist/components/ui/inspector-theme.js.map +0 -1
  182. package/dist/components/ui/load-more-button.js.map +0 -1
  183. package/dist/components/ui/menu-dropdown.js.map +0 -1
  184. package/dist/components/ui/skeleton.js.map +0 -1
  185. package/dist/components/ui/spinner.js.map +0 -1
  186. package/dist/components/ui/timestamp-tooltip.js.map +0 -1
  187. package/dist/components/workflow-trace-view.js.map +0 -1
  188. package/dist/components/workflow-traces/event-colors.js.map +0 -1
  189. package/dist/components/workflow-traces/trace-colors.js.map +0 -1
  190. package/dist/components/workflow-traces/trace-span-construction.js.map +0 -1
  191. package/dist/components/workflow-traces/trace-time-utils.js.map +0 -1
  192. package/dist/hooks/use-dark-mode.js.map +0 -1
  193. package/dist/index.js.map +0 -1
  194. package/dist/lib/event-analysis.js.map +0 -1
  195. package/dist/lib/event-materialization.js.map +0 -1
  196. package/dist/lib/hydration.js.map +0 -1
  197. package/dist/lib/stream-display.js.map +0 -1
  198. package/dist/lib/toast.js.map +0 -1
  199. package/dist/lib/trace-builder.js.map +0 -1
  200. package/dist/lib/utils.js.map +0 -1
@@ -1,8 +1,48 @@
1
1
  'use client';
2
2
 
3
- import { Copy } from 'lucide-react';
4
- import { useToast } from '../../lib/toast';
5
- import { DataInspector } from '../ui/data-inspector';
3
+ import { Lock } from 'lucide-react';
4
+ import { useContext } from 'react';
5
+ import { CopyButton } from '../new-trace-viewer/components/copy-button';
6
+ import { Button } from '../ui/button';
7
+ import { DataInspector, DecryptClickContext } from '../ui/data-inspector';
8
+ import { Spinner } from '../ui/spinner';
9
+
10
+ const encryptedPlaceholderPreview = `{
11
+ "input": "[encrypted]",
12
+ "result": "[encrypted]"
13
+ }`;
14
+
15
+ export function EncryptedDataBlock() {
16
+ const ctx = useContext(DecryptClickContext);
17
+
18
+ return (
19
+ <div className="relative min-h-20 overflow-hidden rounded-md border border-gray-alpha-400 bg-background-100">
20
+ <pre
21
+ aria-hidden="true"
22
+ className="pointer-events-none m-0 select-none p-3 font-mono text-label-12 text-gray-900 blur-[4px]"
23
+ >
24
+ {encryptedPlaceholderPreview}
25
+ </pre>
26
+ <div className="absolute inset-0 flex items-center justify-center">
27
+ {ctx ? (
28
+ <Button onClick={ctx.onDecrypt} disabled={ctx.isDecrypting} size="xs">
29
+ {ctx.isDecrypting ? (
30
+ <Spinner size={10} />
31
+ ) : (
32
+ <Lock className="h-3 w-3" />
33
+ )}
34
+ <span>Decrypt</span>
35
+ </Button>
36
+ ) : (
37
+ <span className="inline-flex items-center gap-1 rounded border border-gray-alpha-400 bg-gray-100 px-1.5 py-0.5 text-button-12 font-medium text-gray-700">
38
+ <Lock className="h-3 w-3" />
39
+ Encrypted
40
+ </span>
41
+ )}
42
+ </div>
43
+ </div>
44
+ );
45
+ }
6
46
 
7
47
  const serializeForClipboard = (value: unknown): string => {
8
48
  if (typeof value === 'string') return value;
@@ -21,31 +61,13 @@ const serializeForClipboard = (value: unknown): string => {
21
61
  };
22
62
 
23
63
  export function CopyableDataBlock({ data }: { data: unknown }) {
24
- const toast = useToast();
25
64
  return (
26
- <div
27
- className="relative overflow-x-auto rounded-md border p-3"
28
- style={{ borderColor: 'var(--ds-gray-300)' }}
29
- >
30
- <button
31
- type="button"
32
- aria-label="Copy data"
33
- title="Copy"
34
- className="!absolute !right-2 !top-2 !flex !h-6 !w-6 !items-center !justify-center !rounded-md !border !bg-[var(--ds-background-100)] !text-[var(--ds-gray-800)] transition-transform transition-colors duration-100 hover:!bg-[var(--ds-gray-alpha-200)] active:!scale-95 active:!bg-[var(--ds-gray-alpha-300)]"
35
- style={{ borderColor: 'var(--ds-gray-300)' }}
36
- onClick={() => {
37
- navigator.clipboard
38
- .writeText(serializeForClipboard(data))
39
- .then(() => {
40
- toast.success('Copied to clipboard');
41
- })
42
- .catch(() => {
43
- toast.error('Failed to copy');
44
- });
45
- }}
46
- >
47
- <Copy size={12} />
48
- </button>
65
+ <div className="relative overflow-x-auto rounded-md border border-gray-alpha-400 p-3">
66
+ <CopyButton
67
+ copyText={serializeForClipboard(data)}
68
+ ariaLabel="Copy data"
69
+ className="absolute right-2 top-2 z-10 flex h-6 w-6 items-center justify-center rounded-md border border-gray-alpha-400 !bg-background-100 p-0 text-gray-900 transition-transform transition-colors duration-100 hover:bg-gray-200 active:scale-95 active:bg-gray-300"
70
+ />
49
71
  <DataInspector data={data} />
50
72
  </div>
51
73
  );
@@ -1,65 +1,143 @@
1
- import { ChevronRight } from 'lucide-react';
2
- import type { ReactNode } from 'react';
1
+ import { ChevronDown, ChevronRight } from 'lucide-react';
2
+ import { type ReactNode, useState } from 'react';
3
+ import { cn } from '../../lib/utils';
3
4
 
4
5
  export function DetailCard({
5
6
  summary,
6
7
  children,
7
8
  onToggle,
8
9
  disabled = false,
10
+ defaultOpen = false,
11
+ variant = 'section',
12
+ trailing,
9
13
  summaryClassName,
10
14
  contentClassName,
11
15
  }: {
12
16
  summary: ReactNode;
13
17
  children?: ReactNode;
14
- /** Called when the detail card is expanded/collapsed */
15
18
  onToggle?: (open: boolean) => void;
16
- /** Renders a non-expandable summary card when true. */
17
19
  disabled?: boolean;
18
- /** Extra classes for the summary row. */
20
+ defaultOpen?: boolean;
21
+ variant?: 'section' | 'card';
22
+ trailing?: ReactNode;
19
23
  summaryClassName?: string;
20
- /** Extra classes for expanded content wrapper. */
21
24
  contentClassName?: string;
22
25
  }) {
23
- if (disabled) {
26
+ const [open, setOpen] = useState(defaultOpen);
27
+
28
+ const handleToggle = (e: React.SyntheticEvent<HTMLDetailsElement>) => {
29
+ // React surfaces `toggle` as a bubbling synthetic event even though the
30
+ // native event doesn't bubble. Without this guard, a nested <details>
31
+ // (e.g. an event card inside the Events section) collapsing would flip
32
+ // the outer DetailCard's state. Only react to direct toggles.
33
+ if (e.target !== e.currentTarget) return;
34
+ const next = e.currentTarget.open;
35
+ setOpen(next);
36
+ onToggle?.(next);
37
+ };
38
+
39
+ if (variant === 'card') {
40
+ if (disabled) {
41
+ return (
42
+ <div
43
+ className={cn(
44
+ 'list-none px-3 py-4 bg-background-200 [&::-webkit-details-marker]:hidden',
45
+ summaryClassName
46
+ )}
47
+ style={{ cursor: 'not-allowed', opacity: 0.8 }}
48
+ >
49
+ {summary}
50
+ </div>
51
+ );
52
+ }
24
53
  return (
25
- <div
26
- className={`rounded-md border px-2.5 py-1.5 text-xs ${summaryClassName ?? ''}`}
27
- style={{
28
- borderColor: 'var(--ds-gray-300)',
29
- backgroundColor: 'var(--ds-gray-100)',
30
- color: 'var(--ds-gray-700)',
31
- cursor: 'not-allowed',
32
- opacity: 0.8,
33
- }}
54
+ <details
55
+ className="group/card last:border-b border-gray-alpha-400"
56
+ open={open}
57
+ onToggle={handleToggle}
34
58
  >
35
- {summary}
36
- </div>
59
+ <summary
60
+ className={cn(
61
+ 'list-none cursor-pointer px-3 py-4 border-t border-gray-alpha-400 bg-background-200 hover:bg-gray-100 [&::-webkit-details-marker]:hidden',
62
+ summaryClassName
63
+ )}
64
+ >
65
+ <span className="flex items-center gap-1.5">
66
+ <ChevronRight
67
+ size={14}
68
+ className={cn(
69
+ 'shrink-0 text-gray-700 group-hover/card:text-gray-1000',
70
+ open && 'rotate-90'
71
+ )}
72
+ />
73
+ {summary}
74
+ </span>
75
+ </summary>
76
+ <div className={contentClassName}>{children}</div>
77
+ </details>
78
+ );
79
+ }
80
+
81
+ // Shared height with the expandable summary row. Keeps the trailing /
82
+ // disabled / chevron variants visually identical in height regardless of
83
+ // what's in the trailing slot.
84
+ const rowClasses =
85
+ 'flex h-9 items-center gap-2 px-2 -mx-2 text-heading-14 font-medium my-2';
86
+
87
+ if (trailing) {
88
+ return (
89
+ <section className="-mx-4 border-t px-4 border-gray-alpha-400">
90
+ <div className={cn(rowClasses, summaryClassName)}>
91
+ <div className="isolate relative shrink-0 w-3.5 h-3.5 text-gray-700">
92
+ <ChevronRight
93
+ size={14}
94
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
95
+ />
96
+ </div>
97
+ <span className="min-w-0 flex-1">{summary}</span>
98
+ <div className="shrink-0 pr-1">{trailing}</div>
99
+ </div>
100
+ </section>
101
+ );
102
+ }
103
+
104
+ if (disabled) {
105
+ return (
106
+ <section className="-mx-4 border-t px-4 border-gray-alpha-400">
107
+ <div
108
+ className={cn(rowClasses, summaryClassName)}
109
+ style={{ color: 'var(--ds-gray-700)', cursor: 'not-allowed' }}
110
+ >
111
+ <span className="min-w-0 flex-1">{summary}</span>
112
+ </div>
113
+ </section>
37
114
  );
38
115
  }
39
116
 
40
117
  return (
41
- <details
42
- className="group"
43
- onToggle={(e) => onToggle?.((e.target as HTMLDetailsElement).open)}
44
- >
45
- <summary
46
- className={`cursor-pointer rounded-md border px-2.5 py-1.5 text-xs hover:brightness-95 [&::-webkit-details-marker]:hidden ${summaryClassName ?? ''}`}
47
- style={{
48
- borderColor: 'var(--ds-gray-300)',
49
- backgroundColor: 'var(--ds-gray-100)',
50
- color: 'var(--ds-gray-900)',
51
- listStyle: 'none',
52
- }}
53
- >
54
- <span className="flex items-center gap-1.5">
55
- <ChevronRight
56
- size={14}
57
- className="shrink-0 transition-transform group-open:rotate-90"
58
- />
59
- {summary}
60
- </span>
61
- </summary>
62
- <div className={`mt-2 ${contentClassName ?? ''}`}>{children}</div>
63
- </details>
118
+ <section className="-mx-4 border-t px-4 border-gray-alpha-400">
119
+ <details className="group" open={open} onToggle={handleToggle}>
120
+ <summary
121
+ className={cn(
122
+ 'group/trigger list-none cursor-pointer rounded hover:bg-gray-alpha-100 [&::-webkit-details-marker]:hidden',
123
+ rowClasses,
124
+ summaryClassName
125
+ )}
126
+ >
127
+ <div className="isolate relative shrink-0 text-gray-700 group-hover/trigger:text-gray-1000 w-3.5 h-3.5">
128
+ <ChevronRight
129
+ size={14}
130
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-100 group-open:opacity-0"
131
+ />
132
+ <ChevronDown
133
+ size={14}
134
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 group-open:opacity-100"
135
+ />
136
+ </div>
137
+ <span className="min-w-0 flex-1">{summary}</span>
138
+ </summary>
139
+ <div className={cn('mb-3', contentClassName)}>{children}</div>
140
+ </details>
141
+ </section>
64
142
  );
65
143
  }
@@ -9,6 +9,7 @@ import { DecryptClickContext } from '../ui/data-inspector';
9
9
  import { AttributePanel } from './attribute-panel';
10
10
  import { EventsList } from './events-list';
11
11
  import { ResolveHookModal } from './resolve-hook-modal';
12
+ import { useSidebarDataOptional } from './sidebar-data-context';
12
13
 
13
14
  // Type guards for runtime validation of span attribute data
14
15
  function isStep(data: unknown): data is Step {
@@ -116,6 +117,9 @@ export function EntityDetailPanel({
116
117
  new Set()
117
118
  );
118
119
 
120
+ const sidebar = useSidebarDataOptional();
121
+ const hasEncryptedData = Boolean(sidebar?.hasEncryptedData && !encryptionKey);
122
+
119
123
  const data = selectedSpan?.data;
120
124
  const rawEvents = selectedSpan?.rawEvents;
121
125
  const rawEventsLength = rawEvents?.length ?? 0;
@@ -345,9 +349,11 @@ export function EntityDetailPanel({
345
349
  return (
346
350
  <div className="flex h-full flex-col">
347
351
  <DecryptClickContext.Provider
348
- value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
352
+ value={
353
+ onDecrypt ? { onDecrypt, isDecrypting, hasEncryptedData } : undefined
354
+ }
349
355
  >
350
- <div className="flex-1 overflow-y-auto px-3 pt-3 pb-8">
356
+ <div className="flex-1 overflow-y-auto px-4 pb-8">
351
357
  {hasPendingActions && (
352
358
  <div
353
359
  className="mb-4 rounded-lg border p-2"
@@ -412,32 +418,26 @@ export function EntityDetailPanel({
412
418
  </div>
413
419
  )}
414
420
 
415
- <div className="space-y-4">
416
- <section>
417
- <AttributePanel
418
- data={displayData}
419
- moduleSpecifier={moduleSpecifier}
420
- expiredAt={run.expiredAt}
421
- isLoading={loading}
422
- error={error ?? undefined}
423
- onStreamClick={onStreamClick}
424
- onRunClick={onRunClick}
425
- onDecrypt={onDecrypt}
426
- isDecrypting={isDecrypting}
427
- resource={resource}
428
- />
429
- </section>
430
-
431
- {resource !== 'run' && rawEvents && (
432
- <section>
433
- <EventsList
434
- events={rawEvents}
435
- onLoadEventData={onLoadEventData}
436
- encryptionKey={encryptionKey}
437
- />
438
- </section>
439
- )}
440
- </div>
421
+ <AttributePanel
422
+ data={displayData}
423
+ moduleSpecifier={moduleSpecifier}
424
+ expiredAt={run.expiredAt}
425
+ isLoading={loading}
426
+ error={error ?? undefined}
427
+ onStreamClick={onStreamClick}
428
+ onRunClick={onRunClick}
429
+ onDecrypt={onDecrypt}
430
+ isDecrypting={isDecrypting}
431
+ resource={resource}
432
+ />
433
+
434
+ {resource !== 'run' && rawEvents && (
435
+ <EventsList
436
+ events={rawEvents}
437
+ onLoadEventData={onLoadEventData}
438
+ encryptionKey={encryptionKey}
439
+ />
440
+ )}
441
441
  </div>
442
442
  </DecryptClickContext.Provider>
443
443
 
@@ -1,16 +1,15 @@
1
1
  'use client';
2
2
 
3
3
  import { EVENT_DATA_REF_FIELDS, type Event } from '@workflow/world';
4
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
- import { isExpiredMarker } from '../../lib/hydration';
4
+ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
+ import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
6
6
  import { ErrorCard } from '../ui/error-card';
7
7
  import {
8
8
  ErrorStackBlock,
9
9
  isStructuredErrorWithStack,
10
10
  } from '../ui/error-stack-block';
11
11
  import { Skeleton } from '../ui/skeleton';
12
- import { localMillisecondTime } from './attribute-panel';
13
- import { CopyableDataBlock } from './copyable-data-block';
12
+ import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
14
13
  import { DetailCard } from './detail-card';
15
14
 
16
15
  /**
@@ -94,7 +93,7 @@ function EventItem({
94
93
 
95
94
  // When the encryption key changes and this event was previously expanded,
96
95
  // re-load the data so it gets decrypted
97
- useEffect(() => {
96
+ useLayoutEffect(() => {
98
97
  if (!encryptionKey || !wasExpandedRef.current) return;
99
98
  loadedDataRef.current = null;
100
99
  setLoadedData(null);
@@ -102,25 +101,27 @@ function EventItem({
102
101
  }, [encryptionKey, loadEventData]);
103
102
 
104
103
  const createdAt = new Date(event.createdAt);
104
+ const createdAtTime = createdAt.toLocaleTimeString(undefined, {
105
+ hour: 'numeric',
106
+ minute: 'numeric',
107
+ second: 'numeric',
108
+ });
105
109
 
106
110
  const displayPayload = isLoading ? loadedData : mergedDisplay;
107
111
 
108
112
  return (
109
113
  <DetailCard
110
- summaryClassName="text-base py-2"
114
+ variant="card"
115
+ summaryClassName="px-3 py-2"
111
116
  summary={
112
- <>
113
- <span
114
- className="font-medium"
115
- style={{ color: 'var(--ds-gray-1000)' }}
116
- >
117
+ <div className="flex w-full items-center justify-between gap-3">
118
+ <span className="text-gray-1000 text-label-12 font-mono">
117
119
  {event.eventType}
118
- </span>{' '}
119
- -{' '}
120
- <span style={{ color: 'var(--ds-gray-700)' }}>
121
- {localMillisecondTime(createdAt.getTime())}
122
120
  </span>
123
- </>
121
+ <span className="shrink-0 text-label-13 text-gray-900">
122
+ {createdAtTime}
123
+ </span>
124
+ </div>
124
125
  }
125
126
  onToggle={
126
127
  canHaveData
@@ -131,47 +132,17 @@ function EventItem({
131
132
  }
132
133
  >
133
134
  {/* Event attributes */}
134
- <div
135
- className="flex flex-col divide-y rounded-md border overflow-hidden"
136
- style={{
137
- borderColor: 'var(--ds-gray-300)',
138
- backgroundColor: 'var(--ds-gray-100)',
139
- }}
140
- >
141
- <div
142
- className="flex min-h-[32px] items-center justify-between gap-4 px-2.5 py-1.5"
143
- style={{ borderColor: 'var(--ds-gray-300)' }}
144
- >
145
- <span
146
- className="text-[13px] font-medium"
147
- style={{ color: 'var(--ds-gray-700)' }}
148
- >
149
- Event ID
150
- </span>
151
- <span
152
- className="max-w-[70%] truncate text-right text-[13px] font-mono"
153
- style={{ color: 'var(--ds-gray-1000)' }}
154
- title={event.eventId}
155
- >
135
+ <div className="flex flex-col bg-background-200 [&:has(+_*)]:border-b [&:has(+_*)]:border-gray-alpha-400">
136
+ <div className="flex items-center justify-between gap-2 py-2 px-3">
137
+ <span className="text-label-12 text-gray-900">Event ID</span>
138
+ <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
156
139
  {event.eventId}
157
140
  </span>
158
141
  </div>
159
142
  {event.correlationId && (
160
- <div
161
- className="flex min-h-[32px] items-center justify-between gap-4 px-2.5 py-1.5"
162
- style={{ borderColor: 'var(--ds-gray-300)' }}
163
- >
164
- <span
165
- className="text-[13px] font-medium"
166
- style={{ color: 'var(--ds-gray-700)' }}
167
- >
168
- Correlation ID
169
- </span>
170
- <span
171
- className="max-w-[70%] truncate text-right text-[13px] font-mono"
172
- style={{ color: 'var(--ds-gray-1000)' }}
173
- title={event.correlationId}
174
- >
143
+ <div className="flex items-center justify-between gap-2 py-2 px-3">
144
+ <span className="text-label-12 text-gray-900">Correlation ID</span>
145
+ <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
175
146
  {event.correlationId}
176
147
  </span>
177
148
  </div>
@@ -180,12 +151,7 @@ function EventItem({
180
151
 
181
152
  {/* Loading state */}
182
153
  {isLoading && (
183
- <div
184
- className="mt-2 rounded-md border p-3"
185
- style={{
186
- borderColor: 'var(--ds-gray-300)',
187
- }}
188
- >
154
+ <div className="p-3">
189
155
  <Skeleton className="h-4 w-[35%]" />
190
156
  <Skeleton className="mt-2 h-4 w-[90%]" />
191
157
  <Skeleton className="mt-2 h-4 w-[75%]" />
@@ -203,7 +169,7 @@ function EventItem({
203
169
 
204
170
  {/* Event data */}
205
171
  {displayPayload != null && (
206
- <div className="mt-2">
172
+ <div className="[&>div]:border-none [&>div]:rounded-none">
207
173
  <EventDataBlock eventType={event.eventType} data={displayPayload} />
208
174
  </div>
209
175
  )}
@@ -258,6 +224,10 @@ function EventDataBlock({
258
224
  );
259
225
  }
260
226
 
227
+ if (hasEncryptedFields({ eventType, eventData: data })) {
228
+ return <EncryptedDataBlock />;
229
+ }
230
+
261
231
  // For error events (step_failed, step_retrying), the eventData has the shape
262
232
  // { error: StructuredError, stack?: string, ... }. Check both the top-level
263
233
  // value and the nested `error` field for a stack trace.
@@ -311,26 +281,28 @@ export function EventsList({
311
281
  [events]
312
282
  );
313
283
 
284
+ const hasEvents = sortedEvents.length > 0 && !error;
285
+
286
+ if (!hasEvents && !isLoading) {
287
+ return <DetailCard summary="Events" disabled />;
288
+ }
289
+
314
290
  return (
315
- <div className="mt-2" style={{ color: 'var(--ds-gray-1000)' }}>
316
- <h3
317
- className="text-label-14 font-medium mt-4 mb-2"
318
- style={{ color: 'var(--ds-gray-1000)' }}
319
- >
320
- Events
321
- </h3>
291
+ <DetailCard summary="Events" contentClassName="mb-0" defaultOpen>
322
292
  {isLoading ? (
323
- <div className="flex flex-col gap-4">
324
- <Skeleton className="h-9 w-full rounded-md" />
325
- <Skeleton className="h-9 w-full rounded-md" />
326
- <Skeleton className="h-9 w-full rounded-md" />
293
+ <div className="flex flex-col -mx-4">
294
+ {[0, 1, 2].map((i) => (
295
+ <div
296
+ key={i}
297
+ className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
298
+ >
299
+ <Skeleton className="h-4 w-32 rounded" />
300
+ <Skeleton className="h-3 w-16 rounded" />
301
+ </div>
302
+ ))}
327
303
  </div>
328
- ) : null}
329
- {!isLoading && !error && sortedEvents.length === 0 && (
330
- <div className="text-sm">No events found</div>
331
- )}
332
- {sortedEvents.length > 0 && !error ? (
333
- <div className="flex flex-col gap-4">
304
+ ) : (
305
+ <div className="flex flex-col -mx-4">
334
306
  {sortedEvents.map((event) => (
335
307
  <EventItem
336
308
  key={event.eventId}
@@ -340,7 +312,7 @@ export function EventsList({
340
312
  />
341
313
  ))}
342
314
  </div>
343
- ) : null}
344
- </div>
315
+ )}
316
+ </DetailCard>
345
317
  );
346
318
  }
@@ -0,0 +1,61 @@
1
+ import * as React from 'react';
2
+ import { cva, type VariantProps } from 'class-variance-authority';
3
+ import { cn } from '../../lib/utils';
4
+
5
+ const buttonVariants = cva(
6
+ [
7
+ 'outline-none m-0 border-0 align-baseline no-underline group/trigger',
8
+ 'relative cursor-pointer select-none transform translate-z-0',
9
+ 'inline-flex max-w-full items-center justify-center gap-x-1 whitespace-nowrap rounded-md font-medium',
10
+ '!text-gray-100',
11
+ 'bg-gray-1000',
12
+ 'transition-[border-color,background,color,transform,box-shadow] duration-150 ease-in-out',
13
+ 'hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_22%))] dark-theme:hover:bg-[var(--themed-hover-bg,_hsl(0,_0%,_80%))]',
14
+ // disabled styles
15
+ 'disabled:cursor-not-allowed aria-disabled:cursor-not-allowed',
16
+ 'disabled:bg-gray-100 disabled:!text-gray-700 disabled:hover:bg-gray-100',
17
+ 'aria-disabled:bg-gray-100 aria-disabled:text-gray-700 aria-disabled:hover:bg-gray-100',
18
+ ],
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default: '',
23
+ secondary:
24
+ 'border border-gray-alpha-400 [--themed-bg:_var(--ds-background-100)] [--themed-fg:_var(--ds-gray-1000)] [--themed-hover-bg:_var(--ds-gray-alpha-200)]',
25
+ ghost:
26
+ '[--themed-bg:_transparent] [--themed-fg:_var(--ds-gray-1000)] [--themed-hover-bg:_var(--ds-gray-alpha-100)]',
27
+ },
28
+ size: {
29
+ default: 'h-10 px-4 text-[14px]',
30
+ sm: 'h-8 px-3 text-[14px]',
31
+ xs: 'h-6 px-1.5 py-0.5 text-button-12',
32
+ icon: 'h-8 w-8',
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: 'default',
37
+ size: 'default',
38
+ },
39
+ }
40
+ );
41
+
42
+ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
43
+ VariantProps<typeof buttonVariants>;
44
+
45
+ export function Button({
46
+ className,
47
+ variant,
48
+ size,
49
+ type = 'button',
50
+ ...props
51
+ }: ButtonProps) {
52
+ return (
53
+ <button
54
+ type={type}
55
+ className={cn(buttonVariants({ variant, size, className }))}
56
+ {...props}
57
+ />
58
+ );
59
+ }
60
+
61
+ export { buttonVariants };