본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 18. 02:20

‘대충 맞는 것 같아’라는 말을 믿지 마세요: AI 에이전트에게 Figma와 UI를 대조하여 검증할 수 있는 방법을 제공했습니다

요약

AI 에이전트가 생성한 UI 코드가 Figma 디자인과 일치하는지 픽셀 단위로 검증하는 로컬 도구 'figma-connect'를 소개합니다. Playwright를 활용해 렌더링된 코드와 Figma 노드를 비교하여 미세한 디자인 오차를 잡아냅니다.

핵심 포인트

  • AI 에이전트의 UI 구현 결과물을 Figma 디자인과 픽셀 단위로 비교 검증
  • verify_node 함수를 통해 코드 렌더링 결과와 실제 디자인 노드 간의 차이 식별
  • Playwright를 사용하여 헤드리스 환경에서 시각적 차이(diff) 이미지 생성
  • 원시 픽셀 비교 및 SSIM 등 다양한 비교 방식을 결합하여 정확도 향상

저는 UI 작업을 많이 하는데, 최근 많은 사람들처럼 저도 AI 에이전트에게 첫 번째 초안 작업을 맡기곤 합니다. Figma 파일을 가리켜주고, 컴포넌트(components)를 작성하게 한 뒤, 90% 정도 완성된 결과물을 받아오는 방식이죠. 운이 좋은 날에는 엄청난 시간을 절약할 수 있습니다.

문제는 나머지 10%이며, 바로 그곳에 문제가 숨어 있다는 점입니다.

결코 명백하게 깨진 상태로 나타나지 않습니다. 16px이어야 할 패딩(padding)이 12px이라거나, 디자인에는 600으로 되어 있는데 폰트 두께(font weight)가 500이라거나, 테두리 반경(border radius)이 몇 픽셀 어긋나 있거나, 그라데이션(gradient)이 잘못된 지점에서 시작되는 식입니다. 각각은 아주 미세하지만, 이들이 모이면 "디자인과 똑같다"와 "디자인을 대충 한 번 본 것 같다"의 차이를 만듭니다. 그리고 제가 이를 잡아낼 수 있는 유일한 방법은 Figma 프레임과 브라우저를 나란히 띄워놓고, 틀린 그림 찾기 퍼즐을 풀듯 눈을 가늘게 뜨고 번갈아 확인하는 것뿐이었습니다.

그 방식은 금방 질렸습니다. 무엇보다 저를 짜증 나게 했던 것은 에이전트가 자신이 틀렸다는 사실을 전혀 모른다는 점이었습니다. 에이전트는 디자인을 읽고, 코드를 작성한 뒤, 자신은 일치한다고 자신 있게 말하곤 했습니다. 스스로의 작업을 확인할 수 없었던 것이죠. 제가 시도해 본 모든 Figma 도구들은 노드(node)에 대한 데이터를 에이전트에게 제공할 수는 있었지만, 그 어떤 것도 실제 질문에 답할 수는 없었습니다: "방금 당신이 만든 것이 디자이너가 그린 것과 똑같이 생겼는가?"

그래서 저는 눈을 가늘게 뜨고 확인하는 일을 멈추고, 빠져 있던 조각을 직접 만들었습니다. figma-connect라고 불리는 로컬 도구이며, 제가 중요하게 생각하는 부분은 verify_node라는 하나의 함수입니다.

핵심 아이디어

verify_node는 에이전트가 작성한 코드를 가져와 실제 브라우저에서 렌더링(render)한 뒤, 실제 Figma 노드와 픽셀 단위로 비교합니다. 통과(pass) 또는 실패(fail)를 판정하고, 차이점 이미지(diff image)를 첨부합니다. 그게 전부입니다. 에이전트에게 마침내 거울이 생긴 셈입니다.

읽기(read) 기능도 있습니다 (기하학적 구조(geometry), 오토 레이아웃(auto-layout), 채우기(fills), 타이포그래피(type), 토큰(tokens), 컴포넌트(components) 등 일반적인 것들을 가져올 수 있습니다). 하지만 솔직히 말해서 읽기 부분은 기본 사양일 뿐입니다. 그런 기능은 이미 많은 도구가 제공하고 있습니다. 검증(verify) 부분이 제가 어디에서도 보지 못했던 부분이며, 에이전트의 행동 방식을 변화시킨 핵심입니다.

