figma-build

Criação e depuração de conteúdo no Figma e FigJam a partir de um plugin — regras de dimensionamento e fonte que falham silenciosamente, clamps que impedem que um valor ruim quebre a construção, leitura de comentários via API REST e o loop que mostra se seu código realmente foi executado. Use ao escrever ou depurar um plugin do Figma, ao gerar quadros ou wireframes programaticamente, ou quando a construção aborta no meio, um layout colapsa, texto quebra uma linha por caractere, quadros se sobrepõem ou "parece igual".

O que é figma-build?

Esta skill contém regras e padrões para construir conteúdo no Figma e FigJam programaticamente por meio de plugins, resolvendo os problemas mais comuns de falhas silenciosas. O foco principal está em validar valores de entrada com clamps para evitar que valores inválidos interrompam a renderização, estabelecer a ordem correta de operações para textos (conteúdo antes de estilos, redimensionamento antes de medir) e gerenciar carregamento de fontes com fallback. Ela também ensina a criar um loop de construção com timestamp para depuração, medir resultados com metadados em vez de capturas de tela e simular plugins localmente usando um stub rigoroso do Figma. Para revisões com comentários, a skill fornece um script Python que lê comentários via API REST (já que nem o Plugin API nem os MCPs padrão têm acesso a eles) e descreve um processo completo de revisão que nunca remove quadros comentados, para não perder o histórico. As plataformas suportadas são plugins do Figma e FigJam, com recomendações de usar o MCP "use_figma" quando disponível.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Figma build

Tudo aqui falhou silenciosamente primeiro. Nada lança exceções, nada avisa, e a tela simplesmente parece errada.

O loop, antes de qualquer coisa

Carimbe a construção e limpe sua própria saída. Caso contrário, você pode depurar "parece igual" por um longo tempo quando o Figma está servindo um pacote em cache.

const BUILD = "15:03";                 // incremente a cada pacote
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 + " — substituídas " + removed + " frames.");

Dois números no toast, e ambos são diagnósticos. Um carimbo desatualizado significa que o pacote está em cache: remova o plugin, reimporte o manifesto, reabra a aba. Substituídas 0 com quadros visíveis na tela significa a mesma coisa.

Sem isso, um plugin que recria quadros empilha um segundo conjunto sobre o primeiro e nada te diz qual deles você está vendo. Contar cópias de um nome de quadro é como você prova se uma execução aconteceu ou não.

Limpar e reconstruir é apenas para as primeiras construções. Depois que um arquivo está em revisão, os comentários são fixados em IDs de nós. Remover um quadro e desenhar um novo órfão todo comentário nele. Durante uma rodada de revisão, edite nós existentes no lugar (veja O loop de revisão).


Um valor ruim quebra toda a construção

Toda falha neste arquivo tem a mesma forma: um único valor que a API rejeita, lançado no meio do desenho, deixando uma página meio renderizada que parece um problema de layout. Culpados típicos: largura negativa, altura fixa de 10px em texto, peso de fonte não carregado.

Use clamping no limite em vez de confiar na aritmética.

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

Direcione cada largura, altura e largura de texto por ele. Um clamp custa uma caixa com aspecto errado; um throw custa a página.

A aritmética que pega é uma subtração fixa que só funciona em uma largura:

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

Qualquer constante subtraída da largura da tela precisa de um guarda de ponto de quebra, ou vai ficar negativa na tela pequena.

Sintoma a reconhecer: os quadros largos parecem prontos e os estreitos estão ausentes ou meio desenhados, sobrepondo outra coisa. Isso não é um bug de posicionamento. Leia o toast de erro — ele nomeia a propriedade e o nó.


Nunca desenhe na origem

Um plugin que começa em (0, 0) cai sobre o que já está lá, e um limpador que só remove seus próprios quadros não vai te salvar: dois plugins compartilhando uma página acreditam que a tela está vazia.

Meça primeiro, então comece limpo:

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

Rotule cada linha na tela, e faça o limpador corresponder a tudo que a construção emite — quadros e rótulos — ou reexecutar deixará texto órfão para trás.


Dimensionamento de texto, nesta ordem exata

O erro mais caro. O Figma recalcula a altura de um nó de texto quando seu conteúdo muda, não quando você o redimensiona.

const str = String(s);
const t = figma.createText();
t.fontName = { family: "Inter", style: weight };
t.characters = str;                    // 1. PRIMEIRO
t.fontSize = size;                     // 2. estilos depois
t.lineHeight = { unit: "PERCENT", value: 145 };
if (width) {
  t.textAutoResize = "HEIGHT";         // 3. NÃO funciona em um nó vazio
  t.resize(width, t.height);           //    nunca uma altura literal
  t.characters = str;                  // 4. força uma remedição na nova largura
} else {
  t.textAutoResize = "WIDTH_AND_HEIGHT";
}
  • textAutoResize = "HEIGHT" definido antes do nó ter caracteres é ignorado. Reordenar para colocar o conteúdo por último sem mover a propriedade piora.
  • resize(w, 10) fixa a altura em 10 para sempre. Passe t.height.
  • Reatribuir a mesma string é a medida de segurança dupla.

