Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

[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

Closed

Description

@objectstack-fleet

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:

  1. delete the Notify node;
  2. select Start → toolbar Add node → Notify;
  3. delete the orphaned End;
  4. 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

Duplicate check

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

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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 does

    Triage 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.tsx and inspectors/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.

  2. added
    area:workflowApprovals and automation — the work that runs without a person driving it
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 8, 2026
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01MgfduSkFrfM3eorB3UGfAU
    Account: os-bill
    Branch: claude/issue-11905-flow-connect-nodes
    Worktree: objectui-issue-11905
    Domain: domain:ui
    Seat: domain:ui#2
    File 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.
    • The flow problems check, wherever the canvas's existing check lives (the dev names the file before editing it): a new edge is refused when it duplicates a (source, target) pair or names a node that does not exist.
    • Only new engine.studio.* / flow-designer rows, en and zh, in the table that already holds that prefix in app-shell.
    • The tests beside these, and .changeset/11905-*.md.

    ⛔ Not on it:

    • The flow spec, and any server-side flow validation: this card lands in objectui only.
    • StudioDesignSurface.tsx: other claims hold regions of it.
    • packages/components/src/ui/** and packages/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/objectui over 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: none blocking.

    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 or packages/i18n key changes.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — PR objectui#11926 · domain:ui seat 2 · 2026-10-08T07:15Z

    Implemented-by: os-dev subagent of session_01MgfduSkFrfM3eorB3UGfAU, branch claude/issue-11905-flow-connect-nodes, head 865128c (code at 5b11534, docs patch round on top).
    Reviewed-by: domain:ui seat 2 (session_01MgfduSkFrfM3eorB3UGfAU), against GitHub and the branch diff, not against the report's prose.

    Checklist:

    • PR shape: draft, base main, first line Fixes #11905. No closing keyword sits next to another card. Clause-②: no is at the start of a line. The body was refreshed by the seat to match 865128c.

    • Scope: 10 files, all inside the claim:

      • FlowCanvas.tsx;
      • FlowEdgeInspector.tsx;
      • previews/flow-problems.ts, the problems check, named before it was edited;
      • the designer table's new engine.flowCanvas.connect / engine.flowProblems.connectRefused.* rows, en and zh;
      • three new tests;
      • the changeset;
      • in the patch round, packages/app-shell/README.md and content/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/** and packages/components/src/ui/** are untouched.

    • Triage direction met:

      • Every card except an End carries a connect handle: press, drag, drop on another node. The edge is built by outEdge, extracted unchanged from addNode with its decision-branch carry, and written as a patch holding the edges alone.
      • From / To are pickers writing through the existing patchEdge.
      • Both doors call one rule, edgeConnectionRefusal (missing source, missing target, self, repeat on the (source, target) pair), and show its reason. A refusal writes nothing.
    • Hypotheses:

      • H1 is falsified: the canvas uses no flow library ("Dependency-free"), so the gesture is hand-built.
      • H2 holds: a self-loop is already refused by the Problems check, which reports it as an undeclared cycle.
      • H3 is pinned: a reconnected node's record is untouched.
      • H4: the inspector's docblock is rewritten.
    • 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-read note 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-②: no holds: flow-problems.ts is not reachable from @object-ui/app-shell's entry. No prop, spec key or packages/i18n key changes.

    • First-load bytes: none expected. The touched modules are reached only from Studio's lazily loaded route. That was read from source: StudioDesignSurface.tsx and FlowNodeInspector.tsx are the only static importers of flow-problems.ts.

    • Changeset: @object-ui/app-shell: patch. Every sentence was checked against the diff.

    • Evidence:

      • 25 files pass, 397 tests: every importer, plus the add-node control FlowCanvas.addNodePaths-11778 unmodified, plus the column-identity ratchet. The designer-table readers pass, 7 files and 396 tests. The type-check passes.
      • Ablations: disabling the repeat arm turns 7 of 28 red; writing nodes with config dropped turns exactly the H3 pin red. Each restore was proven.
      • Chromium drive: a drag drew an edge; a repeat and a self drop were refused with their reasons; a release over the background wrote nothing; a To re-point kept the selection.
      • The docs gates pass on 865128c.
    • CI: 865128c is running. The PR lands through the merge queue only when every check on that head is green.

    Out-of-scope notes, carrier: none:

    • Keyboard: the connect handle is pointer-only. A keyboard user can re-point an existing connection through From / To, but cannot add one between two existing nodes.
    • Pair rule vs. route key: the doors refuse on the (source, target) pair, as ruled. That is wider than the Problems row's route key, so a decision whose two branches lead to one node cannot be drawn by these doors. It never could be before this PR. Widening it is a one-line change if the maintainer prefers it.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions