O alvo de export padrão do figmascope é Jetpack Compose. Isso não é arbitrário — o modelo de layout do Compose (Column, Row, Box, Modifier) mapeia de perto para os tipos de nó do IR (stack, overlay, absolute, leaf). Um stack vertical no Figma é um Column no Compose. A tradução é mecânica, o que a torna adequada ao codegen conduzido por agentes.

Este passo a passo cobre o mapeamento IR-para-Compose em detalhe, mostra um fragmento JSON real com o Composable correspondente e explica a camada de mapeamento de tokens.

Por que Compose é o alvo padrão

Três razões estruturais:

  1. Auto-layout ↔ Column/Row. Os frames de auto-layout do Figma (a grande maioria dos designs modernos do Figma) exportam como nós kind: "stack". Nós stack têm uma propriedade axisvertical mapeia para Column, horizontal mapeia para Row. Este é um mapeamento 1:1 sem etapa de interpretação.
  2. Tokens de espaçamento ↔ valores dp. O Compose usa Dp para todas as dimensões de layout. Valores de token em tokens.json são inteiros sem unidade (ex.: spacing.16 = 16) que mapeiam diretamente para 16.dp. Sem conversão, sem escala.
  3. Tokens de cor ↔ Color composable. Valores hex do Figma em tokens.json mapeiam para Color(0xFFrrggbb) com uma única transformação. A chave do token se torna um nome de variável semântico no seu tema.

Os tipos de nó do IR e seus mapeamentos em Compose

Tipo IRPropriedadesPrimitivo Compose
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlayfilhos em camadasBox
absolutex, y, width, heightBox com Modifier.offset(x.dp, y.dp)
leaftype: "text"Text com TextStyle
leaftype: "rectangle" com fillBox(Modifier.background(Color(...)))

Todos os tipos de nó podem carregar uma propriedade spacing (gap entre filhos) e um objeto padding (top/right/bottom/left). Ambos referenciam chaves de token.

Mapeamento de tokens em detalhe

Um arquivo tokens.json parece assim:

{
  "spacing": {
    "4": 4, "8": 8, "12": 12, "16": 16,
    "20": 20, "24": 24, "32": 32, "48": 48
  },
  "radius": {
    "4": 4, "8": 8, "12": 12, "16": 16, "full": 9999
  },
  "color": {
    "7f5cfe": "#7F5CFE",
    "1a1a2e": "#1A1A2E",
    "f6f2ea": "#F6F2EA",
    "ffffff": "#FFFFFF",
    "e53935": "#E53935"
  },
  "typography": {
    "heading.24": { "size": 24, "weight": 700, "lineHeight": 1.2 },
    "body.14": { "size": 14, "weight": 400, "lineHeight": 1.5 },
    "label.12": { "size": 12, "weight": 500, "lineHeight": 1.4 }
  }
}

As regras de mapeamento:

As chaves de token são intencionalmente opacas (strings hex para cor, strings numéricas para espaçamento) para não enviesar o modelo em direção a nenhuma convenção de nomenclatura específica. Seu tema Compose pode criar aliases com nomes semânticos — colorPrimary, spacingMd — independentemente do IR.

Um exemplo real: JSON da tela home para Composable

Aqui está um IR simplificado mas estruturalmente correto de uma tela home. Um stack vertical com um leaf de cabeçalho e uma lista de cards:

{
  "name": "home",
  "kind": "stack",
  "axis": "vertical",
  "spacing": "spacing.24",
  "padding": { "top": "spacing.16", "right": "spacing.16",
               "bottom": "spacing.16", "left": "spacing.16" },
  "fill": "color.f6f2ea",
  "children": [
    {
      "kind": "leaf",
      "type": "text",
      "stringRef": "home.title",
      "typography": "typography.heading.24",
      "fill": "color.1a1a2e"
    },
    {
      "kind": "stack",
      "axis": "vertical",
      "spacing": "spacing.12",
      "children": [
        {
          "kind": "overlay",
          "radius": "radius.12",
          "fill": "color.ffffff",
          "padding": { "top": "spacing.16", "right": "spacing.16",
                       "bottom": "spacing.16", "left": "spacing.16" },
          "children": [
            {
              "kind": "leaf",
              "type": "text",
              "stringRef": "home.card.label",
              "typography": "typography.label.12",
              "fill": "color.7f5cfe"
            }
          ]
        }
      ]
    }
  ]
}

O Composable correspondente — o que um agente deve produzir a partir deste IR:

@Composable
fun HomeScreen() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xFFF6F2EA))
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(24.dp)
    ) {
        Text(
            text = stringResource(R.string.home_title),
            style = TextStyle(
                fontSize = 24.sp,
                fontWeight = FontWeight.Bold,
                lineHeight = 28.8.sp,
                color = Color(0xFF1A1A2E)
            )
        )
        Column(
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Box(
                modifier = Modifier
                    .clip(RoundedCornerShape(12.dp))
                    .background(Color(0xFFFFFFFF))
                    .padding(16.dp)
            ) {
                Text(
                    text = stringResource(R.string.home_card_label),
                    style = TextStyle(
                        fontSize = 12.sp,
                        fontWeight = FontWeight.Medium,
                        lineHeight = 16.8.sp,
                        color = Color(0xFF7F5CFE)
                    )
                )
            }
        }
    }
}

Cada valor no Composable é rastreável a uma chave de token no IR. Nada é hardcoded — 16.dp vem de spacing.16, 24.dp de spacing.24, Color(0xFF7F5CFE) de color.7f5cfe.

String refs — mapeamento para stringResource

Todo nó de texto no IR carrega um stringRef com uma chave em notação de ponto. O arquivo strings.json mapeia chaves para valores de exibição e fallbacks:

{
  "home.title": { "value": "Bom dia", "fallback": "Bom dia" },
  "home.card.label": { "value": "Resumo de hoje", "fallback": "Resumo" }
}

A notação de ponto mapeia para IDs de recursos de string do Android com pontos substituídos por underscores: home.titleR.string.home_title. O campo fallback é o que você hardcodifica como string literal se o recurso ainda não existir em strings.xml:

text = stringResource(R.string.home_title, "Bom dia")

Diga ao agente para sempre usar o campo fallback — não uma string vazia — para que a tela seja legível durante o desenvolvimento antes que strings.xml seja populado.

Posicionamento absoluto

Nós com kind: "absolute" usam coordenadas do Figma diretamente. Aparecem em designs com elementos sobrepostos ou ancorados em posições específicas. O mapeamento em Compose usa Box como pai e Modifier.offset nos filhos:

// IR: { "kind": "absolute", "x": 24, "y": 80, "width": 120, "height": 40 }

Box(modifier = Modifier.fillMaxSize()) {
    Box(
        modifier = Modifier
            .offset(x = 24.dp, y = 80.dp)
            .size(width = 120.dp, height = 40.dp)
    ) {
        // filhos
    }
}

Posicionamento absoluto é incomum em arquivos Figma bem estruturados (a maioria dos arquivos modernos usa auto-layout). Quando você o vê, verifique se a intenção do design é realmente absoluta ou se o designer simplesmente não aplicou constraints de auto-layout — o IR não consegue inferir a intenção.

Lacunas honestas

O bundle cobre o caso comum bem. Algumas coisas que ele não cobre:

Essas lacunas são explícitas — aparecem nos avisos de _meta.json e no CONTEXT.md. O agente não adivinhou silenciosamente através delas.

Exporte o bundle de contexto no app principal do figmascope, depois use-o com Claude Code ou Cursor para implementar Composables diretamente do IR. Sem adivinhação por screenshot, sem valores hardcoded.