Sintoma: todo texto com largura explícita tem ~10px de altura, todo texto sem largura explícita está correto, e todo quadro cuja altura vem apenas de texto colapsa para seu padding.


width significa largura fixa — qual eixo não é óbvio

if (opts.horizontal) {
  f.primaryAxisSizingMode = width ? "FIXED" : "AUTO";   // largura É o eixo primário
  f.counterAxisSizingMode = "AUTO";
} else {
  f.counterAxisSizingMode = width ? "FIXED" : "AUTO";   // largura é o eixo contrário
  f.primaryAxisSizingMode = "AUTO";
}

Definir counterAxisSizingMode em um quadro horizontal fixa sua altura e deixa a largura ajustada ao conteúdo. Cada linha construída assim não tem largura sobrando, então nenhum filho pode crescer nela.

Redimensione antes de definir os modos de dimensionamento, ou o tamanho explícito luta contra o ajuste ao conteúdo.


Uma coluna que preenche uma linha

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

Um quadro vertical criado com width: null ajusta ao conteúdo. Dê a ele layoutGrow = 1 e filhos com ESTICAR e você tem uma restrição circular: o pai dimensiona para os filhos, os filhos dimensionam para o pai. O Figma resolve isso no mínimo.

Sintoma: cartões com ~45px de largura, corpo do texto quebrando uma linha por caractere, nós de texto relatando 1 x 988. Quadros de página três vezes mais altos do que deveriam, inteiramente devido a espaço vertical falso.

Prefira passar larguras explícitas para linhas em vez de confiar em ESTICAR para resolver.


Meça, não olhe

A geometria responde à pergunta mais rápido que uma captura de tela, e expõe todos os bugs acima. Use get_metadata, então:

  • alturas ≤ 12px → texto ou quadros colapsados
  • larguras de 1 → a restrição circular acima
  • uma página muito mais alta que sua equivalente HTML → quebra estreita, não conteúdo
  • uma cópia de cada quadro quando você esperava duas → a execução nunca aconteceu

Verifique a sanidade dos totais: os intervalos y de um bloco devem alinhar sua página sem lacunas, e o último bloco deve terminar exatamente na altura da página.


Simulando o plugin localmente

tools/figma_stub.js nesta skill é um stub estrito pronto; node tools/test_figma_stub.js prova que ele ainda lança onde o Figma lança.

Um stub do global figma captura throws, anexos nulos e erros estruturais antes de uma ida e volta. Ele não pode emular métricas de texto, então prova que o código executa, nunca que o layout está correto.

Faça o stub tão estrito quanto a API real, ou ele passará construções que morrem no Figma. Cada um destes é um bug que chegou à tela porque o stub era permissivo:

resize(w, h){
  if (!(w >= 0)) throw new Error('em resize: Property "width" failed validation');
  if (!(h >= 0)) throw new Error('em resize: Property "height" failed validation');
  ...
}
set fontName(v){
  if (!loaded.has(v.family + "|" + v.style))
    throw new Error('Cannot use unloaded font "' + v.family + " " + v.style + '"');
  ...
}
appendChild(c){ if (!c) throw new Error("null child"); ... }

Alimente o stub com currentPage.children, e semeie com os quadros que a página real já contém — é assim que uma colisão de origem aparece localmente.

Para verificar o posicionamento, o stub também precisa calcular alturas de layout automático: some os filhos mais itemSpacing e padding para um quadro VERTICAL, pegue o máximo para um HORIZONTAL, e retorne o valor fixo quando primaryAxisSizingMode === "FIXED". Grosseiro é suficiente para detectar sobreposição de linhas.

Execute todos os caminhos de despacho, todos os pontos de quebra e todas as configurações de fonte, não apenas o caminho feliz:

Inter apenas · Inter + mono · todos os três · Inter Regular apenas
desktop · mobile     dark · light

Fontes: carregue o que você vai pedir, e resolva o que você obtém

Atribuir uma fonte não carregada lança exceção. Duas armadilhas por trás disso:

  • Você deve pré-carregar todos os pesos que o código pedir. Carregar Regular, Medium e Bold e então solicitar Semi Bold é uma falha esperando pelo único cartão que o usa.
  • O nome do estilo difere por família. Inter diz "Semi Bold"; JetBrains Mono diz "SemiBold". Pedir o peso certo com a grafia errada ainda lança.

Teste cada peso separadamente, registre o que realmente carregou, e resolva cada requisição através de uma cadeia de fallback em vez de assumir:

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" };
}

Nunca pré-carregue estritamente no ponto de entrada com Promise.all — um peso faltando lá mata uma construção que o resolvedor teria tratado. Relate as famílias que você resolveu no toast de fechamento.


Um plugin, um trabalho

Resista a um switch de despacho dentro de um único plugin — por tipo de editor, nome de página, ou seleção. Um switch por nome de página significa que o usuário deve realizar um ritual antes da coisa funcionar, e quando ela silenciosamente faz a coisa errada, não há nada para ler.

