Files
tripz-llc/docs/37-newapps-cubit-plan.md
T
Hamza-AyedandClaude Opus 5 bb2c0de478 feat(apps): تسجيل السائق والدردشة والكوبون — إكمال التغطية
فحص آليّ لنقاط docs/38 مقابل الكود كشف ثغرات لم تظهر بالقراءة.

## تسجيل السائق — أخطر ثغرة
لم يكن موجوداً أصلاً: سائق جديد يسجّل دخوله ثم يقف. بُنيت features/onboarding
كاملة — تقديم ← ملف ← مركبة ← وثائق ← انتظار الاعتماد.
- الخطوة تُشتقّ من حالة الخادم لا من تقدّم محلّي: سائق يعيد تثبيت التطبيق
  يعود إلى حيث وقف لا إلى البداية
- الاعتماد يقع على الخادم تلقائياً حين تكتمل الوثائق؛ التطبيق لا يعتمد أحداً
- عند الاعتماد خروج إجباري: الدور يتغيّر على الخادم والتوكن القديم يحمل
  القديم، فتفشل نقاط السائق بـ403 (مصيدة docs/38 §5)
- بوابة توجيه: مستخدم دوره ليس driver يُحجز في /onboarding
- الوثائق تُصوَّر بالكاميرا لا من المعرض: أصعب تزويراً

## الدردشة والكوبون
- Features.chat كان مفعّلاً بلا ميزة. features/chat باستطلاع كل خمس ثوان —
  قائمة أحداث الخادم لا تتضمّن الرسائل (docs/38 §9)، فالاستطلاع قيد خادم لا
  اختيار تصميمي
- الكوبون: حقل في ورقة التأكيد خلف طبقتَي الميزات، يُقيَّم على الخادم

## Features.calls أُطفئ صراحةً
الخادم يدعم WebRTC والتطبيق لا. عَلَم مفعّل بلا ميزة كذبٌ على القارئ التالي.

## بنية
- ApiClient.upload للرفع متعدّد الأجزاء
- AuthFailure → ApiFailure في core/api: يخدم المصادقة والملف والتسجيل
- tool/sync_from_rider.sh: المزامنة اليدوية بين التوأمين انكسرت أربع مرات،
  فصارت سكربتاً واحداً يعرّف ما يملكه كل تطبيق

flutter analyze نظيف · الراكب 101 ملف/7,809 سطر · السائق 110 ملف/8,116 سطر.
فحوص آلية: صفر استيراد بين ميزتين · صفر عَلَم مفعّل بلا ميزة · كل نقاط العقد
المخصّصة للتطبيقين مستهلكة.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 00:04:28 +03:00

28 KiB

37 — خطة rider_new و driver_new (Cubit + باك إند NestJS)

تاريخ: 2026-08-04 · الحالة: مسودّة للموافقة

1. القرار المطلوب ومعناه

المطلوب: بناء تطبيقين جديدين (rider_new, driver_new) بـ Bloc/Cubit، يعملان على backend-archive/ (NestJS)، بأخذ الأصول والحزم من تطبيقات سيرو، مع الحفاظ على build.gradle وInfo.plist كي لا تنكسر هوية النشر.

هذا عكس لقرارين مقفلين:

القرار التاريخ المرجع
الباك إند المعتمد = سيرو PHP، وNestJS مؤرشف 2026-07-27 backend-archive/README-ARCHIVE.md, docs/34
التطبيقات = نقل سيرو GetX بدل إعادة بناء Cubit 2026-07-21 docs/30

سبب الأرشفة وقتها كان مكتوباً صراحة: «NestJS مكتمل تقنياً لكن بلا تطبيقات تعمل عليه، وبناء تطبيقات له من الصفر أطول من نقل تطبيقات سيرو». الخطة الحالية تقول: نعم، سنبني التطبيقات. هذا يحلّ الاعتراض — بشرط قبول أن شغل نقل PHP يتوقّف مؤقتاً.

2. القاعدة الحاكمة — بناء من الصفر، وسيرو مرجع لا مصدر نسخ

قرار المالك 2026-08-04، وهو يعلو على أي اقتراح في هذه الوثيقة:

  1. كل كود دارت يُكتب من الصفر. لا يُورَّث سطر من apps/rider-archive-cubit (77 ملف) ولا apps/driver-archive-cubit (53 ملف) — السبب: أداؤها وشكلها مرفوضان، ووراثتها تعني وراثة المشكلة. تبقى للقراءة فقط ثم تُحذف بعد المرحلة 3.
  2. سيرو مرجع بصري وسلوكي، لا مصدر نسخ. نقرأ منه: كيف بُنيت الشاشة، ما ترتيب الخطوات، أي حالات حافة عولجت ميدانياً. لا ننسخ منه ملفات دارت.
  3. سقف الجودة = شكل تريبز الحالي أو أفضل. الهدف ليس المعادلة بل التفوّق: أداء (60fps، بلا rebuild زائد) وشكل (نظام تصميم واحد متّسق).

أ. الشيء الوحيد الذي يُنقل حرفياً: الطبقة الأصلية

لأن كل شيء آخر يُكتب من جديد، تبقى الطبقة الأصلية (android/, ios/, shorebird.yaml) هي الوحيدة التي تُنقل كما هي — لأنها هوية النشر ولا يجوز إعادة اختراعها. مصدرها محسوم في §2.ج.

ب. pubspec — قائمة تُبنى بالطلب، لا تُنسخ

siro_rider/pubspec.yaml فيه ~60 اعتماد، ومنها get وget_storage كحزمتين محليّتين معدّلتين (./packages/get) — أي أن نسخه يجرّ GetX كاملاً معه، وكذلك intaleq_maps وsecure_string_operations المربوطة بعقد PHP.

لذلك pubspec الجديد يُكتب من الصفر، وتُضاف الحزمة فقط عند الحاجة إليها في مرحلتها — لا قائمة مسبقة من 60 اعتماداً. سيرو يجيب على سؤال «أي حزمة نجحت ميدانياً لهذه المهمة»، لا «ما القائمة».

  • ✅ نأخذ منه: الأصول (assets/)، والحزم المحليّة غير المرتبطة بـ GetX (secure_string_operations, trip_overlay_plugin, bubble-master)، والحزم الأصلية (webrtc · geolocator · location · permission_handler · firebase · local_auth · image_cropper · live_activities · quick_actions …) لأن هذي هي القيمة الحقيقية المجرّبة ميدانياً.
  • ❌ لا ننسخ: get, get_storage — تُستبدل بـ flutter_bloc + hydrated_bloc/shared_preferences.
  • ⚠️ نراجع: intaleq_maps (قرار الخرائط المباشرة لـ map-saas بمفتاح x-api-key قائم — يبقى)، وsocket_io_client (NestJS realtime قد يكون عقده مختلفاً — يُتحقّق من backend-archive/src/realtime).

ج. خطر هوية النشر — يُحسم قبل أي نسخ

التطبيق applicationId shorebird app_id
Siro/siro_driver com.siro.siro_driver f8e9c087-…0120c
Tripz/apps/driver com.sefer_driver f8e9c087-…0120c ← نفس سيرو
apps/driver-archive-cubit com.sefer_driver 68cc9345-…5836d
Siro/siro_rider com.siro.rider 44245793-…305dd
Tripz/apps/rider com.mobileapp.store.ride 44245793-…305dd ← نفس سيرو
apps/rider-archive-cubit com.mobileapp.store.ride 496cb3ac-…a13ac

قرار المالك: لكل تطبيق تريبز هويته الخاصة، منفصلة تماماً عن سيرو. لذلك apps/driver وapps/rider — اللذان يحملان app_id سيرو نفسه — أثرُ نسخٍ خاطئ عند نقل GetX، وليسا مرجع هوية. باتش شوربيرد منهما قد يُدفع لتطبيق سيرو المنشور. المرجع الصحيح هو *-archive-cubit:

applicationId shorebird app_id
driver_new com.sefer_driver 68cc9345-…5836d
rider_new com.mobileapp.store.ride 496cb3ac-…a13ac

القاعدة: لا يُنسخ أي build.gradle أو shorebird.yaml أو Info.plist من سيرو. الطبقة الأصلية تُنقل من *-archive-cubit (هي وحدها كودٌ لم يكتبه النموذج السابق — ملفات إعداد مُولّدة من Flutter ومعدّلة يدوياً)، ويُقرأ من سيرو ما يلزم من أذونات وخدمات خلفية و entitlements فيُكتب يدوياً سطراً سطراً.

3. الخطة بالترتيب

المرحلة 0 — تثبيت الأساس (قبل أي كود) — نُفّذت 2026-08-04

  1. ✅ عقد NestJS موثّق في docs/38-api-contract.md — 37 controller + e2e-test.mjs، ومعظم الأشكال مُتحقَّقة حيّاً من السيرفر المنشور. المصدر الوحيد للحقيقة لطبقة الشبكة.
  2. ⚠️ جزئي: الباك إند حيّ ويعمل (/health → 200)، والدخول والمستخدم والمحفظة وأنواع الرحلات والتعرفة والمسار والتقييم المعلّق كلها تحقّقت حيّاً. لكن e2e-test.mjs الكامل لم يُشغَّل: يحتاج OTP_DEV_MODE=true وهو مطفأ على المنشور، والـSSH من الماك مرفوض (Permission denied (publickey)). المتبقّي غير المُتحقَّق مذكور في docs/38 §13. بلوكر يحتاج المالك.
  3. ✅ المرجع البصري في docs/39-siro-screens.md — 89 شاشة راكب + 113 شاشة سائق، مصنّفة داخل/خارج النطاق، مع ترتيب الخطوات وحالات الحافة وملاحظات الأداء. بلا نسخ كود.

ما انحسم في م0 (كان مخاطرة، صار حقيقة):

  • x-app-role منفّذ فعلاً: نفس الرقم أعطى user id مختلفاً للراكب والسائق. لا عمل باك إند إضافي في م3.
  • access_token = 15 دقيقة فقط → تجديد استباقي إجباري في طبقة الشبكة.
  • GET /tenant/config/:slug يرجّع خريطة ميزات كاملة → هي ربط lite/pro/max بالخادم، لا أعلام مكتوبة في الكود (يعدّل فهم م6.25).
  • حسابا مراجعة المتاجر 0790000001/0790000002 بالرمز 1234 يعملان بلا OTP_DEV_MODE — مدخلنا للاختبار حتى يتوفّر وصول السيرفر.
  • ثغرات مؤكَّدة على المنشور: /maps/geocode معطوب (قائمة دول فارغة)، و/tariff/quote يفشل صامتاً بقيم null عند إرسال المعاملات الخطأ.

المرحلة 1 — سقالة rider_new و driver_new من الصفر — نُفّذت 2026-08-04، متوقّفة عند البوابة

الحالة: الخطوات 4–8 ✅ · الخطوة 9 (البوابة) ⚠️ جزئية — flutter analyze نظيف في التطبيقين (0 مشاكل)، لكن البناء الفعلي لم يُجرَّب: SSH للسيرفر مرفوض (بلوكر م0.2 نفسه).

