figmascopeのデフォルトエクスポートターゲットはJetpack Composeです。これは恣意的ではありません—ComposeのレイアウトモデルColumn、Row、Box、ModifierはIRノードの種類(スタック、オーバーレイ、アブソリュート、リーフ)に密接にマッピングされます。FigmaのVerticalスタックはComposeのColumnです。変換は機械的であり、エージェント駆動のコードジェンに適しています。

このウォークスルーではIR→Composeのマッピングを詳しく説明し、対応するComposableを持つ実際のJSONフラグメントを示し、トークンマッピング層を説明します。

Composeがデフォルトターゲットである理由

3つの構造的な理由:

  1. オートレイアウト ↔ Column/Row。FigmaのオートレイアウトフレームEDEK(現代のFigmaデザインの大部分)はkind: "stack"ノードとしてエクスポートされます。スタックノードにはaxisプロパティがあります—verticalColumnに、horizontalRowにマッピングされます。これは解釈ステップなしの1:1マッピングです。
  2. スペーシングトークン ↔ dp値。Composeはすべてのレイアウト寸法にDpを使います。tokens.jsonのトークン値は単位なし整数(例:spacing.16 = 16)で、16.dpに直接マッピングされます。変換も、スケーリングも不要です。
  3. カラートークン ↔ Colorコンポーザブル。tokens.jsonのFigmaの16進値は一つの変換でColor(0xFFrrggbb)にマッピングされます。トークンキーはテーマ内のセマンティック変数名になります。

IRノードの種類とComposeマッピング

IR種類プロパティComposeプリミティブ
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlayレイヤーされた子Box
absolutexywidthheightBox with Modifier.offset(x.dp, y.dp)
leaftype: "text"Text with TextStyle
leaftype: "rectangle" with fillBox(Modifier.background(Color(...)))

すべてのノード種類はspacingプロパティ(子間のギャップ)とpaddingオブジェクト(上/右/下/左)を持てます。両方ともトークンキーを参照します。

トークンマッピングの詳細

tokens.jsonファイルはこのようになっています:

{
  "spacing": {
    "4": 4, "8": 8, "12": 12, "16": 16,
    "20": 20, "24": 24, "32": 32, "48": 48
  },
  "radius": {
    "4": 4, "8": 8, "12": 12, "16": 16, "full": 9999
  },
  "color": {
    "7f5cfe": "#7F5CFE",
    "1a1a2e": "#1A1A2E",
    "f6f2ea": "#F6F2EA",
    "ffffff": "#FFFFFF",
    "e53935": "#E53935"
  },
  "typography": {
    "heading.24": { "size": 24, "weight": 700, "lineHeight": 1.2 },
    "body.14": { "size": 14, "weight": 400, "lineHeight": 1.5 },
    "label.12": { "size": 12, "weight": 500, "lineHeight": 1.4 }
  }
}

マッピングルール:

トークンキーは意図的に不透明(カラーは16進文字列、スペーシングは数値文字列)で、モデルが特定の命名規則に偏らないようにします。あなたのComposeテーマはIRとは独立してセマンティック名—colorPrimaryspacingMd—にエイリアスできます。

実際の例:ホーム画面JSONからComposableへ

簡略化したホーム画面IRを示します。ヘッダーリーフとカードリストを持つVerticalスタック:

{
  "name": "home",
  "kind": "stack",
  "axis": "vertical",
  "spacing": "spacing.24",
  "padding": { "top": "spacing.16", "right": "spacing.16",
               "bottom": "spacing.16", "left": "spacing.16" },
  "fill": "color.f6f2ea",
  "children": [
    {
      "kind": "leaf",
      "type": "text",
      "stringRef": "home.title",
      "typography": "typography.heading.24",
      "fill": "color.1a1a2e"
    },
    {
      "kind": "stack",
      "axis": "vertical",
      "spacing": "spacing.12",
      "children": [
        {
          "kind": "overlay",
          "radius": "radius.12",
          "fill": "color.ffffff",
          "padding": { "top": "spacing.16", "right": "spacing.16",
                       "bottom": "spacing.16", "left": "spacing.16" },
          "children": [
            {
              "kind": "leaf",
              "type": "text",
              "stringRef": "home.card.label",
              "typography": "typography.label.12",
              "fill": "color.7f5cfe"
            }
          ]
        }
      ]
    }
  ]
}

