Commit Graph
100 Commits
Author SHA1 Message Date
Hamza-AyedandClaude Opus 5 e2e663dd61 fix(apps): إسقاط التطبيق عند توليد الخريطة — النقاط صارت طبقة فلاتر
## العطل
Unhandled Exception: This Annotation Manager has not been initialized
  → didUpdateWidget → diffCircles → addCircle
  → libc++abi: std::domain_error → SIGABRT

## السبب البنيوي
MapLibre يُنشئ مديري التعليقات (circle/symbol/line) **عند تحميل الستايل
وحده**، ويهدمهم عند كل إعادة تحميل — وتبديل الوضع الليلي إعادةُ تحميل.
وحارسٌ زمنيّ لا يكفي: didUpdateWidget في الحزمة يستدعي diffCircles بمجرّد
وجود المتحكّم، وهو موجود قبل تحميل الستايل وأثناء إعادة تحميله. أي أن أي
نافذة زمنية نتركها ستُصاب عاجلاً أو آجلاً.

## العلاج — إزالة صنف العطل لا ترقيعه
كل ما يُرسم فوق الخريطة صار **طبقة فلاتر** في Stack: نقطة البداية والنهاية
(MapPin) والأسماء (MapLabel) وأيقونة السائق، بمواضع من getScreenCoordinate.
لا annotation manager، فلا عطل — ومعها نرث الخط العربي والاتجاه والثيم.

Polyline وحده بقي annotation (لا بديل لرسم المسار)، محروساً بعَلَم _styleReady
يُصفَّر عند كل تغيير styleUrl.

## أداء
onCameraMove يُطلق عشرات المرات في الثانية وكل مزامنة ثلاثة نداءات منصّة —
أُضيف حارس _syncing يمنع تكدّسها.

## توثيق
docs/23 §13 قاعدة جديدة: ممنوع markers/circles/polygons مع IntaleqMap.
كُتبت كي لا تتكرر في أي شاشة خريطة قادمة.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 01:43:51 +03:00
Hamza-AyedandClaude Opus 5 ca766a2d4e feat(apps): الدراور على نمط سيرو — المكافآت والدعوات والمساعدة
جُرد دراور سيرو: 14 مدخلاً للراكب (map_menu_widget) و23 للسائق
(drawer_captain). بُني ما له نقطة فعلية على NestJS، والباقي مذكور صراحةً.

## مبنيّ
- **المكافآت** (features/rewards): GET /rewards/referrals و /rewards/coupons —
  كلاهما مُتحقَّق حيّاً. كود الدعوة يُنسخ ويُشارك، وعدّادات دعوتَهم/قيد
  التأكيد/مدفوعة. يقابل Promos + Invite Driver في سيرو
- **شارك التطبيق · قيّم التطبيق · سياسة الخصوصية · تطبيق الطرف الآخر** —
  أفعال محليّة بلا نقطة خادم (share_plus + url_launcher)
- **المساعدة** وُسّعت: أسئلة شائعة قابلة للطيّ · واتساب الدعم ·
  **فحص توافق الجهاز** (نمط سيرو) يفحص ما يُعطّل التطبيق فعلاً: خدمة الموقع
  وإذنها وإذن الإشعارات — أسباب حقيقية لشكوى «التطبيق لا يعمل»
- روابط المتجر والخصوصية والدعم صارت قيم بناء لكل مستأجر في BuildConfig

## غير مبنيّ — ولا نقطة خادم له
الشكاوى (R3 في الباكلوج) · الإحصاءات · التحدّيات ولوحة المتصدّرين · مركز
الصيانة · جدولي · التأمين الصحي (مؤجَّل بقرار المالك) · الطعام والمواصلات
(خارج النطاق، docs/39 §1). بناء واجهة بلا خادم يعني شاشة تكذب على المستخدم.

## سجلّ الإشعارات
سيرو يعرض قائمة إشعارات؛ الخادم يوفّر POST /notifications/token فقط بلا نقطة
سرد. لم تُبنَ شاشة قائمة على فراغ.

flutter analyze نظيف في التطبيقين.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 01:34:23 +03:00
Hamza-AyedandClaude Opus 5 6a33cc7241 fix(rider): البحث عن مكان والمحفظة الحقيقية وأسماء الخريطة
## البحث لم يكن يستجيب — غلافان مختلفان
مثبت بـcurl: /geocoding/search يرجّع {"results":[…]} بينما /geocoding/reverse
يرجّع مصفوفة عارية. افترضتُ كليهما مصفوفة، فكان البحث يفشل صامتاً — ولهذا
«تحديد من الخريطة» يعمل (يستعمل reverse) والبحث لا. أُضيفت رسالة «لا نتائج»
كي لا يبقى الفراغ غامضاً، وأيقونة لكل صنف مكان.

## المحفظة صارت دفعاً حقيقياً لا كاش-إن
كنت أستعمل POST /wallet/topup — وهو بوصف الخادم كاش-إن تجريبي يزيد الرصيد
بلا دفع. المسار الصحيح موجود أصلاً: POST /payments/charge مع provider
وpurpose، وسجلّ بوابات فيه cash · paymob · syriatel · mtn ومحوّل فاتورة
لـ cliq · shamcash · zaincash.

كليك وشام كاش وزين كاش تعمل بنمط **الفاتورة** — نفس ما يفعله سيرو حرفياً
(create_cliq_invoice.php): الخادم يولّد مرجعاً ويعرض حساب الاستلام، ويحوّل
المستخدم، والتسوية تصل لاحقاً بمطابقة رسالة المزوّد. لا بوابة تحويل حيّة.
- InvoiceSheet يعرض المرجع وحساب الاستلام بزرّي نسخ — الرقم يُحوَّل في تطبيق
  بنك آخر فالنسخ ليس رفاهية
- يُقال صراحةً أن الرصيد لا يزيد الآن، قبل الضغط لا بعده
- حساب استلام غير مضبوط للمستأجر يُعرض كخطأ لا كتعليمات ناقصة
- أسماء المزوّدين صُحّحت لتطابق PaymentGatewayRegistry حرفياً (syriatel لا
  syriatel_cash…) — أي اسم آخر يرجّع 400
- qi_card حُذف: لا محوّل له على الخادم
- السائق: chargeCredit بنيّة credit_topup للرصيد التشغيلي

## الخريطة وواجهة «إلى أين»
- أسماء البداية والنهاية تُرسم فوق الخريطة (نمط سيرو) كطبقة فلاتر لا marker
- البطاقة المطوية صارت حقل بحث بارزاً مع الموقع الحالي فوقه
- حقل البحث يفتح مركّزاً مع زرّ مسح

## أداة
tool/sync_from_rider.sh كان يستثني ملفَّي ARB فيُسقط كل مفتاح مشترك جديد
ويكسر بناء السائق — تكرّر ثلاث مرات. صارا يُنسخان ثم تُفرض نصوص السائق فوقهما.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 01:26:05 +03:00
Hamza-AyedandClaude Opus 5 e22cc5c7e4 feat(apps): ثيم سيرو ومفتاح الدولة والعراق
## الثيم مقرَّب من سيرو (قرار المالك 2026-08-05)
- البذرة صارت كحليّ سيرو #1A2340 بدل الأزرق العام، والثانوي #8C9CF8
- fromSeed وحدها تنتج من بذرة داكنة أسطحاً باهتة، فثُبّت الأساسي والثانوي
  والأسطح صراحةً وتُرك الباقي للمشتقّ — شكل سيرو مع تباين M3
- الأدوار الدلالية بأرقام سيرو الميدانية: نجاح #22C55E · تحذير #F59E0B ·
  خطأ #EF4444، وبطاقة الليلي #2C2C2E

