@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
@@ -4,12 +4,13 @@ import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
4
  import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
5
5
  import type { ModelMessage } from 'ai';
6
6
  import { Lock } from 'lucide-react';
7
+ import { Spinner } from '../ui/spinner';
7
8
  import type { KeyboardEvent, ReactNode } from 'react';
8
- import { useCallback, useMemo, useState } from 'react';
9
+ import { useCallback, useContext, useMemo, useState } from 'react';
9
10
  import { isEncryptedMarker, isExpiredMarker } from '../../lib/hydration';
10
11
  import { useToast } from '../../lib/toast';
11
12
  import { extractConversation, isDoStreamStep } from '../../lib/utils';
12
- import { StreamClickContext } from '../ui/data-inspector';
13
+ import { DecryptClickContext, StreamClickContext } from '../ui/data-inspector';
13
14
  import { ErrorCard } from '../ui/error-card';
14
15
  import {
15
16
  ErrorStackBlock,
@@ -180,9 +181,36 @@ function ConversationWithTabs({
180
181
  * with the lucide Lock icon matching the title bar Decrypt button.
181
182
  */
182
183
  function EncryptedFieldBlock() {
184
+ const ctx = useContext(DecryptClickContext);
185
+ if (ctx) {
186
+ return (
187
+ <button
188
+ type="button"
189
+ onClick={ctx.onDecrypt}
190
+ disabled={ctx.isDecrypting}
191
+ className="flex w-full items-center justify-center gap-1.5 rounded-md border px-3 py-2 text-xs cursor-pointer transition-colors"
192
+ style={{
193
+ borderColor: 'var(--ds-gray-400)',
194
+ backgroundColor: 'var(--ds-gray-100)',
195
+ color: 'var(--ds-gray-700)',
196
+ opacity: ctx.isDecrypting ? 0.6 : 1,
197
+ }}
198
+ title="Click to decrypt"
199
+ >
200
+ {ctx.isDecrypting ? (
201
+ <Spinner size={12} />
202
+ ) : (
203
+ <Lock className="h-3 w-3" />
204
+ )}
205
+ <span className="font-medium">
206
+ {ctx.isDecrypting ? 'Decrypting…' : 'Decrypt'}
207
+ </span>
208
+ </button>
209
+ );
210
+ }
183
211
  return (
184
212
  <div
185
- className="flex items-center gap-1.5 rounded-md border px-3 py-2 text-xs"
213
+ className="flex w-full items-center justify-center gap-1.5 rounded-md border px-3 py-2 text-xs"
186
214
  style={{
187
215
  borderColor: 'var(--ds-gray-300)',
188
216
  backgroundColor: 'var(--ds-gray-100)',
@@ -689,6 +717,8 @@ export const AttributePanel = ({
689
717
  error,
690
718
  expiredAt,
691
719
  onStreamClick,
720
+ onDecrypt,
721
+ isDecrypting = false,
692
722
  resource,
693
723
  }: {
694
724
  data: Record<string, unknown>;
@@ -698,6 +728,10 @@ export const AttributePanel = ({
698
728
  expiredAt?: string | Date;
699
729
  /** Callback when a stream reference is clicked */
700
730
  onStreamClick?: (streamId: string) => void;
731
+ /** Callback when an encrypted marker is clicked (triggers decryption) */
732
+ onDecrypt?: () => void;
733
+ /** Whether decryption is currently in progress */
734
+ isDecrypting?: boolean;
701
735
  /** Resource type of the selected span — used to show targeted loading skeletons. */
702
736
  resource?: string;
703
737
  }) => {
@@ -798,116 +832,121 @@ export const AttributePanel = ({
798
832
 
799
833
  return (
800
834
  <StreamClickContext.Provider value={onStreamClick}>
801
- <div>
802
- {/* Basic attributes in a vertical layout with border */}
803
- {visibleBasicAttributes.length > 0 && (
804
- <div
805
- className="mb-3 flex flex-col overflow-hidden rounded-lg border"
806
- style={{
807
- borderColor: 'var(--ds-gray-300)',
808
- }}
809
- >
810
- {orderedBasicAttributes.map((attribute, index) => {
811
- const displayValue = attributeToDisplayFn[
812
- attribute as keyof typeof attributeToDisplayFn
813
- ]?.(displayData[attribute as keyof typeof displayData]);
814
- const isModuleSpecifier = attribute === 'moduleSpecifier';
815
- const moduleSpecifierValue =
816
- typeof displayValue === 'string'
817
- ? displayValue
818
- : String(displayValue ?? displayData.moduleSpecifier ?? '');
819
- const shouldCapitalizeLabel = attribute !== 'workflowCoreVersion';
820
- const showResumeAtSkeleton =
821
- isLoading && resource === 'sleep' && !displayData.resumeAt;
822
- const showDivider =
823
- index < orderedBasicAttributes.length - 1 ||
824
- showResumeAtSkeleton;
825
-
826
- return (
827
- <div key={attribute} className="py-1">
835
+ <DecryptClickContext.Provider
836
+ value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
837
+ >
838
+ <div>
839
+ {/* Basic attributes in a vertical layout with border */}
840
+ {visibleBasicAttributes.length > 0 && (
841
+ <div
842
+ className="mb-3 flex flex-col overflow-hidden rounded-lg border"
843
+ style={{
844
+ borderColor: 'var(--ds-gray-300)',
845
+ }}
846
+ >
847
+ {orderedBasicAttributes.map((attribute, index) => {
848
+ const displayValue = attributeToDisplayFn[
849
+ attribute as keyof typeof attributeToDisplayFn
850
+ ]?.(displayData[attribute as keyof typeof displayData]);
851
+ const isModuleSpecifier = attribute === 'moduleSpecifier';
852
+ const moduleSpecifierValue =
853
+ typeof displayValue === 'string'
854
+ ? displayValue
855
+ : String(displayValue ?? displayData.moduleSpecifier ?? '');
856
+ const shouldCapitalizeLabel =
857
+ attribute !== 'workflowCoreVersion';
858
+ const showResumeAtSkeleton =
859
+ isLoading && resource === 'sleep' && !displayData.resumeAt;
860
+ const showDivider =
861
+ index < orderedBasicAttributes.length - 1 ||
862
+ showResumeAtSkeleton;
863
+
864
+ return (
865
+ <div key={attribute} className="py-1">
866
+ <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
867
+ <span
868
+ className={
869
+ shouldCapitalizeLabel
870
+ ? 'text-[14px] first-letter:uppercase'
871
+ : 'text-[14px]'
872
+ }
873
+ style={{ color: 'var(--ds-gray-700)' }}
874
+ >
875
+ {getAttributeDisplayName(attribute)}
876
+ </span>
877
+ {isModuleSpecifier ? (
878
+ <button
879
+ type="button"
880
+ className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
881
+ style={{
882
+ color: 'var(--ds-gray-1000)',
883
+ background: 'transparent',
884
+ border: 'none',
885
+ padding: 0,
886
+ }}
887
+ title={moduleSpecifierValue}
888
+ onClick={() =>
889
+ handleCopyModuleSpecifier(moduleSpecifierValue)
890
+ }
891
+ >
892
+ {moduleSpecifierValue}
893
+ </button>
894
+ ) : (
895
+ <span
896
+ className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
897
+ style={{ color: 'var(--ds-gray-1000)' }}
898
+ >
899
+ {displayValue}
900
+ </span>
901
+ )}
902
+ </div>
903
+ {showDivider ? (
904
+ <div
905
+ className="mx-2.5 border-b"
906
+ style={{ borderColor: 'var(--ds-gray-300)' }}
907
+ />
908
+ ) : null}
909
+ </div>
910
+ );
911
+ })}
912
+ {isLoading && resource === 'sleep' && !displayData.resumeAt && (
913
+ <div className="py-1">
828
914
  <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
829
915
  <span
830
- className={
831
- shouldCapitalizeLabel
832
- ? 'text-[14px] first-letter:uppercase'
833
- : 'text-[14px]'
834
- }
916
+ className="text-[14px] first-letter:uppercase"
835
917
  style={{ color: 'var(--ds-gray-700)' }}
836
918
  >
837
- {getAttributeDisplayName(attribute)}
919
+ resumeAt
838
920
  </span>
839
- {isModuleSpecifier ? (
840
- <button
841
- type="button"
842
- className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
843
- style={{
844
- color: 'var(--ds-gray-1000)',
845
- background: 'transparent',
846
- border: 'none',
847
- padding: 0,
848
- }}
849
- title={moduleSpecifierValue}
850
- onClick={() =>
851
- handleCopyModuleSpecifier(moduleSpecifierValue)
852
- }
853
- >
854
- {moduleSpecifierValue}
855
- </button>
856
- ) : (
857
- <span
858
- className="min-w-0 max-w-[70%] truncate text-right text-[13px] font-mono"
859
- style={{ color: 'var(--ds-gray-1000)' }}
860
- >
861
- {displayValue}
862
- </span>
863
- )}
921
+ <Skeleton className="h-4 w-[55%]" />
864
922
  </div>
865
- {showDivider ? (
866
- <div
867
- className="mx-2.5 border-b"
868
- style={{ borderColor: 'var(--ds-gray-300)' }}
869
- />
870
- ) : null}
871
- </div>
872
- );
873
- })}
874
- {isLoading && resource === 'sleep' && !displayData.resumeAt && (
875
- <div className="py-1">
876
- <div className="flex min-h-[32px] items-center justify-between gap-4 rounded-sm px-2.5 py-1">
877
- <span
878
- className="text-[14px] first-letter:uppercase"
879
- style={{ color: 'var(--ds-gray-700)' }}
880
- >
881
- resumeAt
882
- </span>
883
- <Skeleton className="h-4 w-[55%]" />
884
923
  </div>
885
- </div>
886
- )}
887
- </div>
888
- )}
889
- {error ? (
890
- <ErrorCard
891
- title="Failed to load resource details"
892
- details={error.message}
893
- className="my-4"
894
- />
895
- ) : hasExpired ? (
896
- <ExpiredDataMessage />
897
- ) : (
898
- <>
899
- {resolvedAttributes.map((attribute) => (
900
- <AttributeBlock
901
- isLoading={isLoading}
902
- key={attribute}
903
- attribute={attribute}
904
- value={displayData[attribute as keyof typeof displayData]}
905
- context={displayContext}
906
- />
907
- ))}
908
- </>
909
- )}
910
- </div>
924
+ )}
925
+ </div>
926
+ )}
927
+ {error ? (
928
+ <ErrorCard
929
+ title="Failed to load resource details"
930
+ details={error.message}
931
+ className="my-4"
932
+ />
933
+ ) : hasExpired ? (
934
+ <ExpiredDataMessage />
935
+ ) : (
936
+ <>
937
+ {resolvedAttributes.map((attribute) => (
938
+ <AttributeBlock
939
+ isLoading={isLoading}
940
+ key={attribute}
941
+ attribute={attribute}
942
+ value={displayData[attribute as keyof typeof displayData]}
943
+ context={displayContext}
944
+ />
945
+ ))}
946
+ </>
947
+ )}
948
+ </div>
949
+ </DecryptClickContext.Provider>
911
950
  </StreamClickContext.Provider>
912
951
  );
913
952
  };
@@ -6,6 +6,7 @@ import { Send, Zap } from 'lucide-react';
6
6
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
7
  import { useToast } from '../../lib/toast';
8
8
  import { isEncryptedMarker } from '../../lib/hydration';
9
+ import { DecryptClickContext } from '../ui/data-inspector';
9
10
  import { DecryptButton } from '../ui/decrypt-button';
10
11
  import { AttributePanel } from './attribute-panel';
11
12
  import { EventsList } from './events-list';
@@ -67,6 +68,7 @@ export function EntityDetailPanel({
67
68
  onDecrypt,
68
69
  isDecrypting = false,
69
70
  selectedSpan,
71
+ hasEncryptedData = false,
70
72
  }: {
71
73
  run: WorkflowRun;
72
74
  /** Callback when a stream reference is clicked */
@@ -103,6 +105,8 @@ export function EntityDetailPanel({
103
105
  isDecrypting?: boolean;
104
106
  /** Info about the currently selected span from the trace viewer */
105
107
  selectedSpan: SelectedSpanInfo | null;
108
+ /** Run-level hint: the run contains encrypted data (from probe). */
109
+ hasEncryptedData?: boolean;
106
110
  }): React.JSX.Element | null {
107
111
  const toast = useToast();
108
112
  const [stoppingSleep, setStoppingSleep] = useState(false);
@@ -391,111 +395,118 @@ export function EntityDetailPanel({
391
395
  {resourceId}
392
396
  </p>
393
397
  </div>
394
- {(hasEncryptedFields || encryptionKey) && onDecrypt && (
395
- <DecryptButton
396
- decrypted={!!encryptionKey}
397
- loading={isDecrypting}
398
- onClick={onDecrypt}
399
- />
400
- )}
398
+ {(hasEncryptedFields || hasEncryptedData || encryptionKey) &&
399
+ onDecrypt && (
400
+ <DecryptButton
401
+ decrypted={!!encryptionKey}
402
+ loading={isDecrypting}
403
+ onClick={onDecrypt}
404
+ />
405
+ )}
401
406
  </div>
402
407
  </div>
403
408
 
404
- <div className="flex-1 overflow-y-auto px-3 pt-3 pb-8">
405
- {hasPendingActions && (
406
- <div
407
- className="mb-4 rounded-lg border p-2"
408
- style={{
409
- borderColor: 'var(--ds-gray-300)',
410
- backgroundColor: 'var(--ds-gray-100)',
411
- }}
412
- >
413
- <p
414
- className="mb-2 px-1 text-[13px] font-medium uppercase tracking-wide"
415
- style={{ color: 'var(--ds-gray-700)' }}
409
+ <DecryptClickContext.Provider
410
+ value={onDecrypt ? { onDecrypt, isDecrypting } : undefined}
411
+ >
412
+ <div className="flex-1 overflow-y-auto px-3 pt-3 pb-8">
413
+ {hasPendingActions && (
414
+ <div
415
+ className="mb-4 rounded-lg border p-2"
416
+ style={{
417
+ borderColor: 'var(--ds-gray-300)',
418
+ backgroundColor: 'var(--ds-gray-100)',
419
+ }}
416
420
  >
417
- Actions
418
- </p>
419
- <div className="flex flex-col gap-2">
420
- {/* Wake up button for pending sleep calls */}
421
- {resource === 'sleep' && canWakeUp && (
422
- <button
423
- type="button"
424
- onClick={handleWakeUp}
425
- disabled={stoppingSleep}
426
- className={clsx(
427
- 'flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium',
428
- 'disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
429
- stoppingSleep
430
- ? 'opacity-50 cursor-not-allowed'
431
- : 'cursor-pointer'
432
- )}
433
- style={{
434
- background: 'var(--ds-amber-200)',
435
- color: 'var(--ds-amber-900)',
436
- }}
437
- >
438
- <Zap className="h-4 w-4" />
439
- {stoppingSleep ? 'Waking up...' : 'Wake Up Sleep'}
440
- </button>
441
- )}
442
-
443
- {/* Resolve hook button for pending hooks */}
444
- {resource === 'hook' && canResolveHook && (
445
- <button
446
- type="button"
447
- onClick={() => setShowResolveHookModal(true)}
448
- disabled={resolvingHook}
449
- className={clsx(
450
- 'flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium',
451
- 'disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
452
- resolvingHook
453
- ? 'opacity-50 cursor-not-allowed'
454
- : 'cursor-pointer'
455
- )}
456
- style={{
457
- background: 'var(--ds-gray-1000)',
458
- color: 'var(--ds-background-100)',
459
- }}
460
- >
461
- <Send className="h-4 w-4" />
462
- Resolve Hook
463
- </button>
464
- )}
421
+ <p
422
+ className="mb-2 px-1 text-[13px] font-medium uppercase tracking-wide"
423
+ style={{ color: 'var(--ds-gray-700)' }}
424
+ >
425
+ Actions
426
+ </p>
427
+ <div className="flex flex-col gap-2">
428
+ {/* Wake up button for pending sleep calls */}
429
+ {resource === 'sleep' && canWakeUp && (
430
+ <button
431
+ type="button"
432
+ onClick={handleWakeUp}
433
+ disabled={stoppingSleep}
434
+ className={clsx(
435
+ 'flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium',
436
+ 'disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
437
+ stoppingSleep
438
+ ? 'opacity-50 cursor-not-allowed'
439
+ : 'cursor-pointer'
440
+ )}
441
+ style={{
442
+ background: 'var(--ds-amber-200)',
443
+ color: 'var(--ds-amber-900)',
444
+ }}
445
+ >
446
+ <Zap className="h-4 w-4" />
447
+ {stoppingSleep ? 'Waking up...' : 'Wake Up Sleep'}
448
+ </button>
449
+ )}
450
+
451
+ {/* Resolve hook button for pending hooks */}
452
+ {resource === 'hook' && canResolveHook && (
453
+ <button
454
+ type="button"
455
+ onClick={() => setShowResolveHookModal(true)}
456
+ disabled={resolvingHook}
457
+ className={clsx(
458
+ 'flex items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-medium',
459
+ 'disabled:opacity-50 disabled:cursor-not-allowed transition-colors',
460
+ resolvingHook
461
+ ? 'opacity-50 cursor-not-allowed'
462
+ : 'cursor-pointer'
463
+ )}
464
+ style={{
465
+ background: 'var(--ds-gray-1000)',
466
+ color: 'var(--ds-background-100)',
467
+ }}
468
+ >
469
+ <Send className="h-4 w-4" />
470
+ Resolve Hook
471
+ </button>
472
+ )}
473
+ </div>
465
474
  </div>
466
- </div>
467
- )}
475
+ )}
468
476
 
469
- <div className="space-y-4">
470
- <section>
471
- <h3
472
- className="mb-2 text-[13px] font-medium uppercase tracking-wide"
473
- style={{ color: 'var(--ds-gray-700)' }}
474
- >
475
- Details
476
- </h3>
477
- <AttributePanel
478
- data={displayData}
479
- moduleSpecifier={moduleSpecifier}
480
- expiredAt={run.expiredAt}
481
- isLoading={loading}
482
- error={error ?? undefined}
483
- onStreamClick={onStreamClick}
484
- resource={resource}
485
- />
486
- </section>
487
-
488
- {resource !== 'run' && rawEvents && (
477
+ <div className="space-y-4">
489
478
  <section>
490
- <EventsList
491
- events={rawEvents}
492
- onLoadEventData={onLoadEventData}
493
- encryptionKey={encryptionKey}
479
+ <h3
480
+ className="mb-2 text-[13px] font-medium uppercase tracking-wide"
481
+ style={{ color: 'var(--ds-gray-700)' }}
482
+ >
483
+ Details
484
+ </h3>
485
+ <AttributePanel
486
+ data={displayData}
487
+ moduleSpecifier={moduleSpecifier}
488
+ expiredAt={run.expiredAt}
489
+ isLoading={loading}
490
+ error={error ?? undefined}
491
+ onStreamClick={onStreamClick}
492
+ onDecrypt={onDecrypt}
493
+ isDecrypting={isDecrypting}
494
+ resource={resource}
494
495
  />
495
496
  </section>
496
- )}
497
+
498
+ {resource !== 'run' && rawEvents && (
499
+ <section>
500
+ <EventsList
501
+ events={rawEvents}
502
+ onLoadEventData={onLoadEventData}
503
+ encryptionKey={encryptionKey}
504
+ />
505
+ </section>
506
+ )}
507
+ </div>
497
508
  </div>
498
- </div>
509
+ </DecryptClickContext.Provider>
499
510
 
500
511
  {/* Resolve Hook Modal */}
501
512
  <ResolveHookModal