스크린샷 차이 비교(diffing)가 AI로 구축한 UI를 망가뜨리는 이유와 기하학적 측정으로 해결하는 방법
요약
AI 에이전트가 UI를 구축할 때, 단순한 스크린샷 픽셀 차이 비교(diffing)는 노이즈에 민감하여 불안정한 피드백 루프를 만듭니다. 이 글은 대신 기하학적 측정과 디자인 토큰을 활용해 구조화된 수정 목록(fix-list)을 전달하는 [designfit]이라는 오픈 소스 도구를 소개합니다.
핵심 포인트
- 픽셀 비교는 노이즈에 민감하여 불안정한 피드백 루프를 만듭니다.
- AI 에이전트는 픽셀 대신 기하학적 속성(크기, 위치 등)을 측정해야 합니다.
- designfit은 Figma 명세를 추출하고 렌더링된 UI와 비교해 수정 목록을 제공합니다.
- 올바른 피드백 루프는 결정론적이며 구조화된 속성 기반이어야 합니다.
만약 코딩 에이전트에게 Figma 프레임을 가리키며 "이것과 일치하게 만들어라"라고 지시해 본 적이 있다면, 그 실패 모드를 알고 있을 겁니다. 에이전트는 비슷한 것을 만듭니다. 당신이나 어떤 도구가 그것을 디자인과 비교합니다. 그리고 "여전히 틀렸다"는 말을 듣고 수정하고, 다시 비교하지만, 어찌 된 일인지 여전히 틀려 있습니다. 영원히요. 문제는 에이전트가 고장 난 것이 아닙니다. 비교 방식 자체가 문제입니다.
이 글은 왜 명백한 비교 방식인 스크린샷 차이 비교(diffing)가 AI 에이전트와 루프를 닫는 데 잘못된 기본 원리인지, 그리고 대신 무엇을 사용해야 하는지에 관한 것입니다. 요약하자면: 픽셀을 비교하지 말고, 기하학적 측정과 토큰을 측정하세요. 이것이 제가 만든 오픈 소스 MCP 서버이자 Claude Code 스킬인 designfit의 기본 아이디어입니다. 이 도구는 Figma 프레임에서 명세를 추출하고, 렌더링된 프런트엔드를 그 명세와 검증하며, 에이전트에게 이미지 대신 기계가 실행 가능한 수정 목록(fix-list)을 전달합니다.
▶ 35초 데모 시청하기: 360노드 Figma 프레임에 대한 실제 실행 사례로, 세 번의 빌드 반복에서 점수를 87점에서 100점으로 올립니다.
문제점: 픽셀 차이 비교는 진동한다 (oscillate)
따라서 에이전트는 '여전히 틀렸다'는 신호를 받지만, 글자 가장자리를 따라 배열된 안티앨리어싱(anti-aliased) 픽셀들 중에서 진정으로 잘못된 색상을 구분할 방법이 없습니다. 계속 편집을 시도합니다. 차이점(diff)이 에이전트가 통제할 수 없는 노이즈에 민감하기 때문에, 수정 작업이 숫자를 신뢰성 있게 0으로 이끌지 못합니다. 점수가 요동치고, 에이전트는 불안정하게 움직이며, '디자인과 일치'하는 상태에 가까워지지 못한 채 토큰과 시간을 소모하게 됩니다.
더 깊은 문제는 결정론(determinism)입니다. 유용한 피드백 루프는 같은 입력에 같은 출력을 필요로 합니다. 픽셀 차이점은 기계, 폰트 스택 또는 재렌더링 과정 전반에 걸쳐 그 속성을 갖지 못하는데, 이는 그 입력값에 레이트라이저(rasterizer)의 노이즈가 포함되기 때문입니다. 측정 자체가 결정론적이지 않으면, 루프는 수렴할 수 없습니다.
통찰: 디자이너들은 픽셀을 비교하지 않는다
목업(mockup)과 구현체를 검토하는 디자이너는 두 이미지를 겹쳐 놓고 다른 픽셀을 찾아다니지 않습니다. 그들은 다음과 같은 작고 구조화된 일련의 문제점들을 포착합니다:
- 잘못된 색상: 저 버튼은 잘못된 파란색이다.
- 잘못된 크기: 저 카드가 너무 넓다.
- 정렬 불일치(Misalignment): 저 요소가 있어야 할 위치보다 왼쪽에 있다.
- 누락 또는 추가 요소: 디자인에 있던 배지(badge)가 없다.
그게 전부입니다. 그들은 몇 가지 의미 있고 이름 붙여진 속성들—디자인 의도와 비교하여—을 검토하고 있는 것입니다. 이 모든 확인 사항은 결정론적으로 측정될 수 있습니다: 색상은 색상이고, 너비는 픽셀의 개수이며, 위치는 좌표이고, 요소는 존재하거나 존재하지 않습니다.
따라서 올바른 기본 단위(primitive)는 '몇 개의 픽셀이 다른가'가 아닙니다. 그것은 '구현체가 디자인에서 선언한 속성들 중 어떤 것을 위반했는가'입니다. 이 집합은 작고 안정적이며 기계가 처리할 수 있는 형태이므로, 에이전트가 허둥대는 대신 문제를 해결하는 데 필요한 정확한 것입니다.
해결책: 공차를 두고 토큰과 기하학을 비교한다
designfit은 두 가지 종류의 것만을 검증하며, 그 외에는 아무것도 하지 않습니다.
디자인 토큰(Design tokens). 디자인이 지정하는 해상된 스타일 값들: fill, color, fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, borderRadius, borderColor, borderWidth, opacity. 색상은 지각적 색 거리인 **CIEDE2000 (ΔE)**로 비교되므로, "육안으로 구별하기 어려울 정도로 다른" 경우에도 실패로 간주되지 않습니다. 숫자 토큰은 픽셀 단위로 비교됩니다.
지오메트리(Geometry). 각 요소의 박스(x, y, width, height)를 절대적인 뷰포트 좌표가 아닌 스크린 루트에 상대적으로 비교합니다. 중앙에 있거나 오프셋된 올바르게 구축된 화면이라도 통과하며, 이는 모든 위치가 먼저 루트의 원점(origin)으로 정규화되기 때문입니다. 사용자가 측정하는 것은 페이지 전체가 어디에 놓였는지가 아니라 레이아웃 자체입니다.
여기에 **존재 여부(presence)**까지 추가됩니다: 각 요소가 DOM에서 디자인이 예상하는지, 그리고 디자인이 알지 못하는 태그가 붙어 있는지는 어떤지 확인합니다.
모든 비교에는 **명시적인 허용 오차(explicit tolerance)**가 적용되므로, 서브픽셀 단위의 노이즈나 지각적으로 구별하기 어려운 색상 변화는 절대 감지되지 않습니다. 기본값은 다음과 같습니다:
| 속성 | 허용 오차 |
|---|---|
| 지오메트리 위치 / 크기 | ±2 px |
| ... |
프로젝트별로 이 값들을 느슨하게 하거나 더 엄격하게 설정할 수 있습니다.
입력이 래스터화된 이미지(rasterized image)가 아니라 계산된 스타일 값 및 경계 상자 측정값이기 때문에, 비교 과정은 결정론적입니다: 동일한 구현, 동일한 디자인, 동일한 결과가 모든 실행에서 나옵니다. 이것이 루프를 수렴하게 만드는 요소입니다. 또한 에이전트가 조기에 멈출 수 있게 합니다: 만약 점수가 두 번의 실행에 걸쳐 개선되지 않는다면, 마지막 편집은 아무것도 변화시키지 않았다는 의미이며, designfit이 측정할 것이 남아있지 않으므로 더 이상 조정할 필요가 없습니다.
루프(The loop)
designfit은 두 가지 MCP 도구를 노출합니다. designfit_extract는 Figma 프레임을 검증을 위한 입력으로 변환합니다. designfit_validate는 해당 입력과 실행 중인 URL을 받아 { pass, score, violations, unmapped }를 반환합니다. Claude Code 스킬이 에이전트를 이 사이클을 통해 구동합니다:
- 디자인 사양 추출(Extract the design spec).
designfit_extract에 프레임의 Figma 링크를 제공합니다. 이 함수는 디자인 트리(각 노드와 그 프레임, 토큰을 포함)와 컴포넌트 맵, 그리고 뷰포트를 반환합니다. 속성에 바인딩된 Figma 변수 및 게시 스타일은 해당 속성의 토큰 소스로 기록됩니다. - UI 구축 및 태깅(Build the UI, tagging as you go). 노드별로 생성되는 각 요소에는
data-designfit-id="<figmaNodeId>"가 부여됩니다. - 실행 URL과 검증(Validate) 합니다.
- 수정하고 반복(Fix and repeat) 하여
pass가true가 될 때까지 진행하거나, 점수가 정체되면 중단하고 보고합니다. - 태그 정리(Clean up) 를 수행합니다. 단, 나중에 재검증을 위해 태그를 유지할 경우 제외합니다.
추출이 중요한 이유 (Why extraction matters)
초기 버전에서는 1단계가 존재하지 않았습니다. 에이전트는 Figma의 자체 MCP 서버를 통해 프레임을 읽고 수동으로 사양을 조립했습니다. 이것이 루프에서 가장 오류가 많이 발생하는 단계였습니다: 오타가 난 프레임이나 누락된 토큰 소스는 디자인과 불일치하는 사양을 생성하고, 그러면 완벽하게 결정론적인 비교가 잘못된 것을 결정론적으로 확인합니다.
designfit_extract는 이것을 코드로 대체합니다. 이는 개인 액세스 토큰(Claude Code 플러그인이 한 번 요청함)을 사용하여 Figma의 REST API에서 프레임을 읽거나, 붙여넣은 /nodes JSON에서 읽습니다. 나머지 도구들과 마찬가지로 결정론적입니다: 동일한 노드 JSON이 들어가면 동일한 사양이 나옵니다. 숨겨진 노드는 건너뛰고, 벡터만으로 구성된 프레임(아이콘)은 하나의 리프로 처리되며, 그라디언트, 이미지 및 효과는 제외됩니다.
실제 프레임에서 발생한 일 (What happened on a real frame)
위의 데모는 실제 실행이었으며, 예상보다 더 많은 것을 가르쳐 주었습니다.
해당 프레임은 밀도가 높은 어두운 대시보드였고, 추출 후 360개의 노드를 포함했으며 약 1.5초가 소요되었습니다. 저는 세 번의 반복에 걸쳐 25개의 태그가 지정된 요소를 검증했습니다:
- Iteration 1: 87/100, 실패(fail). 변수 바인딩 채우기(variable-bound fill)가 잘못된 부분과 몇 가지 하드코딩된 색상 및 반지름이 벗어난 곳(Figma에서 이를 강제하지 않으므로 경고(warnings)로 처리됨), 그리고 기하학적 오류 클러스터 17개와 경고 7개가 있었습니다.
- Iteration 2: 89/100. 토큰 문제는 사라졌지만, 기하학적 오류는 여전히 14개가 남아있었습니다.
- Iteration 3: 100/100, 통과(pass).
이러한 기하학적 오류 중 두 가지는 크기 문제가 아니었습니다. 한 섹션은 빌드에서는 높이가 49px였지만 Figma에서는 542px였습니다. 테이블은 설계보다 73px 더 낮게 배치되어 있었습니다. 이 두 요소 모두 잘못된 노드에 태그가 지정되어 있었기 때문입니다. 태그는 섹션 자체 대신 헤더 행에, 그리고 테이블 자체 대신 테이블의 래퍼(wrapper)에 붙어 있었습니다. 원래 수동으로 조립된 사양은 그 잘못된 태그를 중심으로 구축되었기 때문에 이를 감지하지 못했습니다. 하지만 Figma에서 프레임을 직접 읽자서야 발견할 수 있었습니다.
실제 프레임에 대해 extract를 실행하자, designfit 자체의 버그 두 가지도 포착되었습니다. 이 두 가지는 0.2.1 버전에서 수정되었습니다. Figma의 측면별 스트로크(per-side stroke) 가중치는 무시되었기 때문에, 하단에만 경계선이 예상되어야 했습니다. 또한 CSS letter-spacing: normal은
designfit 0.2는 Figma에서 명세(spec)를 추출한 다음, 하나의 뷰포트에 대해 토큰, 기하학적 측정, 존재 여부 검사를 실행합니다. 즉, 프레임이 디자인된 브레이크포인트가 바로 전체 제품입니다. 이것이 현재 제품의 전부이며 의도된 바입니다.
명시적으로 _로드맵 단계이지 아직 출시되지 않은 기능_으로 남아있는 부분은 다음과 같습니다: 명명된 토큰으로서의 간격(spacing) (잘못된 패딩과 간격은 오늘날 포착되지만, 오직 기하학적 델타로만), 여러 브레이크포인트에 걸친 검증, 그리고 기하학적 측정 및 토큰이 포착할 수 없는 판단을 위한 자문용 시각 모델 계층입니다. 어떠한 모호한 것도 통과/실패 경로에 놓이지 않을 것입니다. 핵심은 대부분의 '디자인에 맞추기' 과정에서 발생하는 혼란(thrash)이 잘못된 색상, 잘못된 크기, 그리고 누락된 요소들에서 비롯되며, 이러한 결정론적 측정만으로도 이를 해결할 수 있다는 점입니다.
사용해보기
무료이며 MIT 라이선스를 따릅니다. Claude Code에서는:
/plugin marketplace add as9978/designfit
/plugin install designfit@designfit
다른 모든 MCP 클라이언트에서는 npm install -g designfit을 사용합니다. 어느 쪽이든, 한 번 npx playwright install chromium을 실행하세요. 그런 다음 Figma 링크를 붙여넣고 에이전트가 측정할 수 있는 것에 대해 폐쇄 루프(close the loop)를 완성하도록 하세요.
제가 제작자이며 이 프로젝트를 혼자 구축하고 있습니다. 여러분의 프레임에서 토큰-및-기하학 모델이 어떤 부분에서 무너지는지 듣고 싶습니다: 이슈 열기를 통해 놓친 사례나, 실제가 아니었음에도 플래그가 지정된 사례와 함께 알려주세요.
GitHub logo as9978 / designfit
AI로 구축한 프론트엔드를 Figma 디자인과 비교 검증 — 픽셀이 아닌 결정론적 토큰 + 기하학 준수성. MCP 서버 + Claude Code 스킬.
designfit
스크린샷 차이 비교(diffing) 과정 없이 AI로 구축한 프론트엔드를 Figma 디자인과 비교 검증.
designfit-v0.2-demo-1080p.mp4
360노드 Figma 프레임에 대한 실제 실행: designfit_extract가 링크에서 프레임을 읽어온 다음, designfit_validate가 세 가지 빌드 반복(87에서 89로, 그리고 100으로)을 점수화합니다. 이 과정에는 스크린샷 차이 비교가 전혀 없습니다.
designfit은 Figma 디자인과 렌더링된 구현을 비교하여 코딩 에이전트에게 기계가 처리 가능한(machine-actionable) 수정 목록을 제공하는 MCP 서버 + Claude Code 스킬입니다. 이는 원시 픽셀(raw pixels)이 아닌 **디자인 토큰(design tokens)**과 기하학적 측정값(geometry) (화면 루트에 대한 요소 박스)을 비교하기 때문에, 글꼴 렌더링 노이즈가 에이전트를 불안정하게 만들지 않습니다. 결정론적으로 입력하고, 결정론적으로 출력합니다.
기하학적 측정값(geometry)이 픽셀(pixels)보다 나은 이유
AI가 구축한 UI를 Figma 프레임과 스크린샷 차이 비교(Screenshot-diffing)하면 문제가 발생합니다. 안티앨리어싱(anti-aliasing)이나 서브픽셀 이동(sub-pixel shifts)이
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기