كتبتُ في مقال التفاعلات الدقيقة أن transform وopacity تتحرّكان على خيط التركيب (compositor)، فحتى الصفحة الثقيلة لا ينبغي أن تُعطّل حركة الزر. هذا لا يزال صحيحًا – مع تحفّظ واحد سكت عنه ذلك المقال: إذا كان الخيط الرئيسي محجوزًا بمهمّة JS طويلة، فقد لا تحصل الحركة أبدًا على فرصة للبدء، لأنّ معالج النقرة نفسه ينتظر في الطابور. INP هو مقياس يقيس بالضبط هذه الفجوة – وscheduler.yield() إحدى الوسائل القليلة لسدّها فعليًّا.
قائمة من ألف تعليق تُعرَض ببطء، فتلجأ إلى react-window – وفي مقابل تمرير سلس تخسر Ctrl+F، وطباعة الصفحة، وجزءًا من تنقّل قارئ الشاشة، لأن العناصر خارج النافذة المرئية تتوقف عن الوجود فعليًا في DOM. تحلّ content-visibility المشكلة نفسها بطريقة مختلفة: تبقى العناصر في DOM، ويكتفي المتصفح بتجاهل العمل المكلف من تخطيط وطلاء لها، إلى أن تقترب من المنطقة المرئية – والمفاجئ أنها قادرة على كشفها مؤقتًا حين تبحث فيها عن نص.
في مقال إتاحة الوصول، كانت النافذة المنبثقة بخاصية display:none، التي بقيت مع ذلك موجودة في شجرة إتاحة الوصول، مثالًا على خطأ نتج عن بناء نافذة منبثقة من الصفر باستخدام عناصر div. السؤال الحقيقي هو: لماذا كان يجب بناؤها من الصفر أصلًا؟ أفحص ما يقدّمه عنصر <dialog> الأصيل وسمة popover مجّانًا – الطبقة العليا، وفخّ التركيز، والإغلاق الخفيف (light dismiss) – وأين يختلف هذان الآليتان بما يكفي لجعل الخلط بينهما طريقًا مباشرًا لواجهة غير قابلة للوصول.
زرّ «إعجاب» يستجيب فورًا، القلب يمتلئ، العدّاد يرتفع – قبل أن يجيب الخادم أصلًا. هذا ليس وهم السرعة من مقال التفاعلات الدقيقة، بل شيء أبعد من ذلك: تُظهر الواجهة حالة لم توجد بعد، وتفترض أنها ستوجد قريبًا. أفحص كيف تُبنى هذه التقنية بأمان – مع تراجع حقيقي عن الكذبة حين لا يتحقّق الافتراض، ولماذا يُعدّ setState اليدوي مع catch نسخة أسوأ ممّا يقدّمه useOptimistic.
الصنف الأداتي .mt-4 يخسر أمام القاعدة .card .card__header .card__title، رغم أنه منطقيًّا ينبغي أن يفوز لأنه أُضيف لاحقًا. هذا ليس مصادفة – إنها الخصوصية (specificity) تعمل تمامًا كما صُمِّمت لتعمل. تُدخل @layer محورًا جديدًا كليًّا لفضّ النزاعات، يعمل قبل الخصوصية لا معها – وله فخّ واحد غير بديهي لدرجة أن حتى بعض التوثيق على الإنترنت يخطئ فيه.
تبدو „use server” وكأنها سكّر نحوي (syntactic sugar) فوق طلب fetch إلى مسار API – ولهذا بالضبط تتعامل معظم التطبيقات معها كأنها استدعاء دالة عادي. هذا خطأ له عواقب ملموسة: كلّ دالة موسومة بـ„use server” تصبح نقطة نهاية عامة على الشبكة، والنموذج المبني على هذه الآلية يعمل حتى قبل أن تتاح فرصة لجافاسكريبت أن تُحمَّل. أفحص ما يجري فعليًا خلف هذا التوجيه، وأين تكمن الفخاخ التي لا تظهر في التوثيق من النظرة الأولى.
حقل النموذج ينبغي أن يُضاء بالأحمر حين يكون الإدخال بداخله غير صالح. أمر بسيط – إلا أن CSS ظلّ طوال 25 عامًا عاجزًا عن أن يسأل عنصرًا عمّا يجري في داخله، بل العكس فقط. أفحص كيف تعكس :has() هذا الاتجاه، وكيف تختلف عن أدوات الربط (combinators) المعتادة، وأين تُكلِّف هذه القدرة الجديدة الأداء فعليًّا.
بطاقة المنتج نفسها تبدو رائعة في الشبكة الرئيسية وتنكسر في الشريط الجانبي الضيّق – رغم استعلامات وسائط مُختارة بعناية. المشكلة ليست في كودك، بل في السؤال الذي يطرحه استعلام الوسائط: عن عرض الشاشة، لا عن المساحة التي حصل عليها المكوّن فعليًا. أفحص كيف تنقل Container Queries هذا السؤال إلى حيث كان ينبغي أن يُطرح من البداية، وما الذي يحدث فعليًا تحت السطح حين يصبح العنصر «حاوية استعلام».
HTML الصحيح ليس سوى نقطة البداية. اكتشف كيف تعمل شجرة إتاحة الوصول فعليًا، ولماذا تضر ARIA أكثر مما تنفع في أحيان كثيرة، وابنِ معي أكورديون حقيقيًا، ومنطقة حية (live region)، وإدارة تركيز في Next.js.
متجران يبيعان المنتج نفسه، وبنفس زمن استجابة الخادم – ومع ذلك يبدو أحدهما أسرع وأكثر جدارة بالثقة من الآخر. الفارق يكمن في التفاعلات الدقيقة: بضع مئات من المللي ثانية من الحركة تقرر ما إذا كان المستخدم سيثق بما يراه على الشاشة. أفكك الآلية من الأساس – نموذج المُحفّز/التغذية الراجعة، وما يحدث فعليًا داخل محرك العرض في المتصفح، وخاصية prefers-reduced-motion.
CSS Houdini ليست تقنية واحدة، بل مجموعة من المواصفات في مراحل نضج مختلفة تماماً. اكتشف أي جزء منها صالح اليوم للاستخدام في الإنتاج، وكيف يعمل @property وpaint worklet فعلياً، ولماذا لا يزال الباقي مجرد تجربة.