Pohon node Figma kaya tetapi berisik. Frame, Group, Component, Instance, Text, Rectangle, Ellipse, Vector — masing-masing memiliki lusinan bidang opsional, beberapa di antaranya saling bertentangan. Agen yang bekerja langsung dari respons API Figma mentah menghabiskan anggaran kognitif untuk memahami skema daripada menulis kode.

figmascope menormalkan pohon ke dalam empat jenis node: stack, overlay, absolute, dan leaf. Setiap node dalam setiap file screens/*.json adalah salah satu dari empat ini. Tidak ada yang lain.

Empat jenis

stack

Stack adalah node dengan Figma Auto Layout — layoutMode: "VERTICAL" atau layoutMode: "HORIZONTAL". Children-nya diposisikan secara alir sepanjang sumbu. Gap dan padding bersifat eksplisit.

{
  "kind": "stack",
  "id": "123:456",
  "name": "ContentColumn",
  "axis": "vertical",
  "gap": 16,
  "paddingTop": 24,
  "paddingBottom": 24,
  "paddingLeft": 20,
  "paddingRight": 20,
  "primaryAxisAlignItems": "SPACE_BETWEEN",
  "counterAxisAlignItems": "CENTER",
  "width": 390,
  "height": 844,
  "fills": [{ "type": "SOLID", "color": "#1a1a2e" }],
  "children": [ ... ]
}

Dipetakan ke Jetpack Compose sebagai Column (sumbu vertikal) atau Row (sumbu horizontal). Bidang primaryAxisAlignItems dan counterAxisAlignItems dipetakan ke Arrangement dan Alignment masing-masing. gap menjadi Arrangement.spacedBy().

overlay

Overlay adalah node yang children-nya memiliki posisi absolut di dalamnya. Figma merepresentasikan ini sebagai Frame dengan layoutMode: "NONE" di mana children memiliki koordinat absoluteBoundingBox. Jenis overlay menangkap ini tanpa mengharuskan agen untuk memikirkan koordinat mentah untuk memahami strukturnya.

{
  "kind": "overlay",
  "id": "123:789",
  "name": "CardOverlay",
  "width": 358,
  "height": 200,
  "fills": [{ "type": "SOLID", "color": "#ffffff" }],
  "children": [
    {
      "kind": "absolute",
      "offset": { "x": 16, "y": 16 },
      "child": { ... }
    }
  ]
}

Dipetakan ke Compose sebagai Box. Children diposisikan menggunakan Modifier.offset() atau Modifier.align() tergantung pada bagaimana posisi mereka berhubungan dengan batas parent.

absolute

Node absolute adalah pembungkus yang membawa satu child di offset (x, y) tertentu dalam overlay parent-nya. Ini adalah node struktural tipis — ada untuk mempertahankan hubungan spasial dari Figma tanpa mencampurkan posisi dengan konten.

{
  "kind": "absolute",
  "offset": { "x": 24, "y": 140 },
  "child": {
    "kind": "leaf",
    "name": "BadgeLabel",
    "text": "NEW",
    "stringRef": "badge.new",
    ...
  }
}

Di Compose, ini biasanya menjadi child dari Box dengan Modifier.offset(x.dp, y.dp). Nilai offset adalah kandidat untuk substitusi token jika token spacing ada dalam rentang tersebut.

leaf

Leaf adalah node terminal — tidak ada children. Ia memiliki styling (fill, stroke, corner radius) dan secara opsional konten teks. Leaf teks memiliki bidang text (string literal) dan bidang stringRef (kunci resource i18n dari strings.json).

{
  "kind": "leaf",
  "id": "123:101",
  "name": "SpeedLabel",
  "text": "Speed Test",
  "stringRef": "speed.test",
  "fontSize": 18,
  "fontWeight": 600,
  "fills": [{ "type": "SOLID", "color": "#ffffff" }],
  "width": 160,
  "height": 28
}

Dipetakan ke Compose sebagai composable Text() untuk konten teks, atau sebagai Surface, Box, atau Image untuk leaf non-teks tergantung jenis fill.

Empat jenis dipetakan satu-ke-satu ke model komposisional framework UI mana pun. Stack adalah layout alir. Overlay adalah kontainer yang diposisikan-z. Absolute membawa metadata spasial. Leaf adalah konten. Hanya itu. Taksonomi node Figma memiliki 12+ jenis node — IR mereduksinya menjadi empat.

Cara jenis ditentukan

Logika klasifikasi:

  1. Jika layoutMode adalah "VERTICAL" atau "HORIZONTAL"stack
  2. Jika layoutMode adalah "NONE" dan node memiliki children → overlay (children dibungkus sebagai absolute)
  3. Jika node adalah child langsung dari overlay dan membawa posisi → absolute
  4. Jika node tidak memiliki children dan bukan pembungkus absolute → leaf

Peringatan layout-mode-none-inferred dalam _meta.json dipicu ketika Frame dengan layoutMode: "NONE" memiliki children dengan bounding box yang saling tumpang tindih secara non-trivial. figmascope memperlakukannya sebagai overlay, tetapi mencatat inferensi karena beberapa frame layoutMode: "NONE" dalam praktiknya hanyalah kontainer untuk satu child (tumpang tindih nol) dan dapat disederhanakan. Peringatan membiarkan agen memutuskan cara menanganinya.

absoluteBoundingBox — mengapa dipertahankan

Setiap node dalam IR mempertahankan absoluteBoundingBox-nya dari Figma, bahkan ketika parent-nya adalah stack (di mana posisi absolut secara teoritis tidak relevan untuk layout):

{
  "kind": "stack",
  "absoluteBoundingBox": { "x": 0, "y": 88, "width": 390, "height": 756 },
  ...
}

Ini ada untuk agen yang perlu memikirkan hubungan spasial antar node yang tidak berbagi hubungan parent-child langsung. Menumpang dua elemen dari cabang pohon yang berbeda memerlukan pengetahuan tentang posisi absolut mereka, bukan hanya offset relatif. Sistem koordinatnya adalah kanvas Figma — asal kiri-atas, y meningkat ke bawah.

Ini juga membantu dengan referensi silang PNG. _meta.json menyertakan bidang pngCount dan PNG yang diekspor diberi nama berdasarkan slug layar. Jika Anda membandingkan IR dengan screenshot, absoluteBoundingBox memungkinkan Anda menemukan node tertentu dalam gambar. Untuk melihat ini di desain Anda sendiri, ekspor bundel dari figmascope.dev.

Fill dan stroke pada jenis kontainer

Titik kebingungan yang umum: node stack dan overlay dapat memiliki fill, bukan hanya leaf. Kolom dengan warna latar belakang tetap merupakan stack — ia hanya juga memiliki array fills. figmascope mempertahankan ini:

{
  "kind": "stack",
  "axis": "vertical",
  "fills": [{ "type": "SOLID", "color": "#f6f2ea" }],
  "cornerRadius": 12,
  ...
}

Di Compose, ini biasanya menjadi Column di dalam Surface atau Card, dengan warna fill dan corner radius diterapkan ke kontainer. IR tidak menggabungkan fill ke dalam children — ia menyimpannya di node tempat Figma menetapkannya.

Fill gradien pada jenis kontainer memancarkan peringatan background-gradient-not-supported:<name>. Node masih ada dalam IR dengan bidang lainnya utuh. Agen harus memperlakukan fill sebagai TODO dan menggunakan warna solid sebagai pendekatan atau menghasilkan panggilan gambar kustom, sesuai catatan cakupan CONTEXT.md.

Hubungan stringRef + text

Node leaf teks membawa keduanya:

Jika konten node teks dikeluarkan dari strings.json karena tabrakan atau filter (numerik saja, kosong, terlalu pendek), leaf tetap memiliki text tetapi stringRef tidak akan ada. Agen harus menggunakan literal sebagai fallback dalam kasus itu. Lihat strings.json untuk logika penanganan tabrakan lengkap.

Ketika keduanya ada, batasan CONTEXT.md mengatakan untuk menggunakan stringRef. Bidang text ada untuk penalaran agen (sehingga ia tahu apa yang dikatakan string tanpa membuka strings.json) dan sebagai fallback.

Instance komponen dalam IR

Ketika node Figma adalah INSTANCE dari sebuah komponen, node IR membawa dua bidang tambahan:

{
  "kind": "stack",
  "componentId": "789:012",
  "componentName": "PrimaryButton",
  ...
}

Ini menautkan kembali ke components/inventory.json. Agen mengetahui node ini adalah instance dari PrimaryButton bukan layout khusus, dan harus mereferensikan komponen yang ada daripada menghasilkan kode duplikat. Cakupan penuh ini ada di Component Inventory.

Contoh layar nyata

Contoh yang disederhanakan tetapi akurat secara struktural dari sebuah layar dengan header, kolom konten, dan tombol mengambang:

{
  "screen": "home",
  "root": {
    "kind": "overlay",
    "name": "HomeScreen",
    "width": 390,
    "height": 844,
    "fills": [{ "type": "SOLID", "color": "#0d0d1a" }],
    "children": [
      {
        "kind": "absolute",
        "offset": { "x": 0, "y": 0 },
        "child": {
          "kind": "stack",
          "name": "ContentArea",
          "axis": "vertical",
          "gap": 24,
          "paddingTop": 56,
          "paddingLeft": 20,
          "paddingRight": 20,
          "children": [
            {
              "kind": "leaf",
              "name": "Title",
              "text": "Speed Test",
              "stringRef": "speed.test",
              "fontSize": 28,
              "fontWeight": 700
            }
          ]
        }
      },
      {
        "kind": "absolute",
        "offset": { "x": 111, "y": 752 },
        "child": {
          "kind": "stack",
          "name": "StartButton",
          "componentId": "321:654",
          "componentName": "PrimaryButton",
          "axis": "horizontal",
          "gap": 8,
          "paddingTop": 16,
          "paddingBottom": 16,
          "paddingLeft": 32,
          "paddingRight": 32,
          "cornerRadius": 24,
          "fills": [{ "type": "SOLID", "color": "#7f5cfe" }]
        }
      }
    ]
  }
}

Dari ini agen dapat dengan benar menghasilkan layar Compose Box dengan Column yang diposisikan secara absolut untuk konten dan komponen PrimaryButton yang diposisikan secara absolut di bagian bawah. Setiap keputusan layout dapat diturunkan dari IR tanpa menebak-nebak.

Untuk cara token diterapkan pada nilai spacing dan warna dalam struktur ini, lihat tokens.json Explained. Untuk alur kerja agen lengkap menggunakan IR ini dengan Cursor atau Claude Code, lihat Jetpack Compose dari Figma. Coba sendiri dengan menempelkan URL Figma Anda ke figmascope.