قرارات اتُّخذت أثناء التنفيذ ولم تكن في الخطة:

  • الأصول مصدرها *-archive-cubit لا سيرو. أصول الأرشيف مجموعة أشمل من أصول سيرو (كل صور سيرو + login_hero/name_hero/onboarding_1..3/ splash_logo) وخطوطها هي خطوط docs/26 (IBM Plex Sans Arabic + Inter) بينما سيرو عليه mohanad/josefin. النسخ من سيرو كان سيكون تراجعاً. استُكمل السائق بعشرة ملفات ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان). الأصول ليست كوداً كتبه النموذج السابق، فلا تخالف قاعدة «من الصفر».
  • أُصلح تعارض هوية في iOS للسائق: هدف RunnerTests في driver_new كان يحمل com.mobileapp.store.ride (bundle الراكب) — نُقل إلى com.sefer.driver.RunnerTests.
  • flutter_secure_storage + shared_preferences أُضيفتا لقائمة م1 الدنيا: التوكن لا يُخزَّن إلا في التخزين الآمن، وهذا أساس لا إضافة لاحقة.
  1. flutter create نظيف لكل تطبيق، ثم استبدال android/ وios/ و shorebird.yaml بنسخة من *-archive-cubit (الهوية فقط — §2.ج).
  2. lib/ فارغ يُبنى من جديد: core/ (شبكة · تخزين · أخطاء · توجيه · DI) ثم features/ — بلا استيراد أي ملف من الأرشيف.
  3. نقل الأصول من سيرو: assets/ + الخطوط + الأيقونات + ملفات الترجمة.
  4. pubspec بالحد الأدنى: flutter_bloc · dio · go_router · get_it · freezed. الباقي يُضاف في مرحلته.
  5. مواءمة الأذونات في AndroidManifest وInfo.plist مع ما تحتاجه كل حزمة عند إضافتها — بلا لمس المعرّفات.
  6. بوابة: يبني على السيرفر، يقلع على المحاكي بشاشة splash، وflutter analyze نظيف.

المرحلة 2 — نظام التصميم — نُفّذت 2026-08-04

مُنجز: core/design/ (tokens · typography · TripzColors · buildTheme) · core/ui/ عدّة من 11 مكوّناً · core/l10n/ ARB عربي/إنجليزي مع gen_l10n · SettingsCubit (المظهر واللغة محفوظان محلياً).

تصحيح بنيوي: بنية م1 كانت تخالف docs/23 §1 المُلزِم (lib/app/ و core/config/ وcore/network/). أُعيدت للشجرة المفروضة حرفياً: app.dart · core/{config,build_config,di,router}.dart · core/{design,ui,l10n,api,storage,session}/ · features/<f>/{cubit,data,view}/.

قراران يستحقّان المراجعة:

  1. لا packages/tripz_ui. خطتي الأصلية (م2.11) اقترحت حزمة مشتركة، لكن docs/23 §1 وdocs/26 §10 كلاهما مُلزِم ويفرض lib/core/design و lib/core/ui داخل كل تطبيق. اتُّبعت الوثيقتان، والملفّان متطابقان حرفياً بين التطبيقين (يُزامنان بـrsync وأي تباعد يُكتشف بـdiff -rq).
  2. رمز الفشل لا نصّه. docs/23 §3 يقول إن الـCubit يبني الرسالة العربية، وdocs/26 §4 يوجب مرور كل نص بالترجمة — والـCubit لا يعرف BuildContext. الحل: الـCubit يُصدر AuthFailure كرمز، وتترجمه الواجهة في ملف واحد (auth_failure_text.dart). الشاشة تبقى بلا منطق، والنص بلغتين.

(الأصل المعتمد للمرحلة 2)

  1. تطبيق docs/26-flutter-design-system.md + docs/23-flutter-conventions.md: theme · ألوان · تايبوغرافي · مسافات · حركة · RTL/LTR بأربع لغات.
  2. حزمة مشتركة packages/tripz_ui يستهلكها التطبيقان (تجنّب ازدواج الشاشات).
  3. ميزانية أداء مكتوبة تُفحص في كل بوابة بعدها: بلا setState فوق شجرة كبيرة · BlocSelector/buildWhen افتراضياً · const على كل widget ساكن · قوائم كسولة · بلا عمل ثقيل في build. سبب الأداء السيئ سابقاً يُشخّص من *-archive-cubit مرة واحدة ويُكتب هنا كقائمة ممنوعات.

