Skip to content

Nest decision boxes and containers in pattern visualisation (follow-up to #2859) #2933

Description

@YoofiTT96

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 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:

const parentId = node.decisionGroupId || parentMap.get(node.uniqueId);

#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:

if (groupId && !groupOptionsMap.has(groupId)) { groupOptionsMap.set(groupId, meta); break; }

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.

What changed under #2859

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:

decisionGroup:node-decision-items<-ROOT   group:host<-ROOT
custom:cache<-node-decision-items         custom:queue<-node-decision-items

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.
  • Stacks after feat(calm-hub-ui): render items catalogues in pattern diagrams #3110, which renders items catalogues.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions