okengine 0.22.0 → 0.23.0
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/AGENTS.md +7 -0
- package/manifest.v1.schema.json +44 -0
- package/package.json +32 -19
- package/site/content/docs/ai/mcp.mdx +13 -9
- package/site/content/docs/ai/skills.mdx +1 -1
- package/site/content/docs/elements/ai/agents.mdx +61 -13
- package/site/content/docs/elements/ai/decide.mdx +536 -0
- package/site/content/docs/elements/ai/events.mdx +373 -0
- package/site/content/docs/elements/ai/index.mdx +9 -3
- package/site/content/docs/elements/ai/meta.json +1 -1
- package/site/content/docs/elements/ai/prompts.mdx +4 -2
- package/site/content/docs/elements/gate/auth.mdx +4 -1
- package/site/content/docs/elements/store/index.mdx +1 -1
- package/site/content/docs/elements/store/kv.mdx +5 -5
- package/site/content/docs/recipes/dragonfly.mdx +5 -5
- package/site/content/docs/recipes/index.mdx +6 -6
- package/site/content/docs/recipes/postgres.mdx +12 -8
- package/site/content/docs/recipes/redis.mdx +4 -4
- package/site/content/docs/reference/cli.mdx +2 -1
- package/site/content/docs/reference/configuration.mdx +9 -7
- package/site/content/docs/reference/fx.mdx +7 -6
- package/src/auth/api-key-sql.test.ts +44 -0
- package/src/auth/api-key-sql.ts +1 -0
- package/src/auth/api-keys.ts +30 -12
- package/src/auth/config.ts +6 -2
- package/src/auth/gate-auth.test.ts +20 -0
- package/src/cli/agents-md.test.ts +25 -0
- package/src/cli/ask-vault-gaps.test.ts +38 -1
- package/src/cli/ask-vault-gaps.ts +35 -2
- package/src/cli/decide.ts +174 -0
- package/src/cli/decision-lock-watch.test.ts +45 -0
- package/src/cli/decision-lock-watch.ts +51 -0
- package/src/cli/dev-app-runner.ts +34 -8
- package/src/cli/dev-auth-secret.test.ts +49 -0
- package/src/cli/dev-auth-secret.ts +49 -0
- package/src/cli/dev-hot.test.ts +41 -0
- package/src/cli/dev-hot.ts +48 -0
- package/src/cli/dev.test.ts +1 -1
- package/src/cli/dev.ts +24 -6
- package/src/cli/eval.ts +142 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/load-config.images.test.ts +2 -2
- package/src/cli/load-config.ts +1 -1
- package/src/cli/mcp-from-console.ts +48 -0
- package/src/cli/registry.ts +35 -1
- package/src/client/agent.ts +307 -0
- package/src/client/transport.test.ts +20 -0
- package/src/client/transport.ts +2 -1
- package/src/client/types.ts +1 -1
- package/src/client-react/index.ts +7 -1
- package/src/client-react/use-agent-run.test.ts +271 -0
- package/src/client-react/use-agent-run.ts +193 -0
- package/src/compiler/ai-approval.test.ts +36 -0
- package/src/compiler/ai-repair.test.ts +22 -0
- package/src/compiler/decisions.extract.test.ts +164 -0
- package/src/compiler/effects-infer.ts +27 -1
- package/src/compiler/extract.ts +268 -12
- package/src/compiler/response.ts +35 -2
- package/src/config/index.ts +21 -3
- package/src/console/server/ai-runs-flows.ts +269 -0
- package/src/console/server/ai-runs.test.ts +323 -0
- package/src/console/server/ai-runs.ts +562 -0
- package/src/console/server/ai.ts +12 -1
- package/src/console/server/app.ts +10 -1
- package/src/console/server/decisions-flows.ts +157 -0
- package/src/console/server/decisions.test.ts +66 -0
- package/src/console/server/decisions.ts +229 -0
- package/src/console/server/flows.ts +50 -2
- package/src/console/server/invoke-user-flow.ts +9 -0
- package/src/console/server/runs-ingest.ts +16 -0
- package/src/console/server/serve.ts +5 -0
- package/src/console/server/state.ts +25 -1
- package/src/console/server/store-stats.test.ts +47 -0
- package/src/console/server/store-stats.ts +92 -18
- package/src/console/ui-next/dist/assets/access-page-DDKkhT9v.js +4 -0
- package/src/console/ui-next/dist/assets/{agent-disclosure-Bkohc_8X.js → agent-disclosure-62Xts8Xi.js} +1 -1
- package/src/console/ui-next/dist/assets/{cache-glyph-rL0lHxat.js → cache-glyph-DyeoKHKA.js} +1 -1
- package/src/console/ui-next/dist/assets/{call-pii-button-Deo4PP18.js → call-pii-button-DvfpLXsU.js} +1 -1
- package/src/console/ui-next/dist/assets/{collapsible-CoJ6amHf.js → collapsible-BY03SeCg.js} +1 -1
- package/src/console/ui-next/dist/assets/confirm-sheet-DZfuJXP4.js +1 -0
- package/src/console/ui-next/dist/assets/copy-inline-button-BTjPVjRT.js +1 -0
- package/src/console/ui-next/dist/assets/decisions-page-CgkKlA56.js +1 -0
- package/src/console/ui-next/dist/assets/detail-header-Dk5LipF6.js +1 -0
- package/src/console/ui-next/dist/assets/dropdown-menu-CRGrj5JU.js +1 -0
- package/src/console/ui-next/dist/assets/duration-tone-Y6HLjyJ5.js +9 -0
- package/src/console/ui-next/dist/assets/element-icons-C6t8Vlml.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-chrome-Dslh2yE8.js +1 -0
- package/src/console/ui-next/dist/assets/explorer-empty-CIVg7Jgb.js +1 -0
- package/src/console/ui-next/dist/assets/flows-page-DLPfNy-_.js +1 -0
- package/src/console/ui-next/dist/assets/{highlighted-json-CPYBzDh0.js → highlighted-json-D7nNzpgB.js} +1 -1
- package/src/console/ui-next/dist/assets/{http-method-DTPevKmM.js → http-method-DdL19zzh.js} +1 -1
- package/src/console/ui-next/dist/assets/index-BlrN49Hs.css +2 -0
- package/src/console/ui-next/dist/assets/index-C0lc_s9d.js +58 -0
- package/src/console/ui-next/dist/assets/observability-page-CRlcyLCC.js +8 -0
- package/src/console/ui-next/dist/assets/{replica-lag-BM27lXld.js → replica-lag-Bb3FWUu9.js} +7 -7
- package/src/console/ui-next/dist/assets/request-meta-DYBMWhX8.js +1 -0
- package/src/console/ui-next/dist/assets/shortcut-keys-DTE1sjTC.js +1 -0
- package/src/console/ui-next/dist/assets/store-page-CcE-SXC8.js +41 -0
- package/src/console/ui-next/dist/assets/trace-detail-sheet-CKMSEQ4s.js +2 -0
- package/src/console/ui-next/dist/assets/tree-expand-toggle-QOfk0M0H.js +55 -0
- package/src/console/ui-next/dist/assets/units-page-DKC1CDDd.js +1 -0
- package/src/console/ui-next/dist/assets/use-vault-list-BAyQG8Re.js +1 -0
- package/src/console/ui-next/dist/assets/vault-page-B54v4Yit.js +2 -0
- package/src/console/ui-next/dist/index.html +4 -3
- package/src/console/ui-next/seed-parked-approval.ts +126 -0
- package/src/console/ui-next/src/client.ts +224 -1
- package/src/console/ui-next/src/components/explorer/detail-header.tsx +9 -0
- package/src/console/ui-next/src/components/explorer/explorer-start-toggle.tsx +5 -1
- package/src/console/ui-next/src/components/ui/sheet-form.tsx +21 -12
- package/src/console/ui-next/src/features/flows/decisions/decisions-page.tsx +216 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/decisions/resolve.ts +111 -0
- package/src/console/ui-next/src/features/flows/flows-page.tsx +10 -0
- package/src/console/ui-next/src/features/flows/graph/element-map.ts +1 -0
- package/src/console/ui-next/src/features/flows/traces/effect-kind.ts +6 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.test.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/effect-summary.ts +24 -0
- package/src/console/ui-next/src/features/flows/traces/http-status.ts +58 -0
- package/src/console/ui-next/src/features/flows/traces/request-client-card.tsx +355 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.test.ts +114 -0
- package/src/console/ui-next/src/features/flows/traces/request-client.ts +423 -0
- package/src/console/ui-next/src/features/flows/traces/request-input-view.test.ts +52 -6
- package/src/console/ui-next/src/features/flows/traces/request-input-view.ts +85 -19
- package/src/console/ui-next/src/features/flows/traces/trace-detail-sheet.tsx +505 -80
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.test.ts +61 -0
- package/src/console/ui-next/src/features/flows/traces/trace-lanes.ts +78 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.test.ts +48 -0
- package/src/console/ui-next/src/features/flows/traces/trace-nav.ts +57 -0
- package/src/console/ui-next/src/features/flows/traces/trace-request-section.tsx +275 -96
- package/src/console/ui-next/src/features/flows/traces/traces-pane.tsx +16 -2
- package/src/console/ui-next/src/features/observability/detail/ai-runs.tsx +369 -0
- package/src/console/ui-next/src/features/observability/observability-page.tsx +31 -1
- package/src/console/ui-next/src/features/observability/state/observability-selection.ts +18 -3
- package/src/console/ui-next/src/features/store/performance/kv-performance-panel.tsx +12 -1
- package/src/console/ui-next/src/features/store/performance/performance-panel.tsx +14 -1
- package/src/console/ui-next/src/features/units/call/call-api-panel.tsx +1 -1
- package/src/console/ui-next/src/features/units/detail/flow-contract-panel.tsx +6 -2
- package/src/console/ui-next/src/features/units/explorer/units-tree.tsx +73 -18
- package/src/console/ui-next/src/features/units/lib/unit-tree.test.ts +21 -0
- package/src/console/ui-next/src/features/units/lib/unit-tree.ts +82 -0
- package/src/console/ui-next/src/features/units/units-page.tsx +25 -7
- package/src/console/ui-next/src/router.tsx +17 -0
- package/src/console/ui-next/ui-next-seed-runs.ts +23 -0
- package/src/console/ui-next/vite-console-kernel-plugin.ts +43 -3
- package/src/console/ui-next/vite.config.ts +9 -0
- package/src/docker/recipes/dragonfly.ts +3 -2
- package/src/docker/recipes/redis.ts +1 -1
- package/src/drivers/ai-anthropic.ts +212 -21
- package/src/drivers/ai-mock.ts +34 -1
- package/src/drivers/ai-openai-compatible.ts +67 -26
- package/src/drivers/ai-preconnect.ts +18 -0
- package/src/drivers/ai-stream.test.ts +62 -0
- package/src/drivers/ai-types.ts +20 -0
- package/src/drivers/journal-postgres.ts +289 -2
- package/src/drivers/memory-ddl.test.ts +18 -0
- package/src/drivers/memory.ts +21 -1
- package/src/drivers/pg-rls.test.ts +31 -0
- package/src/drivers/pg-rls.ts +15 -0
- package/src/drivers/postgres.test.ts +9 -0
- package/src/drivers/postgres.ts +49 -11
- package/src/elements/ai/agent-event-slot.ts +27 -0
- package/src/elements/ai/approval-http.ts +207 -0
- package/src/elements/ai/approval.test.ts +741 -0
- package/src/elements/ai/approval.ts +228 -0
- package/src/elements/ai/decisions/bind.ts +163 -0
- package/src/elements/ai/decisions/certificate.test.ts +291 -0
- package/src/elements/ai/decisions/certificate.ts +377 -0
- package/src/elements/ai/decisions/certify.ts +314 -0
- package/src/elements/ai/decisions/decide.test.ts +953 -0
- package/src/elements/ai/decisions/e2e.test.ts +399 -0
- package/src/elements/ai/decisions/export.test.ts +120 -0
- package/src/elements/ai/decisions/export.ts +146 -0
- package/src/elements/ai/decisions/fixtures/openrouter-request.json +24 -0
- package/src/elements/ai/decisions/fixtures/openrouter-response.json +22 -0
- package/src/elements/ai/decisions/fixtures/typesafe-request.json +19 -0
- package/src/elements/ai/decisions/fixtures/typesafe-response.json +13 -0
- package/src/elements/ai/decisions/http.test.ts +154 -0
- package/src/elements/ai/decisions/http.ts +240 -0
- package/src/elements/ai/decisions/labels.test.ts +282 -0
- package/src/elements/ai/decisions/labels.ts +257 -0
- package/src/elements/ai/decisions/openrouter.live.test.ts +136 -0
- package/src/elements/ai/decisions/openrouter.ts +36 -0
- package/src/elements/ai/decisions/provider.ts +102 -0
- package/src/elements/ai/decisions/typesafe.ts +36 -0
- package/src/elements/ai/declare.ts +252 -1
- package/src/elements/ai/events.test.ts +379 -0
- package/src/elements/ai/events.ts +138 -0
- package/src/elements/ai/run-events.test.ts +461 -0
- package/src/elements/ai/run-events.ts +522 -0
- package/src/elements/ai/runtime.ts +1186 -105
- package/src/elements/ai/stream-turn.ts +214 -0
- package/src/elements/ai/stream.live.test.ts +76 -0
- package/src/elements/ai/subagent.test.ts +136 -0
- package/src/elements/ai.test.ts +497 -3
- package/src/elements/ai.ts +16 -1
- package/src/elements/clock/durable.ts +27 -1
- package/src/elements/store/sql-session.test.ts +51 -0
- package/src/elements/store/sql-session.ts +111 -1
- package/src/i18n/catalogs/ar.ts +5 -0
- package/src/i18n/catalogs/en.ts +5 -0
- package/src/index.ts +2 -0
- package/src/kernel/agent-event-store.ts +448 -0
- package/src/kernel/app.ts +103 -1
- package/src/kernel/boot-bind/ai.ts +3 -0
- package/src/kernel/boot-bind/store.test.ts +26 -0
- package/src/kernel/boot-bind/store.ts +82 -6
- package/src/kernel/boot.ts +15 -4
- package/src/kernel/builtin-errors.ts +2 -0
- package/src/kernel/capability.ts +3 -0
- package/src/kernel/decision-label-store.ts +441 -0
- package/src/kernel/effects.test.ts +2 -1
- package/src/kernel/effects.ts +13 -1
- package/src/kernel/element-registries.ts +3 -0
- package/src/kernel/errors-text.ts +4 -0
- package/src/kernel/errors.ts +2 -0
- package/src/kernel/flow.test.ts +2 -3
- package/src/kernel/fx-decide.ts +761 -0
- package/src/kernel/fx.test.ts +12 -5
- package/src/kernel/fx.ts +312 -29
- package/src/kernel/http-frame.test.ts +94 -0
- package/src/kernel/http-frame.ts +203 -0
- package/src/kernel/journal.ts +104 -0
- package/src/kernel/json-result.ts +10 -0
- package/src/kernel/pipeline.test.ts +45 -0
- package/src/kernel/sse-id.ts +32 -0
- package/src/manifest/diff.ts +1 -0
- package/src/manifest/types.ts +29 -2
- package/src/mcp/ai-tools.test.ts +202 -0
- package/src/mcp/authorization.ts +66 -0
- package/src/mcp/session.ts +3 -0
- package/src/mcp/tools.ts +168 -0
- package/src/release/http-graph.test.ts +2 -0
- package/src/release/http-graph.ts +5 -0
- package/src/release/measure.ts +39 -14
- package/src/runs/collect.ts +4 -1
- package/src/runs/parquet.test.ts +27 -1
- package/src/runs/parquet.ts +19 -0
- package/src/runs/types.ts +31 -0
- package/src/runtime/json-code-block.test.ts +106 -47
- package/src/runtime/json-code-block.ts +1209 -46
- package/src/term.test.ts +13 -0
- package/src/term.ts +81 -49
- package/src/console/ui-next/dist/assets/PlusSignIcon-CwG3nxfu.js +0 -1
- package/src/console/ui-next/dist/assets/access-page-Ze6ckjdd.js +0 -4
- package/src/console/ui-next/dist/assets/copy-inline-button-CWG5_ZOh.js +0 -1
- package/src/console/ui-next/dist/assets/detail-header-b_ZzIsmU.js +0 -1
- package/src/console/ui-next/dist/assets/dropdown-menu-nwXEO1Ac.js +0 -1
- package/src/console/ui-next/dist/assets/duration-tone-BbQ_8z50.js +0 -9
- package/src/console/ui-next/dist/assets/element-icons-CwyLpVXz.js +0 -1
- package/src/console/ui-next/dist/assets/explorer-empty-C8sSCqYT.js +0 -1
- package/src/console/ui-next/dist/assets/flows-page-D2E7BtKK.js +0 -1
- package/src/console/ui-next/dist/assets/index-VxoEz295.css +0 -2
- package/src/console/ui-next/dist/assets/index-vTuwmeQz.js +0 -58
- package/src/console/ui-next/dist/assets/observability-page-SJTSzHFJ.js +0 -4
- package/src/console/ui-next/dist/assets/request-meta-BjFT7DNl.js +0 -1
- package/src/console/ui-next/dist/assets/shortcut-keys-CUNegEV4.js +0 -1
- package/src/console/ui-next/dist/assets/store-page-Qj9ThRE-.js +0 -41
- package/src/console/ui-next/dist/assets/trace-detail-sheet-Cmitq3da.js +0 -2
- package/src/console/ui-next/dist/assets/tree-expand-toggle-CbDIB-7x.js +0 -55
- package/src/console/ui-next/dist/assets/units-page-_PuYqFty.js +0 -1
- package/src/console/ui-next/dist/assets/use-vault-list-Cs45Czkt.js +0 -1
- package/src/console/ui-next/dist/assets/vault-page-BOMr0go9.js +0 -2
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
ArrowLeft01Icon,
|
|
11
11
|
ArrowReloadHorizontalIcon,
|
|
12
12
|
ArrowRight01Icon,
|
|
13
|
+
ArrowUp01Icon,
|
|
13
14
|
LeftToRightListBulletIcon,
|
|
14
15
|
MinusSignIcon,
|
|
15
16
|
PlusSignIcon,
|
|
16
17
|
Timer01Icon,
|
|
17
18
|
} from "@hugeicons/core-free-icons";
|
|
18
19
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
19
|
-
import { tracesReplay, type RunRow } from "@/client.ts";
|
|
20
|
+
import { tracesReplay, type RunEffect, type RunRow } from "@/client.ts";
|
|
20
21
|
import {
|
|
21
22
|
AnimatePresence,
|
|
22
23
|
animate,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
import {
|
|
30
31
|
EXPLORER_CHEVRON_CLASS,
|
|
31
32
|
EXPLORER_COUNT_CLASS,
|
|
33
|
+
EXPLORER_GROUP_ROW_CLASS,
|
|
32
34
|
EXPLORER_ICON_BUTTON_CLASS,
|
|
33
35
|
EXPLORER_ICON_CLASS,
|
|
34
36
|
EXPLORER_RAIL_ACTIVE_CLASS,
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
EXPLORER_STRIP_TOKEN_CLASS,
|
|
40
42
|
SECTION_HEAD_CLASS,
|
|
41
43
|
} from "@/components/explorer/explorer-chrome.ts";
|
|
44
|
+
import { CopyInlineButton } from "@/components/explorer/copy-inline-button.tsx";
|
|
42
45
|
import { Badge } from "@/components/ui/badge";
|
|
43
46
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
44
47
|
import {
|
|
@@ -56,6 +59,7 @@ import { useManifest } from "../data/use-manifest.ts";
|
|
|
56
59
|
import { NODE_ACCENT } from "../graph/flow-graph-theme.ts";
|
|
57
60
|
import { effectBarColor, effectKindIcon, type RunEffectKind } from "./effect-kind.ts";
|
|
58
61
|
import {
|
|
62
|
+
effectAiHref,
|
|
59
63
|
effectEventLabel,
|
|
60
64
|
effectSummaryChips,
|
|
61
65
|
type EffectSummaryChip,
|
|
@@ -67,9 +71,11 @@ import { durationClassName } from "./duration-tone.ts";
|
|
|
67
71
|
import { traceRequestMeta } from "./request-meta.ts";
|
|
68
72
|
import { TraceRequestSection } from "./trace-request-section.tsx";
|
|
69
73
|
import { executeTraceReplay } from "./trace-actions.ts";
|
|
74
|
+
import { traceIdentityLines, traceNav } from "./trace-nav.ts";
|
|
70
75
|
import { traceGateInfos } from "./trace-gates.ts";
|
|
71
76
|
import { triggerIconSpec } from "./trigger-icon.ts";
|
|
72
77
|
import { playbackDurationMs } from "./replay-playback.ts";
|
|
78
|
+
import { traceIsDense, traceLanes, type TraceLane } from "./trace-lanes.ts";
|
|
73
79
|
import { waterfallBars, type WaterfallBar } from "./waterfall-bars.ts";
|
|
74
80
|
import {
|
|
75
81
|
mapToViewport,
|
|
@@ -99,6 +105,13 @@ export type TraceDetailSheetProps = {
|
|
|
99
105
|
readonly playbackKey?: number;
|
|
100
106
|
/** Called when Replay succeeds so the page can pulse the graph chain. */
|
|
101
107
|
readonly onReplayStart?: () => void;
|
|
108
|
+
/**
|
|
109
|
+
* Ordered traces the header can step through. Previous is the row above
|
|
110
|
+
* the open trace; next is the row below.
|
|
111
|
+
*/
|
|
112
|
+
readonly navigationRuns?: readonly RunRow[];
|
|
113
|
+
/** Open another trace from {@link navigationRuns}. */
|
|
114
|
+
readonly onSelectRun?: (id: string) => void;
|
|
102
115
|
};
|
|
103
116
|
|
|
104
117
|
const sectionClassName = "border-b border-border/60 last:border-b-0";
|
|
@@ -118,6 +131,8 @@ export function TraceDetailSheet({
|
|
|
118
131
|
onFocusEffectChange,
|
|
119
132
|
playbackKey = 0,
|
|
120
133
|
onReplayStart,
|
|
134
|
+
navigationRuns,
|
|
135
|
+
onSelectRun,
|
|
121
136
|
}: TraceDetailSheetProps) {
|
|
122
137
|
const manifest = useManifest();
|
|
123
138
|
const reduceMotion = useReducedMotion();
|
|
@@ -132,7 +147,14 @@ export function TraceDetailSheet({
|
|
|
132
147
|
() => (run ? waterfallBars(run.effects, run.startedAt, run.durationMs) : []),
|
|
133
148
|
[run],
|
|
134
149
|
);
|
|
150
|
+
const dense = traceIsDense(bars.length);
|
|
151
|
+
const lanes = useMemo(() => (dense ? traceLanes(bars) : []), [dense, bars]);
|
|
135
152
|
const gaps = useMemo(() => (run ? waterfallGaps(bars, run.durationMs) : []), [run, bars]);
|
|
153
|
+
const identity = useMemo(() => (run ? traceIdentityLines(run) : []), [run]);
|
|
154
|
+
const nav = useMemo(
|
|
155
|
+
() => (run && navigationRuns ? traceNav(navigationRuns, run.id) : null),
|
|
156
|
+
[run, navigationRuns],
|
|
157
|
+
);
|
|
136
158
|
const requestMeta = useMemo(
|
|
137
159
|
() =>
|
|
138
160
|
run
|
|
@@ -151,6 +173,7 @@ export function TraceDetailSheet({
|
|
|
151
173
|
const [zoom, setZoom] = useState(1);
|
|
152
174
|
const [viewStart, setViewStart] = useState(0);
|
|
153
175
|
const [playing, setPlaying] = useState(false);
|
|
176
|
+
const [openLanes, setOpenLanes] = useState<ReadonlySet<string>>(() => new Set());
|
|
154
177
|
|
|
155
178
|
const progress = useMotionValue(0);
|
|
156
179
|
// Hero duration counter — counts up on open, tracks the playhead on Replay.
|
|
@@ -176,6 +199,7 @@ export function TraceDetailSheet({
|
|
|
176
199
|
setZoom(1);
|
|
177
200
|
setViewStart(0);
|
|
178
201
|
setPlaying(false);
|
|
202
|
+
setOpenLanes(new Set());
|
|
179
203
|
progress.set(0);
|
|
180
204
|
if (!run) return;
|
|
181
205
|
if (reduceMotion) {
|
|
@@ -220,6 +244,24 @@ export function TraceDetailSheet({
|
|
|
220
244
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
221
245
|
}, [playbackKey]);
|
|
222
246
|
|
|
247
|
+
useEffect(() => {
|
|
248
|
+
if (focusEffectIndex == null) return;
|
|
249
|
+
const lane = lanes.find((item) => item.bars.some((bar) => bar.index === focusEffectIndex));
|
|
250
|
+
if (!lane || lane.bars.length < 2) return;
|
|
251
|
+
setOpenLanes((prev) => {
|
|
252
|
+
if (prev.has(lane.key)) return prev;
|
|
253
|
+
const next = new Set(prev);
|
|
254
|
+
next.add(lane.key);
|
|
255
|
+
return next;
|
|
256
|
+
});
|
|
257
|
+
}, [focusEffectIndex, lanes]);
|
|
258
|
+
|
|
259
|
+
const stepTo = (id: string | null) => {
|
|
260
|
+
if (!id || !onSelectRun) return;
|
|
261
|
+
onFocusEffectChange?.(null);
|
|
262
|
+
onSelectRun(id);
|
|
263
|
+
};
|
|
264
|
+
|
|
223
265
|
const onReplay = async (e: MouseEvent) => {
|
|
224
266
|
e.stopPropagation();
|
|
225
267
|
if (!run || busy) return;
|
|
@@ -263,7 +305,12 @@ export function TraceDetailSheet({
|
|
|
263
305
|
>
|
|
264
306
|
{run && trigger ? (
|
|
265
307
|
<>
|
|
266
|
-
<SheetHeader
|
|
308
|
+
<SheetHeader
|
|
309
|
+
className={cn(
|
|
310
|
+
"gap-1.5 border-b border-border/60 px-2 py-2",
|
|
311
|
+
nav && nav.index >= 0 && onSelectRun ? "pr-28" : "pr-12",
|
|
312
|
+
)}
|
|
313
|
+
>
|
|
267
314
|
<div className="flex items-start gap-2">
|
|
268
315
|
<span className="mt-0.5 text-muted-foreground" title={trigger.label} aria-hidden>
|
|
269
316
|
<HugeiconsIcon icon={trigger.icon} className={EXPLORER_ICON_CLASS} />
|
|
@@ -289,7 +336,79 @@ export function TraceDetailSheet({
|
|
|
289
336
|
</>
|
|
290
337
|
) : null}
|
|
291
338
|
</SheetDescription>
|
|
339
|
+
<div className="mt-1 flex min-w-0 flex-col gap-0.5" data-slot="trace-sheet-ids">
|
|
340
|
+
{identity.map((line) => (
|
|
341
|
+
<div
|
|
342
|
+
key={line.key}
|
|
343
|
+
className="flex min-w-0 items-center gap-1.5"
|
|
344
|
+
data-slot="trace-sheet-id"
|
|
345
|
+
data-id={line.key}
|
|
346
|
+
>
|
|
347
|
+
<span className="w-12 shrink-0 text-[10px] font-semibold tracking-[0.08em] text-muted-foreground uppercase">
|
|
348
|
+
{line.label}
|
|
349
|
+
</span>
|
|
350
|
+
<code
|
|
351
|
+
className="min-w-0 truncate font-mono text-[11px] leading-none text-foreground/80"
|
|
352
|
+
title={line.value}
|
|
353
|
+
>
|
|
354
|
+
{line.value}
|
|
355
|
+
</code>
|
|
356
|
+
<CopyInlineButton value={line.value} label={`Copy ${line.label} ID`} />
|
|
357
|
+
</div>
|
|
358
|
+
))}
|
|
359
|
+
</div>
|
|
292
360
|
</div>
|
|
361
|
+
{nav && nav.index >= 0 && onSelectRun ? (
|
|
362
|
+
<div
|
|
363
|
+
className="absolute top-3 right-12 flex items-center"
|
|
364
|
+
data-slot="trace-sheet-nav"
|
|
365
|
+
role="group"
|
|
366
|
+
aria-label="Trace navigation"
|
|
367
|
+
>
|
|
368
|
+
<Tooltip>
|
|
369
|
+
<TooltipTrigger
|
|
370
|
+
render={(props) => (
|
|
371
|
+
<button
|
|
372
|
+
{...props}
|
|
373
|
+
type="button"
|
|
374
|
+
className="inline-flex size-7 items-center justify-center text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-40"
|
|
375
|
+
aria-label="Previous trace"
|
|
376
|
+
data-slot="trace-sheet-previous"
|
|
377
|
+
disabled={nav.previousId === null}
|
|
378
|
+
onClick={(event) => {
|
|
379
|
+
props.onClick?.(event);
|
|
380
|
+
stepTo(nav.previousId);
|
|
381
|
+
}}
|
|
382
|
+
>
|
|
383
|
+
<HugeiconsIcon icon={ArrowUp01Icon} className="size-5" />
|
|
384
|
+
</button>
|
|
385
|
+
)}
|
|
386
|
+
/>
|
|
387
|
+
<TooltipContent side="bottom">Previous trace</TooltipContent>
|
|
388
|
+
</Tooltip>
|
|
389
|
+
<Tooltip>
|
|
390
|
+
<TooltipTrigger
|
|
391
|
+
render={(props) => (
|
|
392
|
+
<button
|
|
393
|
+
{...props}
|
|
394
|
+
type="button"
|
|
395
|
+
className="inline-flex size-7 items-center justify-center text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring/40 focus-visible:outline-none disabled:pointer-events-none disabled:opacity-40"
|
|
396
|
+
aria-label="Next trace"
|
|
397
|
+
data-slot="trace-sheet-next"
|
|
398
|
+
disabled={nav.nextId === null}
|
|
399
|
+
onClick={(event) => {
|
|
400
|
+
props.onClick?.(event);
|
|
401
|
+
stepTo(nav.nextId);
|
|
402
|
+
}}
|
|
403
|
+
>
|
|
404
|
+
<HugeiconsIcon icon={ArrowDown01Icon} className="size-5" />
|
|
405
|
+
</button>
|
|
406
|
+
)}
|
|
407
|
+
/>
|
|
408
|
+
<TooltipContent side="bottom">Next trace</TooltipContent>
|
|
409
|
+
</Tooltip>
|
|
410
|
+
</div>
|
|
411
|
+
) : null}
|
|
293
412
|
</div>
|
|
294
413
|
<AnimatePresence mode="wait">
|
|
295
414
|
{hint ? (
|
|
@@ -335,7 +454,14 @@ export function TraceDetailSheet({
|
|
|
335
454
|
|
|
336
455
|
<section className={sectionClassName} data-slot="trace-waterfall">
|
|
337
456
|
<div className={EXPLORER_STRIP_CLASS}>
|
|
338
|
-
<h3 className={cn(SECTION_HEAD_CLASS, "flex items-center px-2")}>
|
|
457
|
+
<h3 className={cn(SECTION_HEAD_CLASS, "flex items-center gap-2 px-2")}>
|
|
458
|
+
Waterfall
|
|
459
|
+
{dense ? (
|
|
460
|
+
<span className={EXPLORER_COUNT_CLASS}>
|
|
461
|
+
{lanes.length} {lanes.length === 1 ? "lane" : "lanes"}
|
|
462
|
+
</span>
|
|
463
|
+
) : null}
|
|
464
|
+
</h3>
|
|
339
465
|
<div
|
|
340
466
|
className="ml-auto flex h-full items-stretch"
|
|
341
467
|
data-slot="trace-waterfall-zoom"
|
|
@@ -414,21 +540,44 @@ export function TraceDetailSheet({
|
|
|
414
540
|
}
|
|
415
541
|
focusIndex={focusEffectIndex}
|
|
416
542
|
/>
|
|
417
|
-
{
|
|
418
|
-
<
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
543
|
+
{dense ? (
|
|
544
|
+
<div
|
|
545
|
+
className="flex max-h-72 flex-col overflow-y-auto"
|
|
546
|
+
data-slot="trace-waterfall-lanes"
|
|
547
|
+
>
|
|
548
|
+
{lanes.map((lane) => (
|
|
549
|
+
<WaterfallLaneRow
|
|
550
|
+
key={lane.key}
|
|
551
|
+
lane={lane}
|
|
552
|
+
view={view}
|
|
553
|
+
hoverIndex={hoverIndex}
|
|
554
|
+
focusIndex={focusEffectIndex}
|
|
555
|
+
progress={progress}
|
|
556
|
+
playing={playing}
|
|
557
|
+
onHover={setHoverIndex}
|
|
558
|
+
onSelect={(index) =>
|
|
559
|
+
onFocusEffectChange?.(focusEffectIndex === index ? null : index)
|
|
560
|
+
}
|
|
561
|
+
/>
|
|
562
|
+
))}
|
|
563
|
+
</div>
|
|
564
|
+
) : (
|
|
565
|
+
bars.map((bar) => (
|
|
566
|
+
<WaterfallBarRow
|
|
567
|
+
key={bar.index}
|
|
568
|
+
bar={bar}
|
|
569
|
+
view={view}
|
|
570
|
+
dimmed={hoverIndex !== null && hoverIndex !== bar.index}
|
|
571
|
+
focused={focusEffectIndex === bar.index}
|
|
572
|
+
progress={progress}
|
|
573
|
+
playing={playing}
|
|
574
|
+
onHover={setHoverIndex}
|
|
575
|
+
onSelect={(index) =>
|
|
576
|
+
onFocusEffectChange?.(focusEffectIndex === index ? null : index)
|
|
577
|
+
}
|
|
578
|
+
/>
|
|
579
|
+
))
|
|
580
|
+
)}
|
|
432
581
|
{playing ? (
|
|
433
582
|
<motion.div
|
|
434
583
|
className="pointer-events-none absolute top-4 bottom-0 w-px bg-foreground/70"
|
|
@@ -475,69 +624,47 @@ export function TraceDetailSheet({
|
|
|
475
624
|
</div>
|
|
476
625
|
<CollapsibleContent>
|
|
477
626
|
<ul className="flex flex-col" data-slot="trace-events">
|
|
478
|
-
{
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
627
|
+
{dense
|
|
628
|
+
? lanes.map((lane) => (
|
|
629
|
+
<EventLane
|
|
630
|
+
key={lane.key}
|
|
631
|
+
lane={lane}
|
|
632
|
+
effects={run.effects}
|
|
633
|
+
open={openLanes.has(lane.key)}
|
|
634
|
+
onToggle={() =>
|
|
635
|
+
setOpenLanes((prev) => {
|
|
636
|
+
const next = new Set(prev);
|
|
637
|
+
if (next.has(lane.key)) next.delete(lane.key);
|
|
638
|
+
else next.add(lane.key);
|
|
639
|
+
return next;
|
|
640
|
+
})
|
|
641
|
+
}
|
|
642
|
+
focusIndex={focusEffectIndex}
|
|
643
|
+
hoverIndex={hoverIndex}
|
|
644
|
+
progress={progress}
|
|
645
|
+
playing={playing}
|
|
646
|
+
onHover={setHoverIndex}
|
|
647
|
+
onSelect={(index) =>
|
|
648
|
+
onFocusEffectChange?.(focusEffectIndex === index ? null : index)
|
|
649
|
+
}
|
|
650
|
+
/>
|
|
651
|
+
))
|
|
652
|
+
: run.effects.map((effect, index) => (
|
|
653
|
+
<EventRow
|
|
654
|
+
key={`${effect.kind}:${effect.resource}:${index}`}
|
|
655
|
+
effect={effect}
|
|
656
|
+
index={index}
|
|
657
|
+
bar={bars[index]}
|
|
658
|
+
focused={focusEffectIndex === index}
|
|
659
|
+
hovered={hoverIndex === index}
|
|
660
|
+
progress={progress}
|
|
661
|
+
playing={playing}
|
|
662
|
+
onHover={setHoverIndex}
|
|
663
|
+
onSelect={(next) =>
|
|
664
|
+
onFocusEffectChange?.(focusEffectIndex === next ? null : next)
|
|
665
|
+
}
|
|
506
666
|
/>
|
|
507
|
-
|
|
508
|
-
<EffectKindGlyph kind={effect.kind} resource={effect.resource} />
|
|
509
|
-
<span className="w-[4.5rem] shrink-0 font-medium text-foreground/90">
|
|
510
|
-
{effectEventLabel(effect)}
|
|
511
|
-
</span>
|
|
512
|
-
<span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
|
|
513
|
-
{effect.resource}
|
|
514
|
-
{effect.external
|
|
515
|
-
? ` · ↗ ${effect.external.host}${effect.external.kind ? ` (${effect.external.kind})` : ""}`
|
|
516
|
-
: ""}
|
|
517
|
-
</span>
|
|
518
|
-
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
519
|
-
{formatDuration(effect.duration)}
|
|
520
|
-
</span>
|
|
521
|
-
</div>
|
|
522
|
-
{bar ? (
|
|
523
|
-
<div
|
|
524
|
-
className="relative ml-7 h-1.5 w-[calc(100%-1.75rem)] rounded-full bg-muted/70"
|
|
525
|
-
data-slot="trace-event-bar-track"
|
|
526
|
-
aria-hidden
|
|
527
|
-
>
|
|
528
|
-
<PlaybackBarFill
|
|
529
|
-
bar={bar}
|
|
530
|
-
left={bar.offsetRatio}
|
|
531
|
-
width={bar.widthRatio}
|
|
532
|
-
progress={progress}
|
|
533
|
-
playing={playing}
|
|
534
|
-
slot="trace-event-bar"
|
|
535
|
-
/>
|
|
536
|
-
</div>
|
|
537
|
-
) : null}
|
|
538
|
-
</li>
|
|
539
|
-
);
|
|
540
|
-
})}
|
|
667
|
+
))}
|
|
541
668
|
</ul>
|
|
542
669
|
</CollapsibleContent>
|
|
543
670
|
</Collapsible>
|
|
@@ -549,6 +676,7 @@ export function TraceDetailSheet({
|
|
|
549
676
|
headline={requestMeta.headline}
|
|
550
677
|
input={run.input}
|
|
551
678
|
output={run.output}
|
|
679
|
+
http={run.http ?? null}
|
|
552
680
|
error={run.error}
|
|
553
681
|
errorMessage={run.errorMessage}
|
|
554
682
|
inputOpen={inputOpen}
|
|
@@ -569,6 +697,200 @@ export function TraceDetailSheet({
|
|
|
569
697
|
*
|
|
570
698
|
* @param props - Effect kind
|
|
571
699
|
*/
|
|
700
|
+
function effectExternalSuffix(external: WaterfallBar["external"]): string {
|
|
701
|
+
if (!external) return "";
|
|
702
|
+
return ` · ↗ ${external.host}${external.kind ? ` (${external.kind})` : ""}`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* One effect row in Event Details — shared by flat runs and expanded lanes.
|
|
707
|
+
*/
|
|
708
|
+
function EffectResource({
|
|
709
|
+
effect,
|
|
710
|
+
}: {
|
|
711
|
+
readonly effect: Pick<RunEffect, "kind" | "resource">;
|
|
712
|
+
}): JSX.Element {
|
|
713
|
+
const manifest = useManifest();
|
|
714
|
+
const agents = new Set(Object.keys(manifest.data?.ai?.agents ?? {}));
|
|
715
|
+
const href = effectAiHref(effect, agents);
|
|
716
|
+
if (!href) return <>{effect.resource}</>;
|
|
717
|
+
return (
|
|
718
|
+
<a href={href} data-slot="trace-effect-link" onClick={(event) => event.stopPropagation()}>
|
|
719
|
+
{effect.resource}
|
|
720
|
+
</a>
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function EventRow({
|
|
725
|
+
effect,
|
|
726
|
+
index,
|
|
727
|
+
bar,
|
|
728
|
+
focused,
|
|
729
|
+
hovered,
|
|
730
|
+
progress,
|
|
731
|
+
playing,
|
|
732
|
+
onHover,
|
|
733
|
+
onSelect,
|
|
734
|
+
}: {
|
|
735
|
+
readonly effect: RunEffect;
|
|
736
|
+
readonly index: number;
|
|
737
|
+
readonly bar: WaterfallBar | undefined;
|
|
738
|
+
readonly focused: boolean;
|
|
739
|
+
readonly hovered: boolean;
|
|
740
|
+
readonly progress: MotionValue<number>;
|
|
741
|
+
readonly playing: boolean;
|
|
742
|
+
readonly onHover: (index: number | null) => void;
|
|
743
|
+
readonly onSelect: (index: number) => void;
|
|
744
|
+
}): JSX.Element {
|
|
745
|
+
return (
|
|
746
|
+
<li
|
|
747
|
+
data-slot="trace-event-row"
|
|
748
|
+
data-index={index}
|
|
749
|
+
data-focused={focused ? "true" : "false"}
|
|
750
|
+
data-external={effect.external ? "true" : undefined}
|
|
751
|
+
className={cn(
|
|
752
|
+
EXPLORER_ROW_CLASS,
|
|
753
|
+
"cursor-pointer flex-col items-stretch gap-1 text-[11px]",
|
|
754
|
+
focused && EXPLORER_ROW_SELECTED_CLASS,
|
|
755
|
+
hovered && !focused && "bg-muted/50",
|
|
756
|
+
)}
|
|
757
|
+
onClick={() => onSelect(index)}
|
|
758
|
+
onMouseEnter={() => onHover(index)}
|
|
759
|
+
onMouseLeave={() => onHover(null)}
|
|
760
|
+
>
|
|
761
|
+
<span
|
|
762
|
+
aria-hidden
|
|
763
|
+
className={cn(EXPLORER_RAIL_CLASS, focused && EXPLORER_RAIL_ACTIVE_CLASS)}
|
|
764
|
+
/>
|
|
765
|
+
<div className="flex items-center gap-2">
|
|
766
|
+
<EffectKindGlyph kind={effect.kind} resource={effect.resource} />
|
|
767
|
+
<span className="w-[4.5rem] shrink-0 font-medium text-foreground/90">
|
|
768
|
+
{effectEventLabel(effect)}
|
|
769
|
+
</span>
|
|
770
|
+
<span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
|
|
771
|
+
<EffectResource effect={effect} />
|
|
772
|
+
{effectExternalSuffix(effect.external)}
|
|
773
|
+
</span>
|
|
774
|
+
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
775
|
+
{formatDuration(effect.duration)}
|
|
776
|
+
</span>
|
|
777
|
+
</div>
|
|
778
|
+
{bar ? (
|
|
779
|
+
<div
|
|
780
|
+
className="relative ml-7 h-1.5 w-[calc(100%-1.75rem)] rounded-full bg-muted/70"
|
|
781
|
+
data-slot="trace-event-bar-track"
|
|
782
|
+
aria-hidden
|
|
783
|
+
>
|
|
784
|
+
<PlaybackBarFill
|
|
785
|
+
bar={bar}
|
|
786
|
+
left={bar.offsetRatio}
|
|
787
|
+
width={bar.widthRatio}
|
|
788
|
+
progress={progress}
|
|
789
|
+
playing={playing}
|
|
790
|
+
slot="trace-event-bar"
|
|
791
|
+
/>
|
|
792
|
+
</div>
|
|
793
|
+
) : null}
|
|
794
|
+
</li>
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Dense event list — one row per repeated operation, expanded to the ledger.
|
|
800
|
+
*/
|
|
801
|
+
function EventLane({
|
|
802
|
+
lane,
|
|
803
|
+
effects,
|
|
804
|
+
open,
|
|
805
|
+
onToggle,
|
|
806
|
+
focusIndex,
|
|
807
|
+
hoverIndex,
|
|
808
|
+
progress,
|
|
809
|
+
playing,
|
|
810
|
+
onHover,
|
|
811
|
+
onSelect,
|
|
812
|
+
}: {
|
|
813
|
+
readonly lane: TraceLane;
|
|
814
|
+
readonly effects: readonly RunEffect[];
|
|
815
|
+
readonly open: boolean;
|
|
816
|
+
readonly onToggle: () => void;
|
|
817
|
+
readonly focusIndex: number | null;
|
|
818
|
+
readonly hoverIndex: number | null;
|
|
819
|
+
readonly progress: MotionValue<number>;
|
|
820
|
+
readonly playing: boolean;
|
|
821
|
+
readonly onHover: (index: number | null) => void;
|
|
822
|
+
readonly onSelect: (index: number) => void;
|
|
823
|
+
}): JSX.Element | null {
|
|
824
|
+
const only = lane.bars.length === 1 ? lane.bars[0] : undefined;
|
|
825
|
+
if (only) {
|
|
826
|
+
const effect = effects[only.index];
|
|
827
|
+
if (!effect) return null;
|
|
828
|
+
return (
|
|
829
|
+
<EventRow
|
|
830
|
+
effect={effect}
|
|
831
|
+
index={only.index}
|
|
832
|
+
bar={only}
|
|
833
|
+
focused={focusIndex === only.index}
|
|
834
|
+
hovered={hoverIndex === only.index}
|
|
835
|
+
progress={progress}
|
|
836
|
+
playing={playing}
|
|
837
|
+
onHover={onHover}
|
|
838
|
+
onSelect={onSelect}
|
|
839
|
+
/>
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const focused = focusIndex !== null && lane.bars.some((bar) => bar.index === focusIndex);
|
|
844
|
+
return (
|
|
845
|
+
<li data-slot="trace-event-lane" data-open={open ? "true" : "false"}>
|
|
846
|
+
<button
|
|
847
|
+
type="button"
|
|
848
|
+
className={cn(EXPLORER_GROUP_ROW_CLASS, focused && EXPLORER_ROW_SELECTED_CLASS)}
|
|
849
|
+
onClick={onToggle}
|
|
850
|
+
>
|
|
851
|
+
<HugeiconsIcon
|
|
852
|
+
icon={ArrowDown01Icon}
|
|
853
|
+
className={cn(EXPLORER_CHEVRON_CLASS, !open && "-rotate-90")}
|
|
854
|
+
/>
|
|
855
|
+
<EffectKindGlyph kind={lane.kind} resource={lane.resource} />
|
|
856
|
+
<span className="w-[4.5rem] shrink-0 font-medium text-foreground/90">
|
|
857
|
+
{effectEventLabel(lane)}
|
|
858
|
+
</span>
|
|
859
|
+
<span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
|
|
860
|
+
<EffectResource effect={lane} />
|
|
861
|
+
{effectExternalSuffix(lane.external)}
|
|
862
|
+
</span>
|
|
863
|
+
<span className={EXPLORER_COUNT_CLASS}>×{lane.bars.length}</span>
|
|
864
|
+
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
865
|
+
{formatDuration(lane.totalDurationMs)}
|
|
866
|
+
</span>
|
|
867
|
+
</button>
|
|
868
|
+
{open ? (
|
|
869
|
+
<ul className="max-h-64 overflow-y-auto" data-slot="trace-event-lane-items">
|
|
870
|
+
{lane.bars.map((bar) => {
|
|
871
|
+
const effect = effects[bar.index];
|
|
872
|
+
if (!effect) return null;
|
|
873
|
+
return (
|
|
874
|
+
<EventRow
|
|
875
|
+
key={bar.index}
|
|
876
|
+
effect={effect}
|
|
877
|
+
index={bar.index}
|
|
878
|
+
bar={bar}
|
|
879
|
+
focused={focusIndex === bar.index}
|
|
880
|
+
hovered={hoverIndex === bar.index}
|
|
881
|
+
progress={progress}
|
|
882
|
+
playing={playing}
|
|
883
|
+
onHover={onHover}
|
|
884
|
+
onSelect={onSelect}
|
|
885
|
+
/>
|
|
886
|
+
);
|
|
887
|
+
})}
|
|
888
|
+
</ul>
|
|
889
|
+
) : null}
|
|
890
|
+
</li>
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
572
894
|
function EffectKindGlyph({
|
|
573
895
|
kind,
|
|
574
896
|
resource,
|
|
@@ -895,6 +1217,109 @@ function GapSegment({
|
|
|
895
1217
|
);
|
|
896
1218
|
}
|
|
897
1219
|
|
|
1220
|
+
/**
|
|
1221
|
+
* One labeled timeline for every occurrence of the same kind and resource.
|
|
1222
|
+
*/
|
|
1223
|
+
function WaterfallLaneRow({
|
|
1224
|
+
lane,
|
|
1225
|
+
view,
|
|
1226
|
+
hoverIndex,
|
|
1227
|
+
focusIndex,
|
|
1228
|
+
progress,
|
|
1229
|
+
playing,
|
|
1230
|
+
onHover,
|
|
1231
|
+
onSelect,
|
|
1232
|
+
}: {
|
|
1233
|
+
readonly lane: TraceLane;
|
|
1234
|
+
readonly view: TimelineView;
|
|
1235
|
+
readonly hoverIndex: number | null;
|
|
1236
|
+
readonly focusIndex: number | null;
|
|
1237
|
+
readonly progress: MotionValue<number>;
|
|
1238
|
+
readonly playing: boolean;
|
|
1239
|
+
readonly onHover: (index: number | null) => void;
|
|
1240
|
+
readonly onSelect?: (index: number) => void;
|
|
1241
|
+
}): JSX.Element {
|
|
1242
|
+
const hot =
|
|
1243
|
+
(hoverIndex !== null && lane.bars.some((bar) => bar.index === hoverIndex)) ||
|
|
1244
|
+
(focusIndex !== null && lane.bars.some((bar) => bar.index === focusIndex));
|
|
1245
|
+
return (
|
|
1246
|
+
<div
|
|
1247
|
+
className={cn(
|
|
1248
|
+
"flex flex-col gap-1 border-b border-border/60 px-2 py-1.5 last:border-b-0",
|
|
1249
|
+
hot && "bg-muted/50",
|
|
1250
|
+
)}
|
|
1251
|
+
data-slot="trace-waterfall-lane"
|
|
1252
|
+
>
|
|
1253
|
+
<div className="flex items-center gap-2 text-[11px]">
|
|
1254
|
+
<EffectKindGlyph kind={lane.kind} resource={lane.resource} />
|
|
1255
|
+
<span className="w-[4.5rem] shrink-0 font-medium text-foreground/90">
|
|
1256
|
+
{effectEventLabel(lane)}
|
|
1257
|
+
</span>
|
|
1258
|
+
<span className="min-w-0 flex-1 truncate font-mono text-muted-foreground">
|
|
1259
|
+
<EffectResource effect={lane} />
|
|
1260
|
+
{effectExternalSuffix(lane.external)}
|
|
1261
|
+
</span>
|
|
1262
|
+
<span className={EXPLORER_COUNT_CLASS}>×{lane.bars.length}</span>
|
|
1263
|
+
<span className="shrink-0 tabular-nums text-muted-foreground">
|
|
1264
|
+
{formatDuration(lane.totalDurationMs)}
|
|
1265
|
+
</span>
|
|
1266
|
+
</div>
|
|
1267
|
+
<div
|
|
1268
|
+
className="relative ml-7 h-2 w-[calc(100%-1.75rem)] rounded-full bg-muted/70"
|
|
1269
|
+
data-slot="trace-waterfall-lane-track"
|
|
1270
|
+
>
|
|
1271
|
+
{lane.bars.map((bar) => {
|
|
1272
|
+
const mapped = mapToViewport(bar.offsetRatio, bar.widthRatio, view);
|
|
1273
|
+
if (!mapped || mapped.width <= 0) return null;
|
|
1274
|
+
const focused = focusIndex === bar.index;
|
|
1275
|
+
return (
|
|
1276
|
+
<Tooltip key={bar.index}>
|
|
1277
|
+
<TooltipTrigger
|
|
1278
|
+
render={(props) => (
|
|
1279
|
+
<div
|
|
1280
|
+
{...props}
|
|
1281
|
+
className="absolute inset-y-0 cursor-pointer"
|
|
1282
|
+
style={{
|
|
1283
|
+
left: `${mapped.left * 100}%`,
|
|
1284
|
+
width: `${Math.max(mapped.width * 100, 0.4)}%`,
|
|
1285
|
+
}}
|
|
1286
|
+
onClick={(event) => {
|
|
1287
|
+
event.stopPropagation();
|
|
1288
|
+
onSelect?.(bar.index);
|
|
1289
|
+
}}
|
|
1290
|
+
onMouseEnter={(event) => {
|
|
1291
|
+
props.onMouseEnter?.(event);
|
|
1292
|
+
onHover(bar.index);
|
|
1293
|
+
}}
|
|
1294
|
+
onMouseLeave={(event) => {
|
|
1295
|
+
props.onMouseLeave?.(event);
|
|
1296
|
+
onHover(null);
|
|
1297
|
+
}}
|
|
1298
|
+
>
|
|
1299
|
+
<PlaybackBarFill
|
|
1300
|
+
bar={bar}
|
|
1301
|
+
left={0}
|
|
1302
|
+
width={1}
|
|
1303
|
+
progress={progress}
|
|
1304
|
+
playing={playing}
|
|
1305
|
+
dimmed={hoverIndex !== null && hoverIndex !== bar.index && !focused}
|
|
1306
|
+
focused={focused}
|
|
1307
|
+
slot="trace-waterfall-bar"
|
|
1308
|
+
/>
|
|
1309
|
+
</div>
|
|
1310
|
+
)}
|
|
1311
|
+
/>
|
|
1312
|
+
<TooltipContent side="top" className="max-w-xs font-mono text-[11px]">
|
|
1313
|
+
{waterfallBarTooltip(bar)}
|
|
1314
|
+
</TooltipContent>
|
|
1315
|
+
</Tooltip>
|
|
1316
|
+
);
|
|
1317
|
+
})}
|
|
1318
|
+
</div>
|
|
1319
|
+
</div>
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
898
1323
|
/**
|
|
899
1324
|
* One waterfall track with a real tooltip (viewport-aware).
|
|
900
1325
|
*
|