디자인 파일과 컴포넌트 라이브러리의 차이는 식별에 있습니다. 디자인 파일에는 도형이 있습니다. 컴포넌트 라이브러리에는 안정적인 식별자를 가진 명명된 재사용 가능한 부품이 있습니다. 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.jsonid와 일치합니다. componentNamename과 일치합니다. 두 필드가 모두 있어 에이전트가 이름을 얻기 위해 inventory.json을 로드할 필요가 없습니다 — 하지만 이 컴포넌트가 COMPONENT_SET의 일부인지 알아야 한다면 인벤토리를 교차 참조합니다.

이것이 코딩 에이전트가 화면의 버튼 노드가 세부적으로 재현해야 할 맞춤형 레이아웃이 아니라 PrimaryButton의 인스턴스임을 알게 되는 방법입니다. IR의 구조적 세부 사항으로 새로운 것을 생성하는 대신 기존의 PrimaryButton() 컴포저블을 호출해야 합니다.

컴포넌트 식별이 없으면 에이전트는 모든 화면에서 동일한 버튼을 처음부터 생성합니다. 있으면 에이전트는 기존 컴포저블을 호출하고 구조적 코드 생성을 완전히 건너뜁니다. 차이는 40개의 Row { Text(...) Surface { ... } } 블록을 얻느냐 40개의 PrimaryButton(...) 호출을 얻느냐입니다.

리팩터링 안전성에 중요한 이유

디자인 시스템은 리팩터링됩니다. 버튼이 새로운 모양, 새로운 패딩, 다른 색상을 얻습니다. 생성된 코드가 모두 구조적으로 리터럴이었다면 리팩터링은 버튼이 있는 모든 화면을 수정해야 합니다. 생성된 코드가 PrimaryButton 컴포저블 참조를 사용했다면 리팩터링은 한 파일입니다.

인벤토리는 생성 시점에 계약을 설정함으로써 이를 가능하게 합니다: "이 노드는 맞춤형 레이아웃이 아닌 알려진 컴포넌트의 인스턴스입니다." 이 계약을 존중하는 에이전트는 컴포넌트 수준 리팩터링을 위해 이미 구조화된 코드를 생성합니다.

이것이 스크린샷 기반 핸드오프보다 가진 주요 구조적 장점입니다. 버튼의 스크린샷은 픽셀입니다. 식별이 없습니다. 스크린샷으로 작업하는 에이전트는 항상 모든 화면에서 버튼에 대한 구조적 코드를 생성합니다. 인벤토리 연결이 있는 IR로 작업하는 에이전트는 인스턴스를 인식하고 구조적 코드 대신 컴포넌트 참조를 사용할 수 있습니다.

COMPONENT vs. COMPONENT_SET

Figma의 컴포넌트 시스템은 두 수준이 있습니다. COMPONENT는 단일 정의입니다. COMPONENT_SET은 변형을 위한 컨테이너입니다 — Figma에서 "변형"(예: 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 코드를 생성하는 에이전트는 알게 됩니다: 컴포넌트는 Primary 스타일과 Default 상태를 가진 Button입니다. 함수 서명이 style: ButtonStylestate: ButtonState 매개변수를 포함할 가능성이 높거나, 최소한 기본 상태의 primary 버튼에 대한 시맨틱 참조로 Button/Primary/Default를 사용할 것입니다.

300개 항목 제한

figmascope는 inventory.json을 300개 항목으로 제한합니다. 규모가 큰 Figma 파일 — 특히 방대한 컴포넌트 라이브러리를 가진 디자인 시스템 — 은 수천 개의 컴포넌트를 가질 수 있습니다. 구현할 화면에 에이전트가 사용하지 않을 정의로 컨텍스트 창을 채우는 것은 바람직하지 않습니다.

제한에 도달하면 인벤토리에 _truncated 필드가 나타납니다:

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

totalCount는 파일에 존재하는 컴포넌트 수를 알려줍니다. included는 인벤토리에 포함된 수를 알려줍니다. 순서는 Figma 노드 트리에서 처음 발견된 순서이므로 문서 초반에 참조된 컴포넌트(일반적으로 기본 화면)가 포함될 가능성이 더 높습니다.

인벤토리에 없는 문서 후반에 정의된 컴포넌트를 사용하는 화면에서 작업하는 경우, 해당 인스턴스의 IR 노드에는 여전히 componentIdcomponentName이 있습니다 — 컴포넌트가 인벤토리에 없어도 식별 정보는 보존됩니다. 에이전트는 인벤토리 항목 없이도 IR에서 컴포넌트 이름을 알 수 있습니다.

인벤토리에 포함되지 않는 것

인벤토리는 식별 목록이지 구현 명세가 아닙니다. ID 789:012를 가진 PrimaryButton이라는 컴포넌트가 존재한다는 것을 알려줍니다. 다음은 알려주지 않습니다:

이러한 공백은 v0.4에서 의도적입니다. IR 구조에서 전체 컴포넌트 prop 추론은 가능하지만 복잡한 변형 로직을 가진 컴포넌트에 대해 신뢰할 수 없는 결과를 생성할 것입니다. 인벤토리는 안정적인 식별을 제공합니다. 구현 세부 사항은 에이전트도 접근할 수 있는 기존 코드베이스에서 나옵니다.

올바른 워크플로: 에이전트가 componentId: "789:012"를 보고, 인벤토리에서 PrimaryButton으로 조회한 다음, Kotlin 코드베이스에서 PrimaryButton을 검색하여 실제 함수 서명을 이해합니다. 인벤토리는 디자인과 코드 사이의 다리이지 코드를 대체하는 것이 아닙니다. figmascope.dev에서 Figma 파일의 인벤토리를 내보낼 수 있습니다.

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을 조회하고, 코드베이스에서 기존 컴포저블을 찾아 생성합니다:

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

두 번째 출력은 디자인 시스템과 통합됩니다. 첫 번째는 발산을 만듭니다. 인벤토리가 두 번째 출력을 가능하게 합니다. 스크린샷이 핸드오프 아티팩트로 실패하는 이유에 대해서는 스크린샷이 실패하는 이유를 참조하세요.

componentId 참조를 포함하는 화면별 IR은 화면별 IR — Stack, Overlay, Absolute, Leaf에서 자세히 다룹니다. 두 아티팩트를 모두 포함하는 전체 컨텍스트 번들 구조는 CONTEXT.md 해부에서 소개됩니다. 자신의 컴포넌트 인벤토리를 얻으려면 figmascope에서 Figma 파일을 실행하세요.