عرفنا كيف نجعل الصفحة جميلة، وكيف نشغّل الوكيل في Cursor. المرحلة التالية أصعب: المعايير التي لا تظهر في لقطة الشاشة. البنية، والوصول، والحالات، والأمان، والاستجابة، والاتساق التقني. هذه بالضبط ما يفرّق بين من «يصنع صفحات» ومن يُقال له: «تعال اعمل معنا».
الذكاء الاصطناعي يكتب كودًا «يعمل»، و«يعمل» ليس «محترفًا»#
اطلب من أي وكيل صفحة تسجيل، وسيعطيك في نصف دقيقة شيئًا يعمل ويبدو جيدًا في اللقطة. المشكلة أن الكود الافتراضي يختبر نفسه في أفضل الظروف: مستخدم يكتب صحيحًا، وشاشة كبيرة، وبيانات موجودة، وفأرة في يده. وحين تخرج من هذه الظروف تسقط الصفحة.
المعايير الستة هي «الظروف الأخرى». وأهم ما فيها أنها قابلة للاختبار في ثلاثين ثانية؛ ليست أذواقًا ولا آراء. لكل معيار أدناه: قاعدة، واختبار تجريه بنفسك، وأمر إصلاح جاهز للوكيل.
الجولة صفر: شخّص بعينك قبل أن تسأل الوكيل#
أول قاعدة في مراجعة الجودة: لا تطلب إصلاح شيء لم تره بنفسك. أجرِ هذه التجارب الخمس، وكلٌّ منها ثانيتان.
أغلق الفأرة واستخدم Tab فقط
هل تستطيع الوصول إلى زرّ «تسجيل» والضغط عليه؟
اضغط «تسجيل» والحقول فارغة
ما الذي ظهر ليشرح المشكلة؟ وهل ظهر شيء أصلًا؟
اضغط «تسجيل» ببيانات صحيحة
هل أخبرتك الصفحة بما حدث؟ وبعد كم من الوقت؟
صفِّ على مستوى لا يوجد فيه أحد
ماذا ترى في الجدول؟
صغّر المتصفّح إلى عرض 390px
هل تنسحب الصفحة يمينًا ويسارًا؟
سجّل كل عطل في ملف bugs.md بصيغة: ما فعلته ← ما توقّعته ← ما حدث فعلًا. هذا الملف ما ستعطيه للوكيل في الجولات القادمة؛ فالأمر المبني على ملاحظتك أقوى دائمًا من «راجع الصفحة وانظر ما فيها».
ست جولات، معيار في كل جولة#
في كل جولة: اقرأ القاعدة، وأجرِ الاختبار، وابحث عن الأعراض في ملفك، ثم أعطِ الأمر للوكيل واقرأ الفرق قبل القبول.
المعايير الستة
العنصر الصحيح للوظيفة الصحيحة. عنصر div بلا معنى؛ وحين تستعمله زرًّا أو عنوانًا تنزع المعنى من الصفحة: محركات البحث لا تفهم، وقارئ الشاشة لا يرى، ولوحة المفاتيح لا تصل. الفرق بين <div class="btn"> و<button> ليس شكليًّا؛ فالثاني يأتي بسلوك كامل مجانًا.
القاعدة: كل عنصر تفاعلي button أو a، وكل قسم في مكانه: header وmain وsection وform. عنوان h1 واحد، وتسلسل العناوين متدرّج.
الاختبار: عطّل CSS كلها من أدوات المطوّر. إن بقي المحتوى مفهومًا مرتّبًا كمستند فبنيتك سليمة؛ وإن صار كومة نصوص متساوية فبنيتك ديكور لا هيكل.
أعراض شائعة: عناوين مزيّفة بأصناف مثل class="h1" بترتيب مقلوب. أزرار وتصفيات مصنوعة من div وspan بـ onclick. لا main ولا form. عنوان الصفحة «Document» بلا وصف. الاتجاه مضبوط بـ CSS بدل خاصية dir.
حوّل بنية الصفحة إلى HTML دلالي دون أي تغيير في الشكل النهائي: الترويسة header، والمحتوى main، والأقسام section، وحقول التسجيل form. حوّل كل عنصر تفاعلي (تسجيل، مسح، أزرار التصفية) إلى button حقيقي بنوع type مناسب. استبدل العناوين المزيّفة بـ h1/h2/h3 فعلية بتسلسل هرمي لا تُتخطّى فيه مرتبة، وh1 واحد فقط. أضف dir="rtl" وlang="ar" على عنصر html، وعنوانًا وصفيًّا وmeta description. لا تغيّر أي قيمة لون أو مسافة؛ الشكل يبقى كما هو تمامًا.
لوحة المفاتيح والتسميات والإعلان. ليس كل مستخدم معه فأرة، وليس كل مستخدم يرى. وحتى المستخدم العادي يستعمل لوحة المفاتيح في النماذج. وهذا المعيار ليس نيّة حسنة فحسب، بل شرط قانوني في أسواق كثيرة ومطلوب في مناقصات المواقع الحكومية والشركات الكبرى.
القاعدة: لكل حقل label مربوط بـ for/id؛ فالنص المؤقّت placeholder ليس تسمية. حلقة تركيز ظاهرة على كل عنصر تفاعلي. الخطأ يُقال بالنص لا باللون وحده. وأي تغيير يحدث دون إعادة تحميل (مثل عدد النتائج) يُعلَن بـ aria-live.
الاختبار: اضغط Tab من أول الصفحة إلى آخرها. يجب أن تعرف أين أنت في كل لحظة، وتصل إلى كل زرّ، وتشغّله بـ Enter أو Space. إن ضعت أو توقّفت فقد رسبت.
أعراض شائعة: outline:none على كل الحقول، فحلقة التركيز مقتولة. لا label إطلاقًا، والاعتماد على placeholder الذي يختفي عند الكتابة. الخطأ حدود حمراء فقط. عدّاد يتغيّر في صمت. نص رمادي فاتح على خلفية فاتحة بتباين أقل من الحدّ.
اجعل الصفحة قابلة للاستخدام بلوحة المفاتيح وقارئ الشاشة: أضف label مرئيًّا لكل حقل مربوطًا بـ for/id، وأبقِ placeholder مثالًا لا تسمية. احذف كل outline:none واستبدلها بحلقة تركيز واضحة عبر :focus-visible بسمك 2px مع إزاحة. حوّل رسائل الخطأ إلى نص عربي صريح أسفل كل حقل يشرح المشكلة والمطلوب، واربطه بالحقل عبر aria-describedby مع aria-invalid. ضع العدّاد وأي رسالة حالة داخل عنصر aria-live="polite". ارفع تباين كل النصوص إلى 4.5:1 على الأقل.
لكل واجهة أربعة وجوه. الوكيل يصمّم للحالة المثالية وحدها: توجد بيانات وكل شيء نجح. لكن كل واجهة حقيقية تمرّ بأربع حالات: فارغة (لا بيانات بعد)، وتحميل (العملية جارية)، وخطأ (حدثت مشكلة)، ونجاح (تمّت). الثلاث الأولى هي التي تصنع الفرق، ولا أحد يطلبها.
القاعدة: كل قائمة لها حالة فارغة تحمل الخطوة التالية، لا «لا توجد بيانات» فحسب. كل عملية تستغرق وقتًا لها مؤشّر تحميل وتعطيل للزرّ أثناءها. كل نتيجة لها تأكيد مرئي يبقى وقتًا كافيًا ليُقرأ.
الاختبار: امسح كل البيانات، فماذا يظهر؟ اضغط الزرّ عشر مرات بسرعة، فكم مرة يُنفَّذ؟ افصل الإنترنت وجرّب، فهل تقول الصفحة ما حدث أم تصمت؟
أعراض شائعة: جدول فارغ برؤوس أعمدة معلّقة في الفراغ. تسجيل يستغرق ثانية بلا مؤشّر فيظنّه المستخدم معلّقًا. زرّ لا يتعطّل أثناء الإرسال، فضغطة زائدة تعني تسجيلًا مكرّرًا. نجاح بلا رسالة؛ النموذج يُمسح والمستخدم لا يعرف ماذا حدث. ولا مسار للخطأ في الكود أصلًا.
أضف الحالات الأربع لكل واجهة في الصفحة. الفارغة: عند عدم وجود نتائج بعد التصفية أو قبل أول تسجيل أخفِ الجدول واعرض بطاقة فيها سبب الفراغ والخطوة التالية بصيغة إرشادية. التحميل: أثناء التسجيل عطّل زرّ الإرسال وغيّر نصّه إلى «جارٍ التسجيل…» وامنع أي ضغط إضافي حتى تنتهي العملية. النجاح: رسالة تأكيد واضحة تبقى 4 ثوانٍ على الأقل داخل aria-live، وأبرز الصف الجديد لحظيًّا. الخطأ: أضف مسارًا للفشل برسالة عربية تشرح ما حدث وتتيح إعادة المحاولة دون فقدان ما كتبه المستخدم.
مدخلات المستخدم بيانات لا كود. هذه أخطر جولة. حين تأخذ ما كتبه المستخدم وتضعه في الصفحة بـ innerHTML فأنت تقول للمتصفّح: «نفّذ هذا». وأي وسم HTML يُكتب في خانة الاسم سيعمل فعلًا. الوكيل يستعمل innerHTML افتراضيًّا لأنه أسهل، والقاعدة أن تنزعها بيدك.
القاعدة: النص الآتي من المستخدم يوضع بـ textContent لا innerHTML. والتحقّق يجري على القيمة الفعلية: النوع، والطول، والنطاق، والصيغة. الحقل المطلوب يُتحقَّق منه في الكود لا بالعين.
الاختبار: اكتب في خانة الاسم <b>تجربة</b> وسجّل. إن ظهر الاسم بخط عريض بدل أن يظهر بالأقواس كما كتبته، ففي صفحتك ثغرة. وجرّب أيضًا: اسمًا من مسافات فقط، وهاتفًا من ثلاثة أرقام، واسمًا من 500 حرف.
أعراض شائعة: الجدول مبني كله بـ innerHTML من قيم المستخدم مباشرة، أي ثغرة حقن كاملة. التحقّق يقارن بـ == "" فقط، فالمسافة اسم صحيح. الهاتف مقبول بأي شكل. حقل إلزامي يمرّ فارغًا. لا حدّ لطول النص، و500 حرف تكسر الجدول.
حصّن الصفحة ضد مدخلات المستخدم. أولًا: أعد بناء صفوف الجدول بـ createElement وtextContent بدل innerHTML، بحيث لا يُنفَّذ أي HTML يكتبه المستخدم إطلاقًا، مع إبقاء الشكل كما هو. ثانيًا: تحقّق حقيقي قبل التسجيل: الاسم بعد trim بين 3 و60 حرفًا، والهاتف 11 رقمًا يبدأ بـ 01 وأرقامًا فقط، والمستوى إلزامي، والملاحظات 200 حرف كحدّ أقصى مع عدّاد أحرف. كل خطأ نصٌّ عربي أسفل حقله يشرح الصواب، وينتقل التركيز تلقائيًّا إلى أول حقل خاطئ.
الشاشة الصغيرة ليست نسخة مصغّرة. «متجاوب» لا تعني أن الصفحة تصغر، بل أن التخطيط يتغيّر ليخدم شاشة أخرى ويدًا تلمس. الجدول الذي ينسحب أفقيًّا، والزرّ الذي ارتفاعه 28 بكسل، والحاوية ذات العرض الثابت: كلها تقول للزائر إن الصفحة لم تُصنع له.
القاعدة: الحاويات بـ max-width لا width ثابت. أهداف اللمس بين 44 و48 بكسل ارتفاعًا كحدّ أدنى. الجدول على الهاتف يتحوّل إلى بطاقات. صفر تمرير أفقي، و100vw ممنوعة لأنها تحتسب عرض شريط التمرير.
الاختبار: افتح بعرض 390px وحاول سحب الصفحة أفقيًّا بإصبع واحد؛ أي حركة أفقية عطل. ثم اضغط كل زرّ بإبهامك لا بالفأرة.
أعراض شائعة: حاوية بعرض ثابت 900px. شريط علوي بعرض 100vw. جدول بـ white-space:nowrap وخمسة أعمدة يخرج من الشاشة. أزرار بارتفاع 28 بكسل. خطوط صغيرة ثابتة على كل المقاسات.
اجعل الصفحة متجاوبة فعلًا بمنهج mobile-first. استبدل العرض الثابت للحاوية بـ max-width مع حشوة جانبية، واحذف أي استخدام لـ 100vw. اضبط ارتفاع كل الأزرار والحقول على 48px على الأقل. تحت عرض 700px حوّل صفوف الجدول إلى بطاقات تعرض التسميات بجوار القيم، مع إزالة white-space:nowrap. اجعل أحجام الخطوط سائلة عبر clamp بحيث يُقرأ النص على 390px دون تكبير. الهدف: صفر تمرير أفقي على 390px؛ تحقّق من ذلك قبل أن تنهي.
مصدر واحد للحقيقة. اتفقنا سابقًا على الاتساق معيارًا جماليًّا، والآن نفرضه تقنيًّا: قيمة واحدة معرّفة في مكان واحد، والباقي يشير إليها. هذا ليس ترتيبًا للكود فحسب؛ إنه ما يجعل طلبًا مثل «غيّر اللون المميّز كله» يُنفَّذ في سطر واحد، بدل أن يدور الوكيل على أربعين موضعًا وينسى ثلاثة.
القاعدة: كل لون ومسافة ونصف قطر وظل يُعرَّف مرة واحدة كمتغيّر CSS في :root. صفر أرقام سحرية مبعثرة، وصفر أنماط مضمّنة inline. والحركة تحترم prefers-reduced-motion.
الاختبار: غيّر قيمة اللون المميّز في مكان واحد. إن تغيّرت الصفحة كلها فنظامك سليم. وإن بقي اللون القديم في موضعين، فعندك نسخ مكرّرة بقيم متقاربة لا تفرّقها العين لكن النظام مكسور.
أعراض شائعة: اللون الأساسي مكتوب بثلاث قيم متقاربة. أربع قيم مختلفة لنصف القطر وظلّان مختلفان بلا سبب. أنماط inline على العناصر. حركة مستمرة تتجاهل تفضيل تقليل الحركة. ولا استعداد للطباعة رغم أن الصفحة قائمة ستُطبع.
وحّد نظام الصفحة تقنيًّا. عرّف في :root متغيّرات CSS لكل الألوان والمسافات وأنصاف الأقطار والظل، ثم استبدل كل قيمة مكرّرة بالمتغيّر المناسب، ووحّد القيم المتقاربة في قيمة واحدة. اجعل المسافات من سلّم مضاعفات 8px، ونصفي قطر فقط (للبطاقات وللأزرار)، وظلًّا واحدًا. انقل كل الأنماط المضمّنة إلى أصناف. أوقف الحركة المستمرة واجعلها استجابة للتفاعل فقط بمدّة 200ms، وأضف كتلة prefers-reduced-motion تلغي الحركة، وكتلة طباعة بسيطة.
جدول الجلسة#
نحو مئة دقيقة، وكل جولة بالإيقاع نفسه: اختبار (دقيقتان)، ثم أمر (دقيقة)، ثم تنفيذ ومراجعة الفرق (ست دقائق)، ثم إعادة اختبار ونقاش (ثلاث دقائق).
| الجولة | المعيار | اختبار القبول السريع | الوقت |
|---|---|---|---|
| 0 | التشخيص بالعين | خمس تجارب وملف bugs.md | 10 د |
| 1 | البنية الدلالية | عطّل CSS، والمستند ما زال مفهومًا | 12 د |
| 2 | إمكانية الوصول | تنقّل بـ Tab حتى النهاية وسجّل بلوحة المفاتيح | 12 د |
| 3 | الحالات الأربع | تصفية فارغة، وعشر ضغطات، ورسالة نجاح | 12 د |
| 4 | تحصين المدخلات | وسم HTML في الاسم يظهر نصًّا | 12 د |
| 5 | الاستجابة واللمس | 390px بصفر تمرير أفقي وأزرار 48px | 12 د |
| 6 | رموز التصميم | تغيير متغيّر واحد يغيّر الصفحة كلها | 12 د |
| 7 | المراجعة والمقارنة | الأصل بجوار المُصلَح، وأمر المراجعة الشامل | 15 د |
أمر مراجعة الجودة الشامل#
الجولات الست تحوّلت إلى أمر واحد. ليس بديلًا عن فهم المعايير، بل اختصار بعد أن تفهمها. استخدمه حين تعمل الصفحة وظيفيًّا وقبل أن تقول «انتهيت»، وتوقّع أن يعود إليك بقائمة طويلة؛ فهذا يعني أنه يعمل، لا أن عملك سيّئ.
راجع هذه الصفحة على المعايير الستة التالية، وقبل أن تعدّل أي شيء اكتب لي تقريرًا بكل مخالفة وجدتها مصنّفة بحسب المعيار ودرجة خطورتها، ثم انتظر موافقتي: 1. البنية الدلالية: عناصر HTML مناسبة لوظائفها، وheader/main/section/form، وأزرار button حقيقية لا div، وتسلسل عناوين سليم بـ h1 واحد، وlang وdir وtitle وmeta description. 2. إمكانية الوصول: label مربوط لكل حقل، وحلقة تركيز ظاهرة عبر :focus-visible، ووصول كامل بلوحة المفاتيح، ورسائل خطأ نصية مربوطة بـ aria-describedby، وaria-live لتغيّرات الحالة، وتباين 4.5:1. 3. الحالات الأربع: فارغة بخطوة تالية، وتحميل مع تعطيل الزرّ، وخطأ برسالة وإعادة محاولة، ونجاح بتأكيد مرئي. 4. تحصين المدخلات: textContent بدل innerHTML لكل قيمة من المستخدم، وتحقّق من النوع والطول والنطاق والصيغة. 5. الاستجابة: max-width لا عرض ثابت، ولا 100vw، وأهداف لمس 48px، وجداول تتحوّل إلى بطاقات، وصفر تمرير أفقي على 390px. 6. رموز التصميم: متغيّرات CSS في :root لكل القيم، وصفر أرقام سحرية أو أنماط inline، واحترام prefers-reduced-motion.
التكليف: طبّق الستة على مشروعك أنت#
التدريب كان على «مريض» جاهز. التكليف أن تكتشف أن مشروعك من التكليف السابق فيه الأمراض نفسها.
الصفحة بعد الجولات الست
النسخة المُصلَحة من الصفحة المريضة.
تقرير المراجعة
كل عطل، والمعيار الذي يخالفه، وكيف أُصلح.
المشروع السابق بعد المراجعة
مشروعك بعد تمريره على أمر المراجعة الشامل.
اختبارات القبول النهائية
اختبر فهمك
كتب مستخدم <b>تجربة</b> في خانة الاسم فظهر بخط عريض في الجدول. ما السبب؟
أي HTML يُدرَج بـ innerHTML يُنفَّذ؛ وهذا باب ثغرات الحقن.
اختبر فهمك
ما الذي يثبت أن رموز التصميم في صفحتك سليمة؟
القيم المتقاربة المكرّرة لا تفرّقها العين، لكنها تكسر النظام عند أول تعديل.
اختبر فهمك
لماذا تُمنع 100vw في الحاويات؟
100vw أعرض من المساحة المتاحة فعلًا حين يظهر شريط التمرير؛ استعمل max-width و100٪.