@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.
Files changed (141) hide show
  1. package/CHANGELOG.md +560 -0
  2. package/dist/console/AppContent.js +23 -17
  3. package/dist/console/ConsoleShell.d.ts +16 -0
  4. package/dist/console/ConsoleShell.js +43 -2
  5. package/dist/console/ai/AiChatPage.js +47 -16
  6. package/dist/console/ai/LiveCanvas.d.ts +8 -2
  7. package/dist/console/ai/LiveCanvas.js +6 -4
  8. package/dist/console/home/HomeLayout.js +5 -7
  9. package/dist/console/home/HomePage.js +1 -9
  10. package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
  11. package/dist/console/organizations/OrganizationsPage.js +22 -3
  12. package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
  13. package/dist/console/organizations/provisionEnvironment.js +64 -0
  14. package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
  15. package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
  16. package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
  17. package/dist/environment/EnvironmentListToolbar.js +59 -0
  18. package/dist/environment/entitlements.d.ts +90 -0
  19. package/dist/environment/entitlements.js +91 -0
  20. package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
  21. package/dist/environment/useEnvironmentEntitlements.js +108 -0
  22. package/dist/hooks/useActionModal.js +15 -1
  23. package/dist/hooks/useAiSurface.d.ts +59 -0
  24. package/dist/hooks/useAiSurface.js +78 -0
  25. package/dist/hooks/useChatConversation.d.ts +30 -0
  26. package/dist/hooks/useChatConversation.js +63 -0
  27. package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
  28. package/dist/hooks/useConsoleActionRuntime.js +42 -10
  29. package/dist/index.d.ts +5 -2
  30. package/dist/index.js +10 -2
  31. package/dist/layout/AppHeader.js +28 -4
  32. package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
  33. package/dist/layout/ConsoleFloatingChatbot.js +41 -10
  34. package/dist/layout/ConsoleLayout.js +5 -6
  35. package/dist/layout/ContextSelectors.js +59 -35
  36. package/dist/layout/agentPicker.d.ts +56 -0
  37. package/dist/layout/agentPicker.js +40 -0
  38. package/dist/preview/CommitTimeline.d.ts +15 -0
  39. package/dist/preview/CommitTimeline.js +82 -0
  40. package/dist/preview/DraftPreviewBar.js +20 -7
  41. package/dist/preview/UnpublishedAppBar.js +11 -7
  42. package/dist/preview/commitHistory.d.ts +28 -0
  43. package/dist/preview/commitHistory.js +48 -0
  44. package/dist/providers/ExpressionProvider.js +9 -3
  45. package/dist/providers/MetadataProvider.js +9 -0
  46. package/dist/utils/index.d.ts +2 -2
  47. package/dist/utils/index.js +1 -1
  48. package/dist/utils/recordFormNavigation.d.ts +60 -0
  49. package/dist/utils/recordFormNavigation.js +35 -0
  50. package/dist/utils/resolvePageVarTokens.d.ts +31 -0
  51. package/dist/utils/resolvePageVarTokens.js +72 -0
  52. package/dist/views/CreateViewDialog.js +14 -1
  53. package/dist/views/FlowRunner.d.ts +2 -30
  54. package/dist/views/FlowRunner.js +18 -50
  55. package/dist/views/ObjectView.js +26 -12
  56. package/dist/views/ScreenView.d.ts +70 -0
  57. package/dist/views/ScreenView.js +73 -0
  58. package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
  59. package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
  60. package/dist/views/metadata-admin/DirectoryPage.js +2 -14
  61. package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
  62. package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
  63. package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
  64. package/dist/views/metadata-admin/PackagesPage.js +58 -5
  65. package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
  66. package/dist/views/metadata-admin/ResourceEditPage.js +83 -24
  67. package/dist/views/metadata-admin/ResourceListPage.js +28 -19
  68. package/dist/views/metadata-admin/StudioHomePage.js +6 -14
  69. package/dist/views/metadata-admin/anchors.js +20 -2
  70. package/dist/views/metadata-admin/createBody.d.ts +26 -0
  71. package/dist/views/metadata-admin/createBody.js +30 -0
  72. package/dist/views/metadata-admin/i18n.js +108 -2
  73. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +10 -2
  74. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +136 -8
  75. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +99 -4
  76. package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
  77. package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
  78. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
  79. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
  80. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
  81. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
  82. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +81 -4
  83. package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
  84. package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
  85. package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
  86. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
  87. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
  88. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
  89. package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
  90. package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
  91. package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
  92. package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
  93. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
  94. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +102 -0
  95. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
  96. package/dist/views/metadata-admin/inspectors/flow-node-config.js +67 -11
  97. package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
  98. package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
  99. package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
  100. package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
  101. package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
  102. package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
  103. package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
  104. package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
  105. package/dist/views/metadata-admin/issuePath.d.ts +22 -0
  106. package/dist/views/metadata-admin/issuePath.js +65 -0
  107. package/dist/views/metadata-admin/package-scope.d.ts +41 -0
  108. package/dist/views/metadata-admin/package-scope.js +59 -0
  109. package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
  110. package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
  111. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +26 -1
  112. package/dist/views/metadata-admin/previews/FlowCanvas.js +143 -16
  113. package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
  114. package/dist/views/metadata-admin/previews/FlowPreview.js +47 -7
  115. package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
  116. package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
  117. package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
  118. package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
  119. package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
  120. package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
  121. package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
  122. package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
  123. package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
  124. package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
  125. package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
  126. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +17 -1
  127. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +23 -6
  128. package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
  129. package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
  130. package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
  131. package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
  132. package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
  133. package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
  134. package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
  135. package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
  136. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +20 -0
  137. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
  138. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +76 -2
  139. package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
  140. package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
  141. package/package.json +38 -38
