<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Oleksii Mazurenko — Blog</title>
    <link>https://oleksiimazurenko.dev/ar/blog</link>
    <description>Technical articles about web development, performance optimization, and developer tools.</description>
    <language>ar</language>
    <lastBuildDate>Tue, 30 Jun 2026 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://oleksiimazurenko.dev/ar/blog/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>أُخرج لوحاتي من Miro إلى Obsidian</title>
      <link>https://oleksiimazurenko.dev/ar/blog/migrate-miro-board-to-obsidian</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/migrate-miro-board-to-obsidian</guid>
      <description>زر Export في Miro يمنحك ملف PNG أو PDF — صورة فوتوغرافية للبيانات، لا البيانات نفسها. إن كانت لوحاتك خرائط تفكير حاملة للأثقال تراكمت على مدى سنوات، فمكانها Obsidian: قابلة للتحرير، قابلة للبحث، في vault محلي. إليك كيف أخرجت اللوحات من Miro عبر REST API وحوّلتها إلى Excalidraw / Obsidian Canvas — مع الحفاظ على المواضع والألوان والخطوط والموصّلات المنحنية وعُقد الخرائط الذهنية — والدرس أوسع من Miro: كل SaaS يستحق خطة خروج قبل أن تفتحه أصلاً.</description>
      <content:encoded><![CDATA[<p>أي أداة تعيش داخل المتصفح تحتجزك رهينةً لواجهتها. خاصةً بعد أن تكون قد صببت فيها الوقت والبطاقات واللوحات — ثم تدرك أن خريطة تفكيرك بأكملها قابعة على خادم شخص آخر، بصيغة لا يمكنك فتحها من دون تحميل صفحتهم.</p>
<p>لديّ بضع لوحات في Miro أصبحت منذ زمن حاملةً للأثقال — تأملات وخطط وخرائط ذهنية تراكمت على مدى سنوات. زر Export يمنحك ملف PNG أو PDF. هذه ليست بيانات، بل صورة فوتوغرافية للبيانات.</p>
<h2>لماذا تُخرجها من هناك</h2>
<p>قفل المورّد ليس مشكلة نظرية. إنه ملموس تماماً: كي تُنزّل بياناتك أنت عبر الـ API، عليك الترقية إلى فئة Expert. أي أنك تدفع مقابل الإذن باستعادة ما هو ملكك أصلاً.</p>
<p>أعيش منذ زمن طويل وفق مبدأ Obsidian-first: كل ما هو مهم يقبع في vault محلي، في ملفات نصية صِرفة أستطيع وضعها في git والبحث فيها بـ grep وقراءتها بعد عشر سنوات. الأداة التي لا تصدّر إلى تلك الصيغة أداةٌ يجب أن تبتعد عنها قبل أن تصبح التبعية مستحيلة الفكاك.</p>
<h2>لماذا لا ينقذك زر Export</h2>
<p>زر Export في Miro يمنحك ملف PNG أو PDF. هذا نقطي (raster) — البنية ضاعت. لا تستطيع تغيير النص داخل صندوق. لا تستطيع سحب شكل. لا تستطيع البحث بـ grep عن عنوان. إنها بطاقة نعي للوحة، لا اللوحة نفسها.</p>
<p>في Obsidian بديلان يعيشان داخل الـ vault: إضافة Excalidraw وكذلك Canvas الأصلي. كلاهما JSON. كلاهما قابل للتحرير. كلاهما قابل للبحث. كلاهما يقبع في vault-ك بجانب ملاحظاتك، ويمكنك البحث في النص بـ grep من داخل Obsidian مباشرةً.</p>
<h2>كيف تُخرجها حين لا يوجد زر &apos;Export to Obsidian&apos;</h2>
<p>حين لا يعطيك Export في الواجهة ما تحتاجه، تغوص في الـ API. لدى Miro واجهة REST API: تعيد وصفاً بصيغة JSON لكل شكل على اللوحة. وExcalidraw وObsidian Canvas كلاهما JSON بصيغة مفتوحة. وبينهما يقع التحويل (mapping).</p>
<p>كتبتُ سكربتَين بلغة Python. <code>miro_to_excalidraw.py</code> و<code>miro_to_canvas.py</code>. المكتبة القياسية فقط — صفر تبعيات. تُشغّلهما برمز مصادقة ومعرّف لوحة، فتحصل على ملف، ترميه في الـ vault.</p>
<h2>ما الذي نجا من التحويل</h2>
<p>لم تكن المهمة مجرد نقل الأشكال — بل الحفاظ على المظهر البصري كي تُقرأ اللوحة تماماً كما كانت داخل Miro.</p>
<ul><li><strong>المواضع والأحجام</strong> — إحداثيات كل شكل بنسبة 1:1</li><li><strong>الألوان</strong> مع شفافية التعبئة (الأصفر بنسبة 40% يبقى أصفر بنسبة 40%)</li><li><strong>الحدود</strong> — اللون، العرض، النمط (solid / dashed)</li><li><strong>الخطوط والمحاذاة</strong> — monospace يبقى monospace، وcenter يبقى center</li><li><strong>الموصّلات</strong> — الأسهم تلتصق عند النقاط ذاتها التي ثبّتها Miro</li><li><strong>الموصّلات المنحنية</strong> — أقواس bow بدلاً من الخطوط المستقيمة</li><li><strong>الصور</strong> مضمّنة داخل الملف (لا روابط URL خارجية)</li><li><strong>عُقد الخرائط الذهنية</strong> — النص مستخرَج عبر واجهة Mindmap API التجريبية</li><li><strong>ترتيب الطبقات (Z-ordering)</strong> — التداخلات لا تتشابك</li></ul>
<h2>أين صار الأمر شائكاً</h2>
<p>في واجهة Miro API ثغرتان أو ثلاث اضطُررت إلى الالتفاف حولها بالهندسة العكسية.</p>
<ul><li><strong>الموصّلات المنحنية</strong> — لا يكشف Miro نقاط التحكم عبر الـ API. لذا بدلاً من المنحنى الأصلي بدقّته، أصطنع bow — قوساً بنصف قطر ثابت بين الطرفَين. قريب بصرياً، لكنه mathematically غير مطابق.</li><li><strong>Z-index</strong> — واجهة Miro API لا تعيد حقل z إطلاقاً. أيُّ شكل يعلو وأيُّها يقبع تحت؟ الاستدلال التقريبي: المساحة الأكبر = أبعد إلى الخلف. وهذا ينجح لأن الحاويات الكبيرة في اللوحات الحقيقية تقبع عادةً تحت العناصر الأصغر.</li><li><strong>الخرائط الذهنية</strong> — نص عُقد الخريطة الذهنية لا يمكن الوصول إليه عبر items API المعتادة. تحتاج إلى نداء منفصل إلى نقطة نهاية Mindmap التجريبية. العُقد المملوءة تصير صناديق، وغير المملوءة تصير نصاً صِرفاً.</li></ul>
<h2>Excalidraw مقابل Canvas — كيف تختار</h2>
<p>إن كنت تفكّر على هيئة رسومات — خطوط تخطيطية، إحساس مرسوم باليد، تركيبات حرة الشكل — فاختر <strong>Excalidraw</strong>. إضافة Excalidraw تعرض الملف بالأسلوب الجمالي ذاته الذي رأيته على الـ whiteboards.</p>
<p>إن كانت لوحاتك في معظمها <em>مستطيلات فيها نص موصولة بأسهم</em>، وكنت تعيش في Obsidian بوصفه دماغك الثاني — فاختر <strong>Canvas</strong>. إن Canvas أصلي، يُفتح بنقرة واحدة، ومنسوج مع backlinks وtags الخاصة بالـ vault.</p>
<p>أحوّل بعض اللوحات إلى Excalidraw (حين يهمّ الأسلوب البصري)، وأخرى إلى Canvas (حين تهمّ السرعة والإحساس الأصلي). المستودع يدعم كلتا الصيغتين — تختار عند التشغيل.</p>
<h2>الخلاصة</h2>
<p>Python 3، المكتبة القياسية فقط. لا pip install، لا virtualenv، لا node_modules. <code>python3 miro_to_excalidraw.py 'BOARD_ID' 'output.excalidraw'</code> — والملف بين يديك.</p>
<p>المستودع: <a href="https://github.com/oleksiimazurenko/miro-to-obsidian">github.com/oleksiimazurenko/miro-to-obsidian</a>. رخصة MIT. طلبات الدمج مرحّب بها — خصوصاً للحالات الحدّية التي لم أصادفها بعد.</p>
<h2>الدرس أكبر من Miro</h2>
<p>قفل المورّد في SaaS مخدّر. الأشهر الأولى مجانية. بعد ستة أشهر يصير عادة. بعد سنة تعيش بياناتك في صيغة لا يقرؤها إلا واجهتهم. بعد سنتين تدفع مقابل فئة Expert كي تستخرج ما هو ملكك أصلاً.</p>
<p>الخلاصة ليست «لا تستخدم SaaS». الخلاصة أن كل أداة تسمح لها بأن تصبح حاملةً للأثقال تحتاج إلى <strong>خطة خروج قبل أن تفتحها للمرة الأولى أصلاً</strong>. سؤال «كيف أخرج من هنا حين أحتاج» يزن تماماً بثقل سؤال «ما مدى لطافة الواجهة». الأداة التي لا تملك تصديراً بنص صِرف أو واجهة API مفتوحة أداةٌ ببطاقة سعر خفية ستدفعها بعد سنتين.</p>
<p>Obsidian-first ليس لأن Obsidian مثالي. بل لأن ملفات markdown النصية الصِرفة صيغة ستُعمّر أطول من Obsidian نفسه. تماماً كما سيُعمّر ملف Excalidraw JSON العاري أطول من الإضافة نفسها. تماماً كما سيُعمّر سكربت Python هذا أطول من Miro.</p>]]></content:encoded>
      <pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate>
      <category>obsidian</category>
      <category>miro</category>
      <category>automation</category>
      <category>tools</category>
      <category>open-source</category>
    </item>
    <item>
      <title>CRAFT: طريقة لاستخدام الذكاء الاصطناعي دون أن تفقد حِرفيّتك</title>
      <link>https://oleksiimazurenko.dev/ar/blog/craft-method-ai-without-deskilling</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/craft-method-ai-without-deskilling</guid>
      <description>التشخيص بلا دواءٍ ليس إلا قلقًا. بعد التدوينة عن فقدان المهارة بسبب الذكاء الاصطناعي، إليك الوصفة: CRAFT — خمسة مبادئ (Challenge First, Review Adversarially, Ask Why Not What, Friction on Purpose, Test Your Understanding) تحت بديهيةٍ واحدة: الذكاء الاصطناعي يفعل ما أتقنته، وأنت تفعل ما لا تزال تتعلّمه. طريقة عملية سهلة الاستحضار لاستخدام الذكاء الاصطناعي كل يوم دون أن تفقد المهارة في صمت.</description>
      <content:encoded><![CDATA[<p>شخّصت تدوينتي السابقة المرض: أوقف الذكاء الاصطناعي بهدوءٍ حلقة التغذية الراجعة القسرية التي كان الواقع يشغّلها نيابةً عنك — خطأ المُصرِّف، والاختبار الفاشل، ونظرة المُراجِع الخاوية. لقد اختفى الضغط الذي كان <em>يجبرك</em> على التعلّم، وصار الطريق السهل يرتدي الآن قناع النجاح. هذه التدوينة هي الوصفة.</p>
<p>ليست قائمة نصائح مفكّكة — فتلك تتبخّر لحظة أن تشعر بالتعب. بل مرجعية لها اسمٌ تستحضرها تحت الضغط، تمامًا كما يعمل SOLID في التصميم الكائنيّ التوجّه. أسمّيها <strong>CRAFT</strong>: خمسة مبادئ، لكلٍّ منها حرفٌ واحد، تجتمع كلها تحت بديهيةٍ واحدة. الاسم نفسه هو جوهر الأمر — البرمجة تبقى حِرفة، والذكاء الاصطناعي مجرّد أداةٍ في يد من لا يزال قادرًا على أداء العمل بدونها.</p>
<h2>البديهية: الخط المتحرّك</h2>
<p>قبل الأحرف الخمسة تأتي قاعدةٌ واحدة تحكمها جميعًا — تمامًا كما يقبع مبدأ «المسؤولية الواحدة» في قلب SOLID:</p>
<blockquote>الذكاء الاصطناعي يفعل ما أتقنتَه بالفعل. أنت تفعل ما لا تزال تتعلّمه. ويتحرّك الخط مع نموّك.</blockquote>
<p>سلّم الجزء الذي أتقنته إلى الذكاء الاصطناعي فتمضي أسرع — وهذا مكسبٌ خالص. سلّم إليه الجزء الذي لا تزال تُكوّنه فلن تُكوّنه أبدًا؛ تدفع من مهارتك ثمنَ سرعةٍ لم تكن بحاجةٍ إليها أصلًا. وكل مبدأٍ في ما يلي ما هو إلا وسيلةٌ لتبقى صادقًا مع نفسك حول موضع ذلك الخط بالنسبة إليك <em>اليوم</em>.</p>
<h2>CRAFT — خمسة مبادئ، اختصارٌ واحد</h2>
<p>إذا قرأتَها من الأعلى إلى الأسفل، فالأحرف مسار عمل: تحدَّ نفسك أولًا، وراجِع ما يعطيك إياه الذكاء الاصطناعي بعينٍ عدائية، واسأل <em>لماذا</em> بدل <em>ماذا</em>، واصنع الاحتكاك عن قصد، وتحقّق من أنك تفهم فعلًا.</p>
<h3>C — Challenge First</h3>
<p><strong>المبدأ.</strong> قبل أن تفتح الذكاء الاصطناعي، حاوِل حلّ المشكلة بنفسك لمدة 5–15 دقيقة. يجب أن يكون أول حلٍّ على الصفحة <em>حلَّك أنت</em> — ولو كان أخرقَ. يأتي الذكاء الاصطناعي كي <strong>يفكّ حصارك</strong>، لا كي <strong>يبدأ</strong> عنك أبدًا.</p>
<p><strong>لماذا.</strong> الكفاح هو ما يبني النموذج الذهني — فالخلايا العصبية تشتعل حين تكون عالقًا، لا حين تقرأ إجابةً جاهزة. هذا هو بالضبط الاحتكاك الذي كان الواقع يفرضه مجانًا؛ أما الآن فعليك أن تجدوِلَه بنفسك.</p>
<h3>R — Review Adversarially</h3>
<p><strong>المبدأ.</strong> لا تدع الذكاء الاصطناعي يراجع كودك — بل <em>أنت</em> من يراجع كوده. اقرأ كل سطرٍ مُولَّد وأنت تصطاد الخلل، والحالة الحديّة، والثغرة الأمنية.</p>
<p><strong>لماذا.</strong> الحُكم الهندسي أكثر المهارات ديمومةً في هذه المهنة، والوحيد الذي تزداد قيمته كلما اشتدّت قوة النماذج. مراجعة الذكاء الاصطناعي تصقله وتمسك بالأخطاء الواثقة التي تعشق النماذج ارتكابها. أما قبول diff دون قراءته فيُقوّض كليهما دفعةً واحدة.</p>
<h3>A — Ask Why, Not What</h3>
<p><strong>المبدأ.</strong> استخدم الذكاء الاصطناعي معلّمًا خاصًّا، لا كاتبًا شبحيًّا. اطلب المنطق والبدائل والمقايضات — لا مجرّد المقطع الجاهز.</p>
<p><strong>لماذا.</strong> «ماذا» تمنحك سمكة؛ أما «لماذا» فتمنحك الصنّارة. «أعطني الكود» لا يترك وراءه شيئًا. أما «لماذا هذا النهج دون ذاك، وما الذي ينكسر عند إدخالٍ فارغ؟» فيتركك أذكى بدرجةٍ مما كنت.</p>
<h3>F — Friction on Purpose</h3>
<p><strong>المبدأ.</strong> لم يعد الواقع يرغمك على بذل الجهد، فأعِد المقاومة بنفسك. مارِس بانتظام <strong>تمارين بلا ذكاء اصطناعي</strong> — ابنِ شيئًا من الصفر دون مساعد. أعِد كتابة الكود بدل لصقه. أغلق الذكاء الاصطناعي وأعِد إنتاج ما علّمك إياه للتو.</p>
<p><strong>لماذا.</strong> لا تترسّخ المهارة إلا عبر صعوبةٍ مرغوبة. النسخ واللصق لا يترك أثرًا في الذاكرة؛ أما إعادة كتابة السطور نفسها ببطء فتترك أثرًا. يبدو الاحتكاك كضريبةٍ على السرعة — لكنه في الحقيقة ثمن الاحتفاظ بالمهارة أصلًا.</p>
<h3>T — Test Your Understanding</h3>
<p><strong>المبدأ.</strong> البوابة الكبرى سؤالٌ واحد: <em>هل أستطيع أن أشرح هذا، سطرًا سطرًا، لشخصٍ آخر؟</em> إن لم تستطع، فالكود ليس ملكك ولا تُطلقه. توقّع المُخرَج قبل تشغيله؛ اكسِره عن قصد ثم أصلحه من جديد.</p>
<p><strong>لماذا.</strong> «إنه يعمل» ليست «أنا أفهمه» — وهذه الفجوة هي وهم الكفاءة، وهي بالضبط حيث يختبئ فقدان المهارة. اختبار فهمك أنت أرخص تأمينٍ ضد أن تستيقظ يومًا عاجزًا عن العمل بلا الأداة.</p>
<h2>CRAFT في لمحة</h2>
<table><thead><tr><th>المبدأ</th><th>الفخّ</th><th>العادة</th></tr></thead><tbody><tr><td>&lt;strong&gt;C&lt;/strong&gt; · Challenge First</td><td>الكتابة للنموذج على صفحةٍ بيضاء</td><td>حاوِل بنفسك 5–15 دقيقة أولًا</td></tr><tr><td>&lt;strong&gt;R&lt;/strong&gt; · Review Adversarially</td><td>قبول diff دون قراءته</td><td>اصطد الخلل في مُخرَج الذكاء الاصطناعي</td></tr><tr><td>&lt;strong&gt;A&lt;/strong&gt; · Ask Why, Not What</td><td>«أعطني الكود فحسب»</td><td>«اشرح النهج والمقايضات»</td></tr><tr><td>&lt;strong&gt;F&lt;/strong&gt; · Friction on Purpose</td><td>نسخ ولصق كل شيء</td><td>تمارين بلا ذكاء اصطناعي؛ أعِد الكتابة ولا تلصق</td></tr><tr><td>&lt;strong&gt;T&lt;/strong&gt; · Test Your Understanding</td><td>إطلاق كودٍ لا تستطيع شرحه</td><td>«هل أستطيع شرح هذا سطرًا سطرًا؟»</td></tr></tbody></table>
<h2>افحص نفسك: أتنمو أم تتآكل؟</h2>
<p>لا تحتاج إلى دراسةٍ لتعرف أيَّ اتجاهٍ تسلك. <strong>أنت تتآكل إذا:</strong></p>
<ul><li>لم تعد تستطيع كتابة نسخةٍ أساسية من الشيء دون الذكاء الاصطناعي.</li><li>تلصق كودًا لن تقدر على شرحه لو سألك أحد.</li><li>يعتريك القلق لمجرّد التفكير في العمل والمساعد مُطفأ.</li><li>توقّفت عن قراءة مُخرَج الذكاء الاصطناعي وصرت تقبله بلا تمحيص.</li><li>لا تستطيع التنبؤ بما يفعله مقطعٌ ما قبل تشغيله.</li></ul>
<p><strong>أنت تنمو إذا:</strong></p>
<ul><li>يجعلك الذكاء الاصطناعي أسرع في أمورٍ تفهمها فعلًا.</li><li>تمسك بأخطاء النموذج بشكلٍ اعتيادي.</li><li>لا تزال قادرًا على العمل بدونه — أبطأ، لكنك قادر.</li><li>تستخدمه لتتعلّم مفهومًا جديدًا، ثم تعيد إنتاج ذلك المفهوم دون عون.</li><li>تقرّر بوعيٍ ما تُسلّمه وما تحتفظ به.</li></ul>
<h2>إن أخذت حرفًا واحدًا فقط</h2>
<p>خذ <strong>T</strong>. بوابة القابلية للشرح — <em>هل أستطيع شرح هذا سطرًا سطرًا؟</em> — أبسط عادةٍ في المنهج كله، وهي وحدها تمسك بنحو 80% من التآكل. وكل ما تبقّى في CRAFT إنما يسهّل عليك اجتياز تلك البوابة بصدق.</p>
<h2>الخلاصة</h2>
<p>لم يتغيّر شيءٌ جوهري في التعلّم. الفهم الواعي كان دائمًا هو المهم؛ والحشو كان دائمًا بديلًا رديئًا. ما تغيّر هو أن البيئة توقّفت عن فرض ذلك الفرق نيابةً عنك. وما CRAFT إلا وسيلةٌ لتفرضه على نفسك.</p>
<blockquote>قواعد اللعبة هي نفسها. غير أن اللعبة صارت أكثر غدرًا — فالطريق السهل يرتدي الآن قناع النجاح.</blockquote>
<p>إن لم تكن قد قرأت التشخيص الكامن خلف هذا كله، فابدأ بالتدوينة السابقة عن الذكاء الاصطناعي وحلقة التغذية الراجعة المفقودة. تلك كانت المرض؛ وهذه هي الدواء.</p>]]></content:encoded>
      <pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate>
      <category>ai</category>
      <category>learning</category>
      <category>career</category>
      <category>methodology</category>
    </item>
    <item>
      <title>الصمت الذي أطفأه الـ AI</title>
      <link>https://oleksiimazurenko.dev/ar/blog/ai-deskilling-and-feedback-loop</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/ai-deskilling-and-feedback-loop</guid>
      <description>هل ما زال يستحق تعلّم البرمجة ما دام الـ AI موجودًا؟ الجواب الصاخب خاطئ من الجانبين. مع قاعدة أدلة 2025–2026 (Nature وAnthropic وAI &amp; Society) فالسؤال ليس &quot;AI أم لا&quot;، بل أيّ المهارات تسلّمها للـ AI وأيّها تحتفظ بها لنفسك — وأمر واحد تغيّر فعلًا: كان الواقع يُجبرك على التعلّم عبر الاحتكاك، فأطفأ الـ AI هذا الضغط بهدوء.</description>
      <content:encoded><![CDATA[<p>نقاش «هل ما زال يستحق تعلّم البرمجة ما دام الـ AI موجودًا» يبدأ الآن من الصفر كل أسبوعين. ظللت أفكّر في هذا شهورًا — أولًا بوصفه سؤالًا نظريًا، ثم بوصفه سؤالًا يحدّد ما تقوله لمبتدئ يسألك «من أين أبدأ». ظهرت قاعدة أدلة، فلم يعد الأمر رأيًا في مواجهة رأي. وأقل ما يُقال إن طرفَي التطرّف الصاخبَين كليهما على خطأ.</p>
<p>المسألة ليست «أتعلّم أم لا». المسألة هي <strong>ما الذي تسلّمه</strong> بالضبط للـ AI وما الذي تحتفظ به لنفسك. وهي كذلك حول أمر واحد غيّره الـ AI فعلًا — لا في اتجاه «انقلب كل شيء»، بل في اتجاه أدق بكثير: <strong>لقد اختفت حلقة التغذية الراجعة القسرية التي كان الواقع يديرها عنك من قبل</strong>.</p>
<h2>طرفان متطرّفان — كلاهما أحادي النظرة</h2>
<p>أولًا، لنُخلِ الطاولة من المعسكرين الصاخبَين اللذين يظهران في كل نقاش:</p>
<ul><li><strong>المعسكر الأول:</strong> «لم يعد هناك داعٍ لتعلّم اللغات — الـ AI سيكتبها». الأعلى صوتًا بينهم هم باعة معسكرات تدريبية «صِرْ مبرمجًا في 30 يومًا مع الـ AI».</li><li><strong>المعسكر الثاني:</strong> «الجميع سيصير أغبى، وخلال بضع سنوات لن يبقى مختصّون». الأعلى صوتًا هم ذوو الخبرة الذين يخافون قليلًا من عجزهم أمام سرعة ظهور المبتدئين بمظهر منتِج.</li></ul>
<p>ثنائية زائفة. في جوهره لم يتغيّر شيء أساسي: كما هو الحال دائمًا، ما يُقدَّر هو <strong>الفهم الواعي</strong> لا الحفظ الأجوف. الـ AI يوسّع فقط الفجوة بين من يفهمون حقًا وبقية الناس — ويفعل ذلك أسرع من ذي قبل.</p>
<h2>ماذا تقول قاعدة الأدلة</h2>
<p>لم يعد الأمر «يبدو لي». فقد ظهرت قياسات ملموسة خلال 2025–2026:</p>
<ul><li><strong>Nature (يونيو 2026)، «Is AI ruining our skills?»</strong> — الاعتماد المفرط على الـ AI يُدهور المهارات. ليس عند المبرمجين وحدهم — بل عند الأطباء أيضًا.</li><li><strong>تجربة Anthropic العشوائية المضبوطة</strong> على 52 مهندسًا: مساعد الـ AI يُنتج <strong>تكوينًا للمهارة أقل بنحو 17%</strong>. النقطة الدقيقة أن ما يتضرر ليس سرعة إنجاز المهمة، بل <em>تكوين</em> المهارة. أي أنك على المدى القصير أسرع، وعلى المدى البعيد أسوأ.</li><li><strong>AI &amp; Society</strong>: فقدان المهارة (deskilling) مشكلة <strong>بنيوية</strong> لا حلّ جاهز لها. ليست «الـ AI سيّئ»، بل «الأداة غيّرت بيئة التعلّم».</li><li><strong>TechCrunch</strong>: المهندسون يرفضون العمل بلا AI لأنهم لم يعودوا قادرين على ذلك من دونه. العنوان: «قد يرتدّ هذا عليهم».</li></ul>
<p>خلاصة الخبير K. Crowston (تنقلها Nature) — الأمر ليس «AI أو لا»، بل: <strong>قرِّر بوعي أيّ المهارات تحتفظ بها لنفسك وأيّها تسلّمه</strong>. هذه هي الجملة التي يقوم عليها بقية المقال.</p>
<h2>ما الذي تسلّمه وما الذي تحتفظ به</h2>
<p>على نحوٍ ملموس — هنا يمرّ الخط الفاصل:</p>
<table><thead><tr><th>يمكن تسليمه للـ AI</th><th>يجب الاحتفاظ به لنفسك</th></tr></thead><tbody><tr><td>استرجاع صياغة نحوية أو توقيع دالة في واجهة برمجية (API)</td><td>&lt;strong&gt;قراءة الكود وفهمه&lt;/strong&gt;</td></tr><tr><td>كتابة boilerplate من الصفر</td><td>&lt;strong&gt;الحُكم&lt;/strong&gt;: أصحيح؟ آمِن؟ أفيه خلل؟</td></tr><tr><td>العثور على مثال بسرعة</td><td>الأساسيات: كيف تعمل الأنظمة والبيانات والشبكة</td></tr></tbody></table>
<p><strong>لا يمكنك مراجعة ما لا تستطيع قراءته.</strong> لذا فإن «الفهم دون معرفة اللغة» وهمٌ؛ إنه ثقة عمياء بالـ AI. وهذه بالضبط نقطة الانتقال بين «مختصّ يستخدم الـ AI» و«مشغِّل AI». الأول يقود الـ AI؛ والثاني يقوده الـ AI.</p>
<p>تمييز آخر يخلط الناس بينه دائمًا: <strong>فقدان المهارة</strong> ≠ <strong>عدم تكوّن المهارة أصلًا</strong>. المهندس ذو الخبرة الذي بدأ فجأة يتّكئ على الـ AI يبقى قويًا — فلديه بالفعل نموذج للعالم. أما المبتدئ الذي يجلس على الـ AI بنسبة 100% من أول يوم فلن يبني هذا النموذج أبدًا. هاتان مشكلتان مختلفتان بعواقب مختلفة.</p>
<h2>كيف تتعلّم بالطريقة الصحيحة — ولماذا لم يتغيّر شيء في الجوهر</h2>
<p>لا يزال عليك أن تتعلّم — لكن <strong>بالطريقة الصحيحة</strong>. لا حفظًا أجوف للمصطلحات أو الأوامر، بل فهمًا للمنطق وإحساسًا بكيفية عمل الأمر ككل. القيمة ليست في «التذكّر»، بل في «الفهم → القدرة على التطبيق → القدرة على الحُكم».</p>
<p>والأهم: <strong>هذا هو ذاته ما كان عليه الحال دائمًا، قبل الـ AI بزمن طويل.</strong> ما كان يُقدَّر دائمًا هم من يفهمون فعلًا كمختصّين، لا من يحفظون بلا فهم. الـ AI لم يقلب شيئًا — إنه فقط جعل الطريق الكسول أسهل (صار بوسعك أن «تُنجز» دون فهم)، ولهذا فالفجوة بين هذين النوعين تتّسع لا غير.</p>
<blockquote>من تعلّم بوعي سيبقى كذلك. ومن كان كسولًا سيبقى كسولًا. الـ AI لم يخلق هذا الانقسام — إنه فقط سلّط عليه الضوء.</blockquote>
<h3>اختبر ذلك على أمثلة بسيطة</h3>
<ul><li><strong>الرياضيات:</strong> حفظ جدول الضرب عن ظهر قلب ليس بعدُ «معرفة بالرياضيات». الآلة الحاسبة قتلت الضرب الطويل بالورقة، لكنها لم تقتل الحاجة إلى فهم ما هو الضرب ومتى يُطبَّق.</li><li><strong>الهواتف:</strong> لم تعد تحفظ الأرقام في رأسك — الهاتف يتذكّرها. لكن عليك أن تعرف بمن تتصل ولماذا. سلّمتَ الذاكرة — واحتفظتَ بالحُكم.</li><li><strong>GPS:</strong> لا تحفظ المسارات في رأسك. لكن عليك أن تفهم إلى أين تتجه — وأن تنتبه حين يقودك الـ GPS في التفافٍ سخيف.</li></ul>
<p>في الحالات الثلاث أخذت الآلة <strong>الذاكرة الآلية</strong>، لا <strong>الفهم والحُكم</strong> — وهكذا كان الأمر دائمًا. الـ AI هو التكرار التالي للشيء نفسه، لكن بنطاق أوسع مما يمكنك تفويضه الآن.</p>
<h3>الصياغة النحوية ناتج جانبي لا هدف</h3>
<p>هذا لا يعني «لا داعي لمعرفة الصياغة النحوية». ستكتسب الصياغة على أي حال — لكن بوصفها <strong>ناتجًا جانبيًا للفهم والممارسة، لا هدفًا</strong>. أنت لم تتعلّم <code>%d</code> ببطاقات تذكّر — بل فهمتَ منطق سلاسل التنسيق (format strings)، فرسخ من تلقاء نفسه. هكذا يعمل التعلّم السليم: تفهم → تطبّق → تستقر الصياغة وحدها. الـ AI يسرّع هذه الحلقة فقط إن استخدمته على نحو صحيح.</p>
<h2>لكن أمرًا واحدًا تغيّر فعلًا: حلقة التغذية الراجعة القسرية اختفت</h2>
<p>«لم يتغيّر شيء» — هذا صحيح بنحو 80%. أمرٌ واحد مهم تغيّر فعلًا، وهو ليس بالأمر الهيّن.</p>
<p>كانت البيئة سابقًا <strong>تُجبِرك</strong> على التعلّم. لم يكن بوسعك إطلاق كود يعمل دون أن تفهمه بالقدر الذي يجعله يشتغل على الأقل. المُصرِّف والخلل والانهيار — كانوا <strong>معلّمين</strong> يجرّون الجميع بالقوة إلى حدٍّ أدنى. كان الصراع إلزاميًا — وكان يبني الفهم حتى لدى غير المجتهدين كثيرًا.</p>
<p>أما الآن فهذا المعلّم <strong>مُطفأ</strong>: بوسعك الحصول على نتيجة تعمل <strong>دون أن تفهم ولو مرة</strong> كيف تعمل. اختفت العقوبة الفورية على عدم الفهم. ومن هنا تنشأ نتيجة تُغفلها مقولة «لم يتغيّر شيء»:</p>
<blockquote>الفجوة لا تتّسع فقط بين أنماط ثابتة — بل إن البيئة الجديدة تسحب إليها حتى من كانوا سيتعلّمون سابقًا، لأن الطريق الكسول صار يقدّم نتيجة تعمل بلا عقوبة فورية.</blockquote>
<p>لذا فمقولة «الكسول يبقى كسولًا» أضيق من اللازم. الأمر يتعلق بـ <strong>البيئة والعادات</strong> أكثر من الطبع: معظم الناس يتفاعلون مع الاحتكاك والتغذية الراجعة، وليسوا كسالى «بالفطرة». أزِل الاحتكاك — وحتى غير الكسالى ينزلقون. الخطر ليس على الكسالى وحدهم.</p>
<p>والأدقّ من ذلك — <strong>وهم الكفاءة</strong>. الـ AI يرقّع الثغرات بسلاسة شديدة حتى إنك لا تصطدم بجدار جهلك ولا تلاحظ وجوده أصلًا. تشعر أنك فاهم لأن «كل شيء يعمل» (أثر <em>metacognitive laziness</em>). لذا صار <strong>البقاء مجتهدًا أصعب</strong>: صار يتطلب منك أن تخلق لنفسك احتكاكًا بوعي — أن تُطفئ الـ AI أحيانًا، أن تحاول الشرح أو الكتابة دونه، أن تتحقق «أأفهم حقًا، أم أظن فقط أنني أفهم». كان الواقع يفعل هذا عنك سابقًا — والآن صار على عاتقك.</p>
<blockquote>القواعد نفسها، لكن اللعبة صارت أكثر مكرًا — الطريق السهل صار يتنكّر الآن في هيئة نجاح.</blockquote>
<h2>لمن يفهم بالفعل — اللعبة مختلفة</h2>
<p>كل ما سبق يخصّ <strong>تكوين</strong> المهارة من الصفر. أما إن كنت متكوّنًا بالفعل — فالصورة عندك مقلوبة، ويجدر قول ذلك صراحةً، وإلا قُرِئ المقال على أنه «الـ AI سيّئ»، وليست تلك هي الفكرة.</p>
<p>المهندس ذو الخبرة يعرف حدوده. يرى ما سلّمه له الـ AI → وفي نصف ثانية يميّز أين هو «غير سليم»، وأين هو «نعم، لكن لا لهذه الحالة»، وأين هو «نعم وجيّد هنا والآن». لديه كاشف مدمج «هذا لا تبدو رائحته صحيحة» مُجمَّع من سنوات من الحوادث الحقيقية. الـ AI لا يُطفئ هذا الكاشف — بل على العكس يغذّيه أسرع: أمثلة أكثر، حالات أكثر، أنماط (patterns) أكثر يتعيّن ترشيحها بسرعة.</p>
<p>لذا فإن الـ AI نفسه الذي يهدّد بتحويل المبتدئ إلى مشغِّل يحوّل الخبير إلى خبير مُعزَّز. boilerplate في 30 ثانية بدل 3 دقائق. استكشاف مكتبة جديدة — في أمسية، لا في أسبوع. نقل وحدة من Python إلى TypeScript — قابل للتنفيذ فعلًا، لا «يومًا ما لاحقًا». هذا نيترو يُضَخّ في محرّك مُجمَّع سلفًا.</p>
<p>وأمر دقيق آخر: <strong>لا يستطيع الـ AI أن يكذب على الخبير دون أن يُلحَظ</strong>. الـ AI يكذب عليه بالقدر نفسه الذي يكذب به على المبتدئ — لكنه يلتقط ذلك قبل أن يُتمّ قراءة الجملة. ما هو للمبتدئ فخّ وهم الكفاءة، هو للخبير مجرد بلاغ خلل آخر في السيل اليومي.</p>
<blockquote>لمن استثمر وقته بالفعل، الـ AI مُعزِّز. ولمن لم يستثمره بعد — هو سبب إضافي لألّا يفعل. لا وسط بين هاتين الحالتين — وهذا ما يجعل «متى تعلّمت بالضبط» من أهم العوامل في المسار المهني.</blockquote>
<h2>خطر منظومي: «السلّم المكسور»</h2>
<p>الخبراء يأتون من المبتدئين. فإن التهم الـ AI عمل المبتدئين ولم يبنِ أحد الأساس → فبعد 5–10 سنوات <strong>لن يبقى من يُنشَّأ ليصبح خبيرًا</strong>. خطر «جيل ضائع» من المختصّين حقيقي — وليس هذا تهويلًا، بل مشكلة أنابيب بسيطة: أُغلق أنبوب الدخول ولم يُغلق أنبوب الخروج.</p>
<p>لماذا لن يصل هذا إلى الصفر (لكنه سيؤلم): توازن «الجميع توقّفوا عن التعلّم» غير مستقر. الندرة تجعل من تعلّموا مفرطي القيمة → فيعود حافز التعلّم. الشركات التي تُنشّئ المختصّين بوعي تحصل على خندق تنافسي (moat). لكن الانتقال مؤلم: المشغّلون سيسقطون عند أول حادث جدّي، حين يكذب الـ AI بوضوح وإقناع ولا يكون حول أحدٌ يميّز الصدق من الكذب.</p>
<h2>موقفي</h2>
<ul><li>تعلّم اللغة <strong>إلزامي</strong> — بهدف <em>القراءة والحُكم</em>، لا الحفظ. لا يمكنك اختصار الطريق إلى طلاقة القراءة؛ الـ AI لا يختصره، بل يجعله اختياريًا بهدوء لا غير.</li><li>وما دامت الأغلبية تسلّم «الفهم» للـ AI — فإن <strong>التعلّم الآن نافذة فرصة</strong>: تصير مختصًّا نادرًا يقود الـ AI، لا من ينافسه. إنها نافذة قصيرة الأمد — فالسوق سيتكيّف عاجلًا أو آجلًا. وما دامت مفتوحة — فمن الخطأ ألّا تنتهزها.</li><li>النمط الصحّي: استخدام الـ AI لتمضي أسرع <strong>وتتعلّم أعمق</strong>، لا لتتجنّب التعلّم. النمط المضادّ هو «أنجِز المهمة كلها بدلًا مني». أما النمط الصحيح فهو أن تكتب بنفسك، ويشرح الـ AI «لماذا»، وتفهم نسخته بنفسك قبل أن تقبلها.</li><li>مفارقة للطريق: كي تستخدم الـ AI بفعالية، عليك أن تعرف ما يكفي <strong>لكي لا تحتاجه في الأمور البسيطة</strong>. من يعتمد على الـ AI في البسيط سيخدعه الـ AI في المعقّد دون أن تكون لديه فرصة لملاحظة ذلك.</li></ul>
<h2>المصادر</h2>
<ul><li>Nature — <em>Is AI ruining our skills? Early results are in</em> (يونيو 2026): <a href="https://www.nature.com/articles/d41586-026-01947-1">nature.com/articles/d41586-026-01947-1</a></li><li>InfoQ — Anthropic study, ~17% skill mastery drop: <a href="https://www.infoq.com/news/2026/02/ai-coding-skill-formation/">infoq.com/news/2026/02/ai-coding-skill-formation</a></li><li>TechCrunch — Coders are refusing to work without AI: <a href="https://techcrunch.com/2026/05/29/coders-are-refusing-to-work-without-ai-and-that-could-come-back-to-bite-them/">techcrunch.com/2026/05/29/coders-refusing-ai</a></li><li>AI &amp; Society — <em>AI deskilling is a structural problem</em>: <a href="https://link.springer.com/article/10.1007/s00146-025-02686-z">link.springer.com/article/10.1007/s00146-025-02686-z</a></li></ul>]]></content:encoded>
      <pubDate>Mon, 22 Jun 2026 00:00:00 GMT</pubDate>
      <category>ai</category>
      <category>learning</category>
      <category>career</category>
      <category>engineering</category>
    </item>
    <item>
      <title>Claude Code متعدّد الملفات الشخصية، v2: بنية XDG نظيفة</title>
      <link>https://oleksiimazurenko.dev/ar/blog/claude-profiles-clean-architecture</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/claude-profiles-clean-architecture</guid>
      <description>بعد شهر على الأسلوب القديم القائم على الـ alias، أعدت بناء إعداد Claude Code متعدّد الملفات الشخصية لديّ إلى بنية متوافقة مع XDG تحت ~/.config/claude-profiles/. لم يكن السبب الحقيقي هو الترتيب — بل اكتشاف أن ~/.claude.json ملف منفصل في جذر الـ home، تعيش فيه بصمت خوادم MCP المُضافة عبر --scope user.</description>
      <content:encoded><![CDATA[<p>قبل شهر نشرت <a href="/ar/blog/multiple-claude-accounts-one-device">تدوينة عن تشغيل حسابَي Claude Code جنبًا إلى جنب على جهاز واحد</a> — شخصي وعمل، عبر <code>CLAUDE_CONFIG_DIR</code> وalias في الـ shell. نجح الأسلوب. فعل كل شيء ما كان يُفترض أن يفعله.</p>
<p>لكن بعد شهر من الاستخدام الحقيقي اصطدمت ببضع مشكلات جوهرية لم تغطّها تلك التدوينة. أكبرها — خاصية غريبة خفية في Claude Code تعثّرت بها مصادفةً، حين أضفت Gmail MCP فـ «اختفى» من ملفي بعد خمس دقائق. اليوم أعدت بناء الأمر كله إلى بنية جديدة — متوافقة مع XDG، مع symlink ومنتقٍ تفاعلي للملفات عبر <code>gum</code>. هذه هي v2 — تطوّر نابع من تجربة حقيقية، لا تحسين نظري.</p>
<h2>ما بدأ يزعجني</h2>
<p>أربعة أمور. الثلاثة الأولى عن الترتيب والوضوح والتوسّع. والرابع هو المطبّ المعماري الحقيقي الذي لم أره فورًا. سأمرّ عليها بالترتيب، لأن الرابع هو الذي فرض إعادة البناء في النهاية.</p>
<h3>١. مجلدان منفصلان في $HOME — فوضى</h3>
<p><code>~/.claude</code> و<code>~/.claude-promova</code> — مجلدا نقطة متجاوران في جذر $HOME. تقول مواصفة XDG Base Directory إن الإعدادات مكانها <code>~/.config/</code>. مجلدات النقطة المبعثرة مباشرةً في الـ home نمط مضاد يحوّل $HOME تدريجيًا إلى درج للنفايات. تجميلي، صحيح، لكنه كان يضايقني في كل مرة أرى فيها <code>ls -la ~</code>.</p>
<h3>٢. لا تأكيد بصري للملف النشط</h3>
<p>أشغّل <code>claude</code> ولا أدري أيُّ ملفٍ نشط — إلى أن أنفّذ <code>claude config list</code> داخل الجلسة. وإن نسيت أي طرفية شغّلت أين، اضطررت للتحقّق. أمر صغير، لكن مع تشغيل الشخصي والعمل بالتوازي عبر تبويبَين يتراكم.</p>
<h3>٣. الـ aliases لا تتوسّع</h3>
<p>ملفان — <code>claude</code> و<code>claude-promova</code> — لا بأس. أضف ثالثًا (عميل مستقل) — تحتاج alias ثالثًا. ورابعًا — رابعًا. بعد ستة أشهر لن أتذكّر أيّ aliases أنشأت فعلًا.</p>
<h3>٤. الفخ الخفي في ~/.claude.json</h3>
<p>وهذا هو السبب الحقيقي لإعادة البناء. لدى Claude Code <strong>مكانان مختلفان</strong> للإعداد، والتوثيق لا يصرخ بذلك: <code>~/.claude/</code> — المجلد الذي يحوي <code>projects/</code> و<code>sessions/</code> و<code>hooks/</code> و<code>skills/</code>. وبشكل منفصل — <code>~/.claude.json</code>، ملف يقع مباشرةً في جذر $HOME، وفيه يعيش <code>oauthAccount</code> و<code>mcpServers</code> وتاريخ <code>projects</code> و<code>skillUsage</code> ونحو 40 حقلًا آخر من الحالة الحيّة الفعلية.</p>
<p>الأمر <code>claude mcp add --scope user</code> يكتب في ذلك <code>~/.claude.json</code> في جذر الـ home، <strong>لا</strong> في <code>~/.claude/.claude.json</code> ولا في مجلد الملف. لم أكن أعلم ذلك. إلى أن اصطدمت به في يوم من الأيام.</p>
<h2>الاكتشاف: لماذا «اختفى» Gmail MCP</h2>
<p>هذا الصباح كنت أُعِدّ Gmail MCP في Claude Code. إعداد قياسي: مشروع Google Cloud، بيانات اعتماد OAuth، <code>claude mcp add gmail --scope user -- npx -y @gongrzhe/server-gmail-autoauth-mcp</code>. كل شيء على ما يرام. أعدت تشغيل الجلسة — يعمل، أقرأ البريد وأردّ على الرسائل. بعد ساعة بدأنا نعيد هيكلة الـ aliases إلى دالة مع منتقي ملفات عبر <code>gum</code>، ثم نقلنا كل شيء إلى XDG. نفّذت <code>mv ~/.claude → ~/.config/claude-profiles/personal</code>، وأعدت تشغيل CC، واخترت personal من القائمة. وفي الجلسة الجديدة فتحت <code>/mcp</code>:</p>
<pre><code>figma            (failed)
playwright-test
claude.ai Notion</code></pre>
<p>لا gmail. لا vaultforge. ثلاثة خوادم فقط، وأحدها حتى فشل. وأنا لتوّي أضفت Gmail. وفي الوقت نفسه كانت جلسة في طرفية أخرى (ملف العمل) تعرض Gmail وVaultforge دون أي مشكلة.</p>
<p>بدأت أنبش فوجدت أن لديّ <strong>ثلاثة</strong> ملفات مختلفة على جهازي باسم <code>.claude.json</code>:</p>
<table><thead><tr><th>Path</th><th>Size</th><th>mcpServers</th></tr></thead><tbody><tr><td>&lt;code&gt;~/.claude.json&lt;/code&gt; (home root)</td><td>113 KB</td><td>figma, gmail, vaultforge</td></tr><tr><td>&lt;code&gt;~/.config/claude-profiles/personal/.claude.json&lt;/code&gt;</td><td>29 KB</td><td>figma, playwright-test</td></tr><tr><td>&lt;code&gt;~/.config/claude-profiles/promova/.claude.json&lt;/code&gt;</td><td>40 KB</td><td>figma, playwright-test, vaultforge</td></tr></tbody></table>
<p>ها هو. هذا هو المطبّ المعماري:</p>
<ol><li><code>claude mcp add --scope user</code> يكتب دائمًا في <code>~/.claude.json</code> في جذر الـ home، بغضّ النظر عن <code>CLAUDE_CONFIG_DIR</code></li><li>حين يكون <code>CLAUDE_CONFIG_DIR</code> معيّنًا، يقرأ Claude Code الملف <code>$CLAUDE_CONFIG_DIR/.claude.json</code> — أي الملف داخل ملف التعريف</li><li>مدخلات «MCP بنطاق المستخدم» ومدخلات «MCP ملف التعريف» تعيش في <strong>ملفين مختلفين</strong> بالاسم نفسه — ويسهل الخلط بينهما</li></ol>
<p>في حالتي كان <code>~/.claude.json</code> (جذر الـ home، 113 كيلوبايت) هو الحالة الحيّة الراهنة — مع gmail وvaultforge وجلسة OAuth، كل شيء. أما <code>~/.config/claude-profiles/personal/.claude.json</code> (29 كيلوبايت) فاتّضح أنه لقطة قديمة كانت قابعة في الـ <code>~/.claude/</code> القديم من قبل — ربما كتبتها نسخة CC أقدم، وربما مكوّن إضافي. أظهر <code>jq -r 'keys[]'</code> على الملفين أن نسخة جذر الـ home فيها 41 مفتاحًا فريدًا غائبًا عن اللقطة.</p>
<p>وهذه المفاتيح الـ 41 ليست نفايات. إنها الحالة الفعلية لـ Claude Code:</p>
<ul><li><code>skillUsage</code> — إحصاءات استخدام المهارات</li><li><code>githubRepoPaths</code> — مستودعات مخزّنة للتنقّل في المشاريع</li><li><code>cachedGrowthBookFeatures</code> + <code>cachedStatsigGates</code> — أعلام الميزات (بدونها يجلب CC نسخًا جديدة عند كل إقلاع)</li><li><code>hasShownOpus45Notice</code> و<code>hasShownOpus46Notice</code> و<code>hasShownS1MWelcomeV2</code> — أعلام واجهة (بدونها تعود النوافذ المنبثقة عند التشغيل التالي)</li><li><code>lastPlanModeUse</code> و<code>feedbackSurveyState</code> و<code>installMethod</code> — حالة التهيئة وتجربة الاستخدام</li></ul>
<p>إن نفّذت فقط <code>mv ~/.claude ~/.config/claude-profiles/personal</code> دون دمج، تفقد كل ذلك. تعود نوافذ الترحيب، ويعمل بحث githubRepoPaths من جديد، وترجع كل مطالبات الاستبيان. وهو ما كدت أفعله.</p>
<h2>البنية الجديدة</h2>
<p>كل شيء يعيش تحت مجلد أب واحد في <code>~/.config/</code>، كما يريد XDG. كل ملف تعريف قائم بذاته — يحمل حالته الكاملة بما فيها <code>.claude.json</code> الخاص به. ويبقى <code>~/.claude</code> بوصفه symlink إلى الملف الشخصي من أجل التوافق مع الخلف.</p>
<pre><code>~/.config/claude-profiles/
├── personal/
│   ├── .claude.json          ← full live state + MCP list (113 KB)
│   ├── projects/, sessions/, hooks/, skills/, ...
│   └── CLAUDE.md
└── promova/
    ├── .claude.json          ← work state + work MCPs (41 KB)
    └── projects/, sessions/, agents/, skills/, ...

~/.claude  →  symlink to ~/.config/claude-profiles/personal/</code></pre>
<p>لا <code>.claude.json</code> في جذر $HOME. كل ملف تعريف مجلد معزول منفصل يحوي كل شيء: مجلدات <code>projects</code>/<code>sessions</code> والملف نفسه بخوادم MCP ورموز OAuth. مصدر واحد للحقيقة لكل ملف تعريف.</p>
<h3>لماذا يشير الـ symlink إلى personal</h3>
<p>كل ما يثبّت مسار <code>~/.claude/</code> بشكل صلب — السكربتات القديمة، والمكوّنات الإضافية، وإضافات Claude Code لبيئات التطوير، وإعدادات شريط الحالة مثل <code>claude-powerline.json</code> — يستمر في العمل دون تغيير. يُحلّ الـ symlink إلى الملف الشخصي. وإن شغّلت بالخطأ <code>command claude</code> (متجاوزًا الدالة الغلاف)، فإنك تصل أيضًا إلى personal عبر البحث بالمسار الافتراضي. يصبح personal هو «الافتراضي الهادئ» كما كان، لكنه الآن يعيش فيزيائيًا في موقع XDG.</p>
<h3>منتقٍ تفاعلي عند التشغيل — دالة + gum</h3>
<p>بدل الـ aliases — دالة <code>claude()</code> في <code>~/.zshrc</code> تعرض قائمة بأسهم لوحة المفاتيح عبر <code>gum</code> (أداة TUI من Charm). تعترض الدالة نداء <code>claude</code> على مستوى الـ shell، وتدعك تختار ملفًا، ثم تشغّل <code>command claude</code> بـ <code>CLAUDE_CONFIG_DIR</code> المطابق. <code>command</code> مهمة هنا — فهي تتجاوز الدالة الغلاف وتستدعي الملف الثنائي الحقيقي.</p>
<pre><code># brew install gum

# Claude Code: profile picker on launch
claude() {
  local profile
  profile=$(gum choose \
    --header &quot;Claude profile:&quot; \
    --cursor &quot;▸ &quot; \
    --selected.foreground 212 \
    --cursor.foreground 212 \
    --header.foreground 244 \
    &quot;personal&quot; &quot;promova&quot;) || return
  case &quot;$profile&quot; in
    personal) CLAUDE_CONFIG_DIR=&quot;$HOME/.config/claude-profiles/personal&quot; command claude &quot;$@&quot; ;;
    promova)  CLAUDE_CONFIG_DIR=&quot;$HOME/.config/claude-profiles/promova&quot;  command claude &quot;$@&quot; ;;
  esac
}</code></pre>
<p>هكذا يبدو عند التشغيل:</p>
<pre><code>Claude profile:
▸ personal
  promova</code></pre>
<p>↑↓ للتنقّل، Enter للاختيار، Esc للإلغاء (فلا يبدأ Claude ببساطة). الملف مرئي دائمًا — يستحيل أن يفوتك.</p>
<h2>من الصفر (لا يوجد v1 لترحيله)</h2>
<p>إذا كنت قد شغّلت من قبل <a href="/ar/blog/multiple-claude-accounts-one-device">حيلة المجلدين القديمة مع الـ alias</a>، فانتقل مباشرةً إلى سكربت الترحيل في الأسفل. هذا القسم هو البناء النظيف من الصفر — جهاز جديد، أو مجرد <code>~/.claude</code> الافتراضي الناتج عن الاستخدام العادي. حدّد الشكل أولًا: حساب واحد بعدّة ملفات تعريف (لعزل المشاريع ومجموعات MCP الخاصة بها — وهي الحالة الشائعة)، أو حسابات مختلفة فعلًا لكل ملف تعريف (شخصي مقابل عمل).</p>
<h3>١. رتّب المجلدات</h3>
<pre><code># prerequisites
brew install gum jq

# 1. XDG parent for all profiles
mkdir -p ~/.config/claude-profiles

# 2. fold an existing default install in as your first profile (no-ops on a fresh machine)
if [ -d ~/.claude ] &amp;&amp; [ ! -L ~/.claude ]; then
  mv ~/.claude ~/.config/claude-profiles/personal
  [ -f ~/.claude.json ] &amp;&amp; mv ~/.claude.json ~/.config/claude-profiles/personal/.claude.json
fi

# 3. one directory = one profile
mkdir -p ~/.config/claude-profiles/personal ~/.config/claude-profiles/work

# 4. these files will hold OAuth tokens — lock them down
chmod 600 ~/.config/claude-profiles/*/.claude.json 2&gt;/dev/null</code></pre>
<p>مجلد واحد لكل ملف تعريف تحت أب XDG واحد. إذا كنت قد استخدمت Claude Code من قبل، فإن المقتطف يدمج <code>~/.claude</code> الحالي بوصفه <code>personal</code>، ويحمل معه الحالة الحيّة من <code>~/.claude.json</code> في جذر الـ home؛ وعلى جهاز جديد تمامًا لا تفعل هذه الأسطر شيئًا. كل ما تبقّى هو مجلدات فارغة تملأ نفسها عند أول تشغيل — و<code>chmod 600</code> مهم، لأن هذه الملفات ينتهي بها الأمر بحمل رموز OAuth.</p>
<h3>٢. وثّق كل ملف تعريف</h3>
<pre><code># launch each profile once and authenticate it
CLAUDE_CONFIG_DIR=~/.config/claude-profiles/work claude
#   → run /login in the session, sign in, done.
# Same account for every profile? One /login is enough — macOS keeps it in the
# shared Keychain, so the other profiles are already authenticated.</code></pre>
<p>شغّل كل ملف تعريف مرة واحدة بـ <code>CLAUDE_CONFIG_DIR</code> الخاص به ونفّذ <code>/login</code>. التفصيلة الأساسية: إذا كان كل ملف تعريف يستخدم <strong>الحساب نفسه</strong>، فأنت تسجّل الدخول مرة واحدة فقط — يحفظ macOS بيانات الاعتماد في مدخل Keychain مشترك واحد، وبذلك يُوثَّق كل ملف تعريف آخر تلقائيًا.</p>
<p><strong>حسابات مختلفة لكل ملف تعريف</strong> هو المسار الأصعب: مدخل Keychain المشترك ذاك يحمل تسجيل دخول واحدًا، لذا فإن إدخال حساب ثانٍ إلى ملف تعريف آخر قد يطمس الأول. إذا كانت هذه حالتك، فاتّبع الشرح المخصّص في <a href="/ar/blog/multiple-claude-accounts-one-device">تشغيل حسابَي Claude على جهاز واحد</a>، وتحقّق من عزل بيانات الاعتماد على نسختك من Claude Code قبل أن تعتمد عليه.</p>
<h3>٣. الـ Symlink والمنتقي</h3>
<pre><code># personal becomes the default target of ~/.claude (back-compat)
ln -s ~/.config/claude-profiles/personal ~/.claude

# add the claude() gum picker from the architecture section to ~/.zshrc, then:
source ~/.zshrc
claude        # ← now shows the profile menu on every launch</code></pre>
<p>وجّه <code>~/.claude</code> إلى ملف التعريف الشخصي كي يستمر كل ما يثبّت هذا المسار بشكل صلب في العمل، ثم ضَع منتقي <code>gum</code> الخاص بدالة <code>claude()</code> من قسم البنية أعلاه داخل <code>~/.zshrc</code>. بعد إعادة التحميل يعرض <code>claude</code> قائمة الملفات عند كل تشغيل — الحالة النهائية نفسها كإعداد الترحيل، لكن مبنية من جديد.</p>
<h3>٤. نسّق MCP كل ملف تعريف</h3>
<p>أخيرًا، امنح كل ملف تعريف الأدوات التي ينبغي أن يراها فقط: عيّن <code>disableClaudeAiConnectors: true</code> في <code>settings.json</code> الخاص بذلك الملف، وأضف ما يحتاجه عبر <code>claude mcp add</code>، تمامًا كما في قسم الموصّلات أعلاه. هذا ما يحوّل عشرة ملفات تعريف قابلة للتبديل إلى عشرة مصمّمة لغرض محدّد.</p>
<h2>سكربت الترحيل</h2>
<p>لكل من يقرأ هذا ويريد <a href="/ar/blog/multiple-claude-accounts-one-device">الانتقال عن المخطّط القديم</a>. أكثر الخطوات حرجًا هي الثانية: تدمج <code>~/.claude.json</code> من جذر الـ home مع ما هو موجود أصلًا في الملف الشخصي، جامعةً قائمتَي <code>mcpServers</code>. بدون هذه الخطوة يفقد الملف كلًّا من MCP الخاصة به وكامل حالته الحيّة.</p>
<pre><code># 1. Move existing dotfolders into a new XDG-style parent.
#    APFS mv is an inode rename — safe even if claude --resume is open in
#    another terminal, file descriptors stay alive on the same inode.
mkdir -p ~/.config/claude-profiles
mv ~/.claude          ~/.config/claude-profiles/personal
mv ~/.claude-promova  ~/.config/claude-profiles/promova   # rename as needed

# 2. CRITICAL: merge ~/.claude.json (the live state — likely 100+ KB) into
#    the personal profile, unioning mcpServers so no MCP is dropped.
jq -s &apos;.[0] * {mcpServers: (.[0].mcpServers + .[1].mcpServers)}&apos; \
  ~/.claude.json \
  ~/.config/claude-profiles/personal/.claude.json \
  &gt; /tmp/personal-merged.json
mv /tmp/personal-merged.json ~/.config/claude-profiles/personal/.claude.json

# 3. Fix permissions — jq+mv inherits umask (likely 644), but this file
#    holds OAuth tokens. Tighten to 600 immediately.
chmod 600 ~/.config/claude-profiles/personal/.claude.json

# 4. Back up the orphaned home-root file (delete once verified)
mv ~/.claude.json ~/.claude.json.migrated.bak

# 5. Symlink for backward compatibility (statusline configs, IDE plugins,
#    anything that hardcodes ~/.claude/ keeps working unchanged)
ln -s ~/.config/claude-profiles/personal ~/.claude</code></pre>
<p>الخطوة الثالثة عن الأذونات أمر مهم بذاته. <code>jq | mv</code> ينشئ ملفًا بـ umask 644 (قابل للقراءة من الجميع). وفيه رموز OAuth. <code>chmod 600</code> فور الدمج إلزامي.</p>
<p>بعد الترحيل — أغلق كل جلسات Claude النشطة وأعد فتحها، وأعد تحميل الـ shell (<code>source ~/.zshrc</code> أو طرفية جديدة)، وشغّل <code>claude</code>، واختر ملفًا، وتحقّق عبر <code>claude mcp list</code> من وجود كل MCP في مكانها. إن كان كل شيء جيدًا — احذف <code>~/.claude.json.migrated.bak</code>. وإن كان ثمة خلل — التراجع بسيط: <code>mv ~/.claude.json.migrated.bak ~/.claude.json</code> وإزالة الـ symlink.</p>
<h2>إضافة: موصّلات MCP لكل ملف تعريف (حين تشاركها مؤسستك)</h2>
<p>تفتح البنية أعلاه محورًا ثانيًا للعزل — وقد صار الأمر الذي أستخدمه فعليًا أكثر من غيره. إذا كان حساب Claude الخاص بك تابعًا لمؤسسة، فبإمكان المؤسسة توزيع مجموعة مشتركة من <strong>موصّلات MCP البعيدة</strong> (Figma، متتبّع تذاكر، خلفية للمقاييس، بوابة قاعدة بيانات، نظام CMS…). يجلب Claude Code تلقائيًا <strong>كل</strong> موصّل فعّلته المؤسسة داخل <strong>كل</strong> جلسة، لأنها مرتبطة بالحساب لا بالمجلد الذي شغّلت منه.</p>
<p>مع موصّلين أو ثلاثة يكون هذا مقبولًا. أما مع عشرة مشاريع واثني عشر موصّلًا مؤسسيًا فيتوقّف عن كونه مقبولًا: كل جلسة تحمّل أدوات لا شأن لها بها. هذا ضجيج في قائمة أدوات النموذج، وهو هدر للسياق، والجزء الذي يؤذي فعلًا — أنه يتيح لجلسة أن تصل إلى نظام يخصّ مشروعًا آخر. موصّل المشروع A ينبغي ألا يكون موجودًا أصلًا وأنت تعمل على المشروع B.</p>
<h3>عطّل الجلب التلقائي ثم نسّق</h3>
<p>الرافعة إعداد واحد فقط، <code>disableClaudeAiConnectors</code> (Claude Code v2.1.182+). يعيش في أي نطاق للإعدادات — بما في ذلك <code>settings.json</code> الخاص بملف التعريف نفسه — لذا فهو <strong>لكل <code>CLAUDE_CONFIG_DIR</code></strong>. عيّنه إلى <code>true</code> فيتوقّف ذلك الملف عن جلب موصّلات المؤسسة تلقائيًا تمامًا:</p>
<pre><code>{
  &quot;disableClaudeAiConnectors&quot;: true
}</code></pre>
<p>ثم أعد إضافة الموصّلات التي يحتاجها ذلك الملف فعلًا فقط. الخوادم التي تضيفها صراحةً <strong>لا</strong> يمسّها العَلَم، فتُحمَّل بشكل طبيعي — والنتيجة مجموعة فرعية منسّقة، ملفًا تلو الآخر:</p>
<pre><code># add back only what this profile uses — writes to the profile&apos;s own .claude.json
claude mcp add --scope user --transport http figma  https://&lt;your-figma-connector&gt;/mcp
claude mcp add --scope user --transport http linear https://&lt;your-linear-connector&gt;/mcp</code></pre>
<table><thead><tr><th>ملف التعريف</th><th>ما يحمّله</th></tr></thead><tbody><tr><td>&lt;code&gt;frontend&lt;/code&gt;</td><td>Figma, Linear, Sentry</td></tr><tr><td>&lt;code&gt;backend&lt;/code&gt;</td><td>Postgres, Grafana, Linear</td></tr><tr><td>&lt;code&gt;content&lt;/code&gt;</td><td>Notion, Figma, Linear</td></tr></tbody></table>
<p>الحساب نفسه، وكتالوج المؤسسة نفسه — لكن كل ملف تعريف يرى شريحته فقط، إضافةً إلى ما تختار تكراره من الموصّلات المشتركة (متتبّع التذاكر عادةً يستحق مكانه في كل مكان).</p>
<h3>اختبرته قبل أن أثق به</h3>
<p>لم أرد أن آخذ كلام التوثيق كمسلّمة، لذا أجريت اختبار A/B نظيفًا على ملف تعريف واحد — الحساب نفسه، ورمز OAuth نفسه، والفرق الوحيد هو ذلك الـ <code>settings.json</code>:</p>
<pre><code>profile WITHOUT the flag
  /mcp → Figma, Linear, Grafana, Postgres, Notion, Sentry, …   (entire org catalog)

same account, profile WITH  disableClaudeAiConnectors: true
  /mcp → (nothing auto-loaded)

…then  claude mcp add  Figma + Linear
  /mcp → Figma, Linear                                         (exactly the curated subset)</code></pre>
<p>يفعل بالضبط ما يدّعيه. تحفّظان يستحقان المعرفة قبل أن تعتمد عليه:</p>
<ul><li><strong>إعادة إضافة موصّل OAuth تحتاج تسجيل دخول تفاعلي واحدًا.</strong> الموصّلات التي توثّق بمفتاح داخل الـ URL تعود تلقائيًا بالكامل عبر <code>claude mcp add</code>. أما الموصّلات خلف OAuth فتطلب توثيقًا واحدًا عبر المتصفح في أول مرة تستخدمها فيها ضمن ذلك الملف.</li><li><strong><code>claude mcp list</code> لا يعرض موصّلات المؤسسة إطلاقًا</strong> — بل الخوادم المعرّفة محليًا والمُضافة صراحةً فقط. أما المجلوبة تلقائيًا فلا تظهر إلا داخل جلسة حيّة عبر <code>/mcp</code>. لا تفزع حين يبدو <code>mcp list</code> خاليًا منها.</li></ul>
<h2>ما الذي تحصل عليه</h2>
<ul><li>مجلد أب واحد بدل مجلدَي نقطة في $HOME — متوافق مع XDG</li><li>يحفظ الـ symlink التوافق مع كل ما يثبّت <code>~/.claude/</code> بشكل صلب</li><li>كل ملف تعريف قائم بذاته — حالته الكاملة وMCP الخاصة به تعيش في <code>.claude.json</code> الخاص به</li><li>مصدر واحد للحقيقة لكل ملف تعريف — لا مزيد من إعداد يتيم في جذر الـ home ينحرف بصمت عن إعداد الملف</li><li>البيانات الحساسة (oauthAccount، الرموز) مضمون لها أذونات 600</li><li>تأكيد بصري للملف النشط عند كل تشغيل — يستحيل أن تنسى أيّ ملف نشط</li><li>إضافة ملف تعريف ثالث = إضافة سطر واحد إلى <code>case</code> داخل الدالة، لا استنساخ alias جديد وتذكّر اسمه</li></ul>
<h2>أين يقصّر</h2>
<p>أريد أن أكون صريحًا. هذا ليس تحسينًا مجانيًا — رافقته بضع مقايضات، ومن الجدير معرفتها سلفًا.</p>
<ul><li><strong><code>gum</code> اعتمادية إضافية</strong> (<code>brew install gum</code>، نحو 13 ميغابايت). إن كنت لا تريد تثبيتها إطلاقًا — ارجع إلى <code>select</code> في zsh أو <code>read</code> بسيط. يعمل، لكنه لا يبدو بالجمال نفسه ولا تنقّل بالأسهم فيه.</li><li><strong>ضغطة Enter واحدة عند كل تشغيل.</strong> لمن يشعلون <code>claude</code> عشرات المرات يوميًا، قد يصير مزعجًا. البديل في الأسفل (direnv).</li><li><strong>الـ symlink <code>~/.claude → personal</code> يجعل personal هو الافتراضي.</strong> إن أردت ملف العمل افتراضيًا، عليك إعادة توجيه الـ symlink (<code>ln -sf</code>). ليس صعبًا، لكنه ليس «انسه ولا شيء ينكسر».</li><li><strong>قد ينكسر الـ symlink نظريًا</strong> إن أعادت أداةٌ ما كتابة <code>~/.claude.json</code> ذرّيًا عبر نمط temp+rename (write-file-atomic). عمليًا لا يفعل Claude Code ذلك بنفسه، لكن إن ثبّتّ مكوّنات طرف ثالث — تحقّق.</li><li><strong>إن كان لديك حسابات Anthropic مختلفة على ملفات مختلفة بخطط مختلفة</strong> — فقد يكون هناك تأخّر دون الثانية بعد التبديل بينما يزامن Claude Code حالة OAuth. في استخدامي غير ملحوظ، لكنه ليس صفرًا.</li></ul>
<h2>بدائل نظرت فيها</h2>
<p><strong>direnv</strong> — يعيّن <code>CLAUDE_CONFIG_DIR</code> تلقائيًا بناءً على <code>.envrc</code> في جذر كل مشروع. صفر تفاعل، صفر نقرات. العيب: عليك وضع <code>.envrc</code> في كل جذر عمل، وإن شغّلت <code>claude</code> في مجلد غير معروف، تحصل على الملف الافتراضي (وقد لا يكون الذي تريده). لمن يعيشون داخل مجموعة صغيرة من جذور العمل ويريدون ألا ينقروا شيئًا أبدًا، فإن direnv أفضل فعلًا.</p>
<p><strong>التبديل القائم على الـ symlink</strong> (ملف نشط واحد عبر إعادة توجيه symlink الـ <code>~/.claude</code>) نظرت فيه أيضًا وأسقطته فورًا. لا يمكنك أن تفتح طرفيتين بملفين مختلفين في آنٍ واحد — فـ «الحالي» العام واحد فقط. بالنسبة لي هذا مانع قاطع.</p>
<h2>الخلاصة</h2>
<p>v2 ليست مجرد تجربة استخدام أفضل فوق v1. إنها إقرار بأن لدى Claude Code سلوكًا معماريًا شاذًّا (<code>~/.claude.json</code> بوصفه ملفًا منفصلًا في جذر الـ home، تكتبه أوامر <code>--scope user</code> بغضّ النظر عن <code>CLAUDE_CONFIG_DIR</code>) عليك أن تحسب حسابها إن أردت عزلًا حقيقيًا بين الملفات. <a href="/ar/blog/multiple-claude-accounts-one-device">الأسلوب الأول (<code>~/.claude</code> + <code>~/.claude-promova</code> + alias)</a> نجح بنسبة 80%، لكن الـ 20% الباقية ظهرت في صورة انحراف صامت للحالة بين الملفات. الآن صار محسوبًا. إن كنت تبدأ للتوّ — ابدأ بـ v2 مباشرةً. وإن كنت أصلًا على v1 — سكربت الترحيل في الأعلى، والنقل يستغرق خمس دقائق ولا يكسر شيئًا (دمج <code>jq</code> بالذات هو الخطوة التي تنقذك من فقدان الحالة).</p>]]></content:encoded>
      <pubDate>Sat, 13 Jun 2026 00:00:00 GMT</pubDate>
      <category>claude-code</category>
      <category>productivity</category>
      <category>cli</category>
      <category>devtools</category>
    </item>
    <item>
      <title>كيف تكتب وكلاء Claude Code لا يكذبون عليك</title>
      <link>https://oleksiimazurenko.dev/ar/blog/writing-specialized-agents</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/writing-specialized-agents</guid>
      <description>قاعدتان لبناء خطوط معالجة موثوقة لوكلاء Claude Code: وكيل واحد لكل تخصص، وأوامر shell بدل المُوجِّهات في كل موضع تكون فيه الإجابة كمّية.</description>
      <content:encoded><![CDATA[<p>طلبت من Claude Code أن <em>ينفّذ هذا التصميم ويتحقق من مطابقته لماكيت Figma</em>. فعاد قائلاً: <em>تمّ. كل الأقسام مطابقة، والمسافات صحيحة، والألوان سليمة.</em> فتحتَ الصفحة. نصف المسافات كان خاطئاً. حالة الـ hover لم تكن موجودة أصلاً. الأزرار كانت مزاحة بدرجة لونية. لم يكذب النموذج بسوء نية، بل توقّع أنك تريد أن تسمع كلمة <em>تمّ التحقق</em>، فأنتج تلك السلسلة من الرموز بالضبط. لم تكن هناك خطوة تحقق. ولم يكن بالإمكان أن تكون هناك واحدة أصلاً، فالتحقق يتطلب مقارنة بالحقيقة المرجعية، ووكيل واحد داخل سياق واحد لا يملك أي وسيلة للخروج من إجابته والتدقيق فيها.</p>
<p>قاعدتان حوّلتا لديّ سير عمل مليئة بالهلوسة إلى خطوط معالجة موثوقة: <strong>وكيل واحد، تخصص واحد</strong>، و<strong>كل ما يمكن تنفيذه كأمر shell يجب تنفيذه كأمر shell</strong>. هذه ليست نظرية. هذا ما أفعله يومياً مع Claude Code، وهذه هي الأنماط التي تُحدث فرقاً حقيقياً.</p>
<h2>لماذا يكذب الوكلاء العامّون</h2>
<p>نماذج LLM أدوات تتنبأ بالرمز التالي. حين يطلب المُوجِّه دورين — <em>ابنِ X</em> و<em>تحقق من X</em> — ينهي النموذج الدور الأول، ثم يتنبأ بالشكل الذي سيبدو عليه ناتج الدور الثاني، دون أن ينفّذه فعلاً. التحقق الذاتي ضعيف بنيوياً: السياق نفسه، النموذج نفسه، النقاط العمياء نفسها. <em>نجاح</em> التحقق مرتبط بـ<em>نجاح</em> البناء، فهما يفشلان معاً.</p>
<p>لا يعرف النموذج أنه يكذب. من منظوره، فإن سرد جملة <em>تحققتُ من كل شيء بعناية</em> امتداد متماسك لكونه كتب الشيفرة. وهذا هو السبب نفسه الذي يجعل مُوجِّهات مثل <em>هل أنت متأكد؟</em> لا تلتقط الهلوسات: النموذج على القدر نفسه من الثقة في المرور الثاني. الثقة ليست مرتبطة بالصحة، بل بمدى معقولية وقع الجملة التالية.</p>
<p>الحل ليس <em>أوامر أفضل</em>. عبارات مثل <em>كن حذراً</em> و<em>دقّق مرة أخرى</em> و<em>لا تهلوس</em> لا تفعل شيئاً. الحل بنيوي: خصّص الوكيل بحيث يعجز عملياً عن التظاهر، ومرّر العمل الكمّي عبر الـ shell كي تأتي الإجابة من حالة حقيقية، لا من احتمالية الرموز.</p>
<h2>القاعدة 1: وكيل واحد، تخصص واحد</h2>
<p>قسّم العمل إلى وكلاء منفصلين بسياقات منفصلة. لكل وكيل مسؤولية واحدة ومجموعة أدوات ضيّقة. يتحول سير العمل بأكمله إلى سباق تتابع بدل وكيل واحد يدور في حلقات:</p>
<ul><li><strong>وكيل البناء (Builder):</strong> يأخذ المواصفة، ويكتب الشيفرة. هذه مهمته كلها. لديه <code>Read</code> و<code>Edit</code> و<code>Write</code> و<code>Bash</code>.</li><li><strong>وكيل المراجعة (Reviewer):</strong> يأخذ المواصفة مع الـ diff، ويفحص معايير القبول. سياق جديد. لا علم له بـ<em>كيف</em> كُتبت الشيفرة، بل بما نتج فقط. لديه <code>Bash</code> و<code>Read</code> و<code>Grep</code> و<code>Glob</code> — ولا أدوات كتابة على الإطلاق.</li><li><strong>وكيل التحليلات (Analytics):</strong> يجيب عن أسئلة البيانات ببناء الاستعلامات وتشغيلها. <code>Bash</code> فقط. لا يمكنه بلوغ الإجابة دون تشغيل أمر حقيقي.</li><li><strong>المنسّق (Orchestrator):</strong> الجلسة الرئيسية التي تُرسل كل وكيل بدوره ولا تطلب من وكيل قط أن يؤدي عمل وكيل آخر.</li></ul>
<p>مثال ملموس: تنفيذ واجهة مستخدم مع فحص بصري مقابل ماكيت Figma. يكتب وكيل البناء المكونات ويُودع الـ diff. ثم يستدعي المنسّق وكيل المراجعة مزوّداً إياه برابط التصميم والـ diff ومعايير قبول صريحة. يشغّل وكيل المراجعة أداة Playwright، ويلتقط لقطات الشاشة، ويقارنها بالمرجع، ويعيد <code>PASS</code> أو <code>FAIL</code> مع مسارات لقطات الشاشة الفعلية وفروق البكسل. لا يقترب وكيل البناء من خطوة التحقق أبداً، وهذا بالضبط ما يجعل التحقق حقيقياً.</p>
<p>النمط المضاد هو الوكيل العملاق: مُوجِّه واحد يقول <em>ابنِ هذه الواجهة وتأكد من مطابقتها للماكيت</em>. أضمن لك أنه سيبلّغ بأن كل شيء مطابق. لن يكون كذلك. سردية <em>لقد تحققت</em> ليست إلا أرجح سلسلة رموز تأتي بعد <em>لقد بنيته</em>.</p>
<h2>القاعدة 2: الـ shell بدل المُوجِّه، دائماً</h2>
<p>كل ما هو كمّي، وكل ما يمسّ حالة حقيقية، وكل ما قد تكون فيه الإجابة خاطئة بشكل يبدو صحيحاً — مرّره عبر <code>sh</code>. مهمة الوكيل أن يبني الأمر ويشغّله، ثم يقرأ ناتجه. الوكيل ليس مصدر الحقيقة. ناتج الـ shell هو المصدر.</p>
<ul><li><strong>العدّ:</strong> <code>wc -l logs.txt</code> حقيقة. أما <em>هناك نحو 47 سطر سجل</em> صادرة عن نموذج فهي هلوسة.</li><li><strong>التحليلات:</strong> <code>psql -c "SELECT count(*) FROM events WHERE created_at &gt; now() - interval '30 days'"</code>. وليس <em>قدّر الحجم</em>.</li><li><strong>الاختبارات:</strong> <code>pnpm test --reporter=json | jq '.numFailedTests'</code>. وليس <em>لخّص ما فشل</em>.</li><li><strong>حالة Git:</strong> <code>git rev-list --count main..HEAD</code>، <code>git diff --stat</code>. وليس <em>عُدّ الإيداعات</em> أو <em>صِف التغييرات</em>.</li></ul>
<p>حين تستوعب هذا، تبدأ بملاحظة كل موضع كان الوكيل فيه على وشك اختلاق رقم. <em>يبدو أن هناك قرابة 200 سجل...</em> — لا. شغّل <code>SELECT count(*)</code>. <em>معظم الاختبارات تمر...</em> — لا. شغّل حزمة الاختبارات، وحلّل الـ JSON. النموذج بارع في بناء الأمر. لكنه غير موثوق في أن يكون هو الأمر نفسه.</p>
<h2>أنماط فشل واجهتها فعلاً</h2>
<p>هذه ليست حالات افتراضية. كلّ منها كلّفني وقتاً حقيقياً قبل أن أغيّر النمط:</p>
<ul><li><strong>تحقق وهمي.</strong> قال الوكيل <em>فحصتُ الأقسام الـ 14 كلها مقابل الماكيت</em>. لم يفتح الماكيت. لم يلتقط لقطة شاشة. كان الفحص خطوة مهلوَسة في السرد.</li><li><strong>أرقام خاطئة بثقة.</strong> طلبت المستخدمين النشطين شهرياً من بيانات التحليلات. فحصلت على رقم مخطئ بنحو 3 أضعاف. استوفى النموذج القيمة من صفوف عيّنة بدل تشغيل الاستعلام الفعلي.</li><li><strong>تغييرات ملفات مختلقة.</strong> قال الوكيل <em>حدّثتُ <code>config/feature-flags.json</code></em>. لم يفعل. كان قد نوى ذلك فحسب. وكان <code>git diff</code> فارغاً.</li><li><strong>تشغيلات اختبار زائفة.</strong> <em>كل الاختبارات تمر.</em> لم يُنفَّذ أي اختبار. لم يستدعِ الوكيل مشغّل الاختبارات قط، بل تنبأ بالشكل الذي كان سيبدو عليه ناتج المشغّل.</li></ul>
<p>الأربعة كلها تُحلّ بالقاعدتين نفسيهما: قسّم الوكيل، وادفع إلى الـ shell. لا يملك وكيل المراجعة <code>Write</code>، فلا يمكنه تزييف تعديل الملفات. ولا يملك وكيل التحليلات سوى <code>Bash</code>، فلا يمكنه إعادة رقم لم يأتِ من استعلام. الاستحالة البنيوية تغلب النوايا الحسنة في كل مرة.</p>
<h2>كيف تُبنى هذه في Claude Code</h2>
<p>يدعم Claude Code الوكلاء الفرعيين المعرّفين في <code>.claude/agents/*.md</code>. يعلن كل ملف وكيل عن اسم ووصف ومجموعة أدوات مسموحة ومُوجِّه النظام. يستدعيهم المنسّق (جلستك الرئيسية) عبر أداة <code>Agent</code>. وهذا نوع التعريف الذي أستخدمه لوكيل المراجعة — قصير، ضيّق، وعاجز عملياً عن كتابة الشيفرة:</p>
<pre><code>---
name: reviewer
description: Reviews a diff against acceptance criteria. Cannot edit code.
tools: Bash, Read, Grep, Glob
---

You are a strict code reviewer. You receive:
- A diff (already produced by the builder)
- Acceptance criteria

Your job:
1. Run the build, the tests, the linter — through Bash.
2. Read the changed files directly.
3. Compare the actual behavior to the criteria.
4. Report PASS or FAIL with concrete evidence (command output, file excerpts).

You must NOT:
- Trust the builder&apos;s summary
- Assume anything was verified just because it was claimed
- Mark something PASS without running the actual check</code></pre>
<p>لاحظ مجموعة الأدوات: <code>Bash, Read, Grep, Glob</code>. لا <code>Write</code>، ولا <code>Edit</code>، ولا <code>Agent</code>. يستطيع وكيل المراجعة تشغيل الأوامر وقراءة الملفات والبحث عن الأنماط — لا غير. وإن حاول تمرير diff مهلوَس على أنه <em>مُتحقَّق منه</em>، فإن شكل استدعاءات أدواته يفضح الأمر: لم تكن هناك أي فحوصات حقيقية. يمكنك تدقيق استدعاءات الأدوات ورؤية ما جرى فحصه بالضبط.</p>
<p>نمط التنسيق: تستدعي الجلسة الرئيسية وكيل البناء ← تنتظر ← تشغّل هي نفسها <code>git diff</code> لالتقاط التغيير الفعلي ← تستدعي وكيل المراجعة بالمواصفة والـ diff ← تقرأ الحكم. لا تطلب الجلسة الرئيسية قط من وكيل واحد أن يؤدي الدورين. قيود الأدوات أقوى من تعليمات المُوجِّه: <em>لا تزيّف التحقق</em> أمنية. وأما عدم امتلاك <code>Write</code> فحقيقة.</p>
<h2>أنماط مضادة يجب التخلص منها</h2>
<p>أشياء أراها في المُوجِّهات ولا تفعل شيئاً — أو أسوأ، تمنح إحساساً زائفاً بالأمان:</p>
<ul><li><strong><em>كن حذراً ودقّق في عملك مرة أخرى.</em></strong> لا يولّد أي سلوك إضافي. النموذج ينتج أصلاً ما يبدو كعمل دقيق.</li><li><strong><em>تأكد من أنك تحققت فعلاً.</em></strong> كلمة <em>فعلاً</em> لا تضيف دلالة يستطيع النموذج التصرف بناءً عليها. سيدّعي <em>فعلاً</em> أنه تحقق.</li><li><strong><em>لا تهلوس.</em></strong> نكتة رائجة في هندسة المُوجِّهات. الهلوسة ليست مفتاحاً يستطيع النموذج إطفاءه.</li><li><strong>الثقة بالوكيل في الأرقام <em>الصغيرة</em>.</strong> في الأرقام الصغيرة تحديداً يكذب بأشد ثقة. لا يوجد حدّ أدنى للصدق.</li><li><strong>إضافة قواعد أكثر إلى المُوجِّه <em>لفرض</em> الصدق.</strong> الإصلاحات البنيوية (التقسيم + الـ shell) تغلب تعديلات الأوامر في كل مرة. إن كانت قاعدة تحتاج إلى إنفاذ، فرمّزها في صلاحيات الأدوات، لا في لغة بشرية.</li></ul>
<p>إن كانت استراتيجيتك لالتقاط الهلوسات هي صياغة أشد توكيداً، فليست لديك استراتيجية. لديك أمنية.</p>
<h2>النموذج الذهني</h2>
<p>الوكيل ليس زميلاً. إنه دالة: <code>prompt → tokens</code>. الدالة بارعة في كتابة الشيفرة وسيئة في التأمل الذاتي حول ما إذا فعلت الصواب. عامِل ادّعاءاته عن عمله بوصفها فرضية. الـ diff، ورمز الخروج، ولقطة الشاشة، وعدد الصفوف — تلك هي الأدلة. أما ملخّص نهاية الدور فهو السطح الأكثر عرضة للكذب في المنظومة كلها.</p>
<p>التخصص هو تأمينك ضد انحراف السرد. الـ shell هو مصدر الحقيقة الوحيد لديك. وكيل البناء يكتب. وكيل المراجعة يفحص. والـ Bash يقرّر.</p>
<h2>الخلاصة</h2>
<p>إن حفظت شيئاً واحداً: لا تدع وكيلاً واحداً يُنتج ناتجه ويحكم عليه في آنٍ، ولا تدع أي وكيل يجيب عن سؤال كمّي دون تشغيل أمر. كل ما عدا ذلك متفرّع عن هاتين القاعدتين. اضبط صلاحيات الأدوات بحزم، ودقّق استدعاءات الأدوات بدل الملخّصات، عندها ينكمش سطح الهلوسة من <em>كل مكان</em> إلى <em>بضعة مواضع محددة تعرف مسبقاً أن تنظر إليها</em>.</p>]]></content:encoded>
      <pubDate>Fri, 29 May 2026 00:00:00 GMT</pubDate>
      <category>claude-code</category>
      <category>agents</category>
      <category>ai</category>
      <category>prompt-engineering</category>
      <category>automation</category>
    </item>
    <item>
      <title>تشغيل عدة حسابات Claude Code على جهاز واحد</title>
      <link>https://oleksiimazurenko.dev/ar/blog/multiple-claude-accounts-one-device</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/multiple-claude-accounts-one-device</guid>
      <description>كيفية استخدام حسابين (أو أكثر) من Claude Code جنبًا إلى جنب — شخصي وحساب الشركة — بعزل كامل باستخدام متغيّر بيئة واحد.</description>
      <content:encoded><![CDATA[<p>أستخدم Claude Code يوميًا لمشاريعي الشخصية ولعملي في الشركة على حدٍّ سواء. المشكلة أن هذين حسابان مختلفان تمامًا، بجلسات OAuth مختلفة، وتعليمات CLAUDE.md مختلفة، وخوادم MCP مختلفة، وذاكرة مشاريع مختلفة. وإليك كيف أُشغّلهما جنبًا إلى جنب على جهاز واحد بسطر واحد من alias في الـ shell.</p>
<h2>المشكلة</h2>
<p>يُخزّن Claude Code كل شيء في <code>~/.claude</code> افتراضيًا — رمز OAuth، وسجل المحادثات، وملف CLAUDE.md العام، وذاكرة المشاريع، وإعدادات خوادم MCP، والتفضيلات. وعندما يكون لديك حسابان، فأنت بحاجة إلى عالمين منفصلين تمامًا:</p>
<ul><li><strong>الحساب الشخصي:</strong> اشتراك Max/Pro الخاص بك، وملف CLAUDE.md شخصي بتفضيلاتك، وخوادم MCP الخاصة بك (Obsidian، أدوات شخصية)</li><li><strong>حساب الشركة:</strong> خطة تديرها الشركة، وملف CLAUDE.md للعمل بتعليمات تكامل Jira/Slack، وخوادم MCP الخاصة بالشركة</li><li><strong>جلسات OAuth مختلفة:</strong> لا يمكنك تسجيل الدخول إلى حسابين في دليل الإعدادات نفسه</li><li><strong>ذاكرة مشاريع مختلفة:</strong> لا تريد أن يتسرّب سياق مشاريع العمل إلى الجلسات الشخصية والعكس صحيح</li></ul>
<p>تسجيل الخروج ثم الدخول من جديد في كل مرة تُبدّل فيها السياق ليس خيارًا مطروحًا. فأنت تفقد حالة الجلسة، والأمر مُرهق ببساطة.</p>
<h2>الحل: CLAUDE_CONFIG_DIR</h2>
<p>يحترم Claude Code متغيّر بيئة واحدًا: <code>CLAUDE_CONFIG_DIR</code>. اضبطه على أي مسار، وسيستخدم Claude ذلك الدليل بدلًا من <code>~/.claude</code> لكل شيء — المصادقة والسجل والإعدادات والذاكرة، كل ذلك. يستغرق الإعداد بأكمله 60 ثانية.</p>
<h3>الخطوة 1: أنشئ دليل إعدادات ثانيًا</h3>
<p>اختر اسمًا يناسب حالة استخدامك:</p>
<pre><code>mkdir ~/.claude-work</code></pre>
<p>هذا كل شيء. سيملأ Claude الدليل بالبنية اللازمة عند أول تشغيل.</p>
<h3>الخطوة 2: صادِق على الحساب الثاني</h3>
<p>شغّل Claude مرة واحدة بدليل الإعدادات الجديد لتشغيل عملية تسجيل الدخول عبر OAuth:</p>
<pre><code>CLAUDE_CONFIG_DIR=~/.claude-work claude</code></pre>
<p>سيفتح هذا متصفحك. سجّل الدخول بحساب الشركة. يُخزَّن رمز OAuth في <code>~/.claude-work</code> — منفصلًا تمامًا عن جلستك الشخصية في <code>~/.claude</code>.</p>
<h3>الخطوة 3: أضِف alias في الـ shell</h3>
<p>أضِف هذا إلى إعدادات الـ shell لديك حتى لا تضطر إلى تذكّر المتغيّر:</p>
<pre><code>alias claude-work=&apos;CLAUDE_CONFIG_DIR=~/.claude-work claude&apos;</code></pre>
<p>أعِد تحميل الـ shell:</p>
<pre><code>source ~/.zshrc</code></pre>
<h2>ما الذي تحصل عليه</h2>
<p>الآن لديك بيئتان معزولتان تمامًا من Claude:</p>
<ul><li><strong><code>claude</code></strong> — يُشغَّل بحسابك الشخصي، وملف CLAUDE.md الشخصي، والذاكرة الشخصية</li><li><strong><code>claude-work</code></strong> — يُشغَّل بحساب الشركة، وملف CLAUDE.md الخاص بالعمل، وذاكرة منفصلة</li><li><strong>سجل معزول:</strong> محادثات العمل تبقى في العمل، والشخصية تبقى شخصية</li><li><strong>خوادم MCP منفصلة:</strong> خادم MCP لـ Obsidian vault الشخصي لا يظهر في جلسات العمل</li><li><strong>إعدادات مستقلة:</strong> أدوات مسموح بها مختلفة، ومستويات أذونات مختلفة، وتفضيلات نماذج مختلفة لكل حساب</li></ul>
<h2>كيف يعمل من الداخل</h2>
<p>دليل الإعدادات هو المصدر الوحيد للحقيقة بشأن حالة Claude Code. وإليك ما يوجد داخل كل واحد منها:</p>
<pre><code>~/.claude/              ← personal account
├── CLAUDE.md           ← personal global instructions
├── projects/           ← personal project memory
├── settings.json       ← personal permissions &amp; MCP servers
└── ...                 ← OAuth session, history, cache

~/.claude-work/         ← corporate account
├── CLAUDE.md           ← company-specific instructions (Jira, Slack, etc.)
├── projects/           ← separate project memory
├── settings.json       ← different MCP servers, permissions
└── ...                 ← separate OAuth session</code></pre>
<p>عندما تُشغّل <code>claude-work</code>، يقرأ Claude كل شيء من <code>~/.claude-work</code>. وهو لا يدرك أصلًا وجود <code>~/.claude</code>. النسختان مستقلتان تمامًا — بل يمكنك تشغيلهما في آنٍ واحد في علامتي تبويب مختلفتين في الطرفية.</p>
<h2>التوسّع إلى N من الحسابات</h2>
<p>يمتد هذا النمط إلى أي عدد من الحسابات. مستقل لديه عدة عملاء؟ أضِف المزيد من الـ aliases:</p>
<pre><code># Personal (default — no alias needed)
# Just run: claude

# Corporate
alias claude-work=&apos;CLAUDE_CONFIG_DIR=~/.claude-work claude&apos;

# Freelance client
alias claude-client=&apos;CLAUDE_CONFIG_DIR=~/.claude-client claude&apos;</code></pre>
<p>يحصل كل alias على دليل إعدادات خاص به، وجلسة OAuth خاصة به، وملف CLAUDE.md خاص به بتعليمات مخصّصة للعميل. يمكن أن يكون لديك ملف CLAUDE.md للعميل A يعرف أعراف قاعدة الكود ومشروع Jira الخاص به، وآخر مختلف تمامًا للعميل B.</p>
<h2>نصائح عملية</h2>
<ul><li><strong>سمِّ أدلّتك بوضوح:</strong> <code>~/.claude-work</code>، <code>~/.claude-clientname</code> — ستشكر نفسك عندما يصبح لديك ثلاثة أو أربعة منها</li><li><strong>اكتب ملف CLAUDE.md مخصّصًا لكل واحد:</strong> يمكن أن يتضمّن ملف العمل تعليمات خاصة بالشركة (كيفية إنشاء تذاكر Jira، وقنوات Slack المرجعية، وإجراءات النشر). ويبقى ملفك الشخصي مقتضبًا.</li><li><strong>خوادم MCP مختلفة لكل حساب:</strong> اضبط أدوات العمل (Jira MCP، Slack MCP، وواجهات API الداخلية) في إعدادات العمل فقط. وأبقِ إعداداتك الشخصية نظيفة.</li><li><strong>تحقّق من الحساب النشط:</strong> شغّل <code>claude config list</code> داخل جلسة إن كنت غير متأكد من الحساب الذي أنت فيه — فهو يعرض مسار دليل الإعدادات</li></ul>
<h2>أين يقصُر هذا الأسلوب</h2>
<p><code>CLAUDE_CONFIG_DIR</code> يعزل حسب <em>الحساب</em>، لا حسب <em>المشروع</em>. فداخل ملف تعريف واحد، يرى Claude كل خادم MCP سجّلته يومًا لذلك الحساب — عبر جميع مشاريعك. وللاستخدام الشخصي الفردي، يكون هذا مقبولًا عادةً. لكن ما إن يصبح لديك عدة مشاريع حرجة للإنتاج تحت حساب واحد، خصوصًا في مجالات متداخلة كالفوترة أو أدوات الإدارة أو البنية التحتية، حتى يظهر خطر ملموس عابر للمشاريع: قد يستدعي مساعد الذكاء الاصطناعي أداةً من المشروع A أثناء عمله على المشروع B، لا سيما عندما يكشف كلاهما عمليات متشابهة الأسماء.</p>
<p>نمط ملفات التعريف يجيب عن سؤال <em>في أي حساب أنا؟</em>. لكنه لا يجيب عن سؤال <em>أدوات أي مشروع ينبغي أن تكون نشطة الآن؟</em>. وللأعمال الأعلى مخاطرةً، أضِف طبقة عزل ثانية فوق التقسيم حسب الحساب:</p>
<ul><li><strong>ملف تعريف واحد لكل مشروع حرج للإنتاج، لا لكل حساب فحسب:</strong> بدلًا من <code>~/.claude</code> و<code>~/.claude-work</code>، أنشئ <code>~/.claude-work-billing</code> و<code>~/.claude-work-admin</code>. يرى كل ملف تعريف خوادم MCP التي يحتاجها فعليًا فقط.</li><li><strong>MCP على مستوى المشروع عبر <code>.mcp.json</code>:</strong> ضع ملف <code>.mcp.json</code> في جذر المشروع يسرد خوادم MCP الخاصة بذلك المشروع فقط. يلتقطها Claude عند تشغيله من ذلك الدليل. أبقِ إعداداتك العامة في حدّها الأدنى — أدوات عامة فقط (ملاحظات، بحث)، دون أي نقاط نهاية إنتاجية.</li><li><strong>سمِّ خوادم MCP بلا لبس:</strong> تجنّب الأسماء العامة مثل <code>admin</code> و<code>billing</code> و<code>mcp-server</code>. أضِف بادئة باسم المشروع: <code>acme_billing_prod</code>، <code>acme_admin_stage</code>. الاسم الوصفي يفرض توقّفًا لحظة أن يوشك شيء أن يُستدعى من السياق الخطأ.</li><li><strong>راجِع كل استدعاء لأداة MCP قبل الموافقة عليه:</strong> استدعاءات مثل <code>*_create_*</code> و<code>*_delete_*</code> و<code>*_charge_*</code> تستحق نظرة ثانية متأنّية. فأي سرعة تكسبها من الموافقة التلقائية الشاملة تتبخّر في أول مرة تنطلق فيها أداة من المشروع الخطأ نحو الإنتاج.</li></ul>
<p>القاعدة العامة: قسّم ملفات التعريف بجرأة، وأبقِ خوادم MCP الإنتاجية بعيدًا عن ملف التعريف الافتراضي، وعامِل تداخل أسماء الأدوات بين المشاريع بوصفه علامةً تستحق إعادة الهيكلة.</p>
<h2>الخلاصة</h2>
<p>متغيّر بيئة واحد. alias واحد. عزل كامل بين الحسابات. لا رقصة تسجيل خروج ودخول، ولا تعارضات في الإعدادات، ولا تسرّب للسياق. إنه من نوع الحلول البسيطة إلى حدٍّ يكاد يخيّب الظن — لكن هذا بالضبط ما يجعله جيدًا. اضبطه مرة واحدة ولا تفكّر فيه مجددًا أبدًا.</p>]]></content:encoded>
      <pubDate>Tue, 05 May 2026 00:00:00 GMT</pubDate>
      <category>claude-code</category>
      <category>productivity</category>
      <category>cli</category>
      <category>devtools</category>
    </item>
    <item>
      <title>كيف تربط Claude Desktop بـ Obsidian — رحلة عبر 4 خوادم MCP</title>
      <link>https://oleksiimazurenko.dev/ar/blog/claude-obsidian-mcp-servers</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/claude-obsidian-mcp-servers</guid>
      <description>قصة حقيقية عن البحث عن طريقة مستقرّة لأتمتة إعادة هيكلة الـ vault في Obsidian عبر Claude. ما الذي انكسر، وما الذي نجح، ولماذا تبيّن أن VaultForge هو الخيار الوحيد الذي يعمل.</description>
      <content:encoded><![CDATA[<p>تخيّل الموقف: لديك أكثر من 400 ملاحظة في Obsidian، تراكمت على مدى سنوات. كل شيء مبعثر في جذر الـ vault، والمفاهيم مختلطة بالملاحظات التقنية، وهناك نسخ مكررة (<code>ideas.md</code> ومجلد <code>ideas/</code> يحتوي 13 ملفًا بداخله)، بلا أي نظام. تريد أن تُرتّب الأمور — أن تبني بنية مجلدات سليمة، وتضيف ملفات MOC، وتنظّم الوسوم. القيام بذلك يدويًا مُمِلّ وبطيء. الفكرة المنطقية: <strong>اربط Claude بـ Obsidian عبر MCP، ودَعِ الذكاء الاصطناعي يتولّى إعادة الهيكلة</strong>. تبيّن أنّ هذا طريق عبر حقل ألغام. إليك ما اضطررت إلى المرور به للوصول إلى حل يعمل فعلًا.</p>
<h2>ما هو MCP ولماذا الأمر ليس بهذه البساطة</h2>
<p>MCP (Model Context Protocol) بروتوكول مفتوح من Anthropic يتيح لـ Claude الاتصال بأدوات وبيانات خارجية. المبدأ بسيط: يعمل خادم محلي يعرض «أدوات» (tools)، ويستدعيها Claude أثناء المحادثة.</p>
<p>بالنسبة إلى Obsidian، توجد نظريًا وفرة من خوادم MCP. أما عمليًا — فلكلٍّ منها مشاكله الخاصة.</p>
<p><strong>المشكلة الأساسية في منظومة Obsidian:</strong> إنّ Obsidian تطبيق مغلق بلا MCP رسمي. سدّ المجتمع الفجوة، لكن كل تنفيذ يسير في طريقه الخاص، ولا يحظى أيٌّ منها بـ«مباركة رسمية».</p>
<h2>المحاولة 1: MarkusPfundstein/mcp-obsidian</h2>
<p>أول أداة تظهر عند البحث. 3400 نجمة على GitHub، وحاضرة في كل الدروس التعليمية. تبدو خيارًا آمنًا.</p>
<p><strong>كيف تعمل:</strong> خادم Python مبني على إضافة Local REST API في Obsidian. يتواصل الخادم مع الإضافة عبر HTTPS، وتنفّذ الإضافة العمليات عبر Obsidian API.</p>
<h3>ما الذي حدث بشكل خاطئ</h3>
<ul><li>لم يُحدَّث منذ 17 شهرًا</li><li>85 مشكلة (issue) مفتوحة</li><li><strong>لا يوجد <code>move</code>/<code>rename</code></strong> — فقط read وwrite وappend وdelete</li><li>لدى Local REST API خلل موثّق لفقدان البيانات: قد تقوم نقطة نهاية POST بالكتابة فوق ملف بصمت أثناء عملية append</li></ul>
<p>لا يصلح لإعادة الهيكلة — نحتاج إلى نقل الملفات مع الحفاظ على الروابط. نمضي قُدُمًا.</p>
<h2>المحاولة 2: aaronsb/obsidian-mcp-plugin</h2>
<p>وجدت خيارًا يعمل كـ<strong>إضافة Obsidian أصلية</strong>. هذا يعني وصولًا مباشرًا إلى واجهة Obsidian الداخلية — backlinks وDataview ورسم بياني للروابط. النقل عبر الواجهة الأصلية يُحدّث كل روابط الويكي تلقائيًا، لأن Obsidian يتولّى ذلك بنفسه.</p>
<h3>صعوبات الإعداد</h3>
<ul><li>الإضافة <strong>ليست في الكتالوج الرسمي لـ Obsidian</strong> (طلب الدمج معلّق بأخطاء تحقّق)</li><li>يجب التثبيت عبر <strong>BRAT</strong> (Beta Reviewers Auto-update Tool)</li><li>لا يقبل Claude Desktop رمز Bearer مباشرة عبر الواجهة — ما أجبرني على تفعيل HTTPS في الإضافة</li><li>الشهادة الموقّعة ذاتيًا (self-signed) لـ localhost تثير مشاكل ثقة</li></ul>
<p>بعد كل هذه الحلول الالتفافية، تمكّنت أخيرًا من ربطها. اختبار أساسي — <code>vault.move</code> يعيد بالفعل كتابة <code>[[wikilinks]]</code>، ويعمل كما هو متوقّع.</p>
<h3>ما الذي حدث بشكل خاطئ في الإنتاج</h3>
<p>عندما بدأت إعادة الهيكلة على نطاق واسع (سحب وإفلات لعشرات المجلدات في Obsidian + عمليات MCP متزامنة)، <strong>تجمّد الخادم لأكثر من 4 دقائق</strong>. السبب: الإضافة تعمل <em>داخل</em> Obsidian. عندما يُعيد Obsidian فهرسة آلاف الملفات بعد تغيير هيكلي واسع، تُحجَب الإضافة معه.</p>
<p>الخلاصة: <strong>الاعتماد على نسخة Obsidian مفتوحة وعلى فهرسها قاتل للعمليات المجمّعة</strong>.</p>
<h2>المحاولة 3: @bitbonsai/mcpvault</h2>
<p>منطقيًا — نحتاج خادمًا <strong>لا يعتمد على Obsidian</strong>. يعمل مباشرة مع الملفات على القرص. <code>@bitbonsai/mcpvault</code> — موصى به في كثير من المراجعات. وصول مباشر إلى نظام الملفات، وإعداد بسيط (<code>npx @bitbonsai/mcpvault@latest /path/to/vault</code>)، و14 أداة. لا حاجة حتى لإبقاء Obsidian مفتوحًا.</p>
<p><strong>قبل التثبيت، تحقّقت من أمر حاسم واحد</strong> — هل تُحدَّث روابط الويكي عند النقل. عثرت على مراجعة أحد المستخدمين:</p>
<blockquote>موصل نظام الملفات لا يعرف أنه داخل Obsidian — يرى مجلدًا من ملفات &lt;code&gt;.md&lt;/code&gt; وحسب. لا يعرف أن أسماء الملفات تحمل وزنًا دلاليًا، وأن كل &lt;code&gt;[[wikilink]]&lt;/code&gt; سينكسر لحظة إعادة التسمية أو النقل. التحديث التلقائي للروابط يعمل فقط عندما تحدث إعادة التسمية من داخل التطبيق. تعلّمت هذا بعدما طلبت من Claude تنظيف أسماء الملفات، فعُدت إلى لوحة تحكّم نصف روابطها مكسور.</blockquote>
<p>تأكّد ذلك في توثيق mcpvault نفسه: طلب الدمج #101 (wiki link resolution) <strong>قيد المراجعة، ولم يُدمج بعد</strong>. أي أن النقل عبر <code>mcpvault</code> سيكسر نصف الـ vault. لا يصلح.</p>
<h2>المحاولة 4: VaultForge (النهائية)</h2>
<p><code>blacksmithers/vaultforge</code> — مبني خصّيصًا لوكلاء الذكاء الاصطناعي الذين يقومون بإعادة الهيكلة.</p>
<h3>صحيح معماريًا</h3>
<ul><li><strong>نظام ملفات مباشر</strong> — لا يعتمد على Obsidian</li><li><strong>محرّك wikilink خاص به</strong> — يُنفّذ منطق تحليل <code>[[wikilink]]</code> الذي يُحدّث كل الصيغ (stem، المسار الكامل، alias، embed)</li><li><strong>تشغيل تجريبي (dry run) افتراضيًا</strong> في كل العمليات المدمّرة — يُظهر أولًا ما سيتغيّر، ثم تؤكّد أنت</li><li><strong>27 أداة</strong> مقابل 8–14 لدى المنافسين: batch_rename وupdate_links وbacklinks (تحليل الأثر) وprune_empty_dirs وfrontmatter وsmart_search (BM25) وvault_themes (تجميع TF-IDF)</li><li><strong>رخصة MIT</strong>، TypeScript، صفر تبعيات فرعية</li><li><strong>تثبيت في 30 ثانية</strong> عبر <code>.mcpb</code> (امتداد بنقرة واحدة لـ Claude Desktop)</li></ul>
<h3>اختبار أمان على ملفات معزولة</h3>
<p>أنشأت 4 ملفات اختبار بروابط متقاطعة — روابط stem، وروابط بأسماء بديلة (alias)، وروابط بمسار كامل. أنقل ملفًا واحدًا إلى مجلد فرعي:</p>
<pre><code>delta.md → subfolder/delta-renamed.md</code></pre>
<p>أظهر VaultForge تشغيلًا تجريبيًا: «ستُعاد تسمية ملف واحد، وستُحدَّث 3 روابط». ثم نفّذت العملية فعليًا.</p>
<table><thead><tr><th>Link type</th><th>Before</th><th>After</th></tr></thead><tbody><tr><td>Stem</td><td>[[delta]]</td><td>[[delta-renamed]]</td></tr><tr><td>Alias</td><td>[[delta|D]]</td><td>[[delta-renamed|D]]</td></tr><tr><td>Full path + alias</td><td>[[_vf-test/delta|D]]</td><td>[[_vf-test/subfolder/delta-renamed|D]]</td></tr></tbody></table>
<p>تحقّقت بعد ذلك — <strong>حُدِّثت أنواع الروابط الثلاثة جميعها بشكل صحيح</strong>. هذا بالضبط ما كان ينقص كل الأدوات السابقة.</p>
<h2>كيفية تثبيت VaultForge — التعليمات النهائية</h2>
<p>إذا كان لديك macOS و Claude Desktop:</p>
<h3>الخطوة 1</h3>
<p>نزّل ملف <code>.mcpb</code>:</p>
<pre><code>curl -fsSL https://github.com/blacksmithers/vaultforge/releases/latest/download/vaultforge.mcpb \
  -o /tmp/vaultforge.mcpb &amp;&amp; open /tmp/vaultforge.mcpb</code></pre>
<h3>الخطوة 2</h3>
<p>سيفتح Claude Desktop مربّع حوار تثبيت الامتداد. أدخِل <strong>المسار المطلق</strong> إلى الـ vault الخاص بك — بلا شرطات مائلة عكسية، وبمسافات عادية:</p>
<pre><code>/Users/yourname/Library/Mobile Documents/iCloud~md~obsidian/Documents/MyVault</code></pre>
<h3>الخطوة 3</h3>
<p>انقر على Save. سيضيف Claude Desktop الامتداد إلى ملف الإعداد تلقائيًا. <strong>لا حاجة لإعادة التشغيل</strong> — تُلتقط امتدادات <code>.mcpb</code> تلقائيًا.</p>
<h3>الخطوة 4</h3>
<p>تحقّق: في محادثة جديدة اسأل: <em>«What is the status of my Obsidian vault?»</em> — يُفترض أن يُعيد شيئًا مثل <code>totalFiles: 416, totalDirs: 135, ...</code></p>
<h2>ما تعلّمته عن منظومة MCP في Obsidian</h2>
<p><strong>أولًا، «الأكثر شهرة» لا يعني «الذي يعمل».</strong> يملك MarkusPfundstein/mcp-obsidian عددًا يبلغ 3400 نجمة وهو التوصية الافتراضية، لكنه قديم وتنقصه عمليات أساسية.</p>
<p><strong>ثانيًا، للإضافة الأصلية كُلفة خفيّة.</strong> بدت إضافة aaronsb مثالية — رسم بياني، Dataview، نقل أصلي. لكن الاعتماد على نسخة Obsidian قيد التشغيل وعلى فهرسها يجعلها غير صالحة للعمليات المجمّعة الجادّة.</p>
<p><strong>ثالثًا، نظام الملفات المباشر بلا محرّك روابط فخّ.</strong> إن mcpvault سريع وبسيط، لكن «مجرّد نقل الملفات» يُدمّر بنية الـ vault. تحمل الروابط <strong>دلالة مفروضة</strong> لا يعرفها نظام الملفات. من دون تنفيذ خاص لمنطق الويكي-لينك، تتحوّل الأداة إلى لغم.</p>
<p><strong>رابعًا، اختبِر على بيانات معزولة.</strong> قبل أن تأتمن أي أداة على إعادة هيكلة على نطاق واسع — أنشئ مجلد اختبار فيه 4–5 ملفات بروابط متقاطعة وشاهد ما يحدث. خمس دقائق من الاختبار توفّر ساعات من الاستعادة من النسخة الاحتياطية.</p>
<p><strong>خامسًا، احتفظ بنسخة git احتياطية للـ vault الخاص بك.</strong> الأهم من كل شيء. مجرّد <code>git init</code> واحد داخل الـ vault وعمليات commit دورية — هذا هو التأمين ضد أي خطأ من وكيل ذكاء اصطناعي أو أداة. إذا انكسر شيء ما — يُعيد <code>git reset --hard</code> كل شيء إلى ما كان.</p>
<h2>الخلاصة</h2>
<p>استغرقت الرحلة عدة ساعات وثلاث محاولات فاشلة. تبدو البنية النهائية هكذا:</p>
<ul><li><strong>VaultForge</strong> — أداة العمل الرئيسية. نظام ملفات مباشر + محرّك wikilink خاص + 27 أداة = إعادة هيكلة مستقرّة على أي نطاق.</li><li><strong>Git</strong> — إدارة إصدارات الـ vault. تراجُع مجاني عن أي خطأ.</li></ul>
<p>الآن يمكنني فعل ما بدأ كل هذا من أجله: أن أطلب من Claude ترتيب 400 ملاحظة وفق بنية PARA سليمة، ودمج النسخ المكررة، وإضافة frontmatter، وبناء خرائط MOC. كل عملية آمنة، والروابط محفوظة، والتشغيل التجريبي يُظهر ما سيحدث قبل أن يتغيّر أي شيء.</p>
<p>إن كنت أنت أيضًا تنظر إلى Obsidian المزدحم لديك وتريد مساعدًا ذكيًا — ابدأ فورًا بـ VaultForge. لا تُكرّر مساري عبر مشاريع ميتة وإضافات تجريبية وخوادم نظام ملفات بلا منطق روابط.</p>]]></content:encoded>
      <pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate>
      <category>Obsidian</category>
      <category>Claude</category>
      <category>MCP</category>
      <category>Automation</category>
      <category>AI</category>
    </item>
    <item>
      <title>الثقوب السوداء بوصفها أكوانًا عوديّة: من الفيزياء إلى غرض الوجود</title>
      <link>https://oleksiimazurenko.dev/ar/blog/black-holes-recursive-universes</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/black-holes-recursive-universes</guid>
      <description>ماذا لو كان كل ثقب أسود انفجارًا عظيمًا لكونٍ جديد؟ استكشافٌ لعلم الكونيات العَوْدي، وإشعاع هوكينغ، والانغلاق الإدراكي، ولماذا قد يكون الكون مصمّمًا لإرغامنا على تحسين الذات.</description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>ماذا لو كان كل ثقب أسود انفجارًا عظيمًا لكونٍ جديد؟ يستكشف هذا المقال فكرة أن كوننا قد يكون مجرّد عقدة واحدة في شجرةٍ عوديّة لا نهائية — حيث تلد الثقوب السوداء أكوانًا فرعية، وتعود الطاقة أدراجها عبر إشعاع هوكينغ، وقد صُمّمت قوانين الفيزياء الأساسية عمدًا لتجعل التواصل بين الأكوان مستحيلًا.</p>
<h2>الثقب الأسود = كون</h2>
<p>جاءت الفكرة في لحظة تأمّل: يتشكّل الثقب الأسود حين تتركّز كتلةٌ وضغطٌ كافيان في نقطة واحدة. وتلك المتفردة — كثافة لا متناهية، وانحناءٌ لا متناهٍ — تشبه على نحوٍ مريب الظروف التي نصف بها الانفجار العظيم.</p>
<p>ماذا لو كانا الحدث نفسه، منظورًا إليه من جهتين مختلفتين؟ من الخارج، نرى ثقبًا أسود يبتلع المادة. ومن الداخل — كونًا جديدًا ينفجر إلى الوجود. تصبح الكتلة والطاقة اللتان انهارتا في الثقب الأسود المادةَ الخام لكونٍ جديد كليًا، له نجومه وكواكبه، وربما ثقوبه السوداء الخاصة.</p>
<blockquote>قد يحتوي كل ثقب أسود في كوننا على كون. وقد يكون كوننا موجودًا داخل ثقب أسود لكونٍ أمّ.</blockquote>
<h2>لماذا لا تستطيع الأكوان أن يتواصل بعضها مع بعض</h2>
<p>وهنا يكمن الجانب الأنيق: ما إن تعبر أفق الحدث حتى ينقطع طريق العودة. تكفل النسبية العامة ذلك — إذ يقع مستقبل الكون الأمّ بأكمله خارج أفق الحدث، بعيد المنال من الداخل. ومن منظور الكون الفرعي، يكون الكون الأمّ قد انتهى بالفعل. لقد انقضى خطّه الزمني كله.</p>
<p>ليس هذا قيدًا تقنيًا قد نتجاوزه بتقنيةٍ أفضل. إنه مغروسٌ في هندسة الزمكان نفسها. فالأكوان معزولة بعضها عن بعض عزلًا جوهريًا — لا بالمسافة، بل ببنية الزمن.</p>
<h2>دورة الطاقة: الاقتراض والردّ</h2>
<p>لكن الطاقة لا تُفقد. فإشعاع هوكينغ — العملية الكمّية التي تتبخّر بها الثقوب السوداء ببطء — يخلق دورةً مدهشة:</p>
<ol><li>يُنشئ كونٌ أمّ ثقبًا أسود، ناقلًا الطاقة إلى كونٍ فرعي</li><li>يعيش الكون الفرعي دورة حياته الكاملة على مدى تريليونات السنين</li><li>يتبخّر الثقب الأسود ببطء، رادًّا الطاقة إلى الكون الأمّ عبر إشعاع هوكينغ</li><li>يستعيد الكون الأمّ طاقته — مع فائدة</li></ol>
<p>وتلك «الفائدة» مثيرة للدهشة: يعتقد الفيزيائيون الآن أن إشعاع هوكينغ يحفظ المعلومات. فالكون الأمّ لا يستعيد طاقةً فارغة فحسب — بل يستعيد بصمةً لكل ما جرى في الداخل. كل نجمٍ تكوّن، وكل كوكب، وكل لحظة وعي — مُشفّرة في الإشعاع.</p>
<h2>عوديّة حتى القاع</h2>
<p>إن كنت مبرمجًا، فالنمط لا تخطئه عين. هذه عوديّة. يستدعي كل كونٍ <code>universe()</code> بطاقة أقل، مُنشئًا أكوانًا فرعية تُنشئ بدورها أكوانًا فرعيةً فرعية، إلى أن لا تبقى طاقة كافية لتكوين الثقوب السوداء — وهي الحالة الأساس.</p>
<pre><code>universe(energy)
  ├── creates black holes
  │     ├── universe(energy - n)
  │     │     ├── universe(energy - n - m)
  │     │     │     └── base case: not enough energy for black holes
  │     │     └── returns energy via Hawking radiation
  │     └── returns energy via Hawking radiation
  └── receives all energy back</code></pre>
<p>صاغ الفيزيائي لي سمولين فكرةً مشابهة تحت اسم <strong>الانتقاء الطبيعي الكوني</strong>: تتكاثر الأكوان عبر الثقوب السوداء، ويحمل كل جيلٍ ثوابت فيزيائية مختلفة اختلافًا طفيفًا — مُحسَّنة عبر دوراتٍ لا تُحصى لإنتاج مزيدٍ من الثقوب السوداء، ومزيدٍ من الأكوان.</p>
<h2>أين نحن في هذه الدورة؟</h2>
<p>يبلغ عمر كوننا نحو 13.8 مليار سنة. يبدو هذا موغلًا في القِدم، لكن في سياق عمره الكامل، فإننا نشهد البداية المطلقة:</p>
<table><thead><tr><th>الحدث</th><th>المقياس الزمني</th></tr></thead><tbody><tr><td>العمر الحالي للكون</td><td>~10¹⁰ years</td></tr><tr><td>توقّف النجوم عن التكوّن</td><td>~10¹⁴ years</td></tr><tr><td>عصر الثقوب السوداء</td><td>~10⁴⁰ years</td></tr><tr><td>تبخّر آخر ثقب أسود</td><td>~10¹⁰⁰ years</td></tr></tbody></table>
<p>نحن موجودون عند نحو 0.00000000...01% من إجمالي عمر كوننا. فعصر النجوم — كل ما يمكننا رؤيته — ومضة عابرة في البداية المطلقة. أما القصة الحقيقية للكون فهي عصر الثقوب السوداء البطيء الصبور، وهي تُنشئ الأكوان الفرعية وتبخّرها.</p>
<h2>مسألة الأبعاد الأعلى</h2>
<p>كل ما نوقش حتى الآن يعمل ضمن فهمنا الثلاثي الأبعاد. لكن إذا كان كوننا «شريحة» من شيءٍ أعلى بُعدًا، فقد تكون شجرة الثقوب السوداء والأكوان الفرعية العوديّة بأسرها مجرّد ظلٍّ لبنيةٍ نعجز عن إدراكها.</p>
<p>في عام 1884، كتب إدوين آبوت <em>فلاتلاند</em> — قصةً عن كائناتٍ ثنائية الأبعاد عاجزة عن تصوّر بُعدٍ ثالث. فالكرة التي تمرّ عبر فلاتلاند تبدو دائرةً تكبر ثم تصغر. يستطيع «سكان فلاتلاند» وصفها رياضيًا، لكنهم لا يفهمون قط حقيقة ما يرونه. وقد نكون في الموقف ذاته تمامًا إزاء كوننا.</p>
<blockquote>ما الوعي؟ ولماذا توجد التجربة الذاتية؟ سمّى ديفيد تشالمرز هذا «المشكلة الصعبة» — وقد يكون أقوى دليلٍ على أن شيئًا ما يعمل خارج مدى أبعادنا.</blockquote>
<h2>كل شيء مقفلٌ عند المستوى الأساسي</h2>
<p>أشدّ ما يستوقفنا ليس أننا لا نعرف — بل أننا <em>لا نقدر</em> أن نعرف. فكل اتجاهٍ من اتجاهات البحث يصطدم بحاجزٍ أساسي:</p>
<ul><li><strong>أتريد رؤية الكون الأمّ؟</strong> محجوبٌ بأفق الحدث</li><li><strong>أتريد فهم الوعي؟</strong> محجوب — فالنظام لا يستطيع تحليل نفسه تحليلًا كاملًا (مبرهنتا عدم الاكتمال لغودل)</li><li><strong>أتريد معرفة ما كان «قبلُ»؟</strong> محجوب — فالزمن بدأ مع الانفجار العظيم</li><li><strong>أتريد إدراك الأبعاد الأعلى؟</strong> محجوبٌ بالقيود الإدراكية لكائنٍ ثلاثي الأبعاد</li></ul>
<p>يسمّي الفيلسوف كولن ماكغين هذا <strong>الانغلاق الإدراكي</strong>: بعض الأسئلة مغلقة أمام العقل البشري لا لنقص البيانات، بل بسبب بنية العقل نفسه. والفرق بين «لا نعرف بعد» و«لا نقدر أن نعرف» فرقٌ عميق.</p>
<h2>الشيء الوحيد المتبقّي: تحسين الذات</h2>
<p>إذا كان كل مخرجٍ محجوبًا بحكم التصميم — إن لم تستطع النظر إلى الخارج، ولا إلى الوراء، ولا إلى الأعلى — فلا يبقى إلا اتجاهٌ واحد: إلى الداخل. يبدو الكون مبنيًا عمدًا ليُرغم المرء على التركيز على ذاته.</p>
<p>لا ينبع هذا الاستنتاج من الدين ولا من كتب الفلسفة. بل ينبع من تتبّع منطق الثقوب السوداء، والعوديّة، ونظرية المعلومات، وحدود الإدراك. فالوجوديون والبوذيون والرواقيون والفيزيائيون يصلون جميعًا إلى النقطة ذاتها عبر دروبٍ مختلفة: قد يكون غرض الوجود ببساطة هو صقل الكائن الموجود.</p>
<blockquote>وصلنا إلى هذا لا عبر الإيمان، بل عبر الفيزياء — من الثقوب السوداء، مرورًا بالأكوان العوديّة، إلى حواجز المعرفة الأساسية، وصولًا إلى الباب الوحيد المفتوح: أن نصير أفضل.</blockquote>
<h2>المراجع</h2>
<ul><li><a href="https://en.wikipedia.org/wiki/Cosmological_natural_selection" target="_blank" rel="noopener">لي سمولين — الانتقاء الطبيعي الكوني</a></li><li><a href="https://en.wikipedia.org/wiki/Hawking_radiation" target="_blank" rel="noopener">ستيفن هوكينغ — إشعاع هوكينغ</a></li><li><a href="https://en.wikipedia.org/wiki/Hard_problem_of_consciousness" target="_blank" rel="noopener">ديفيد تشالمرز — المشكلة الصعبة للوعي</a></li><li><a href="https://en.wikipedia.org/wiki/Flatland" target="_blank" rel="noopener">إدوين آبوت — فلاتلاند: رواية أبعادٍ كثيرة (1884)</a></li><li><a href="https://en.wikipedia.org/wiki/G%C3%B6del%27s_incompleteness_theorems" target="_blank" rel="noopener">كورت غودل — مبرهنتا عدم الاكتمال</a></li></ul>]]></content:encoded>
      <pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate>
      <category>physics</category>
      <category>philosophy</category>
      <category>consciousness</category>
      <category>black-holes</category>
      <category>cosmology</category>
    </item>
    <item>
      <title>الذكاء الاصطناعي قتل نظام إدارة المحتوى — على الأقل للمواقع البسيطة</title>
      <link>https://oleksiimazurenko.dev/ar/blog/ai-killed-cms-for-simple-sites</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/ai-killed-cms-for-simple-sites</guid>
      <description>لماذا صارت أنظمة إدارة المحتوى التقليدية غير ضرورية لمعارض الأعمال والمدونات وصفحات الهبوط — وكيف تستبدل أدوات الذكاء الاصطناعي طبقة نظام إدارة المحتوى بأكملها باللغة الطبيعية.</description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>بالنسبة للمواقع البسيطة — معارض الأعمال والمدونات وصفحات الهبوط ومواقع الأعمال الصغيرة — أصبح نظام إدارة المحتوى التقليدي عبئًا زائدًا لا لزوم له. صارت أدوات الذكاء الاصطناعي مثل Claude Code وCursor وGitHub Copilot قادرة الآن على تعديل قاعدة الكود مباشرةً، وفهم السياق، وترجمة المحتوى، ونشر التغييرات عبر git. طبقة التجريد التي كان يوفرها نظام إدارة المحتوى يجري استبدالها بواجهة أذكى: اللغة الطبيعية.</p>
<h2>ضريبة نظام إدارة المحتوى التي تدفعها</h2>
<p>كل نظام إدارة محتوى يأتي بتكلفة خفية. ليست رسوم الاشتراك فحسب — بل منظومة التعقيد الكاملة التي تلتف حول موقعك البسيط أصلًا:</p>
<ul><li><strong>البنية التحتية:</strong> قاعدة بيانات لاستضافتها، وواجهة API لصيانتها، ولوحة تحكم لتأمينها. يشكّل WordPress وحده نحو 43% من الويب ونحو 90% من الهجمات الموجهة إلى أنظمة إدارة المحتوى.</li><li><strong>الأداء:</strong> توليد ديناميكي للصفحات، واستدعاءات API عند كل طلب، وإماهة بيانات نظام إدارة المحتوى من جهة العميل. معرض أعمالك المكوّن من 3 صفحات صار الآن بمعمارية منتج SaaS.</li><li><strong>الارتهان للمورّد:</strong> يعيش محتواك داخل مخطط قاعدة بيانات يملكها غيرك. الانتقال من Contentful إلى Sanity؟ ذاك مشروع كامل، لا مجرد تغيير في الإعدادات.</li><li><strong>تبديل السياق:</strong> تحرّر الكود في محرر IDE، ثم تنتقل إلى لوحة تحكم نظام إدارة المحتوى في المتصفح لتغيير عنوان. نموذجان ذهنيان مختلفان لما هو في جوهره العملية ذاتها.</li><li><strong>التكلفة:</strong> غالبًا ما تتوسّع أسعار أنظمة إدارة المحتوى من نوع headless مع عدد استدعاءات API أو مدخلات المحتوى. المدونة الشخصية لا تحتاج بنية محتوى بتكلفة 99 دولارًا شهريًا.</li></ul>
<p>بالنسبة لموقع تسويقي يحرّر فيه 50 شخصًا المحتوى يوميًا، تكون هذه التكلفة مبرّرة. أمّا لمعرض أعمال مطوّر أو صفحة هبوط لعمل صغير؟ فأنت تبني جسرًا لتعبر بركة ماء.</p>
<h2>ما الذي تغيّر: الذكاء الاصطناعي يفهم كودك</h2>
<p>كان سبب وجود أنظمة إدارة المحتوى واضحًا: الأشخاص غير التقنيين (بل حتى المطوّرون الذين لم يرغبوا في لمس الكود من أجل تغييرات المحتوى) كانوا بحاجة إلى واجهة مرئية لتحديث المواقع. كان الكود معقّدًا أكثر من اللازم، وهشًّا أكثر من اللازم، وسهل الكسر أكثر من اللازم.</p>
<p>غيّر الذكاء الاصطناعي هذه المعادلة من جذورها. أدوات البرمجة الحديثة المدعومة بالذكاء الاصطناعي لا تكتفي بالإكمال التلقائي — بل تفهم بنية المشروع، وتقرأ الأنماط القائمة، وتُجري تعديلات صحيحة سياقيًا. التحوّل في سير العمل هائل:</p>
<pre><code># Old workflow: CMS
1. Open browser → log into CMS dashboard
2. Navigate to content → find the right page
3. Edit in WYSIWYG editor → fight with formatting
4. Preview → looks different from production
5. Hit publish → pray the cache invalidates

# New workflow: AI
1. Tell AI: &quot;Change the pricing on the landing page to $29/month&quot;
2. AI edits the file, you review the diff
3. Push to git → deploy</code></pre>
<p>هذا ليس افتراضًا نظريًا. هذه المدونة نفسها تعمل على SolidStart بمحتوى مخزَّن كملفات TypeScript. كل مقال — بما في ذلك هذا — أُنشئ بإخبار الذكاء الاصطناعي بما يكتب، ومراجعة الناتج، ثم دفعه عبر push إلى git. لا لوحة تحكم لنظام إدارة محتوى. لا قاعدة بيانات. لا طبقة API بين المحتوى والكود.</p>
<h2>أمثلة حقيقية من هذا الموقع</h2>
<p>يدعم هذا الموقع 10 لغات، وفيه مدونة، ويولّد صور OG ديناميكيًا، وينتج خلاصات RSS وخرائط موقع. هكذا تبدو طبقة المحتوى — TypeScript خالص:</p>
<pre><code>// This blog post you&apos;re reading right now is a TypeScript file.
// No database. No API. No CMS dashboard.
// Just a typed object that AI can read and edit directly.

export const myPost: BlogPost = {
  slug: &quot;ai-killed-cms&quot;,
  date: &quot;2026-04-17&quot;,
  translations: {
    en: {
      title: &quot;Why I stopped using a CMS&quot;,
      description: &quot;AI understands my codebase better than any CMS UI.&quot;,
      content: makeContent(proseEn),
    },
    uk: { /* ... */ },
    de: { /* ... */ },
    // 10 languages — AI translates them all
  },
};</code></pre>
<p>أشياء أفعلها بالذكاء الاصطناعي كانت تتطلّب تقليديًا نظام إدارة محتوى:</p>
<ul><li><strong>إضافة مقال جديد:</strong> «اكتب مقالًا جديدًا حول X، والتزم بالبنية نفسها للمقالات القائمة» — ينشئ الذكاء الاصطناعي الملف، ويضيف الترجمات، ويسجّله في الفهرس</li><li><strong>تحديث نص صفحة الهبوط:</strong> «غيّر العنوان الرئيسي للـ hero إلى Y» — يعثر الذكاء الاصطناعي على الملف الصحيح ويحدّثه</li><li><strong>ترجمة المحتوى:</strong> «أضف الترجمة الألمانية لصفحة الأسعار» — يقرأ الذكاء الاصطناعي النسخة الإنجليزية وينتج ترجمة مكيّفة ثقافيًا، لا استبدالًا كلمة بكلمة</li><li><strong>إصلاح خطأ إملائي:</strong> «هناك خطأ إملائي في صفحة About، الكلمة recieve يجب أن تكون receive» — أُنجز في 3 ثوانٍ، ودُفع إلى git بالتزام commit يحمل رسالة ذات معنى</li></ul>
<h2>ما الذي حلّه نظام إدارة المحتوى فعليًا — وكيف يستبدله الذكاء الاصطناعي</h2>
<p>لنكن صادقين بشأن ما قدّمه نظام إدارة المحتوى، وكيف تنعكس كل قدرة منه على سير العمل بالذكاء الاصطناعي:</p>
<table><thead><tr><th>المشكلة</th><th>حل نظام إدارة المحتوى</th><th>حل الذكاء الاصطناعي</th></tr></thead><tbody><tr><td>التحرير غير التقني</td><td>محرر WYSIWYG</td><td>تعليمات باللغة الطبيعية</td></tr><tr><td>المحتوى متعدد اللغات</td><td>إضافات i18n وحقول اللغات</td><td>الذكاء الاصطناعي يترجم بسياق ثقافي</td></tr><tr><td>جدولة المحتوى</td><td>تواريخ نشر مدمجة</td><td>CI/CD قائم على git مع cron أو حقول تاريخ في الكود</td></tr><tr><td>سجل الإصدارات</td><td>نظام مراجعات نظام إدارة المحتوى</td><td>Git — المعيار الذهبي للتحكم في الإصدارات</td></tr><tr><td>إدارة الوسائط</td><td>مكتبة أصول مدمجة</td><td>CDN مع git LFS أو تخزين سحابي</td></tr></tbody></table>
<p>الخلاصة الجوهرية: git بالفعل نظام تحكم في الإصدارات أفضل من أي شيء بناه نظام إدارة محتوى على الإطلاق. واللغة الطبيعية واجهة أفضل من أي محرر WYSIWYG — لأنها تحمل القصد، لا مجرد التنسيق.</p>
<h2>نقلة النموذج: الكود هو طبقة المحتوى</h2>
<p>نشهد انقلابًا. طوال عقدين كان الاتجاه هو فصل المحتوى عن الكود — وضع المحتوى في قاعدة بيانات، وكشفه عبر API، وعرضه في الواجهة الأمامية. كان لهذا معنى حين كان تعديل الكود صعبًا، وحين كان المحتوى بحاجة إلى أن يظل في متناول غير المطوّرين.</p>
<blockquote>لم يجعل الذكاء الاصطناعي نظام إدارة المحتوى عتيقًا بأن صار نظام إدارة محتوى أفضل، بل جعله عتيقًا بأن جعل الكود في متناول اليد كأنه لوحة تحكم.</blockquote>
<p>يتّبع تطوّر إدارة محتوى الويب مسارًا واضحًا:</p>
<ol><li><strong>العقد الأول من الألفية:</strong> أنظمة إدارة محتوى متجانسة (WordPress، Drupal) — المحتوى والعرض مقترنان في نظام واحد</li><li><strong>عقد 2010:</strong> أنظمة إدارة محتوى headless (Contentful، Strapi) — المحتوى مفصول عبر API، ويُعرض بأطر الواجهة الأمامية</li><li><strong>عقد 2020:</strong> مولّدات المواقع الساكنة + Markdown (Hugo، Astro) — المحتوى كملفات، يُبنى وقت النشر</li><li><strong>2025 وما بعده:</strong> الكود بوصفه محتوى + الذكاء الاصطناعي — المحتوى يعيش في كود مُنمَّط، والذكاء الاصطناعي هو واجهة التحرير</li></ol>
<h2>متى تظل بحاجة إلى نظام إدارة محتوى</h2>
<p>هذا ليس موقف «نظام إدارة المحتوى قد مات». فهو يحلّ مشكلات حقيقية على نطاق واسع. ما زلت بحاجة إليه حين:</p>
<ul><li><strong>الفرق التحريرية الكبيرة:</strong> أكثر من 10 محرّري محتوى يحتاجون إلى وصول قائم على الأدوار، وسير عمل للموافقات، وتحرير متزامن. تعارضات دمج git ليست مشكلة يحلّها محرّر المحتوى.</li><li><strong>المحتوى عالي التواتر:</strong> المواقع الإخبارية التي تنشر أكثر من 50 مقالًا يوميًا تحتاج إلى خطوط تحرير محسّنة، لا إلى التزامات git.</li><li><strong>علاقات محتوى معقّدة:</strong> كتالوجات التجارة الإلكترونية التي تضم آلاف رموز SKU وخيارات المنتجات والتسعير الديناميكي تحتاج إلى قواعد بيانات مهيكلة.</li><li><strong>الامتثال التنظيمي:</strong> القطاعات التي تتطلّب مسارات تدقيق، وسلاسل موافقة على المحتوى، وعمليات مراجعة يفرضها القانون، تحتاج إلى أنظمة مصمّمة لهذا الغرض.</li></ul>
<p>الحدّ واضح: إن كانت تغييرات محتواك تتطلّب تنسيقًا بين عدة أطراف غير تقنية وبتواتر عالٍ، فإن نظام إدارة المحتوى يستحق تعقيده. أمّا إن كنت مطوّرًا منفردًا، أو فريقًا صغيرًا، أو تدير موقعًا يتغيّر أسبوعيًا لا كل ساعة — فإن الذكاء الاصطناعي + الكود أبسط وأسرع وأرخص وأكثر موثوقية.</p>
<h2>المستقبل: الذكاء الاصطناعي بوصفه الواجهة الشاملة</h2>
<p>يتجاوز الاتجاه أنظمة إدارة المحتوى. كل طبقة تجريد برمجية وُجدت لأن «النظام الأساسي أعقد من أن يُتعامل معه مباشرةً» يجري ضغطها بالذكاء الاصطناعي. لوحات الإدارة، وواجهات الإعدادات، والمحرّرات المرئية لقواعد البيانات — كلها واجهات تترجم قصد الإنسان إلى تغييرات في النظام. والذكاء الاصطناعي يؤدي هذه الترجمة على نحو أصيل.</p>
<p>بالنسبة للمواقع البسيطة، المستقبل هنا بالفعل. محتواك هو الكود. محرّرك هو الذكاء الاصطناعي. تحكّمك في الإصدارات هو git. نشرك هو دفعة push واحدة. طبقة نظام إدارة المحتوى بأكملها — اللوحة، وقاعدة البيانات، وواجهة API، والاستضافة — كانت وسيطًا برمجيًا (middleware) بين قصدك وموقعك. أزال الذكاء الاصطناعي الحاجة إلى ذلك الوسيط.</p>
<blockquote>أفضل نظام إدارة محتوى هو ألّا يكون هناك نظام إدارة محتوى. ليس لأن إدارة المحتوى غير مهمة — بل لأن الذكاء الاصطناعي جعل الكود نفسه أكثر واجهات إدارة المحتوى بداهةً التي حظينا بها على الإطلاق.</blockquote>]]></content:encoded>
      <pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate>
      <category>ai</category>
      <category>cms</category>
      <category>web-development</category>
      <category>opinion</category>
      <category>workflow</category>
    </item>
    <item>
      <title>كيفية ربط Perplexity AI بـ Obsidian عبر MCP — تدوين الملاحظات مباشرةً من المحادثة</title>
      <link>https://oleksiimazurenko.dev/ar/blog/perplexity-obsidian-mcp-integration</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/perplexity-obsidian-mcp-integration</guid>
      <description>اضبط Perplexity Desktop لقراءة vault الخاص بك في Obsidian والكتابة إليه باستخدام خادم MCP filesystem. ابحث على الويب واحفظ في ملاحظاتك في محادثة واحدة.</description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>يدعم Perplexity Desktop موصّلات <strong>MCP (Model Context Protocol)</strong>. بإضافة الخادم الرسمي <code>@modelcontextprotocol/server-filesystem</code> موجَّهًا إلى vault الخاص بك في Obsidian، يمكنك أن تطلب من Perplexity باللغة الطبيعية قراءة الملاحظات وإنشاءها وتحريرها — مباشرةً من المحادثة. بلا إضافات، بلا امتدادات، بلا نسخ ولصق.</p>
