@object-ui/app-shell 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +560 -0
- package/dist/console/AppContent.js +23 -17
- package/dist/console/ConsoleShell.d.ts +16 -0
- package/dist/console/ConsoleShell.js +43 -2
- package/dist/console/ai/AiChatPage.js +47 -16
- package/dist/console/ai/LiveCanvas.d.ts +8 -2
- package/dist/console/ai/LiveCanvas.js +6 -4
- package/dist/console/home/HomeLayout.js +5 -7
- package/dist/console/home/HomePage.js +1 -9
- package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
- package/dist/console/organizations/OrganizationsPage.js +22 -3
- package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
- package/dist/console/organizations/provisionEnvironment.js +64 -0
- package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
- package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
- package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
- package/dist/environment/EnvironmentListToolbar.js +59 -0
- package/dist/environment/entitlements.d.ts +90 -0
- package/dist/environment/entitlements.js +91 -0
- package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
- package/dist/environment/useEnvironmentEntitlements.js +108 -0
- package/dist/hooks/useActionModal.js +15 -1
- package/dist/hooks/useAiSurface.d.ts +59 -0
- package/dist/hooks/useAiSurface.js +78 -0
- package/dist/hooks/useChatConversation.d.ts +30 -0
- package/dist/hooks/useChatConversation.js +63 -0
- package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
- package/dist/hooks/useConsoleActionRuntime.js +42 -10
- package/dist/index.d.ts +5 -2
- package/dist/index.js +10 -2
- package/dist/layout/AppHeader.js +28 -4
- package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
- package/dist/layout/ConsoleFloatingChatbot.js +41 -10
- package/dist/layout/ConsoleLayout.js +5 -6
- package/dist/layout/ContextSelectors.js +59 -35
- package/dist/layout/agentPicker.d.ts +56 -0
- package/dist/layout/agentPicker.js +40 -0
- package/dist/preview/CommitTimeline.d.ts +15 -0
- package/dist/preview/CommitTimeline.js +82 -0
- package/dist/preview/DraftPreviewBar.js +20 -7
- package/dist/preview/UnpublishedAppBar.js +11 -7
- package/dist/preview/commitHistory.d.ts +28 -0
- package/dist/preview/commitHistory.js +48 -0
- package/dist/providers/ExpressionProvider.js +9 -3
- package/dist/providers/MetadataProvider.js +9 -0
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/recordFormNavigation.d.ts +60 -0
- package/dist/utils/recordFormNavigation.js +35 -0
- package/dist/utils/resolvePageVarTokens.d.ts +31 -0
- package/dist/utils/resolvePageVarTokens.js +72 -0
- package/dist/views/CreateViewDialog.js +14 -1
- package/dist/views/FlowRunner.d.ts +2 -30
- package/dist/views/FlowRunner.js +18 -50
- package/dist/views/ObjectView.js +26 -12
- package/dist/views/ScreenView.d.ts +70 -0
- package/dist/views/ScreenView.js +73 -0
- package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
- package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
- package/dist/views/metadata-admin/DirectoryPage.js +2 -14
- package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
- package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
- package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
- package/dist/views/metadata-admin/PackagesPage.js +58 -5
- package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +83 -24
- package/dist/views/metadata-admin/ResourceListPage.js +28 -19
- package/dist/views/metadata-admin/StudioHomePage.js +6 -14
- package/dist/views/metadata-admin/anchors.js +20 -2
- package/dist/views/metadata-admin/createBody.d.ts +26 -0
- package/dist/views/metadata-admin/createBody.js +30 -0
- package/dist/views/metadata-admin/i18n.js +108 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +10 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +136 -8
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +99 -4
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +81 -4
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
- package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
- package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
- package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
- package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
- package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +102 -0
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +67 -11
- package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
- package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
- package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
- package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
- package/dist/views/metadata-admin/issuePath.d.ts +22 -0
- package/dist/views/metadata-admin/issuePath.js +65 -0
- package/dist/views/metadata-admin/package-scope.d.ts +41 -0
- package/dist/views/metadata-admin/package-scope.js +59 -0
- package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
- package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +26 -1
- package/dist/views/metadata-admin/previews/FlowCanvas.js +143 -16
- package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
- package/dist/views/metadata-admin/previews/FlowPreview.js +47 -7
- package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
- package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
- package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
- package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
- package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
- package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
- package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
- package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +17 -1
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +23 -6
- package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
- package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
- package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
- package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
- package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
- package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
- package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
- package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +20 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +76 -2
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
- package/package.json +38 -38
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
/** Trigger types that fire on a single record (so `record` is in scope). */
|
|
3
|
+
const RECORD_TRIGGER_TYPES = new Set([
|
|
4
|
+
'record-after-create',
|
|
5
|
+
'record-after-update',
|
|
6
|
+
'record-before-update',
|
|
7
|
+
'record-after-delete',
|
|
8
|
+
'record-change',
|
|
9
|
+
]);
|
|
10
|
+
/** Trigger types that carry a meaningful `previous` snapshot of the record. */
|
|
11
|
+
const PREVIOUS_TRIGGER_TYPES = new Set([
|
|
12
|
+
'record-after-update',
|
|
13
|
+
'record-before-update',
|
|
14
|
+
'record-change',
|
|
15
|
+
]);
|
|
16
|
+
function asArray(v) {
|
|
17
|
+
return Array.isArray(v) ? v : [];
|
|
18
|
+
}
|
|
19
|
+
function asRecord(v) {
|
|
20
|
+
return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
|
|
21
|
+
}
|
|
22
|
+
function str(v) {
|
|
23
|
+
return typeof v === 'string' && v ? v : undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Ancestor node ids of `nodeId` — every node from which `nodeId` is reachable
|
|
27
|
+
* by following edges forward (equivalently, a reverse breadth-first walk from
|
|
28
|
+
* `nodeId`). Cycle-safe (a declared `back`-edge revise loop won't spin) and
|
|
29
|
+
* never includes `nodeId` itself.
|
|
30
|
+
*/
|
|
31
|
+
export function flowAncestors(nodeId, edges) {
|
|
32
|
+
const rev = new Map();
|
|
33
|
+
for (const e of edges) {
|
|
34
|
+
const s = str(e.source);
|
|
35
|
+
const t = str(e.target);
|
|
36
|
+
if (!s || !t)
|
|
37
|
+
continue;
|
|
38
|
+
const list = rev.get(t);
|
|
39
|
+
if (list)
|
|
40
|
+
list.push(s);
|
|
41
|
+
else
|
|
42
|
+
rev.set(t, [s]);
|
|
43
|
+
}
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const stack = [...(rev.get(nodeId) ?? [])];
|
|
46
|
+
while (stack.length) {
|
|
47
|
+
const cur = stack.pop();
|
|
48
|
+
if (cur === nodeId || seen.has(cur))
|
|
49
|
+
continue;
|
|
50
|
+
seen.add(cur);
|
|
51
|
+
for (const p of rev.get(cur) ?? [])
|
|
52
|
+
stack.push(p);
|
|
53
|
+
}
|
|
54
|
+
return seen;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The variable names a node INTRODUCES into scope for its successors — mirroring
|
|
58
|
+
* what the simulator (flow-simulator.ts) and engine actually write:
|
|
59
|
+
* `outputVariable` (single), `outputVariables` (list), an assignment node's
|
|
60
|
+
* `assignments` keys (map / array / flat shapes), a screen's collected
|
|
61
|
+
* `fields[].name`, a screen object-form's `idVariable`, and a loop/map
|
|
62
|
+
* `iteratorVariable` (flagged as a `loop` ref). The start node is NOT handled
|
|
63
|
+
* here — its trigger record is resolved separately.
|
|
64
|
+
*/
|
|
65
|
+
export function nodeOutputRefs(node) {
|
|
66
|
+
const type = str(node.type);
|
|
67
|
+
const cfg = asRecord(node.config);
|
|
68
|
+
const nodeId = str(node.id) ?? '';
|
|
69
|
+
const label = str(node.label);
|
|
70
|
+
const detail = label && label !== nodeId ? label : nodeId || undefined;
|
|
71
|
+
const out = [];
|
|
72
|
+
const add = (token, group = 'outputs') => {
|
|
73
|
+
if (token && !out.some((r) => r.token === token)) {
|
|
74
|
+
out.push({ token, label: token, detail, group });
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
// Loop / map iterator — its own group.
|
|
78
|
+
if (type === 'loop' || type === 'map')
|
|
79
|
+
add(str(cfg.iteratorVariable), 'loop');
|
|
80
|
+
// Single + multi output variables (create/get/http/subflow/map/end/script).
|
|
81
|
+
add(str(cfg.outputVariable));
|
|
82
|
+
for (const name of asArray(cfg.outputVariables))
|
|
83
|
+
add(str(name));
|
|
84
|
+
// Screen object-form: the saved record's id is bound to a variable.
|
|
85
|
+
add(str(cfg.idVariable));
|
|
86
|
+
// Assignment node — the assigned variable names. Accepts the three authoring
|
|
87
|
+
// shapes the simulator/engine accept: an array of `{variable|name|key}`, a
|
|
88
|
+
// flat `{ var: value }` map, or (legacy) keys directly on config.
|
|
89
|
+
if (type === 'assignment') {
|
|
90
|
+
const raw = cfg.assignments;
|
|
91
|
+
if (Array.isArray(raw)) {
|
|
92
|
+
for (const item of raw) {
|
|
93
|
+
const e = asRecord(item);
|
|
94
|
+
add(str(e.variable) ?? str(e.name) ?? str(e.key));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (raw && typeof raw === 'object') {
|
|
98
|
+
for (const k of Object.keys(raw))
|
|
99
|
+
add(k);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Screen — collected input field names become variables for downstream nodes.
|
|
103
|
+
if (type === 'screen' || type === 'user_task') {
|
|
104
|
+
for (const f of asArray(cfg.fields))
|
|
105
|
+
add(str(asRecord(f).name));
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/** De-duplicate by token, keeping the first (group-priority) occurrence. */
|
|
110
|
+
function dedupeByToken(refs) {
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
return refs.filter((r) => (seen.has(r.token) ? false : (seen.add(r.token), true)));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the in-scope reference set at `nodeId` (graph-aware). Pure: the
|
|
116
|
+
* trigger object's fields are NOT expanded here (that needs an async fetch) —
|
|
117
|
+
* the returned `trigger` carries the object name and per-field token prefix for
|
|
118
|
+
* the UI layer to expand. Order: flow variables, upstream outputs, loop
|
|
119
|
+
* iterators, then trigger refs, de-duplicated by token.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveFlowScope(draft, nodeId) {
|
|
122
|
+
const nodes = asArray(draft.nodes).map(asRecord);
|
|
123
|
+
const edges = asArray(draft.edges);
|
|
124
|
+
const refs = [];
|
|
125
|
+
// 1. Flow variables — always in scope (declared up-front).
|
|
126
|
+
for (const v of asArray(draft.variables)) {
|
|
127
|
+
const rec = asRecord(v);
|
|
128
|
+
const name = str(rec.name);
|
|
129
|
+
if (!name)
|
|
130
|
+
continue;
|
|
131
|
+
const type = str(rec.type);
|
|
132
|
+
refs.push({ token: name, label: name, detail: type ? `variable · ${type}` : 'variable', group: 'variables' });
|
|
133
|
+
}
|
|
134
|
+
if (!nodeId)
|
|
135
|
+
return { refs: dedupeByToken(refs) };
|
|
136
|
+
// 2. Upstream outputs + loop iterators — from ANCESTOR nodes only.
|
|
137
|
+
const ancestors = flowAncestors(nodeId, edges);
|
|
138
|
+
const startNode = nodes.find((n) => str(n.type) === 'start');
|
|
139
|
+
const startId = str(startNode?.id);
|
|
140
|
+
for (const node of nodes) {
|
|
141
|
+
const id = str(node.id);
|
|
142
|
+
if (!id || id === nodeId || !ancestors.has(id))
|
|
143
|
+
continue;
|
|
144
|
+
if (id === startId)
|
|
145
|
+
continue; // the start node contributes the trigger record, below
|
|
146
|
+
for (const ref of nodeOutputRefs(node))
|
|
147
|
+
refs.push(ref);
|
|
148
|
+
}
|
|
149
|
+
// 3. Trigger record — on a record-triggered flow, when the start node is in
|
|
150
|
+
// scope: either we ARE the start node (editing its own entry condition) or
|
|
151
|
+
// the start node is an ancestor (it is the ancestor of everything reachable).
|
|
152
|
+
if (startNode) {
|
|
153
|
+
const cfg = asRecord(startNode.config);
|
|
154
|
+
const triggerType = str(cfg.triggerType);
|
|
155
|
+
const objectName = str(cfg.objectName);
|
|
156
|
+
const isRecordTrigger = !!triggerType && RECORD_TRIGGER_TYPES.has(triggerType);
|
|
157
|
+
const startInScope = startId === nodeId || (!!startId && ancestors.has(startId));
|
|
158
|
+
if (isRecordTrigger && objectName && startInScope) {
|
|
159
|
+
const onStart = startId === nodeId;
|
|
160
|
+
const includePrevious = PREVIOUS_TRIGGER_TYPES.has(triggerType);
|
|
161
|
+
// On the start node the record's fields ARE the bare evaluation context
|
|
162
|
+
// (`status`), so the whole record is not a named ref there; `previous` is
|
|
163
|
+
// (`previous.status`). Downstream the record is the named `record` object.
|
|
164
|
+
if (!onStart) {
|
|
165
|
+
refs.push({ token: 'record', label: 'record', detail: `trigger record · ${objectName}`, group: 'trigger' });
|
|
166
|
+
}
|
|
167
|
+
if (includePrevious) {
|
|
168
|
+
refs.push({ token: 'previous', label: 'previous', detail: 'record values before the change', group: 'trigger' });
|
|
169
|
+
}
|
|
170
|
+
return { refs: dedupeByToken(refs), trigger: { objectName, fieldPrefix: onStart ? '' : 'record.', includePrevious } };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { refs: dedupeByToken(refs) };
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Expand a trigger object's fields into per-field refs — `record.<field>`
|
|
177
|
+
* downstream, bare `<field>` on the start node — given an already-fetched field
|
|
178
|
+
* list. Split out from {@link resolveFlowScope} so it is unit-testable without a
|
|
179
|
+
* metadata client.
|
|
180
|
+
*/
|
|
181
|
+
export function triggerFieldRefs(trigger, fields) {
|
|
182
|
+
const out = [];
|
|
183
|
+
for (const f of fields) {
|
|
184
|
+
if (!f?.name)
|
|
185
|
+
continue;
|
|
186
|
+
const token = `${trigger.fieldPrefix}${f.name}`;
|
|
187
|
+
const detail = f.label && f.label !== f.name ? f.label : f.type;
|
|
188
|
+
out.push({ token, label: token, detail, group: 'trigger' });
|
|
189
|
+
if (trigger.includePrevious) {
|
|
190
|
+
out.push({
|
|
191
|
+
token: `previous.${f.name}`,
|
|
192
|
+
label: `previous.${f.name}`,
|
|
193
|
+
detail: detail ? `prior ${detail}` : 'prior value',
|
|
194
|
+
group: 'trigger',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
@@ -46,10 +46,21 @@ export interface NormalizedObject {
|
|
|
46
46
|
/** Normalize a raw object metadata doc into label + fields + relationships. */
|
|
47
47
|
export declare function normalizeObject(doc: Record<string, unknown> | null | undefined, name: string): NormalizedObject;
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
49
|
+
* Walk a dotted relationship PATH from the base object, returning the object at
|
|
50
|
+
* its end (whose fields a `path.field` references) plus each hop's relationship
|
|
51
|
+
* label, or undefined if any hop can't be resolved (ADR-0071 multi-hop).
|
|
52
|
+
* `objectsByName` holds the already-fetched objects along the chain.
|
|
51
53
|
*/
|
|
52
|
-
export declare function
|
|
54
|
+
export declare function resolvePath(base: NormalizedObject, path: string, objectsByName: Record<string, NormalizedObject>): {
|
|
55
|
+
target: NormalizedObject;
|
|
56
|
+
labels: string[];
|
|
57
|
+
} | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Build the flat `field` / `relationship[.relationship].field` option list from
|
|
60
|
+
* the base object and the (already-fetched) objects along each included PATH.
|
|
61
|
+
* Single-hop paths behave exactly as before.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildFieldOptions(base: NormalizedObject, include: string[], objectsByName: Record<string, NormalizedObject>): DatasetFieldOption[];
|
|
53
64
|
/** Recursively test whether a metadata doc references `datasetName` via a `dataset` key. */
|
|
54
65
|
export declare function referencesDataset(doc: unknown, datasetName: string): boolean;
|
|
55
66
|
/** Every object as `{ name, label }`, sorted by label. Fetched once. */
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ScopeGroupId, type ScopeRef } from './flow-scope';
|
|
2
|
+
export interface ScopeGroup {
|
|
3
|
+
id: ScopeGroupId;
|
|
4
|
+
label: string;
|
|
5
|
+
refs: ScopeRef[];
|
|
6
|
+
}
|
|
7
|
+
export interface UseFlowScopeResult {
|
|
8
|
+
/** Non-empty groups, in display order. */
|
|
9
|
+
groups: ScopeGroup[];
|
|
10
|
+
/** Flat, de-duplicated ref list (all groups). */
|
|
11
|
+
refs: ScopeRef[];
|
|
12
|
+
/** True while the trigger object's fields are still loading. */
|
|
13
|
+
loading: boolean;
|
|
14
|
+
/** No references in scope — the field should render as a plain input. */
|
|
15
|
+
isEmpty: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve + (async) expand the in-scope references at a flow node. `draft` is
|
|
19
|
+
* the whole flow draft; `nodeId` the node being edited (for an edge, pass its
|
|
20
|
+
* source node id — references available on an edge are those in scope at its
|
|
21
|
+
* source).
|
|
22
|
+
*/
|
|
23
|
+
export declare function useFlowScope(draft: Record<string, unknown> | undefined, nodeId: string | undefined): UseFlowScopeResult;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
/**
|
|
3
|
+
* useFlowScope — React adapter over the pure {@link resolveFlowScope} graph-walk
|
|
4
|
+
* that powers the inspector's variable data-picker (#1934).
|
|
5
|
+
*
|
|
6
|
+
* It resolves the graph-aware refs synchronously, then lazily fetches the
|
|
7
|
+
* trigger object's field catalog (via the shared metadata client) and merges
|
|
8
|
+
* the expanded `record.<field>` refs in — grouping everything into the ordered
|
|
9
|
+
* sections the picker renders. An empty result (`isEmpty`) tells the field to
|
|
10
|
+
* degrade to a plain input.
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
import { resolveFlowScope, triggerFieldRefs, } from './flow-scope';
|
|
14
|
+
import { useObjectFields } from '../previews/useObjectFields';
|
|
15
|
+
const GROUP_ORDER = ['variables', 'outputs', 'loop', 'trigger'];
|
|
16
|
+
const GROUP_LABELS = {
|
|
17
|
+
variables: 'Flow variables',
|
|
18
|
+
outputs: 'Upstream outputs',
|
|
19
|
+
loop: 'Loop item',
|
|
20
|
+
trigger: 'Trigger record',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Resolve + (async) expand the in-scope references at a flow node. `draft` is
|
|
24
|
+
* the whole flow draft; `nodeId` the node being edited (for an edge, pass its
|
|
25
|
+
* source node id — references available on an edge are those in scope at its
|
|
26
|
+
* source).
|
|
27
|
+
*/
|
|
28
|
+
export function useFlowScope(draft, nodeId) {
|
|
29
|
+
const scope = React.useMemo(() => resolveFlowScope(draft ?? {}, nodeId), [draft, nodeId]);
|
|
30
|
+
const { fields, loading } = useObjectFields(scope.trigger?.objectName);
|
|
31
|
+
return React.useMemo(() => {
|
|
32
|
+
const all = [...scope.refs];
|
|
33
|
+
if (scope.trigger)
|
|
34
|
+
all.push(...triggerFieldRefs(scope.trigger, fields));
|
|
35
|
+
// Global de-dup by token (a declared var also written upstream shows once).
|
|
36
|
+
const seen = new Set();
|
|
37
|
+
const refs = all.filter((r) => (seen.has(r.token) ? false : (seen.add(r.token), true)));
|
|
38
|
+
const groups = GROUP_ORDER.map((id) => ({
|
|
39
|
+
id,
|
|
40
|
+
label: GROUP_LABELS[id],
|
|
41
|
+
refs: refs.filter((r) => r.group === id),
|
|
42
|
+
})).filter((g) => g.refs.length > 0);
|
|
43
|
+
return { groups, refs, loading: !!scope.trigger && loading, isEmpty: refs.length === 0 };
|
|
44
|
+
}, [scope, fields, loading]);
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Render a (possibly nested) validation issue path into a human-readable trail
|
|
10
|
+
* that names the offending element. A Zod issue on a dashboard widget arrives as
|
|
11
|
+
* a dot-joined path like `widgets.2.layout`; shown as just its head field
|
|
12
|
+
* ("Widgets") the author can't tell WHICH widget or sub-field is at fault. This
|
|
13
|
+
* turns it into "Widgets → priority_split → layout" by resolving each array
|
|
14
|
+
* index to the item's stable identity (id/name/title) from the draft value.
|
|
15
|
+
*
|
|
16
|
+
* @param headLabel resolved human label for the first segment (caller knows the
|
|
17
|
+
* form/schema labels).
|
|
18
|
+
* @param path dot-joined issue path (e.g. `widgets.2.layout`).
|
|
19
|
+
* @param rootValue the draft object the path indexes into (used to resolve an
|
|
20
|
+
* array index to the item's identity).
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeIssuePath(headLabel: string, path: string, rootValue: unknown): string;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Render a (possibly nested) validation issue path into a human-readable trail
|
|
10
|
+
* that names the offending element. A Zod issue on a dashboard widget arrives as
|
|
11
|
+
* a dot-joined path like `widgets.2.layout`; shown as just its head field
|
|
12
|
+
* ("Widgets") the author can't tell WHICH widget or sub-field is at fault. This
|
|
13
|
+
* turns it into "Widgets → priority_split → layout" by resolving each array
|
|
14
|
+
* index to the item's stable identity (id/name/title) from the draft value.
|
|
15
|
+
*
|
|
16
|
+
* @param headLabel resolved human label for the first segment (caller knows the
|
|
17
|
+
* form/schema labels).
|
|
18
|
+
* @param path dot-joined issue path (e.g. `widgets.2.layout`).
|
|
19
|
+
* @param rootValue the draft object the path indexes into (used to resolve an
|
|
20
|
+
* array index to the item's identity).
|
|
21
|
+
*/
|
|
22
|
+
export function describeIssuePath(headLabel, path, rootValue) {
|
|
23
|
+
const segments = path.split('.');
|
|
24
|
+
if (segments.length <= 1)
|
|
25
|
+
return headLabel;
|
|
26
|
+
const parts = [headLabel];
|
|
27
|
+
let cursor = asRecord(rootValue)?.[segments[0]];
|
|
28
|
+
for (let i = 1; i < segments.length; i++) {
|
|
29
|
+
const seg = segments[i];
|
|
30
|
+
if (/^\d+$/.test(seg)) {
|
|
31
|
+
const idx = Number(seg);
|
|
32
|
+
const item = Array.isArray(cursor) ? cursor[idx] : undefined;
|
|
33
|
+
// 1-based index reads naturally for non-developers ("#1" not "#0").
|
|
34
|
+
parts.push(itemIdentity(item) ?? `#${idx + 1}`);
|
|
35
|
+
cursor = item;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
parts.push(seg);
|
|
39
|
+
cursor = asRecord(cursor)?.[seg];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return parts.join(' → ');
|
|
43
|
+
}
|
|
44
|
+
/** Best-effort stable identity of an array item, resolving an I18nLabel object
|
|
45
|
+
* ({ key, defaultValue }) to its string. Returns undefined when none usable. */
|
|
46
|
+
function itemIdentity(item) {
|
|
47
|
+
const o = asRecord(item);
|
|
48
|
+
if (!o)
|
|
49
|
+
return undefined;
|
|
50
|
+
for (const k of ['id', 'name', 'key', 'title', 'label']) {
|
|
51
|
+
const v = o[k];
|
|
52
|
+
if (typeof v === 'string' && v.trim())
|
|
53
|
+
return v;
|
|
54
|
+
const nested = asRecord(v);
|
|
55
|
+
if (nested) {
|
|
56
|
+
const s = nested.defaultValue ?? nested.key;
|
|
57
|
+
if (typeof s === 'string' && s.trim())
|
|
58
|
+
return s;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function asRecord(v) {
|
|
64
|
+
return v && typeof v === 'object' ? v : undefined;
|
|
65
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel "package" id for this environment's runtime, DB-authored metadata —
|
|
3
|
+
* items with no code-package binding (`package_id IS NULL`). The metadata
|
|
4
|
+
* list/get API treats `?package=sys_metadata` as exactly that local scope on
|
|
5
|
+
* READ, and a WRITE under it persists `package_id = null` (matching the
|
|
6
|
+
* server's runtime-only provenance, see framework #2252).
|
|
7
|
+
*
|
|
8
|
+
* Why this exists: a self-hosted, metadata-customizable environment is
|
|
9
|
+
* single-tenant — there is no "org" dimension here; the real axis is
|
|
10
|
+
* code-package vs. runtime (DB-authored). Before this scope, the package
|
|
11
|
+
* selector only listed code packages, so metadata authored at runtime
|
|
12
|
+
* (`package_id = null`) was filtered out of every code-package view and became
|
|
13
|
+
* un-navigable (the route redirected to "new"). Surfacing the local scope as a
|
|
14
|
+
* first-class, always-present selector entry makes it discoverable and editable.
|
|
15
|
+
*/
|
|
16
|
+
export declare const LOCAL_PACKAGE_ID = "sys_metadata";
|
|
17
|
+
/**
|
|
18
|
+
* Build the Studio package-scope options from the raw `package` metadata list.
|
|
19
|
+
* Filters out system/cloud-scoped packages and appends a stable
|
|
20
|
+
* "Local / Custom (this environment)" scope so runtime metadata authored here
|
|
21
|
+
* is always selectable/visible — even when zero items exist yet.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildPackageScopeOptions(rawList: unknown[] | null | undefined): {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
}[];
|
|
27
|
+
/**
|
|
28
|
+
* True for the runtime/null "Local / Custom" sentinel scope. Per ADR-0070 D5
|
|
29
|
+
* this is a *migration* surface (move loose items into a base), never a valid
|
|
30
|
+
* create destination — callers gate "create" on a real writable base.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isLocalScope(id: string | null | undefined): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The writable bases (project-scoped DB packages) from the raw package list —
|
|
35
|
+
* the only valid authoring destinations (ADR-0070 D2). Excludes code/installed
|
|
36
|
+
* (system|cloud) packages AND the Local sentinel.
|
|
37
|
+
*/
|
|
38
|
+
export declare function writableBaseOptions(rawList: unknown[] | null | undefined): {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
}[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
import { detectLocale, t } from './i18n';
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel "package" id for this environment's runtime, DB-authored metadata —
|
|
5
|
+
* items with no code-package binding (`package_id IS NULL`). The metadata
|
|
6
|
+
* list/get API treats `?package=sys_metadata` as exactly that local scope on
|
|
7
|
+
* READ, and a WRITE under it persists `package_id = null` (matching the
|
|
8
|
+
* server's runtime-only provenance, see framework #2252).
|
|
9
|
+
*
|
|
10
|
+
* Why this exists: a self-hosted, metadata-customizable environment is
|
|
11
|
+
* single-tenant — there is no "org" dimension here; the real axis is
|
|
12
|
+
* code-package vs. runtime (DB-authored). Before this scope, the package
|
|
13
|
+
* selector only listed code packages, so metadata authored at runtime
|
|
14
|
+
* (`package_id = null`) was filtered out of every code-package view and became
|
|
15
|
+
* un-navigable (the route redirected to "new"). Surfacing the local scope as a
|
|
16
|
+
* first-class, always-present selector entry makes it discoverable and editable.
|
|
17
|
+
*/
|
|
18
|
+
export const LOCAL_PACKAGE_ID = 'sys_metadata';
|
|
19
|
+
const SYSTEM_SCOPES = new Set(['system', 'cloud']);
|
|
20
|
+
/**
|
|
21
|
+
* Build the Studio package-scope options from the raw `package` metadata list.
|
|
22
|
+
* Filters out system/cloud-scoped packages and appends a stable
|
|
23
|
+
* "Local / Custom (this environment)" scope so runtime metadata authored here
|
|
24
|
+
* is always selectable/visible — even when zero items exist yet.
|
|
25
|
+
*/
|
|
26
|
+
export function buildPackageScopeOptions(rawList) {
|
|
27
|
+
const rows = (rawList ?? [])
|
|
28
|
+
.map((raw) => {
|
|
29
|
+
const item = raw && typeof raw === 'object' && 'item' in raw ? raw.item : raw;
|
|
30
|
+
const m = (item?.manifest ?? item ?? {});
|
|
31
|
+
return {
|
|
32
|
+
id: m.id,
|
|
33
|
+
scope: m.scope,
|
|
34
|
+
name: m.name || m.id,
|
|
35
|
+
};
|
|
36
|
+
})
|
|
37
|
+
.filter((p) => p.id && !SYSTEM_SCOPES.has(p.scope));
|
|
38
|
+
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
39
|
+
const opts = rows.map((p) => ({ id: p.id, name: p.name }));
|
|
40
|
+
// Append (never default) so the existing first-code-package default is
|
|
41
|
+
// preserved; the user opts into the local scope explicitly.
|
|
42
|
+
return [...opts, { id: LOCAL_PACKAGE_ID, name: t('engine.package.local', detectLocale()) }];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* True for the runtime/null "Local / Custom" sentinel scope. Per ADR-0070 D5
|
|
46
|
+
* this is a *migration* surface (move loose items into a base), never a valid
|
|
47
|
+
* create destination — callers gate "create" on a real writable base.
|
|
48
|
+
*/
|
|
49
|
+
export function isLocalScope(id) {
|
|
50
|
+
return !id || id === LOCAL_PACKAGE_ID;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The writable bases (project-scoped DB packages) from the raw package list —
|
|
54
|
+
* the only valid authoring destinations (ADR-0070 D2). Excludes code/installed
|
|
55
|
+
* (system|cloud) packages AND the Local sentinel.
|
|
56
|
+
*/
|
|
57
|
+
export function writableBaseOptions(rawList) {
|
|
58
|
+
return buildPackageScopeOptions(rawList).filter((o) => o.id !== LOCAL_PACKAGE_ID);
|
|
59
|
+
}
|
|
@@ -84,6 +84,18 @@ export interface MetadataPreviewProps {
|
|
|
84
84
|
* "click widget → edit widget in the right panel" pattern.
|
|
85
85
|
*/
|
|
86
86
|
onSelectionChange?: (selection: MetadataSelection | null) => void;
|
|
87
|
+
/**
|
|
88
|
+
* Optional: server-computed validation diagnostics for the current draft
|
|
89
|
+
* (the layered record's `_diagnostics`, kept in sync with live client-side
|
|
90
|
+
* issues). Each entry carries a dotted JSON path so a preview can map it onto
|
|
91
|
+
* the offending sub-element. Used by the flow preview's Problems panel +
|
|
92
|
+
* on-canvas badges; ignored by previews that don't surface diagnostics.
|
|
93
|
+
*/
|
|
94
|
+
diagnostics?: Array<{
|
|
95
|
+
path?: string;
|
|
96
|
+
message: string;
|
|
97
|
+
severity?: 'error' | 'warning';
|
|
98
|
+
}>;
|
|
87
99
|
}
|
|
88
100
|
export type MetadataPreview = ComponentType<MetadataPreviewProps>;
|
|
89
101
|
/**
|
|
@@ -77,13 +77,29 @@ export function DatasetPreview({ draft }) {
|
|
|
77
77
|
const resultObject = state.status === 'ok' ? state.object : undefined;
|
|
78
78
|
const { measureField, headerLabel } = buildDatasetFieldHelpers(resultFields, resultObject, fieldLabel);
|
|
79
79
|
const columns = [...dimensionNames, ...measureNames];
|
|
80
|
+
// A ratio/percent measure (format like `0.0%`) on the same axis as a
|
|
81
|
+
// magnitude measure (currency in the hundred-thousands) renders as an
|
|
82
|
+
// invisible sliver. When the selection MIXES the two scales, plot the ratio
|
|
83
|
+
// measures as a line on a secondary (right) Y axis via the `combo` chart —
|
|
84
|
+
// bars (magnitude) keep the left axis. Same-scale selections stay a plain bar.
|
|
85
|
+
const isRatioMeasure = (m) => {
|
|
86
|
+
const f = measureField(m)?.format;
|
|
87
|
+
return typeof f === 'string' && f.includes('%');
|
|
88
|
+
};
|
|
89
|
+
const ratioMeasures = measureNames.filter(isRatioMeasure);
|
|
90
|
+
const mixedScale = ratioMeasures.length > 0 && ratioMeasures.length < measureNames.length;
|
|
80
91
|
return (_jsx(PreviewShell, { hint: `dataset · ${objectName}${dimensionNames.length ? ' · by ' + dimensionNames.join(', ') : ''}`, children: _jsxs("div", { className: "p-3 space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("button", { type: "button", onClick: () => void run(), disabled: state.status === 'loading', className: "inline-flex items-center gap-1.5 rounded-md border bg-background px-3 py-1.5 text-xs font-medium hover:bg-accent disabled:opacity-50", children: [state.status === 'loading'
|
|
81
92
|
? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })
|
|
82
|
-
: _jsx(BarChart3, { className: "h-3.5 w-3.5" }), "Run preview"] }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: [measureNames.length, " measure", measureNames.length === 1 ? '' : 's', " \u00B7 ", dimensionNames.length, " dimension", dimensionNames.length === 1 ? '' : 's'] })] }), state.status === 'error' && (_jsxs("div", { role: "alert", className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5 mt-0.5 shrink-0" }), _jsx("span", { className: "break-words", children: state.error })] })), state.status === 'ok' && state.rows.length === 0 && (_jsx(PreviewEmptyState, { icon: _jsx(BarChart3, { className: "h-8 w-8" }), title: "No rows", description: "The dataset returned no rows for the current scope." })), state.rows.length > 0 && dimensionNames.length >= 1 && (_jsx(PreviewErrorBoundary, { fallbackHint: "Couldn't render the chart for this result \u2014 the table below still shows the data.", children: _jsx(React.Suspense, { fallback: _jsx("div", { className: "h-[260px] flex items-center justify-center text-xs text-muted-foreground", children: _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) }), children:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
: _jsx(BarChart3, { className: "h-3.5 w-3.5" }), "Run preview"] }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: [measureNames.length, " measure", measureNames.length === 1 ? '' : 's', " \u00B7 ", dimensionNames.length, " dimension", dimensionNames.length === 1 ? '' : 's'] })] }), state.status === 'error' && (_jsxs("div", { role: "alert", className: "flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive", children: [_jsx(AlertTriangle, { className: "h-3.5 w-3.5 mt-0.5 shrink-0" }), _jsx("span", { className: "break-words", children: state.error })] })), state.status === 'ok' && state.rows.length === 0 && (_jsx(PreviewEmptyState, { icon: _jsx(BarChart3, { className: "h-8 w-8" }), title: "No rows", description: "The dataset returned no rows for the current scope." })), state.rows.length > 0 && dimensionNames.length >= 1 && (_jsx(PreviewErrorBoundary, { fallbackHint: "Couldn't render the chart for this result \u2014 the table below still shows the data.", children: _jsx(React.Suspense, { fallback: _jsx("div", { className: "h-[260px] flex items-center justify-center text-xs text-muted-foreground", children: _jsx(Loader2, { className: "h-4 w-4 animate-spin" }) }), children: _jsxs("div", { className: "rounded-md border p-2", children: [_jsx(ChartRenderer, { schema: {
|
|
94
|
+
data: state.rows,
|
|
95
|
+
xAxisKey: dimensionNames[0],
|
|
96
|
+
chartType: mixedScale ? 'combo' : 'bar',
|
|
97
|
+
series: measureNames.map((m) => ({
|
|
98
|
+
dataKey: m,
|
|
99
|
+
label: headerLabel(m),
|
|
100
|
+
chartType: mixedScale ? (isRatioMeasure(m) ? 'line' : 'bar') : 'bar',
|
|
101
|
+
})),
|
|
102
|
+
} }), mixedScale && (_jsxs("p", { className: "mt-1 px-1 text-[10px] text-muted-foreground", children: ["Ratio measures (", ratioMeasures.map(headerLabel).join(', '), ") use the right axis."] }))] }) }) })), state.rows.length > 0 && (_jsx("div", { className: "overflow-auto max-h-[60vh] rounded-md border", children: _jsxs("table", { className: "w-full text-xs", children: [_jsx("thead", { className: "bg-muted/40", children: _jsx("tr", { children: columns.map((c) => (_jsx("th", { className: "px-2 py-1.5 text-left font-medium whitespace-nowrap", children: headerLabel(c) }, c))) }) }), _jsx("tbody", { children: state.rows.map((row, i) => (_jsx("tr", { className: "border-t", children: columns.map((c) => (_jsx("td", { className: "px-2 py-1 tabular-nums whitespace-nowrap", children: measureNames.includes(c)
|
|
87
103
|
? formatMeasure(row[c], measureField(c)?.format, measureField(c)?.currency)
|
|
88
104
|
: formatDimensionValue(row[c]) }, c))) }, i))) })] }) }))] }) }));
|
|
89
105
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import * as React from 'react';
|
|
22
22
|
import { type FlowNode, type FlowEdge } from './flow-canvas-layout';
|
|
23
|
+
import { type FlowProblem } from './flow-problems';
|
|
23
24
|
export interface FlowCanvasProps {
|
|
24
25
|
nodes: FlowNode[];
|
|
25
26
|
edges: FlowEdge[];
|
|
@@ -35,9 +36,33 @@ export interface FlowCanvasProps {
|
|
|
35
36
|
visitedNodeIds?: string[];
|
|
36
37
|
/** Simulation overlay: ids of edges that were traversed. */
|
|
37
38
|
traversedEdgeIds?: string[];
|
|
39
|
+
/** Structural-validation: node ids to paint with a red error ring. */
|
|
40
|
+
invalidNodeIds?: string[];
|
|
41
|
+
/** Structural-validation: edges (keyed `${source}->${target}`) to paint red. */
|
|
42
|
+
invalidEdges?: ReadonlySet<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Select + reveal a problem when its inline-banner row is clicked — wired to
|
|
45
|
+
* the same handler the Problems panel uses, so the always-visible banner is
|
|
46
|
+
* actionable without opening the panel.
|
|
47
|
+
*/
|
|
48
|
+
onRevealProblem?: (problem: FlowProblem) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Unified validation issues (structural + server) rendered as per-element
|
|
51
|
+
* badges; the Problems panel shares the same list.
|
|
52
|
+
*/
|
|
53
|
+
problems?: FlowProblem[];
|
|
54
|
+
/**
|
|
55
|
+
* Imperative "reveal" request from the Problems panel: when `nonce` changes
|
|
56
|
+
* the canvas pans to center the targeted node/edge. Selection highlight is
|
|
57
|
+
* driven separately via `selectedId` / `selectedEdgeId`.
|
|
58
|
+
*/
|
|
59
|
+
revealSignal?: {
|
|
60
|
+
target: FlowProblem['target'];
|
|
61
|
+
nonce: number;
|
|
62
|
+
} | null;
|
|
38
63
|
onSelect: (node: FlowNode | null) => void;
|
|
39
64
|
/** Select an edge (its `edgeKey`), or clear selection with `null`. */
|
|
40
65
|
onSelectEdge?: (edge: FlowEdge | null, key: string) => void;
|
|
41
66
|
onPatch?: (partial: Record<string, unknown>) => void;
|
|
42
67
|
}
|
|
43
|
-
export declare function FlowCanvas({ nodes, edges, editable, designMode, selectedId, selectedEdgeId, locale, activeNodeId, visitedNodeIds, traversedEdgeIds, onSelect, onSelectEdge, onPatch, }: FlowCanvasProps): React.JSX.Element;
|
|
68
|
+
export declare function FlowCanvas({ nodes, edges, editable, designMode, selectedId, selectedEdgeId, locale, activeNodeId, visitedNodeIds, traversedEdgeIds, invalidNodeIds, invalidEdges, onRevealProblem, problems, revealSignal, onSelect, onSelectEdge, onPatch, }: FlowCanvasProps): React.JSX.Element;
|