@@ -28,6 +28,63 @@ export function evalCondition(expr, variables) {
28
28
  return { result: false, error: err.message || 'Evaluation failed.' };
29
29
  }
30
30
  }
31
+ /**
32
+ * Find a directed cycle in `edges` over `nodeIds`, returned as the node path
33
+ * that closes the loop (e.g. `['a','b','a']`), or `null` when the graph is a
34
+ * DAG. Iterative DFS with a recursion-stack colour map; the first cycle found
35
+ * wins (enough to report — the author fixes one at a time).
36
+ */
37
+ export function findCycle(nodeIds, edges) {
38
+ const adj = new Map();
39
+ for (const id of nodeIds)
40
+ adj.set(id, []);
41
+ for (const e of edges) {
42
+ if (adj.has(e.source) && adj.has(e.target))
43
+ adj.get(e.source).push(e.target);
44
+ }
45
+ const WHITE = 0, GRAY = 1, BLACK = 2;
46
+ const color = new Map(nodeIds.map((id) => [id, WHITE]));
47
+ const stack = [];
48
+ const visit = (start) => {
49
+ // Explicit stack of {node, next-child-index} frames so a deep graph can't
50
+ // blow the JS call stack.
51
+ const frames = [{ id: start, i: 0 }];
52
+ color.set(start, GRAY);
53
+ stack.push(start);
54
+ while (frames.length) {
55
+ const frame = frames[frames.length - 1];
56
+ const children = adj.get(frame.id) ?? [];
57
+ if (frame.i < children.length) {
58
+ const next = children[frame.i++];
59
+ const c = color.get(next);
60
+ if (c === GRAY) {
61
+ // Back into the active path → cycle. Slice from `next` to close it.
62
+ const from = stack.indexOf(next);
63
+ return [...stack.slice(from), next];
64
+ }
65
+ if (c === WHITE) {
66
+ color.set(next, GRAY);
67
+ stack.push(next);
68
+ frames.push({ id: next, i: 0 });
69
+ }
70
+ }
71
+ else {
72
+ color.set(frame.id, BLACK);
73
+ stack.pop();
74
+ frames.pop();
75
+ }
76
+ }
77
+ return null;
78
+ };
79
+ for (const id of nodeIds) {
80
+ if (color.get(id) === WHITE) {
81
+ const cycle = visit(id);
82
+ if (cycle)
83
+ return cycle;
84
+ }
85
+ }
86
+ return null;
87
+ }
31
88
  /** Static structural checks; `errors` block Run, `warnings` are advisory. */