المرحلة 3 — التسجيل والدخول — نُفّذت 2026-08-04، البوابة معلّقة

مُنجز: طبقة الشبكة (Dio + تجديد استباقي بطلقة واحدة + x-app-role + x-device-id من device_info_plus) · AuthRepository · SessionCubit في core/session/ خلف واجهة SessionSource (كي لا يستورد core من features) · LoginCubit بخطواته الخمس · ProfileCubit · ست شاشات: الشروط ← إذن الموقع ← الهاتف ← الرمز ← إكمال الملف ← هيكل الرئيسية.

نُقلت من سيرو كسلوك لا ككود: بوابتان قبل أي حقل إدخال · إعادة فحص الإذن عند العودة من إعدادات النظام (didChangeAppLifecycleState) · تحقّق الهاتف الثلاثي (فارغ/صفر بادئ/قصير). أُضيف ما ينقص سيرو: عدّاد إعادة الإرسال — بدونه تُحظر ثلاث ضغطات المستخدمَ خمس دقائق (docs/38 §2).

flutter analyze نظيف في التطبيقين · 48 ملف · 3,140 سطر لكل تطبيق · الفرق بينهما أربعة ملفات فقط (build_config · config · ملفّا ARB). البوابة (دخول حقيقي على الخادم) تنتظر التشغيل.

(الأصل المعتمد للمرحلة 3)

  1. طبقة الشبكة: Dio + interceptors + تجديد التوكن + x-app-role.
  2. شاشات: onboarding → إدخال الهاتف → OTP → إكمال الملف → استعادة الجلسة.
  3. مراعاة قرار هوية الراكب/السائق المنفصلة (قيد tenant,phone_bidx,role
    • ترويسة x-app-role) — يجب أن يكون موجوداً في NestJS، وإن لم يكن فهو عمل باك إند إضافي يُحسب في هذه المرحلة.
  4. بوابة: دخول حقيقي من التطبيقين على NestJS المشتغل + لقطات شاشة تُقارن بمرجع سيرو، ولا تُقبل إن كانت أدنى منه.

المرحلة 4 — الخريطة وطلب الرحلة — نُفّذت 2026-08-04، البوابة معلّقة

