هدف التصدير الافتراضي لـ figmascope هو Jetpack Compose. هذا ليس اعتباطياً — نموذج التخطيط في Compose (Column وRow وBox وModifier) يتطابق بشكل وثيق مع أنواع عقد IR (stack وoverlay وabsolute وleaf). المكدّس الرأسي في Figma هو Column في Compose. الترجمة ميكانيكية، مما يجعلها مناسبة لتوليد الكود المدفوع بالوكلاء.

تغطي هذه الجولة تعيين IR إلى Compose بالتفصيل، وتعرض مقطع JSON حقيقياً مع Composable المقابل، وتشرح طبقة تعيين الرموز.

لماذا Compose هو الهدف الافتراضي

ثلاثة أسباب هيكلية:

  1. Auto-layout ↔ Column/Row. إطارات Auto-layout في Figma (الغالبية العظمى من تصاميم Figma الحديثة) تُصدَّر كعقد kind: "stack". عقد Stack لها خاصية axisvertical تُعيَّن إلى Column، وhorizontal تُعيَّن إلى Row. هذا تعيين 1:1 بلا خطوة تفسير.
  2. رموز التباعد ↔ قيم dp. يستخدم Compose Dp لجميع أبعاد التخطيط. قيم الرموز في tokens.json أعداد صحيحة بلا وحدات (مثل spacing.16 = 16) تُعيَّن مباشرة إلى 16.dp. لا تحويل، لا تحجيم.
  3. رموز الألوان ↔ Composable للألوان. قيم hex في tokens.json تُعيَّن إلى Color(0xFFrrggbb) بتحويل واحد. مفتاح الرمز يصبح اسم متغير دلالياً في ثيمك.

أنواع عقد IR وتعيينها في Compose

نوع IRالخصائصعنصر Compose الأساسي
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlayأطفال متراكبةBox
absolutex، y، width، heightBox مع Modifier.offset(x.dp, y.dp)
leaftype: "text"Text مع TextStyle
leaftype: "rectangle" مع 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 }
  }
}

قواعد التعيين:

مفاتيح الرموز غامضة عمداً (سلاسل hex للألوان، سلاسل رقمية للتباعد) حتى لا تحيّز النموذج نحو أي اتفاقية تسمية معينة. يمكن لثيم Compose الخاص بك تسميتها بأسماء دلالية — colorPrimary، spacingMd — بشكل مستقل عن IR.

مثال حقيقي: من JSON لشاشة رئيسية إلى Composable

إليك IR مبسط لشاشة رئيسية. مكدّس رأسي مع عقدة رأس وقائمة بطاقات:

{
  "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"
            }
          ]
        }
      ]
    }
  ]
}

الـ Composable المقابل — ما يجب على الوكيل إنتاجه من هذا IR:

@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.dp تأتي من spacing.16، و24.dp من spacing.24، وColor(0xFF7F5CFE) من color.7f5cfe.

مراجع السلاسل — تعيين stringResource

كل عقدة نص في IR تحمل stringRef بمفتاح بتدوين النقطة. ملف strings.json يُعيّن المفاتيح إلى قيم العرض والاحتياطيات:

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

تدوين النقطة يُعيَّن إلى معرّفات موارد سلاسل Android مع استبدال النقاط بشرطات سفلية: home.titleR.string.home_title. حقل fallback هو ما تُشفّره كسلسلة نصية حرفية إذا لم يكن المورد موجوداً بعد في strings.xml:

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

أخبر الوكيل باستخدام حقل الاحتياط دائماً — لا سلسلة فارغة — حتى تكون الشاشة قابلة للقراءة أثناء التطوير قبل ملء strings.xml.

التموضع المطلق

العقد ذات kind: "absolute" تستخدم إحداثيات Figma مباشرة. تظهر في التصاميم ذات العناصر المتداخلة أو المثبّتة في مواضع محددة. تعيين Compose يستخدم 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 جيدة البنية (معظم الملفات الحديثة تستخدم auto-layout). عند رؤيته، تحقق مما إذا كانت نية التصميم مطلقة حقاً أم أن المصمم لم يُطبّق قيود auto-layout — لا يمكن لـ IR استنتاج النية.

الفجوات الصادقة

تغطي الحزمة الحالة الشائعة جيداً. بعض الأشياء التي لا تغطيها:

هذه الفجوات صريحة — تظهر في تحذيرات _meta.json وCONTEXT.md. الوكيل لا يخمّن من خلالها بصمت.

صدّر حزمة السياق من تطبيق figmascope الرئيسي، ثم استخدمها مع Claude Code أو Cursor لتنفيذ Composables مباشرة من IR. لا تخمين من لقطات الشاشة، لا قيم مُشفَّرة.