@workflow/web-shared 4.1.0-beta.52 → 4.1.0-beta.53
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.
- package/dist/components/error-boundary.d.ts +15 -20
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +17 -31
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/event-list-view.d.ts +7 -6
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +492 -109
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/run-trace-view.d.ts +2 -1
- package/dist/components/run-trace-view.d.ts.map +1 -1
- package/dist/components/run-trace-view.js +2 -2
- package/dist/components/run-trace-view.js.map +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts +2 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +53 -142
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/conversation-view.d.ts.map +1 -1
- package/dist/components/sidebar/conversation-view.js +3 -17
- package/dist/components/sidebar/conversation-view.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +63 -10
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +4 -8
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/sidebar/resolve-hook-modal.d.ts +3 -0
- package/dist/components/sidebar/resolve-hook-modal.d.ts.map +1 -1
- package/dist/components/sidebar/resolve-hook-modal.js +152 -3
- package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
- package/dist/components/stream-viewer.d.ts +7 -5
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +54 -22
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer/components/markers.d.ts +2 -1
- package/dist/components/trace-viewer/components/markers.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/markers.js +59 -20
- package/dist/components/trace-viewer/components/markers.js.map +1 -1
- package/dist/components/trace-viewer/components/node.d.ts +5 -1
- package/dist/components/trace-viewer/components/node.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/node.js +250 -68
- package/dist/components/trace-viewer/components/node.js.map +1 -1
- package/dist/components/trace-viewer/components/span-content.d.ts +19 -0
- package/dist/components/trace-viewer/components/span-content.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-content.js +137 -0
- package/dist/components/trace-viewer/components/span-content.js.map +1 -0
- package/dist/components/trace-viewer/components/span-detail-panel.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js +3 -2
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
- package/dist/components/trace-viewer/components/span-segments.d.ts +50 -0
- package/dist/components/trace-viewer/components/span-segments.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-segments.js +392 -0
- package/dist/components/trace-viewer/components/span-segments.js.map +1 -0
- package/dist/components/trace-viewer/components/span-strategies.d.ts +46 -0
- package/dist/components/trace-viewer/components/span-strategies.d.ts.map +1 -0
- package/dist/components/trace-viewer/components/span-strategies.js +108 -0
- package/dist/components/trace-viewer/components/span-strategies.js.map +1 -0
- package/dist/components/trace-viewer/context.d.ts +7 -6
- package/dist/components/trace-viewer/context.d.ts.map +1 -1
- package/dist/components/trace-viewer/context.js +47 -18
- package/dist/components/trace-viewer/context.js.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.d.ts +5 -1
- package/dist/components/trace-viewer/trace-viewer.d.ts.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.js +87 -11
- package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.module.css +179 -6
- package/dist/components/trace-viewer/util/timing.d.ts +5 -0
- package/dist/components/trace-viewer/util/timing.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/timing.js +12 -0
- package/dist/components/trace-viewer/util/timing.js.map +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.d.ts.map +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js +29 -17
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
- package/dist/components/trace-viewer/worker.js +3 -1
- package/dist/components/trace-viewer/worker.js.map +1 -1
- package/dist/components/ui/alert.js +3 -3
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/card.js +2 -2
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/data-inspector.d.ts +17 -0
- package/dist/components/ui/data-inspector.d.ts.map +1 -0
- package/dist/components/ui/data-inspector.js +184 -0
- package/dist/components/ui/data-inspector.js.map +1 -0
- package/dist/components/ui/error-card.d.ts.map +1 -1
- package/dist/components/ui/error-card.js +4 -1
- package/dist/components/ui/error-card.js.map +1 -1
- package/dist/components/ui/inspector-theme.d.ts +39 -24
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +90 -38
- package/dist/components/ui/inspector-theme.js.map +1 -1
- package/dist/components/ui/skeleton.d.ts +1 -1
- package/dist/components/ui/skeleton.d.ts.map +1 -1
- package/dist/components/ui/skeleton.js +2 -2
- package/dist/components/ui/skeleton.js.map +1 -1
- package/dist/components/workflow-trace-view.d.ts +3 -1
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +435 -21
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts +1 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +2 -2
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +17 -3
- package/dist/lib/hydration.js.map +1 -1
- package/dist/styles.css +186 -0
- package/package.json +8 -7
- package/src/components/error-boundary.tsx +29 -40
- package/src/components/event-list-view.tsx +1000 -287
- package/src/components/index.ts +1 -0
- package/src/components/run-trace-view.tsx +3 -0
- package/src/components/sidebar/attribute-panel.tsx +58 -258
- package/src/components/sidebar/conversation-view.tsx +30 -27
- package/src/components/sidebar/entity-detail-panel.tsx +86 -20
- package/src/components/sidebar/events-list.tsx +4 -11
- package/src/components/sidebar/resolve-hook-modal.tsx +206 -47
- package/src/components/stream-viewer.tsx +138 -61
- package/src/components/trace-viewer/components/markers.tsx +69 -21
- package/src/components/trace-viewer/components/node.tsx +346 -100
- package/src/components/trace-viewer/components/span-content.tsx +247 -0
- package/src/components/trace-viewer/components/span-detail-panel.tsx +7 -2
- package/src/components/trace-viewer/components/span-segments.ts +516 -0
- package/src/components/trace-viewer/components/span-strategies.ts +205 -0
- package/src/components/trace-viewer/context.tsx +92 -40
- package/src/components/trace-viewer/trace-viewer.module.css +179 -6
- package/src/components/trace-viewer/trace-viewer.tsx +115 -11
- package/src/components/trace-viewer/util/timing.ts +13 -0
- package/src/components/trace-viewer/util/use-streaming-spans.ts +28 -17
- package/src/components/trace-viewer/worker.ts +4 -1
- package/src/components/ui/alert.tsx +3 -3
- package/src/components/ui/card.tsx +3 -5
- package/src/components/ui/data-inspector.tsx +318 -0
- package/src/components/ui/error-card.tsx +17 -6
- package/src/components/ui/inspector-theme.ts +127 -39
- package/src/components/ui/skeleton.tsx +3 -1
- package/src/components/workflow-trace-view.tsx +625 -26
- package/src/components/workflow-traces/trace-span-construction.ts +3 -2
- package/src/lib/hydration.ts +17 -8
- package/src/styles.css +186 -0
|
@@ -1,27 +1,116 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { DataInspector } from './ui/data-inspector';
|
|
5
|
+
import { Skeleton } from './ui/skeleton';
|
|
6
|
+
|
|
7
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// Helpers
|
|
9
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
function deserializeChunkText(text: string): string {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(text);
|
|
14
|
+
if (typeof parsed === 'string') {
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(parsed, null, 2);
|
|
18
|
+
} catch {
|
|
19
|
+
return text;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseChunkData(text: string): unknown {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
} catch {
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
32
|
+
// Types
|
|
33
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
6
34
|
|
|
7
35
|
export interface StreamChunk {
|
|
8
36
|
id: number;
|
|
9
|
-
|
|
10
|
-
data: unknown;
|
|
37
|
+
text: string;
|
|
11
38
|
}
|
|
12
39
|
|
|
40
|
+
type Chunk = StreamChunk;
|
|
41
|
+
|
|
13
42
|
interface StreamViewerProps {
|
|
14
43
|
streamId: string;
|
|
15
|
-
chunks:
|
|
44
|
+
chunks: Chunk[];
|
|
16
45
|
isLive: boolean;
|
|
17
46
|
error?: string | null;
|
|
47
|
+
/** True while the initial stream connection is being established */
|
|
48
|
+
isLoading?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
52
|
+
// Chunk row — memoized to prevent remounts during polling
|
|
53
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
const ChunkRow = React.memo(function ChunkRow({
|
|
56
|
+
chunk,
|
|
57
|
+
index,
|
|
58
|
+
}: {
|
|
59
|
+
chunk: Chunk;
|
|
60
|
+
index: number;
|
|
61
|
+
}) {
|
|
62
|
+
const parsed = parseChunkData(chunk.text);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
className="text-[11px] rounded-md border p-3"
|
|
67
|
+
style={{
|
|
68
|
+
borderColor: 'var(--ds-gray-300)',
|
|
69
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
70
|
+
}}
|
|
71
|
+
>
|
|
72
|
+
<span
|
|
73
|
+
className="select-none mr-2"
|
|
74
|
+
style={{ color: 'var(--ds-gray-500)' }}
|
|
75
|
+
>
|
|
76
|
+
[{index}]
|
|
77
|
+
</span>
|
|
78
|
+
{typeof parsed === 'string' ? (
|
|
79
|
+
<span
|
|
80
|
+
className="whitespace-pre-wrap break-words"
|
|
81
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
82
|
+
>
|
|
83
|
+
{deserializeChunkText(parsed)}
|
|
84
|
+
</span>
|
|
85
|
+
) : (
|
|
86
|
+
<DataInspector data={parsed} expandLevel={1} />
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
93
|
+
// Skeleton loading
|
|
94
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
function StreamSkeleton() {
|
|
97
|
+
return (
|
|
98
|
+
<div className="flex flex-col gap-3 animate-in fade-in">
|
|
99
|
+
<Skeleton style={{ width: 120, height: 16, borderRadius: 4 }} />
|
|
100
|
+
{[1, 2, 3, 4].map((i) => (
|
|
101
|
+
<Skeleton key={i} style={{ height: 56, borderRadius: 6 }} />
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
18
105
|
}
|
|
19
106
|
|
|
20
|
-
|
|
107
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
108
|
+
// Main component
|
|
109
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
21
110
|
|
|
22
111
|
/**
|
|
23
112
|
* StreamViewer component that displays real-time stream data.
|
|
24
|
-
* Each chunk is rendered with
|
|
113
|
+
* Each chunk is rendered with DataInspector for proper display
|
|
25
114
|
* of complex types (Map, Set, Date, custom classes, etc.).
|
|
26
115
|
*/
|
|
27
116
|
export function StreamViewer({
|
|
@@ -29,8 +118,8 @@ export function StreamViewer({
|
|
|
29
118
|
chunks,
|
|
30
119
|
isLive,
|
|
31
120
|
error,
|
|
121
|
+
isLoading,
|
|
32
122
|
}: StreamViewerProps) {
|
|
33
|
-
const isDark = useDarkMode();
|
|
34
123
|
const [hasMoreBelow, setHasMoreBelow] = useState(false);
|
|
35
124
|
const scrollRef = useRef<HTMLDivElement>(null);
|
|
36
125
|
|
|
@@ -50,36 +139,49 @@ export function StreamViewer({
|
|
|
50
139
|
checkScrollPosition();
|
|
51
140
|
}, [chunks.length, checkScrollPosition]);
|
|
52
141
|
|
|
53
|
-
|
|
142
|
+
// Show skeleton when loading and no chunks have arrived yet
|
|
143
|
+
if (isLoading && chunks.length === 0) {
|
|
144
|
+
return (
|
|
145
|
+
<div className="flex flex-col h-full pb-4">
|
|
146
|
+
<StreamSkeleton />
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
54
150
|
|
|
55
151
|
return (
|
|
56
152
|
<div className="flex flex-col h-full pb-4">
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
style={{ color: 'var(--ds-gray-900)' }}
|
|
61
|
-
title={streamId}
|
|
62
|
-
>
|
|
63
|
-
{streamId}
|
|
64
|
-
</code>
|
|
65
|
-
<span
|
|
66
|
-
className="text-xs flex items-center gap-1.5"
|
|
67
|
-
style={{
|
|
68
|
-
color: isLive ? 'var(--ds-green-700)' : 'var(--ds-gray-600)',
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
153
|
+
{/* Live indicator */}
|
|
154
|
+
{isLive && (
|
|
155
|
+
<div className="flex items-center gap-1.5 mb-3 px-1">
|
|
71
156
|
<span
|
|
72
157
|
className="inline-block w-2 h-2 rounded-full"
|
|
73
|
-
style={{
|
|
74
|
-
backgroundColor: isLive
|
|
75
|
-
? 'var(--ds-green-600)'
|
|
76
|
-
: 'var(--ds-gray-500)',
|
|
77
|
-
}}
|
|
158
|
+
style={{ backgroundColor: 'var(--ds-green-600)' }}
|
|
78
159
|
/>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
160
|
+
<span className="text-xs" style={{ color: 'var(--ds-green-700)' }}>
|
|
161
|
+
Live
|
|
162
|
+
</span>
|
|
163
|
+
</div>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
{/* Header */}
|
|
167
|
+
{chunks.length > 0 && (
|
|
168
|
+
<div className="flex items-center gap-2 mb-2 px-1">
|
|
169
|
+
<span
|
|
170
|
+
className="text-[13px] font-medium"
|
|
171
|
+
style={{ color: 'var(--ds-gray-900)' }}
|
|
172
|
+
>
|
|
173
|
+
Stream Chunks
|
|
174
|
+
</span>
|
|
175
|
+
<span
|
|
176
|
+
className="text-xs tabular-nums"
|
|
177
|
+
style={{ color: 'var(--ds-gray-600)' }}
|
|
178
|
+
>
|
|
179
|
+
({chunks.length})
|
|
180
|
+
</span>
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
82
183
|
|
|
184
|
+
{/* Content */}
|
|
83
185
|
<div className="relative flex-1 min-h-[200px]">
|
|
84
186
|
<div
|
|
85
187
|
ref={scrollRef}
|
|
@@ -111,36 +213,11 @@ export function StreamViewer({
|
|
|
111
213
|
</div>
|
|
112
214
|
) : (
|
|
113
215
|
chunks.map((chunk, index) => (
|
|
114
|
-
<
|
|
216
|
+
<ChunkRow
|
|
115
217
|
key={`${streamId}-chunk-${chunk.id}`}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}}
|
|
120
|
-
>
|
|
121
|
-
<span
|
|
122
|
-
className="select-none mr-2 text-[11px] font-mono"
|
|
123
|
-
style={{ color: 'var(--ds-gray-500)' }}
|
|
124
|
-
>
|
|
125
|
-
[{index}]
|
|
126
|
-
</span>
|
|
127
|
-
{typeof chunk.data === 'string' ? (
|
|
128
|
-
<span
|
|
129
|
-
className="text-[11px] font-mono"
|
|
130
|
-
style={{ color: 'var(--ds-gray-1000)' }}
|
|
131
|
-
>
|
|
132
|
-
{chunk.data}
|
|
133
|
-
</span>
|
|
134
|
-
) : (
|
|
135
|
-
<ObjectInspector
|
|
136
|
-
data={chunk.data}
|
|
137
|
-
// @ts-expect-error react-inspector accepts theme objects at runtime despite
|
|
138
|
-
// types declaring string only — see https://github.com/storybookjs/react-inspector/blob/main/README.md#theme
|
|
139
|
-
theme={theme}
|
|
140
|
-
expandLevel={1}
|
|
141
|
-
/>
|
|
142
|
-
)}
|
|
143
|
-
</div>
|
|
218
|
+
chunk={chunk}
|
|
219
|
+
index={index}
|
|
220
|
+
/>
|
|
144
221
|
))
|
|
145
222
|
)}
|
|
146
223
|
</div>
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
MutableRefObject,
|
|
8
8
|
ReactNode,
|
|
9
9
|
} from 'react';
|
|
10
|
-
import { useEffect, useMemo, useRef } from 'react';
|
|
10
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
11
11
|
import { type TraceViewerAction, useTraceViewer } from '../context';
|
|
12
12
|
import styles from '../trace-viewer.module.css';
|
|
13
13
|
import type {
|
|
@@ -23,32 +23,70 @@ import {
|
|
|
23
23
|
ROW_PADDING,
|
|
24
24
|
TIMELINE_PADDING,
|
|
25
25
|
} from '../util/constants';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
formatDurationForTimeline,
|
|
28
|
+
formatTimeSelection,
|
|
29
|
+
formatWallClockTime,
|
|
30
|
+
} from '../util/timing';
|
|
27
31
|
import { useImmediateStyle } from '../util/use-immediate-style';
|
|
28
32
|
import { useTrackpadZoom } from '../util/use-trackpad-zoom';
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Snap a raw duration to the nearest "nice" number in the 1-2-5 × 10^n
|
|
36
|
+
* sequence (e.g. …, 0.5, 1, 2, 5, 10, 20, 50, 100, …).
|
|
37
|
+
*/
|
|
38
|
+
function snapToNice(raw: number): number {
|
|
39
|
+
if (raw <= 0) return 1;
|
|
40
|
+
const log10 = Math.floor(Math.log10(raw));
|
|
41
|
+
const pow = 10 ** log10;
|
|
42
|
+
const normalized = raw / pow;
|
|
43
|
+
|
|
44
|
+
if (normalized <= 1.5) return pow;
|
|
45
|
+
if (normalized <= 3.5) return 2 * pow;
|
|
46
|
+
if (normalized <= 7.5) return 5 * pow;
|
|
47
|
+
return 10 * pow;
|
|
48
|
+
}
|
|
31
49
|
|
|
32
|
-
export function Markers({
|
|
50
|
+
export function Markers({
|
|
51
|
+
scale,
|
|
52
|
+
isLive = false,
|
|
53
|
+
}: {
|
|
54
|
+
scale: number;
|
|
55
|
+
isLive?: boolean;
|
|
56
|
+
}): ReactNode {
|
|
33
57
|
const {
|
|
34
58
|
state: { root },
|
|
35
59
|
} = useTraceViewer();
|
|
60
|
+
// Force a re-render every second when live to pick up new tick marks.
|
|
61
|
+
// The markers container width is grown at 60fps by useLiveTick;
|
|
62
|
+
// this interval only ensures the marker *labels* stay current.
|
|
63
|
+
const [, forceMarkerUpdate] = useState(0);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!isLive) return;
|
|
66
|
+
const id = setInterval(() => forceMarkerUpdate((v) => v + 1), 1000);
|
|
67
|
+
return () => clearInterval(id);
|
|
68
|
+
}, [isLive]);
|
|
36
69
|
|
|
37
70
|
const fullDuration = root.duration;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
71
|
+
|
|
72
|
+
// Calculate a marker interval that gives reasonable spacing at any zoom level.
|
|
73
|
+
// We target ~50px per notch mark; labels appear on every Nth notch via labelSpacing.
|
|
74
|
+
// When scale is <= 0 (e.g. the initial -1 sentinel), fall back to a safe default
|
|
75
|
+
// to avoid generating an absurd number of markers.
|
|
76
|
+
const effectiveScale =
|
|
77
|
+
scale > 0 ? scale : fullDuration > 0 ? 1 / fullDuration : 1;
|
|
78
|
+
const targetNotchPx = 50;
|
|
79
|
+
let markerDuration = snapToNice(targetNotchPx / effectiveScale);
|
|
80
|
+
markerDuration = Math.max(1, markerDuration);
|
|
81
|
+
let markerWidth = markerDuration * effectiveScale;
|
|
82
|
+
|
|
83
|
+
// Cap marker count to avoid creating too many DOM elements at extreme zoom
|
|
84
|
+
// on very long traces. Only the visible portion is shown on screen anyway.
|
|
85
|
+
const MAX_MARKERS = 1000;
|
|
86
|
+
if (fullDuration / markerDuration > MAX_MARKERS) {
|
|
87
|
+
markerDuration = snapToNice(fullDuration / MAX_MARKERS);
|
|
88
|
+
markerWidth = markerDuration * effectiveScale;
|
|
49
89
|
}
|
|
50
|
-
markerDuration /= divisor;
|
|
51
|
-
markerWidth /= divisor;
|
|
52
90
|
const markerCount = Math.ceil(fullDuration / markerDuration);
|
|
53
91
|
|
|
54
92
|
// How often labels should appear for markers, e.g. 3 === one label for every third marker
|
|
@@ -77,6 +115,9 @@ export function Markers({ scale }: { scale: number }): ReactNode {
|
|
|
77
115
|
{hasLabel ? (
|
|
78
116
|
<span className={styles.markerLabel}>
|
|
79
117
|
{formatDurationForTimeline(markerDuration * i)}
|
|
118
|
+
<span className={styles.markerClockTime}>
|
|
119
|
+
{formatWallClockTime(root.startTime + markerDuration * i)}
|
|
120
|
+
</span>
|
|
80
121
|
</span>
|
|
81
122
|
) : null}
|
|
82
123
|
</span>
|
|
@@ -327,13 +368,20 @@ export function CursorMarker({
|
|
|
327
368
|
cache.set(span.span.spanId, {});
|
|
328
369
|
}
|
|
329
370
|
|
|
330
|
-
// Event Hover
|
|
371
|
+
// Event Hover — only show the nearest event when multiple overlap
|
|
331
372
|
const eventSpreadPx = 12;
|
|
332
373
|
const eventSpreadMs = eventSpreadPx / scale;
|
|
374
|
+
let closestEvent: (typeof eventsRef.current)[number] | null = null;
|
|
375
|
+
let closestDist = Infinity;
|
|
333
376
|
for (const event of eventsRef.current) {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
377
|
+
const dist = Math.abs(event.timestamp - t);
|
|
378
|
+
if (dist <= eventSpreadMs && dist < closestDist) {
|
|
379
|
+
closestDist = dist;
|
|
380
|
+
closestEvent = event;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
for (const event of eventsRef.current) {
|
|
384
|
+
const isHovered = event === closestEvent;
|
|
337
385
|
if (event.isHovered === isHovered) continue;
|
|
338
386
|
event.isHovered = isHovered;
|
|
339
387
|
const $event = event.ref?.current;
|