Community코딩 & 개발github.com

figma-build

Figma 및 FigJam 플러그인 빌드 시 자주 발생하는 조용한 실패(레이아웃 붕괴, 텍스트 깨짐, 캐시 문제 등)를 디버깅하고 방지하는 방법을 제공합니다. 플러그인을 작성하거나 디버깅할 때, 프레임이나 와이어프레임을 프로그래매틱 방식으로 생성할 때, 또는 빌드 도중 오류가 발생할 때 유용합니다.

figma-build란 무엇인가요?

이 스킬은 Figma와 FigJam 플러그인을 개발할 때 흔히 발생하지만 명시적인 오류를 던지지 않아 디버깅이 어려운 문제들을 해결하는 데 초점을 맞춥니다. 크게 프레임 생성 및 레이아웃 처리, 텍스트 크기 조절, 오류 방지를 위한 클램핑, 캐시 문제를 확인하는 빌드 스탬프, 주석 처리, 자동 레이아웃 설정 등 8가지 주요 패턴을 다룹니다. 특히 `textAutoResize` 설정 순서, 명시적 너비와 고정 크기 전환, 컬럼이 행을 채우는 문제 등 세부적인 함정에 대한 해결책을 제시합니다. 또한 로컬에서 플러그인을 시뮬레이션할 수 있는 스텁(stub) 코드와, Figma REST API를 통해 주석을 읽고 처리하는 도구를 함께 제공합니다. `name`은 'figma-build'로, 원래 이름을 그대로 사용하며, 간결하고 명확하게 스킬의 목적을 전달합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ABarracu87/figma-build-skill/tree/HEAD

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Figma 빌드

여기에 나오는 모든 것은 조용히 실패합니다. 아무것도 예외를 던지지 않고, 아무것도 경고하지 않으며, 캔버스가 그냥 잘못 보일 뿐입니다.

무엇보다 먼저, 루프

빌드 스탬프를 찍고 자신의 출력물을 지우십시오. 그렇지 않으면 Figma가 캐시된 번들을 제공할 때 "같아 보인다"는 디버깅에 오랜 시간을 허비할 수 있습니다.

const BUILD = "15:03";                 // 번들을 변경할 때마다 갱신
function clearPrevious() {
  const mine = figma.currentPage.children.filter(n => /^\d\d · /.test(n.name));
  mine.forEach(n => n.remove());
  return mine.length;
}
figma.closePlugin("Build " + BUILD + " — replaced " + removed + " frames.");

토스트에 두 개의 숫자가 표시되며, 둘 다 진단용입니다. 오래된 스탬프는 번들이 캐시되었음을 의미합니다: 플러그인을 제거하고, 매니페스트를 다시 가져온 후, 탭을 다시 여십시오. 캔버스에 프레임이 분명히 있는데 Replaced 0이 표시되는 것도 같은 의미입니다.

이것이 없으면 프레임을 다시 생성하는 플러그인이 첫 번째 세트 위에 두 번째 세트를 쌓고, 어떤 것을 보고 있는지 알려주는 것이 없습니다. 프레임 이름의 복사본을 세는 것이 실행이 실제로 발생했는지 증명하는 방법입니다.

지우고-다시-빌드하기는 첫 번째 빌드에만 해당됩니다. 파일이 검토 중이면 주석이 노드 ID에 고정됩니다. 프레임을 제거하고 새로 그리면 해당 프레임의 모든 주석이 고아가 됩니다. 검토 라운드 중에는 기존 노드를 제자리에서 편집하십시오 (검토 루프 참조).


하나의 잘못된 값이 전체 빌드를 망가뜨립니다

이 파일의 모든 실패는 같은 형태입니다: API가 거부하는 하나의 값이 도중에 던져져, 레이아웃 문제로 읽히는 반쯤 렌더링된 페이지를 남깁니다. 일반적인 원인: 음수 너비, 고정된 10px 텍스트 높이, 로드되지 않은 글꼴 두께.

산술을 신뢰하지 말고 경계에서 클램핑하십시오.

function W1(v, min){ const n = Number(v); return (isFinite(n) && n > 0) ? n : (min || 1); }

