- مقدمة
- العرض
- MVP
- خارطة الطريق
- متطلبات المنتج
- ابدأ الآن
- البيئة المحلية
- البنية المعمارية
- الهيكل
- الأنماط
- الحزمة التقنية
- قاعدة البيانات
- تعدّد المستأجرين
- الفهرس (Catalog)
- العمل دون اتصال
- المصادقة
- التهيئة (Onboarding)
- تدفق التكامل (Integration Flow)
- التهيئة (Provision)
- القبول
- الحضور
- الامتثال التنظيمي
- الملف الشخصي
- الاجتماعات
- إدارة الرسوم
- الفاتورة
- التدويل
- الترجمة
- دليل الترجمة
- أمان قاعدة البيانات
- المساهمة
- هَوْغوارتس
- عرض حي
- الإلهام
تستهدف Hogwarts المدارس التي يكون فيها التراجع إلى 2G/3G، والواي-فاي المتقطّع، والانقطاعات الكاملة هو القاعدة — تجارب السودان التجريبية، ومسارات النقل الريفية، وقاعات الامتحانات في الأقبية، والمتقدِّمون على بيانات الجوّال المدفوعة مسبقاً. هذه الصفحة هي الاستراتيجية على مستوى المنصّة لتقديم محرّك عمل دون اتصال بدرجة إنتاجية بأساس بلا تنازلات، ثم طبقات ميزات لكل دور على مراحل فوق هذا الأساس الصلب.
لا نُسلِّم أنصاف حلول. الأساس يُبنى مرّة واحدة ويُبنى بشكل صحيح؛ ميزات كل دور تُطبَّق فوقه على مراحل. لقيود تعدّد المستأجرين والمزامنة راجع تعدّد المستأجرين وقاعدة البيانات. لشكل الملحمة السابق الذي تتّبعه هذه الوثيقة راجع Document Intelligence. لاتفاقيات مفاتيح القاموس راجع الترجمة.
لماذا هذا مهم
تتعامل المنصّة اليوم مع فشل الشبكة بطرق متفرّقة خاصة بكل ميزة. تتراكم نقاط الألم في اللحظات الأكثر أهمية.
| اللحظة | اليوم | التكلفة |
|---|---|---|
| تسجيل المعلّم للحضور دون اتصال | POST بمعاملة واحدة؛ الفشل يُظهر toast ويُلغي البيانات | فقدان حضور اليوم؛ إعادة الإدخال يدوياً |
| تسليم الطالب للامتحان على 3G ضعيف | الحفظ التلقائي يبقى؛ submitExamSession النهائي لا يبقى | تطبيق غرامة التسليم المتأخّر على طلاب بإشارة سيئة |
| السائق في نفق أثناء المسار | تذهب كتابات الصعود داخل التطبيق إلى الخادم مباشرة؛ تفشل بصمت | لا يرى أولياء الأمور حدث الصعود؛ تتآكل الثقة |
| المتقدِّم يرفع نسخة دراسية على 2G | S3 PUT، بلا تقسيم، بلا استئناف | التخلّي عن الطلب عند 90% من الرفع |
| تصحيح المعلّم بالسحب 50 ورقة على الهاتف | إجراء خادم واحد لكل سحبة؛ أي فشل يُوقف الطابور | إعادة بدء جلسة التصحيح؛ ~30 دقيقة ضائعة |
| قراءة أي شخص للجدول الدراسي دون اتصال | ذاكرة SW المؤقتة الديناميكية فقط إذا تمّت الزيارة سابقاً | ذاكرة مؤقتة باردة = شاشة فارغة + صفحة الفولباك دون اتصال |
تخدم المنصّة اليوم ~12 ألف طالب عبر 8 مدارس نشطة؛ تستهدف تجارب السودان والمملكة دفع هذا الرقم إلى ~40 ألف خلال 12 شهراً. متوسّط شركات الجوّال في السودان 1.2 ميغابت/ثانية تنزيل مع انقطاعات متكرّرة؛ مدارس المملكة الداخلية ترى تشبّعاً في واي-فاي قاعة الامتحانات خلال الأسابيع الحرجة. محرّك عمل دون اتصال صلب هو ما يفتح الباب لكلا النشرين.
كيف يبدو النجاح
هذه هي المقاييس الرئيسية التي يلتزم بها الفريق عند "اجتياز بوّابة الأساس" + "تسليم كل ملاحم الأدوار".
- صفر فقدان للحضور عند الانقطاع — يُسجَّل 100% من دفعات حضور المعلّمين خلال 60 ثانية من إعادة الاتصال.
- صفر فقدان لتسليم الامتحان — تكتمل 100% من استدعاءات
submitExamSessionفي النهاية، حتى بعد إعادة تشغيل المتصفّح. - تحميل دون اتصال أقل من ثانية للجدول الدراسي والإعلانات بعد الزيارة الأولى (عرض فوري من Dexie live query).
- معدّل تثبيت PWA ≥ 35% على الهاتف للمعلّمين النشطين خلال 4 أسابيع من الإطلاق.
- معدّل نجاح المزامنة ≥ 99.9% عبر جميع التعديلات المُطابورة (نافذة متدحرجة 7 أيام).
- استخدام التخزين ≤ 25 ميغابايت لكل مستخدم نشط في المتوسّط (هامش حصّة Safari iOS).
- زيادة حجم الحزمة — حزمة الأساس ≤ 35 كيلوبايت مضغوطة gzip؛ حزم كل ملحمة تُحمَّل بكسل.
- ميزانية الخيط الرئيسي — تفريغ المزامنة لا يحجب الخيط الرئيسي > 16 مللي ثانية (العمل ينتقل إلى Web Worker).
- إبطال الذاكرة المؤقتة من أوّل بايت — push WebSocket من الخادم يُبطل ذاكرة العميل خلال 200 مللي ثانية من الكتابة البعيدة.
مبادئ هندسية (بلا تنازلات)
هذه غير قابلة للتفاوض. كل مبدأ يُقود خيارات معمارية ملموسة.
- المزامنة لا تحجب الخيط الرئيسي أبداً. كل إعادة التشغيل، التشفير، الضغط، فحص التعارض يعمل في Web Worker مخصّص (
sync-worker.ts). الخيط الرئيسي محجوز للواجهة. - الكتل الكبيرة تذهب إلى OPFS، ليس IDB. المرفقات، أوراق الامتحان، الصوت المُسجَّل، الصور كلّها تستخدم Origin Private File System. IDB يحفظ فقط الحمولات المُهيكَلة وmetadata الأجزاء.
- كل تعديل دليل-عبث. كل envelope يحمل بصمة محتوى SHA-256. يُتحقَّق من الخادم عند الاستقبال؛ يُتحقَّق من العميل عند إعادة التشغيل.
- إبطال ذاكرة في الوقت الحقيقي. WebSocket يدفع أحداث إبطال من الخادم لكل العملاء المتّصلين. لا قِدَم صامت؛ لا انتظار
refetchOnReconnect. - تقسيم الكود لكل ملحمة. حزمة الأساس ≤ 35 كيلوبايت. كل ملحمة دور تُسلِّم حزمتها المحمَّلة بكسل. المدارس الجديدة تُنزّل فقط الأساس؛ كود كل ميزة يُحمَّل عند أوّل استخدام.
- القياس عن بُعد من اليوم 1. كل انتقال حالة (enqueue، flush، conflict، error، retry) يُصدر حدثاً مُهيكَلاً إلى Sentry و
OfflineSyncLog. لا إعادة تطبيق لاحقة. - علم الميزة يُتحكَّم في الميزات الجديدة، ليس الأساس.
School.offlineModeيُبدّل الميزات؛ مكتبة الأساس مُحمَّلة دائماً للـPWA المُثبَّتة. الأساس لا يمكن تبنّيه جزئياً. - ميزانيات الأداء بوابات CI. size-limit، Lighthouse CI، ميزانيات Sentry مخصّصة. CI يفشل عند التراجع؛ لا "نُصلحه في متابعة".
- حلّ التعارضات صريح دائماً للمستخدم. الكتابات الفوقية الصامتة ممنوعة. كل تعديل رفضه الخادم يُظهر واجهة تعارض مُهيكَلة مع البيانات على كلا الجانبين + خيار قرار.
- مكتبة الأساس مُصدَّرة بإصدارات. ترحيلات المخطّط بين الإصدارات مُختبَرة. لا نكسر PWA قديمة في الحقل بدون مسار ترقية موثّق.
- شروط الاستخدام قابلة للملاحظة.
useNetworkStatus،useStorageEstimate،usePendingSyncCountخطّافات عامة. أي سطح ميزة يمكنه إظهار ما يحدث للمستخدم. - أمان متعدّد المستأجرين معماري، ليس إجرائياً. اسم IDB DB معزول بـ
schoolId؛ SW يقرأ المستأجر من كوكي الجلسة وقت الطلب، ليس من global؛ التسرّب عبر المستأجرين مستحيل بحكم البناء.
قرارات معمارية للأساس
كل تنازل اعتبرناه، ولماذا رفضناه.
| القرار | نصف-الحلّ | الخيار بلا تنازلات |
|---|---|---|
| غلاف IDB | indexedDB خام (~0 KB) | dexie@^4 (~21 KB) مع dexie-react-hooks لـlive queries، نسخ المخطّط، المعاملات |
| موقع محرّك المزامنة | الخيط الرئيسي | sync-worker.ts Web Worker — إعادة التشغيل، التشفير، الضغط، فحوص النزاهة كلّها خارج الرئيسي |
| تخزين الكتل | حقل blob في IDB | OPFS لأي حمولة > 100 KB؛ IDB يحفظ فقط إشارة handle لملف OPFS |
| إبطال الذاكرة المؤقتة | refetchOnReconnect | بثّ خادم WebSocket cache-invalidate؛ إبطال أقل من 200 مللي ثانية |
| نزاهة التعديل | لا شيء | بصمة contentHash SHA-256 على كل envelope؛ يُتحقَّق منها عند الاستقبال وإعادة التشغيل |
| ضغط الحمولة | لا شيء | MessagePack + LZ4 (عبر @msgpack/msgpack + WASM lz4-wasm) للحمولات > 1 KB |
| طوبولوجيا استقبال المزامنة | نقطة نهاية واحدة | شظايا لكل مجال: /api/offline/sync/{attendance,exam,transport,messaging,application} بالتوازي |
| استراتيجية الحزمة | حزمة أساس واحدة | أساس (≤ 35 KB) + حزم كل ملحمة بكسل عبر حدود dynamic() |
| آلة الحالة | enum status | آلة حالة منتهية بنمط XState: idle → pending → in_flight → synced ∣ conflicted ∣ failed |
| حلّ التعارضات | LWW من الخادم | الخادم-مرجع + واجهة دمج مُهيكَلة (<ConflictResolver>) للتعارضات القابلة للحلّ |
| دور service worker | ذاكرة + stub مزامنة | ناقل رسائل بين التبويب وعامل المزامنة؛ يُشغّل إعادة التشغيل عند عدم وجود تبويب (background sync) |
| التسليم في الوقت الحقيقي | لا شيء / استطلاع SSE | إعادة استخدام socket.io-client@4.8.1 الحالي للإبطال منخفض الكمون + push notifications |
| أمان المستأجر | كوكي الجلسة في طبقة API | + Dexie معزول بـschoolId؛ SW يقرأ المستأجر لكل طلب من كوكي الجلسة |
| القياس عن بُعد | يُضاف لاحقاً | OfflineSyncLog + breadcrumbs في Sentry + لوحة <OfflineDevtools> تُسلَّم مع EPIC-0 |
ما شُحن في 2026-08-29/30 — صندوق الصادر، وسياسة
أول شريحة من هذه الخطة صارت حيّة، محصورةً في دروس لوموس: صندوق صادر
(src/lib/offline/outbox.ts، IndexedDB) يصفّ موضعَ المشاهدة والإكمال وإجابات
الاختبار ونصّ الواجب إلى POST /api/offline/sync الذي يعمل بلا تكرار لكلّ عنصر
ويردّ بحكم لكلّ عنصر؛ والمرفوضات تُركن ظاهرةً في /offline مع إعادة المحاولة أو
التجاهل — فصفّ «ظهور المزامنة المعلّقة» أعلاه صار ✓ للدروس وما يزال ✗ لبقية
الإجراءات. شريحة «التنزيل للمشاهدة دون اتصال» (الفيديو والمواد على الجهاز)
شُحنت في 08-29 وسُحبت في 08-30 بقرار المدرسة: الفيديوهات والمواد تُشاهَد داخل
التطبيق ولا تُنسخ إلى جهاز، والإصدار 2 من IndexedDB يحذف أيّ وسائط خزّنها جهاز.
عامل الخدمة (public/service-worker.js، الإصدار 2) يُثبَّت فعلًا الآن.
النطاق
10 ملاحم، ~42 قصة، 231 نقطة قصّة. EPIC-0 هو الأساس الذي تعتمد عليه كل ملحمة أخرى ويجب أن يجتاز بوّابة الأساس (انظر أدناه) قبل بدء أي عمل لكل دور.
| الملحمة | الهدف | النقاط |
|---|---|---|
| EPIC-0 — الأساس | تخزين Dexie، كتل OPFS، محرّك مزامنة Web Worker، ناقل رسائل SW، إبطال WebSocket، بصمات محتوى، قياس، devtools | 62 |
| EPIC-1 — قابلية تثبيت PWA | تخزين مسبق لهيكل التطبيق، نافذة التثبيت، تدفّق التحديث، واجهة ترويج التثبيت | 15 |
| EPIC-2 — ذاكرة قراءة ذكية | الجدول الدراسي، الإعلانات، الملف الشخصي، القائمة عبر Dexie live queries؛ إبطال ذاكرة WebSocket | 24 |
| EPIC-3 — المعلّم دون اتصال | الحضور بالجملة، مسح QR، طابور التصحيح بالسحب على الهاتف، واجهة تعارض مُهيكَلة | 32 |
| EPIC-4 — الطالب دون اتصال | طابور تسليم الامتحان، استمرارية سجلّ المراقَبة، تخزين الورقة مسبقاً، تكامل lockdown | 22 |
| EPIC-5 — السائق دون اتصال | صعود الرحلة للنقل بمحاكاة نمط geofence؛ تحميل مسبق للمسار + القائمة عبر OPFS | 15 |
| EPIC-6 — المتقدِّم دون اتصال | رفع مرفقات قابل للاستئناف عبر OPFS، مسوّدة طلب في Dexie، تسليم مُطابور | 18 |
| EPIC-7 — طابور المراسلة الصادرة | إرسال متفائل، طابور مرفقات، سياسة جسر واتساب، مزامنة استلام في الوقت الحقيقي | 16 |
| EPIC-8 — قابلية المراقبة | تحليلات OfflineSyncLog، breadcrumbs في Sentry، بلاطة لوحة المطوّر، لوحة SLI لكل مدرسة | 14 |
| EPIC-9 — الوثائق، التعريب، إعداد المسؤول | هذه الوثيقة + النسخة الإنجليزية، ~30 مفتاح قاموس، علم School.offlineMode، مجموعة Playwright E2E | 13 |
السياق المعماري
تمدّ الخطّة سقالة المرونة القائمة بدلاً من استبدالها. تطبيقان معياريان ذهبيان موجودان بالفعل في الكود — خطّاف الحفظ التلقائي للامتحان، ومتعقّب GPS لـgeofence — وتعمّم ملحمة الأساس كليهما في مكتبة قابلة لإعادة الاستخدام بدرجة إنتاجية.
| المكوّن | الموقع | الحالة الحالية |
|---|---|---|
| Service worker | public/service-worker.js | ذاكرة الشبكة-أولاً للـHTML/API؛ syncOfflineForms() في السطر 160 هو stub |
| تسجيل SW | src/components/providers/service-worker-provider.tsx | تسجيل window.load للإنتاج فقط |
| صفحة الفولباك دون اتصال | src/components/offline/content.tsx | ساكنة؛ لا مؤشّر آخر مزامنة |
| Manifest | src/app/manifest.ts | قابل للتثبيت؛ اختصارات لـ/attendance + /attendance/qr-code |
useOfflineQueue<T> | src/components/school-dashboard/attendance/shared/hooks.ts:541 | في الذاكرة فقط؛ يُفقَد عند إعادة التحميل — للتقاعد |
LocationQueue (IDB) | src/components/school-dashboard/attendance/geofencee/geo-tracker.tsx:59 | طابور IndexedDB كامل، تفريغ دفعي — مُعمَّم في عنصر أوّلي مشترك |
| الحفظ التلقائي للامتحان | src/components/school-dashboard/exams/take/hooks/use-auto-save.ts | debounce + localStorage + exp-backoff — مُعمَّم في عنصر أوّلي مشترك |
| مسوّدة الطلب | src/components/school-marketing/application/application-context.tsx:412 | جلسة خادم + localStorage لكل حملة + لكل مستخدم — مُرحَّل إلى Dexie |
| مسوّدة الإعداد | src/components/onboarding/util.ts:291 | DRAFT_STORAGE_KEY = "onboarding_draft_" — مُرحَّل إلى Dexie |
| استراتيجيات الشبكة | src/lib/performance-optimization.ts | slow-2g/2g/3g/4g/saveData/rtt — مُوصَّل في useNetworkStatus |
| ذاكرة LRU للخادم | src/lib/cache/exam-cache.ts | ذاكرة TTL لحدود الدرجات، علامة المدرسة |
| Circuit breaker | src/lib/circuit-breaker.ts | 5 إخفاقات → 30 ثانية تهدئة لـDB |
@tanstack/react-query | package.json | مثبّت 5.90.12 — مُوصَّل في EPIC-0 |
socket.io-client | package.json | مثبّت 4.8.1 — يُستخدم لإبطال ذاكرة WebSocket في EPIC-0 |
| Mobile API | src/app/api/mobile/** | 30+ نقطة نهاية بمصادقة JWT (JSON بحالة snake_case) |
| خطّاف صعود geofence | src/app/api/transportation/geofence-boarding/route.ts | webhook بمصادقة Bearer-token؛ idempotent؛ ack-and-ignore لرموز عدم التطابق |
حقائق المخطّط
- كل نموذج تجاري لديه
schoolIdمع@@index([schoolId]). كل كتابة دون اتصال يجب أن تحفظ هذا. KioskLog.syncedToAttendanceموجود بالفعل فيprisma/models/attendance.prisma:404— يُضبَط حالياً بشكل متزامن؛ سيُعاد استخدامه كعلم مزامنة مؤجَّلة.- نماذج جديدة مطلوبة:
OfflineMutation(طابور خادم دائم)،OfflineSyncLog(ذاكرة idempotency، TTL 30 يوماً)،CacheInvalidation(سجلّ بثّ موضوع، TTL 7 أيام). - حقل تعدادي جديد
School.offlineMode:disabled/limited/full. الافتراضيlimitedللمدارس الجديدة،fullللنشر التجريبي. - سلاسل الترجمة تعيش في
src/components/internationalization/{school-en,school-ar}.jsonتحتdictionary.offline.*.
الأنماط
كل ملحمة تتّبع نفس الاتفاقيات.
- Dexie DB لكل مستأجر. قاعدة Dexie واحدة لكل أصل تُسمّى
hogwarts-${schoolId}مع مخطّط بإصدارات. live queries عبرuseLiveQuery. الترحيلات مُختبَرة في CI. - كتل OPFS. الملفّات > 100 KB تذهب إلى OPFS. IDB يحفظ
{ opfsPath, size, sha256, mimeType }فقط. - عامل المزامنة.
sync-worker.tsيُشغّل استعلامات Dexie، ضغط الحمولة، التحقّق من بصمة المحتوى، إعادة تشغيل HTTP. الخيط الرئيسي يُرسل رسائل إليه؛ العامل يبثّ التقدّم. - مظروف تعديل من نوع discriminated.
{ id, schoolId, userId, action, payload, idempotencyKey, contentHash, attempts, lastError, state, createdAt }حيثactionهو سلسلة حرفية تُحلّ عبرactionRegistryوقت التفريغ. - مفاتيح idempotency. cuid v2 مولَّد من جانب العميل؛ يبقى في الحمولة؛ يفحص الخادم
OfflineSyncLogقبل إعادة التطبيق. - شظايا مزامنة لكل مجال.
/api/offline/sync/{attendance|exam|transport|messaging|application}للمعالجة المتوازية. كل نقطة نهاية تُرسل إلى معالجات الإجراءات لمجالها. - إبطال WebSocket. الخادم يبثّ رسائل
cache-invalidateعلى الكتابات. العميل يُطابق ضدّ جداول Dexie النشطة ويُطلق تحديث live-query. - خطّافات react رقيقة. كود الميزة يستخدم
useOfflineMutation/useLiveQuery/useNetworkStatus/usePendingSyncCount/useStorageEstimate. لا يستوردdexieأو@tanstack/*مباشرة أبداً. - التعريب أوّلاً. كل سلسلة دون اتصال تعيش في
dictionary.offline.*. لا إنجليزية مدمجة في JSX أو toasts أو إرجاعات الأخطاء. - تقسيم الكود لكل ملحمة. كود عدم الاتصال لكل ميزة يعيش في حزم بكسل. حدود
import("./feature/offline.ts").
الحالة الحالية مقابل الحالة المستهدفة
| القدرة | اليوم | بعد الأساس + ملاحم الأدوار |
|---|---|---|
| الحضور بالجملة دون اتصال | ✗ ضائع | ✓ مُطابور في Dexie، يُعاد تشغيله بواسطة عامل المزامنة، بصمة محتوى مُتحقَّقة |
| تسليم الامتحان النهائي دون اتصال | ✗ يرى المستخدم toast خطأ | ✓ شاشة كاملة "التسليم مُطابور"، تكتمل عند إعادة الاتصال خلال 30 ثانية |
| تصحيح بالسحب على الهاتف | ✗ RPC لكل سحبة | ✓ دفعة من 25 سحبة، تعديل واحد، واجهة متفائلة مع تراجع |
| صعود السائق داخل التطبيق دون اتصال | ✗ فشل صامت | ✓ طابور Dexie + شارة تفريغ يدوي + ذاكرة geofence دون اتصال |
| مرفق المتقدِّم على 2G | ✗ إعادة البدء من البايت 0 | ✓ نمط tus قابل للاستئناف عبر OPFS، الأجزاء المكتملة محفوظة |
| المراسلة الصادرة دون اتصال | ✗ واجهة متفائلة لكن بلا طابور | ✓ حالات فقاعة queued/sending/sent/failed + استلامات في الوقت الحقيقي |
| قراءة الجدول الدراسي دون اتصال | △ ذاكرة الشبكة-أولاً، فارغة في الذاكرة الباردة | ✓ Dexie live query، عرض فوري، إبطال WebSocket |
| تثبيت PWA | △ manifest قابل للتثبيت، بلا واجهة نافذة | ✓ شريط تثبيت في الزيارة الثالثة + تثبيت يدوي في الإعدادات |
| تدفّق تحديث SW | △ تحديثات SW صامتة | ✓ toast يطلب إعادة التحميل عند انتظار SW جديد؛ إطلاق مرحلي |
| رؤية المزامنة المعلّقة | ✗ لا شيء | ✓ درج في الرأس + إعادة المحاولة/التجاهل + واجهة تعارض |
| أمان المستأجر | △ كوكي الجلسة + middleware | ✓ + Dexie معزول؛ SW يقرأ المستأجر من كوكي الجلسة لكل طلب |
| القياس عن بُعد | ✗ لا شيء على أحداث عدم الاتصال | ✓ OfflineSyncLog + breadcrumbs في Sentry + لوحة المطوّر + devtools |
| ميزانية الخيط الرئيسي | △ المزامنة تعمل على الخيط الرئيسي | ✓ Web Worker؛ الخيط الرئيسي لا يُحجَب > 16 مللي ثانية أثناء المزامنة |
| إبطال الذاكرة المؤقتة | △ refetchOnReconnect | ✓ push WebSocket؛ إبطال أقل من 200 مللي ثانية |
| حلّ التعارضات | ✗ كتابة فوقية صامتة أو toast | ✓ واجهة <ConflictResolver> مُهيكَلة مع كلا الجانبين + خيار قرار |
| تقسيم الكود | △ حزمة واحدة | ✓ أساس < 35 KB؛ حزم كل ملحمة بكسل |
سياسة حلّ التعارضات لكل إجراء
الافتراضي هو الخادم-هو-المرجع مع واجهة دمج مُهيكَلة. التجاوزات لكل إجراء أدناه.
| الإجراء | الاستراتيجية | الواجهة عند التعارض |
|---|---|---|
attendance.bulkUpload | آخر كتابة تفوز بـmarkedAt | Toast "كان حفظك أقدم من تحرير {الزميل}. القيم الأحدث محفوظة." مع رابط diff |
attendance.qrScan | أول كتابة تفوز بـscannedAt | Toast "الطالب مُحدَّد حاضراً بالفعل في {الوقت}." |
exam.submit | رفض عند عدم تطابق paperVersion | شاشة كاملة <PaperVersionConflict> مع خيار إعادة الجلب |
exam.autoSave | آخر كتابة تفوز بـsavedAt | صامت (المسوّدات تقبل دائماً الأحدث) |
exam.proctorEvent | إلحاق فقط | صامت (سجلّ مراجعة؛ إزالة التكرار من جانب الخادم) |
grading.batchApply | رفض عند عدم تطابق gradeVersion | لوحة <GradingConflict> تُظهر تغييراتك مقابل الحالة الحالية، احتفاظ/تجاهل لكل صف |
transportation.boardingEvent | خادم-مرجع idempotent | صامت (بالفعل upsert على (schoolId, tripId, studentId)) |
application.submit | أول كتابة تفوز بـsubmittedAt | Toast "الطلب مُسلَّم بالفعل في {التاريخ}." |
application.draftSave | آخر كتابة تفوز بـsavedAt | صامت |
messaging.send | إلحاق فقط | صامت (idempotency على clientMessageId) |
messaging.markRead | آخر كتابة تفوز (أقصى حالة قراءة) | صامت (زيادة أحادية) |
notification.acknowledge | إلحاق فقط idempotent | صامت |
مصفوفة توافق المتصفحات
تستهدف الاستراتيجية المتصفحات التي تستخدمها المدارس فعلياً. كشف القدرات في وقت التشغيل في useNetworkStatus و useOPFSSupport.
| المتصفّح / النظام | Dexie | OPFS | Web Worker | SW | Background Sync | beforeinstallprompt | Network Info | ملاحظات |
|---|---|---|---|---|---|---|---|---|
| Chrome Android 110+ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | دعم كامل؛ الهدف الأساسي |
| Safari iOS 16+ | ✓ | ✓ (16.4+) | ✓ | ✓ | ✗ | ✗ (أضف للشاشة) | ✗ | OPFS في 16.4؛ SW مقيَّد؛ الرجوع إلى online |
| Samsung Internet 19+ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | دعم كامل |
| Chrome Desktop 110+ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | دعم كامل |
| Safari macOS 16+ | ✓ | ✓ (16.4+) | ✓ | ✓ | ✗ | ✗ | ✗ | نفس Safari iOS |
| Firefox 110+ | ✓ | ✓ (111+) | ✓ | ✓ | ✗ | ✗ | جزئي | OPFS في 111؛ عامِله كـSafari للمزامنة |
| Edge 110+ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | مبني على Chromium، دعم كامل |
| متصفّحات داخل التطبيق (واتساب) | ✓ | ✗ | ✓ | △ | ✗ | ✗ | ✗ | OPFS غير متاح؛ الرجوع إلى تخزين blob في IDB |
حيث OPFS مفقود، طبقة التخزين ترجع إلى حقول blob في IDB مع تحذير حصّة. حيث Background Sync مفقود، المزامنة تعمل على حدث online + تغيّر visibility أثناء فتح التبويب؛ عند إعادة فتح PWA، العامل يُفرّغ فوراً.
ميزانية التخزين لكل دور
مراقب navigator.storage.estimate() يُحذّر عند 80% من الاستخدام. OPFS مُضمَّن في نفس حصّة الأصل؛ نُقسِّم عمداً.
| الدور | IDB (Dexie) | OPFS | المجموع النموذجي | الأسوأ | سياسة الإجلاء |
|---|---|---|---|---|---|
| المعلّم | 3 ميغابايت | 2 ميغابايت | 5 ميغابايت | 15 ميغابايت | LRU 7 أيام للقائمة؛ طابور الحضور لا يُجلى قبل الإرسال |
| الطالب | 2 ميغابايت | 3 ميغابايت | 5 ميغابايت | 12 ميغابايت | PDF ورقة الامتحان يُجلى عند نهاية الجلسة؛ الحفظ التلقائي لا يُجلى قبل الإرسال |
| السائق | 4 ميغابايت | 1 ميغابايت | 5 ميغابايت | 18 ميغابايت | قائمة الرحلة تُجلى عند نهاية الرحلة؛ طابور الصعود لا يُجلى قبل الإرسال |
| المتقدِّم | 2 ميغابايت | 8 ميغابايت | 10 ميغابايت | 25 ميغابايت | أجزاء المرفقات في OPFS حتى اكتمال الرفع؛ المسوّدات تبقى حتى التسليم |
| ولي الأمر | 1 ميغابايت | 0 ميغابايت | 1 ميغابايت | 4 ميغابايت | للقراءة فقط؛ الإعلانات المخزّنة LRU 14 يوماً |
| المسؤول | 3 ميغابايت | 1 ميغابايت | 4 ميغابايت | 12 ميغابايت | نفس المعلّم |
ميزانيات الأداء
هذه هي الميزانيات التي يفرضها CI.
| المقياس | الميزانية | كيف يُقاس |
|---|---|---|
| حزمة الأساس مضغوطة gzip | ≤ 35 KB | CI size-limit |
| كل حزمة ملحمة مضغوطة gzip | ≤ 15 KB | CI size-limit |
| وقت التفاعل دون اتصال (IDB بارد) | ≤ 1.5 ثانية على 3G | Lighthouse CI |
| كتابة Dexie p95 | ≤ 50 ms | معاملة Sentry |
| قراءة Dexie p95 | ≤ 20 ms | معاملة Sentry |
| كتابة OPFS p95 (لكل جزء 1 ميغابايت) | ≤ 200 ms | معاملة Sentry |
| تفريغ عامل المزامنة لكل تعديل | ≤ 300 ms | OfflineSyncLog.replayedAt - createdAt |
| كمون إبطال WebSocket | ≤ 200 ms | timestamp الخادم → تحديث live-query العميل |
| وقت تنشيط SW | ≤ 800 ms | توقيت navigator.serviceWorker.ready |
| إعادة الاتصال إلى طابور مُفرَّغ (10 عناصر) | ≤ 3 ثوانٍ | اختبار Playwright يدوي + Sentry |
| نسبة إصابة الذاكرة للجدول الدراسي | ≥ 95% | قياس مخصّص |
| حجب الخيط الرئيسي أثناء المزامنة (p99) | ≤ 16 ms | Performance API long-task observer |
المرحلة التمهيدية (الأسبوع 0): استكشافات وأبحاث
عشرة استكشافات تُسلَّم قبل بدء EPIC-0. كل واحد محدّد بـ0.5-1 يوم وينتج Decision Record. كلّها يجب أن تُدمَج قبل بدء EPIC-0.
| الاستكشاف | الهدف | النتيجة | المسؤول |
|---|---|---|---|
| S-0.1 | قياس أداء Dexie مقابل idb لدفعات 1000 كتابة، كمون live-query، ترحيل المخطّط | اختير Dexie؛ أرقام قياس في DR | المطوّر أ |
| S-0.2 | حصص OPFS + أداء الكتابة على Safari iOS 16.4، Chrome Android 110، Firefox 111 | مصفوفة القدرات + الفولباك | المطوّر ب |
| S-0.3 | نموذج أوّلي Web Worker + Comlink RPC لمحرّك المزامنة | POC يعمل؛ ميزانية الخيط الرئيسي مُتحقَّق منها | المطوّر أ |
| S-0.4 | قياس LZ4-wasm مقابل Snappy على حمولات نموذجية (دفعة حضور، إجابات امتحان) | اختير الضغط؛ أرقام نسبة الضغط | المطوّر ب |
| S-0.5 | أداء SHA-256 عبر SubtleCrypto مقابل WASM على حمولات 1 ميغابايت | اختير التجزئة؛ تكلفة لكل بايت | المطوّر أ |
| S-0.6 | طوبولوجيا cache-invalidate لـWebSocket — قناة واحدة مقابل غرف لكل مدرسة | الطوبولوجيا + خطة التوسعة | المطوّر ب |
| S-0.7 | نمط ناقل رسائل service-worker → sync-worker | مخطّط معماري + كود مرجعي | المطوّر أ |
| S-0.8 | XState مقابل آلة حالة يدوية لدورة حياة التعديل | قرار + تحليل تكلفة الحزمة | المطوّر ب |
| S-0.9 | سلوك Background Sync في iOS PWA — متى يطلق فعلياً | خطة الفولباك iOS موثّقة | المطوّر أ |
| S-0.10 | خط أساس لأنماط الاتصال الحالية من Sentry / Vercel Analytics | خط أساس "X% من الجلسات ترى ≥1 فشل شبكة" | المطوّر ب |
القصص لكل ملحمة
الملحمة-0 — الأساس: محرّك عمل دون اتصال بدرجة إنتاجية (62 نقطة)
الأساس يُبنى مرّة، يُبنى بشكل صحيح، ويُسلَّم قبل بدء أي عمل لكل دور. خمس ملاحم فرعية، كلّها مُدمَجة قبل بوّابة الأساس (انظر أدناه).
0.A — طبقة التخزين (18 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 0.A.1 | إضافة Dexie + dexie-react-hooks؛ مصنع DB لكل مستأجر hogwarts-${schoolId} | 5 | INFRA |
| 0.A.2 | إصدارات المخطّط + إطار الترحيل + اختبارات ترحيل CI | 5 | INFRA |
| 0.A.3 | تجريد OPFS (putBlob/getBlob/deleteBlob) مع فولباك IDB | 5 | INFRA |
| 0.A.4 | نماذج Prisma OfflineMutation + OfflineSyncLog + CacheInvalidation | 3 | DB |
0.B — عامل المزامنة (18 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 0.B.1 | سقالة sync-worker.ts Web Worker + Comlink RPC | 5 | INFRA |
| 0.B.2 | أنواع envelope discriminated-union في actionRegistry + مخططات Zod | 3 | API |
| 0.B.3 | آلة حالة دورة حياة التعديل بنمط XState (idle → pending → in_flight → ...) | 5 | API |
| 0.B.4 | خطّ ضغط LZ4 + MessagePack | 3 | INFRA |
| 0.B.5 | بصمة محتوى SHA-256 + تحقّق إعادة التشغيل | 2 | API |
0.C — أوّليات الشبكة والتعارض (12 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 0.C.1 | خطّاف useNetworkStatus المعياري (navigator.onLine + Network Info + heartbeat) | 3 | UI |
| 0.C.2 | خطّافات react رقيقة useMutationQueue<T> / useOfflineMutation / useLiveQuery | 5 | UI |
| 0.C.3 | ذرّة <ConflictResolver> + عنصر أوّلي لواجهة الدمج المُهيكَلة | 4 | UI |
0.D — service worker كمنسّق (8 نقاط)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 0.D.1 | استبدال stub syncOfflineForms() في public/service-worker.js:160 | 3 | INFRA |
| 0.D.2 | نقاط نهاية شظايا المزامنة لكل مجال /api/offline/sync/{domain} | 3 | API |
| 0.D.3 | بثّ خادم cache-invalidate عبر WebSocket + مُستقبِل عميل عبر socket.io | 2 | API |
0.E — القياس عن بُعد + devtools (6 نقاط)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 0.E.1 | breadcrumbs في Sentry + قياس OfflineSyncLog على كل انتقال | 3 | INFRA |
| 0.E.2 | لوحة <OfflineDevtools> عائمة (DEVELOPER فقط) تُظهر الطابور + الذاكرات | 3 | UI |
تُسلِّم ملحمة الأساس محرّك العمل دون اتصال بدرجة إنتاجية الذي تستهلكه كل ملحمة دور. ملفّات جديدة تهبط تحت src/lib/offline/{db,opfs,registry,types,state-machine,compression,integrity}.ts، عامل sync-worker.ts، و src/components/offline/{hooks,provider,devtools}/. يحجب كل ملحمة أخرى حتى تمرير بوّابة الأساس.
الملحمة-1 — قابلية تثبيت PWA + تخزين هيكل التطبيق مسبقاً (15 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 1.1 | قائمة التخزين المسبق لهيكل التطبيق + ختم الإصدار وقت البناء | 5 | INFRA |
| 1.2 | ذرّة <InstallPromptBanner> + التقاط beforeinstallprompt | 3 | UI |
| 1.3 | تدفّق تحديث SW + <UpdateAvailableToast> + إطلاق مرحلي | 3 | UI |
| 1.4 | اختصارات manifest لـ /exams، /messages، /timetable | 1 | INFRA |
| 1.5 | تلميع صفحة عدم الاتصال (تعريب + عدد التعديلات المعلّقة + واجهة تخزين) | 3 | UI |
تُرقّي manifest القابل للتثبيت الحالي إلى PWA حقيقي. اسم الذاكرة المؤقتة يستخدم __BUILD_ID__ للإبطال عند النشر. شريط التثبيت يظهر في الزيارة الثالثة للوحة التحكّم، التجاهل محفوظ في Dexie. تدفّق التحديث يستمع لـcontrollerchange ويطلب من المستخدم إعادة التحميل. الإطلاق المرحلي يُمكّن تحديثات SW لـ10% → 50% → 100% من الأجهزة عبر 48 ساعة.
الملحمة-2 — ذاكرة قراءة ذكية (24 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 2.1 | جداول Dexie للقراءات المخزّنة + ربط live-query | 5 | API |
| 2.2 | قراءة مخزّنة للجدول الدراسي (getTimetableForOffline) مع إبطال WebSocket | 5 | API |
| 2.3 | قراءة مخزّنة للإعلانات + مزامنة حالة القراءة عند الاتصال | 3 | API |
| 2.4 | قراءات مخزّنة للملف الشخصي + قائمة الفصل | 3 | API |
| 2.5 | ذرّة <CachedDataBanner> (تُعرَض عندما يكون عمر الذاكرة > 5 دقائق ودون اتصال) | 3 | UI |
| 2.6 | إجلاء الذاكرة عند تبديل المستأجر | 2 | INFRA |
| 2.7 | مراقب حصّة التخزين + فرض سياسة إجلاء LRU | 3 | INFRA |
تُعرَض الأسطح غالباً-للقراءة فورياً دون اتصال من Dexie live queries. مفاتيح الذاكرة تتّبع [schoolId, resource, id]. staleTime المُدرك للشبكة يُشتقّ من عتبات performance-optimization.ts — 5min على 4G، 30min على slow-2g. WebSocket يُبطل المُدخلات المخزّنة خلال 200 مللي ثانية من الكتابات البعيدة.
الملحمة-3 — المعلّم دون اتصال (الحضور + QR + التصحيح) (32 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 3.1 | تسجيل bulkUploadAttendance كمعالج إجراء "attendance.bulkUpload" | 5 | API |
| 3.2 | تخزين مسبق لقائمة الحضور (Dexie + OPFS لصور الطلاب) عند فتح الفصل | 5 | API |
| 3.3 | طابور مسح QR (يُعيد استخدام KioskLog.syncedToAttendance) | 5 | UI |
| 3.4 | دفعة تصحيح بالسحب على الهاتف (تجميع 25 سحبة في تعديل واحد) | 5 | UI |
| 3.5 | <PendingMutationsDrawer> مع أدوات إعادة المحاولة/التجاهل + مُجمَّعة حسب الإجراء | 5 | UI |
| 3.6 | واجهة تعارض مُهيكَلة لـlast-write-wins (409 ATTENDANCE_NEWER_RECORD) عبر <ConflictResolver> | 5 | UI |
| 3.7 | واجهة متفائلة في جدول الحضور عبر useOptimistic (React 19) مع تراجع | 2 | UI |
تُغلّف bulkUploadAttendance في src/components/school-dashboard/attendance/actions/bulk.ts:33 كإجراء مسجَّل. التخزين المسبق يملأ Dexie + OPFS (صور الطلاب) عندما يفتح المعلّم فصلاً. طابور QR يستبدل toast بمحاولة واحدة في qr-scanner.tsx:209. التصحيح بالسحب يُدفِّع 25 سحبة في تعديل واحد.
الملحمة-4 — الطالب دون اتصال (تسليم الامتحان + سجلّ المراقَبة) (22 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 4.1 | تسجيل submitExamSession كمعالج إجراء "exam.submit" | 5 | API |
| 4.2 | ترحيل use-auto-save إلى أوّليات الأساس (إسقاط إعادة المحاولة المخصّصة) | 3 | API |
| 4.3 | حفظ أحداث securityFlags للمراقَبة دون اتصال | 3 | API |
| 4.4 | شاشة كاملة "التسليم مُطابور" عند الانقطاع أثناء التسليم | 5 | UI |
| 4.5 | تخزين مسبق للامتحان (الأسئلة + الخيارات + الأصول) في OPFS | 3 | INFRA |
| 4.6 | حلّ تعارض paperVersion (شاشة كاملة <PaperVersionConflict>) | 3 | UI |
يُغلق الفجوة الحرجة: اليوم الحفظ التلقائي يصمد لكن submitExamSession النهائي في src/components/school-dashboard/exams/take/actions.ts:369 لا يفعل. مفتاح idempotency = sessionId. الخادم يرفض التسليم عند عدم تطابق paperVersion مع واجهة تعارض مُهيكَلة. الترحيل يُسقط حوالي 120 سطراً من إعادة المحاولة/backoff المخصّصين.
الملحمة-5 — السائق دون اتصال (صعود الرحلة) (15 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 5.1 | تسجيل recordBoardingFromGeofenceInternal كمعالج إجراء | 3 | API |
| 5.2 | تخزين مسبق لوضع رحلة السائق (المسار + القائمة + الصور في OPFS) | 5 | API |
| 5.3 | <TripQueueBadge> مع تفريغ يدوي + واجهة إعادة محاولة | 4 | UI |
| 5.4 | تقاعد LocationQueue المخصّص؛ ترحيل إلى أوّليات الأساس | 3 | INFRA |
يحاكي نمط geofence المعياري الذهبي في تجربة السائق داخل التطبيق. recordBoardingFromGeofenceInternal هو بالفعل idempotent على (schoolId, tripId, studentId). التخزين المسبق يملأ Dexie + OPFS عند بدء الرحلة. القصة 5.4 تتقاعد عن صنف LocationQueue في geo-tracker.tsx:59.
الملحمة-6 — المتقدِّم دون اتصال (مرفقات قابلة للاستئناف + تسليم) (18 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 6.1 | رفع S3 متعدّد الأجزاء بنمط tus قابل للاستئناف مدعوم بمتجر أجزاء OPFS | 8 | API |
| 6.2 | ترحيل مسوّدة الطلب من localStorage → Dexie (مع BroadcastChannel عبر التبويبات) | 5 | API |
| 6.3 | تسليم نهائي مُطابور ("application.submit") | 3 | API |
| 6.4 | واجهة تقدّم رفع مُدركة للشبكة (تُظهر الوقت المُقدَّر لكل effectiveType) | 2 | UI |
المتقدِّمون على 3G ضعيف يمكنهم استئناف رفع نسخة دراسية 5 ميغابايت — الأجزاء تعيش في OPFS. نقطة نهاية جديدة /api/uploads/resumable/[uploadId]/[partNumber] تتعقّب الأجزاء المكتملة. سياق الطلب في application-context.tsx:412 يترقّى من localStorage إلى Dexie.
الملحمة-7 — طابور المراسلة الصادرة (16 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 7.1 | إجراء مسجَّل "messaging.send" مع idempotency | 3 | API |
| 7.2 | واجهة متفائلة: فقاعة الدردشة تُظهر queued/sending/sent/failed عبر useOptimistic | 5 | UI |
| 7.3 | طابور المرفقات بإعادة استخدام رفع EPIC-6 القابل للاستئناف عبر OPFS | 3 | API |
| 7.4 | سياسة جسر واتساب دون اتصال (تجاوز أثناء عدم الاتصال؛ إرسال idempotent بـclientMessageId) | 3 | API |
| 7.5 | استلامات تسليم في الوقت الحقيقي عبر WebSocket | 2 | UI |
تُغلق فجوة المراسلة. حالات الفقاعة تتطابق مع دورة حياة XState من EPIC-0. الجسر يستقبل clientMessageId لمنع التكرار-على-الإعادة.
الملحمة-8 — قابلية المراقبة + تحليلات عدم الاتصال (14 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 8.1 | استعلامات تحليلات OfflineSyncLog + getOfflineSyncStats(schoolId) | 3 | DB |
| 8.2 | breadcrumbs في Sentry + معاملات مخصّصة لحالة الطابور | 3 | INFRA |
| 8.3 | بلاطة لوحة المطوّر: تعديلات مُطابورة، معدّل نجاح المزامنة، p95 وقت المزامنة | 5 | UI |
| 8.4 | لوحة SLI لكل مدرسة (معدّل نجاح المزامنة، معدّل إصابة الذاكرة، معدّل التثبيت) | 3 | UI |
تُجيب "هل يساعد عدم الاتصال المستخدمين" بأرقام صلبة. مقاييس لكل مدرسة تظهر في لوحة SaaS. لوحة SLI مكشوفة لدور ADMIN ليتمكّن كل مدرسة من المراقبة الذاتية.
الملحمة-9 — الوثائق والتعريب وإعداد المسؤول (13 نقطة)
| القصة | العنوان | النقاط | النوع |
|---|---|---|---|
| 9.1 | content/docs-en/offline.mdx + content/docs-ar/offline.mdx | 3 | DEVX |
| 9.2 | ~30 مفتاح قاموس تحت dictionary.offline.* (en + ar) | 5 | UI |
| 9.3 | واجهة تبديل المسؤول لـSchool.offlineMode | 3 | UI |
| 9.4 | Playwright offline E2E + مجموعة وحدات (≥ 95% تغطية على الأساس) | 2 | DEVX |
ملحمة تسليم قائمة بذاتها. القصة 9.4 تغطّي مسارات الأدوار الأربعة عبر context.setOffline(true). مفاتيح القاموس: online، offline، slowConnection، backOnline، syncing، pendingCount، pendingDrawerTitle، retry، discard، discardConfirm، lastSynced، cached، submissionQueued، submissionInFlight، conflictTitle، conflictBody، conflictResolveKeep، conflictResolveDiscard، conflictResolveMerge، installAvailable، installCta، updateAvailable، updateCta، offlinePageTitle، offlinePageBody، dataSaver، featureUnavailableOffline، storageNearFull، storageFullError، paperVersionConflict، gradingConflict.
استراتيجية الانتقال
الانتقال من حالة اليوم إلى عمل دون اتصال بدرجة إنتاجية يحدث على ثلاث مراحل لكل ميزة. لا تبديل مفاجئ.
- مرحلة الكتابة المزدوجة. تكتب الميزة عبر الإنترنت مباشرة وتُطابور أيضاً لإعادة التشغيل عبر الأساس الجديد.
OfflineSyncLogيُزيل التكرار عبر مفتاح idempotency. اكتشف الاختلاف في القياس عن بُعد؛ نبّه على أي. - التحويل. بدّل الميزة إلى الطابور-أولاً. التفريغ عبر الإنترنت تلقائي وفوري. علم
School.offlineMode='full'يُمكّن مسار الطابور؛limitedيبقي عبر الإنترنت فقط. - التنظيف. أزل إعادة المحاولة/localStorage/طابور الذاكرة المخصّص. احذف الكود الميت في
use-auto-save.ts،useOfflineQueue(attendance/shared/hooks.ts:541)،LocationQueue(geo-tracker.tsx:59).
ترتيب التحويل: الحضور → تسليم الامتحان → صعود السائق → مرفقات الطلب → المراسلة. كل تحويل يعيش خلف علم الميزة ويُسلَّم بشكل مستقلّ.
متعدّد الجوانب
إضافات المخطّط
طبّق عبر بروتوكول Neon branch-before-touch.
OfflineMutation— طابور خادم دائم:id،schoolId @@index،userId @@index،action،idempotencyKey @unique،contentHash،payload Json،status enum(PENDING|SUCCESS|FAILED|CONFLICT)،attempts،lastError،createdAt،syncedAt.OfflineSyncLog— ذاكرة idempotency:idempotencyKey @id،schoolId @@index،userId،action،responseJson Json،replayedAt. وظيفة TTL 30 يوماً.CacheInvalidation— سجلّ بثّ موضوع:id،schoolId @@index،topic،keyHash،emittedAt،propagatedAt. وظيفة TTL 7 أيام.School.offlineMode— حقل تعداديdisabled/limited/full. الافتراضيlimited.KioskLog.syncedToAttendance— موجود بالفعل. أعد الاستخدام، لا تكرّر.
التحكّم في الوصول حسب الدور
- درج المزامنة المعلّقة: مرئي لجميع الأدوار المُصادَق عليها.
- إعادة المحاولة / التجاهل: محدّد لدور المالك لكل ميزة.
- بلاطة لوحة المطوّر: تجميع عبر المدارس.
- لوحة SLI لكل مدرسة: دور ADMIN.
- تبديل وضع عدم الاتصال للمسؤول: دور ADMIN فقط.
- لوحة
<OfflineDevtools>: دور DEVELOPER فقط (محمي بفحص جلسة).
التعريب
30 مفتاح قاموس جديد تحت dictionary.offline.*. صفر سلاسل مدمجة في كود واجهة عدم الاتصال. السلاسل العربية يراجعها متحدّث أصلي قبل الإطلاق التجريبي.
استراتيجية الاختبار
| طبقة الاختبار | التغطية | الأداة |
|---|---|---|
| وحدة | توقيت useMutationQueue، عزل Dexie لكل مستأجر، انتقالات XState، تحقّق بصمة المحتوى | Vitest |
| تكامل | إرسال actionRegistry، شظايا مزامنة لكل مجال، سياسات التعارض، إبطال WebSocket | Vitest + msw |
| E2E | مسارات الأدوار الأربعة مع context.setOffline(true) + إعادة الاتصال؛ استمرارية إغلاق التبويب؛ تبديل النطاق | Playwright |
| بصري | <PendingMutationsDrawer>، <CachedDataBanner>، <ConflictResolver>، <OfflineDevtools> في الحالات | Storybook + Chromatic |
| يدوي | فحص جهاز حقيقي على Safari iOS 16.4، Chrome Android 110، Samsung Internet 19 | داخلي بنمط TestFlight |
| أداء | كل ميزانيات الأداء أعلاه + Lighthouse CI | size-limit + Sentry + LH |
| إمكانية الوصول | كل واجهة عدم اتصال قابلة للتنقّل بلوحة المفاتيح + قارئ الشاشة يُعلن "أنت دون اتصال" | axe + يدوي VoiceOver/TB |
| ترحيل | ترحيل مخطّط Dexie للأمام + توافق خلفي | حاوية اختبار Dexie |
القياس عن بُعد والمقاييس
كل انتقال حالة يُصدر حدثاً مُهيكَلاً. أسماء الأحداث تستخدم namespace offline.*.
| الحدث | الحمولة | الوجهة |
|---|---|---|
offline.mutation.enqueued | { schoolId, userId, action, idempotencyKey, contentHash } | Sentry + OfflineSyncLog |
offline.mutation.flushed | { ..., durationMs, attempts, compressionRatio } | Sentry + OfflineSyncLog |
offline.mutation.failed | { ..., error, willRetry } | Sentry + OfflineSyncLog |
offline.mutation.conflict | { ..., conflictType, resolution } | Sentry + OfflineSyncLog |
offline.network.changed | { from, to, effectiveType, saveData } | Sentry |
offline.cache.hit | { schoolId, queryKey, ageMs, source } | تحليلات مخصّصة |
offline.cache.miss | { schoolId, queryKey } | تحليلات مخصّصة |
offline.cache.invalidated | { schoolId, topic, keyHash, latencyMs } | تحليلات مخصّصة |
offline.install.prompted | { schoolId, userId, role } | تحليلات مخصّصة |
offline.install.accepted | { schoolId, userId, role } | تحليلات مخصّصة |
offline.install.dismissed | { schoolId, userId, role, dismissCount } | تحليلات مخصّصة |
offline.sw.updated | { schoolId, fromVersion, toVersion, rolloutPercent } | Sentry |
offline.storage.warning | { schoolId, usagePercent, quota } | Sentry |
offline.worker.long_task | { schoolId, taskMs, action } | Sentry |
الإطلاق التدريجي (علم الميزة)
School.offlineMode | SW مُسجَّل | ذاكرة قراءة | طابور كتابة | نافذة تثبيت |
|---|---|---|---|---|
disabled | ✗ | ✗ | ✗ | ✗ |
limited (الافتراضي) | ✓ | ✓ | ✗ | ✗ |
full | ✓ | ✓ | ✓ | ✓ |
المدارس التجريبية (السودان، الملك فهد) تختار full. SaaS Marketing يبقى على الافتراضي.
إعداد المطوّر
يضيف مساهم جديد دعم عدم الاتصال لميزته في خمس خطوات.
- سجّل معالج إجراء جديد في
src/lib/offline/registry.tsمع مخطّط حمولة Zod وسياسة تعارض. - استدعِ
useOfflineMutation("yourFeature.actionName", payload)من العميل؛ للقراءات استخدمuseLiveQuery(() => db.yourFeature.where(...).toArray()). - أضف دخول Storybook لواجهة التعارض إن كانت التعارضات محتملة.
- أضف اختبار Playwright في
playwright/tests/offline.spec.ts. - أضف مفاتيح قاموس لأي سلاسل عدم اتصال جديدة تحت
dictionary.offline.<feature>.*.
مثال عمل كامل يعيش في src/components/school-dashboard/attendance/ بعد تسليم EPIC-3.
بوّابة الأساس
EPIC-0 يجب أن يجتاز كل ما يلي قبل بدء أي ملحمة دور. عمل الأساس ليس تسليماً جزئياً.
- مصنع DB Dexie لكل مستأجر + إصدارات المخطّط + اختبارات الترحيل مُدمَجة
- تجريد OPFS مع فولباك IDB مُدمَج + اختبار Safari iOS 16.4 اليدوي ناجح
-
sync-worker.tsيُشغّل كل إعادة تشغيل التعديلات؛ مراقب long-task للخيط الرئيسي يُبلِّغ عن صفر حجب > 16 مللي ثانية أثناء المزامنة -
actionRegistry+ دورة حياة XState مُدمَجة بتغطية ≥ 95% اختبارات وحدة - ضغط LZ4 + MessagePack مقيس بنسبة ≥ 3× على الحمولات النموذجية
- بصمة محتوى SHA-256 تتحقّق في كل إعادة تشغيل؛ اختبار العبث يفشل مغلقاً
- كمون cache-invalidate عبر WebSocket من طرف إلى طرف ≤ 200 مللي ثانية في staging
- service worker يُفرّغ الطابور على حدث Background Sync (Chrome) وعلى إعادة فتح التبويب (iOS)
- ذرّة
<ConflictResolver>مُسلَّمة مع تغطية Storybook لكل سياسة تعارض -
OfflineSyncLogيكتب من كل انتقال؛ breadcrumbs في Sentry مرئية في اللوحة - لوحة
<OfflineDevtools>تُعرَض لدور DEVELOPER وتُظهر حالة الطابور الحيّة - حجم حزمة الأساس ≤ 35 KB مضغوطة gzip (بوّابة CI size-limit)
- اختبار Playwright لتسرّب المستأجر أخضر (طابور على
school-a→ تبديل إلىschool-b→ الطابور غير مرئي)
عمل الأساس يستمرّ حتى تُحدَّد كل المربّعات. لا ملحمة دور تبدأ قبل ذلك.
المخاطر والمقايضات
- حصص IndexedDB + OPFS. Safari iOS يُحدّد حوالي 50 ميغابايت؛ Chrome Android يسمح حتى 60% من القرص. التخفيف: OPFS للكتل؛ حمولات فقط في IDB؛ مراقب
navigator.storage.estimate()يُحذّر عند 80%؛ يُجلي ذاكرات القراءة قبل الكتابات المُطابورة. - تسرّب عبر المستأجرين عند تبديل النطاق الفرعي. التخفيف: اسم DB معزول بـ
schoolId؛ SW يقرأschoolIdمن كوكي الجلسة لكل طلب؛ اختبار Playwright في بوّابة الأساس. - أسئلة امتحان بائتة مقابل النزاهة. التخفيف: التخزين المسبق يحفظ
paperVersion؛ الخادم يرفض التسليم عند عدم التطابق مع<PaperVersionConflict>مُهيكَل. - تكرارات جسر واتساب. التخفيف:
clientMessageIdيُمرَّر إلى الجسر كمفتاح idempotency. - نطاق SW متعدّد النطاقات الفرعية. كل
school-X.databayt.orgيحصل على نطاق SW خاص (صحيح). - غياب Background Sync في Safari iOS. التخفيف: حدث
online+ مستمع visibility-change في التبويب؛ عند إعادة فتح PWA، عامل المزامنة يُفرّغ الطابور فوراً. - OPFS غير متاح على Safari قديم / متصفّحات داخل التطبيق. التخفيف: طبقة التخزين تكتشف القدرة عند التهيئة؛ ترجع إلى حقول blob في IDB مع تحذير تخزين.
- صفحة عدم اتصال باردة. التخفيف: تخزين مسبق عدواني للهيكل عند التثبيت؛ صفحة عدم الاتصال مفيدة حتى في أوّل زيارة (تُظهر CTA التثبيت).
- تقييد Background Sync. التخفيف: التفريغ الأمامي هو المسار الأساسي؛ المزامنة الخلفية بذل أفضل.
- انحراف الوقت على التعديلات المحفوظة. التخفيف: الخادم يسجّل
serverAcceptedAtكمرجع؛markedAtالعميل يبقى كـmetadata مراجعة فقط. - حدود اتصال WebSocket لكل أصل. التخفيف: socket واحد لكل تبويب؛ تنسيق بنمط SharedWorker عبر التبويبات لنفس الأصل.
- إجلاء التخزين على مستوى نظام التشغيل. التخفيف: تثبيت PWA يُرقّي إلى "تخزين مُدار" بضمانات إجلاء؛ المستخدمون غير المُثبَّتين يرون toast تحذير.
الجدول الزمني التفصيلي (14 أسبوعاً، أسبوع-بأسبوع)
مطوّران، مراحل مُنسَّقة. أرقام الأسابيع تقويمية بدءاً من انطلاق المشروع.
| الأسبوع | المرحلة | تركيز المطوّر أ | تركيز المطوّر ب | المعلم |
|---|---|---|---|---|
| 0 | استكشافات | S-0.1، S-0.3، S-0.5، S-0.7، S-0.9 | S-0.2، S-0.4، S-0.6، S-0.8، S-0.10 | كل Decision Records مُدمَجة |
| 1 | الأساس 0.A | Dexie tenant DB + إصدارات المخطّط | OfflineMutation + OfflineSyncLog + CacheInvalidation | نماذج Prisma تعمل على Neon |
| 2 | الأساس 0.A+B | تجريد OPFS + فولباك IDB | سقالة sync-worker.ts + Comlink RPC | Dexie + OPFS مُتحقَّق منهما على iOS 16.4 |
| 3 | الأساس 0.B+C | دورة حياة XState + الضغط + بصمات المحتوى | useNetworkStatus + useOfflineMutation + useLiveQuery | ميزانية الخيط الرئيسي مُتحقَّق منها |
| 4 | الأساس 0.D+E | ناقل رسائل SW + شظايا مزامنة لكل مجال | إبطال WebSocket + Sentry + <OfflineDevtools> | بوّابة الأساس — كل المربّعات خضراء |
| 5 | PWA + ذاكرة قراءة | EPIC-1.1، 1.2، 1.4 | EPIC-2.1، 2.2، 2.6 | نافذة التثبيت مرئية في staging |
| 6 | PWA + ذاكرة قراءة | EPIC-1.3، 1.5 | EPIC-2.3، 2.4، 2.5، 2.7 | EPIC-1 منجَز؛ EPIC-2 منجَز |
| 7 | المعلّم دون اتصال | EPIC-3.1، 3.2، 3.7 | EPIC-4.1، 4.5 | حضور المعلّم دون اتصال يعمل في staging |
| 8 | المعلّم دون اتصال | EPIC-3.3، 3.4 | EPIC-4.2، 4.3 | الحفظ التلقائي للامتحان مُرحَّل إلى الأساس |
| 9 | المعلّم + الطالب | EPIC-3.5، 3.6 | EPIC-4.4، 4.6 | EPIC-3 منجَز؛ EPIC-4 منجَز |
| 10 | السائق + المتقدِّم | EPIC-5.1، 5.2 | EPIC-6.1، 6.2 | صعود السائق دون اتصال + رفع OPFS قابل للاستئناف |
| 11 | السائق + المتقدِّم | EPIC-5.3، 5.4 | EPIC-6.3، 6.4 | EPIC-5 منجَز؛ EPIC-6 منجَز |
| 12 | المراسلة | EPIC-7.1، 7.2، 7.5 | EPIC-7.3، 7.4 | EPIC-7 منجَز |
| 13 | قابلية المراقبة | EPIC-8.1، 8.2 | EPIC-8.3، 8.4 | لوحة المطوّر + لوحة SLI لكل مدرسة تعمل |
| 14 | الوثائق + التجريبي | EPIC-9.1، 9.2 | EPIC-9.3، 9.4 + تمكين التجريبي | التجريبي مُمكَّن في مدرسة سودانية واحدة |
احتياطي: أسبوع واحد محجوز لـsoak في staging قبل تمكين التجريبي.
P0 — EPIC-0، 1، 3، 4 (الأساس + المعلّم اليومي + الطالب عالي المخاطر). P1 — EPIC-2، 5، 6، 9 (ذاكرة قراءة، سائق، متقدِّم، وثائق). P2 — EPIC-7، 8 (طابور المراسلة + تحليلات).
دليل الإطلاق التجريبي
تمكين School.offlineMode='full' لمدرسة تجريبية تغيير مدروس وقابل للملاحظة. خمس خطوات.
- ما قبل الإطلاق (T-7 أيام). تأكّد أن المدرسة على أحدث إصدار؛ فحص مسارات الإنترنت؛ خط أساس لمعدّل فشل الاتصال؛ إحاطة المسؤول.
- تمكين ناعم (T-0). يُبدّل ADMIN
offlineMode='full'في إعدادات المدرسة. service worker يُسجَّل في الزيارة التالية. - مراقبة اليوم 1. راقب
OfflineSyncLogلأول ~50 تعديل مُطابور؛ تحقّق من نجاح كل تفريغ؛ افحص Sentry. - مراجعة اليوم 7. قارن المقاييس الرئيسية. إذا أي شيء تحت 95%، جذر المشكلة أو ارجع إلى
limited. - استرجاع اليوم 30. قرّر: الإبقاء على
full، الترقية إلى الافتراضي، أو التحسين.
الرجوع فوري: يضبط ADMIN offlineMode='limited'.
قائمة اختبار التحقّق السريع
فحوصات يدوية قبل إعلان "ميزة منجَزة دون اتصال". شغّل على Safari iOS 16.4، Chrome Android 110، Chrome Desktop 110 كلّ منها.
- service worker مُسجَّل (
chrome://serviceworker-internalsيُظهره نشطاً) - manifest قابل للتثبيت
- هيكل التطبيق يُحمَّل مع
Network: Offlineبعد زيارة سابقة عبر الإنترنت - صفحة عدم الاتصال تُعرَض مع وقت آخر مزامنة + عدد التعديلات المعلّقة + واجهة تخزين
- حضور المعلّم بالجملة: حدّد 20 طالباً دون اتصال → أعد التحميل → كلّهم مرئيون كمعلّقين → أعد الاتصال → كلّهم مُفرَّغون → لا تكرارات
- امتحان الطالب: أدخل الإجابات دون اتصال → سلّم دون اتصال → شاشة كاملة "التسليم مُطابور" → أعد الاتصال → مُسلَّم
- صعود السائق: امسح 5 طلاب في عدم اتصال محاكٍ للنفق → أعد الاتصال → كلّ الخمسة مُسجَّلون
- المتقدِّم: ارفع مرفق 5 ميغابايت، اقتل الاتصال منتصف الرفع → أعد الاتصال → الرفع يستأنف من الأجزاء المكتملة في OPFS
- المراسلة: أرسل 3 رسائل دون اتصال → الفقاعة تُظهر
queued→ أعد الاتصال → كلّها تُظهرsent - تبديل النطاق الفرعي: عناصر طابور على
school-a→ انتقل إلىschool-b→ الطابور غير مرئي → عُد إلىschool-a→ الطابور سليم - نافذة تثبيت PWA: زر لوحة التحكّم 3 مرّات → يظهر الشريط → قبول → يطلق كـstandalone
- تحديث SW: انشر SW جديد → يظهر toast → إعادة التحميل تكتمل
- كل الواجهة بالعربية عند
?lang=ar→ السلاسل تُعرَض من اليمين إلى اليسار - الدرج المعلّق: زرّ إعادة المحاولة يعمل؛ زرّ التجاهل يعمل؛ كلاهما يؤكّد قبل الإتلاف
- التخزين شبه ممتلئ: املأ IDB+OPFS إلى 80% → يظهر toast تحذير
-
offlineMode='disabled'→ لا SW مُسجَّل - إبطال WebSocket: اكتب من جهاز أ → live query لجهاز ب يُحدَّث خلال ثانية واحدة
- واجهة التعارض: حاكِ تعارض نسخة →
<ConflictResolver>يُظهر كلا الجانبين → المستخدم يختار احتفاظ/تجاهل - مُحلّل الخيط الرئيسي لا يُظهر مهام طويلة > 16 مللي ثانية أثناء تفريغ مزامنة 50 عنصراً
معايير القبول — "عمل دون اتصال بدرجة إنتاجية مُسلَّم"
- مع
School.offlineMode='full'، معلّم علىchrome://devtools network: Offlineيُسجّل الحضور، يُعيد التحميل، يُغلق التبويب، يعيد الفتح — كل العلامات معلّقة؛ إعادة الاتصال تُعيد التشغيل بصفر تكرار وصفر حجب للخيط الرئيسي. - طالب يفقد الاتصال لـ10+ دقائق منتصف الامتحان، يستمرّ في الإجابة، يضغط تسليم، يرى "التسليم مُطابور"؛ إعادة الاتصال تكتمل خلال 30 ثانية بصفر فقدان بيانات.
- حدث SW
sync-formsيطلق عند إعادة الاتصال؛ يُفرّغ طابور Dexie دون أن تكون الصفحة مفتوحة. - إغلاق/إعادة فتح المتصفّح تحافظ على الطابور (Dexie، ليس في الذاكرة).
- تبديل النطاق الفرعي
school-a→school-bلا يُعيد تشغيل طابورschool-aضدّschool-b. - Playwright
tests/offline.spec.tsأخضر عبر مسارات الأدوار الأربعة + أمان المستأجر + حلّ التعارضات. - كل سلاسل واجهة عدم الاتصال تُعرَض في
enوarمن مفاتيح القاموس؛ صفر إنجليزية مدمجة في grep. offlineMode='disabled'يُعطّل تسجيل SW كلّياً.- حزمة الأساس ≤ 35 KB مضغوطة gzip (بوّابة CI). حزم كل ملحمة ≤ 15 KB لكلٍّ منها.
- القياس في Sentry يُظهر ≥ 99.9% معدّل نجاح مزامنة على نافذة متدحرجة 7 أيام عبر كل مدارس
full. - مراقب long-task للخيط الرئيسي يُظهر صفر حجب > 16 مللي ثانية أثناء تفريغ المزامنة في قياسات p99.
- كمون إبطال ذاكرة WebSocket p95 ≤ 200 مللي ثانية في الإنتاج.
- استئناف مرفقات OPFS يعمل بعد قتل الرفع منتصف الطيران على iOS 16.4.
الملفّات الرئيسية
| المسار | الدور |
|---|---|
src/lib/offline/db.ts (جديد) | مصنع Dexie tenant DB + إصدارات المخطّط |
src/lib/offline/opfs.ts (جديد) | تجريد OPFS (putBlob/getBlob/deleteBlob) |
src/lib/offline/registry.ts (جديد) | خريطة actionRegistry المكتوبة |
src/lib/offline/types.ts (جديد) | أنواع envelope التعديل discriminated-union |
src/lib/offline/state-machine.ts (جديد) | دورة حياة التعديل XState |
src/lib/offline/compression.ts (جديد) | خطّ ضغط LZ4 + MessagePack |
src/lib/offline/integrity.ts (جديد) | تحقّق بصمة محتوى SHA-256 |
src/lib/offline/sync-worker.ts (جديد) | محرّك مزامنة Web Worker |
src/components/offline/use-mutation-queue.ts (جديد) | خطّاف الطابور الدائم المعياري |
src/components/offline/use-network-status.ts (جديد) | خطّاف حالة الشبكة المركَّب |
src/components/offline/use-offline-mutation.ts (جديد) | غلاف react-query للتعديلات |
src/components/offline/use-live-query.ts (جديد) | غلاف Dexie live query |
src/components/offline/use-storage-estimate.ts (جديد) | خطّاف تفاعلي لـnavigator.storage.estimate() |
src/components/offline/provider.tsx (جديد) | <OfflineProvider> |
src/components/offline/pending-mutations-drawer.tsx (جديد) | واجهة الطابور قيد التشغيل |
src/components/offline/conflict-resolver.tsx (جديد) | عنصر أوّلي لواجهة الدمج المُهيكَلة |
src/components/offline/install-prompt-banner.tsx (جديد) | واجهة التقاط beforeinstallprompt |
src/components/offline/update-available-toast.tsx (جديد) | واجهة تدفّق تحديث SW |
src/components/offline/cached-data-banner.tsx (جديد) | مؤشّر "البيانات من ذاكرة مؤقتة" |
src/components/offline/devtools.tsx (جديد) | لوحة <OfflineDevtools> (DEVELOPER فقط) |
src/app/api/offline/sync/[domain]/route.ts (جديد) | نقاط نهاية شظايا مزامنة لكل مجال |
src/app/api/cache-invalidate/route.ts (جديد) | بثّ إبطال ذاكرة WebSocket |
src/app/api/uploads/resumable/[uploadId]/[partNumber]/route.ts (جديد) | رفع مقسّم بنمط tus |
public/service-worker.js | استبدل stub syncOfflineForms() في السطر 160 |
prisma/models/offline-sync.prisma (جديد) | OfflineMutation + OfflineSyncLog + CacheInvalidation |
src/components/school-dashboard/attendance/actions/bulk.ts:33 | سجّل كـ"attendance.bulkUpload" |
src/components/school-dashboard/exams/take/actions.ts:369 | سجّل كـ"exam.submit" |
src/components/school-dashboard/attendance/geofencee/geo-tracker.tsx:59 | تقاعد LocationQueue؛ استخدم العناصر الأوّلية المشتركة |
src/components/school-dashboard/attendance/shared/hooks.ts:541 | استبدل useOfflineQueue في الذاكرة |
src/lib/performance-optimization.ts | اربط كتبعية useNetworkStatus |
src/app/manifest.ts | أضف اختصارات /exams، /messages، /timetable |
src/components/internationalization/{school-en,school-ar}.json | أضف مفاتيح dictionary.offline.* |
playwright/tests/offline.spec.ts (جديد) | E2E يغطّي مسارات الأدوار الأربعة + المستأجر + التعارضات |
المسرد
- معالج إجراء (Action handler). إجراء خادم مسجَّل في
actionRegistryبسلسلة اسم. نفس الدالة المُستدعاة عبر الإنترنت أو عبر إعادة تشغيل دون اتصال. - هيكل التطبيق (App shell). الحدّ الأدنى من HTML و CSS و JS اللازمة لعرض كروم التنقّل. يُخزَّن مسبقاً وقت التثبيت.
- Background Sync. واجهة برمجة متصفّح تطلق حدث
syncعندما يكون الجهاز متّصلاً. دعم متصفّح محدود. - Comlink. مكتبة لـRPC مريح مع Web Worker.
- حلّ التعارض. سياسة تُطبَّق عندما لا يمكن تطبيق تعديل مُطابور بنظافة. الافتراضي خادم-مرجع مع واجهة دمج مُهيكَلة.
- بصمة محتوى (Content hash). SHA-256 لحمولة التعديل، محفوظة على envelope. يُتحقَّق منها عند الاستقبال وإعادة التشغيل. دليل عبث.
- Dexie. مكتبة غلاف فوق IndexedDB مع دلالات استعلام غنية، معاملات، live queries، إصدارات مخطّط.
- مفتاح idempotency. cuid v2 مولَّد من جانب العميل مرفق بكل تعديل. الخادم يستخدم
OfflineSyncLogلإزالة التكرار. - IDB. IndexedDB، متجر مفتاح-قيمة مفهرس أصلي للمتصفّح.
- LZ4. خوارزمية ضغط سريعة؛ مستخدمة هنا عبر WASM لضغط الحمولة > 1 KB.
- MessagePack. صيغة تسلسل ثنائية. أكثر إحكاماً من JSON لترميز envelope.
- مظروف التعديل. الغلاف المكتوب حول كتابة مُطابورة. يحمل metadata لإعادة التشغيل بما في ذلك مفتاح idempotency وبصمة المحتوى.
- Network Information API.
navigator.connection.effectiveType/saveData/rtt. مستخدم لتكييف الواجهة مع جودة الاتصال. - OPFS. Origin Private File System. نظام ملفّات أصلي للمتصفّح محدود بالأصل؛ أفضل من IDB للكتل الكبيرة.
- PWA. تطبيق ويب تقدّمي. تطبيق ويب قابل للتثبيت مع قدرة دون اتصال.
- التخزين المسبق. تخزين الأصول أو البيانات قبل أن يحتاج إليها المستخدم دون اتصال.
- استقبال المزامنة.
POST /api/offline/sync/{domain}، نقاط النهاية لكل مجال التي تستقبل التعديلات المُطابورة. - عامل المزامنة.
sync-worker.ts، Web Worker الذي يُعالج كل إعادة تشغيل دون اتصال خارج الخيط الرئيسي. - معزول لكل مستأجر. تخزين مفهرس بـ
schoolIdبحيث لا تتسرّب البيانات عبر المدارس. - XState. مكتبة لنمذجة آلة الحالة المنتهية. مستخدمة لدورة حياة التعديل.
انظر أيضاً
- تعدّد المستأجرين — قواعد تحديد نطاق
schoolIdالتي يجب أن تحترمها كل كتابة دون اتصال - قاعدة البيانات — بروتوكول أمان Prisma + Neon للجداول الجديدة
- Notifications — أنماط ذاكرة جانب القراءة تشارك البنية التحتية
- الترجمة — اتفاقيات مفاتيح القاموس لسلاسل عدم الاتصال
- Document Intelligence — قالب وثيقة الملحمة السابق الذي تتّبعه هذه الوثيقة
- البنية المعمارية — نمط المرآة وحلّ سياق المستأجر
- MVP — اتفاقيات شارات النضج لتتبّع تقدّم الملحمة
- خارطة الطريق — نظرة ربعية لإطلاق عدم الاتصال
On This Page
لماذا هذا مهمكيف يبدو النجاحمبادئ هندسية (بلا تنازلات)قرارات معمارية للأساسما شُحن في 2026-08-29/30 — صندوق الصادر، وسياسةالنطاقالسياق المعماريحقائق المخطّطالأنماطالحالة الحالية مقابل الحالة المستهدفةسياسة حلّ التعارضات لكل إجراءمصفوفة توافق المتصفحاتميزانية التخزين لكل دورميزانيات الأداءالمرحلة التمهيدية (الأسبوع 0): استكشافات وأبحاثالقصص لكل ملحمةالملحمة-0 — الأساس: محرّك عمل دون اتصال بدرجة إنتاجية (62 نقطة)الملحمة-1 — قابلية تثبيت PWA + تخزين هيكل التطبيق مسبقاً (15 نقطة)الملحمة-2 — ذاكرة قراءة ذكية (24 نقطة)الملحمة-3 — المعلّم دون اتصال (الحضور + QR + التصحيح) (32 نقطة)الملحمة-4 — الطالب دون اتصال (تسليم الامتحان + سجلّ المراقَبة) (22 نقطة)الملحمة-5 — السائق دون اتصال (صعود الرحلة) (15 نقطة)الملحمة-6 — المتقدِّم دون اتصال (مرفقات قابلة للاستئناف + تسليم) (18 نقطة)الملحمة-7 — طابور المراسلة الصادرة (16 نقطة)الملحمة-8 — قابلية المراقبة + تحليلات عدم الاتصال (14 نقطة)الملحمة-9 — الوثائق والتعريب وإعداد المسؤول (13 نقطة)استراتيجية الانتقالمتعدّد الجوانبإضافات المخطّطالتحكّم في الوصول حسب الدورالتعريباستراتيجية الاختبارالقياس عن بُعد والمقاييسالإطلاق التدريجي (علم الميزة)إعداد المطوّربوّابة الأساسالمخاطر والمقايضاتالجدول الزمني التفصيلي (14 أسبوعاً، أسبوع-بأسبوع)دليل الإطلاق التجريبيقائمة اختبار التحقّق السريعمعايير القبول — "عمل دون اتصال بدرجة إنتاجية مُسلَّم"الملفّات الرئيسيةالمسردانظر أيضاً