Skip to content

FE-502: Improve SDCPN file import/export with Zod validation and layout fix#8536

Open
kube wants to merge 1 commit intocf/petrinaut-copy-pastefrom
cf/petrinaut-better-export-import
Open

FE-502: Improve SDCPN file import/export with Zod validation and layout fix#8536
kube wants to merge 1 commit intocf/petrinaut-copy-pastefrom
cf/petrinaut-better-export-import

Conversation

@kube
Copy link
Collaborator

@kube kube commented Mar 12, 2026

🌟 What is the purpose of this PR?

Fixes the bug where importing an SDCPN file exported without visual info breaks the editor (all nodes stacked at the same position). Also improves the file format with Zod validation and a versioned envelope.

🔗 Related links

🔍 What does this change?

  • Moves file format code into src/file-format/ (export-sdcpn, import-sdcpn, remove-visual-info, old-formats/)
  • Replaces manual type guards with Zod schemas for import validation, matching the pattern used for clipboard in clipboard/types.ts
  • Adds versioned file format envelope (version, meta.generator) to exported files
  • Fixes layout on import: when nodes are missing x/y, runs ELK layout before calling createNewNet (avoids a stale closure bug where positions were applied to the wrong net)
  • Adds onlyMissingPositions option to calculateGraphLayout for partial re-layout of imported nodes
  • Adds ImportErrorDialog showing Zod validation errors with a "Create empty net" fallback

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • does not modify any publishable blocks or libraries, or modifications do not need publishing

📜 Does this require a change to the docs?

The changes in this PR:

  • are internal and do not require a docs change

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

❓ How to test this?

  1. Export a net as "JSON without visual info"
  2. Import the exported file back
  3. Confirm all nodes are laid out by ELK (not stacked at origin)
  4. Import a malformed JSON file and confirm the error dialog appears

- Move file format code into `src/file-format/` (export, import, remove-visual-info, old-formats)
- Replace manual type guards with Zod schemas for import validation, matching clipboard types pattern
- Add versioned file format envelope (version, meta.generator) to exports
- Fix stale closure bug: run ELK layout before createNewNet so positions are baked into the data
- Add `onlyMissingPositions` option to calculateGraphLayout for partial re-layout
- Add ImportErrorDialog showing Zod validation errors with "Create empty net" fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Mar 12, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview, Comment Mar 12, 2026 11:21pm
petrinaut Ready Ready Preview Mar 12, 2026 11:21pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
hashdotdesign Ignored Ignored Preview Mar 12, 2026 11:21pm
hashdotdesign-tokens Ignored Ignored Preview Mar 12, 2026 11:21pm

@github-actions github-actions bot added area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team labels Mar 12, 2026
Copy link
Collaborator Author

kube commented Mar 12, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@kube kube marked this pull request as ready for review March 12, 2026 23:15
@cursor
Copy link

cursor bot commented Mar 12, 2026

PR Summary

Medium Risk
Changes the on-disk SDCPN JSON format and centralizes import/export with Zod validation, which could break compatibility with external files if schemas/fields diverge. Import now mutates missing coordinates via ELK layout, so layout behavior may change for some nets.

Overview
Adds a versioned SDCPN JSON file format (version, meta.generator) and updates export to always emit this metadata.

Replaces the editor’s ad-hoc JSON import with a new importSDCPN that Zod-validates both the new versioned format and the legacy unversioned format, normalizes missing x/y to 0, and surfaces failures via a new ImportErrorDialog instead of alerts.

Fixes imports of legacy converters under file-format/old-formats, and updates calculateGraphLayout to support onlyMissingPositions so imported nets with missing coordinates can be laid out without moving already-positioned nodes (using current node sizing from user settings).

Written by Cursor Bugbot for commit 82c0b1c. This will update automatically on new commits. Configure here.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.


const { sdcpn: loadedSDCPN, hadMissingPositions } = result;
const convertedSdcpn = convertOldFormatToSDCPN(loadedSDCPN);
let sdcpnToLoad = convertedSdcpn ?? loadedSDCPN;
Copy link

Choose a reason for hiding this comment

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

Old format conversion is dead code after Zod validation

Medium Severity