모든 너비, 높이 및 텍스트 너비를 이 함수를 통해 라우팅하십시오. 클램프 하나는 잘못된 박스 하나를 만들 뿐이지만, 예외 하나는 페이지 전체를 망가뜨립니다.

문제가 되는 산술은 특정 너비에서만 성립하는 고정된 뺄셈입니다:

const FEED = DW - PAD*2 - 400;   // 1440에서 840. 390에서 -50.

캔버스 너비에서 어떤 상수를 빼는 경우에는 중단점 가드가 필요합니다. 그렇지 않으면 좁은 화면에서 음수가 됩니다.

인식해야 할 증상: 넓은 프레임은 완성된 것처럼 보이고 좁은 프레임은 없거나 반쯤 그려져 다른 것과 겹칩니다. 위치 지정 버그가 아닙니다. 오류 토스트를 읽으십시오. 속성과 노드를 알려줍니다.


원점에 그리지 마십시오

(0, 0)에서 시작하는 플러그인은 이미 있는 것 위에 위치하며, 자신의 프레임만 제거하는 클리너로는 해결되지 않습니다: 페이지를 공유하는 두 플러그인이 모두 캔버스가 비어 있다고 믿습니다.

먼저 측정한 다음, 깨끗하게 시작하십시오:

let startY = 0;
figma.currentPage.children.forEach(n => { startY = Math.max(startY, n.y + n.height); });
if (figma.currentPage.children.length) startY += 600;

캔버스의 각 행에 레이블을 지정하고, 클리너가 빌드가 내보내는 모든 것(프레임 및 레이블)과 일치하도록 하십시오. 그렇지 않으면 다시 실행할 때 고아 텍스트가 남습니다.


이 정확한 순서로 텍스트 크기 조절

가장 비용이 많이 드는 실수입니다. Figma는 텍스트 노드의 내용이 변경될 때 높이를 다시 계산하며, 크기를 조정할 때가 아닙니다.

const str = String(s);
const t = figma.createText();
t.fontName = { family: "Inter", style: weight };
t.characters = str;                    // 1. 첫 번째
t.fontSize = size;                     // 2. 스타일 다음
t.lineHeight = { unit: "PERCENT", value: 145 };
if (width) {
  t.textAutoResize = "HEIGHT";         // 3. 빈 노드에서는 적용되지 않음
  t.resize(width, t.height);           //    절대 리터럴 높이를 사용하지 마십시오
  t.characters = str;                  // 4. 새 너비에서 다시 측정하도록 강제
} else {
  t.textAutoResize = "WIDTH_AND_HEIGHT";
}
  • textAutoResize = "HEIGHT"는 노드에 문자가 있기 전에 설정하면 무시됩니다. 속성을 이동하지 않고 내용을 마지막에 배치하도록 재정렬하면 상황이 더 나빠집니다.
  • resize(w, 10)는 높이를 영원히 10으로 고정합니다. t.height를 전달하십시오.
  • 동일한 문자열을 다시 할당하는 것은 이중 안전 장치 역할을 합니다.

증상: 명시적 너비가 있는 모든 텍스트는 ~10px 높이이고, 없는 텍스트는 정확하며, 높이가 텍스트에서만 나오는 모든 프레임은 패딩으로 축소됩니다.


width는 고정 너비를 의미합니다 — 어떤 축인지는 명확하지 않습니다

if (opts.horizontal) {
  f.primaryAxisSizingMode = width ? "FIXED" : "AUTO";   // width가 기본 축
  f.counterAxisSizingMode = "AUTO";
} else {
  f.counterAxisSizingMode = width ? "FIXED" : "AUTO";   // width가 보조 축
  f.primaryAxisSizingMode = "AUTO";
}

가로 프레임에서 counterAxisSizingMode를 설정하면 높이가 고정되고 너비는 내용에 맞게 조정됩니다. 그렇게 빌드된 모든 행에는 여유 너비가 없으므로 어떤 자식도 그 안으로 커질 수 없습니다.

크기 조정 모드를 설정하기 전에 크기를 조정하거나, 명시적 크기가 내용 맞춤과 충돌합니다.


