top of page

O primeiro resultado: como definimos a governança dos tokens do Harness Design System

  • Foto do escritor: john morais
    john morais
  • 22 de jun.
  • 7 min de leitura

Read in english Nesses três dias — o início das duas semanas de dedicação exclusiva do projeto, que a gente começou numa quarta-feira — definimos um processo de governança e uma forma de criar, editar e atualizar os tokens, tanto no Figma quanto no código.


Fizemos questão de resolver a governança logo de cara, porque o objetivo nunca foi só fazer funcionar uma vez. Era entregar uma solução escalável, que outras pessoas do time consigam manter depois.



O primeiro dia: colocar todo mundo na mesma página


O kickoff foi sobre alinhamento. Falamos do estado atual do design system, das próximas etapas, e colocamos designers e desenvolvedores na mesma página antes de qualquer linha de código. E já começamos o experimento central da semana: aplicar documentação de harness nos tokens usando o Claude Code.


Em paralelo, o time de desenvolvimento começou a estudar uma forma de ter um repositório no GitHub como fonte da verdade dos tokens — um lugar único, atualizado sempre que necessário, de onde React e Flutter vão consumir.



E fizemos uma POC importante: o de-para da mudança dos tokens. A revisão que fizemos mexeu em muita coisa, então criamos um mapa que mostra cada token atual do sistema e qual será o novo equivalente. A ideia é simples: com esse mapa, a IA consegue atualizar os tokens no código muito mais rápido do que faríamos na mão.




O bind: o problema que não tinha solução limpa


Antes do problema, preciso explicar um termo que vou usar bastante: bind. Fazer o bind é "amarrar" uma propriedade de um elemento a um token — uma variável do Design System — em vez de deixar um valor solto, digitado na mão. No nosso caso, fazer (ou corrigir) o bind é garantir que cada cor, borda e arredondamento de um botão esteja amarrado ao token certo: não num valor digitado à mão, não num token errado, nem apontando para uma variável de outra biblioteca. É isso que mantém o componente consistente e fácil de manter — e, lá na frente, é o que reduz tempo e custo.



E foi exatamente nessa etapa que apareceu uma questão que tomou um tempo bom: precisávamos fazer o bind dos componentes com os novos tokens no Figma. Parece simples. Não foi.


Primeiro testei o Claude Code via MCP. Não rolou. A solução seguinte foi pedir pro Claude gerar scripts e usar o plugin Scripter pra rodar o bind. Também não funcionou bem. No segundo dia recebi uma atualização do Figma Agents e, depois de iterar, cheguei num prompt de bind que funcionou melhor. Não perfeito — mas ajudou de verdade.


O prompt ficou assim:

Faça o bind das variáveis semânticas nas propriedades visuais do componente selecionado, seguindo estas regras:

Corner radius → bind cada canto individualmente (topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius) à variável correspondente da coleção Radius (ex: valor 4 → sm, 8 → md, 12 → lg, 16 → xl). Aplica em todos os nós com cornerRadius > 0 sem binding: COMPONENTs, FRAMEs, INSTANCEs, RECTANGLEs.

Stroke weight → bind cada lado individualmente (strokeTopWeight, strokeBottomWeight, strokeLeftWeight, strokeRightWeight) à variável correspondente da coleção Stroke Width (ex: valor 0.5 → sm, 1 → md, 2 → lg, 4 → xl). Aplica em qualquer nó com strokes e sem strokeWeight binding. Para VECTORs de ícone, usa setBoundVariable('strokeWeight', ...) direto.

