@workflow/web-shared 5.0.0-beta.43 → 5.0.0-beta.44
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/README.md +4 -4
- package/dist/components/event-list-view.d.ts +12 -0
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +41 -20
- package/dist/components/sidebar/attribute-panel.d.ts +6 -2
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +10 -8
- package/dist/components/sidebar/copyable-data-block.d.ts.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +2 -2
- package/dist/components/sidebar/entity-detail-panel.d.ts +6 -2
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +13 -5
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +12 -5
- package/dist/components/sidebar/resolve-hook-modal.js +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +2 -0
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -1
- package/dist/components/sidebar/sidebar-data-context.js +1 -1
- package/dist/components/stream-viewer.js +2 -2
- package/dist/components/trace-viewer/components/detail-panel-width.d.ts +4 -4
- package/dist/components/trace-viewer/components/detail-panel-width.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/detail-panel-width.js +5 -5
- package/dist/components/trace-viewer/components/detail-panel.d.ts +1 -1
- package/dist/components/trace-viewer/components/detail-panel.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/detail-panel.js +3 -3
- package/dist/components/trace-viewer/components/draggable-border.d.ts +1 -1
- package/dist/components/trace-viewer/components/draggable-border.js +2 -2
- package/dist/components/trace-viewer/components/minimap.js +4 -4
- package/dist/components/trace-viewer/components/span-markers.d.ts +8 -9
- package/dist/components/trace-viewer/components/span-markers.d.ts.map +1 -1
- package/dist/components/trace-viewer/components/span-markers.js +13 -10
- package/dist/components/trace-viewer/components/timeline.d.ts +1 -1
- package/dist/components/trace-viewer/components/timeline.js +4 -4
- package/dist/components/trace-viewer/components/use-row-window.d.ts +1 -1
- package/dist/components/trace-viewer/components/use-row-window.js +2 -2
- package/dist/components/trace-viewer/trace-viewer.js +3 -3
- package/dist/components/trace-viewer/utils.d.ts +3 -1
- package/dist/components/trace-viewer/utils.d.ts.map +1 -1
- package/dist/components/trace-viewer/utils.js +6 -5
- package/dist/components/ui/data-inspector.d.ts +7 -1
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +10 -5
- package/dist/components/ui/decrypt-button.d.ts +5 -1
- package/dist/components/ui/decrypt-button.d.ts.map +1 -1
- package/dist/components/ui/decrypt-button.js +4 -4
- package/dist/components/ui/duplicate-event-tooltip.d.ts +15 -5
- package/dist/components/ui/duplicate-event-tooltip.d.ts.map +1 -1
- package/dist/components/ui/duplicate-event-tooltip.js +18 -9
- package/dist/components/ui/error-stack-block.js +4 -4
- package/dist/components/ui/timestamp-tooltip.d.ts +5 -3
- package/dist/components/ui/timestamp-tooltip.d.ts.map +1 -1
- package/dist/components/ui/timestamp-tooltip.js +9 -9
- package/dist/components/workflow-traces/event-colors.d.ts.map +1 -1
- package/dist/components/workflow-traces/event-colors.js +11 -1
- package/dist/components/workflow-traces/trace-span-construction.d.ts.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +5 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/lib/duplicate-events.d.ts +1 -1
- package/dist/lib/duplicate-events.js +2 -2
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +8 -13
- package/dist/lib/sealed-events.d.ts +28 -0
- package/dist/lib/sealed-events.d.ts.map +1 -0
- package/dist/lib/sealed-events.js +30 -0
- package/dist/lib/trace-builder.d.ts.map +1 -1
- package/dist/lib/trace-builder.js +8 -5
- package/dist/lib/utils.d.ts +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/lib/zstd-browser-decoder.js +2 -2
- package/package.json +5 -5
- package/src/components/event-list-view.tsx +57 -22
- package/src/components/sidebar/attribute-panel.tsx +15 -7
- package/src/components/sidebar/copyable-data-block.tsx +8 -1
- package/src/components/sidebar/entity-detail-panel.tsx +18 -2
- package/src/components/sidebar/events-list.tsx +16 -6
- package/src/components/sidebar/resolve-hook-modal.tsx +2 -2
- package/src/components/sidebar/sidebar-data-context.tsx +2 -0
- package/src/components/stream-viewer.tsx +1 -1
- package/src/components/trace-viewer/components/detail-panel-width.ts +4 -4
- package/src/components/trace-viewer/components/detail-panel.tsx +3 -1
- package/src/components/trace-viewer/components/draggable-border.tsx +1 -1
- package/src/components/trace-viewer/components/minimap.tsx +3 -3
- package/src/components/trace-viewer/components/span-markers.tsx +18 -13
- package/src/components/trace-viewer/components/timeline.tsx +4 -4
- package/src/components/trace-viewer/components/use-row-window.ts +1 -1
- package/src/components/trace-viewer/trace-viewer.tsx +2 -2
- package/src/components/trace-viewer/utils.test.ts +5 -3
- package/src/components/trace-viewer/utils.ts +12 -8
- package/src/components/ui/data-inspector.tsx +19 -3
- package/src/components/ui/decrypt-button.tsx +9 -2
- package/src/components/ui/duplicate-event-tooltip.tsx +32 -10
- package/src/components/ui/error-stack-block.tsx +3 -3
- package/src/components/ui/timestamp-tooltip.tsx +16 -6
- package/src/components/workflow-traces/event-colors.ts +11 -0
- package/src/components/workflow-traces/trace-span-construction.test.ts +27 -1
- package/src/components/workflow-traces/trace-span-construction.ts +5 -3
- package/src/index.ts +4 -0
- package/src/lib/duplicate-events.ts +1 -1
- package/src/lib/hydration.ts +7 -12
- package/src/lib/sealed-events.ts +32 -0
- package/src/lib/trace-builder.test.ts +29 -1
- package/src/lib/trace-builder.ts +10 -5
- package/src/lib/utils.ts +1 -1
- package/src/lib/zstd-browser-decoder.ts +1 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* The package leaves WASM sourcing to the caller; we resolve the shipped
|
|
10
10
|
* `zstd.wasm` as a bundler asset (`new URL(..., import.meta.url)`, the same
|
|
11
|
-
* pattern the trace-viewer Worker uses) and compile it once, lazily
|
|
11
|
+
* pattern the trace-viewer Worker uses) and compile it once, lazily: the
|
|
12
12
|
* WASM is fetched only the first time a zstd payload is actually decoded.
|
|
13
13
|
*/
|
|
14
14
|
import { registerZstdDecoder } from '@workflow/core/serialization-format';
|
|
@@ -38,4 +38,4 @@ export function ensureZstdDecoderRegistered() {
|
|
|
38
38
|
return decompressBytes(wasmModule, payload);
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
41
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoienN0ZC1icm93c2VyLWRlY29kZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbGliL3pzdGQtYnJvd3Nlci1kZWNvZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7R0FZRztBQUNILE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHFDQUFxQyxDQUFDO0FBRTFFLElBQUksVUFBVSxHQUFHLEtBQUssQ0FBQztBQUN2QixJQUFJLGFBQXNELENBQUM7QUFFM0QsU0FBUyxjQUFjO0lBQ3JCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUNuQixNQUFNLEdBQUcsR0FBRyxJQUFJLEdBQUcsQ0FBQyxrQ0FBa0MsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3pFLGFBQWEsR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDO2FBQ3ZCLElBQUksQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO2FBQ2hDLElBQUksQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO0lBQ2pELENBQUM7SUFDRCxPQUFPLGFBQWEsQ0FBQztBQUN2QixDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSwyQkFBMkI7SUFDekMsSUFBSSxVQUFVO1FBQUUsT0FBTztJQUN2QixVQUFVLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLG1CQUFtQixDQUFDLEtBQUssRUFBRSxPQUFPLEVBQUUsRUFBRTtRQUNwQyxNQUFNLEVBQUUsZUFBZSxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsd0JBQXdCLENBQUMsQ0FBQztRQUNuRSxNQUFNLFVBQVUsR0FBRyxNQUFNLGNBQWMsRUFBRSxDQUFDO1FBQzFDLE9BQU8sZUFBZSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEJyb3dzZXIgenN0ZCBkZWNvZGVyIGZvciB0aGUgbzExeSByZWFkIHBhdGguXG4gKlxuICogVGhlIFdlYiBgRGVjb21wcmVzc2lvblN0cmVhbWAgaGFzIG5vIHpzdGQgc3VwcG9ydCwgc28gYEB3b3JrZmxvdy9jb3JlYCdzXG4gKiBgaHlkcmF0ZURhdGFXaXRoS2V5YCBkZWxlZ2F0ZXMgenN0ZCBpbmZsYXRpb24gdG8gYSBkZWNvZGVyIHJlZ2lzdGVyZWQgdmlhXG4gKiBgcmVnaXN0ZXJac3RkRGVjb2RlcmAuIFRoaXMgbW9kdWxlIHN1cHBsaWVzIHRoYXQgZGVjb2RlciwgYmFja2VkIGJ5IHRoZVxuICogYEB0b290YWxsbmF0ZS96c3RkLXdhc21gIHNpbmdsZS1maWxlIFdBU00gZGVjb2Rlci5cbiAqXG4gKiBUaGUgcGFja2FnZSBsZWF2ZXMgV0FTTSBzb3VyY2luZyB0byB0aGUgY2FsbGVyOyB3ZSByZXNvbHZlIHRoZSBzaGlwcGVkXG4gKiBgenN0ZC53YXNtYCBhcyBhIGJ1bmRsZXIgYXNzZXQgKGBuZXcgVVJMKC4uLiwgaW1wb3J0Lm1ldGEudXJsKWAsIHRoZSBzYW1lXG4gKiBwYXR0ZXJuIHRoZSB0cmFjZS12aWV3ZXIgV29ya2VyIHVzZXMpIGFuZCBjb21waWxlIGl0IG9uY2UsIGxhemlseTogdGhlXG4gKiBXQVNNIGlzIGZldGNoZWQgb25seSB0aGUgZmlyc3QgdGltZSBhIHpzdGQgcGF5bG9hZCBpcyBhY3R1YWxseSBkZWNvZGVkLlxuICovXG5pbXBvcnQgeyByZWdpc3RlclpzdGREZWNvZGVyIH0gZnJvbSAnQHdvcmtmbG93L2NvcmUvc2VyaWFsaXphdGlvbi1mb3JtYXQnO1xuXG5sZXQgcmVnaXN0ZXJlZCA9IGZhbHNlO1xubGV0IG1vZHVsZVByb21pc2U6IFByb21pc2U8V2ViQXNzZW1ibHkuTW9kdWxlPiB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gbG9hZFdhc21Nb2R1bGUoKTogUHJvbWlzZTxXZWJBc3NlbWJseS5Nb2R1bGU+IHtcbiAgaWYgKCFtb2R1bGVQcm9taXNlKSB7XG4gICAgY29uc3QgdXJsID0gbmV3IFVSTCgnQHRvb3RhbGxuYXRlL3pzdGQtd2FzbS96c3RkLndhc20nLCBpbXBvcnQubWV0YS51cmwpO1xuICAgIG1vZHVsZVByb21pc2UgPSBmZXRjaCh1cmwpXG4gICAgICAudGhlbigocmVzKSA9PiByZXMuYXJyYXlCdWZmZXIoKSlcbiAgICAgIC50aGVuKChieXRlcykgPT4gV2ViQXNzZW1ibHkuY29tcGlsZShieXRlcykpO1xuICB9XG4gIHJldHVybiBtb2R1bGVQcm9taXNlO1xufVxuXG4vKipcbiAqIFJlZ2lzdGVyIHRoZSBicm93c2VyIHpzdGQgZGVjb2RlciB3aXRoIGBAd29ya2Zsb3cvY29yZWAgKGlkZW1wb3RlbnQpLlxuICogQ2FsbCB0aGlzIGJlZm9yZSBoeWRyYXRpbmcgcGF5bG9hZHMgdGhhdCBtYXkgYmUgenN0ZC1jb21wcmVzc2VkOyB0aGVcbiAqIGFjdHVhbCBXQVNNIGNvbXBpbGUgKyBkZWNvZGUgaGFwcGVucyBsYXppbHkgb24gZmlyc3QgdXNlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZW5zdXJlWnN0ZERlY29kZXJSZWdpc3RlcmVkKCk6IHZvaWQge1xuICBpZiAocmVnaXN0ZXJlZCkgcmV0dXJuO1xuICByZWdpc3RlcmVkID0gdHJ1ZTtcbiAgcmVnaXN0ZXJac3RkRGVjb2Rlcihhc3luYyAocGF5bG9hZCkgPT4ge1xuICAgIGNvbnN0IHsgZGVjb21wcmVzc0J5dGVzIH0gPSBhd2FpdCBpbXBvcnQoJ0B0b290YWxsbmF0ZS96c3RkLXdhc20nKTtcbiAgICBjb25zdCB3YXNtTW9kdWxlID0gYXdhaXQgbG9hZFdhc21Nb2R1bGUoKTtcbiAgICByZXR1cm4gZGVjb21wcmVzc0J5dGVzKHdhc21Nb2R1bGUsIHBheWxvYWQpO1xuICB9KTtcbn1cbiJdfQ==
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workflow/web-shared",
|
|
3
3
|
"description": "Shared components for Workflow Observability UI",
|
|
4
|
-
"version": "5.0.0-beta.
|
|
4
|
+
"version": "5.0.0-beta.44",
|
|
5
5
|
"private": false,
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"streamdown": "2.3.0",
|
|
56
56
|
"tailwind-merge": "3.5.0",
|
|
57
57
|
"tailwindcss": "4",
|
|
58
|
-
"@workflow/core": "5.0.0-beta.
|
|
59
|
-
"@workflow/utils": "5.0.0-beta.
|
|
60
|
-
"@workflow/world": "5.0.0-beta.
|
|
58
|
+
"@workflow/core": "5.0.0-beta.44",
|
|
59
|
+
"@workflow/utils": "5.0.0-beta.9",
|
|
60
|
+
"@workflow/world": "5.0.0-beta.29"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@biomejs/biome": "^2.4.4",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"ai": "6.0.116",
|
|
68
68
|
"typescript": "^6.0.3",
|
|
69
69
|
"vitest": "^4.1.10",
|
|
70
|
-
"@workflow/errors": "5.0.0-beta.
|
|
70
|
+
"@workflow/errors": "5.0.0-beta.18",
|
|
71
71
|
"@workflow/tsconfig": "5.0.0-beta.0"
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
@@ -10,7 +10,10 @@ import type {
|
|
|
10
10
|
} from 'react';
|
|
11
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
12
|
import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
DUPLICATE_EVENT_MESSAGE,
|
|
15
|
+
findDuplicateEventIds,
|
|
16
|
+
} from '../lib/duplicate-events';
|
|
14
17
|
import {
|
|
15
18
|
type ExactIdSearchResult,
|
|
16
19
|
type ExactWorkflowSearchIdKind,
|
|
@@ -18,13 +21,14 @@ import {
|
|
|
18
21
|
parseExactWorkflowSearchId,
|
|
19
22
|
} from '../lib/exact-event-search-id';
|
|
20
23
|
import { isEncryptedMarker } from '../lib/hydration';
|
|
24
|
+
import { isSealedNoopEvent, SEALED_EVENT_MESSAGE } from '../lib/sealed-events';
|
|
21
25
|
import { useToast } from '../lib/toast';
|
|
22
26
|
import { formatDuration } from '../lib/utils';
|
|
23
27
|
import { AttrSetEventBlock } from './sidebar/attributes-block';
|
|
24
28
|
import { ContextCardProvider } from './ui/context-card';
|
|
25
29
|
import { DataInspector, DecryptClickContext } from './ui/data-inspector';
|
|
26
30
|
import { DecryptButton } from './ui/decrypt-button';
|
|
27
|
-
import {
|
|
31
|
+
import { EventNoticeTooltip } from './ui/duplicate-event-tooltip';
|
|
28
32
|
import {
|
|
29
33
|
ErrorStackBlock,
|
|
30
34
|
isStructuredError,
|
|
@@ -142,6 +146,11 @@ function getStatusDotColor(eventType: string): string {
|
|
|
142
146
|
) {
|
|
143
147
|
return 'var(--ds-blue-700)';
|
|
144
148
|
}
|
|
149
|
+
// Sealed positions → dim gray, one step quieter than pending: the row is
|
|
150
|
+
// log filler the run never observed.
|
|
151
|
+
if (eventType === 'noop') {
|
|
152
|
+
return 'var(--ds-gray-500)';
|
|
153
|
+
}
|
|
145
154
|
// Created/pending → gray
|
|
146
155
|
return 'var(--ds-gray-600)';
|
|
147
156
|
}
|
|
@@ -150,7 +159,7 @@ function getStatusDotColor(eventType: string): string {
|
|
|
150
159
|
* Build a map from correlationId (stepId) → display name using step_created
|
|
151
160
|
* events, and parse the workflow name from the run.
|
|
152
161
|
*/
|
|
153
|
-
function buildNameMaps(
|
|
162
|
+
export function buildNameMaps(
|
|
154
163
|
events: Event[] | null,
|
|
155
164
|
run: WorkflowRun | null
|
|
156
165
|
): {
|
|
@@ -164,7 +173,9 @@ function buildNameMaps(
|
|
|
164
173
|
for (const event of events) {
|
|
165
174
|
if (event.eventType === 'step_created' && event.correlationId) {
|
|
166
175
|
const stepName = event.eventData?.stepName ?? '';
|
|
167
|
-
const parsed =
|
|
176
|
+
const parsed =
|
|
177
|
+
parseStepName(String(stepName)) ??
|
|
178
|
+
parseWorkflowName(String(stepName));
|
|
168
179
|
correlationNameMap.set(
|
|
169
180
|
event.correlationId,
|
|
170
181
|
parsed?.shortName ?? stepName
|
|
@@ -235,7 +246,7 @@ export function buildDurationMap(
|
|
|
235
246
|
type === 'workflow_started'
|
|
236
247
|
) {
|
|
237
248
|
startedTimes.set(key, ts);
|
|
238
|
-
// The queued duration is anchored on the first start event only
|
|
249
|
+
// The queued duration is anchored on the first start event only, since
|
|
239
250
|
// subsequent step_started events come from retries.
|
|
240
251
|
if (!firstStartedTimes.has(key)) {
|
|
241
252
|
firstStartedTimes.set(key, ts);
|
|
@@ -295,14 +306,15 @@ function isRunLevel(eventType: string): boolean {
|
|
|
295
306
|
eventType === 'workflow_started' ||
|
|
296
307
|
eventType === 'workflow_completed' ||
|
|
297
308
|
eventType === 'workflow_failed' ||
|
|
298
|
-
// attr_set
|
|
299
|
-
// so
|
|
300
|
-
eventType === 'attr_set'
|
|
309
|
+
// attr_set and noop carry a dedup/positional correlationId rather than a
|
|
310
|
+
// child entity ID, so they group and label with the run itself.
|
|
311
|
+
eventType === 'attr_set' ||
|
|
312
|
+
eventType === 'noop'
|
|
301
313
|
);
|
|
302
314
|
}
|
|
303
315
|
|
|
304
316
|
// ──────────────────────────────────────────────────────────────────────────
|
|
305
|
-
// Tree gutter
|
|
317
|
+
// Tree gutter: fixed-width, shows branch lines only for the selected group
|
|
306
318
|
// ──────────────────────────────────────────────────────────────────────────
|
|
307
319
|
|
|
308
320
|
/** Fixed gutter width: 20px root area + 16px for one branch lane */
|
|
@@ -453,7 +465,7 @@ function TreeGutter({
|
|
|
453
465
|
}
|
|
454
466
|
|
|
455
467
|
// ──────────────────────────────────────────────────────────────────────────
|
|
456
|
-
// Copyable cell
|
|
468
|
+
// Copyable cell: shows a copy button on hover
|
|
457
469
|
// ──────────────────────────────────────────────────────────────────────────
|
|
458
470
|
|
|
459
471
|
function CopyableCell({
|
|
@@ -545,7 +557,7 @@ function deepParseJson(value: unknown): unknown {
|
|
|
545
557
|
}
|
|
546
558
|
if (value !== null && typeof value === 'object') {
|
|
547
559
|
// Preserve objects with custom constructors (e.g., encrypted markers,
|
|
548
|
-
// class instance refs)
|
|
560
|
+
// class instance refs); don't destructure them into plain objects
|
|
549
561
|
if (value.constructor !== Object) {
|
|
550
562
|
return value;
|
|
551
563
|
}
|
|
@@ -633,13 +645,13 @@ function PayloadBlock({
|
|
|
633
645
|
);
|
|
634
646
|
}
|
|
635
647
|
|
|
636
|
-
// Attribute changes
|
|
648
|
+
// Attribute changes: render the changed keys and the writer instead of
|
|
637
649
|
// the raw JSON payload.
|
|
638
650
|
if (eventType === 'attr_set') {
|
|
639
651
|
return <AttrSetEventBlock data={cleaned} />;
|
|
640
652
|
}
|
|
641
653
|
|
|
642
|
-
// Cancellation reason
|
|
654
|
+
// Cancellation reason: render the free-text reason as a readable line
|
|
643
655
|
// instead of a raw JSON payload (the only field run_cancelled carries).
|
|
644
656
|
if (eventType === 'run_cancelled') {
|
|
645
657
|
const cancelReason =
|
|
@@ -809,6 +821,10 @@ interface EventsListProps {
|
|
|
809
821
|
onDecrypt?: () => void;
|
|
810
822
|
/** Whether the encryption key is currently being fetched. */
|
|
811
823
|
isDecrypting?: boolean;
|
|
824
|
+
/** Whether decryption is unavailable. */
|
|
825
|
+
isDecryptDisabled?: boolean;
|
|
826
|
+
/** Explains why decryption is unavailable. */
|
|
827
|
+
decryptDisabledReason?: string;
|
|
812
828
|
/** Run-level hint: the run contains encrypted data (from probe). */
|
|
813
829
|
hasEncryptedData?: boolean;
|
|
814
830
|
/** Fetch events for an exact correlation or event ID. */
|
|
@@ -896,6 +912,12 @@ export function EventRow({
|
|
|
896
912
|
? '__run__'
|
|
897
913
|
: (event.correlationId ?? undefined);
|
|
898
914
|
|
|
915
|
+
const isSealed = isSealedNoopEvent(event);
|
|
916
|
+
const rowNotice = isDuplicate
|
|
917
|
+
? DUPLICATE_EVENT_MESSAGE
|
|
918
|
+
: isSealed
|
|
919
|
+
? SEALED_EVENT_MESSAGE
|
|
920
|
+
: undefined;
|
|
899
921
|
const statusDotColor = getStatusDotColor(event.eventType);
|
|
900
922
|
const createdAt = new Date(event.createdAt);
|
|
901
923
|
const occurredAt = parseEventDate(event.occurredAt);
|
|
@@ -1061,7 +1083,7 @@ export function EventRow({
|
|
|
1061
1083
|
isLaneEnd={isLaneEnd}
|
|
1062
1084
|
/>
|
|
1063
1085
|
|
|
1064
|
-
{/* Content area
|
|
1086
|
+
{/* Content area: dims when unrelated */}
|
|
1065
1087
|
<div
|
|
1066
1088
|
className="flex items-center flex-1 min-w-0"
|
|
1067
1089
|
style={{ opacity: contentOpacity, transition: 'opacity 150ms' }}
|
|
@@ -1109,11 +1131,11 @@ export function EventRow({
|
|
|
1109
1131
|
|
|
1110
1132
|
{/* Event Type */}
|
|
1111
1133
|
<div className="font-medium min-w-0 px-4" style={{ flex: '2 1 0%' }}>
|
|
1112
|
-
<
|
|
1134
|
+
<EventNoticeTooltip notice={rowNotice}>
|
|
1113
1135
|
<span
|
|
1114
1136
|
className="inline-flex items-center gap-1.5"
|
|
1115
1137
|
style={{
|
|
1116
|
-
color:
|
|
1138
|
+
color: rowNotice
|
|
1117
1139
|
? 'var(--ds-gray-700)'
|
|
1118
1140
|
: 'var(--ds-gray-900)',
|
|
1119
1141
|
}}
|
|
@@ -1151,7 +1173,7 @@ export function EventRow({
|
|
|
1151
1173
|
</span>
|
|
1152
1174
|
{formatEventType(event.eventType)}
|
|
1153
1175
|
</span>
|
|
1154
|
-
</
|
|
1176
|
+
</EventNoticeTooltip>
|
|
1155
1177
|
</div>
|
|
1156
1178
|
|
|
1157
1179
|
{/* Name */}
|
|
@@ -1179,10 +1201,10 @@ export function EventRow({
|
|
|
1179
1201
|
</div>
|
|
1180
1202
|
</div>
|
|
1181
1203
|
|
|
1182
|
-
{/* Expanded details
|
|
1204
|
+
{/* Expanded details: tree lines continue through this area */}
|
|
1183
1205
|
{isExpanded && (
|
|
1184
1206
|
<div className="flex">
|
|
1185
|
-
{/* Continuation gutter
|
|
1207
|
+
{/* Continuation gutter: lane line continues if not at lane end */}
|
|
1186
1208
|
<TreeGutter
|
|
1187
1209
|
isFirst={false}
|
|
1188
1210
|
isLast={isLast}
|
|
@@ -1286,6 +1308,8 @@ function EventListViewInner({
|
|
|
1286
1308
|
onSortOrderChange,
|
|
1287
1309
|
onDecrypt,
|
|
1288
1310
|
isDecrypting = false,
|
|
1311
|
+
isDecryptDisabled = false,
|
|
1312
|
+
decryptDisabledReason,
|
|
1289
1313
|
hasEncryptedData: hasEncryptedDataProp = false,
|
|
1290
1314
|
onExactIdSearch,
|
|
1291
1315
|
showSeparateEventOccurrenceTimestamps = false,
|
|
@@ -1414,7 +1438,7 @@ function EventListViewInner({
|
|
|
1414
1438
|
});
|
|
1415
1439
|
}, []);
|
|
1416
1440
|
|
|
1417
|
-
// Event data cache
|
|
1441
|
+
// Event data cache: ref avoids re-renders when cache updates
|
|
1418
1442
|
const eventDataCacheRef = useRef<Map<string, unknown>>(new Map());
|
|
1419
1443
|
const cacheEventData = useCallback((eventId: string, data: unknown) => {
|
|
1420
1444
|
eventDataCacheRef.current.set(eventId, data);
|
|
@@ -1645,7 +1669,16 @@ function EventListViewInner({
|
|
|
1645
1669
|
|
|
1646
1670
|
return (
|
|
1647
1671
|
<DecryptClickContext.Provider
|
|
1648
|
-
value={
|
|
1672
|
+
value={
|
|
1673
|
+
onDecrypt
|
|
1674
|
+
? {
|
|
1675
|
+
onDecrypt,
|
|
1676
|
+
isDecrypting,
|
|
1677
|
+
isDecryptDisabled,
|
|
1678
|
+
decryptDisabledReason,
|
|
1679
|
+
}
|
|
1680
|
+
: undefined
|
|
1681
|
+
}
|
|
1649
1682
|
>
|
|
1650
1683
|
<div className="h-full flex flex-col overflow-hidden">
|
|
1651
1684
|
<style>{`@keyframes workflow-dot-pulse{0%{transform:scale(1);opacity:.7}70%,100%{transform:scale(2.2);opacity:0}}`}</style>
|
|
@@ -1741,6 +1774,8 @@ function EventListViewInner({
|
|
|
1741
1774
|
<DecryptButton
|
|
1742
1775
|
decrypted={!!encryptionKey}
|
|
1743
1776
|
loading={isDecrypting}
|
|
1777
|
+
disabled={isDecryptDisabled}
|
|
1778
|
+
disabledReason={decryptDisabledReason}
|
|
1744
1779
|
onClick={onDecrypt}
|
|
1745
1780
|
/>
|
|
1746
1781
|
)}
|
|
@@ -1852,7 +1887,7 @@ function EventListViewInner({
|
|
|
1852
1887
|
/>
|
|
1853
1888
|
)}
|
|
1854
1889
|
|
|
1855
|
-
{/* Fixed footer
|
|
1890
|
+
{/* Fixed footer: count + load more */}
|
|
1856
1891
|
<div
|
|
1857
1892
|
className="relative flex-shrink-0 flex items-center h-10 border-t px-4 text-label-12"
|
|
1858
1893
|
style={{
|
|
@@ -199,7 +199,7 @@ function EncryptedFieldBlock() {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/**
|
|
202
|
-
* Inline display for an expired field
|
|
202
|
+
* Inline display for an expired field: flat label indicating data is no longer available.
|
|
203
203
|
*/
|
|
204
204
|
function ExpiredFieldBlock() {
|
|
205
205
|
return (
|
|
@@ -299,7 +299,7 @@ const attributeOrder: AttributeKey[] = [
|
|
|
299
299
|
|
|
300
300
|
/**
|
|
301
301
|
* Rank of an attribute in {@link attributeOrder}. Keys absent from that list
|
|
302
|
-
* sort after every listed one rather than before them
|
|
302
|
+
* sort after every listed one rather than before them: `indexOf` reports a
|
|
303
303
|
* miss as `-1`, which is truthy, so a `|| 0` fallback never fires.
|
|
304
304
|
*/
|
|
305
305
|
const attributeOrderIndex = (attribute: string): number => {
|
|
@@ -449,7 +449,7 @@ const attributeToDisplayFn: Record<
|
|
|
449
449
|
receivedCount: (value: unknown) => String(value),
|
|
450
450
|
lastReceivedAt: localMillisecondTimeOrNull,
|
|
451
451
|
disposedAt: localMillisecondTimeOrNull,
|
|
452
|
-
// Internal resume plumbing
|
|
452
|
+
// Internal resume plumbing, not surfaced in the UI
|
|
453
453
|
resumeContext: (_value: unknown) => null,
|
|
454
454
|
resumeId: (_value: unknown) => null,
|
|
455
455
|
resumeCapabilities: (_value: unknown) => null,
|
|
@@ -467,14 +467,14 @@ const attributeToDisplayFn: Record<
|
|
|
467
467
|
projectId: (_value: unknown) => null,
|
|
468
468
|
environment: (_value: unknown) => null,
|
|
469
469
|
executionContext: (_value: unknown) => null,
|
|
470
|
-
// Attributes
|
|
470
|
+
// Attributes: string-string metadata attached to the run.
|
|
471
471
|
// Rendered as key-value rows in its own collapsible section;
|
|
472
472
|
// if empty/missing, hidden by the hasDisplayContent gate.
|
|
473
473
|
attributes: (value: unknown) => {
|
|
474
474
|
if (!hasDisplayContent(value)) return null;
|
|
475
475
|
return <RunAttributesCard attributes={value as Record<string, string>} />;
|
|
476
476
|
},
|
|
477
|
-
// Dates
|
|
477
|
+
// Dates: wrapped with TimestampTooltip showing UTC/local + relative time
|
|
478
478
|
occurredAt: timestampWithTooltipOrNull,
|
|
479
479
|
createdAt: timestampWithTooltipOrNull,
|
|
480
480
|
startedAt: timestampWithTooltipOrNull,
|
|
@@ -678,7 +678,7 @@ const attributeToDisplayFn: Record<
|
|
|
678
678
|
},
|
|
679
679
|
// Internal encryption plumbing: the run's X25519 public key, used by
|
|
680
680
|
// cross-run writers to seal payloads to this run. Not actionable for users
|
|
681
|
-
// and not secret
|
|
681
|
+
// and not secret, so hidden rather than rendered as 44 opaque base64 chars.
|
|
682
682
|
encryptionPublicKey: (_value: unknown) => null,
|
|
683
683
|
};
|
|
684
684
|
|
|
@@ -836,6 +836,8 @@ export const AttributePanel = ({
|
|
|
836
836
|
onRunClick,
|
|
837
837
|
onDecrypt,
|
|
838
838
|
isDecrypting = false,
|
|
839
|
+
isDecryptDisabled = false,
|
|
840
|
+
decryptDisabledReason,
|
|
839
841
|
resource,
|
|
840
842
|
}: {
|
|
841
843
|
data: Record<string, unknown>;
|
|
@@ -852,7 +854,11 @@ export const AttributePanel = ({
|
|
|
852
854
|
onDecrypt?: () => void;
|
|
853
855
|
/** Whether decryption is currently in progress */
|
|
854
856
|
isDecrypting?: boolean;
|
|
855
|
-
/**
|
|
857
|
+
/** Whether decryption is unavailable */
|
|
858
|
+
isDecryptDisabled?: boolean;
|
|
859
|
+
/** Explains why decryption is unavailable */
|
|
860
|
+
decryptDisabledReason?: string;
|
|
861
|
+
/** Resource type of the selected span, used to show targeted loading skeletons. */
|
|
856
862
|
resource?: string;
|
|
857
863
|
}) => {
|
|
858
864
|
// Extract workflowCoreVersion from executionContext for display
|
|
@@ -943,6 +949,8 @@ export const AttributePanel = ({
|
|
|
943
949
|
? {
|
|
944
950
|
onDecrypt,
|
|
945
951
|
isDecrypting,
|
|
952
|
+
isDecryptDisabled,
|
|
953
|
+
decryptDisabledReason,
|
|
946
954
|
hasEncryptedData: outerDecryptCtx?.hasEncryptedData,
|
|
947
955
|
}
|
|
948
956
|
: outerDecryptCtx;
|
|
@@ -25,7 +25,14 @@ export function EncryptedDataBlock() {
|
|
|
25
25
|
</pre>
|
|
26
26
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
27
27
|
{ctx ? (
|
|
28
|
-
<Button
|
|
28
|
+
<Button
|
|
29
|
+
onClick={ctx.onDecrypt}
|
|
30
|
+
disabled={ctx.isDecrypting || ctx.isDecryptDisabled}
|
|
31
|
+
title={
|
|
32
|
+
ctx.isDecryptDisabled ? ctx.decryptDisabledReason : undefined
|
|
33
|
+
}
|
|
34
|
+
size="xs"
|
|
35
|
+
>
|
|
29
36
|
{ctx.isDecrypting ? (
|
|
30
37
|
<Spinner size={10} />
|
|
31
38
|
) : (
|
|
@@ -57,7 +57,7 @@ export interface SelectedSpanInfo {
|
|
|
57
57
|
/**
|
|
58
58
|
* Panel component for workflow traces that displays entity details.
|
|
59
59
|
*
|
|
60
|
-
* This component is rendered OUTSIDE the trace viewer context
|
|
60
|
+
* This component is rendered OUTSIDE the trace viewer context: it
|
|
61
61
|
* receives all data via props rather than reading from context.
|
|
62
62
|
*/
|
|
63
63
|
export function EntityDetailPanel({
|
|
@@ -71,6 +71,8 @@ export function EntityDetailPanel({
|
|
|
71
71
|
encryptionKey,
|
|
72
72
|
onDecrypt,
|
|
73
73
|
isDecrypting = false,
|
|
74
|
+
isDecryptDisabled = false,
|
|
75
|
+
decryptDisabledReason,
|
|
74
76
|
selectedSpan,
|
|
75
77
|
showSeparateEventOccurrenceTimestamps = false,
|
|
76
78
|
getModuleSourceUrl,
|
|
@@ -100,6 +102,10 @@ export function EntityDetailPanel({
|
|
|
100
102
|
onDecrypt?: () => void;
|
|
101
103
|
/** Whether the encryption key is currently being fetched */
|
|
102
104
|
isDecrypting?: boolean;
|
|
105
|
+
/** Whether decryption is unavailable */
|
|
106
|
+
isDecryptDisabled?: boolean;
|
|
107
|
+
/** Explains why decryption is unavailable */
|
|
108
|
+
decryptDisabledReason?: string;
|
|
103
109
|
/** Info about the currently selected span from the trace viewer */
|
|
104
110
|
selectedSpan: SelectedSpanInfo | null;
|
|
105
111
|
/** Show occurredAt separately instead of folding it into the Created timestamp. */
|
|
@@ -315,7 +321,15 @@ export function EntityDetailPanel({
|
|
|
315
321
|
<div className="flex h-full flex-col">
|
|
316
322
|
<DecryptClickContext.Provider
|
|
317
323
|
value={
|
|
318
|
-
onDecrypt
|
|
324
|
+
onDecrypt
|
|
325
|
+
? {
|
|
326
|
+
onDecrypt,
|
|
327
|
+
isDecrypting,
|
|
328
|
+
isDecryptDisabled,
|
|
329
|
+
decryptDisabledReason,
|
|
330
|
+
hasEncryptedData,
|
|
331
|
+
}
|
|
332
|
+
: undefined
|
|
319
333
|
}
|
|
320
334
|
>
|
|
321
335
|
<div className="flex-1 overflow-y-auto px-4 pb-8">
|
|
@@ -394,6 +408,8 @@ export function EntityDetailPanel({
|
|
|
394
408
|
onRunClick={onRunClick}
|
|
395
409
|
onDecrypt={onDecrypt}
|
|
396
410
|
isDecrypting={isDecrypting}
|
|
411
|
+
isDecryptDisabled={isDecryptDisabled}
|
|
412
|
+
decryptDisabledReason={decryptDisabledReason}
|
|
397
413
|
resource={resource}
|
|
398
414
|
/>
|
|
399
415
|
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { type Event, getEventDataRefFields } from '@workflow/world';
|
|
4
4
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
5
|
+
import { DUPLICATE_EVENT_MESSAGE } from '../../lib/duplicate-events';
|
|
5
6
|
import { hasEncryptedFields, isExpiredMarker } from '../../lib/hydration';
|
|
7
|
+
import {
|
|
8
|
+
isSealedNoopEvent,
|
|
9
|
+
SEALED_EVENT_MESSAGE,
|
|
10
|
+
} from '../../lib/sealed-events';
|
|
6
11
|
import {
|
|
7
12
|
Collapsible,
|
|
8
13
|
CollapsibleContent,
|
|
@@ -10,7 +15,7 @@ import {
|
|
|
10
15
|
CollapsibleTrigger,
|
|
11
16
|
} from '../ui/collapsible';
|
|
12
17
|
import { RunClickContext, StreamClickContext } from '../ui/data-inspector';
|
|
13
|
-
import {
|
|
18
|
+
import { EventNoticeTooltip } from '../ui/duplicate-event-tooltip';
|
|
14
19
|
import { ErrorCard } from '../ui/error-card';
|
|
15
20
|
import { ErrorStackBlock, isStructuredError } from '../ui/error-stack-block';
|
|
16
21
|
import { Skeleton } from '../ui/skeleton';
|
|
@@ -110,6 +115,11 @@ function EventItem({
|
|
|
110
115
|
void loadEventData(true);
|
|
111
116
|
}, [encryptionKey, loadEventData]);
|
|
112
117
|
|
|
118
|
+
const rowNotice = isDuplicate
|
|
119
|
+
? DUPLICATE_EVENT_MESSAGE
|
|
120
|
+
: isSealedNoopEvent(event)
|
|
121
|
+
? SEALED_EVENT_MESSAGE
|
|
122
|
+
: undefined;
|
|
113
123
|
const createdAt = new Date(event.createdAt);
|
|
114
124
|
const occurredAt = parseDateValue(event.occurredAt);
|
|
115
125
|
const displayedCreatedAt = showSeparateEventOccurrenceTimestamps
|
|
@@ -139,15 +149,15 @@ function EventItem({
|
|
|
139
149
|
>
|
|
140
150
|
<CollapsibleTrigger className="px-3 py-2">
|
|
141
151
|
<div className="flex w-full items-center justify-between gap-3">
|
|
142
|
-
<
|
|
152
|
+
<EventNoticeTooltip notice={rowNotice}>
|
|
143
153
|
<span
|
|
144
154
|
className={`text-label-12 font-mono ${
|
|
145
|
-
|
|
155
|
+
rowNotice ? 'text-gray-700' : 'text-gray-1000'
|
|
146
156
|
}`}
|
|
147
157
|
>
|
|
148
158
|
{event.eventType}
|
|
149
159
|
</span>
|
|
150
|
-
</
|
|
160
|
+
</EventNoticeTooltip>
|
|
151
161
|
<span className="shrink-0 text-label-13 text-gray-900">
|
|
152
162
|
{displayedCreatedAtTime}
|
|
153
163
|
</span>
|
|
@@ -242,7 +252,7 @@ function EventDataBlock({
|
|
|
242
252
|
eventType: string;
|
|
243
253
|
data: unknown;
|
|
244
254
|
}) {
|
|
245
|
-
// Expired data
|
|
255
|
+
// Expired data: show a message instead of the raw stub.
|
|
246
256
|
// Check both the top-level eventData and nested sub-fields (result, input, etc.)
|
|
247
257
|
// since the server stubs each ref field independently.
|
|
248
258
|
if (isExpiredMarker(data) || hasOnlyExpiredFields(data, eventType)) {
|
|
@@ -264,7 +274,7 @@ function EventDataBlock({
|
|
|
264
274
|
return <EncryptedDataBlock />;
|
|
265
275
|
}
|
|
266
276
|
|
|
267
|
-
// Attribute changes
|
|
277
|
+
// Attribute changes: render the changed keys and the writer instead of
|
|
268
278
|
// the raw JSON payload.
|
|
269
279
|
if (eventType === 'attr_set') {
|
|
270
280
|
return <AttrSetEventBlock data={data} />;
|
|
@@ -116,13 +116,13 @@ export function ResolveHookModal({
|
|
|
116
116
|
aria-modal="true"
|
|
117
117
|
aria-labelledby="resolve-hook-modal-title"
|
|
118
118
|
>
|
|
119
|
-
{/* Backdrop
|
|
119
|
+
{/* Backdrop: matches Geist dialog ::backdrop */}
|
|
120
120
|
<div
|
|
121
121
|
className="absolute inset-0 bg-black/70"
|
|
122
122
|
onClick={isSubmitting ? undefined : onClose}
|
|
123
123
|
/>
|
|
124
124
|
|
|
125
|
-
{/* Modal card
|
|
125
|
+
{/* Modal card: matches Geist dialog.geist-dialog */}
|
|
126
126
|
<div className="relative z-10 w-[480px] max-w-[calc(100%_-_32px)] overflow-hidden !border-none bg-background-100 text-gray-1000 material-menu">
|
|
127
127
|
{/* Header */}
|
|
128
128
|
<div className="flex items-center justify-between px-6 py-4">
|
|
@@ -29,6 +29,8 @@ export interface SidebarDataContextValue {
|
|
|
29
29
|
encryptionKey?: Uint8Array;
|
|
30
30
|
onDecrypt?: () => void;
|
|
31
31
|
isDecrypting?: boolean;
|
|
32
|
+
isDecryptDisabled?: boolean;
|
|
33
|
+
decryptDisabledReason?: string;
|
|
32
34
|
hasEncryptedData?: boolean;
|
|
33
35
|
/** Show occurredAt separately instead of folding it into the Created timestamp. */
|
|
34
36
|
showSeparateEventOccurrenceTimestamps?: boolean;
|
|
@@ -30,7 +30,7 @@ interface StreamViewerProps {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// ──────────────────────────────────────────────────────────────────────────
|
|
33
|
-
// Chunk row
|
|
33
|
+
// Chunk row: memoized to prevent remounts during polling
|
|
34
34
|
// ──────────────────────────────────────────────────────────────────────────
|
|
35
35
|
|
|
36
36
|
const ChunkRow = React.memo(function ChunkRow({ chunk }: { chunk: Chunk }) {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Width model for the span detail panel: the stored value is the user's
|
|
3
3
|
* preferred width (absolute px, persisted to localStorage on explicit
|
|
4
4
|
* interaction only), while the container-relative maximum is applied at
|
|
5
|
-
* render time
|
|
5
|
+
* render time, so a width preferred on a wide screen survives a narrower
|
|
6
6
|
* session and restores when the viewer grows again.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
/** Floor for the detail panel
|
|
9
|
+
/** Floor for the detail panel; matches the previous `clamp(280px, …)` floor. */
|
|
10
10
|
export const PANEL_MIN_WIDTH = 280;
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -15,13 +15,13 @@ export const PANEL_MIN_WIDTH = 280;
|
|
|
15
15
|
*/
|
|
16
16
|
export const PANEL_HARD_MAX_WIDTH = 1300;
|
|
17
17
|
|
|
18
|
-
/** Default width
|
|
18
|
+
/** Default width; matches the previous fixed 360px column. */
|
|
19
19
|
export const PANEL_DEFAULT_WIDTH = 360;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Width the main (event list + timeline) area keeps while the panel is
|
|
23
23
|
* resized or the container shrinks. Below `PANEL_MIN_WIDTH + MAIN_MIN_WIDTH`
|
|
24
|
-
* of container the panel gives way first, compressing below its own floor
|
|
24
|
+
* of container the panel gives way first, compressing below its own floor:
|
|
25
25
|
* the list and timeline stay usable at the panel's expense.
|
|
26
26
|
*/
|
|
27
27
|
export const MAIN_MIN_WIDTH = 400;
|
|
@@ -64,7 +64,7 @@ function useSelectedSpanInfo(): SelectedSpanInfo | null {
|
|
|
64
64
|
* The span detail aside: content, prev/next/close header, J/K navigation, and
|
|
65
65
|
* the resizable left border with its width model (see detail-panel-width.ts).
|
|
66
66
|
*
|
|
67
|
-
* Always mounted
|
|
67
|
+
* Always mounted (it renders null without a selection) so the panel width
|
|
68
68
|
* survives closing and reopening within a session.
|
|
69
69
|
*/
|
|
70
70
|
export function TraceDetailPanel({
|
|
@@ -261,6 +261,8 @@ export function TraceDetailPanel({
|
|
|
261
261
|
encryptionKey={sidebar.encryptionKey}
|
|
262
262
|
onDecrypt={sidebar.onDecrypt}
|
|
263
263
|
isDecrypting={sidebar.isDecrypting}
|
|
264
|
+
isDecryptDisabled={sidebar.isDecryptDisabled}
|
|
265
|
+
decryptDisabledReason={sidebar.decryptDisabledReason}
|
|
264
266
|
selectedSpan={selectedSpan}
|
|
265
267
|
showSeparateEventOccurrenceTimestamps={
|
|
266
268
|
sidebar.showSeparateEventOccurrenceTimestamps
|
|
@@ -35,7 +35,7 @@ interface DraggableBorderProps {
|
|
|
35
35
|
* double-click reset, pointer-capture dragging (works with touch/pen, no
|
|
36
36
|
* ghost image), and keyboard/ARIA window-splitter support.
|
|
37
37
|
*
|
|
38
|
-
* The panel's positioning ancestor must not clip overflow
|
|
38
|
+
* The panel's positioning ancestor must not clip overflow: the strip hangs
|
|
39
39
|
* ~8px past the panel edge.
|
|
40
40
|
*/
|
|
41
41
|
export function DraggableBorder({
|
|
@@ -200,9 +200,9 @@ export const Minimap = memo(function Minimap({
|
|
|
200
200
|
|
|
201
201
|
// Density canvas: one thin line per span across the full run. Sizing is
|
|
202
202
|
// read live and redraws run straight from the ResizeObserver (which fires
|
|
203
|
-
// before paint), so layout changes
|
|
203
|
+
// before paint), so layout changes (e.g. the detail panel opening) never
|
|
204
204
|
// paint a frame with a stale, stretched bitmap.
|
|
205
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies: themeVersion is a redraw trigger
|
|
205
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: themeVersion is a redraw trigger; the canvas re-resolves its token colors when the theme flips
|
|
206
206
|
useLayoutEffect(() => {
|
|
207
207
|
const canvas = canvasRef.current;
|
|
208
208
|
const container = containerRef.current;
|
|
@@ -276,7 +276,7 @@ export const Minimap = memo(function Minimap({
|
|
|
276
276
|
return { mode: 'resize-right' };
|
|
277
277
|
}
|
|
278
278
|
// Panning a full-extent window is a no-op, so let a drag on the brush
|
|
279
|
-
// start a selection instead
|
|
279
|
+
// start a selection instead, the natural first gesture on the map.
|
|
280
280
|
const { start, end } = viewportRef.current;
|
|
281
281
|
const isFullExtent =
|
|
282
282
|
start - rootStartMs < rootDurationMs * 0.001 &&
|