이 모든 과정은 제 노트북에서 실행됩니다. 브라우저 기반의 Figma가 작동하므로, 데스크톱 앱도 필요 없고, 클라우드 API도 필요 없으며, 디자인 파일이 제 컴퓨터를 떠날 일도 없습니다.

검증이 실제로 작동하는 방식

노드 ID (node id)와 후보 코드 (candidate code)를 입력합니다. 시스템은 Playwright를 사용하여 헤드리스 크로미움 (headless Chromium) 환경에 코드를 마운트하고, Figma에서 일치하는 노드를 내보낸(export) 뒤, 두 항목을 비교(diff)합니다. 저는 세 가지 서로 다른 비교 방식을 동시에 실행하는데, 이는 각각의 방식을 단독으로 시도했을 때 저마다의 방식으로 저를 속였기 때문입니다.

원시 픽셀 비교 (Raw pixel diffing)가 가장 많은 것을 잡아냅니다: 4px의 이동, 잘못된 곡률 (radius), 위치가 바뀐 그라데이션 등이 그것입니다. 하지만 이 방식은 1픽셀의 전역 오프셋 (global offset)에도 히스테리컬하게 반응하며, 전체 요소가 옆으로 살짝 밀렸을 뿐인데 모든 것이 망가졌다고 비명을 지릅니다. 그래서 저는 그 위에 SSIM (Structural Similarity Index Measure)을 계층적으로 쌓았습니다. SSIM은 구조적 유사도를 점수화하며, 사람이 실제로 "비슷하다"라고 부를 만한 기준에 더 가깝게 추적합니다. 그리고 axe-core를 이용한 텍스트 및 접근성 (accessibility) 검사 단계를 추가했습니다. 픽셀은 완벽하게 일치하지만, 레이블 (label)이 조용히 누락되었거나 제목 (heading)이 소문자로 바뀌어 렌더링된 사례를 여러 번 겪었기 때문입니다. "보기 좋은 것"과 "옳은 것"은 같지 않으며, 저는 아주 골치 아픈 방식으로 그 사실을 배웠습니다.

출력물은 EXPECTED / ACTUAL / DIFF 레이블이 붙은 이미지입니다. 이는 의도적인 설계입니다. 단순한 유사도 점수만 있다면 에이전트 (agent)는 기꺼이 이를 합리화할 것입니다 ("0.94니까 충분히 비슷해!"). 하지만 무엇이 잘못되었는지 정확히 보여주는 사진 앞에서는 그렇지 못합니다.

진정한 승리는 "대충 맞는 것 같다"라는 말이 더 이상 통하지 않게 되었다는 점입니다. 이제 에이전트는 작업을 완료했다고 선언하기 전에 반드시 통과해야 하는 관문을 갖게 되었습니다.

실제로 제 일주일의 시간을 잡아먹은 것들

렌더링 후 비교 (render-and-diff) 아이디어를 구현하는 데는 오후 한나절이면 충분했습니다. 하지만 이를 신뢰할 수 있게 만드는 데는 훨씬 더 오랜 시간이 걸렸습니다. 어리석은 이유로 실패하는 검증기는 아예 없는 것보다 못하기 때문입니다. 처음 시스템이 잘못된 경고를 보냈을 때 저는 신뢰를 멈췄고, 그것은 검증의 목적 자체를 무색하게 만들었습니다.

저를 가장 먼저 괴롭힌 것은 폰트 (fonts)였습니다. 육안으로는 동일해 보이는 텍스트에서 계속 실패가 발생했고, 웹 폰트 (web fonts)가 로드되기 전에 스크린샷을 찍고 있었다는 사실을 깨닫기 전까지 부끄러울 정도로 많은 시간을 허비했습니다. 렌더링 과정에서 폴백 폰트 (fallback font)를 디자인의 실제 폰트와 비교하여 차이점을 표시하고 있었던 것입니다. 캡처 과정을 document.fonts.ready 상태에 맞게 제한함으로써 이러한 유형의 허위 실패 (false failures)를 완전히 제거할 수 있었습니다.

