Skip to content

fix(app-shell): a flow node rename carries the expressions that read its outputs, and Problems names a reference to a missing node (objectui#11838) - #11850

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11838-rename-expression-refs
Oct 8, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11838-rename-expression-refs

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11838

Clause-②: no

What was wrong

Renaming a node in the flow designer's ID field carried its edges and a boundary event's host (objectui#11827), but not the expressions that read the node's outputs. The engine writes every node output under the node's id, so a later node reads it by that id: x.decision == 'approve' on a decision branch and its edge guard, or {x.field} in a record field value. After a rename from x to renamed, all of these still named x. The saved draft read a node that no longer existed, and the Problems panel showed only the scope warning.

Measured on this branch through the real FlowNodeInspector: with FlowNodeInspector.tsx reverted to the base commit, the card's pin receives "expression": "x.decision == 'approve'" where "renamed.decision == 'approve'" is expected. The reverse checks are below.

The design

A rename rewrites every expression reference whose root is the old id, through the expression parsers. This is expressionRefsAfterNodeRename in the new module previews/flow-node-refs.ts. FlowNodeInspector commits it in the same patch as edgesAfterNodeRename and boundaryRefsAfterNodeRename from objectui#11827. One builder (renamePatch) feeds both the field's refusal and the commit, so the field refuses exactly what the commit could not carry.

  • CEL is parsed by parseCelToAst from @objectstack/formula. A reference is an id node of that AST that no comprehension macro (all / exists / exists_one / map / filter) or cel.bind binds. Only the root identifier's own characters are replaced, at the positions the AST reports, so the rest of the author's text keeps its exact bytes.
    • Some sources reach the parser rewritten, such as a cond ? value : null with its dyn wrap. For these, the AST's identifiers are placed back on the author's text by order, and only when the names match one for one; otherwise the source counts as unparsed.
  • Template holes use the engine's own hole pattern from interpolateString. That pattern takes the inner {x.field} of a {{x.field}}, so both spellings read the same reference. A hole's content is read with the engine's dotted-path grammar from resolveToken; when the content is arithmetic, it is read as CEL. The | formatter of a double-brace hole is not part of the reference. No spelling is inserted (objectui#11824 is not touched).
  • Untouched: an identifier that only contains the old id (xy, ax), a member name (y.x), a string literal ("x.decision"), a macro-bound x, and a script body. A script body is a code slot and no parser here reads it.
  • Every rewritten source is parsed again. It must read the same references, with only the root renamed.

What is refused. In each case the field shows the message and the stored id again, and nothing is written. The refused expressions are listed in a locale-free form: the node or edge, then the path, then the expression.

  • engine.inspector.flowNode.idRefsUnparsed: an expression, or a template hole, reads the old id as a root but does not parse. No parser can say where the reference is, so the rename refuses and names it.
  • engine.inspector.flowNode.idRefsAmbiguous: the old or new id is also a declared variable or a root that every run binds (record, previous, vars, or a name starting with $). The engine nests both the variable and the node's outputs under the same name, so the reference could mean either one. This also covers a new id that a comprehension macro in that expression already binds, which would capture the reference.

A removal is refused while a boundary event's host or an expression root still names the node (revision 1, the review's ruling B). A removal carries the node's edges (edgesAfterNodeRemoval, objectui#11772) but has no new id for the other two kinds of position to follow, so it would leave them naming a node that does not exist.

  • nodeRemovalRefusal in flow-problems.ts, beside edgesAfterNodeRemoval, is the one rule both removal gestures apply: FlowNodeInspector's remove ("Remove node") and FlowCanvas's deleteNode (the Delete and Backspace keys). It reads nodeIdPositions on the draft before the removal and returns each boundary event whose host is the node and each expression with a reference rooted at it, or null.
  • Not counted, because the removal itself takes them away: an expression held by the removed node or inside its regions, one on an edge the removal drops (an approval's own guarded out-edge reading its decision), and a boundary event inside the removed node. The edge in that a splice reconnects keeps its guard, so a reference there is counted. A duplicate id (another node still carries it) refuses nothing, the rule the edge half already applies. A source that does not parse names no position, so it does not refuse; the expression checks already report it as malformed.
  • engine.inspector.flowNode.removeRefused (EN and ZH) names each site in the rename refusal's locale-free form, for example be › boundaryConfig.attachedToNodeId: `x`; d › config.conditions[0].expression: `x.decision == 'approve'`. Nothing is written, and the node stays selected.
  • Where each gesture shows it. The inspector shows it under the Remove node button, with role="alert". The canvas Delete key had no refusal surface. Measured: FlowCanvas has one alert surface, its inline banner stack at the top left where structural errors already show (role="alert" rows); FlowPreview has none; toasts are used by page-level metadata-admin components (ResourceEditPage, PackagesPage and others), never by the canvas, and would be transient. The smallest honest surface is therefore a role="alert" row at the top of that existing stack: no new dependency, and it stays while the node is selected. A silent no-op was not an option.
  • Both messages are derived from the draft while the node stays selected, so they name what still blocks the removal and go away once nothing does. Both reset when the selection moves, so re-selecting the node does not bring back a refusal nobody asked for again.
  • Cascading (also removing the attached boundary event, or rewriting the expressions) is not done: it would delete or rewrite what the author did not select.

The list of node-id positions is nodeIdPositions in flow-node-refs.ts. It holds edge source and target, a boundary event's boundaryConfig.attachedToNodeId, and an expression reference's root, at every depth, including the regions of loop, parallel and try/catch containers. An expression root counts as a position in two cases:

  • the root is a node id;
  • the root is read as a node output (x.decision), and no node, declared variable or runtime root answers to it. The start node's own expressions are excluded from this case, because its entry condition reads the trigger record's fields bare.

The list lives in the new module, not in flow-problems.ts: the Problems rows in flow-sim-validate.ts read the same list, and flow-problems.ts already imports that file, so putting the list there would create an import cycle. The new exports are module-internal: the package's exports map names only . and ./styles.css, and src/index.ts re-exports none of these modules.

"An error, not a warning" is met in the problems check. missingNodeRefDiagnostics in flow-sim-validate.ts adds two rows beside edgeSourceMissing:

  • engine.flowValidate.exprRefNodeMissing: an expression root that names a missing node, shown on the top-level node or edge that holds it;
  • engine.flowValidate.boundaryHostMissing.

buildFlowProblems lists both as errors. The scope warning in flow-ref-check.ts is not changed.

Placement, stated as a deviation from the letter of the ruling. The rows sit beside validateFlowDraft in the same file, not inside it. validateFlowDraft is also the debugger's Run preflight, which refuses what registerFlow refuses. The platform registers a flow that has either of these, and a missing root faults only when its node runs, so the debugger's Run is unchanged.

The boundary-host row is a bounded in-place fix in a file already on the claim: the same defect class (a position names a missing node and no row reports it), a mechanical row shaped like edgeSourceMissing, the same gate family, and the enumeration pin needs it, because without it the pin has nothing to find for the boundary-host position.

The enumeration pin: FlowPreview.nodeIdPositions-11838.test.tsx

The pin runs a fixture flow through the real canvas and inspector. The fixture holds every kind of position, each naming the approval x. Each designer write is either applied (the draft changed) or refused (the draft is the very same object, and the surface that refused shows the refusal naming each site). After every write, no position names a missing node (missingNodePositions is empty), and the Problems check names none of the fixture's positions as missing. A new kind of position fails the fixture control (nodeIdPositions of the fixture must hold every kind in NODE_ID_POSITION_KINDS) until the fixture holds one, and every write then runs against it.

Designer write paths (H4):

  • Add:
    • addNode: a node card's "Add connected node", and the toolbar's Add node through addAfter;
    • insertOnEdge: "Insert node here";
    • addReviseLoop: "Add revision loop".
  • Remove: FlowNodeInspector's "Remove node", and FlowCanvas's Delete and Backspace keys (the pin drives Delete). Both use edgesAfterNodeRemoval.
  • Rename: the inspector's ID field.
  • Duplicate: none exists. A search of FlowCanvas, FlowPreview, flow-canvas-parts, FlowInspector and FlowNodeInspector for duplicate, clone, copy and paste finds no write that copies a node.

Every add, every rename, and every removal of a node that no position names is applied and leaves nothing behind. Removing the node that every position names is refused by both gestures, naming the boundary event and the three expressions that read x; it flipped from reported in revision 1. A further test pins that neither refusal is left standing once another node is selected.

Expression positions read (H2), derived from the descriptors and the spec's ledger

Positions are read in this order (fieldsForNodeType field kinds and FLOW_NODE_EXPRESSION_PATHS, both read-only):

  1. Code (not read): a text field the descriptor marks refMode: 'expression': the script body.
  2. CEL:
    • a descriptor field with kind: 'expression' that is not a template: the start node's condition and criteria, the decision node's condition, and legacy_action's recordId;
    • an expression column of an objectList field: a decision branch's expression, a screen field's visibleWhen;
    • a predicate slot in FLOW_NODE_EXPRESSION_PATHS (the same two columns).
  3. Template:
    • a descriptor text or textarea field (the start node's schedule.expression is one, so a cron string is never read as CEL);
    • an expression field marked refMode: 'template': the loop and map collection;
    • a flow-template slot in the ledger (the same two).
  4. CEL: any other condition or expression key, following the platform linter's CEL_KEYS rule, including keys no descriptor describes.
  5. Template: every other string in config and in connectorConfig.input. The engine interpolates {token} holes into node config strings wholesale.

An edge's condition is CEL. A { dialect, source } envelope anywhere is read by its own dialect, for example the value slots of assignments, create_record fields and update_record fields.

Positions whose kind cannot be derived, or that this rename does not carry (listed for the record, not changed here):

  • An approver of type expression stores its CEL in the approvers[].value column, of kind reference. Its roots are closed (current, trigger, vars), so it names a node as a member, vars.x.key, not as a root.
  • In any expression, a node output written as vars.x.key has a root that is not the node id. The ruling covers roots only, so the rename does not carry these.
  • Strings in waitEventConfig, and strings in boundaryConfig other than the host, are not walked.
  • A plugin node type with no descriptor has only its condition and expression keys read as CEL, and its other config strings as templates.
  • The decision branch target column is virtual. It is stored on the edges.

Tests and gates (all on c7b3699 unless marked)

  • New pins, all passing:
    • flow-node-refs.renameExprRefs-11838.test.ts: 20 tests;
    • FlowNodeInspector.renameExprRefs-11838.test.tsx: 5 tests;
    • FlowPreview.nodeIdPositions-11838.test.tsx: 10 tests (the fixture control, the 8 writes, and the refusal-not-left-standing test);
    • flow-problems.removalRefusal-11838.test.ts: 8 tests (the sites named, the message in EN and ZH, an edge-only node, a dropped guarded out-edge, a spliced edge in, a container's own regions, a duplicate id, an unparsed source);
    • FlowNodeInspector.removeRefusal-11838.test.tsx: 4 tests (the refusal in EN and ZH with nothing written and the node still selected, the refusal clearing once nothing names the node and the removal then going through, and an edge-only node removed at once).
  • One existing control was updated: FlowPreview.connectorOutputProblems-11085.test.tsx ("a root no node writes is still reported"). In that autolaunched flow, ghost.ok reads a node the flow does not have, so the panel now lists the new error beside the scope warning, and the test asserts both rows.
  • Family union, pnpm exec vitest run --maxWorkers=2 over 78 files: every flow-problems*, FlowNodeInspector*, FlowCanvas*, FlowPreview*, simulator and flow-expr-problems test, every test that imports a touched module (including FlowCanvas and FlowInspector importers, among them the Studio design-surface suites), and the metadata-admin i18n string tests. Result: Test Files 78 passed (78), Tests 1209 passed (1209), lock verdict command-exit 0.
  • pnpm --filter @object-ui/app-shell type-check: command-exit 0. Its chained tsconfig.test.json includes src/**/*.test.ts and src/**/*.test.tsx, so it compiles all six touched test files.
  • pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (29 of 47 workspace projects): command-exit 0.
  • check:* gates, each exit 0:
    • named in the dispatch: control-bytes, new-line-citations, changeset-claims, pending-changeset-literals, i18n-designer-parity;
    • derived from the diff: i18n-keys, i18n-drift, i18n-dead-keys, spec-symbols, phantom-deps, unused-deps, self-import, esm-specifiers, test-path-roots, vi-mock-specifiers, vi-mock-inherit, vi-mock-override-shape, unreferenced-sources, handler-key-reads, metadata-write-doors, designer-field-key-parity, comment-mask-corpus;
    • also scripts/check-changeset-presence.mjs and scripts/check-changeset-no-major.mjs.
  • NOT MEASURED: check:eager-closure, reason: it reads a built console bundle (apps/console/dist/eager-closure.json), which was not built locally; it printed PREREQUISITE NOT MET. Left to CI.
  • Narrowed lint: eslint --no-inline-config --format json over the 12 changed .ts and .tsx files. The json lists 12 files, 0 errors and 7 warnings. All 7 are on FlowNodeInspector and FlowCanvas lines outside this diff's hunks, and the same rules fire on the earlier copies of those files (four on FlowNodeInspector at the base commit, three exhaustive-deps on FlowCanvas at 252b045), at lines shifted by the inserted code. Type-aware linting is not enabled (the **/*.{ts,tsx} block of eslint.config.js sets no parserOptions.project or projectService), so this diff cannot change the result for an untouched file. The custom object-ui/* rules were not audited for cross-file reads. The repo-wide pnpm lint is left to CI.

Reverse checks. Each one reverts committed files, runs the pins under the lock, and restores with git checkout HEAD inside a trap. The restore is proven by an empty git diff HEAD and a blob hash equal to HEAD's.

  1. On 252b045, FlowNodeInspector.tsx reverted to the base: 5 failed and 11 passed.
    • The card's pin receives "expression": "x.decision == 'approve'" where "renamed.decision == 'approve'" is expected.
    • Both refusal tests and the ambiguity test find a rename that landed (expected [ Array(1) ] to deeply equal []).
    • The pin's rename leg fails with expression-root: none: expected true to be false.
    • The controls stay green.
  2. On 252b045, flow-problems.ts reverted to the base: 4 failed and 27 passed. "it reaches the Problems panel as errors, not only the scope warning" fails with expected [] to deeply equal [ 'error', 'error' ], and both removal legs and the panel test find no error row. flow-problems.ts changed after this run only by additions (git diff --stat 252b045 c7b3699 on it: 90 insertions, 0 deletions), so the Problems wiring this check proved is unchanged.
  3. On c7b3699, FlowNodeInspector.tsx reverted to the base again: 7 failed and 8 passed. The card's pin again receives "expression": "x.decision == 'approve'", and the inspector's refused removal leg now fails too; the canvas's refused leg stays green, because FlowCanvas.tsx was not reverted.
  4. The removal refusal, on c7b3699: FlowNodeInspector.tsx and FlowCanvas.tsx both reverted to 252b045 (marker nodeRemovalRefusal 4 and 3 before, 0 and 0 after; both blobs equal to 252b045's), with the helper left in place: 6 failed and 16 passed. Both refused legs of the enumeration pin fail with a refused write changes nothing, the inspector refusal tests find a removal that landed (expected [ Array(1) ] to deeply equal [], expected null not to be null), and the not-left-standing test finds the Problems banner rows of an applied removal instead of the refusal. All 8 helper tests and every control stay green.

False positives measured on real flows. Over the 35 flows of the objectstack example apps (showcase, CRM and todo, objectstack bafb58bb), which hold 435 expression sites, the parser reads 161 references (111 of them member reads; the most frequent roots are record and previous). None of them is an expression-root position, and the new rows fire 0 times. Positive control: injecting a decision that reads loop_tasks.decision into showcase_batch_reminders and removing loop_tasks makes the list report it.

Acceptance notes

  • The Problems check does not report a region's own edges that name a missing node, because validateFlowDraft reads only top-level edges. The designer has no nested structural editing, so only hand-written JSON can produce one. Noted, not filed.
  • One false-positive class is left for the new error row. On a record-triggered flow the engine binds the trigger record's fields bare for the whole run, so a bare field.key that reads an object-valued trigger field on a non-start node would be reported as a missing node. The designer's picker writes record.field there, the start node is exempt, and the 35 example flows report none.
  • RUNTIME_ROOTS in flow-node-refs.ts mirrors the module-private RUNTIME_GLOBALS of flow-ref-check.ts, which this claim may import but not edit. Exporting that set from flow-ref-check.ts would remove the copy; the natural carrier is objectui#11789, which owns that file.

Fence

Every changed file is on the claim's surface:

  • flow-node-refs.ts, the new module;
  • the rename commit path in FlowNodeInspector.tsx, and (revision 1) its remove path and the refusal under the Remove node button;
  • (revision 1) FlowCanvas.tsx's deleteNode and the refusal row at the top of the canvas's inline alert stack;
  • the new rows in flow-sim-validate.ts;
  • the buildFlowProblems wiring in flow-problems.ts, and (revision 1) the removal helper nodeRemovalRefusal with its message builder describeNodeRemovalRefusal;
  • the new engine.flowValidate.* and engine.inspector.flowNode.* rows in i18n.ts, en and zh (revision 1 adds engine.inspector.flowNode.removeRefused);
  • the tests beside these, including the updated FlowPreview control;
  • the changeset .changeset/11838-rename-expression-refs.md (@object-ui/app-shell patch). Revision 1 also edits its text, outside the revision's listed surface, because its example "x.decision after x is removed" became untrue: it now states the removal rule.

No package export, prop, @object-ui/types member or packages/i18n key is added. flow-scope.ts (nodeOutputRefs) and flow-node-config.ts (fieldsForNodeType) are imported read-only; flow-ref-check.ts is neither imported nor edited. No file of objectui#11783 or objectui#11789 is edited.

Written by the os-dev agent of session https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z (revision 1 in the same session).


Generated by Claude Code

claude added 3 commits October 8, 2026 00:49
…its outputs, and Problems names a reference to a missing node (objectui#11838)

A rename now rewrites every expression reference whose root is the old id
(CEL member roots, template holes in both spellings), read through
parseCelToAst and the engine's template hole grammar, in the same patch as
the edges and boundary host. A reference that does not parse, or a root
that is also a variable name, refuses the rename and names it.

flow-node-refs.ts holds the one list of node-id positions (edge source and
target, boundary host, expression root); missingNodeRefDiagnostics in
flow-sim-validate.ts adds the Problems error rows for an expression root
and a boundary host naming a missing node. The enumeration pin runs every
designer write against the list.

Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z
Co-authored-by: Claude <noreply@anthropic.com>
…node error row (objectui#11838)

`ghost.ok` in an autolaunched flow reads a node the flow does not have, so
beside the scope warning the Problems panel lists the new
engine.flowValidate.exprRefNodeMissing error; the control asserts both.

Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z
Co-authored-by: Claude <noreply@anthropic.com>
…s alone precisely (objectui#11838)

"A loop variable" read like a flow loop node's iterator, which a rename
refuses as ambiguous rather than leaves alone; the untouched case is a
variable the expression declares itself, as in rows.exists(x, x.ok).

Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3531.9 KB 3551.8 KB
Main entry chunk (gzip) 158.5 KB 350 KB
Entry file index-DBQGf3ML.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.82KB 6.58KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 582.41KB 140.21KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 235.41KB 65.46KB
fields (index.js) 266.88KB 67.46KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.39KB 15.52KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 239.75KB 65.92KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.42KB 29.32KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

… or an expression still names the node (objectui#11838)

The removal carries a node's edges but has no new id for a boundary
event's host or an expression root to follow, so it left them naming a
node that no longer exists. nodeRemovalRefusal (flow-problems.ts) is the
one rule both removal gestures apply: the inspector's Remove node shows
the refusal under the button, and the canvas Delete key at the top of the
canvas's inline alert stack, each naming every site in the rename
refusal's form; nothing is written and the node stays selected.

The enumeration pin's removal of the node every position names flips
from reported to refused: after every designer write, no position names
a missing node.

Claude-Session: https://claude.ai/code/session_01DrKzdPdyLLBW3qpZ4vtk7z
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 336 chunks) 3532.5 KB 3551.8 KB
Main entry chunk (gzip) 158.5 KB 350 KB
Entry file index-yMTqBp2p.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.82KB 6.58KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 582.41KB 140.21KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 235.41KB 65.46KB
fields (index.js) 266.88KB 67.46KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 38.37KB 10.31KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.50KB 11.82KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.39KB 15.52KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 199.63KB 47.46KB
plugin-dashboard (index.js) 144.82KB 39.17KB
plugin-designer (index.js) 231.46KB 48.87KB
plugin-detail (index.js) 248.57KB 65.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.62KB 45.75KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 239.75KB 65.92KB
plugin-kanban (index.js) 52.17KB 16.37KB
plugin-list (index.js) 117.42KB 29.32KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.10KB 11.81KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 91.07KB 22.93KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 8, 2026 02:03
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 8, 2026 02:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 8, 2026
Merged via the queue into main with commit 9b530d8 Oct 8, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11838-rename-expression-refs branch October 8, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

flow designer: renaming a node leaves expression references to its outputs (x.decision, {{x.field}}) naming the old id; Problems shows only a warning

2 participants