Дерево вузлів Figma — багате, але галасливе. Frame, Group, Component, Instance, Text, Rectangle, Ellipse, Vector — кожен має десятки необов'язкових полів, деякі з яких суперечать одне одному. Агент, що працює безпосередньо з сирою відповіддю API Figma, витрачає когнітивний бюджет на розуміння схеми замість написання коду.
figmascope нормалізує дерево до чотирьох типів вузлів: stack, overlay, absolute та leaf. Кожен вузол у кожному файлі screens/*.json — один із цих чотирьох. Нічого більше.
Чотири типи
stack
Stack — це вузол із Auto Layout Figma — layoutMode: "VERTICAL" або layoutMode: "HORIZONTAL". Його дочірні елементи розміщені в потоці вздовж осі. Відступи між елементами та внутрішні відступи є явними.
{
"kind": "stack",
"id": "123:456",
"name": "ContentColumn",
"axis": "vertical",
"gap": 16,
"paddingTop": 24,
"paddingBottom": 24,
"paddingLeft": 20,
"paddingRight": 20,
"primaryAxisAlignItems": "SPACE_BETWEEN",
"counterAxisAlignItems": "CENTER",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#1a1a2e" }],
"children": [ ... ]
}
Відображається у Jetpack Compose як Column (вертикальна вісь) або Row (горизонтальна вісь). Поля primaryAxisAlignItems та counterAxisAlignItems відповідно відображаються на Arrangement та Alignment. gap стає Arrangement.spacedBy().
overlay
Overlay — це вузол, дочірні елементи якого мають абсолютні позиції всередині нього. Figma представляє це як Frame із layoutMode: "NONE", де дочірні елементи мають координати absoluteBoundingBox. Тип overlay фіксує це без необхідності для агента міркувати про сирі координати для розуміння структури.
{
"kind": "overlay",
"id": "123:789",
"name": "CardOverlay",
"width": 358,
"height": 200,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 16, "y": 16 },
"child": { ... }
}
]
}
Відображається у Compose як Box. Дочірні елементи позиціонуються за допомогою Modifier.offset() або Modifier.align() залежно від того, як їх позиції співвідносяться з межами батьківського вузла.
absolute
Absolute — це вузол-обгортка, що несе єдиного нащадка у конкретному зміщенні (x, y) всередині батьківського overlay. Це тонкий структурний вузол — він існує для збереження просторових зв'язків із Figma без змішування позиції та вмісту.
{
"kind": "absolute",
"offset": { "x": 24, "y": 140 },
"child": {
"kind": "leaf",
"name": "BadgeLabel",
"text": "NEW",
"stringRef": "badge.new",
...
}
}
У Compose це зазвичай стає дочірнім елементом Box із Modifier.offset(x.dp, y.dp). Значення зміщення є кандидатами для підстановки токенів, якщо існують токени відступів у відповідному діапазоні.
leaf
Leaf — термінальний вузол без дочірніх елементів. Він має стилізацію (заливки, обводки, радіус кутів) і необов'язково текстовий вміст. Текстові листи мають поле text (літеральний рядок) та поле stringRef (ключ i18n-ресурсу з strings.json).
{
"kind": "leaf",
"id": "123:101",
"name": "SpeedLabel",
"text": "Speed Test",
"stringRef": "speed.test",
"fontSize": 18,
"fontWeight": 600,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"width": 160,
"height": 28
}
Відображається у Compose як composable Text() для текстового вмісту або як Surface, Box чи Image для нетекстових листів залежно від типу заливки.
Чотири типи відображаються один до одного на композиційну модель будь-якого UI-фреймворку. Stacks — потокові розкладки. Overlays — контейнери з z-позиціонуванням. Absolutes — носії просторових метаданих. Leaves — вміст. Це все. Таксономія вузлів Figma має 12+ типів — IR зводить їх до чотирьох.
Як визначається тип
Логіка класифікації:
- Якщо
layoutMode—"VERTICAL"або"HORIZONTAL"→stack - Якщо
layoutMode—"NONE"і вузол має дочірні елементи →overlay(дочірні обгортаються якabsolute) - Якщо вузол є прямим дочірнім overlay і несе позицію →
absolute - Якщо вузол не має дочірніх і не є обгорткою absolute →
leaf
Попередження layout-mode-none-inferred у _meta.json спрацьовує, коли Frame із layoutMode: "NONE" має дочірні елементи з нетривіально перекриваючими bounding box. figmascope обробляє його як overlay, але помічає виведення, оскільки деякі фрейми layoutMode: "NONE" на практиці є просто контейнерами для єдиного нащадка (нульове перекриття) і могли б бути спрощені. Попередження дозволяє агенту вирішити, як його обробити.
absoluteBoundingBox — чому він збережений
Кожен вузол у IR зберігає свій absoluteBoundingBox із Figma, навіть коли батьківський — stack (де абсолютні позиції теоретично нерелевантні для розкладки):
{
"kind": "stack",
"absoluteBoundingBox": { "x": 0, "y": 88, "width": 390, "height": 756 },
...
}
Це потрібно агентам, яким необхідно міркувати про просторові зв'язки між вузлами, що не мають прямого зв'язку батько-нащадок. Накладання двох елементів із різних гілок дерева вимагає знання їх абсолютних позицій, а не лише відносних зміщень. Система координат — полотно Figma: початок у верхньому лівому куті, y збільшується донизу.
Це також допомагає при перехресному посиланні з PNG. _meta.json містить поле pngCount, а експортовані PNG названі за slug екрана. При порівнянні IR зі скриншотом absoluteBoundingBox дозволяє знайти конкретні вузли на зображенні. Щоб побачити це на власному дизайні — експортуйте бандл із figmascope.dev.
Заливки та обводки на контейнерних типах
Поширена точка плутанини: вузли stack та overlay можуть мати заливки, не лише листи. Колонка з кольором фону — все одно stack, що просто також має масив fills. figmascope зберігає це:
{
"kind": "stack",
"axis": "vertical",
"fills": [{ "type": "SOLID", "color": "#f6f2ea" }],
"cornerRadius": 12,
...
}
У Compose це зазвичай стає Column всередині Surface або Card із кольором заливки та радіусом кутів, застосованими до контейнера. IR не переносить заливку на дочірні елементи — він залишає її на вузлі, де Figma її встановила.
Градієнтні заливки на контейнерних типах генерують попередження background-gradient-not-supported:<name>. Вузол все одно присутній у IR з іншими своїми полями. Агент повинен обробити заливку як TODO і або наблизити суцільним кольором, або генерувати кастомний виклик малювання відповідно до нотаток про скоуп у CONTEXT.md.
Зв'язок stringRef та text
Текстові вузли-листи несуть обидва:
text: літеральне значення рядка з Figma (наприклад,"Speed Test")stringRef: ключ ресурсу зstrings.json(наприклад,"speed.test")
Якщо вміст текстового вузла було вилучено з strings.json через колізію або фільтр (лише цифри, порожній, надто короткий) — лист все одно має text, але stringRef буде відсутнім. Агент повинен у такому випадку повернутися до літерального значення. Повну логіку обробки колізій дивіться у strings.json.
Коли обидва присутні, обмеження CONTEXT.md каже використовувати stringRef. Поле text — для міркувань агента (щоб він знав, що говорить рядок, не відкриваючи strings.json) і як резервне значення.
Інстанції компонентів у IR
Коли вузол Figma є INSTANCE компонента, вузол IR несе два додаткові поля:
{
"kind": "stack",
"componentId": "789:012",
"componentName": "PrimaryButton",
...
}
Це посилається на components/inventory.json. Агент знає, що цей вузол є інстанцією PrimaryButton, а не індивідуальною розкладкою, і повинен посилатися на існуючий компонент замість генерації дублювального коду. Повне розкриття цього — у інвентарі компонентів.
Реальний приклад екрана
Спрощений, але структурно точний приклад екрана із заголовком, колонкою вмісту та плаваючою кнопкою:
{
"screen": "home",
"root": {
"kind": "overlay",
"name": "HomeScreen",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#0d0d1a" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 0, "y": 0 },
"child": {
"kind": "stack",
"name": "ContentArea",
"axis": "vertical",
"gap": 24,
"paddingTop": 56,
"paddingLeft": 20,
"paddingRight": 20,
"children": [
{
"kind": "leaf",
"name": "Title",
"text": "Speed Test",
"stringRef": "speed.test",
"fontSize": 28,
"fontWeight": 700
}
]
}
},
{
"kind": "absolute",
"offset": { "x": 111, "y": 752 },
"child": {
"kind": "stack",
"name": "StartButton",
"componentId": "321:654",
"componentName": "PrimaryButton",
"axis": "horizontal",
"gap": 8,
"paddingTop": 16,
"paddingBottom": 16,
"paddingLeft": 32,
"paddingRight": 32,
"cornerRadius": 24,
"fills": [{ "type": "SOLID", "color": "#7f5cfe" }]
}
}
]
}
}
З цього агент може правильно генерувати екран Compose Box із абсолютно-позиціонованою Column для вмісту та абсолютно-позиціонованим компонентом PrimaryButton знизу. Кожне рішення щодо розкладки виводиться з IR без здогадок.
Для розуміння того, як токени застосовуються до значень відступів та кольорів у цій структурі, дивіться tokens.json пояснено. Для повного воркфлоу агента з використанням цього IR у Cursor або Claude Code — дивіться Jetpack Compose з Figma. Спробуйте самостійно, вставивши URL Figma у figmascope.