@workflow/web-shared 4.1.1 → 4.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/components/event-list-view.d.ts +3 -1
  2. package/dist/components/event-list-view.d.ts.map +1 -1
  3. package/dist/components/event-list-view.js +53 -53
  4. package/dist/components/event-list-view.js.map +1 -1
  5. package/dist/components/index.d.ts +1 -1
  6. package/dist/components/index.d.ts.map +1 -1
  7. package/dist/components/index.js +1 -1
  8. package/dist/components/index.js.map +1 -1
  9. package/dist/components/sidebar/attribute-panel.d.ts +5 -1
  10. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  11. package/dist/components/sidebar/attribute-panel.js +35 -25
  12. package/dist/components/sidebar/attribute-panel.js.map +1 -1
  13. package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
  14. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  15. package/dist/components/sidebar/entity-detail-panel.js +17 -15
  16. package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
  17. package/dist/components/ui/data-inspector.d.ts +14 -1
  18. package/dist/components/ui/data-inspector.d.ts.map +1 -1
  19. package/dist/components/ui/data-inspector.js +32 -11
  20. package/dist/components/ui/data-inspector.js.map +1 -1
  21. package/dist/components/workflow-trace-view.d.ts +3 -1
  22. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  23. package/dist/components/workflow-trace-view.js +2 -2
  24. package/dist/components/workflow-trace-view.js.map +1 -1
  25. package/dist/index.d.ts +1 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/lib/hydration.d.ts +7 -0
  30. package/dist/lib/hydration.d.ts.map +1 -1
  31. package/dist/lib/hydration.js +25 -0
  32. package/dist/lib/hydration.js.map +1 -1
  33. package/package.json +2 -2
  34. package/src/components/event-list-view.tsx +181 -173
  35. package/src/components/index.ts +6 -1
  36. package/src/components/sidebar/attribute-panel.tsx +145 -106
  37. package/src/components/sidebar/entity-detail-panel.tsx +106 -95
  38. package/src/components/ui/data-inspector.tsx +84 -19
  39. package/src/components/workflow-trace-view.tsx +4 -0
  40. package/src/index.ts +1 -0
  41. package/src/lib/hydration.ts +26 -0
@@ -10,6 +10,7 @@
10
10
 
11
11
  import { Lock } from 'lucide-react';
12
12
  import { createContext, useContext, useEffect, useRef, useState } from 'react';
