Разница между файлом дизайна и библиотекой компонентов — это идентичность. Файл дизайна содержит фигуры. Библиотека компонентов содержит именованные, многоразовые части со стабильными идентификаторами. 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"
}
]
Каждая запись имеет три поля:
id: ID узла Figma, стабильный между сессиями для одного и того же файла.name: имя компонента из Figma. Для вариантов внутри COMPONENT_SET Figma использует slash-нотацию:Button/Primary/Default.type: либо"COMPONENT"(одно определение компонента), либо"COMPONENT_SET"(группа вариантов).
Это вся схема. Она намеренно минималистична.
Как экземпляры связываются обратно
Связь от экземпляров к инвентарю находится в постраничном 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. Он не говорит:
- Какие props принимает компонент
- Какие состояния он имеет
- Как компонент структурирован внутри (это в IR-узлах, которые являются INSTANCE)
- Что такое исходный Figma-файл компонента (если он из связанной библиотеки)
Эти пробелы намеренны в 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-файле.