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

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 (74) 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/middle-truncate/truncate.d.ts +1 -1
  11. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.d.ts.map +1 -1
  12. package/dist/components/new-trace-viewer/components/middle-truncate/truncate.js +2 -2
  13. package/dist/components/new-trace-viewer/components/pane-constants.d.ts +14 -0
  14. package/dist/components/new-trace-viewer/components/pane-constants.d.ts.map +1 -0
  15. package/dist/components/new-trace-viewer/components/pane-constants.js +16 -0
  16. package/dist/components/new-trace-viewer/components/split-pane.d.ts +1 -4
  17. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
  18. package/dist/components/new-trace-viewer/components/split-pane.js +18 -16
  19. package/dist/components/new-trace-viewer/components/timeline.d.ts +2 -2
  20. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  21. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.d.ts.map +1 -1
  22. package/dist/components/new-trace-viewer/components/trace-viewer-skeleton.js +5 -2
  23. package/dist/components/new-trace-viewer/components/use-element-width.d.ts +8 -0
  24. package/dist/components/new-trace-viewer/components/use-element-width.d.ts.map +1 -0
  25. package/dist/components/new-trace-viewer/components/use-element-width.js +25 -0
  26. package/dist/components/new-trace-viewer/context.d.ts +2 -0
  27. package/dist/components/new-trace-viewer/context.d.ts.map +1 -1
  28. package/dist/components/new-trace-viewer/context.js +3 -2
  29. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  30. package/dist/components/new-trace-viewer/trace-viewer.js +10 -90
  31. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  32. package/dist/components/sidebar/attribute-panel.js +38 -35
  33. package/dist/components/sidebar/attributes-block.d.ts +18 -7
  34. package/dist/components/sidebar/attributes-block.d.ts.map +1 -1
  35. package/dist/components/sidebar/attributes-block.js +76 -18
  36. package/dist/components/sidebar/events-list.d.ts +1 -1
  37. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  38. package/dist/components/sidebar/events-list.js +9 -10
  39. package/dist/components/trace-viewer/components/map.d.ts +2 -2
  40. package/dist/components/trace-viewer/components/map.d.ts.map +1 -1
  41. package/dist/components/trace-viewer/components/node.d.ts +7 -7
  42. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  43. package/dist/components/ui/collapsible.d.ts +46 -0
  44. package/dist/components/ui/collapsible.d.ts.map +1 -0
  45. package/dist/components/ui/collapsible.js +66 -0
  46. package/dist/components/ui/data-inspector.js +2 -7
  47. package/dist/index.d.ts +2 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -3
  50. package/dist/lib/hydration.d.ts +12 -2
  51. package/dist/lib/hydration.d.ts.map +1 -1
  52. package/dist/lib/hydration.js +41 -18
  53. package/package.json +6 -6
  54. package/src/components/new-trace-viewer/components/detail-panel-width.test.ts +123 -0
  55. package/src/components/new-trace-viewer/components/detail-panel-width.ts +76 -0
  56. package/src/components/new-trace-viewer/components/detail-panel.tsx +272 -0
  57. package/src/components/new-trace-viewer/components/draggable-border.tsx +216 -0
  58. package/src/components/new-trace-viewer/components/middle-truncate/truncate.ts +1 -1
  59. package/src/components/new-trace-viewer/components/pane-constants.ts +19 -0
  60. package/src/components/new-trace-viewer/components/split-pane.tsx +44 -55
  61. package/src/components/new-trace-viewer/components/trace-viewer-skeleton.tsx +4 -1
  62. package/src/components/new-trace-viewer/components/use-element-width.ts +29 -0
  63. package/src/components/new-trace-viewer/context.tsx +4 -1
  64. package/src/components/new-trace-viewer/trace-viewer.tsx +18 -199
  65. package/src/components/sidebar/attribute-panel.tsx +106 -105
  66. package/src/components/sidebar/attributes-block.tsx +170 -50
  67. package/src/components/sidebar/events-list.tsx +107 -92
  68. package/src/components/ui/collapsible.tsx +219 -0
  69. package/src/index.ts +8 -7
  70. package/src/lib/hydration.ts +48 -25
  71. package/dist/components/sidebar/detail-card.d.ts +0 -13
  72. package/dist/components/sidebar/detail-card.d.ts.map +0 -1
  73. package/dist/components/sidebar/detail-card.js +0 -36
  74. package/src/components/sidebar/detail-card.tsx +0 -143