<h2>المشكلة</h2>
<p>يبرع Perplexity في البحث — فهو يبحث في الويب، ويلخّص المصادر، ويقدّم لك إجابات مع الاستشهادات. لكن حين تريد حفظ هذه النتائج في vault الخاص بك في Obsidian، ينكسر سير العمل: تنسخ النص، وتنتقل إلى Obsidian، وتبحث عن الملاحظة المناسبة، وتلصق، وتنسّق. في كل. مرة.</p>
<p>تساعد امتدادات المتصفح مثل «Perplexity to Obsidian» في التصدير، لكنها أحادية الاتجاه — إذ لا يستطيع الذكاء الاصطناعي أن <em>يرى</em> vault الخاص بك، ولا أن يقرأ ملاحظاتك الموجودة، ولا أن يقرّر أين يضع الأشياء بناءً على بنية مجلداتك.</p>
<h2>ما هو MCP؟</h2>
<p><strong>Model Context Protocol (MCP)</strong> معيار مفتوح يتيح لنماذج الذكاء الاصطناعي التفاعل مع الأدوات ومصادر البيانات المحلية. تخيّله منفذ USB للذكاء الاصطناعي — تُوصِّل «خادمًا» (برنامجًا صغيرًا)، فيكتسب الذكاء الاصطناعي قدرات جديدة. في حالتنا، يمنح خادم filesystem منصّة Perplexity 14 أداة للعمل مع الملفات:</p>
<pre><code>Perplexity Desktop (Mac App)
  │
  ├── MCP Connector: &quot;Obsidian Vault&quot;
  │     │
  │     └── @modelcontextprotocol/server-filesystem
  │           │
  │           ├── read_file()      → read any .md file
  │           ├── write_file()     → create or overwrite files
  │           ├── edit_file()      → patch existing files
  │           ├── list_directory() → browse vault structure
  │           ├── search_files()   → find files by pattern
  │           └── ... 14 tools total
  │
  └── Perplexity AI Model
        │
        └── &quot;Save this to my daily note&quot; → calls write_file()</code></pre>
