This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit 32124ac
fix(app-shell): the flow designer's three add-node paths open one palette and add into the path (objectui#11778) (#11832)
Fixes #11778
Clause-②: no
## What was wrong, measured on `main` (`2b30d39`)
A probe through the real `FlowCanvas` on the draft Studio creates for a
new flow (`start → end`, edge `e1`), before any change:
| Gesture | What it wrote |
|---|---|
| the edge's "+" (Insert node here) | a `create_record` node at once, no
palette, pinned at `position {x: 28, y: 89}`: the midpoint of the two
cards' corners, on top of the Start card |
| Start's "Add connected node" | a `create_record` node at once, no
palette, wired `start → node_1` beside `start → end`: a second branch
that leads nowhere |
| toolbar Add node, nothing selected | the picked `notify` node with no
edge at all |
| toolbar Add node, Start selected | the picked `notify` node as a
second branch `start → node_1` beside `start → end` (the card's report)
|
| the Node Type select on Start | the trigger read the raw `start`; the
list was the hand list `FLOW_NODE_TYPE_OPTIONS`, which has no `notify`
and spells `http_request`, `connector_action` and `try_catch` raw |
## What changed
- **Every "+" opens the one add-node palette.** The edge's "+" and a
card's "Add connected node" now wrap their button in the same
`NodePalette` the toolbar uses (grouped, searchable, with Recently
used). Nothing is added until a type is picked. The canvas keeps one key
for which "+" has the palette open (`toolbar`, `edge:EDGEKEY` or
`node:ID`), so at most one is open and a pick closes it wherever it was
opened. No second picker was built.
- **The picked node goes into the path, by one named rule.** The edge
"+" splits its edge, as before: A → B becomes A → N (the original edge,
its id and guard kept) plus N → B. The other two entry points share
`placeAfter`, "add after this node":
- the anchor is the card's node for "Add connected node", and for the
toolbar the selected node, else Start;
- an anchor with exactly one way on: split that edge, so N takes the
next node's place in the path (`start → end` becomes `start → N → end`);
- an anchor with no way on: append (anchor → N);
- a branching anchor (`decision`, `approval`, `parallel_gateway`), or
one that already leads to two or more nodes, has no single path after
it: N becomes a new branch from it. That is the existing append, so a
decision still carries its next unused condition onto the new edge;
- End has no "after": with End selected the toolbar puts N before End,
splitting its one way in;
- **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, as it names any
unconnected node.
"A way on" is a forward edge between two distinct nodes that both exist:
a declared back-edge is not one, and neither is an edge naming a missing
node (objectui#11772 leaves those for the Problems panel to name).
Because the canvas selects the node it adds, toolbar adds in a row build
the path in order: `start → N1 → N2 → N3 → end` (pinned).
- **The inserted node is laid out.** The edge insert no longer pins
`position` at its endpoints' midpoint. The new node carries no position,
like an append always did, so the layered layout gives it its own row
and moves the nodes below it down. Nodes the author dragged keep their
stored positions.
- **The Node Type select is the palette's list.** `FlowNodeInspector`
reads `useFlowNodePalette()` (the hardcoded `NODE_PALETTE` merged with
the engine's published descriptors, the same list the canvas adds from)
through a new module-local helper `paletteTypeOptions`, which keeps the
palette's order (its category sections) and its display names
(`translateNodeLabel` over the palette's label, exactly as the palette
draws them). So `notify` is offered, no raw type name is shown, and an
engine-published type joins the list as it joins the palette. A stored
type the palette does not offer (`start`, the deprecated alias
`http_request`, a plugin type whose engine is not answering) is added
for that node only, under its display name, so the trigger still says
what the node is.
- **`FLOW_NODE_TYPE_OPTIONS` is retired.** The select was its only
production reader. Its six test readers used it as the population of a
sweep over the inspector's field-group table; they now walk
`FLOW_NODE_CONFIG_TYPES`, the table's own keys, a strict superset of the
retired list (it adds `notify`, `boundary_event`, `parallel_gateway`,
`join_gateway`, `legacy_action`), so no sweep lost a type.
`flow-node-config.spec-reconciliation.test.ts` now also asserts its
source-parsed table keys equal that export exactly.
## The PM's mechanism hypotheses, measured
- **H1 (what each entry point produced): confirmed, with the two open
halves now measured** (table above). Add connected node called
`addNode('create_record', { from })`, and the toolbar passed `from:
selectedId`, so with nothing selected it wrote no edge and with Start
selected it wrote the second branch.
- **H2 (the rule for "inserted into the path"): decided as above.**
"After the selected node" for the toolbar, with Start standing in for no
selection, because the canvas selects every node it adds: repeated adds
then extend the path in order instead of stacking after Start. "Add
connected node" gets the same rule so the two never disagree; a decision
or an approval keeps getting a new branch, because their out-edges are
their outcomes and the canvas has no other gesture that draws an edge.
- **H3 (dropping the pin is the auto-layout): confirmed.** Nothing else
was needed; the layered layout already places an unpinned node. The
objectui#11772 round trip is untouched by it: inserting and then
removing the node gives the draft back byte for byte, and that pin
stayed green under every ablation below, including the one that restores
the pin.
- **H4 (`FLOW_NODE_TYPE_OPTIONS` readers): one production reader (the
select) and six test readers**, handled as above.
- **H5 (reuse `NodePalette`): confirmed, with one finding.** A palette
opened from an edge or a card is portaled out of the canvas in the DOM
but still sits inside the canvas viewport in the React tree, so a press
on a palette row bubbled to the viewport's pan handler, which clears the
selection and takes pointer capture; the browser then fires the click at
the viewport and the row's select never runs (objectui#11546's shape).
`NodePalette`'s content now stops that press. Ablation B below shows the
five edge- and card-palette picks red without it, and the toolbar's
(outside the viewport) green.
## objectui#11772's pins
`FlowPreview.removeNode-11772.test.tsx` changed in three places, none of
them an assertion's strength:
- its palette hook stub returned `[]`, which is not the hook's offline
answer (`NODE_PALETTE`, as the stub's own comment says) and leaves a
palette with nothing to pick; it returns `NODE_PALETTE` now;
- its two gesture helpers pick "Create record" in the palette after the
"+", the type the "+" used to add on its own;
- the control "adding a connected node mints exactly what it did before"
keeps its exact ids (`node_1`, `edge_1`) and its exact `toEqual`, but
its wiring is this card's: `e1: start → node_1`, `edge_1: node_1 → end`,
where it used to pin the second branch `start → node_1` beside `start →
end`, which is the defect this card reports.
The byte-for-byte round trip assertion is unchanged and green.
`FlowNodeInspector.removeEdges-11772.test.tsx` and
`flow-problems.repeatedEdge-11772.test.ts` are untouched and green.
## Tests
New pins, through the real components:
- `previews/FlowCanvas.addNodePaths-11778.test.tsx` (11): each "+" opens
the palette and adds nothing before a pick; the edge "+" with Notify
gives `start → notify → end`, keeps `e1`, and the node has no position
and sits a full row below Start with End moved below it; Add connected
node on Start inserts into the path, on a node with no way on appends,
on a decision adds a branch carrying its `Else` default; the toolbar
with nothing selected, with Start selected and with End selected, and
three adds in a row. Palette rows are pressed with `browserClick`, which
routes a click the way a browser does after pointer capture.
- `inspectors/FlowNodeInspector.nodeTypeOptions-11778.test.tsx` (6): the
option list equals the palette's labels in its order, Notify included;
no raw type name; picking Notify writes `type: 'notify'`; zh-CN names;
Start shown as "Start" on its own node and not offered to another; an
engine-published type joins the list.
Touched existing tests: `FlowCanvas.test.tsx` (the insert pin now
asserts no position, and both gestures pick a type),
`flow-node-producers.label.test.tsx` (both gestures pick a type),
`FlowNodeInspector.connectorInput.test.tsx` (its text query for the
"Connector" field label is scoped to the label, because the Node Type
trigger now reads "Connector" too), and the six sweep tests above.
Ablations, each through `ablation-replace.mjs` from the committed tree
at `84f3800`, each restore proven by blob hash equal to HEAD and an
empty `git diff HEAD`, over the two new pin files, `FlowCanvas.test.tsx`
and the objectui#11772 pin file (50 tests):
| Mutation | Red |
|---|---|
| A: the edge "+" adds `create_record` at once, no palette (the card's
suggested ablation) | 6: the three edge-"+" pins, the 11772 reproduction
and round trip (they now need a palette row), the `FlowCanvas.test.tsx`
insert pin |
| B: the palette no longer stops a press on its rows | 5: every edge-
and card-palette pick; the four toolbar pins stay green |
| C: the toolbar back to `addNode(type, { from: selectedId })` | 4: all
four toolbar pins |
| D: Add connected node back to a plain `from` append | 2: the Start pin
and the 11772 control |
| E: the edge insert pinned at the midpoint again | 4: the layout pin,
the Start insert, both `FlowCanvas.test.tsx` position pins; the 11772
round trip stays green |
| F: the select back to the hand list with raw names | 6: all six
inspector pins |
The first attempt at B measured nothing: the tool refused it before
running, because the replacement text was a substring of the anchor. It
was re-run with a distinct marker; the row above is that run.
Browser check: Chromium (`/opt/pw-browsers/chromium`) on the console's
preview gallery (`preview-gallery.html?only=flow`, the offline palette),
real mouse clicks. The edge "+" opened the palette beside the "+" and
added nothing; picking Notify inserted `node_1` between Start and the
next node on its own row (card tops after the pick: Start 28, new node
150, the next node 272); a card's "+" then Create record inserted below
that card; the toolbar then put a Script node after the selected one;
the inspector's Node Type read "Script" and listed the palette's 18
names, Notify among them; no page errors.
Local verification at `3947a9a` (HEAD):
- `pnpm exec vitest run` over
`packages/app-shell/src/views/metadata-admin/previews/`,
`.../inspectors/` and the two Studio files that count the canvas's "+"
buttons: 230 files, 3043 passed, 1 skipped.
- `pnpm --filter @object-ui/app-shell type-check`: exit 0. Both of its
projects ran; `tsconfig.test.json --listFiles` includes every touched
and new test file.
- `pnpm exec eslint` on the 16 touched `.ts`/`.tsx` files: 0 errors, 12
warnings, one more than on `main` (a
`react-refresh/only-export-components` warning for the new
`paletteTypeOptions` export, the same warning the file's seven other
non-component exports carry).
- `check:control-bytes`, `check:test-path-roots`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:new-line-citations`, `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:unreferenced-sources`, `check-changeset-presence`,
`check-changeset-no-major`: all exit 0.
- The whole `@object-ui/app-shell` suite ran once, at `acdf998`: 1064
files, 2 failed. One was `FlowNodeInspector.connectorInput.test.tsx`,
the label query fixed in `84f3800`. The other,
`AppContent.inaccessibleAppStrand.test.tsx`, failed on a network escape
to `localhost:3000` under full-suite load; this branch does not touch
it, and it passed alone. The suite was not re-run after `84f3800`, which
changed a test file and the changeset only; CI runs it.
## Files and surface
Every file is on the claim's file surface: `FlowCanvas.tsx`,
`flow-canvas-parts.tsx`, `flow-node-config.ts`, `FlowNodeInspector.tsx`,
tests in `previews/` and `inspectors/`, and
`.changeset/11778-flow-add-node-paths.md`. `i18n.ts` is not touched:
every string these entry points show already existed.
`StudioDesignSurface.tsx` is not touched.
Nothing is added to the package entry. After a build, none of the four
changed source modules is reachable from `dist/index.d.ts` by its
relative imports (168 declaration files reached; `SchemaForm.d.ts`,
which the entry re-exports, is the positive control). Patch changeset on
`@object-ui/app-shell`.
## Acceptance notes
- `content/docs/guide/flow-designer.md` still describes the card's "+"
as "Append a connected child node", and its palette section names only
the toolbar's Add node. It is not wrong, but it does not mention that
every "+" opens the palette or the edge "+". It is off the claim's
surface, so it is not edited here.
- The inspector now reads `GET /api/v1/automation/actions` twice per
mount (`useActionConfigSchemas` and `useFlowNodePalette` each run
`useActionDescriptors`). Sharing one read means changing
`useFlowNodePalette.ts`, which is off the surface; nearly every
inspector test stubs that module by name, so a combined hook would also
need those stubs changed.
- Offline, the Node Type list no longer offers `map`, which the hand
list had: `NODE_PALETTE` does not list it. The engine publishes a `map`
descriptor, so against a running backend the palette and the list both
offer it.
- When nodes around an insert carry dragged positions, the new node
takes its layout slot, not a point between its pinned neighbours.
- `main` moved to `d53fd02` after this branch was cut (objectui#11773,
objectui#11780). Neither commit touches the flow canvas or inspector
files; this branch has not merged it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01CGZy1BGCjdN5cXqL9cnvB8)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8273f4b commit 32124ac
17 files changed
Lines changed: 673 additions & 117 deletions
File tree
- .changeset
- packages/app-shell/src/views/metadata-admin
- inspectors
- previews
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
294 | 294 | | |
295 | 295 | | |
296 | 296 | | |
297 | | - | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
298 | 300 | | |
299 | 301 | | |
300 | 302 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
| |||
989 | 989 | | |
990 | 990 | | |
991 | 991 | | |
992 | | - | |
993 | | - | |
994 | | - | |
995 | | - | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
996 | 998 | | |
997 | 999 | | |
998 | 1000 | | |
999 | 1001 | | |
1000 | | - | |
| 1002 | + | |
1001 | 1003 | | |
1002 | | - | |
1003 | | - | |
1004 | | - | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
1005 | 1007 | | |
1006 | 1008 | | |
1007 | 1009 | | |
| |||
1040 | 1042 | | |
1041 | 1043 | | |
1042 | 1044 | | |
1043 | | - | |
| 1045 | + | |
1044 | 1046 | | |
1045 | 1047 | | |
1046 | 1048 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
| 72 | + | |
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
| |||
221 | 221 | | |
222 | 222 | | |
223 | 223 | | |
224 | | - | |
| 224 | + | |
225 | 225 | | |
226 | 226 | | |
227 | 227 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
66 | | - | |
| 66 | + | |
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| |||
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
239 | | - | |
| 239 | + | |
240 | 240 | | |
241 | 241 | | |
242 | 242 | | |
| |||
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
262 | | - | |
263 | | - | |
| 262 | + | |
| 263 | + | |
264 | 264 | | |
265 | 265 | | |
266 | | - | |
| 266 | + | |
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
45 | 44 | | |
46 | 45 | | |
47 | | - | |
48 | 46 | | |
49 | 47 | | |
50 | 48 | | |
| |||
55 | 53 | | |
56 | 54 | | |
57 | 55 | | |
58 | | - | |
| 56 | + | |
| 57 | + | |
59 | 58 | | |
60 | 59 | | |
61 | 60 | | |
| |||
178 | 177 | | |
179 | 178 | | |
180 | 179 | | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
181 | 183 | | |
182 | 184 | | |
183 | 185 | | |
| |||
443 | 445 | | |
444 | 446 | | |
445 | 447 | | |
446 | | - | |
447 | | - | |
448 | | - | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
449 | 457 | | |
450 | 458 | | |
451 | 459 | | |
| |||
482 | 490 | | |
483 | 491 | | |
484 | 492 | | |
485 | | - | |
| 493 | + | |
486 | 494 | | |
487 | 495 | | |
488 | 496 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
134 | | - | |
| 134 | + | |
135 | 135 | | |
136 | 136 | | |
137 | 137 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
369 | 369 | | |
370 | 370 | | |
371 | 371 | | |
372 | | - | |
373 | | - | |
374 | | - | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
375 | 376 | | |
376 | 377 | | |
377 | 378 | | |
| |||
0 commit comments