@object-ui/app-shell 7.1.0 → 7.3.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 +320 -0
- package/dist/components/ManagedByBadge.js +1 -1
- package/dist/console/AppContent.js +9 -15
- package/dist/console/ConsoleShell.d.ts +16 -0
- package/dist/console/ConsoleShell.js +43 -2
- package/dist/console/ai/AiChatPage.js +64 -14
- package/dist/console/ai/BuildDebugDrawer.d.ts +20 -0
- package/dist/console/ai/BuildDebugDrawer.js +75 -0
- package/dist/console/ai/buildDebugApi.d.ts +94 -0
- package/dist/console/ai/buildDebugApi.js +16 -0
- 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 +32 -4
- package/dist/console/organizations/manage/OrganizationLayout.js +1 -1
- 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/useConsoleActionRuntime.d.ts +3 -0
- package/dist/hooks/useConsoleActionRuntime.js +36 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/layout/AppHeader.js +30 -5
- package/dist/layout/ConsoleFloatingChatbot.js +22 -4
- package/dist/layout/ConsoleLayout.js +5 -6
- package/dist/layout/ContextSelectors.js +0 -19
- package/dist/layout/WorkspaceSwitcher.d.ts +14 -0
- package/dist/layout/WorkspaceSwitcher.js +76 -0
- package/dist/preview/DraftPreviewBar.js +20 -7
- package/dist/providers/ExpressionProvider.js +9 -3
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/managedByEmptyState.d.ts +1 -1
- package/dist/utils/managedByEmptyState.js +20 -2
- 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/ObjectView.js +27 -13
- package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
- package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
- package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
- package/dist/views/metadata-admin/PackagesPage.js +49 -4
- package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +36 -4
- package/dist/views/metadata-admin/ResourceListPage.js +25 -10
- package/dist/views/metadata-admin/StudioHomePage.js +1 -5
- 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 +20 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +8 -0
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +17 -3
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +16 -2
- 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 +15 -3
- 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/VariableTextInput.d.ts +47 -0
- package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +6 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +21 -10
- 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/package-scope.d.ts +9 -19
- package/dist/views/metadata-admin/package-scope.js +11 -25
- package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +22 -3
- package/dist/views/metadata-admin/previews/FlowCanvas.js +45 -6
- package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
- package/dist/views/metadata-admin/previews/FlowPreview.js +42 -30
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
- 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/flow-canvas-parts.d.ts +9 -1
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +5 -3
- 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/simulator/flow-sim-types.d.ts +9 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +4 -2
- 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
|
+
}
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* server's runtime-only provenance, see framework #2252).
|
|
2
|
+
* Build the Studio package-scope options from the raw `package` metadata list:
|
|
3
|
+
* the **writable bases** (project-scoped, DB-backed packages) only — the sole
|
|
4
|
+
* valid authoring destinations (ADR-0070 D2). Code/installed (system|cloud)
|
|
5
|
+
* packages are filtered out.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* selector
|
|
12
|
-
*
|
|
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.
|
|
7
|
+
* There is no package-less "Local / Custom" scope: every runtime-authored item
|
|
8
|
+
* lives in a writable base (ADR-0070 D1/D5 — the kernel rejects orphan creates
|
|
9
|
+
* with `writable_package_required`, and legacy orphans are adopted into a base),
|
|
10
|
+
* so the selector never offers an orphan bucket. The kernel keeps `null` /
|
|
11
|
+
* `sys_metadata` provenance only as a read-side rehydration tag for legacy rows.
|
|
22
12
|
*/
|
|
23
13
|
export declare function buildPackageScopeOptions(rawList: unknown[] | null | undefined): {
|
|
24
14
|
id: string;
|
|
@@ -1,27 +1,16 @@
|
|
|
1
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
2
|
const SYSTEM_SCOPES = new Set(['system', 'cloud']);
|
|
20
3
|
/**
|
|
21
|
-
* Build the Studio package-scope options from the raw `package` metadata list
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
4
|
+
* Build the Studio package-scope options from the raw `package` metadata list:
|
|
5
|
+
* the **writable bases** (project-scoped, DB-backed packages) only — the sole
|
|
6
|
+
* valid authoring destinations (ADR-0070 D2). Code/installed (system|cloud)
|
|
7
|
+
* packages are filtered out.
|
|
8
|
+
*
|
|
9
|
+
* There is no package-less "Local / Custom" scope: every runtime-authored item
|
|
10
|
+
* lives in a writable base (ADR-0070 D1/D5 — the kernel rejects orphan creates
|
|
11
|
+
* with `writable_package_required`, and legacy orphans are adopted into a base),
|
|
12
|
+
* so the selector never offers an orphan bucket. The kernel keeps `null` /
|
|
13
|
+
* `sys_metadata` provenance only as a read-side rehydration tag for legacy rows.
|
|
25
14
|
*/
|
|
26
15
|
export function buildPackageScopeOptions(rawList) {
|
|
27
16
|
const rows = (rawList ?? [])
|
|
@@ -36,8 +25,5 @@ export function buildPackageScopeOptions(rawList) {
|
|
|
36
25
|
})
|
|
37
26
|
.filter((p) => p.id && !SYSTEM_SCOPES.has(p.scope));
|
|
38
27
|
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
39
|
-
|
|
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()) }];
|
|
28
|
+
return rows.map((p) => ({ id: p.id, name: p.name }));
|
|
43
29
|
}
|
|
@@ -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
|
/**
|
|
@@ -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[];
|
|
@@ -39,11 +40,29 @@ export interface FlowCanvasProps {
|
|
|
39
40
|
invalidNodeIds?: string[];
|
|
40
41
|
/** Structural-validation: edges (keyed `${source}->${target}`) to paint red. */
|
|
41
42
|
invalidEdges?: ReadonlySet<string>;
|
|
42
|
-
/**
|
|
43
|
-
|
|
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;
|
|
44
63
|
onSelect: (node: FlowNode | null) => void;
|
|
45
64
|
/** Select an edge (its `edgeKey`), or clear selection with `null`. */
|
|
46
65
|
onSelectEdge?: (edge: FlowEdge | null, key: string) => void;
|
|
47
66
|
onPatch?: (partial: Record<string, unknown>) => void;
|
|
48
67
|
}
|
|
49
|
-
export declare function FlowCanvas({ nodes, edges, editable, designMode, selectedId, selectedEdgeId, locale, activeNodeId, visitedNodeIds, traversedEdgeIds, invalidNodeIds, invalidEdges,
|
|
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;
|
|
@@ -21,17 +21,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
21
21
|
* `onPatch(partial)` and the host merges + persists.
|
|
22
22
|
*/
|
|
23
23
|
import * as React from 'react';
|
|
24
|
-
import { AlertTriangle, Maximize2, Plus, ZoomIn, ZoomOut } from 'lucide-react';
|
|
24
|
+
import { AlertCircle, AlertTriangle, Maximize2, Plus, ZoomIn, ZoomOut } from 'lucide-react';
|
|
25
25
|
import { cn } from '@object-ui/components';
|
|
26
26
|
import { uniqueId, appendArray, spliceArray } from '../inspectors/_shared';
|
|
27
27
|
import { t as tr } from '../i18n';
|
|
28
|
-
import { computeLayout, diagramSize, bottomAnchor, topAnchor, rightAnchor, edgePath, edgeMidpoint, backEdgePath, backEdgeLabelAnchor, isBackEdge, edgeKey, conditionText, } from './flow-canvas-layout';
|
|
28
|
+
import { computeLayout, diagramSize, NODE_W, NODE_H, bottomAnchor, topAnchor, rightAnchor, edgePath, edgeMidpoint, backEdgePath, backEdgeLabelAnchor, isBackEdge, edgeKey, conditionText, } from './flow-canvas-layout';
|
|
29
29
|
import { NodeCard, NodePalette, defaultNodeLabel, defaultNodeExtras } from './flow-canvas-parts';
|
|
30
30
|
import { useFlowNodePalette } from './useFlowNodePalette';
|
|
31
|
+
import { indexProblemBadges, edgeProblemKey } from './flow-problems';
|
|
31
32
|
const MIN_ZOOM = 0.4;
|
|
32
33
|
const MAX_ZOOM = 1.6;
|
|
33
34
|
const DRAG_THRESHOLD = 4;
|
|
34
|
-
export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, selectedEdgeId, locale, activeNodeId, visitedNodeIds, traversedEdgeIds, invalidNodeIds, invalidEdges,
|
|
35
|
+
export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, selectedEdgeId, locale, activeNodeId, visitedNodeIds, traversedEdgeIds, invalidNodeIds, invalidEdges, onRevealProblem, problems, revealSignal, onSelect, onSelectEdge, onPatch, }) {
|
|
35
36
|
const viewportRef = React.useRef(null);
|
|
36
37
|
const [zoom, setZoom] = React.useState(1);
|
|
37
38
|
const [pan, setPan] = React.useState({ x: 0, y: 0 });
|
|
@@ -53,6 +54,13 @@ export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, sel
|
|
|
53
54
|
const traversedSet = React.useMemo(() => new Set(traversedEdgeIds ?? []), [traversedEdgeIds]);
|
|
54
55
|
const invalidNodeSet = React.useMemo(() => new Set(invalidNodeIds ?? []), [invalidNodeIds]);
|
|
55
56
|
const simRunning = (visitedNodeIds?.length ?? 0) > 0 || !!activeNodeId;
|
|
57
|
+
// Per-element validation badges (errors dominate warnings on the same
|
|
58
|
+
// element). Derived from the live `problems` list so badges clear as issues
|
|
59
|
+
// are resolved.
|
|
60
|
+
const { byNode: nodeBadges, byEdge: edgeBadges } = React.useMemo(() => indexProblemBadges(problems ?? []), [problems]);
|
|
61
|
+
// Error-level problems shown in the always-visible inline banner — driven by
|
|
62
|
+
// the same `problems` list as the panel/badges so the three stay in lock-step.
|
|
63
|
+
const bannerErrors = React.useMemo(() => (problems ?? []).filter((p) => p.level === 'error'), [problems]);
|
|
56
64
|
const positionOf = React.useCallback((id) => {
|
|
57
65
|
if (dragPos && dragPos.id === id)
|
|
58
66
|
return { x: dragPos.x, y: dragPos.y };
|
|
@@ -272,6 +280,31 @@ export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, sel
|
|
|
272
280
|
y: Math.max(16, (vp.clientHeight - size.height * z) / 2),
|
|
273
281
|
});
|
|
274
282
|
}, [size.height, size.width]);
|
|
283
|
+
// Pan to center an element when the Problems panel asks to reveal it. Driven
|
|
284
|
+
// by a changing `nonce` so re-clicking the same problem re-centers it.
|
|
285
|
+
React.useEffect(() => {
|
|
286
|
+
if (!revealSignal)
|
|
287
|
+
return;
|
|
288
|
+
const vp = viewportRef.current;
|
|
289
|
+
if (!vp)
|
|
290
|
+
return;
|
|
291
|
+
const t = revealSignal.target;
|
|
292
|
+
let pt = null;
|
|
293
|
+
if (t.kind === 'node') {
|
|
294
|
+
const p = layout.get(t.nodeId);
|
|
295
|
+
if (p)
|
|
296
|
+
pt = { x: p.x + NODE_W / 2, y: p.y + NODE_H / 2 };
|
|
297
|
+
}
|
|
298
|
+
else if (t.kind === 'edge') {
|
|
299
|
+
const s = layout.get(t.source);
|
|
300
|
+
const d = layout.get(t.target);
|
|
301
|
+
if (s && d)
|
|
302
|
+
pt = { x: (s.x + d.x) / 2 + NODE_W / 2, y: (s.y + d.y) / 2 + NODE_H / 2 };
|
|
303
|
+
}
|
|
304
|
+
if (pt)
|
|
305
|
+
setPan({ x: vp.clientWidth / 2 - pt.x * zoom, y: vp.clientHeight / 2 - pt.y * zoom });
|
|
306
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
307
|
+
}, [revealSignal?.nonce]);
|
|
275
308
|
// ── Keyboard: delete selected node ─────────────────────────────────────────
|
|
276
309
|
const onKeyDown = React.useCallback((e) => {
|
|
277
310
|
if (!editable || !selectedId)
|
|
@@ -286,7 +319,10 @@ export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, sel
|
|
|
286
319
|
}
|
|
287
320
|
}, [deleteNode, editable, selectedId]);
|
|
288
321
|
// ── Render ─────────────────────────────────────────────────────────────────
|
|
289
|
-
return (_jsxs("div", { className: "relative h-full min-h-[320px] w-full overflow-hidden", children: [
|
|
322
|
+
return (_jsxs("div", { className: "relative h-full min-h-[320px] w-full overflow-hidden", children: [bannerErrors.length > 0 && (_jsxs("div", { className: "absolute left-2 top-2 z-30 max-w-[min(60%,420px)] space-y-1", children: [bannerErrors.slice(0, 3).map((p) => {
|
|
323
|
+
const clickable = !!onRevealProblem && p.target.kind !== 'flow';
|
|
324
|
+
return (_jsxs("button", { type: "button", role: "alert", disabled: !clickable, onPointerDown: (e) => e.stopPropagation(), onClick: clickable ? (e) => { e.stopPropagation(); onRevealProblem(p); } : undefined, title: clickable ? 'Reveal on canvas' : undefined, className: cn('flex w-full items-start gap-1.5 rounded-lg border border-destructive/40 bg-destructive/10 px-2.5 py-1.5 text-left text-[11px] leading-snug text-destructive shadow-sm backdrop-blur-sm transition-colors', clickable && 'cursor-pointer hover:border-destructive/60 hover:bg-destructive/20'), children: [_jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), _jsx("span", { children: p.message })] }, p.id));
|
|
325
|
+
}), bannerErrors.length > 3 && (_jsxs("div", { className: "px-2.5 text-[10px] text-destructive/80", children: ["+", bannerErrors.length - 3, " more\u2026"] }))] })), _jsxs("div", { className: "absolute right-2 top-2 z-30 flex items-center gap-1.5", children: [editable && (_jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setPaletteOpen((v) => !v), className: "inline-flex items-center gap-1.5 rounded-lg border bg-background/90 px-2.5 py-1.5 text-xs font-medium shadow-sm backdrop-blur-sm transition-colors hover:border-primary/50 hover:bg-accent hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), tr('engine.inspector.add.node', locale)] }), paletteOpen && (_jsx(NodePalette, { locale: locale, items: paletteItems, onClose: () => setPaletteOpen(false), onPick: (type) => addNode(type, { from: selectedId ?? undefined }) }))] })), _jsxs("div", { className: "flex items-center rounded-lg border bg-background/90 shadow-sm backdrop-blur-sm", children: [_jsx("button", { type: "button", title: "Zoom out", "aria-label": "Zoom out", onClick: () => setZoom((z) => clampZoom(z - 0.15)), className: "inline-flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground", children: _jsx(ZoomOut, { className: "h-3.5 w-3.5" }) }), _jsxs("span", { className: "w-10 text-center text-[11px] tabular-nums text-muted-foreground", children: [Math.round(zoom * 100), "%"] }), _jsx("button", { type: "button", title: "Zoom in", "aria-label": "Zoom in", onClick: () => setZoom((z) => clampZoom(z + 0.15)), className: "inline-flex h-7 w-7 items-center justify-center text-muted-foreground hover:text-foreground", children: _jsx(ZoomIn, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", title: "Fit to view", "aria-label": "Fit to view", onClick: fitToView, className: "inline-flex h-7 w-7 items-center justify-center border-l text-muted-foreground hover:text-foreground", children: _jsx(Maximize2, { className: "h-3.5 w-3.5" }) })] })] }), _jsx("div", { ref: viewportRef, tabIndex: 0, role: "application", "aria-label": "Flow canvas", onKeyDown: onKeyDown, onPointerDown: onBgPointerDown, onPointerMove: (e) => {
|
|
290
326
|
onBgPointerMove(e);
|
|
291
327
|
onNodePointerMove(e);
|
|
292
328
|
}, onPointerUp: (e) => {
|
|
@@ -325,6 +361,7 @@ export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, sel
|
|
|
325
361
|
const cond = conditionText(edge.condition);
|
|
326
362
|
const branchLabel = edge.isDefault ? 'else' : cond ? `if ${cond}` : edge.label;
|
|
327
363
|
const eid = edgeKey(edge, i);
|
|
364
|
+
const edgeBadge = edgeBadges.get(edgeProblemKey(edge.source, edge.target));
|
|
328
365
|
const traversed = traversedSet.has(eid);
|
|
329
366
|
const selected = selectedEdgeId === eid;
|
|
330
367
|
const d = back ? backEdgePath(from, to) : edgePath(from, to);
|
|
@@ -351,7 +388,9 @@ export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, sel
|
|
|
351
388
|
? 'border-destructive/60 text-destructive'
|
|
352
389
|
: back
|
|
353
390
|
? 'border-amber-500/50 text-amber-600 dark:text-amber-400'
|
|
354
|
-
: 'border-border text-muted-foreground'), children: branchLabel }) }) })),
|
|
391
|
+
: 'border-border text-muted-foreground'), children: branchLabel }) }) })), edgeBadge && (_jsx("foreignObject", { x: labelPos.x - 9, y: labelPos.y - 30, width: 18, height: 18, className: "pointer-events-auto overflow-visible", children: _jsx("span", { title: edgeBadge.title, "data-problem": edgeBadge.level, className: cn('inline-flex h-[18px] w-[18px] items-center justify-center rounded-full border bg-background shadow-sm', edgeBadge.level === 'error'
|
|
392
|
+
? 'border-destructive/50 text-destructive'
|
|
393
|
+
: 'border-amber-500/50 text-amber-600 dark:text-amber-400'), children: edgeBadge.level === 'error' ? (_jsx(AlertCircle, { className: "h-3 w-3" })) : (_jsx(AlertTriangle, { className: "h-3 w-3" })) }) })), editable && !back && (_jsx("foreignObject", {
|
|
355
394
|
// Sit the insert handle at the edge midpoint, but slide it
|
|
356
395
|
// to the right of the branch-label pill when one is present
|
|
357
396
|
// so the two don't stack on the same spot.
|
|
@@ -363,7 +402,7 @@ export function FlowCanvas({ nodes, edges, editable, designMode, selectedId, sel
|
|
|
363
402
|
const runState = activeNodeId === node.id ? 'active' : visitedSet.has(node.id) ? 'visited' : undefined;
|
|
364
403
|
return (_jsx(NodeCard, { id: node.id, type: node.type, label: node.label || node.id, summary: nodeSummary(node), position: positionOf(node.id), selected: selectedId === node.id, editable: editable, runState: runState, dimmed: simRunning && !runState, onPointerDown: onNodePointerDown(node.id), onSelect: () => designMode && onSelect(node), onAppend: () => addNode('create_record', { from: node.id }), onAddReviseLoop: editable && node.type === 'approval' && !reviseLoopSources.has(node.id)
|
|
365
404
|
? () => addReviseLoop(node.id)
|
|
366
|
-
: undefined, invalid: invalidNodeSet.has(node.id) }, node.id));
|
|
405
|
+
: undefined, invalid: invalidNodeSet.has(node.id), badge: nodeBadges.get(node.id) }, node.id));
|
|
367
406
|
})] }) })] }));
|
|
368
407
|
}
|
|
369
408
|
/** One-line config summary shown on the node card (best-effort, type-aware). */
|
|
@@ -17,4 +17,4 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import * as React from 'react';
|
|
19
19
|
import type { MetadataPreviewProps } from '../preview-registry';
|
|
20
|
-
export declare function FlowPreview({ draft, editing, selection, onSelectionChange, onPatch, locale }: MetadataPreviewProps): React.JSX.Element;
|
|
20
|
+
export declare function FlowPreview({ draft, editing, selection, onSelectionChange, onPatch, locale, diagnostics }: MetadataPreviewProps): React.JSX.Element;
|