Files
tripz-llc/docs/37-newapps-cubit-plan.md
T
Hamza-AyedandClaude Opus 5 69da4abc01 feat(apps): سقالة rider_new و driver_new على Cubit — المرحلة 1
بناء من الصفر على باك إند NestJS، بقرار المالك 2026-08-04 الذي يعكس قرارَي
نقل GetX (2026-07-21) واعتماد باك إند PHP (2026-07-27). لا يُورَّث سطر دارت
من apps/*-archive-cubit، وسيرو مرجع بصري وسلوكي لا مصدر نسخ.

الوثائق:
- docs/37: الخطة الكاملة بستّ مراحل وبواباتها
- docs/38: عقد الـAPI من 37 controller، معظمه متحقَّق حيّاً من السيرفر المنشور
- docs/39: جرد 202 شاشة في تطبيقَي سيرو، مصنّفة داخل/خارج النطاق

الطبقة الأصلية منقولة من *-archive-cubit وحدها لأنها هوية النشر:
- rider_new  → com.mobileapp.store.ride · shorebird 496cb3ac
- driver_new → com.sefer_driver (أندرويد) · com.sefer.driver (iOS) · 68cc9345
- أُصلح تعارض: هدف RunnerTests في driver_new كان يحمل bundle الراكب

الأصول مصدرها *-archive-cubit لا سيرو: أصول الأرشيف مجموعة أشمل (كل صور
سيرو + صور تريبز) وخطوطها هي خطوط docs/26. استُكمل السائق بعشرة ملفات
ناقصة من سيرو (شعارات مزوّدي الدفع + صوتان).

lib/ مكتوب من الصفر (11 ملف لكل تطبيق):
- AppConfig بأعلام const — أساس نموذج lite/pro/max
- TokenStore على التخزين الآمن، يقرأ exp محليّاً بلا حزمة خارجية
- AuthInterceptor بتجديد استباقي وطلقة واحدة — عمر التوكن 15 دقيقة فقط
- ApiClient و ApiException يفهم مصفوفة message في NestJS

flutter analyze نظيف في التطبيقين. البناء الفعلي لم يُجرَّب بعد: بوابة
المرحلة 1 تتطلّب البناء على السيرفر، والوصول إليه غير متاح حالياً.

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

202 lines
15 KiB
Markdown

# 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`](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`](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 الدنيا:
التوكن لا يُخزَّن إلا في التخزين الآمن، وهذا أساس لا إضافة لاحقة.
4. `flutter create` نظيف لكل تطبيق، ثم **استبدال `android/` و`ios/` و
`shorebird.yaml` بنسخة من `*-archive-cubit`** (الهوية فقط — §2.ج).
5. `lib/` فارغ يُبنى من جديد: `core/` (شبكة · تخزين · أخطاء · توجيه · DI)
ثم `features/` — بلا استيراد أي ملف من الأرشيف.
6. نقل الأصول من سيرو: `assets/` + الخطوط + الأيقونات + ملفات الترجمة.
7. `pubspec` بالحد الأدنى: `flutter_bloc` · `dio` · `go_router` ·
`get_it` · `freezed`. الباقي يُضاف في مرحلته.
8. مواءمة الأذونات في `AndroidManifest` و`Info.plist` مع ما تحتاجه كل حزمة
عند إضافتها — بلا لمس المعرّفات.
9. **بوابة**: يبني على السيرفر، يقلع على المحاكي بشاشة splash،
و`flutter analyze` نظيف.
### المرحلة 2 — نظام التصميم (هنا يُكسب الشكل أو يُخسر)
10. تطبيق `docs/26-flutter-design-system.md` + `docs/23-flutter-conventions.md`:
theme · ألوان · تايبوغرافي · مسافات · حركة · RTL/LTR بأربع لغات.
11. حزمة مشتركة `packages/tripz_ui` يستهلكها التطبيقان (تجنّب ازدواج الشاشات).
12. **ميزانية أداء مكتوبة** تُفحص في كل بوابة بعدها: بلا `setState` فوق شجرة
كبيرة · `BlocSelector`/`buildWhen` افتراضياً · `const` على كل widget ساكن ·
قوائم كسولة · بلا عمل ثقيل في `build`. سبب الأداء السيئ سابقاً يُشخّص من
`*-archive-cubit` **مرة واحدة** ويُكتب هنا كقائمة ممنوعات.
### المرحلة 3 — التسجيل والدخول (OTP) كاملاً
13. طبقة الشبكة: Dio + interceptors + تجديد التوكن + `x-app-role`.
14. شاشات: onboarding → إدخال الهاتف → OTP → إكمال الملف → استعادة الجلسة.
15. مراعاة قرار **هوية الراكب/السائق المنفصلة** (قيد `tenant,phone_bidx,role`
+ ترويسة `x-app-role`) — يجب أن يكون موجوداً في NestJS، وإن لم يكن فهو
عمل باك إند إضافي يُحسب في هذه المرحلة.
16. **بوابة**: دخول حقيقي من التطبيقين على NestJS المشتغل + لقطات شاشة تُقارن
بمرجع سيرو، ولا تُقبل إن كانت أدنى منه.
### المرحلة 4 — الخريطة وطلب الرحلة
17. الخريطة مباشرة لـ map-saas بمفتاح `x-api-key` مقيّد ببصمة التطبيق
(قرار `maps-direct-decision`) — لا تمرير عبر الباك إند.
18. الراكب: اختيار المصدر/الوجهة · التسعير المسبق · تأكيد الطلب · انتظار سائق.
19. السائق: أونلاين/أوفلاين · استقبال العروض · قبول/رفض.
20. الواقع اللحظي (WebSocket) حسب `backend-archive/src/realtime`.
21. **بوابة**: رحلة كاملة من الطلب إلى الإنهاء بين جهازين + الخريطة تتحرّك
بسلاسة أثناء التتبّع (لا اهتزاز، لا إعادة رسم للشاشة كاملة).
### المرحلة 5 — مسار الرحلة وما بعدها
22. حالات الرحلة · التتبّع الحيّ · الإلغاء · الإنهاء · الأجرة · التقييم.
23. المحفظتان (محفظة الراكب = التزام، محفظة السائق = إيراد — لا تُدمجان).
### المرحلة 6 — الدراور والإضافات
24. الدراور · الملف الشخصي · الرحلات السابقة · الإشعارات · الدعم · اللغة.
25. الوحدات المدفوعة السبع (`docs/33`) خلف أعلام `const` حسب نموذج
lite/pro/max — بناء مولَّد، لا فروع.
## 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` |
| بناء/اختبار على الماك | كل بناء وهجرة على السيرفر |