jev-devtools 0.5.4 → 0.5.5

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.
@@ -0,0 +1,191 @@
1
+ import { array, boolean, finite, id, ids, integer, json, literal, nonnegative, optional, parse, record, shape, status, strings, text, } from "./transport-validation.js";
2
+ const stepStatus = literal("waiting", "running", "completed", "untaken", "blocked", "error", "cancelled");
3
+ const count = (value) => integer(value) && value <= 64;
4
+ const concurrency = (value) => integer(value) && value >= 1 && value <= 16;
5
+ const iterations = (value) => count(value) && value >= 1;
6
+ // Supported nesting is a loop containing a map containing a plain program.
7
+ // The shared definition budget includes every nested template, as the builder does.
8
+ function definitionCheck() {
9
+ let remaining = 256;
10
+ const definition = (depth) => shape({
11
+ version: literal(1),
12
+ id,
13
+ name: text,
14
+ nodes: (value) => {
15
+ if (!array(node(depth), 256)(value))
16
+ return false;
17
+ const nodes = new Map(value.map((node) => [node.id, node]));
18
+ if (nodes.size !== value.length)
19
+ return false;
20
+ // Parallel joins are emitted before their lane dependencies. Validate
21
+ // the graph, not the producer's array order, without allowing cycles.
22
+ const active = new Set(), complete = new Set();
23
+ const visit = (id) => {
24
+ if (active.has(id))
25
+ return false;
26
+ if (complete.has(id))
27
+ return true;
28
+ const node = nodes.get(id);
29
+ if (!node)
30
+ return false;
31
+ active.add(id);
32
+ if (!node.dependencies.every(visit))
33
+ return false;
34
+ active.delete(id);
35
+ complete.add(id);
36
+ return true;
37
+ };
38
+ return value.every((node) => visit(node.id) &&
39
+ (!node.merge ||
40
+ [node.merge.condition, node.merge.then, node.merge.else].every((key) => nodes.has(key))));
41
+ },
42
+ });
43
+ const node = (depth) => (value) => {
44
+ if (--remaining < 0 || depth > 2)
45
+ return false;
46
+ return (shape({
47
+ id,
48
+ kind: literal("jev", "task", "map", "loop", "if", "merge", "branch", "parallel", "output"),
49
+ // Empty parallel lanes can repeat a predecessor without adding nodes.
50
+ dependencies: array(id),
51
+ path: array(text, 256),
52
+ title: optional(text),
53
+ description: optional(text),
54
+ condition: optional(text),
55
+ merge: optional(shape({ condition: id, then: id, else: id })),
56
+ conditionRule: optional(json),
57
+ conditionCaptureWarnings: optional(strings),
58
+ lanes: optional(array(text)),
59
+ itemDefinition: optional(definition(depth + 1)),
60
+ })(value) &&
61
+ (!value.itemDefinition ||
62
+ ((value.kind === "map" || value.kind === "loop") &&
63
+ value.itemDefinition.nodes.every((child) => child.kind !== "loop" &&
64
+ (value.kind !== "map" || child.kind !== "map")))));
65
+ };
66
+ return definition(0);
67
+ }
68
+ function runCheck(definition) {
69
+ let remaining = 1024;
70
+ const runIds = new Set(), executionIds = new Set();
71
+ const run = (owner) => (value) => {
72
+ const nodes = new Map(owner.nodes.map((node) => [node.id, node]));
73
+ const valid = shape({
74
+ version: literal(1),
75
+ id,
76
+ definitionId: literal(owner.id),
77
+ name: text,
78
+ startedAt: finite,
79
+ durationMs: optional(nonnegative),
80
+ status,
81
+ outcome: optional(text),
82
+ value: optional(json),
83
+ captureWarnings: optional(strings),
84
+ error: optional(text),
85
+ steps: (value) => {
86
+ if (!record(value) || Object.keys(value).length !== nodes.size)
87
+ return false;
88
+ return Object.entries(value).every(([key, item]) => {
89
+ const node = nodes.get(key);
90
+ if (!node || --remaining < 0)
91
+ return false;
92
+ const childDefinition = node.itemDefinition;
93
+ const step = shape({
94
+ id: literal(key),
95
+ executionId: id,
96
+ status: stepStatus,
97
+ startedAt: optional(finite),
98
+ durationMs: optional(nonnegative),
99
+ decision: optional(boolean),
100
+ traceId: optional(id),
101
+ error: optional(text),
102
+ data: optional(json),
103
+ captureWarnings: optional(strings),
104
+ loop: optional(shape({
105
+ maxIterations: iterations,
106
+ stopReason: optional(literal("condition", "limit")),
107
+ })),
108
+ collection: optional(shape({
109
+ total: count,
110
+ concurrency,
111
+ items: array(shape({
112
+ index: count,
113
+ status: stepStatus,
114
+ run: optional((child) => !!childDefinition && run(childDefinition)(child)),
115
+ }), 64),
116
+ })),
117
+ });
118
+ if (!step(item) || executionIds.has(item.executionId))
119
+ return false;
120
+ executionIds.add(item.executionId);
121
+ if (item.loop && node.kind !== "loop")
122
+ return false;
123
+ const collection = item.collection;
124
+ if (!collection)
125
+ return true;
126
+ if (!childDefinition ||
127
+ (node.kind !== "loop" && node.kind !== "map"))
128
+ return false;
129
+ if (node.kind === "loop" && collection.concurrency !== 1)
130
+ return false;
131
+ if (collection.total !== collection.items.length)
132
+ return false;
133
+ if (item.loop && collection.total > item.loop.maxIterations)
134
+ return false;
135
+ return collection.items.every((child, index) => child.index === index);
136
+ });
137
+ },
138
+ })(value);
139
+ if (!valid || runIds.has(value.id))
140
+ return false;
141
+ runIds.add(value.id);
142
+ return true;
143
+ };
144
+ return run(definition);
145
+ }
146
+ /** Validate an entire event in staging maps before publishing either cache. */
147
+ export function parseFlowUpdate(input, currentRuns, currentDefinitions) {
148
+ // Runs are parsed only after resolving their definitions in the staged cache.
149
+ const data = parse(shape({
150
+ version: literal(1),
151
+ order: ids,
152
+ definitions: array((value) => definitionCheck()(value), 200),
153
+ upserts: array(record, 200),
154
+ }), input, "flow update");
155
+ const definitions = new Map(currentDefinitions);
156
+ const definitionIds = new Set();
157
+ for (const definition of data.definitions) {
158
+ if (definitionIds.has(definition.id))
159
+ throw new Error("Duplicate flow definition");
160
+ definitionIds.add(definition.id);
161
+ if (!definitions.has(definition.id))
162
+ definitions.set(definition.id, definition);
163
+ }
164
+ const runs = new Map(currentRuns);
165
+ const retained = new Set(data.order), upsertIds = new Set();
166
+ for (const raw of data.upserts) {
167
+ const definition = typeof raw.definitionId === "string"
168
+ ? definitions.get(raw.definitionId)
169
+ : undefined;
170
+ if (!definition)
171
+ throw new Error("Unknown flow definition");
172
+ const run = parse(runCheck(definition), raw, "flow run");
173
+ if (upsertIds.has(run.id) || !retained.has(run.id))
174
+ throw new Error("Invalid flow upsert ID");
175
+ upsertIds.add(run.id);
176
+ runs.set(run.id, run);
177
+ }
178
+ const ordered = data.order.map((id) => {
179
+ const run = runs.get(id);
180
+ if (!run)
181
+ throw new Error("Missing flow run");
182
+ return run;
183
+ });
184
+ const used = new Set(ordered.map((run) => run.definitionId));
185
+ if (data.definitions.some((definition) => !used.has(definition.id)))
186
+ throw new Error("Unused flow definition");
187
+ return {
188
+ runs: new Map(ordered.map((run) => [run.id, run])),
189
+ definitions: new Map([...definitions].filter(([id]) => used.has(id))),
190
+ };
191
+ }
@@ -1,3 +1,4 @@
1
+ import { parseFlowUpdate } from "./flow-parser.js";
1
2
  export const emptyFlows = { runs: [], definitions: [] };
2
3
  /** Separate opt-in stream; no extra rendering of request rows on flow-only updates. */