<p>الفكرة الجوهرية: نموذج الذكاء الاصطناعي لا يصل إلى ملفاتك مباشرةً، بل يستدعي أدوات يوفّرها خادم MCP الذي يعمل محليًا على جهازك. لا تغادر بياناتك حاسوبك أبدًا ما لم تطلب صراحةً من الذكاء الاصطناعي أن يفعل شيئًا بها.</p>
<h2>المتطلبات</h2>
<ul><li>اشتراك <strong>Perplexity Pro</strong> (موصّلات MCP متاحة للمشتركين في الخطة المدفوعة)</li><li><strong>Perplexity Mac App</strong> من App Store (وليس نسخة المتصفح)</li><li><strong>Node.js</strong> مثبَّت على جهاز Mac (كي يعمل <code>npx</code>)</li></ul>
<h2>الإعداد خطوة بخطوة</h2>
<p>يستغرق الإعداد بأكمله نحو دقيقتين:</p>
<table><thead><tr><th>Step</th><th>Action</th></tr></thead><tbody><tr><td>1</td><td>Open &lt;strong&gt;Perplexity Mac App&lt;/strong&gt; (App Store version)</td></tr><tr><td>2</td><td>Click avatar → &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Connectors&lt;/strong&gt;</td></tr><tr><td>3</td><td>Click &lt;strong&gt;Add Connector&lt;/strong&gt;</td></tr><tr><td>4</td><td>Set Server Name: &lt;code&gt;Obsidian Vault&lt;/code&gt;, Type: &lt;code&gt;Standard I/O&lt;/code&gt;</td></tr><tr><td>5</td><td>Paste the npx command with your vault path</td></tr><tr><td>6</td><td>Leave Environment Variables empty</td></tr><tr><td>7</td><td>Click &lt;strong&gt;Save&lt;/strong&gt; → confirm Security Warning</td></tr><tr><td>8</td><td>Verify status shows &lt;strong&gt;Running&lt;/strong&gt; with 14 tools</td></tr></tbody></table>
<h3>الأمر</h3>
<p>الأمر الذي تلصقه في حقل <strong>Command</strong>:</p>
<pre><code>npx -y @modelcontextprotocol/server-filesystem &quot;/Users/yourname/Library/Mobile Documents/iCloud~md~obsidian/Documents/Obsidian Vault&quot;</code></pre>
<p>استبدل المسار بالموقع الفعلي لـ vault الخاص بك في Obsidian. إذا كان vault يُزامَن عبر iCloud، فسيكون المسار ضمن <code>~/Library/Mobile Documents/iCloud~md~obsidian/Documents/</code>. احرص على إبقاء علامتَي الاقتباس — فالمسار يحتوي على الأرجح على مسافات.</p>
<h2>كيفية استخدامه</h2>
<p>بمجرد أن يُظهر الموصّل حالة <strong>Running</strong> مع توفّر 14 أداة، انتقل إلى أي محادثة في Perplexity وابدأ الحديث مع vault الخاص بك:</p>
<pre><code>&gt; Show me the structure of my Obsidian vault
&gt; Add a new reflection to daily notes/2026-04-12.md: &quot;Started using MCP today&quot;
&gt; Find all notes that mention &quot;meditation&quot;
&gt; Create a new note in concepts/ about quantum computing
&gt; List all files in my ideas/ folder</code></pre>
<p>يفهم الذكاء الاصطناعي بنية vault الخاص بك، ويحترم أعراف التنسيق لديك، ويستطيع العمل مع المحتوى الموجود. يمكنك أن تطلب منه البحث عن موضوع على الويب وحفظ الملخّص مباشرةً في ملاحظة محددة — بما يجمع بحث Perplexity مع تنظيم Obsidian في سير عمل واحد.</p>
<h2>لماذا يتفوّق MCP على الأساليب الأخرى</h2>
<p>قبل MCP، كانت هناك طرق محدودة للربط بين Perplexity و Obsidian:</p>
<table><thead><tr><th>Feature</th><th>Copy-paste</th><th>Browser extension</th><th>MCP integration</th></tr></thead><tbody><tr><td>AI sees vault structure</td><td>No</td><td>No</td><td>&lt;strong&gt;Yes&lt;/strong&gt;</td></tr><tr><td>AI reads existing notes</td><td>No</td><td>No</td><td>&lt;strong&gt;Yes&lt;/strong&gt;</td></tr><tr><td>AI writes to vault</td><td>No</td><td>Export only</td><td>&lt;strong&gt;Yes&lt;/strong&gt;</td></tr><tr><td>AI edits existing files</td><td>No</td><td>No</td><td>&lt;strong&gt;Yes&lt;/strong&gt;</td></tr><tr><td>Works from chat</td><td>No</td><td>Partially</td><td>&lt;strong&gt;Yes&lt;/strong&gt;</td></tr><tr><td>Setup complexity</td><td>None</td><td>Low</td><td>Medium (one-time)</td></tr></tbody></table>
<h2>القيود الحالية</h2>
<ul><li><strong>Mac فقط</strong> — تعمل موصّلات MCP في Perplexity حاليًا على نسخة Mac App Store فقط</li><li><strong>لا تكامل مع واجهة برمجة تطبيقات Obsidian</strong> — يعمل خادم filesystem مع الملفات الخام، لا عبر واجهة Obsidian البرمجية. هذا يعني أنه لن يُشغِّل إضافات Obsidian (مثل Linter أو Templater) عند إنشاء الملفات</li><li><strong>الموافقة مطلوبة</strong> — قد تتطلب عمليات الملفات الحساسة تأكيدك في تطبيق Perplexity — وهذه ميزة أمان، لا خلل</li></ul>
<h2>الخلاصة</h2>
<p>يحوّل هذا الإعداد Perplexity من أداة بحث إلى أداة بحث والتقاط معًا:</p>
<ol><li>ابحث على الويب واحفظ في Obsidian في محادثة واحدة</li><li>يرى الذكاء الاصطناعي بنية vault الخاص بك ويتكيّف مع نظام تنظيمك</li><li>صفر تنقّل بين التطبيقات — كل شيء يحدث في محادثة Perplexity</li></ol>
<h2>المصادر</h2>
<ul><li><a href="https://modelcontextprotocol.io" target="_blank" rel="noopener noreferrer">Model Context Protocol</a> — official MCP specification</li><li><a href="https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem" target="_blank" rel="noopener noreferrer">MCP Filesystem Server</a> — the server used in this setup</li><li><a href="https://www.perplexity.ai/help-center/en/articles/11502712-local-and-remote-mcps-for-perplexity" target="_blank" rel="noopener noreferrer">Perplexity Help Center</a> — Local and Remote MCPs for Perplexity</li><li><a href="https://www.perplexity.ai/hub/blog/everything-is-computer" target="_blank" rel="noopener noreferrer">Perplexity Blog</a> — Everything is Computer</li></ul>]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <category>Obsidian</category>
      <category>Perplexity</category>
      <category>MCP</category>
      <category>AI</category>
      <category>Productivity</category>
    </item>
    <item>
      <title>ملخص أخبار AI اليومي عبر Claude Code CLI وObsidian — صفر تبعيات</title>
      <link>https://oleksiimazurenko.dev/ar/blog/ai-news-digest-claude-code-obsidian</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/ai-news-digest-claude-code-obsidian</guid>
      <description>كيف بنيت وكيل بحث إخباري يومي بسكربت bash من 6 أسطر، ووضع headless في Claude Code، وmacOS launchd. يبحث في 11 موضوعًا ويكتب الملخصات مباشرةً إلى Obsidian.</description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>سكربت bash من 6 أسطر يشغّل <strong>Claude Code CLI</strong> في وضع headless كل صباح عند الساعة 9:00. يبحث في الويب عن الأخبار عبر 11 موضوعًا قابلًا للتهيئة، ويرشّح الضجيج، ويكتب ملخصًا منسّقًا بصيغة markdown مباشرةً داخل <strong>Obsidian vault</strong> متزامن عبر iCloud. صفر تبعيات. نحو 100 سطر من الإعدادات إجمالًا.</p>