## مفتاح الدولة عاد
core/ui/phone_field.dart — النمط المجرَّب في سيرو، لكن بلا intl_phone_field:
تلك الحزمة تحمل 240 دولة ونحتاج أربعاً. CountryCode صريح ومقصور على أسواقنا.
- الاختيار من ورقة سفلية لا قائمة منسدلة: أهداف لمس أكبر وأربع دول لا مئتان
- الترتيب يبدأ بحزمة دولة المستأجر — الأردنيّ يرى الأردن أولاً بلا بحث
- الرقم يُرسل كاملاً بالمفتاح بلا + (962790000001) كما يقبله الخادم
- التحقّق صار بطول الدولة لا برقم ثابت: jo/sy تسع خانات، eg/iq عشر

## العراق
- CountryCode.iraq (+964) في قائمة الهاتف
- قنوات دفعه: زين كاش · كي كارد · نقد
⚠️ الباك إند لا يعرف حزمة `iq`: default-tariffs.ts فيه jo/sy/eg فقط، فمستأجر
عراقي يُزوَّد بلا أي تعرفة. موثَّق في الكود — وهو شغل خادم يسبق أي إطلاق عراقي.

flutter analyze نظيف في التطبيقين.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 01:08:18 +03:00
Hamza-AyedandClaude Opus 5 3a8caf7b27 fix(rider): تعطيل أنواع الرحلات بلا تعرفة نشطة
فحص حيّ كشف أن `GET /ride-types` يرجّع ستّة أنواع للمستأجر siro بينما ثلاثة
فقط لها تعرفة نشطة:
  economy 1.55 · comfort 1.85 · electric 1.75  ✅
  family_van · women · scooter                 ❌ 404

أي أن الراكب يرى ستّة خيارات، وثلاثة منها لو اختارها يفشل طلب الرحلة بـ
«No pricing available» بعد أن يكون قد أكمل تحديد وجهته.

المعالجة في التطبيق: النوع بلا تعرفة يظهر **معطّلاً مكتوباً عليه «غير متاح»**
لا مخفياً — إخفاؤه يترك المستخدم يتساءل أين ذهب الخيار، وإظهاره قابلاً
للاختيار يوصله إلى فشل بعد خطوتين. وإن كان المختار بلا تعرفة يُنتقل تلقائياً
لأول نوع مسعَّر، فلا يبقى زرّ التأكيد معطّلاً بلا سبب ظاهر.

هذا علاج للعَرَض؛ السبب بيانات على الخادم: كتالوج ride-types والتعرفات غير
متزامنين.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 00:39:30 +03:00
Hamza-AyedandClaude Opus 5 8976821a93 fix(apps): إصلاحات من أول تجربة حقيقية على الجهاز
## تعذّر حساب الأجرة — الخطأ في التطبيق لا في جدول الكازان
الخادم يبذر التعرفة بـ city: 'default' (tenants.service.ts:156) ويستعمل
dto.city ?? 'default' عند إنشاء الرحلة، وكنت أرسل city: '' فيرجّع 404
«No active tariff for /economy». تحقّق حيّ:
  city='default' → total 1.55 JOD ✅   city='' → 404 ❌   city='amman' → 404 ❌
لا تغيير مطلوب على السيرفر. أُضيف AppConfig.defaultCity موثّقاً بالسبب.

## التسعير مرة واحدة لكل الأنواع
كان نداء شبكة عند كل ضغطة نوع. صار _priceAll يسعّر كل الأنواع بالتوازي بعد
اكتمال المسار — المسافة والمدة ثابتتان والفرق في التعرفة وحدها. الراكب يرى
سعر الاقتصادي والمريح معاً تحت كل خيار ويقارن بنظرة، وتبديل النوع بلا نداء.

## الكاميرا لا تتبع الموقع
سببان: الموقع يصل قبل إنشاء الخريطة فيضيع التحريك، ولا فحص للإذن/الخدمة قبل
getCurrentPosition فيُبتلع الخطأ صامتاً.
- _onCreated يلحق بما فات عند جهوز الخريطة
- LocationService.ensureReady يفحص الخدمة والإذن أولاً
- didChangeAppLifecycleState يعيد المحاولة عند العودة من إعدادات النظام
- حالة noLocation تُعرض للمستخدم مع زر إعادة بدل خريطة صامتة

## الخريطة والتنقّل
- إطار المسار من حدوده الفعلية مع حشوة سفلية بمقدار ما تغطّيه الورقة
- علامتا البداية والنهاية دائرتان ملوّنتان تطابقان ألوان الخط الزمني
- زر رجوع في ورقة التأكيد + زر إعادة حساب عند فشل التسعيرة: كان المستخدم
  يعلق في الشاشة بلا مخرج

## المحفظة — على أي أساس يُشحن
POST /wallet/topup شحنٌ تجريبي بلا مزوّد دفع خلفه (الخادم نفسه يصفه كذلك).
أُضيف PaymentChannel مشتقّاً من countryPack المستأجر لا من لغة الجهاز:
jo → CliQ · sy → سيرياتيل/MTN/شام كاش · eg → PayMob. ويُعرض تنبيه صريح بأن
الشحن بلا تحصيل فعلي — إخفاؤه يجعل المستخدم يظنّ أنه دفع. وسحب السائق صار
يستعمل القناة المختارة بدل 'cliq' المكتوبة في الكود.

flutter analyze نظيف في التطبيقين.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 00:36:02 +03:00
Hamza-Ayed 29f375b122 Update codebase 2026-08-05 00:28:04 +03:00
Hamza-Ayed 36833b039d Update codebase 2026-08-05 00:07:11 +03:00
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
Hamza-AyedandClaude Opus 5 096acae74e feat(apps): الدراور وطبقتا الميزات والإشعارات — المرحلة 6
## طبقتا الميزات مُطبَّقتان معاً
AppDrawer يفحص: Features.wallet && context.tenantHas('wallet')
- Features.x ثابت const: يقرّر ما يُبنى في الـbinary. إطفاؤه يحذف الكود
  فلا يُستخرج (docs/23 §5)
- tenantHas من GET /tenant/config/:slug: يقرّر ما يُعرض ممّا بُني حسب اشتراك
  المستأجر (docs/38 §10)
هذا هو ربط lite/pro/max عملياً. TenantCubit يعرض النسخة المخزّنة فوراً ثم
يحدّثها: إقلاع بلا إنترنت يعرض الميزات المعروفة آخر مرة لا شاشة فارغة.

## الشاشات
الدراور · تعديل الحساب · سجلّ الرحلات · الإعدادات (مظهر + لغة) · المساعدة.
الرقم في تعديل الحساب معطّل مع سبب معروض لا مخفي: هو هوية الحساب على الخادم
وتغييره يعني حساباً آخر (docs/38 §1).

## الإشعارات
PushService معزول — الملف الوحيد الذي يعرف Firebase. التسجيل بعد إثبات
الجلسة عبر خطّاف في SessionCubit، ومع اشتراك onTokenRefresh: بدونه تتوقّف
الإشعارات بصمت بعد أسابيع. الإعداد الأصلي كان جاهزاً مسبقاً.

## تصحيحات بنيوية كشفها فحص آلي
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 و
router وحدهما · Firebase و intaleq_maps و geolocator و socket_io كلٌّ معزول.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 23:52:27 +03:00
Hamza-AyedandClaude Opus 5 66e28acc58 feat(apps): التقييم والمحفظتان ومسار الرحلة — المرحلة 5
## التقييم مفروض لا اختياري
SessionCubit يفحص GET /trips/rating/pending بعد إثبات الجلسة، ووجود رحلة
معلّقة يحجز التطبيق كله عند /rating حتى تُحسم. دور المستخدم في الرحلة يأتي
من الخادم لا من نوع التطبيق — نفس الرقم قد يكون له حسابان (docs/38 §1).
التقييم المزدوج يرجّع 400، وهو نجاح من منظور المستخدم: الرحلة مُقيَّمة فعلاً
فلا يُحبس في شاشة بلا مخرج.

