@workflow/web-shared 5.0.0-beta.26 → 5.0.0-beta.28

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 (94) hide show
  1. package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts +34 -0
  2. package/dist/components/new-trace-viewer/components/detail-panel-width.d.ts.map +1 -0
  3. package/dist/components/new-trace-viewer/components/detail-panel-width.js +67 -0
  4. package/dist/components/new-trace-viewer/components/detail-panel.d.ts +14 -0
  5. package/dist/components/new-trace-viewer/components/detail-panel.d.ts.map +1 -0
  6. package/dist/components/new-trace-viewer/components/detail-panel.js +122 -0
  7. package/dist/components/new-trace-viewer/components/draggable-border.d.ts +31 -0
  8. package/dist/components/new-trace-viewer/components/draggable-border.d.ts.map +1 -0
  9. package/dist/components/new-trace-viewer/components/draggable-border.js +140 -0
  10. package/dist/components/new-trace-viewer/components/event-list.d.ts +1 -1
  11. package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -1
  12. package/dist/components/new-trace-viewer/components/event-list.js +3 -5
  13. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts +1 -1
  14. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -1
  15. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +2 -2
  16. package/dist/components/new-trace-viewer/components/minimap.d.ts +14 -0
  17. package/dist/components/new-trace-viewer/components/minimap.d.ts.map +1 -0
  18. package/dist/components/new-trace-viewer/components/minimap.js +337 -0
  19. package/dist/components/new-trace-viewer/components/pane-constants.d.ts +14 -0
  20. package/dist/components/new-trace-viewer/components/pane-constants.d.ts.map +1 -0
  21. package/dist/components/new-trace-viewer/components/pane-constants.js +16 -0
  22. package/dist/components/new-trace-viewer/components/split-pane.d.ts +1 -4
  23. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
  24. package/dist/components/new-trace-viewer/components/split-pane.js +18 -16
  25. package/dist/components/new-trace-viewer/components/timeline.d.ts +2 -2
  26. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  27. package/dist/components/new-trace-viewer/components/timeline.js +5 -7
  28. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -1
  29. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +10 -3
  30. package/dist/components/new-trace-viewer/components/use-element-width.d.ts +8 -0
  31. package/dist/components/new-trace-viewer/components/use-element-width.d.ts.map +1 -0
  32. package/dist/components/new-trace-viewer/components/use-element-width.js +25 -0
  33. package/dist/components/new-trace-viewer/context.d.ts +2 -0
  34. package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
  35. package/dist/components/new-trace-viewer/context.js +3 -2
  36. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  37. package/dist/components/new-trace-viewer/trace-viewer.js +53 -181
  38. package/dist/components/new-trace-viewer/utils.d.ts +21 -0
  39. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  40. package/dist/components/new-trace-viewer/utils.js +44 -3
  41. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  42. package/dist/components/sidebar/attribute-panel.js +38 -35
  43. package/dist/components/sidebar/attributes-block.d.ts +18 -7
  44. package/dist/components/sidebar/attributes-block.d.ts.map +1 -1
  45. package/dist/components/sidebar/attributes-block.js +76 -18
  46. package/dist/components/sidebar/events-list.d.ts +1 -1
  47. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  48. package/dist/components/sidebar/events-list.js +9 -10
  49. package/dist/components/trace-viewer/components/map.d.ts +2 -2
  50. package/dist/components/trace-viewer/components/map.d.ts.map +1 -1
  51. package/dist/components/trace-viewer/components/node.d.ts +7 -7
  52. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  53. package/dist/components/ui/collapsible.d.ts +46 -0
  54. package/dist/components/ui/collapsible.d.ts.map +1 -0
  55. package/dist/components/ui/collapsible.js +66 -0
  56. package/dist/components/ui/data-inspector.js +2 -7
  57. package/dist/index.d.ts +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +3 -3
  60. package/dist/lib/hydration.d.ts +12 -2
  61. package/dist/lib/hydration.d.ts.map +1 -1
  62. package/dist/lib/hydration.js +41 -18
  63. package/dist/lib/utils.d.ts +8 -5
  64. package/dist/lib/utils.d.ts.map +1 -1
  65. package/dist/lib/utils.js +13 -16
  66. package/package.json +6 -6
  67. package/src/components/new-trace-viewer/components/detail-panel-width.test.ts +123 -0
  68. package/src/components/new-trace-viewer/components/detail-panel-width.ts +76 -0
  69. package/src/components/new-trace-viewer/components/detail-panel.tsx +272 -0
  70. package/src/components/new-trace-viewer/components/draggable-border.tsx +216 -0
  71. package/src/components/new-trace-viewer/components/event-list.tsx +3 -5
  72. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +1 -1
  73. package/src/components/new-trace-viewer/components/minimap.tsx +559 -0
  74. package/src/components/new-trace-viewer/components/pane-constants.ts +19 -0
  75. package/src/components/new-trace-viewer/components/split-pane.tsx +44 -55
  76. package/src/components/new-trace-viewer/components/timeline.tsx +3 -5
  77. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +21 -1
  78. package/src/components/new-trace-viewer/components/use-element-width.ts +29 -0
  79. package/src/components/new-trace-viewer/context.tsx +4 -1
  80. package/src/components/new-trace-viewer/trace-viewer.tsx +93 -312
  81. package/src/components/new-trace-viewer/utils.test.ts +65 -0
  82. package/src/components/new-trace-viewer/utils.ts +76 -2
  83. package/src/components/sidebar/attribute-panel.tsx +106 -105
  84. package/src/components/sidebar/attributes-block.tsx +170 -50
  85. package/src/components/sidebar/events-list.tsx +107 -92
  86. package/src/components/ui/collapsible.tsx +219 -0
  87. package/src/index.ts +8 -7
  88. package/src/lib/hydration.ts +48 -25
  89. package/src/lib/utils.test.ts +33 -0
  90. package/src/lib/utils.ts +12 -16
  91. package/dist/components/sidebar/detail-card.d.ts +0 -13
  92. package/dist/components/sidebar/detail-card.d.ts.map +0 -1
  93. package/dist/components/sidebar/detail-card.js +0 -36
  94. package/src/components/sidebar/detail-card.tsx +0 -143
