The difference between a design file and a component library is identity. A design file has shapes. A component library has named, reusable parts with stable identifiers. components/inventory.json is figmascope's answer to: "which parts in this design are meant to be components, and how do instances connect back to them?"
The schema
components/inventory.json is an array of objects:
[
{
"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"
}
]
Each entry has three fields:
id: The Figma node ID, stable across sessions for the same file.name: The component name from Figma. For variants within a COMPONENT_SET, Figma uses slash-notation:Button/Primary/Default.type: Either"COMPONENT"(a single component definition) or"COMPONENT_SET"(a group of variants).
That's the full schema. It's intentionally minimal.
How instances link back
The link from instances to inventory is in the per-screen IR. Any node that is an INSTANCE of a component carries componentId and componentName:
// screens/home.json
{
"kind": "stack",
"id": "555:201",
"componentId": "789:012",
"componentName": "PrimaryButton",
"axis": "horizontal",
...
}
The componentId matches an id in inventory.json. The componentName matches the name. Both fields are present so the agent doesn't have to load inventory.json to get the name — but if it needs to know that this component is part of a COMPONENT_SET, it cross-references inventory.
This is how a coding agent knows that the button node on screen isn't a bespoke layout it should reproduce in detail — it's an instance of PrimaryButton, and it should call the existing PrimaryButton() composable rather than generating a new one from the IR's structural details.
Without component identity, an agent generates the same button from scratch on every screen. With it, the agent calls the existing composable and skips structural codegen entirely. The difference is whether you get 40
Row { Text(...) Surface { ... } }blocks or 40PrimaryButton(...)calls.
Why this matters for refactor safety
Design systems get refactored. A button gets a new shape, new padding, a different color. If all your generated code was structurally literal, the refactor means touching every screen that has a button. If the generated code used the PrimaryButton composable reference, the refactor is one file.
The inventory makes this possible by establishing the contract at generation time: "this node is not a custom layout, it's an instance of a known component." The agent that respects this contract generates code that's already structured for component-level refactoring.
This is the primary structural advantage over screenshot-based handoffs. A screenshot of a button is pixels. It has no identity. An agent working from a screenshot will generate structural code for the button on every screen, always. An agent working from the IR with inventory linkage can recognize the instance and use the component reference instead.
COMPONENT vs. COMPONENT_SET
Figma's component system has two levels. A COMPONENT is a single definition. A COMPONENT_SET is a container for variants — what Figma calls "variants" (e.g., a button with Primary/Secondary/Destructive and Default/Hovered/Pressed states).
In practice, an instance in a screen will always reference a COMPONENT (a specific variant), not the COMPONENT_SET. The COMPONENT_SET is there so the agent knows the full variant surface when it needs to implement the component's state machine.
// Inventory entries for a Button 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" }
// Instance in screen IR references a specific variant
{ "componentId": "890:101", "componentName": "Button/Primary/Default" }
An agent generating Compose code for this knows: the component is a Button with a Primary style and a Default state. It can infer the function signature likely involves style: ButtonStyle and state: ButtonState parameters, or at minimum use Button/Primary/Default as the semantic reference for a primary button in its default state.
The 300-entry cap
figmascope caps inventory.json at 300 entries. Figma files at scale — especially design systems with exhaustive component libraries — can have thousands of components. Including all of them in a context bundle that's meant to be sent to an LLM would pad the context window with definitions the agent won't use for the screens being implemented.
When the cap is hit, a _truncated field appears in the inventory:
[
{ "id": "...", "name": "...", "type": "COMPONENT" },
...
{ "_truncated": true, "totalCount": 847, "included": 300 }
]
The totalCount tells you how many components exist in the file. The included tells you how many made it into the inventory. The ordering is by first-encountered in the Figma node tree, so components referenced early in the document (typically the primary screens) are more likely to be included.
If you're working on screens that use components defined late in the document and they're not in the inventory, the IR nodes for those instances still have componentId and componentName — the identity information is preserved even when the component isn't listed in the inventory. The agent knows the name of the component from the IR, even without the inventory entry.
What the inventory doesn't include
The inventory is an identity list, not an implementation spec. It tells you a component named PrimaryButton with ID 789:012 exists. It does not tell you:
- What props the component accepts
- What states it has
- How the component is internally structured (that's in the IR nodes that are INSTANCEs)
- What the component's source Figma file is (if it's from a linked library)
These gaps are intentional in v0.4. Full component prop inference from IR structure is possible but would produce unreliable results for components with complex variant logic. The inventory gives stable identity. Implementation details come from your existing codebase, which the agent also has access to.
The right workflow: the agent sees a componentId: "789:012", looks it up in inventory as PrimaryButton, then searches your Kotlin codebase for PrimaryButton to understand the actual function signature. The inventory is the bridge between the design and the code, not a replacement for the code. You can export an inventory from any Figma file on figmascope.dev.
Component identity in the IR is what separates "generate code from this design" from "generate code that fits into an existing codebase." The former is a toy. The latter is the job.
Comparison with screenshot-only handoff
An agent working from a PNG of the same screen has no component identity. It sees a blue rounded rectangle with centered text and generates:
Box(
modifier = Modifier
.background(Color(0xFF7F5CFE), RoundedCornerShape(24.dp))
.padding(horizontal = 32.dp, vertical = 16.dp)
) {
Text("Start", color = Color.White, fontWeight = FontWeight.SemiBold)
}
An agent working from the IR with inventory sees componentId: "789:012", looks up PrimaryButton, finds the existing composable in the codebase, and generates:
PrimaryButton(
label = stringResource(R.string.start),
onClick = { /* TODO */ }
)
The second output integrates with your design system. The first creates divergence. The inventory is what makes the second output possible. For more on why screenshots fail as handoff artifacts, see Why Screenshots Fail.
The per-screen IR that contains componentId references is covered in detail in Per-Screen IR — Stack, Overlay, Absolute, Leaf. The full context bundle structure that contains both artifacts is introduced via Anatomy of CONTEXT.md. To get your own component inventory, run figmascope on your Figma file.