Різниця між файлом дизайну та бібліотекою компонентів — це ідентичність. Файл дизайну містить фігури. Бібліотека компонентів містить іменовані, багаторазово використовувані частини зі стабільними ідентифікаторами. components/inventory.json — відповідь figmascope на питання: "які частини цього дизайну є компонентами, і як екземпляри з'єднуються з їхніми визначеннями?"

Схема

components/inventory.json — це масив об'єктів:

[
  {
    "id": "789:012",
    "name": "PrimaryButton",
    "type": "COMPONENT"
  },
  {
    "id": "789:013",
    "name": "SecondaryButton",
    "type": "COMPONENT"
  },
  {
    "id": "890:100",
    "name": "Button",
    "type": "COMPONENT_SET"
  },
  {
    "id": "890:101",
    "name": "Button/Primary/Default",
    "type": "COMPONENT"
  },
  {
    "id": "890:102",
    "name": "Button/Primary/Pressed",
    "type": "COMPONENT"
  }
]

Кожен запис має три поля:

Це вся схема. Вона навмисно мінімальна.

Як екземпляри зв'язуються назад

Зв'язок від екземплярів до інвентаря знаходиться в покроковому IR. Будь-який вузол, що є INSTANCE компонента, несе componentId та componentName:

// screens/home.json
{
  "kind": "stack",
  "id": "555:201",
  "componentId": "789:012",
  "componentName": "PrimaryButton",
  "axis": "horizontal",
  ...
}

componentId відповідає id в inventory.json. componentName відповідає name. Обидва поля присутні, щоб агент не мав завантажувати inventory.json для отримання імені — але якщо йому потрібно знати, чи є цей компонент частиною COMPONENT_SET, він звертається до інвентаря.

Саме так кодувальний агент знає, що вузол кнопки на екрані — це не власний макет, який слід відтворити детально, а екземпляр PrimaryButton, і він повинен викликати наявний composable PrimaryButton(), а не генерувати новий з деталей структури IR.

Без ідентичності компонентів агент генерує ту саму кнопку з нуля на кожному екрані. З нею агент викликає наявний composable і повністю пропускає структурну кодогенерацію. Різниця — між 40 блоками Row { Text(...) Surface { ... } } та 40 викликами PrimaryButton(...).

Чому це важливо для безпеки рефакторингу

Дизайн-системи рефакторяться. Кнопка отримує нову форму, нові відступи, інший колір. Якщо весь ваш згенерований код був структурно буквальним, рефакторинг означає торкатися кожного екрана з кнопкою. Якщо згенерований код використовував посилання на composable PrimaryButton, рефакторинг — це один файл.

Інвентар робить це можливим, встановлюючи контракт під час генерації: "цей вузол не є власним макетом, це екземпляр відомого компонента." Агент, який дотримується цього контракту, генерує код, вже структурований для рефакторингу на рівні компонентів.

Це основна структурна перевага над передачею дизайну на основі скріншотів. Скріншот кнопки — це пікселі. Він не має ідентичності. Агент, що працює зі скріншотом, генеруватиме структурний код для кнопки на кожному екрані, завжди. Агент, що працює з IR з прив'язкою до інвентаря, може розпізнати екземпляр і використати посилання на компонент замість.

COMPONENT проти COMPONENT_SET

Система компонентів Figma має два рівні. COMPONENT — це єдине визначення. COMPONENT_SET — контейнер для варіантів (наприклад, кнопка зі станами Primary/Secondary/Destructive та Default/Hovered/Pressed).

На практиці екземпляр на екрані завжди посилатиметься на COMPONENT (конкретний варіант), а не на COMPONENT_SET. COMPONENT_SET існує, щоб агент знав повну поверхню варіантів, коли потрібно реалізувати стан-машину компонента.

// Записи інвентаря для набору кнопок
{ "id": "890:100", "name": "Button", "type": "COMPONENT_SET" }
{ "id": "890:101", "name": "Button/Primary/Default", "type": "COMPONENT" }
{ "id": "890:102", "name": "Button/Primary/Pressed", "type": "COMPONENT" }
{ "id": "890:103", "name": "Button/Secondary/Default", "type": "COMPONENT" }

