@workflow/web-shared 5.0.0-beta.25 → 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
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+
3
+ import { type RefObject, useLayoutEffect, useState } from 'react';
4
+
5
+ /**
6
+ * Observed width (px) of `ref`'s element. Measures synchronously before paint
7
+ * when (re-)enabled, then tracks resizes. While disabled it keeps the last
8
+ * measured value; re-enabling re-measures before the next paint.
9
+ */
10
+ export function useElementWidth(
11
+ ref: RefObject<HTMLElement | null>,
12
+ enabled = true
13
+ ): number {
14
+ const [width, setWidth] = useState(0);
15
+
16
+ useLayoutEffect(() => {
17
+ if (!enabled) return;
18
+ const el = ref.current;
19
+ if (!el) return;
20
+ setWidth(Math.round(el.getBoundingClientRect().width));
21
+ const observer = new ResizeObserver(([entry]) => {
22
+ setWidth(Math.round(entry?.contentRect.width ?? 0));
23
+ });
24
+ observer.observe(el);
25
+ return () => observer.disconnect();
26
+ }, [ref, enabled]);
27
+
28
+ return width;
29
+ }
@@ -14,6 +14,8 @@ import type { Span } from './types';
14
14
  interface ActiveSpanContextValue {
15
15
  activeSpanId: string | null;
16
16
  activeSpan: Span | null;
17
+ /** The full span list the selection lives in (ordered as rendered). */
18
+ spans: Span[];
17
19
  setActiveSpan: (spanId: string) => void;
18
20
  clearActiveSpan: () => void;
19
21
  }