Dois manifestos com diferentes id e name custam nada e removem toda a classe de problema. O Figma também reutiliza um registro em cache quando um manifesto reimportado carrega um id que já conhece, então mudar o id é como você força um carregamento limpo.


Lendo comentários de volta

O MCP expõe get_metadata, get_design_context, get_screenshot, get_figjam, use_figma — nenhum deles lê comentários, e a Plugin API também não tem acesso a comentários. Comentários vivem na API REST.

Use tools/figma_comments.py nesta pasta da skill (Python 3, biblioteca padrão):

T=~/.claude/skills/figma-build/tools/figma_comments.py
python3 $T check <file-url>            # OK: token lê comentários (N no KEY do arquivo)
python3 $T fetch <file-url>            # tópicos abertos como JSON, ledger e resolvidos filtrados
python3 $T mark  <file-url> ID... --change "o que mudou"
python3 $T reply <file-url> ROOT_ID "Feito: logo 20% maior"   # precisa do escopo de escrita

Ele lê o token de ~/.figma-token e nunca o imprime. Execute da pasta de trabalho para que o ledger seja salvo lá, ou defina FIGMA_LEDGER. Não há comando de exclusão. Se reportar uma falha de certificado SSL, o Python não tem certificados raiz: use /usr/bin/python3, ou pip install certifi.

Escopo: Comentários do arquivo → Leitura. Armazene o token em um arquivo (~/.figma-token, chmod 600), nunca na conversa. Se a pessoa não puder executar um comando de shell, escreva o arquivo com um placeholder e use open -a TextEdit para que ela cole.

/v1/me retorna 403 em um token com escopo de comentários. Isso é esperado, não um token quebrado. /v1/files/:key/nodes também 403a sem file_content:read.

Cada comentário carrega client_meta:

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

Mapeie para um bloco usando get_metadata e encontrando o quadro filho cujo intervalo y contém o deslocamento. Os tópicos vêm planos — agrupe por parent_id, e as respostas geralmente carregam a instrução real enquanto a raiz é apenas um ponteiro.

Comentários são por arquivo. Mover conteúdo para um novo arquivo os deixa para trás.

O loop de revisão, quando solicitado a "ler meus comentários"

  1. Busque com figma_comments.py fetch, que agrupa comentários em tópicos. Pule os resolvidos (resolved_at definido) e todo id já no ledger de tratados (abaixo). Comentários antigos permanecem no arquivo até serem resolvidos, então sem esses dois filtros toda revisão, e toda conversa posterior, reaplica o feedback da rodada anterior.
  2. Mapeie cada tópico para seu quadro através de client_meta, como acima.
  3. Confirme antes de desenhar: liste cada comentário, o quadro ao qual ele mapeia, e a mudança planejada, em uma tabela curta. Um comentário fixado em tela vazia, ou ambíguo, recebe uma pergunta em vez de um palpite.
  4. Aplique as mudanças no lugar: altere as propriedades dos nós existentes, mantendo seus ids. Nunca remova e redesenhe um quadro comentado, porque isso órfão seus comentários. Através do MCP (use_figma), a tela atualiza diretamente. Através de um plugin de desenvolvimento, edite seu código no disco, incremente o carimbo de construção, e peça à pessoa para executar o plugin novamente.
  5. Verifique com get_metadata e uma captura de tela (veja Meça, não olhe). Na rota do plugin, verifique se o toast de fechamento mostra o novo carimbo antes de confiar na tela.
  6. Reporte comentário por comentário o que mudou. Se o token tem o escopo de escrita e a pessoa deseja, responda em cada tópico.
  7. Registre todo id de comentário tratado (raízes e respostas) com figma_comments.py mark antes do turno terminar, incluindo aqueles que você apenas respondeu com uma pergunta.
  8. Nunca delete comentários, mesmo quando solicitado a limpar. Eles são o registro da revisão. Feedback antigo fica fora das rodadas posteriores através do ledger, não por exclusão. Se uma rodada precisa ser limpa no Figma, a pessoa resolve os comentários.

O ledger de tratados. Um arquivo JSON próximo ao trabalho: figma/comments-handled.json em uma pasta de campanha, senão figma-comments-handled.json no diretório de trabalho. Leia no início de toda revisão, incluindo em uma nova conversa.

{ "<file_key>": { "<comment_id>": { "handled_at": "2026-11-03T14:05:00Z", "change": "logo 20% maior em 1080x1920" } } }

Não há endpoint de API para resolver um comentário, então resolver fica com a pessoa.

Quando pedir uma reimportação (apenas na rota do plugin): quando o toast mostra um carimbo desatualizado, ou "substituídas 0" enquanto quadros estão visíveis. Peça para remover o plugin, importar seu manifest.json novamente e reabrir a aba. Se falhar, mude o id do manifesto (veja Um plugin, um trabalho).


Capturas de tela em escala

Uma captura por página mais recortes locais é melhor que uma chamada por bloco. O y/altura do bloco vem de get_metadata, maxDimension igual ou acima da altura do quadro dá 1:1, e os recortes se alinham exatamente.

Habilidades Relacionadas