Figma·HTML 같은 서로 다른 디자인 포맷을 한 스키마로 정규화하는 포맷 중립 문서.
코드상 타입명은 DesignDoc 이다. 담는 것은 레이아웃 의도(layout-intent)이고,
렌더링 방법 — 이를테면 차트를 어떻게 그리는가 — 은 담지 않는다.
문서 하나가 파일 하나다. 페이지는 Figma 프레임 · HTML 페이지와 1:1 이고, 그 안은 노드 트리 하나다.
DesignDoc 문서 하나 = 파일 하나 ├─ version IR 스키마 버전 (현재 0.10.0) ├─ tokens? 문서 공통 색 · 글자 크기 ├─ components[] 재사용 정의 — instance 가 가리킨다 └─ pages[] 페이지 = Figma 프레임 = HTML 페이지 (1:1) ├─ size {w, h?} ├─ style? 페이지 자체 칠 (fills · opacity 만 의미가 있다) ├─ background? 순서를 바꿀 수 없는 배경 슬롯 (노드 하나) └─ root 노드 트리의 뿌리 — 보통 layout └─ children[] 자식을 갖는 것은 layout · diagram 뿐
.ir.json 과 .ir.xml 은 서로 다른 포맷이 아니라 같은 값의 다른 적기다.
같은지는 표기가 아니라 정규 직렬화가 판정한다.
| 키 | 뜻 | 값 |
|---|---|---|
version | IR 스키마 버전. 새 필드는 전부 optional 로 들어오므로 옛 문서는 계속 유효하다. 반대로 새 문서를 옛 코드가 읽으면 모르는 필드가 _unknown 에 남아 적합성에서 걸린다 — 버전 스큐가 드러나는 자리 | 문자열 (현재 0.10.0) |
pages | 페이지 배열. 비어 있으면 문서가 아니다 | Page[] |
components | 재사용 정의. instance.def 가 이 id 를 가리킨다 | {id, name, root}[] |
tokens | 문서 공통 값. colors 는 이름→#hex, fontSizes 는 이름→px | {colors?, fontSizes?} |
_unknown | 읽을 때 몰랐던 필드를 그대로 담아 두는 자리. 쓸 때 다시 펼쳐진다. 값이 있으면 적합성 검사가 WARP_UNKNOWN_FIELDS 로 신고한다 — 손실을 막는 장치이지 정상 상태가 아니다 | 객체 |
| 키 | 뜻 | 값 |
|---|---|---|
id · name | 문서 안에서 유일한 식별자 · 사람이 읽는 이름 | 문자열 |
size | 캔버스 크기. h 는 생략 가능 — 세로가 내용에 따라 늘어나는 페이지 | {w, h?} px |
root | 노드 트리의 뿌리 | Node |
style | 페이지 자신의 칠. fills 와 opacity 만 의미가 있다 (페이지에는 테두리·곡률·그림자가 없다) | Style |
background | 순서를 바꿀 수 없는 배경 슬롯의 노드 | Node |
XML 표기는 스키마를 모른다. 그래서 IR 이 아닌 문서도 똑같이 생긴 XML 로 적힌다 — 가장 헷갈리는 상대가 적재용 미리캔버스 엔진 데이터다. 파일이 무엇인지 먼저 본다.
<ir codec="1"> 또는 codec="2"version · pages 가 있다#rrggbbaa 문자열designId · nodes 가 보인다id·designId·rootNodeIds·data·nodes[]{hex, colorType} 객체
디자인 2.0 JSON(id/designId/rootNodeIds/data/nodes[])은
미리캔버스 엔진 고유의 템플릿·디자인 저장 포맷이고 IR 과는 별개 스키마다.
엔진은 아직 Warp IR 파이프라인으로 이관되지 않아 엔진 JSON 과 IR JSON 이 별개로 공존한다
(§8 — adapter-miricanvas 가 IR 을 거치지 않는 예외인 것과 같은 이야기다).
엔진 쪽 구조 해부는 sheetJson 해부도.
node 값이 종류 판별자다 — 이 값이 어느 키 표를 읽을지 정한다.
컨테이너는 layout 하나뿐이고, diagram 만 예외적으로 자식을 함께 나른다.
node | 무엇 | 고유 키 |
|---|---|---|
| layout | 유일한 컨테이너. 표는 layout:"grid" + semantic.tag:"table" 에 자식이 행 우선 셀(th/td)인 것 |
layout(stack 세로·row 가로·grid·overlay 절대) · gap · padding · align(교차축) · justify(주축) · grid · children |
| text | 순서 있는 런의 묶음. 런마다 스타일이 다를 수 있고 링크도 런이 갖는다. 평범한 한 줄은 런 하나 | runs[{text, style?, href?}] · textStyle(블록 기본값 — 런이 덮어쓴다) |
| image | 이미 그려진 그림을 그대로 그린다 | src(포맷 중립 URL) · resource · fit · crop · alt |
| vector | 문서가 칠하는 윤곽. 자산의 자기 색이 아니라 style.fills 로 칠한다 — 이미지와 갈라 둔 이유가 이것이다 |
path(SVG d) · svg(인라인 마크업) · src — 셋 중 하나 |
| line | 두 점이 정체다. 커넥터는 끝을 다른 노드에 매단다 — 가리키는 node 가 없으면 적합성 위반 |
start·end · thickness(0 이면 위반) · cap · dash · startMarker·endMarker · startAttachment·endAttachment{node, anchor} |
| media | 재생은 상태다. embed 는 외부 플레이어라 poster/muted 가 의미 없다 |
kind(video·audio·embed) · src · fit · poster · autoplay·loop·muted |
| barcode | 무엇을 인코딩하는가 | symbology(7종) · value(비면 위반) · logo{kind, ref} |
| chart | 종류 + 데이터. data 는 표다 — 첫 행이 시리즈 이름, 첫 열이 카테고리 이름 |
chartType(15종) · data(비면 위반) · palette · styleRef · scale{min?,max?} · recipe |
| diagram | 컨테이너이면서 저작 소스를 함께 나른다. diagramType 은 열거하지 않는 라벨 — 어휘가 서버 것이라(예: hub_and_spoke) 아는 쪽만 이해하고 나머지는 children 을 그린다 |
diagramType(비면 위반) · source(실무에서는 마크다운) · children(렌더된 진짜 노드) · recipe |
| instance | components[] 의 정의를 가리킨다 |
def(없는 id 면 위반) · overrides(대상 노드 id → 덮어쓸 값) |
차트의 렌더 트리나 프리셋 키를 IR 이 들고 있지 않은 이유는, 그것이 “차트가 무엇인가” 가 아니라
“한 제품이 차트를 어떻게 그리는가” 이기 때문이다.
담아야 할 것만 recipe{schema?, data} 로 해석하지 않고 불투명하게 나른다.
diagram.recipe · adjust.extra 도 같은 대우다.
문서 안의 resource{provider, key, env?, type?}(생산자 자산 id)와,
문서 밖 키 저장소(src→키, 환경별 사이드카). 앞의 것이 왕복을 닫고, 사이드카는 아직 문서에 없는
신규 업로드 키를 주입한다. 소비 우선순위는
주입 키 → 문서의 resource(발급자·환경 일치) → 주소에서 추출 → 없음(거부).
키가 src 를 대체하지는 않는다 — 키는 다른 포맷이 해석하지 못하고
발급 환경 밖에서 조회되지 않으며(프로덕션 키는 staging 에서 안 나온다) 키→URL 복원 규칙도 없다. 그래서 둘 다 싣는다.
붙는 자리는 다섯 — image · vector · media · mask · 이미지 칠 fills[].
종류와 무관하게 붙는 자리다. 코드에서는 SERIALIZED_KEYS.common() 이 정본이다.
SERIALIZED_KEYS.common() = id name node sizing position cell mask clip adjust transform style semantic
| 키 | 뜻 | 값 · 단위 | 조건 |
|---|---|---|---|
sizing | 크기 의도 — 값이 아니라 규칙이다 | {w, h} 각각 {kind:"hug"}(내용만큼) · {kind:"fill"}(부모를 채운다) · {kind:"fixed", px} | 필수 |
position | 부모 좌상단 기준 절대 위치 | {x, y} px | overlay 자식만. 다른 곳에 있으면 위반이고, overlay 자식에 없어도 위반 |
cell | 격자 칸 배정. 0-기반 | {row, column, rowSpan?, colSpan?} | grid 자식만. 없으면 문서 순서로 채운다 |
transform | 회전·반전. 자신과 자손에 함께 걸리고 곱해진다 | {rotation? 도·시계방향, flipH?, flipV?} | |
style | 칠·테두리·곡률·그림자·불투명도 | fills[](아래에서 위로 쌓는다) · stroke{color,width} · radius · shadow[] · opacity | |
semantic | HTML 의미 태그. Figma 에는 없지만 어느 방향으로도 보존한다 | {tag, role?} — 태그 29종 | |
mask | 실루엣. 이 모양으로 노드의 칠과 자식을 오려낸다 | {src} | |
clip | 렌더 결과에서 보이는 사각형 | {left, top, width, height} — 자기 박스의 0–1 정규 | image.crop 과 다르다 ↓ |
adjust | 색·블러 조정. 종류와 무관하게 아무 노드에나 붙는다 | brightness·contrast·saturation·temperature·tint -1…1 · blur px · sharpen·vignette 0…1 · overlay{color, strength} · extra |
angle 은 크롭 사각형의 회전adjust 값은 배수가 아니라 슬라이더 양이다
“슬라이더를 얼마나 올렸나” 를 -1…1 로 적는다. 실제 곡선은 렌더러마다 다르고,
왕복하는 것은 이 양이다. blur 만 물리량(가우시안 시그마)이라 px 이고,
sharpen 은 블러의 반대 연산이지 음수 블러가 아니다.
엔진(미리캔버스) 쪽 단위는 IR 단위가 아니다. 환산은 어댑터의 일이다. 그대로 넘기면 조용히 틀린 값이 된다.
| 값 | IR 단위 | 흔한 오해 · 엔진 쪽 |
|---|---|---|
letterSpacing | px | 미리캔버스는 폰트 크기 대비 % — 그대로 넘기면 24px 글자에서 정답의 24% |
lineHeight | px | 엔진 lineSpacing 은 비율(1.4 = 140%) |
style.radius | px | 엔진 cornerRadius 는 0–1 비율 (r = ratio × min(w,h)/2) |
adjust.* (blur 제외) | -1…1 | 엔진 displayFilter 는 -100…100 |
adjust.blur | px (가우시안 시그마) | 엔진은 노드 크기에 상대적 |
clip · crop | 0–1 정규 | px 로 넣으면 전부 오른쪽 아래로 잘린다 |
transform.rotation · gradient angle | 도, 시계방향 | |
opacity · stops[].at | 0–1 | 엔진 shadow.alpha 는 0–100 |
fontSize · gap · padding · position · size | px |
TextStyle 의 나머지 — fontFamily · fontWeight(100–900) · color(#hex) ·
align(left·center·right·justify). 런은 {text, style?, href?} 이고 href 가 있으면 그 런이 링크다.
@warp/profile 이 좁힌 목록이다. 여기 없는 값은 적합성 검사에서 걸린다.
bar 는 가로, column 은 세로#rgb · #rrggbb · #rrggbbaa@warp/profile 의 PROFILE 과 profile.ts 안의
CHART_TYPES/SYMBOLOGIES/MEDIA_KINDS.
Fill 은 다섯 갈래 — solid · linear-gradient · radial-gradient · conic-gradient · image.
XML 에는 타입도 배열도 없다. <x>1</x> 이 숫자 1 인지 문자열 "1" 인지 알 수 없으므로
추측하는 표기는 왕복하지 못한다. 그래서 두 판 모두 타입을 어딘가에 선언한다 —
v1 은 요소의 t 속성에, v2 는 값의 모양에.
키의 뜻은 판과 무관하다. 읽기는 루트의 codec 으로 자동 판별하므로 두 판이 섞여 있어도 된다.
v1 (codec="1") | v2 (codec="2" — 쓰기 기본) | |
|---|---|---|
| 스칼라 | 자기 요소 + t | 속성, 타입은 렉시컬 규칙 |
| 구조 객체 | 요소 | 요소 (단축되는 것은 속성 한 개) |
| 실측 크기 | 1,919줄 · 54KB · 속성 1,020개(t 1,013) | 524줄 · 21KB · 속성 690개(t 114) |
{ "id": "p1-body", "node": "text",
"runs": [ { "text": "런 단위 스타일과 " },
{ "href": "https://example.com/ir?a=1&b=2", "style": { "color": "#2c6bed" }, "text": "링크" } ],
"semantic": { "tag": "p" },
"sizing": { "h": { "kind": "hug" }, "w": { "kind": "fill" } },
"textStyle": { "align": "justify", "color": "#444444", "fontSize": 16, "lineHeight": 26 } }
sizing 이 한 줄로 접힌다. 키·값이 CSS 어휘로 바뀐다<i id="p1-body" node="text"> <runs t="a"> <i text="런 단위 스타일과 "/> <i href="https://example.com/ir?a=1&b=2" text="링크"><style color="#2c6bed"/></i> </runs> <semantic tag="p"/> <sizing height="fit-content" width="stretch"/> <textStyle align="justify" color="#444444" font-size="16px" line-height="26px"/> </i>
codec·t·key·key64 넷뿐<i> <id t="s">p1-body</id> <node t="s">text</node> <runs t="a"> <i><text t="s">런 단위 스타일과 </text></i> <i> <href t="s">https://example.com/ir?a=1&b=2</href> <style><color t="s">#2c6bed</color></style> <text t="s">링크</text> </i> </runs> <sizing><h><kind t="s">hug</kind></h><w><kind t="s">fill</kind></w></sizing> </i>
| 모양 | 읽는 값 | 예 |
|---|---|---|
"…" 로 시작 | 문자열 (JSON 인용 해제) | text=""8"" → "8" |
null / true / false | null / boolean | visible="true" |
| 숫자 리터럴 | number | opacity="0.5" |
숫자 + px | number (단위를 벗긴다) | gap="8px" → 8 |
[ … ] | 스칼라 배열 | palette="[#deff58 #343434]" |
| 그 밖 | 문자열 (값 어휘를 되돌린다) | display="flex-column" → "stack" |
그래서 그 규칙으로 읽힐 문자열만 인용한다 — "8"·"true"·"null"·"10px"·"[a b]"·"".
속성에 담을 수 없는 값(제어문자가 든 문자열·배열)은 v1 의 t 형태로 떨어진다 —
v2 는 v1 을 대체하는 표기가 아니라 그 위에 빠른 길을 얹은 것이다.
타입 태그 t 는 두 판 공통: a 배열 · s 문자열 · s64 base64 · n 숫자 · b 불린 · z 널 · l 스칼라 배열(v2 전용).
이름공간 없음(접두사가 붙으면 “요소 이름 = 필드 이름” 이 깨진다) ·
XSD/DTD 없음(검증 책임은 @warp/profile 이 갖는다) ·
정책·옵션 흔적 없음(같은 문서를 다른 옵션으로 변환해도 문서 값은 같다) ·
리소스 키 없음(문서에는 src 주소만, 키는 사이드카에).
등록된 정식 IR(layout-intent)이 있고 어댑터들이 그 IR 을 대상으로 한다.
그런데 adapter-miricanvas 만 그 바깥에 있다.
McNode 직접 생성figma 와 html 은 같은 IR 을 대상으로 같은 문서 타입(DesignDoc)을 만든다.
adapter-miricanvas 는 core·profile·ir-layout-intent 참조가 0건이고,
Warp 저장소 어디에도 이 어댑터를 registry 에 등록하는 코드가 없다.
| 패키지 | 역할 | 메모 |
|---|---|---|
@warp/core | IR 구조·직렬화의 source of truth | types.ts(구조) · serialize.ts(직렬화 키 집합 SERIALIZED_KEYS) |
@warp/profile | 허용 어휘 · 적합성 검사 | profile.ts(어휘) · check.ts·violations.ts(무엇이 위반인가) |
@warp/registry | 어댑터·IR 등록, IR↔IR bridge 경로 관리 | 축이 둘 — format(어댑터) · bridge(IR 버전) |
adapter-figma | Figma → IR | Registry 등록됨 · ir: layout-intent |
adapter-html | HTML → IR (정적 파싱, 측정 없음) | Registry 등록됨 · ir: layout-intent |
adapter-xml | JSON ↔ XML(v1/v2) 상호 변환 · 왕복성 검증 | xml-compact.ts 가 v2 어휘표의 출처 |
adapter-miricanvas | IR 을 거치지 않고 McNode 를 직접 만든다 | Registry 미등록 · 예외 상태. Adapter 계약이 요구하는 serialize(엔진 노드 → HTML) 방향이 없어 지금은 등록이 성립하지 않는다 |
| main | html-import (PoC 브랜치) | |
|---|---|---|
| 위치 | packages/engine/vendor/warp/ | packages/engine/editor/src/html-import/vendor/warp-mc/ |
| 가져온 패키지 | packages/registry (원본 4파일) | packages/adapter-miricanvas (원본 25파일) |
| 쓰는 곳 | framework — 노드 props 마이그레이션 | editor — HTML 가져오기 (DEV 전용) |
| registry 사용 | bridge 축만. format 축은 항등 어댑터로 의도적으로 막아 뒀다 | 0건 — 직접 함수 파이프라인 |
같은 Warp 저장소에서 왔지만 가져온 부분이 겹치지 않는다 — 파일 내용 SHA-256 교차 0개, 파일명 교집합 0개.
HTML 가져오기가 지금은 adapter-html(정적 파싱)이 아니라 브라우저 실측 → adapter-miricanvas 경로를 쓰는 것도 이 때문이다.
기준: main 병합 커밋 6ae349c9765 · Warp cf87a75(mirror) / 3964a2c(main vendor) · 2026-08-12 확인.
이 페이지는 Confluence 원문을 옮긴 것이고, 그 원문도 코드를 옮긴 것이다.
어긋나면 타입이 이긴다 — packages/core/src/types.ts(구조) ·
serialize.ts(직렬화 키 집합) · packages/profile/src/profile.ts(허용 어휘).
키 집합은 SERIALIZED_KEYS.common()/.kinds()/.extra(k) 로 직접 뽑아 대조할 수 있다.
| 무엇 | 어디 |
|---|---|
| IR 스키마 참조: JSON 키와 XML 표기 설명 — 이 페이지 §1–§7 의 원문 | Confluence 2814312600 |
| main 브랜치의 vendor 병합 — §8 의 원문 (Warp 저장소 구조 · registry 축 · vendor 두 자리) | Confluence 2777154274 |
figma-import: JSON → 엔진 요소 변환 기준 — adapter-figma 실질 설계문서 | Confluence 2703820539 |
AIP HTML import — adapter-html 실질 설계문서 | Confluence 2772238832 |
| 디자인 2.0 JSON 구조 — §2 에서 갈라 둔 엔진 쪽 포맷 | Confluence 1066862669 |
Warp 저장소 안의 근거 문서 — 왜 XML 이 이렇게 생겼나: records/decisions/DR-041-ir-xml-form.md(v1) ·
DR-059-xml-ir-v2-compact.md(v2 축약) · DR-042(URL 대체 정책) ·
DR-043·DR-045(리소스 키) · 엔진 단위·한계는 docs/wiki/pages/concept-miricanvas-engine-contracts.md.
모든 종류를 담은 실물 표본은 examples/artifacts/all-node-kinds.ir.{json,xml}.