Skip to content

feat(vscode): #2869 plugin updates (merged with main + lockfile fix) - #2930

Closed
LeighFinegold wants to merge 20 commits into
finos:mainfrom
LeighFinegold:pr-2912-merge-main
Closed

feat(vscode): #2869 plugin updates (merged with main + lockfile fix)#2930
LeighFinegold wants to merge 20 commits into
finos:mainfrom
LeighFinegold:pr-2912-merge-main

Conversation

@LeighFinegold

@LeighFinegold LeighFinegold commented Aug 3, 2026

Copy link
Copy Markdown
Member

Purpose

This raises byrash's CALM Canvas VS Code plugin rewrite (#2912) against main, merged with the latest main and with build fixes on top, so CI is green.

Raised at byrash's request per the discussion on #2912 (#2912 (comment) and the follow-up), where a maintainer offered to patch the build on top of the original work and byrash agreed ("Please do the required").

All of byrash's original commits are retained. Created via git worktree off the #2912 PR head, merged with latest main, with fix-ups added on top.

Commits

  • byrash's original PR commits (unchanged): CALM Canvas v1.0.0 visual editor
  • Merge of latest upstream main
  • fix(deps): regenerate lockfile for vscode plugin dependencies
  • ci(vscode): remove dead screenshots workflow
  • fix(deps): pin docusaurus runtime packages to 3.10.1
  • ci(vscode): drop integration-test job for removed harness
  • fix(vscode): build extension before packaging

Fixes

  1. Lockfile out of sync. The rewrite added new deps to calm-plugins/vscode/package.json (vite, @dagrejs/dagre, elkjs, lucide-react, ajv-formats, and others) but the root package-lock.json was never regenerated, so CI's npm ci failed. Regenerated via rm -rf node_modules package-lock.json && npm install on Node 26, preserving all platform-specific optional binaries (rollup/oxide/swc/esbuild Linux variants intact for validate-lockfile).

  2. docs build (useTitleFormatterContext called outside <TitleFormatterProvider>). Regenerating the lockfile let @docusaurus/theme-mermaid drift to 3.10.2, pulling a second @docusaurus/core + theme-common tree that mismatched the 3.10.1 tree used by the docs preset. Added overrides pinning @docusaurus/core, theme-common, and theme-mermaid to 3.10.1 (single consistent tree, matching main; faster stays 3.10.2 as on main).

  3. VS Code packaging (Extension entrypoint(s) missing). The rewrite changed the package script to run vsce package without building first and dropped the vscode:prepublish hook, so dist/extension.js did not exist at package time. Restored npm run build && in the package script and re-added vscode:prepublish (matching pre-rewrite behaviour).

  4. Orphaned CI. The rewrite deleted calm-plugins/vscode/screenshots/ and the test/integration harness (plus the test:integration script), but the workflows still referenced them:

    • Removed the dead build-vscode-screenshots.yml workflow, because its working-directory (calm-plugins/vscode/screenshots) no longer exists so every run failed before doing any work.
    • Removed the integration-test matrix job from build-vscode-extension.yml and dropped it from package-and-publish's needs, because the job invoked the now-deleted test:integration script and could only fail with Missing script, which also blocked the packaging job that depended on it.

Verification (Node 26)

  • npm ci: pass (was failing)
  • VS Code plugin build / test / lint: pass (155 tests, 0 lint errors)
  • docs build: pass
  • package:vscode: produces .vsix
  • Full PR CI on the fork: green

Note: PR only touches calm-plugins/ plus the root lockfile/overrides and two VS Code CI workflows. Pre-existing docs/calm-guard/calm-studio issues on main are unrelated.

Supersedes / continues #2912. Credit for the feature work goes to @byrash.

byrash and others added 20 commits July 29, 2026 09:04
…actFlow

Major rewrite of the VS Code extension from Mermaid-based preview to a
full visual architecture editor:

- Interactive ReactFlow canvas for editing CALM architectures
- Drag-and-drop node palette with workspace building blocks
- Standards/guidelines drag-onto-node with dotted "adheres to" edges
- Edge direction (unidirectional, bidirectional, none) and line styles
- Theme-aware UI following VS Code light/dark/high-contrast themes
- Real-time bidirectional sync between JSON editor and canvas
- Validation panel with clickable issues
- Drill-down navigation into building blocks with breadcrumbs
- SVG export, pattern application, and spec generation
- Configurable extension packs (calm.packs.enabled, calm.packs.excludeNodes)
- Edge metadata (key-value pairs) support
The vscode plugin PR added new dependencies (vite, @dagrejs/dagre, elkjs, lucide-react, ajv-formats, etc.) to calm-plugins/vscode/package.json but the root package-lock.json was not regenerated, so 'npm ci' fails in CI. Regenerated the lockfile from a clean install (rm -rf node_modules package-lock.json && npm install) on Node 26, preserving all platform-specific optional binaries.
The vscode plugin rewrite deleted calm-plugins/vscode/screenshots/, but the build-vscode-screenshots.yml workflow remained. It triggers on changes under that path and fails because its working-directory no longer exists. Removing the orphaned workflow.
Regenerating the lockfile let @docusaurus/theme-mermaid drift to 3.10.2, pulling a second @docusaurus/core + theme-common tree that mismatched the 3.10.1 tree used by the docs preset. This broke 'docs' build with 'useTitleFormatterContext called outside <TitleFormatterProvider>'. Added overrides pinning @docusaurus/core, theme-common and theme-mermaid to 3.10.1 to match upstream main (single consistent tree; faster stays 3.10.2 as on main).
The plugin rewrite removed the test/integration harness and the 'test:integration' script, so the integration-test matrix job failed with 'Missing script: test:integration'. Removed the job and updated package-and-publish needs accordingly.
The rewrite changed the 'package' script to run 'vsce package' without first building, and dropped the 'vscode:prepublish' hook. As a result 'package:vscode' produced no dist/extension.js and vsce failed with 'Extension entrypoint(s) missing'. Restored 'npm run build &&' in the package script and re-added the standard vscode:prepublish hook (matching pre-rewrite behaviour).
@LeighFinegold
LeighFinegold requested a review from a team as a code owner August 3, 2026 08:23
@github-actions github-actions Bot added the config label Aug 3, 2026

@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.

Reviewed the rewrite plus the 5 build-fixup commits. The fixups (lockfile regen, docusaurus pin, dead-workflow removal, integration-test job removal, build-before-package fix) are correct — verified by rebuilding and packaging the extension from this branch myself. The findings below are in the carried-over feature code, mostly data-loss/correctness bugs on save plus a couple of path-handling issues, and one open scope question that wasn't explicitly resolved in the #2912 discussion.

if (!arch || !arch.nodes || arch.nodes.length === 0) {
setNodes([]); setEdges([]); setLastParsedArch(null); return;
}
setLastParsedArch(arch);

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.

documentControls defaults to {} and is never hydrated from the loaded document's controls field here. flowToCalm only writes arch.controls back out when documentControls is non-empty, so any document with solution-level controls silently loses them on the first save.

Suggested change
setLastParsedArch(arch);
setLastParsedArch(arch);
useCanvasStore.setState({ documentControls: ((arch as Record<string, unknown>).controls as Record<string, unknown>) ?? {} });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You.

}

// Build edge relationships
for (const edge of edges) {

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.

This loop builds one relationship per edge and reuses data.calmRelId as unique-id for all of them. calm-parser.ts splits a single multi-target interacts (actor + nodes: [B, C]) into N edges that all share one calmRelId — so a document with a multi-target interacts relationship round-trips into N relationships with the same unique-id, which is invalid per the CALM schema. Needs to group edges by calmRelId (for the interacts variant) back into one relationship with multiple nodes before pushing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You.

// Build edges from non-containment relationships
interface ConnectsEntry { relId: string; src: string; dst: string; protocol?: string; description?: string; controls?: unknown; lineStyle: string; routing: string; edgeMetadata: Record<string, unknown> }
const connectsList: ConnectsEntry[] = [];
for (const rel of arch.relationships ?? []) {

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.

Only connects, interacts, deployed-in and composed-of are turned into edges here, and calm-editor-transformer.ts rebuilds relationships purely from edges with no fallback for anything else. A relationship with any other relationship-type (e.g. a decorator-style or custom type) never becomes an edge and is silently deleted the first time the document is saved. Needs a passthrough for unrecognized types from lastParsedArch when rebuilding.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You.

await vscode.workspace.fs.createDirectory(buildingBlocksDir);
}

const fileUri = vscode.Uri.joinPath(buildingBlocksDir, filename);

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.

filename comes straight from a webview message with no validation before being joined into building-blocks/. A crafted filename (e.g. ../../foo.ts) writes content outside building-blocks/, potentially outside the workspace entirely — arbitrary file write.

Suggested change
const fileUri = vscode.Uri.joinPath(buildingBlocksDir, filename);
const safeName = filename.replace(/[/\\]/g, '');
if (!safeName || safeName !== filename || filename.includes('..')) {
vscode.window.showErrorMessage(`Invalid building block filename: ${filename}`);
return;
}
const fileUri = vscode.Uri.joinPath(buildingBlocksDir, safeName);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You.

metadata: n.metadata,
containmentType: containerVariant.get(id),
},
...(parent ? { parentId: parent, extent: 'parent' as const } : {}),

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.