<h2>المشكلة</h2>
<p>بصفتك مطوّرًا، مواكبة عدة تقنيات في آنٍ واحد ضريبة يومية. خلاصات RSS مزدحمة بالضجيج، وTwitter يبتلع الوقت، والنشرات البريدية تصل حين تكون غارقًا في تدفّق العمل. كنت بحاجة إلى شيء يقوم بالبحث <em>نيابةً</em> عني ويعرض النتائج حيث أعمل بالفعل — داخل Obsidian vault الخاص بي.</p>
<p>الحل المعتاد هو بناء خط سكرابينغ: مجدول، وزاحف، وخط معالجة لغة طبيعية NLP، وقاعدة بيانات، وخدمة إشعارات. أسابيع من العمل لأجل شيء قد ينهار حين يغيّر أي موقع بنية HTML الخاصة به. كنت أريد شيئًا أبنيه في فترة بعد ظهر واحدة.</p>
<h2>البنية</h2>
<p>النظام بأكمله 4 ملفات وصفر تبعيات. إليك كيف يعمل من البداية إلى النهاية:</p>
<pre><code>macOS launchd (9:00 AM daily)
  │
  └── digest.sh
        │
        └── claude -p &quot;$(cat prompt.md)&quot; --max-turns 20 --allowedTools Read,WebSearch,WebFetch,Write
              │
              ├── Reads topics.yaml (11 configurable topics)
              ├── WebSearch → finds news for each topic (last 24-48h)
              ├── WebFetch → reads full articles
              ├── Filters noise: old tutorials, promos, AI spam
              └── Write → saves digest to Obsidian Vault
                    │
                    └── ~/Obsidian Vault/digests/2026-04-12.md
                          │
                          └── iCloud sync → available on all devices</code></pre>
<h2>الشيفرة (كاملةً)</h2>
<p>المشروع بسيط عن قصد. كل سطر يستحق مكانه.</p>
<h3>نقطة الدخول: digest.sh</h3>
<p>التطبيق بأكمله سكربت bash من 6 أسطر:</p>
<pre><code>#!/bin/bash
DIGEST_DIR=&quot;$HOME/Developer/news-digest&quot;

claude -p &quot;$(cat &quot;$DIGEST_DIR/prompt.md&quot;)&quot; \
  --max-turns 20 \
  --allowedTools Read,WebSearch,WebFetch,Write</code></pre>
<p>الخيارات الأساسية: <code>-p</code> يشغّل Claude في وضع headless (دون طرفية تفاعلية)، و<code>--max-turns 20</code> يمنح الوكيل مساحة كافية للبحث في كل المواضيع، و<code>--allowedTools</code> يقصر الوكيل على قراءة الملفات والبحث في الويب وكتابة المخرجات فقط.</p>
<h3>العقل: prompt.md</h3>
<p>هنا يكمن الذكاء. يحوّل الـ prompt نموذج Claude إلى وكيل بحث إخباري بتعليمات محددة عمّا يبحث عنه، وكيف يرشّح، وأين يحفظ:</p>
<pre><code># News Digest Agent

You are a news research agent. Your job is to find today&apos;s most important
and interesting news for a senior frontend developer.

## Instructions

1. Read the topics file at ~/Developer/news-digest/topics.yaml
2. For EACH topic, search the web for news from the last 24-48 hours
3. Filter: only include genuinely new and noteworthy items
4. Write the digest as a markdown file to Obsidian Vault digests/YYYY-MM-DD.md
5. IMPORTANT: Use the Write tool to save the file. Do NOT output to stdout.

## Rules

- Language: Ukrainian for summaries, English for titles and technical terms
- If there&apos;s no real news for a topic — SKIP IT ENTIRELY
- Prioritize: releases &gt; breaking changes &gt; security &gt; new patterns &gt; discussions
- Max 5 items per topic, sorted by importance
- Include direct links to sources
- Skip promotional content, generic tutorials, and AI-generated spam</code></pre>
<h3>الإعداد: topics.yaml</h3>
<p>المواضيع قابلة للتهيئة بالكامل — أضِف موضوعًا جديدًا فيُدرَج في ملخص الغد. لكل موضوع اسم وسياق اختياري يوجّه بحث الذكاء الاصطناعي:</p>
<pre><code>topics:
  - name: better-auth
    context: &quot;auth library for TypeScript. New releases, breaking changes&quot;

  - name: Next.js
    context: &quot;GitHub issues, releases, App Router, Turbopack, performance&quot;

  - name: SolidJS
    context: &quot;SolidStart, releases, ecosystem, comparison with React&quot;

  - name: Tailwind CSS
    context: &quot;v4 updates, new utilities, plugins&quot;

  - name: Claude AI
    context: &quot;Anthropic announcements, Claude Code, new models, MCP, API&quot;

  - name: GPT AI
    context: &quot;OpenAI announcements, new models, ChatGPT features&quot;

  - name: React
    context: &quot;React 19+, Server Components, new patterns, ecosystem&quot;

  - name: Apple
    context: &quot;Hardware, software, WWDC, developer tools, Apple Intelligence&quot;

  - name: Notable People
    context: &gt;
      Latest tweets from: Elon Musk, Dario Amodei, Sam Altman,
      Jensen Huang, Andrej Karpathy, Simon Willison, Swyx...

  - name: AI Global
    context: &quot;Major AI news, new models, regulations, open-source AI&quot;</code></pre>
<h2>الجدولة عبر launchd</h2>
<p>على macOS، يُعدّ <code>launchd</code> الطريقة الأصيلة لجدولة المهام المتكررة (مثل cron على Linux). يحدّد ملف plist متى وكيف يُشغَّل السكربت:</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE plist PUBLIC &quot;-//Apple//DTD PLIST 1.0//EN&quot;
  &quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&quot;&gt;
&lt;plist version=&quot;1.0&quot;&gt;
&lt;dict&gt;
    &lt;key&gt;Label&lt;/key&gt;
    &lt;string&gt;com.news-digest&lt;/string&gt;

    &lt;key&gt;ProgramArguments&lt;/key&gt;
    &lt;array&gt;
        &lt;string&gt;/bin/bash&lt;/string&gt;
        &lt;string&gt;/Users/you/Developer/news-digest/digest.sh&lt;/string&gt;
    &lt;/array&gt;

    &lt;key&gt;StartCalendarInterval&lt;/key&gt;
    &lt;dict&gt;
        &lt;key&gt;Hour&lt;/key&gt;
        &lt;integer&gt;9&lt;/integer&gt;
        &lt;key&gt;Minute&lt;/key&gt;
        &lt;integer&gt;0&lt;/integer&gt;
    &lt;/dict&gt;

    &lt;key&gt;StandardOutPath&lt;/key&gt;
    &lt;string&gt;/Users/you/Developer/news-digest/logs/stdout.log&lt;/string&gt;
    &lt;key&gt;StandardErrorPath&lt;/key&gt;
    &lt;string&gt;/Users/you/Developer/news-digest/logs/stderr.log&lt;/string&gt;

    &lt;key&gt;EnvironmentVariables&lt;/key&gt;
    &lt;dict&gt;
        &lt;key&gt;PATH&lt;/key&gt;
        &lt;string&gt;/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin&lt;/string&gt;
        &lt;key&gt;HOME&lt;/key&gt;
        &lt;string&gt;/Users/you&lt;/string&gt;
    &lt;/dict&gt;
&lt;/dict&gt;
&lt;/plist&gt;</code></pre>
<p>ثبّته عبر <code>launchctl load ~/Library/LaunchAgents/com.news-digest.plist</code>. يعمل السكربت يوميًا عند الساعة 9:00، حتى لو كنت قد سجّلت الخروج — سيشغّل launchd المهام الفائتة حين يستيقظ النظام.</p>
<h2>كيف تبدو المخرجات</h2>
<p>كل صباح، يظهر ملف markdown جديد في Obsidian vault يحوي أخبارًا منظمة ومرتّبة حسب الأولوية:</p>
<pre><code>---
date: 2026-04-12
---

# News Digest — 2026-04-12

## Next.js

