jev-devtools 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/lib/flow/index.d.ts +64 -2
- package/lib/flow/index.js +466 -25
- package/lib/flow/runtime.d.ts +5 -0
- package/lib/flow/runtime.js +81 -0
- package/lib/server/flows.d.ts +3 -0
- package/lib/server/flows.js +89 -17
- package/lib/server/index.d.ts +6 -1
- package/lib/server/index.js +15 -0
- package/lib/server/recorder.js +7 -2
- package/lib/src/FlowDetailsPanel.d.ts +8 -0
- package/lib/src/FlowDetailsPanel.js +79 -0
- package/lib/src/FlowOverview.d.ts +2 -1
- package/lib/src/FlowOverview.js +292 -80
- package/lib/src/JevDrawer.js +2 -4
- package/lib/src/condition-inputs.js +12 -3
- package/lib/src/flow-condition.js +3 -2
- package/lib/src/flow-protocol.d.ts +27 -2
- package/lib/src/inspector-styles.js +1 -1
- package/lib/src/inspector.css +371 -260
- package/lib/src/useDiagramViewport.d.ts +1 -1
- package/lib/src/useDiagramViewport.js +11 -6
- package/package.json +6 -3
- package/skills/setup-jev-devtools/SKILL.md +7 -7
- package/skills/setup-jev-devtools/references/flows.md +86 -9
- package/skills/setup-jev-devtools/references/nextjs.md +3 -1
- package/skills/setup-jev-devtools/references/react-node.md +3 -4
- package/skills/setup-jev-devtools/references/verify.md +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Detached JSON snapshot, without capture truncation or lossy JSON.stringify. */
|
|
2
|
+
export function jsonSnapshot(value, seen = new Set(), depth = 0) {
|
|
3
|
+
if (depth > 40)
|
|
4
|
+
throw new TypeError("Task data nesting exceeds 40");
|
|
5
|
+
if (value === null || typeof value === "string" || typeof value === "boolean")
|
|
6
|
+
return value;
|
|
7
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
8
|
+
return value;
|
|
9
|
+
if (!value ||
|
|
10
|
+
typeof value !== "object" ||
|
|
11
|
+
(!Array.isArray(value) &&
|
|
12
|
+
Object.getPrototypeOf(value) !== Object.prototype &&
|
|
13
|
+
Object.getPrototypeOf(value) !== null))
|
|
14
|
+
throw new TypeError("Task inputs and outputs must be JSON-compatible");
|
|
15
|
+
if (seen.has(value))
|
|
16
|
+
throw new TypeError("Task data cannot contain cycles");
|
|
17
|
+
if (Object.getOwnPropertySymbols(value).length)
|
|
18
|
+
throw new TypeError("Task data cannot contain symbol keys");
|
|
19
|
+
seen.add(value);
|
|
20
|
+
try {
|
|
21
|
+
if (Array.isArray(value))
|
|
22
|
+
return Array.from(value, (v) => jsonSnapshot(v, seen, depth + 1));
|
|
23
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [
|
|
24
|
+
k,
|
|
25
|
+
jsonSnapshot(v, seen, depth + 1),
|
|
26
|
+
]));
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
seen.delete(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Cancellation ends awaiting, even when user code ignores the cooperative signal. */
|
|
33
|
+
export function runCallback(callback, parent, timeoutMs) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const started = performance.now();
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
let timer;
|
|
38
|
+
let settled = false;
|
|
39
|
+
const cleanup = () => {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
parent.removeEventListener("abort", cancel);
|
|
42
|
+
};
|
|
43
|
+
const finish = (error, value, failed = false) => {
|
|
44
|
+
if (settled)
|
|
45
|
+
return;
|
|
46
|
+
settled = true;
|
|
47
|
+
cleanup();
|
|
48
|
+
if (failed)
|
|
49
|
+
reject(error);
|
|
50
|
+
else
|
|
51
|
+
resolve(value);
|
|
52
|
+
};
|
|
53
|
+
const stop = (error) => {
|
|
54
|
+
controller.abort(error);
|
|
55
|
+
finish(error, undefined, true);
|
|
56
|
+
};
|
|
57
|
+
const cancel = () => stop(new DOMException("Flow cancelled", "AbortError"));
|
|
58
|
+
if (parent.aborted) {
|
|
59
|
+
cancel();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
parent.addEventListener("abort", cancel, { once: true });
|
|
63
|
+
if (timeoutMs !== undefined)
|
|
64
|
+
timer = setTimeout(() => stop(new DOMException("Task timed out", "TimeoutError")), timeoutMs);
|
|
65
|
+
Promise.resolve()
|
|
66
|
+
.then(() => {
|
|
67
|
+
if (controller.signal.aborted)
|
|
68
|
+
throw controller.signal.reason;
|
|
69
|
+
return callback(controller.signal);
|
|
70
|
+
})
|
|
71
|
+
.then((value) => {
|
|
72
|
+
// CPU-bound callbacks cannot be interrupted, but their late result must
|
|
73
|
+
// still not advance the flow after the configured deadline.
|
|
74
|
+
if (timeoutMs !== undefined &&
|
|
75
|
+
performance.now() - started >= timeoutMs)
|
|
76
|
+
stop(new DOMException("Task timed out", "TimeoutError"));
|
|
77
|
+
else
|
|
78
|
+
finish(undefined, value);
|
|
79
|
+
}, (error) => finish(error, undefined, true));
|
|
80
|
+
});
|
|
81
|
+
}
|
package/lib/server/flows.d.ts
CHANGED
|
@@ -10,10 +10,13 @@ export declare class FlowRecorder {
|
|
|
10
10
|
private revisions;
|
|
11
11
|
private revision;
|
|
12
12
|
private listeners;
|
|
13
|
+
private itemCaptures;
|
|
14
|
+
private taskCaptures;
|
|
13
15
|
constructor(options: CaptureOptions);
|
|
14
16
|
subscribe(fn: (sessions: Set<string>) => void): () => void;
|
|
15
17
|
private notify;
|
|
16
18
|
start(definition: FlowDefinition, run: FlowRun, session: string): void;
|
|
19
|
+
private captureRun;
|
|
17
20
|
update(run: FlowRun): void;
|
|
18
21
|
private remove;
|
|
19
22
|
private prune;
|
package/lib/server/flows.js
CHANGED
|
@@ -9,6 +9,8 @@ export class FlowRecorder {
|
|
|
9
9
|
revisions = new Map();
|
|
10
10
|
revision = 0;
|
|
11
11
|
listeners = new Set();
|
|
12
|
+
itemCaptures = new WeakMap();
|
|
13
|
+
taskCaptures = new WeakMap();
|
|
12
14
|
constructor(options) {
|
|
13
15
|
this.options = options;
|
|
14
16
|
}
|
|
@@ -29,18 +31,23 @@ export class FlowRecorder {
|
|
|
29
31
|
start(definition, run, session) {
|
|
30
32
|
if (!this.definitions.has(definition.id)) {
|
|
31
33
|
const stored = structuredClone(definition);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const redactRules = (definition, prefix = []) => {
|
|
35
|
+
for (const node of definition.nodes) {
|
|
36
|
+
if (node.itemDefinition)
|
|
37
|
+
redactRules(node.itemDefinition, [...prefix, node.id, "items"]);
|
|
38
|
+
if (node.conditionRule === undefined)
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
node.conditionRule = capture(node.conditionRule, new Set(), this.options.redact
|
|
42
|
+
? (path, value) => this.options.redact(["conditions", ...prefix, node.id, ...path], value)
|
|
43
|
+
: undefined);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
node.conditionRule = "[rule capture unavailable]";
|
|
47
|
+
}
|
|
39
48
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
49
|
+
};
|
|
50
|
+
redactRules(stored);
|
|
44
51
|
this.definitions.set(definition.id, stored);
|
|
45
52
|
this.definitionRevisions.set(definition.id, ++this.revision);
|
|
46
53
|
}
|
|
@@ -57,19 +64,84 @@ export class FlowRecorder {
|
|
|
57
64
|
this.prune();
|
|
58
65
|
this.notify(sessions);
|
|
59
66
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
captureRun(run, prefix = []) {
|
|
68
|
+
// Child updates are immutable snapshots; unchanged items need no recapture.
|
|
69
|
+
const previous = prefix.length ? this.itemCaptures.get(run) : undefined;
|
|
70
|
+
if (previous)
|
|
71
|
+
return previous;
|
|
72
|
+
const stored = {
|
|
73
|
+
...run,
|
|
74
|
+
steps: Object.fromEntries(Object.entries(run.steps).map(([id, raw]) => {
|
|
75
|
+
const step = { ...raw };
|
|
76
|
+
if (raw.collection)
|
|
77
|
+
step.collection = {
|
|
78
|
+
...raw.collection,
|
|
79
|
+
items: raw.collection.items.map((item) => ({
|
|
80
|
+
...item,
|
|
81
|
+
...(item.run
|
|
82
|
+
? {
|
|
83
|
+
run: this.captureRun(item.run, [
|
|
84
|
+
...prefix,
|
|
85
|
+
"steps",
|
|
86
|
+
id,
|
|
87
|
+
"items",
|
|
88
|
+
String(item.index),
|
|
89
|
+
]),
|
|
90
|
+
}
|
|
91
|
+
: {}),
|
|
92
|
+
})),
|
|
93
|
+
};
|
|
94
|
+
if (step.data === undefined)
|
|
95
|
+
return [id, { ...step }];
|
|
96
|
+
const key = step.data;
|
|
97
|
+
let captured = key && typeof key === "object"
|
|
98
|
+
? this.taskCaptures.get(key)
|
|
99
|
+
: undefined;
|
|
100
|
+
if (!captured) {
|
|
101
|
+
const warnings = new Set();
|
|
102
|
+
let data;
|
|
103
|
+
try {
|
|
104
|
+
data =
|
|
105
|
+
capture(step.data, warnings, this.options.redact
|
|
106
|
+
? (path, value) => this.options.redact([...prefix, "steps", id, "data", ...path], value)
|
|
107
|
+
: undefined) ?? null;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
data = "[capture unavailable]";
|
|
111
|
+
warnings.add("Task capture unavailable.");
|
|
112
|
+
}
|
|
113
|
+
captured = { data, warnings: [...warnings] };
|
|
114
|
+
if (key && typeof key === "object")
|
|
115
|
+
this.taskCaptures.set(key, captured);
|
|
116
|
+
}
|
|
117
|
+
return [
|
|
118
|
+
id,
|
|
119
|
+
{
|
|
120
|
+
...step,
|
|
121
|
+
data: captured.data,
|
|
122
|
+
captureWarnings: captured.warnings,
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
})),
|
|
126
|
+
};
|
|
64
127
|
if ("value" in stored) {
|
|
65
128
|
try {
|
|
66
|
-
stored.value = capture(stored.value, new Set(), this.options.redact
|
|
129
|
+
stored.value = capture(stored.value, new Set(), this.options.redact
|
|
130
|
+
? (path, value) => this.options.redact([...prefix, ...path], value)
|
|
131
|
+
: undefined);
|
|
67
132
|
}
|
|
68
133
|
catch {
|
|
69
134
|
stored.value = "[capture unavailable]";
|
|
70
135
|
}
|
|
71
136
|
}
|
|
72
|
-
|
|
137
|
+
if (prefix.length)
|
|
138
|
+
this.itemCaptures.set(run, stored);
|
|
139
|
+
return stored;
|
|
140
|
+
}
|
|
141
|
+
update(run) {
|
|
142
|
+
if (!this.runs.has(run.id))
|
|
143
|
+
return; // Clear/eviction cannot resurrect pending runs.
|
|
144
|
+
this.runs.set(run.id, this.captureRun(run));
|
|
73
145
|
this.revisions.set(run.id, ++this.revision);
|
|
74
146
|
this.notify(new Set([this.sessions.get(run.id)]));
|
|
75
147
|
}
|
package/lib/server/index.d.ts
CHANGED
|
@@ -8,7 +8,10 @@ export type JevDevtoolsServerOptions = CaptureOptions & {
|
|
|
8
8
|
enabled?: boolean;
|
|
9
9
|
origin: string;
|
|
10
10
|
endpoint?: string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Required for non-loopback origins. Without it, Node middleware only accepts
|
|
13
|
+
* loopback connections. Apply your application's authentication.
|
|
14
|
+
*/
|
|
12
15
|
authorize?: (request: Request) => boolean | Promise<boolean>;
|
|
13
16
|
/** Server-derived scope. Defaults to a shared local-development history. */
|
|
14
17
|
getSession?: (request: Request) => string | Promise<string>;
|
|
@@ -32,6 +35,8 @@ export declare function createJevDevtools(options: JevDevtoolsServerOptions): {
|
|
|
32
35
|
* For Node framework routes whose Request URL uses an internal hostname.
|
|
33
36
|
* Require the actual Host header to match the configured public authority;
|
|
34
37
|
* never trust forwarded host/protocol headers. Origin checks still apply.
|
|
38
|
+
* A Request does not expose the socket peer and any non-browser client can
|
|
39
|
+
* set Host, so bind the server to loopback or set authorize.
|
|
35
40
|
*/
|
|
36
41
|
handleNodeRoute(request: Request): Promise<Response>;
|
|
37
42
|
middleware: (req: IncomingMessage, res: ServerResponse, next: () => void) => Promise<void>;
|
package/lib/server/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { pipeline } from "node:stream/promises";
|
|
|
5
5
|
import { TypeSafeClient } from "@typesafe-ai/sdk";
|
|
6
6
|
import { JevRecorder } from "./recorder.js";
|
|
7
7
|
export { JevRecorder } from "./recorder.js";
|
|
8
|
+
// Host and Origin are client-controlled, so locality is decided by the socket peer.
|
|
9
|
+
const isLoopback = (address = "") => address === "::1" || /^(::ffff:)?127\./.test(address);
|
|
8
10
|
const json = (data, status = 200) => Response.json(data, { status, headers: { "cache-control": "no-store" } });
|
|
9
11
|
/** Node runtime adapter: framework-neutral Request/Response plus Node middleware. */
|
|
10
12
|
export function createJevDevtools(options) {
|
|
@@ -256,6 +258,17 @@ export function createJevDevtools(options) {
|
|
|
256
258
|
next();
|
|
257
259
|
return;
|
|
258
260
|
}
|
|
261
|
+
if (enabled() &&
|
|
262
|
+
!options.authorize &&
|
|
263
|
+
!isLoopback(req.socket.remoteAddress)) {
|
|
264
|
+
res
|
|
265
|
+
.writeHead(403, {
|
|
266
|
+
"content-type": "application/json",
|
|
267
|
+
"cache-control": "no-store",
|
|
268
|
+
})
|
|
269
|
+
.end('{"error":"Loopback connections required without authorize"}');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
259
272
|
const abort = new AbortController();
|
|
260
273
|
res.once("close", () => abort.abort());
|
|
261
274
|
try {
|
|
@@ -299,6 +312,8 @@ export function createJevDevtools(options) {
|
|
|
299
312
|
* For Node framework routes whose Request URL uses an internal hostname.
|
|
300
313
|
* Require the actual Host header to match the configured public authority;
|
|
301
314
|
* never trust forwarded host/protocol headers. Origin checks still apply.
|
|
315
|
+
* A Request does not expose the socket peer and any non-browser client can
|
|
316
|
+
* set Host, so bind the server to loopback or set authorize.
|
|
302
317
|
*/
|
|
303
318
|
handleNodeRoute(request) {
|
|
304
319
|
if (!enabled())
|
package/lib/server/recorder.js
CHANGED
|
@@ -67,10 +67,15 @@ export function capture(value, warnings, redact) {
|
|
|
67
67
|
.map((v, i) => visit(v, [...path, String(i)], depth + 1));
|
|
68
68
|
}
|
|
69
69
|
const entries = Object.entries(input);
|
|
70
|
-
|
|
70
|
+
// Choice maps need room for the full roster; retain the overall byte/depth
|
|
71
|
+
// budget and the smaller general-purpose object/array limit.
|
|
72
|
+
const entryLimit = ["criteria", "probabilities"].includes(path.at(-1) ?? "")
|
|
73
|
+
? 256
|
|
74
|
+
: 150;
|
|
75
|
+
if (entries.length > entryLimit)
|
|
71
76
|
warnings.add("Long collection truncated in capture.");
|
|
72
77
|
return Object.fromEntries(entries
|
|
73
|
-
.slice(0,
|
|
78
|
+
.slice(0, entryLimit)
|
|
74
79
|
.map(([key, v]) => [key, visit(v, [...path, key], depth + 1)]));
|
|
75
80
|
};
|
|
76
81
|
return visit(value, [], 0);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** Keep metadata out of the canvas until requested; size it within the flow workspace. */
|
|
3
|
+
export declare function FlowDetailsPanel({ children, selection, onLayoutChange, inline, }: {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
selection?: string;
|
|
6
|
+
onLayoutChange: () => void;
|
|
7
|
+
inline?: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
/** Keep metadata out of the canvas until requested; size it within the flow workspace. */
|
|
4
|
+
export function FlowDetailsPanel({ children, selection, onLayoutChange, inline = false, }) {
|
|
5
|
+
const id = useId();
|
|
6
|
+
const panel = useRef(null);
|
|
7
|
+
const drag = useRef(null);
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [height, setHeight] = useState(240);
|
|
10
|
+
const [max, setMax] = useState(0);
|
|
11
|
+
const min = Math.min(96, max);
|
|
12
|
+
const visibleHeight = Math.max(min, Math.min(max, height));
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const workspace = panel.current?.parentElement;
|
|
15
|
+
if (!workspace)
|
|
16
|
+
return;
|
|
17
|
+
const measure = () => {
|
|
18
|
+
// Inline panels grow the canvas instead of taking space from a fixed viewport.
|
|
19
|
+
// Exclude the panel itself so resizing cannot grow its own upper bound.
|
|
20
|
+
const available = workspace.clientHeight -
|
|
21
|
+
(inline ? panel.current?.offsetHeight ?? 0 : 0);
|
|
22
|
+
// Leave room for the bar/divider and a usable canvas, including short drawers.
|
|
23
|
+
setMax(Math.max(0, available - Math.min(140, available * 0.45) - 44));
|
|
24
|
+
};
|
|
25
|
+
measure();
|
|
26
|
+
const observer = new ResizeObserver(measure);
|
|
27
|
+
observer.observe(workspace);
|
|
28
|
+
return () => observer.disconnect();
|
|
29
|
+
}, [inline]);
|
|
30
|
+
const resize = (next) => {
|
|
31
|
+
onLayoutChange();
|
|
32
|
+
setHeight(Math.max(min, Math.min(max, next)));
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs("div", { className: "flow-details-panel", ref: panel, "data-diagram-scroll": true, children: [open && (_jsx("div", { className: "flow-details-resize", role: "separator", tabIndex: 0, "aria-label": "Resize flow details", "aria-orientation": "horizontal", "aria-controls": id, "aria-valuemin": Math.round(min), "aria-valuemax": Math.round(max), "aria-valuenow": Math.round(visibleHeight), "aria-valuetext": `${Math.round(visibleHeight)} pixels high`, onPointerDown: (event) => {
|
|
35
|
+
if (event.button !== 0)
|
|
36
|
+
return;
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
39
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
40
|
+
drag.current = {
|
|
41
|
+
y: event.clientY,
|
|
42
|
+
height: visibleHeight,
|
|
43
|
+
// Inline item details share the outer canvas zoom.
|
|
44
|
+
scale: event.currentTarget.getBoundingClientRect().height /
|
|
45
|
+
event.currentTarget.offsetHeight || 1,
|
|
46
|
+
};
|
|
47
|
+
}, onPointerMove: (event) => {
|
|
48
|
+
if (drag.current)
|
|
49
|
+
resize(drag.current.height +
|
|
50
|
+
(drag.current.y - event.clientY) / drag.current.scale);
|
|
51
|
+
}, onPointerUp: (event) => {
|
|
52
|
+
drag.current = null;
|
|
53
|
+
if (event.currentTarget.hasPointerCapture(event.pointerId))
|
|
54
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
55
|
+
}, onPointerCancel: () => {
|
|
56
|
+
drag.current = null;
|
|
57
|
+
}, onLostPointerCapture: () => {
|
|
58
|
+
drag.current = null;
|
|
59
|
+
}, onKeyDown: (event) => {
|
|
60
|
+
const increment = event.shiftKey ? 80 : 20;
|
|
61
|
+
const next = event.key === "ArrowUp"
|
|
62
|
+
? visibleHeight + increment
|
|
63
|
+
: event.key === "ArrowDown"
|
|
64
|
+
? visibleHeight - increment
|
|
65
|
+
: event.key === "Home"
|
|
66
|
+
? min
|
|
67
|
+
: event.key === "End"
|
|
68
|
+
? max
|
|
69
|
+
: undefined;
|
|
70
|
+
if (next !== undefined) {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
event.stopPropagation();
|
|
73
|
+
resize(next);
|
|
74
|
+
}
|
|
75
|
+
}, children: _jsx("span", {}) })), _jsxs("div", { className: "flow-details-bar", children: [_jsx("button", { "aria-expanded": open, "aria-controls": id, onClick: () => {
|
|
76
|
+
onLayoutChange();
|
|
77
|
+
setOpen(!open);
|
|
78
|
+
}, children: open ? "Hide details" : "Show details" }), selection && _jsx("span", { title: selection, children: selection })] }), _jsx("div", { id: id, hidden: !open, className: "flow-details-body", role: "region", "aria-label": "Flow details", tabIndex: 0, style: { height: visibleHeight }, children: open && children })] }));
|
|
79
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { FlowDefinition, FlowRun } from "./flow-protocol.js";
|
|
2
2
|
import type { Trace } from "./protocol.js";
|
|
3
|
-
export declare const FlowOverview: import("react").MemoExoticComponent<({ definition, run, traces, }: {
|
|
3
|
+
export declare const FlowOverview: import("react").MemoExoticComponent<({ definition, run, traces, inline, }: {
|
|
4
4
|
definition: FlowDefinition;
|
|
5
5
|
run: FlowRun;
|
|
6
6
|
traces: Trace[];
|
|
7
|
+
inline?: boolean;
|
|
7
8
|
}) => import("react").JSX.Element>;
|