행을 채우는 열

function growCol(n) { n.layoutGrow = 1; n.counterAxisSizingMode = "FIXED"; return n; }

width: null로 생성된 세로 프레임은 내용에 맞게 조정됩니다. layoutGrow = 1과 STRETCH 자식을 지정하면 순환 제약 조건이 발생합니다: 부모는 자식에 맞춰지고, 자식은 부모에 맞춰집니다. Figma는 최소값으로 해결합니다.

증상: ~45px 너비의 카드, 한 줄에 한 문자씩 줄바꿈되는 본문 텍스트, 1 x 988을 보고하는 텍스트 노드. 페이지 프레임이 있어야 할 것보다 세 배 더 크며, 전적으로 잘못된 수직 공간 때문입니다.

STRETCH에 의존하여 해결하는 것보다 행에 명시적 너비를 전달하는 것이 좋습니다.


측정하십시오, 보지 마십시오

기하학 정보는 스크린샷보다 빠르게 질문에 답하며, 위의 모든 버그를 드러냅니다. get_metadata를 가져온 다음:

  • 높이 ≤ 12px → 축소된 텍스트 또는 프레임
  • 너비가 1 → 위의 순환 제약 조건
  • HTML 등가물보다 훨씬 더 큰 페이지 → 내용이 아닌 좁은 줄바꿈
  • 두 개를 예상했는데 각 프레임이 하나의 복사본 → 실행이 발생하지 않음

총계를 이상 확인(sanity-check)하십시오: 블록의 y-범위는 페이지에 빈틈없이 타일링되어야 하며, 마지막 블록은 페이지 높이에서 정확히 끝나야 합니다.


로컬에서 플러그인 시뮬레이션

이 스킬의 tools/figma_stub.js는 준비된 엄격한 스텁입니다; node tools/test_figma_stub.js는 Figma가 던지는 곳에서 여전히 예외를 던지는지 증명합니다.

figma 전역 객체의 스텁은 예외, null 추가 및 구조적 실수를 왕복 전에 잡아냅니다. 텍스트 측정을 에뮬레이션할 수 없으므로 코드가 실행된다는 것은 증명하지만 레이아웃이 올바르다는 것은 증명하지 않습니다.

스텁을 실제 API만큼 엄격하게 만드십시오. 그렇지 않으면 Figma에서 죽는 빌드를 통과시킬 수 있습니다. 다음 각각은 스텁이 관대했기 때문에 캔버스에 도달한 버그입니다:

resize(w, h){
  if (!(w >= 0)) throw new Error('resize에서: Property "width" failed validation');
  if (!(h >= 0)) throw new Error('resize에서: Property "height" failed validation');
  ...
}
set fontName(v){
  if (!loaded.has(v.family + "|" + v.style))
    throw new Error('로드되지 않은 글꼴 사용 불가: "' + v.family + " " + v.style + '"');
  ...
}
appendChild(c){ if (!c) throw new Error("null child"); ... }

스텁에 currentPage.children을 제공하고, 실제 페이지가 이미 보유한 프레임을 시드하십시오 — 이것이 로컬에서 원점 충돌이 나타나는 방식입니다.

위치 지정을 확인하려면 스텁이 자동 레이아웃 높이도 계산해야 합니다: VERTICAL 프레임의 경우 자식에 itemSpacing과 패딩을 더하고, HORIZONTAL 프레임의 경우 최대값을 취하며, primaryAxisSizingMode === "FIXED"일 때 고정 값을 반환합니다. 대략적이어도 행 중첩을 잡기에 충분합니다.

모든 디스패치 경로, 모든 중단점 및 모든 글꼴 설정을 실행하십시오. 행복한 경로만이 아닙니다:

Inter만 · Inter + 모노 · 모두 세 개 · Inter Regular만
데스크탑 · 모바일     다크 · 라이트

글꼴: 요청할 것을 로드하고, 얻은 것을 해결하십시오