@@ -4,60 +4,94 @@ import {
4
4
  type AttributeChange,
5
5
  RESERVED_ATTRIBUTE_KEY_PREFIX,
6
6
  } from '@workflow/world';
7
+ import { cva, type VariantProps } from 'class-variance-authority';
8
+ import type { ReactNode } from 'react';
9
+ import { cn } from '../../lib/cn';
10
+ import { CopyButton } from '../new-trace-viewer/components/copy-button';
11
+ import { MiddleTruncate } from '../new-trace-viewer/components/middle-truncate/middle-truncate';
12
+ import {
13
+ CollapsibleContent,
14
+ CollapsibleRoot,
15
+ CollapsibleTrigger,
16
+ } from '../ui/collapsible';
7
17
  import { CopyableDataBlock } from './copyable-data-block';
8
- import { DetailCard } from './detail-card';
9
-
10
- /**
11
- * Check whether an attribute key is in the reserved (`$`-prefixed)
12
- * namespace used by framework/library code.
13
- */
14
- export const isReservedAttributeKey = (key: string): boolean =>
15
- key.startsWith(RESERVED_ATTRIBUTE_KEY_PREFIX);
16
18
 
17
- function ReservedBadge() {
18
- return (
19
- <span
20
- className="shrink-0 rounded border px-1 py-px text-[10px] font-medium leading-3"
21
- style={{
22
- borderColor: 'var(--ds-gray-alpha-400)',
23
- backgroundColor: 'var(--ds-gray-100)',
24
- color: 'var(--ds-gray-700)',
25
- }}
26
- title={`Keys prefixed with "${RESERVED_ATTRIBUTE_KEY_PREFIX}" are reserved for framework and library code`}
27
- >
28
- Reserved
29
- </span>
30
- );
19
+ function isReservedAttributeKey(key: string): boolean {
20
+ return key.startsWith(RESERVED_ATTRIBUTE_KEY_PREFIX);
31
21
  }
32
22
 
33
- function AttributeRow({
34
- attributeKey,
23
+ const rowValueVariants = cva(
24
+ 'max-w-[60%] truncate text-right text-copy-13 text-gray-1000',
25
+ {
26
+ variants: {
27
+ variant: {
28
+ default: '',
29
+ mono: 'font-mono',
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: 'default',
34
+ },
35
+ }
36
+ );
37
+
38
+ const rowCopyValueVariants = cva(
39
+ 'flex min-w-0 max-w-[60%] items-center justify-end gap-1 text-copy-13 text-gray-1000',
40
+ {
41
+ variants: {
42
+ variant: {
43
+ default: '',
44
+ mono: 'font-mono',
45
+ },
46
+ },
47
+ defaultVariants: {
48
+ variant: 'default',
49
+ },
50
+ }
51
+ );
52
+
53
+ type DetailKeyValueRowProps = {
54
+ label: string;
55
+ value?: ReactNode;
56
+ copyText?: string;
57
+ removed?: boolean;
58
+ };
59
+
60
+ function DetailKeyValueRowBase({
61
+ label,
35
62
  value,
63
+ copyText,
36
64
  removed = false,
37
- }: {
38
- attributeKey: string;
39
- value?: string;
40
- removed?: boolean;
41
- }) {
42
- const reserved = isReservedAttributeKey(attributeKey);
65
+ variant,
66
+ }: DetailKeyValueRowProps & VariantProps<typeof rowValueVariants>) {
67
+ const stringValue = typeof value === 'string' ? value : undefined;
68
+
43
69
  return (
44
- <div className="flex items-center justify-between gap-3 py-0.5">
45
- <span
46
- className="flex min-w-0 items-center gap-1.5 text-label-13 text-gray-900"
47
- style={reserved ? { color: 'var(--ds-gray-700)' } : undefined}
48
- >
49
- <span className="truncate">{attributeKey}</span>
50
- {reserved && <ReservedBadge />}
70
+ <div className="px-1.5 hover:bg-gray-100 flex justify-between gap-3 -mx-1.5 py-0.5 rounded items-center">
71
+ <span className="flex min-w-0 items-center gap-1.5 truncate text-label-13 text-gray-900">
72
+ {label}
51
73
  </span>
52
74
  {removed ? (
53
75
  <span className="shrink-0 text-copy-13 italic text-gray-700">
54
76
  removed
55
77
  </span>
78
+ ) : copyText ? (
79
+ <div className={cn(rowCopyValueVariants({ variant }))} title={copyText}>
80
+ <MiddleTruncate
81
+ value={copyText}
82
+ className={cn(
83
+ rowValueVariants({ variant, className: 'text-right' })
84
+ )}
85
+ style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}
86
+ />
87
+ <CopyButton
88
+ copyText={copyText}
89
+ ariaLabel={`Copy ${label}`}
90
+ className="shrink-0 -mr-1"
91
+ />
92
+ </div>
56
93
  ) : (
57
- <span
58
- className="max-w-[60%] truncate text-copy-13 text-gray-1000"
59
- title={value}
60
- >
94
+ <span className={cn(rowValueVariants({ variant }))} title={stringValue}>
61
95
  {value}
62
96
  </span>
63
97
  )}
@@ -65,6 +99,28 @@ function AttributeRow({
65
99
  );
66
100
  }
67
101
 
102
+ export function DetailKeyValueRow(props: DetailKeyValueRowProps) {
103
+ return <DetailKeyValueRowBase {...props} />;
104
+ }
105
+
106
+ export function DetailMonoKeyValueRow(props: DetailKeyValueRowProps) {
107
+ return <DetailKeyValueRowBase {...props} variant="mono" />;
108
+ }
109
+
110
+ function AttributeRow({
111
+ attributeKey,
112
+ value,
113
+ removed = false,
114
+ }: {
115
+ attributeKey: string;
116
+ value?: string;
117
+ removed?: boolean;
118
+ }) {
119
+ return (
120
+ <DetailKeyValueRow label={attributeKey} value={value} removed={removed} />
121
+ );
122
+ }
123
+
68
124
  /**
69
125
  * Sort attribute keys for display: user keys first (alphabetical), then
70
126
  * reserved `$`-prefixed keys (alphabetical), so framework metadata doesn't
@@ -81,8 +137,7 @@ export function sortAttributeKeys(keys: string[]): string[] {
81
137
 
82
138
  /**
83
139
  * Collapsible section showing a run's materialized attributes as key-value
84
- * rows. Reserved (`$`-prefixed) keys are visually de-emphasized with a
85
- * badge and sorted after user keys.
140
+ * rows. Reserved (`$`-prefixed) keys are sorted after user keys.
86
141
  */
87
142
  export function RunAttributesCard({
88
143
  attributes,
@@ -93,13 +148,78 @@ export function RunAttributesCard({
93
148
  if (keys.length === 0) return null;
94
149
 
95
150
  return (
96
- <DetailCard summary="Attributes" defaultOpen contentClassName="mb-4">
97
- <div className="flex flex-col">
98
- {keys.map((key) => (
99
- <AttributeRow attributeKey={key} key={key} value={attributes[key]} />
100
- ))}
101
- </div>
102
- </DetailCard>
151
+ <CollapsibleRoot defaultOpen>
152
+ <CollapsibleTrigger>Attributes</CollapsibleTrigger>
153
+ <CollapsibleContent className="mb-4">
154
+ <div className="flex flex-col">
155
+ {keys.map((key) => (
156
+ <AttributeRow
157
+ attributeKey={key}
158
+ key={key}
159
+ value={attributes[key]}
160
+ />
161
+ ))}
162
+ </div>
163
+ </CollapsibleContent>
164
+ </CollapsibleRoot>
165
+ );
166
+ }
167
+
168
+ function formatMetadataValue(value: unknown): string {
169
+ if (value == null) {
170
+ return String(value);
171
+ }
172
+ if (typeof value === 'string') {
173
+ return value;
174
+ }
175
+ if (typeof value === 'number' || typeof value === 'boolean') {
176
+ return String(value);
177
+ }
178
+ try {
179
+ return JSON.stringify(value);
180
+ } catch {
181
+ return String(value);
182
+ }
183
+ }
184
+
185
+ function isMetadataRecord(value: unknown): value is Record<string, unknown> {
186
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
187
+ }
188
+
189
+ /**
190
+ * Collapsible section showing hook (or other resource) metadata as key-value
191
+ * rows, matching the Attributes section styling.
192
+ */
193
+ export function RunMetadataCard({ metadata }: { metadata: unknown }) {
194
+ if (!isMetadataRecord(metadata)) {
195
+ return (
196
+ <CollapsibleRoot defaultOpen>
197
+ <CollapsibleTrigger>Metadata</CollapsibleTrigger>
198
+ <CollapsibleContent className="mb-4">
199
+ <CopyableDataBlock data={metadata} />
200
+ </CollapsibleContent>
201
+ </CollapsibleRoot>
202
+ );
203
+ }
204
+
205
+ const keys = Object.keys(metadata).sort((a, b) => a.localeCompare(b));
206
+ if (keys.length === 0) return null;
207
+
208
+ return (
209
+ <CollapsibleRoot defaultOpen>
210
+ <CollapsibleTrigger>Metadata</CollapsibleTrigger>
211
+ <CollapsibleContent className="mb-4">
212
+ <div className="flex flex-col">
213
+ {keys.map((key) => (
214
+ <AttributeRow
215
+ attributeKey={key}
216
+ key={key}
217
+ value={formatMetadataValue(metadata[key])}
218
+ />
219
+ ))}
220
+ </div>
221
+ </CollapsibleContent>
222
+ </CollapsibleRoot>
103
223
  );
104
224
  }
105
225
 
@@ -1,8 +1,18 @@
1
1
  'use client';
2
2
 
3
- import { EVENT_DATA_REF_FIELDS, type Event } from '@workflow/world';
3
+ import type { Event } from '@workflow/world';
4
4
  import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
- import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
5
+ import {
6
+ getEventDataRefFields,
7
+ hasEncryptedFields,
8
+ isExpiredMarker,
9
+ } from '../../lib/hydration';
10
+ import {
11
+ Collapsible,
12
+ CollapsibleContent,
13
+ CollapsibleRoot,
14
+ CollapsibleTrigger,
15
+ } from '../ui/collapsible';
6
16
  import { RunClickContext, StreamClickContext } from '../ui/data-inspector';
7
17
  import { ErrorCard } from '../ui/error-card';
8
18
  import { ErrorStackBlock, isStructuredError } from '../ui/error-stack-block';
@@ -10,7 +20,6 @@ import { Skeleton } from '../ui/skeleton';
10
20
  import { TimestampTooltip } from '../ui/timestamp-tooltip';
11
21
  import { AttrSetEventBlock } from './attributes-block';
12
22
  import { CopyableDataBlock, EncryptedDataBlock } from './copyable-data-block';
13
- import { DetailCard } from './detail-card';
14
23
 
15
24
  /**
16
25
  * Event types whose eventData contains an error field with a StructuredError.
@@ -143,20 +152,9 @@ function EventItem({
143
152
  const displayPayload = isLoading ? loadedData : mergedDisplay;
144
153
 
145
154
  return (
146
- <DetailCard
155
+ <CollapsibleRoot
147
156
  variant="card"
148
- summaryClassName="px-3 py-2"
149
- summary={
150
- <div className="flex w-full items-center justify-between gap-3">
151
- <span className="text-gray-1000 text-label-12 font-mono">
152
- {event.eventType}
153
- </span>
154
- <span className="shrink-0 text-label-13 text-gray-900">
155
- {displayedCreatedAtTime}
156
- </span>
157
- </div>
158
- }
159
- onToggle={
157
+ onOpenChange={
160
158
  canHaveData
161
159
  ? (open) => {
162
160
  if (open) handleExpand();
@@ -164,65 +162,79 @@ function EventItem({
164
162
  : undefined
165
163
  }
166
164
  >
167
- {/* Event attributes */}
168
- <div className="flex flex-col bg-background-200 [&:has(+_*)]:border-b [&:has(+_*)]:border-gray-alpha-400">
169
- {showSeparateEventOccurrenceTimestamps && occurredAt && (
170
- <div className="flex items-center justify-between gap-2 py-2 px-3">
171
- <span className="text-label-12 text-gray-900">Occurred</span>
172
- <TimestampTooltip date={occurredAt}>
173
- <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
174
- {formatEventTimestamp(occurredAt)}
175
- </span>
176
- </TimestampTooltip>
177
- </div>
178
- )}
179
- <div className="flex items-center justify-between gap-2 py-2 px-3">
180
- <span className="text-label-12 text-gray-900">Event ID</span>
181
- <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
182
- {event.eventId}
165
+ <CollapsibleTrigger className="px-3 py-2">
166
+ <div className="flex w-full items-center justify-between gap-3">
167
+ <span className="text-gray-1000 text-label-12 font-mono">
168
+ {event.eventType}
169
+ </span>
170
+ <span className="shrink-0 text-label-13 text-gray-900">
171
+ {displayedCreatedAtTime}
183
172
  </span>
184
173
  </div>
185
- {event.correlationId && (
174
+ </CollapsibleTrigger>
175
+ <CollapsibleContent>
176
+ {/* Event attributes */}
177
+ <div className="flex flex-col bg-background-200 [&:has(+_*)]:border-b [&:has(+_*)]:border-gray-alpha-400">
178
+ {showSeparateEventOccurrenceTimestamps && occurredAt && (
179
+ <div className="flex items-center justify-between gap-2 py-2 px-3">
180
+ <span className="text-label-12 text-gray-900">Occurred</span>
181
+ <TimestampTooltip date={occurredAt}>
182
+ <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
183
+ {formatEventTimestamp(occurredAt)}
184
+ </span>
185
+ </TimestampTooltip>
186
+ </div>
187
+ )}
186
188
  <div className="flex items-center justify-between gap-2 py-2 px-3">
187
- <span className="text-label-12 text-gray-900">Correlation ID</span>
189
+ <span className="text-label-12 text-gray-900">Event ID</span>
188
190
  <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
189
- {event.correlationId}
191
+ {event.eventId}
190
192
  </span>
191
193
  </div>
192
- )}
193
- </div>
194
-
195
- {/* Loading state */}
196
- {isLoading && (
197
- <div className="p-3">
198
- <Skeleton className="h-4 w-[35%]" />
199
- <Skeleton className="mt-2 h-4 w-[90%]" />
200
- <Skeleton className="mt-2 h-4 w-[75%]" />
194
+ {event.correlationId && (
195
+ <div className="flex items-center justify-between gap-2 py-2 px-3">
196
+ <span className="text-label-12 text-gray-900">
197
+ Correlation ID
198
+ </span>
199
+ <span className="max-w-[70%] truncate text-right text-label-12 font-mono">
200
+ {event.correlationId}
201
+ </span>
202
+ </div>
203
+ )}
201
204
  </div>
202
- )}
203
205
 
204
- {/* Error state */}
205
- {loadError && (
206
- <ErrorCard
207
- title="Failed to load event data"
208
- details={loadError}
209
- className="mt-2"
210
- />
211
- )}
206
+ {/* Loading state */}
207
+ {isLoading && (
208
+ <div className="p-3">
209
+ <Skeleton className="h-4 w-[35%]" />
210
+ <Skeleton className="mt-2 h-4 w-[90%]" />
211
+ <Skeleton className="mt-2 h-4 w-[75%]" />
212
+ </div>
213
+ )}
212
214
 
213
- {/* Event data */}
214
- {displayPayload != null && (
215
- <div className="[&>div]:border-none [&>div]:rounded-none">
216
- <EventDataBlock eventType={event.eventType} data={displayPayload} />
217
- </div>
218
- )}
219
- </DetailCard>
215
+ {/* Error state */}
216
+ {loadError && (
217
+ <ErrorCard
218
+ title="Failed to load event data"
219
+ details={loadError}
220
+ className="mt-2"
221
+ />
222
+ )}
223
+
224
+ {/* Event data */}
225
+ {displayPayload != null && (
226
+ <div className="[&>div]:border-none [&>div]:rounded-none">
227
+ <EventDataBlock eventType={event.eventType} data={displayPayload} />
228
+ </div>
229
+ )}
230
+ </CollapsibleContent>
231
+ </CollapsibleRoot>
220
232
  );
221
233
  }
222
234
 
223
235
  /**
224
236
  * Check if an eventData object has only expired marker values in ref/payload
225
- * fields for this event type (see {@link EVENT_DATA_REF_FIELDS}). Other keys
237
+ * fields for this event type (see {@link getEventDataRefFields}). Other keys
226
238
  * (e.g. `resumeAt`, `stepName`) are ignored.
227
239
  */
228
240
  function hasOnlyExpiredFields(data: unknown, eventType: string): boolean {
@@ -230,7 +242,7 @@ function hasOnlyExpiredFields(data: unknown, eventType: string): boolean {
230
242
  return false;
231
243
  }
232
244
  const record = data as Record<string, unknown>;
233
- const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
245
+ const refKeys = getEventDataRefFields(eventType);
234
246
  const presentKeys = refKeys.filter((k) => k in record);
235
247
  return (
236
248
  presentKeys.length > 0 &&
@@ -341,41 +353,44 @@ export function EventsList({
341
353
  const hasEvents = sortedEvents.length > 0 && !error;
342
354
 
343
355
  if (!hasEvents && !isLoading) {
344
- return <DetailCard summary="Events" disabled />;
356
+ return <Collapsible label="Events" disabled />;
345
357
  }
346
358
 
347
359
  return (
348
360
  <RunClickContext.Provider value={onRunClick}>
349
361
  <StreamClickContext.Provider value={onStreamClick}>
350
- <DetailCard summary="Events" contentClassName="mb-0" defaultOpen>
351
- {isLoading ? (
352
- <div className="flex flex-col -mx-4">
353
- {[0, 1, 2].map((i) => (
354
- <div
355
- key={i}
356
- className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
357
- >
358
- <Skeleton className="h-4 w-32 rounded" />
359
- <Skeleton className="h-3 w-16 rounded" />
360
- </div>
361
- ))}
362
- </div>
363
- ) : (
364
- <div className="flex flex-col -mx-4">
365
- {sortedEvents.map((event) => (
366
- <EventItem
367
- key={event.eventId}
368
- event={event}
369
- onLoadEventData={onLoadEventData}
370
- encryptionKey={encryptionKey}
371
- showSeparateEventOccurrenceTimestamps={
372
- showSeparateEventOccurrenceTimestamps
373
- }
374
- />
375
- ))}
376
- </div>
377
- )}
378
- </DetailCard>
362
+ <CollapsibleRoot defaultOpen>
363
+ <CollapsibleTrigger>Events</CollapsibleTrigger>
364
+ <CollapsibleContent className="mb-0">
365
+ {isLoading ? (
366
+ <div className="flex flex-col -mx-4">
367
+ {[0, 1, 2].map((i) => (
368
+ <div
369
+ key={i}
370
+ className="flex items-center justify-between gap-3 bg-background-200 px-4 py-2"
371
+ >
372
+ <Skeleton className="h-4 w-32 rounded" />
373
+ <Skeleton className="h-3 w-16 rounded" />
374
+ </div>
375
+ ))}
376
+ </div>
377
+ ) : (
378
+ <div className="flex flex-col -mx-4">
379
+ {sortedEvents.map((event) => (
380
+ <EventItem
381
+ key={event.eventId}
382
+ event={event}
383
+ onLoadEventData={onLoadEventData}
384
+ encryptionKey={encryptionKey}
385
+ showSeparateEventOccurrenceTimestamps={
386
+ showSeparateEventOccurrenceTimestamps
387
+ }
388
+ />
389
+ ))}
390
+ </div>
391
+ )}
392
+ </CollapsibleContent>
393
+ </CollapsibleRoot>
379
394
  </StreamClickContext.Provider>
380
395
  </RunClickContext.Provider>
381
396
  );