هدف التصدير الافتراضي لـ figmascope هو Jetpack Compose. هذا ليس اعتباطياً — نموذج التخطيط في Compose (Column وRow وBox وModifier) يتطابق بشكل وثيق مع أنواع عقد IR (stack وoverlay وabsolute وleaf). المكدّس الرأسي في Figma هو Column في Compose. الترجمة ميكانيكية، مما يجعلها مناسبة لتوليد الكود المدفوع بالوكلاء.
تغطي هذه الجولة تعيين IR إلى Compose بالتفصيل، وتعرض مقطع JSON حقيقياً مع Composable المقابل، وتشرح طبقة تعيين الرموز.
لماذا Compose هو الهدف الافتراضي
ثلاثة أسباب هيكلية:
- Auto-layout ↔ Column/Row. إطارات Auto-layout في Figma (الغالبية العظمى من تصاميم Figma الحديثة) تُصدَّر كعقد
kind: "stack". عقد Stack لها خاصيةaxis—verticalتُعيَّن إلىColumn، وhorizontalتُعيَّن إلىRow. هذا تعيين 1:1 بلا خطوة تفسير. - رموز التباعد ↔ قيم dp. يستخدم Compose
Dpلجميع أبعاد التخطيط. قيم الرموز فيtokens.jsonأعداد صحيحة بلا وحدات (مثلspacing.16 = 16) تُعيَّن مباشرة إلى16.dp. لا تحويل، لا تحجيم. - رموز الألوان ↔ Composable للألوان. قيم hex في
tokens.jsonتُعيَّن إلىColor(0xFFrrggbb)بتحويل واحد. مفتاح الرمز يصبح اسم متغير دلالياً في ثيمك.
أنواع عقد IR وتعيينها في Compose
| نوع IR | الخصائص | عنصر Compose الأساسي |
|---|---|---|
stack | axis: "vertical" | Column |
stack | axis: "horizontal" | Row |
overlay | أطفال متراكبة | Box |
absolute | x، y، width، height | Box مع Modifier.offset(x.dp, y.dp) |
leaf | type: "text" | Text مع TextStyle |
leaf | type: "rectangle" مع fill | Box(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 }
}
}
قواعد التعيين:
spacing.16→16.dpradius.12→RoundedCornerShape(12.dp)color.7f5cfe→Color(0xFF7F5CFE)(أضف0xFFللشفافية الكاملة)typography.heading.24→TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, lineHeight = 28.8.sp)
مفاتيح الرموز غامضة عمداً (سلاسل 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.title → R.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 استنتاج النية.
الفجوات الصادقة
تغطي الحزمة الحالة الشائعة جيداً. بعض الأشياء التي لا تغطيها:
- ملءات التدرج. يُصدر IR تحذيراً عند مواجهة تدرج. ملء خلفية العقدة يُحذف. اترك
// TODO: gradientونفّذه يدوياً. - تأثيرات معقدة. ظلال الإسقاط والتمويه غير ممثلة في IR. تظهر في تحذيرات
_meta.jsonإن وُجدت. - أيقونات متجهية. يخزن IR معرّف مرجعي لعقد الأيقونات، لا بيانات المسار. ستحتاج لحل الأيقونة إلى drawable أو Compose icon فعلي بشكل منفصل.
- مكونات متداخلة. يتضمن IR
componentIdعلى عقد INSTANCE.components/inventory.jsonيُعيّن المعرّفات إلى الأسماء. نفّذ المكوّن بشكل منفصل وأشر إليه بالاسم في Composable الأصل.
هذه الفجوات صريحة — تظهر في تحذيرات _meta.json وCONTEXT.md. الوكيل لا يخمّن من خلالها بصمت.
صدّر حزمة السياق من تطبيق figmascope الرئيسي، ثم استخدمها مع Claude Code أو Cursor لتنفيذ Composables مباشرة من IR. لا تخمين من لقطات الشاشة، لا قيم مُشفَّرة.