FE-502: Improve SDCPN file import/export with Zod validation and layout fix#8536
FE-502: Improve SDCPN file import/export with Zod validation and layout fix#8536kube wants to merge 1 commit intocf/petrinaut-copy-pastefrom
Conversation
- 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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
PR SummaryMedium Risk Overview Replaces the editor’s ad-hoc JSON import with a new Fixes imports of legacy converters under Written by Cursor Bugbot for commit 82c0b1c. This will update automatically on new commits. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
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; |
There was a problem hiding this comment.
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)
| ok: true, | ||
| sdcpn: fillMissingPositions(legacy.data), | ||
| hadMissingPositions: hadMissing, | ||
| }; |
There was a problem hiding this comment.
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)
🤖 Augment PR SummarySummary: 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:
Technical Notes: Imported files are validated with Zod, missing 🤖 Was this summary useful? React with 👍 or 👎 |
| const needsPosition = new Set<string>(); | ||
| if (options?.onlyMissingPositions) { | ||
| for (const place of sdcpn.places) { | ||
| if (place.x === 0 && place.y === 0) { |
There was a problem hiding this comment.
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:38libs/@hashintel/petrinaut/src/views/Editor/editor-view.tsx:147
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| } | ||
|
|
||
| // Fall back to legacy format | ||
| const legacy = legacySdcpnFileSchema.safeParse(data); |
There was a problem hiding this comment.
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
🤖 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({ |
There was a problem hiding this comment.
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
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.



🌟 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?
src/file-format/(export-sdcpn,import-sdcpn,remove-visual-info,old-formats/)clipboard/types.tsversion,meta.generator) to exported filesx/y, runs ELK layout before callingcreateNewNet(avoids a stale closure bug where positions were applied to the wrong net)onlyMissingPositionsoption tocalculateGraphLayoutfor partial re-layout of imported nodesImportErrorDialogshowing Zod validation errors with a "Create empty net" fallbackPre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
❓ How to test this?