3
4
  export function connectFlowStream(url, onUpdate) {
@@ -36,37 +37,16 @@ export function connectFlowStream(url, onUpdate) {
36
37
  if (!alive || source !== current)
37
38
  return;
38
39
  try {
39
- const data = JSON.parse(event.data);
40
- if (data.version !== 1 ||
41
- !Array.isArray(data.order) ||
42
- data.order.length > 200 ||
43
- new Set(data.order).size !== data.order.length ||
44
- !Array.isArray(data.upserts) ||
45
- !Array.isArray(data.definitions))
46
- throw new Error("Invalid flow stream");
47
- for (const definition of data.definitions) {
48
- if (definition.version !== 1 ||
49
- !Array.isArray(definition.nodes) ||
50
- definition.nodes.length > 256)
51
- throw new Error("Invalid flow definition");
52
- if (!definitions.has(definition.id))
53
- definitions.set(definition.id, definition);
54
- }
55
- for (const run of data.upserts) {
56
- if (run.version !== 1 || !definitions.has(run.definitionId))
57
- throw new Error("Invalid flow run");
58
- runs.set(run.id, run);
59
- }
60
- const ordered = data.order.map((id) => {
61
- const run = runs.get(id);
62
- if (!run)
63
- throw new Error("Missing run");
64
- return run;
65
- });
66
- runs = new Map(ordered.map((r) => [r.id, r]));
67
- const used = new Set(ordered.map((r) => r.definitionId));
68
- definitions = new Map([...definitions].filter(([id]) => used.has(id)));
69
- pending = { runs: ordered, definitions: [...definitions.values()] };
40
+ if (!(event instanceof MessageEvent) || typeof event.data !== "string")
41
+ throw new Error("Invalid flow event");
42
+ const input = JSON.parse(event.data);
43
+ const next = parseFlowUpdate(input, runs, definitions);
44
+ runs = next.runs;
45
+ definitions = next.definitions;
46
+ pending = {
47
+ runs: [...runs.values()],
48
+ definitions: [...definitions.values()],
49
+ };
70
50
  if (frame === undefined) {
71
51
  frame = requestAnimationFrame(publish);
72
52
  fallback = setTimeout(publish, 100);
@@ -75,6 +55,7 @@ export function connectFlowStream(url, onUpdate) {
75
55
  catch {
76
56
  cancel();
77
57
  current.close();
58
+ source = undefined;
78
59
  retry = setTimeout(connect, 1000);
79
60
  }
80
61
  });
@@ -1 +1 @@
1
- export const inspectorStyles = "/* Important declarations in the inner shadow context beat page-authored host\n styles, including important ones. Keep the reset here instead of expanding\n the all shorthand into hundreds of inline properties on the host element.\n These qualified host selectors are inert in the public document stylesheet. */\n:host(jev-devtools-root) {\n all: initial !important;\n direction: ltr !important;\n unicode-bidi: isolate !important;\n position: fixed !important;\n inset: 0 !important;\n pointer-events: none !important;\n z-index: 2147483000 !important;\n}\n\n:host(jev-devtools-root)::before,\n:host(jev-devtools-root)::after {\n content: none !important;\n display: none !important;\n}\n\n.jev-devtools {\n all: initial;\n /* all does not reset custom properties. Establish our own inherited defaults;\n cameras and probability cards can still set local values below this root. */\n --diagram-grid-opacity: 1;\n --diagram-grid-weight: 1;\n --lab-noul-probability: 1;\n --flow-item-offset: 0px;\n display: block;\n direction: ltr;\n unicode-bidi: isolate;\n pointer-events: auto;\n color-scheme: dark;\n font:\n 12px system-ui,\n sans-serif;\n color: #eee8ed;\n line-height: 1.5;\n}\n.jev-devtools,\n.jev-devtools * {\n box-sizing: border-box;\n}\n.jev-devtools button,\n.jev-devtools input,\n.jev-devtools select,\n.jev-devtools textarea {\n font: inherit;\n color: inherit;\n border: 1px solid #3b303d;\n border-radius: 5px;\n background: #17121b;\n}\n.jev-devtools button {\n padding: 7px 11px;\n cursor: pointer;\n}\n.jev-devtools button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n.jev-devtools input,\n.jev-devtools select {\n padding: 8px;\n width: 100%;\n}\n.jev-devtools select {\n appearance: none;\n padding-right: 34px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='m4 6 4 4 4-4' stroke='%23bcadbf' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 10px center;\n background-size: 16px 16px;\n}\n@media (forced-colors: active) {\n .jev-devtools select {\n /* Let the system draw a visible arrow in high-contrast themes. */\n appearance: auto;\n background-image: none;\n }\n}\n.jev-devtools button:hover:not(:disabled) {\n border-color: #e551ba;\n}\n.jev-devtools :is(button, input, select, textarea, summary):focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: 3px;\n}\n.jev-devtools .eyebrow {\n font-size: 10px;\n letter-spacing: 0.12em;\n color: #bc9db5;\n text-transform: uppercase;\n}\n.jev-devtools .jev-inspector {\n height: 100%;\n display: flex;\n color: #eee8ed;\n background: #0d0a0f;\n font:\n 12px \"DM Sans\",\n sans-serif;\n min-height: 0;\n overflow: hidden;\n}\n.jev-devtools .jev-inspector h2 {\n font-size: 15px;\n margin: 0;\n font-weight: 600;\n}\n.jev-devtools .jev-inspector h3 {\n font-size: 13px;\n margin: 0 0 8px;\n}\n.jev-devtools .jev-inspector code,\n.jev-devtools .jev-inspector pre {\n font:\n 11px \"JetBrains Mono\",\n monospace;\n}\n.jev-devtools .jev-inspector pre {\n line-height: 1.7;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 460px;\n overflow: auto;\n background: #100d13;\n padding: 16px;\n border: 1px solid #2e2533;\n border-radius: 5px;\n}\n.jev-devtools .jev-inspector .muted {\n color: #a89bac;\n font-size: 11px;\n line-height: 1.6;\n}\n.jev-devtools .request-list {\n flex: 0 0 300px;\n border-right: 1px solid #302634;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.jev-devtools .request-list > header {\n padding: 17px 14px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n.jev-devtools .request-list h2 span {\n font-size: 11px;\n color: #b796ad;\n margin-left: 5px;\n}\n.jev-devtools .request-list button {\n font-size: 11px;\n}\n.jev-devtools .list-filters {\n display: flex;\n gap: 7px;\n padding: 0 12px 12px;\n}\n.jev-devtools .list-filters input {\n min-width: 0;\n}\n.jev-devtools .list-filters select {\n width: 100px;\n flex-shrink: 0;\n font-size: 11px;\n}\n.jev-devtools .request-items {\n overflow: auto;\n flex: 1;\n}\n.jev-devtools .request-navigation {\n display: none;\n}\n.jev-devtools .request-row {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n text-align: left;\n border: 0;\n border-top: 1px solid #251d2a;\n border-radius: 0;\n background: transparent;\n padding: 16px 12px;\n}\n.jev-devtools .request-row.selected {\n background: #251423;\n box-shadow: inset 2px 0 #e551ba;\n}\n.jev-devtools .request-row strong {\n display: block;\n font-size: 12px;\n font-weight: 500;\n}\n.jev-devtools .request-row small {\n display: block;\n font-size: 10px;\n color: #a99aac;\n margin-top: 6px;\n}\n.jev-devtools .row-status {\n font-size: 9px;\n color: #ad9bb0;\n margin-left: auto;\n}\n.jev-devtools .status-dot {\n width: 6px;\n height: 6px;\n display: inline-block;\n border-radius: 50%;\n background: #b09ab7;\n flex-shrink: 0;\n}\n.jev-devtools .status-dot.success {\n background: #b3c79c;\n}\n.jev-devtools .status-dot.pending {\n background: #e551ba;\n}\n.jev-devtools .status-dot.error {\n background: #ee8e85;\n}\n.jev-devtools .request-list footer {\n padding: 12px;\n color: #9e8ea5;\n border-top: 1px solid #302634;\n font-size: 10px;\n display: flex;\n gap: 7px;\n}\n.jev-devtools .request-detail {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.jev-devtools .detail-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 18px 24px 10px;\n}\n.jev-devtools .detail-header h2 {\n margin-top: 6px;\n}\n.jev-devtools .metrics {\n display: flex;\n gap: 24px;\n font:\n 12px \"JetBrains Mono\",\n monospace;\n white-space: nowrap;\n}\n.jev-devtools .metrics small {\n display: block;\n font:\n 9px \"DM Sans\",\n sans-serif;\n color: #97879f;\n margin-top: 6px;\n}\n.jev-devtools .detail-meta {\n display: flex;\n gap: 20px;\n flex-wrap: wrap;\n padding: 0 24px 14px;\n color: #a08aa5;\n}\n.jev-devtools .detail-meta code {\n font-size: 10px;\n}\n.jev-devtools .detail-tabs {\n display: flex;\n gap: 20px;\n border-bottom: 1px solid #302634;\n padding: 0 24px;\n}\n.jev-devtools .detail-tabs button {\n border: 0;\n border-radius: 0;\n background: none;\n padding: 10px 0;\n color: #a89bac;\n font-size: 11px;\n}\n.jev-devtools .detail-tabs button[aria-pressed=\"true\"] {\n box-shadow: inset 0 -2px #e551ba;\n color: #f5c1e6;\n}\n.jev-devtools .detail-body {\n overflow: auto;\n flex: 1;\n min-height: 0;\n padding: 16px 24px 28px;\n background-image: radial-gradient(#342337 0.6px, transparent 0.6px);\n background-size: 16px 16px;\n}\n.jev-devtools .question-node {\n position: relative;\n border: 1px solid #75516c;\n border-radius: 6px;\n padding: 15px;\n background: #110d14;\n min-width: 0;\n}\n/* Retain the legacy question connectors and standalone suppression together.\n Remove only after resolving the narrow-screen visual differences in\n https://github.com/diegoag64/jev-devtools/issues/57. */\n.jev-devtools .question-node:before {\n content: \"\";\n position: absolute;\n right: 100%;\n top: 50%;\n width: 21px;\n border-top: 1px solid #7f416d;\n}\n.jev-devtools .question-branches .question-node:not(:first-child):before {\n display: none;\n}\n.jev-devtools .question-node header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.jev-devtools .question-node header h3 {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.jev-devtools .type {\n font:\n 9px \"JetBrains Mono\",\n monospace;\n color: #e59acc;\n border: 1px solid #5e3654;\n border-radius: 3px;\n padding: 3px 5px;\n}\n.jev-devtools .answer-value {\n display: block;\n font-size: 20px;\n font-weight: 500;\n margin: 15px 0;\n color: #f0c7e5;\n}\n.jev-devtools .answer-value small {\n font-size: 10px;\n color: #b5a1ba;\n}\n.jev-devtools .probability {\n margin: 10px 0;\n font-size: 10px;\n}\n.jev-devtools .probability > div:first-child {\n display: flex;\n justify-content: space-between;\n gap: 5px;\n margin-bottom: 5px;\n}\n.jev-devtools .probability b {\n font-weight: 400;\n color: #d8abc9;\n}\n.jev-devtools .bar {\n height: 4px;\n background: #302033;\n border-radius: 2px;\n overflow: hidden;\n}\n.jev-devtools .bar i {\n display: block;\n height: 100%;\n background: #e551ba;\n}\n.jev-devtools .confidence {\n font-size: 10px;\n color: #a68cac;\n border-top: 1px solid #302634;\n padding-top: 10px;\n display: flex;\n justify-content: space-between;\n}\n.jev-devtools .confidence b {\n color: #d1bad2;\n font-weight: 400;\n}\n.jev-devtools details {\n margin-top: 12px;\n font-size: 10px;\n color: #b7a0bd;\n}\n.jev-devtools summary {\n cursor: pointer;\n}\n.jev-devtools .standalone {\n margin-bottom: 14px;\n max-width: 750px;\n}\n.jev-devtools .standalone:before {\n display: none;\n}\n.jev-devtools .standalone .answer {\n max-width: 450px;\n}\n.jev-devtools .box-preview {\n position: relative;\n margin-top: 14px;\n min-height: 80px;\n background: #19121c;\n}\n.jev-devtools .box-preview img {\n width: 100%;\n display: block;\n}\n.jev-devtools .bounding-box {\n position: absolute;\n border: 1.5px solid #e551ba;\n background: #e551ba15;\n}\n.jev-devtools .coordinates {\n display: block;\n font-size: 9px !important;\n line-height: 1.5;\n margin-top: 8px;\n color: #ac93b0;\n}\n.jev-devtools .context-image {\n max-width: 400px;\n width: 100%;\n border: 1px solid #75516c;\n border-radius: 6px;\n}\n.jev-devtools .empty {\n padding: 20px;\n color: #a294a8;\n line-height: 1.7;\n}\n.jev-devtools .empty-detail {\n margin: auto;\n padding: 40px;\n text-align: center;\n color: #b2a0ba;\n}\n.jev-devtools .empty-symbol {\n font-size: 36px;\n color: #e551ba;\n display: block;\n margin-bottom: 20px;\n}\n.jev-devtools .empty-detail p {\n font-size: 12px;\n line-height: 1.8;\n}\n.jev-devtools .error-banner {\n border: 1px solid #874b50;\n background: #31191e;\n padding: 12px;\n line-height: 1.5;\n margin-bottom: 16px;\n}\n.jev-devtools .warning {\n color: #e2b97d;\n}\n.jev-devtools .jev-inspector table {\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n}\n.jev-devtools .jev-inspector th,\n.jev-devtools .jev-inspector td {\n border-bottom: 1px solid #302634;\n padding: 14px;\n}\n@media (max-width: 1100px) {\n .jev-devtools .question-branches .question-node:not(:first-child):before {\n display: block;\n }\n .jev-devtools .metrics {\n gap: 12px;\n }\n .jev-devtools .request-list {\n flex-basis: 250px;\n }\n}\n@media (max-width: 700px) {\n .jev-devtools .jev-inspector {\n flex-direction: column;\n }\n .jev-devtools .request-list {\n flex-basis: 140px;\n flex-shrink: 0;\n border-right: 0;\n border-bottom: 1px solid #302634;\n }\n .jev-devtools .request-list > header {\n padding: 6px 12px;\n }\n .jev-devtools .request-list footer {\n display: none;\n }\n .jev-devtools .list-filters {\n padding-bottom: 6px;\n }\n .jev-devtools .request-items {\n display: flex;\n }\n .jev-devtools .request-row {\n min-width: 240px;\n padding: 8px 12px;\n }\n /* Requests use separate list/detail screens so history cannot collapse into\n the fixed-height strip used by flow runs. CSS keeps desktop split panes. */\n .jev-devtools .jev-inspector[data-mobile-view=\"list\"] > .request-list {\n flex: 1;\n }\n .jev-devtools .jev-inspector[data-mobile-view=\"list\"] > .request-detail,\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] .list-filters,\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] .request-items {\n display: none;\n }\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] > .request-list {\n flex: none;\n }\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] .request-navigation {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 44px;\n margin: 6px 12px;\n text-align: left;\n }\n .jev-devtools .jev-inspector[data-mobile-view] .request-items {\n flex-direction: column;\n }\n .jev-devtools .jev-inspector[data-mobile-view] .request-row {\n flex: none;\n min-width: 0;\n min-height: 60px;\n padding: 12px;\n }\n .jev-devtools\n .jev-inspector[data-mobile-view]\n .request-row\n > span:nth-child(2) {\n min-width: 0;\n overflow-wrap: anywhere;\n }\n .jev-devtools .jev-inspector[data-mobile-view] > .request-detail {\n overflow: auto;\n }\n .jev-devtools\n .jev-inspector[data-mobile-view]\n > .request-detail\n > :is(.detail-header, .detail-meta, .detail-tabs) {\n flex: none;\n }\n .jev-devtools\n .jev-inspector[data-mobile-view]\n > .request-detail\n > .detail-body-flow {\n /* Let metadata scroll above the diagram instead of clipping its toolbar. */\n flex: 1 0 auto;\n min-height: min-content;\n }\n .jev-devtools .flow-details-bar > span {\n display: none;\n }\n .jev-devtools .detail-header {\n padding: 12px;\n }\n .jev-devtools .detail-meta,\n .jev-devtools .detail-tabs {\n padding-left: 12px;\n padding-right: 12px;\n }\n .jev-devtools .detail-tabs {\n overflow-x: auto;\n flex-shrink: 0;\n }\n .jev-devtools .detail-body {\n padding: 12px;\n }\n .jev-devtools .metrics {\n display: none;\n }\n .jev-devtools .question-node:before {\n width: 13px;\n }\n .jev-devtools .question-node {\n padding: 12px;\n }\n}\n.jev-devtools .question-node:before,\n.jev-devtools .question-branches .question-node:not(:first-child):before {\n display: block;\n right: auto;\n left: 50%;\n top: -21px;\n width: 0;\n height: 20px;\n border-top: 0;\n border-left: 1px solid #7f416d;\n}\n.jev-devtools .standalone:before {\n display: none;\n}\n@media (max-width: 900px) {\n .jev-devtools .question-node:before,\n .jev-devtools .question-branches .question-node:not(:first-child):before {\n height: 12px;\n top: -13px;\n }\n .jev-devtools .question-branches .question-node:first-child:before {\n height: 20px;\n top: -21px;\n }\n}\n\n.jev-devtools.jev-drawer {\n margin: 0;\n padding: 0;\n position: fixed;\n z-index: 1000;\n inset: auto 0 0;\n display: flex;\n flex-direction: column;\n background: #0d0a0f;\n color: #eee8ed;\n border-top: 1px solid #604058;\n box-shadow: 0 -12px 48px #0005;\n font:\n 12px \"DM Sans\",\n sans-serif;\n}\n.jev-devtools.jev-drawer[hidden],\n.jev-devtools.jev-drawer-trigger[hidden] {\n display: none;\n}\n.jev-devtools .jev-drawer-resize {\n position: absolute;\n z-index: 1;\n top: -6px;\n width: 100%;\n height: 12px;\n cursor: ns-resize;\n touch-action: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.jev-devtools .jev-drawer-resize span {\n width: 42px;\n height: 3px;\n border-radius: 3px;\n background: #79506f;\n}\n.jev-devtools .jev-drawer-resize:hover span,\n.jev-devtools .jev-drawer-resize:focus-visible span {\n background: #e551ba;\n}\n.jev-devtools .jev-drawer-resize:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: -2px;\n}\n.jev-devtools .jev-drawer-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex: 0 0 46px;\n padding: 0 16px;\n border-bottom: 1px solid #302634;\n}\n.jev-devtools .jev-drawer-header strong {\n display: flex;\n align-items: center;\n gap: 9px;\n font-weight: 600;\n}\n.jev-devtools .jev-drawer-header > div {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.jev-devtools .jev-drawer-header button {\n background: transparent;\n border-color: transparent;\n font-size: 11px;\n color: #c4afc8;\n}\n.jev-devtools .jev-drawer-header .jev-drawer-close {\n font-size: 22px;\n line-height: 1;\n padding: 4px 8px;\n}\n.jev-devtools .jev-drawer-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n.jev-devtools.jev-drawer-trigger {\n position: fixed;\n bottom: 18px;\n right: 18px;\n z-index: 1000;\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 10px 16px;\n border: 1px solid #a24487;\n border-radius: 8px;\n cursor: pointer;\n background: #211321;\n color: #f1cbe6;\n font:\n 600 13px \"DM Sans\",\n sans-serif;\n box-shadow: 0 4px 20px #0005;\n}\n.jev-devtools.jev-drawer-trigger span {\n color: #e551ba;\n font-size: 20px;\n}\n/* Baseline cards and authored left-to-right composition from the lab's Jev presets. */\n.jev-devtools .detail-body.detail-body-flow {\n display: flex;\n padding: 0;\n overflow: hidden;\n}\n.jev-devtools .lab-flow {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n.jev-devtools .lab-flow-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-shrink: 0;\n padding: 12px 16px;\n flex-wrap: wrap;\n}\n.jev-devtools .lab-flow-toolbar label,\n.jev-devtools .flow-step-select {\n display: flex;\n gap: 10px;\n align-items: center;\n font-size: 11px;\n min-width: 0;\n}\n.jev-devtools .lab-flow-toolbar select,\n.jev-devtools .flow-step-select select {\n width: auto;\n max-width: 240px;\n font-size: 11px;\n}\n.jev-devtools .lab-flow-toolbar > div {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.jev-devtools .lab-flow-toolbar button,\n.jev-devtools .flow-canvas-controls button {\n font-size: 11px;\n}\n.jev-devtools .lab-flow-toolbar output,\n.jev-devtools .flow-canvas-controls output {\n font-size: 11px;\n min-width: 35px;\n text-align: center;\n}\n.jev-devtools .lab-flow-viewport {\n position: relative;\n flex: 1;\n min-height: 100px;\n overflow: hidden;\n background-color: #0d0a0f;\n background-image: radial-gradient(\n rgb(70 52 73 / var(--diagram-grid-opacity, 1))\n calc(0.8px * var(--diagram-grid-weight, 1)),\n transparent calc(0.8px * var(--diagram-grid-weight, 1))\n );\n background-size: 20px 20px;\n border-block: 1px solid #302634;\n cursor: grab;\n touch-action: none;\n user-select: none;\n overscroll-behavior: contain;\n}\n.jev-devtools .lab-flow-viewport[data-panning] {\n cursor: grabbing;\n}\n.jev-devtools .lab-flow-viewport:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: -2px;\n}\n.jev-devtools .lab-flow-caption {\n flex-shrink: 0;\n padding: 8px 16px;\n margin: 0;\n color: #c6a7bd;\n font-size: 11px;\n line-height: 1.6;\n}\n.jev-devtools .lab-flow-canvas {\n position: absolute;\n inset: 0 auto auto 0;\n transform-origin: top left;\n will-change: transform;\n font-family: system-ui, sans-serif;\n color: #f5f3f6;\n}\n.jev-devtools .lab-connections {\n position: absolute;\n inset: 0;\n overflow: visible;\n}\n.jev-devtools .lab-connections path {\n fill: none;\n stroke: #645a68;\n stroke-width: 1;\n}\n.jev-devtools .lab-connections path.active {\n stroke: #968592;\n}\n.jev-devtools .lab-connections text {\n fill: #b7b1bc;\n font:\n 12px ui-monospace,\n monospace;\n}\n.jev-devtools .lab-flow-dot {\n fill: #e551ba;\n}\n.jev-devtools .lab-card {\n position: absolute;\n border: 1.3px solid #f5f3f6;\n border-radius: 14px;\n padding: 16px;\n background: #0d0a0f;\n overflow: hidden;\n}\n.jev-devtools .lab-eyebrow {\n display: block;\n color: #e551ba;\n font:\n 500 10px ui-monospace,\n monospace;\n letter-spacing: 1px;\n margin: 0 0 7px;\n}\n.jev-devtools .lab-card h3 {\n font-size: 16px;\n font-weight: 600;\n line-height: 1.3;\n letter-spacing: -0.2px;\n margin: 0 0 10px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.jev-devtools .lab-card p {\n font-size: 13px;\n line-height: 1.4;\n margin: 10px 0;\n}\n.jev-devtools .lab-copy {\n display: -webkit-box;\n -webkit-line-clamp: 5;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.jev-devtools .lab-probability {\n margin: 17px 0;\n}\n.jev-devtools .lab-probability > div:first-child {\n display: flex;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 9px;\n font-size: 15px;\n}\n.jev-devtools .lab-probability span {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.jev-devtools .lab-probability code {\n font:\n 13px ui-monospace,\n monospace;\n}\n.jev-devtools .lab-track {\n height: 7px;\n border-radius: 4px;\n background: #f5f3f61a;\n overflow: hidden;\n}\n.jev-devtools .lab-track i {\n display: block;\n height: 100%;\n border-radius: 4px;\n background: #e551ba;\n}\n.jev-devtools .lab-track i.lab-alternative {\n opacity: 0.35;\n}\n.jev-devtools .lab-card .lab-result {\n font-size: 14px;\n margin-top: 24px;\n}\n.jev-devtools .lab-card .lab-small {\n font-size: 12px;\n color: #b7b1bc;\n margin-top: 20px;\n}\n.jev-devtools .lab-idle {\n opacity: 0.65;\n}\n.jev-devtools .lab-scale {\n position: relative;\n margin-top: 18px;\n border-top: 1px solid #b7b1bc;\n display: flex;\n justify-content: space-between;\n padding-top: 9px;\n}\n.jev-devtools .lab-scale > span {\n position: absolute;\n top: -5px;\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background: #e551ba;\n transform: translateX(-50%);\n}\n.jev-devtools .lab-big {\n display: block;\n font-size: 36px;\n font-weight: 500;\n margin-top: 26px;\n}\n.jev-devtools .lab-endpoints {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: #b7b1bc;\n margin-top: 10px;\n}\n.jev-devtools .lab-image {\n position: relative;\n}\n.jev-devtools .lab-image img {\n display: block;\n width: 100%;\n}\n.jev-devtools .lab-box {\n position: absolute;\n border: 1.5px solid #e551ba;\n background: #e551ba15;\n}\n.jev-devtools .lab-source-image {\n width: 100%;\n max-height: 105px;\n object-fit: contain;\n}\n.jev-devtools .lab-coordinates {\n font-family: ui-monospace, monospace;\n}\n@media (prefers-reduced-motion: reduce) {\n .jev-devtools .lab-flow-dot {\n display: none;\n }\n}\n/* The public TypeSafe mark travels with the package, independent of host assets. */\n.jev-devtools .typesafe-mark {\n display: inline-block;\n flex: 0 0 23px;\n width: 23px;\n height: 32px;\n overflow: hidden;\n line-height: 0;\n}\n.jev-devtools .typesafe-mark img {\n display: block;\n height: 32px;\n width: auto;\n max-width: none;\n}\n\n.jev-devtools .detail-body-questions {\n display: flex;\n flex-direction: column;\n overflow: auto;\n}\n.jev-devtools .questions-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 180px;\n}\n.jev-devtools .questions-navigation {\n display: flex;\n align-items: end;\n gap: 8px;\n flex-wrap: wrap;\n flex-shrink: 0;\n}\n.jev-devtools .questions-navigation label {\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n flex: 1;\n min-width: 240px;\n max-width: 480px;\n font-size: 11px;\n color: #bc9db5;\n}\n.jev-devtools .questions-navigation select {\n display: block;\n min-width: 0;\n}\n.jev-devtools .questions-navigation > span {\n align-self: center;\n color: #a89bac;\n}\n.jev-devtools .questions-help {\n margin: 8px 0;\n flex-shrink: 0;\n}\n.jev-devtools .questions-viewport {\n flex: 1;\n min-height: 0;\n overflow: auto;\n overflow-anchor: none;\n scrollbar-gutter: stable;\n padding: 3px;\n}\n.jev-devtools .questions-spacer {\n position: relative;\n width: 100%;\n}\n.jev-devtools .questions-row {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n padding-bottom: 14px;\n}\n.jev-devtools .questions-row .standalone {\n margin-bottom: 0;\n}\n.jev-devtools .questions-viewport:focus-visible,\n.jev-devtools .questions-row article:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: -2px;\n}\n\n/* Reserve measured card geometry while rapid scrolling uses a light preview. */\n.jev-devtools .questions-row[data-preview=\"true\"] article {\n height: 100%;\n}\n.jev-devtools .question-scroll-preview p {\n margin-top: 16px;\n}\n\n/* The full distribution stays at reading size, outside the zoomable canvas. */\n.jev-devtools .lab-flow-viewport[hidden] {\n display: none;\n}\n.jev-devtools .lab-choice-browser {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n padding: 8px 16px;\n gap: 10px;\n}\n.jev-devtools .lab-choice-controls {\n display: flex;\n gap: 12px;\n align-items: end;\n flex-wrap: wrap;\n font-size: 12px;\n}\n.jev-devtools .lab-choice-controls label {\n display: grid;\n gap: 5px;\n}\n.jev-devtools .lab-choice-table-scroll {\n overflow: auto;\n overscroll-behavior: contain;\n min-height: 0;\n}\n.jev-devtools .lab-choice-browser table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n.jev-devtools .lab-choice-browser caption {\n text-align: left;\n padding: 8px 0;\n color: #aaa0af;\n}\n.jev-devtools .lab-choice-browser th,\n.jev-devtools .lab-choice-browser td {\n padding: 9px 12px;\n border-bottom: 1px solid #302634;\n text-align: left;\n}\n.jev-devtools .lab-choice-browser th {\n overflow-wrap: anywhere;\n max-width: 320px;\n}\n.jev-devtools .lab-choice-browser td {\n font-variant-numeric: tabular-nums;\n}\n.jev-devtools .lab-choice-browser code {\n white-space: normal;\n overflow-wrap: anywhere;\n}\n.jev-devtools .lab-choice-returned {\n display: block;\n font-size: 10px;\n color: #e551ba;\n}\n.jev-devtools .lab-choice-pagination {\n display: flex;\n align-items: center;\n gap: 12px;\n justify-content: space-between;\n font-size: 12px;\n}\n.jev-devtools .lab-choice-remainder button {\n margin-top: 7px;\n}\n\n.jev-devtools .lab-flow-compact .lab-output {\n padding: 12px;\n height: 104px;\n}\n.jev-devtools .lab-flow-compact .lab-output .lab-eyebrow {\n margin-bottom: 6px;\n}\n.jev-devtools .lab-flow-compact .lab-output h3 {\n font-size: 16px;\n margin-bottom: 8px;\n}\n.jev-devtools .lab-flow-compact .lab-output p {\n font-size: 12px;\n margin: 4px 0;\n}\n.jev-devtools .lab-flow-compact .lab-result {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.jev-devtools .lab-choice-remainder {\n padding: 12px;\n}\n.jev-devtools .lab-choice-remainder .lab-small {\n margin: 8px 0 0;\n font-size: 11px;\n}\n\n.jev-devtools .detail-body.detail-body-flow {\n flex-direction: column;\n}\n.jev-devtools .detail-body-flow > .warning,\n.jev-devtools .detail-body-flow > .error-banner {\n flex-shrink: 0;\n margin: 6px 16px;\n}\n.jev-devtools .lab-choice-browser {\n overflow: auto;\n}\n.jev-devtools .lab-choice-table-scroll {\n flex: 1 0 110px;\n}\n\n/* Only freshly revealed branches enter; pan/zoom remains on the canvas. */\n.jev-devtools .lab-card-enter {\n animation: jev-card-enter 280ms cubic-bezier(0.2, 0.8, 0.2, 1) backwards;\n}\n.jev-devtools .lab-branch-enter {\n stroke-dasharray: 1;\n animation: jev-branch-enter 280ms ease-out backwards;\n}\n@keyframes jev-card-enter {\n from {\n opacity: 0;\n transform: translateX(-16px) scale(0.96);\n }\n}\n@keyframes jev-branch-enter {\n from {\n opacity: 0;\n stroke-dashoffset: 1;\n }\n to {\n opacity: 1;\n stroke-dashoffset: 0;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .jev-devtools .lab-card-enter,\n .jev-devtools .lab-branch-enter {\n animation: none;\n }\n}\n\n/* Connected requests share one camera and one opt-in full request detail.\n :where scopes these selectors without changing their existing specificity. */\n:where(.jev-devtools) .whole-flow {\n min-width: 0;\n overflow: hidden;\n}\n:where(.jev-devtools)\n .flow-overview-stack:not(.flow-inline-overview)\n > .whole-flow {\n /* Short drawers must scroll metadata instead of clipping the canvas controls. */\n overflow: auto;\n}\n:where(.jev-devtools) .whole-flow > .detail-header {\n flex-wrap: wrap;\n flex-shrink: 0;\n}\n:where(.jev-devtools) .flow-workspace {\n display: flex;\n flex-direction: column;\n flex: 1 0 auto;\n min-height: 0;\n overflow: hidden;\n}\n:where(.jev-devtools) .flow-details-panel {\n flex: none;\n min-width: 0;\n background: #130f17;\n border-top: 1px solid #342a38;\n}\n:where(.jev-devtools) .flow-details-bar {\n display: flex;\n align-items: center;\n gap: 12px;\n height: 36px;\n padding: 0 12px;\n}\n.jev-devtools .flow-details-bar button {\n flex: none;\n padding: 4px 8px;\n font-size: 11px;\n}\n:where(.jev-devtools) .flow-details-bar > span {\n color: #b5a6bc;\n font-size: 11px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n:where(.jev-devtools) .flow-details-body {\n overflow: auto;\n overscroll-behavior: contain;\n scrollbar-gutter: stable;\n}\n:where(.jev-devtools) .flow-details-resize {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 8px;\n cursor: ns-resize;\n touch-action: none;\n user-select: none;\n}\n:where(.jev-devtools) .flow-details-resize span {\n width: 40px;\n height: 2px;\n border-radius: 2px;\n background: #6c5874;\n}\n:where(.jev-devtools) .flow-details-resize:hover,\n:where(.jev-devtools) .flow-details-resize:focus-visible {\n background: #392239;\n outline: 1px solid #e551ba;\n outline-offset: -1px;\n}\n:where(.jev-devtools) .flow-overview-viewport {\n background-color: #08060a;\n position: relative;\n /* The panel's grid row reserves its actual height, including wrapped buttons. */\n display: grid;\n min-height: min-content;\n flex: 1 1 0;\n overflow: hidden;\n touch-action: none;\n cursor: grab;\n background-image: radial-gradient(\n rgb(83 67 87 / var(--diagram-grid-opacity, 1))\n calc(1px * var(--diagram-grid-weight, 1)),\n transparent calc(1px * var(--diagram-grid-weight, 1))\n );\n background-size: 20px 20px;\n}\n.jev-devtools .flow-canvas-controls {\n align-self: start;\n justify-self: end;\n margin: 12px;\n z-index: 1;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n max-width: calc(100% - 24px);\n padding: 8px;\n border: 1px solid #3b303d;\n border-radius: 10px;\n background: #17121b;\n box-shadow: 0 4px 16px #0006;\n cursor: default;\n}\n:where(.jev-devtools) .flow-overview-viewport[data-panning] {\n cursor: grabbing;\n}\n:where(.jev-devtools) .flow-overview-canvas {\n position: absolute;\n /* Keep camera coordinates at the viewport origin when the overlay uses grid. */\n top: 0;\n left: 0;\n transform-origin: 0 0;\n}\n:where(.jev-devtools) .flow-connections {\n position: absolute;\n inset: 0;\n overflow: visible;\n color: #c0b8c4;\n pointer-events: none;\n}\n:where(.jev-devtools) .flow-step {\n position: absolute;\n min-height: 172px;\n overflow-wrap: anywhere;\n display: flex;\n flex-direction: column;\n gap: 8px;\n text-align: left;\n padding: 14px;\n border: 1px solid #736476;\n border-radius: 12px;\n background: #151017;\n color: inherit;\n}\n:where(.jev-devtools) .flow-step strong {\n white-space: normal;\n width: 100%;\n}\n:where(.jev-devtools) .flow-step-description {\n overflow-wrap: anywhere;\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-step.flow-request {\n display: block;\n padding: 0;\n background: #0d0a0f;\n /* Skip offscreen paint/layout while preserving each question/expansion state. */\n content-visibility: auto;\n contain-intrinsic-size: auto 640px;\n}\n:where(.jev-devtools) .flow-request-heading {\n width: 100%;\n min-height: 64px;\n gap: 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n text-align: left;\n padding: 12px 20px;\n border: 0;\n border-bottom: 1px solid #342a38;\n border-radius: 12px 12px 0 0;\n background: transparent;\n color: inherit;\n}\n:where(.jev-devtools) .flow-request-heading .eyebrow {\n display: block;\n margin-bottom: 4px;\n}\n.jev-devtools .lab-flow-embedded {\n width: 1120px;\n display: block;\n}\n.jev-devtools .lab-flow-surface {\n position: relative;\n width: 1120px;\n}\n.jev-devtools .lab-flow-surface[hidden] {\n display: none;\n}\n.jev-devtools .lab-flow-embedded .lab-flow-canvas {\n will-change: auto;\n}\n.jev-devtools .lab-flow-embedded .lab-choice-browser {\n height: 520px;\n}\n:where(.jev-devtools) .flow-step.selected {\n outline: 2px solid #e551ba;\n outline-offset: 3px;\n}\n:where(.jev-devtools) .flow-step[data-status=\"completed\"] {\n border-color: #e551ba;\n}\n:where(.jev-devtools) .flow-step[data-status=\"running\"] {\n border-color: #e551ba;\n background: #2b1728;\n}\n:where(.jev-devtools) .flow-step[data-status=\"untaken\"],\n:where(.jev-devtools) .flow-step[data-status=\"blocked\"] {\n color: #948797;\n border-color: #403746;\n background: #151017;\n}\n:where(.jev-devtools) .flow-step[data-status=\"error\"] {\n border-color: #f17c78;\n}\n:where(.jev-devtools) .flow-step[data-status=\"cancelled\"] {\n border-style: dashed;\n}\n:where(.jev-devtools) .flow-selection {\n padding: 12px 20px;\n border-top: 1px solid #342a38;\n}\n:where(.jev-devtools) .flow-selection p {\n margin: 6px 0;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-request-detail {\n min-height: 450px;\n border-top: 1px solid #534357;\n}\n:where(.jev-devtools) .flow-request-detail > .request-detail {\n min-height: 450px;\n}\n\n:where(.jev-devtools) .flow-card-heading,\n:where(.jev-devtools) .flow-card-footer,\n:where(.jev-devtools) .flow-join-summary,\n:where(.jev-devtools) .flow-lane {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n}\n:where(.jev-devtools) .flow-card-heading {\n justify-content: flex-start;\n}\n:where(.jev-devtools) .flow-task .flow-kind-icon {\n border: 1px solid currentColor;\n border-radius: 6px;\n padding: 4px;\n}\n:where(.jev-devtools) .flow-task-preview {\n display: grid;\n gap: 8px;\n min-width: 0;\n}\n.jev-devtools .flow-task-preview .diagram-content {\n max-height: 280px;\n padding: 8px 10px;\n border: 1px solid #35283b;\n border-radius: 4px;\n background: #100c13;\n}\n:where(.jev-devtools) .flow-task-preview small {\n color: #a89bac;\n font-size: 11px;\n}\n:where(.jev-devtools) .flow-task-details {\n display: grid;\n gap: 12px;\n}\n:where(.jev-devtools) .flow-task-details details {\n min-width: 0;\n}\n:where(.jev-devtools) .flow-task-details summary {\n cursor: pointer;\n margin-bottom: 8px;\n}\n:where(.jev-devtools) .flow-task-details .diagram-content {\n max-height: 320px;\n overflow: auto;\n}\n:where(.jev-devtools) .flow-kind-icon {\n width: 26px;\n height: 26px;\n color: #e551ba;\n flex-shrink: 0;\n}\n:where(.jev-devtools) .flow-kind-icon svg {\n width: 100%;\n height: 100%;\n}\n:where(.jev-devtools) .flow-card-heading .eyebrow {\n font-size: 10px;\n}\n:where(.jev-devtools) .flow-status {\n margin-left: auto;\n font-size: 10px;\n border-radius: 20px;\n padding: 4px 8px;\n background: #342031;\n color: #f0b7e0;\n}\n:where(.jev-devtools) .flow-step[data-status=\"untaken\"] .flow-status {\n background: #241e28;\n color: #948797;\n}\n:where(.jev-devtools) .flow-step[data-status=\"untaken\"] .flow-kind-icon {\n color: #948797;\n}\n:where(.jev-devtools) .flow-card-body > strong {\n white-space: normal;\n font-size: 18px;\n line-height: 1.25;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-step-description {\n line-height: 1.5;\n color: #b9aebf;\n white-space: pre-wrap;\n}\n:where(.jev-devtools) .flow-card-footer {\n margin-top: auto;\n padding-top: 10px;\n border-top: 1px solid #342a38;\n font-size: 10px;\n color: #a092a7;\n}\n:where(.jev-devtools) .flow-card-footer code {\n max-width: 45%;\n white-space: normal;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-card-footer small {\n text-align: right;\n}\n:where(.jev-devtools) .flow-join-summary {\n margin-top: 4px;\n font-size: 11px;\n color: #c9b9cd;\n}\n:where(.jev-devtools) .flow-join-summary > span {\n color: #ef9ed9;\n font-variant-numeric: tabular-nums;\n}\n:where(.jev-devtools) .flow-lanes {\n display: flex;\n flex-direction: column;\n gap: 4px;\n width: 100%;\n}\n:where(.jev-devtools) .flow-lane {\n border-radius: 5px;\n background: #211923;\n padding: 6px 8px;\n font-size: 11px;\n min-height: 28px;\n}\n:where(.jev-devtools) .flow-lane > span {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-lane > span > span {\n color: #ef9ed9;\n margin-right: 4px;\n}\n:where(.jev-devtools) .flow-lane small {\n color: #b9aebf;\n}\n:where(.jev-devtools) .flow-lane-details {\n padding-left: 18px;\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-request-heading > span:first-child {\n min-width: 0;\n max-width: 80%;\n}\n:where(.jev-devtools) .flow-request-description {\n display: block;\n white-space: pre-wrap;\n font-size: 12px;\n line-height: 1.5;\n color: #b9aebf;\n margin-top: 5px;\n overflow-wrap: anywhere;\n}\n\n/* Structured context stays within the card; its gestures never move the camera. */\n.jev-devtools .lab-content-card {\n display: flex;\n flex-direction: column;\n padding: 14px;\n}\n.jev-devtools .lab-content-card h3 {\n font-size: 14px;\n margin-bottom: 8px;\n flex-shrink: 0;\n}\n.jev-devtools .lab-content-card .lab-eyebrow {\n font-size: 9px;\n margin-bottom: 5px;\n flex-shrink: 0;\n}\n.jev-devtools .diagram-content {\n min-height: 0;\n flex: 1;\n overflow: auto;\n overscroll-behavior: contain;\n touch-action: pan-y;\n scrollbar-width: thin;\n scrollbar-color: #514256 transparent;\n padding-right: 4px;\n font-size: 12px;\n line-height: 1.5;\n color: #d5cdd9;\n cursor: auto;\n user-select: text;\n}\n.jev-devtools .diagram-content:focus-visible {\n outline: 1px solid #e551ba;\n outline-offset: -1px;\n border-radius: 3px;\n}\n.jev-devtools .diagram-property {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 8px;\n padding: 5px 0;\n border-bottom: 1px solid #261e2a;\n}\n.jev-devtools .diagram-property-text {\n display: block;\n}\n.jev-devtools .diagram-property-key {\n font:\n 10px/1.5 ui-monospace,\n monospace;\n color: #c8a8c8;\n overflow-wrap: anywhere;\n min-width: 0;\n}\n.jev-devtools .diagram-property-text > .diagram-property-key {\n display: block;\n margin-bottom: 2px;\n}\n.jev-devtools .diagram-text-value {\n display: block;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.jev-devtools .diagram-text-preview {\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.jev-devtools .diagram-value-number {\n color: #a6d6ca;\n font:\n 11px/1.5 ui-monospace,\n monospace;\n font-variant-numeric: tabular-nums;\n overflow-wrap: anywhere;\n}\n.jev-devtools .diagram-value-boolean {\n color: #c4b1ed;\n font:\n 11px/1.5 ui-monospace,\n monospace;\n}\n.jev-devtools .diagram-value-empty {\n color: #9c8ca4;\n font:\n 10px/1.5 ui-monospace,\n monospace;\n}\n.jev-devtools .diagram-property-disclosure {\n width: 100%;\n display: flex;\n align-items: baseline;\n gap: 5px;\n padding: 6px 0;\n border: 0;\n border-bottom: 1px solid #261e2a;\n border-radius: 0;\n background: transparent;\n text-align: left;\n color: inherit;\n}\n.jev-devtools .diagram-property-disclosure:hover {\n background: #211723;\n}\n.jev-devtools .diagram-property-caret {\n color: #ab8aab;\n flex-shrink: 0;\n}\n.jev-devtools .diagram-property-type {\n color: #9c8ca4;\n font:\n 10px/1.5 ui-monospace,\n monospace;\n margin-left: auto;\n flex-shrink: 0;\n}\n.jev-devtools .diagram-property-children {\n margin-left: 4px;\n padding-left: 9px;\n border-left: 1px solid #49324b;\n}\n.jev-devtools .diagram-content-toggle {\n display: block;\n padding: 3px 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: #e9a4d7;\n font-size: 10px;\n text-align: left;\n}\n.jev-devtools .diagram-content-toggle:hover {\n color: #ffd9f3;\n text-decoration: underline;\n}\n\n/* Noul is a probability of true; its two outputs carry p and 1-p.\n Keep the fill opaque, with readable text even at zero probability. */\n.jev-devtools .lab-noul-output {\n border-color: rgb(\n 245 243 246 / calc(0.35 + 0.65 * var(--lab-noul-probability, 1))\n );\n}\n.jev-devtools .lab-noul-output > h3,\n.jev-devtools .lab-noul-output > p {\n opacity: calc(0.65 + 0.35 * var(--lab-noul-probability, 1));\n}\n\n:where(.jev-devtools) .flow-condition-rule {\n display: block;\n padding: 10px 12px;\n border: 1px solid #49324b;\n border-radius: 6px;\n background: #211723;\n width: 100%;\n}\n:where(.jev-devtools) .flow-condition-label {\n display: block;\n font-size: 10px;\n color: #ba9dbe;\n margin-bottom: 5px;\n}\n:where(.jev-devtools) .flow-condition-rule code {\n white-space: pre-wrap;\n font:\n 12px/1.6 ui-monospace,\n monospace;\n color: #f2d7eb;\n}\n/* Bound card previews so long captured literals cannot push warnings off-canvas.\n The complete captured rule remains available in selected-step details. */\n:where(.jev-devtools) .flow-condition-rule code,\n:where(.jev-devtools) .flow-map-progress > code {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 4;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n\n:where(.jev-devtools) .flow-condition-incoming {\n display: block;\n padding-bottom: 10px;\n margin-bottom: 10px;\n border-bottom: 1px solid #49324b;\n}\n:where(.jev-devtools) .flow-condition-input {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 12px;\n font-size: 12px;\n padding: 3px 0;\n}\n:where(.jev-devtools) .flow-condition-input > span {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-condition-input > b {\n font-size: 17px;\n font-weight: 600;\n color: #f2d7eb;\n overflow-wrap: anywhere;\n white-space: normal;\n max-width: 65%;\n}\n:where(.jev-devtools) .flow-input-details {\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-input-details > div {\n display: flex;\n gap: 16px;\n margin: 6px 0;\n}\n:where(.jev-devtools) .flow-input-details code {\n color: #a092a7;\n margin-left: 8px;\n}\n:where(.jev-devtools) .flow-input-details dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n:where(.jev-devtools) .flow-overview-stack {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n:where(.jev-devtools) .flow-map-progress {\n display: grid;\n gap: 10px;\n font-size: 14px;\n line-height: 1.5;\n}\n:where(.jev-devtools) .flow-map-progress small {\n color: #b5a6bc;\n}\n\n.jev-devtools .flow-card-body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: 100%;\n flex: 1;\n padding: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n}\n.jev-devtools .flow-card-body:hover {\n background: transparent;\n}\n:where(.jev-devtools) .flow-collection-controls {\n display: flex;\n align-items: end;\n gap: 10px;\n border-top: 1px solid #49324b;\n margin-top: 4px;\n padding-top: 12px;\n}\n:where(.jev-devtools) .flow-collection-controls label {\n display: grid;\n gap: 6px;\n flex: 1;\n min-width: 0;\n color: #b9aebf;\n font-size: 11px;\n}\n.jev-devtools .flow-collection-controls select {\n width: 100%;\n min-width: 0;\n}\n.jev-devtools .flow-collection-controls button {\n flex: none;\n color: #efb1df;\n}\n:where(.jev-devtools) .flow-collection-expansion {\n position: absolute;\n width: max-content;\n}\n:where(.jev-devtools) .flow-item-frame {\n border: 1px solid #64415d;\n border-radius: 12px;\n background: #100c13;\n overflow: hidden;\n}\n:where(.jev-devtools) .flow-collection-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 20px;\n border-bottom: 1px solid #49324b;\n background: #1c1220;\n}\n:where(.jev-devtools) .flow-collection-heading h3 {\n margin: 5px 0;\n font-size: 16px;\n}\n:where(.jev-devtools) .flow-collection-heading small {\n color: #b9aebf;\n}\n:where(.jev-devtools) .flow-collection-link text {\n fill: #ce9abd;\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-item-placeholder {\n width: 600px;\n padding: 24px;\n line-height: 1.6;\n}\n:where(.jev-devtools) .flow-inline-overview,\n:where(.jev-devtools) .flow-inline-overview > .whole-flow,\n:where(.jev-devtools) .flow-inline-overview > .whole-flow > .flow-workspace {\n display: block;\n flex: none;\n}\n:where(.jev-devtools) .flow-inline-surface {\n position: relative;\n}\n\n:where(.jev-devtools) .flow-item-content {\n min-width: 0;\n animation: jev-flow-item-enter 240ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n:where(.jev-devtools) .flow-item-forward {\n --flow-item-offset: 28px;\n}\n:where(.jev-devtools) .flow-item-backward {\n --flow-item-offset: -28px;\n}\n@keyframes jev-flow-item-enter {\n from {\n opacity: 0;\n transform: translateX(var(--flow-item-offset));\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n :where(.jev-devtools) .flow-item-content {\n animation: none;\n }\n}\n.jev-devtools .flow-item-arrow {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n display: grid;\n place-items: center;\n width: 56px;\n height: 56px;\n padding: 12px;\n margin: 0 auto;\n border: 1px solid #64415d;\n border-radius: 6px;\n background: #211623;\n color: #efa7db;\n}\n.jev-devtools .flow-item-arrow:hover:not(:disabled) {\n background: #382035;\n border-color: #e551ba;\n}\n:where(.jev-devtools) .flow-item-arrow svg {\n width: 28px;\n height: 28px;\n}\n\n:where(.jev-devtools) .flow-item-previous {\n left: -76px;\n}\n:where(.jev-devtools) .flow-item-next {\n right: -76px;\n}\n.jev-devtools .flow-run-summary {\n max-width: 100%;\n margin-left: auto;\n text-align: right;\n}\n.jev-devtools .flow-run-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 6px;\n}\n.jev-devtools .flow-run-controls .flow-step-select {\n max-width: 100%;\n}\n.jev-devtools .flow-step-select select {\n min-width: 0;\n}\n.jev-devtools .flow-fixture-button {\n flex-shrink: 0;\n /* Keep the header still when the shorter confirmation replaces the label. */\n min-width: 96px;\n font-size: 11px;\n transition:\n background-color 160ms ease,\n border-color 160ms ease;\n}\n.jev-devtools .flow-fixture-button[data-copied] {\n border-color: #e551ba;\n background: #382035;\n animation: jev-fixture-copied 200ms ease-out;\n}\n@keyframes jev-fixture-copied {\n from {\n transform: scale(0.96);\n }\n to {\n transform: scale(1);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .jev-devtools .flow-fixture-button {\n transition: none;\n }\n .jev-devtools .flow-fixture-button[data-copied] {\n animation: none;\n }\n}\n.jev-devtools .flow-fixture {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n flex-shrink: 0;\n gap: 4px;\n padding: 0 24px;\n text-align: right;\n}\n.jev-devtools .flow-fixture p {\n margin: 0;\n max-width: 360px;\n font-size: 11px;\n}\n.jev-devtools .flow-fixture details {\n max-width: 360px;\n font-size: 11px;\n}\n.jev-devtools .flow-fixture details ul {\n max-height: 120px;\n margin: 4px 0 0;\n padding: 0;\n overflow: auto;\n list-style: none;\n font-family: \"JetBrains Mono\", monospace;\n overflow-wrap: anywhere;\n}\n.jev-devtools .flow-fixture details ul:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: 2px;\n}\n.jev-devtools .flow-fixture textarea {\n width: 360px;\n max-width: 100%;\n height: 120px;\n padding: 8px;\n font:\n 11px \"JetBrains Mono\",\n monospace;\n text-align: left;\n}\n";
1
+ export const inspectorStyles = "/* Important declarations in the inner shadow context beat page-authored host\n styles, including important ones. Keep the reset here instead of expanding\n the all shorthand into hundreds of inline properties on the host element.\n These qualified host selectors are inert in the public document stylesheet. */\n:host(jev-devtools-root) {\n all: initial !important;\n direction: ltr !important;\n unicode-bidi: isolate !important;\n position: fixed !important;\n inset: 0 !important;\n pointer-events: none !important;\n z-index: 2147483000 !important;\n}\n\n:host(jev-devtools-root)::before,\n:host(jev-devtools-root)::after {\n content: none !important;\n display: none !important;\n}\n\n.jev-devtools {\n all: initial;\n /* all does not reset custom properties. Establish our own inherited defaults;\n cameras and probability cards can still set local values below this root. */\n --diagram-grid-opacity: 1;\n --diagram-grid-weight: 1;\n --lab-noul-probability: 1;\n --flow-item-offset: 0px;\n display: block;\n direction: ltr;\n unicode-bidi: isolate;\n pointer-events: auto;\n color-scheme: dark;\n font:\n 12px system-ui,\n sans-serif;\n color: #eee8ed;\n line-height: 1.5;\n}\n.jev-devtools,\n.jev-devtools * {\n box-sizing: border-box;\n}\n.jev-devtools button,\n.jev-devtools input,\n.jev-devtools select,\n.jev-devtools textarea {\n font: inherit;\n color: inherit;\n border: 1px solid #3b303d;\n border-radius: 5px;\n background: #17121b;\n}\n.jev-devtools button {\n padding: 7px 11px;\n cursor: pointer;\n}\n.jev-devtools button:disabled {\n opacity: 0.45;\n cursor: default;\n}\n.jev-devtools input,\n.jev-devtools select {\n padding: 8px;\n width: 100%;\n}\n.jev-devtools select {\n appearance: none;\n padding-right: 34px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='m4 6 4 4 4-4' stroke='%23bcadbf' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 10px center;\n background-size: 16px 16px;\n}\n@media (forced-colors: active) {\n .jev-devtools select {\n /* Let the system draw a visible arrow in high-contrast themes. */\n appearance: auto;\n background-image: none;\n }\n}\n.jev-devtools button:hover:not(:disabled) {\n border-color: #e551ba;\n}\n.jev-devtools :is(button, input, select, textarea, summary):focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: 3px;\n}\n.jev-devtools .eyebrow {\n font-size: 10px;\n letter-spacing: 0.12em;\n color: #bc9db5;\n text-transform: uppercase;\n}\n.jev-devtools .jev-inspector {\n height: 100%;\n display: flex;\n color: #eee8ed;\n background: #0d0a0f;\n font:\n 12px \"DM Sans\",\n sans-serif;\n min-height: 0;\n overflow: hidden;\n}\n.jev-devtools .jev-inspector h2 {\n font-size: 15px;\n margin: 0;\n font-weight: 600;\n}\n.jev-devtools .jev-inspector h3 {\n font-size: 13px;\n margin: 0 0 8px;\n}\n.jev-devtools .jev-inspector code,\n.jev-devtools .jev-inspector pre {\n font:\n 11px \"JetBrains Mono\",\n monospace;\n}\n.jev-devtools .jev-inspector pre {\n line-height: 1.7;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n max-height: 460px;\n overflow: auto;\n background: #100d13;\n padding: 16px;\n border: 1px solid #2e2533;\n border-radius: 5px;\n}\n.jev-devtools .jev-inspector .muted {\n color: #a89bac;\n font-size: 11px;\n line-height: 1.6;\n}\n.jev-devtools .request-list {\n flex: 0 0 300px;\n border-right: 1px solid #302634;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.jev-devtools .request-list > header {\n padding: 17px 14px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n}\n.jev-devtools .request-list h2 span {\n font-size: 11px;\n color: #b796ad;\n margin-left: 5px;\n}\n.jev-devtools .request-list button {\n font-size: 11px;\n}\n.jev-devtools .list-filters {\n display: flex;\n gap: 7px;\n padding: 0 12px 12px;\n}\n.jev-devtools .list-filters input {\n min-width: 0;\n}\n.jev-devtools .list-filters select {\n width: 100px;\n flex-shrink: 0;\n font-size: 11px;\n}\n.jev-devtools .request-items {\n overflow: auto;\n flex: 1;\n}\n.jev-devtools .request-navigation {\n display: none;\n}\n.jev-devtools .request-row {\n display: flex;\n align-items: center;\n gap: 8px;\n width: 100%;\n text-align: left;\n border: 0;\n border-top: 1px solid #251d2a;\n border-radius: 0;\n background: transparent;\n padding: 16px 12px;\n}\n.jev-devtools .request-row.selected {\n background: #251423;\n box-shadow: inset 2px 0 #e551ba;\n}\n.jev-devtools .request-row strong {\n display: block;\n font-size: 12px;\n font-weight: 500;\n}\n.jev-devtools .request-row small {\n display: block;\n font-size: 10px;\n color: #a99aac;\n margin-top: 6px;\n}\n.jev-devtools .row-status {\n font-size: 9px;\n color: #ad9bb0;\n margin-left: auto;\n}\n.jev-devtools .status-dot {\n width: 6px;\n height: 6px;\n display: inline-block;\n border-radius: 50%;\n background: #b09ab7;\n flex-shrink: 0;\n}\n.jev-devtools .status-dot.success {\n background: #b3c79c;\n}\n.jev-devtools .status-dot.pending {\n background: #e551ba;\n}\n.jev-devtools .status-dot.error {\n background: #ee8e85;\n}\n.jev-devtools .request-list footer {\n padding: 12px;\n color: #9e8ea5;\n border-top: 1px solid #302634;\n font-size: 10px;\n display: flex;\n gap: 7px;\n}\n.jev-devtools .request-detail {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.jev-devtools .detail-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 18px 24px 10px;\n}\n.jev-devtools .detail-header h2 {\n margin-top: 6px;\n}\n.jev-devtools .metrics {\n display: flex;\n gap: 24px;\n font:\n 12px \"JetBrains Mono\",\n monospace;\n white-space: nowrap;\n}\n.jev-devtools .metrics small {\n display: block;\n font:\n 9px \"DM Sans\",\n sans-serif;\n color: #97879f;\n margin-top: 6px;\n}\n.jev-devtools .detail-meta {\n display: flex;\n gap: 20px;\n flex-wrap: wrap;\n padding: 0 24px 14px;\n color: #a08aa5;\n}\n.jev-devtools .detail-meta code {\n font-size: 10px;\n}\n.jev-devtools .detail-tabs {\n display: flex;\n gap: 20px;\n border-bottom: 1px solid #302634;\n padding: 0 24px;\n}\n.jev-devtools .detail-tabs button {\n border: 0;\n border-radius: 0;\n background: none;\n padding: 10px 0;\n color: #a89bac;\n font-size: 11px;\n}\n.jev-devtools .detail-tabs button[aria-pressed=\"true\"] {\n box-shadow: inset 0 -2px #e551ba;\n color: #f5c1e6;\n}\n.jev-devtools .detail-body {\n overflow: auto;\n flex: 1;\n min-height: 0;\n padding: 16px 24px 28px;\n background-image: radial-gradient(#342337 0.6px, transparent 0.6px);\n background-size: 16px 16px;\n}\n.jev-devtools .question-node {\n position: relative;\n border: 1px solid #75516c;\n border-radius: 6px;\n padding: 15px;\n background: #110d14;\n min-width: 0;\n}\n/* Retain the legacy question connectors and standalone suppression together.\n Remove only after resolving the narrow-screen visual differences in\n https://github.com/diegoag64/jev-devtools/issues/57. */\n.jev-devtools .question-node:before {\n content: \"\";\n position: absolute;\n right: 100%;\n top: 50%;\n width: 21px;\n border-top: 1px solid #7f416d;\n}\n.jev-devtools .question-branches .question-node:not(:first-child):before {\n display: none;\n}\n.jev-devtools .question-node header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n.jev-devtools .question-node header h3 {\n margin: 0;\n overflow-wrap: anywhere;\n}\n.jev-devtools .type {\n font:\n 9px \"JetBrains Mono\",\n monospace;\n color: #e59acc;\n border: 1px solid #5e3654;\n border-radius: 3px;\n padding: 3px 5px;\n}\n.jev-devtools .answer-value {\n display: block;\n font-size: 20px;\n font-weight: 500;\n margin: 15px 0;\n color: #f0c7e5;\n}\n.jev-devtools .answer-value small {\n font-size: 10px;\n color: #b5a1ba;\n}\n.jev-devtools .probability {\n margin: 10px 0;\n font-size: 10px;\n}\n.jev-devtools .probability > div:first-child {\n display: flex;\n justify-content: space-between;\n gap: 5px;\n margin-bottom: 5px;\n}\n.jev-devtools .probability b {\n font-weight: 400;\n color: #d8abc9;\n}\n.jev-devtools .bar {\n height: 4px;\n background: #302033;\n border-radius: 2px;\n overflow: hidden;\n}\n.jev-devtools .bar i {\n display: block;\n height: 100%;\n background: #e551ba;\n}\n.jev-devtools .confidence {\n font-size: 10px;\n color: #a68cac;\n border-top: 1px solid #302634;\n padding-top: 10px;\n display: flex;\n justify-content: space-between;\n}\n.jev-devtools .confidence b {\n color: #d1bad2;\n font-weight: 400;\n}\n.jev-devtools details {\n margin-top: 12px;\n font-size: 10px;\n color: #b7a0bd;\n}\n.jev-devtools summary {\n cursor: pointer;\n}\n.jev-devtools .standalone {\n margin-bottom: 14px;\n max-width: 750px;\n}\n.jev-devtools .standalone:before {\n display: none;\n}\n.jev-devtools .standalone .answer {\n max-width: 450px;\n}\n.jev-devtools .box-preview {\n position: relative;\n margin-top: 14px;\n min-height: 80px;\n background: #19121c;\n}\n.jev-devtools .box-preview img {\n width: 100%;\n display: block;\n}\n.jev-devtools .bounding-box {\n position: absolute;\n border: 1.5px solid #e551ba;\n background: #e551ba15;\n}\n.jev-devtools .coordinates {\n display: block;\n font-size: 9px !important;\n line-height: 1.5;\n margin-top: 8px;\n color: #ac93b0;\n}\n.jev-devtools .context-image {\n max-width: 400px;\n width: 100%;\n border: 1px solid #75516c;\n border-radius: 6px;\n}\n.jev-devtools .empty {\n padding: 20px;\n color: #a294a8;\n line-height: 1.7;\n}\n.jev-devtools .empty-detail {\n margin: auto;\n padding: 40px;\n text-align: center;\n color: #b2a0ba;\n}\n.jev-devtools .empty-symbol {\n font-size: 36px;\n color: #e551ba;\n display: block;\n margin-bottom: 20px;\n}\n.jev-devtools .empty-detail p {\n font-size: 12px;\n line-height: 1.8;\n}\n.jev-devtools .error-banner {\n border: 1px solid #874b50;\n background: #31191e;\n padding: 12px;\n line-height: 1.5;\n margin-bottom: 16px;\n}\n.jev-devtools .warning {\n color: #e2b97d;\n}\n.jev-devtools .jev-inspector table {\n border-collapse: collapse;\n width: 100%;\n text-align: left;\n}\n.jev-devtools .jev-inspector th,\n.jev-devtools .jev-inspector td {\n border-bottom: 1px solid #302634;\n padding: 14px;\n}\n@media (max-width: 1100px) {\n .jev-devtools .question-branches .question-node:not(:first-child):before {\n display: block;\n }\n .jev-devtools .metrics {\n gap: 12px;\n }\n .jev-devtools .request-list {\n flex-basis: 250px;\n }\n}\n@media (max-width: 700px) {\n .jev-devtools .jev-inspector {\n flex-direction: column;\n }\n .jev-devtools .request-list {\n flex-basis: 140px;\n flex-shrink: 0;\n border-right: 0;\n border-bottom: 1px solid #302634;\n }\n .jev-devtools .request-list > header {\n padding: 6px 12px;\n }\n .jev-devtools .request-list footer {\n display: none;\n }\n .jev-devtools .list-filters {\n padding-bottom: 6px;\n }\n .jev-devtools .request-items {\n display: flex;\n }\n .jev-devtools .request-row {\n min-width: 240px;\n padding: 8px 12px;\n }\n /* Requests use separate list/detail screens so history cannot collapse into\n the fixed-height strip used by flow runs. CSS keeps desktop split panes. */\n .jev-devtools .jev-inspector[data-mobile-view=\"list\"] > .request-list {\n flex: 1;\n }\n .jev-devtools .jev-inspector[data-mobile-view=\"list\"] > .request-detail,\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] .list-filters,\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] .request-items {\n display: none;\n }\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] > .request-list {\n flex: none;\n }\n .jev-devtools .jev-inspector[data-mobile-view=\"detail\"] .request-navigation {\n display: flex;\n align-items: center;\n justify-content: space-between;\n min-height: 44px;\n margin: 6px 12px;\n text-align: left;\n }\n .jev-devtools .jev-inspector[data-mobile-view] .request-items {\n flex-direction: column;\n }\n .jev-devtools .jev-inspector[data-mobile-view] .request-row {\n flex: none;\n min-width: 0;\n min-height: 60px;\n padding: 12px;\n }\n .jev-devtools\n .jev-inspector[data-mobile-view]\n .request-row\n > span:nth-child(2) {\n min-width: 0;\n overflow-wrap: anywhere;\n }\n .jev-devtools .jev-inspector[data-mobile-view] > .request-detail {\n overflow: auto;\n }\n .jev-devtools\n .jev-inspector[data-mobile-view]\n > .request-detail\n > :is(.detail-header, .detail-meta, .detail-tabs) {\n flex: none;\n }\n .jev-devtools\n .jev-inspector[data-mobile-view]\n > .request-detail\n > .detail-body-flow {\n /* Let metadata scroll above the diagram instead of clipping its toolbar. */\n flex: 1 0 auto;\n min-height: min-content;\n }\n .jev-devtools .flow-details-bar > span {\n display: none;\n }\n .jev-devtools .detail-header {\n padding: 12px;\n }\n .jev-devtools .detail-meta,\n .jev-devtools .detail-tabs {\n padding-left: 12px;\n padding-right: 12px;\n }\n .jev-devtools .detail-tabs {\n overflow-x: auto;\n flex-shrink: 0;\n }\n .jev-devtools .detail-body {\n padding: 12px;\n }\n .jev-devtools .metrics {\n display: none;\n }\n .jev-devtools .question-node:before {\n width: 13px;\n }\n .jev-devtools .question-node {\n padding: 12px;\n }\n}\n.jev-devtools .question-node:before,\n.jev-devtools .question-branches .question-node:not(:first-child):before {\n display: block;\n right: auto;\n left: 50%;\n top: -21px;\n width: 0;\n height: 20px;\n border-top: 0;\n border-left: 1px solid #7f416d;\n}\n.jev-devtools .standalone:before {\n display: none;\n}\n@media (max-width: 900px) {\n .jev-devtools .question-node:before,\n .jev-devtools .question-branches .question-node:not(:first-child):before {\n height: 12px;\n top: -13px;\n }\n .jev-devtools .question-branches .question-node:first-child:before {\n height: 20px;\n top: -21px;\n }\n}\n\n.jev-devtools.jev-drawer {\n margin: 0;\n padding: 0;\n position: fixed;\n z-index: 1000;\n inset: auto 0 0;\n display: flex;\n flex-direction: column;\n background: #0d0a0f;\n color: #eee8ed;\n border-top: 1px solid #604058;\n box-shadow: 0 -12px 48px #0005;\n font:\n 12px \"DM Sans\",\n sans-serif;\n}\n.jev-devtools.jev-drawer[hidden],\n.jev-devtools.jev-drawer-trigger[hidden] {\n display: none;\n}\n.jev-devtools .jev-drawer-resize {\n position: absolute;\n z-index: 1;\n top: -6px;\n width: 100%;\n height: 12px;\n cursor: ns-resize;\n touch-action: none;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.jev-devtools .jev-drawer-resize span {\n width: 42px;\n height: 3px;\n border-radius: 3px;\n background: #79506f;\n}\n.jev-devtools .jev-drawer-resize:hover span,\n.jev-devtools .jev-drawer-resize:focus-visible span {\n background: #e551ba;\n}\n.jev-devtools .jev-drawer-resize:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: -2px;\n}\n.jev-devtools .jev-drawer-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex: 0 0 46px;\n padding: 0 16px;\n border-bottom: 1px solid #302634;\n}\n.jev-devtools .jev-drawer-header strong {\n display: flex;\n align-items: center;\n gap: 9px;\n font-weight: 600;\n}\n.jev-devtools .jev-drawer-header > div {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.jev-devtools .jev-drawer-header button {\n background: transparent;\n border-color: transparent;\n font-size: 11px;\n color: #c4afc8;\n}\n.jev-devtools .jev-drawer-header .jev-drawer-close {\n font-size: 22px;\n line-height: 1;\n padding: 4px 8px;\n}\n.jev-devtools .jev-drawer-content {\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n.jev-devtools.jev-drawer-trigger {\n position: fixed;\n bottom: 18px;\n right: 18px;\n z-index: 1000;\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 10px 16px;\n border: 1px solid #a24487;\n border-radius: 8px;\n cursor: pointer;\n background: #211321;\n color: #f1cbe6;\n font:\n 600 13px \"DM Sans\",\n sans-serif;\n box-shadow: 0 4px 20px #0005;\n}\n.jev-devtools.jev-drawer-trigger span {\n color: #e551ba;\n font-size: 20px;\n}\n/* Baseline cards and authored left-to-right composition from the lab's Jev presets. */\n.jev-devtools .detail-body.detail-body-flow {\n display: flex;\n padding: 0;\n overflow: hidden;\n}\n.jev-devtools .lab-flow {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n.jev-devtools .lab-flow-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-shrink: 0;\n padding: 12px 16px;\n flex-wrap: wrap;\n}\n.jev-devtools .lab-flow-toolbar label,\n.jev-devtools .flow-step-select {\n display: flex;\n gap: 10px;\n align-items: center;\n font-size: 11px;\n min-width: 0;\n}\n.jev-devtools .lab-flow-toolbar select,\n.jev-devtools .flow-step-select select {\n width: auto;\n max-width: 240px;\n font-size: 11px;\n}\n.jev-devtools .lab-flow-toolbar > div {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.jev-devtools .lab-flow-toolbar button,\n.jev-devtools .flow-canvas-controls button {\n font-size: 11px;\n}\n.jev-devtools .lab-flow-toolbar output,\n.jev-devtools .flow-canvas-controls output {\n font-size: 11px;\n min-width: 35px;\n text-align: center;\n}\n.jev-devtools .lab-flow-viewport {\n position: relative;\n flex: 1;\n min-height: 100px;\n overflow: hidden;\n background-color: #0d0a0f;\n background-image: radial-gradient(\n rgb(70 52 73 / var(--diagram-grid-opacity, 1))\n calc(0.8px * var(--diagram-grid-weight, 1)),\n transparent calc(0.8px * var(--diagram-grid-weight, 1))\n );\n background-size: 20px 20px;\n border-block: 1px solid #302634;\n cursor: grab;\n touch-action: none;\n user-select: none;\n overscroll-behavior: contain;\n}\n.jev-devtools .lab-flow-viewport[data-panning] {\n cursor: grabbing;\n}\n.jev-devtools .lab-flow-viewport:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: -2px;\n}\n.jev-devtools .lab-flow-caption {\n flex-shrink: 0;\n padding: 8px 16px;\n margin: 0;\n color: #c6a7bd;\n font-size: 11px;\n line-height: 1.6;\n}\n.jev-devtools .lab-flow-canvas {\n position: absolute;\n inset: 0 auto auto 0;\n transform-origin: top left;\n will-change: transform;\n font-family: system-ui, sans-serif;\n color: #f5f3f6;\n}\n.jev-devtools .lab-connections {\n position: absolute;\n inset: 0;\n overflow: visible;\n}\n.jev-devtools .lab-connections path {\n fill: none;\n stroke: #645a68;\n stroke-width: 1;\n}\n.jev-devtools .lab-connections path.active {\n stroke: #968592;\n}\n.jev-devtools .lab-connections text {\n fill: #b7b1bc;\n font:\n 12px ui-monospace,\n monospace;\n}\n.jev-devtools .lab-flow-dot {\n fill: #e551ba;\n}\n.jev-devtools .lab-card {\n position: absolute;\n border: 1.3px solid #f5f3f6;\n border-radius: 14px;\n padding: 16px;\n background: #0d0a0f;\n overflow: hidden;\n}\n.jev-devtools .lab-eyebrow {\n display: block;\n color: #e551ba;\n font:\n 500 10px ui-monospace,\n monospace;\n letter-spacing: 1px;\n margin: 0 0 7px;\n}\n.jev-devtools .lab-card h3 {\n font-size: 16px;\n font-weight: 600;\n line-height: 1.3;\n letter-spacing: -0.2px;\n margin: 0 0 10px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.jev-devtools .lab-card p {\n font-size: 13px;\n line-height: 1.4;\n margin: 10px 0;\n}\n.jev-devtools .lab-copy {\n display: -webkit-box;\n -webkit-line-clamp: 5;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.jev-devtools .lab-probability {\n margin: 17px 0;\n}\n.jev-devtools .lab-probability > div:first-child {\n display: flex;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 9px;\n font-size: 15px;\n}\n.jev-devtools .lab-probability span {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.jev-devtools .lab-probability code {\n font:\n 13px ui-monospace,\n monospace;\n}\n.jev-devtools .lab-track {\n height: 7px;\n border-radius: 4px;\n background: #f5f3f61a;\n overflow: hidden;\n}\n.jev-devtools .lab-track i {\n display: block;\n height: 100%;\n border-radius: 4px;\n background: #e551ba;\n}\n.jev-devtools .lab-track i.lab-alternative {\n opacity: 0.35;\n}\n.jev-devtools .lab-card .lab-result {\n font-size: 14px;\n margin-top: 24px;\n}\n.jev-devtools .lab-card .lab-small {\n font-size: 12px;\n color: #b7b1bc;\n margin-top: 20px;\n}\n.jev-devtools .lab-idle {\n opacity: 0.65;\n}\n.jev-devtools .lab-scale {\n position: relative;\n margin-top: 18px;\n border-top: 1px solid #b7b1bc;\n display: flex;\n justify-content: space-between;\n padding-top: 9px;\n}\n.jev-devtools .lab-scale > span {\n position: absolute;\n top: -5px;\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background: #e551ba;\n transform: translateX(-50%);\n}\n.jev-devtools .lab-big {\n display: block;\n font-size: 36px;\n font-weight: 500;\n margin-top: 26px;\n}\n.jev-devtools .lab-endpoints {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: #b7b1bc;\n margin-top: 10px;\n}\n.jev-devtools .lab-image {\n position: relative;\n}\n.jev-devtools .lab-image img {\n display: block;\n width: 100%;\n}\n.jev-devtools .lab-box {\n position: absolute;\n border: 1.5px solid #e551ba;\n background: #e551ba15;\n}\n.jev-devtools .lab-source-image {\n width: 100%;\n max-height: 105px;\n object-fit: contain;\n}\n.jev-devtools .lab-coordinates {\n font-family: ui-monospace, monospace;\n}\n@media (prefers-reduced-motion: reduce) {\n .jev-devtools .lab-flow-dot {\n display: none;\n }\n}\n/* The public TypeSafe mark travels with the package, independent of host assets. */\n.jev-devtools .typesafe-mark {\n display: inline-block;\n flex: 0 0 23px;\n width: 23px;\n height: 32px;\n overflow: hidden;\n line-height: 0;\n}\n.jev-devtools .typesafe-mark img {\n display: block;\n height: 32px;\n width: auto;\n max-width: none;\n}\n\n.jev-devtools .detail-body-questions {\n display: flex;\n flex-direction: column;\n overflow: auto;\n}\n.jev-devtools .questions-list {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 180px;\n}\n.jev-devtools .questions-navigation {\n display: flex;\n align-items: end;\n gap: 8px;\n flex-wrap: wrap;\n flex-shrink: 0;\n}\n.jev-devtools .questions-navigation label {\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n flex: 1;\n min-width: 240px;\n max-width: 480px;\n font-size: 11px;\n color: #bc9db5;\n}\n.jev-devtools .questions-navigation select {\n display: block;\n min-width: 0;\n}\n.jev-devtools .questions-navigation > span {\n align-self: center;\n color: #a89bac;\n}\n.jev-devtools .questions-help {\n margin: 8px 0;\n flex-shrink: 0;\n}\n.jev-devtools .questions-viewport {\n flex: 1;\n min-height: 0;\n overflow: auto;\n overflow-anchor: none;\n scrollbar-gutter: stable;\n padding: 3px;\n}\n.jev-devtools .questions-spacer {\n position: relative;\n width: 100%;\n}\n.jev-devtools .questions-row {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n padding-bottom: 14px;\n}\n.jev-devtools .questions-row .standalone {\n margin-bottom: 0;\n}\n.jev-devtools .questions-viewport:focus-visible,\n.jev-devtools .questions-row article:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: -2px;\n}\n\n/* Reserve measured card geometry while rapid scrolling uses a light preview. */\n.jev-devtools .questions-row[data-preview=\"true\"] article {\n height: 100%;\n}\n.jev-devtools .question-scroll-preview p {\n margin-top: 16px;\n}\n\n/* The full distribution stays at reading size, outside the zoomable canvas. */\n.jev-devtools .lab-flow-viewport[hidden] {\n display: none;\n}\n.jev-devtools .lab-choice-browser {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n padding: 8px 16px;\n gap: 10px;\n}\n.jev-devtools .lab-choice-controls {\n display: flex;\n gap: 12px;\n align-items: end;\n flex-wrap: wrap;\n font-size: 12px;\n}\n.jev-devtools .lab-choice-controls label {\n display: grid;\n gap: 5px;\n}\n.jev-devtools .lab-choice-table-scroll {\n overflow: auto;\n overscroll-behavior: contain;\n min-height: 0;\n}\n.jev-devtools .lab-choice-browser table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n.jev-devtools .lab-choice-browser caption {\n text-align: left;\n padding: 8px 0;\n color: #aaa0af;\n}\n.jev-devtools .lab-choice-browser th,\n.jev-devtools .lab-choice-browser td {\n padding: 9px 12px;\n border-bottom: 1px solid #302634;\n text-align: left;\n}\n.jev-devtools .lab-choice-browser th {\n overflow-wrap: anywhere;\n max-width: 320px;\n}\n.jev-devtools .lab-choice-browser td {\n font-variant-numeric: tabular-nums;\n}\n.jev-devtools .lab-choice-browser code {\n white-space: normal;\n overflow-wrap: anywhere;\n}\n.jev-devtools .lab-choice-returned {\n display: block;\n font-size: 10px;\n color: #e551ba;\n}\n.jev-devtools .lab-choice-pagination {\n display: flex;\n align-items: center;\n gap: 12px;\n justify-content: space-between;\n font-size: 12px;\n}\n.jev-devtools .lab-choice-remainder button {\n margin-top: 7px;\n}\n\n.jev-devtools .lab-flow-compact .lab-output {\n padding: 12px;\n height: 104px;\n}\n.jev-devtools .lab-flow-compact .lab-output .lab-eyebrow {\n margin-bottom: 6px;\n}\n.jev-devtools .lab-flow-compact .lab-output h3 {\n font-size: 16px;\n margin-bottom: 8px;\n}\n.jev-devtools .lab-flow-compact .lab-output p {\n font-size: 12px;\n margin: 4px 0;\n}\n.jev-devtools .lab-flow-compact .lab-result {\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n.jev-devtools .lab-choice-remainder {\n padding: 12px;\n}\n.jev-devtools .lab-choice-remainder .lab-small {\n margin: 8px 0 0;\n font-size: 11px;\n}\n\n.jev-devtools .detail-body.detail-body-flow {\n flex-direction: column;\n}\n.jev-devtools .detail-body-flow > .muted,\n.jev-devtools .detail-body-flow > .warning,\n.jev-devtools .detail-body-flow > .error-banner {\n flex-shrink: 0;\n margin: 6px 16px;\n}\n.jev-devtools .lab-choice-browser {\n overflow: auto;\n}\n.jev-devtools .lab-choice-table-scroll {\n flex: 1 0 110px;\n}\n\n/* Only freshly revealed branches enter; pan/zoom remains on the canvas. */\n.jev-devtools .lab-card-enter {\n animation: jev-card-enter 280ms cubic-bezier(0.2, 0.8, 0.2, 1) backwards;\n}\n.jev-devtools .lab-branch-enter {\n stroke-dasharray: 1;\n animation: jev-branch-enter 280ms ease-out backwards;\n}\n@keyframes jev-card-enter {\n from {\n opacity: 0;\n transform: translateX(-16px) scale(0.96);\n }\n}\n@keyframes jev-branch-enter {\n from {\n opacity: 0;\n stroke-dashoffset: 1;\n }\n to {\n opacity: 1;\n stroke-dashoffset: 0;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .jev-devtools .lab-card-enter,\n .jev-devtools .lab-branch-enter {\n animation: none;\n }\n}\n\n/* Connected requests share one camera and one opt-in full request detail.\n :where scopes these selectors without changing their existing specificity. */\n:where(.jev-devtools) .whole-flow {\n min-width: 0;\n overflow: hidden;\n}\n:where(.jev-devtools)\n .flow-overview-stack:not(.flow-inline-overview)\n > .whole-flow {\n /* Short drawers must scroll metadata instead of clipping the canvas controls. */\n overflow: auto;\n}\n:where(.jev-devtools) .whole-flow > .detail-header {\n flex-wrap: wrap;\n flex-shrink: 0;\n}\n:where(.jev-devtools) .flow-workspace {\n display: flex;\n flex-direction: column;\n flex: 1 0 auto;\n min-height: 0;\n overflow: hidden;\n}\n:where(.jev-devtools) .flow-details-panel {\n flex: none;\n min-width: 0;\n background: #130f17;\n border-top: 1px solid #342a38;\n}\n:where(.jev-devtools) .flow-details-bar {\n display: flex;\n align-items: center;\n gap: 12px;\n height: 36px;\n padding: 0 12px;\n}\n.jev-devtools .flow-details-bar button {\n flex: none;\n padding: 4px 8px;\n font-size: 11px;\n}\n:where(.jev-devtools) .flow-details-bar > span {\n color: #b5a6bc;\n font-size: 11px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n:where(.jev-devtools) .flow-details-body {\n overflow: auto;\n overscroll-behavior: contain;\n scrollbar-gutter: stable;\n}\n:where(.jev-devtools) .flow-details-resize {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 8px;\n cursor: ns-resize;\n touch-action: none;\n user-select: none;\n}\n:where(.jev-devtools) .flow-details-resize span {\n width: 40px;\n height: 2px;\n border-radius: 2px;\n background: #6c5874;\n}\n:where(.jev-devtools) .flow-details-resize:hover,\n:where(.jev-devtools) .flow-details-resize:focus-visible {\n background: #392239;\n outline: 1px solid #e551ba;\n outline-offset: -1px;\n}\n:where(.jev-devtools) .flow-overview-viewport {\n background-color: #08060a;\n position: relative;\n /* The panel's grid row reserves its actual height, including wrapped buttons. */\n display: grid;\n min-height: min-content;\n flex: 1 1 0;\n overflow: hidden;\n touch-action: none;\n cursor: grab;\n background-image: radial-gradient(\n rgb(83 67 87 / var(--diagram-grid-opacity, 1))\n calc(1px * var(--diagram-grid-weight, 1)),\n transparent calc(1px * var(--diagram-grid-weight, 1))\n );\n background-size: 20px 20px;\n}\n.jev-devtools .flow-canvas-controls {\n align-self: start;\n justify-self: end;\n margin: 12px;\n z-index: 1;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n max-width: calc(100% - 24px);\n padding: 8px;\n border: 1px solid #3b303d;\n border-radius: 10px;\n background: #17121b;\n box-shadow: 0 4px 16px #0006;\n cursor: default;\n}\n:where(.jev-devtools) .flow-overview-viewport[data-panning] {\n cursor: grabbing;\n}\n:where(.jev-devtools) .flow-overview-canvas {\n position: absolute;\n /* Keep camera coordinates at the viewport origin when the overlay uses grid. */\n top: 0;\n left: 0;\n transform-origin: 0 0;\n}\n:where(.jev-devtools) .flow-connections {\n position: absolute;\n inset: 0;\n overflow: visible;\n color: #c0b8c4;\n pointer-events: none;\n}\n:where(.jev-devtools) .flow-step {\n position: absolute;\n min-height: 172px;\n overflow-wrap: anywhere;\n display: flex;\n flex-direction: column;\n gap: 8px;\n text-align: left;\n padding: 14px;\n border: 1px solid #736476;\n border-radius: 12px;\n background: #151017;\n color: inherit;\n}\n:where(.jev-devtools) .flow-step strong {\n white-space: normal;\n width: 100%;\n}\n:where(.jev-devtools) .flow-step-description {\n overflow-wrap: anywhere;\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-step.flow-request {\n display: block;\n padding: 0;\n background: #0d0a0f;\n /* Skip offscreen paint/layout while preserving each question/expansion state. */\n content-visibility: auto;\n contain-intrinsic-size: auto 640px;\n}\n:where(.jev-devtools) .flow-request-heading {\n width: 100%;\n min-height: 64px;\n gap: 20px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n text-align: left;\n padding: 12px 20px;\n border: 0;\n border-bottom: 1px solid #342a38;\n border-radius: 12px 12px 0 0;\n background: transparent;\n color: inherit;\n}\n:where(.jev-devtools) .flow-request-heading .eyebrow {\n display: block;\n margin-bottom: 4px;\n}\n.jev-devtools .lab-flow-embedded {\n width: 1120px;\n display: block;\n}\n.jev-devtools .lab-flow-surface {\n position: relative;\n width: 1120px;\n}\n.jev-devtools .lab-flow-surface[hidden] {\n display: none;\n}\n.jev-devtools .lab-flow-embedded .lab-flow-canvas {\n will-change: auto;\n}\n.jev-devtools .lab-flow-embedded .lab-choice-browser {\n height: 520px;\n}\n:where(.jev-devtools) .flow-step.selected {\n outline: 2px solid #e551ba;\n outline-offset: 3px;\n}\n:where(.jev-devtools) .flow-step[data-status=\"completed\"] {\n border-color: #e551ba;\n}\n:where(.jev-devtools) .flow-step[data-status=\"running\"] {\n border-color: #e551ba;\n background: #2b1728;\n}\n:where(.jev-devtools) .flow-step[data-status=\"untaken\"],\n:where(.jev-devtools) .flow-step[data-status=\"blocked\"] {\n color: #948797;\n border-color: #403746;\n background: #151017;\n}\n:where(.jev-devtools) .flow-step[data-status=\"error\"] {\n border-color: #f17c78;\n}\n:where(.jev-devtools) .flow-step[data-status=\"cancelled\"] {\n border-style: dashed;\n}\n:where(.jev-devtools) .flow-selection {\n padding: 12px 20px;\n border-top: 1px solid #342a38;\n}\n:where(.jev-devtools) .flow-selection p {\n margin: 6px 0;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-request-detail {\n min-height: 450px;\n border-top: 1px solid #534357;\n}\n:where(.jev-devtools) .flow-request-detail > .request-detail {\n min-height: 450px;\n}\n\n:where(.jev-devtools) .flow-card-heading,\n:where(.jev-devtools) .flow-card-footer,\n:where(.jev-devtools) .flow-join-summary,\n:where(.jev-devtools) .flow-lane {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n}\n:where(.jev-devtools) .flow-card-heading {\n justify-content: flex-start;\n}\n:where(.jev-devtools) .flow-task .flow-kind-icon {\n border: 1px solid currentColor;\n border-radius: 6px;\n padding: 4px;\n}\n:where(.jev-devtools) .flow-task-preview {\n display: grid;\n gap: 8px;\n min-width: 0;\n}\n.jev-devtools .flow-task-preview .diagram-content {\n max-height: 280px;\n padding: 8px 10px;\n border: 1px solid #35283b;\n border-radius: 4px;\n background: #100c13;\n}\n:where(.jev-devtools) .flow-task-preview small {\n color: #a89bac;\n font-size: 11px;\n}\n:where(.jev-devtools) .flow-task-details {\n display: grid;\n gap: 12px;\n}\n:where(.jev-devtools) .flow-task-details details {\n min-width: 0;\n}\n:where(.jev-devtools) .flow-task-details summary {\n cursor: pointer;\n margin-bottom: 8px;\n}\n:where(.jev-devtools) .flow-task-details .diagram-content {\n max-height: 320px;\n overflow: auto;\n}\n:where(.jev-devtools) .flow-kind-icon {\n width: 26px;\n height: 26px;\n color: #e551ba;\n flex-shrink: 0;\n}\n:where(.jev-devtools) .flow-kind-icon svg {\n width: 100%;\n height: 100%;\n}\n:where(.jev-devtools) .flow-card-heading .eyebrow {\n font-size: 10px;\n}\n:where(.jev-devtools) .flow-status {\n margin-left: auto;\n font-size: 10px;\n border-radius: 20px;\n padding: 4px 8px;\n background: #342031;\n color: #f0b7e0;\n}\n:where(.jev-devtools) .flow-step[data-status=\"untaken\"] .flow-status {\n background: #241e28;\n color: #948797;\n}\n:where(.jev-devtools) .flow-step[data-status=\"untaken\"] .flow-kind-icon {\n color: #948797;\n}\n:where(.jev-devtools) .flow-card-body > strong {\n white-space: normal;\n font-size: 18px;\n line-height: 1.25;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-step-description {\n line-height: 1.5;\n color: #b9aebf;\n white-space: pre-wrap;\n}\n:where(.jev-devtools) .flow-card-footer {\n margin-top: auto;\n padding-top: 10px;\n border-top: 1px solid #342a38;\n font-size: 10px;\n color: #a092a7;\n}\n:where(.jev-devtools) .flow-card-footer code {\n max-width: 45%;\n white-space: normal;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-card-footer small {\n text-align: right;\n}\n:where(.jev-devtools) .flow-join-summary {\n margin-top: 4px;\n font-size: 11px;\n color: #c9b9cd;\n}\n:where(.jev-devtools) .flow-join-summary > span {\n color: #ef9ed9;\n font-variant-numeric: tabular-nums;\n}\n:where(.jev-devtools) .flow-lanes {\n display: flex;\n flex-direction: column;\n gap: 4px;\n width: 100%;\n}\n:where(.jev-devtools) .flow-lane {\n border-radius: 5px;\n background: #211923;\n padding: 6px 8px;\n font-size: 11px;\n min-height: 28px;\n}\n:where(.jev-devtools) .flow-lane > span {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-lane > span > span {\n color: #ef9ed9;\n margin-right: 4px;\n}\n:where(.jev-devtools) .flow-lane small {\n color: #b9aebf;\n}\n:where(.jev-devtools) .flow-lane-details {\n padding-left: 18px;\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-request-heading > span:first-child {\n min-width: 0;\n max-width: 80%;\n}\n:where(.jev-devtools) .flow-request-description {\n display: block;\n white-space: pre-wrap;\n font-size: 12px;\n line-height: 1.5;\n color: #b9aebf;\n margin-top: 5px;\n overflow-wrap: anywhere;\n}\n\n/* Structured context stays within the card; its gestures never move the camera. */\n.jev-devtools .lab-content-card {\n display: flex;\n flex-direction: column;\n padding: 14px;\n}\n.jev-devtools .lab-content-card h3 {\n font-size: 14px;\n margin-bottom: 8px;\n flex-shrink: 0;\n}\n.jev-devtools .lab-content-card .lab-eyebrow {\n font-size: 9px;\n margin-bottom: 5px;\n flex-shrink: 0;\n}\n.jev-devtools .diagram-content {\n min-height: 0;\n flex: 1;\n overflow: auto;\n overscroll-behavior: contain;\n touch-action: pan-y;\n scrollbar-width: thin;\n scrollbar-color: #514256 transparent;\n padding-right: 4px;\n font-size: 12px;\n line-height: 1.5;\n color: #d5cdd9;\n cursor: auto;\n user-select: text;\n}\n.jev-devtools .diagram-content:focus-visible {\n outline: 1px solid #e551ba;\n outline-offset: -1px;\n border-radius: 3px;\n}\n.jev-devtools .diagram-property {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 8px;\n padding: 5px 0;\n border-bottom: 1px solid #261e2a;\n}\n.jev-devtools .diagram-property-text {\n display: block;\n}\n.jev-devtools .diagram-property-key {\n font:\n 10px/1.5 ui-monospace,\n monospace;\n color: #c8a8c8;\n overflow-wrap: anywhere;\n min-width: 0;\n}\n.jev-devtools .diagram-property-text > .diagram-property-key {\n display: block;\n margin-bottom: 2px;\n}\n.jev-devtools .diagram-text-value {\n display: block;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n.jev-devtools .diagram-text-preview {\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n.jev-devtools .diagram-value-number {\n color: #a6d6ca;\n font:\n 11px/1.5 ui-monospace,\n monospace;\n font-variant-numeric: tabular-nums;\n overflow-wrap: anywhere;\n}\n.jev-devtools .diagram-value-boolean {\n color: #c4b1ed;\n font:\n 11px/1.5 ui-monospace,\n monospace;\n}\n.jev-devtools .diagram-value-empty {\n color: #9c8ca4;\n font:\n 10px/1.5 ui-monospace,\n monospace;\n}\n.jev-devtools .diagram-property-disclosure {\n width: 100%;\n display: flex;\n align-items: baseline;\n gap: 5px;\n padding: 6px 0;\n border: 0;\n border-bottom: 1px solid #261e2a;\n border-radius: 0;\n background: transparent;\n text-align: left;\n color: inherit;\n}\n.jev-devtools .diagram-property-disclosure:hover {\n background: #211723;\n}\n.jev-devtools .diagram-property-caret {\n color: #ab8aab;\n flex-shrink: 0;\n}\n.jev-devtools .diagram-property-type {\n color: #9c8ca4;\n font:\n 10px/1.5 ui-monospace,\n monospace;\n margin-left: auto;\n flex-shrink: 0;\n}\n.jev-devtools .diagram-property-children {\n margin-left: 4px;\n padding-left: 9px;\n border-left: 1px solid #49324b;\n}\n.jev-devtools .diagram-content-toggle {\n display: block;\n padding: 3px 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: #e9a4d7;\n font-size: 10px;\n text-align: left;\n}\n.jev-devtools .diagram-content-toggle:hover {\n color: #ffd9f3;\n text-decoration: underline;\n}\n\n/* Noul is a probability of true; its two outputs carry p and 1-p.\n Keep the fill opaque, with readable text even at zero probability. */\n.jev-devtools .lab-noul-output {\n border-color: rgb(\n 245 243 246 / calc(0.35 + 0.65 * var(--lab-noul-probability, 1))\n );\n}\n.jev-devtools .lab-noul-output > h3,\n.jev-devtools .lab-noul-output > p {\n opacity: calc(0.65 + 0.35 * var(--lab-noul-probability, 1));\n}\n\n:where(.jev-devtools) .flow-condition-rule {\n display: block;\n padding: 10px 12px;\n border: 1px solid #49324b;\n border-radius: 6px;\n background: #211723;\n width: 100%;\n}\n:where(.jev-devtools) .flow-condition-label {\n display: block;\n font-size: 10px;\n color: #ba9dbe;\n margin-bottom: 5px;\n}\n:where(.jev-devtools) .flow-condition-rule code {\n white-space: pre-wrap;\n font:\n 12px/1.6 ui-monospace,\n monospace;\n color: #f2d7eb;\n}\n/* Bound card previews so long captured literals cannot push warnings off-canvas.\n The complete captured rule remains available in selected-step details. */\n:where(.jev-devtools) .flow-condition-rule code,\n:where(.jev-devtools) .flow-map-progress > code {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 4;\n overflow: hidden;\n overflow-wrap: anywhere;\n}\n\n:where(.jev-devtools) .flow-condition-incoming {\n display: block;\n padding-bottom: 10px;\n margin-bottom: 10px;\n border-bottom: 1px solid #49324b;\n}\n:where(.jev-devtools) .flow-condition-input {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 12px;\n font-size: 12px;\n padding: 3px 0;\n}\n:where(.jev-devtools) .flow-condition-input > span {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n:where(.jev-devtools) .flow-condition-input > b {\n font-size: 17px;\n font-weight: 600;\n color: #f2d7eb;\n overflow-wrap: anywhere;\n white-space: normal;\n max-width: 65%;\n}\n:where(.jev-devtools) .flow-input-details {\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-input-details > div {\n display: flex;\n gap: 16px;\n margin: 6px 0;\n}\n:where(.jev-devtools) .flow-input-details code {\n color: #a092a7;\n margin-left: 8px;\n}\n:where(.jev-devtools) .flow-input-details dd {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n:where(.jev-devtools) .flow-overview-stack {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n min-width: 0;\n overflow: hidden;\n}\n:where(.jev-devtools) .flow-map-progress {\n display: grid;\n gap: 10px;\n font-size: 14px;\n line-height: 1.5;\n}\n:where(.jev-devtools) .flow-map-progress small {\n color: #b5a6bc;\n}\n\n.jev-devtools .flow-card-body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: 100%;\n flex: 1;\n padding: 0;\n border: 0;\n border-radius: 0;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n}\n.jev-devtools .flow-card-body:hover {\n background: transparent;\n}\n:where(.jev-devtools) .flow-collection-controls {\n display: flex;\n align-items: end;\n gap: 10px;\n border-top: 1px solid #49324b;\n margin-top: 4px;\n padding-top: 12px;\n}\n:where(.jev-devtools) .flow-collection-controls label {\n display: grid;\n gap: 6px;\n flex: 1;\n min-width: 0;\n color: #b9aebf;\n font-size: 11px;\n}\n.jev-devtools .flow-collection-controls select {\n width: 100%;\n min-width: 0;\n}\n.jev-devtools .flow-collection-controls button {\n flex: none;\n color: #efb1df;\n}\n:where(.jev-devtools) .flow-collection-expansion {\n position: absolute;\n width: max-content;\n}\n:where(.jev-devtools) .flow-item-frame {\n border: 1px solid #64415d;\n border-radius: 12px;\n background: #100c13;\n overflow: hidden;\n}\n:where(.jev-devtools) .flow-collection-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 24px;\n padding: 16px 20px;\n border-bottom: 1px solid #49324b;\n background: #1c1220;\n}\n:where(.jev-devtools) .flow-collection-heading h3 {\n margin: 5px 0;\n font-size: 16px;\n}\n:where(.jev-devtools) .flow-collection-heading small {\n color: #b9aebf;\n}\n:where(.jev-devtools) .flow-collection-link text {\n fill: #ce9abd;\n font-size: 12px;\n}\n:where(.jev-devtools) .flow-item-placeholder {\n width: 600px;\n padding: 24px;\n line-height: 1.6;\n}\n:where(.jev-devtools) .flow-inline-overview,\n:where(.jev-devtools) .flow-inline-overview > .whole-flow,\n:where(.jev-devtools) .flow-inline-overview > .whole-flow > .flow-workspace {\n display: block;\n flex: none;\n}\n:where(.jev-devtools) .flow-inline-surface {\n position: relative;\n}\n\n:where(.jev-devtools) .flow-item-content {\n min-width: 0;\n animation: jev-flow-item-enter 240ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n:where(.jev-devtools) .flow-item-forward {\n --flow-item-offset: 28px;\n}\n:where(.jev-devtools) .flow-item-backward {\n --flow-item-offset: -28px;\n}\n@keyframes jev-flow-item-enter {\n from {\n opacity: 0;\n transform: translateX(var(--flow-item-offset));\n }\n to {\n opacity: 1;\n transform: translateX(0);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n :where(.jev-devtools) .flow-item-content {\n animation: none;\n }\n}\n.jev-devtools .flow-item-arrow {\n position: absolute;\n top: 50%;\n transform: translateY(-50%);\n display: grid;\n place-items: center;\n width: 56px;\n height: 56px;\n padding: 12px;\n margin: 0 auto;\n border: 1px solid #64415d;\n border-radius: 6px;\n background: #211623;\n color: #efa7db;\n}\n.jev-devtools .flow-item-arrow:hover:not(:disabled) {\n background: #382035;\n border-color: #e551ba;\n}\n:where(.jev-devtools) .flow-item-arrow svg {\n width: 28px;\n height: 28px;\n}\n\n:where(.jev-devtools) .flow-item-previous {\n left: -76px;\n}\n:where(.jev-devtools) .flow-item-next {\n right: -76px;\n}\n.jev-devtools .flow-run-summary {\n max-width: 100%;\n margin-left: auto;\n text-align: right;\n}\n.jev-devtools .flow-run-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 6px;\n}\n.jev-devtools .flow-run-controls .flow-step-select {\n max-width: 100%;\n}\n.jev-devtools .flow-step-select select {\n min-width: 0;\n}\n.jev-devtools .flow-fixture-button {\n flex-shrink: 0;\n /* Keep the header still when the shorter confirmation replaces the label. */\n min-width: 96px;\n font-size: 11px;\n transition:\n background-color 160ms ease,\n border-color 160ms ease;\n}\n.jev-devtools .flow-fixture-button[data-copied] {\n border-color: #e551ba;\n background: #382035;\n animation: jev-fixture-copied 200ms ease-out;\n}\n@keyframes jev-fixture-copied {\n from {\n transform: scale(0.96);\n }\n to {\n transform: scale(1);\n }\n}\n@media (prefers-reduced-motion: reduce) {\n .jev-devtools .flow-fixture-button {\n transition: none;\n }\n .jev-devtools .flow-fixture-button[data-copied] {\n animation: none;\n }\n}\n.jev-devtools .flow-fixture {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n flex-shrink: 0;\n gap: 4px;\n padding: 0 24px;\n text-align: right;\n}\n.jev-devtools .flow-fixture p {\n margin: 0;\n max-width: 360px;\n font-size: 11px;\n}\n.jev-devtools .flow-fixture details {\n max-width: 360px;\n font-size: 11px;\n}\n.jev-devtools .flow-fixture details ul {\n max-height: 120px;\n margin: 4px 0 0;\n padding: 0;\n overflow: auto;\n list-style: none;\n font-family: \"JetBrains Mono\", monospace;\n overflow-wrap: anywhere;\n}\n.jev-devtools .flow-fixture details ul:focus-visible {\n outline: 2px solid #e551ba;\n outline-offset: 2px;\n}\n.jev-devtools .flow-fixture textarea {\n width: 360px;\n max-width: 100%;\n height: 120px;\n padding: 8px;\n font:\n 11px \"JetBrains Mono\",\n monospace;\n text-align: left;\n}\n";
@@ -1178,6 +1178,7 @@
1178
1178
  .jev-devtools .detail-body.detail-body-flow {
1179
1179
  flex-direction: column;
1180
1180
  }
1181
+ .jev-devtools .detail-body-flow > .muted,
1181
1182
  .jev-devtools .detail-body-flow > .warning,
1182
1183
  .jev-devtools .detail-body-flow > .error-banner {
1183
1184
  flex-shrink: 0;
@@ -49,6 +49,13 @@ export type Trace = {
49
49
  output_tokens: number;
50
50
  };
51
51
  };
52
+ /** Bounded redacted JSON whose structured request/result view is unavailable.
53
+ * Presence, including null, identifies an unavailable section. Never infer
54
+ * availability from user strings. The recorder owns this metadata. */
55
+ capture?: {
56
+ request?: Json;
57
+ result?: Json;
58
+ };
52
59
  requestId?: string;
53
60
  error?: {
54
61
  name: string;
@@ -0,0 +1,7 @@
1
+ import type { Json, Trace } from "./protocol.js";
2
+ type WireTrace = Omit<Trace, "request" | "result"> & {
3
+ request: Json;
4
+ result?: Json;
5
+ };
6
+ export declare function parseTraceRecord(input: unknown): WireTrace;
7
+ export {};
@@ -0,0 +1,27 @@
1
+ import { array, finite, id, integer, json, literal, nonnegative, optional, parse, shape, status, strings, text, } from "./transport-validation.js";
2
+ const trace = shape({
3
+ version: literal(1),
4
+ id,
5
+ session: text,
6
+ label: text,
7
+ source: literal("sample", "live"),
8
+ status,
9
+ startedAt: finite,
10
+ durationMs: optional(nonnegative),
11
+ request: json,
12
+ result: optional(json),
13
+ capture: optional(shape({ request: optional(json), result: optional(json) })),
14
+ flow: optional(shape({ runId: id, stepId: id, executionId: id })),
15
+ requestId: optional(text),
16
+ error: optional(shape({ name: text, message: text, status: optional(finite) })),
17
+ attempts: array(shape({
18
+ number: integer,
19
+ durationMs: optional(nonnegative),
20
+ status: optional(finite),
21
+ error: optional(text),
22
+ })),
23
+ captureWarnings: strings,
24
+ });
25
+ export function parseTraceRecord(input) {
26
+ return parse(trace, input, "trace record");
27
+ }
@@ -1,18 +1,17 @@
1
- const revision = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
1
+ import { projectTraceCapture } from "./capture-view.js";
2
+ import { parseTraceRecord } from "./trace-parser.js";
3
+ import { ids, integer, parse, record } from "./transport-validation.js";
2
4
  function traces(value) {
3
5
  if (!Array.isArray(value) || value.length > 200)
4
6
  throw new Error("Invalid traces");
5
7
  const ids = new Set();
6
- for (const trace of value) {
7
- if (!trace ||
8
- trace.version !== 1 ||
9
- typeof trace.id !== "string" ||
10
- !trace.id ||
11
- ids.has(trace.id))
12
- throw new Error("Invalid trace ID");
8
+ return value.map((input) => {
9
+ const trace = projectTraceCapture(parseTraceRecord(input));
10
+ if (ids.has(trace.id))
11
+ throw new Error("Duplicate trace ID");
13
12
  ids.add(trace.id);
14
- }
15
- return value;
13
+ return trace;
14
+ });
16
15
  }
17
16
  // Owned by one EventSource connection. Unchanged trace objects keep their
18
17
  // identity; validate the complete update before replacing any current state.
@@ -20,32 +19,25 @@ export class TraceStore {
20
19
  current = [];
21
20
  revision;
22
21
  apply(event, input) {
23
- if (!input || typeof input !== "object")
24
- throw new Error("Invalid update");
25
- const data = input;
22
+ const data = parse(record, input, "update");
26
23
  if (event === "snapshot") {
27
24
  if (data.version !== 1 && data.version !== 2)
28
25
  throw new Error("Unsupported stream");
29
- if (data.version === 2 && !revision(data.revision))
30
- throw new Error("Invalid revision");
26
+ const nextRevision = data.version === 2
27
+ ? parse(integer, data.revision, "revision")
28
+ : undefined;
31
29
  const next = traces(data.traces);
32
30
  this.current = next;
33
- this.revision =
34
- data.version === 2 ? data.revision : undefined;
31
+ this.revision = nextRevision;
35
32
  return next;
36
33
  }
37
34
  if (data.version !== 2 ||
38
35
  this.revision === undefined ||
39
36
  data.base !== this.revision ||
40
- !revision(data.revision) ||
37
+ !integer(data.revision) ||
41
38
  data.revision <= this.revision)
42
39
  throw new Error("Inspector stream needs a fresh snapshot");
43
- if (!Array.isArray(data.order) ||
44
- data.order.length > 200 ||
45
- data.order.some((id) => typeof id !== "string") ||
46
- new Set(data.order).size !== data.order.length)
47
- throw new Error("Invalid retained order");
48
- const order = data.order;
40
+ const order = parse(ids, data.order, "retained order");
49
41
  const retained = new Set(order);
50
42
  const upserts = traces(data.upserts);
51
43
  if (upserts.some((t) => !retained.has(t.id)))
@@ -0,0 +1,20 @@
1
+ import type { Json } from "./protocol.js";
2
+ export type Check<T> = (value: unknown) => value is T;
3
+ export declare const record: (value: unknown) => value is Record<string, unknown>;
4
+ export declare const text: Check<string>;
5
+ export declare const id: Check<string>;
6
+ export declare const finite: Check<number>;
7
+ export declare const nonnegative: Check<number>;
8
+ export declare const integer: Check<number>;
9
+ export declare const boolean: Check<boolean>;
10
+ export declare const literal: <T extends string | number>(...values: T[]) => Check<T>;
11
+ export declare const optional: <T>(check: Check<T>) => Check<T | undefined>;
12
+ export declare const array: <T>(check: Check<T>, max?: number) => Check<T[]>;
13
+ export declare function shape<T>(fields: {
14
+ [K in keyof T]-?: Check<T[K]>;
15
+ }): Check<T>;
16
+ export declare const strings: Check<string[]>;
17
+ export declare const ids: (value: unknown) => value is string[];
18
+ export declare const status: Check<"cancelled" | "error" | "pending" | "success">;
19
+ export declare const json: Check<Json>;
20
+ export declare function parse<T>(check: Check<T>, value: unknown, name: string): T;