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 relitsigned by Lollyvector SVGVérifie par toi-mêmeGet the signed file12 paths~5.1k nodes12 groups60 KBLes deux boutons qui déplacent toute une installation : Export my data écrit un zip, Import data le relitsigned by Lollyvector SVGVérifie par toi-mêmeGet the signed file12 paths~5.1k nodes12 groups60 KB

Cette page est la spécification du format. Pour le guide utilisateur final, voir Using Lolly → Moving to another device. 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. Les outils 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). L'import n'installe ni ne met jamais à niveau un outil.

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 de l'utilisateur (nom, contact, référence de portrait, indicateurs). 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), 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.
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 de détails du profil qui devient profile.json - nom, contact, portrait et l'opt-in à côtésigned by Lollyvector SVGVérifie par toi-mêmeGet the signed file18 paths~2.0k nodes41 groups30 KBLe formulaire de détails du profil qui devient profile.json - nom, contact, portrait et l'opt-in à côtésigned by Lollyvector SVGVérifie par toi-mêmeGet 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-…"
  }
}
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 avec écrasement, 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é.

Ce qui ne voyage pas

Le compteur de stockage détaille la même séparation. Saved sessions et My images 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 videsigned by Lollyvector SVGVérifie par toi-mêmeGet the signed file25 paths~4.8k nodes46 groups59 KBLe 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 videsigned by Lollyvector SVGVérifie par toi-mêmeGet the signed file25 paths~4.8k nodes46 groups59 KB

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. Comme ce pont est le seul point de passage, le même module produit un bundle identique octet pour octet sur chaque shell, même si le stockage sous-jacent diffère - IndexedDB sur le web, le système de fichiers sur Tauri. Les shells Tauri réutilisent ce module sans modification. Seule leur implémentation de host.state diffère. Le test headless exerce l'aller-retour complet contre un pont en mémoire, ce qui explique qu'il tienne lieu de test pour tous.

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 figurent sur la feuille de route, non encore implémentés. 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