@@ -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
  );
@@ -0,0 +1,219 @@
1
+ 'use client';
2
+
3
+ import { ChevronDown, ChevronRight } from 'lucide-react';
4
+ import {
5
+ createContext,
6
+ type ReactNode,
7
+ type SyntheticEvent,
8
+ useContext,
9
+ useState,
10
+ } from 'react';
11
+ import { cn } from '../../lib/cn';
12
+
13
+ type CollapsibleVariant = 'section' | 'card';
14
+
15
+ type CollapsibleContextValue = {
16
+ variant: CollapsibleVariant;
17
+ disabled: boolean;
18
+ };
19
+
20
+ const CollapsibleContext = createContext<CollapsibleContextValue | null>(null);
21
+
22
+ function useCollapsibleContext(part: string): CollapsibleContextValue {
23
+ const context = useContext(CollapsibleContext);
24
+ if (!context) {
25
+ throw new Error(
26
+ `<Collapsible${part}> must be rendered inside <CollapsibleRoot>`
27
+ );
28
+ }
29
+ return context;
30
+ }
31
+
32
+ type CollapsibleRootProps = {
33
+ children: ReactNode;
34
+ variant?: CollapsibleVariant;
35
+ disabled?: boolean;
36
+ defaultOpen?: boolean;
37
+ onOpenChange?: (open: boolean) => void;
38
+ className?: string;
39
+ };
40
+
41
+ /**
42
+ * Low-level collapsible container. Use this together with `CollapsibleTrigger`
43
+ * and `CollapsibleContent` when you need to override the styling of individual
44
+ * parts. For the common case, prefer the all-in-one `Collapsible`.
45
+ */
46
+ export function CollapsibleRoot({
47
+ children,
48
+ variant = 'section',
49
+ disabled = false,
50
+ defaultOpen = false,
51
+ onOpenChange,
52
+ className,
53
+ }: CollapsibleRootProps) {
54
+ const [open, setOpen] = useState(defaultOpen);
55
+
56
+ const handleToggle = (event: SyntheticEvent<HTMLDetailsElement>) => {
57
+ // React bubbles the `toggle` event even though the native one doesn't, so
58
+ // a nested <details> (e.g. an event card inside the Events section)
59
+ // collapsing would otherwise flip this card. Only react to direct toggles.
60
+ if (event.target !== event.currentTarget) return;
61
+ const next = event.currentTarget.open;
62
+ setOpen(next);
63
+ onOpenChange?.(next);
64
+ };
65
+
66
+ const details = (
67
+ <details
68
+ data-slot="collapsible"
69
+ data-state={open ? 'open' : 'closed'}
70
+ open={open}
71
+ onToggle={disabled ? undefined : handleToggle}
72
+ className={cn(
73
+ variant === 'card'
74
+ ? 'group/card border-gray-alpha-400 last:border-b'
75
+ : 'group',
76
+ className
77
+ )}
78
+ >
79
+ <CollapsibleContext.Provider value={{ variant, disabled }}>
80
+ {children}
81
+ </CollapsibleContext.Provider>
82
+ </details>
83
+ );
84
+
85
+ if (variant === 'card') {
86
+ return details;
87
+ }
88
+
89
+ return (
90
+ <section className="-mx-4 border-t border-gray-alpha-400 px-4 py-2">
91
+ {details}
92
+ </section>
93
+ );
94
+ }
95
+
96
+ type CollapsibleTriggerProps = {
97
+ children: ReactNode;
98
+ className?: string;
99
+ };
100
+
101
+ /** The clickable header of a `CollapsibleRoot`. */
102
+ export function CollapsibleTrigger({
103
+ children,
104
+ className,
105
+ }: CollapsibleTriggerProps) {
106
+ const { variant, disabled } = useCollapsibleContext('Trigger');
107
+
108
+ if (variant === 'card') {
109
+ return (
110
+ <summary
111
+ data-slot="collapsible-trigger"
112
+ className={cn(
113
+ 'flex cursor-pointer list-none items-center gap-1.5 border-t border-gray-alpha-400 bg-background-200 px-3 py-4 hover:bg-gray-100 [&::-webkit-details-marker]:hidden',
114
+ className
115
+ )}
116
+ >
117
+ <ChevronRight
118
+ size={14}
119
+ className="shrink-0 text-gray-700 group-hover/card:text-gray-1000 group-open/card:rotate-90"
120
+ />
121
+ {children}
122
+ </summary>
123
+ );
124
+ }
125
+
126
+ // Shared row metrics keep every header the same height regardless of variant.
127
+ const row =
128
+ 'flex h-9 items-center gap-2 -mx-2 px-2 text-heading-14 font-medium list-none [&::-webkit-details-marker]:hidden';
129
+
130
+ if (disabled) {
131
+ return (
132
+ <summary
133
+ data-slot="collapsible-trigger"
134
+ className={cn(row, 'pointer-events-none text-gray-700', className)}
135
+ >
136
+ <span className="min-w-0 flex-1">{children}</span>
137
+ </summary>
138
+ );
139
+ }
140
+
141
+ return (
142
+ <summary
143
+ data-slot="collapsible-trigger"
144
+ className={cn(
145
+ row,
146
+ 'group/trigger cursor-pointer rounded hover:bg-gray-alpha-100',
147
+ className
148
+ )}
149
+ >
150
+ <span className="relative isolate h-3.5 w-3.5 shrink-0 text-gray-700 group-hover/trigger:text-gray-1000">
151
+ <ChevronRight
152
+ size={14}
153
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-100 group-open:opacity-0"
154
+ />
155
+ <ChevronDown
156
+ size={14}
157
+ className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 group-open:opacity-100"
158
+ />
159
+ </span>
160
+ <span className="min-w-0 flex-1">{children}</span>
161
+ </summary>
162
+ );
163
+ }
164
+
165
+ type CollapsibleContentProps = {
166
+ children: ReactNode;
167
+ className?: string;
168
+ };
169
+
170
+ /** The collapsible body of a `CollapsibleRoot`. */
171
+ export function CollapsibleContent({
172
+ children,
173
+ className,
174
+ }: CollapsibleContentProps) {
175
+ const { variant } = useCollapsibleContext('Content');
176
+ return (
177
+ <div
178
+ data-slot="collapsible-content"
179
+ className={cn(variant === 'section' && 'mt-2 mb-3', className)}
180
+ >
181
+ {children}
182
+ </div>
183
+ );
184
+ }
185
+
186
+ type CollapsibleProps = {
187
+ /** Header content shown in the trigger row. */
188
+ label: ReactNode;
189
+ /** Body content. Omit for a header-only (e.g. disabled or skeleton) row. */
190
+ children?: ReactNode;
191
+ disabled?: boolean;
192
+ defaultOpen?: boolean;
193
+ onOpenChange?: (open: boolean) => void;
194
+ };
195
+
196
+ /**
197
+ * All-in-one collapsible section: a labeled header plus body content. This is
198
+ * the component to reach for in most cases. When you need to override the
199
+ * styling of a specific part, compose `CollapsibleRoot`, `CollapsibleTrigger`,
200
+ * and `CollapsibleContent` directly instead.
201
+ */
202
+ export function Collapsible({
203
+ label,
204
+ children,
205
+ disabled,
206
+ defaultOpen,
207
+ onOpenChange,
208
+ }: CollapsibleProps) {
209
+ return (
210
+ <CollapsibleRoot
211
+ disabled={disabled}
212
+ defaultOpen={defaultOpen}
213
+ onOpenChange={onOpenChange}
214
+ >
215
+ <CollapsibleTrigger>{label}</CollapsibleTrigger>
216
+ {children != null && <CollapsibleContent>{children}</CollapsibleContent>}
217
+ </CollapsibleRoot>
218
+ );
219
+ }
package/src/index.ts CHANGED
@@ -14,13 +14,6 @@ export {
14
14
  waitEventsToWaitEntity,
15
15
  } from './components/workflow-traces/trace-span-construction';
