Skip to content

fix(vscode): keep every container of a node with several parents - #3574

Open
caisindenis wants to merge 17 commits into
finos:mainfrom
caisindenis:fix/vscode-canvas-multi-parent
Open

caisindenis wants to merge 17 commits into
finos:mainfrom
caisindenis:fix/vscode-canvas-multi-parent

Conversation

@caisindenis

@caisindenis caisindenis commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

Closes #3571. Related to #3569 and #3570.

CALM Canvas kept one container per node and rebuilt deployed-in/composed-of from it on save. A node in several containers (for example a namespace in two clusters) lost all but one membership from the file after any canvas edit.

Now the canvas draws the node, with everything nested in it, in each container:

  • The drawing in the container that main used (the last one that does not close a cycle) keeps the CALM id, so positions saved in _layout still apply. Each other drawing is a copy with its own React Flow id and the CALM id in data.calmId.
  • On save, copies map back to the CALM id and each node is written once. Each containment relationship is written back with its id, place and fields. Memberships the canvas does not draw (a container that is not a node, a cycle, a self reference, a parent that is already an ancestor) are kept.
  • Edits to a copy (properties, controls) apply to every drawing. Deleting or unparenting a drawing removes that membership from every drawing of it, and its edges move to a remaining drawing. Deleting a container also deletes the nodes nested in it, except a node that is also in a container that stays. A node dragged into a container is drawn in every drawing of it. When the diagram has copies or memberships that are not drawn, these edits rebuild the canvas from the file, with one save. Positions carry over by each drawing's path of CALM ids.
  • Edges stay on the drawing with the CALM id. The canvas is an editor, so it does not duplicate relationships.
  • A Standard dropped on any drawing adds its controls to the CALM node, so every drawing shows them.
  • Limit: copies multiply with each level of shared containers. Above 1,000 copies, each node is drawn in its last container only, the canvas shows a notice, and the save still writes its other memberships.

Known limit, as on main: dagre fails on an edge to a container that has children, and the canvas falls back to a grid. Copies make such containers more common.

Also fixed while here:

Change Before After
Node listed as its own container, or a containment cycle The parser looped forever The self or cyclic parent is not drawn, and the save keeps it
Node listed in a container and in a container inside it Drawn in the last container Drawn in the inner container, in each drawing of it. The save keeps both. A position saved for it is now relative to the inner container
Child listed before its container in the file Child placed before its parent in the React Flow array Containers first. The save still writes nodes in file order
Containment relationships on save Grouped by container; extra fields and memberships that were not drawn were lost Each relationship written back with its id, place and fields. A relationship that an edit leaves empty is dropped when its container has another one
Two nodes with the same unique-id (not valid CALM) Both written The first is written

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📚 Documentation update
  • 🎨 Code style/formatting changes
  • ♻️ Refactoring (no functional changes)
  • ⚡ Performance improvements
  • ✅ Test additions or updates
  • 🔧 Chore (maintenance, dependencies, CI, etc.)

Affected Components

  • CLI (cli/)
  • Schema (calm/)
  • CALM AI (calm-ai/)
  • CALM Hub (calm-hub/)
  • CALM Hub UI (calm-hub-ui/)
  • CALM Lab (calm-lab/)
  • CALM Models (calm-models/)
  • CALM Server (calm-server/)
  • CALM Studio (calm-studio/)
  • CALM Widgets (calm-widgets/)
  • CALMGuard (calm-guard/)
  • Documentation (docs/)
  • Shared (shared/)
  • VS Code Extension (calm-plugins/vscode/)
  • Dependencies
  • CI/CD

Commit Message Format ✅

fix(vscode): keep every container of a node with several parents

Testing

  • I have tested my changes locally
  • I have added/updated unit tests
  • All existing tests pass

Written test-first: parser, save round-trip, canvas edit helpers, and new App tests for delete, unparent, rename and drop on copies. Fresh-context reviews ran, and each finding was fixed with a test that fails without the fix. On Node 26, npm run build, npm test (all workspaces) and npm run lint pass.

