@workflow/web-shared 5.0.0-beta.14 → 5.0.0-beta.16

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 (57) hide show
  1. package/dist/components/event-list-view.d.ts +1 -1
  2. package/dist/components/event-list-view.d.ts.map +1 -1
  3. package/dist/components/event-list-view.js +20 -3
  4. package/dist/components/new-trace-viewer/components/timeline.d.ts +1 -1
  5. package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -1
  6. package/dist/components/new-trace-viewer/components/timeline.js +61 -13
  7. package/dist/components/new-trace-viewer/components/timeline.module.css +31 -0
  8. package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -1
  9. package/dist/components/new-trace-viewer/trace-viewer.js +8 -9
  10. package/dist/components/new-trace-viewer/utils.d.ts +1 -1
  11. package/dist/components/new-trace-viewer/utils.d.ts.map +1 -1
  12. package/dist/components/new-trace-viewer/utils.js +12 -5
  13. package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
  14. package/dist/components/sidebar/attribute-panel.js +26 -32
  15. package/dist/components/sidebar/attributes-block.d.ts +28 -0
  16. package/dist/components/sidebar/attributes-block.d.ts.map +1 -0
  17. package/dist/components/sidebar/attributes-block.js +83 -0
  18. package/dist/components/sidebar/entity-detail-panel.js +2 -2
  19. package/dist/components/sidebar/events-list.d.ts.map +1 -1
  20. package/dist/components/sidebar/events-list.js +8 -1
  21. package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
  22. package/dist/components/trace-viewer/components/node.js +2 -1
  23. package/dist/components/ui/context-card.d.ts +47 -0
  24. package/dist/components/ui/context-card.d.ts.map +1 -0
  25. package/dist/components/ui/context-card.js +471 -0
  26. package/dist/components/ui/timestamp-tooltip.d.ts +34 -1
  27. package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
  28. package/dist/components/ui/timestamp-tooltip.js +80 -132
  29. package/dist/components/workflow-trace-view.d.ts.map +1 -1
  30. package/dist/components/workflow-trace-view.js +8 -10
  31. package/dist/components/workflow-traces/event-colors.d.ts +1 -0
  32. package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
  33. package/dist/components/workflow-traces/event-colors.js +12 -1
  34. package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
  35. package/dist/components/workflow-traces/trace-span-construction.js +2 -1
  36. package/dist/lib/trace-builder.d.ts +15 -0
  37. package/dist/lib/trace-builder.d.ts.map +1 -1
  38. package/dist/lib/trace-builder.js +27 -2
  39. package/dist/styles.css +9 -0
  40. package/package.json +6 -4
  41. package/src/components/event-list-view.tsx +25 -2
  42. package/src/components/new-trace-viewer/components/timeline.module.css +31 -0
  43. package/src/components/new-trace-viewer/components/timeline.tsx +134 -10
  44. package/src/components/new-trace-viewer/trace-viewer.tsx +17 -14
  45. package/src/components/new-trace-viewer/utils.ts +12 -4
  46. package/src/components/sidebar/attribute-panel.tsx +117 -119
  47. package/src/components/sidebar/attributes-block.tsx +182 -0
  48. package/src/components/sidebar/entity-detail-panel.tsx +1 -1
  49. package/src/components/sidebar/events-list.tsx +8 -0
  50. package/src/components/trace-viewer/components/node.tsx +3 -2
  51. package/src/components/ui/context-card.tsx +784 -0
  52. package/src/components/ui/timestamp-tooltip.tsx +132 -194
  53. package/src/components/workflow-trace-view.tsx +18 -10
  54. package/src/components/workflow-traces/event-colors.ts +12 -0
  55. package/src/components/workflow-traces/trace-span-construction.ts +1 -0
  56. package/src/lib/trace-builder.ts +32 -1
  57. package/src/styles.css +9 -0
@@ -1,15 +1,20 @@
1
1
  'use client';
2
2
 
3
3
  import type { ReactNode } from 'react';
4
- import { useEffect, useRef, useState } from 'react';
5
- import { createPortal } from 'react-dom';
4
+ import { useEffect, useState } from 'react';
5
+ import {
6
+ ContextCardProvider,
7
+ ContextCardTrigger,
8
+ type ContextCardTriggerProps,
9
+ useHasContextCardProvider,
10
+ } from './context-card';
6
11
 
7
12
  // ---------------------------------------------------------------------------
8
13
  // Time formatting helpers
9
14
  // ---------------------------------------------------------------------------
10
15
 
11
16
  interface TimeUnit {
12
- unit: string;
17
+ unit: Intl.RelativeTimeFormatUnit;
13
18
  ms: number;
14
19
  }
15
20
 
@@ -38,25 +43,69 @@ function formatTimeDifference(diff: number): string {
38
43
  return result.join(', ');
39
44
  }
40
45
 
41
- function useTimeAgo(date: number): string {
46
+ /**
47
+ * Detailed relative time string that auto-updates every second
48
+ * (e.g. "2 hours, 15 minutes, 30 seconds ago"). Used inside the hover card.
49
+ */
50
+ export function useTimeAgo(date: number): string {
42
51
  const [timeAgo, setTimeAgo] = useState<string>('');
43
52
 
44
53
  useEffect(() => {
45
- const update = (): void => {
54
+ const updateTimeAgo = (): void => {
46
55
  const diff = Date.now() - date;
47
- const formatted = formatTimeDifference(diff);
48
- setTimeAgo(formatted ? `${formatted} ago` : 'Just now');
56
+ const formattedDiff = formatTimeDifference(diff);
57
+ setTimeAgo(formattedDiff ? `${formattedDiff} ago` : 'Just now');
49
58
  };
50
- update();
51
- const timer = setInterval(update, 1000);
59
+
60
+ updateTimeAgo();
61
+ const timer = setInterval(updateTimeAgo, 1000);
52
62
  return () => clearInterval(timer);
53
63
  }, [date]);
54
64
 
55
65
  return timeAgo;
56
66
  }
57
67
 
68
+ /**
69
+ * Short relative time string that auto-updates every minute
70
+ * (e.g. "3 days ago", "5 hours ago"). Returns an empty string if `date` is
71
+ * nullish.
72
+ */
73
+ export function useShortTimeAgo(date: number | null | undefined): string {
74
+ const [shortTimeAgo, setShortTimeAgo] = useState<string>('');
75
+
76
+ useEffect(() => {
77
+ if (!date) {
78
+ setShortTimeAgo('');
79
+ return;
80
+ }
81
+
82
+ const updateShortTimeAgo = (): void => {
83
+ const diff = Date.now() - date;
84
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
85
+ const hours = Math.floor(diff / (1000 * 60 * 60));
86
+ const minutes = Math.floor(diff / (1000 * 60));
87
+
88
+ if (days > 0) {
89
+ setShortTimeAgo(`${days} day${days > 1 ? 's' : ''} ago`);
90
+ } else if (hours > 0) {
91
+ setShortTimeAgo(`${hours} hour${hours > 1 ? 's' : ''} ago`);
92
+ } else if (minutes > 0) {
93
+ setShortTimeAgo(`${minutes} minute${minutes > 1 ? 's' : ''} ago`);
94
+ } else {
95
+ setShortTimeAgo('Just now');
96
+ }
97
+ };
98
+
99
+ updateShortTimeAgo();
100
+ const timer = setInterval(updateShortTimeAgo, 60000);
101
+ return () => clearInterval(timer);
102
+ }, [date]);
103
+
104
+ return shortTimeAgo;
105
+ }
106
+
58
107
  // ---------------------------------------------------------------------------
59
- // Timezone row
108
+ // Hover card content
60
109
  // ---------------------------------------------------------------------------
61
110
 
62
111
  function ZoneDateTimeRow({
@@ -91,86 +140,34 @@ function ZoneDateTimeRow({
91
140
  });
92
141
 
93
142
  return (
94
- <div
95
- style={{
96
- display: 'flex',
97
- alignItems: 'center',
98
- justifyContent: 'space-between',
99
- gap: 12,
100
- }}
101
- >
102
- <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
103
- <div
104
- style={{
105
- display: 'inline-flex',
106
- alignItems: 'center',
107
- justifyContent: 'center',
108
- height: 16,
109
- padding: '0 6px',
110
- backgroundColor: 'var(--ds-gray-200)',
111
- borderRadius: 3,
112
- fontSize: 11,
113
- fontFamily: 'var(--font-mono, monospace)',
114
- fontWeight: 500,
115
- color: 'var(--ds-gray-900)',
116
- whiteSpace: 'nowrap',
117
- }}
118
- >
119
- {formattedZone}
143
+ <div className="flex items-center justify-between gap-3">
144
+ <div className="flex items-center gap-1.5">
145
+ <div className="flex items-center justify-center h-4 px-1.5 bg-gray-200 rounded-xs">
146
+ <span className="text-[12px] font-mono text-gray-900">
147
+ {formattedZone}
148
+ </span>
120
149
  </div>
121
- <span
122
- style={{
123
- fontSize: 13,
124
- color: 'var(--ds-gray-1000)',
125
- whiteSpace: 'nowrap',
126
- }}
127
- >
128
- {formattedDate}
129
- </span>
150
+ <span className="text-[13px] text-gray-1000">{formattedDate}</span>
130
151
  </div>
131
- <span
132
- style={{
133
- fontSize: 11,
134
- fontFamily: 'var(--font-mono, monospace)',
135
- fontVariantNumeric: 'tabular-nums',
136
- color: 'var(--ds-gray-900)',
137
- whiteSpace: 'nowrap',
138
- }}
139
- >
152
+ <span className="tabular-nums text-[12px] font-mono text-gray-900">
140
153
  {formattedTime}
141
154
  </span>
142
155
  </div>
143
156
  );
144
157
  }
145
158
 
146
- // ---------------------------------------------------------------------------
147
- // Tooltip card content
148
- // ---------------------------------------------------------------------------
149
-
150
- function TimestampTooltipContent({ date }: { date: number }): ReactNode {
159
+ function RelativeTimeContextCardContent({ date }: { date: number }): ReactNode {
151
160
  const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
152
161
  const timeAgo = useTimeAgo(date);
153
162
 
154
163
  return (
155
- <div
156
- style={{
157
- display: 'flex',
158
- flexDirection: 'column',
159
- gap: 12,
160
- minWidth: 300,
161
- padding: '12px 14px',
162
- }}
163
- >
164
- <span
165
- style={{
166
- fontSize: 13,
167
- fontVariantNumeric: 'tabular-nums',
168
- color: 'var(--ds-gray-900)',
169
- }}
170
- >
171
- {timeAgo}
172
- </span>
173
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
164
+ <div className="flex flex-col gap-3 min-w-[300px]">
165
+ <div className="flex flex-col gap-3">
166
+ <span className="tabular-nums text-[13px] text-gray-900">
167
+ {timeAgo}
168
+ </span>
169
+ </div>
170
+ <div className="flex flex-col gap-2">
174
171
  <ZoneDateTimeRow date={date} zone="UTC" />
175
172
  <ZoneDateTimeRow date={date} zone={localTimezone} />
176
173
  </div>
@@ -178,102 +175,75 @@ function TimestampTooltipContent({ date }: { date: number }): ReactNode {
178
175
  );
179
176
  }
180
177
 
178
+ function DefaultTimeText({
179
+ date,
180
+ }: {
181
+ date: number | null | undefined;
182
+ }): ReactNode {
183
+ const shortTimeAgo = useShortTimeAgo(date);
184
+ return <span className="text-label-14 text-gray-900">{shortTimeAgo}</span>;
185
+ }
186
+
181
187
  // ---------------------------------------------------------------------------
182
- // Hover tooltip wrapper
188
+ // RelativeTimeCard
183
189
  // ---------------------------------------------------------------------------
184
190
 
185
- const TOOLTIP_WIDTH = 330;
186
- const VIEWPORT_PAD = 8;
187
-
188
- function TooltipPortal({
189
- triggerRect,
190
- onMouseEnter,
191
- onMouseLeave,
191
+ type RelativeTimeCardProps = Omit<
192
+ ContextCardTriggerProps,
193
+ 'content' | 'children'
194
+ > & {
195
+ /** Timestamp in milliseconds to display as a relative time. */
196
+ date?: number | null;
197
+ /** Custom content to render instead of the default relative time text. */
198
+ children?: ReactNode;
199
+ };
200
+
201
+ /**
202
+ * Relative time label that reveals a context card with detailed UTC and local
203
+ * timestamps on hover. Renders a default short relative time label (e.g.
204
+ * "3 days ago") when `children` is omitted; renders just the children without
205
+ * the hover card when `date` is nullish.
206
+ */
207
+ export function RelativeTimeCard({
192
208
  date,
193
- }: {
194
- triggerRect: DOMRect;
195
- onMouseEnter: () => void;
196
- onMouseLeave: () => void;
197
- date: number;
198
- }): ReactNode {
199
- const tooltipRef = useRef<HTMLDivElement>(null);
200
- const [style, setStyle] = useState<React.CSSProperties>({
201
- position: 'fixed',
202
- zIndex: 9999,
203
- visibility: 'hidden',
204
- });
209
+ children: _children,
210
+ ...props
211
+ }: RelativeTimeCardProps): ReactNode {
212
+ const children =
213
+ _children === undefined ? <DefaultTimeText date={date} /> : _children;
205
214
 
206
- useEffect(() => {
207
- const placement = triggerRect.top > 240 ? 'above' : 'below';
208
- const centerX = triggerRect.left + triggerRect.width / 2;
209
-
210
- const el = tooltipRef.current;
211
- const w = el ? el.offsetWidth : TOOLTIP_WIDTH;
212
- const h = el ? el.offsetHeight : 100;
213
-
214
- let left = centerX - w / 2;
215
- left = Math.max(
216
- VIEWPORT_PAD,
217
- Math.min(left, window.innerWidth - w - VIEWPORT_PAD)
218
- );
219
-
220
- let top: number;
221
- if (placement === 'above') {
222
- top = triggerRect.top - h - 6;
223
- if (top < VIEWPORT_PAD) {
224
- top = triggerRect.bottom + 6;
225
- }
226
- } else {
227
- top = triggerRect.bottom + 6;
228
- if (top + h > window.innerHeight - VIEWPORT_PAD) {
229
- top = triggerRect.top - h - 6;
230
- }
231
- }
215
+ if (!date) return children;
232
216
 
233
- setStyle({
234
- position: 'fixed',
235
- left,
236
- top,
237
- zIndex: 9999,
238
- borderRadius: 10,
239
- border: '1px solid var(--ds-gray-alpha-200)',
240
- backgroundColor: 'var(--ds-background-100)',
241
- boxShadow: '0 4px 12px rgba(0,0,0,0.08), 0 1px 3px rgba(0,0,0,0.06)',
242
- visibility: 'visible',
243
- });
244
- }, [triggerRect]);
245
-
246
- return createPortal(
247
- // biome-ignore lint/a11y/noStaticElementInteractions: tooltip hover zone
248
- <div
249
- ref={tooltipRef}
250
- onMouseEnter={onMouseEnter}
251
- onMouseLeave={onMouseLeave}
252
- style={style}
217
+ return (
218
+ <ContextCardTrigger
219
+ content={<RelativeTimeContextCardContent date={date} />}
220
+ {...props}
253
221
  >
254
- <TimestampTooltipContent date={date} />
255
- </div>,
256
- document.body
222
+ {children}
223
+ </ContextCardTrigger>
257
224
  );
258
225
  }
259
226
 
227
+ // ---------------------------------------------------------------------------
228
+ // TimestampTooltip — convenience wrapper used across the observability UI
229
+ // ---------------------------------------------------------------------------
230
+
231
+ /**
232
+ * Wraps an already-formatted timestamp display with a relative-time hover
233
+ * card. Self-mounts a {@link ContextCardProvider} when one isn't already
234
+ * present so it works anywhere, but shares a provider (enabling the animated
235
+ * card morph between adjacent timestamps) when rendered inside one.
236
+ */
260
237
  export function TimestampTooltip({
261
238
  date,
262
239
  children,
240
+ side = 'top',
263
241
  }: {
264
242
  date: number | Date | string | null | undefined;
265
243
  children: ReactNode;
244
+ side?: ContextCardTriggerProps['side'];
266
245
  }): ReactNode {
267
- const [open, setOpen] = useState(false);
268
- const [triggerRect, setTriggerRect] = useState<DOMRect | null>(null);
269
- const triggerRef = useRef<HTMLSpanElement>(null);
270
- const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
271
-
272
- useEffect(() => {
273
- return () => {
274
- if (closeTimer.current) clearTimeout(closeTimer.current);
275
- };
276
- }, []);
246
+ const hasProvider = useHasContextCardProvider();
277
247
 
278
248
  const ts =
279
249
  date == null
@@ -284,43 +254,11 @@ export function TimestampTooltip({
284
254
 
285
255
  if (ts == null || Number.isNaN(ts)) return <>{children}</>;
286
256
 
287
- const cancelClose = () => {
288
- if (closeTimer.current) {
289
- clearTimeout(closeTimer.current);
290
- closeTimer.current = null;
291
- }
292
- };
293
-
294
- const scheduleClose = () => {
295
- cancelClose();
296
- closeTimer.current = setTimeout(() => setOpen(false), 120);
297
- };
298
-
299
- const handleOpen = () => {
300
- cancelClose();
301
- if (triggerRef.current) {
302
- setTriggerRect(triggerRef.current.getBoundingClientRect());
303
- }
304
- setOpen(true);
305
- };
306
-
307
- return (
308
- // biome-ignore lint/a11y/noStaticElementInteractions: tooltip trigger
309
- <span
310
- ref={triggerRef}
311
- onMouseEnter={handleOpen}
312
- onMouseLeave={scheduleClose}
313
- style={{ display: 'inline-flex' }}
314
- >
257
+ const card = (
258
+ <RelativeTimeCard date={ts} side={side} asChild>
315
259
  {children}
316
- {open && triggerRect && (
317
- <TooltipPortal
318
- triggerRect={triggerRect}
319
- onMouseEnter={cancelClose}
320
- onMouseLeave={scheduleClose}
321
- date={ts}
322
- />
323
- )}
324
- </span>
260
+ </RelativeTimeCard>
325
261
  );
262
+
263
+ return hasProvider ? card : <ContextCardProvider>{card}</ContextCardProvider>;
326
264
  }
@@ -17,7 +17,11 @@ import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
17
17
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
18
18
  import { createPortal } from 'react-dom';
19
19
  import { useToast } from '../lib/toast';
20
- import { buildTrace, type TraceWithMeta } from '../lib/trace-builder';
20
+ import {
21
+ buildTrace,
22
+ filterSpanRawEvents,
23
+ type TraceWithMeta,
24
+ } from '../lib/trace-builder';
21
25
  import { ErrorBoundary } from './error-boundary';
22
26
  import {
23
27
  EntityDetailPanel,
@@ -862,12 +866,14 @@ export const WorkflowTraceViewer = ({
862
866
  const handleSelectionChange = useCallback(
863
867
  (info: SelectedSpanInfo | null) => {
864
868
  if (info) {
865
- // Filter raw events by the selected span's correlationId (stepId/hookId)
866
- // This bypasses the trace worker pipeline entirely.
867
- const correlationId = info.spanId;
868
- const rawEvents = correlationId
869
- ? events.filter((e) => e.correlationId === correlationId)
870
- : [];
869
+ // Filter raw events for the selected span: child spans match on
870
+ // correlationId (stepId/hookId), the run root span gets run-level
871
+ // events. This bypasses the trace worker pipeline entirely.
872
+ const rawEvents = filterSpanRawEvents(
873
+ events,
874
+ info.resource,
875
+ info.spanId
876
+ );
871
877
  setSelectedSpan({ ...info, rawEvents });
872
878
  } else {
873
879
  setSelectedSpan(null);
@@ -881,8 +887,10 @@ export const WorkflowTraceViewer = ({
881
887
  const correlationId = selectedSpan?.spanId;
882
888
  if (!correlationId) return;
883
889
 
884
- const nextRawEvents = events.filter(
885
- (e) => e.correlationId === correlationId
890
+ const nextRawEvents = filterSpanRawEvents(
891
+ events,
892
+ selectedSpan?.resource,
893
+ correlationId
886
894
  );
887
895
  setSelectedSpan((prev) => {
888
896
  if (!prev || prev.spanId !== correlationId) {
@@ -904,7 +912,7 @@ export const WorkflowTraceViewer = ({
904
912
  rawEvents: nextRawEvents,
905
913
  };
906
914
  });
907
- }, [events, selectedSpan?.spanId]);
915
+ }, [events, selectedSpan?.spanId, selectedSpan?.resource]);
908
916
 
909
917
  // Reset selected span when navigating to a different run
910
918
  useEffect(() => {
@@ -22,6 +22,7 @@ export interface EventColorPalette {
22
22
  * - Red for failures (step_failed, run_failed)
23
23
  * - Orange/yellow for retries (step_retrying)
24
24
  * - Purple for webhook-related events
25
+ * - Teal for attribute changes (attr_set)
25
26
  * - Blue otherwise (default)
26
27
  */
27
28
  export function getEventColor(
@@ -64,6 +65,17 @@ export function getEventColor(
64
65
  };
65
66
  }
66
67
 
68
+ // Attribute changes - Teal
69
+ if (eventType === 'attr_set') {
70
+ return {
71
+ color: 'var(--ds-teal-900)',
72
+ background: 'var(--ds-teal-200)',
73
+ border: 'var(--ds-teal-500)',
74
+ text: 'var(--ds-teal-900)',
75
+ secondary: 'var(--ds-gray-900)',
76
+ };
77
+ }
78
+
67
79
  // Default - Blue
68
80
  return {
69
81
  color: 'var(--ds-blue-600)',
@@ -26,6 +26,7 @@ const MARKER_EVENT_TYPES: Set<Event['eventType']> = new Set([
26
26
  'run_failed',
27
27
  'wait_created',
28
28
  'wait_completed',
29
+ 'attr_set',
29
30
  ]);
30
31
 
31
32
  /**
@@ -12,8 +12,8 @@ import {
12
12
  hookToSpan,
13
13
  runToSpan,
14
14
  stepToSpan,
15
- waitToSpan,
16
15
  WORKFLOW_LIBRARY,
16
+ waitToSpan,
17
17
  } from '../components/workflow-traces/trace-span-construction';
18
18
  import { otelTimeToMs } from '../components/workflow-traces/trace-time-utils';
19
19
 
@@ -31,6 +31,37 @@ export const isHookLifecycleEvent = (eventType: string) =>
31
31
  eventType === 'hook_created' ||
32
32
  eventType === 'hook_disposed';
33
33
 
34
+ /**
35
+ * Events that belong to the run root span rather than a child entity span.
36
+ * Mirrors the fallthrough logic in {@link groupEventsByCorrelation}: events
37
+ * without a correlationId (run lifecycle) plus correlated events that aren't
38
+ * step/timer/hook events (e.g. `attr_set`, whose correlationId is a dedup
39
+ * token rather than a child entity ID).
40
+ */
41
+ export const isRunLevelEvent = (event: Event): boolean =>
42
+ !event.correlationId ||
43
+ (!isTimerEvent(event.eventType) &&
44
+ !isHookLifecycleEvent(event.eventType) &&
45
+ !isStepEvent(event.eventType));
46
+
47
+ /**
48
+ * Filter the raw event list down to the events for a selected span.
49
+ * For the run root span this returns run-level events (see
50
+ * {@link isRunLevelEvent}); for child spans it returns the events
51
+ * correlated to that span's ID.
52
+ */
53
+ export function filterSpanRawEvents(
54
+ events: Event[],
55
+ resource: string | undefined,
56
+ spanId: string | undefined
57
+ ): Event[] {
58
+ if (resource === 'run') {
59
+ return events.filter(isRunLevelEvent);
60
+ }
61
+ if (!spanId) return [];
62
+ return events.filter((e) => e.correlationId === spanId);
63
+ }
64
+
34
65
  // ---------------------------------------------------------------------------
35
66
  // Event grouping
36
67
  // ---------------------------------------------------------------------------
package/src/styles.css CHANGED
@@ -12,6 +12,9 @@
12
12
  * import '@workflow/web-shared/styles.css';
13
13
  */
14
14
 
15
+ @custom-variant dark-theme (&:where(.dark-theme, .dark-theme *, .dark, .dark *, [data-theme="dark"], [data-theme="dark"] *));
16
+ @custom-variant light-theme (&:where(.light-theme, .light-theme *, .light, .light *));
17
+
15
18
  :root {
16
19
  /* Background */
17
20
  --ds-background-100: hsl(0, 0%, 100%);
@@ -103,6 +106,9 @@
103
106
 
104
107
  /* Shadows */
105
108
  --ds-shadow-small: 0px 1px 2px rgba(0, 0, 0, 0.04);
109
+ --ds-shadow-tooltip:
110
+ 0 0 0 1px rgba(0, 0, 0, 0.08), 0px 1px 1px rgba(0, 0, 0, 0.02),
111
+ 0px 4px 8px rgba(0, 0, 0, 0.04), 0 0 0 1px var(--ds-background-200);
106
112
  }
107
113
 
108
114
  /* Dark theme defaults */
@@ -187,6 +193,9 @@
187
193
  --ds-pink-900: hsl(336, 80%, 70%);
188
194
 
189
195
  --ds-shadow-small: 0px 1px 2px rgba(0, 0, 0, 0.3);
196
+ --ds-shadow-tooltip:
197
+ 0 0 0 1px rgba(255, 255, 255, 0.145), 0px 1px 1px rgba(0, 0, 0, 0.02),
198
+ 0px 4px 8px rgba(0, 0, 0, 0.04), 0 0 0 1px var(--ds-background-200);
190
199
  }
191
200
 
192
201
  @theme inline {