### Next.js 16.1 Released with Improved Turbopack Caching
Нова версія Next.js 16.1 включає покращене кешування для Turbopack,
що зменшує час холодного старту на ~40%.
[Посилання](https://nextjs.org/blog/next-16-1)

### Critical Memory Leak Fix in App Router
Виправлено витік пам&apos;яті в App Router при частому перемиканні
між динамічними маршрутами.
[GitHub Issue](https://github.com/vercel/next.js/issues/...)

## Claude AI

### Claude Code 1.5 — MCP Server Auto-Discovery
Anthropic випустив оновлення Claude Code з автоматичним
виявленням MCP серверів у проєкті.
[Блог](https://www.anthropic.com/news/...)</code></pre>
<h2>بالأرقام</h2>
<table><thead><tr><th>Metric</th><th>Value</th></tr></thead><tbody><tr><td>Total files in project</td><td>&lt;strong&gt;4&lt;/strong&gt; (digest.sh, prompt.md, topics.yaml, .gitignore)</td></tr><tr><td>Lines of code</td><td>&lt;strong&gt;~100&lt;/strong&gt;</td></tr><tr><td>External dependencies</td><td>&lt;strong&gt;0&lt;/strong&gt;</td></tr><tr><td>Setup time</td><td>&lt;strong&gt;~10 minutes&lt;/strong&gt;</td></tr><tr><td>Daily execution time</td><td>&lt;strong&gt;2-5 minutes&lt;/strong&gt;</td></tr><tr><td>Cost per run</td><td>&lt;strong&gt;~$0.10-0.30&lt;/strong&gt; (Claude API usage)</td></tr></tbody></table>
<h2>قرارات التصميم الأساسية</h2>
<ul><li><strong>Claude Code CLI بدلًا من الـ API</strong> — لا حاجة لإدارة مفاتيح API أو عملاء HTTP أو تحليل الاستجابات. تتولى الـ CLI المصادقة وتنفيذ الأدوات والتعافي من الأخطاء جاهزةً</li><li><strong>Obsidian بدلًا من البريد الإلكتروني</strong> — الملخصات قابلة للبحث والربط ودائمة. تعيش إلى جوار ملاحظاتي بدل أن تغرق في صندوق الوارد</li><li><strong>launchd بدلًا من cron</strong> — launchd هو المجدول الأصيل في macOS. يعالج التشغيلات الفائتة والتسجيل ومتغيرات البيئة بنظافة</li><li><strong>YAML للمواضيع</strong> — إضافة موضوع جديد تغييرٌ من سطرين. لا حاجة لتعديل الشيفرة</li><li><strong>تخطّي المواضيع الفارغة</strong> — إن لم تكن هناك أخبار حقيقية، يُحذف القسم بالكامل. بلا حشو، وبلا محتوى ممطوط</li></ul>
<h2>كيف تبني نسختك الخاصة</h2>
<p>يمكنك تشغيل هذا خلال 10 دقائق:</p>
<ol><li>ثبّت <a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank" rel="noopener noreferrer">Claude Code CLI</a> وسجّل الدخول</li><li>استنسخ المستودع: <code>git clone https://github.com/oleksiimazurenko/news-digest</code></li><li>عدّل <code>topics.yaml</code> باهتماماتك و<code>prompt.md</code> بمسار الحفظ الخاص بك</li><li>عدّل ملف plist باسم المستخدم والمسارات الخاصة بك، ثم شغّل <code>launchctl load</code></li><li>انتظر الساعة 9:00 — أو اختبر يدويًا عبر <code>bash digest.sh</code></li></ol>
<h2>الخلاصة</h2>
<p>أكثر ما يثير الاهتمام في هذا المشروع هو ما <em>ليس</em> فيه. لا قاعدة بيانات، ولا خادم API، ولا حاوية Docker، ولا حزم npm، ولا Python virtualenv، ولا محلل HTML، ولا خط معالجة NLP. مجرد prompt، وقائمة مواضيع، وسكربت من 6 أسطر يفوّض كل العمل الشاق إلى وكيل ذكاء اصطناعي.</p>
<p>هكذا يبدو البناء بوكلاء الذكاء الاصطناعي عمليًا: تحدّد أنت <em>ماذا</em> و<em>أين</em>، ويتولى الوكيل <em>كيف</em>. بلغ إجمالي وقت التطوير نحو ساعتين، بما في ذلك تكرار صياغة الـ prompt للوصول إلى جودة ترشيح جيدة.</p>
<h2>المصادر</h2>
<ul><li><a href="https://github.com/oleksiimazurenko/news-digest" target="_blank" rel="noopener noreferrer">news-digest on GitHub</a> — full source code</li><li><a href="https://docs.anthropic.com/en/docs/claude-code" target="_blank" rel="noopener noreferrer">Claude Code Documentation</a> — headless mode and CLI flags</li><li><a href="https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html" target="_blank" rel="noopener noreferrer">Apple Developer</a> — creating launchd jobs</li><li><a href="https://obsidian.md" target="_blank" rel="noopener noreferrer">Obsidian</a> — markdown knowledge base</li></ul>]]></content:encoded>
      <pubDate>Sun, 12 Apr 2026 00:00:00 GMT</pubDate>
      <category>Claude Code</category>
      <category>AI</category>
      <category>Obsidian</category>
      <category>Automation</category>
      <category>Productivity</category>
    </item>
    <item>
      <title>الوضع الداكن في Next.js دون وميض أو تحذيرات React 19</title>
      <link>https://oleksiimazurenko.dev/ar/blog/nextjs-dark-mode-without-flash</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/nextjs-dark-mode-without-flash</guid>
      <description>كيفية استبدال next-themes بـ Zustand مع useServerInsertedHTML للحصول على وضع داكن دون وميض في Next.js 15+، ودون إطلاق تحذيرات سكربت React 19.</description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>يعرض <code>next-themes</code> وسم <code>&lt;script&gt;</code> داخل مكوّن عميل React لمنع وميض السمة (FOUC). صار React 19 الآن يُصدر تحذيرًا بشأن ذلك — ولا توجد أي طريقة لكتمه. لم تُحدَّث المكتبة منذ مارس 2025. الحل: استبدال <code>next-themes</code> بمخزن Zustand مع <code>useServerInsertedHTML</code> لحقن السكربت خارج شجرة React. صفر تبعيات مُضافة. صفر وميض FOUC. صفر تحذيرات.</p>
<h2>المشكلة</h2>
<p>إذا كنت تستخدم <code>next-themes</code> مع Next.js 15+ وReact 19، فستحصل على هذا الخطأ في وحدة التحكم عند كل تحميل للصفحة:</p>
<pre><code>Encountered a script tag while rendering React component.
Scripts inside React components are never executed when rendering on the client.
Consider using template tag instead.

src/providers/theme-provider.tsx (7:10) @ ThemeProvider</code></pre>
<p>هذا ليس عدم تطابق في الإماهة (hydration). إنه تحذير صريح من React 19 بأن وسوم <code>&lt;script&gt;</code> التي تعرضها مكوّنات React على العميل <strong>لن تُنفَّذ أبدًا</strong>. يعمل السكربت أثناء التصيير من الخادم SSR (فهو موجود في HTML)، لكن React يعتبره غير صحيح.</p>
<h2>لماذا يحدث ذلك</h2>
<p>يحتاج <code>next-themes</code> إلى ضبط صنف السمة الصحيح على <code>&lt;html&gt;</code> قبل أن يُميه React الصفحة (إماهة/hydration) — وإلا فسيظهر وميض بالسمة الخاطئة. ولتحقيق ذلك، يحقن وسم <code>&lt;script&gt;</code> مضمّنًا عبر <code>React.createElement</code>:</p>
<pre><code>// next-themes renders a &lt;script&gt; inside a Client Component
return React.createElement(Provider, { value },
  React.createElement(&quot;script&quot;, {
    suppressHydrationWarning: true,
    dangerouslySetInnerHTML: { __html: `(...theme init code...)` }
  }),
  children
)</code></pre>
<p>غيّر React 19 سلوكه: صارت وسوم script داخل المكوّنات تُعلَّم الآن صراحةً. قبل React 19 كان يتم تجاهل ذلك بصمت. ولا تُجدي خاصية <code>suppressHydrationWarning</code> على السكربت نفعًا — فهي تكتم تحذيرات الإماهة، لا تحذير «script داخل مكوّن».</p>
<h2>ما الذي جرّبناه (ولماذا فشل)</h2>
<p>جرّبنا كل نهج بشكل منهجي قبل أن نصل إلى الحل:</p>
<table><thead><tr><th>Attempt</th><th>Result</th></tr></thead><tbody><tr><td>&lt;code&gt;suppressHydrationWarning&lt;/code&gt; on &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;</td><td>Suppresses hydration mismatch, but NOT the script tag warning</td></tr><tr><td>Delay mount with &lt;code&gt;useState&lt;/code&gt; + &lt;code&gt;useEffect&lt;/code&gt;</td><td>Warning disappears, but causes FOUC (theme flash)</td></tr><tr><td>Raw &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; in layout &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;</td><td>React 19 catches it — same warning even in Server Components</td></tr><tr><td>&lt;code&gt;next/script&lt;/code&gt; with &lt;code&gt;beforeInteractive&lt;/code&gt;</td><td>Still rendered inside React tree — same warning</td></tr><tr><td>Remove &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;, put Script in body</td><td>Same warning — it&apos;s inside &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; which is React-managed</td></tr><tr><td>&lt;code&gt;next-themes@1.0.0-beta.0&lt;/code&gt;</td><td>Beta, no release date, unclear if fixed</td></tr></tbody></table>
<h2>الحل: Zustand مع useServerInsertedHTML</h2>
<p>الفكرة الجوهرية: <code>useServerInsertedHTML</code> هو خطّاف Next.js يحقن HTML في تدفّق التصيير من الخادم SSR <strong>خارج شجرة مكوّنات React</strong>. ينتهي السكربت داخل HTML لكن React لا «يراه» أبدًا أثناء التصيير على العميل — ومن ثمّ لا تحذير. وعند دمجه مع مخزن Zustand لإدارة حالة السمة التفاعلية، نحصل على بديل كامل بصفر تبعيات.</p>
<h3>كيف يعمل</h3>
<pre><code>SSR (server):
  useServerInsertedHTML → injects &lt;script&gt; into HTML stream
  ↓
  Browser receives HTML with theme script already in it
  ↓
  Script runs BEFORE React hydrates → correct class on &lt;html&gt;
  ↓
  No flash. No mismatch. No warning.

Client (after hydration):
  useEffect → _init() → Zustand store syncs with localStorage
  ↓
  useTheme() → reactive theme state for components</code></pre>
<h3>الخطوة 1: مخزن Zustand</h3>
<p>يدير المخزن حالة السمة، ويطبّق الأصناف على DOM، ويتولّى كشف سمة النظام، ويزامن بين علامات التبويب. تُرجِع دالة <code>_init()</code> دالة تنظيف تُستخدم داخل <code>useEffect</code>:</p>
<pre><code>import { create } from &quot;zustand&quot;;

const STORAGE_KEY = &quot;theme&quot;;
const MEDIA_QUERY = &quot;(prefers-color-scheme: dark)&quot;;

function getSystemTheme(): &quot;light&quot; | &quot;dark&quot; {
  return window.matchMedia(MEDIA_QUERY).matches ? &quot;dark&quot; : &quot;light&quot;;
}

function applyTheme(resolved: string, disableTransition: boolean) {
  const d = document.documentElement;

  if (disableTransition) {
    const style = document.createElement(&quot;style&quot;);
    style.appendChild(
      document.createTextNode(
        &quot;*,*::before,*::after{transition:none!important}&quot;
      )
    );
    document.head.appendChild(style);
    window.getComputedStyle(document.body);
    setTimeout(() =&gt; document.head.removeChild(style), 1);
  }

  d.classList.remove(&quot;light&quot;, &quot;dark&quot;);
  d.classList.add(resolved);
  d.style.colorScheme = resolved;
}

interface ThemeState {
  theme: string;
  systemTheme: &quot;light&quot; | &quot;dark&quot;;
  resolvedTheme: string;
  setTheme: (theme: string) =&gt; void;
  _init: (disableTransition: boolean) =&gt; () =&gt; void;
}

export const useThemeStore = create&lt;ThemeState&gt;((set, get) =&gt; ({
  theme: &quot;system&quot;,
  systemTheme: &quot;light&quot;,
  resolvedTheme: &quot;light&quot;,

  setTheme: (newTheme) =&gt; {
    const { systemTheme, disableTransitionOnChange } = get();
    const resolved = newTheme === &quot;system&quot; ? systemTheme : newTheme;
    localStorage.setItem(STORAGE_KEY, newTheme);
    applyTheme(resolved, disableTransitionOnChange);
    set({ theme: newTheme, resolvedTheme: resolved });
  },

  _init: (disableTransition) =&gt; {
    const stored = localStorage.getItem(STORAGE_KEY) || &quot;system&quot;;
    const system = getSystemTheme();
    const resolved = stored === &quot;system&quot; ? system : stored;

    set({ theme: stored, systemTheme: system, resolvedTheme: resolved });
    applyTheme(resolved, disableTransition);

    // System theme changes
    const mq = window.matchMedia(MEDIA_QUERY);
    const onChange = (e: MediaQueryListEvent) =&gt; {
      const newSystem = e.matches ? &quot;dark&quot; : &quot;light&quot;;
      const { theme } = get();
      set({ systemTheme: newSystem,
            resolvedTheme: theme === &quot;system&quot; ? newSystem : theme });
      if (theme === &quot;system&quot;) applyTheme(newSystem, disableTransition);
    };
    mq.addEventListener(&quot;change&quot;, onChange);

    // Cross-tab sync
    const onStorage = (e: StorageEvent) =&gt; {
      if (e.key !== STORAGE_KEY || !e.newValue) return;
      const system = getSystemTheme();
      const resolved = e.newValue === &quot;system&quot; ? system : e.newValue;
      set({ theme: e.newValue, resolvedTheme: resolved });
      applyTheme(resolved, disableTransition);
    };
    window.addEventListener(&quot;storage&quot;, onStorage);

    return () =&gt; {
      mq.removeEventListener(&quot;change&quot;, onChange);
      window.removeEventListener(&quot;storage&quot;, onStorage);
    };
  },
}));

export function useTheme() {
  return useThemeStore((s) =&gt; ({
    theme: s.theme,
    setTheme: s.setTheme,
    resolvedTheme: s.resolvedTheme,
    systemTheme: s.systemTheme,
  }));
}</code></pre>
<h3>الخطوة 2: ThemeProvider</h3>
<p>يقوم المزوّد بأمرين: حقن سكربت منع الوميض FOUC عبر <code>useServerInsertedHTML</code>، وتهيئة مخزن Zustand عند التركيب:</p>
<pre><code>&quot;use client&quot;

import { useEffect } from &quot;react&quot;
import { useServerInsertedHTML } from &quot;next/navigation&quot;
import { useThemeStore } from &quot;@/store/use-theme-store&quot;

const THEME_INIT_SCRIPT = `(function(){
  try {
    var t = localStorage.getItem(&quot;theme&quot;) || &quot;system&quot;;
    var r = t;
    if (t === &quot;system&quot;) {
      r = window.matchMedia(&quot;(prefers-color-scheme: dark)&quot;).matches
        ? &quot;dark&quot; : &quot;light&quot;;
    }
    document.documentElement.classList.remove(&quot;light&quot;, &quot;dark&quot;);
    document.documentElement.classList.add(r);
    document.documentElement.style.colorScheme = r;
  } catch(e) {}
})()`

export function ThemeProvider({
  children,
  disableTransitionOnChange = false,
}) {
  // Injects script into SSR HTML outside the React tree
  // — prevents FOUC without triggering React 19 warning
  useServerInsertedHTML(() =&gt; (
    &lt;script dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }} /&gt;
  ))

  useEffect(() =&gt; {
    return useThemeStore.getState()._init(disableTransitionOnChange)
  }, [disableTransitionOnChange])

  return &lt;&gt;{children}&lt;/&gt;
}</code></pre>
<h3>الخطوة 3: Layout</h3>
<pre><code>import { ThemeProvider } from &quot;@/providers/theme-provider&quot;

export default function RootLayout({ children }) {
  return (
    &lt;html lang=&quot;en&quot; suppressHydrationWarning&gt;
      &lt;body&gt;
        &lt;ThemeProvider disableTransitionOnChange&gt;
          {children}
        &lt;/ThemeProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  )
}</code></pre>
<h3>الخطوة 4: استخدمه</h3>
<pre><code>import { useTheme } from &quot;@/store/use-theme-store&quot;

export function ThemeSwitch() {
  const { theme, setTheme } = useTheme();

  return (
    &lt;button onClick={() =&gt; setTheme(
      theme === &quot;dark&quot; ? &quot;light&quot; : &quot;dark&quot;
    )}&gt;
      {theme === &quot;dark&quot; ? &quot;☀️&quot; : &quot;🌙&quot;}
    &lt;/button&gt;
  );
}</code></pre>
<h2>الانتقال من next-themes</h2>
<p>الواجهة البرمجية متطابقة عن قصد. الانتقال هو تغيير استيراد واحد لكل ملف:</p>
<pre><code>- import { useTheme } from &quot;next-themes&quot;
+ import { useTheme } from &quot;@/store/use-theme-store&quot;

  // API is identical — no other changes needed
  const { theme, setTheme, resolvedTheme, systemTheme } = useTheme()</code></pre>