Cores de stroke de ícones → ícones que renderizam via stroke (VECTORs sem fill, apenas strokes) devem ter o stroke color bound a variável da coleção Colour Usage > Icon/*. Identifica a variável correta pelo contexto da variante (cor do botão/estado). Não tocar nos fills de INSTANCE que envolvem ícones — se o ícone é stroke-based, limpar qualquer fill residual da instância wrapper em vez de bindá-lo.

Importante: Antes de executar, escaneia todas as variantes do component set para mapear o que está sem bind. Divide as operações em scripts separados por tipo de propriedade para evitar timeout (corner radius primeiro, depois stroke weight nos components, depois nos VECTORs internos). Verifica o resultado exportando uma imagem e checando node.boundVariables (atenção: cornerRadius é armazenado como topLeftRadius etc., e strokeWeight como strokeTopWeight etc. — checar esses campos, não cornerRadius diretamente).


Mas mesmo esse prompt não era a solução definitiva. Ela veio depois — e de um jeito que reforça uma das coisas que mais defendo. A gente construiu um plugin próprio pro Figma, feito de forma multidisciplinar, designers e desenvolvedores juntos. Foi essa colaboração que permitiu chegar numa solução de verdade: o plugin entregou resultados muito melhores e personalizados exatamente ao que a gente precisava, em vez de depender de script genérico ou de um prompt que às vezes acerta e às vezes não. E ele não resolveu só o bind, também passou a exportar os tokens do jeito que a gente queria.



Esse é o tipo de coisa que não aparece no slide bonito. Você tenta um caminho, falha, tenta outro, falha de novo, e o que destrava de verdade, no fim, foi o time sentar junto e construir a ferramenta que faltava. Faz parte.


Nesse mesmo período também encontramos problemas estruturais na semântica dos nomes dos tokens e ajustamos isso. Nome de token importa mais do que parece quando quem vai ler é uma LLM.



O terceiro dia: o schema, o momento que destravou tudo


O terceiro dia foi o mais importante. Foi quando paramos pra estruturar de verdade como o harness seria construído: onde essa informação ficaria, como seria atualizada, quem mexe nela. E foi aí que definimos o nosso padrão de Schema para a description dos tokens — e finalmente aplicamos a camada de Harness Design.



Esse foi o exemplo de schema que criamos:

{
  "purpose": "Fill sólido de alta ênfase de marca — identidade de marca e ação primária. Passo Bold base: superfície protagonista (ação primária, item selecionado, badge sólido).",
  "useWhen": ["Ação primária ou elemento de máxima proeminência da marca."],
  "doNotUseWhen": [
    { "scenario": "Sinalizar status (sucesso, erro, atenção, processo)", "reason": "marca não comunica estado; usá-la para status gera leitura ambígua de severidade", "alternative": "Background|Text|Icon/Status/*" },
    { "scenario": "Conteúdo apenas informativo, que não é a ação principal", "alternative": "Status/Info/* (mesma cor, papel semântico correto)" }
  ],
  "pairsWith": { "text": "Text/Brand/Inverse", "icon": "Icon/Brand/Inverse" },
  "note": null
}

Criar esse schema só foi possível por uma decisão de estratégia: nesse primeiro momento, colocamos a documentação direto na description de cada variable, dentro do próprio Figma. E validamos algo que era a peça que faltava — o Claude Code consegue puxar essa informação junto com o JSON das variables. Ou seja: tanto os tokens quanto o schema de harness vão morar no Figma.


Faço questão de dizer uma coisa: essa estrutura é totalmente funcional pro nosso contexto, com as ferramentas e o time que a gente tem. Não é a arquitetura definitiva do universo. É a que faz sentido pra gente agora.



O fluxo que ficou decidido



Com o schema definido, o fluxo de aplicação de harness nos tokens ficou assim:


  1. Definir o schema primeiro. Antes de qualquer coisa, o contrato de documentação que vai no campo description — usando o W3C DTCG como base (que já garante name, title e value como campos estáticos) e adicionando as propriedades de harness por cima. Esse esquema fica acessível ao Claude no Prolog Claude, nosso repositório com as informações do Prolog que o Claude consome, no arquivo CLAUDE.md.



  2. Carga inicial via Claude Code: o experimento que fizemos. Com o schema pronto, pedimos pro Claude Code entrar no Figma via MCP e analisar como cada token está sendo usado. É desse contexto que ele tira o que precisa pra escrever o schema de cada um dos ~600 tokens. Depois de escrever os schemas, ele volta ao Figma, também via MCP, e insere cada um na description da variable correspondente — em vez de alguém preencher 600 tokens na mão. Não esperamos que saia perfeito de primeira; a ideia é testar a eficácia desse caminho e ir aprimorando depois.



  3. Manutenção quando um token muda. O designer cria ou edita a variable no Figma e escreve a description seguindo o schema. Uma skill no Claude Code garante que ele não esqueça de aplicar o esquema, não apague nada que já existe e edite do jeito certo.


  4. Export e commit, a parte manual de verdade. Um parser no Claude Code lê as descriptions via Figma MCP, destrincha o harness, baixa o JSON atual do repositório, compara o diff e comita no Prolog Claude. Esse JSON é o que React e Flutter consomem depois.



Por que manual, e não a versão "ideal"


A pergunta óbvia é: por que não automatizar tudo? A gente desenhou, sim, versões mais automatizadas — inclusive uma com uma ferramenta nossa centralizando tudo. Mas chegamos a uma conclusão que vale o registro: atualizar token é raro. Acontece menos de uma vez por mês, é pontual.


Construir toda a automação agora não compensava. Cada passo automático abre porta pra um edge case, o processo cai no meio, a IA edita errado, alguém esquece de exportar e o custo de desenhar isso direito, lidando com cada uma dessas falhas, é alto. Pra uma tarefa que acontece uma vez por mês, o caminho manual é mais barato e mais confiável.


Isso conversa direto com o que escrevi em "Antes de AI-first, human-first". A solução ideal no papel é linda. A solução real é a que o time consegue operar hoje, sem se afogar nos casos de exceção. Escolhemos a real e gastamos bastante tempo de discussão num único brainstorm pra chegar nessa escolha aparentemente simples. Valeu cada minuto.



Harness para token não é harness para componente


Uma coisa que ficou clara nesse processo: harness para token é diferente de harness para componente. O normal é que a LLM construa interfaces usando os componentes que já existem — ela não fica criando componente novo o tempo todo. A necessidade de ela entender como usar um token aparece mais no cenário em que precisa tomar decisões ao criar algo do zero, sem um componente pronto pra reaproveitar.


Esse cenário tende a ficar mais raro à medida que a gente aplica harness nos componentes e amplia a quantidade de componentes utilizáveis. Por isso, inclusive, o schema dos tokens acabou menor do que eu imaginava — ele não precisa dar conta de tudo, porque a maior parte das decisões de interface vai vir dos componentes, não dos tokens soltos.



O que vem: refinamento e testes para harness em tokens

O que vamos fazer em seguida é testar como a LLM está entendendo os design tokens e validar todo o fluxo que criamos para atualizar tokens. Depois disso, finalmente chegamos nos testes com componentes.

Comentários


bottom of page