@object-ui/app-shell 7.1.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 (95) hide show
  1. package/CHANGELOG.md +279 -0
  2. package/dist/console/AppContent.js +9 -15
  3. package/dist/console/ConsoleShell.d.ts +16 -0
  4. package/dist/console/ConsoleShell.js +43 -2
  5. package/dist/console/ai/AiChatPage.js +36 -9
  6. package/dist/console/home/HomeLayout.js +5 -7
  7. package/dist/console/home/HomePage.js +1 -9
  8. package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
  9. package/dist/console/organizations/OrganizationsPage.js +22 -3
  10. package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
  11. package/dist/console/organizations/provisionEnvironment.js +64 -0
  12. package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
  13. package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
  14. package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
  15. package/dist/environment/EnvironmentListToolbar.js +59 -0
  16. package/dist/environment/entitlements.d.ts +90 -0
  17. package/dist/environment/entitlements.js +91 -0
  18. package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
  19. package/dist/environment/useEnvironmentEntitlements.js +108 -0
  20. package/dist/hooks/useActionModal.js +15 -1
  21. package/dist/hooks/useAiSurface.d.ts +59 -0
  22. package/dist/hooks/useAiSurface.js +78 -0
  23. package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
  24. package/dist/hooks/useConsoleActionRuntime.js +36 -8
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.js +5 -1
  27. package/dist/layout/AppHeader.js +28 -4
  28. package/dist/layout/ConsoleFloatingChatbot.js +16 -2
  29. package/dist/layout/ConsoleLayout.js +5 -6
  30. package/dist/preview/DraftPreviewBar.js +20 -7
  31. package/dist/providers/ExpressionProvider.js +9 -3
  32. package/dist/utils/index.d.ts +2 -2
  33. package/dist/utils/index.js +1 -1
  34. package/dist/utils/recordFormNavigation.d.ts +60 -0
  35. package/dist/utils/recordFormNavigation.js +35 -0
  36. package/dist/utils/resolvePageVarTokens.d.ts +31 -0
  37. package/dist/utils/resolvePageVarTokens.js +72 -0
  38. package/dist/views/CreateViewDialog.js +14 -1
  39. package/dist/views/ObjectView.js +26 -12
  40. package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
  41. package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
  42. package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
  43. package/dist/views/metadata-admin/PackagesPage.js +49 -4
  44. package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
  45. package/dist/views/metadata-admin/ResourceEditPage.js +36 -4
  46. package/dist/views/metadata-admin/ResourceListPage.js +21 -4
  47. package/dist/views/metadata-admin/createBody.d.ts +26 -0
  48. package/dist/views/metadata-admin/createBody.js +30 -0
  49. package/dist/views/metadata-admin/i18n.js +20 -0
  50. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +8 -0
  51. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +17 -3
  52. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +16 -2
  53. package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
  54. package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
  55. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
  56. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
  57. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
  58. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
  59. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +15 -3
  60. package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
  61. package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
  62. package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
  63. package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
  64. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +6 -1
  65. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
  66. package/dist/views/metadata-admin/inspectors/flow-node-config.js +21 -10
  67. package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
  68. package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
  69. package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
  70. package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
  71. package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
  72. package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
  73. package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
  74. package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
  75. package/dist/views/metadata-admin/package-scope.d.ts +15 -0
  76. package/dist/views/metadata-admin/package-scope.js +16 -0
  77. package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
  78. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +22 -3
  79. package/dist/views/metadata-admin/previews/FlowCanvas.js +45 -6
  80. package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
  81. package/dist/views/metadata-admin/previews/FlowPreview.js +42 -30
  82. package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
  83. package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
  84. package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
  85. package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
  86. package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
  87. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +9 -1
  88. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +5 -3
  89. package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
  90. package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
  91. package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
  92. package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
  93. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +9 -0
  94. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +4 -2
  95. package/package.json +38 -38