الجلسة تحتاج معرفة التقييم المعلّق، و core لا يجوز أن يستورد ميزة التقييم —
فأُضيفت الطريقة لواجهة SessionSource ونفّذها AuthRepository.

## core/format/money.dart
كل المبالغ تصل نصوصاً ("0.000")؛ التحويل والعرض من نقطة واحدة. عدد المنازل
من العملة (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 سطر.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 23:42:07 +03:00
Hamza-AyedandClaude Opus 5 1156299d09 feat(apps): الخريطة وطلب الرحلة ودورة السائق — المرحلة 4
## حسم تعارض الخرائط (docs/38 §7)
لصالح القرار الأحدث 2026-07-20: كل الخرائط — البلاطات والبحث والعكسي
والمسار — مباشرة إلى map-saas بترويسة x-api-key، بلا مرور بباك إند تريبز.
نقاط /maps/* في الباك إند لا تُستعمل، وإحداها (geocode) معطوبة على المنشور.
العقد تُحقّق حيّاً بـcurl قبل كتابة سطر.

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

## الراكب
RideCubit بستّ مراحل + RidePage وثمانية ويدجت: الخريطة · دبّوس المنتصف ·
المخطّط · الخط الزمني · اختيار النوع · التأكيد · البحث عن سائق · الرحلة.

## السائق
DutyCubit بأربع مراحل + DutyPage: مفتاح الاتصال · العرض بمؤقّت · الرحلة.

## قرارات تحمي الأداء والصحّة
- GeoPoint مستقلّ عن حزمة الخريطة؛ intaleq_maps مستوردة في ملف واحد فقط
  لكل تطبيق. تبديل محرّك الخريطة لا يلمس منطقاً
- الخريطة خارج BlocBuilder الورقة ولها BlocSelector خاص: نبضة موقع كل 25م
  لا تعيد بناء الشاشة
- مهلة بحث محليّة 90ث + استطلاع كل 5ث: الخادم قد لا يُطلق expired/no_drivers
  أبداً (ثغرة R1)، وانتظار حدث قد لا يصل = شاشة بحث أبدية
- offeredDrivers == 0 جواب نهائي فوري بلا انتظار المهلة
- الأجرة لا تُعرض إلا إن كانت صالحة: /tariff/quote يرجّع 200 بقيم null صامتة
- موقع السائق بمسارين عمداً: REST لمطابقة Redis والسوكت لخريطة الراكب

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 23:33:56 +03:00
Hamza-AyedandClaude Opus 5 c52338f3bb feat(apps): نظام التصميم والمصادقة الكاملة — المرحلتان 2 و3
## تصحيح بنيوي أولاً
بنية المرحلة 1 كانت تخالف docs/23 §1 المُلزِم. أُعيدت للشجرة المفروضة
حرفياً: app.dart في الجذر · core/{config,build_config,di,router}.dart ·
core/{design,ui,l10n,api,storage,session}/ · features/<f>/{cubit,data,view}/

## المرحلة 2 — نظام التصميم (docs/26)
- core/design: tokens (مسافات/زوايا/حركة/أحجام — الشاشة لا تخترع رقماً) ·
  typography (IBM Plex Sans Arabic + Inter محليّان، بأرقام tabular للأسعار
  والعدّادات) · TripzColors كـThemeExtension للأدوار الدلالية ·
  buildTheme(brightness, locale) بمدخلين لا ثالث لهما
- core/ui: 11 مكوّناً. TripzScaffold يحوّل status الـCubit وحده إلى
  skeleton/خطأ/فراغ/محتوى — فلا تكتب أي شاشة if (loading)
- core/l10n: ARB عربي/إنجليزي + gen_l10n. لا نص مرئي داخل widget
- SettingsCubit: المظهر واللغة. الاتجاه يتبع اللغة آلياً بلا Directionality
  مفروضة، وسقف تكبير النص 1.3

## المرحلة 3 — المصادقة
- طبقة الشبكة: تجديد استباقي بطلقة واحدة (عمر التوكن 15 دقيقة) ·
  x-app-role · x-device-id من device_info_plus — يُرسَل الآن كي يُفعَّل علم
  الخادم لاحقاً بلا تعديل التطبيق
- SessionCubit في core/session خلف واجهة SessionSource: الجلسة حالة على
  مستوى التطبيق لا ميزة، و core لا يستورد من features
- LoginCubit بخطواته الخمس، ProfileCubit، وست شاشات:
  الشروط ← إذن الموقع ← الهاتف ← الرمز ← إكمال الملف ← هيكل الرئيسية

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

قراران موثّقان في docs/37: لا packages/tripz_ui (الوثيقتان المُلزِمتان تفرضان
core/design و core/ui داخل التطبيق)، والـCubit يُصدر رمز فشل لا نصّاً
(يجمع بين docs/23 §3 و docs/26 §4).

flutter analyze نظيف في التطبيقين · 48 ملف و3,140 سطر لكل تطبيق · الفرق
بينهما أربعة ملفات فقط: build_config · config · ملفّا ARB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:42:19 +03:00
Hamza-AyedandClaude Opus 5 0de8573d27 fix(marketing): تصحيح حاسبة التوفير ونسبة GMV لباقة النمو
- باقة النمو صارت شريحتين: 2% حتى $50k ثم 1.5% حتى $200k (كانت 2% حتى
  $200k)، محدَّثة في الخمس لغات
- مقارنة المنافس صارت 15% من GMV مع حدّ أدنى $0.10 للرحلة، بدل 10%/5%
- استُخرجت tripzMonthly كدالة مستقلة، وأُضيفت crossoverTripsPerDay
- تُعرض الآن نقطة التعادل تحت رقم التوفير، وكذلك حين لا يوجد توفير بعد

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:21:34 +03:00
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
Hamza-Ayed 1243e5a3b2 chore: initialize new database schema and update marketing tool dependencies 2026-08-04 12:30:54 +03:00
Hamza-Ayed 3b0b257528 تعديل منفذ Nginx في env.example 2026-07-29 03:00:35 +03:00
Hamza-Ayed f428b8b300 تحديث ملف .env.example ببيانات قواعد البيانات الجديدة 2026-07-29 02:58:22 +03:00
Hamza-Ayed 5dcdcd8a3a ضبط بورتات الدوكر لتفادي التعارض مع siro 2026-07-29 02:47:03 +03:00
Hamza-Ayed d34550e1ca إضافة الهوية الجديدة Tripz 2026-07-29 02:41:05 +03:00
Hamza-Ayed b9a97effe5 إضافة الهوية الجديدة Tripz 2026-07-29 02:31:04 +03:00
Hamza-AyedandClaude Opus 5 ecbe245b77 fix(driver): ضمّ حزم get و get_storage داخل المستودع
apps/driver كان يشير إلى `../../Intaleq/packages/get` خارج المستودع — وهو
مسار يُحل من apps/driver إلى Tripz/Intaleq/ غير الموجود، فلا يمرّ
`flutter pub get` إطلاقاً. (فجوة docs/30 §2.6.)

ضُمّت نسخة **انطلق** تحديداً (App/Intaleq/packages) لا فرع apps/rider:
الفرعان يختلفان فعلاً — snackbar_controller.dart و get_queue.dart وغيرهما
مختلفة بين النسختين، فاستعارة فرع rider كانت ستغيّر سلوك السائق صامتة.

  apps/driver/packages/get         (3.5 م.ب، 202 ملف dart مع get_storage)
  apps/driver/packages/get_storage
  pubspec: ../../Intaleq/packages/* → ./packages/*  (3 مواضع، منها
  dependency_overrides سطر 142)

المستودع صار مكتفياً ذاتياً: لا تبعية مسار خارج شجرته. لم يُمسّ App/Intaleq.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:16:10 +03:00
Hamza-AyedandClaude Opus 5 4d8414c96b feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل
قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:14:13 +03:00
Hamza-AyedandClaude Opus 5 9909d9b4c1 chore: أرشفة تطبيقات ولوحات تريبز التي سيحلّ محلها كود سيرو
استكمال قرار 2026-07-27 (كوميت fff76c9 أرشف الباك إند):
  apps/rider          → apps/rider-archive-cubit
  apps/driver         → apps/driver-archive-cubit
  dashboards/admin-web    → dashboards/admin-web-archive
  dashboards/service-web  → dashboards/service-web-archive

إعادة تسمية بلا تعديل محتوى. rider/driver كانا إعادة بناء Cubit مبنية على
عقد NestJS المؤرشف. admin-web و service-web نموذجان بملف HTML واحد (164 و
128 سطراً) — يحلّ محلهما siro_admin و siro_service المكتملان.

يُحمل من المؤرشف ولا يُعاد اكتشافه: عقد map-saas المثبَّت بالتجربة في
apps/rider-archive-cubit/lib/core/api/ (x-api-key ترويسة، reverse مصفوفة،
route في points) ودرس مسار FCM.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 05:12:04 +03:00
Hamza-AyedandClaude Opus 5 fff76c949f chore: أرشفة باك إند NestJS إلى backend-archive
قرار المالك 2026-07-27: الباك إند المعتمد صار باك إند سيرو PHP، ويُنقل
إلى هذا المستودع كنسخة جديدة باسم «انطلق» على api.intaleqapp.com.

backend/ → backend-archive/ (305 ملفاً، إعادة تسمية بلا تعديل محتوى)
+ README-ARCHIVE.md يوضّح سبب الأرشفة وسبب عدم الحذف: scripts/e2e-test.mjs
  هو أدق توثيق سلوكي لدورة الرحلة كاملة، ويبقى مرجعاً عند بناء الوحدات
  الناقصة في باك إند PHP (المحفظتان · الاستحقاقات · محرّك التسعير · الوحدات).

مسارات backend/ في docs/*.md لم تُعدَّل عمداً — سجلّ تاريخي.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 04:54:35 +03:00
Hamza-AyedandClaude Opus 5 75b80bd299 refactor(marketing): تفكيك الموقع إلى وحدات + توحيد الباقات الثلاث
- marketing: index.html من 2274 إلى 775 سطراً، والباقي إلى
  styles/ (tokens·base·components·sections·responsive) و
  js/ (app·i18n·calculator·analytics) و i18n/ بخمس لغات
  (ar·en·fr·es·tr) عبر سمة data-i18n.
- docs/05 + docs/33 + لوحة السوبر-أدمن: الباقات صارت ثلاثاً
  (إطلاق · نمو · سيادة) بدل الأربع، وتحديث docs/31 بما يطابقها.
- docs/36: توثيق سيرو إكسبرس للتوصيل.
- تنظيف استيرادات غير مستعملة في apps/rider و apps/driver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-27 04:53:19 +03:00
Hamza-Ayed 5675a4d928 fix: update marketing page content and formatting in index.html 2026-07-24 23:55:29 +03:00
Hamza-Ayed 7ab18358ce Embed dedicated MyHub operations video and Dispatch Reseller partner video 2026-07-24 22:51:55 +03:00
Hamza-Ayed 1434b1da39 Direct standalone Gemini and Meta Graph API in studio.html 2026-07-24 17:52:08 +03:00
Hamza-Ayed 1f27d3ab7b Update to gemini-flash-lite-latest & dynamic AI image generation 2026-07-24 17:23:50 +03:00
Hamza-Ayed 7c05b0d9ab Direct standalone Gemini and Meta Graph API in studio.html 2026-07-24 17:18:11 +03:00
Hamza-Ayed 6fe7a093a9 Add Web AI Marketing Studio with Voice Recorder and Live Facebook Preview 2026-07-24 17:04:44 +03:00
Hamza-Ayed 16d7ec0364 Add NestJS Admin Facebook Publishing API with Image Support 2026-07-24 17:00:52 +03:00
Hamza-Ayed 0b9dca5d75 Direct PAGE_ID/feed endpoint update 2026-07-24 16:51:33 +03:00
Hamza-Ayed b47102f36b Support scripts/.env path 2026-07-24 16:49:21 +03:00
Hamza-Ayed eacd71349e Support scripts/.env path 2026-07-24 16:44:58 +03:00
Hamza-Ayed b2261d9192 Clean env enforcement & add Gemini AI Post Generator 2026-07-24 16:38:47 +03:00
Hamza-Ayed 4621ede040 Update codebase 2026-07-24 16:18:06 +03:00
Hamza-Ayed ed15bae804 Update codebase 2026-07-24 16:17:10 +03:00
Hamza-Ayed 1515a7bfc9 Dynamic Page ID match 2026-07-24 16:10:19 +03:00
Hamza-Ayed 6d66f33bc3 Dynamic Page ID match 2026-07-24 16:07:06 +03:00
Hamza-Ayed 9995656a84 Update scripts with new Meta Access Token 2026-07-24 16:05:44 +03:00
Hamza-Ayed 075e21e557 Update publish-now script with auto Page Token resolution 2026-07-24 16:00:32 +03:00
Hamza-Ayed 535d5b40ef Update codebase 2026-07-24 15:58:57 +03:00
Hamza-Ayed f9b55ddf94 docs: update siro audit documentation with recent findings 2026-07-24 05:37:13 +03:00
Hamza-Ayed 1be74d764d docs: update marketing index and audit documentation for the Siro release 2026-07-24 05:37:08 +03:00
Hamza-AyedandClaude Opus 4.8 7b02b27d92 feat(marketing): consent-gated measurement layer + pricing-intelligence legal brief
البند ٢ — استخبارات التسعير (docs/35 جديد):
- توصيف تقني دقيق من كود سيرو: المصدر ليس "كشط أسعار معلنة" بل
  بوت أندرويد على أجهزة حقيقية يشغّل 7 تطبيقات منافسة مُسمّاة
  بمعرّف الحزمة (standalone_worker.php:665) بدفعات حتى 100 مهمة
- فصل ثلاثة أخطار مستقلة: الوصول الآلي · قانون المنافسة · البيانات
- الخطر الأكبر (لم يكن موثّقاً): بيع المحرّك لمشغّلَين في نفس المدينة
  = ترتيب hub-and-spoke تقع مسؤوليته على المحور أي Tripz
- ضابط مقترح: مشغّل واحد لكل مدينة، في العقد وفي الكود
- تنظيف صياغة الموقع: صفر أسماء منافسين، وحُذفت "اعرف كيف يسعّر
  منافسك قبل أن يعرف هو" لأنها تصلح دليل نيّة في نزاع

البند ٣ — طبقة القياس (docs/32 §5):
- شريط موافقة بأربع لغات، الرفض بنفس بروز القبول
- البكسل لا يُحمَّل قبل الموافقة (مطلوب لزوّار الاتحاد الأوروبي:
  الموقع بالفرنسية والإسبانية)
- 8 أحداث تغطي القمع كاملاً حتى Lead بمعامل القناة
- PIXEL_ID فارغ عمداً: لا شيء ينكسر ولا شيء يُقاس حتى يُملأ

تحقُّق: صفر أخطاء console · صفر عناصر i18n فارغة في اللغات الأربع ·
fbq غير معرّف قبل الموافقة · الأحداث تُطلق بعدها

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 05:14:28 +03:00
Hamza-AyedandClaude Opus 4.8 8cbaa89642 refactor(pricing): restructure add-ons 7→3, safety+payments to core, transit as own product line
تنفيذ توصيات المراجعة النقدية التي لم تُنفَّذ في الجولة السابقة:

- حذف «الأمان والامتثال» $79 كوحدة → النواة (سبب قانوني: بيع الأمان
  يُنشئ وثيقة إدانة يوم يقع حادث عند مستأجر لم يشترِ الوحدة)
- حذف «حزمة المدفوعات» $99 → النواة (الحلقة الأساسية لا ترقية)
- دمج «ذكاء الأسطول» $99 في باقة أسطول+ (تناقض تسموي)
- «المواصلات» تخرج من الشبكة → قسم #transit مستقل بجمهوره وCTA خاص
- شريط .core-band يعرض ما لا يُباع كحجة بيع
- إصلاح الانقلاب السعري: كانت سيادة $879 أرخص من أسطول+ محمّلة $903
  الآن: علامة $585 < أسطول+ $599 < سيادة $879
- الشارات ← «متاح الآن» بتأكيد المالك أنها تعمل في إنتاج سيرو
- تصحيح سردية docs/33 §0 المستحيلة (كانت تبني التوسّع على باقة
  تنتهي بعد 90 يوماً)

الترجمات مكتملة في اللغات الأربع — تحقُّق برمجي: صفر عناصر فارغة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 05:06:40 +03:00
Hamza-AyedandClaude Opus 4.8 822e4e8724 refactor: correct add-ons pricing, remove duplicate AI section, mark all modules as coming-soon
✨ تحسينات رئيسية:

1️⃣ إصلاحات توثيقية (docs/21, docs/33):
   - S18 ليست مشكلة — النظام يفرض سحب الرصيد الكامل فقط
   - إضافة تنبيه حالة عن عدم جاهزية الوحدات

2️⃣ إصلاحات الموقع (marketing/index.html):
   - حذف قسم #ai-modules المكرر (التكرار المحرج)
   - إضافة رابط «الإضافات» في القائمة العلوية (4 لغات)
   - تغيير جميع الشارات إلى «قريباً الربع القادم» (حماية قانونية)
   - 7 وحدات مدفوعة واضحة مع أسعار + وصف

3️⃣ النموذج التجاري:
   - النواة رخيصة = سلاح اكتساب
   - الوحدات = هامش الربح الحقيقي (land-and-expand)
   - بدون تكرار محرج للزائر

⚠️ الحد الأدنى قبل التسويق:
   - S4 و S5 تُصلح قبل أول استنساخ على سيرفر مستأجر
   - جرد صدق بند بند على إنتاج سيرو

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 04:59:23 +03:00
Hamza-Ayed 213b459912 feat: implement splash and onboarding flows, update app assets, and add project design documentation 2026-07-24 04:15:07 +03:00
Hamza-AyedandClaude Fable 5 9f7bc31408 feat(auth): separate rider/driver identities per phone
Same phone can now hold distinct rider and driver accounts (and wallets)
within a tenant. Unique constraint moves from (tenant, phone_bidx) to
(tenant, phone_bidx, role); login creates/finds the record matching the
app's x-app-role header. Fixes rider-app login returning role:driver for
a phone previously registered as a driver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:21:01 +03:00
Hamza-Ayed c67453aeba feat: configure custom app icons and add splash screen support for Android and iOS 2026-07-19 18:20:41 +03:00
Hamza-Ayed 603f0c676d fix: add missing dependencies in TripsService constructor for tests 2026-07-19 16:47:05 +03:00
Hamza-Ayed e577afdb2c fix: add missing OnModuleInit import in notifications service 2026-07-19 16:43:07 +03:00
Hamza-AyedandClaude Fable 5 eebc2ccfb9 docs(compose): comment the firebase key mount (secret ships via scp, boot survives its absence)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:26:10 +03:00
Hamza-Ayed c4dfeb4c6f feat: implement core architecture, design system, localization, and trip features for both driver and rider apps. 2026-07-19 16:21:58 +03:00
Hamza-Ayed 959e2fa758 Update backend to use firebase-admin SDK for FCM 2026-07-19 16:21:43 +03:00
Hamza-AyedandClaude Opus 4.8 978a7489e6 docs: record the kazan-based pricing rebuild and its server proof
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 02:13:21 +03:00
Hamza-AyedandClaude Opus 4.8 2045896b95 fix(test): expect fixed to be rejected + flush tariff cache before testing
الدورة السابقة أعلنت الفشل والنظام سليم — العطل كان في السكربت:

1. **`fixed` تُرفض بـ400 وهذا هو المطلوب** (أُخرجت من الزرع عمداً)، لكن
   السكربت كان يعدّها فئة متوقَّعة فأسقط التغطية إلى 72/81. صارت في
   `REJECTED_CLASSES`: الرفض يُتحقَّق منه صراحةً، وغيابه هو الفشل.

2. **429 على `send-otp`** (10 لكل 5 دقائق) عند تشغيلتين متتاليتين — يُنتظر
   ويُعاد بدل الإبلاغ عن فشل ليس في النظام.

3. **إبطال كاش التعرفة صار خطوة في الدورة.** `getActive` تكاشي 900 ثانية،
   و**Redis حاوية منفصلة لا تتأثر بإعادة تشغيل الـapi** — وتطبيق
   `tariffs.sql` مباشرةً على القاعدة يتجاوز التطبيق فلا يُبطل شيئاً. النتيجة
   أن النظام خدم أسعاراً قديمة عبر دورتَي نشر كاملتين وهو يعلن النجاح.
   المسح الآن مبنيّ في السكربت لا متروك للتذكّر.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 02:09:05 +03:00
Hamza-Ayed 7e06b1241f feat: add automated stress testing script for driver location tracking and trip lifecycle simulation 2026-07-19 01:51:47 +03:00
Hamza-AyedandClaude Opus 4.8 e9beeb90b4 feat: rebuild pricing on Siro's live kazan table + close two fare leaks
## الأرقام صارت من جدول `kazan` الحيّ لا من تقدير
العمولة 11٪/12٪/14٪ مطابقة حرفياً. وثلاثة تصحيحات على الخام:
- **محورا الأردن مقلوبان**: أعمدة الفئات تحمل سعر الدقيقة (0.043) وأعمدة
  النوافذ سعر الكيلومتر (0.184) — عكس مصر وسوريا. قراءتها كما هي تجعل
  رحلة 27 كم بأقلّ من دينارين.
- **نِسَب الفئات في مصر وسوريا معطوبة**: الكهربائي 0.23 والفان 0.25–0.40 من
  الاقتصادي، أي فان أرخص من اقتصادي. نِسَب الأردن وحدها متماسكة فاعتُمدت.
- سعر الكيلو في مصر 4 جنيهات لا 3.5 (قرار المالك).

## البنية صارت مطابقة لسيرو — وهي ما سيكتب فيه بوت استخبارات الأسعار
سعر الكيلو يتغيّر بالفئة فقط (رقم يعرفه السوق)، وسعر الدقيقة بالنافذة فقط.
**لا فتحة عدّاد** (`startPrice = 0` في كل صفوف سيرو): الحدّ الأدنى أرضية لا
رسم يُضاف — «ما في رحلة أقل من 20 جنيهاً». `fuelPrice` محفوظ كمدخل للبوت.

## تسريبان أغلقهما الاختبار الحيّ (بموافقة المالك)
1. **فئة مجهولة = رحلة مجانية**: `service_class` يأتي من الطلب بلا تحقّق،
   والاستثناء كان يُبتلع فتُنشأ رحلة بـ`quoted_fare = null` ثم تُسوّى بصفر.
   صار الطلب يُرفض بـ400 — الفشل الصريح أأمن من الصامت.
2. **`fixed` لا تُزرع**: سعر ثابت بلا جدول خطوط يعني رقماً واحداً لكل
   الوجهات — أظهر الاختبار رحلة 55.8 كم بـ2.5 ديناراً مقابل 23.2 للاقتصادي.

300 اختبار خضر.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 01:38:20 +03:00
Hamza-AyedandClaude Opus 4.8 f58f2e2e2c chore: gate the test loop behind a real health check
ضاعت دورتان كاملتان على السبب نفسه: تسلسل `up -d && restart && <اختبار>`
كان يشغّل الاختبار بينما الـapi ساقطة، فيظهر خطأ الإقلاع كـ`EAI_AGAIN` داخل
الاختبار — أي في المكان الخطأ تماماً، فيُشخَّص كعطل شبكة أو عطل سكربت.

`verify-and-test.sh` يجعل الدورة واحدة ومحروسة:
- يفحص `.env` قبل الإقلاع ويصلّح التعارض القاتل (OTP_DEV_MODE=true مع
  NODE_ENV=production — حارس `main.ts` ينهي العملية لا يحذّر، فتدخل الحاوية
  حلقة إعادة تشغيل).
- يميّز «حلقة إعادة تشغيل» عن «بطء إقلاع»: الأولى خطأ إقلاع يُطبع سببه من
  اللوغ فوراً بدل انتظار عقيم.
- **لا اختبار قبل ردّ /health فعلياً** — هذه هي البوابة التي كانت غائبة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 01:11:43 +03:00
Hamza-AyedandClaude Opus 4.8 5838e92980 fix(test): read OTP from Redis instead of requiring OTP_DEV_MODE
نصيحتي السابقة بتفعيل `OTP_DEV_MODE=true` على السيرفر كانت خاطئة: حارس في
`main.ts` يرفض الإقلاع عند `OTP_DEV_MODE=true` مع `NODE_ENV=production`،
فسقطت حاوية الـapi في حلقة إعادة تشغيل وظهر الفشل كـ`EAI_AGAIN` في السكربت.

الحارس صحيح ولا يُضعَف لأجل اختبار: رمز ثابت على سيرفر متاح للإنترنت يعني
أن معرفة رقم هاتف تكفي لدخول أي حساب.

السكربت صار يقرأ الرمز الحقيقي من Redis (`send-otp` يخزّنه قبل محاولة
الإرسال، فيبقى محفوظاً حتى حين يفشل الإرسال لرقم وهمي). عميل RESP أدنى على
`node:net` بلا تبعيات — الحاوية عابرة بلا `node_modules`. المفتاح يستعمل
UUID المستأجر لا الـslug، فتُجلب خريطة الـslug→UUID من `/admin/tenants`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 01:01:51 +03:00
Hamza-AyedandClaude Opus 4.8 7501b0bb33 fix(test): pricing script declared success after doing nothing
عطلان في سكربت التسعير، كشفهما أول تشغيل حقيقي:

1. **إعلان نجاح على العدم** — الأخطر. فشل تسجيل الدخول فجُمع صفر صفّ، فمرّ
   كل تحقّق فراغاً وطبع «الاختبار ناجح». اختبار يعلن النجاح وهو لم ينفّذ
   شيئاً أسوأ من اختبار يفشل: الفشل يُرى. أُضيف حارس عدم + تحقّق تغطية
   (المنفَّذ مقابل المتوقَّع)، وكلاهما يُخرج برمز فشل.

2. **تسجيل الدخول تجاوز `send-otp`** — تجاوز التطوير في `verifyOtp` مشروط
   بـ`OTP_DEV_MODE=true`، وبدونه لا رمز في Redis فيُرفض الثابت 1234 بـ401.
   صار يمرّ بـ`send-otp` ويأخذ `dev_code` من استجابتها، ويقول صراحةً حين
   يكون الوضع التطويري مطفأً بدل أن يفشل بلا تفسير.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:54:13 +03:00
Hamza-AyedandClaude Opus 4.8 900c78bb97 fix: seed starting tariffs on tenant provision, not for siro only
ثغرة كُشفت على السيرفر: `SeedService` يزرع التعرفة لـ«سيرو» وحده، فمستأجرا
`intaleq` و`yalla` وُلدا بلا تسعير وبقيا كذلك حتى شُغّل `tariffs.sql` يدوياً.

الأثر صامت لا ظاهر: بلا تعرفة فعّالة لا يُقفل سعر للراكب، ثم تأخذ
`settleFare` القيمة `quoted_fare ?? 0` — رحلة مجانية وعمولة صفر.

`provision()` صار يزرع كل فئات الخدمة بعملة البلد ومنطقته الزمنية. مستودع
التعرفة محقون مباشرةً لا عبر `TariffService`: تلك الوحدة تستورد وحدة
المستأجرين، فالاستيراد المتبادل دورة.

وسطر لوغ الزرع كان يطبع 9 دائماً (ثابتاً لا عدّاداً) فلا يميّز «زُرعت» من
«كانت موجودة» — وهو الفرق الوحيد الذي يُقرأ منه.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:45:26 +03:00
Hamza-AyedandClaude Opus 4.8 90c3f71430 test: add live pricing script across all service classes and countries
يطلب رحلة حقيقية لكل فئة خدمة (9) × 3 مسافات × 3 بلدان عبر الـAPI الحي،
فالمسافة والزمن يأتيان من محرّك المسارات (انطلق) لا من افتراض.

يتحقّق آلياً من أمرين كانا عطلين حقيقيين قبل المجموعة M:
- ولا طلب يرجع بلا سعر (التعرفة المفقودة كانت تنتج رحلة مجانية بعمولة صفر).
- الفان أغلى من الاقتصادي في كل بلد (الأرقام الخام السابقة عكست الترتيب).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 00:40:30 +03:00
Hamza-Ayed 92f1eeb48c feat: implement rewards module with referral/coupon systems, update tariff seeding, and add trip distance service for improved billing accuracy. 2026-07-19 00:20:37 +03:00
Hamza-Ayed 22d18f32fe chore: add standard international tariff seeder script 2026-07-18 23:31:55 +03:00
Hamza-Ayed 840d0e54fe sec: scope app review 1234 bypass exclusively to siro tenant 2026-07-18 22:57:12 +03:00
Hamza-Ayed 898136bc1b fix: add missing driver_arriving state transition in simulation 2026-07-18 22:14:33 +03:00
Hamza-Ayed be18050bf2 fix: remove send-otp from simulation to bypass rate limiter 2026-07-18 22:11:56 +03:00
Hamza-Ayed fe376f8aa6 refactor: rename trip dispatch socket event to trip offer and update corresponding listener to trip:update 2026-07-18 22:10:54 +03:00
Hamza-Ayed 7a0372b018 chore: add comprehensive error logging to simulation 2026-07-18 22:07:47 +03:00
Hamza-Ayed e2a472356c fix: remove + from App Review accounts to match normalized format 2026-07-18 21:12:24 +03:00
Hamza-Ayed 79cb19d4f8 feat: setup app review accounts and automated E2E simulation 2026-07-18 21:08:42 +03:00
Hamza-AyedandClaude Opus 4.8 836c77bb08 fix(e2e): point the simulation at the seeded tenant slug
السكربت أرسل x-tenant-id=tripz بينما المزروع على السيرفر «siro»، فردّ
الـAPI بـ401 Unknown tenant. صار قابلاً للتجاوز عبر TENANT_ID.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:50:46 +03:00
Hamza-Ayed 71f6ab92e4 fix: append /api prefix to endpoints in simulation script 2026-07-18 19:49:27 +03:00
Hamza-AyedandClaude Opus 4.8 964b076c84 refactor(worker): declare the flusher directly instead of importing LocationsModule
جذر العلّة وراء أعطال الـDI الأربعة: WorkerModule استورد LocationsModule
كاملة، فجرّت الجيوفنس ← الإشعارات ← المستخدمين ← المستأجرين، وأربعة
متحكّمات HTTP تُنشأ في عملية لا تخدم HTTP.

المفرّغ يحقن Redis ومستودعَين لا غير. بإعلانه مباشرةً تسقط الحاجة إلى
I18n و Cache و Entitlements التي أضفتها في الدورتين السابقتين.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:44:41 +03:00
Hamza-AyedandClaude Opus 4.8 2a5adcf1fa fix(worker): register EntitlementsModule — closes the worker DI graph
آخر ورقة في السلسلة: Notifications → Users → Tenants → Entitlements،
وتبعياتها مستودعٌ و CacheService فقط. الباقي مستودعات TypeORM و
ConfigService العالمي، فلا طبقة خامسة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:40:22 +03:00
Hamza-AyedandClaude Opus 4.8 9bc52dbb9e fix(worker): register I18nModule and CacheModule in the worker composition root
الجيوفنس جرّ الإشعارات إلى شجرة الـworker لأول مرة، والإشعارات (و
UsersService خلفها) تحقن I18nService و CacheService. الوحدات @Global
تُسجَّل فقط إن استوردها أحد في الشجرة — وما يسجّله AppModule لا يصل
إلى جذر تجميع آخر.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:37:21 +03:00
Hamza-AyedandClaude Opus 4.8 ba7c99a05b fix(billing): import PaymentGatewaysModule so BillingService can resolve the registry
الـAPI كان في حلقة سقوط: BillingService يحقن PaymentGatewayRegistry، لكنه
يعيش في PaymentGatewaysModule الذي يستورده PaymentsModule دون إعادة تصدير.
الاستيراد المباشر يجعل التبعية صريحة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:32:39 +03:00
Hamza-AyedandClaude Opus 4.8 5b101bca57 fix(tests): clear geofence mock before the insignificant-ping assertion
النبضة الأولى في الاختبار ذات دلالة وتنادي الجيوفنس، فالعدّاد لم يكن صفراً
عند فحص النبضة الثانية. خطأ في الاختبار لا في الخدمة.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:29:32 +03:00
Hamza-AyedandClaude Opus 4.8 4db7af9301 fix(tests): inject GeofenceService into DriverLocationService spec
الاختبار بقي على constructor بثلاثة وسائط بعد إضافة الجيوفنس، فأسقط
`npm test` وأوقف بناء الصورة — فلم يقلع الـAPI أصلاً على 4010.

يضيف أيضاً تغطية للتعاون الجديد: الجيوفنس لا يُنادى على نبضة بلا دلالة،
ولا يُفشل النبضة إذا سقط هو.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 19:25:57 +03:00
Hamza-Ayed ab5f2938d5 fix: resolve typescript arguments error for notifications 2026-07-18 19:22:03 +03:00
Hamza-Ayed 736af62344 fix: correct method name for sending push notification 2026-07-18 19:19:57 +03:00
Hamza-Ayed 741c6d36de fix: resolve import path error in geofence controller causing container crash 2026-07-18 19:11:49 +03:00
Hamza-Ayed fef1235fc9 feat: add location intelligence engine and E2E simulation script 2026-07-18 19:08:52 +03:00
Hamza-Ayed a15e65c42e feat: implement geofence using redis pub-sub 2026-07-18 18:48:25 +03:00
Hamza-Ayed cc5bc8c867 fix: change transit db port to 5434 to avoid conflict 2026-07-18 18:40:44 +03:00
Hamza-Ayed 12d576a5bc feat: add multi-stage Dockerfile for production NestJS deployment 2026-07-18 18:29:16 +03:00
Hamza-Ayed a90b31c3ad fix: Resolve TypeScript build errors in billing service 2026-07-18 18:21:54 +03:00
Hamza-Ayed f30209b1d7 feat: add transit microservice and supervisor dashboard 2026-07-18 18:18:40 +03:00
Hamza-Ayed 4aeab5275a feat: Add tenant wallet, billing tables, and transit microservice 2026-07-18 18:15:23 +03:00
Hamza-Ayed d239f51d5d feat: implement billing module for tenant subscription management and super-admin payment processing 2026-07-18 17:26:16 +03:00
Hamza-AyedandClaude Sonnet 5 d4ac38ca87 feat: P1 — بوابات دفع حقيقية (PayMob) + إغلاق ثغرة webhook حرجة
الأهم أولاً: `PaymentsService.webhook()` كان يقبل أي جسم `{ payment_id }`
بلا أي تحقّق توقيع — من يعرف معرّف دفعة معلَّقة كان يستطيع تحويلها «ناجحة»
ويشحن رصيداً من عدم (محفظة راكب أو رصيد سائق تشغيلي). الآن كل تغيير حالة
محروس بـ`adapter.verifyWebhook(headers, payload, tenant)`، ولا شيء يُقرأ من
الحمولة قبل ذلك كقرار ثقة — قراءة المرجع لتحديد المستأجر ليست قراراً.

البنية (docs/07 · docs/24 — P1):
- `PaymentAdapter`: charge() يعيد instant (كاش) · redirect (بوابة API حقيقية)
  · invoice (بلا API، تسوية عبر P2).
- PayMob (مصر): تسلسل auth→order→payment_key→iframe حقيقي عبر fetch،
  وتحقّق HMAC-SHA512 على تسلسل حقول ثابت (بروتوكول PayMob الرسمي بالضبط)
  بمقارنة ثابتة الزمن. مفاتيح كل مستأجر مستقلة — حساب تاجر خاص به.
- كليق/شام كاش/MTN/سيرياتيل/زين كاش: محوّل مشترك واحد لأن سلوكها متطابق
  فعلياً في سيرو (`create_*_invoice.php` تُنشئ فاتورة فقط، لا نداء بوابة
  حيّاً) — مرجع + حساب استلام معروض، والتسوية عبر رسالة SMS لا webhook.
  MTN/سيرياتيل الحقيقيَّين (توكن+OTP) موثَّقان كبند مفتوح: لا نبني تكاملاً
  لا نملك اعتماداً حيّاً للتحقّق منه.
- `PATCH /payments/settings` لأدمن المستأجر: مفاتيح PayMob · حسابات
  الاستلام · سرّ webhook الرسائل — الاستجابة لا تُعيد الأسرار.

تنظيف: إزالة الإشارات المتبقّية لحاوية `martin` من docs/07 (أُزيلت فعلياً
سابقاً)، وتحديث هيكل الكود الموثَّق ليطابق ما هو مبنيّ فعلاً.

25 اختباراً جديداً (226 إجمالاً) — منها توقيع PayMob محسوب فعلياً ومُتحقَّق،
وتلاعبٌ بالحمولة بعد التوقيع يُرفض، وسبع حالات تثبت إغلاق ثغرة الـwebhook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 15:50:05 +03:00
Hamza-AyedandClaude Fable 5 da035e46a4 feat: P2 — التسوية بالرسائل + إزالة مارتن + مستند البنية (docs/25)
P2 (كليك/شام كاش بلا API):
- الرسالة تُحفظ خاماً **قبل** أي تحليل: التحليل قد يفشل فنحتاج الأصل لإعادة
  المعالجة، وعند النزاع يكون النصّ الأصلي هو الحجّة لا تفسيرُنا له.
- النقطة تصنع المال، فرسالة مزوّرة = رصيد من عدم. الحماية: سرّ لكل مستأجر
  بمقارنة ثابتة الزمن (المقارنة النصّية تسرّب السرّ حرفاً حرفاً زمنياً)،
  وبصمة محتوى فريدة تمنع احتساب إعادة الإرسال مرتين.
- Gemini بحرارة صفر ومطالَب بإرجاع null عند عدم اليقين: نموذج يخمّن مبلغاً
  يسوّي فاتورة بمال لم يصل. بلا مبلغ صريح → مراجعة بشرية لا تسوية.
- المطابقة بالمرجع أولاً، ثم بالمبلغ خلال 24 ساعة وبشرط فاتورة وحيدة —
  فاتورتان بنفس المبلغ التباسٌ يُراجَع، لا تسويةٌ عشوائية لإحداهما.
- التسوية تمرّ بـmarkSuccess نفسه فلا يتفرّع مسار مالي ثانٍ.

إزالة حاوية martin: انطلق منصّة قائمة بذاتها لها خوادمها؛ دورنا طلب وردّ لا
استضافة خرائط (قرار المالك).

docs/25: جرد الحاويات · لماذا الدفع ليس خدمة منفصلة · سيرفر السوبر-أدمن
المنفصل ونطاقه الفرعي · WebSocket مقابل الاستطلاع بالأرقام · أحجام السيرفرات
على أساس الذروة لا المعدّل · نقل مستأجر · ترتيب التوسّع.

13 اختباراً جديداً (201 إجمالاً، كلها خضراء).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:19:17 +03:00
Hamza-AyedandClaude Fable 5 b1a060c5ed feat: P0/P3/P5 — دفترا المستأجر ورسم العملية وتوجيه المال
الأساس المالي حسب docs/24: فصل ما نملكه عمّا نحتفظ به لغيرنا.

- جدولان منفصلان (`tenant_revenue_ledger` · `tenant_pending_ledger`) لا جدول
  واحد بعمود نوع: استعلامٌ ينسى الشرط كان يجعل المالك يسحب من مال الركّاب.
  مضافان فقط، والرصيد مشتقّ لا حقل يُحدَّث.
- فهرس فريد (tenant_id, ref) = حارس التسوية المزدوجة. التصادم يُلتقط من
  القاعدة لا بالفحص المسبق وحده — نداءان متزامنان يمرّان معاً قبل أي كتابة.
- رسم العملية من مصدر واحد (35 ل.س · 5 ج.م · 0.20 د.أ) قابل للتجاوز من إعداد
  المستأجر، مع قصّه عند المبلغ حتى لا يخرج المستخدم بصافٍ سالب.
- توجيه الدفع: شحن السائق ← إيراد + رصيده التشغيلي · شحن الراكب ← أمانة ·
  الرسم ← إيراد. وفُتح مسار شحن السائق الذي لم يكن له مدخل إطلاقاً.
- ثغرة سُدّت: `purpose` يصل من الجسم، فراكب كان يستطيع إرسال `credit_topup`
  فيُسجَّل مالُه إيراداً ويُشحن حساب سائق لا يملكه. الدور الآن من التوكن.
- `/admin/overview`: الإيراد من الدفتر، وعمولة الرحلات حقل منفصل عنه.
- `/admin/wallet/summary` و`/revenue-by-reason` لأدمن المستأجر.

25 اختباراً جديداً (188 إجمالاً، كلها خضراء). أحدها أمسك عطلاً فعلياً: صافٍ
صفري كان ينادي الدفتر بصفر فيرمي خطأً بعد قيد الرسم — عملية نصف مطبَّقة.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:04:45 +03:00
Hamza-AyedandClaude Fable 5 4ca932dd1f fix: عمود overview + قرارات المالك (الخرائط · فلسفة المال · تنظيف المستودع)
إصلاح انحدار أدخلتُه: /admin/overview كان يستعلم عن `t.created_at` وهو غير
موجود في كيان الرحلة (الاسم `requested_at`)، فترجع النقطة 500 ويُفرَّغ جدول
المستأجرين في لوحة السوبر-أدمن. فحص الأنواع لم يمسكه لأن استعلامات
QueryBuilder نصوص، والمحكّ الذي كان سيمسكه لم يُشغَّل.

قرارات المالك 2026-07-18:
- الخرائط: `intaleq_maps` حصراً (SDK انطلق على MapLibre). تصحيح قرار سابق
  خاطئ: flutter_map + latlong2 طبقة منافسة تعطي نوعَي LatLng متضاربين.
  السبب ليس عمل جوجل في سوريا — بل قِدَم بياناتها في المنطقة.
- docs/24 جديد: فلسفة المال — فصل الإيراد (شحن السائق · رسوم العمليات) عن
  الأمانة (شحن الراكب)، بمحفظتين لا بحقل حالة، ودفتر مضاف فقط.
- P موسّعة (بوابات · تسوية بالرسائل لكليك/شام كاش · رسوم بالدولة · تقارير)
  و O5 (ترتيب المهام المجدولة)، وتأجيل Android Auto/CarPlay.
- الحزم: نسخ سيرو الأحدث + live_activities لشاشة القفل في iOS.

تنظيف: إخراج 463 ملف بناء NDK من التتبّع، وإخراج شهادات التوقيع ومفتاح
App Store من المستودع (تُولَّد من جديد عند الحاجة) مع تحديث .gitignore.
الملفات باقية على القرص — أُزيلت من الفهرس فقط.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:37:26 +03:00
Hamza-AyedandClaude Fable 5 de4bbd00ac feat: N1d/N2/N3 + قانون فلاتر وإعادة ضبط تطبيق السائق
الباك إند:
- تعيين أول أدمن صار يُنشئ المستخدم إن لم يوجد — كان يشترط دخولاً سابقاً،
  وهي بيضة ودجاجة تمنع الدخول إلى لوحة أي مستأجر جديد أصلاً.
- OTP_DEV_MODE كان يفشل مفتوحاً (`!== 'false'`): غياب المتغيّر أو خطأ مطبعي
  يترك الإنتاج برمز ثابت يفتح كل حساب، ويحرس السحب المالي كذلك. صار
  `=== 'true'` في الإعداد وفي قارئَيه، مع تحذير عند الإقلاع.
- N2: التحقّق من اللوغو عند الرفع (PNG/JPEG · 512+ · مربّع · سقف 5MB) عبر
  قراءة الترويسة بلا اعتمادية — بدل اكتشاف أيقونة ممطوطة بعد النشر.

N3: السكربت يولّد build_config.dart بأعلام const (طبقات docs/22 §1.5)،
ويضبط bundle IDs واسم التطبيق والأيقونات/splash، و--build يشغّل Shorebird.

فلاتر:
- docs/23: قانون مُلزِم للتطبيقين (البنية · Cubit · طبقة الشبكة · الأعلام).
- السائق: أُزيل Dart القديم (GetX) مع الإبقاء على الأصيل والإضافات وشهادات
  التوقيع وShorebird وFirebase الخاص به، وأُعيد هيكلته مطابقاً للراكب.
- طبقة الشبكة ترسل x-device-id وتجدّد التوكن عند 401 مرة واحدة فقط.
- الحزم موحّدة بين التطبيقين، والـAPI https حصراً في الاثنين.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 13:06:46 +03:00
Hamza-AyedandClaude Fable 5 3ab74a13a8 feat: N1 — خدمة اللوحات + فرض تعليق المستأجر + النظرة الشاملة
- اللوحات الثلاث تُخدم من نفس أصل الـAPI (/panel/{superadmin,admin,service})
  عبر useStaticAssets + bind mount، فترث TLS القائم بلا دومين ولا CORS.
- تعليق المستأجر كان زخرفة: tenant.status يُكتب ويُعرض بلا أي فرض. الآن
  يُفرض في JwtStrategy (نقطة واحدة) + AuthService.resolveTenant، مع إبطال
  الكاش ليسري فوراً، وتمرير عند تعذّر القراءة حتى لا تسقط المنصة كلها.
- GET /admin/overview: رحلات · GMV · إيراد المنصة لكل مستأجر باستعلام
  واحد مجمَّع (لا N+1).
- توثيق قرار طبقات التطبيق (const flags + tree-shaking · أصيل موحّد
  لـShorebird · طبقتا الإعداد) في docs/22 §1.5.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:35:20 +03:00