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

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 (31) hide show
  1. package/dist/components/event-list-view.d.ts.map +1 -1
  2. package/dist/components/event-list-view.js +13 -1
  3. package/dist/components/new-trace-viewer/components/detail-panel.d.ts.map +1 -1
  4. package/dist/components/new-trace-viewer/components/detail-panel.js +2 -2
  5. package/dist/components/new-trace-viewer/components/event-list.js +2 -2
  6. package/dist/components/new-trace-viewer/components/split-pane.d.ts +0 -2
  7. package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -1
  8. package/dist/components/new-trace-viewer/components/split-pane.js +9 -75
  9. package/dist/components/new-trace-viewer/trace-viewer.js +3 -3
  10. package/dist/components/sidebar/attribute-panel.d.ts +4 -1
  11. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  12. package/dist/components/sidebar/attribute-panel.js +32 -24
  13. package/dist/components/sidebar/attributes-block.d.ts +1 -0
  14. package/dist/components/sidebar/attributes-block.d.ts.map +1 -1
  15. package/dist/components/sidebar/attributes-block.js +6 -5
  16. package/dist/components/sidebar/entity-detail-panel.d.ts +5 -1
  17. package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
  18. package/dist/components/sidebar/entity-detail-panel.js +18 -3
  19. package/dist/components/sidebar/sidebar-data-context.d.ts +4 -0
  20. package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
  21. package/dist/components/sidebar/sidebar-data-context.js +1 -1
  22. package/package.json +3 -3
  23. package/src/components/event-list-view.tsx +21 -0
  24. package/src/components/new-trace-viewer/components/detail-panel.tsx +1 -0
  25. package/src/components/new-trace-viewer/components/event-list.tsx +1 -1
  26. package/src/components/new-trace-viewer/components/split-pane.tsx +39 -95
  27. package/src/components/new-trace-viewer/trace-viewer.tsx +2 -2
  28. package/src/components/sidebar/attribute-panel.tsx +77 -22
  29. package/src/components/sidebar/attributes-block.tsx +43 -10
  30. package/src/components/sidebar/entity-detail-panel.tsx +22 -0
  31. package/src/components/sidebar/sidebar-data-context.tsx +4 -0
@@ -5,7 +5,7 @@ import type { Event, Hook, Step, WorkflowRun } from '@workflow/world';
5
5
  import type { ModelMessage } from 'ai';
6
6
  import { format } from 'date-fns';
7
7
  import type { KeyboardEvent, ReactNode } from 'react';
8
- import { useCallback, useContext, useMemo, useState } from 'react';
8
+ import { useCallback, useContext, useMemo, useRef, useState } from 'react';
9
9
  import { isEncryptedMarker, isExpiredMarker } from '../../lib/hydration';
10
10
  import { extractConversation, isDoStreamStep } from '../../lib/utils';
