Lolly
Deschide aplicația

Transfer de date - pachetul lolly-backup

Tot ce acumulează un utilizator Lolly rămâne pe dispozitivul lui - fără cont, fără cloud. Pachetul de transfer de date este modul în care această valoare se mută: îl exporți pe o instalare, transporți fișierul prin orice mijloc (USB, AirDrop, email către tine însuți, o partajare de rețea) și îl imporți pe alta. Fișierul este transportul. Ținta poate fi offline sau online. Nu contează, pentru că nimic nu comunică vreodată cu un server.

Cele două butoane care mută o instalare întreagă: Export my data scrie un zip, Import data îl citește înapoi signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBVerifică singurDescarcă fișierul semnatCele două butoane care mută o instalare întreagă: Export my data scrie un zip, Import data îl citește înapoi signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBVerifică singurDescarcă fișierul semnat

Această pagină este specificația formatului. Pentru ghidul pentru utilizatorul final, vezi Găsește și recuperează-ți lucrarea → Mută-ți lucrarea pe alt dispozitiv. Implementarea este shells/web/src/data-transfer.ts, iar tests/data-transfer.test.ts fixează contractul de round-trip.

Domeniu de aplicare. Un pachet poartă date de utilizator, nu instrumente din catalog. Instrumentele din catalog și activele din catalog sunt sincronizate separat și se presupune că sunt deja prezente pe țintă (în cel mai rău caz la o versiune mai nouă); instrumentele pe care un utilizator și le-a făcut singur călătoresc în interiorul profile.json. Importul nu instalează și nu actualizează niciodată un instrument din catalog.

Obiective

Plicul

Un pachet este un simplu .zip. Descărcarea este denumită după persoana căreia îi aparține - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip (de exemplu LollyTools-Ada-Lovelace-2026-06-26-1.zip) - astfel încât un folder Descărcări plin de backup-uri rămâne lizibil. Părțile de prenume și nume vin din profil și sunt omise când nu sunt setate. Fără profil rezultă LollyTools-2026-06-26-1.zip, iar doar un prenume dă LollyTools-Ada-2026-06-26-1.zip. Fiecare parte este curățată într-un token sigur pentru nume de fișier (literele/cifrele Unicode păstrate, spațiile/punctuația eliminate, plafonat la 32 de caractere). <n> este o secvență per-zi, per-dispozitiv, astfel încât exporturile repetate în aceeași zi nu se ciocnesc și rămân în ordine. backupFilename() din shells/web/src/data-transfer.ts construiește numele. Conținutul zip-ului este identic indiferent de nume. În interior:

CaleObligatoriuConținut
manifest.jsondaId-ul formatului, versiuni, numărători și integritate per-parte. Primul lucru la care se uită un cititor.
profile.jsondacă e setatÎntreaga înregistrare me a utilizatorului: nume, contact, referință fotografie de profil și flag-uri, plus foldere, coș de gunoi, planuri de proiect, șabloane de utilizator și instrumente făcute de utilizator, favorite, instrumente ascunse, alegerea de limbă și de emoji. Citit prin host.profile.
sessions.jsondaFiecare sesiune salvată: slot, id/versiune instrument, etichetă, miniatură (data-URL) și datele complete de intrare. Citit prin host.state.
assets.jsondaMetadate pentru fiecare activ încărcat (imagini, fonturi, tokenuri de brand, logouri, copii salvate ale descărcărilor), fiecare indicând spre octeții săi din assets/blobs/.
assets/blobs/<n>.<ext>per activOcteții bruți ai activului (fișiere de imagine și font). Stocați necomprimați (formate deja comprimate). Extensia este cosmetică. MIME-ul din assets.json este autoritatea.
assets/blobs/<n>.c2pacând e prezentContent Credentials extrase, ca octeți binari exacți, referențiate prin _credentialFile în înregistrarea activului. Acestea nu sunt chei de semnare ale dispozitivului.
design-systems.jsoncând e prezentSistemele de design create sau adăugate pe această instalare, ca { active, records }. Îmbinate după id la import; alegerea activă a pachetului se aplică doar când ținta n-are propriul sistem de design.
file-history.jsonopționalInstantanee versionate ale activelor, rapoarte finale de operații pe fișiere și manifeste complete de loturi. Partea de istoric are propria versiune; furnizată de adaptorul intern de backup fileHistory al shell-ului.
revision-history.jsonopțional, copii de rezervă manualeID-uri stabile de creație, puncte de control reținute, miniaturi și ciorne de recuperare glisante. Furnizată de host.state.history.backup unde e acceptat.
file-history/versions/per instantaneuOcteții activului anterior și acreditările extrase, indiferent dacă activul curent mai există.
file-history/results/per operație finalizatăOcteții exacți de ieșire. Niciun fișier original selectat pentru conversie nu e reținut sau inclus.
prefs.jsondaPreferințe locale deținute de utilizator: theme, sidebarWidth și contorul de activitate ct-metrics.
lolly.txtdaUn rezumat lizibil pentru oameni al pachetului (numărători, profil, nume de fișier) pentru oricine deschide zip-ul fără Lolly. Regenerat la fiecare export și recunoscut la import, astfel încât nu este niciodată numărat ca parte omisă. Este scris după harta de integritate, deci rămâne în afara ei.

