设计文件与组件库的区别在于标识。设计文件有形状。组件库有具名、可复用、带稳定标识符的部件。components/inventory.jsonfigmascope 对以下问题的回答:"这个设计中哪些部分是组件,实例如何链接回其定义?"

数据结构

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 的任何节点都携带 componentIdcomponentName

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

componentIdinventory.json 中的 id 匹配,componentNamename 匹配。两个字段都存在,因此 Agent 无需加载 inventory.json 就能获取名称——但如果需要知道该组件是否属于 COMPONENT_SET,则通过交叉引用清单获取。

这就是编程 Agent 知道屏幕上的按钮节点不是需要详细重现的自定义布局——它是 PrimaryButton 的一个实例,应该调用现有的 PrimaryButton() 可组合函数,而不是根据 IR 的结构细节生成新代码。

没有组件标识,Agent 在每个屏幕上从头生成相同的按钮。有了组件标识,Agent 调用现有的可组合函数,完全跳过结构性代码生成。区别在于:你得到的是 40 个 Row { Text(...) Surface { ... } } 块,还是 40 个 PrimaryButton(...) 调用。

为什么这对重构安全性很重要

设计系统会被重构。按钮可能获得新的形状、新的内边距、不同的颜色。如果所有生成的代码都是字面上的结构代码,重构意味着要修改每个有按钮的屏幕。如果生成的代码使用了 PrimaryButton 可组合函数引用,重构只需修改一个文件。

清单使这成为可能,它在生成时建立了契约:"这个节点不是自定义布局,它是已知组件的实例。"遵守这一契约的 Agent 生成的代码已经为组件级重构做好了结构准备。

这是相对于基于截图的交付方式的主要结构优势。截图中的按钮是像素,没有标识。从截图工作的 Agent 会在每个屏幕上为按钮生成结构代码,总是如此。从带有清单链接的 IR 工作的 Agent 可以识别实例并使用组件引用。

COMPONENT 与 COMPONENT_SET 的区别

Figma 的组件系统有两个层次。COMPONENT 是单个定义。COMPONENT_SET 是变体的容器——Figma 称之为"变体"(例如,具有 Primary/Secondary/Destructive 和 Default/Hovered/Pressed 状态的按钮)。

实际上,屏幕中的实例总是引用一个 COMPONENT(特定变体),而不是 COMPONENT_SET。COMPONENT_SET 的存在是为了让 Agent 在需要实现组件状态机时了解完整的变体集合。

// 按钮集的清单条目
{ "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 代码的 Agent 知道:该组件是具有 Primary 样式和 Default 状态的 Button。它可以推断函数签名可能涉及 style: ButtonStylestate: 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 中这些实例的节点仍然携带 componentIdcomponentName——即使组件未列在清单中,标识信息也会保留。Agent 从 IR 中知道组件的名称,即使没有清单条目。

清单不包含的内容

清单是标识列表,不是实现规范。它告诉你存在一个 ID 为 789:012、名为 PrimaryButton 的组件。它不告诉你:

这些缺口在 v0.4 中是有意为之的。从 IR 结构推断完整的组件属性是可能的,但对于具有复杂变体逻辑的组件会产生不可靠的结果。清单提供稳定的标识,实现细节来自你的现有代码库,Agent 也可以访问这些代码库。

正确的工作流是:Agent 看到 componentId: "789:012",在清单中将其查找为 PrimaryButton,然后在你的 Kotlin 代码库中搜索 PrimaryButton 以了解实际的函数签名。清单是设计与代码之间的桥梁,不是代码的替代品。你可以在 figmascope.dev 上从任何 Figma 文件导出清单

IR 中的组件标识是"从这个设计生成代码"与"生成适配现有代码库的代码"之间的分水岭。前者是玩具,后者才是真正的工作。

与纯截图交付方式的对比

从同一屏幕的 PNG 工作的 Agent 没有组件标识。它看到一个带有居中文字的蓝色圆角矩形,生成:

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 工作的 Agent 看到 componentId: "789:012",查找 PrimaryButton,在代码库中找到现有的可组合函数,生成:

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

第二个输出与你的设计系统集成。第一个制造了分歧。清单使第二个输出成为可能。关于截图为何不适合作为交付物,请参阅为什么截图会失败

包含 componentId 引用的逐屏 IR 在逐屏 IR — Stack、Overlay、Absolute、Leaf 中有详细介绍。包含这两个产物的完整上下文包结构通过CONTEXT.md 解析介绍。要获取你自己的组件清单,请在 figmascope 上运行你的 Figma 文件