فحص آليّ لنقاط 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>
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، وهو يعلو على أي اقتراح في هذه الوثيقة:
- كل كود دارت يُكتب من الصفر. لا يُورَّث سطر من
apps/rider-archive-cubit(77 ملف) ولاapps/driver-archive-cubit(53 ملف) — السبب: أداؤها وشكلها مرفوضان، ووراثتها تعني وراثة المشكلة. تبقى للقراءة فقط ثم تُحذف بعد المرحلة 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
- ✅ عقد NestJS موثّق في
docs/38-api-contract.md— 37 controller +e2e-test.mjs، ومعظم الأشكال مُتحقَّقة حيّاً من السيرفر المنشور. المصدر الوحيد للحقيقة لطبقة الشبكة. - ⚠️ جزئي: الباك إند حيّ ويعمل (
/health→ 200)، والدخول والمستخدم والمحفظة وأنواع الرحلات والتعرفة والمسار والتقييم المعلّق كلها تحقّقت حيّاً. لكنe2e-test.mjsالكامل لم يُشغَّل: يحتاجOTP_DEV_MODE=trueوهو مطفأ على المنشور، والـSSH من الماك مرفوض (Permission denied (publickey)). المتبقّي غير المُتحقَّق مذكور فيdocs/38§13. بلوكر يحتاج المالك. - ✅ المرجع البصري في
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 الدنيا: التوكن لا يُخزَّن إلا في التخزين الآمن، وهذا أساس لا إضافة لاحقة.
flutter createنظيف لكل تطبيق، ثم استبدالandroid/وios/وshorebird.yamlبنسخة من*-archive-cubit(الهوية فقط — §2.ج).lib/فارغ يُبنى من جديد:core/(شبكة · تخزين · أخطاء · توجيه · DI) ثمfeatures/— بلا استيراد أي ملف من الأرشيف.- نقل الأصول من سيرو:
assets/+ الخطوط + الأيقونات + ملفات الترجمة. pubspecبالحد الأدنى:flutter_bloc·dio·go_router·get_it·freezed. الباقي يُضاف في مرحلته.- مواءمة الأذونات في
AndroidManifestوInfo.plistمع ما تحتاجه كل حزمة عند إضافتها — بلا لمس المعرّفات. - بوابة: يبني على السيرفر، يقلع على المحاكي بشاشة 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}/.
قراران يستحقّان المراجعة:
- لا
packages/tripz_ui. خطتي الأصلية (م2.11) اقترحت حزمة مشتركة، لكنdocs/23 §1وdocs/26 §10كلاهما مُلزِم ويفرضlib/core/designوlib/core/uiداخل كل تطبيق. اتُّبعت الوثيقتان، والملفّان متطابقان حرفياً بين التطبيقين (يُزامنان بـrsyncوأي تباعد يُكتشف بـdiff -rq). - رمز الفشل لا نصّه.
docs/23 §3يقول إن الـCubit يبني الرسالة العربية، وdocs/26 §4يوجب مرور كل نص بالترجمة — والـCubit لا يعرفBuildContext. الحل: الـCubit يُصدرAuthFailureكرمز، وتترجمه الواجهة في ملف واحد (auth_failure_text.dart). الشاشة تبقى بلا منطق، والنص بلغتين.
(الأصل المعتمد للمرحلة 2)
- تطبيق
docs/26-flutter-design-system.md+docs/23-flutter-conventions.md: theme · ألوان · تايبوغرافي · مسافات · حركة · RTL/LTR بأربع لغات. - حزمة مشتركة
packages/tripz_uiيستهلكها التطبيقان (تجنّب ازدواج الشاشات). - ميزانية أداء مكتوبة تُفحص في كل بوابة بعدها: بلا
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)
- طبقة الشبكة: Dio + interceptors + تجديد التوكن +
x-app-role. - شاشات: onboarding → إدخال الهاتف → OTP → إكمال الملف → استعادة الجلسة.
- مراعاة قرار هوية الراكب/السائق المنفصلة (قيد
tenant,phone_bidx,role- ترويسة
x-app-role) — يجب أن يكون موجوداً في NestJS، وإن لم يكن فهو عمل باك إند إضافي يُحسب في هذه المرحلة.
- ترويسة
- بوابة: دخول حقيقي من التطبيقين على 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وأربعة ويدجت (مفتاح الاتصال · العرض بمؤقّت · الرحلة الجارية · بطاقة الراكب).
قرارات تصميمية مقصودة:
GeoPointمستقلّ عن حزمة الخريطة؛intaleq_mapsمستوردة في ملف واحد فقط لكل تطبيق (ride_map.dart). تبديل محرّك الخريطة لا يلمس منطقاً.- الخريطة خارج الـ
BlocBuilderالذي يبني الورقة، ولهاBlocSelectorبما تحتاجه وحدها — نبضة موقع كل 25م لا تعيد بناء الشاشة. هذا تطبيق مباشر لميزانية الأداء (م2.12). - مهلة بحث محليّة 90 ثانية + استطلاع كل 5ث: الخادم قد لا يُطلق
expired/no_driversأبداً (ثغرة R1)، وانتظار حدثٍ قد لا يصل = شاشة بحث أبدية. وofferedDrivers == 0يُعالَج فوراً بلا انتظار المهلة. - الأجرة لا تُعرض إلا إن كانت صالحة:
/tariff/quoteيرجّع 200 بقيمnullصامتة عند المدخلات الخطأ (docs/38 §7). - موقع السائق يُرسَل بمسارين عمداً: REST يغذّي مطابقة Redis، والسوكت يغذّي خريطة الراكب — سقوط أحدهما لا يُعمي الآخر.
flutter analyze نظيف · الراكب 70 ملف/5,294 سطر · السائق 67 ملف/4,771 سطر.
(الأصل المعتمد للمرحلة 4)
- الخريطة مباشرة لـ map-saas بمفتاح
x-api-keyمقيّد ببصمة التطبيق (قرارmaps-direct-decision) — لا تمرير عبر الباك إند. - الراكب: اختيار المصدر/الوجهة · التسعير المسبق · تأكيد الطلب · انتظار سائق.
- السائق: أونلاين/أوفلاين · استقبال العروض · قبول/رفض.
- الواقع اللحظي (WebSocket) حسب
backend-archive/src/realtime. - بوابة: رحلة كاملة من الطلب إلى الإنهاء بين جهازين + الخريطة تتحرّك بسلاسة أثناء التتبّع (لا اهتزاز، لا إعادة رسم للشاشة كاملة).
المرحلة 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)
- حالات الرحلة · التتبّع الحيّ · الإلغاء · الإنهاء · الأجرة · التقييم.
- المحفظتان (محفظة الراكب = التزام، محفظة السائق = إيراد — لا تُدمجان).
المرحلة 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 يمنع أن تستورد ميزةٌ من أخرى، وكان هناك خمس مخالفات:
features/historyأُدمجت فيfeatures/trip— سجلّ الرحلات جزء من ميزة الرحلة لا ميزة مستقلّة.AppDrawerصعد إلىcore/ui/— هيكل تطبيق لا ميزة.AuthFailureصعد إلىcore/api/api_failure.dart، ومترجمه إلىcore/ui/failure_text.dart— مشتركان بين المصادقة والملف.AuthHeaderصارcore/ui/form_header.dart.ProfileRepositoryمستقلّ عنAuthRepository: تعديل الملف ليس مصادقة.
flutter analyze نظيف · الراكب 98 ملف/7,497 سطر · السائق 95 ملف/6,931 سطر.
فحوص آلية نظيفة: صفر استيراد بين ميزتين · core يستورد features من
di.dart وrouter.dart وحدهما · Firebase وintaleq_maps وgeolocator و
socket_io كلٌّ معزول في ملفه.
(الأصل المعتمد للمرحلة 6)
- الدراور · الملف الشخصي · الرحلات السابقة · الإشعارات · الدعم · اللغة.
- الوحدات المدفوعة السبع (
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 |
| بناء/اختبار على الماك | كل بناء وهجرة على السيرفر |