- مقدمة
- العرض
- MVP
- خارطة الطريق
- متطلبات المنتج
- ابدأ الآن
- البيئة المحلية
- البنية المعمارية
- الهيكل
- الأنماط
- الحزمة التقنية
- قاعدة البيانات
- تعدّد المستأجرين
- الفهرس (Catalog)
- العمل دون اتصال
- المصادقة
- التهيئة (Onboarding)
- تدفق التكامل (Integration Flow)
- التهيئة (Provision)
- القبول
- الحضور
- الامتثال التنظيمي
- الملف الشخصي
- الاجتماعات
- إدارة الرسوم
- الفاتورة
- التدويل
- الترجمة
- دليل الترجمة
- أمان قاعدة البيانات
- المساهمة
- هَوْغوارتس
- عرض حي
- الإلهام
مؤتمرات الفيديو — كتلة واحدة قائمة بذاتها في src/components/school-dashboard/conference، منعكسة 1:1 على المسار /conference. الرابط الخارجي الملصوق فعّال؛ وخادم LiveKit SFU يبعد أربعة متغيرات بيئة عن التشغيل (والتسجيل بوابة منفصلة)؛ ومحوّلات Meet/Zoom/Teams الأصلية موصولة لكنها خاملة.
كتلة واحدة قائمة بذاتها في src/components/school-dashboard/conference/، منعكسة بنسبة 1:1 على المسار
/conference. تشغّل ثلاثة خلفيات اجتماع خلف واجهة واحدة: مزوّد الرابط الخارجي الملصوق فعّال في كل
مكان؛ وخادم LiveKit SFU (غرف داخل التطبيق) مكتوب بالكامل ولا يفصله عن التشغيل سوى أربعة متغيرات
LIVEKIT_* — راجع RUNBOOK.md لمسار LiveKit Cloud السريع؛ والتسجيل بوابة منفصلة لم تعد تمنع فتح
الغرفة؛ ومحوّلات Meet / Zoom / Teams الأصلية تنشئ اجتماعات حقيقية عبر واجهة كل مزوّد لكنها تبقى
خاملة حتى تصل بيانات اعتماد OAuth.
البنية
src/components/school-dashboard/conference/— one block, mirrored 1:1 to /conferencecontent.tsx— server list (fetch + batched translate → table)queries.ts— read-only Prisma buildersactions.ts— server-action barrellist-actions.ts— list-layer CRUD (create/update/delete/list)online-policy.ts— is this online, over which back-end, delivered how (one resolver)day-window.ts— school-calendar day math (pure, DST-safe)school-calendar.ts— holiday gate for the sweep (write side only)actions/— rich sessions-layer server actionshelpers.ts— requireContext · canAccessSessionslot-session.ts— day-qualified slot lookup + cron writermaterialize-day.ts— online policy → one school day of sessionsopen-room.ts— loose mode — one standing room per section per daysessions.ts— lifecycle state machinejoin-core.ts— shared eligibility + token mint (action + route)tokens.ts— join → 5-min JWTrecordings.ts— list / signed-URL / deletenotifications.ts— 5 events → notification hub (in-app + email)attendance-sync.ts— presence → attendance (opt-in, LiveKit-only)settings.ts— capacity · recording · attendance sync · online window + mode + standing linkmoderation.ts— kickParticipantrecurring.ts— link carry-forward across termsauthorization.ts— PERMISSION_MATRIX (strict RBAC)permissions.ts— UI gating (rich layer)list-permissions.ts— UI gating + CRUD guards (list layer)validation.ts— Zod (sessions)list-validation.ts— Zod (table)list-params.ts— nuqs URL cachenav.tsx— heading + tab strip for the (app) surfaceslanding/— the /conference landing page (hero + bands)content.tsx— composes the sectionshero.tsx— copy + room collage (student photo + tiles)features-section.tsx— four value cardsnow-section.tsx— live / coming-up strip, section-scopedeverything-section.tsx— full-bleed value bandhow-to-section.tsx— three setup stepswelcome-section.tsx— closing band + settings CTAtypes.ts— shared section props + LandingSessiontable.tsx— DataTable + gridcolumns.tsx— column defs + action menuform.tsx— create/edit 5-step wizard (modal)form-steps.tsx— wizard steps: basics · schedule · meeting · references · accessschedule-form.tsx— standalone schedulersettings-form.tsx— admin school-policy formsection-recording-policy.tsx— per-section recording opt-outsection-online-policy.tsx— per-section online override (inherit/on/off)detail.tsx— single-session detailroom.tsx— in-app LiveKit roomparticipants-panel.tsx— HOST/CO_HOST in-room kick UIrecordings.tsx— recordings listrecording-player.tsx— lazy signed-URL playernetwork-test.tsx— LiveKit RTT/ICE diagnosticloading-skeleton.tsx— loading skeletontypes.ts— domain types + RoomJoinTicketerror-map.ts— error-code → stringnetwork-protocol.ts— pure ICE-path classifierlivekit/— SFU server SDK wrappersclient.ts— singletons + readinesstoken.ts— role → VideoGrantrooms.ts— room lifecycleegress.ts— recording egressrecording-urls.ts— signed S3 URLsroom-naming.ts— sch-{schoolId}-lc-{id}webhook.ts— HMAC verify + idempotent routerproviders/— link-provider adapterstypes.ts— adapter contractexternal.ts— pasted link (live)google-meet.ts— wired, darkzoom.ts— wired, darkteams.ts— wired, darktoken-cache.ts— OAuth token cacheindex.ts— registryREADME.md— provisioning referenceRUNBOOK.md— LiveKit 6-gate provisioningREADME.md— feature purpose, routes, statusISSUE.md— open backlog and follow-upsCLAUDE.md— block-level contextالمسارات تعكس هذه الكتلة (انظر المسارات). نماذج Prisma الستة تقع في
prisma/models/conference.prisma. الاختبارات تقع تحت src/tests/ (إعادة تنظيم
بمحاكاة المسارات)، لا في مجلّد __tests__/ داخل الكتلة.
المكوّنات
تتعايش طبقتان للصلاحيات والتحقّق عن قصد: طبقة الجلسات الغنية (authorization.ts · permissions.ts
· validation.ts) هي البوابة الصارمة وقت التشغيل لتدفّق غرفة LiveKit (الانضمام / الرمز / البدء /
الإنهاء)، بينما طبقة القائمة (list-*.ts) هي بوابة الـ CRUD لجدول لوحة التحكّم وجلسات الروابط
الخارجية.
| الملف | الغرض |
|---|---|
content.tsx | مكوّن خادمي — يجلب القائمة وخيارات النموذج في مسار متوازٍ واحد، ترجمة مُجمّعة، ثم يعرض الجدول. |
actions.ts + actions/ | إجراءات خادمية: sessions (دورة الحياة)، tokens (الانضمام)، recordings، notifications، settings، moderation (الطرد)، recurring، helpers. |
list-actions.ts · queries.ts | CRUD لطبقة القائمة (create/update/delete/list) + بُناة استعلامات للقراءة فقط. |
authorization.ts | PERMISSION_MATRIX — 10 أفعال إجراء → UserRole[] المسموح؛ حُرّاس assert/check. |
permissions.ts · list-permissions.ts | التحكّم في الواجهة (التبويبات + الإظهار/الإخفاء حسب الدور) للطبقتين الغنية والقائمة. |
validation.ts · list-validation.ts | مخطّطات Zod والأنواع المستنتجة (جلسات غنية / نماذج الجدول). |
columns.tsx · table.tsx · form.tsx · schedule-form.tsx · list-params.ts | واجهة DataTable والشبكة على العميل ونموذجا الإنشاء (المنبثق + صفحة مستقلّة). |
landing/ · nav.tsx | صفحة الوصول لـ /conference (الواجهة الرئيسة · بطاقات القيمة · شريط المباشر/القادم · الأقسام الطويلة) والعنوان وشريط التبويبات فوق أسطح (app). |
detail.tsx · room.tsx · participants-panel.tsx | تفاصيل الجلسة · غرفة LiveKit داخل التطبيق · طبقة إشراف المضيف/المضيف المساعد (الطرد). |
recordings.tsx · recording-player.tsx · section-recording-policy.tsx | قائمة التسجيلات · مشغّل برابط موقّع كسول · إلغاء التسجيل لكل شعبة. |
settings-form.tsx | نموذج سياسة المدرسة للمدير (الاحتفاظ / الحد الأقصى للتزامن / المدة القصوى / التسجيل الافتراضي) + ترحيل الفصول. |
network-test.tsx · network-protocol.ts | تشخيص اتصال LiveKit + مُصنِّف مسار ICE نقي (getLiveKitReadiness() / classifyFromStats). |
types.ts · error-map.ts | أنواع النطاق و RoomJoinTicket · ربط رموز الأخطاء بالنصوص المترجمة. |
livekit/ | أغلفة SDK لخادم SFU: client (الإعداد والجاهزية)، token، rooms، egress، recording-urls، room-naming، webhook. |
providers/ | محوّلات مزوّدي الروابط: types، external (فعّال)، google-meet/zoom/teams (خاملة)، token-cache، سجلّ index. |
تدفّق البيانات
landing (/conference) landing/content.tsx (server) → hero · cards · live/upcoming strip · bands
└─ عرض الجلسات
index (/conference/dashboard) content.tsx (server) → table.tsx (client) → form.tsx (modal create)
└─ row Join / detail
detail (/conference/[id]) detail.tsx → Join (live/scheduled) | Recordings (ended)
└─ Join
room (/conference/[id]/room) joinLiveClass → eligibility → 5-min JWT → room.tsx (LiveKit prebuilt)
↑ refreshLiveClassToken every ~60s re-runs eligibility
recordings (/[id]/recordings) recordings.tsx → recording-player.tsx (signed S3 URL, 4h; re-mints on expiry via onError)
- آلة حالة الجلسة (
actions/sessions.ts):scheduled → live → ended، إضافةً إلىcancelledوfailed. يجهّزstartLiveClassغرفة الـ SFU (يتحقّق منconferenceMaxConcurrent)؛ وendLiveClassعديم التأثير عند التكرار. الـ Webhook هو الكاتب الموثوق لحالتيlive(room_started) وended(room_finished). - الأهلية تُحَلّ بكسل عند كل انضمام (
actions/tokens.ts): المعلّم→مضيف (لجلسته) / مضيف مساعد، الطالب→مشارك (مطابقة الشعبة)، وليّ الأمر→مراقب (ابنه في الشعبة)، المدير→مضيف مساعد، المطوّر→مضيف. المستخدمون المطرودون (status="removed") يُحجبون عند تحديث الرمز التالي. - التسجيلات (
pending → processing → ready → expired): LiveKit Egress → S3me-central-1؛ الاحتفاظ لكل مدرسة تفرضه المهمة المجدولة اليومية. - الإشعارات (
actions/notifications.ts): جميع أحداثlive_class_*الخمسة تحدّد جمهورها (المعلّم + طلاب الشعبة + أولياء الأمور) وتُرسَل عبر الموزّع المشترك (dispatchNotificationsToAudience)، فتصل إلى جرس التطبيق والبريد الإلكتروني، وتحترم تفضيلات القنوات لكل مستخدم، وتحمل رابطاً مطلقاً. يُرسِل كلا المسارين: غرفة التطبيق (sessions.ts) ومسار الرابط الخارجي (list-actions.tsإنشاء / تعديل / إلغاء). - الحضور (
actions/attendance-sync.ts، اختياري عبرSchool.conferenceAttendanceSync): عند انتهاء الجلسة (Webhookroom_finishedأو مهمة الجلسات المعلّقة)، يسجّلsyncConferenceAttendanceكل طالب في الشعبة حاضراً/متأخراً حسب وجوده في الغرفة، وغائباً لمن لم ينضمّ، بكتابة سجلاتAttendanceبالطريقةVIRTUAL. يتطلّبsectionId+timetableId؛ LiveKit فقط — الروابط الخارجية لا تحمل بيانات حضور. عديم التأثير عند التكرار (مفتاح الشعبة الفريد، إحياء عند التحديث).
المسارات
| المسار | التخطيط | يعرض | الأدوار |
|---|---|---|---|
/conference | school-dashboard | landing/content.tsx — صفحة الوصول | كل أدوار المدرسة السبعة |
/conference/dashboard | school-dashboard (app) | فهرس DataTable | كل أدوار المدرسة السبعة |
/conference/schedule | school-dashboard (app) | schedule-form.tsx | المطوّر · المدير · المعلّم |
/conference/settings | school-dashboard (app) | settings-form.tsx + section-recording-policy.tsx | المطوّر · المدير |
/conference/network-test | school-dashboard (app) | network-test.tsx | المطوّر · المدير (محكوم بالبيئة) |
/conference/[id] | school-dashboard | detail.tsx | كل أدوار المدرسة السبعة |
/conference/[id]/recordings | school-dashboard | recordings.tsx | الكل عدا المحاسب (أكاديمي لا مالي) |
/conference/[id]/room | (live-room) | room.tsx | أي مشارك جلسة مُصادَق |
/live-classes/* | — | إعادة توجيه → /conference | حشوة توافق قديمة (روابط ما قبل إعادة التسمية) |
(app) مجموعة مسارات: لا تضيف مقطعاً إلى الرابط، بل تضيف العنوان وشريط التبويبات المشترك فقط
(الجلسات · الجدولة · الإعدادات · فحص الشبكة، بحسب الدور). يقع /conference و/conference/[id]
خارجها عمداً — صفحة الوصول تملك واجهتها الرئيسة، وتفاصيل الجلسة ورقة يُوصل إليها من صفٍّ لا من تبويب.
تقع غرفة المسار تحت مجموعة (live-room) منفصلة بتخطيط عارٍ بملء الشاشة (بلا شريط جانبي / زخارف
لوحة التحكّم) كي يمتلك بثّ الـ SFU الشاشة؛ وهي force-dynamic لبناءات آمنة من نفاد الذاكرة. واجهات
API (مُستدعاة خارجياً / عبر المجدول / بالاستطلاع → تبقى مسارات، لا إجراءات خادمية): api/webhooks/livekit (HMAC)،
api/conference/token (تجديد رمز الغرفة كل ~4 دقائق — مسار GET لأن استطلاعاً عبر إجراء خادمي يشحن
إعادة تصيير RSC كاملة مع كل استجابة)، api/cron/live-class-reminders (كل 15 دقيقة، بنافذة 5–20 دقيقة
غير مكرِّرة)، api/cron/end-stale-live-classes (كل 30 دقيقة — يُنهي الجلسات العالقة في حالة live
بعد وقتها ويشغّل مزامنة الحضور)، api/cron/expire-live-recordings (يومياً).
المخطّط — prisma/models/conference.prisma
LiveClass* → Conference*؛ مع الحفاظ على كل اسم جدول / عمود / نوع تعداد عبر @@map/@map →
ترحيل بلا تغيير (فرق migrate diff مطابق تماماً للأساس).
| النموذج | الغرض |
|---|---|
Conference | جلسة. provider (livekit|external)، meetingUrl?، visibility (section|school)، catalogLessonId?. |
ConferenceParticipant | صفّ لكل مستخدم مدعوّ + قياسات الاتصال (RTT، علامة الرجوع لـ TCP). |
ConferenceRecording | بيانات الـ Egress وموقع S3 + expiresAt للاحتفاظ. |
ConferenceEvent | سجلّ تدقيق الـ Webhook + eventId @unique لضمان عدم التكرار. |
ConferenceLink | رابط متكرّر يُضبط مرة واحدة [schoolId, subjectId, sectionId, termId]. |
ConferenceResource | مرجع مرفق — واحد فقط من schoolExamId / schoolAssignmentId / رابط http(s) في كل صف. |
التعدادات ConferenceProvider · ConferenceStatus · ConferenceRecordingStatus ·
ConferenceParticipantRole · ConferenceParticipantStatus · ConferenceVisibility؛ و5 أعمدة إعداد
School.conference* (conferenceRetentionDays 90 · conferenceMaxConcurrent 50 ·
conferenceMaxDuration 120 · conferenceRecordingDefault true · conferenceAttendanceSync false).
التحكّم بالوصول — خاص أم على مستوى المدرسة
visibility يحدّد من يرى الجلسة وينضم إليها، فوق حدود المستأجر المطلقة:
section(الافتراضي) — طلاب شعبة الجلسة ينضمون كـ PARTICIPANT وأولياء أمورهم كـ OBSERVER؛ الجلسة بلا شعبة تكون للمضيف فقط (اجتماع خاص).school— كل عضو في المدرسة: الطلاب PARTICIPANT، أولياء الأمور OBSERVER، الموظفون والمحاسب PARTICIPANT. للطوابير والندوات واللقاءات العامة. جدولتها تُشعر المدرسة كاملة عبر مركز الإشعارات.
لا يوجد عمداً أي مستوى عابر للمدارس أو مجهول — الأهلية وتقييد القوائم وقراءة التفاصيل والوصول
للتسجيلات كلها تفرض القاعدة نفسها (resolveParticipantRole، canAccessSession،
buildLiveClassWhere). روابط الاجتماع والمراجع مقفولة على http(s) — تُرفض javascript:/data:
عند التحقق.
المراجع — درس، كويز، واجب، روابط
مفتاح catalogLessonId واحد يربط الجلسة بدرس من المنهج؛ فتعرض صفحة الجلسة تلقائياً فيديوهات
الدرس ومرفقاته ومواده وعدد أسئلة التدريب. الاختبارات والكويزات (SchoolExam — الكويز هو
examType: QUIZ وليس نموذجاً منفصلاً) والواجبات والروابط الحرة تُرفق كصفوف ConferenceResource —
تُتحقق ملكيتها للمدرسة قبل الكتابة وتُستبدل كمجموعة عند التعديل. خطوة «المراجع» في المعالج تحمّل
خياراتها حسب المادة المختارة (getLiveClassReferenceOptions، لطاقم الإدارة فقط).
معالج الإنشاء
نموذج اللوحة معالج مضغوط من 5 خطوات (form.tsx + form-steps.tsx) على نمط النوافذ متعددة الخطوات
المعتمد — الأساسيات ← الجدولة ← الاجتماع ← المراجع ← الوصول، بضعة حقول في كل خطوة.
تبدأ خطوة «الأساسيات» بمنتقي الحصة: حصص الجدول الدراسي الحقيقية للفصل الدراسي النشط
(«الرياضيات · الصف 1-أ · الأحد 08:00»). هذا ما يجعل الجلسة تقديماً إلكترونياً لحصة واقعية لا
اجتماعاً منفصلاً — فاختيار حصة يملأ المعلّم والمادة والشعبة ونافذة وقت الحصة وأقرب تاريخ يقع فيه
ذلك اليوم، ويخزّن timetableId. هذا الارتباط جوهري: به تستطيع الجلسة رصد الحضور ممّن ينضم فعلاً
(تحتاج المزامنة إلى كشف الشعبة و الحصة)، وبه تظهر الحصة كنقطة «مباشر/مجدول» على شبكة الجدول
الأسبوعي. الخادم يعيد اشتقاق المعلّم والمادة والشعبة من صف الحصة، فالجدول — لا النموذج — هو مصدر
الحقيقة. اترك المنتقي على «غير مرتبطة بحصة» للطوابير الصباحية ولقاءات أولياء الأمور والدروس
المنفردة. ولا يمكن تغيير الارتباط لاحقاً (فإعادة الربط تُعيد ترقيم حضور مكتوب سلفاً).
ويتبع منتقيا المادة والدرس الكتالوج، مضبوطاً على صف الشعبة: المواد هي ما اعتمدته المدرسة لذلك الصف (بالاسم الذي تستخدمه المدرسة لها)، ودروس الكتالوج تأتي من فصول ذلك الصف. وتُنشأ كل جلسة بحالة «مجدولة»؛ أما البدء والإنهاء فخطوتان منفصلتان محروستان.
خطوة «الاجتماع» تختار الخلفية: غرفة داخل المنصة (LiveKit — الافتراضي متى كان isLiveKitConfigured()،
مع مفاتيح التسجيل والحد الأقصى للمشاركين) أو رابط خارجي (رابط Meet/Zoom/Teams + خيار الرابط
الأسبوعي المتكرر). لا يتغيّر المزوّد بعد الإنشاء؛ وزر الانضمام واعٍ بالمزوّد في كل مكان (قائمة
الجدول، صفحة التفاصيل، ومسار الغرفة يحوّل الجلسات الخارجية إلى رابط المزوّد).
التدريس عبر الإنترنت
أول إعداد هو كيف تُدرِّس المدرسة — حضوري أو عبر الإنترنت أو مختلط — من المدرسة ← الإعدادات ← الحصص المباشرة (وهو نفسه تبويب الإعدادات داخل الحصص المباشرة). حضوري يعني لا غرف مباشرة ولا أزرار انضمام، مهما قالت أيّ شعبة. عبر الإنترنت يعني أنّ كل حصة في الجدول حصة مباشرة. المختلط هو حيث تعيش الخيارات التالية: افتراضي للشُّعب، وتجاوزات لكل شعبة، ونافذة مؤقتة عبر الإنترنت. التحويل إلى حضوري يسري على الحصص الجديدة؛ وحصص اليوم المجدولة مسبقًا تبقى وتُغلق تلقائيًا.
إلى جانب النمط، تحوي اللوحة نفسها: منصّة الاجتماع والرابط الدائم؛ كيف تعمل الحصص عبر الإنترنت (الجدول، غرفة مفتوحة، كلاهما)؛ التسجيل ومدّة الاحتفاظ والإشعار الذي يراه الطلاب عند دخول حصة مسجَّلة ونشر التسجيلات إلى الدرس تلقائيًا؛ هل يشاهد أولياء الأمور؛ هل يدخل الطلاب بصمت (ويمكن للحصة تجاوز ذلك)؛ أدوات الغرفة المفعّلة — الدردشة ورفع اليد والاستطلاعات والسبورة ومشاركة شاشة الطلاب؛ كم دقيقة قبل الحصة يُرسَل التذكير؛ حدود الحضور؛ تجاوزات التدريس عبر الإنترنت لكل صف ولكل شعبة؛ وبيانًا للقراءة فقط بسياسة حماية المحتوى مع مساحة التسجيلات المستخدَمة.
المدرسة التي تقدّم حصصها عبر الإنترنت لا تجدول كل حصة على حدة، بل تعلن الأمر مرة واحدة فتتحول كل حصة في الجدول الدراسي إلى حصة مباشرة.
التحوّل إلى الإنترنت لا يُغلق المدرسة. فالتقديم عبر الإنترنت إضافة — قناة مباشرة تُفتح إلى جانب الحصة الحضورية — ومن ثم يمكن للمدرسة أن تكون مفتوحة وعلى الإنترنت في اليوم نفسه، أو على الإنترنت يوم الأحد وحضوريًا يوم الإثنين، أو على الإنترنت لشعبة دون أخرى. ولا شيء هنا يُلغي حصة.
هناك ثلاثة مفاتيح تجعل الحصة على الإنترنت، وهي تتكامل ولا تتنافس:
| المفتاح | الموضع | المعنى |
|---|---|---|
| التدريس عبر الإنترنت | /conference/settings | الإعداد الدائم: «نحن مدرسة على الإنترنت». |
| التدريس عبر الإنترنت حسب الشعبة | الصفحة نفسها، لكل شعبة | يتجاوز إعداد المدرسة في الاتجاهين. و«إعداد المدرسة» يعني الاتّباع. |
| التحوّل إلى الأونلاين مؤقتاً | الصفحة نفسها، بمدى زمني | مفتاح الطوارئ — أدناه. يرفع إعداد المدرسة لتلك الأيام. |
وقرار الشعبة يتقدّم دائمًا على الإعدادين العامّين، حتى أثناء الطوارئ: فالفترة المؤقتة ترفع الإعداد الافتراضي للمدرسة ولا تُلغي قرارًا اتُّخذ بشأن شعبة بعينها. والشعبة المستثناة التي تريد إعادتها إلى الإنترنت تحتاج نقرة واحدة — أعِدها إلى «إعداد المدرسة».
التحوّل إلى الإنترنت في وقت قصير
الطقس، طريق مغلق، وباء، حرب. من /conference/settings → التحوّل إلى
الأونلاين مؤقتاً:
- من — أول يوم. مطلوب.
- إلى — آخر يوم، شامل. اتركه فارغًا للبقاء على الإنترنت حتى إشعار آخر، وهو ما تعرفه عادةً في اليوم الأول.
- السبب — نص حر يظهر للموظفين في هذه الصفحة.
يُقرأ التاريخان بتوقيت المدرسة نفسها ويغطيان أيامًا دراسية كاملة. ومسح من يُنهي الفترة — فلا يوجد زر «إلغاء» منفصل يمكن نسيانه. وعند انقضاء تاريخ النهاية تعود المدرسة إلى ما كانت عليه دون أي خطوة يدوية.
ويسري الحفظ فورًا: تُنشأ حصص اليوم وأنت ما زلت في الصفحة، لا عند ربع الساعة التالي.
مُقيَّد بالجدول أم بتوقيت حر
كيف تُقدَّم الحصص أونلاين خيار منفصل عن كونها تُقدَّم أصلًا:
- حسب الجدول الدراسي — حصة مباشرة لكل حصة في الجدول، تبدأ وتنتهي مع الحصة. وهي الصيغة المُلزِمة، وهي التي تغذّي حضور الحصص.
- غرفة مفتوحة في أي وقت — غرفة ثابتة واحدة لكل شعبة طوال اليوم الدراسي، بلا التزام بأوقات الحصص. فالمدرسة التي تحوّلت إلى الإنترنت بسبب عاصفة نادرًا ما تُعيد إنتاج جرس حصصها في اليوم الأول، والمدرسة التي هي على الإنترنت بالأساس قد لا تريد جرسًا إطلاقًا. يستضيفها مربّي الشعبة. ولأنها غير مرتبطة بحصة فهي لا تسجّل حضور الحصص، ولا تظهر في بطاقات الجدول كذلك — يُوصل إليها من الاجتماعات.
- الجدول مع غرفة مفتوحة — كلاهما، فتتبع الشعبة جدولها ويبقى لها مكان تلتقي فيه بين الحصص.
الإعداد
-
اختر المنصة. من
/conference/settings(مدير المدرسة أو المطوّر) فعّل التدريس عبر الإنترنت ثم اختر منصة الاجتماع:- رابط خارجي — روابط Google Meet أو Zoom أو Teams. يعمل اليوم دون أي بنية تحتية.
- غرفة داخل التطبيق — LiveKit. يمكن اختيارها فورًا، لكنها لا تصبح فاعلة
إلا بعد تجهيز خادم البث (البوابات الست في
conference/RUNBOOK.md). حتى ذلك الحين تعمل الحصص عبر الروابط الخارجية ثم تنتقل تلقائيًا يوم تجهيز البنية التحتية — دون ترحيل بيانات ودون إعادة إعداد.
-
للروابط الخارجية: حدّد رابطًا لكل حصة. أنشئ جلسة من المعالج مع تفعيل الحفظ كافتراضي، فيُخزَّن رابط متكرر لهذه المادة والشعبة والفصل الدراسي. من هنا تأخذ الحصة المُنشأة تلقائيًا رابط اجتماعها — والحصة بلا رابط تُتخطّى بدلًا من نشر زر انضمام معطّل. وعند بداية فصل دراسي جديد يَنقل زر ترحيل الروابط المتكررة في الصفحة نفسها هذه الروابط إلى الفصل التالي.
ويخبرك قسم تغطية روابط الاجتماعات في الصفحة نفسها بما أنجزته: كم زوجًا من (شعبة، مادة) في الفصل النشط له رابط خاص، وأيّها بلا رابط. راجعه قبل أن تحتاجه.
-
حدّد رابطًا ثابتًا كحدّ أدنى. المدرسة التي تتحوّل إلى الإنترنت في وقت قصير لن يكون لديها رابط جاهز لكل حصة. ويُستخدم رابط اجتماع ثابت لأي حصة بلا رابط خاص بها، فيُنتج التحوّل حصصًا قابلة للانضمام بدل ألا يُنتج شيئًا. وهو غرفة واحدة مشتركة — كل حصة تعود إليه تصل إلى الاجتماع نفسه — فتعامَل معه كحدّ أدنى للطوارئ وأضِف روابط لكل حصة تباعًا. أما الغرفة المفتوحة فلا مصدر آخر لرابطها، لأنها لا تنتمي إلى مادة.
-
اضبط الشعب إن كان التعليم مُدمجًا. تتجاوز قائمة التدريس عبر الإنترنت حسب الشعبة إعداد المدرسة في الاتجاهين: استثناء شعبة من مدرسة تحوّلت إلى الإنترنت، أو تحويل شعبة واحدة في مدرسة لم تتحوّل. ويعني «إعداد المدرسة» اتّباع الإعداد العام.
ما الذي يحدث بعد ذلك
كل خمس عشرة دقيقة تُنشئ مهمة التذكيرات حصص اليوم الدراسي الحالي — صفًا واحدًا لكل حصة في اليوم، بتوقيت المدرسة نفسها. وهي تطابق مرشِّح الجدول الدراسي نفسه (الفصل النشط، ذلك اليوم من الأسبوع، بلا فترات الفسحة، والحصص التي لها شعبة ومعلّم)، فلا يمكن أن تخترع حصة لا يعرضها الجدول. وتكرار التشغيل لا يُنشئ شيئًا جديدًا، والحصة التي ألغاها المدير لا تعود.
ولا يُنشأ شيء في يوم أعلنت المدرسة إغلاقه — فوجود استثناء «عطلة» أو «ملغى» في جدول الاستثناءات يوقف العملية لذلك اليوم، فلا يُذكَّر أحد بحصة في العيد. أما الجدول نفسه فيظل ظاهرًا مع تنبيه «المدرسة مغلقة اليوم» فوقه: فاستثناءات التقويم تُدخَل يدويًا، ولا ينبغي لإدخال خاطئ أن يُخفي جدولًا كاملًا دون تفسير. وتُتخطّى كذلك الحصص التي انتهى وقتها، فلا يؤدي تفعيل المفتاح وقت الغداء إلى نشر أزرار انضمام لحصص الصباح.
ولأن الصف موجود فعلًا قبل أن ينقر أحد، يستيقظ بقية النظام: يصل الطلاب إشعار بدء الحصة، وتظهر النقطة على الجدول الأسبوعي، وتُوسم الحصة بـأونلاين في كل بطاقة في الجدول — إلى جانب اسم القاعة لا بدلًا منه، فالقاعة تبقى مكان اللقاء لمن يستطيع الحضور — ويجد المعلّم زر البدء في بطاقة الحصة الحالية، ومع غرف التطبيق وتفعيل مزامنة الحضور يُسجَّل الحضور من الحضور الفعلي في الغرفة عند انتهاء الحصة. أما الحصص التي لم يبدأها أحد فتُغلق بحالة «ملغاة» بدل أن تبقى معلّقة.
وتذكر صفحة كل جلسة كيف يُعالَج الحضور فيها — أيُرصد تلقائيًا عند انتهائها أم يُرصد كالمعتاد، مع رابط إلى صفحة الحضور. فالاجتماعات الخارجية لا تنقل أي بيانات حضور، وهي الحالة الشائعة لمدرسة تحوّلت للتوّ إلى الإنترنت، فيُذكر ذلك صراحةً بدل أن يُكتشف من سجلّ فارغ.
ما الذي تقدّمه كل منصة
| رابط خارجي | غرفة داخل التطبيق | |
|---|---|---|
| يعمل اليوم | ✅ | ⛔ ست بوابات بنية تحتية |
| الانضمام من الجدول الدراسي | ✅ | ✅ |
| التذكيرات والإشعارات | ✅ | ✅ |
| مؤشر الحصة على الجدول الأسبوعي | ✅ | ✅ |
| التسجيل | ⛔ من جهة المزوّد فقط | ✅ إلى S3 مع مدة حفظ لكل مدرسة |
| الحضور من التواجد | ⛔ لا توجد بيانات تواجد | ✅ اختياري |
| إدارة المشاركين (الطرد) | ⛔ | ✅ |
كيف ينضم الطالب
خمسة مداخل تصل إلى الحصة المباشرة، وتختلف في صرامة البوابة الزمنية:
| المدخل | البوابة |
|---|---|
| صفوف اليوم في الجدول (طالب · معلّم · وليّ أمر) | جارية الآن، أو تبدأ خلال ١٠ دقائق (isRowLiveJoinable) |
| بطاقة الحالية/التالية في الجدول | الحصة الحالية، أو التالية خلال ١٠ دقائق (isLiveJoinable) |
شريط "الآن" في صفحة /conference | المباشر الآن والقادم، مقيّد بالشعبة |
جدول /conference/dashboard | أي صف live أو scheduled يراه المستخدم |
| بوابة الوالدين ← الطفل ← الجدول | شريط "اليوم" أعلى الجدول الأسبوعي |
| الإشعار | started يفتح الغرفة مباشرة؛ والبقية تفتح تفاصيل الجلسة |
| الجوال | حقل live_class في /api/mobile/timetable/:userId، والانضمام عبر GET /api/mobile/conference/:id/join |
الشبكة الأسبوعية في الجدول تعرض نقطة مباشرة فقط — إشارة وعي، لا زر انضمام.
جلسة LiveKit تفتح الغرفة داخل التطبيق على /conference/[id]/room، والجلسة الخارجية تفتح رابط
المزوّد مباشرة. مسار الغرفة يحوّل الجلسات الخارجية بعد التحقق من التسجيل في الشعبة، فيعمل كلا
المزوّدين من كل المداخل أعلاه.
إشعار started وحده يفتح الغرفة مباشرة. إشعار startingSoon يصل قبل ٥–٢٠ دقيقة من الحصة،
ومن ليس مضيفاً يُرفض انضمامه لجلسة ما زالت scheduled — فرابط غرفة مبكر يوصل الطالب إلى خطأ لا
إلى غرفة انتظار.
داخل الغرفة
الغرفة من صنع التطبيق نفسه — كل زرّ يأخذ نصّه من القاموس، والتخطيط يتبع اتجاه الصفحة. ما تستطيع الحصة فعله فيها:
- المعلّم: الميكروفون، الكاميرا، مشاركة الشاشة، السبورة، الشرائح (أي ملف PDF مرفق بالدرس، مع مزامنة الصفحة للجميع)، الاستطلاعات، قائمة الأسئلة مع «تعليم كمُجاب»، طابور الأيدي المرفوعة، والإشراف (إزالة مشارك).
- الطالب: الميكروفون والكاميرا (مغلقان عند الدخول)، رفع اليد، طرح سؤال، التصويت، الدردشة. وليّ الأمر يشاهد فقط.
- جودة البثّ تتكيّف مع اتصال الطالب: ضعفٌ مستمرّ يُنزل فيديو المعلّم درجةً (720 ← 360 ← 180) ثم إلى الصوت والشرائح — ويظهر شريط «تم تشغيل وضع الصوت والشرائح» — واتصالٌ جيّد مستمرّ يرفعه درجةً. الاختيار اليدوي من قائمة «الجودة» يتجاوز السلّم.
- إعادة الاتصال تُظهر طبقة «جارٍ إعادة الاتصال…»؛ وانقطاع الاتصال يعرض «إعادة الانضمام» بدل الإخراج. الإزالة من قِبل المعلّم، وانتهاء الحصة، وفتح الحصة على جهاز آخر، كلٌّ منها يقول ذلك بوضوح.
- الحضور يُسجَّل من وقت الوجود في الغرفة عبر إعادات الاتصال — لا شيء يُنقر.
- الاستطلاعات المنتهية وكلّ الأسئلة تُحفظ على الحصة كأحداث.
بعد الحصة
عند تفعيل التسجيل، يُنشر التسجيل المكتمل فيديوًا للدرس في لوموس (ظاهر للمدرسة، قابل للتنزيل)، وتتنقّل صفحة الحصة بين «جاري تجهيز تسجيل الحصة…» ← «التسجيل متاح للمشاهدة» — أو تقول بصراحة إن التسجيل فشل.
حماية الفيديوهات والمواد
التسجيلات وفيديوهات الدروس والمواد تُشاهَد داخل التطبيق ولا تُنزَّل أبدًا: لا زرّ
تنزيل، ولا صورة داخل صورة أو بثّ إلى شاشة أخرى، والمواد تُفتح في عارض داخل
التطبيق لا كملفات. كلّ واجهة من هذه تحمل علامة مائية باهتة متحرّكة بهويّة
المشاهد، فتُنسب أيّ لقطة شاشة أو تسجيل بالهاتف إلى صاحبها. أمّا عمل الطالب
نفسه — موضع المشاهدة وإجابات الاختبار وتسليم الواجب — فما يزال يصطفّ دون
اتصال ويُزامَن عند عودته؛ وصفحة /offline تعرض ما ينتظر.
التشغيل
- الخادم (SFU): أربعة متغيرات بيئة (
LIVEKIT_HOSTوLIVEKIT_WS_URLوLIVEKIT_API_KEYوLIVEKIT_API_SECRET). التسجيل بوابة منفصلة (LIVEKIT_RECORDING_BUCKETمع بيانات اعتماد S3) ولم يعد يمنع عقد المكالمة. صفحة/conference/network-testتُبلّغ عن الاثنين. - الخادم المهيّأ لا يفعل شيئاً وحده — تبقى قيمة
School.conferenceProviderDefaultهيexternalحتى تختار المدرسة غير ذلك من/conference/settings. - مهمة توليد الجلسات تعمل على GitHub Actions لا على Vercel Cron ما دام المشروع على الخطة
المجانية (
.github/workflows/conference-crons.yml). بدونها لا تولّد المدرسة المتصلة أي جلسة بعد اليوم الذي حفظت فيه إعداداتها — فلا شيء للانضمام إليه ولا تذكيرات.
العرض التجريبي والمدرسة الهجينة
الأمر pnpm db:seed:single conference يحوّل المدرسة التجريبية إلى مدرسة هجينة عاملة: متصلة
افتراضياً عبر LiveKit، شعبتان حضورياً، خمسة أيام من السجل بحضور حقيقي وغياب افتراضي
(VIRTUAL)، جلسات اليوم الدراسي التالي التي ستجدها المهمة الدورية موجودة (exists)، بديل يستضيف
إحداها، حصة مرتبطة بدرس واختبار وواجب ورابط، ثلاثة روابط خارجية متكررة، وعطلة معلنة. يُصلح أولاً —
فترات الفسحة، خبرات المعلمين، ومعلماً لكل حصة بلا معلم — لأن الحصة بلا معلم بلا مضيف ولا تتصل أبداً.
معنى «الهجين» في المنتج: الاتصال إضافة لا بديل. تفاصيل الجلسة تُظهر القاعة الفعلية التي ما زالت الحصة تنعقد فيها؛ بطاقة الجدول تعرض القاعة وبجانبها علامة «مباشر»؛ البديل المؤكَّد يستضيف الجانب المتصل من الحصة التي يغطيها؛ درس lumos يعرض جلسته المباشرة؛ والصفحات الرئيسية تحمل زر الانضمام. والتسجيل صادق — كل مفتاح يقول ذلك حين لا توجد حاوية تخزين.
المزوّدون
| المزوّد | الحالة | الناقص |
|---|---|---|
| الرابط الخارجي | ✅ فعّال | — (الافتراضي العام بلا بنية تحتية) |
| LiveKit SFU | 🟡 مكتوب، خامل | 6 بوابات بنية تحتية → conference/RUNBOOK.md |
| Google Meet | 🟡 createMeeting موصول، خامل | بيانات اعتماد OAuth من Google Cloud (GOOGLE_MEET_*) |
| Zoom | 🟡 createMeeting موصول، خامل | بيانات اعتماد Server-to-Server OAuth (ZOOM_*) |
| Microsoft Teams | 🟡 createMeeting موصول، خامل | بيانات اعتماد Azure AD (AZURE_*، Graph OnlineMeetings) |
ينشئ كل محوّل أصلي اجتماعاً حقيقياً عبر واجهة المزوّد (createMeeting) محكوماً بـ isConfigured()؛
أما getRecording/getAttendance فما زالا يطلقان ProviderNotImplementedError. تُحفظ المزوّدات
الأصلية كـ provider="external" + meetingProvider="<id>" → دون ترحيل تعداد.
القرارات
- كتلة واحدة، تجريد واحد للمزوّد. الرابط الخارجي هو الحدّ الأدنى المتاح لكل مدرسة؛ وLiveKit هو
السقف المتميّز الاختياري، محكوم بـ
isLiveKitConfigured(). تبقى دورة حياة SFU خارج طبقةproviders/(لأنها قائمة على الغرف لا الروابط). - إعادة التسمية شفّافة لقاعدة البيانات.
@@map/@mapتحفظ كل اسم جدول / عمود / نوع تعداد. - الـ Webhook والمهام المجدولة تبقى مسارات API — مُستدعاة خارجياً / عبر المجدول؛ لا يستطيع الإجراء الخادمي استقبالها.
- مدة الرمز = 5 دقائق، مُحدَّث من العميل. رموز LiveKit عديمة الحالة، لذا يسري إلغاء الوصول عند حدّ
التحديث التالي (≤5 دقائق) لا فوراً؛ ويُفرَض الطرد عبر بوابة
status="removed"عند الانضمام. - قيد الانتظار (غير برمجي): تجهيز بنية LiveKit التحتية (الدليل جاهز)، وبيانات اعتماد OAuth الأصلية.
ذات صلة
- المعمارية · النمط · الجدول الدراسي —
Conference.timetableIdيربط أزرار البدء / الانضمام في الجدول · الإشعارات — 5 أنواعlive_class_*
On This Page
البنيةالمكوّناتتدفّق البياناتالمساراتالمخطّط —prisma/models/conference.prismaالتحكّم بالوصول — خاص أم على مستوى المدرسةالمراجع — درس، كويز، واجب، روابطمعالج الإنشاءالتدريس عبر الإنترنتالتحوّل إلى الإنترنت في وقت قصيرمُقيَّد بالجدول أم بتوقيت حرالإعدادما الذي يحدث بعد ذلكما الذي تقدّمه كل منصةكيف ينضم الطالبداخل الغرفةبعد الحصةحماية الفيديوهات والموادالتشغيلالعرض التجريبي والمدرسة الهجينةالمزوّدونالقراراتذات صلة