このIRからエージェントが生成すべき対応するComposable:

@Composable
fun HomeScreen() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xFFF6F2EA))
            .padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(24.dp)
    ) {
        Text(
            text = stringResource(R.string.home_title),
            style = TextStyle(
                fontSize = 24.sp,
                fontWeight = FontWeight.Bold,
                lineHeight = 28.8.sp,
                color = Color(0xFF1A1A2E)
            )
        )
        Column(
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            Box(
                modifier = Modifier
                    .clip(RoundedCornerShape(12.dp))
                    .background(Color(0xFFFFFFFF))
                    .padding(16.dp)
            ) {
                Text(
                    text = stringResource(R.string.home_card_label),
                    style = TextStyle(
                        fontSize = 12.sp,
                        fontWeight = FontWeight.Medium,
                        lineHeight = 16.8.sp,
                        color = Color(0xFF7F5CFE)
                    )
                )
            }
        }
    }
}

Composable内のすべての値はIRのトークンキーに追跡されます。何もハードコードされていません—16.dpspacing.16から、24.dpspacing.24から、Color(0xFF7F5CFE)color.7f5cfeから来ています。

文字列ref — stringResourceマッピング

IR内のすべてのテキストノードはドット記法キーを持つstringRefを持ちます。strings.jsonファイルはキーを表示値とフォールバックにマッピングします:

{
  "home.title": { "value": "Good morning", "fallback": "Good morning" },
  "home.card.label": { "value": "Today's summary", "fallback": "Summary" }
}

ドット記法はAndroid文字列リソースIDにドットをアンダースコアに置き換えてマッピングされます:home.titleR.string.home_titlefallbackフィールドはstrings.xmlにリソースがまだ存在しない場合にリテラル文字列としてハードコードするものです:

text = stringResource(R.string.home_title, "Good morning")

エージェントには常にフォールバックフィールドを使うよう指示してください—空文字列ではなく—そうするとstrings.xmlが追加される前の開発中も画面が読めます。

アブソリュートポジショニング

kind: "absolute"のノードはFigmaの座標を直接使います。これらは要素が重なり合っているデザインや特定の位置にアンカーされた要素に現れます。ComposeのマッピングはParentとしてBoxを使い、子にModifier.offsetを使います:

// IR: { "kind": "absolute", "x": 24, "y": 80, "width": 120, "height": 40 }

Box(modifier = Modifier.fillMaxSize()) {
    Box(
        modifier = Modifier
            .offset(x = 24.dp, y = 80.dp)
            .size(width = 120.dp, height = 40.dp)
    ) {
        // children
    }
}

アブソリュートポジショニングは構造化されたFigmaファイル(ほとんどの現代ファイルはオートレイアウトを使う)では一般的ではありません。見た場合は、デザイン意図が本当にアブソリュートかどうか、それともデザイナーがオートレイアウト制約を適用しなかっただけかを確認してください—IRは意図を推測できません。

正直なギャップ

バンドルは一般的なケースをうまくカバーします。いくつかカバーしない点があります:

これらのギャップは明示的です—_meta.jsonの警告とCONTEXT.mdに現れます。エージェントはそれらを静かに推測しません。

figmascopeのメインアプリからコンテキストバンドルをエクスポートして、Claude CodeまたはCursorと一緒に使って、IRから直接Composableを実装してください。スクリーンショットの推測なし、ハードコードされた値なし。