32
89
  export function validateFlowDraft(nodes, edges) {
33
90
  const errors = [];
@@ -44,10 +101,12 @@ export function validateFlowDraft(nodes, edges) {
44
101
  idSet.add(id);
45
102
  }
46
103
  for (const e of edges) {
104
+ // Attach the endpoints so a dangling-edge error can badge the offending
105
+ // connection on the canvas (not just appear as a flow-level message).
47
106
  if (!idSet.has(e.source))
48
- errors.push({ level: 'error', message: `Edge source "${e.source}" does not exist.` });
107
+ errors.push({ level: 'error', edge: { source: e.source, target: e.target }, message: `Edge source "${e.source}" does not exist.` });
49
108
  if (!idSet.has(e.target))
50
- errors.push({ level: 'error', message: `Edge target "${e.target}" does not exist.` });
109
+ errors.push({ level: 'error', edge: { source: e.source, target: e.target }, message: `Edge target "${e.target}" does not exist.` });
51
110
  }
52
111
  // Entry resolution: prefer an explicit `start` node, else a node with no
53
112
  // incoming edge. Zero or many → the author must fix it before running.
@@ -109,5 +168,20 @@ export function validateFlowDraft(nodes, edges) {
109
168
  }
110
169
  }
111
170
  }
171
+ // DAG-modulo-back-edges (ADR-0044): the engine requires the flow graph MINUS
172
+ // declared back-edges to be acyclic. A declared revise loop (its closing edge
173
+ // marked `type: 'back'`) is excluded and passes; any *unmarked* cycle is an
174
+ // error — the author must opt in, edge by edge, exactly as `registerFlow`
175
+ // enforces server-side.
176
+ const forwardEdges = edges.filter((e) => e.type !== 'back');
177
+ const cycle = findCycle(ids.filter((id) => !!id), forwardEdges);
178
+ if (cycle) {
179
+ errors.push({
180
+ level: 'error',
181
+ nodeId: cycle[0],
182
+ cycle,
183
+ message: `Cycle detected (${cycle.join(' → ')}). Mark the connection that closes the loop as a back-edge (Connection type → Back-edge) to declare an intentional revise/rework loop.`,
184
+ });
185
+ }
112
186
  return { errors, warnings, startNodeId };
113
187
  }
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Supported faithfully: start, decision (edge-first CEL routing), the CRUD /
6
6
  * get / http / connector / script(notification|code) side-effects (MOCKED), and
