DesignIR v1¶
DesignIR v1 is the canonical interchange format for design import. Source adapters may accept older shortcut shapes, but the public writer emits this versioned envelope:
{
"version": 1,
"source": "figma",
"sourceFile": "design.json",
"capture_method": "adapter_parse",
"settle_rounds": 0,
"fallback_reason": "",
"source_adapter": "figma",
"source_version": "1",
"imported_at": "2026-05-21T09:07:34Z",
"root": {},
"tokens": {},
"assetManifest": { "version": 1, "assets": [] },
"diagnostics": []
}
Round-trip compatibility is canonical equivalence, not byte identity:
- Parse a legacy or source-adapter shape.
- Normalize to
DesignIR. - Serialize with
serialize_design_ir(). - Parse that canonical JSON with
parse_design_ir_json(). - Compare the canonical serialization of both parsed documents.
The permissive reader accepts both camelCase and snake_case for source metadata fields where older adapters already emitted one form. The canonical writer uses the names listed below.
Document Metadata¶
The v1 envelope carries import provenance at the document level so later normalizers, resolvers, and materializers can reason about how the tree was captured:
| Field | Type | Meaning |
|---|---|---|
capture_method |
string | adapter_parse for static adapters or runtime_snapshot for runtime materialization. |
settle_rounds |
integer | Runtime pump rounds completed before snapshot capture. |
fallback_reason |
string | Why a runtime capture fell back to a static adapter, if it did. |
source_adapter |
string | Adapter/runtime that produced the document. |
source_version |
string | Adapter/runtime schema version. |
imported_at |
string | Import timestamp when produced by the CLI. |
Node Metadata¶
Each node keeps enough provenance for downstream diffing and re-import:
| Field | Type | Meaning |
|---|---|---|
stable_anchor_id |
string | Stable tweak/re-import anchor. |
anchor_strategy |
string | adapter, content-hash, or path. |
source_node_id |
string | Source-native node ID when available. |
source_adapter |
string | Adapter that produced the node. |
source_version |
string | Adapter schema/parser version. |
confidence |
string | pass, diverge, or not_impl. |
raw_source |
string | Optional source snippet for diagnostics. |
Layout¶
IRLayout v1 includes the existing flex fields plus the fields needed by
static design adapters and later baked materializers:
| Field | Type |
|---|---|
display |
string |
direction |
row or column |
gap, rowGap, columnGap |
number |
paddingTop, paddingRight, paddingBottom, paddingLeft |
number |
marginTop, marginRight, marginBottom, marginLeft |
number |
justify, align, alignSelf, alignContent |
string |
wrap |
bool |
flexGrow, flexShrink, aspectRatio |
number |
flexBasis |
string |
order |
integer |
overflowX, overflowY |
string |
widthMode, heightMode |
fixed, hug, or fill |
Style¶
IRStyle v1 carries CSS-like visual fields in their resolved form:
| Field | Type |
|---|---|
backgroundColor, backgroundGradient, backgroundImage, backgroundRepeat |
string |
color, opacity |
string / number |
border, borderColor, borderWidth, borderStyle |
string / number |
borderTopColor, borderRightColor, borderBottomColor, borderLeftColor |
string |
borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth |
number |
borderRadius, borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius |
number |
boxShadow, filter, backdropFilter |
string |
fontFamily, fontStyle, textAlign, textTransform, textDecoration, whiteSpace, textOverflow |
string |
fontSize, fontWeight, letterSpacing, lineHeight |
number |
overflow, cursor, position, transform |
string |
top, left, right, bottom, zIndex |
number |
width, height, minWidth, minHeight, maxWidth, maxHeight |
number |
Token Identity¶
Resolved theme tokens remain flat (colors, dimensions, strings) so
existing runtime consumers keep working. Source identity is preserved in a
sidecar map keyed by canonical token path:
{
"tokens": {
"colors": { "accent": "#57a6ff" },
"sourceIdentity": {
"colors.accent": {
"sourceId": "var-accent",
"sourceCollection": "palette",
"sourceMode": "dark",
"sourceAdapter": "figma"
}
}
}
}
Asset Manifest¶
Every canonical DesignIR carries an asset manifest. It records local files,
HTTP(S) resources, CSS url(...) references, font URLs, and inline data URIs:
{
"version": 1,
"assets": [
{
"asset_id": "asset-...",
"original_uri": "meter.png",
"local_path": "/abs/path/meter.png",
"content_hash": "sha256-hex",
"mime": "image/png",
"width": 640,
"height": 360,
"font_family": "Inter",
"license": "",
"source_url": "",
"diagnostics": []
}
]
}
Network assets are not fetched by default. pulp import-design --emit ir-json
requires --allow-network-fetch for first-time HTTP(S) resolution. Fetched
bytes are cached by content hash, indexed by URL, and subsequent imports verify
any expected hash supplied with --asset-hash <uri=sha256>. When the import
source came from --url, relative asset references resolve against that source
URL; the manifest keeps the authored value in original_uri and stores the
resolved fetch target in source_url.
Phase 1 does not auto-load a previous manifest to detect remote drift. Callers
that need drift detection should pass the prior manifest's hash back through
--asset-hash; cache hits still verify that expected hash and emit
asset-hash-mismatch on disagreement.
External files and URLs keep distinct manifest entries even when their bytes
match, so diagnostics and provenance remain tied to the authored resource.
Data URIs are recorded with their decoded content hash so duplicated inline
assets can be deduplicated by later baked outputs. Nodes preserve their raw
URI attributes and may receive a parallel *AssetId attribute such as
srcAssetId or backgroundImageAssetId for stable manifest references.
Diagnostics¶
Import diagnostics are structured and serializable. The top-level
diagnostics array carries document-level warnings/errors; asset entries may
also carry diagnostics scoped to that asset.
{
"severity": "warning",
"kind": "snapshot_semantics_warning",
"code": "snapshot-dynamic-api",
"path": "$.root",
"message": "JSX baked snapshot references dynamic APIs: setInterval",
"anchor_id": "root",
"property": "snapshotSemantics"
}
Known kind values are unsupported_property, unresolved_asset,
snapshot_semantics_warning, legacy_field_shortcut, capture_partial,
fallback_used, and unknown. Legacy diagnostics that only include code
are accepted; the reader infers known kinds from established diagnostic codes.
Normalization¶
parse_design_ir_json() and the source adapters run shared normalization before
returning a DesignIR. In v1.5 that includes interactive-frame promotion:
frame nodes with onclick/onClick, role="button", or cursor: pointer
are promoted to button unless role="presentation" explicitly opts out of
the cursor heuristic. Adapters that assign stable anchors promote before anchor
assignment so content-hash anchors reflect the normalized node type.
C++ API¶
std::string serialize_design_ir(const DesignIR&, const DesignIrJsonOptions& = {});
DesignIR parse_design_ir_json(const std::string& json);
IRAssetManifest collect_design_ir_assets(const DesignIR&, const DesignIrAssetOptions& = {});
void refresh_design_ir_asset_manifest(DesignIR&, const DesignIrAssetOptions& = {});
WidgetPromotionSignal classify_interactive_signal(const IRNode&);
std::size_t promote_interactive_frames(IRNode&);
SnapshotDynamicApiScan detect_jsx_snapshot_dynamic_apis(std::string_view source);