Skip to content

fix(vscode): keep fields the canvas does not edit - #3566

Open
markscott-ms wants to merge 1 commit into
mainfrom
fix/3518-canvas-keeps-fields
Open

markscott-ms wants to merge 1 commit into
mainfrom
fix/3518-canvas-keeps-fields

Conversation

@markscott-ms

Copy link
Copy Markdown
Contributor

Description

Closes #3518.

CALM Canvas wrote each node and relationship again from only the fields that it edits. Thus, to open the canvas removed all other fields from the document (for example development-language, the interfaces of connects ends, and containment descriptions).

flowToCalm now starts from the original element and changes only the fields that the canvas edits. If the user clears one of those fields, it is removed.

Decisions and gotchas:

Case Behaviour
An end of a connects (or custom) relationship moves to a different node That end loses its interfaces, because they belong to the old node
A relationship that the canvas does not draw (for example connects with no destination.node) Kept as it is. A deleted edge stays deleted
interacts edges The parser now also copies protocol and controls. Both are valid on all relationship types
Order Relationships, top-level keys, containment children and edge metadata keys keep their order, so an unchanged document gives the same text, except metadata._layout

The canvas still emits a change when it opens, so the buffer becomes dirty because of _layout (#3552). The reverse relationship of an A→B/B→A pair now keeps its own controls and unedited fields, but it still gets the protocol, description and metadata of its pair (#3519).

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 ✅

Testing

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

A new round-trip test uses a document with each optional field and checks that parse, then flowToCalm, gives the same text. Other new tests cover edits, reconnected ends, relationships that are not drawn, containment and bidirectional pairs. calm/architecture/calm-3.json also gives the same text, except _layout.

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

@rocketstack-matt rocketstack-matt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two more cases where opening the canvas still changes the document.

const relId = (existingRel?.['unique-id'] as string) ?? `containment-${parentId}`;
calmRelationships.push({
const originalInner = originalVariant(existingRel, variant);
calmRelationships.push(applyCanvasFields(existingRel, {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#3518 expects the document not to change on open, other than _layout. This loop writes one relationship for each container, from the first match only. In all six examples/fluxnova architectures, the platform has one composed-of relationship for each node: after open, the first has all the nodes and keeps its description ("contains the BPM engine"), and the others are removed. Also, a node that is in two containers (composed-of a system and deployed-in a cluster) is drawn in one only, so the other relationship loses it.

Please write each original containment relationship back with its own nodes. Remove only a node that was drawn in that container and is not now, and add new children to the first relationship of the container.

Comment on lines +234 to +240
): Record<string, unknown> | undefined {
const lineStyle = (data.lineStyle as string) ?? 'solid';
const routing = (data.routing as string) ?? 'bezier';
const meta: Record<string, unknown> = { ...((data.edgeMetadata as Record<string, unknown>) ?? {}) };
if (lineStyle && lineStyle !== 'solid') meta['line-style'] = lineStyle;
if (routing && routing !== 'bezier') meta['routing'] = routing;
if (Object.keys(meta).length === 0) return undefined;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CALM allows metadata to be an array. The parser copies an array into an object, so on open "metadata": [{ "owner": "a" }] on a connects or interacts relationship becomes { "0": { "owner": "a" } }. The docify C4 fixtures in shared/test_fixtures have this form. #3552 covers only the architecture metadata. If the canvas did not change it, write the array back:

Suggested change
): Record<string, unknown> | undefined {
const lineStyle = (data.lineStyle as string) ?? 'solid';
const routing = (data.routing as string) ?? 'bezier';
const meta: Record<string, unknown> = { ...((data.edgeMetadata as Record<string, unknown>) ?? {}) };
if (lineStyle && lineStyle !== 'solid') meta['line-style'] = lineStyle;
if (routing && routing !== 'bezier') meta['routing'] = routing;
if (Object.keys(meta).length === 0) return undefined;
): Record<string, unknown> | unknown[] | undefined {
const lineStyle = (data.lineStyle as string) ?? 'solid';
const routing = (data.routing as string) ?? 'bezier';
const meta: Record<string, unknown> = { ...((data.edgeMetadata as Record<string, unknown>) ?? {}) };
if (lineStyle && lineStyle !== 'solid') meta['line-style'] = lineStyle;
if (routing && routing !== 'bezier') meta['routing'] = routing;
const originalMeta = original?.metadata;
if (Array.isArray(originalMeta) && JSON.stringify(meta) === JSON.stringify({ ...originalMeta })) return originalMeta;
if (Object.keys(meta).length === 0) return undefined;

Please also add an array metadata to the round-trip fixture.

CALM Canvas wrote each node and relationship again from the fields it
edits, so opening the canvas removed all other fields. Start from the
original element instead, keep relationships the canvas does not draw,
and keep the document order.

Closes #3518
@rocketstack-matt
rocketstack-matt force-pushed the fix/3518-canvas-keeps-fields branch from 9689eee to 701242b Compare November 6, 2026 15:22

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 removes interfaces, development-language and other fields from the architecture when it opens

2 participants