<h2>مقارنة</h2>
<table><thead><tr><th></th><th>next-themes</th><th>Zustand + useServerInsertedHTML</th></tr></thead><tbody><tr><td>React 19 warning</td><td>Yes — &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; in client component</td><td>No</td></tr><tr><td>FOUC prevention</td><td>Yes</td><td>Yes</td></tr><tr><td>Cross-tab sync</td><td>Yes</td><td>Yes</td></tr><tr><td>System theme detection</td><td>Yes</td><td>Yes</td></tr><tr><td>&lt;code&gt;disableTransitionOnChange&lt;/code&gt;</td><td>Yes</td><td>Yes</td></tr><tr><td>Bundle size</td><td>~3.5 KB</td><td>~1.5 KB (uses existing Zustand)</td></tr><tr><td>Dependencies</td><td>+1 (next-themes)</td><td>0 (Zustand already in project)</td></tr><tr><td>Maintenance risk</td><td>Abandoned since March 2025</td><td>Your own code</td></tr></tbody></table>
<h2>لماذا لا نستخدم البدائل الأخرى؟</h2>
<h3>@wrksz/themes</h3>
<p>بديل جاهز للاستبدال المباشر يستخدم هو أيضًا <code>useServerInsertedHTML</code>. إنه يعمل، لكنه تبعية إضافية من صاحب صيانة واحد. وإن كان <code>next-themes</code> قد علّمنا شيئًا — فالتبعيات يتم التخلّي عنها. بنحو 100 سطر من الكود، يمكنك امتلاك الحل بالكامل.</p>
<h3>next-themes@1.0.0-beta.0</h3>
<p>موجود على npm، لكن بلا تاريخ إصدار، وبلا سجل تغييرات، وبلا إشارة واضحة إلى أن تحذير React 19 قد أُصلِح. المراهنة بكود الإنتاج على نسخة بيتا مفتوحة الأجل ليست مخاطرة تستحق العناء.</p>
<h3>بـ CSS فقط (prefers-color-scheme)</h3>
<p>يصلح لكشف سمة النظام، لكنه لا يستطيع التعامل مع استمرار تفضيل المستخدم (localStorage)، ولا التبديل اليدوي للسمة، ولا خيار «system». تحتاج إلى JavaScript لذلك.</p>
<h2>الخلاصات</h2>
<ol><li><code>next-themes</code> متروك فعليًا — آخر إصدار في مارس 2025، وتحذير React 19 لم يُصلَح</li><li><code>useServerInsertedHTML</code> هو العنصر البدائي الصحيح في Next.js لحقن السكربتات دون تحذيرات React</li><li>يوفّر Zustand حالة سمة تفاعلية بكود أقل من مزوّد Context</li><li>الحل بأكمله نحو 100 سطر، صفر تبعيات جديدة، وأنت تملك كل سطر</li></ol>
<h2>المصادر</h2>
<ul><li><a href="https://github.com/shadcn-ui/ui/issues/10104" target="_blank" rel="noopener noreferrer">shadcn/ui #10104</a> — Dark mode guide should address React 19 script warning</li><li><a href="https://github.com/pacocoursey/next-themes/issues/296" target="_blank" rel="noopener noreferrer">next-themes #296</a> — React 19 support request (open since 2024)</li><li><a href="https://github.com/facebook/react/issues/34008" target="_blank" rel="noopener noreferrer">React #34008</a> — Script tags not executing in components (by design)</li><li><a href="https://dev.to/wrksz/i-was-tired-of-next-themes-being-abandoned-so-i-built-a-drop-in-replacement-4dbk" target="_blank" rel="noopener noreferrer">@wrksz/themes</a> — Drop-in replacement using useServerInsertedHTML</li><li><a href="https://nextjs.org/docs/app/building-your-application/styling/css#server-inserted-html" target="_blank" rel="noopener noreferrer">Next.js Docs</a> — useServerInsertedHTML API</li></ul>]]></content:encoded>
      <pubDate>Sat, 11 Apr 2026 00:00:00 GMT</pubDate>
      <category>next.js</category>
      <category>react-19</category>
      <category>dark-mode</category>
      <category>zustand</category>
      <category>performance</category>
    </item>
    <item>
      <title>كيف تخلّصت من 94 ملف CSS حاجب للعرض في Next.js 16 بميزة Turbopack موثَّقة بشكل ضعيف</title>
      <link>https://oleksiimazurenko.dev/ar/blog/eliminating-render-blocking-css</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/eliminating-render-blocking-css</guid>
      <description>بعد أيام من تجربة كل نهج — من experimental.inlineCss إلى حيل MutationObserver — اكتشفت Turbopack Import Attributes التي تحل مشكلة CSS الحاجب للعرض في Next.js App Router.</description>
      <content:encoded><![CDATA[<h2>المشكلة</h2>
<p>يستخدم تطبيقنا (<a href="https://promova.com" target="_blank" rel="noopener noreferrer">Promova</a>) إطار Next.js 16 مع <strong>Landing Builder</strong> — نظام يعتمد على نظام إدارة محتوى (CMS) يجمّع صفحات التسويق من نحو 90 مكوّن قسم مختلفًا (أقسام Hero وأسئلة شائعة وتسعير ومراجعات وغيرها). تعتمد البنية على <code>sectionRegistry.tsx</code> الذي يربط أسماء الأقسام باستدعاءات <code>next/dynamic()</code>:</p>
<pre><code>// sectionRegistry.tsx — imports all 90 sections
export const sectionRegistry = {
  HeroSection: dynamic(() =&gt; import(&apos;./HeroSection/HeroSection&apos;)),
  FAQSection: dynamic(() =&gt; import(&apos;./FAQSection/FAQSection&apos;)),
  ReviewsSection: dynamic(() =&gt; import(&apos;./ReviewsSection/ReviewsSection&apos;)),
  // ... 87 more
}</code></pre>
<p>لا تعرض صفحة الهبوط الواحدة سوى <strong>5-8 أقسام</strong>. لكن Lighthouse كان يُظهر ما يلي:</p>
<pre><code>Eliminate render-blocking resources
  94 CSS resources (~330 KB)
  Potential savings: 5,440 ms</code></pre>
<p><strong>لماذا؟</strong> يرى Turbopack جميع مسارات <code>import()</code> التسعين قابلةً للوصول، فيولّد <code>&lt;link rel="stylesheet"&gt;</code> لكل وحدة SCSS. حتى الأقسام التي لا تُعرَض أبدًا على الصفحة يُحقَن ملف CSS الخاص بها في <code>&lt;head&gt;</code>. هذا <a href="https://github.com/vercel/next.js/issues/62485" target="_blank" rel="noopener noreferrer">سلوك مؤكَّد ومتوقَّع</a> لموجّه App Router في Next.js. إذ لا يُقسَّم CSS إلى أجزاء لاستيرادات <code>dynamic()</code> القادمة من Server Components. يعدّه المشرفون مقايضةً متعمَّدة لتفادي وميض المحتوى غير المنسَّق (FOUC). <strong>ولا يُخطَّط لإصلاحه.</strong></p>
<h2>كل ما جرّبته (ولماذا فشل)</h2>
<p>قضيت أيامًا أستعرض كل نهج أمكنني إيجاده. إليك القائمة الكاملة:</p>
<table><thead><tr><th>Approach</th><th>Why it doesn&apos;t work</th></tr></thead><tbody><tr><td>Split &lt;code&gt;sectionRegistry&lt;/code&gt; into per-section files</td><td>CSS still loaded — &lt;a href=&quot;https://github.com/vercel/next.js/issues/61066&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#61066&lt;/a&gt;</td></tr><tr><td>&lt;code&gt;experimental.inlineCss: true&lt;/code&gt;</td><td>Inlines CSS on &lt;strong&gt;every SSR request&lt;/strong&gt; — crashed our CMS under load</td></tr><tr><td>&lt;code&gt;experimental.optimizeCss&lt;/code&gt; (Critters)</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/issues/62485&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Incompatible with streaming&lt;/a&gt; in App Router</td></tr><tr><td>CSS-in-JS rewrite</td><td>Rewriting 90 sections from SCSS to CSS-in-JS is not realistic</td></tr><tr><td>&lt;code&gt;media=&quot;print&quot;&lt;/code&gt; hack</td><td>Doesn&apos;t work with CSS Modules in Turbopack — &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tags are managed by the framework</td></tr><tr><td>Switch back to Webpack</td><td>Has &lt;code&gt;experiments.css&lt;/code&gt; options, but we&apos;re committed to Turbopack</td></tr><tr><td>Turbopack plugins</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/discussions/85465&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Don&apos;t exist&lt;/a&gt; — no plugin API</td></tr><tr><td>Turbopack CSS loaders</td><td>Not supported — only JS output</td></tr><tr><td>SWC plugins for CSS</td><td>SWC only processes JavaScript</td></tr><tr><td>Client Component wrapper for &lt;code&gt;dynamic()&lt;/code&gt;</td><td>Registry is a global constant — bundler sees all dependencies</td></tr><tr><td>Next.js middleware HTML rewrite</td><td>&lt;a href=&quot;https://github.com/orgs/vercel/discussions/3874&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Middleware can&apos;t modify response body&lt;/a&gt;</td></tr><tr><td>Suspense + streaming with async import</td><td>React Float always pulls CSS into initial &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;</td></tr><tr><td>Suspense + 3s delay</td><td>Content streams later, but CSS is in the initial &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; chunk</td></tr><tr><td>&lt;code&gt;experimental.cssChunking&lt;/code&gt;</td><td>Already default. Merges chunks but doesn&apos;t remove irrelevant CSS</td></tr><tr><td>Post-build Beasties/Critters</td><td>Only for static export, &lt;a href=&quot;https://github.com/vercel/next.js/discussions/59989&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;not with ISR&lt;/a&gt;</td></tr><tr><td>npm libraries (next-critical, fg-loadcss)</td><td>Pages Router only or abandoned (6+ years)</td></tr><tr><td>&lt;code&gt;inlineCss&lt;/code&gt; per-route exclusion</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/pull/72195&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;Not supported&lt;/a&gt; — all-or-nothing global flag</td></tr><tr><td>Turbopack &lt;code&gt;as: &apos;raw&apos;&lt;/code&gt; for SCSS</td><td>Returns &lt;code&gt;undefined&lt;/code&gt; instead of text</td></tr><tr><td>Turbopack rule for &lt;code&gt;*.inline.module.scss&lt;/code&gt;</td><td>Turbopack intercepts &lt;code&gt;.module.scss&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; custom rules</td></tr><tr><td>Turbopack rule for &lt;code&gt;*.inline.scss&lt;/code&gt;</td><td>Turbopack intercepts &lt;code&gt;.scss&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; custom rules</td></tr><tr><td>MutationObserver &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;</td><td>Tested in dev, risky — CSS still loads as separate files, possible FOUC</td></tr></tbody></table>
<h3>فخّ inlineCss</h3>
<p>يمتلك Next.js خيار <a href="https://github.com/vercel/next.js/pull/72195" target="_blank" rel="noopener noreferrer"><code>experimental.inlineCss</code></a> تستبدل جميع وسوم <code>&lt;link rel="stylesheet"&gt;</code> بوسوم <code>&lt;style&gt;</code> مضمَّنة. يبدو ذلك مثاليًا، أليس كذلك؟</p>
<p>المشكلة أنه <strong>إما الكل أو لا شيء</strong>. لا يمكنك تفعيله لكل مسار على حدة. إن كانت لديك صفحات SSR (<code>force-dynamic</code>) فإن كل طلب يعيد بناء كل CSS بشكل مضمَّن. جرّبنا ذلك — فلم يحتمل نظام CMS بلا واجهة (headless) لدينا الحِمل وتعطّل. ولكي يعمل بأمان، يجب أن تكون <strong>100% من صفحاتك</strong> بنمط <code>force-static</code> أو ISR. ومع أكثر من 20 صفحة SSR (مصادقة، ولوحات تحكم، وصفحات ديناميكية) فتلك عملية ترحيل ضخمة.</p>
<h2>الاكتشاف: Turbopack Import Attributes</h2>
<p>أثناء تنقيبي في <a href="https://nextjs.org/blog/next-16-2-turbopack#inline-loader-configuration" target="_blank" rel="noopener noreferrer">ملاحظات إصدار Next.js 16.2</a>، عثرت على ميزة موثَّقة بشكل ضعيف: <strong>Turbopack Import Attributes</strong>. تتيح لك تجاوز خط أنابيب المجمِّع المدمَج لاستيراد محدَّد باستخدام صياغة <code>with {}</code> من TC39:</p>
<pre><code>import { cssText, styles } from &apos;./hero.module.scss&apos; with {
  turbopackLoader: &apos;@promova/scss-to-inline-loader&apos;,
  turbopackAs: &apos;*.js&apos;
}</code></pre>
<p>يقول هذا لـ Turbopack: <em>«لا تعالج هذا الاستيراد بوصفه ورقة أنماط. مرِّره عبر الـ loader المخصَّص لديّ وتعامل مع الخرج بوصفه JavaScript.»</em></p>
<p><strong>هذه هي الفكرة الجوهرية.</strong> بدلًا من أن يولّد Turbopack وسم <code>&lt;link rel="stylesheet"&gt;</code> يحجب العرض، يقوم الـ loader لدينا بترجمة SCSS وتصديره كسلسلة JavaScript. ثم نحقنه بوصفه وسم <code>&lt;style&gt;</code> مضمَّنًا مباشرة داخل المكوّن. النتيجة: <strong>لا يصل إلى HTML الصفحة سوى CSS الأقسام التي تُعرَض فعلًا</strong>. تحصل الصفحة ذات الأقسام الخمسة على CSS لخمسة أقسام — لا لـ 94.</p>
<h2>الحل</h2>
<h3>1. Turbopack Loader مخصَّص</h3>
<p>نص Node.js بنحو 70 سطرًا كحزمة ضمن مساحة عمل yarn (<code>@promova/scss-to-inline-loader</code>):</p>
<pre><code>const { createHash } = require(&apos;node:crypto&apos;)
const sass = require(&apos;sass&apos;)
const postcss = require(&apos;postcss&apos;)
const postcssModules = require(&apos;postcss-modules&apos;)
const path = require(&apos;path&apos;)

const SHARED_STYLES = path.resolve(__dirname, &apos;../shared/styles&apos;)

// Same prependData as sassOptions.prependData in next.config.ts
const PREPEND_DATA = `
@use &quot;sass:math&quot; as math;
@use &quot;sass:list&quot; as list;
@use &quot;${SHARED_STYLES}/_variables.scss&quot; as *;
@use &quot;${SHARED_STYLES}/_breakpoints.scss&quot; as *;
@use &quot;${SHARED_STYLES}/_mixins.scss&quot; as *;
`

module.exports = function scssToInlineLoader(source) {
  const callback = this.async()
  processScss(source, this.resourcePath)
    .then((js) =&gt; callback(null, js))
    .catch((err) =&gt; callback(err))
}

async function processScss(source, resourcePath) {
  // 1. Compile SCSS → CSS (with global variables, mixins, breakpoints)
  const sassResult = sass.compileString(PREPEND_DATA + &apos;\n&apos; + source, {
    loadPaths: [path.dirname(resourcePath), SHARED_STYLES],
    style: &apos;compressed&apos;,
    sourceMap: false,
    url: new URL(&apos;file://&apos; + resourcePath),
  })

  // 2. Scope class names with postcss-modules (CSS Modules compatible)
  let classNames = {}
  const result = await postcss([
    postcssModules({
      generateScopedName(name, filename) {
        const file = path.basename(filename, path.extname(filename))
          .replace(&apos;.module&apos;, &apos;&apos;)
        const hash = createHash(&apos;md5&apos;)
          .update(filename + name)
          .digest(&apos;base64url&apos;)
          .slice(0, 6)
        return `${file}__${name}__${hash}`
      },
      getJSON(_, json) { classNames = json },
    }),
  ]).process(sassResult.css, { from: resourcePath })

  // 3. Export as JS module — same interface as CSS Modules
  return [
    `export const cssText = ${JSON.stringify(result.css)};`,
    `export const styles = ${JSON.stringify(classNames)};`,
    `export default styles;`,
  ].join(&apos;\n&apos;)
}</code></pre>
<p>ما يفعله: <strong><code>styles</code></strong> — خريطة أسماء الأصناف محدَّدة النطاق (scoped) ذاتها الموجودة في CSS Modules القياسية. <strong><code>cssText</code></strong> — ملف CSS المترجَم كسلسلة نصية.</p>
<h3>2. مكوّن InlineStyle</h3>
<p>يستخدم واجهة <code>&lt;style href precedence&gt;</code> المدمَجة في React 19 لإزالة التكرار تلقائيًا:</p>
<pre><code>export function InlineStyle({ css, id }: { css: string; id: string }) {
  return (
    &lt;style href={id} precedence=&quot;default&quot;&gt;
      {css}
    &lt;/style&gt;
  )
}</code></pre>
<p>يضمن React 19: نفس قيمة <code>href</code> → وسم <code>&lt;style&gt;</code> واحد فقط في DOM. فإن عُرِض قسم مرتين على الصفحة، يُحقَن CSS مرة واحدة رغم ذلك.</p>
<h3>3. ترحيل لكل مكوّن (نحو 6 أسطر لكل قسم)</h3>
<pre><code>// BEFORE: Turbopack → &lt;link rel=&quot;stylesheet&quot;&gt; (render-blocking)
import styles from &apos;./hero_section.module.scss&apos;</code></pre>
<pre><code>// AFTER: Custom loader → JS module → inline &lt;style&gt; (non-blocking)
import { cssText, styles } from &apos;./hero_section.module.scss&apos; with {
  turbopackLoader: &apos;@promova/scss-to-inline-loader&apos;,
  turbopackAs: &apos;*.js&apos;
}
import { InlineStyle } from &apos;@promova/scss-to-inline-loader/InlineStyle&apos;

export function HeroSection() {
  return (
    &lt;section className={styles.hero}&gt;
      &lt;InlineStyle css={cssText} id=&quot;hero-section&quot; /&gt;
      {/* ... component JSX, className usage is identical */}
    &lt;/section&gt;
  )
}</code></pre>
<p><strong>تبقى ملفات <code>.module.scss</code> كما هي تمامًا.</strong> لا إعادة كتابة لـ CSS. Stylelint وPrettier ودعم بيئة التطوير (IDE) — كلها محفوظة.</p>
<h2>لماذا هذا أفضل من inlineCss: true</h2>
<p>هذا هو الفرق الجوهري:</p>
<table><thead><tr><th></th><th>&lt;code&gt;inlineCss: true&lt;/code&gt;</th><th>Import Attributes + Loader</th></tr></thead><tbody><tr><td>What gets inlined</td><td>&lt;strong&gt;ALL CSS on the page&lt;/strong&gt; (94 files)</td><td>&lt;strong&gt;Only CSS for rendered sections&lt;/strong&gt; (5-8 files)</td></tr><tr><td>SSR overhead</td><td>CSS rebuilt on every request</td><td>CSS compiled at build time</td></tr><tr><td>Per-route control</td><td>No (all-or-nothing)</td><td>Yes (per-import)</td></tr><tr><td>SSR pages safety</td><td>Risky (overloads server)</td><td>Safe (component-level)</td></tr></tbody></table>
<p>عند استخدام <code>inlineCss: true</code>، تحصل الصفحة رغم ذلك على جميع أوراق الأنماط الأربع والتسعين مضمَّنة — لكنها وسوم <code>&lt;style&gt;</code> بدلًا من وسوم <code>&lt;link&gt;</code>. يتضخّم HTML. أما مع نهجنا، فإن <strong>ما يصل إلى HTML هو CSS الذي يُعرَض فعلًا لا غير</strong>. عُرِضت 5 أقسام → 5 وسوم <code>&lt;style&gt;</code>. هذا كل شيء.</p>
<h2>مطبّ Turbopack: لا قواعد عامة لـ .module.scss</h2>
<p>أحد المطبّات التي وقعت فيها: قد تظن أن بإمكانك إضافة قاعدة Turbopack في <code>next.config.ts</code> لتطبيق الـ loader عالميًا:</p>
<pre><code>// ❌ THIS CAUSES A FATAL PANIC
turbopack: {
  rules: {
    &apos;**/components/**/*.module.scss&apos;: {
      loaders: [&apos;@promova/scss-to-inline-loader&apos;],
      as: &apos;*.js&apos;
    }
  }
}</code></pre>
<p><strong>لا تفعل هذا.</strong> يعترض خط أنابيب وحدات CSS المدمَج في Turbopack ملفات <code>.module.scss</code> <strong>قبل</strong> تطبيق القواعد المخصَّصة. فيُنشئ أصلًا من نوع <code>EcmascriptCssModule</code> يتعارض مع خرج <code>as: '*.js'</code>، مسبِّبًا:</p>
<pre><code>FATAL PANIC: inner asset should be CSS processable</code></pre>
<p>تعمل سمات <code>with {}</code> لأنها توعز إلى Turbopack <strong>عند موضع الاستيراد</strong> بتجاوز خط أنابيب وحدات CSS كليًا. ولا سبيل إلى فعل ذلك عالميًا عبر ملف الإعداد.</p>
<h2>النتائج</h2>
<p>رحّلنا 127 مكوّن قسم عبر Landing Builder. وتم التحقق من بناء الإنتاج.</p>
<table><thead><tr><th>Metric</th><th>Before</th><th>After</th></tr></thead><tbody><tr><td>Render-blocking CSS files</td><td>94</td><td>0 (for landing sections)</td></tr><tr><td>CSS in HTML per page</td><td>~330 KB (all sections)</td><td>~20-40 KB (rendered sections only)</td></tr><tr><td>CSS delivery</td><td>&lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; (network request, blocking)</td><td>&lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; (inline, non-blocking)</td></tr><tr><td>Sass features</td><td>Full</td><td>Full (variables, mixins, nesting, @use)</td></tr><tr><td>CSS Modules scoping</td><td>Built-in</td><td>postcss-modules (compatible)</td></tr><tr><td>Code change per section</td><td>—</td><td>~6 lines (import + InlineStyle)</td></tr></tbody></table>
<h2>القيود</h2>
<ul><li><strong><code>with {}</code> لكل استيراد مُسهَب</strong> — يحتاج كل استيراد إلى 3 أسطر إضافية. ولا سبيل إلى تطبيقه عالميًا (انظر المطبّ أعلاه).</li><li><strong>Turbopack فقط</strong> — لا يدعم Webpack سمات <code>with {}</code>. وإن احتجت إلى بديل Webpack، تعود الاستيرادات إلى CSS Modules القياسية (وتكون قيمة <code>cssText</code> هي <code>undefined</code>).</li><li><strong>تجزئة أسماء الأصناف</strong> — يستخدم الـ loader لدينا خوارزمية تجزئة مختلفة عن تلك المدمَجة في Turbopack. تبدو أسماء الأصناف مختلفة في DevTools. لكنها متطابقة وظيفيًا.</li><li><strong>يزداد حجم HTML</strong> — يُضمَّن CSS داخل HTML بدلًا من تخزينه مؤقتًا كملفات منفصلة. أما للصفحات الثابتة/ISR المقدَّمة من CDN، فهذه ليست مشكلة.</li></ul>
<h2>متى تستخدم هذا</h2>
<p>تكون هذه التقنية أكثر فاعلية عندما:</p>
<ol><li><strong>يكون لديك نمط registry/barrel</strong> — ملف واحد يستورد مكوّنات كثيرة، لكن قليلًا منها فقط يُعرَض في كل صفحة</li><li><strong>تكون على Turbopack</strong> — سمات Import Attributes خاصة بـ Turbopack</li><li><strong>تريد تحكمًا لكل مكوّن</strong> — لا خيار «إما الكل أو لا شيء»</li><li><strong>تكون ملفات SCSS لديك معقّدة</strong> — متغيّرات وmixins ونقاط توقّف (breakpoints) وتداخل — كلها مدعومة</li><li><strong>لا يمكنك استخدام <code>experimental.inlineCss</code></strong> — لأن لديك صفحات SSR أو تريد تحكمًا دقيقًا</li></ol>
<h2>مشكلات GitHub ذات الصلة</h2>
<p>إن كنت متأثرًا بمشكلة CSS الحاجب للعرض في Next.js App Router، فأنت لست وحدك:</p>
<h3>المشكلة الجوهرية</h3>
<ul><li><a href="https://github.com/vercel/next.js/issues/62485" target="_blank" rel="noopener noreferrer"><strong>#62485</strong></a> — Render blocking CSS (maintainers: "expected behavior")</li><li><a href="https://github.com/vercel/next.js/issues/61066" target="_blank" rel="noopener noreferrer"><strong>#61066</strong></a> — Dynamic imports from Server Components are not code-split</li><li><a href="https://github.com/vercel/next.js/issues/54935" target="_blank" rel="noopener noreferrer"><strong>#54935</strong></a> — Server-side dynamic imports don't split client modules</li><li><a href="https://github.com/vercel/next.js/issues/61574" target="_blank" rel="noopener noreferrer"><strong>#61574</strong></a> — JS/CSS code splitting doesn't work as documented</li><li><a href="https://github.com/vercel/next.js/issues/57634" target="_blank" rel="noopener noreferrer"><strong>#57634</strong></a> — Add support for critical CSS inlining with App Router</li><li><a href="https://github.com/vercel/next.js/issues/50300" target="_blank" rel="noopener noreferrer"><strong>#50300</strong></a> — next/dynamic on server component does not build CSS modules</li></ul>
<h3>ميزة inlineCss ومشكلاتها</h3>
<ul><li><a href="https://github.com/vercel/next.js/pull/72195" target="_blank" rel="noopener noreferrer"><strong>PR #72195</strong></a> — experimental: css inlining (the implementation)</li><li><a href="https://github.com/vercel/next.js/pull/73182" target="_blank" rel="noopener noreferrer"><strong>PR #73182</strong></a> — Don't inline CSS in RSC payload for client navigation</li><li><a href="https://github.com/vercel/next.js/issues/75648" target="_blank" rel="noopener noreferrer"><strong>#75648</strong></a> — WhatsApp preview broken with inlineCss + Tailwind</li><li><a href="https://github.com/vercel/next.js/issues/83612" target="_blank" rel="noopener noreferrer"><strong>#83612</strong></a> — Turbopack wrong font URL with inline CSS</li></ul>
<h3>المجتمع يطلب حلولًا</h3>
<ul><li><a href="https://github.com/vercel/next.js/discussions/82894" target="_blank" rel="noopener noreferrer"><strong>Discussion #82894</strong></a> — How to prevent render-blocking with CSS Modules? (2025)</li><li><a href="https://github.com/vercel/next.js/discussions/70526" target="_blank" rel="noopener noreferrer"><strong>Discussion #70526</strong></a> — Ideas for reducing render-blocking CSS</li><li><a href="https://github.com/vercel/next.js/discussions/59814" target="_blank" rel="noopener noreferrer"><strong>Discussion #59814</strong></a> — Render blocking styles with Tailwind</li><li><a href="https://github.com/vercel/next.js/discussions/49691" target="_blank" rel="noopener noreferrer"><strong>Discussion #49691</strong></a> — How to deal with render-blocking modular CSS</li><li><a href="https://github.com/vercel/next.js/discussions/59989" target="_blank" rel="noopener noreferrer"><strong>Discussion #59989</strong></a> — Critical CSS inlining with App Router</li><li><a href="https://github.com/vercel/next.js/discussions/80486" target="_blank" rel="noopener noreferrer"><strong>Discussion #80486</strong></a> — Is optimizeCss still in use?</li><li><a href="https://github.com/vercel/next.js/discussions/85465" target="_blank" rel="noopener noreferrer"><strong>Discussion #85465</strong></a> — Turbopack plugin API (doesn't exist)</li></ul>
<h3>علل CSS في Turbopack</h3>
<ul><li><a href="https://github.com/vercel/next.js/issues/68412" target="_blank" rel="noopener noreferrer"><strong>#68412</strong></a> — Incorrect CSS modules load order with external components</li><li><a href="https://github.com/vercel/next.js/issues/76464" target="_blank" rel="noopener noreferrer"><strong>#76464</strong></a> — Turbopack sometimes strips SCSS imports</li><li><a href="https://github.com/vercel/next.js/issues/82497" target="_blank" rel="noopener noreferrer"><strong>#82497</strong></a> — SCSS module not loading when not-found.tsx is present</li><li><a href="https://github.com/vercel/next.js/issues/88544" target="_blank" rel="noopener noreferrer"><strong>#88544</strong></a> — Exporting Sass variables from CSS modules doesn't work with Turbopack</li></ul>
<p>صُنِع في <a href="https://promova.com" target="_blank" rel="noopener noreferrer">Promova</a> — منصة لتعلّم اللغات تخدم ملايين المستخدمين. إن كنت تصارع المشكلة ذاتها في CSS، فلا تتردد في الإشارة إلى هذا المقال في مشكلات GitHub أعلاه.</p>]]></content:encoded>
      <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
      <category>Next.js</category>
      <category>CSS</category>
      <category>Performance</category>
      <category>Turbopack</category>
      <category>SCSS</category>
    </item>
    <item>
      <title>تسريب ذاكرة Next.js: Fetch + وضع Standalone — سنتان دون إصلاح</title>
      <link>https://oleksiimazurenko.dev/ar/blog/nextjs-memory-leak-fetch-standalone</link>
      <guid isPermaLink="true">https://oleksiimazurenko.dev/ar/blog/nextjs-memory-leak-fetch-standalone</guid>
      <description>يقوم Next.js بترقيع دالة fetch العامة ويضيف طبقة تخزين مؤقت تتسرب منها الذاكرة مع كل طلب. في Docker/K8s يؤدي هذا إلى انهيارات OOM كل بضع ساعات. توجد العلة منذ Next.js 14 ولا تزال دون حل في 16.2.x.</description>
      <content:encoded><![CDATA[<h2>TL;DR</h2>
<p>يقوم Next.js بترقيع دالة <code>fetch</code> العامة ويضيف طبقة تخزين مؤقت تحتفظ بمراجع إلى بيانات الاستجابة بعد أن كان ينبغي تحريرها. كل استدعاء لـ <code>fetch</code> يضيف ذاكرة لا تُعاد أبدًا إلى الـ GC. في Docker/Kubernetes يؤدي هذا إلى انهيارات OOM كل بضع ساعات. توجد هذه العلة منذ Next.js 14 (أبريل 2024) ولا تزال دون حل في 16.2.x (مارس 2026). أما على Vercel فلا تظهر المشكلة بفضل دوال serverless العابرة.</p>
<h2>كيف يعمل fetch العادي</h2>
<pre><code>Request → fetch → got data → response to user → GC cleans up → memory free</code></pre>
<h2>كيف يعمل fetch في Next.js</h2>
<p>يعترض Next.js دالة <code>fetch</code> العامة ويلفّها بطبقة تخزين مؤقت/تتبّع خاصة به:</p>
<pre><code>Request → Next.js fetch wrapper → creates:
  - Performance entry (speed tracking)    ← not cleaned
  - Request metadata object               ← not cleaned
  - Internal promise wrapper              ← not cleaned
  - Cache lookup entry                    ← not cleaned
  - Response body (for unique URL)        ← not cleaned
→ Response to user
→ GC sees objects are still referenced → doesn&apos;t touch them
→ Memory grows indefinitely</code></pre>
<h2>ماذا يحدث في بيئة الإنتاج</h2>
<pre><code>Self-hosted (Docker/K8s):
  Request 1:      fetch → +100KB → RAM: 100KB
  Request 2:      fetch → +100KB → RAM: 200KB
  Request 1000:   fetch → +100KB → RAM: 100MB
  Request 50,000: fetch → +100KB → RAM: 5GB → OOM Kill 💀
  → Kubernetes restarts the pod → cycle repeats

Vercel (Serverless):
  Request 1: [start] → fetch → +100KB → [process dies] → 0 KB ✅
  Request 2: [start] → fetch → +100KB → [process dies] → 0 KB ✅
  → Memory never accumulates</code></pre>
<h2>الإصدارات المتأثرة</h2>
<h3>Next.js — كل الإصدارات التي تستخدم App Router</h3>
<table><thead><tr><th>Version</th><th>Issue</th><th>Date</th></tr></thead><tbody><tr><td>14.2.x</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/issues/64212&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#64212&lt;/a&gt;</td><td>April 2024</td></tr><tr><td>14.x-15.x</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/issues/68578&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#68578&lt;/a&gt;</td><td>August 2024</td></tr><tr><td>14.3.0-canary</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/issues/79588&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#79588&lt;/a&gt;</td><td>May 2025</td></tr><tr><td>16.0.1</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/issues/85914&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#85914&lt;/a&gt;</td><td>November 2025</td></tr><tr><td>16.1.0</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/discussions/88603&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#88603&lt;/a&gt;</td><td>January 2026</td></tr><tr><td>16.0.10</td><td>&lt;a href=&quot;https://github.com/vercel/next.js/issues/90433&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;#90433&lt;/a&gt;</td><td>February 2026</td></tr><tr><td>16.2.0-canary.51</td><td>Confirmed in #90433 comments</td><td>March 2026</td></tr></tbody></table>
<h3>Node.js</h3>
<p>تم الاختبار على Node.js 20 و22 و24 و25 — تتسرب الذاكرة في جميعها.</p>
<h2>ما الذي لا ينفع</h2>
<table><thead><tr><th>Attempt</th><th>Result</th></tr></thead><tbody><tr><td>&lt;code&gt;cacheMaxMemorySize: 0&lt;/code&gt;</td><td>Doesn&apos;t help — leak is not from this cache</td></tr><tr><td>Disable image optimization</td><td>Doesn&apos;t help</td></tr><tr><td>&lt;code&gt;--max-old-space-size=6144&lt;/code&gt;</td><td>Just crashes slower</td></tr><tr><td>Read response via &lt;code&gt;.json()&lt;/code&gt; / &lt;code&gt;.text()&lt;/code&gt;</td><td>Doesn&apos;t help</td></tr><tr><td>Remove React fetch patching (canary.45)</td><td>Doesn&apos;t help</td></tr><tr><td>Upgrade to latest version</td><td>Doesn&apos;t help (leaks on 16.2.0-canary.51 too)</td></tr></tbody></table>
<h2>ما الذي ينفع (حلول التفافية)</h2>
<table><thead><tr><th>Workaround</th><th>Why it helps</th></tr></thead><tbody><tr><td>Replace &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;axios&lt;/code&gt;</td><td>Bypasses Next.js wrapper</td></tr><tr><td>Replace &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;node-fetch&lt;/code&gt;</td><td>Same reason</td></tr><tr><td>Downgrade Node.js to 20.15.1</td><td>Older undici has fewer leaks (for some)</td></tr><tr><td>Docker image &lt;code&gt;node:20-alpine3.21&lt;/code&gt;</td><td>Helps in some cases</td></tr><tr><td>Deploy to Lambda (SST + OpenNext)</td><td>Ephemeral functions — memory doesn&apos;t accumulate</td></tr><tr><td>Deploy to Vercel</td><td>Same — serverless</td></tr></tbody></table>
<h3>قيود الحل الالتفافي باستخدام axios</h3>
<p>يمكنك استبدال استدعاءات الـ API الخاصة بك بـ axios. لكن Next.js يستخدم داخليًا نسخة fetch المرقّعة من أجل:</p>
<ul><li>ISR (Incremental Static Regeneration)</li><li><code>revalidatePath</code> / <code>revalidateTag</code></li><li>جلب بيانات Server Components مع إزالة التكرار</li><li><code>use cache</code> (Next.js 16)</li></ul>
<p>أي حتى من دون استدعاء واحد لـ <code>fetch</code> في شيفرتك — يظل Next.js يستخدمه في الخفاء.</p>
<h2>لماذا لا تُصلح Vercel المشكلة</h2>
<h3>منطق العمل التجاري</h3>
<p>Vercel شركة تجني المال من استضافة Next.js. على منصتها لا تظهر المشكلة (serverless = عابر). العلة تؤثر فقط على الاستضافة الذاتية (Docker وK8s وVPS) — أي على من لا يدفع لـ Vercel.</p>
<h3>الموقف الرسمي</h3>
<p>قام Tim Neutkens (أحد مشرفي Vercel) بتحليل المشكلة وأعلن أنها مشكلة <strong>undici</strong> (مكتبة fetch في Node.js) وليست مشكلة Next.js. وأُغلق Issue #90433. وذلك رغم أن:</p>
<ul><li>axios وnode-fetch يعملان على نفس Node.js دون تسريبات</li><li>التسريب لا يظهر إلا عندما يمر fetch عبر غلاف Next.js</li><li>العلة مفتوحة منذ سنتين دون إصلاح</li></ul>
<h3>الأولويات</h3>
<p>خلال هاتين السنتين أصدر فريق Next.js ما يلي:</p>
<ul><li>Turbopack (بناء أسرع بمقدار 2-5x) — ميزة تسويقية</li><li>Cache Components / <code>use cache</code> — يقلل الحِمل على خوادم Vercel</li><li><code>proxy.ts</code> بدلًا من middleware — يبسّط النشر على edge في Vercel</li><li>DevTools MCP — ضجّة الذكاء الاصطناعي</li></ul>
<p>تسريب الذاكرة في الاستضافة الذاتية؟ ليس من الأولويات.</p>
<h2>الحل: AWS Lambda (SST + OpenNext)</h2>
<h3>ما هذا</h3>
<p>OpenNext مُحوِّل مفتوح المصدر يحوّل بناء Next.js إلى صيغة تناسب AWS Lambda. أما SST فهو إطار عمل يؤتمت البنية التحتية.</p>
<h3>البنية المعمارية</h3>
<pre><code>Next.js build
  → OpenNext
    → AWS Lambda (SSR, API routes)
    → S3 (static, assets)
    → CloudFront (CDN)
    → SQS + DynamoDB (ISR revalidation)</code></pre>
<h3>لماذا يحل هذا تسريب الذاكرة</h3>
<p>تعالج دوال Lambda الطلبات ثم يُعاد تدويرها بعد 5-15 دقيقة من الخمول. فلا تجد الذاكرة وقتًا لتتراكم.</p>
<h3>النشر</h3>
<pre><code>npx sst@latest init
npx sst deploy --stage production</code></pre>
<h3>المقارنة</h3>
<table><thead><tr><th></th><th>Vercel</th><th>AWS Lambda (SST)</th><th>Docker self-hosted</th></tr></thead><tbody><tr><td>Memory leak</td><td>Not felt</td><td>Not felt</td><td>Critical</td></tr><tr><td>Cold starts</td><td>Yes</td><td>Yes (~200-500ms)</td><td>No</td></tr><tr><td>Price (~medium traffic)</td><td>$20-150/mo</td><td>$5-30/mo</td><td>$5-50/mo</td></tr><tr><td>Control</td><td>Minimal</td><td>Full</td><td>Full</td></tr><tr><td>ISR/Revalidation</td><td>Works</td><td>Works (SQS)</td><td>Works (with leak)</td></tr><tr><td>Vendor lock-in</td><td>Vercel</td><td>AWS</td><td>None</td></tr></tbody></table>
<h3>اعتبارات Lambda</h3>
<ul><li><strong>Cold starts</strong> — الطلب الأول أبطأ (~200-500ms)</li><li><strong>الأمان</strong> — فعّل OAC (Origin Access Control)، وإلا فإن رابط Lambda يكون علنيًا</li><li><strong>OpenNext</strong> — مشروع مجتمعي وليس رسميًا من Vercel. قد تتعطل ميزات Next.js الجديدة</li><li><strong>هجوم المحفظة</strong> — أثناء هجوم DDoS قد يؤدي التوسع التلقائي لـ Lambda إلى فاتورة ضخمة</li></ul>
<h2>لماذا يُعدّ الإصلاح الحقيقي غير واقعي</h2>
<h3>1. مشكلة معمارية</h3>
<p>التسريب ليس علة عرضية بل نتيجة قرار تصميمي: يعترض Next.js دالة <code>fetch</code> العامة ويضيف فوقها التخزين المؤقت/التتبّع. ولإصلاحه يلزم إعادة تصميم طريقة تفاعل App Router مع fetch. وهذا يمسّ ISR وrevalidation وdata cache وrequest deduplication — أي نواة الإطار.</p>
<h3>2. تضارب المصالح</h3>
<p>Vercel غير متحفزة لإصلاح ما لا يؤثر على منصتها. الاستضافة الذاتية تنافس عملها التجاري. وكلما زادت المشكلات في الاستضافة الذاتية — زاد عدد من ينتقلون إلى Vercel.</p>
<h3>3. تحميل المسؤولية لغيرها</h3>
<p>الموقف الرسمي هو «إنها undici، لسنا نحن». وحتى يتغير هذا — لن يعملوا على إصلاح.</p>
<h3>4. لا يوجد إصلاح من المجتمع</h3>
<p>رخصة AGPL-3.0 الخاصة بـ Next.js تسمح بالتفريعات، لكن قاعدة الشيفرة ضخمة ومترابطة بإحكام مع بنية Vercel التحتية. أي طلب دمج مجتمعي لإصلاح غلاف fetch سيتطلب فهمًا عميقًا للبنية الداخلية وموافقة المشرفين — الذين أغلقوا الـ issue بالفعل.</p>
<h2>الخلاصات</h2>
<ol><li><strong>إن كنت على Vercel</strong> — لا مشكلة، لا شيء تفعله</li><li><strong>إن كنت على استضافة ذاتية وتحتاج serverless</strong> — SST + OpenNext على AWS Lambda</li><li><strong>إن كنت على استضافة ذاتية بـ Docker</strong> — استبدل fetch بـ axios حيثما أمكن، وراقب الـ RAM، واضبط إعادة تشغيل تلقائية للـ pods</li><li><strong>إن كنت تبدأ مشروعًا جديدًا</strong> — فكّر في SvelteKit أو Nuxt كبدائل خالية من هذه المشكلة</li></ol>
<h2>المصادر</h2>
<ul><li><a href="https://github.com/vercel/next.js/issues/64212" target="_blank" rel="noopener noreferrer">Issue #64212</a> — Memory Leak with global fetch (April 2024)</li><li><a href="https://github.com/vercel/next.js/issues/68578" target="_blank" rel="noopener noreferrer">Issue #68578</a> — Possible memory leak in Fetch API (August 2024)</li><li><a href="https://github.com/vercel/next.js/issues/85914" target="_blank" rel="noopener noreferrer">Issue #85914</a> — Memory Leak with fetch + standalone (November 2025)</li><li><a href="https://github.com/vercel/next.js/issues/90433" target="_blank" rel="noopener noreferrer">Issue #90433</a> — OOM in 16.0.10 (February 2026)</li><li><a href="https://github.com/vercel/next.js/discussions/88603" target="_blank" rel="noopener noreferrer">Discussion #88603</a> — OOM in Docker/K8s (January 2026)</li><li><a href="https://github.com/vercel/next.js/discussions/88078" target="_blank" rel="noopener noreferrer">Discussion #88078</a> — cacheMaxMemorySize behavior</li><li><a href="https://opennext.js.org/" target="_blank" rel="noopener noreferrer">OpenNext</a></li><li><a href="https://sst.dev/docs/start/aws/nextjs/" target="_blank" rel="noopener noreferrer">SST — Next.js on AWS</a></li><li><a href="https://www.flightcontrol.dev/blog/secret-knowledge-to-self-host-nextjs" target="_blank" rel="noopener noreferrer">Secret knowledge to self-host Next.js</a></li><li><a href="https://nextjs.org/docs/app/guides/memory-usage" target="_blank" rel="noopener noreferrer">Next.js Memory Usage Guide</a></li></ul>]]></content:encoded>
      <pubDate>Thu, 19 Mar 2026 00:00:00 GMT</pubDate>
      <category>Next.js</category>
      <category>Memory Leak</category>
      <category>Docker</category>
      <category>AWS Lambda</category>
      <category>Node.js</category>
    </item>
  </channel>
</rss>