Repository navigation
[repo:objectui] Studio flow designer: no gesture connects two existing nodes, so a node left unconnected can only be deleted and re-added, losing its configuration #11905
Description
Filing gate ① — a user-visible defect with a named site, filed on the maintainer's word: 「把流程设计器那几个问题也立卡」. If triage reads it as a capability proposal instead, it is a ② candidate; the reproduction below is the case for ①.
Who acts on it: an objectui app-shell executor. The fix lands in objectstack-ai/objectui (packages/app-shell/src/views/metadata-admin/previews/FlowCanvas.tsx and inspectors/FlowEdgeInspector.tsx). The card sits on the objectstack board because the session that found it could not read or search objectstack-ai/objectui issues.
What happens
The flow designer creates an edge in only three ways, all of them while adding a new node:
- the edge's + (splits that edge);
- a card's Add connected node;
- the toolbar Add node, placed after the selection.
The revise-loop helper is a special case. Nothing connects two nodes that already exist. The connection inspector shows From / To read-only. Its own docblock says "rewiring is done on the canvas, not here", but the canvas has no such gesture.
So once a node is left without an edge, the Problems panel names it unreachable, and the only way back is to delete it, re-add it from a connected anchor, and configure it again.
Reach
Measured on a fresh showcase boot: objectstack 8fc50b7647d30db2a0837877cf251c1163a3239e, console at the .objectui-sha pin a58626c88dc85954bd0af24f16ebb69454c03eee.
During the Studio task benchmark a configured-to-be Notify node and the End node ended up with no edges (start, end and notify all disconnected). No drag, handle or inspector field could connect Start → Notify → End. Recovery took:
- delete the Notify node;
- select Start → toolbar Add node → Notify;
- delete the orphaned End;
- select Notify → Add node → End.
The flow draft PUT answered 422 INVALID_METADATA throughout, until the graph was rebuilt that way.
On main (b6f8fc8718e4, 2026-10-08) the disconnected state is still reachable by the product's own rules:
- objectui#11778 (
32124ac) records that, with "no single path to insert into — an End reached by several paths or by none, or a flow with no Start and nothing selected: N is added with no edge, and the Problems panel names it unreachable". - objectui#11772's fix (
2b30d39) splices a removed node's predecessor to its successor only for a node on a single path; removing a node on a branching path still leaves what followed it unconnected.
FlowCanvas.tsx on main builds edges only in addNode (from an anchor), insertOnEdge and the revise-loop helper. FlowEdgeInspector.tsx renders the endpoints with a read-only EndpointRow.
Expected
An author can connect two existing nodes without code. Any of these would do:
- drag from a node's out-handle to another node;
- a Connect to… action on a card that lists the reachable targets;
- editable To / From on the connection inspector.
Ideally the Problems panel's "unreachable" entry offers that action, so the error it names has a fix in the UI. The connection inspector's docblock should stop pointing at a canvas gesture that does not exist.
Seam: renderer:objectui FlowCanvas (edge creation only via add-node paths) · FlowEdgeInspector (read-only endpoints) — consumer: none for rewiring
Related, not duplicates
- objectui#11778 (closed) — the three add-node paths share one palette and insert into the path; it adds no way to connect existing nodes.
- objectui#11772 (closed), spec(flow): FlowSchema accepts edges whose source/target names no node and repeated (source,target) pairs; the engine then runs the target once per incoming edge objectstack#22088 (closed) — edges left behind by node removal (both halves); not rewiring.
- qa: a repeatable Studio task benchmark — "build a repair-ticket app with no code", timed, counting red errors — as the Studio UX baseline objectstack#22137 — the Studio task benchmark whose step 5 met this (
docs/qa/studio-benchmark/baselines/2026-10-08-8fc50b76-a58626c8.md, Warnings and notes; PR docs(qa): a repeatable Studio task benchmark — repair-ticket scenario, scoring sheet, user-session protocol and the first baseline objectstack#22180).
Duplicate check
- Semantic issue search on objectstack-ai/objectstack (open and closed), flow designer cannot connect two existing nodes, orphaned unreachable node can only be deleted: 0 hits.
- flow edge rewire source target read-only draw connection between nodes: 1 hit (#22088 — the spec half of dangling edges), not this.
- Positive control in the same search family: flow designer insert node on edge defaults to create_record, node type dropdown missing notify returned spec: a notify node's refused
title/messageis told to write'{{record.name}}', the spelling the build'sflow-double-brace-interpolationrule then flags on the same node and the notify renderer does not resolve objectstack#22081, flow designer: the Start node's Entry condition is raw-CEL-only — ConditionBuilder ships in five other inspectors but not the one condition most users meet objectstack#11940 and record_change flow start node binds to a single lifecycle event — no create-OR-update in one flow objectstack#3427 (flow-designer cards exist and are found). - objectstack-ai/objectui issues: NOT MEASURED — no issue read access in this session. The objectui
maincommit messages since the pin (79 commits) name no connect or rewire gesture.
Dedupe words: flow designer connect existing nodes · rewire edge source target · unreachable node no fix
Filed by Claude Code (session session_01VenZErfhzG51sJBHRM1pWJ) from the Studio task benchmark baseline run on objectstack-ai/objectstack#22137.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 8, 2026
Path: approvals and automation — a flow built in Studio runs as drawn | 缺项 | P2
Triage: first grade,
bug·priority:p2·domain:ui·area:workflow·pm:queue. Moved here from objectstack-ai/objectstack#22192, where the fix lands. Direction: the canvas connects two existing nodes, as the edge inspector's own docblock says it doesTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T06:01Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/app-shell/src/views/metadata-admin/previews/FlowCanvas.tsxandinspectors/FlowEdgeInspector.tsx⇒domain:ui; rationale: Studio's flow designer in objectui. Transferred from the objectstack board: issues live in the repository where the fix lands. A dedupe on this repository found no card for it.Clause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 3
Session:
session_01MgfduSkFrfM3eorB3UGfAUAccount:
os-billBranch:
claude/issue-11905-flow-connect-nodesWorktree:
objectui-issue-11905Domain:
domain:uiSeat:
domain:ui#2File surface (on
3683591):packages/app-shell/src/views/metadata-admin/previews/FlowCanvas.tsx: a drag-from-handle connection between two existing nodes, written through the canvas's existing patch path.packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.tsx: From / To become editable through the same patch path, and its docblock (about:13) states what the canvas does.engine.studio.*/ flow-designer rows, en and zh, in the table that already holds that prefix in app-shell..changeset/11905-*.md.⛔ Not on it:
StudioDesignSurface.tsx: other claims hold regions of it.packages/components/src/ui/**andpackages/i18n/**.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/objectuiover these paths: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui app-shell Studio flow designer: the canvas has no gesture that connects two existing nodes, although the edge inspector says rewiring is done on the canvas, so a node left without an edge can only be deleted and re-added, losing its configuration | the canvas already writes edges through its patch path when a node is added | every author editing a flow in Studio, measured by triage (objectstack-ai/objectstack#22192, moved here)Thread-read: 6053456870
Serial constraints cleared:
noneblocking.FlowCanvas.tsxorFlowEdgeInspector.tsx.Why
Clause-②: no: an authoring gesture in Studio's own canvas. It writes the flow's existing edge shape through the existing patch path. No export, prop, spec key orpackages/i18nkey changes.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 11905,
"status": "done",
"branch": "claude/issue-11905-flow-connect-nodes",
"pr": "#11926",
"session": "session_01MgfduSkFrfM3eorB3UGfAU",
"premise_still_valid": true,
"summary": "Premise verified on origin/main 3683591: FlowCanvas built edges only in addNode / insertOnEdge / addReviseLoop, and FlowEdgeInspector rendered From / To with a read-only EndpointRow while its docblock said rewiring is done on the canvas. Now each card except an End carries a connect handle (press, drag, drop on another node); the edge is built by outEdge (extracted from addNode, byte-identical output, decision branch carry included) and written as a patch holding the edges alone, so a reconnected node's record is untouched. From / To are InspectorSelectField pickers writing through the existing patchEdge, re-selecting the edge so an id-less edgeKey follows. Both doors call one rule, edgeConnectionRefusal in previews/flow-problems.ts (missing-source, missing-target, self, repeat), and show describeEdgeConnectionRefusal's reason; a refusal writes nothing. Mechanism hypotheses: H1 falsified (no flow library: the canvas docblock reads 'Dependency-free: no reactflow/@xyflow'; the gesture is hand-built with window listeners and an elementFromPoint drop hit-test); H2 held, with self-loop measured as refused by the Problems check (findCycle reports A to A as an undeclared cycle), and the duplicate refusal implemented on the PAIR as the ruling says, which is wider than the Problems row for a repeat (edgeRouteKey leaves differently routed same-pair edges unflagged); cycles are not refused; H3 held and pinned; H4 docblock rewritten.",
"tests": "All at HEAD 5b11534. New pins 28 tests in 3 files. Existing importers of FlowCanvas / FlowEdgeInspector / flow-problems (21 files, incl. FlowCanvas.addNodePaths-11778 unmodified as the add-node control) + 3 new + core column-identity.ratchet: 'Test Files 25 passed (25)' / 'Tests 397 passed (397)', lock VERDICT command-exit 0. Designer-table readers (7 files): 'Test Files 7 passed (7)' / 'Tests 396 passed (396)'. pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json) after turbo build of the app-shell^... closure ('Tasks: 28 successful, 28 total'): VERDICT command-exit 0. eslint on 7 touched files: exit 0, 0 errors, 3 warnings on unchanged lines. Ablation via objectstack scripts/ablation-replace.mjs (anchor x1 → x0, blob changed, restore blob == HEAD, git diff HEAD empty): repeat arm disabled → 'Tests 7 failed | 21 passed (28)'; drop writes nodes with config dropped → 'Tests 1 failed | 11 passed (12)' (the H3 pin). Chromium (/opt/pw-browsers/chromium) on the console preview gallery ?only=flow, own vite :5291 stopped by PID: drag find → skip drew edge 15 (ring 'connects', preview line, inspector opened with From / To); start → find refused (ring 'refused', repeat reason, edges unchanged); wait → wait refused with the self reason; release over background wrote nothing; inspector To re-pointed find → email with selection following; To = From refused under the field.",
"gates": {
"head": "5b115344c",
"commands": [
"pnpm exec vitest run (25 files) → Test Files 25 passed (25)",
"pnpm exec vitest run (7 designer-table readers) → Test Files 7 passed (7)",
"pnpm --filter @object-ui/app-shell type-check → os-verify-lock VERDICT command-exit 0",
"pnpm exec eslint (7 files) → exit 0",
"pnpm check:i18n-keys → exit 0",
"pnpm check:i18n-drift → exit 0 'No designer-table en value changed in this range.'",
"pnpm check:i18n-designer-parity → exit 0 'Every en row has a zh row, and every shared row carries the same placeholders.'",
"pnpm check:i18n-dead-keys → exit 0 (report; no new key listed)",
"node scripts/check-changeset-presence.mjs → exit 0",
"node scripts/check-changeset-no-major.mjs → exit 0",
"node scripts/check-changeset-fixed.mjs → exit 0",
"pnpm check:changeset-claims → exit 0",
"pnpm check:pending-changeset-literals → exit 0",
"pnpm check:new-line-citations → exit 0 'VERDICT new-cross-file-line-citations: 0 new citation(s)'",
"pnpm check:control-bytes → exit 0",
"pnpm check:unreferenced-sources → exit 0",
"pnpm check:vi-mock-specifiers → exit 0",
"node scripts/check-vi-mock-override-shape.mjs → exit 0",
"node scripts/check-test-path-roots.mjs → exit 0",
"pnpm check:spec-symbols → exit 0",
"pnpm check:handler-key-reads → exit 0",
"pnpm check:metadata-write-doors → exit 0",
"pnpm check:designer-field-key-parity → exit 0"
],
"ci": "in_progress at report time; not awaited"
},
"files_changed": [
".changeset/11905-flow-connect-nodes.md",
"packages/app-shell/src/views/metadata-admin/i18n.ts",
"packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.tsx",
"packages/app-shell/src/views/metadata-admin/inspectors/FlowEdgeInspector.endpoints-11905.test.tsx",
"packages/app-shell/src/views/metadata-admin/previews/FlowCanvas.tsx",
"packages/app-shell/src/views/metadata-admin/previews/FlowCanvas.connectNodes-11905.test.tsx",
"packages/app-shell/src/views/metadata-admin/previews/flow-problems.ts",
"packages/app-shell/src/views/metadata-admin/previews/flow-problems.connectRefusal-11905.test.ts"
],
"deviations": [
"Docs-driven rule (objectui AGENTS.md commandment 2, named binding by the dispatch) not met: packages/app-shell/README.md (Visual flow canvas, Interactions list) and content/docs/guide/flow-designer.md (The canvas, gesture table) do not list the connect handle or editable From / To. Both are outside the claim surface, whose stop rule wins, so they were not edited. Conflict surfaced in open_questions.",
"The problems-check file named before editing: packages/app-shell/src/views/metadata-admin/previews/flow-problems.ts (buildFlowProblems lives there). No file outside the claim surface was edited.",
"The connect handle is rendered by FlowCanvas.tsx as a sibling of NodeCard (flow-canvas-parts.tsx is outside the surface), so it does not use the card's hover group and is shown at all times, at low emphasis.",
"Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude) per objectui AGENTS.md, not the harness reminder's model-named Co-Authored-By; the PR footer uses the os-dev session-URL form, not the harness's form.",
"Browser drive: the first run's task → end drag targeted cards the gallery's 400px canvas box clips at 40% zoom (no press landed: a driver miss, not a product result); rerun on on-screen nodes. The self-loop drag on wait was observed in run 1; in run 2 wait sat in the clipped strip."
],
"mcp_calls": "0",
"api_writes": "3 (all REST, all through the fleet-write relay as POST /repos/objectstack-ai/objectstack/dispatches): pr_create → POST /repos/objectstack-ai/objectui/pulls (#11926, draft, 9162 bytes sent = stored); label-write --assign os-bill → POST /repos//issues/11926/assignees (read back matches); post-stamped os-dev-report → POST /repos//issues/11905/comments. Plus 3 git pushes (empty probe, implementation, tests).",
"open_questions": [
{
"question": "AGENTS.md commandment 2 asks for README + guide updates for a new gesture; both files are outside claim 6053893117's surface, whose stop rule held. Extend the surface?",
"options": [
"A: seat extends the surface to packages/app-shell/README.md and content/docs/guide/flow-designer.md and sends a patch round: one docs commit on this branch before ready (guide table row 'Drag from a card's connect handle onto another node | Connect the two (refused, with the reason, for a node to itself, a pair already connected, or a node the flow does not have)'; a sentence under the decision-branch paragraph that a selected connection's From / To re-point it; README Interactions bullet 'Connect: drag from a node's connect handle onto another node; the edge inspector's From / To re-point an existing connection; both refuse via edgeConnectionRefusal')",
"B: land as is and file a docs-only follow-up"
],
"recommendation": "A, because the rule says a feature is not done until docs reflect it, the edit is a few lines, and the README already claims canvas rewiring exists"
}
],
"out_of_scope_findings": [
"carrier: none (noted, not filed) - the connect handle is pointer-only (aria-hidden); a keyboard user can re-point an existing connection through From / To but cannot add a connection between two existing nodes",
"carrier: none (noted, not filed) - the card's 'ideally the Problems panel unreachable row offers the action' is outside the triage direction; ProblemsPanel is outside the surface",
"carrier: none (noted, not filed) - design note: the doors refuse a duplicate on the (source, target) pair, as ruled, which is wider than the Problems repeat row (edgeRouteKey); a decision whose two branches lead to one node can no longer be drawn by these doors (it never could be: before this PR no door drew an edge between existing nodes). Widening to edgeRouteKey would be a one-line change in edgeConnectionRefusal if the maintainer prefers it"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Review: ACCEPT — PR objectui#11926 ·
domain:uiseat 2 · 2026-10-08T07:15ZImplemented-by:
os-devsubagent ofsession_01MgfduSkFrfM3eorB3UGfAU, branchclaude/issue-11905-flow-connect-nodes, head865128c(code at5b11534, docs patch round on top).Reviewed-by:
domain:uiseat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub and the branch diff, not against the report's prose.Checklist:
PR shape: draft, base
main, first lineFixes #11905. No closing keyword sits next to another card.Clause-②: nois at the start of a line. The body was refreshed by the seat to match865128c.Scope: 10 files, all inside the claim:
FlowCanvas.tsx;FlowEdgeInspector.tsx;previews/flow-problems.ts, the problems check, named before it was edited;engine.flowCanvas.connect/engine.flowProblems.connectRefused.*rows, en and zh;packages/app-shell/README.mdandcontent/docs/guide/flow-designer.md.The surface was amended by the two docs files under the bounded in-place exemption: the same feature, docs only, and no claim or open PR holds either.
StudioDesignSurface.tsx,flow-canvas-parts.tsx,ProblemsPanel, the flow spec,packages/i18n/**andpackages/components/src/ui/**are untouched.Triage direction met:
outEdge, extracted unchanged fromaddNodewith its decision-branch carry, and written as a patch holding the edges alone.patchEdge.edgeConnectionRefusal(missing source, missing target, self, repeat on the (source, target) pair), and show its reason. A refusal writes nothing.Hypotheses:
Docs (AGENTS.md, docs-driven): the patch round adds a gesture-table row and a From / To sentence to the guide, and a Connect bullet to the README. The README sentence that claimed "canvas rewiring", false before this PR, now names the two doors. The guide promises no keyboard path. The
changeset-claim-re-readnote about objectui#11806's pending changeset is answered: this round does not touch the README's sign-up section, so that changeset's paragraph stays true.Clause-②: noholds:flow-problems.tsis not reachable from@object-ui/app-shell's entry. No prop, spec key orpackages/i18nkey changes.First-load bytes: none expected. The touched modules are reached only from Studio's lazily loaded route. That was read from source:
StudioDesignSurface.tsxandFlowNodeInspector.tsxare the only static importers offlow-problems.ts.Changeset:
@object-ui/app-shell: patch. Every sentence was checked against the diff.Evidence:
FlowCanvas.addNodePaths-11778unmodified, plus the column-identity ratchet. The designer-table readers pass, 7 files and 396 tests. The type-check passes.865128c.CI:
865128cis running. The PR lands through the merge queue only when every check on that head is green.Out-of-scope notes,
carrier: none:Generated by Claude Code
fix(app-shell): the flow designer connects two existing nodes, by a c…