The call to convertOldFormatToSDCPN(loadedSDCPN) in handleImport is unreachable dead code that also represents a regression. Old format files (pre-2025-11-28) use different field names (type instead of colorId, differentialEquationCode instead of differentialEquationId), so they fail Zod validation in parseSDCPNFile before reaching the conversion call. Even if Zod somehow accepted them, it strips unknown keys like id, which isPre20251128SDCPN relies on. Previously, old format files could be imported and converted; now they're rejected with an error.

Additional Locations (1)
Fix in Cursor Fix in Web

ok: true,
sdcpn: fillMissingPositions(legacy.data),
hadMissingPositions: hadMissing,
};
Copy link

Choose a reason for hiding this comment

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

Legacy fallback silently bypasses file version check

Low Severity

The sdcpnFileSchema enforces version with .max(SDCPN_FILE_FORMAT_VERSION), but a file with an unsupported future version (e.g., version: 999) simply fails the versioned schema and then passes legacySdcpnFileSchema, which silently strips version and meta. This makes the version check ineffective — future-version files with compatible data structures are accepted without warning, potentially losing new fields that Zod strips as unknown keys.

Additional Locations (1)
Fix in Cursor Fix in Web

@augmentcode
Copy link

augmentcode bot commented Mar 12, 2026

🤖 Augment PR Summary

Summary: This PR updates Petrinaut’s SDCPN import/export to use a versioned JSON file format with Zod validation, and improves the import experience when node positions are missing.

Changes:

  • Moved SDCPN file IO helpers into a dedicated src/file-format module.
  • Added a v1 SDCPN file format with version and meta.generator metadata, and updated export to emit this payload.
  • Introduced Zod schemas for both the new versioned format and a legacy (unversioned) format.
  • Implemented a new promise-based importSDCPN() that returns structured success/error results.
  • Added an import error dialog in the editor UI instead of alert-based errors.
  • On import, detects missing node positions and runs ELK layout for those nodes prior to creating the net.
  • Extended calculateGraphLayout with an onlyMissingPositions option to return positions for a subset of nodes.

Technical Notes: Imported files are validated with Zod, missing x/y coordinates are filled with 0 to satisfy runtime types, and optionally re-laid out using ELK based on editor node dimensions (classic vs compact).

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

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

Review completed. 3 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

const needsPosition = new Set<string>();
if (options?.onlyMissingPositions) {
for (const place of sdcpn.places) {
if (place.x === 0 && place.y === 0) {
Copy link

Choose a reason for hiding this comment

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

In onlyMissingPositions mode, a node is treated as “missing” only when both x and y are 0, but the import path can produce partially-missing coordinates (e.g. x filled to 0 while y was present), which would then never get a new position. This can leave imported nodes stuck at a 0 coordinate even though hadMissingPositions triggered layout.

Severity: medium

Other Locations
  • libs/@hashintel/petrinaut/src/file-format/import-sdcpn.ts:38
  • libs/@hashintel/petrinaut/src/views/Editor/editor-view.tsx:147

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

}

// Fall back to legacy format
const legacy = legacySdcpnFileSchema.safeParse(data);
Copy link

Choose a reason for hiding this comment

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

parseSDCPNFile currently requires the file to match the current SDCPN shape (colorId, differentialEquationId, etc.) before convertOldFormatToSDCPN runs, so pre-2025-11-28 exports (which use fields like place.type) will fail validation and never be migrated. If the goal is to keep old-file import working, this validation step may be too strict for legacy inputs.

Severity: medium

Other Locations
  • libs/@hashintel/petrinaut/src/file-format/types.ts:10

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

/**
* Schema for the legacy file format (no version/meta, just title + SDCPN data).
*/
export const legacySdcpnFileSchema = sdcpnSchema.extend({
Copy link

Choose a reason for hiding this comment

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

legacySdcpnFileSchema is non-strict, so a file that includes version/meta but fails sdcpnFileSchema (e.g., version > supported) can still be accepted as “legacy” after those keys are stripped. That could silently import future/incompatible versioned files instead of surfacing an error.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@kube kube changed the title Improve SDCPN file import/export with Zod validation and layout fix FE-502: Improve SDCPN file import/export with Zod validation and layout fix Mar 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

1 participant