{"id":"l-ds-5-3","courseId":"design-systems","lang":"ar","kind":"lesson","summary":"قِس تبنّي نظام التصميم (adoption)، وتغطية الـ tokens، وحداثة الإصدارات، ورضا الفرق، بعمليات مسح آلية واستبيانات، وقل «لا» للطلبات بطريقة تحمي النظام وتحفظ ثقة الفرق.","html":"<p>بعد عامين من الحياة الثانية لـ Northwind UI، أعدت تشغيل سكربت الألوان من درس جرد الواجهة. صارت الدرجات الرمادية الأربع والستون تسعًا، وسبع منها primitives في ملف الـ tokens. هذا الرقم الواحد فعل لتمويل الفريق أكثر من أي عرض تقديمي، لأنه القياس نفسه، مأخوذًا بالطريقة نفسها، يُظهر «قبل» و«بعد». القياس هو الطريقة التي يثبت بها نظام التصميم أنه منتج يستحقّ أن يُدفع له، والطريقة التي يكتشف بها أين يخذل مستخدميه.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">ماذا تقيس</h2>\n<p>لا يوجد رقم واحد يخبرك هل النظام يعمل. تتابع Northwind أربعة اتّجاهات، وتراجعها شهريًا:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>المقياس</th>\n<th>السؤال الذي يجيب عنه</th>\n<th>كيف نجمعه</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>تبنّي المكوّنات</td>\n<td>كم من الواجهة مبني بمكوّنات النظام؟</td>\n<td>مسح الاستيرادات في كل مستودع، ومسح الـ DOM للصفحات الرئيسية</td>\n</tr>\n<tr>\n<td>تغطية الـ tokens</td>\n<td>هل تستخدم الأنماط الـ tokens أم القيم الخام؟</td>\n<td>سكربت الألوان، موسّعًا ليشمل المسافات والطباعة</td>\n</tr>\n<tr>\n<td>تأخّر الإصدارات</td>\n<td>كم تتأخّر الفرق؟</td>\n<td>إصدارات الحزم عبر المستودعات</td>\n</tr>\n<tr>\n<td>الرضا</td>\n<td>هل يجعل النظام الفرق أسرع؟</td>\n<td>استبيان من خمسة أسئلة كل ربع سنة</td>\n</tr>\n</tbody>\n</table></div><p>معًا تقاوم هذه المقاييس التلاعب. يمكن لفريق أن يضخّم التبنّي بتغليف كل شيء في <code dir=\"ltr\">Box</code> من النظام، لكن تغطية الـ tokens والرضا لن يتحرّكا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">جمع بيانات التبنّي بنزاهة</h2>\n<p>مسح الاستيرادات هو العمود الفقري. يمرّ سكربت على كل مستودع، ويحلّل ملفات المصدر، ويعدّ عناصر JSX التي استُورد مكوّنها من <code dir=\"ltr\">@northwind/ui</code> مقابل المكوّنات المعرّفة محليًا. والبحث في الكود على GitHub يعطي إجابة أولى سريعة عن سؤال منفرد، مثل «أين ما زال الـ prop المُهمَل <code dir=\"ltr\">kind</code> مستخدمًا؟»، وهكذا يعمل تتبّع الاستخدام في درس الـ deprecation.</p>\n<p>ولصفحات الإنتاج، كل مكوّن في Northwind يعرض سمة علامة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"html\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">HTML</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> class</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"nw-button\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> data-variant</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"primary\"</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> data-nw-component</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Button\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>Assign driver&#x3C;/</span><span style=\"color:#116329;--shiki-dark:#7EE787\">button</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">></span></span></code></pre></div>\n<p>مسح الصفحة المعروضة يعدّ العناصر التي تحمل <code dir=\"ltr\">data-nw-component</code> مقابل كل العناصر من النوع نفسه. وهذه السمة أهمّ مما تبدو. النسخة الأولى من مسحنا كانت تعدّ الـ class المسمّى <code dir=\"ltr\">nw-button</code>، فأبلغت عن تبنٍّ بنسبة 94% لـ Button في الموقع التسويقي. ثم اكتشفنا أن ثلاث فرق نسخت الـ CSS الخاص بالـ Button إلى مكوّناتها، بأسماء الـ classes. المكوّن الحقيقي وحده يكتب الـ data attribute، فتتوقّف النسخ المنفصلة عن الاحتساب تبنّيًا.</p>\n<p>ثم تُجمَّع الأرقام لكل فريق:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> scans</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dispatch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">412</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">455</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'5.2.0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Billing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">96</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">180</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'4.9.1'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Marketing'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">230</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">251</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'5.2.0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { team: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Reporting'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, system: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">40</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, total: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">160</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, version: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'3.8.0'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> latestMajor</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 5</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> s</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> scans) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> percent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((s.system </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">/</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> s.total) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> majorsBehind</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> latestMajor </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(s.version.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">split</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'.'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">s</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">team</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">padEnd</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">10</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} ${</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">String</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">percent</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">padStart</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">)</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}%  ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">majorsBehind</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">majorsBehind</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} major(s) behind`</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'current'}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>نسبة 25% لدى فريق Reporting ليست سببًا لإرسال بريد إلى مديره. إنها سؤال بحثي: ما الذي يحتاجه Reporting ولا يملكه النظام؟ في حالة Northwind كانت الإجابة الرسوم البيانية والجداول الكثيفة، وصارتا خارطة الطريق لربعَي السنة التاليين.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>استخدام أرقام التبنّي كعصا</span></p>\n<p>حين تظهر أرقام التبنّي في مراجعات الإدارة مقرونة بأسماء الفرق، تتعلّم الفرق التلاعب بها: إعادة تسمية المكوّنات المحلية، وتغليف الأشياء بـ primitives من النظام لا تحتاجها. ارفع للإدارة اتّجاهات المؤسسة كلها، واستخدم أرقام كل فريق داخل حواراتك مع ذلك الفريق فقط، لتعرف ما الناقص.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">قول «لا»</h2>\n<p>النظام الذي يقول نعم لكل شيء يعود إلى الدرجات الرمادية الأربع والستين، بخطوات إضافية. تقول Northwind «لا» للمكوّنات الخاصة بفريق واحد، ولتغييرات السلوك الخاصة بعلامة، وللـ props التي تتجاوز الـ tokens، ولأي شيء يكسر مبدأً. والمهارة في قولها بطريقة تحفظ ثقة الفريق:</p>\n<ol>\n<li><strong>سمِّ السبب</strong>، مستخدمًا المبدأ: «الألوان الخاصة بكل مكوّن ستكسر الوضع الداكن وTidewater، ومبدؤنا هو إمكانية الوصول قبل الجمال».</li>\n<li><strong>قدّم بديلًا</strong>: بناءً محليًا من قطع النظام، أو مكوّنًا موجودًا بتوثيق أفضل، أو مخرجًا موثّقًا مثل component token.</li>\n<li><strong>اكتبها</strong> في سجلّ القرارات، حتى يحصل الفريق التالي صاحب الطلب نفسه على الإجابة نفسها والبديل نفسه.</li>\n<li><strong>اترك الباب مفتوحًا</strong>: «إن احتاج فريقان آخران هذا، أعيدوه إلى مراجعة الـ API».</li>\n</ol>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>لماذا يهمّ هذا</span></p>\n<p>كل «نعم» وعدٌ بصيانة شيء في كل theme وعلامة وإصدار لسنوات. أربعون فريقًا يعتمدون على وفاء فريق النواة بهذه الوعود. وقول «لا» بوضوح ولطف هو الطريقة التي تُبقي بها النظام صغيرًا بما يكفي للوفاء بها.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">أين وصلت الآن</h2>\n<p>تابعت Northwind UI من تدقيق 64 درجة رمادي إلى نظام يستخدمه أربعون فريقًا: مبادئ ونموذج فريق، وtokens في ثلاث طبقات مع themes وعلامات، ومكوّنات بـ APIs قابلة للتركيب وإمكانية وصول مبنية من الأساس، وإصدارات مرقّمة بمسارات deprecation واختبارات بصرية، وcontribution model مع governance ومقاييس. لا شيء من هذا منتهٍ، وهذا بالضبط معنى أن تديره كمنتج. في التمرين تكتب مسح التبنّي الذي يخبرك أين تنظر بعد ذلك.</p>\n","toc":[{"id":"sec-1","text":"ماذا تقيس","level":2},{"id":"sec-2","text":"جمع بيانات التبنّي بنزاهة","level":2},{"id":"sec-3","text":"قول «لا»","level":2},{"id":"sec-4","text":"أين وصلت الآن","level":2}],"takeaways":["قِس التبنّي بإشارات لا ينتجها إلا النظام الحقيقي، مثل استيراد الحزمة أو data attribute خاصة بالمكوّن، لا بأسماء classes يمكن نسخها.","تابع بضعة اتّجاهات معًا - تبنّي المكوّنات، وتغطية الـ tokens، وتأخّر الإصدارات، والرضا - لأن أي رقم منفرد يمكن التلاعب به.","استخدم المقاييس لتجد أين يخذل النظام الفرق، لا لتُخجل الفرق بسبب أرقامها المنخفضة أبدًا.","«لا» الجيدة تسمّي المبدأ، وتقدّم بديلًا، وتُكتب حتى يمكن مراجعتها بأدلّة جديدة."],"further":[{"title":"Data attributes (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/data-*"},{"title":"About code search (GitHub Docs)","url":"https://docs.github.com/en/search-github/github-code-search/about-github-code-search"},{"title":"Design Systems 101 (Nielsen Norman Group)","url":"https://www.nngroup.com/articles/design-systems-101/"}],"quiz":[{"q":"<p>يضع نائب الرئيس في Northwind هدفًا «تبنٍّ لنظام التصميم بنسبة 100% بحلول الربع الرابع». ما الخطر الرئيسي؟</p>","options":[{"text":"ستصل الفرق إلى 100% بسرعة كبيرة وسينفد العمل لدى فريق النواة.","why":"لدى الأنظمة دائمًا عمل أكثر من الأشخاص؛ وهذا ليس الخطر الواقعي."},{"text":"سكربتات المسح لا تستطيع العدّ فوق 100%.","why":"الحساب سليم؛ المشكلة فيما يفعله الناس للوصول إلى الرقم."},{"text":"أرقام التبنّي لا يمكن قياسها آليًا.","why":"مسح الاستيرادات والـ DOM يقيس التبنّي جيدًا؛ قابلية القياس ليست المشكلة."},{"text":"ستحشر الفرق مكوّنات النظام في أماكن لا تناسبها، أو تعيد تسمية مكوّناتها المحلية، لتصل إلى الهدف.","why":"صحيح. حين يصبح المقياس هدفًا، يحسّن الناس المقياس؛ وبعض المكوّنات المحلية هي الإجابة الصحيحة."}],"answer":3},{"q":"<p>أيّ إشارة هي الأكثر موثوقية لعدّ المواضع التي يُستخدم فيها Button الحقيقي من النظام في صفحات الإنتاج؟</p>","options":[{"text":"العناصر التي تحمل الـ class المسمّى <code dir=\"ltr\">nw-button</code>.","why":"الفرق تنسخ أسماء الـ classes مع الـ CSS المنسوخ، فتُحتسب النسخ المنفصلة تبنّيًا."},{"text":"العناصر التي تحمل <code dir=\"ltr\">data-nw-component=&quot;Button&quot;</code>، وهي سمة لا يعرضها إلا مكوّن النظام.","why":"صحيح. العلامة التي لا يكتبها إلا المكوّن الحقيقي لا يمكن نسخها مع الـ CSS عن غير قصد."},{"text":"الأزرار التي يطابق لون خلفيتها token الإجراء.","why":"تطابق الألوان يحدث صدفة، ويفوّت كل variant غير أساسي."},{"text":"الصفحات التي تحمّل ملف CSS الخاص بالنظام.","why":"تحميل الملف لا يقول شيئًا عن عدد العناصر في الصفحة التي تستخدمه فعلًا."}],"answer":1},{"q":"<p>يطلب فريق التقارير prop باسم <code dir=\"ltr\">chartColors</code> على كل مكوّن ليطابق لوحات المعلومات الخاصة به. كيف يجب أن يقول فريق النواة «لا»؟</p>","options":[{"text":"تجاهل الطلب حتى يتوقّف الفريق عن السؤال.","why":"الصمت يُقرأ كعدم احترام، ويدفع الفريق نحو نسخ النظام."},{"text":"قبوله، لأن قول «لا» يضرّ بالتبنّي.","why":"prop يتجاوز الـ tokens في كل مكوّن يضرّ بكل علامة وكل theme لإرضاء فريق واحد."},{"text":"شرح أن الألوان الخاصة بكل مكوّن تكسر الـ theming، وتوجيههم إلى tokens لوحة ألوان الرسوم البيانية، وتسجيل القرار، ودعوتهم إلى تقديم أدلّة إن تغيّرت الاحتياجات.","why":"صحيح. يسمّي المبدأ، ويقدّم بديلًا حقيقيًا، ويُبقي الباب مفتوحًا كتابةً."},{"text":"إخبارهم أن لفريق نظام التصميم الكلمة الأخيرة.","why":"السلطة بلا أسباب تكسب الجدال وتخسر الفريق."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<header class=\"bar\">\n  <button class=\"nw-button\" data-nw-component=\"Button\" data-variant=\"primary\">New shipment</button>\n  <button class=\"btn-legacy\">Export</button>\n</header>\n<main>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Filter</button>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Sort</button>\n  <button class=\"nw-button\">Refresh</button>\n  <section id=\"settings\">\n    <button class=\"nw-button\" data-nw-component=\"Button\">Save</button>\n    <button class=\"btn-legacy\">Reset</button>\n    <button class=\"nw-button\">Advanced</button>\n  </section>\n</main>\n<p id=\"report\"></p>","css":"button { margin: 4px; }\n#report { font-weight: 600; }","js":"// Every real Northwind component renders data-nw-component=\"…\".\n// Some teams copied the nw-button class into their own components.\nfunction measureAdoption(root) {\n  const total = 0;\n  const system = 0;\n  const percent = 0;\n  return { total, system, percent };\n}\n\nconst result = measureAdoption(document);\ndocument.querySelector('#report').textContent = '';\n"},"solution":{"html":"<header class=\"bar\">\n  <button class=\"nw-button\" data-nw-component=\"Button\" data-variant=\"primary\">New shipment</button>\n  <button class=\"btn-legacy\">Export</button>\n</header>\n<main>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Filter</button>\n  <button class=\"nw-button\" data-nw-component=\"Button\">Sort</button>\n  <button class=\"nw-button\">Refresh</button>\n  <section id=\"settings\">\n    <button class=\"nw-button\" data-nw-component=\"Button\">Save</button>\n    <button class=\"btn-legacy\">Reset</button>\n    <button class=\"nw-button\">Advanced</button>\n  </section>\n</main>\n<p id=\"report\"></p>","css":"button { margin: 4px; }\n#report { font-weight: 600; }","js":"// Every real Northwind component renders data-nw-component=\"…\".\n// Some teams copied the nw-button class into their own components.\nfunction measureAdoption(root) {\n  const buttons = [...root.querySelectorAll('button')];\n  const total = buttons.length;\n  const system = buttons.filter((b) => b.dataset.nwComponent === 'Button').length;\n  const percent = total === 0 ? 0 : Math.round((system / total) * 100);\n  return { total, system, percent };\n}\n\nconst result = measureAdoption(document);\ndocument.querySelector('#report').textContent =\n  `${result.system} of ${result.total} buttons use Northwind UI (${result.percent}%)`;\n"},"checks":[{"id":"counts-page","test":"const r = window.measureAdoption(document); return r.total === 8 && r.system === 4 && r.percent === 50;"},{"id":"ignores-copies","test":"const r = window.measureAdoption(document); return r.system === $$('[data-nw-component=\"Button\"]').length && r.system < $$('.nw-button').length;"},{"id":"scoped","test":"const r = window.measureAdoption($('#settings')); return r.total === 3 && r.system === 1 && r.percent === 33;"},{"id":"live","test":"const b = document.createElement('button'); b.dataset.nwComponent = 'Button'; $('main').append(b); const r = window.measureAdoption(document); b.remove(); return r.total === 9 && r.system === 5 && r.percent === 56;"},{"id":"report","test":"const t = text('#report'); return t.includes('4 of 8') && t.includes('50%');"}],"title":"اكتب مسح التبنّي","prompt":"<p>اكتب الدالة <code dir=\"ltr\">measureAdoption(root)</code> بحيث تمسح عناصر <code dir=\"ltr\">&lt;button&gt;</code> داخل <code dir=\"ltr\">root</code> (مستند أو أي عنصر) وتعيد <code dir=\"ltr\">{ total, system, percent }</code>:</p>\n<ul>\n<li><code dir=\"ltr\">total</code>: عدد عناصر <code dir=\"ltr\">&lt;button&gt;</code>،</li>\n<li><code dir=\"ltr\">system</code>: عدد ما يحمل منها <code dir=\"ltr\">data-nw-component=&quot;Button&quot;</code>، وهي العلامة التي لا يعرضها إلا المكوّن الحقيقي (الـ class المسمّى <code dir=\"ltr\">nw-button</code> لا يُحتسب؛ فقد نسخته الفرق)،</li>\n<li><code dir=\"ltr\">percent</code>: ناتج <code dir=\"ltr\">system / total</code> كعدد صحيح مقرَّب بـ <code dir=\"ltr\">Math.round</code>، أو <code dir=\"ltr\">0</code> حين لا توجد أزرار.</li>\n</ul>\n<p>ثم اكتب نتيجة الصفحة داخل <code dir=\"ltr\">#report</code> بالشكل <code dir=\"ltr\">4 of 8 buttons use Northwind UI (50%)</code>، مستخدمًا الأرقام التي حسبتها.</p>\n","hints":["<code dir=\"ltr\">[...root.querySelectorAll('button')]</code> يعطيك مصفوفة بكل زر داخل <code dir=\"ltr\">root</code>.","<code dir=\"ltr\">button.dataset.nwComponent</code> يقرأ <code dir=\"ltr\">data-nw-component</code>. صفِّ على أن تكون قيمتها <code dir=\"ltr\">'Button'</code>.","ابنِ التقرير بـ template literal: <code dir=\"ltr\">${result.system} of ${result.total} buttons use Northwind UI (${result.percent}%)</code>."],"explanation":"<p>العدّ بالـ data attribute يقيس ما يهمّك فعلًا: العناصر التي عرضها مكوّن النظام الحقيقي. أما العدّ بالـ class المسمّى <code dir=\"ltr\">nw-button</code> فسيُبلغ هنا عن 6 من 8 ‏(75%)، لأن فريقين نسخا الـ class إلى أزرارهما، ولن تعرف أبدًا أن هذه النسخ المنفصلة موجودة. وقبول أي <code dir=\"ltr\">root</code> يسمح للدالة نفسها بالإبلاغ عن كل قسم من الصفحة أو منطقة كل فريق، والحساب عند الطلب يعني أن الأرقام تبقى صحيحة مع تغيّر الصفحة.</p>\n","checkLabels":{"counts-page":"<code dir=\"ltr\">measureAdoption(document)</code> تعيد 8 أزرار، 4 منها من النظام، أي 50%","ignores-copies":"الـ classes المنسوخة باسم <code dir=\"ltr\">nw-button</code> لا تُحتسب تبنّيًا","scoped":"مسح <code dir=\"ltr\">#settings</code> وحده يعيد 1 من 3 ‏(33%)","live":"يتحدّث العدد حين يُضاف زر من النظام إلى الصفحة","report":"يعرض <code dir=\"ltr\">#report</code> النص «4 of 8» و«50%»"}},"words":624,"readMinutes":4}