Pachetul este un zip simplu în mod intenționat: supraviețuiește intact oricărui transport, iar orice instrument de dezarhivare îl poate inspecta.

profile.json este cea mai mică parte și cea pe care un cititor o vede prima în aplicație: detaliile pe care un producător le completează o singură dată, plus opțiunea de consimțământ care permite instrumentelor să le folosească.

Formularul cu detaliile de profil care devine profile.json: nume, detalii de contact și fotografie de profil signed by Lollyvector SVG15 paths~1.2k nodes30 groups1 image40 KBVerifică singurDescarcă fișierul semnat

manifest.json

{
  "format": "lolly-backup",
  "formatVersion": 3,
  "minReader": 1,
  "app": "lolly",
  "exportedAt": "2026-06-22T09:30:00.000Z",
  "counts": { "profile": true, "sessions": 2, "userAssets": 4, "prefs": 3, "assetVersions": 1, "fileOperations": 1 },
  "integrity": {
    "profile.json": "sha256-…",
    "sessions.json": "sha256-…",
    "assets.json": "sha256-…",
    "assets/blobs/0.bin": "sha256-…",
    "file-history.json": "sha256-…",
    "file-history/versions/0.bin": "sha256-…",
    "file-history/results/0.bin": "sha256-…",
    "prefs.json": "sha256-…"
  }
}
CâmpSemnificație
formatÎntotdeauna lolly-backup. Un fișier fără el este respins ca „not a Lolly backup”.
formatVersionStructura cu care a fost scris acest pachet. Crescut la orice schimbare a setului sau formei părților. Cititorii nu se condiționează de el.
minReaderVersiunea minimă de cititor necesară pentru a importa acest pachet în siguranță. Acesta este câmpul de care se condiționează cititorii.
appId-ul aplicației producătoare, pentru diagnosticare.
exportedAtMarca de timp ISO la care a fost creat pachetul.
countsCe a pus scriitorul înăuntru, pentru afișare și verificare de sanitate.
integrityOpțional. Mapează fiecare parte, cu excepția manifest.json, la un digest în stil SRI sha256-<base64> al octeților săi necomprimați.

Politica de versiuni (compatibilitate înainte)

Separarea dintre formatVersion și minReader este ceea ce permite formatului să crească fără a abandona instalările mai vechi:

Regulă practică pentru autori: dacă fiecare cititor existent ar face totuși ce trebuie ignorând adăugarea ta, este aditivă - crește formatVersion, lasă minReader. Altfel, crește minReader.

Integritate

Când manifest.integrity este prezent, un cititor verifică SHA-256 al fiecărei părți listate înainte de a scrie ceva. O nepotrivire („failed its integrity check”) sau o parte lipsă („incomplete”) abandonează întregul import - nu există restaurare parțială. Acest lucru prinde coruperea pe care un transport de fișiere o poate introduce (un AirDrop trunchiat, o poartă de email care a recodat atașamentul, un sector USB defect).

