Разница между файлом дизайна и библиотекой компонентов — это идентичность. Файл дизайна содержит фигуры. Библиотека компонентов содержит именованные, многоразовые части со стабильными идентификаторами. 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, он обращается к инвентарю.

Именно так кодирующий агент узнаёт, что узел кнопки на экране — это не бespoke-макет, который надо дословно воспроизвести, а экземпляр PrimaryButton, и следует вызвать существующий composable PrimaryButton(), а не генерировать новый из структурных деталей IR.

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

Почему это важно для безопасности рефакторинга

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

Инвентарь делает это возможным, устанавливая контракт во время генерации: «этот узел — не пользовательский макет, это экземпляр известного компонента». Агент, соблюдающий этот контракт, генерирует код, уже структурированный для рефакторинга на уровне компонентов.

Это главное структурное преимущество перед скриншотным хэндоффом. Скриншот кнопки — пиксели. Идентичности нет. Агент, работающий со скриншотом, будет генерировать структурный код для кнопки на каждом экране, всегда. Агент, работающий с IR со связями инвентаря, может распознать экземпляр и использовать ссылку на компонент вместо этого.

COMPONENT vs. COMPONENT_SET

Компонентная система Figma имеет два уровня. COMPONENT — одно определение. COMPONENT_SET — контейнер для вариантов (то, что Figma называет «variants»: кнопка с состояниями Primary/Secondary/Destructive и Default/Hovered/Pressed).

На практике экземпляр на экране всегда ссылается на COMPONENT (конкретный вариант), не на COMPONENT_SET. COMPONENT_SET присутствует, чтобы агент знал полную поверхность вариантов при реализации конечного автомата компонента.

// Записи инвентаря для набора Button
{ "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, затем ищет в вашей Kotlin-кодовой базе PrimaryButton, чтобы понять фактическую сигнатуру функции. Инвентарь — мост между дизайном и кодом, а не замена коду. Вы можете экспортировать инвентарь из любого 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, подробно рассматривается в Постраничный IR — Stack, Overlay, Absolute, Leaf. Полная структура контекстного бандла, содержащего оба артефакта, вводится через Анатомию CONTEXT.md. Чтобы получить собственный инвентарь компонентов, запустите figmascope на вашем Figma-файле.