نَفْس
متتبِّع عبادات عربي في المقام الأول — صلاة وأذكار ومصحف كامل وصيام وغيرها — أُعيد بناؤه من ملف واحد بثمانية آلاف سطر إلى نظام وحدات مُختبَر، ثم أُعيد تصميمه ليفتح على إيقاع يومك لا على درجاتك.
طلب العميل كان جملة واحدة: أن يبقى المسلم في التطبيق حبًّا لا واجبًا. وكل ما يلي معلَّق عليها.

- الدور
- تدقيق، وإعادة بناء معماري، وإعادة تصميم
- النطاق
- ٢٢ شاشة، تطبيق ويب تقدُّمي، يعمل دون اتصال
- التقنيات
- JavaScript خام بوحدات ES · Firebase · Tailwind
- الحالة
- منشور ويعمل فعليًا
المطلوب
كان نَفْس منشورًا بالفعل وله مستخدمون. لم يكن بناءً من الصفر: كان ملف `app.js` واحدًا بـ٧٬٩٥٢ سطرًا، وملف أنماط بـ٤٬٤٠٠ سطر، ومجموعة مزايا نمت أسرع من بنيتها — ٢٢ شاشة، ومصادقة Firebase وقاعدة Firestore، وservice worker، وخمسة تكاملات مع واجهات خارجية.
كان المطلوب ثلاثة أجزاء بترتيبها. اعثر على كل خطأ فيه — لا فحصًا سريعًا، بل قراءة كاملة. أصلحه كله، وأعد تنظيم المشروع ليصبح التغيير التالي سهلًا. ثم أعد تصميمه ليبقى المسلم في التطبيق حبًّا لا واجبًا. والجملة الأخيرة من العميل نفسه، وعليها معلَّق وصف التصميم كله.
قيد واحد شكّل العمل: التطبيق يعمل فعليًا وفيه سجل عبادة حقيقي في Firestore. فما كان ممكنًا أن يُعاد بناء شيء بطريقة تُفقد أو تُفسد يومًا واحدًا من سجل أحدهم. وهذا استبعد إعادة الكتابة، وجعلها إعادة هيكلة في مكانها، مع مسار ترحيل لكل تغيير في بنية البيانات.
ما تم تسليمه
- تدقيق يدوي كامل لـ١٥٬٤٤٧ سطرًا عبر ٣٣ ملفًا — ٤٣ ملاحظة، كل واحدة مصنَّفة ومربوطة بملف وسطر
- ٤١ من الـ٤٣ أُصلحت، منها ثلاث كانت تُفسد بيانات المستخدمين بصمت
- تقسيم `app.js` إلى ٥٣ وحدة ES بقاعدة أحادية الاتجاه: المزايا تستورد من النواة، ولا تستورد النواة منها أبدًا
- نظام تصميم — ست درجات عمق للأرضية، ولون دلالي، وسُلَّم للمسافات والحركة — بدل الألوان المكتوبة يدويًا
- إعادة بناء شاشات اللوحة والترويسة والدخول والصلاة والقرآن والمصحف عليه
- ٣٨ اختبار Jest حيث لم يكن هناك أي اختبار، تغطي حساب النقاط الذي كان خاطئًا والتهريب الذي كان غير آمن
- توحيد ترويسات الأمان في مصدر واحد، وتصحيح سياسة أمان المحتوى، وإضافة HSTS
- النشر للإنتاج على نفس الرابط، مع ترحيل بيانات يعمل عند أول تحميل لكل مستخدم
- مشكلة كشفها التدقيق
- 43
- وحدة، من ملف واحد
- 53
- اختبارًا، من الصفر
- 38
- سطر JS وCSS وHTML
- 19,562
القرارات، ولماذا
سرد التقنيات المستخدمة لا يقول شيئًا. المفيد في دراسة الحالة هو القرارات التي كان لها بديل حقيقي — لذلك يذكر كل قرار هنا ثمنه، لا ما تم اختياره فحسب.
- ٠١
اللوحة تفتح على اليوم، لا على الدرجة
الصلاة القادمة، وآية مختارة لهذه الساعة، ودعوة واحدة. أما التتابع والنقاط فانتقلا تحت عنوان «رحلتك».
ست بطاقات متساوية من العدّادات — التتابع والنقاط والصفحات والتحدي والأذكار والختمات — كانت تعطي من جاء ليعبد لوحةَ نتائج بلا أولوية. طلب العميل الحب لا الواجب، والعدّادات هي الواجب. ولم يُحذف شيء: الأرقام ما زالت موجودة بعد قسم واحد، بحجم يتيح لك أن تجدها لا أن يفرض عليك قراءتها.
الثمن · التقدُّم صار على بُعد تمريرة بدل أن يكون أول ما تراه. ولتطبيق يحصي العبادة، هذا هو الثمن المقصود، وقد طلبه العميل بالاسم.
- ٠٢
المصحف ورقة، لا مستند
صفحة محدودة بإطار مذهَّب مزدوج، وأسطر مضبوطة الطرفين، وكارتوش مزخرف لكل سورة، وعلامات آيات على هيئة وريدات.
كانت شاشة القراءة تعرض النص كنص ويب سائل: طرف أيسر غير مستوٍ، وخلفية مسطحة، وحرف زخرفي على جانبي اسم السورة. وضبط الطرفين هو أقوى إشارة على الإطلاق — فكل سطر في المصحف المدني يبلغ الهامشين، وعدم استوائهما هو ما يجعل نص القرآن على الويب يُقرأ كموقع. وفوق ذلك: أرضية ورقية بملمس ليفي، وخط مذهَّب مزدوج، وكارتوش مرسوم بـSVG، وأرقام آيات داخل وريدات ثمانية، وميدالية للصفحة، وترويسة جارية تحمل السورة والجزء والحزب. والوضع الليلي ورقةٌ مُدبوغة لا أبيض على أسود، لأن القراءة الطويلة على ضوء المصباح هي غرضه.
الثمن · المطابقة التامة للصفحة تحتاج خطوط QCF لكل صفحة — ٦٠٤ ملفات، ملف لكل صفحة، مصمَّمة بمقاييس تجعل كل صفحة ١٥ سطرًا بالضبط. وخط أميري قرآن مع ضبط الحجم بالقياس تنزيل أصغر بكثير ويقترب منها.
- ٠٣
النص القرآني لا يُقتطع أبدًا
يُضبط حجم الخط على الورقة ببحث ثنائي، بحدٍّ أدنى ١٧ بكسل. وتحته، تُمرَّر الورقة.
ضبط النص على صفحة ثابتة مسألة قياس: حدِّد حجمًا، وقِس، ثم نصِّف المجال. لكن صفحة كثيفة على هاتف صغير لا تتسع بأي حجم يستطيع إنسان أن يقرأه مرتاحًا. وكان الاختيار بين تصغير الخط حتى يتسع وبين ترك الصفحة تُمرَّر. فانتصرت المقروئية، وهي مفروضة في الكود: إخفاء جزء من آية ليس نتيجة مقبولة في هذا التطبيق.
الثمن · على هاتف صغير، الصفحة الكثيفة ليست شاشة واحدة. تحتفظ بالإطار والكارتوش ورقم الورقة — لكنها تتحرك.
- ٠٤
النقاط تُشتق، لا تُراكَم
يُعاد حساب `totalPoints` من السجلات مع كل تغيير؛ والمكافآت المفردة تُسجَّل ولا تُضاف.
كان التطبيق يمنح النقاط من ستة مواضع. خمسة تكتب في مجموع جارٍ، وواحد يعيد حساب ذلك المجموع من الصفر. فإتمام التحدي اليومي يُظهر +١٠، وفي المرة التالية التي تسجّل فيها صلاة يمحوها إعادة الحساب — ومعها مكافآت الصيام والتسبيح. كان المستخدمون يفقدون نقاطًا استحقوها ولا يملكون وصفًا للعطل. وكان الحل اختيار نموذج واحد والالتزام به: المجموع مشتق من النشاط المخزَّن، فوجب أن يكون كل مصدر قابلًا للتمثيل، وصارت المكافآت المفردة تدخل خريطة مفهرسة بالتاريخ يقرأها إعادة الحساب.
الثمن · كان المستخدمون الحاليون يحملون تلك المكافآت داخل مجموع مخزَّن لا يستطيع الاشتقاق الجديد تفسيره. فترحيل لمرة واحدة يضع الفائض غير المفسَّر كمكافأة قديمة، فلم يتحرك رقم أحد يوم النشر.
- ٠٥
التهريب يُختار حسب السياق، لا حسب القيمة
تهريب HTML داخل HTML، وتهريب بالشرطة المائلة داخل JavaScript المضمَّن.
كان `escapeHtml` يُستخدم في كل مكان، بما في ذلك داخل خصائص `onclick`. والمتصفح يفكّ ترميز `'` إلى فاصلة عليا **قبل** أن يحلّل الخاصية كـJavaScript، فتُغلق الفاصلة النصَّ ويُنفَّذ كل ما بعده. عبارة تسبيح مخصصة فيها فاصلة عليا كانت تكفي لكسر الصفحة؛ وملف نسخة احتياطية مُعَدّ كان يكفي لتنفيذ كود بجلسة Firebase الخاصة بالمستخدم. وصارت عشرة مواضع تستخدم دالة تهرّب لطبقة JavaScript أولًا ولطبقة HTML ثانيًا، وتُرشَّح النسخ المستوردة مقابل قائمة مفاتيح مسموح بها مع إعادة التحقق من كل معرّف.
الثمن · المعالِجات المضمَّنة تبقى مضمَّنة حاليًا. والدالة موثَّقة كجسر للترميز القائم لا كنمط يُحتذى، والمستمعون المفوَّضون هم الخطوة التالية.
- ٠٦
المستند هو الذي يُمرَّر، لا الـ body
فقد `html` و`body` ارتفاعهما الثابت وتمريرهما الخاص.
`html { height: 100% }` مع `body { overflow-y: auto }` جعلا الـbody صندوق تمرير خاصًّا به بهدوء. فكان `window.scrollY` صفرًا دائمًا — ما يعني أن زر العودة للأعلى لا يفعل شيئًا، وأن تمرير الموجِّه لأعلى عند تغيير الشاشة لا يفعل شيئًا، وأن متصفحات الهاتف لا تستطيع طيّ شريط العنوان أبدًا. ثلاث مزايا ظاهرة كسرها تصريحان في CSS، ولم يبدُ أيٌّ منها مشكلة CSS.
الثمن · صندوق التمرير المتداخل يعطي تحكّمًا أدقّ فيما يتحرك. والتخلي عنه يستعيد سلوكيات المنصّة التي كان التطبيق يصارعها.
- ٠٧
اللمسة تُرسَم قبل أن تُحفَظ
تُعاد الشاشة من الحالة المحلية، ثم تذهب الكتابة إلى Firestore.
كان كل زر صلاة ينتظر رحلة كاملة إلى Firestore قبل تحديث الشاشة. وعلى اتصال بطيء كانت اللمسة تبدو ميتة؛ وحين تفشل الكتابة كانت تتعلّق ثوانيَ. والرسم أولًا يجعل اللمسة تحطّ في إطار واحد — قيست عند ٨٠ مللي ثانية — ثم يتبعها الحفظ.
الثمن · الكتابة الفاشلة تترك الشاشة متقدّمة على الخادم للحظة — ومسار الحفظ صار يُظهر فشله بنفسه، بعد أن كان يبتلعه تمامًا.
- ٠٨
مصدر واحد لترويسات الأمان
`vercel.json` وحده. وحُذفت سياسة الـ`<meta>` وملف `_headers`.
كانت هناك ثلاث سياسات لأمان المحتوى — وسم meta، و`vercel.json`، وملف `_headers` بصيغة Netlify — وقد تباعدت. والمتصفحات تطبّق **تقاطع** سياسة الترويسة وسياسة الـmeta، فكان كل مضيف يحجب مجموعة مختلفة من المزايا بصمت، ولم يكن في `firebase.json` ترويسات أصلًا. وتوحيدها أصلح كذلك عطلًا قائمًا: كان الكود ينادي `ipapi.co` ولا تسمح به أيٌّ من الثلاث، فكان تحديد الموقع بالـIP محجوبًا في الإنتاج.
الثمن · كانت سياسة الـmeta هي الحماية الوحيدة عند تقديم المجلد مباشرةً أثناء التطوير. وصار ذلك فرقًا موثَّقًا بين المحلي والمنشور.
التدقيق
قرأ التدقيق الـ١٥٬٤٤٧ سطرًا كلها بدل أخذ عينات، وأنتج ٤٣ ملاحظة: ٥ حرجة، و١١ عالية، و١٤ متوسطة، و١٣ طفيفة. كلٌّ منها تسمّي ملفًا وسطرًا وتصف العطل وصفًا محددًا.
أهم ثلاث كانت غير مرئية من الخارج، لأن كلًّا منها كانت تنتج بيانات خاطئة لا رسالة خطأ: نقاط يمحوها إعادة الحساب التالية، وصفحات قرآن تُحتسب بثماني نقاط بدل خمس، وبيانات خريطة النشاط تُحسب مع كل تغيير ثم لا تُكتب أبدًا — فكانت الخريطة لا تُظهر إلا اليوم الحالي، ومنذ أول نشر.
واثنتان أخريان كانتا ميزتين منشورتين لم تعملا قط. فطابور الكتابة دون اتصال كان كودًا ميتًا: دالة الإضافة معرَّفة ولا تُنادى أبدًا. والاختصارات الأربعة في ملف التعريف كانت تشير إلى معاملات `?screen=` لا يقرأها أي كود، فكانت الأربعة تفتح الشاشة الرئيسية.
وصحّح التقرير نفسه كذلك، وهذا هو الجزء الجدير بالبقاء. ادّعت ملاحظة وجود ثمانية محدِّدات CSS مكرَّرة؛ وقياس تداخل الخصائص أظهر أن ستة منها طبقات تتالٍ عادية وأن اثنين فقط ميتان فعلًا. فحُذف الاثنان وعُدِّل الادّعاء بدل التصرف بناءً عليه بصمت. فالملاحظة التي لا تستطيع إعادة إنتاجها ليست ملاحظة.
تهدئة الواجهة
كانت الواجهة القديمة تضع الذهبي على كل عنوان وحدّ وزر، إلى جانب أزرق وأصفر وأخضر شاردة من Tailwind لا علاقة لها بالهوية. وحين يكون كل شيء ذهبيًا، لا شيء ذهبي. فصار العمق من ست درجات خضراء متراكبة بدل الحدود، واللون الدلالي مجموعة هادئة من ثلاثة، واستُبدل كل لون مكتوب يدويًا في ملف الأنماط بـtoken — فصارت لوحة الألوان تعديلًا واحدًا لا بحثًا واستبدالًا.
ونالت الحركة المعاملة نفسها. كان `animate-pulse` يعمل دائمًا على أرقام ساكنة، والحركة بلا سبب للحركة ضجيج — وهي أيضًا أكثر ما يجعل الواجهة تبدو قديمة. والباقي نَفَسٌ مدته أربع عشرة ثانية خلف الصلاة القادمة: بطيء بما يكفي لتجاهله، وهو الموضع الوحيد الذي يُمثَّل فيه شيء حيّ. و`prefers-reduced-motion` محترمة في كل مكان.
والشاشات التي كانت تعتمد على اللون للفصل بين أقسامها وجب أن تجد ذلك الفصل في المسافات والخطوط بدلًا منه، وهو ما عنى إعادة بنائها لا إعادة تنسيقها. وهي تُعاد بناءً زوجًا زوجًا، مع تحقق ونشر، فيبقى أي ارتداد محصورًا في شاشتين لا في اثنتين وعشرين.
ما استُبعد عن قصد
ضبط النطاق جزء من العمل. كل بند هنا دُرس ثم استُبعد لسبب مذكور.
- إعادة كتابة
- التطبيق يعمل فعليًا وفيه سجل عبادة حقيقي. فوجب أن يكون كل تغيير ترحيلًا لا استبدالًا.
- أداة تجميع
- التطبيق يُنشر كوحدات ES خام بلا خطوة بناء. وإضافتها تُدخل خطوة يمكن أن تُنسى قبل النشر.
- إزالة نظام النقاط
- اختار العميل الإبقاء عليه وتهدئته. وحذفه كان سيصير قرار منتج لا قرار تصميم.
- خطوط QCF لكل صفحة
- ٦٠٤ ملفات خطوط لمطابقة تامة للصفحة. وأميري مع ضبط بالقياس تنزيل أصغر ويقترب بما يكفي.
- إعادة تصميم الـ٢٢ شاشة دفعة واحدة
- تُعاد الشاشات زوجًا زوجًا، مع تحقق ونشر، فيبقى أي ارتداد محصورًا في شاشتين.
لا تُدَّعى هنا أعداد مستخدمين ولا إيرادات ولا تحسينات أداء، لأن شيئًا من ذلك لم يُقَس. والقابل للتحقق هو الأعداد في هذه الصفحة، والاختبارات الـ٣٨ الناجحة، والرابط المنشور. وقد رُوجعت الأرقام على المستودع في ٥ سبتمبر ٢٠٢٦.
التقنيات
- JavaScript
- ES Modules
- Firebase
- Firestore
- Tailwind CSS
- Jest
- Vercel
الخدمات
- تدقيق الكود
- إعادة البناء المعماري
- تصميم المنتج
- نظام تصميم
- هندسة الواجهة
محتاج حاجة مبنية بنفس المستوى؟
نصمم ونبني أنظمة كاملة — مسار شراء يعمل، وصلاحيات، ولوحة تحكم تدير الشغل فعليًا. والعربية عندنا ليست ترجمة تُركَّب في الآخر.