Checklist

  • My commits follow the conventional commit format
  • I have updated documentation if necessary
  • I have added tests for my changes (if applicable)
  • My changes follow the project's coding standards

🤖 Generated with Claude Code

CALM Canvas kept one parent per node and rebuilt containment from it on
save, so a node in several containers lost all but one membership.

The canvas now draws the node in each container. Copies share the CALM id,
are written once, and edits apply to every copy.

Closes finos#3571

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@caisindenis
caisindenis requested a review from a team as a code owner October 29, 2026 03:27
caisindenis and others added 12 commits October 29, 2026 12:38
Copies multiply with each level of shared containers. Above 1000 drawings,
each node is drawn in its first container only, and the save still writes
its other memberships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A large architecture with no shared nodes no longer disables copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saved positions in _layout are keyed by node id, and main drew a
node in its last container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… drawings

Write each containment relationship back with its id, place and
fields, and keep memberships the canvas does not draw (a container
that is not a node, a cycle, a self reference, a redundant parent).
Each node's first container is the one main drew it in. Deleting or
unparenting one drawing removes its membership from every drawing,
and a drop into a container with several drawings rebuilds the canvas
from the file. Paste no longer copies hidden memberships.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

When the diagram has copies or memberships that are not drawn, a
delete or unparent rebuilds the canvas from the file, so the canvas
and the file agree. Positions carry over by each drawing's path of
CALM ids, because a drawing can take the CALM id from one that goes.
Write the layout in file order, resolve definitions once per CALM
node, and remove a loop that could no longer change anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The schema needs at least one member. Drop a relationship that an edit
left empty when its container has another one or is not a node, and give
a relationship with no unique-id the id main gave it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…her container

React Flow passes the drawings nested in a deleted drawing too, and each
was treated as a pick, so other drawings of them went too. A nested node
that is also in a container that stays is now kept. A delete that reloads
the canvas saves once, and a standard dropped on a copy links the node
once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vas edits

A standard dropped on a copy linked the copy when the DOM found the
drawing, so a second drop added the relationship again. Delete, unparent,
drop, paste, validation badges and definition lookups for nodes in
several containers are now pure functions in canvas-edits, tested by
round trips through the parser and the save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts remove changes

React Flow calls onNodesChange with a remove change for every deleted
drawing after onNodesDelete, so a nested node that a delete kept was
removed again. Add a test that renders the canvas and plays the React
Flow callbacks in order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ere it was

React Flow removes the edges of deleted drawings before onNodesDelete,
and that save ran before the reload, so a delete with edges saved twice.
A node a delete kept was placed at its position relative to its old
container. A node with no unique-id kept its place in the file only by
chance. Remove the drop reload: a drop always lands in the outer
container, which has one drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each new test fails when its behaviour is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@markscott-ms

Copy link
Copy Markdown
Contributor

@caisindenis please resolve the merge conflicts and then we'll take a look. You may also want to ensure any fixes that were done on #3570 and also need applied here are done.

caisindenis and others added 3 commits November 2, 2026 23:58
…ulti-parent

A standard dropped on a node now adds its controls to every drawing of
that node, so a drop on a copy is saved. linkTargetOf had no user left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… container

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s and drags

The notice moves to the canvas store, because a state setter in the load
effect fails lint. A drag into a container rebuilds the canvas when the
diagram has copies, as delete and unparent do. A standard drop resolves
each control once per node, not once per drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@caisindenis

Copy link
Copy Markdown
Contributor Author

Done. 90920fd merges main and resolves the conflicts with #3568: a Standard dropped on a copy now adds its controls to every drawing of the node.

From #3570, the edge fixes do not apply here, because the canvas keeps each edge on one drawing. The copy limit notice does apply: 9e420ae and 537fc6a show it on the canvas.

@caisindenis

Copy link
Copy Markdown
Contributor Author

@caisindenis please resolve the merge conflicts and then we'll take a look. You may also want to ensure any fixes that were done on #3570 and also need applied here are done.

@markscott-ms this is done

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CALM Canvas deletes container memberships of a node with several parents

2 participants