Lolly
Ouvrir l'app

Transfert de données - le bundle lolly-backup

Tout ce qu'un utilisateur de Lolly accumule vit sur son appareil - pas de compte, pas de cloud. Le bundle de transfert de données est la façon dont cette valeur se déplace : exporte-le depuis une installation, transporte le fichier par n'importe quel moyen (USB, AirDrop, e-mail à soi-même, un partage réseau) et importe-le sur une autre. Le fichier est le transport. La cible peut être hors ligne ou en ligne. Cela ne fait aucune différence, car rien ne parle jamais à un serveur.

Les deux boutons qui déplacent toute une installation : Export my data écrit un zip, Import data le relit signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBVérifie par toi-mêmeRécupérer le fichier signéLes deux boutons qui déplacent toute une installation : Export my data écrit un zip, Import data le relit signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBVérifie par toi-mêmeRécupérer le fichier signé

Cette page est la spécification du format. Pour le guide utilisateur final, voir Retrouver et récupérer ton travail → Transférer ton travail vers un autre appareil. L'implémentation se trouve dans shells/web/src/data-transfer.ts, et tests/data-transfer.test.ts fixe le contrat d'aller-retour.

Portée. Un bundle porte des données utilisateur, pas des outils du catalogue. Les outils du catalogue et les assets du catalogue sont synchronisés séparément et sont supposés déjà présents sur la cible (dans le pire cas à une version plus récente) ; les outils qu'un utilisateur a lui-même créés voyagent à l'intérieur de profile.json. L'import n'installe ni ne met jamais à niveau un outil du catalogue.

Objectifs

L'enveloppe

Un bundle est un simple .zip. Le téléchargement porte le nom de la personne à qui il appartient - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip (par exemple LollyTools-Ada-Lovelace-2026-06-26-1.zip) - pour qu'un dossier Téléchargements plein de sauvegardes reste lisible. Les parties prénom et nom viennent du profil et sont omises quand elles ne sont pas définies. Sans profil, on obtient LollyTools-2026-06-26-1.zip, et un simple prénom donne LollyTools-Ada-2026-06-26-1.zip. Chaque partie est nettoyée en un jeton sûr pour un nom de fichier (lettres/chiffres Unicode conservés, espaces/ponctuation retirés, plafonné à 32 caractères). <n> est une séquence par jour et par appareil, pour que des exports répétés le même jour ne se percutent pas et restent dans l'ordre. backupFilename() dans shells/web/src/data-transfer.ts construit le nom. Le contenu du zip est identique quel que soit le nom. À l'intérieur :

CheminRequisContenu
manifest.jsonouiId de format, versions, compteurs et intégrité par partie. La première chose qu'un lecteur consulte.
profile.jsonsi définiLa fiche me complète de l'utilisateur : nom, contact, référence de portrait et indicateurs, plus dossiers, Corbeille, modèles de projet, modèles utilisateur et outils créés par l'utilisateur, favoris, outils masqués, choix de langue et d'emoji. Lue via host.profile.
sessions.jsonouiChaque session enregistrée : emplacement, id/version de l'outil, libellé, vignette (data-URL) et données d'entrée complètes. Lue via host.state.
assets.jsonouiMétadonnées de chaque asset téléversé (images, polices, tokens de marque, logos, copies enregistrées de téléchargements), chacune pointant vers ses octets sous assets/blobs/.
assets/blobs/<n>.<ext>par assetLes octets bruts de l'asset (fichiers image et police). Stockés non compressés (formats déjà compressés). L'extension est cosmétique. Le MIME dans assets.json fait foi.
assets/blobs/<n>.c2pasi présentContent Credentials extraits comme octets binaires exacts, référencés par _credentialFile dans la fiche de l'asset. Ce ne sont pas des clés de signature de l'appareil.
design-systems.jsonsi présentLes systèmes de design créés ou ajoutés sur cette installation, sous la forme { active, records }. Fusionnés par id à l'import ; le choix actif du bundle ne s'applique que si la cible n'a pas de système de design propre.
file-history.jsonoptionnelInstantanés d'assets versionnés, rapports d'opérations de fichiers terminées et manifestes de lots complets. Cette partie de l'historique a sa propre version ; fournie par l'adaptateur de sauvegarde interne fileHistory du shell.
revision-history.jsonoptionnel, sauvegardes manuellesIdentifiants de création stables, points de contrôle conservés, vignettes et brouillons de récupération glissants. Fournie par host.state.history.backup là où c'est pris en charge.
file-history/versions/par instantanéOctets précédents de l'asset et identifiants extraits, indépendamment du fait que l'asset actuel existe encore.
file-history/results/par opération terminéeOctets de sortie exacts. Aucun fichier d'origine sélectionné pour conversion n'est conservé ni inclus.
prefs.jsonouiPréférences locales propres à l'utilisateur : theme, sidebarWidth et le compteur d'activité ct-metrics.
lolly.txtouiUn résumé lisible par un humain du bundle (compteurs, profil, nom de fichier) pour quiconque ouvre le zip sans Lolly. Régénéré à chaque export et reconnu à l'import, donc il ne compte jamais comme une partie ignorée. Il est écrit après la carte d'intégrité, donc il reste en dehors d'elle.