로드되지 않은 글꼴을 할당하면 예외가 발생합니다. 그 뒤에 두 가지 함정이 있습니다:

  • 코드가 요청하는 모든 두께를 미리 로드해야 합니다. Regular, Medium 및 Bold를 로드한 다음 Semi Bold를 요청하는 것은 그것을 사용하는 하나의 카드를 기다리는 충돌입니다.
  • 스타일 이름은 패밀리마다 다릅니다. Inter는 "Semi Bold"라고 말하고; JetBrains Mono는 "SemiBold"라고 말합니다. 올바른 두께를 잘못된 철자로 요청해도 여전히 예외가 발생합니다.

각 두께를 개별적으로 탐색하고, 실제로 로드된 것을 기록하고, 가정하지 않고 대체 체인을 통해 모든 요청을 해결하십시오:

const ALIAS = { "Semi Bold": ["Semi Bold","SemiBold","Medium","Bold","Regular"], … };
function font(family, style){
  for (const s of ALIAS[style]) if (LOADED[family+"|"+s]) return { family, style:s };
  for (const s of ALIAS[style]) if (LOADED["Inter|"+s]) return { family:"Inter", style:s };
  return { family:"Inter", style:"Regular" };
}

진입점에서 Promise.all로 엄격하게 미리 로드하지 마십시오 — 누락된 하나의 두께가 해석기가 처리했을 빌드를 죽입니다. 닫는 토스트에 해결된 패밀리를 보고하십시오.


하나의 플러그인, 하나의 작업

단일 플러그인 내에서 편집기 유형, 페이지 이름 또는 선택 항목에 대한 디스패치 스위치를 피하십시오. 페이지 이름 스위치는 사용자가 작업이 작동하기 전에 의식을 수행해야 함을 의미하며, 조용히 잘못된 일을 할 때 읽을 것이 없습니다.

다른 id와 name 값을 가진 두 개의 매니페스트는 비용이 들지 않으며 전체 문제 클래스를 제거합니다. Figma는 또한 다시 가져온 매니페스트가 이미 알고 있는 id를 가져올 때 캐시된 등록을 재사용하므로 id를 변경하는 것이 깨끗한 로드를 강제하는 방법입니다.


주석 다시 읽기

MCP는 get_metadata, get_design_context, get_screenshot, get_figjam, use_figma를 노출합니다 — 그 중 어느 것도 주석을 읽지 않으며, Plugin API도 주석에 접근할 수 없습니다. 주석은 REST API에 있습니다.

이 스킬 폴더의 tools/figma_comments.py를 사용하십시오 (Python 3, 표준 라이브러리):

T=~/.claude/skills/figma-build/tools/figma_comments.py
python3 $T check <file-url>            # 확인: 토큰이 주석을 읽음 (파일 KEY에 N)
python3 $T fetch <file-url>            # 열린 스레드를 JSON으로, 원장 및 해결된 것은 필터링
python3 $T mark  <file-url> ID... --change "변경 사항"
python3 $T reply <file-url> ROOT_ID "완료: 로고 20% 더 크게"   # 쓰기 범위 필요

~/.figma-token에서 토큰을 읽고 절대 출력하지 않습니다. 작업 폴더에서 실행하여 원장이 거기에 저장되도록 하거나 FIGMA_LEDGER를 설정하십시오. 삭제 명령은 없습니다. SSL 인증서 오류가 보고되면 Python에 루트 인증서가 없는 것입니다: /usr/bin/python3를 대신 실행하거나 pip install certifi를 실행하십시오.

범위: 파일 주석 → 읽기. 토큰을 파일에 저장하고 (~/.figma-token, chmod 600), 대화에 절대 저장하지 마십시오. 사용자가 셸 명령을 실행할 수 없는 경우, 자리 표시자로 파일을 작성하고 open -a TextEdit로 열어 붙여넣도록 하십시오.

/v1/me는 주석 범위의 토큰에서 403을 반환합니다. 이는 예상된 동작이며, 토큰이 손상된 것이 아닙니다. /v1/files/:key/nodes도 file_content:read 없이 403을 반환합니다.

각 주석은 client_meta를 전달합니다:

{ "node_id": "2:95", "node_offset": { "x": 279, "y": 1257 } }