@@ -67,10 +69,11 @@ export function ActiveSpanProvider({
67
69
  () => ({
68
70
  activeSpanId,
69
71
  activeSpan,
72
+ spans,
70
73
  setActiveSpan,
71
74
  clearActiveSpan,
72
75
  }),
73
- [activeSpanId, activeSpan, setActiveSpan, clearActiveSpan]
76
+ [activeSpanId, activeSpan, spans, setActiveSpan, clearActiveSpan]
74
77
  );
75
78
 
76
79
  return (
@@ -1,15 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { parseStepName, parseWorkflowName } from '@workflow/utils/parse-name';
4
- import {
5
- ChevronDown,
6
- ChevronUp,
7
- RotateCcw,
8
- Search,
9
- X,
10
- ZoomIn,
11
- ZoomOut,
12
- } from 'lucide-react';
3
+ import { RotateCcw, Search, ZoomIn, ZoomOut } from 'lucide-react';
13
4
  import {
14
5
  type ReactNode,
15
6
  useCallback,
@@ -21,23 +12,12 @@ import {
21
12
  } from 'react';
22
13
  import { useLoadMoreOnScroll } from '../../hooks/use-load-more-on-scroll';
23
14
  import { useReducedMotion } from '../../hooks/use-reduced-motion';
24
- import { filterSpanRawEvents } from '../../lib/trace-builder';
25
- import { ErrorBoundary } from '../error-boundary';
26
- import {
27
- EntityDetailPanel,
28
- type SelectedSpanInfo,
29
- } from '../sidebar/entity-detail-panel';
30
- import { useSidebarData } from '../sidebar/sidebar-data-context';
31
15
  import { formatDuration, getHighResInMs } from '../trace-viewer/util/timing';
32
16
  import { IconButton } from '../ui/icon-button';
33
17
  import { Kbd } from '../ui/kbd';
34
18
  import { Spinner } from '../ui/spinner';
35
- import {
36
- Tooltip,
37
- TooltipContent,
38
- TooltipProvider,
39
- TooltipTrigger,
40
- } from '../ui/tooltip';
19
+ import { TooltipProvider } from '../ui/tooltip';
20
+ import { TraceDetailPanel } from './components/detail-panel';
41
21
  import EventList from './components/event-list';
42
22
  import { SplitPane } from './components/split-pane';
43
23
  import {
@@ -139,33 +119,6 @@ function useAnimatedViewport(initial: Viewport) {
139
119
  return { viewport, setViewport, animateTo };
140
120
  }
141
121
 
142
- // ---------------------------------------------------------------------------
143
- // Hook: bridge ActiveSpanContext + SidebarDataContext → SelectedSpanInfo
144
- // ---------------------------------------------------------------------------
145
-
146
- function useSelectedSpanInfo(): SelectedSpanInfo | null {
147
- const { activeSpan } = useActiveSpan();
148
- const sidebar = useSidebarData();
149
-
150
- return useMemo(() => {
151
- if (!activeSpan) return null;
152
-
153
- const resource = activeSpan.attributes?.resource as string | undefined;
154
- const rawEvents = filterSpanRawEvents(
155
- sidebar.events,
156
- resource,
157
- activeSpan.spanId
158
- );
159
-
160
- return {
161
- data: activeSpan.attributes?.data,
162
- resource,
163
- spanId: activeSpan.spanId,
164
- rawEvents,
165
- };
166
- }, [activeSpan, sidebar]);
167
- }
168
-
169
122
  // ---------------------------------------------------------------------------
170
123
  // Root component
171
124
  // ---------------------------------------------------------------------------
@@ -196,16 +149,15 @@ function NewTraceViewerContent({
196
149
  hasMore,
197
150
  isLoadingMore,
198
151
  }: NewTraceViewerProps): ReactNode {
199
- const { activeSpan, activeSpanId, setActiveSpan, clearActiveSpan } =
200
- useActiveSpan();
152
+ const { activeSpanId, setActiveSpan, clearActiveSpan } = useActiveSpan();
201
153
 
202
- const sidebar = useSidebarData();
203
- const selectedSpan = useSelectedSpanInfo();
204
154
  const reducedMotion = useReducedMotion();
205
155
 
206
156
  const [searchQuery, setSearchQuery] = useState('');
207
157
  const deferredSearchQuery = useDeferredValue(searchQuery);
208
158
 
159
+ const paneRootRef = useRef<HTMLDivElement>(null);
160
+
209
161
  const searchResult = useMemo(
210
162
  () => searchSpans(trace.spans, deferredSearchQuery),
211
163
  [trace.spans, deferredSearchQuery]
@@ -327,6 +279,8 @@ function NewTraceViewerContent({
327
279
 
328
280
  const zoomIn = useCallback(() => zoomBy(ZOOM_FACTOR), [zoomBy]);
329
281
  const zoomOut = useCallback(() => zoomBy(1 / ZOOM_FACTOR), [zoomBy]);
282
+ const isAtMinZoom = viewDuration >= root.duration;
283
+ const isAtMaxZoom = viewDuration <= MIN_VIEWPORT_MS;
330
284
 
331
285
  const focusViewportOnSpan = useCallback(
332
286
  (spanId: string) => {
@@ -440,30 +394,11 @@ function NewTraceViewerContent({
440
394
  const [altHeld, setAltHeld] = useState(false);
441
395
 
442
396
  useEffect(() => {
443
- const handleSidebarNavKey = (e: KeyboardEvent): void => {
444
- const target = e.target as HTMLElement | null;
445
- if (
446
- target instanceof HTMLInputElement ||
447
- target instanceof HTMLTextAreaElement ||
448
- target?.isContentEditable
449
- ) {
450
- return;
451
- }
452
- const targetId =
453
- e.key === 'k' ? prevSpanIdRef.current : nextSpanIdRef.current;
454
- if (targetId) {
455
- e.preventDefault();
456
- navigateToSpanRef.current(targetId);
457
- }
458
- };
459
-
460
397
  const onKeyDown = (e: KeyboardEvent): void => {
461
398
  if (e.key === 'Escape') {
462
399
  handleClearActiveSpan();
463
400
  } else if (e.key === 'Alt') {
464
401
  setAltHeld(true);
465
- } else if (e.key === 'j' || e.key === 'k') {
466
- handleSidebarNavKey(e);
467
402
  }
468
403
  };
469
404
  const onKeyUp = (e: KeyboardEvent): void => {
@@ -599,60 +534,15 @@ function NewTraceViewerContent({
599
534
  return () => el.removeEventListener('wheel', onWheel);
600
535
  }, [root.startTime, root.duration, setViewport]);
601
536
 
602
- // Derive the selected span name and metadata for the panel header
603
- const selectedSpanName = useMemo(() => {
604
- if (!selectedSpan?.data) return 'Details';
605
- const data = selectedSpan.data as Record<string, unknown>;
606
- if (selectedSpan.resource === 'hook') {
607
- return (data.token as string | undefined) ?? (data.hookId as string);
608
- }
609
-
610
- const stepName = data.stepName as string | undefined;
611
- const workflowName = data.workflowName as string | undefined;
612
- return (
613
- (stepName ? parseStepName(stepName)?.shortName : undefined) ??
614
- (workflowName ? parseWorkflowName(workflowName)?.shortName : undefined) ??
615
- stepName ??
616
- workflowName ??
617
- (data.hookId as string) ??
618
- 'Details'
619
- );
620
- }, [selectedSpan?.data, selectedSpan?.resource]);
621
-
622
- const { prevSpanId, nextSpanId } = useMemo(() => {
623
- if (!activeSpanId) return { prevSpanId: null, nextSpanId: null };
624
- const i = trace.spans.findIndex((s) => s.spanId === activeSpanId);
625
- if (i === -1) return { prevSpanId: null, nextSpanId: null };
626
- return {
627
- prevSpanId: trace.spans[i - 1]?.spanId ?? null,
628
- nextSpanId: trace.spans[i + 1]?.spanId ?? null,
629
- };
630
- }, [activeSpanId, trace.spans]);
631
-
632
- const handleSelectPrevSpan = useCallback(() => {
633
- if (prevSpanId) navigateToSpan(prevSpanId);
634
- }, [prevSpanId, navigateToSpan]);
635
-
636
- const handleSelectNextSpan = useCallback(() => {
637
- if (nextSpanId) navigateToSpan(nextSpanId);
638
- }, [nextSpanId, navigateToSpan]);
639
-
640
- const prevSpanIdRef = useRef(prevSpanId);
641
- const nextSpanIdRef = useRef(nextSpanId);
642
- const navigateToSpanRef = useRef(navigateToSpan);
643
- prevSpanIdRef.current = prevSpanId;
644
- nextSpanIdRef.current = nextSpanId;
645
- navigateToSpanRef.current = navigateToSpan;
646
-
647
537
  return (
648
538
  <div
539
+ ref={paneRootRef}
649
540
  data-pane="pane-root"
650
- data-has-detail={activeSpan ? '' : undefined}
651
- className="relative grid w-full h-full max-h-full grid-cols-[minmax(100px,1fr)] data-[has-detail]:grid-cols-[minmax(100px,1fr)_clamp(280px,360px,100%)]"
541
+ className="relative flex w-full h-full max-h-full"
652
542
  >
653
543
  <div
654
544
  id="trace-parent"
655
- className="grid grid-rows-[1fr] h-full min-h-0 overflow-hidden relative bg-background-100"
545
+ className="flex-1 min-w-0 grid grid-rows-[1fr] h-full min-h-0 overflow-hidden relative bg-background-100"
656
546
  >
657
547
  <SplitPane
658
548
  scrollContainerRef={scrollContainerRef}
@@ -737,6 +627,7 @@ function NewTraceViewerContent({
737
627
  variant="muted"
738
628
  size="small"
739
629
  onClick={zoomOut}
630
+ disabled={isAtMinZoom}
740
631
  aria-label="Zoom out"
741
632
  >
742
633
  <ZoomOut className="w-4 h-4" />
@@ -753,6 +644,7 @@ function NewTraceViewerContent({
753
644
  variant="muted"
754
645
  size="small"
755
646
  onClick={zoomIn}
647
+ disabled={isAtMaxZoom}
756
648
  aria-label="Zoom in"
757
649
  >
758
650
  <ZoomIn className="w-4 h-4" />
@@ -760,84 +652,11 @@ function NewTraceViewerContent({
760
652
  </div>
761
653
  </div>
762
654
 
763
- {/* Detail panel */}
764
- {activeSpan ? (
765
- <aside className="flex flex-col h-full max-h-full bg-background-100 border-l border-gray-alpha-400 overflow-auto">
766
- {/* Panel header */}
767
- <div className="flex items-center justify-between gap-2 shrink-0 px-4 pt-3 pb-3">
768
- <span className="text-label-14 font-medium text-gray-1000 truncate block">
769
- {selectedSpanName}
770
- </span>
771
- <div className="flex items-center gap-0.5 shrink-0">
772
- <Tooltip>
773
- <TooltipTrigger asChild>
774
- <IconButton
775
- aria-label="Navigate up"
776
- aria-keyshortcuts="K"
777
- onClick={handleSelectPrevSpan}
778
- disabled={!prevSpanId}
779
- >
780
- <ChevronUp className="w-4 h-4" />
781
- </IconButton>
782
- </TooltipTrigger>
783
- {prevSpanId ? (
784
- <TooltipContent>
785
- Navigate up
786
- <Kbd>K</Kbd>
787
- </TooltipContent>
788
- ) : null}
789
- </Tooltip>
790
- <Tooltip>
791
- <TooltipTrigger asChild>
792
- <IconButton
793
- aria-label="Navigate down"
794
- aria-keyshortcuts="J"
795
- onClick={handleSelectNextSpan}
796
- disabled={!nextSpanId}
797
- >
798
- <ChevronDown className="w-4 h-4" />
799
- </IconButton>
800
- </TooltipTrigger>
801
- {nextSpanId ? (
802
- <TooltipContent>
803
- Navigate down
804
- <Kbd>J</Kbd>
805
- </TooltipContent>
806
- ) : null}
807
- </Tooltip>
808
- <div aria-hidden className="w-px h-4 bg-gray-alpha-400 mx-1" />
809
- <IconButton
810
- aria-label="Close span details"
811
- aria-keyshortcuts="Escape"
812
- onClick={handleClearActiveSpan}
813
- >
814
- <X className="w-4 h-4" />
815
- </IconButton>
816
- </div>
817
- </div>
818
- {/* Panel body */}
819
- <div className="flex-1 overflow-y-auto">
820
- <ErrorBoundary>
821
- <EntityDetailPanel
822
- run={sidebar.run}
823
- onStreamClick={sidebar.onStreamClick}
824
- onRunClick={sidebar.onRunClick}
825
- fetchSpanDetail={sidebar.fetchSpanDetail}
826
- onWakeUpSleep={sidebar.onWakeUpSleep}
827
- onLoadEventData={sidebar.onLoadEventData}
828
- onResolveHook={sidebar.onResolveHook}
829
- encryptionKey={sidebar.encryptionKey}
830
- onDecrypt={sidebar.onDecrypt}
831
- isDecrypting={sidebar.isDecrypting}
832
- selectedSpan={selectedSpan}
833
- showSeparateEventOccurrenceTimestamps={
834
- sidebar.showSeparateEventOccurrenceTimestamps
835
- }
836
- />
837
- </ErrorBoundary>
838
- </div>
839
- </aside>
840
- ) : null}
655
+ <TraceDetailPanel
656
+ containerRef={paneRootRef}
657
+ onNavigateToSpan={navigateToSpan}
658
+ onClose={handleClearActiveSpan}
659
+ />
841
660
 
842
661
  <TraceShortcutHelper
843
662
  hasMultipleSpans={trace.spans.length > 1}