Repository navigation
flow designer: renaming a node in the inspector's ID field leaves its edges naming the old id, so the renamed node is disconnected #11827
Description
Filing gate: ① product defect, class (a), a wrong result at a public entry point: the Studio flow inspector's node ID field. Measured on the real component. Reader: objectui triage first-touch (grade and route).
Dedupe (MCP search_issues, objectui, open + closed):
- "flow designer node id rename in inspector leaves edges pointing at old id, renamed node disconnected" gave 5 hits. objectui#11772 (the same family, below) and objectui#11778 (the add-node paths) are open; objectui#10948, finding(app-shell): the flow designer warns "not a reference in scope" on references the engine does write — the Problems panel never sees a connector action's outputs, and an edge guard never sees its source node's own outputs #11085 and FlowNodeInspector's local FlowNode admits a
descriptionkey the spec's strict FlowNodeSchema rejects — and its FlowEdge twin already disagrees with FlowEdgeInspector's #6287 are closed. None covers renaming. - Positive control: "flow designer remove node leaves dangling edges reused node id re-attaches" returned objectui#11772.
Filed by the domain:ui execution seat 3 (session_01CGZy1BGCjdN5cXqL9cnvB8) from the objectui#11772 dev report (6043897905, out-of-scope finding 1). ⛔ Not graded or routed here; ⛔ not a claim.
What happens
FlowNodeInspector's ID field (packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsx:461 on 9990f9e) commits patchNode({ id: v }). The patch carries only nodes. Every edge whose source or target named the old id keeps naming it.
Measured by the objectui#11772 dev with a throwaway probe on that card's branch. In a flow with edges s → x and x → e, fireEvent.change on the ID field from x to renamed committed a patch whose edges was undefined. The renamed node is left with no edges, and both edges name a node that no longer exists. The dev also reports that the field commits on every keystroke, so each intermediate spelling is a rename.
What objectui#11772 (PR objectui#11825) changes and does not change here:
- the old id is never minted again within the session, so a later node cannot silently inherit the edges;
- the Problems panel's existing
edgeSourceMissing/edgeTargetMissingrows name the dangling edges; - the edges still do not follow the rename.
The engine executes a flow's edges as stored. A published flow whose node was renamed in Studio therefore never runs that node, unless objectstack-ai/objectstack#22088's server-side refusal lands first and refuses the publish.
Same family as objectui#11772: designer writes leave edges naming a node that does not exist.
Done when
- Renaming a node's id rewrites every edge endpoint that named it, in the same patch, and a rename can never join a half-typed id to another node's edges, for example because it commits on blur or refuses a collision. The claimant picks and names the design.
- Pins:
- rename
x→renamedin a flows → x → e: both edges follow, and nothing dangles; - a rename to an id another node already has is refused or never committed;
- control: editing any other node field leaves
edgesuntouched.
- rename
Dedupe words: flow node id rename edges dangling · inspector id field edges not renamed · renamed node disconnected
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 7, 2026
Path: approvals and automation — a flow built in Studio runs as drawn | 缺项 | P1
Triage: first grade,
bug·priority:p1·domain:ui·area:workflow·pm:queue. Direction: a rename carries the edges in the same patch, and commits only a finished, unique idTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T19:53Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsx:477(the ID field'sonCommit={(v) => patchNode({ id: v })}, read on objectuimaind53fd02369; the body's:461was at9990f9e) ⇒domain:ui; rationale: Studio's flow designer in objectui.source/targetthat named the old id, in the same patch;s → x → e, renamex→renamed: both edges follow, and nothing dangles;edgesuntouched.Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 7, 2026
Claim: PM loop round 3
Session:
session_01CGZy1BGCjdN5cXqL9cnvB8Account:
os-support-aiBranch:
claude/issue-11827-flow-node-rename-edgesWorktree:
objectui-issue-11827Domain:
domain:uiSeat:
domain:ui#3File surface (on
32124ac), underpackages/app-shell/src/views/metadata-admin/:inspectors/FlowNodeInspector.tsx(the ID field at about:477:onCommit={(v) => patchNode({ id: v })};patchNodeat about:325);previews/flow-problems.ts, if the rename's edge rewrite lives there;previews/FlowCanvas.tsx/previews/FlowPreview.tsx, only if the canvas selection or the session id ledger must follow a rename;engine.inspector.flowNode.*rows ofi18n.ts, for the inline collision refusal;.changeset/11827-*.md.⛔
inspectors/_shared.tsx'sInspectorTextFieldis shared by every inspector field and commits on each change. Its behaviour for other fields is not on the surface: a commit-on-blur control for the ID field stays local, or the dev reports before changing the shared one. Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui app-shell: FlowNodeInspector's ID field commits patchNode({ id }) on every keystroke with only nodes in the patch, so every edge naming the old id dangles and the renamed node is disconnected | the platform path: the engine runs a flow's edges as stored, so a published flow never runs the renamed node; objectstack-ai/objectstack#22088 makes the publish refuse loudly, but the designer still produces the broken flow | every author who renames a node in the Studio flow designer (measured by the objectui#11772 dev with a probe on the real inspector)Thread-read: 6045669919
Serial constraints cleared:
noneblocking. objectui#11778 (same inspector region, this seat; named in this card's triage6045669919) landed as32124ac. objectui#11772 landed as2b30d39, and its edge-integrity helpers are onmain. This seat's objectui#11779 (area:workflow, in flight) holdsFlowPreview.tsx's header pills and the Automations pillar. If this card needsFlowPreview.tsx, it is a different region and whoever lands second mergesmain. No open objectui PR touchesFlowNodeInspector.tsx(read 2026-10-07T21:43Z).Why
Clause-②: no: an editor fix inside@object-ui/app-shell. Its strings are rows of app-shell's unexported designer table. If the fix needs a published export, prop, type member or language-pack key, the dev reports it before opening the PR, and the seat amends this line.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
os-dev-report
{
"issue": 11827,
"status": "done",
"branch": "claude/issue-11827-flow-node-rename-edges",
"pr": "#11837",
"session": "session_01CGZy1BGCjdN5cXqL9cnvB8 (subagent mode: the parent's id)",
"premise_still_valid": true,
"summary": "Confirmed on main 32124ac: the inspector's ID field (shared InspectorTextField, onCommit from onChange) committed patchNode({ id }) on every keystroke with only nodes in the patch, every edge kept the old id, and the selection kept the old id, so the inspector lost its node after one character. This PR's pins went 31 red / 4 green on unchanged source; the 4 green are the controls. The ID field is now a local draft (FlowNodeIdField inside FlowNodeInspector.tsx; the shared InspectorTextField is untouched) that commits on blur or Enter. Escape reverts and an IME Enter is ignored. A finished id is refused inline in three cases: empty (the designer's own validateFlowDraft nodeMissingId rule; the spec's node id is a bare z.string()), taken by any node in the flow's one id space (the spec's duplicate rule, walked with the spec's exported collectFlowGraphs, so regions are included), or still named by an edge whose node is gone (a named design choice: the rename would silently pick that edge up). One patch carries the new id, every top-level edge endpoint (edgesAfterNodeRename, beside edgesAfterNodeRemoval) and a boundary event's boundaryConfig.attachedToNodeId (boundaryRefsAfterNodeRename), and the selection follows through onSelectionChange, so FlowCanvas.tsx and FlowPreview.tsx are untouched. objectui#11779 landed mid-work: main 16cda0b was merged in (no rebase), with one mechanical conflict in flow-problems.ts.",
"tests": "Before the fix (tests added, source at 32124ac): pnpm exec vitest run on the 3 pin files: 31 failed | 4 passed (35). Failure signatures: 'after typing r: expected [ { nodes: [ …(3) ] } ] to deeply equal []' and 'Unable to find a label with the text of: ID'. After the fix: 3 files 35/35 passed. Whole package at 714010b (pre-merge): pnpm exec vitest run --maxWorkers=2 packages/app-shell/: Test Files 1072 passed | 1 skipped (1073), Tests 10534 passed | 9 skipped (10543), lock VERDICT command-exit 0. Targeted at 714010b: 89 files (every flow designer test, the importers of the changed modules, the designer-table i18n tests) plus a probe: 90 passed, 1400 passed | 1 skipped. After merging main, at f8e702b: pnpm --filter @object-ui/app-shell type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json') exit 0. Targeted vitest at f8e702b: 97 files (the above plus every test in main's delta, the Problems panel and Studio flow/automation tests): 97 passed, 1481 passed | 1 skipped, exit 0. Declared narrowing: the whole package was not re-run after the merge (main's delta is app-shell only and passed its own CI); CI runs the full farm. Ablation: fix committed first. node /home/user/objectstack/scripts/ablation-replace.mjs (WRAP mode, its own EXIT/INT/TERM restore on the absolute path) replaced 'onRename={rename}' with 'onRename={(v) => patchNode({ id: v })}'. On disk: anchor x1 to x0, replacement x0 to x1, blob 634f4e3 to 96e6d3c. Result on the inspector and canvas pin files: 8 failed | 13 passed (21). Exactly the rename pins went red (card pin, Enter, selection follows, edge attributes, boundary event, the 3 canvas pins); the refusal pins stayed green, as expected. Restore: blob after restore 634f4e3 == HEAD blob, git diff HEAD empty, re-checked independently with git hash-object against git rev-parse HEAD:PATH. No dist/ is on the resolution path (the pins import the component by relative specifier). Throwaway probe, deleted and never committed, through the real inspector and buildFlowProblems: after the fixed rename x to renamed, a downstream decision keeps "x.decision == 'approve'" and Problems shows only a warning (see out_of_scope_findings[0]).",
"mcp_calls": "0",
"api_writes": "2 sent through the fleet relay: (1) pr_create, POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos/objectstack-ai/objectui/pulls (#11837; read-back identical, 11759 bytes); (2) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/11827/comments. 0 assignee writes: label-write.mjs --assign was refused by the auto-mode classifier before it sent anything (see deviations). git push x5 (the empty-branch probe and four pushes after it), not REST.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door, the Studio flow inspector's ID field. Measured on this branch after the fix, through the real FlowNodeInspector and buildFlowProblems: renaming an approval node x to renamed leaves a downstream decision branch reading "x.decision == 'approve'", and the Problems panel shows only a WARNING ('ok:
xis not a reference in scope at this step.'). The run-time effect is NOT MEASURED (no engine run); the spec text says downstream nodes read prior outputs by node id ({{nodeId.field}}). A rename does not carry expression references to the node's outputs (NODEID.decision, NODEID.KEY) in CEL or template text. That is not a mechanical edit, so it is not fixed here (bounded in-place fix condition 2 fails). Same family as objectui#11772 and this card: designer writes leave a reference naming a node id that no longer exists. The seat decides whether it joins a family card · evidence: PR objectui#11837 body, section 'What a rename does not carry' · dedupe words: flow node rename expression reference stale · node output reference after rename · approval decision reference renamed node","carrier: 承接者:无 · noted, not filed. A renamed EXPANDED container (loop / parallel / try_catch) collapses on the canvas, because FlowCanvas keeps session-only expandedIds keyed by node id. This is ephemeral view state in the AGENTS.md #8 sense, recorded in the PR body's 'What a rename does not carry'."
],
"gates": [
{
"command": "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (via os-verify-lock)",
"exit": 0,
"verdict": "os-verify-lock: VERDICT command-exit 0 · held the lock 183s"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 --reporter=dot packages/app-shell/ (at 714010b, pre-merge)",
"exit": 0,
"verdict": "Test Files 1072 passed | 1 skipped (1073); Tests 10534 passed | 9 skipped (10543); VERDICT command-exit 0"
},
{
"command": "pnpm --filter @object-ui/app-shell type-check (at f8e702b)",
"exit": 0,
"verdict": "TYPECHECK_EXIT=0 (script echoed: tsc --noEmit && tsc -p tsconfig.test.json)"
},
{
"command": "pnpm exec vitest run --maxWorkers=2 TARGETED-97-FILES (at f8e702b)",
"exit": 0,
"verdict": "Test Files 97 passed (97); Tests 1481 passed | 1 skipped (1482)"
},
{
"command": "pnpm exec vitest run 3 pin files (at 32124ac source, tests added)",
"exit": 1,
"verdict": "Tests 31 failed | 4 passed (35): the before-measurement, red as intended"
},
{
"command": "ablation-replace.mjs WRAP onRename={rename} to the old patchNode({ id }) write, pin files",
"exit": 1,
"verdict": "8 failed | 13 passed (21); ok restored: blob == HEAD (634f4e3ad1de) and git diff HEAD is empty"
},
{
"command": "pnpm exec eslint --no-inline-config 6 changed ts/tsx files (at f8e702b)",
"exit": 0,
"verdict": "4 problems (0 errors, 4 warnings), all on pre-existing lines"
},
{
"command": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7816 tracked text file(s); skipped 85 binary)."
},
{
"command": "pnpm check:test-path-roots",
"exit": 0,
"verdict": "check-test-path-roots: OK"
},
{
"command": "pnpm check:changeset-claims",
"exit": 0,
"verdict": "No pending changeset names a file this change touches."
},
{
"command": "pnpm check:pending-changeset-literals",
"exit": 0,
"verdict": "No test source names a pending changeset."
},
{
"command": "pnpm check:i18n-keys",
"exit": 0,
"verdict": "Every in-scope call-site key resolves against the en pack (3297 keys) ..."
},
{
"command": "pnpm check:i18n-drift",
"exit": 0,
"verdict": "No designer-table en value changed in this range. (3 keys added)"
},
{
"command": "pnpm check:i18n-dead-keys",
"exit": 0,
"verdict": "report: 343 candidate(s); none of engine.inspector.flowNode.idRequired / idTaken / idEdgeNamed among them"
},
{
"command": "pnpm check:i18n-designer-parity",
"exit": 0,
"verdict": "Every en row has a zh row, and every shared row carries the same placeholders."
},
{
"command": "node scripts/check-vi-mock-specifiers.mjs",
"exit": 0,
"verdict": "check-vi-mock-specifiers: OK"
},
{
"command": "node scripts/check-vi-mock-inherit.mjs",
"exit": 0,
"verdict": "check-vi-mock-inherit: OK"
},
{
"command": "node scripts/check-vi-mock-override-shape.mjs",
"exit": 0,
"verdict": "check-vi-mock-override-shape: OK"
},
{
"command": "node scripts/check-new-cross-file-line-citations.mjs",
"exit": 0,
"verdict": "Cross-file line-address citations ADDED by this branch : 0 (report-only)"
},
{
"command": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/11827-flow-node-rename-edges.md."
},
{
"command": "node scripts/check-changeset-no-major.mjs",
"exit": 0,
"verdict": "No changeset declares a
majorbump."},
{
"command": "node scripts/check-changeset-overwrite.mjs",
"exit": 0,
"verdict": "No pre-existing changeset was modified or deleted."
},
{
"command": "node scripts/check-changeset-fixed.mjs",
"exit": 0,
"verdict": "privatePackages declared: version=true, tag=false."
},
{
"command": "pnpm check:spec-symbols",
"exit": 0,
"verdict": "ledger anchors: 2 declared; none renders an instruction its anchor card can no longer receive."
},
{
"command": "pnpm check:designer-field-key-parity",
"exit": 0,
"verdict": "designer-field-key-parity: OK"
},
{
"command": "pnpm check:unreferenced-sources",
"exit": 0,
"verdict": "OK Every shipped source file in every covered package is reachable."
},
{
"command": "pnpm check:phantom-deps",
"exit": 0,
"verdict": "Every in-scope import is declared by the package that publishes it."
},
{
"command": "pnpm check:self-import",
"exit": 0,
"verdict": "No package names itself inside its own src/."
},
{
"command": "pnpm check:handler-key-reads",
"exit": 0,
"verdict": "OK"
},
{
"command": "pnpm check:metadata-write-doors",
"exit": 0,
"verdict": "OK 17 metadata write door(s) derived"
},
{
"command": "node scripts/check-lint-coverage.mjs",
"exit": 0,
"verdict": "lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total)."
},
{
"command": "node scripts/check-type-check-coverage.mjs",
"exit": 0,
"verdict": "test type-check coverage: 43/43 packages compile their tests"
},
{
"command": "node scripts/check-shell-escape-residue.mjs",
"exit": 0,
"verdict": "check-shell-escape-residue: OK"
},
{
"command": "pnpm check:eager-closure",
"exit": 2,
"verdict": "NOT MEASURED: PREREQUISITE NOT MET (reads apps/console/dist, no console build). No new module enters any closure: flow-problems.ts already reached @objectstack/spec/automation at runtime via flow-expr-problems.ts and screen-spec.ts, and FlowNodeInspector.tsx already reached @object-ui/components via _shared.tsx. Left to CI."
}
],
"deviations": [
"PR assignee NOT set.
node scripts/pm/label-write.mjs --repo objectstack-ai/objectui --issue 11837 --assign os-support-aiwas refused by the Claude Code auto-mode classifier ('[External System Writes]') before it sent anything. Not retried by any other route; the seat applies it.","Whole-package app-shell test run measured at 714010b, before merging main (16cda0b, objectui#11779). At the final head f8e702b: type-check plus a declared targeted set of 97 files; CI runs the full package on the merge.",
"check:eager-closure NOT MEASURED locally (needs a built console); see gates.",
"Zone 2 hypothesis 4 measured: the installed spec 17.7.0's node id rule is a bare z.string() plus the one-space duplicate refusal, and FlowSchema accepts an empty id. The empty-id refusal therefore rests on the designer's own validateFlowDraft rule, not the spec. An added refusal (an id only a stale edge names) is a design choice named in the PR.",
"Footer forms: the PR body ends with the os-dev session-URL footer (the session URL is also in prose as a code span); commits carry objectui AGENTS.md's model-free trailer pair. The harness's model-named attribution lines were not used, per AGENTS.md precedence.",
"One merge commit's message was amended before it was pushed, to drop git's '# Conflicts' comment lines. No pushed history was rewritten."
],
"files_changed": [
".changeset/11827-flow-node-rename-edges.md",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.tsx",
"packages/app-shell/src/views/metadata-admin/inspectors/FlowNodeInspector.renameNode-11827.test.tsx",
"packages/app-shell/src/views/metadata-admin/previews/FlowPreview.renameNode-11827.test.tsx",
"packages/app-shell/src/views/metadata-admin/previews/flow-problems.ts",
"packages/app-shell/src/views/metadata-admin/previews/flow-problems.renameNode-11827.test.ts"
],
"line_budget": "not applicable (no skills/** surface)",
"cleanup": "worktree /home/user/objectui-issue-11827 removed (node_modules first, no --force); private ref refs/os-dev/11827/main deleted; no background process left running"
}
Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
ACCEPT —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-07T23:36Z. PR objectui#11837, headf8e702b.PR shape: draft against
main, with16cda0bmerged in (no rebase; one mechanical conflict inflow-problems.ts, resolved). First lineFixes #11827, no other closing keyword.Clause-②: noat line start. The three commits carry only the model-free trailer pair.Scope: 7 files, +1006/−9, on the claim's surface (
6047461043):FlowNodeInspector.tsx;flow-problems.ts;i18n.ts;InspectorTextField(shared),FlowCanvas.tsxandFlowPreview.tsxare untouched. Nothing is added to the package entry. No governed path.Diff read (the seat's own): the ID field is a local
FlowNodeIdField. It commits on blur or Enter, reverts on Escape, and ignores an IME Enter.renamebuilds one patch:boundaryRefsAfterNodeRename(a boundary event'sattachedToNodeId);edgesAfterNodeRename, which rewrites onlysource/targetand keeps each edge's id, condition, label, default flag and order. It is a no-op when the old id is still a node.The selection follows through
onSelectionChange. A finished id is refused inline in three cases:nodeMissingIdrule, because the installed spec's node id is a barez.string());collectFlowGraphs, so regions count);Ruling honoured (triage
6045669919):The ruling's pins are present:
s → x → erename leaves nothing dangling, a colliding id is never committed, and a label edit leavesedgesuntouched (control).Changeset sentences checked against the head:
patchon@object-ui/app-shell.Reverse verification (dev report):
32124acsource with the new tests: 31 red, 4 green (the controls).onRenameback to the barepatchNode({ id })) throughablation-replace.mjs: exactly the 8 rename pins red, with the refusal pins green. Restored with the blob equal to HEAD.@object-ui/app-shellsuite at714010bpassed 10534. The targeted 97 files atf8e702bpassed 1481.Gates: CI on
f8e702b(head_shaequals the PR head): 43 runs, 40 success, 3 skipped (Test (coverage), its shard template,dependabot), 0 failure.LintandType Checkare success. Mergeable stateclean.Contract review: not required (
Clause-②: no).Findings:
x.decisionin CEL,{{x.field}}in templates). Measured on the real inspector andbuildFlowProblems: the stale reference stays, and Problems shows only a warning. It is the third member of the objectui#11772 / objectui#11827 family, and rewriting expression text is not mechanical → filed flow designer: renaming a node leaves expression references to its outputs (x.decision, {{x.field}}) naming the old id; Problems shows only a warning #11838expandedIdsare keyed by node id. That is ephemeral view state (objectui AGENTS.md feat: add live playground for interactive schema demonstration #8) →Acceptance notesLanding: ready + auto-merge through the queue, now.
Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Landed —
domain:uiseat 3,session_01CGZy1BGCjdN5cXqL9cnvB8, 2026-10-07T23:55Z. PR objectui#11837 merged through the queue as87f7b6cat 2026-10-07T23:53Z.16cda0b, an ancestor oforigin/main.git merge-treeof that parent and the accepted headf8e702b(tree4bec99f9on both).16cda0b→87f7b6c:edgesAfterNodeRenameinflow-problems.ts: 0 → 2.onCommit={(v) => patchNode({ id: v })}inFlowNodeInspector.tsx: 1 → 0.inspectors/_shared.tsx(InspectorTextField) is byte-identical on both sides, so the shared field's commit-on-change behaviour is untouched.Fixes #11827. It is the only issue closed since 23:00Z.pm:dispatchedis removed.FlowNodeInspector.tsx) no longer waits on this card.Generated by Claude Code
fix(app-shell): renaming a flow node carries its edges, and commits o…