Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/decision-default-path-is-the-edge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
"@object-ui/app-shell": patch
---

Say what the Decision inspector actually does: the default path is the edge marker, not the branch.

Two help strings described mechanisms the engine does not have.

The **Branches** editor said a branch whose expression is `"true"` *is* the
default/else path. It is how you **ask** for one — `FlowEdgeInspector.applyBranch()`
turns such a branch into `isDefault: true` on the out-edge it wires, and the marker
on that edge is what routes. Conflating the two is the reading that let
objectstack-ai/objectstack#4414 ship a decision whose guard did not guard, and it is
worth being exact about now that `isDefault` is finally enforced: the key had **zero
readers** in the engine until then, so this designer had been writing a marker
nothing honoured, and every Studio "default/else" edge ran unconditionally alongside
whichever branch matched. The help also now states that branches are tried in order
and that the expression is bare CEL — a braced predicate there is a build failure
since objectstack-ai/objectstack#4439.

The legacy single **Condition** field said *"Prefer Branches above"*, which reads as
"this works, but the other is better". It does not work at all: the decision executor
never reads `config.condition`. The engine honours that key only on a Start node, as
the trigger gate, and `os validate` now reports it as `flow-inert-node-condition`.
The field stays render-only (its `__legacy__` controller never matches, so it is not
offered for new authoring) so a stored value is not invisible — but the help says it
is inert and where the predicate belongs instead.

Text only; no behaviour change on this side.
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,12 @@ const FLOW_NODE_CONFIG: Record<string, FlowConfigField[]> = {
],
decision: [
cfg('conditions', 'Branches', 'objectList', {
help: 'Each branch has a label, a CEL expression, and a target node. A branch whose expression is "true" is the default/else path. Picking a target wires the branch’s outgoing edge (creating or updating it); clearing it detaches that edge.',
// framework#4414: the default/else path is the out-edge marked
// `isDefault`, not the branch itself — a `true` branch is how you ask for
// one, and FlowEdgeInspector.applyBranch() writes the marker. Saying
// "a `true` branch IS the default path" conflated the two, which is the
// reading that let a decision ship with a guard that did not guard.
help: 'Each branch has a label, a CEL expression (no {braces}), and a target node. Branches are tried in order and the first match wins. A branch whose expression is "true" is the catch-all: picking its target marks that out-edge as the default path, taken only when no other branch matched. Picking a target wires the branch’s outgoing edge (creating or updating it); clearing it detaches that edge.',
columns: [
{ key: 'label', label: 'Label', kind: 'text', placeholder: 'Has deals' },
{ key: 'expression', label: 'Expression', kind: 'expression', placeholder: 'expiring_deals.length > 0' },
Expand All @@ -416,9 +421,16 @@ const FLOW_NODE_CONFIG: Record<string, FlowConfigField[]> = {
{ key: 'target', label: 'Target', kind: 'reference', placeholder: 'next node', ref: { kind: 'node' } },
],
}),
// Render-only for a stored legacy value (`__legacy__` never matches, so it
// is not offered for new authoring). The old help said "Prefer Branches
// above", which reads as "this works, but the other is better" — it does
// not work at all: framework#4414 confirmed the decision executor never
// reads `config.condition`. The key is the trigger gate on a `start` node
// and inert on every other node type, and `os validate` now reports it as
// `flow-inert-node-condition`.
cfg('condition', 'Condition (single)', 'expression', {
placeholder: 'amount > 10000',
help: 'Legacy single-branch condition (CEL). Prefer Branches above; per-edge conditions also live on the outgoing edges.',
help: 'Inert — nothing reads this. The engine only honours `condition` on a Start node (the trigger gate); on a Decision it gates nothing. Shown so a stored value is not invisible. Move the predicate to a branch above, or to the outgoing edge’s own condition, then clear this.',
showWhen: { field: '__legacy__', equals: [] },
}),
],
Expand Down
Loading