Nodes parsed from the document get parentId/extent: 'parent' here but no zIndex, whereas containment.ts#makeContainment (used when a user manually drags a node into a container) explicitly sets zIndex: depth. Without that, a node that's already nested when the file loads can sit at/below its parent container's stacking order, so a drag on the child hits the container underneath instead. This matches manual testing against the TraderX architecture from CALM Hub: some contained nodes aren't draggable — dragging them moves the container (e.g. "Web GUI", "TraderX DB") instead. Needs the same depth-based zIndex assigned on initial parse, not just on manual re-parenting.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You. As a side note format in calm hub is using draft version schema and in later iteration we will have to support new version when published in's code extension.

Comment on lines +429 to +431
dispose(): void {
if (this.debounceTimer) clearTimeout(this.debounceTimer);
}

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.

dispose() only clears the debounce timer — this.watchers (pushed in registerWatchers) is never disposed. Since a WorkspaceAssetService is created per CanvasPanel, every open/close cycle leaks 6×N watchers for the life of the extension, and the leaked watchers keep firing rescans against already-closed panels.

Suggested change
dispose(): void {
if (this.debounceTimer) clearTimeout(this.debounceTimer);
}
dispose(): void {
if (this.debounceTimer) clearTimeout(this.debounceTimer);
for (const watcher of this.watchers) watcher.dispose();
this.watchers = [];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You.

Comment on lines +293 to +295
private stem(uri: vscode.Uri): string {
const base = path.basename(uri.fsPath);
return base.replace(/\.calm\.json$/, '').replace(/\.md$/, '');

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.

stem() strips .calm.json and .md but not .architecture.json, even though scanBuildingBlocks explicitly globs *.architecture.json files too. Building blocks sourced from an .architecture.json file get an id like foo.architecture.json instead of foo, inconsistent with their .calm.json siblings.

Suggested change
private stem(uri: vscode.Uri): string {
const base = path.basename(uri.fsPath);
return base.replace(/\.calm\.json$/, '').replace(/\.md$/, '');
private stem(uri: vscode.Uri): string {
const base = path.basename(uri.fsPath);
return base.replace(/\.(calm|architecture)\.json$/, '').replace(/\.md$/, '');
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Adding support for .calm.json, .architecture.json, .template.json, .solution.json, .standard.json, .guideline.json types. Whilst we were wondering on how we could potential have node names enforced for consistency. We talk more about that in our WG. Thanks!

<body>
<div id="root"></div>
<script nonce="${nonce}">
window.__INITIAL_CALM_JSON__ = ${initialJson};

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.

JSON.stringify doesn't escape </script>. If the opened document's raw text contains that literal substring anywhere (e.g. inside a description field), it closes this script block early and injects the remainder as HTML into the page. The CSP nonce blocks a new <script> from executing, but not the markup injection itself.

Suggested change
window.__INITIAL_CALM_JSON__ = ${initialJson};
window.__INITIAL_CALM_JSON__ = ${initialJson.replace(/</g, '\\u003c')};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We shall address and push a fix soon. Thank You.

this.disposeCallbacks.push(callback);
}

dispose(): void {

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.

dispose() calls this.panel?.dispose(), which fires onDidDispose synchronously and re-enters dispose() before this.panel = undefined runs. disposeCallbacks isn't cleared, so every registered callback fires twice per panel close. Harmless today since the callbacks look idempotent, but fragile — worth guarding with a private disposed = false flag checked at the top of the method.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's harmless today since the callbacks are idempotent, but it's fragile, we will introduce a safe dispose option. Thanks!

let canvasPanel: CanvasPanel | undefined;
let outputChannel: vscode.OutputChannel;

const CALM_FILE_SUFFIXES = [

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.

This narrows file recognition to a fixed suffix list (.calm.json, .architecture.json, etc.), dropping the old plugin's looser matching (a plain architecture.json, or generic JSON under calm/, is no longer picked up). This was flagged by @LeighFinegold in the #2912 discussion as one of the "likely minor" parity gaps but wasn't explicitly agreed as an accepted direction before merge — worth a maintainer decision here rather than merging it as an unstated behavior change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have introduced this consciously to allow for where plugin should be activate for. Happy to discuss this in WG. Thanks!

@byrash

byrash commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Hello @LeighFinegold for fixes to above noted items, We will have to push changes to our repo. I shall uptake your changes into my code and will push into my PR. Once that's done, I can let you know and you can close this PR. Thank You.

@byrash

byrash commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Hello @rocketstack-matt all the above mentioned issues are addressed and push into #2912 . @LeighFinegold you may now close this PR. Thank You.

@byrash byrash mentioned this pull request Aug 4, 2026
28 tasks
@LeighFinegold

Copy link
Copy Markdown
Member Author

Hello @LeighFinegold for fixes to above noted items, We will have to push changes to our repo. I shall uptake your changes into my code and will push into my PR. Once that's done, I can let you know and you can close this PR. Thank You.

Sure. Sounds good to me.

@LeighFinegold

Copy link
Copy Markdown
Member Author

Closing as @byrash now has managed to update his original PR at source.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants