نقل البيانات - حزمة lolly-backup

كل ما يجمعه مستخدم Lolly يعيش على جهازه - بلا حساب، بلا سحابة. حزمة نقل البيانات هي وسيلة انتقال تلك القيمة: صدّرها من تثبيت واحد، واحمل الملف بأي وسيلة (USB، AirDrop، بريد إلكتروني لنفسك، مشاركة شبكية) واستورده في تثبيت آخر. الملف هو وسيلة النقل. يمكن أن تكون الوجهة غير متصلة أو متصلة بالإنترنت. لا فرق، لأن لا شيء يتواصل مع خادم على الإطلاق.

الزران اللذان ينقلان تثبيتا كاملا: تصدير بياناتي يكتب ملفا مضغوطا واحدا، واستيراد بيانات يقرأه من جديدsigned by Lollyvector SVGتحقق بنفسكGet the signed file12 paths~5.1k nodes12 groups60 KBالزران اللذان ينقلان تثبيتا كاملا: تصدير بياناتي يكتب ملفا مضغوطا واحدا، واستيراد بيانات يقرأه من جديدsigned by Lollyvector SVGتحقق بنفسكGet the signed file12 paths~5.1k nodes12 groups60 KB

هذه الصفحة هي مواصفة التنسيق. لدليل المستخدم النهائي راجع Using Lolly → الانتقال إلى جهاز آخر. التنفيذ موجود في shells/web/src/data-transfer.ts، ويثبّت tests/data-transfer.test.ts عقد الذهاب والإياب.

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

الأهداف

المغلّف

الحزمة ملف .zip عادي. يُسمّى التنزيل باسم الشخص الذي يخصّه - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip (على سبيل المثال LollyTools-Ada-Lovelace-2026-06-26-1.zip) - بحيث يبقى مجلد التنزيلات المليء بالنسخ الاحتياطية مقروءا. يأتي الاسم الأول والأخير من الملف الشخصي ويُحذفان عندما لا يكونان مضبوطين. عدم وجود ملف شخصي يعطي LollyTools-2026-06-26-1.zip، والاسم الأول وحده يعطي LollyTools-Ada-2026-06-26-1.zip. يُنقّى كل جزء إلى رمز آمن لاسم الملف (تُبقى حروف وأرقام Unicode، وتُحذف المسافات وعلامات الترقيم، بحد أقصى 32 حرفا). <n> تسلسل يومي لكل جهاز، بحيث لا تتصادم الصادرات المتكررة في اليوم نفسه وتبقى مرتبة. تبني backupFilename() في shells/web/src/data-transfer.ts الاسم. محتويات ملف zip متطابقة بصرف النظر عن الاسم. بداخله:

المسارمطلوبالمحتويات
manifest.jsonنعممعرّف التنسيق، والإصدارات، والأعداد، وسلامة كل جزء. أول شيء ينظر إليه القارئ.
profile.jsonعند ضبطهسجل me الخاص بالمستخدم (الاسم، جهة الاتصال، مرجع الصورة الشخصية، الأعلام). يُقرأ عبر host.profile.
sessions.jsonنعمكل جلسة محفوظة: الفتحة، معرّف/إصدار الأداة، التسمية، الصورة المصغّرة (data-URL) وكامل بيانات الإدخال. تُقرأ عبر host.state.
assets.jsonنعمبيانات وصفية لكل أصل مرفوع (صور، خطوط، رموز العلامة)، يشير كل منها إلى بايتاته تحت assets/blobs/.
assets/blobs/<n>.<ext>لكل أصلبايتات الأصل الخام (ملفات الصور والخطوط). تُخزَّن غير مضغوطة (تنسيقات مضغوطة أصلا). الامتداد تجميلي فقط. نوع MIME في assets.json هو المرجع الموثوق.
prefs.jsonنعمتفضيلات محلية يملكها المستخدم: theme، وsidebarWidth، وعدّاد نشاط ct-metrics.
lolly.txtنعمملخص مقروء للبشر عن الحزمة (الأعداد، الملف الشخصي، اسم الملف) لأي شخص يفتح ملف zip دون Lolly. يُعاد توليده مع كل تصدير ويُتعرَّف عليه عند الاستيراد، لذا لا يُحتسب أبدا كجزء متخطى. يُكتب بعد خريطة السلامة، لذا يبقى خارجها.

الحزمة ملف zip عادي عن قصد: تنجو سليمة من أي وسيلة نقل، ويمكن لأي أداة فك ضغط فحصها.

profile.json هو أصغر جزء والجزء الذي يراه القارئ أولا في التطبيق: التفاصيل التي يملؤها المُنشئ مرة واحدة، بالإضافة إلى الموافقة التي تسمح للأدوات باستخدامها.

نموذج تفاصيل Profile الذي يصبح profile.json - الاسم وجهة الاتصال والصورة الشخصية والموافقة بجانبهاsigned by Lollyvector SVGتحقق بنفسكGet the signed file18 paths~2.0k nodes41 groups30 KBنموذج تفاصيل Profile الذي يصبح profile.json - الاسم وجهة الاتصال والصورة الشخصية والموافقة بجانبهاsigned by Lollyvector SVGتحقق بنفسكGet the signed file18 paths~2.0k nodes41 groups30 KB

manifest.json

{
  "format": "lolly-backup",
  "formatVersion": 1,
  "minReader": 1,
  "app": "lolly",
  "exportedAt": "2026-06-22T09:30:00.000Z",
  "counts": { "profile": true, "sessions": 2, "userAssets": 4, "prefs": 3 },
  "integrity": {
    "profile.json": "sha256-…",
    "sessions.json": "sha256-…",
    "assets.json": "sha256-…",
    "assets/blobs/0.webp": "sha256-…",
    "prefs.json": "sha256-…"
  }
}
الحقلالمعنى
formatدائما lolly-backup. يُرفض الملف الذي يفتقده باعتباره "ليس نسخة احتياطية من Lolly".
formatVersionالتخطيط الذي كُتبت به هذه الحزمة. يُرفع عند أي تغيير في مجموعة الأجزاء أو أشكالها. لا يستند القارئ إليه في القرار.
minReaderالحد الأدنى لإصدار القارئ اللازم لاستيراد هذه الحزمة بأمان. هذا هو الحقل الذي يستند إليه القارئ في القرار.
appمعرّف التطبيق المُنتِج، للتشخيص.
exportedAtالطابع الزمني ISO الذي أُنشئت فيه الحزمة.
countsما وضعه الكاتب، للعرض والتحقق من السلامة.
integrityاختياري. يربط كل جزء عدا manifest.json بملخّص (digest) بنمط SRI ‏sha256-<base64> لبايتاته غير المضغوطة.

سياسة الإصدارات (التوافق المستقبلي)

الفصل بين formatVersion وminReader هو ما يسمح للتنسيق بالنمو دون التخلي عن التثبيتات الأقدم:

قاعدة عملية للمؤلفين: إذا كان كل قارئ موجود سيتصرف بشكل صحيح رغم تجاهل إضافتك، فهي إضافية - ارفع formatVersion واترك minReader. وإلا فارفع minReader.

السلامة

عندما يكون manifest.integrity موجودا، يتحقق القارئ من SHA-256 لكل جزء مُدرج قبل كتابة أي شيء. عدم التطابق ("failed its integrity check") أو غياب جزء ("incomplete") يُلغي الاستيراد بأكمله - لا استعادة جزئية. يلتقط هذا التلف الذي قد تُحدثه وسيلة نقل الملف (عملية AirDrop مبتورة، بوابة بريد إلكتروني أعادت ترميز المرفق، قطاع USB تالف).

السلامة مبذولة على أساس أفضل جهد بالتصميم: تُكتب فقط حيث تتوفر Web Crypto (كل سياق متصفح آمن وكل Node حديث)، وتُتحقَّق منها فقط عندما تتوفر الخريطة وWeb Crypto معا. تُستورَد الحزمة الخالية من الخريطة - مثلا حزمة من قبل وجود السلامة - دون تغيير. "لا يمكن التحقق" لا تُعامَل أبدا كـ"تالفة".

لا يُدرج البيان نفسه ولا ملف lolly.txt التمهيدي المُعاد توليده. تغطي الملخصات (digests) الأجزاء التي يكفلها البيان.

دلالات الاستيراد

الاستيراد هو دمج مع الاستبدال، وليس استبدالا كليا أبدا:

تُعيد الجلسات المحفوظة الربط بصورها تلقائيا: تُحفظ إشارات الأصول بالمعرّف، ويُعيد الجسر تحليلها بعد استعادة الصور المرفوعة (وهو مضطر لذلك على أي حال، لأن روابط blob: لا تنجو من إعادة التحميل).

يُبلّغ ملخص الاستيراد عن { profile, sessions, userAssets, prefs, skipped, failedAssets }. يحسب failedAssets الأصول المرفوعة التي تعذّرت استعادتها (امتلاء تخزين الجهاز، مثلا). وهو متمايز عن skipped، الذي يحسب الأجزاء من كاتب أحدث متوافق مستقبليا لم يتعرف عليها هذا الإصدار. تُظهر الواجهة skipped ("… · N newer items skipped")، بحيث تكون الاستعادة صادقة بشأن ما تركته وراءها.

ما لا ينتقل

يفصّل مقياس التخزين الانقسام نفسه. تنتقل الجلسات المحفوظة وMy images داخل الحزمة. أما ذاكرة الأصول المؤقتة ومعاينات الأدوات والتثبيتات غير المتصلة أسفلها فكلها قابلة لإعادة الاشتقاق، لذا تبقى خلفا.

مقياس التخزين يقسّم بيانات هذا الجهاز إلى فئات مسمّاة، مع تتبّع Saved sessions وMy images بشكل منفصل عن Asset cache، هنا في تثبيت جديد حيث لا تزال كل فئة فارغةsigned by Lollyvector SVGتحقق بنفسكGet the signed file25 paths~4.8k nodes46 groups59 KBمقياس التخزين يقسّم بيانات هذا الجهاز إلى فئات مسمّاة، مع تتبّع Saved sessions وMy images بشكل منفصل عن Asset cache، هنا في تثبيت جديد حيث لا تزال كل فئة فارغةsigned by Lollyvector SVGتحقق بنفسكGet the signed file25 paths~4.8k nodes46 groups59 KB

ضمان عبر القشرات

تقرأ وحدة data-transfer.ts وتكتب حصريا عبر جسر القدرات (host.profile، host.state، host.assets) وتفضيلات localStorage المشتركة. ولأن الجسر هو النقطة الوحيدة للاتصال، تنتج نفس الوحدة حزمة متطابقة بايت لبايت على كل غلاف رغم اختلاف التخزين تحتها - IndexedDB على الويب، ونظام الملفات على Tauri. تعيد أغلفة Tauri استخدام هذه الوحدة دون تغيير. يختلف فقط تنفيذها لـ host.state. يختبر الاختبار غير المرئي (headless) الدورة الكاملة ذهابا وإيابا مقابل جسر في الذاكرة، ولهذا فهو يمثل جميعها.

يقع غلافان خارج هذا الضمان، لأسباب مختلفة:

نقاط امتداد محجوزة

المغلف عبارة عن بيان إضافة إلى مجموعة من الأجزاء المسماة بالتصميم، بحيث يمكن لأنواع جديدة من البيانات القابلة للنقل أن تُحمل عليه لاحقا دون تغيير جذري (breaking change). تُدرج كأجزاء إضافية (formatVersion جديد، ونفس minReader)، ويتجاهل القارئ الحالي ما لا يتعرف عليه. هذه الأجزاء مدرجة في خارطة الطريق، ولم تُنفذ بعد. الأسماء محجوزة هنا حتى يبقى التنسيق متسقا عند إضافتها.

أي شيء خارج هذه الأسماء المحجوزة والأجزاء أعلاه يُعامله القارئ كجزء غير معروف: يُترك دون مساس ويُحسب ضمن skipped.

مرجع