7
- * end. `wait` and `screen` PAUSE for manual continuation. `loop` resolves its
7
+ * end. `wait`, `screen`, and `approval` PAUSE for manual continuation (an approval
8
+ * resumes down the chosen decision's branch — approve / reject / revise). `loop` resolves its
8
9
  * collection and exposes the iterator but is a labelled single pass (the edge
9
10
  * model has no separate body/exit edge). `parallel_gateway` fans out WITHOUT
10
11
  * join synchronization; the ADR-0031 structured containers (`parallel`,
@@ -27,10 +28,38 @@ export declare class FlowSimulator {
27
28
  step(): SimStep | null;
28
29
  /** Run to completion (or until a pause / error). */
29
30
  runToEnd(): SimState;
30
- /** Continue a flow paused on a `wait` / `screen` node. */
31
- resume(screenOutputs?: Record<string, unknown>): void;
31
+ /**
32
+ * Continue a flow paused on a `wait`, `screen`, or `approval` node.
33
+ * - `screenOutputs` — inputs captured from a paused screen.
34
+ * - `decision` — the branch an approval resumes down (ADR-0019/0044:
35
+ * `approve` / `reject` / `revise`). The run takes ONLY the out-edge whose
36
+ * label matches, mirroring how the engine resumes a suspended approval by
37
+ * branch label — instead of fanning out to every out-edge.
38
+ */
39
+ resume(opts?: {
40
+ screenOutputs?: Record<string, unknown>;
41
+ decision?: string;
42
+ }): void;
43
+ /**
44
+ * Resume a suspended approval down the chosen decision's out-edge: the one
45
+ * whose `label` equals `decision` (case-insensitive — `approve` / `reject` /
46
+ * `revise`). With no match (or no decision) it falls back to fanning out —
47
+ * mirroring the engine's unmatched-`branchLabel` fallback — and logs that so
48
+ * the author notices the unrouted decision.
49
+ */
50
+ private resumeApproval;
32
51
  private execute;
33
52
  private executeDecision;
53
+ /**
54
+ * assignment node — set flow variables. Normalizes the three authoring
55
+ * shapes the engine accepts (Studio's `{ assignments: { var: value } }`
56
+ * map, the example `{ assignments: [{ variable, value }] }` array, and the
57
+ * legacy flat `{ var: value }`) and interpolates `{var}` templates — so the
58
+ * Debug run mirrors runtime instead of silently no-oping.
59
+ */
60
+ private executeAssignment;
61
+ /** Resolve `{var}` templates in an assignment value against live variables. */
62
+ private interpolateValue;
34
63
  private executeLoop;
35
64
  /** Resolve a `{var}` template ref or a plain variable name from `variables`. */
36
65
  private resolveRef;
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
  import { evalCondition, validateFlowDraft } from './flow-sim-validate';
3
3
  const MAX_STEPS = 500;
4
- const PASS_THROUGH = new Set(['start', 'assignment']);
4
+ const PASS_THROUGH = new Set(['start']);
5
5
  const MOCKED_SIDE_EFFECT = new Set([
6
6
  'create_record',
7
7
  'update_record',
@@ -124,24 +124,58 @@ export class FlowSimulator {
124
124
  }
125
125
  return this.state;
126
126
  }
127
- /** Continue a flow paused on a `wait` / `screen` node. */
128
- resume(screenOutputs) {
127
+ /**
128
+ * Continue a flow paused on a `wait`, `screen`, or `approval` node.
129
+ * - `screenOutputs` — inputs captured from a paused screen.
130
+ * - `decision` — the branch an approval resumes down (ADR-0019/0044:
131
+ * `approve` / `reject` / `revise`). The run takes ONLY the out-edge whose
132
+ * label matches, mirroring how the engine resumes a suspended approval by
133
+ * branch label — instead of fanning out to every out-edge.
134
+ */
135
+ resume(opts = {}) {
129
136
  const s = this.state;
130
137
  if (s.status !== 'paused' || !s.activeNodeId)
131
138
  return;
132
139
  const node = this.nodes.get(s.activeNodeId);
133
- if (screenOutputs && node?.type === 'screen') {
134
- Object.assign(s.variables, screenOutputs);
140
+ if (opts.screenOutputs && node?.type === 'screen') {
141
+ Object.assign(s.variables, opts.screenOutputs);
135
142
  }
136
143
  s.pausedReason = undefined;
137
144
  s.status = 'running';
138
- if (node)
145
+ if (node?.type === 'approval')
146
+ this.resumeApproval(node, opts.decision);
147
+ else if (node)
139
148
  this.enqueueSuccessors(node);
140
149
  if (s.frontier.length === 0) {
141
150
  s.status = 'done';
142
151
  s.activeNodeId = null;
143
152
  }
144
153
  }
154
+ /**
155
+ * Resume a suspended approval down the chosen decision's out-edge: the one
156
+ * whose `label` equals `decision` (case-insensitive — `approve` / `reject` /
157
+ * `revise`). With no match (or no decision) it falls back to fanning out —
158
+ * mirroring the engine's unmatched-`branchLabel` fallback — and logs that so
159
+ * the author notices the unrouted decision.
160
+ */
161
+ resumeApproval(node, decision) {
162
+ const out = this.edges.map((e, i) => ({ e, i })).filter((x) => x.e.source === node.id);
163
+ const want = (decision ?? '').trim().toLowerCase();
164
+ const chosen = want ? out.find((x) => (x.e.label ?? '').trim().toLowerCase() === want) : undefined;
165
+ if (chosen) {
166
+ this.traverse(chosen.e, chosen.i);
167
+ this.record(node.id, 'approval', node.label, 'ok', { note: `Decision: ${decision} → ${chosen.e.target}` });
168
+ }
169
+ else {
170
+ for (const x of out)
171
+ this.traverse(x.e, x.i);
172
+ this.record(node.id, 'approval', node.label, 'ok', {
173
+ note: want
174
+ ? `No out-edge labelled "${decision}"; took all branches (engine label-fallback).`
175
+ : 'No decision supplied; took all branches.',
176
+ });
177
+ }
178
+ }
145
179
  // ---- node execution -----------------------------------------------------
146
180
  execute(node) {
147
181
  const type = node.type;
@@ -151,6 +185,8 @@ export class FlowSimulator {
151
185
  }
152
186
  if (type === 'decision')
153
187
  return this.executeDecision(node);
188
+ if (type === 'assignment')
189
+ return this.executeAssignment(node);
154
190
  if (UNSUPPORTED.has(type)) {
155
191
  this.enqueueSuccessors(node);
156
192
  return this.record(node.id, type, node.label, 'skipped', {
@@ -163,15 +199,34 @@ export class FlowSimulator {
163
199
  note: 'Parallel split — branches fan out (no join synchronization is simulated).',
164
200
  });
165
201
  }
202
+ if (type === 'approval') {
203
+ // ADR-0019: an approval node opens a request and SUSPENDS the run until a
204
+ // decision is recorded. Model that as a pause; the author resumes down the
205
+ // chosen approve / reject / revise out-edge (see resumeApproval) rather
206
+ // than fanning out to every out-edge at once.
207
+ this.state.status = 'paused';
208
+ this.state.pausedReason = 'approval';
209
+ return this.record(node.id, type, node.label, 'paused', { note: 'Approval reached — choose a decision to continue.' });
210
+ }
166
211
  if (type === 'wait') {
167
212
  this.state.status = 'paused';
168
213
  this.state.pausedReason = 'wait';
169
214
  return this.record(node.id, type, node.label, 'paused', { note: 'Wait reached — continue manually.' });
170
215
  }
171
216
  if (type === 'screen') {
172
- this.state.status = 'paused';
173
- this.state.pausedReason = 'screen';
174
- return this.record(node.id, type, node.label, 'paused', { note: 'Screen reached provide inputs, then continue.' });
217
+ // Mirror the engine's `shouldPause`: a screen suspends only when it
218
+ // collects input (`fields`) or explicitly opts in (`waitForInput`).
219
+ // A field-less / `waitForInput:false` screen is a server pass-through.
220
+ const fields = Array.isArray(node.config?.fields) ? node.config.fields : [];
221
+ const waitForInput = node.config?.waitForInput;
222
+ const shouldPause = waitForInput === true || (fields.length > 0 && waitForInput !== false);
223
+ if (shouldPause) {
224
+ this.state.status = 'paused';
225
+ this.state.pausedReason = 'screen';
226
+ return this.record(node.id, type, node.label, 'paused', { note: 'Screen reached — provide inputs, then continue.' });
227
+ }
228
+ this.enqueueSuccessors(node);
229
+ return this.record(node.id, type, node.label, 'ok', { note: 'Screen has no input — passed through (matches runtime).' });
175
230
  }
176
231
  if (type === 'loop') {
177
232
  const step = this.executeLoop(node);
@@ -233,6 +288,61 @@ export class FlowSimulator {
233
288
  note: multiMatch ? 'Multiple conditions matched; the first declared branch was taken.' : undefined,
234
289
  });
235
290
  }
291
+ /**
292
+ * assignment node — set flow variables. Normalizes the three authoring
293
+ * shapes the engine accepts (Studio's `{ assignments: { var: value } }`
294
+ * map, the example `{ assignments: [{ variable, value }] }` array, and the
295
+ * legacy flat `{ var: value }`) and interpolates `{var}` templates — so the
296
+ * Debug run mirrors runtime instead of silently no-oping.
297
+ */
298
+ executeAssignment(node) {
299
+ const cfg = node.config ?? {};
300
+ const raw = cfg.assignments;
301
+ const pairs = [];
302
+ if (Array.isArray(raw)) {
303
+ for (const item of raw) {
304
+ if (item && typeof item === 'object') {
305
+ const e = item;
306
+ const name = e.variable ?? e.name ?? e.key;
307
+ if (typeof name === 'string' && name)
308
+ pairs.push([name, e.value]);
309
+ }
310
+ }
311
+ }
312
+ else if (raw && typeof raw === 'object') {
313
+ for (const [k, v] of Object.entries(raw))
314
+ pairs.push([k, v]);
315
+ }
316
+ else {
317
+ for (const [k, v] of Object.entries(cfg))
318
+ pairs.push([k, v]);
319
+ }
320
+ const wrote = {};
321
+ for (const [key, value] of pairs) {
322
+ const resolved = this.interpolateValue(value);
323
+ this.state.variables[key] = resolved;
324
+ wrote[key] = resolved;
325
+ }
326
+ this.enqueueSuccessors(node);
327
+ return this.record(node.id, 'assignment', node.label, 'ok', {
328
+ wrote: Object.keys(wrote).length ? wrote : undefined,
329
+ note: Object.keys(wrote).length ? undefined : 'No assignments defined.',
330
+ });
331
+ }
332
+ /** Resolve `{var}` templates in an assignment value against live variables. */
333
+ interpolateValue(value) {
334
+ if (typeof value !== 'string')
335
+ return value;
336
+ const whole = value.match(/^\{([^}]+)\}$/);
337
+ if (whole) {
338
+ const v = this.state.variables[whole[1].trim()];
339
+ return v !== undefined ? v : value;
340
+ }
341
+ return value.replace(/\{([^}]+)\}/g, (_m, k) => {
342
+ const v = this.state.variables[String(k).trim()];
343
+ return v === undefined || v === null ? '' : String(v);
344
+ });
345
+ }
236
346
  executeLoop(node) {
237
347
  const ref = str(node.config?.collection);
238
348
  const iterVar = str(node.config?.iteratorVariable);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/app-shell",
3
- "version": "7.0.0",
3
+ "version": "7.2.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
@@ -33,36 +33,36 @@
33
33
  "qrcode": "^1.5.4",
34
34
  "sonner": "^2.0.7",
35
35
  "zod": "^4.4.3",
36
- "@object-ui/auth": "7.0.0",
37
- "@object-ui/collaboration": "7.0.0",
38
- "@object-ui/components": "7.0.0",
39
- "@object-ui/core": "7.0.0",
40
- "@object-ui/data-objectstack": "7.0.0",
41
- "@object-ui/fields": "7.0.0",
42
- "@object-ui/i18n": "7.0.0",
43
- "@object-ui/layout": "7.0.0",
44
- "@object-ui/permissions": "7.0.0",
45
- "@object-ui/plugin-editor": "7.0.0",
46
- "@object-ui/providers": "7.0.0",
47
- "@object-ui/react": "7.0.0",
48
- "@object-ui/types": "7.0.0"
36
+ "@object-ui/auth": "7.2.0",
37
+ "@object-ui/collaboration": "7.2.0",
38
+ "@object-ui/components": "7.2.0",
39
+ "@object-ui/core": "7.2.0",
40
+ "@object-ui/data-objectstack": "7.2.0",
41
+ "@object-ui/fields": "7.2.0",
42
+ "@object-ui/i18n": "7.2.0",
43
+ "@object-ui/layout": "7.2.0",
44
+ "@object-ui/permissions": "7.2.0",
45
+ "@object-ui/plugin-editor": "7.2.0",
46
+ "@object-ui/providers": "7.2.0",
47
+ "@object-ui/react": "7.2.0",
48
+ "@object-ui/types": "7.2.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "react": "^18.0.0 || ^19.0.0",
52
52
  "react-dom": "^18.0.0 || ^19.0.0",
53
53
  "react-router-dom": "^6.0.0 || ^7.0.0",
54
- "@object-ui/plugin-calendar": "^7.0.0",
55
- "@object-ui/plugin-charts": "^7.0.0",
56
- "@object-ui/plugin-chatbot": "^7.0.0",
57
- "@object-ui/plugin-dashboard": "^7.0.0",
58
- "@object-ui/plugin-designer": "^7.0.0",
59
- "@object-ui/plugin-detail": "^7.0.0",
60
- "@object-ui/plugin-form": "^7.0.0",
61
- "@object-ui/plugin-grid": "^7.0.0",
62
- "@object-ui/plugin-kanban": "^7.0.0",
63
- "@object-ui/plugin-list": "^7.0.0",
64
- "@object-ui/plugin-report": "^7.0.0",
65
- "@object-ui/plugin-view": "^7.0.0"
54
+ "@object-ui/plugin-calendar": "^7.2.0",
55
+ "@object-ui/plugin-charts": "^7.2.0",
56
+ "@object-ui/plugin-chatbot": "^7.2.0",
57
+ "@object-ui/plugin-dashboard": "^7.2.0",
58
+ "@object-ui/plugin-designer": "^7.2.0",
59
+ "@object-ui/plugin-detail": "^7.2.0",
60
+ "@object-ui/plugin-form": "^7.2.0",
61
+ "@object-ui/plugin-grid": "^7.2.0",
62
+ "@object-ui/plugin-kanban": "^7.2.0",
63
+ "@object-ui/plugin-list": "^7.2.0",
64
+ "@object-ui/plugin-report": "^7.2.0",
65
+ "@object-ui/plugin-view": "^7.2.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "^26.0.0",
@@ -75,18 +75,18 @@
75
75
  "sonner": "^2.0.7",
76
76
  "typescript": "^6.0.3",
77
77
  "vite": "^8.0.16",
78
- "@object-ui/plugin-calendar": "7.0.0",
79
- "@object-ui/plugin-charts": "7.0.0",
80
- "@object-ui/plugin-chatbot": "7.0.0",
81
- "@object-ui/plugin-dashboard": "7.0.0",
82
- "@object-ui/plugin-designer": "7.0.0",
83
- "@object-ui/plugin-detail": "7.0.0",
84
- "@object-ui/plugin-form": "7.0.0",
85
- "@object-ui/plugin-grid": "7.0.0",
86
- "@object-ui/plugin-kanban": "7.0.0",
87
- "@object-ui/plugin-list": "7.0.0",
88
- "@object-ui/plugin-report": "7.0.0",
89
- "@object-ui/plugin-view": "7.0.0"
78
+ "@object-ui/plugin-calendar": "7.2.0",
79
+ "@object-ui/plugin-charts": "7.2.0",
80
+ "@object-ui/plugin-chatbot": "7.2.0",
81
+ "@object-ui/plugin-dashboard": "7.2.0",
82
+ "@object-ui/plugin-designer": "7.2.0",
83
+ "@object-ui/plugin-detail": "7.2.0",
84
+ "@object-ui/plugin-form": "7.2.0",
85
+ "@object-ui/plugin-grid": "7.2.0",
86
+ "@object-ui/plugin-kanban": "7.2.0",
87
+ "@object-ui/plugin-list": "7.2.0",
88
+ "@object-ui/plugin-report": "7.2.0",
89
+ "@object-ui/plugin-view": "7.2.0"
90
90
  },
91
91
  "keywords": [
92
92
  "objectui",