Стандартна ціль експорту figmascope — Jetpack Compose. Це не випадково: модель компонування Compose (Column, Row, Box, Modifier) тісно відповідає видам вузлів IR (stack, overlay, absolute, leaf). Вертикальний stack у Figma — це Column у Compose. Трансляція механічна, що робить її добре придатною для кодогенерації агентами.

Це покрокове керівництво детально розглядає маппінг IR → Compose, показує реальний JSON-фрагмент із відповідним Composable і пояснює шар маппінгу токенів.

Чому Compose є ціллю за замовчуванням

Три структурні причини:

  1. Auto-layout ↔ Column/Row. Авто-layout фрейми Figma (переважна більшість сучасних дизайнів) експортуються як вузли kind: "stack". Stack-вузли мають властивість axisvertical відповідає Column, horizontalRow. Це відображення 1:1 без кроку інтерпретації.
  2. Токени відступів ↔ значення dp. Compose використовує Dp для всіх розмірів розкладки. Значення токенів у tokens.json — цілі числа без одиниць (наприклад, spacing.16 = 16), що напряму відповідають 16.dp. Без конверсії, без масштабування.
  3. Токени кольорів ↔ Color у Compose. Hex-значення Figma у tokens.json відображаються на Color(0xFFrrggbb) з однією трансформацією. Ключ токена стає семантичною змінною у вашій темі.

Вузлові типи IR і їх відповідники в Compose

Тип IRВластивостіПримітив Compose
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlayдочірні шари накладаютьсяBox
absolutex, y, width, heightBox з Modifier.offset(x.dp, y.dp)
leaftype: "text"Text з TextStyle
leaftype: "rectangle" із заливкоюBox(Modifier.background(Color(...)))

Усі вузлові типи можуть мати властивість spacing (відступ між дочірніми елементами) та об'єкт padding (верх/право/низ/ліво). Обидва посилаються на ключі токенів.

Детальний маппінг токенів

Файл tokens.json виглядає так:

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

Правила маппінгу:

Ключі токенів навмисно непрозорі (hex-рядки для кольорів, числові рядки для відступів), щоб не нав'язувати модель жодній конвенції іменування. Ваша тема Compose може алясувати їх до семантичних назв — colorPrimary, spacingMd — незалежно від IR.

Реальний приклад: JSON екрану до Composable

Ось спрощений IR екрану. Вертикальний stack із заголовком-листом і списком карток:

{
  "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"
            }
          ]
        }
      ]
    }
  ]
}

Відповідний Composable — те, що агент повинен створити з цього 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)
                    )
                )
            }
        }
    }
}

Кожне значення у Composable прослідковується до ключа токена у IR. Нічого не захардкоджено — 16.dp походить із spacing.16, 24.dp — із spacing.24, Color(0xFF7F5CFE) — із color.7f5cfe.

Посилання на рядки — маппінг stringResource

Кожен текстовий вузол у IR містить stringRef із ключем у нотації через крапку. Файл strings.json відображає ключі на значення для відображення та резервні значення:

{
  "home.title": { "value": "Good morning", "fallback": "Good morning" },
  "home.card.label": { "value": "Today's summary", "fallback": "Summary" }
}

Нотація через крапку відповідає ідентифікаторам Android string-ресурсів із заміною крапок на підкреслення: home.titleR.string.home_title. Поле fallback — це те, що хардкодите як літеральний рядок, якщо ресурс ще не існує у strings.xml:

text = stringResource(R.string.home_title, "Good morning")

Скажіть агенту завжди використовувати поле fallback — не порожній рядок — щоб екран був читабельним під час розробки до того, як strings.xml заповнений.

Абсолютне позиціонування

Вузли з kind: "absolute" використовують координати Figma напряму. Вони з'являються в дизайнах із елементами, що накладаються, або прив'язані до конкретних позицій. Маппінг Compose використовує Box як батьківський і Modifier.offset на дочірніх елементах:

// 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)
    ) {
        // дочірні елементи
    }
}

Абсолютне позиціонування рідкісне в добре структурованих файлах Figma (більшість сучасних файлів використовують auto-layout). Коли ви це бачите — перевірте, чи справді дизайнерський намір є абсолютним, або дизайнер просто не застосував auto-layout обмеження: IR не може вивести намір.

Чесні прогалини

Бандл добре покриває загальний випадок. Кілька речей, які він не охоплює:

Ці прогалини є явними — вони з'являються у попередженнях _meta.json та CONTEXT.md. Агент не намагається безмовно заповнити їх здогадками.

Експортуйте контекстний бандл із головного застосунку figmascope, потім використовуйте його з Claude Code або Cursor для реалізації Composables безпосередньо з IR. Без здогадок за скриншотом, без захардкоджених значень.