13
+ import { Spinner } from './spinner';
13
14
  import {
14
15
  ObjectInspector,
15
16
  ObjectLabel,
@@ -75,6 +76,68 @@ export const StreamClickContext = createContext<
75
76
  ((streamId: string) => void) | undefined
76
77
  >(undefined);
77
78
 
79
+ /**
80
+ * Context for passing a decrypt handler down to DataInspector instances.
81
+ * When provided, encrypted markers become clickable buttons that trigger decryption.
82
+ */
83
+ export type DecryptClickContextValue = {
84
+ onDecrypt: () => void;
85
+ isDecrypting: boolean;
86
+ };
87
+
88
+ export const DecryptClickContext = createContext<
89
+ DecryptClickContextValue | undefined
90
+ >(undefined);
91
+
92
+ function EncryptedInlineLabel() {
93
+ const ctx = useContext(DecryptClickContext);
94
+ if (ctx) {
95
+ return (
96
+ <button
97
+ type="button"
98
+ className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] cursor-pointer"
99
+ style={{
100
+ backgroundColor: 'var(--ds-gray-100)',
101
+ color: 'var(--ds-gray-700)',
102
+ border: '1px solid var(--ds-gray-400)',
103
+ fontStyle: 'italic',
104
+ opacity: ctx.isDecrypting ? 0.6 : 1,
105
+ }}
106
+ disabled={ctx.isDecrypting}
107
+ onClick={(e) => {
108
+ e.stopPropagation();
109
+ ctx.onDecrypt();
110
+ }}
111
+ title="Click to decrypt"
112
+ >
113
+ {ctx.isDecrypting ? (
114
+ <Spinner size={12} />
115
+ ) : (
116
+ <Lock
117
+ className="h-3 w-3"
118
+ style={{ display: 'inline', flexShrink: 0 }}
119
+ />
120
+ )}
121
+ <span>{ctx.isDecrypting ? 'Decrypting…' : 'Decrypt'}</span>
122
+ </button>
123
+ );
124
+ }
125
+ return (
126
+ <span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
127
+ <Lock
128
+ className="h-3 w-3"
129
+ style={{
130
+ display: 'inline',
131
+ verticalAlign: 'middle',
132
+ marginRight: '3px',
133
+ marginTop: '-1px',
134
+ }}
135
+ />
136
+ Encrypted
137
+ </span>
138
+ );
139
+ }
140
+
78
141
  function StreamRefInline({ streamRef }: { streamRef: StreamRef }) {
79
142
  const onStreamClick = useContext(StreamClickContext);
80
143
  return (
@@ -132,26 +195,13 @@ function NodeRenderer({
132
195
  }) {
133
196
  const extendedTheme = useContext(ExtendedThemeContext);
134
197
 
135
- // Encrypted marker → flat label with Lock icon, non-expandable
198
+ // Encrypted marker → flat label with Lock icon, clickable when onDecrypt is available
136
199
  if (
137
200
  data !== null &&
138
201
  typeof data === 'object' &&
139
202
  data.constructor?.name === ENCRYPTED_DISPLAY_NAME
140
203
  ) {
141
- const label = (
142
- <span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
143
- <Lock
144
- className="h-3 w-3"
145
- style={{
146
- display: 'inline',
147
- verticalAlign: 'middle',
148
- marginRight: '3px',
149
- marginTop: '-1px',
150
- }}
151
- />
152
- Encrypted
153
- </span>
154
- );
204
+ const label = <EncryptedInlineLabel />;
155
205
  if (depth === 0) {
156
206
  return label;
157
207
  }
@@ -234,6 +284,10 @@ export interface DataInspectorProps {
234
284
  name?: string;
235
285
  /** Callback when a stream reference is clicked */
236
286
  onStreamClick?: (streamId: string) => void;
287
+ /** Callback when an encrypted marker is clicked (triggers decryption) */
288
+ onDecrypt?: () => void;
289
+ /** Whether decryption is currently in progress */
290
+ isDecrypting?: boolean;
237
291
  }
238
292
 
239
293
  export function DataInspector({
@@ -241,6 +295,8 @@ export function DataInspector({
241
295
  expandLevel = 2,
242
296
  name,
243
297
  onStreamClick,
298
+ onDecrypt,
299
+ isDecrypting = false,
244
300
  }: DataInspectorProps) {
245
301
  const stableData = useStableInspectorData(data);
246
302
  const [initialExpandLevel, setInitialExpandLevel] = useState(expandLevel);
@@ -269,16 +325,25 @@ export function DataInspector({
269
325
  </ExtendedThemeContext.Provider>
270
326
  );
271
327
 
272
- // Wrap in StreamClickContext if a handler is provided
328
+ let wrapped = content;
329
+
273
330
  if (onStreamClick) {
274
- return (
331
+ wrapped = (
275
332
  <StreamClickContext.Provider value={onStreamClick}>
276
- {content}
333
+ {wrapped}
277
334
  </StreamClickContext.Provider>
278
335
  );
279
336
  }
280
337
 
281
- return content;
338
+ if (onDecrypt) {
339
+ wrapped = (
340
+ <DecryptClickContext.Provider value={{ onDecrypt, isDecrypting }}>
341
+ {wrapped}
342
+ </DecryptClickContext.Provider>
343
+ );
344
+ }
345
+
346
+ return wrapped;
282
347
  }
283
348
 
284
349
  function useStableInspectorData<T>(next: T): T {
@@ -775,6 +775,7 @@ export const WorkflowTraceViewer = ({
775
775
  encryptionKey,
776
776
  onDecrypt,
777
777
  isDecrypting = false,
778
+ hasEncryptedData = false,
778
779
  }: {
779
780
  run: WorkflowRun;
780
781
  events: Event[];
@@ -815,6 +816,8 @@ export const WorkflowTraceViewer = ({
815
816
  onDecrypt?: () => void;
816
817
  /** Whether the encryption key is currently being fetched */
817
818
  isDecrypting?: boolean;
819
+ /** Run-level hint: the run contains encrypted data (from probe). */
820
+ hasEncryptedData?: boolean;
818
821
  }) => {
819
822
  const toast = useToast();
820
823
  const [selectedSpan, setSelectedSpan] = useState<SelectedSpanInfo | null>(
@@ -1151,6 +1154,7 @@ export const WorkflowTraceViewer = ({
1151
1154
  onDecrypt={onDecrypt}
1152
1155
  isDecrypting={isDecrypting}
1153
1156
  selectedSpan={selectedSpan}
1157
+ hasEncryptedData={hasEncryptedData}
1154
1158
  />
1155
1159
  </ErrorBoundary>
1156
1160
  </div>
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ export {
40
40
  ENCRYPTED_PLACEHOLDER,
41
41
  extractStreamIds,
42
42
  getWebRevivers,
43
+ hasEncryptedFields,
43
44
  hydrateResourceIO,
44
45
  hydrateResourceIOWithKey,
45
46
  isClassInstanceRef,
@@ -359,3 +359,29 @@ export async function hydrateResourceIOWithKey<T>(
359
359
 
360
360
  return result as T;
361
361
  }
362
+
363
+ /**
364
+ * Check whether a hydrated resource (event, step, run, etc.) contains any
365
+ * encrypted display markers. Inspects the standard top-level fields
366
+ * (`input`, `output`, `error`, `metadata`) as well as the event-type-specific
367
+ * `eventData` ref fields.
368
+ */
369
+ export function hasEncryptedFields(resource: unknown): boolean {
370
+ if (!resource || typeof resource !== 'object') return false;
371
+ const r = resource as Record<string, unknown>;
372
+
373
+ for (const key of ['input', 'output', 'metadata', 'error']) {
374
+ if (isEncryptedMarker(r[key])) return true;
375
+ }
376
+
377
+ if (r.eventData && typeof r.eventData === 'object') {
378
+ const eventType = typeof r.eventType === 'string' ? r.eventType : '';
379
+ const refKeys = EVENT_DATA_REF_FIELDS[eventType] ?? [];
380
+ const ed = r.eventData as Record<string, unknown>;
381
+ for (const key of refKeys) {
382
+ if (key in ed && isEncryptedMarker(ed[key])) return true;
383
+ }
384
+ }
385
+
386
+ return false;
387
+ }