zudo-panel-designer docs
GitHub repository

Type to search...

to open search from anywhere

Defensive Parsing

parsePanelConfig() (packages/core/src/serialize.ts) is a total, non-throwing repair boundary for arbitrary JSON. tryParsePanelConfig() is the stricter import/autosave gate: it first requires app: "zpd", an integer version from 1 through 5, and a layers array, then delegates field recovery to parsePanelConfig().

Top-level recovery

InputResult
Not a plain objectThe complete createDefaultDoc() fixed-stack starter.
Object with missing/non-array layersA canonical three-container stack with empty children.
Missing/non-array guides[].
Invalid/missing/non-positive hpDEFAULT_PANEL_HP; values above MAX_PANEL_HP are clamped.
Hand-edited panel dimensions or paletteIgnored; they are derived advisory output.
Unknown keysDropped.

parsePanelConfig() treats an absent/non-integer version as legacy v4 for repair purposes. The strict sibling rejects a missing, future, or otherwise unsupported version instead.

Fixed-stack canonicalization

Every object input returns the same physical bottom-to-top roots:

  1. Copper

  2. Solder mask

  3. Silkscreen

For v1–v4 free-root documents, ordinary leaves and groups are partitioned by their legacy paint. A group spanning materials is split while preserving its ordinary shells, order, names, and visibility where possible. A path whose fill and stroke map to different materials is split into material-specific copies. Images, paintless paths, and empty groups default to Copper.

For v5, wrapper ids, labels, and order are not trusted. Valid roles are merged into the canonical roots, missing roles are synthesized, duplicate roles are combined, and recoverable children of malformed wrappers are legacy- partitioned. Every non-null vector paint is normalized to its final container membership. Fixed wrappers can never be recovered as selectable ordinary groups.

Ordinary nodes and ids

parseLayerNode() accepts ordinary kind: "group" nodes recursively up to MAX_GROUP_DEPTH, plus the five leaf types. Malformed/non-object nodes and unknown types are dropped independently. Unknown fields do not survive.

Ids are allocated deterministically across the whole ordinary tree. Missing or empty ids receive a deterministic type/group fallback; collisions with an earlier id or a fixed root receive stable numeric suffixes. Repeating the same parse produces byte-identical output. No time/random mintId() call occurs at this persistence boundary.

Common field behavior:

FieldRecovery
nameNon-string becomes "".
hiddenKept only when boolean; otherwise omitted.
finite geometry/rotationNon-finite values fall back or are omitted, depending on whether the field is required.
color, non-null fill/strokeInvalid indexes first fall back to 0, then final container membership normalizes them.
path fill/strokenull remains disabled; non-null remains enabled in the owning material.
path points/handles/subpathsInvalid entries are dropped individually; handles require finite x and y.
pattern paramsOnly finite numeric entries survive.
pattern geometryInvalid size falls back to safe panel-cover geometry and is capped by MAX_PATTERN_SIZE_MM.

Guides

A guide survives only with a valid horizontal/vertical orientation and finite position. Missing ids use a deterministic one-based guide-${sourceIndex + 1} fallback, then all explicit/generated collisions are repaired with stable suffixes. hidden is retained only when boolean. One malformed guide never drops the others.

Why two entry points?

  • parsePanelConfig() is useful for trusted migration and low-level recovery: it never throws and always returns a safe DocState.

  • tryParsePanelConfig() prevents a random JSON file, foreign app envelope, or future format from being silently accepted by the import UI and autosave. See Round-Trip → Importing.

The repaired result is still PanelConfig JSON, not Gerber data. Gerber files come from the separate artwork-only .zip export, which runs off the in-memory document rather than off this file.