다음으로는 대기 전략(wait strategy)이 있었습니다. 저는 캡처하기 전에 networkidle 상태를 기다렸는데, 이는 롱 폴링 (long-poll)이나 스트리밍 연결 (streaming connection)이 있는 페이지를 만날 때까지는 괜찮지만, 그 이후에는 결코 유휴 (idle) 상태가 되지 않습니다. 그러면 검증 과정이 영원히 멈춰버리게 됩니다. 저는 이러한 일괄적인 대기 방식을 제거하고 명시적인 준비 신호 (explicit readiness signals)로 교체했습니다.

제가 아직 완전히 마무리하지 못한 부분은 충실도 (fidelity) 대 예산 (budget)의 문제입니다. 에이전트에게 전달하는 노드 (node)의 요약본에 모든 속성을 완전한 정밀도로 담을 수는 없습니다. 그렇지 않으면 컨텍스트 윈도우 (context window)가 터져버리기 때문입니다. 그래서 무엇을 정확하게 유지하고 무엇을 근사치 (approximate)로 처리할지에 대해 결정을 내려야 했고, 그 사실을 솔직하게 명시해야 했습니다. 이제 요약본 (digest)에는 그라디언트 (gradients), 그림자 (shadows), 스트로크 (strokes), 불투명도 (opacity), 마스크 (masks)를 위한 명시적인 플래그 (flags)가 포함되어 있어, 에이전트는 값이 실제 값인지 아니면 최선의 추측치인지 알 수 있습니다.

내부 구조 (Under the hood), 간략히

작은 pnpm 모노레포 (monorepo) 구조입니다. Figma 플러그인이 파일 내에 존재합니다 (문서를 실제로 읽을 수 있는 유일한 요소이기 때문입니다). 로컬 브리지 데몬 (bridge daemon)이 파일을 전체 텍스트 검색 (full-text search)이 가능한 SQLite로 인덱싱하고, 디자인이 변경됨에 따라 스스로를 업데이트하며, 모든 것을 MCP를 통해 노출합니다. 별도의 하네스 (harness)가 렌더링과 디핑 (diffing)을 수행합니다. 에이전트는 작은 stdio 심 (shim)을 통해 데몬과 통신하므로 세션 사이에도 파일 인덱싱이 유지됩니다. 약 15,000줄의 TypeScript, 35개의 도구(tools)로 구성되어 있으며, 검증 (verify) 단계를 제외하고는 모두 읽기 전용 (read-only)이며 localhost에만 바인딩되어 있습니다.

아직 할 수 없는 것들

한계점에 대해 솔직해지겠습니다. 자신의 작업물이 완성된 것처럼 가장하는 포스트를 싫어하기 때문입니다.

검색은 의미론적 (semantic)이지 않고 어휘적 (lexical)입니다. 따라서 레이어 이름이나 텍스트에 문자 그대로 나타나는 단어와 일치해야 합니다. '분위기(vibes)' 기반의 쿼리는 일반적인 이름으로 명명된 그룹을 찾지 못할 것입니다. 요약본은 예산 (budgeted) 내에서 관리되므로 충실도 플래그 (fidelity flags)가 존재합니다. 그리고 이 시스템은 오직 읽기만 수행하며, 의도적으로 Figma에 다시 쓰지는 (write back) 않습니다.

만약 여러분이 AI가 미묘하면서도 자신만만하게 틀린 UI를 내뱉는 것을 지켜보면서, 자신의 눈으로 직접 확인하는 것 외에는 잡아낼 방법이 없었던 경험이 있다면, 이것은 AI에게 부족했던 피드백 루프 (feedback loop)를 제공하려는 저의 시도였습니다.

스크린샷과 아키텍처 (architecture)를 포함한 전체 상세 내용: https://www.arjunp.pro/projects/figma-connect.html

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0