المشكلة
يوم الطالب متناثر بين لوحات الإعلانات والرسائل، والعثور على قاعة فارغة يعني التجول في المبنى.
متاح للتجربةمنصة تعليميةقراءة في دقيقة
الجداول وإتاحة القاعات والطلبات الأكاديمية والإعلانات في منصة جامعية واحدة باللغتين.

01
يوم الطالب متناثر بين لوحات الإعلانات والرسائل، والعثور على قاعة فارغة يعني التجول في المبنى.
02
شاشة واحدة مصممة للجوال تجيب عن أسئلة الطالب الخمسة اليومية: ما لديه اليوم، وأين، وأي قاعة متاحة الآن، وما يحتاج إلى انتباهه، وما يمكنه المشاركة فيه. وكل ما في النظام موجود لإبقاء هذه الإجابات صحيحة، بالعربية والإنجليزية.
03
تصميم المنتج والتنفيذ الكامل: نموذج البيانات ومحرك الإتاحة والأمان واللغتان.
شاشة واحدة تجيب: ما التالي، وأين، وأي قاعة متاحة الآن.
نسخة تجريبية عامة ببيانات وهمية، وتوضح دراسة الحالة حدودها: لا يوجد تدقيق أمني من جهة خارجية.
دليل الطالب منصة جامعية ثنائية اللغة بنيت حول فكرة واحدة: الطالب الذي يفتح هاتفه بين محاضرتين لديه خمسة أسئلة، وبوابة الجامعة تجعله يكد للحصول على إجابة كل واحد منها.
تجيب المنصة عن الأسئلة الخمسة في شاشة واحدة — ما محاضرات اليوم، وأين مكانها، وأي قاعة فارغة الآن، وما الذي يحتاج انتباهك، وما الذي يمكنك المشاركة فيه — ثم تبتعد عن الطريق. كل ما تبقى في النظام موجود ليبقي هذه الإجابات الخمس صحيحة.
كل شيء. قرار المنتج بشأن الأسئلة الخمسة التي تستحق الإجابة، وهندسة المعلومات، والهوية البصرية ونظام التصميم، ونموذج البيانات، ومحرك الإتاحة، والنموذج الأمني، والنصوص باللغتين، ومجموعة الاختبارات، وأدوات النشر، وتشغيل النسخة التجريبية في الإنتاج.
كان المفهوم الأصلي لعام 2022 الذي يعيد هذا المشروع النظر فيه قائمة ميزات — جداول، وقاعات فارغة، ونماذج، وانتخابات، ومعلومات عن الحرم. وتسليمه كقائمة ميزات يجعله بوابة أخرى.
لذلك أعيدت صياغة المهمة كخمسة أسئلة يطرحها الطالب فعلا، بالترتيب الذي تخطر به على باله:
كل شاشة تستحق مكانها بإجابتها عن واحد من هذه الأسئلة. تجيب اللوحة عن الأربعة الأولى دون تمرير، ويجيب باحث القاعات عن الثالث بضغطة واحدة. أما الميزات التي لا تجيب عن أي منها — المراسلة والخلاصات الاجتماعية والتلعيب — فقد استبعدت عمدا.
خمس وجهات، اختيرت كي لا يحتاج شريط التبويب على الهاتف إلى سادسة: الرئيسية، والجدول، والقاعات، والطلبات، وقائمة إضافية تضم الحرم والإعلانات والانتخابات والإشعارات والحجوزات والإعدادات.
يحصل الموظفون والمسؤولون على شاشات إضافية داخل الهيكل نفسه بدل لوحة تحكم منفصلة — فعضو هيئة التدريس الذي يراجع طلبا يبعد ضغطة واحدة عن الجدول الذي كان ينظر إليه.
لوحة بعد تنازلي حي، وجدول لليوم والأسبوع والأجندة مع كشف التعارضات، وباحث القاعات، والحجوزات وتسجيل الحضور عبر QR، والطلبات الأكاديمية، والانتخابات، والإشعارات والتفضيلات.
جدول التدريس، وقاعات اليوم مع الإتاحة اللحظية، وقائمة مراجعة: أخذ الطلب إلى المراجعة، والموافقة أو الرفض أو طلب التعديل، إضافة إلى ملاحظات داخلية لا يراها الطالب إطلاقا.
المستخدمون والأدوار، والمباني والقاعات، والمساقات، والإعلانات التي ترسل كإشعارات، وأنواع الطلبات، والانتخابات وإظهار النتائج، والتقارير مع التصدير إلى CSV، وسجل النشاط.
هذا هو الجزء الأرجح أن يكون خاطئا بطريقة لا يلاحظها أحد حتى تصل مجموعتان إلى الباب نفسه.
لا يوجد عمود is_free في أي مكان. تكون القاعة مشغولة في فترة ما إذا تداخلت معها محاضرة متكررة في ذلك اليوم من الأسبوع، أو تداخل معها حجز غير ملغى. والتداخل هو المقارنة نصف المفتوحة المعتادة — a.start < b.end AND b.start < a.end — لذا تتسع الفترتان 10:00–11:00 و11:00–12:00 معا، وهو ما يفترض أن يفعله حجزان متتاليان.
يجري الحجز داخل معاملة تأخذ أولا قفلا على صف القاعة. وهذا يسلسل كل المحاولات المتزامنة على القاعة نفسها، فلا يمكن لفحص التداخل الذي يليه أن يسبق بطلب ثان يصل بعده بجزء من الثانية. ومفتاح فريد على الفترة يمثل خط دفاع ثان أخشن.
لماذا يهم القفل. فحص الإتاحة ثم الإدراج عمليتان منفصلتان. وبدون القفل يمكن لطالبين أن يجتازا الفحص كلاهما قبل أن يدرج أي منهما، فيحصل كلاهما على القاعة. القفل يجعل الفحص والإدراج عملية ذرية واحدة لكل قاعة، دون تعطيل الحجز في أي قاعة أخرى.
مصفوفة التداخل مغطاة بالاختبارات: حجز يتداخل مع البداية، ومع النهاية، وحجز داخلها بالكامل، وآخر يحتويها بالكامل، وحجز مطابق، وكلتا الحالتين المتتاليتين — إضافة إلى الفترات الصفرية والمقلوبة، وهي مرفوضة من الأساس.
خمسة أنواع من الطلبات — نشاط، ودعم أكاديمي، ووثيقة، وبلاغ عن عطل، وخدمة طلابية — يعلن كل نوع منها حقوله في JSON. وهذا الإعلان يقود النموذج المعروض والتحقق على الخادم معا، فلا يمكن إرسال حقل لم يعلنه النوع، والحمولة التي تحمل status or is_admin تحذف منها هذه المفاتيح بدل تخزينها.
سير العمل آلة حالات حقيقية: مسودة، ومرسل، وقيد المراجعة، ومطلوب تعديل، وموافق عليه، ومرفوض، ومسحوب. كل انتقال يحدد من يحق له تنفيذه، والخادم يفرض ذلك — فلا يستطيع الطالب الموافقة على طلبه حتى لو أرسل مباشرة إلى مسار المراجع.
رفض الطلب أو طلب التعديل يستلزم سببا. فالطالب الذي يقال له «مرفوض» دون تفسير مضطر لبدء المحادثة من جديد، ولذلك لا يرسل النموذج دون سبب.
يستطيع المراجعون إضافة ملاحظات داخلية. تخزن بعلامة ظهور وتصفى على الخادم، ويسجل اختبار الدخول بحساب الطالب مقدم الطلب ليتأكد من غياب الملاحظة عن شاشته.
وحدة انتخابات طلابية تستدعي سؤالا بديهيا — هل يستطيع من يدير قاعدة البيانات أن يرى لمن صوت؟ كان لا بد أن تكون الإجابة الصادقة لا، وأن تكون بنيوية لا مجرد وعد.
لذلك هناك جدولان. الأول يسجل أن شخصا ما صوت، ويحمل مفتاحا فريدا على (الانتخاب، المستخدم) — وهذا المفتاح، لا شيفرة التطبيق، هو ما يفرض صوتا واحدا لكل ناخب. والثاني يسجل ما الذي جرى التصويت له، ولا يحمل أي إشارة إلى الناخب. ولا يمكن ربط الجدولين. فالفرز ممكن، أما النسبة إلى شخص فلا.
تتحقق الاختبارات من ذلك على مستوى المخطط نفسه: جدول الأصوات لا يحوي عمود مستخدم، وجدول الناخبين لا يحوي عمود مرشح، والمفتاح الفريد موجود، وإدراج مكرر مباشر ترفضه قاعدة البيانات نفسها. ويسجل سجل التدقيق أن المستخدم صوت، ولا يسجل عمدا لمن.
هذا تصميم سليم لعرض توضيحي. وهو ليس نظام تصويت معتمدا، ودراسة الحالة تقول ذلك صراحة في قسم الحدود أدناه بدل تركه ضمنيا.
حسابات العرض مطبوعة في صفحة الدخول، ما يعني أن أي زائر يستطيع الدخول بصفة مسؤول. وهذا هو المقصود — وهو أيضا المشكلة.
يرفض الوضع التجريبي، على الخادم، العمليات التي تتيح لزائر واحد أن يفسد التجربة على البقية: إنشاء المستخدمين أو حذفهم، وتغيير الأدوار، وتعطيل الحسابات، وحذف السجلات الأساسية، وكتابة الإعدادات، وإرسال البريد. وكل رفض يسجل في سجل التدقيق باسم العملية، ويظهر في لوحة إدارية تعرض ما هو معطل وما جرت محاولته مؤخرا.
أما كل ما هو من صميم المنتج فيبقى مفعلا — حجز القاعات، وتقديم الطلبات ومراجعتها، والتصويت، ونشر الإعلانات، وتعديل القاعات — لأن مهمة ليلية محروسة تعيد بيانات نظيفة موثقة بمجموع تحقق، ما يجعل ذلك كله قابلا للتراجع.
وترفض هذه الإعادة العمل ما لم تتحقق كل الشروط: قفل حصري، والوضع التجريبي مفعل، واسم قاعدة البيانات المستهدفة صحيح، والنسخة مطابقة لمجموع التحقق المسجل، وخلوها من أي أوامر على مستوى قاعدة البيانات، ووجود ملف صلاحيات محدود. ثم تتحقق من أن الاستعادة أبقت عددا غير صفري من المستخدمين والقاعات.
default-src 'self' دون unsafe-inline في أي موضع — وهو ممكن لأن خريطة الحرم تحدد مواضع العناصر بسمات SVG، وأشرطة التقارير تستخدم أصناف عرض مولدة بدل أنماط مضمنة.تختبر مجموعة الأمان هذه الضوابط كسلوك لا كإعدادات: حمولات XSS عبر الحقول المخزنة، وحمولات SQL عبر كل بحث وتصفية، ومحاولات اجتياز المسارات على تنزيل المرفقات، ومحاولات إعادة توجيه خارجية على مبدل اللغة والمظهر، وتأكيدات بأن أي كلمة مرور لقاعدة البيانات أو مفتاح تطبيق أو بصمة كلمة مرور أو مسار على الخادم لا يظهر في أي صفحة.
٥١٤ مفتاح ترجمة لكل لغة، مع اختبار يفشل البناء إذا اختلفت مجموعتا المفاتيح، أو كانت أي قيمة فارغة، أو اختلفت متغيرات الاستبدال بين اللغتين، أو بقيت قيمة عربية نسخة من الإنجليزية.
المحتوى ثنائي اللغة في نموذج البيانات نفسه لا في طبقة ترجمة: المساقات والقاعات والمباني والإعلانات والمرشحون جميعها تحمل أعمدة مزدوجة، فالعربية محتوى حقيقي لا عرض للإنجليزية.
يستخدم التخطيط الخصائص المنطقية في CSS في كل موضع — margin-inline-start, border-inline-start, inset-inline-* — ولهذا يكون العرض من اليمين إلى اليسار انعكاسا حقيقيا دون أي تجاوزات خاصة بالاتجاه. وتعرض التواريخ بالتقويمين الميلادي والهجري، وتعرض الأرقام بالصيغة العربية الهندية، عبر امتداد intl .
ما الذي كسره اتجاه اليمين إلى اليسار فعلا. أمران، اكتشفا بالنظر لا بالافتراض. سهم القائمة المنسدلة كان مرسوما بتدرجين في CSS يشكلان مثلثا؛ وعند الانعكاس كون النصفان علامة مشوهة، فصار سهما واحدا متماثلا بصيغة SVG. كما أن صف تصفية الحالات بالعربية أعرض من نظيره الإنجليزي — بما يكفي لدفع الصفحة جانبا عند عرض 360 بكسل — فصار يتمرر داخل نفسه.
صمم انطلاقا من عرض 360 بكسل أولا، لأنه العرض الذي يتفقد به الطالب جدوله فعلا. وجرى التحقق بعرض التطبيق داخل إطارات بعرض ثابت عند مقاسات هاتف حقيقية وقياس النتيجة، بدل الوثوق بنافذة سطح مكتب مصغرة.
كشف هذا القياس عيوبا حقيقية: تجاوز أفقي في الشبكة سببه القيمة الافتراضية min-width: auto على أبناء الشبكة، وعناوين تبويب تلتف وتقتطع عند 360 بكسل، واقتطاع نصي لا يعمل لأن text-overflow لا ينطبق على العناصر السطرية، وعدة عناصر تفاعلية دون هدف اللمس 44 بكسل. عولجت جميعها ثم أعيد القياس.
prefers-reduced-motion محترم؛ و prefers-color-scheme مطبق مع إمكانية التجاوز الصريح.PHP 8.3 على MySQL 8 مع صفر اعتماديات تشغيل. لا يوجد ملف composer.json لأنه لا شيء لتثبيته: الموجه وطبقة الخدمات والمحقق والمترجم ومدير الترحيلات ومشغل الاختبارات كلها جزء من التطبيق.
كانت تلك مقايضة مقصودة. إطار عمل كان سيوفر أسبوعا ربما؛ وعدم استخدامه يعني أن هذا المشروع سيظل يثبت ويعمل بعد سنوات بأمر git cloneوقاعدة بيانات وأمرين اثنين — وكل سطر من سلوكه قابل للفحص في مستودع واحد.
قاعدة الطبقات بسيطة وملتزم بها: المتحكمات تستدعي الخدمات، والخدمات تستدعي بعضها والنواة، ولا شيء في طبقة الخدمات يعرف ما هو HTTP. ولهذا يمكن اختبار محرك الإتاحة مباشرة، ولهذا يعيد مولد البيانات استخدام مسار الحجز الآمن نفسه الذي يستخدمه طلب حقيقي — فالبيانات التجريبية تولد عبر شيفرة الإنتاج، ولا يمكن أن تحتوي تعارضا يرفضه التطبيق.
٥٥٦ تأكيدا موزعة على مجموعات الوحدة والوظائف والأمان، ترسل عبر النواة الحقيقية داخل العملية — فالتوجيه والوسطاء وحماية CSRF والتفويض والمتحكمات والقوالب تشارك جميعها. هذه اختبارات وظيفية لا محاكاة.
مصفوفة تداخل الإتاحة، وحراسات تسجيل الحضور، والجدول وكشف التعارضات، وفرض صوت واحد في الانتخابات وسرية الاقتراع، وقواعد التحقق.
المسارات العامة، والمصادقة وتحديد المحاولات، وكل دور مقابل كل مسار محمي، ودورة الطلب الكاملة، والتصويت، والإشعارات، وقيود الوضع التجريبي، وتطابق مفاتيح اللغتين.
مسح شامل لحماية CSRF على كل مسار يغير الحالة، والحقن والهروب، والتحكم في الوصول ومعالجة الملفات، والترويسات وتسرب الأسرار.
ترفض المجموعة العمل على بيئة إنتاج أو على قاعدة بيانات لا تحمل اسم هذا المشروع، وتعيد أي حالة تغيرها فلا يترك التشغيل أثرا.
تعمل النسخة التجريبية على خادم افتراضي مشترك إلى جانب تطبيقات أخرى غير ذات صلة، وهو ما شكل عملية النشر أكثر من أي شيء آخر: لا يجوز لأي إجراء أن يؤثر في جار.
open_basedir لا يمكنه الوصول إلى أي تطبيق آخر.php-fpm -t and nginx -t قبل إعادة تحميل أي شيء، ويرفض أي حزمة تحتوي على ملفات PHP في جذر الويب أو أي ملف قابل للكتابة من الجميع.خللان كشفتهما عملية النشر نفسها. قاعدة دفاع متعمق في nginx تمنع أسماء أدلة الشيفرة كانت تطابق بصمت مسار /app/ الخاص بالتطبيق نفسه، فتعيد 404 لكل مسار بعد تسجيل الدخول — واكتشف ذلك بالتحقق من الإنتاج لا بافتراض سلامته. كما أن الملفات الثابتة المخدومة بترويسة immutable لمدة شهر كانت تعني أن تغييرا في CSS لن يصل إلى زائر عائد، فصارت روابط الملفات تحمل بصمة لكل إصدار.
جرى التحقق على النطاق الحي بعد النشر: كل مسار للأدوار الثلاثة، واللغتان باتجاه نص صحيح، وإعادة التوجيه من HTTP إلى HTTPS، وسلسلة شهادات صالحة عبر HTTP/2، ومجموعة ترويسات الأمان كاملة دون تكرار، ولا أخطاء في وحدة التحكم، ولا مخالفات لسياسة أمان المحتوى، وكل التطبيقات المستضافة معه دون تغيير مقارنة بخط الأساس قبل النشر.
هذه نتائج هندسية. ولا توجد هنا أرقام تبن أو استخدام لأن المنتج بلا مستخدمين — فهو عرض توضيحي، واختلاق رواج له ينسف الغاية من نشر الشيفرة.