11
11
  import {
@@ -148,16 +148,24 @@ const conversationTabs = [
148
148
  function ConversationWithTabs({
149
149
  conversation,
150
150
  args,
151
+ defaultOpen,
152
+ onOpenChange,
151
153
  }: {
152
154
  conversation: ModelMessage[];
153
155
  args: unknown[];
156
+ defaultOpen?: boolean;
157
+ onOpenChange?: (open: boolean) => void;
154
158
  }) {
155
159
  const [activeTab, setActiveTab] = useState<'conversation' | 'json'>(
156
160
  'conversation'
157
161
  );
158
162
 
159
163
  return (
160
- <Collapsible label="Input">
164
+ <Collapsible
165
+ label="Input"
166
+ defaultOpen={defaultOpen}
167
+ onOpenChange={onOpenChange}
168
+ >
161
169
  <TabbedContainer
162
170
  tabs={conversationTabs}
163
171
  activeTab={activeTab}
@@ -382,6 +390,8 @@ const timestampWithTooltipOrNull = (value: unknown): ReactNode | null => {
382
390
 
383
391
  interface DisplayContext {
384
392
  stepName?: string;
393
+ sectionOpen?: boolean;
394
+ onSectionOpenChange?: (open: boolean) => void;
385
395
  }
386
396
 
387
397
  const attributeToDisplayFn: Record<
@@ -457,7 +467,11 @@ const attributeToDisplayFn: Record<
457
467
  input: (value: unknown, context?: DisplayContext) => {
458
468
  if (isEncryptedMarker(value)) {
459
469
  return (
460
- <Collapsible label="Input">
470
+ <Collapsible
471
+ label="Input"
472
+ defaultOpen={context?.sectionOpen}
473
+ onOpenChange={context?.onSectionOpenChange}
474
+ >
461
475
  <EncryptedFieldBlock />
462
476
  </Collapsible>
463
477
  );
@@ -480,7 +494,12 @@ const attributeToDisplayFn: Record<
480
494
  if (conversation && conversation.length > 0) {
481
495
  return (
482
496
  <>
483
- <ConversationWithTabs conversation={conversation} args={args} />
497
+ <ConversationWithTabs
498
+ conversation={conversation}
499
+ args={args}
500
+ defaultOpen={context?.sectionOpen}
501
+ onOpenChange={context?.onSectionOpenChange}
502
+ />
484
503
  {hasClosureVars && (
485
504
  <Collapsible label="Closure Variables">
486
505
  {JsonBlock(closureVars)}
@@ -503,7 +522,11 @@ const attributeToDisplayFn: Record<
503
522
 
504
523
  return (
505
524
  <>
506
- <Collapsible label="Input">
525
+ <Collapsible
526
+ label="Input"
527
+ defaultOpen={context?.sectionOpen}
528
+ onOpenChange={context?.onSectionOpenChange}
529
+ >
507
530
  {Array.isArray(args)
508
531
  ? args.map((v, i) => (
509
532
  <div className="mt-2 first:mt-0" key={i}>
@@ -529,7 +552,11 @@ const attributeToDisplayFn: Record<
529
552
  return <Collapsible label="Input (no data)" disabled />;
530
553
  }
531
554
  return (
532
- <Collapsible label="Input">
555
+ <Collapsible
556
+ label="Input"
557
+ defaultOpen={context?.sectionOpen}
558
+ onOpenChange={context?.onSectionOpenChange}
559
+ >
533
560
  {Array.isArray(value)
534
561
  ? value.map((v, i) => (
535
562
  <div className="mt-2 first:mt-0" key={i}>
@@ -540,17 +567,29 @@ const attributeToDisplayFn: Record<
540
567
  </Collapsible>
541
568
  );
542
569
  },
543
- output: (value: unknown) => {
570
+ output: (value: unknown, context?: DisplayContext) => {
544
571
  if (isEncryptedMarker(value)) {
545
572
  return (
546
- <Collapsible label="Output">
573
+ <Collapsible
574
+ label="Output"
575
+ defaultOpen={context?.sectionOpen}
576
+ onOpenChange={context?.onSectionOpenChange}
577
+ >
547
578
  <EncryptedFieldBlock />
548
579
  </Collapsible>
549
580
  );
550
581
  }
551
582
  if (!hasDisplayContent(value)) return null;
552
583
  if (isExpiredMarker(value)) return <ExpiredFieldBlock />;
553
- return <Collapsible label="Output">{JsonBlock(value)}</Collapsible>;
584
+ return (
585
+ <Collapsible
586
+ label="Output"
587
+ defaultOpen={context?.sectionOpen}
588
+ onOpenChange={context?.onSectionOpenChange}
589
+ >
590
+ {JsonBlock(value)}
591
+ </Collapsible>
592
+ );
554
593
  },
555
594
  error: (value: unknown) => {
556
595
  if (isEncryptedMarker(value)) {
@@ -642,6 +681,12 @@ const copyableBasicAttributes = new Set<AttributeKey>([
642
681
  'moduleSpecifier',
643
682
  ]);
644
683
 
684
+ const loadingSectionLabels: Partial<Record<AttributeKey, string>> = {
685
+ input: 'Input',
686
+ output: 'Output',
687
+ eventData: 'Event Data',
688
+ };
689
+
645
690
  export const AttributeBlock = ({
646
691
  attribute,
647
692
  value,
@@ -656,20 +701,19 @@ export const AttributeBlock = ({
656
701
  context?: DisplayContext;
657
702
  }) => {
658
703
  const decryptCtx = useContext(DecryptClickContext);
659
- const isExpandableLoadingTarget =
660
- attribute === 'input' ||
661
- attribute === 'output' ||
662
- attribute === 'eventData';
663
- if (isLoading && isExpandableLoadingTarget && !hasDisplayContent(value)) {
664
- const label =
665
- attribute === 'eventData'
666
- ? 'Event Data'
667
- : attribute === 'output'
668
- ? 'Output'
669
- : 'Input';
704
+ const sectionOpenRef = useRef(false);
705
+ const handleSectionOpenChange = useCallback((open: boolean) => {
706
+ sectionOpenRef.current = open;
707
+ }, []);
708
+ const label = loadingSectionLabels[attribute as AttributeKey];
709
+ if (isLoading && label && !hasDisplayContent(value)) {
670
710
  if (decryptCtx?.hasEncryptedData) {
671
711
  return (
672
- <Collapsible label={label} defaultOpen={attribute === 'eventData'}>
712
+ <Collapsible
713
+ label={label}
714
+ defaultOpen={attribute === 'eventData' || sectionOpenRef.current}
715
+ onOpenChange={handleSectionOpenChange}
716
+ >
673
717
  <EncryptedFieldBlock />
674
718
  </Collapsible>
675
719
  );
@@ -682,7 +726,11 @@ export const AttributeBlock = ({
682
726
  if (!displayFn) {
683
727
  return null;
684
728
  }
685
- const displayValue = displayFn(value, context);
729
+ const displayValue = displayFn(value, {
730
+ ...context,
731
+ sectionOpen: sectionOpenRef.current,
732
+ onSectionOpenChange: handleSectionOpenChange,
733
+ });
686
734
  if (!displayValue) {
687
735
  return null;
688
736
  }
@@ -732,6 +780,7 @@ export const AttributeBlock = ({
732
780
  export const AttributePanel = ({
733
781
  data,
734
782
  moduleSpecifier,
783
+ moduleSourceUrl,
735
784
  isLoading,
736
785
  error,
737
786
  expiredAt,
@@ -743,6 +792,7 @@ export const AttributePanel = ({
743
792
  }: {
744
793
  data: Record<string, unknown>;
745
794
  moduleSpecifier?: string;
795
+ moduleSourceUrl?: string;
746
796
  isLoading?: boolean;
747
797
  error?: Error;
748
798
  expiredAt?: string | Date;
@@ -877,6 +927,11 @@ export const AttributePanel = ({
877
927
  copyText={
878
928
  isCopyableBasicAttribute ? displayValue : undefined
879
929
  }
930
+ href={
931
+ attribute === 'moduleSpecifier'
932
+ ? moduleSourceUrl
933
+ : undefined
934
+ }
880
935
  />
881
936
  );
882
937
  })}
@@ -5,6 +5,7 @@ import {
5
5
  RESERVED_ATTRIBUTE_KEY_PREFIX,
6
6
  } from '@workflow/world';
7
7
  import { cva, type VariantProps } from 'class-variance-authority';
8
+ import { ArrowUpRight } from 'lucide-react';
8
9
  import type { ReactNode } from 'react';
9
10
  import { cn } from '../../lib/cn';
10
11
  import { CopyButton } from '../new-trace-viewer/components/copy-button';
@@ -21,7 +22,7 @@ function isReservedAttributeKey(key: string): boolean {
21
22
  }
22
23
 
23
24
  const rowValueVariants = cva(
24
- 'max-w-[60%] truncate text-right text-copy-13 text-gray-1000',
25
+ 'min-w-0 flex-1 truncate text-right text-copy-13 text-gray-1000',
25
26
  {
26
27
  variants: {
27
28
  variant: {
@@ -36,7 +37,7 @@ const rowValueVariants = cva(
36
37
  );
37
38
 
38
39
  const rowCopyValueVariants = cva(
39
- 'flex min-w-0 max-w-[60%] items-center justify-end gap-1 text-copy-13 text-gray-1000',
40
+ 'flex min-w-0 flex-1 items-center justify-end gap-1 text-copy-13 text-gray-1000',
40
41
  {
41
42
  variants: {
42
43
  variant: {
@@ -54,6 +55,7 @@ type DetailKeyValueRowProps = {
54
55
  label: string;
55
56
  value?: ReactNode;
56
57
  copyText?: string;
58
+ href?: string;
57
59
  removed?: boolean;
58
60
  };
59
61
 
@@ -61,6 +63,7 @@ function DetailKeyValueRowBase({
61
63
  label,
62
64
  value,
63
65
  copyText,
66
+ href,
64
67
  removed = false,
65
68
  variant,
66
69
  }: DetailKeyValueRowProps & VariantProps<typeof rowValueVariants>) {
@@ -77,13 +80,32 @@ function DetailKeyValueRowBase({
77
80
  </span>
78
81
  ) : copyText ? (
79
82
  <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
- />
83
+ {href ? (
84
+ <a
85
+ href={href}
86
+ target="_blank"
87
+ rel="noreferrer"
88
+ className={cn(
89
+ rowValueVariants({ variant }),
90
+ 'flex min-w-0 items-center gap-0.5 hover:underline'
91
+ )}
92
+ >
93
+ <MiddleTruncate
94
+ value={copyText}
95
+ className="min-w-0 text-right"
96
+ style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}
97
+ />
98
+ <ArrowUpRight aria-hidden className="h-3 w-3 shrink-0" />
99
+ </a>
100
+ ) : (
101
+ <MiddleTruncate
102
+ value={copyText}
103
+ className={cn(
104
+ rowValueVariants({ variant, className: 'text-right' })
105
+ )}
106
+ style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}
107
+ />
108
+ )}
87
109
  <CopyButton
88
110
  copyText={copyText}
89
111
  ariaLabel={`Copy ${label}`}
@@ -92,7 +114,18 @@ function DetailKeyValueRowBase({
92
114
  </div>
93
115
  ) : (
94
116
  <span className={cn(rowValueVariants({ variant }))} title={stringValue}>
95
- {value}
117
+ {href ? (
118
+ <a
119
+ href={href}
120
+ target="_blank"
121
+ rel="noreferrer"
122
+ className="hover:underline"
123
+ >
124
+ {value}
125
+ </a>
126
+ ) : (
127
+ value
128
+ )}
96
129
  </span>
97
130
  )}
98
131
  </div>
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
3
4
  import type { Event, Hook, WorkflowRun } from '@workflow/world';
4
5
  import clsx from 'clsx';
5
6
  import { Send, Zap } from 'lucide-react';
@@ -62,6 +63,7 @@ export function EntityDetailPanel({
62
63
  isDecrypting = false,
63
64
  selectedSpan,
64
65
  showSeparateEventOccurrenceTimestamps = false,
66
+ getModuleSourceUrl,
65
67
  }: {
66
68
  run: WorkflowRun;
67
69
  /** Callback when a stream reference is clicked */
@@ -95,6 +97,10 @@ export function EntityDetailPanel({
95
97
  selectedSpan: SelectedSpanInfo | null;
96
98
  /** Show occurredAt separately instead of folding it into the Created timestamp. */
97
99
  showSeparateEventOccurrenceTimestamps?: boolean;
100
+ getModuleSourceUrl?: (info: {
101
+ moduleSpecifier: string;
102
+ deploymentId: string;
103
+ }) => string | undefined;
98
104
  }): React.JSX.Element | null {
99
105
  const toast = useToast();
100
106
  const [stoppingSleep, setStoppingSleep] = useState(false);
@@ -277,6 +283,21 @@ export function EntityDetailPanel({
277
283
  return undefined;
278
284
  }, [displayData, run.workflowName]);
279
285
 
286
+ const moduleSourceUrl = useMemo(() => {
287
+ if (!getModuleSourceUrl || !moduleSpecifier) return undefined;
288
+ const parsed =
289
+ parseStepName(moduleSpecifier) ?? parseWorkflowName(moduleSpecifier);
290
+ if (!parsed) return undefined;
291
+ const dataDeploymentId = displayData.deploymentId;
292
+ return getModuleSourceUrl({
293
+ moduleSpecifier: parsed.moduleSpecifier,
294
+ deploymentId:
295
+ typeof dataDeploymentId === 'string'
296
+ ? dataDeploymentId
297
+ : run.deploymentId,
298
+ });
299
+ }, [getModuleSourceUrl, moduleSpecifier, displayData, run.deploymentId]);
300
+
280
301
  if (!selectedSpan || !resource || !resourceId) {
281
302
  return null;
282
303
  }
@@ -360,6 +381,7 @@ export function EntityDetailPanel({
360
381
  <AttributePanel
361
382
  data={displayData}
362
383
  moduleSpecifier={moduleSpecifier}
384
+ moduleSourceUrl={moduleSourceUrl}
363
385
  expiredAt={run.expiredAt}
364
386
  isLoading={loading}
365
387
  error={error ?? undefined}
@@ -29,6 +29,10 @@ export interface SidebarDataContextValue {
29
29
  hasEncryptedData?: boolean;
30
30
  /** Show occurredAt separately instead of folding it into the Created timestamp. */
31
31
  showSeparateEventOccurrenceTimestamps?: boolean;
32
+ getModuleSourceUrl?: (info: {
33
+ moduleSpecifier: string;
34
+ deploymentId: string;
35
+ }) => string | undefined;
32
36
  }
33
37
 
34
38
  const SidebarDataContext = createContext<SidebarDataContextValue | null>(null);