Integritatea este „best-effort” prin design: este scrisă doar acolo unde Web Crypto este disponibil (orice context de browser securizat și Node modern), și verificată doar când atât harta, cât și Web Crypto sunt prezente. Un pachet fără hartă - de exemplu unul dinainte de existența integrității - se importă neschimbat. „Cannot verify” nu este niciodată tratat drept „corrupt”.

Manifestul nu se listează nici pe sine, nici README-ul regenerat lolly.txt. Digest-urile acoperă părțile pentru care manifestul garantează.

Semantica importului

Importul este o îmbinare, niciodată o înlocuire totală:

Sesiunile salvate se relegă automat de imaginile lor: referințele către active sunt păstrate după id, iar puntea le rerezolvă după ce imaginile încărcate sunt restaurate (oricum trebuie să o facă, deoarece URL-urile blob: nu supraviețuiesc unei reîncărcări).

Rezumatul importului raportează { profile, sessions, userAssets, prefs, skipped, failedAssets }. failedAssets numără activele încărcate care nu au putut fi restaurate (de exemplu, stocarea dispozitivului plină). Este distinct de skipped, care numără părțile de la un scriitor mai nou, compatibil înainte, pe care această versiune nu le-a recunoscut. UI-ul afișează skipped („… · N newer items skipped”), astfel încât restaurarea este onestă cu privire la ce a lăsat în urmă.

Când istoricul de fișiere e prezent, rezumatul mai poartă și assetVersions, fileOperations și failedHistory. Epuizarea stocării sau conflictele de ID imuabil pot cauza o restaurare parțială; interfața îi spune utilizatorului să păstreze copia de rezervă sursă. Sincronizarea cloud nu își avansează revizia aplicată după o restaurare parțială sau neacceptată, așa că instantaneul rămâne disponibil pentru reîncercare. Restaurarea nu e o singură tranzacție de-a lungul tuturor depozitelor de profil/sesiune/activ/istoric.

Istoricul de creație (v3)

Copiile de rezervă manuale de la o gazdă web capabilă de istoric includ revision-history.json cu propria schemă { version: 1, documents, revisions, recoveries }. Poartă ID-urile reținute, instantanee canonice de intrare, mărci de versiune, previzualizări raster și ciorne separate de scriitor. Adaptorul de istoric captează sesiunile curente și capetele lor într-o singură tranzacție de citire; sessions.json folosește aceleași instantanee curente pentru cititorii mai vechi.

Restaurarea verifică SHA-256 și numărul de octeți ai payload-ului, identitățile unice, relațiile document/cap, ascendența, mărcile temporale, tipurile de previzualizare și limitele înainte de a angaja arhiva într-o singură tranzacție. Referințele părinte compactate pot lipsi. Lucrarea curentă existentă nu este niciodată înlocuită în tăcere: când o creație există pe ambele părți, partea care nu este păstrată drept curentă devine o ciornă protejată. Limita de transfer de 384 MiB a arhivei e verificată explicit, iar limitele de stocare sunt impuse fără să trunchieze punctele de control reținute. Copia de rezervă generală folosește încă o implementare ZIP în memorie și nu e o arhivă în flux (streaming).

Rezumatul adaugă revisions și recoveryDrafts, numărând doar ce a adăugat acest import, și added, kept, replaced, copies și hidden pentru cum a fost îmbinată fiecare creație. Un shell fără această capabilitate restaurează sesiunile obișnuite și raportează partea de istoric ca omisă. Istoricul pe sistemul de fișiere nativ rămâne neacceptat până când adaptorul lui furnizează tranzacții de istoric durabile. Starea de invitat P2P nu are niciun istoric durabil sau arhivă de recuperare.

Sincronizarea personală de instantanee exclude explicit istoricul de creație. Aplicarea unui instantaneu unui document local cu istoric păstrează starea lui de lucru anterioară ca o ciornă de recuperare separată și invalidează tokenul de scriere al oricărui editor deschis. Punctele lui de control imuabile rămân pe dispozitiv. Asta protejează istoricul local în timpul înlocuirii instantaneului; nu îmbină istoricele concurente ale dispozitivelor.

Referințele istorice ale activelor sunt reținute, în timp ce randarea rezolvă în continuare activele prin biblioteca existentă a destinației. Această arhivă nu garantează încă octeții exacți ai activelor vechi sau randările vechilor instrumente. Octeții versiunilor de active și ai rezultatelor de fișiere continuă să călătorească prin partea lor separată existentă de backup.

Versiuni salvate și rezultate de fișiere (v2)

Partea opțională de istoric conține { version: 2, assetVersions: [...], operations: [...], batches: [...] }; cititorii mai acceptă și forma anterioară history-v1, fără loturi. Fiecare instantaneu identifică ID-ul stabil al activului și versiunea exactă, ora salvării, lungimea în octeți și SHA-256 hex, plus o înregistrare de activ ale cărei _file și, opțional, _credentialFile indică spre părți binare. Operațiile poartă faptele fișierului original, cererea, raportul, mărcile temporale și opționalul _file de rezultat; numele de backend de stocare, handle-urile OPFS și contractele de execuție (leases) nu călătoresc. Cititorii mai vechi, doar history-v1, refuză noua versiune de istoric înainte de import, în loc să renunțe în tăcere la apartenența la lot.

Manifestele de loturi înregistrează fiecare sursă selectată înainte de procesare, inclusiv fișiere niciodată citite, membri anulați, eșecuri de rezervare a spațiului de rezultat și lucrări întrerupte. Fiecare membru are un ID de operație stabil, o referință/fapte de sursă, un nume de ieșire cerut și un raport final. O sursă necitită are fapte declarate, nu un digest inventat. Importul validează identitatea membrului și consistența cu orice raport de operație purtat. Rapoartele de lot rămân disponibile chiar și când rezultatele individuale au fost eliminate explicit, dar o chitanță nu implică faptul că octeții ei de ieșire sunt încă stocați.

Cititorii mai vechi acceptă în continuare plicul v2 (minReader: 1) și restaurează părțile familiare, numărând părțile de istoric neacceptate ca omise. Recuperarea completă a istoricului cere un shell cu adaptorul fileHistory; asta e o cusătură internă shell-ului, nu o nouă capabilitate HostV1 orientată spre instrumente. Restaurarea reală între două dispozitive e acoperită de poarta Chromium locală; acceptarea recuperării pe Tauri/iOS/Android instalate rămâne separată.

Ce nu călătorește

Contorul de stocare detaliază aceeași separare. Sesiuni salvate, Imaginile mele și Rezultatele fișierului & versiunile călătoresc într-un pachet. Cache de active, Previzualizări instrumente și fixările offline de sub ele sunt toate re-derivabile, deci rămân în urmă.

Contorul de stocare împărțind datele acestui dispozitiv în categorii denumite, cu Saved sessions și My images urmărite separat de Asset cache, aici pe o instalare nouă unde fiecare categorie este încă goală signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBVerifică singurDescarcă fișierul semnatContorul de stocare împărțind datele acestui dispozitiv în categorii denumite, cu Saved sessions și My images urmărite separat de Asset cache, aici pe o instalare nouă unde fiecare categorie este încă goală signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBVerifică singurDescarcă fișierul semnat

Garanția cross-shell

data-transfer.ts citește și scrie exclusiv prin puntea de capabilități (host.profile, host.state, host.assets) și preferințele partajate din localStorage. Același modul citește și scrie plicul comun pe web și pe Tauri, peste stocarea IndexedDB sau de sistem de fișiere. Părțile opționale de istoric apar doar acolo unde adaptorul corespunzător e disponibil; o parte neacceptată e raportată ca omisă la import. Suita headless exersează părțile comune față de o punte în memorie, în timp ce tranzacțiile de istoric au și teste în browser real.

Două shell-uri stau în afara acestei garanții, din motive diferite:

Puncte de extensie rezervate

Plicul este, prin proiectare, un manifest plus un set de părți numite, astfel încât noi tipuri de date portabile să îl poată folosi mai târziu fără o schimbare incompatibilă. Ele se integrează ca părți aditive (formatVersion nou, același minReader), iar cititorul de azi sare peste ce nu recunoaște. Acestea nu sunt încă implementate. Numele sunt rezervate aici pentru ca formatul să rămână coerent atunci când vor apărea.

Orice se află în afara acestor nume rezervate și a părților de mai sus reprezintă, pentru un cititor, o parte necunoscută: lăsată neatinsă și numărată în skipped.

Referință