zudo-panel-designer docs
GitHub repository

Type to search...

to open search from anywhere

PanelConfig Format

PanelConfig is the versioned JSON envelope the editor downloads as the panel order artifact. It is produced by serializePanelConfig() and consumed by parsePanelConfig(), both exported from @zpd/core (source: packages/core/src/serialize.ts).

Envelope shape

{
  "version": 1,
  "app": "zpd",
  "panel": { "hp": 12, "widthMm": 60.6, "heightMm": 128.5 },
  "palette": ["black", "gold", "white"],
  "layers": []
}
FieldTypeNotes
version1PANEL_CONFIG_VERSION. See Versioning below.
app"zpd"Fixed literal identifying the producing app.
panel.hpnumberPanel width in Eurorack HP, from the live document (doc.panelHp).
panel.widthMmnumberDerived from hp via panelWidthMm()advisory output, not re-trusted on import (see below).
panel.heightMmnumberPANEL_HEIGHT_MM, the fixed 3U Eurorack panel height (128.5mm).
palettestring[]The three fixed color names, in index order: ["black", "gold", "white"].
layersLayer[]The document's layer stack, bottom to top. See Layer fields.

Note

panel.widthMm, panel.heightMm, and palette are derived/advisory output for the human or order-system reader — they exist so a person or a downstream fab tool can read the file without recomputing dimensions fromhp. On the way back in, parsePanelConfig recomputes them from hp rather than trusting hand-edited values. panel.hp and layers are the authoritative source data.

Layer fields

All five layer types share a base shape plus type-specific fields.

Common to every layer

FieldTypeNotes
idstringStable layer id. Minted via mintId('layer') if missing on import.
namestringDisplay name. Defaults to "" if missing.
hiddenboolean (optional)Omitted while a layer has never been hidden. Once its visibility is toggled, the field is written explicitly — including false after an even number of toggles — since the toggle op flips whatever value is already there.
type'shape' | 'pattern' | 'path' | 'text' | 'image'Discriminates the fields below.

shape

FieldTypeNotes
shape'rect' | 'ellipse'
x, y, width, heightnumberMillimeters, document space.
rotationnumber (optional)Degrees clockwise around the bbox center. Omitted when unset.
color0 | 1 | 2See ColorIndex.

pattern

FieldTypeNotes
patternTypestringFree-form identifier (e.g. "dot-grid"). Kept verbatim even if unrecognized — core has no dependency on the patterns registry that would validate it.
paramsRecord<string, number>Pattern-specific numeric parameters (e.g. pitch, radius).
color0 | 1 | 2

path

FieldTypeNotes
pointsPathPoint[]Primary subpath — the one the pen tool edits. See PathPoint.
extraSubpathsPathPoint[][] (optional)Additional closed subpaths, e.g. holes/islands produced by image tracing. Presence/absence (even an empty array) is preserved exactly for round-tripping.
closedboolean
fill0 | 1 | 2 | nullnull means no fill.
stroke0 | 1 | 2 | nullnull means no stroke.
strokeWidthnumberMillimeters.

PathPoint

FieldTypeNotes
x, ynumberAnchor point, millimeters.
hin{ x: number, y: number } (optional)Absolute bezier handle-in coordinates.
hout{ x: number, y: number } (optional)Absolute bezier handle-out coordinates.

text

FieldTypeNotes
contentstringMay contain newlines.
fontFamilystring
sizeMmnumberFont size in millimeters (canvas font px equals mm in document space).
x, ynumberBounding-box top-left, millimeters.
rotationnumber (optional)Degrees clockwise. Omitted when unset.
color0 | 1 | 2

image

FieldTypeNotes
srcstringA data URL. Design-time reference only — a raster cannot be manufactured; the final panel uses the vector layers traced from it.
x, y, width, heightnumberMillimeters.

ColorIndex + palette

Every layer's color (and a path layer's fill/stroke) is a ColorIndex: 0, 1, or 2. The index maps to a fixed 3-color panel finish, defined once in packages/core/src/palette.ts:

IndexNameMeaning
0blackSoldermask
1goldExposed copper (ENIG-plated)
2whiteSilkscreen

The palette array in the envelope (["black", "gold", "white"]) is these names in index order — it exists so a reader of the JSON doesn't need to know the index-to-name mapping by heart. The palette is fixed; it is not user-configurable per document.

Versioning

PANEL_CONFIG_VERSION is currently 1, and the PanelConfig TypeScript type pins version: 1 — there is only one format generation so far. The field exists so a future format change has somewhere to signal itself: a later parser could branch on version to handle older files, or reject/ migrate a file whose version it doesn't recognize. Today, parsePanelConfig does not actually read or validate the incoming version field — see Defensive Parsing for why the parser is lenient by design rather than strict about the envelope shape.