حُسم تعارض الخرائط المسجّل في docs/38 §7 لصالح القرار الأحدث (2026-07-20): كل الخرائط — البلاطات والبحث والعكسي والمسار — مباشرة إلى map-saas.intaleqapp.com بترويسة x-api-key، بلا مرور بباك إند تريبز. نقاط /maps/* في الباك إند لا تُستعمل، وإحداها (geocode) معطوبة على المنشور أصلاً. تُحقّق العقد حيّاً بـcurl قبل الكتابة.

المنجز:

  • core/api/antlaq_api.dart — عميل منفصل بلا AuthInterceptor: توكن تريبز لا شأن لخادم الخرائط به، وإرساله إليه تسريبٌ بلا مقابل.
  • core/realtime/realtime_service.dart — Socket.IO واحد للتطبيق: trip:update · driver:location · trip:offer · trip:offer_taken.
  • core/location/location_service.dart — نقطة الموقع الوحيدة، بمرشّح 25م.
  • features/trip/data/ — سبعة نماذج + MapsRepository + TripRepository (متطابق حرفياً بين التوأمين).
  • الراكب: RideCubit بستّ مراحل + RidePage وثمانية ويدجت (الخريطة · دبّوس المنتصف · المخطّط · الخط الزمني · اختيار النوع · التأكيد · البحث عن سائق · الرحلة الجارية).
  • السائق: DutyCubit بأربع مراحل + DutyPage وأربعة ويدجت (مفتاح الاتصال · العرض بمؤقّت · الرحلة الجارية · بطاقة الراكب).

قرارات تصميمية مقصودة:

  1. GeoPoint مستقلّ عن حزمة الخريطة؛ intaleq_maps مستوردة في ملف واحد فقط لكل تطبيق (ride_map.dart). تبديل محرّك الخريطة لا يلمس منطقاً.
  2. الخريطة خارج الـBlocBuilder الذي يبني الورقة، ولها BlocSelector بما تحتاجه وحدها — نبضة موقع كل 25م لا تعيد بناء الشاشة. هذا تطبيق مباشر لميزانية الأداء (م2.12).
  3. مهلة بحث محليّة 90 ثانية + استطلاع كل 5ث: الخادم قد لا يُطلق expired/no_drivers أبداً (ثغرة R1)، وانتظار حدثٍ قد لا يصل = شاشة بحث أبدية. وofferedDrivers == 0 يُعالَج فوراً بلا انتظار المهلة.
  4. الأجرة لا تُعرض إلا إن كانت صالحة: /tariff/quote يرجّع 200 بقيم null صامتة عند المدخلات الخطأ (docs/38 §7).
  5. موقع السائق يُرسَل بمسارين عمداً: REST يغذّي مطابقة Redis، والسوكت يغذّي خريطة الراكب — سقوط أحدهما لا يُعمي الآخر.

flutter analyze نظيف · الراكب 70 ملف/5,294 سطر · السائق 67 ملف/4,771 سطر.

(الأصل المعتمد للمرحلة 4)

  1. الخريطة مباشرة لـ map-saas بمفتاح x-api-key مقيّد ببصمة التطبيق (قرار maps-direct-decision) — لا تمرير عبر الباك إند.
  2. الراكب: اختيار المصدر/الوجهة · التسعير المسبق · تأكيد الطلب · انتظار سائق.
  3. السائق: أونلاين/أوفلاين · استقبال العروض · قبول/رفض.
  4. الواقع اللحظي (WebSocket) حسب backend-archive/src/realtime.
  5. بوابة: رحلة كاملة من الطلب إلى الإنهاء بين جهازين + الخريطة تتحرّك بسلاسة أثناء التتبّع (لا اهتزاز، لا إعادة رسم للشاشة كاملة).

المرحلة 5 — مسار الرحلة وما بعدها — نُفّذت 2026-08-04

  • التقييم مفروض لا اختياري: SessionCubit يفحص GET /trips/rating/pending بعد إثبات الجلسة، ووجودُ رحلة معلّقة يحجز التطبيق كله عند /rating حتى تُحسم. دورُ المستخدم في الرحلة يأتي من الخادم لا من نوع التطبيق (نفس الرقم قد يكون له حسابان — docs/38 §1). التقييم المزدوج يرجّع 400 وهو نجاحٌ من منظور المستخدم: الرحلة مُقيَّمة فعلاً، فلا يُحبس في شاشة بلا مخرج.
  • core/format/money.dart: كل المبالغ تصل نصوصاً، والتحويل والعرض يمرّان من نقطة واحدة. عدد المنازل من العملة (JOD ثلاث)، والعملة من إعداد المستأجر لا من ثابت في التطبيق.
  • المحفظتان لا تُوحَّدان: الراكب WalletPage (رصيد + شحن = التزام على المنصّة)، والسائق EarningsPage (أرباح + سحب = إيراد له). شكلهما متقارب وحسابهما مختلف — دمجهما خطأ محاسبي لا اختصار (docs/39 §4).
  • السحب بخطوتين: request يرسل رمزاً بلا خصم، وconfirm هو ما يخصم ويحجز. شاشة سيرو كانت خطوة واحدة ولم تُقلَّد. الرمز الخاطئ لا يُرجع المستخدم لخطوة المبلغ — الطلب قائم على الخادم أصلاً.
  • الرصيد التشغيلي معروض منفصلاً عن الأرباح: منه تُخصم العمولة، ونفاده يوقف وصول الطلبات — خلطه بالأرباح يخفي سبب توقّف العمل عن السائق.
  • FareSummary يعرض رقم كلّ طرف: price_for_passenger للراكب و price_for_driver للسائق — حقلان منفصلان والفرق عمولة (docs/38 §4).
  • الإلغاء بتأكيد صريح مع سبب. السبب لا يقبله عقد الخادم بعد، لكن السؤال يخفض الإلغاء العرضي ويجهّز الحقل.

flutter analyze نظيف · الراكب 86 ملف/6,280 سطر · السائق 83 ملف/5,818 سطر.

(الأصل المعتمد للمرحلة 5)

  1. حالات الرحلة · التتبّع الحيّ · الإلغاء · الإنهاء · الأجرة · التقييم.
  2. المحفظتان (محفظة الراكب = التزام، محفظة السائق = إيراد — لا تُدمجان).

المرحلة 6 — الدراور والإضافات — نُفّذت 2026-08-04

  • طبقتا الميزات مُطبَّقتان معاً في AppDrawer: Features.wallet && context.tenantHas('wallet'). الأولى const تقرّر ما يُبنى في الـbinary (إطفاؤها يحذف الكود فلا يُستخرج — docs/23 §5)، والثانية من GET /tenant/config/:slug تقرّر ما يُعرض ممّا بُني حسب اشتراك المستأجر (docs/38 §10). هذا هو ربط lite/pro/max عملياً.
  • TenantCubit يعرض النسخة المخزّنة فوراً ثم يحدّثها من الشبكة: إقلاعٌ بلا إنترنت يعرض التطبيق بميزاته المعروفة آخر مرة، لا شاشة فارغة.
  • الشاشات: الدراور · تعديل الحساب · سجلّ الرحلات · الإعدادات (مظهر + لغة) · المساعدة. الرقم في تعديل الحساب معطّل مع سبب معروض لا مخفي: هو هوية الحساب على الخادم وتغييره يعني حساباً آخر (docs/38 §1).
  • الإشعارات: PushService معزول — الملف الوحيد الذي يعرف Firebase. التسجيل يقع بعد إثبات الجلسة عبر خطّاف في SessionCubit، ويشترك في onTokenRefresh (بدونه تتوقّف الإشعارات بصمت بعد أسابيع). الإعداد الأصلي كان جاهزاً مسبقاً: google-services.json منقول وbuild.gradle يطبّق com.google.gms.google-services أصلاً.

تصحيحات بنيوية أُجريت في هذه المرحلة (كشفها فحص آلي لا قراءة): docs/23 §1 يمنع أن تستورد ميزةٌ من أخرى، وكان هناك خمس مخالفات:

  1. features/history أُدمجت في features/trip — سجلّ الرحلات جزء من ميزة الرحلة لا ميزة مستقلّة.
  2. AppDrawer صعد إلى core/ui/ — هيكل تطبيق لا ميزة.
  3. AuthFailure صعد إلى core/api/api_failure.dart، ومترجمه إلى core/ui/failure_text.dart — مشتركان بين المصادقة والملف.
  4. AuthHeader صار core/ui/form_header.dart.
  5. ProfileRepository مستقلّ عن AuthRepository: تعديل الملف ليس مصادقة.

flutter analyze نظيف · الراكب 98 ملف/7,497 سطر · السائق 95 ملف/6,931 سطر. فحوص آلية نظيفة: صفر استيراد بين ميزتين · core يستورد features من di.dart وrouter.dart وحدهما · Firebase وintaleq_maps وgeolocator و socket_io كلٌّ معزول في ملفه.

(الأصل المعتمد للمرحلة 6)

  1. الدراور · الملف الشخصي · الرحلات السابقة · الإشعارات · الدعم · اللغة.
  2. الوحدات المدفوعة السبع (docs/33) خلف أعلام const حسب نموذج lite/pro/max — بناء مولَّد، لا فروع.

3.7 إكمال التغطية (2026-08-05)

فحصٌ آليّ لنقاط docs/38 مقابل الكود كشف ثغرات لم تظهر بالقراءة:

  • تسجيل السائق لم يكن موجوداً أصلاً — أخطر ثغرة: سائق جديد يسجّل دخوله ثم يقف. بُنيت features/onboarding كاملة: تقديم ← ملف ← مركبة ← وثائق ← انتظار الاعتماد. الخطوة تُشتقّ من حالة الخادم لا من تقدّم محلّي، فسائقٌ يعيد تثبيت التطبيق يعود إلى حيث وقف. والاعتماد يقع على الخادم تلقائياً حين تكتمل الوثائق — التطبيق لا يعتمد أحداً. وعند الاعتماد خروجٌ إجباري: الدور يتغيّر على الخادم والتوكن القديم يحمل القديم فتفشل نقاط السائق بـ403.
  • بوابة توجيه: مستخدمٌ دوره ليس driver يُحجز في /onboarding.
  • الدردشة (Features.chat كان مفعّلاً بلا ميزة): features/chat باستطلاع كل خمس ثوانٍ — قائمة أحداث الخادم لا تتضمّن الرسائل (docs/38 §9)، فالاستطلاع قيد خادم لا اختيار تصميمي.
  • الكوبون: حقل في ورقة التأكيد خلف طبقتَي الميزات؛ يُقيَّم على الخادم.
  • Features.calls أُطفئ صراحةً: الخادم يدعم WebRTC والتطبيق لا. عَلَمٌ مفعّل بلا ميزة كذبٌ على القارئ التالي.
  • ApiClient.upload للرفع متعدّد الأجزاء · AuthFailure صار ApiFailure في core/api لأنه يخدم المصادقة والملف والتسجيل معاً.
  • tool/sync_from_rider.sh: المزامنة اليدوية بين التوأمين انكسرت أربع مرات، فصارت سكربتاً واحداً يعرّف ما يملكه كل تطبيق. تُشغَّل بعده flutter gen-l10n وflutter analyze.

flutter analyze نظيف · الراكب 101 ملف/7,809 سطر · السائق 110 ملف/8,116 سطر.

4. مصير المجلدات القائمة

المجلد المصير
apps/rider-archive-cubit, driver-archive-cubit مصدر الطبقة الأصلية في م1، ومصدر تشخيص الأداء في م2 — ثم يُحذفان بعد نجاح بوابة م3
apps/rider, apps/driver (نقل GetX) مرجع سلوك ميداني فقط → apps/*-archive-getx بعد م3. ليسا مرجع هوية (§2.ج)
Siro/siro_rider, siro_driver مرجع خارجي دائم — لا يُعدَّل ولا يُنسخ منه دارت
باك إند PHP (docs/34) يتوقّف العمل عليه — لا يُحذف

5. المخاطر

الخطر التخفيف
تعارض shorebird app_id بين سيرو وتريبز الهوية من *-archive-cubit حصراً (§2.ج)؛ يُفحص قبل أول باتش
تكرار الأداء السيئ نفسه م2.12 ميزانية أداء مكتوبة + بوابة أداء في كل مرحلة
«من الصفر» تتحول لنسخ صامت من الأرشيف ممنوع أي import من *-archive-*؛ يُفحص بـ grep عند كل بوابة
NestJS «جاهز» لكن غير مختبر منذ 2026-07 بوابة م0.2 قبل أي شغل تطبيق
هوية الراكب/السائق المنفصلة غير منفّذة في NestJS يُتحقّق في م0.1
جرّ GetX بالغلط عبر pubspec ممنوع get/get_storage في *_new
بناء/اختبار على الماك كل بناء وهجرة على السيرفر