デザインファイルとコンポーネントライブラリの違いはアイデンティティです。デザインファイルには図形があります。コンポーネントライブラリには安定した識別子を持つ名前付きの再利用可能なパーツがあります。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"
  }
]

各エントリには3つのフィールドがあります:

これが完全なスキーマです。意図的にミニマルに設計されています。

インスタンスがリンクバックする仕組み

インスタンスからインベントリへのリンクは、画面ごとの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 コンポーザブル参照を使用していれば、リファクタリングは1ファイルで済みます。

インベントリは生成時にこの契約を確立することでこれを可能にします。「このノードはカスタムレイアウトではなく、既知のコンポーネントのインスタンスです。」この契約を尊重するエージェントは、コンポーネントレベルのリファクタリングのために既に構造化されたコードを生成します。

これはスクリーンショットベースのハンドオフに対する主要な構造的優位性です。ボタンのスクリーンショットはピクセルです。アイデンティティはありません。スクリーンショットから作業するエージェントは、常にすべての画面でボタンの構造的なコードを生成します。インベントリのリンクを含むIRから作業するエージェントはインスタンスを認識し、代わりにコンポーネント参照を使用できます。

COMPONENT vs. COMPONENT_SET

Figmaのコンポーネントシステムには2つのレベルがあります。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 パラメータを含むと推測するか、少なくともデフォルト状態のプライマリボタンのセマンティック参照として 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 が含まれています。インベントリエントリがなくてもアイデンティティ情報は保持されます。エージェントはインベントリエントリなしでもIRからコンポーネントの名前を知ることができます。

インベントリに含まれないもの

インベントリはアイデンティティリストであり、実装仕様ではありません。ID 789:012 を持つ PrimaryButton という名前のコンポーネントが存在することを示します。以下は示しません:

これらのギャップはv0.4では意図的なものです。IRの構造からのフルコンポーネントプロップ推論は可能ですが、複雑なバリアントロジックを持つコンポーネントでは信頼性の低い結果を生成します。インベントリは安定したアイデンティティを提供します。実装の詳細はコードベースから得られます。エージェントもそれにアクセスできます。

正しいワークフロー:エージェントが 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 */ }
)

2番目の出力はデザインシステムと統合されています。1番目は乖離を生み出します。インベントリが2番目の出力を可能にするものです。スクリーンショットがハンドオフアーティファクトとして失敗する理由の詳細については スクリーンショットが失敗する理由 をご覧ください。

componentId 参照を含む画面ごとのIRは Per-Screen IR — スタック・オーバーレイ・アブソリュート・リーフ で詳しく説明しています。両方のアーティファクトを含む完全なコンテキストバンドル構造は CONTEXT.md の解剖 で紹介しています。コンポーネントインベントリを取得するには、figmascopeでFigmaファイルを実行してください