16
16
  export type { EventAnalysis } from './lib/event-analysis';
17
- export {
18
- parseExactWorkflowSearchId,
19
- looksLikeWorkflowIdSearchInput,
20
- type ExactWorkflowSearchId,
21
- type ExactWorkflowSearchIdKind,
22
- type ExactIdSearchResult,
23
- } from './lib/exact-event-search-id';
24
17
  export {
25
18
  analyzeEvents,
26
19
  hasPendingHooksFromEvents,
@@ -40,6 +33,13 @@ export {
40
33
  materializeSteps,
41
34
  materializeWaits,
42
35
  } from './lib/event-materialization';
36
+ export {
37
+ type ExactIdSearchResult,
38
+ type ExactWorkflowSearchId,
39
+ type ExactWorkflowSearchIdKind,
40
+ looksLikeWorkflowIdSearchInput,
41
+ parseExactWorkflowSearchId,
42
+ } from './lib/exact-event-search-id';
43
43
  export type { Revivers, StreamRef } from './lib/hydration';
44
44
  export {
45
45
  CLASS_INSTANCE_REF_TYPE,
@@ -49,6 +49,7 @@ export {
49
49
  getWebRevivers,
50
50
  hasEncryptedFields,
51
51
  hydrateResourceIO,
52
+ hydrateResourceIOAsync,
52
53
  hydrateResourceIOWithKey,
53
54
  isClassInstanceRef,
54
55
  isEncryptedMarker,
@@ -18,6 +18,20 @@ import {
18
18
  } from '@workflow/core/serialization-format';
19
19
  import { EVENT_DATA_REF_FIELDS } from '@workflow/world';
20
20
 
21
+ const V4_EXTRA_EVENT_DATA_REF_FIELDS: Record<string, string[]> = {
22
+ run_started: ['input'],
23
+ step_started: ['input'],
24
+ };
25
+
26
+ export function getEventDataRefFields(eventType: string): string[] {
27
+ return [
28
+ ...new Set([
29
+ ...(EVENT_DATA_REF_FIELDS[eventType] ?? []),
30
+ ...(V4_EXTRA_EVENT_DATA_REF_FIELDS[eventType] ?? []),
31
+ ]),
32
+ ];
33
+ }
34
+
21
35
  // Re-export types and utilities that consumers need
22
36
  export {
23
37
  CLASS_INSTANCE_REF_TYPE,
@@ -446,7 +460,7 @@ function replaceEncryptedAndExpiredWithMarkers<T>(resource: T): T {
446
460
  if (result.eventData && typeof result.eventData === 'object') {
447
461
  const eventType =
448
462
  typeof result.eventType === 'string' ? result.eventType : '';
449
- const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
463
+ const refKeys = getEventDataRefFields(eventType);
450
464
  const ed = { ...(result.eventData as Record<string, unknown>) };
451
465
  for (const key of refKeys) {
452
466
  if (key in ed) {
@@ -465,17 +479,31 @@ function replaceEncryptedAndExpiredWithMarkers<T>(resource: T): T {
465
479
  * When a key is provided, encrypted fields are decrypted before hydration.
466
480
  * This is the async version used when the user clicks "Decrypt" in the web UI.
467
481
  *
468
- * Handles both top-level fields (input, output, metadata) and nested
469
- * eventData subfields per `EVENT_DATA_REF_FIELDS` from `@workflow/world` for that event type.
482
+ * Handles both top-level fields (input, output, metadata) and nested eventData
483
+ * payload fields for that event type.
470
484
  */
471
485
  export async function hydrateResourceIOWithKey<T>(
472
486
  resource: T,
473
487
  key: Uint8Array
488
+ ): Promise<T> {
489
+ return hydrateResourceIOAsync(resource, key);
490
+ }
491
+
492
+ /**
493
+ * Async hydration for web display.
494
+ *
495
+ * This follows the same resource-field mapping as {@link hydrateResourceIO},
496
+ * but can also inflate compressed browser payloads through the registered
497
+ * zstd WASM decoder. When a key is provided, encrypted fields are decrypted
498
+ * first and then inflated/hydrated.
499
+ */
500
+ export async function hydrateResourceIOAsync<T>(
501
+ resource: T,
502
+ key?: Uint8Array
474
503
  ): Promise<T> {
475
504
  const { hydrateDataWithKey } = await import(
476
505
  '@workflow/core/serialization-format'
477
506
  );
478
- const { importKey } = await import('@workflow/core/encryption');
479
507
  // Payloads may be zstd-compressed (the Web DecompressionStream has no zstd);
480
508
  // register the WASM-backed browser decoder before hydrating. Idempotent and
481
509
  // lazy — the WASM is only compiled when a zstd payload is actually decoded.
@@ -483,25 +511,24 @@ export async function hydrateResourceIOWithKey<T>(
483
511
  './zstd-browser-decoder.js'
484
512
  );
485
513
  ensureZstdDecoderRegistered();
486
- const cryptoKey = await importKey(key);
514
+ const cryptoKey = key
515
+ ? await import('@workflow/core/encryption').then(({ importKey }) =>
516
+ importKey(key)
517
+ )
518
+ : undefined;
487
519
  const revivers = getRevivers();
488
520
 
489
- /** Extract original encrypted bytes from a marker or raw Uint8Array, then decrypt + hydrate */
490
- async function decryptField(
491
- value: unknown,
492
- rev: Revivers,
493
- k: Awaited<ReturnType<typeof importKey>>
494
- ): Promise<unknown> {
521
+ async function hydrateField(value: unknown): Promise<unknown> {
495
522
  // Already-hydrated: encrypted marker with stored bytes
496
523
  if (isEncryptedMarker(value)) {
497
524
  const raw = (value as any).__encryptedData as Uint8Array;
498
- return hydrateDataWithKey(raw, rev, k);
525
+ return cryptoKey ? hydrateDataWithKey(raw, revivers, cryptoKey) : value;
499
526
  }
500
- // Raw encrypted Uint8Array (not yet hydrated)
527
+ // Raw Uint8Array: may be encrypted, compressed, or plain devalue.
501
528
  if (value instanceof Uint8Array) {
502
- return hydrateDataWithKey(value, rev, k);
529
+ return hydrateDataWithKey(value, revivers, cryptoKey);
503
530
  }
504
- // Not encrypted — return as-is
531
+ // Not serialized — return as-is.
505
532
  return value;
506
533
  }
507
534
 
@@ -511,29 +538,25 @@ export async function hydrateResourceIOWithKey<T>(
511
538
  // Decrypt + hydrate top-level serialized fields (runs, steps, hooks)
512
539
  for (const field of ['input', 'output', 'metadata', 'error']) {
513
540
  if (field in result) {
514
- result[field] = await decryptField(result[field], revivers, cryptoKey);
541
+ result[field] = await hydrateField(result[field]);
515
542
  }
516
543
  }
517
544
 
518
- // Decrypt + hydrate eventData subfields (events)
545
+ // Hydrate eventData subfields (events)
519
546
  if (result.eventData && typeof result.eventData === 'object') {
520
547
  const eventType =
521
548
  typeof result.eventType === 'string' ? result.eventType : '';
522
- const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
549
+ const refKeys = getEventDataRefFields(eventType);
523
550
  const eventData = { ...(result.eventData as Record<string, unknown>) };
524
551
  for (const field of refKeys) {
525
552
  if (field in eventData) {
526
- eventData[field] = await decryptField(
527
- eventData[field],
528
- revivers,
529
- cryptoKey
530
- );
553
+ eventData[field] = await hydrateField(eventData[field]);
531
554
  }
532
555
  }
533
556
  result.eventData = eventData;
534
557
  }
535
558
 
536
- return result as T;
559
+ return replaceEncryptedAndExpiredWithMarkers(result as T);
537
560
  }
538
561
 
539
562
  /**
@@ -552,7 +575,7 @@ export function hasEncryptedFields(resource: unknown): boolean {
552
575
 
553
576
  if (r.eventData && typeof r.eventData === 'object') {
554
577
  const eventType = typeof r.eventType === 'string' ? r.eventType : '';
555
- const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
578
+ const refKeys = getEventDataRefFields(eventType);
556
579
  const ed = r.eventData as Record<string, unknown>;
557
580
  for (const key of refKeys) {
558
581
  if (key in ed && isEncryptedMarker(ed[key])) return true;
@@ -1,13 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- export declare function DetailCard({ summary, children, onToggle, disabled, defaultOpen, variant, trailing, summaryClassName, contentClassName, }: {
3
- summary: ReactNode;
4
- children?: ReactNode;
5
- onToggle?: (open: boolean) => void;
6
- disabled?: boolean;
7
- defaultOpen?: boolean;
8
- variant?: 'section' | 'card';
9
- trailing?: ReactNode;
10
- summaryClassName?: string;
11
- contentClassName?: string;
12
- }): import("react/jsx-runtime").JSX.Element;
13
- //# sourceMappingURL=detail-card.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"detail-card.d.ts","sourceRoot":"","sources":["../../../src/components/sidebar/detail-card.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAC;AAGjD,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,QAAgB,EAChB,WAAmB,EACnB,OAAmB,EACnB,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,GACjB,EAAE;IACD,OAAO,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,2CAsHA"}