fix(calm-suite): preserve CALM document-level keys through Studio round-trip#2691
Closed
eddie-knight wants to merge 1 commit into
Closed
Conversation
…nd-trip
CALM Studio's model store held only { nodes, relationships }, so opening any
CALM document and saving silently dropped every other top-level section
($schema, document-level metadata, flows, adrs, decorators, document-level
controls). This broke the cross-tool round trip — a doc authored in the CLI/Hub
or any tool lost its flows/decorators/schema the moment it passed through Studio.
- applyFromJson now preserves all document-level keys; applyFromCanvas merges
(graph from the canvas, doc-level keys retained) so a canvas edit no longer
wipes them.
- Add a shared finalizeCalmForWrite(json) used by Save, Save As, and Export:
strips Studio-internal _template scratch and injects the canonical CALM 1.2
$schema when absent, so every written file is self-describing. AIGF governance
decorator generation stays Export-only (no date-stamp churn on Save).
- exportAsCalm now merges the AIGF decorator by unique-id instead of overwriting
the decorators array, preserving any existing decorators.
- Also fixes Studio's flow overlay for loaded documents: flowState reads
model.flows, which was always undefined for opened files.
Tests: store-level preservation through applyFromJson + applyFromCanvas for
$schema, flows, metadata, adrs, document-level controls, and decorators; the
flow-overlay regression guard; a full Save-path composition (getModelJson ->
finalizeCalmForWrite -> reparse); finalizeCalmForWrite ($schema inject/preserve,
_template strip); and decorator merge keep-path + re-export idempotency.
Signed-off-by: Eddie Knight <knight@linux.com>
Contributor
Author
|
My currently open PRs are stacked to avoid any merge conflicts or rebasing overhead. This can be taken out of draft when the preceding PR is merged: #2683 |
Closed
28 tasks
Closed
28 tasks
gjs-opsflo
approved these changes
Jun 23, 2026
gjs-opsflo
left a comment
Contributor
There was a problem hiding this comment.
Clean, surgical, well-scoped. Tests directly pin every guarantee the PR body makes.
Strengths
- AIGF kept Export-only. This was the most failure-prone refactor risk and the explicit comment +
does NOT add a governance decorator (AIGF is Export-only)test guards it. The AIGF merge byunique-id(filteringaigf-governance-overlay) is also a quiet upgrade — idempotent re-export, no overwrite of user decorators. $schemainjection gated on absence. A non-canonical existing$schemais preserved. Tested viapreserves an existing $schema untouched.finalizeCalmForWriteis pure + idempotent + handles malformed JSON. Right shape for a shared writer step.- All save paths covered. Verified
handleSave,handleSaveAs, Cmd+S / Cmd+Shift+S, TauriwriteTextFileroute through the wrapped handlers. No autosave or direct-write bypass. - Tests assert the contract, not the implementation. Inject-when-absent, preserve-when-present,
_templatestrip, AIGF-not-on-Save, malformed-JSON passthrough. Integration block walks the fullapplyFromJson → calmToFlow → applyFromCanvasround-trip for every doc-level section.
Minor follow-up suggestions (non-blocking)
- Nested doc-level keys shared by reference in
model = { ...model, nodes: [...], relationships: [...] }.metadata/flows/decorators/adrs/ document-levelcontrolscarry through by reference. Confirmed safe today (no in-place mutator exists), but worth a code comment so a future "edit metadata" UI doesn't introduce a stealth reactivity bug. $schemanot inCalmArchitecturetype. Currently lives in& { $schema?: string }casts. Would be cleaner to extend the canonical type in@calmstudio/calm-coreso callers don't keep re-casting. Trivial follow-up.- Calmscript export bypasses finalize — correct (text artifact, not CALM JSON), but worth a one-line comment so a future reader doesn't "fix" it.
LGTM as-is.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The journey this fixes: You open a CALM document in CALM Studio — one with
flows, document-levelmetadata,adrs,decorators, a$schema, and/or document-levelcontrols. You edit it (even just rearrange a node) and save. The saved file should differ from the original only by your edit.Today it doesn't. Studio's model store held only
{ nodes, relationships }, so the moment you open a document every other top-level section is dropped — and on save the file is rebuilt from that two-key model, so$schema,flows,adrs,decorators, document-levelmetadata, and document-levelcontrolsare silently gone. A document authored in the CLI/Hub (or anywhere) loses those sections the instant it passes through Studio, breaking the cross-tool round trip.After this PR, the full document round-trips:
$schema,metadata,flows,adrs,decorators,controls) survive open → edit → save.$schemawhen absent.decorators— it merges the managed AIGF overlay byunique-id.flowStatereadsmodel.flows, which was alwaysundefinedfor opened files before.What changed
applyFromJsonpreserves all document-level keys;applyFromCanvasmerges (graph from the canvas, doc-level keys retained), so a canvas edit no longer wipes them.finalizeCalmForWrite(json)used by Save / Save As / Export strips Studio-internal_templatescratch and injects the canonical$schemawhen absent. AIGF governance-decorator generation stays Export-only (no date-stamp churn on Save).exportAsCalmmerges the AIGF decorator byunique-idinstead of overwriting the array.Type of Change
Affected Components
(All unchecked — surface is CALM Studio (
calm-suite/calm-studio/), no template checkbox.)Commit Message Format ✅
Conventional Commits, DCO signed-off:
fix(calm-suite): preserve CALM document-level keys through Studio round-tripTesting
Verified on Node 22:
npm run test --workspace=@calmstudio/studio(all pass, incl. new preservation/finalize/decorator-merge coverage),npm run typecheck(no new errors vs the pre-existing baseline), andnpm run build. New tests cover preservation of$schema/flows/metadata/adrs/document-levelcontrols/decoratorsthroughapplyFromJsonand a subsequentapplyFromCanvas; the flow-overlay regression guard; the fullgetModelJson → finalizeCalmForWrite → reparseSave path;finalizeCalmForWrite($schemainject/preserve,_templatestrip); and decorator merge keep-path + re-export idempotency. Each guard was checked to fail on pre-fix code.Checklist