You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When the pattern visualiser draws a diagram, every node gets exactly one parent box. A node can sit in a container (deployed-in / composed-of) or in a decision box, never in a decision box that is itself inside a container. That single-parent rule is why several ordinary patterns draw incorrectly, and why a decision can disappear from the diagram without any warning.
This issue replaces the rule.
Current behaviour
Verified by running parsePatternData against main. None of it is pinned by a test.
Shape
What draws
A decision whose candidates share a container
The decision box takes the candidates. The container draws empty beside it
A decision between containers
The box draws empty; the containers sit outside it
Two decisions over separate choices
Correct. Two boxes, both prompts
Two decisions naming the same candidate
One box. The second decision draws nothing — no box, no prompt
One decision with two option blocks
Two boxes, but only the first carries the prompt
A decision naming a node the pattern does not declare
Nothing, silently
A oneOf of relationships
Both edges draw. No box, so the choice is invisible
patternTransformer.ts resolves a parent in one line, decision first:
#2932 never merged, so none of its interim behaviour exists. There is no empty-box suppression in the file, and container precedence was never shipped.
The two shapes to support
A container holds a decision. The candidates are leaf nodes sharing one container. The decision box nests inside that container: container, then decision box, then candidates.
A decision chooses between containers. The candidates are themselves containers. The containers nest inside the decision box: decision box, then container, then contents. This is the natural way to author "choose which subsystem".
Also in scope
A candidate named by two decisions. A node has one parent, so one box cannot hold it twice. Today the first decision claims the group and the second is dropped entirely:
Any model this issue lands has to answer this case, because a single-parent tree cannot express it.
Three faults that are not about nesting. All three are "a decision that does not draw", and all three live in the code this issue rewrites.
Fault
Cause
A decision with two option blocks keeps only the first prompt
extractOptionsMetadata returns on the first block that yields choices
A decision naming an unknown node draws nothing, silently
No group matches it, and nothing reports that
A oneOf of relationships draws no box
extractRelationshipsFromPattern sets a group id, but nothing downstream reads a relationship's decisionGroupId
Decision group ids keyed by array index. A box is identified as node-decision-<index> from its position in prefixItems. Insert an entry above it and every box below is renumbered, so anything remembering a box by id follows the wrong box. An items catalogue already had to sidestep this with a fixed name, node-decision-items.
items catalogues now draw (PR #3110). That did not change the parenting model, but it makes one shape reachable that was not before: a catalogue node placed in a container. Verified on that branch:
So the container draws empty, exactly as the prefixItems case does. Whatever model this issue lands must cover catalogue-derived groups as well as prefixItems ones.
Design sketch
createReactFlowNodes already receives the three inputs a correct answer needs: each node's container parent (parentMap), its decision-group membership (decisionGroupId), and whether it is itself a container (containerNodeIds). Compute one "where does this group belong" answer per decision group, then assign parents top-down — container, then decision box, then leaves.
OptionsMetadata should carry the decision's unique-id. shared already returns optionId and the UI type drops it, which is why a decision is matched to a group today by guessing from its first candidate.
For layout, applyPatternLayout already sizes nested groups innermost-first via sortContainersDeepestFirst. Extend it to treat a decision box the same when it has a parent or has group children, rather than adding a second layout pass.
Consider a validation rule flagging a decision whose candidates neither all share one container nor are all containers themselves, so the visualiser only has the two clean shapes to draw.
Open questions
Question
Why it blocks
Support both nesting directions, or only decision-between-containers?
Decides how much of the parenting rewrite is needed
How should a candidate named by two decisions draw?
A box cannot hold a node twice. The alternative is to reject the pattern
Is inconsistent container membership an error, a warning, or drawn flat?
Decides whether the renderer must cope with a third shape
Notes
Pattern visualisation only. Architectures are drawn by calmTransformer.ts, which has no decision-group concept because the choices are already resolved.
patternTransformer.test.ts pins none of the behaviour in the table above.
test_fixtures/decision-agreement/ holds a fixture contract asserted by both shared and calm-hub-ui. Keep both sides passing.
Summary
When the pattern visualiser draws a diagram, every node gets exactly one parent box. A node can sit in a container (
deployed-in/composed-of) or in a decision box, never in a decision box that is itself inside a container. That single-parent rule is why several ordinary patterns draw incorrectly, and why a decision can disappear from the diagram without any warning.This issue replaces the rule.
Current behaviour
Verified by running
parsePatternDataagainstmain. None of it is pinned by a test.oneOfof relationshipspatternTransformer.tsresolves a parent in one line, decision first:#2932 never merged, so none of its interim behaviour exists. There is no empty-box suppression in the file, and container precedence was never shipped.
The two shapes to support
A container holds a decision. The candidates are leaf nodes sharing one container. The decision box nests inside that container: container, then decision box, then candidates.
A decision chooses between containers. The candidates are themselves containers. The containers nest inside the decision box: decision box, then container, then contents. This is the natural way to author "choose which subsystem".
Also in scope
A candidate named by two decisions. A node has one parent, so one box cannot hold it twice. Today the first decision claims the group and the second is dropped entirely:
Any model this issue lands has to answer this case, because a single-parent tree cannot express it.
Three faults that are not about nesting. All three are "a decision that does not draw", and all three live in the code this issue rewrites.
extractOptionsMetadatareturns on the first block that yields choicesoneOfof relationships draws no boxextractRelationshipsFromPatternsets a group id, but nothing downstream reads a relationship'sdecisionGroupIdDecision group ids keyed by array index. A box is identified as
node-decision-<index>from its position inprefixItems. Insert an entry above it and every box below is renumbered, so anything remembering a box by id follows the wrong box. Anitemscatalogue already had to sidestep this with a fixed name,node-decision-items.What changed under #2859
itemscatalogues now draw (PR #3110). That did not change the parenting model, but it makes one shape reachable that was not before: a catalogue node placed in a container. Verified on that branch:So the container draws empty, exactly as the
prefixItemscase does. Whatever model this issue lands must cover catalogue-derived groups as well asprefixItemsones.Design sketch
createReactFlowNodesalready receives the three inputs a correct answer needs: each node's container parent (parentMap), its decision-group membership (decisionGroupId), and whether it is itself a container (containerNodeIds). Compute one "where does this group belong" answer per decision group, then assign parents top-down — container, then decision box, then leaves.OptionsMetadatashould carry the decision'sunique-id.sharedalready returnsoptionIdand the UI type drops it, which is why a decision is matched to a group today by guessing from its first candidate.For layout,
applyPatternLayoutalready sizes nested groups innermost-first viasortContainersDeepestFirst. Extend it to treat a decision box the same when it has a parent or has group children, rather than adding a second layout pass.Consider a validation rule flagging a decision whose candidates neither all share one container nor are all containers themselves, so the visualiser only has the two clean shapes to draw.
Open questions
Notes
calmTransformer.ts, which has no decision-group concept because the choices are already resolved.patternTransformer.test.tspins none of the behaviour in the table above.test_fixtures/decision-agreement/holds a fixture contract asserted by bothsharedandcalm-hub-ui. Keep both sides passing.itemscatalogues.