// Екземпляр у IR екрана посилається на конкретний варіант
{ "componentId": "890:101", "componentName": "Button/Primary/Default" }

Агент, що генерує Compose-код для цього, знає: компонент — це Button зі стилем Primary та станом Default. Він може вивести, що підпис функції, ймовірно, включає параметри style: ButtonStyle та state: ButtonState, або принаймні використати Button/Primary/Default як семантичне посилання для основної кнопки в стані за замовчуванням.

Обмеження у 300 записів

figmascope обмежує inventory.json до 300 записів. Файли Figma у масштабі — особливо дизайн-системи з вичерпними бібліотеками компонентів — можуть мати тисячі компонентів. Включення всіх у контекстний бандл, призначений для надсилання до LLM, заповнило б контекстне вікно визначеннями, які агент не використовуватиме для реалізованих екранів.

Коли ліміт досягнуто, у інвентарі з'являється поле _truncated:

[
  { "id": "...", "name": "...", "type": "COMPONENT" },
  ...
  { "_truncated": true, "totalCount": 847, "included": 300 }
]

totalCount повідомляє, скільки компонентів існує у файлі. included — скільки потрапило до інвентаря. Порядок — за першою зустріччю у дереві вузлів Figma, тому компоненти, на які посилаються на початку документа (зазвичай основні екрани), швидше за все будуть включені.

Якщо ви працюєте з екранами, що використовують компоненти, визначені пізніше у документі, і вони відсутні в інвентарі, вузли IR для цих екземплярів все одно мають componentId та componentName — інформація про ідентичність зберігається, навіть якщо компонент не перерахований в інвентарі. Агент знає ім'я компонента з IR, навіть без запису в інвентарі.

Що інвентар не включає

Інвентар — це список ідентичностей, а не специфікація реалізації. Він повідомляє, що компонент з іменем PrimaryButton та ID 789:012 існує. Він не говорить:

Ці прогалини є навмисними у v0.4. Повне виведення props компонентів зі структури IR можливе, але давало б ненадійні результати для компонентів зі складною логікою варіантів. Інвентар дає стабільну ідентичність. Деталі реалізації приходять з вашої наявної кодової бази, до якої агент також має доступ.

Правильний підхід: агент бачить componentId: "789:012", шукає його в інвентарі як PrimaryButton, потім шукає PrimaryButton у вашій Kotlin-кодовій базі, щоб зрозуміти реальний підпис функції. Інвентар — це міст між дизайном і кодом, а не замінник коду. Ви можете експортувати інвентар з будь-якого файлу Figma на figmascope.dev.

Ідентичність компонентів в IR — це те, що відділяє "генерувати код з цього дизайну" від "генерувати код, що вписується в наявну кодову базу". Перше — іграшка. Друге — робота.

Порівняння з передачею дизайну лише зі скріншотами

Агент, що працює з PNG того самого екрана, не має ідентичності компонентів. Він бачить синій заокруглений прямокутник з центрованим текстом і генерує:

Box(
    modifier = Modifier
        .background(Color(0xFF7F5CFE), RoundedCornerShape(24.dp))
        .padding(horizontal = 32.dp, vertical = 16.dp)
) {
    Text("Start", color = Color.White, fontWeight = FontWeight.SemiBold)
}

Агент, що працює з IR з інвентарем, бачить componentId: "789:012", шукає PrimaryButton, знаходить наявний composable у кодовій базі та генерує:

PrimaryButton(
    label = stringResource(R.string.start),
    onClick = { /* TODO */ }
)

Другий вивід інтегрується з вашою дизайн-системою. Перший створює розбіжність. Інвентар робить другий вивід можливим. Детальніше про те, чому скріншоти погані як артефакти передачі, дивіться Чому скріншоти не працюють.

Покроковий IR, що містить посилання componentId, детально розглянуто в Per-Screen IR — Stack, Overlay, Absolute, Leaf. Повна структура контекстного бандлу, що містить обидва артефакти, представлена через Анатомію CONTEXT.md. Щоб отримати власний інвентар компонентів, запустіть figmascope для вашого файлу Figma.