Le bundle est délibérément un simple zip : il survit intact à n'importe quel transport, et n'importe quel outil de décompression peut l'inspecter.

profile.json est la plus petite partie et celle qu'un lecteur voit en premier dans l'application : les informations qu'un producteur renseigne une fois, plus l'opt-in qui permet aux outils de les utiliser.

Le formulaire des détails du profil qui devient profile.json : nom, coordonnées et photo de profil signed by Lollyvector SVG15 paths~1.2k nodes30 groups1 image40 KBVérifie par toi-mêmeRécupérer le fichier signé

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-…"
  }
}
ChampSignification
formatToujours lolly-backup. Un fichier qui en est dépourvu est rejeté comme "not a Lolly backup".
formatVersionLa disposition avec laquelle ce bundle a été écrit. Incrémenté à chaque changement de l'ensemble ou de la forme des parties. Les lecteurs ne s'appuient pas dessus.
minReaderLa version minimale de lecteur requise pour importer ce bundle en toute sécurité. C'est le champ sur lequel les lecteurs s'appuient.
appId de l'application productrice, pour le diagnostic.
exportedAtHorodatage ISO de la création du bundle.
countsCe que l'écrivain y a mis, pour l'affichage et la vérification de cohérence.
integrityOptionnel. Associe chaque partie sauf manifest.json à un digest de type SRI sha256-<base64> de ses octets non compressés.

Politique de version (compatibilité ascendante)

La séparation entre formatVersion et minReader est ce qui permet au format d'évoluer sans abandonner les installations plus anciennes :

Règle empirique pour les auteurs : si tout lecteur existant continuerait à bien se comporter en ignorant ton ajout, c'est additif - incrémente formatVersion, laisse minReader. Sinon, fais monter minReader.

Intégrité

Quand manifest.integrity est présent, un lecteur vérifie le SHA-256 de chaque partie listée avant d'écrire quoi que ce soit. Une non-correspondance ("failed its integrity check") ou une partie manquante ("incomplete") interrompt tout l'import - il n'y a pas de restauration partielle. Cela détecte la corruption qu'un transport de fichier peut introduire (un AirDrop tronqué, une passerelle e-mail qui a réencodé la pièce jointe, un mauvais secteur USB).

L'intégrité est du best-effort par conception : elle n'est écrite que là où Web Crypto est disponible (tout contexte de navigateur sécurisé et Node moderne), et vérifiée seulement quand la carte et Web Crypto sont tous deux présents. Un bundle sans la carte - par exemple un bundle antérieur à l'existence de l'intégrité - s'importe sans changement. "Cannot verify" n'est jamais traité comme "corrupt".

Le manifeste ne se liste ni lui-même ni le README lolly.txt régénéré. Les digests couvrent les parties dont le manifeste se porte garant.