get_metadata를 가져와서 y-범위에 오프셋이 포함된 자식 프레임을 찾아 블록에 매핑하십시오. 스레드는 평평하게 반환됩니다 — parent_id로 그룹화하고, 답글은 종종 실제 지시를 전달하는 반면 루트는 포인터일 뿐입니다.

주석은 파일별로 다릅니다. 콘텐츠를 새 파일로 이동하면 주석이 남겨집니다.

"내 주석을 읽어줘"라고 요청받았을 때의 검토 루프

  1. 가져오기 figma_comments.py fetch로 주석을 그룹화하여 스레드로 만듭니다. 해결된 것(resolved_at 설정됨)과 이미 처리된 원장에 있는 모든 ID를 건너뜁니다 (아래). 이전 주석은 해결될 때까지 파일에 남아 있으므로, 이 두 필터가 없으면 모든 검토와 이후 대화가 이전 라운드의 피드백을 다시 적용합니다.
  2. 매핑 각 스레드를 위와 같이 client_meta를 통해 프레임에 매핑합니다.
  3. 그리기 전에 확인: 각 주석, 매핑된 프레임 및 계획된 변경 사항을 하나의 짧은 표로 나열하십시오. 빈 캔버스에 고정된 주석이나 모호한 주석은 추측 대신 질문을 받습니다.
  4. 제자리에서 변경 사항을 적용: 기존 노드의 속성을 변경하고 ID를 유지하십시오. 주석이 있는 프레임을 제거하고 다시 그리지 마십시오. 그러면 주석이 고아가 됩니다. MCP(use_figma)를 통해 캔버스가 직접 업데이트됩니다. 개발 플러그인을 통해 디스크의 코드를 편집하고, 빌드 스탬프를 올린 다음, 사용자에게 플러그인을 다시 실행하도록 요청하십시오.
  5. 확인 get_metadata와 스크린샷으로 확인하십시오 (측정하십시오, 보지 마십시오 참조). 플러그인 경로에서는 닫는 토스트에 새 스탬프가 표시되는지 확인한 후 캔버스를 신뢰하십시오.
  6. 보고 주석별로 변경된 사항을 보고하십시오. 토큰에 쓰기 범위가 있고 사용자가 원하는 경우 각 스레드에 답글을 남기십시오.
  7. 기록 턴이 끝나기 전에 처리된 모든 주석 ID(스레드 루트 및 답글)를 figma_comments.py mark로 기록하십시오. 질문으로만 답변한 것도 포함합니다.
  8. 절대 주석을 삭제하지 마십시오. 정리를 요청받더라도 삭제하지 마십시오. 이는 검토 기록입니다. 이전 피드백은 삭제를 통해서가 아니라 원장을 통해 이후 라운드에서 제외됩니다. Figma에서 라운드를 지워야 하는 경우 사용자가 주석을 해결합니다.

처리된 원장. 작업 옆에 있는 JSON 파일: 캠페인 폴더의 figma/comments-handled.json, 그렇지 않으면 작업 디렉토리의 figma-comments-handled.json. 새 대화를 포함한 모든 검토 시작 시 읽으십시오.

{ "<file_key>": { "<comment_id>": { "handled_at": "2026-11-03T14:05:00Z", "change": "1080x1920에서 로고 20% 더 크게" } } }

주석을 해결하기 위한 API 엔드포인트는 없으므로 해결은 사용자에게 맡깁니다.

재가져오기를 요청해야 하는 경우 (플러그인 경로 전용): 토스트에 오래된 스탬프가 표시되거나 프레임이 보이는 상태에서 "replaced 0"이 표시될 때입니다. 플러그인을 제거하고, manifest.json을 다시 가져온 다음 탭을 다시 열도록 요청하십시오. 그래도 실패하면 매니페스트 id를 변경하십시오 (하나의 플러그인, 하나의 작업 참조).


대규모 스크린샷

블록당 하나의 호출보다 페이지당 하나의 캡처와 로컬 자르기가 더 좋습니다. 블록 y/높이는 get_metadata에서 가져오고, 프레임 높이 이상의 maxDimension은 1:1을 제공하며 자르기가 정확하게 맞습니다.

관련 스킬