{"id":"l-ds-4-2","courseId":"design-systems","lang":"ar","kind":"lesson","summary":"طبّق الـ semver (الترقيم الدلالي للإصدارات) على نظام التصميم بتحديد الـ API العام بدقّة، وتصنيف التغييرات على الـ props والـ tokens والمظهر، وكتابة changelog يخبر المستخدمين بما عليهم فعله.","html":"<p>حين أطلقت Northwind الإصدار 3.4.0 من حزمة الواجهة، فشل البناء لدى أحد عشر فريقًا في الصباح التالي. ملاحظات الإصدار كانت تقول «تحسينات طفيفة». أحد تلك التحسينات أعاد تسمية الـ token المسمّى <code dir=\"ltr\">--nw-color-text-light</code>، لأن الفريق قرّر (محقًّا) أن الاسم مضلِّل. كان تغييرًا جيدًا أُطلق برقم خاطئ، وكلّفنا من الثقة أكثر من أي خلل في تلك السنة. الترقيم هو الطريقة التي يقطع بها نظام التصميم وعوده لمستخدميه، والوعود المكسورة باهظة الثمن.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ semver في فقرة واحدة</h2>\n<p>الترقيم الدلالي للإصدارات (semantic versioning أو semver) يعطي كل إصدار رقمًا بالشكل <code dir=\"ltr\">MAJOR.MINOR.PATCH</code>. الـ <strong>patch</strong> ‏(4.7.2 ← 4.7.3) يصلح أخطاء دون تغيير الـ API العام. والـ <strong>minor</strong> ‏(4.7.2 ← 4.8.0) يضيف وظائف بطريقة متوافقة مع ما سبق. والـ <strong>major</strong> ‏(4.7.2 ← 5.0.0) يحتوي تغييرات قد تكسر الاستخدام الحالي. ومديرو الحزم يعتمدون على هذا: الاعتمادية المكتوبة بالشكل <code dir=\"ltr\">^4.7.2</code> تقبل أي 4.x.x من 4.7.2 فصاعدًا، لكنها لا تقبل 5.0.0 أبدًا. وقبل 1.0.0 يمكن أن يتغيّر أي شيء في أي وقت، لذا يجب أن يصل النظام الذي تعتمد عليه الفرق إلى 1.0 مبكرًا.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">حدّد الـ API العام أولًا</h2>\n<p>الـ semver لا يعني شيئًا حتى تكتب ما هو «الـ API العام». في Northwind UI هو:</p>\n<ul>\n<li>كل مكوّن مُصدَّر والـ props الخاصة به، بما فيها قيمها الافتراضية؛</li>\n<li>كل اسم token في حزمة الـ tokens، والدور الذي يصفه كلٌّ منها؛</li>\n<li>نقاط ربط CSS الموثّقة: <code dir=\"ltr\">data-variant</code> و<code dir=\"ltr\">data-size</code> والـ component tokens؛</li>\n<li>السلوك الموثّق، مثل «يعيد Dialog التركيز إلى العنصر الذي فتحه».</li>\n</ul>\n<p>أي شيء خارج هذه القائمة، مثل أسماء الـ classes الداخلية، أو تداخل الـ DOM داخل المكوّن، أو الدوال المساعدة الخاصة، يمكن أن يتغيّر في أي إصدار. اكتب القائمة في التوثيق، وأخبر الفرق بوضوح أن الاعتماد على التفاصيل الداخلية غير الموثّقة على مسؤوليتها.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">تصنيف التغييرات الحقيقية</h2>\n<p>بعد تحديد الـ API، تصبح معظم القرارات آلية:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>التغيير</th>\n<th>الرفع</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>إصلاح Dialog الذي لا يعيد التركيز عند الإغلاق</td>\n<td>Patch</td>\n</tr>\n<tr>\n<td>إضافة prop باسم <code dir=\"ltr\">loading</code> إلى Button</td>\n<td>Minor</td>\n</tr>\n<tr>\n<td>إضافة مكوّن Tabs جديد أو token جديد</td>\n<td>Minor</td>\n</tr>\n<tr>\n<td>تحسين قيمة token ضمن الدور نفسه</td>\n<td>Minor، مع ملاحظة وفروقات بصرية</td>\n</tr>\n<tr>\n<td>إعادة تسمية token أو prop أو مكوّن، أو حذفه</td>\n<td>Major</td>\n</tr>\n<tr>\n<td>تغيير قيمة افتراضية، مثل الـ variant الافتراضي لـ Button</td>\n<td>Major</td>\n</tr>\n</tbody>\n</table></div><p>صفّان يستحقّان تعليقًا. تغيير القيمة الافتراضية major لأن الكود الذي اعتمد عليها صار يُعرض بشكل مختلف دون تغيير سطر واحد. وتحسينات القيم minor لأن عقد الـ token هو دوره، لا لونه بالضبط؛ فإن صار «النص الهادئ» أغمق قليلًا لينجح في التباين، فكل شاشة استخدمته ما زالت تحصل على نص هادئ. عامل هذه كتحديثات تصميم: أبرزها في الـ changelog بصور قبل وبعد، لأن الفرق التي لديها اختبارات لقطات شاشة سترى صورها المرجعية تتغيّر.</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>إعادة التسمية تبدو غير مؤذية لأن شيئًا في التصميم لم يتغيّر. لكن بالنسبة إلى عملية البناء لدى فريق يستخدم الاسم القديم، إعادة التسمية مطابقة للحذف تمامًا. أطلق الاسم الجديد في إصدار minor، وأبقِ القديم اسمًا مستعارًا مُهمَلًا، ولا تحذفه إلا في الـ major التالي. والدرس القادم يبني هذا المسار خطوة بخطوة.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">الـ Changesets والـ changelogs</h2>\n<p>تذكّر الرفع الصحيح وقت الإصدار لا يصمد مع كثرة المساهمين. تستخدم Northwind أداة Changesets: كل pull request يغيّر حزمة يضيف ملفًا صغيرًا يذكر نوع الرفع وملخّصًا مكتوبًا للمستخدمين.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"markdown\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">.changeset/quiet-trucks-dance.md</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\">---</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"@northwind/ui\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">minor</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">---</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">Button: add a </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">`loading`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> prop. While loading, the button keeps its width, sets </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">`aria-busy`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, and ignores clicks.</span></span></code></pre></div>\n<p>حين يُجهَّز إصدار، تأخذ الأداة أعلى رفع بين الـ changesets المعلّقة، وتحدّث الإصدار، وتحوّل الملخّصات إلى <code dir=\"ltr\">CHANGELOG.md</code>. وهذا المنطق نفسه في صورة مصغّرة:</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\"> order</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { patch: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, minor: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, major: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">2</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> nextVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">current</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">changes</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:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">major</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">minor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">patch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> current.</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:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(Number);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> bump</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> changes.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">top</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">c</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (order[c.bump] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> order[top] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c.bump </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> top), </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'patch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (bump </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'major'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">major</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.0.0`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (bump </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> 'minor'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">major</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">minor</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.0`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">major</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">minor</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}.${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">patch</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</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>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> pending</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\">  { bump: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'patch'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, summary: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Dialog returns focus to its trigger on close.'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { bump: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'minor'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, summary: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Button: add a `loading` prop.'</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:#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:#8250DF;--shiki-dark:#D2A8FF\">nextVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'4.7.2'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, pending));</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:#8250DF;--shiki-dark:#D2A8FF\">nextVersion</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'4.7.2'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">pending, { bump: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'major'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, summary: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'Remove Button `kind`.'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }]));</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>الـ changelog الجيد يجمّع الإدخالات حسب الأثر (كاسر، مُضاف، مُصلَح)، ويُكتب للأشخاص الذين يرفعون الإصدار، ويعطي كل إدخال كاسر خطوات الانتقال الخاصة به. عبارة «Refactored Button internals» لا تقول لفريق المنتج شيئًا؛ أما «حُذف <code dir=\"ltr\">kind</code> من Button، استخدم <code dir=\"ltr\">variant</code>، وهناك codemod متاح» فتقول له بالضبط ما يفعل.</p>\n<aside class=\"callout callout-tip\" 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=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>إصدار واحد للحزمة كلها</span></p>\n<p>بعض الأنظمة تعطي كل مكوّن رقم إصدار منفصلًا. جرّبت Northwind ذلك وانتهت بمصفوفة توافق لا يستطيع أحد قراءتها: Dialog 2.x يحتاج Button 3.x، لكن Tabs 1.x يحتاج Button 2.x. إصدار واحد لـ <code dir=\"ltr\">@northwind/ui</code> وآخر لـ <code dir=\"ltr\">@northwind/tokens</code> أسهل على الجميع في الفهم.</p>\n</aside>\n<p>في التمرين تعطي أرقام إصدارات لدفعة من التغييرات. وبعدها تخطّط لكيفية حذف الأشياء دون أن تكسر عمل أربعين فريقًا في صباح واحد.</p>\n","toc":[{"id":"sec-1","text":"الـ semver في فقرة واحدة","level":2},{"id":"sec-2","text":"حدّد الـ API العام أولًا","level":2},{"id":"sec-3","text":"تصنيف التغييرات الحقيقية","level":2},{"id":"sec-4","text":"الـ Changesets والـ changelogs","level":2}],"takeaways":["لا يعمل الـ semver إلا حين تكتب ما هو الـ API العام لديك؛ وفي نظام التصميم يشمل ذلك props المكوّنات، والأسماء المصدَّرة، وأسماء الـ tokens، ونقاط ربط CSS الموثّقة.","حذف أي شيء عام أو إعادة تسميته، أو تغيير قيمة افتراضية، تغيير major، حتى حين يبدو صغيرًا.","إضافة props أو مكوّنات أو tokens تغيير minor؛ وإصلاح السلوك ليطابق التوثيق patch.","الـ changelog يُكتب للمستخدمين، ويُجمَّع حسب الأثر، وكل إدخال كاسر يتضمّن خطوات الانتقال."],"further":[{"title":"About semantic versioning (npm Docs)","url":"https://docs.npmjs.com/about-semantic-versioning"},{"title":"Semantic Versioning specification (source)","url":"https://github.com/semver/semver/blob/master/semver.md"},{"title":"Changesets (official repository)","url":"https://github.com/changesets/changesets"}],"quiz":[{"q":"<p>الحزمة <code dir=\"ltr\">@northwind/ui</code> في الإصدار 4.7.2. إصدار جديد يحذف الـ prop المُهمَل <code dir=\"ltr\">kind</code> من Button (حلّ محلّه <code dir=\"ltr\">variant</code> في 4.3.0). ما الإصدار التالي؟</p>","options":[{"text":"4.7.3، لأن الـ prop كان مُهمَلًا مسبقًا.","why":"الـ deprecation تحذير، لا حذف؛ والكود الذي ما زال يمرّر <code dir=\"ltr\">kind</code> ينكسر حين يختفي."},{"text":"4.8.0، لأن prop واحدًا فقط تغيّر.","why":"عدد التغييرات لا يهمّ؛ أي حذف من الـ API العام تغيير كاسر."},{"text":"5.0.0، لأن حذف prop عام يكسر الكود الذي ما زال يستخدمه.","why":"صحيح. الحذف مكانه إصدار major، ويُفضّل تجميعه مع عمليات الحذف المخطّطة الأخرى."}],"answer":2},{"q":"<p>تجعل Northwind اللون <code dir=\"ltr\">--nw-color-text-subtle</code> أغمق قليلًا حتى ينجح في التباين في الوضع الداكن. الاسم والدور يبقيان كما هما. كيف يجب أن يُطلَق؟</p>","options":[{"text":"كإصدار major، لأن كل شاشة فيها نص subtle ستبدو مختلفة.","why":"معاملة كل تعديل في القيم كـ major تعني إصدار major كل بضعة أسابيع، وتعلّم الناس تجاهل الـ majors."},{"text":"كإصدار minor مع ملاحظة واضحة في الـ changelog وفروقات بصرية، لأن عقد الـ token (دوره) لم يتغيّر.","why":"صحيح. الاسم والمعنى هما العقد؛ وتحسين القيمة ضمن ذلك الدور تحديث تصميم موثّق."},{"text":"كـ patch دون إدخال في الـ changelog.","why":"التغييرات المرئية تستحقّ ملاحظة حتى حين تكون متوافقة؛ فالفرق التي لديها اختبارات لقطات شاشة تحتاج أن تعرف لماذا تغيّرت صورها المرجعية."},{"text":"يجب ألا يُطلَق؛ فقيم الـ tokens يجب ألا تتغيّر أبدًا.","why":"يُتوقَّع أن تتطوّر القيم؛ ولهذا تشير المنتجات إلى الأسماء بدل كتابة القيم يدويًا."}],"answer":1},{"q":"<p>أيّ إدخال في الـ changelog هو الأكثر فائدة لفريق منتج؟</p>","options":[{"text":"“Refactored Button internals and cleaned up styles.”","why":"يصف عمل القائمين على النظام، لا ما يتغيّر عند المستخدم ولا هل عليه أن يفعل شيئًا."},{"text":"“Bumped dependencies.”","why":"دقيق لكنه فارغ؛ لا يقول هل تغيّر شيء مرئي أو سلوكي."},{"text":"“Various fixes and improvements.”","why":"يجبر كل فريق على قراءة الفروقات ليعرف هل هو متأثّر."},{"text":"“Breaking: Button <code dir=\"ltr\">kind</code> prop removed. Replace <code dir=\"ltr\">kind=&quot;primary&quot;</code> with <code dir=\"ltr\">variant=&quot;primary&quot;</code>, or run the codemod <code dir=\"ltr\">nw-codemods button-kind</code>.”","why":"صحيح. يقول ما الذي تغيّر، ومَن المتأثّر، وكيف ينتقل بالضبط."}],"answer":3}],"exercise":{"kind":"fill","language":"text","template":"@northwind/ui is at 4.7.2. Each line is a separate release from 4.7.2.\n\nFix: Dialog now returns focus to its trigger on close        -> {{0}}\nAdd a `loading` prop to Button                               -> {{1}}\nRemove the old token name --nw-color-text-light              -> {{2}}\nChange Button's default variant from \"primary\" to \"secondary\" -> {{3}}\nA Dialog focus fix plus a new Tabs component                 -> {{4}}\nDarken --nw-color-text-subtle slightly (same name and role)  -> {{5}}","blanks":[{"answers":["4.7.3"]},{"answers":["4.8.0"]},{"answers":["5.0.0"]},{"answers":["5.0.0"]},{"answers":["4.8.0"]},{"answers":["4.8.0"]}],"title":"رقّم الإصدارات","prompt":"<p>طبّق سياسة الترقيم في Northwind من الدرس. كل سطر إصدار مستقل يبدأ من <strong>4.7.2</strong>. اكتب رقم الإصدار التالي كاملًا لكلٍّ منها، مثل <code dir=\"ltr\">4.7.3</code>.</p>\n","hints":["اسأل عن كل تغيير: هل يصلح شيئًا في الـ API العام (الـ props والقيم الافتراضية وأسماء الـ tokens)، أم يضيف إليه، أم يكسره؟","يأخذ الإصدار أعلى رفع بين تغييراته.","قيم الـ tokens يمكن أن تتطوّر ضمن دورها؛ أما أسماء الـ tokens فهي العقد."],"explanation":"<p>إصلاح التركيز يعيد سلوكًا موثّقًا، فهو patch ‏(4.7.3). والـ prop الجديد إضافة متوافقة مع ما سبق (4.8.0). وحذف اسم token يكسر كل من ما زال يستخدمه، وتغيير القيمة الافتراضية يغيّر العرض دون أي تغيير في الكود، فكلاهما major ‏(5.0.0). والإصدار الذي يجمع إصلاحًا ومكوّنًا جديدًا يأخذ الرفع الأعلى، أي minor ‏(4.8.0). وتحسين القيمة الذي يحافظ على اسم الـ token ودوره تحديث تصميم من نوع minor ‏(4.8.0)، يُطلَق مع ملاحظة في الـ changelog وفروقات بصرية.</p>\n","checkLabels":{}},"words":639,"readMinutes":4}