Sémantique de l'import

L'import est une fusion, jamais un remplacement total :

Les sessions enregistrées se relient automatiquement à leurs images : les références d'assets sont conservées par id, et le pont les résout à nouveau après la restauration des images téléversées (il doit le faire de toute façon, car les URL blob: ne survivent pas à un rechargement).

Le résumé de l'import rapporte { profile, sessions, userAssets, prefs, skipped, failedAssets }. failedAssets compte les assets téléversés qui n'ont pas pu être restaurés (stockage de l'appareil plein, par exemple). C'est distinct de skipped, qui compte les parties d'un écrivain plus récent et rétrocompatible que cette build n'a pas reconnues. L'interface affiche skipped ("… · N newer items skipped"), pour que la restauration soit honnête sur ce qu'elle a laissé de côté.

Quand l'historique des fichiers est présent, le résumé porte aussi assetVersions, fileOperations et failedHistory. Un épuisement du stockage ou des conflits d'id immuables peuvent provoquer une restauration partielle ; l'interface indique à l'utilisateur de conserver la sauvegarde source. La synchronisation cloud n'avance pas sa révision appliquée après une restauration partielle ou non prise en charge, si bien que l'instantané reste disponible pour une nouvelle tentative. La restauration n'est pas une transaction unique couvrant tous les stockages de profil/session/asset/historique.

Historique de création (v3)

Les sauvegardes manuelles depuis un hôte web capable d'historique incluent revision-history.json avec son propre schéma { version: 1, documents, revisions, recoveries }. Il porte les id conservés, les instantanés d'entrée canoniques, les horodatages de version, les aperçus raster et des brouillons d'écriture séparés. L'adaptateur d'historique capture les sessions actuelles et leurs têtes en une seule transaction de lecture ; sessions.json utilise ces mêmes instantanés actuels pour les lecteurs plus anciens.

La restauration vérifie le SHA-256 et le nombre d'octets de la charge utile, les identités uniques, les relations document/tête, l'ascendance, les horodatages, les types d'aperçu et les limites avant de valider l'archive en une seule transaction. Des références parentes compactées peuvent être absentes. Le travail actuel existant n'est jamais remplacé en silence : quand une création existe des deux côtés, le côté non conservé comme actif devient un brouillon protégé. La limite de transfert de 384 Mio de l'archive est vérifiée explicitement, et les limites de stockage sont appliquées sans tronquer les points de contrôle conservés. La sauvegarde globale utilise toujours une implémentation ZIP en mémoire et n'est pas une archive en flux continu.

Le résumé ajoute revisions et recoveryDrafts, qui ne comptent que ce que cet import a ajouté, ainsi que added, kept, replaced, copies et hidden pour la façon dont chaque création a été fusionnée. Un shell sans cette capacité restaure les sessions ordinaires et signale la partie historique comme ignorée. L'historique du système de fichiers natif reste non pris en charge tant que son adaptateur ne fournit pas de transactions d'historique durables. L'état d'invité P2P n'a ni historique durable ni archive de récupération.

La synchronisation d'instantanés personnels exclut explicitement l'historique de création. Appliquer un instantané à un document local porteur d'historique préserve son état de travail précédent comme un brouillon de récupération séparé et invalide le jeton d'écriture de tout éditeur ouvert. Ses points de contrôle immuables restent sur l'appareil. Cela protège l'historique local pendant le remplacement d'instantané ; cela ne fusionne pas les historiques concurrents des appareils.

Les références d'assets historiques sont conservées, tandis que le rendu résout toujours les assets via la bibliothèque existante de la destination. Cette archive ne garantit pas encore les octets exacts des anciens assets ni les anciens rendus d'outils. Les octets de version d'asset et de résultat de fichier continuent de voyager via leur partie de sauvegarde séparée existante.

Versions enregistrées et résultats de fichiers (v2)

La partie optionnelle de l'historique contient { version: 2, assetVersions: [...], operations: [...], batches: [...] } ; les lecteurs acceptent aussi l'ancienne forme history-v1 sans lots. Chaque instantané identifie l'id d'asset stable et la version exacte, son heure d'enregistrement, sa longueur en octets et son SHA-256 hexadécimal, plus une fiche d'asset dont _file et l'optionnel _credentialFile pointent vers des parties binaires. Les opérations portent les faits du fichier d'origine, la requête, le rapport, les horodatages et un _file de résultat optionnel ; les noms de backend de stockage, les handles OPFS et les baux d'exécution ne voyagent pas. Les lecteurs plus anciens, limités à history-v1, refusent la nouvelle version de l'historique avant l'import, plutôt que d'abandonner silencieusement l'appartenance à un lot.

Les manifestes de lot enregistrent chaque source sélectionnée avant le traitement, y compris les fichiers jamais lus, les membres annulés, les échecs de réservation d'espace de résultat et le travail interrompu. Chaque membre a un id d'opération stable, une référence/des faits sur la source, le nom de sortie demandé et un rapport terminal. Une source non lue a des faits déclarés, pas une empreinte inventée. L'import valide l'identité du membre et sa cohérence avec tout rapport d'opération porté. Les rapports de lot restent disponibles quand des résultats individuels ont été explicitement retirés, mais un reçu n'implique pas que ses octets de sortie sont encore stockés.

Les lecteurs plus anciens acceptent toujours l'enveloppe v2 (minReader: 1) et restaurent les parties familières, en comptant les parties d'historique non prises en charge comme ignorées. Une récupération complète de l'historique nécessite un shell avec l'adaptateur fileHistory ; c'est une jointure interne au shell, pas une nouvelle capacité HostV1 côté outil. La véritable restauration à deux appareils est couverte par le contrôle Chromium local ; l'acceptation de la récupération sur les apps Tauri/iOS/Android installées reste distincte.

Ce qui ne voyage pas

Le compteur de stockage détaille la même séparation. Les sessions enregistrées, Mes images et Résultats de fichiers & versions voyagent dans un bundle. Le cache d'assets, les aperçus d'outils et les épingles hors ligne en dessous sont tous re-dérivables, donc ils restent en arrière.

Le compteur de stockage qui décompose les données de cet appareil en catégories nommées, avec Saved sessions et My images suivies séparément de l'Asset cache, ici sur une installation neuve où chaque catégorie est encore vide signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBVérifie par toi-mêmeRécupérer le fichier signéLe compteur de stockage qui décompose les données de cet appareil en catégories nommées, avec Saved sessions et My images suivies séparément de l'Asset cache, ici sur une installation neuve où chaque catégorie est encore vide signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBVérifie par toi-mêmeRécupérer le fichier signé

Garantie multi-shell

data-transfer.ts lit et écrit exclusivement via le pont de capacités (host.profile, host.state, host.assets) et les préférences localStorage partagées. Le même module lit et écrit l'enveloppe commune sur le web et Tauri, via IndexedDB ou le stockage du système de fichiers. Les parties optionnelles d'historique n'apparaissent que là où l'adaptateur correspondant est disponible ; une partie non prise en charge est signalée comme ignorée à l'import. La suite headless exerce les parties communes contre un pont en mémoire, tandis que les transactions d'historique ont aussi des tests en navigateur réel.

Deux shells échappent à cette garantie, pour des raisons différentes :

Points d'extension réservés

L'enveloppe est, par conception, un manifeste plus un ensemble de parties nommées, pour que de nouveaux types de données portables puissent s'y greffer plus tard sans rupture de compatibilité. Elles s'insèrent comme des parties additives (nouveau formatVersion, même minReader), et le lecteur actuel ignore ce qu'il ne reconnaît pas. Ces éléments ne sont pas encore construits. Les noms sont réservés ici pour que le format reste cohérent à leur arrivée.

Tout ce qui sort de ces noms réservés et des parties ci-dessus est, pour un lecteur, une partie inconnue : laissée intacte et comptée dans skipped.

Référence