@@ -0,0 +1,97 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+ /**
3
+ * flow-expr-problems — pure, client-side detection of EXPRESSION issues across a
4
+ * whole flow draft, for surfacing in the Problems panel + canvas badges (#1934).
5
+ *
6
+ * Two kinds, mirroring the inline inspector checks but aggregated per node/edge:
7
+ * • ADR-0032 brace / shape ERRORS on every CEL field (decision conditions +
8
+ * branch expressions, screen `visibleWhen`, loop collection, edge guards…).
9
+ * Deterministic, scope-free → zero false positives.
10
+ * • Scope-aware "unknown reference" WARNINGS — a bare root not in scope at the
11
+ * node. The START node is skipped: its entry condition legitimately uses the
12
+ * trigger record's fields *bare* (`status`), which can't be told apart from a
13
+ * typo without the object schema (an async fetch this pure pass avoids); the
14
+ * inline inspector check, which does fetch, still covers it.
15
+ *
16
+ * Only CEL (`expression`) surfaces are scanned — template (`{var}`) values use
17
+ * single braces legally and are left to the inline check.
18
+ */
19
+ import { fieldsForNodeType, getFieldValue } from '../inspectors/flow-node-config';
20
+ import { resolveFlowScope } from '../inspectors/flow-scope';
21
+ import { scopeRoots, findUnknownRefs, describeUnknownRefs } from '../inspectors/flow-ref-check';
22
+ import { validateExpressionClient } from '../inspectors/expression-validate';
23
+ function asArray(v) {
24
+ return Array.isArray(v) ? v : [];
25
+ }
26
+ function asRecord(v) {
27
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : {};
28
+ }
29
+ function str(v) {
30
+ return typeof v === 'string' && v ? v : undefined;
31
+ }
32
+ /** Brace error (error) else unknown-ref (warning, when `roots` given) for one CEL value. */
33
+ function checkCel(value, roots) {
34
+ const issue = validateExpressionClient('predicate', value);
35
+ if (issue)
36
+ return { level: 'error', message: issue.message };
37
+ if (roots && roots.size > 0) {
38
+ const unknown = findUnknownRefs(value, 'predicate', roots);
39
+ if (unknown.length > 0)
40
+ return { level: 'warning', message: describeUnknownRefs(unknown) };
41
+ }
42
+ return null;
43
+ }
44
+ /**
45
+ * Scan a flow draft for expression problems, resolved onto node / edge targets.
46
+ * Pure: no network — the trigger object's fields are not expanded (root-only
47
+ * scope), which is why the start node is excluded from the ref check.
48
+ */
49
+ export function flowExpressionProblems(draft) {
50
+ const nodes = asArray(draft.nodes).map(asRecord);
51
+ const edges = asArray(draft.edges).map(asRecord);
52
+ const startId = str(nodes.find((n) => str(n.type) === 'start')?.id);
53
+ const out = [];
54
+ for (const node of nodes) {
55
+ const nodeId = str(node.id);
56
+ const type = str(node.type);
57
+ if (!nodeId || !type)
58
+ continue;
59
+ // Root-only scope at this node; skip the ref check on the start node (its
60
+ // bare trigger-record fields are indistinguishable from typos here).
61
+ const roots = nodeId === startId ? null : scopeRoots(resolveFlowScope(draft, nodeId).refs);
62
+ for (const field of fieldsForNodeType(type)) {
63
+ if (field.kind === 'expression') {
64
+ const hit = checkCel(getFieldValue(node, field), roots);
65
+ if (hit)
66
+ out.push({ target: { kind: 'node', nodeId }, level: hit.level, message: hit.message });
67
+ }
68
+ else if (field.kind === 'objectList' && field.columns) {
69
+ const exprCols = field.columns.filter((c) => c.kind === 'expression');
70
+ if (exprCols.length === 0)
71
+ continue;
72
+ for (const row of asArray(getFieldValue(node, field))) {
73
+ const r = asRecord(row);
74
+ const rowLabel = str(r.label);
75
+ for (const col of exprCols) {
76
+ const hit = checkCel(r[col.key], roots);
77
+ if (hit) {
78
+ const prefix = rowLabel || col.label;
79
+ out.push({ target: { kind: 'node', nodeId }, level: hit.level, message: prefix ? `${prefix}: ${hit.message}` : hit.message });
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ for (const edge of edges) {
87
+ const source = str(edge.source);
88
+ const target = str(edge.target);
89
+ if (!source || !target || edge.isDefault === true)
90
+ continue;
91
+ const roots = source === startId ? null : scopeRoots(resolveFlowScope(draft, source).refs);
92
+ const hit = checkCel(edge.condition, roots);
93
+ if (hit)
94
+ out.push({ target: { kind: 'edge', source, target }, level: hit.level, message: hit.message });
95
+ }
96
+ return out;
97
+ }
@@ -0,0 +1,84 @@
1
+ import type { DiagnosticLevel } from './simulator/flow-sim-types';
2
+ import { type FlowEdge, type FlowNode } from './flow-canvas-layout';
3
+ /** What a problem points at on the canvas — drives badge placement + reveal. */
4
+ export type FlowProblemTarget = {
5
+ kind: 'node';
6
+ nodeId: string;
7
+ } | {
8
+ kind: 'edge';
9
+ edgeKey: string;
10
+ source: string;
11
+ target: string;
12
+ } | {
13
+ kind: 'flow';
14
+ };
15
+ /** Origin of a problem — labels the panel row and lets the UI group counts. */
16
+ export type FlowProblemSource = 'structural' | 'server' | 'expression';
17
+ /** One actionable issue, resolved onto a concrete canvas element. */
18
+ export interface FlowProblem {
19
+ /** Stable-enough key for React lists. */
20
+ id: string;
21
+ level: DiagnosticLevel;
22
+ message: string;
23
+ target: FlowProblemTarget;
24
+ source: FlowProblemSource;
25
+ /**
26
+ * Extra elements to flag with the red error ring/stroke beyond `target` —
27
+ * e.g. every hop of a cycle. The badge + click-reveal still use `target`.
28
+ */
29
+ highlight?: {
30
+ nodeIds: string[];
31
+ edges: Array<{
32
+ source: string;
33
+ target: string;
34
+ }>;
35
+ };
36
+ }
37
+ /** A server diagnostic entry (subset of the layered record's `_diagnostics`). */
38
+ export interface ServerDiagnostic {
39
+ /** Dotted (or array) JSON path, e.g. `nodes.2.config.objectName`. */
40
+ path?: string | Array<string | number>;
41
+ message: string;
42
+ /** Defaults to `'error'`. */
43
+ severity?: DiagnosticLevel;
44
+ }
45
+ /** Stable `source->target` key matching an edge problem to a rendered edge. */
46
+ export declare function edgeProblemKey(source: string, target: string): string;
47
+ export interface BuildFlowProblemsArgs {
48
+ nodes: FlowNode[];
49
+ edges: FlowEdge[];
50
+ /** Server `_diagnostics`, flattened to a severity-tagged, path-keyed list. */
51
+ serverDiagnostics?: ServerDiagnostic[];
52
+ /** Declared flow variables — needed to resolve scope for the expression check. */
53
+ variables?: unknown[];
54
+ }
55
+ /**
56
+ * Build the unified problem list from structural validation + server
57
+ * diagnostics. Errors are listed before warnings; within a level each source
58
+ * keeps its own emit order (structural before server).
59
+ */
60
+ export declare function buildFlowProblems({ nodes, edges, serverDiagnostics, variables }: BuildFlowProblemsArgs): FlowProblem[];
61
+ /** A folded badge for one canvas element (errors dominate warnings). */
62
+ export interface ProblemBadge {
63
+ level: DiagnosticLevel;
64
+ /** Tooltip text — each problem message on its own line. */
65
+ title: string;
66
+ count: number;
67
+ }
68
+ export interface ProblemIndex {
69
+ byNode: Map<string, ProblemBadge>;
70
+ byEdge: Map<string, ProblemBadge>;
71
+ }
72
+ /** Group problems into per-node / per-edge badges for the canvas overlay. */
73
+ export declare function indexProblemBadges(problems: FlowProblem[]): ProblemIndex;
74
+ /**
75
+ * Error elements to paint with the red ring/stroke, derived from the unified
76
+ * problem list. ERRORS ONLY — warnings get an amber badge but no ring. Includes
77
+ * each error's `highlight` set, so a cycle paints its whole loop (every hop node
78
+ * + edge) red while its badge still sits on the closing edge. Lets the preview
79
+ * derive the red sets from `problems` instead of a second validateFlowDraft pass.
80
+ */
81
+ export declare function deriveInvalidElements(problems: FlowProblem[]): {
82
+ invalidNodeIds: string[];
83
+ invalidEdges: Set<string>;
84
+ };
@@ -0,0 +1,209 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+ /**
3
+ * flow-problems — unify the two flow-validation sources into one flat,
4
+ * per-element issue list that the canvas badges and the Problems panel both
5
+ * render.
6
+ *
7
+ * 1. `validateFlowDraft` (client, structural): no resolvable entry,
8
+ * unreachable nodes, a decision with no default branch, duplicate node
9
+ * ids, dangling edges, un-declared cycles.
10
+ * 2. The server `_diagnostics` already attached to the layered record
11
+ * (schema validation), each keyed by a dotted JSON path.
12
+ *
13
+ * "Surfacing, not detection": detection already exists. This module only maps
14
+ * each detected issue onto a concrete canvas element — a node id or a stable
15
+ * edge key — so a badge can sit on the offending element and a Problems-panel
16
+ * row can select + reveal it. Flow-level issues (no specific element) are kept
17
+ * too: listed in the panel, but without a badge.
18
+ */
19
+ import { validateFlowDraft } from './simulator/flow-sim-validate';
20
+ import { edgeKey } from './flow-canvas-layout';
21
+ import { flowExpressionProblems } from './flow-expr-problems';
22
+ /** Stable `source->target` key matching an edge problem to a rendered edge. */
23
+ export function edgeProblemKey(source, target) {
24
+ return `${source}->${target}`;
25
+ }
26
+ /** Resolve an edge's selection key (`edgeKey`) from its endpoints. */
27
+ function resolveEdgeKey(edges, source, target) {
28
+ const idx = edges.findIndex((e) => e.source === source && e.target === target);
29
+ return idx >= 0 ? edgeKey(edges[idx], idx) : `${source}->${target}#-1`;
30
+ }
31
+ /** Normalize a dotted/array JSON path to segments (numbers stay numeric). */
32
+ function pathSegments(path) {
33
+ if (Array.isArray(path))
34
+ return path;
35
+ if (typeof path !== 'string' || !path)
36
+ return [];
37
+ return path.split('.').map((seg) => {
38
+ const n = Number(seg);
39
+ return Number.isInteger(n) && String(n) === seg ? n : seg;
40
+ });
41
+ }
42
+ /**
43
+ * Map a structural diagnostic's optional anchors (`edge`, `cycle`, `nodeId`)
44
+ * onto a badge target. A cycle points its badge at the *closing* hop — the edge
45
+ * the author marks as a back-edge to resolve it — but flags EVERY hop (nodes +
46
+ * edges) for the red error highlight so the whole loop reads as the problem.
47
+ */
48
+ function structuralMapping(diag, edges) {
49
+ if (diag.edge) {
50
+ const { source, target } = diag.edge;
51
+ return { target: { kind: 'edge', source, target, edgeKey: resolveEdgeKey(edges, source, target) } };
52
+ }
53
+ if (diag.cycle && diag.cycle.length >= 2) {
54
+ const c = diag.cycle;
55
+ const source = c[c.length - 2];
56
+ const target = c[c.length - 1];
57
+ const nodeIds = [];
58
+ const hops = [];
59
+ for (let i = 0; i < c.length - 1; i++) {
60
+ nodeIds.push(c[i]);
61
+ hops.push({ source: c[i], target: c[i + 1] });
62
+ }
63
+ return {
64
+ target: { kind: 'edge', source, target, edgeKey: resolveEdgeKey(edges, source, target) },
65
+ highlight: { nodeIds, edges: hops },
66
+ };
67
+ }
68
+ if (diag.nodeId)
69
+ return { target: { kind: 'node', nodeId: diag.nodeId } };
70
+ return { target: { kind: 'flow' } };
71
+ }
72
+ /** Map a server diagnostic's JSON path onto a node/edge/flow target. */
73
+ function serverTarget(path, nodes, edges) {
74
+ const segs = pathSegments(path);
75
+ if (segs.length >= 2 && typeof segs[1] === 'number') {
76
+ const idx = segs[1];
77
+ if (segs[0] === 'nodes' && nodes[idx]?.id)
78
+ return { kind: 'node', nodeId: nodes[idx].id };
79
+ if (segs[0] === 'edges' && edges[idx]) {
80
+ const e = edges[idx];
81
+ return { kind: 'edge', source: e.source, target: e.target, edgeKey: edgeKey(e, idx) };
82
+ }
83
+ }
84
+ return { kind: 'flow' };
85
+ }
86
+ /** Short stable token for a target, used in a problem's React key. */
87
+ function targetKey(t) {
88
+ if (t.kind === 'node')
89
+ return `n:${t.nodeId}`;
90
+ if (t.kind === 'edge')
91
+ return `e:${t.source}->${t.target}`;
92
+ return 'flow';
93
+ }
94
+ /**
95
+ * Build the unified problem list from structural validation + server
96
+ * diagnostics. Errors are listed before warnings; within a level each source
97
+ * keeps its own emit order (structural before server).
98
+ */
99
+ export function buildFlowProblems({ nodes, edges, serverDiagnostics, variables }) {
100
+ const problems = [];
101
+ const v = validateFlowDraft(nodes, edges);
102
+ const pushStructural = (level, list) => {
103
+ list.forEach((diag, i) => {
104
+ const { target, highlight } = structuralMapping(diag, edges);
105
+ problems.push({
106
+ id: `structural:${level}:${i}:${targetKey(target)}`,
107
+ level,
108
+ message: diag.message,
109
+ target,
110
+ source: 'structural',
111
+ ...(highlight ? { highlight } : {}),
112
+ });
113
+ });
114
+ };
115
+ pushStructural('error', v.errors);
116
+ pushStructural('warning', v.warnings);
117
+ (serverDiagnostics ?? []).forEach((diag, i) => {
118
+ const level = diag.severity === 'warning' ? 'warning' : 'error';
119
+ const target = serverTarget(diag.path, nodes, edges);
120
+ problems.push({
121
+ id: `server:${i}:${targetKey(target)}`,
122
+ level,
123
+ message: diag.message,
124
+ target,
125
+ source: 'server',
126
+ });
127
+ });
128
+ // Client-side EXPRESSION issues (ADR-0032 braces + scope-aware unknown refs),
129
+ // resolved onto node / edge targets — see flow-expr-problems.
130
+ flowExpressionProblems({ nodes, edges, variables: variables ?? [] }).forEach((ep, i) => {
131
+ const target = ep.target.kind === 'edge'
132
+ ? {
133
+ kind: 'edge',
134
+ source: ep.target.source,
135
+ target: ep.target.target,
136
+ edgeKey: resolveEdgeKey(edges, ep.target.source, ep.target.target),
137
+ }
138
+ : { kind: 'node', nodeId: ep.target.nodeId };
139
+ problems.push({
140
+ id: `expression:${ep.level}:${i}:${targetKey(target)}`,
141
+ level: ep.level,
142
+ message: ep.message,
143
+ target,
144
+ source: 'expression',
145
+ });
146
+ });
147
+ // Errors first so the panel + counts lead with blockers (stable within level).
148
+ return problems
149
+ .map((p, i) => [p, i])
150
+ .sort((a, b) => {
151
+ if (a[0].level !== b[0].level)
152
+ return a[0].level === 'error' ? -1 : 1;
153
+ return a[1] - b[1];
154
+ })
155
+ .map(([p]) => p);
156
+ }
157
+ function foldBadge(list) {
158
+ const level = list.some((p) => p.level === 'error') ? 'error' : 'warning';
159
+ return { level, title: list.map((p) => p.message).join('\n'), count: list.length };
160
+ }
161
+ /** Group problems into per-node / per-edge badges for the canvas overlay. */
162
+ export function indexProblemBadges(problems) {
163
+ const nodeLists = new Map();
164
+ const edgeLists = new Map();
165
+ for (const p of problems) {
166
+ if (p.target.kind === 'node') {
167
+ const l = nodeLists.get(p.target.nodeId) ?? [];
168
+ l.push(p);
169
+ nodeLists.set(p.target.nodeId, l);
170
+ }
171
+ else if (p.target.kind === 'edge') {
172
+ const k = edgeProblemKey(p.target.source, p.target.target);
173
+ const l = edgeLists.get(k) ?? [];
174
+ l.push(p);
175
+ edgeLists.set(k, l);
176
+ }
177
+ }
178
+ const byNode = new Map();
179
+ for (const [id, list] of nodeLists)
180
+ byNode.set(id, foldBadge(list));
181
+ const byEdge = new Map();
182
+ for (const [k, list] of edgeLists)
183
+ byEdge.set(k, foldBadge(list));
184
+ return { byNode, byEdge };
185
+ }
186
+ /**
187
+ * Error elements to paint with the red ring/stroke, derived from the unified
188
+ * problem list. ERRORS ONLY — warnings get an amber badge but no ring. Includes
189
+ * each error's `highlight` set, so a cycle paints its whole loop (every hop node
190
+ * + edge) red while its badge still sits on the closing edge. Lets the preview
191
+ * derive the red sets from `problems` instead of a second validateFlowDraft pass.
192
+ */
193
+ export function deriveInvalidElements(problems) {
194
+ const nodeSet = new Set();
195
+ const edgeSet = new Set();
196
+ for (const p of problems) {
197
+ if (p.level !== 'error')
198
+ continue;
199
+ if (p.target.kind === 'node')
200
+ nodeSet.add(p.target.nodeId);
201
+ else if (p.target.kind === 'edge')
202
+ edgeSet.add(edgeProblemKey(p.target.source, p.target.target));
203
+ for (const id of p.highlight?.nodeIds ?? [])
204
+ nodeSet.add(id);
205
+ for (const e of p.highlight?.edges ?? [])
206
+ edgeSet.add(edgeProblemKey(e.source, e.target));
207
+ }
208
+ return { invalidNodeIds: [...nodeSet], invalidEdges: edgeSet };
209
+ }
@@ -80,7 +80,16 @@ export interface SimState {
80
80
  export type DiagnosticLevel = 'error' | 'warning';
81
81
  export interface Diagnostic {
82
82
  level: DiagnosticLevel;
83
+ /** The node this diagnostic points at (for an inline badge + click-to-reveal). */
83
84
  nodeId?: string;
85
+ /**
86
+ * The edge this diagnostic points at (e.g. a dangling endpoint). Carries the
87
+ * endpoints so the designer can key the inline badge by `source->target`.
88
+ */
89
+ edge?: {
90
+ source: string;
91
+ target: string;
92
+ };
84
93
  message: string;
85
94
  /**
86
95
  * For a cycle error: the node path that closes the loop (e.g. `['a','b','a']`),
@@ -101,10 +101,12 @@ export function validateFlowDraft(nodes, edges) {
101
101
  idSet.add(id);
102
102
  }
103
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).
104
106
  if (!idSet.has(e.source))
105
- 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.` });
106
108
  if (!idSet.has(e.target))
107
- 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.` });
108
110
  }
109
111
  // Entry resolution: prefer an explicit `start` node, else a node with no
110
112
  // incoming edge. Zero or many → the author must fix it before running.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/app-shell",
3
- "version": "7.1.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.1.0",
37
- "@object-ui/collaboration": "7.1.0",
38
- "@object-ui/components": "7.1.0",
39
- "@object-ui/core": "7.1.0",
40
- "@object-ui/data-objectstack": "7.1.0",
41
- "@object-ui/fields": "7.1.0",
42
- "@object-ui/i18n": "7.1.0",
43
- "@object-ui/layout": "7.1.0",
44
- "@object-ui/permissions": "7.1.0",
45
- "@object-ui/plugin-editor": "7.1.0",
46
- "@object-ui/providers": "7.1.0",
47
- "@object-ui/react": "7.1.0",
48
- "@object-ui/types": "7.1.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.1.0",
55
- "@object-ui/plugin-charts": "^7.1.0",
56
- "@object-ui/plugin-chatbot": "^7.1.0",
57
- "@object-ui/plugin-dashboard": "^7.1.0",
58
- "@object-ui/plugin-designer": "^7.1.0",
59
- "@object-ui/plugin-detail": "^7.1.0",
60
- "@object-ui/plugin-form": "^7.1.0",
61
- "@object-ui/plugin-grid": "^7.1.0",
62
- "@object-ui/plugin-kanban": "^7.1.0",
63
- "@object-ui/plugin-list": "^7.1.0",
64
- "@object-ui/plugin-report": "^7.1.0",
65
- "@object-ui/plugin-view": "^7.1.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.1.0",
79
- "@object-ui/plugin-charts": "7.1.0",
80
- "@object-ui/plugin-chatbot": "7.1.0",
81
- "@object-ui/plugin-dashboard": "7.1.0",
82
- "@object-ui/plugin-designer": "7.1.0",
83
- "@object-ui/plugin-detail": "7.1.0",
84
- "@object-ui/plugin-form": "7.1.0",
85
- "@object-ui/plugin-grid": "7.1.0",
86
- "@object-ui/plugin-kanban": "7.1.0",
87
- "@object-ui/plugin-list": "7.1.0",
88
- "@object-ui/plugin-report": "7.1.0",
89
- "@object-ui/plugin-view": "7.1.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",