Lolly
Abrir la app

Transferencia de datos - el paquete lolly-backup

Todo lo que acumula un usuario de Lolly vive en su dispositivo - sin cuenta, sin nube. El paquete de transferencia de datos es cómo se mueve ese valor: expórtalo en una instalación, lleva el archivo por cualquier medio (USB, AirDrop, correo a ti mismo, una carpeta compartida en red) e impórtalo en otra. El archivo es el transporte. El destino puede estar sin conexión o con conexión. No importa, porque nada habla nunca con un servidor.

Los dos botones que mueven una instalación entera: Exportar mis datos escribe un zip, Importar datos lo vuelve a leer signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBCompruébalo tú mismoDescarga el archivo firmadoLos dos botones que mueven una instalación entera: Exportar mis datos escribe un zip, Importar datos lo vuelve a leer signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBCompruébalo tú mismoDescarga el archivo firmado

Esta página es la especificación del formato. Para el recorrido pensado para el usuario final, consulta Encuentra y recupera tu trabajo → Pasa tu trabajo a otro dispositivo. La implementación es shells/web/src/data-transfer.ts, y tests/data-transfer.test.ts fija el contrato de ida y vuelta.

Alcance. Un paquete lleva datos del usuario, no herramientas del catálogo. Las herramientas y los recursos del catálogo se sincronizan por separado y se asume que ya están presentes en el destino (en el peor caso, en una versión más nueva); las herramientas que el propio usuario creó viajan dentro de profile.json. Importar nunca instala ni actualiza una herramienta del catálogo.

Objetivos

El sobre

Un paquete es un simple .zip. La descarga se nombra según la persona a la que pertenece - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip (por ejemplo, LollyTools-Ada-Lovelace-2026-06-26-1.zip) - para que una carpeta de Descargas llena de copias de seguridad siga siendo legible. Las partes de nombre y apellido provienen del perfil y se omiten cuando no están definidas. Sin perfil se obtiene LollyTools-2026-06-26-1.zip, y solo con un nombre de pila se obtiene LollyTools-Ada-2026-06-26-1.zip. Cada parte se sanea a un token seguro para nombres de archivo (se conservan letras/dígitos Unicode, se eliminan espacios/puntuación, con un tope de 32 caracteres). <n> es una secuencia por día y por dispositivo, de modo que las exportaciones repetidas el mismo día no chocan entre sí y se mantienen en orden. backupFilename() en shells/web/src/data-transfer.ts construye el nombre. El contenido del zip es idéntico sin importar el nombre. Dentro:

PathObligatorioContenido
manifest.jsonsíId de formato, versiones, recuentos e integridad por parte. Lo primero que mira un lector.
profile.jsonsi está definidoTodo el registro me del usuario: nombre, contacto, referencia de foto e indicadores, más carpetas, Papelera, plantillas de proyecto, plantillas de usuario y herramientas creadas por el usuario, favoritos, herramientas ocultas, idioma y elección de emoji. Se lee mediante host.profile.
sessions.jsonsíCada sesión guardada: slot, id/versión de herramienta, etiqueta, miniatura (data-URL) y datos de entrada completos. Se lee mediante host.state.
assets.jsonsíMetadatos de cada recurso subido (imágenes, fuentes, tokens de marca, logotipos, copias guardadas de descargas), cada uno apuntando a sus bytes bajo assets/blobs/.
assets/blobs/<n>.<ext>por recursoLos bytes en bruto del recurso (archivos de imagen y de fuente). Almacenados sin comprimir (formatos ya comprimidos). La extensión es cosmética. El MIME en assets.json es el que manda.
assets/blobs/<n>.c2pacuando está presenteContent Credentials extraídas como bytes binarios exactos, referenciadas por _credentialFile en el registro del recurso. No son claves de firma del dispositivo.
design-systems.jsoncuando está presenteLos sistemas de diseño creados o añadidos en esta instalación, como { active, records }. Se fusionan por id al importar; la elección activa del paquete se aplica solo cuando el destino no tiene un sistema de diseño propio.
file-history.jsonopcionalInstantáneas de recursos con versión, informes finales de operación de archivo y manifiestos de lote completos. La parte de historial tiene su propia versión; la proporciona el adaptador interno de copia de seguridad fileHistory del shell.
revision-history.jsonopcional, copias de seguridad manualesIds de creación estables, puntos de control retenidos, miniaturas y borradores de recuperación continuos. Lo proporciona host.state.history.backup donde se admite.
file-history/versions/por instantáneaBytes de recurso anteriores y credenciales extraídas, independientemente de si el recurso actual todavía existe.
file-history/results/por operación completadaBytes de salida exactos. No se conserva ni se incluye ningún archivo original seleccionado para conversión.
prefs.jsonsíPreferencias locales del usuario: theme, sidebarWidth y el recuento de actividad ct-metrics.
lolly.txtsíUn resumen legible del paquete (recuentos, perfil, nombre de archivo) para quien abra el zip sin Lolly. Se regenera en cada exportación y se reconoce al importar, así que nunca cuenta como parte omitida. Se escribe después del mapa de integridad, así que queda fuera de él.

El paquete es un zip normal a propósito: sobrevive intacto a cualquier transporte, y cualquier herramienta de descompresión puede inspeccionarlo.

profile.json es la parte más pequeña y la primera que ve un lector en la app: los datos que un productor rellena una sola vez, más el opt-in que permite que las herramientas los usen.

El formulario de datos del perfil que se convierte en profile.json: nombre, datos de contacto y foto de perfil signed by Lollyvector SVG15 paths~1.2k nodes30 groups1 image40 KBCompruébalo tú mismoDescarga el archivo firmado

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-…"
  }
}
CampoSignificado
formatSiempre lolly-backup. Un archivo sin él se rechaza como "no es una copia de seguridad de Lolly".
formatVersionLa versión con la que se escribió este paquete. Se incrementa con cualquier cambio en el conjunto o la forma de las partes. Los lectores no se rigen por ella.
minReaderLa versión mínima de lector necesaria para importar este paquete con seguridad. Este es el campo que rige a los lectores.
appId de la app productora, para diagnóstico.
exportedAtMarca de tiempo ISO en la que se creó el paquete.
countsLo que puso el escritor, para mostrar y para comprobaciones de coherencia.
integrityOpcional. Asocia cada parte excepto manifest.json con un resumen estilo SRI sha256-<base64> de sus bytes sin comprimir.

Política de versiones (compatibilidad hacia adelante)

La división entre formatVersion y minReader es lo que permite que el formato crezca sin dejar huérfanas a las instalaciones más antiguas:

Regla práctica para autores: si todo lector existente seguiría haciendo lo correcto al ignorar tu adición, es aditivo - incrementa formatVersion, deja minReader igual. En caso contrario, eleva minReader.

Integridad

Cuando manifest.integrity está presente, un lector verifica el SHA-256 de cada parte listada antes de escribir nada. Una discordancia ("falló su comprobación de integridad") o una parte faltante ("incompleto") aborta toda la importación - no hay restauración parcial. Esto detecta la corrupción que puede introducir un transporte de archivos (un AirDrop truncado, una pasarela de correo que recodificó el adjunto, un sector USB defectuoso).

La integridad es de mejor esfuerzo por diseño: solo se escribe donde Web Crypto está disponible (todo contexto seguro de navegador y Node moderno), y solo se verifica cuando el mapa y Web Crypto están presentes a la vez. Un paquete sin el mapa - por ejemplo uno de antes de que existiera la integridad - se importa sin cambios. "No se puede verificar" nunca se trata como "corrupto".

El manifiesto no se lista a sí mismo ni al README lolly.txt regenerado. Los resúmenes cubren las partes que el manifiesto avala.

Semántica de importación

Importar es una fusión, nunca un reemplazo total:

Las sesiones guardadas se reenlazan automáticamente con sus imágenes: las referencias a recursos se conservan por id, y el puente las vuelve a resolver después de restaurar las imágenes subidas (tiene que hacerlo de todos modos, porque las URLs blob: no sobreviven a una recarga).

El resumen de importación reporta { profile, sessions, userAssets, prefs, skipped, failedAssets }. failedAssets cuenta los recursos subidos que no se pudieron restaurar (almacenamiento del dispositivo lleno, por ejemplo). Es distinto de skipped, que cuenta partes de un escritor más nuevo compatible hacia adelante que esta build no reconoció. La interfaz muestra skipped ("… · N elementos más nuevos omitidos"), para que la restauración sea honesta sobre lo que dejó atrás.

Cuando el historial de archivos está presente, el resumen también lleva assetVersions, fileOperations y failedHistory. El agotamiento del almacenamiento o los conflictos de id inmutable pueden causar una restauración parcial; la interfaz le dice al usuario que conserve la copia de seguridad de origen. La sincronización en la nube no avanza su revisión aplicada tras una restauración parcial o no admitida, así que la instantánea sigue disponible para reintentarlo. La restauración no es una única transacción a través de todos los almacenes de perfil, sesión, recurso e historial.

Historial de creaciones (v3)

Las copias de seguridad manuales desde un host web con capacidad de historial incluyen revision-history.json con su propio esquema { version: 1, documents, revisions, recoveries }. Lleva los ids retenidos, las instantáneas canónicas de entrada, las marcas de versión, las vistas previas ráster y borradores de escritor independientes. El adaptador de historial captura las sesiones actuales y sus cabezas en una sola transacción de lectura; sessions.json usa esas mismas instantáneas actuales para lectores más antiguos.

La restauración comprueba el SHA-256 y el recuento de bytes de la carga útil, las identidades únicas, las relaciones documento/cabeza, la ascendencia, las marcas de tiempo, los tipos de vista previa y los límites antes de confirmar el archivo en una sola transacción. Las referencias al padre compactadas pueden estar ausentes. El trabajo actual existente nunca se reemplaza en silencio: cuando una creación está en ambos lados, el lado que no se conserva como actual se convierte en un borrador protegido. El límite de transferencia de 384 MiB del archivo se comprueba explícitamente, y los límites de almacenamiento se hacen cumplir sin truncar los puntos de control retenidos. La copia de seguridad global sigue usando una implementación de ZIP en memoria y no es un archivo en streaming.

El resumen añade revisions y recoveryDrafts, que solo cuentan lo que esta importación añadió, y added, kept, replaced, copies y hidden para indicar cómo se fusionó cada creación. Un shell sin esta capacidad restaura las sesiones normales y reporta la parte de historial como omitida. El historial del sistema de archivos nativo sigue sin admitirse hasta que su adaptador aporte transacciones de historial duraderas. El estado de invitado P2P no tiene historial duradero ni archivo de recuperación.

La sincronización de instantáneas personales excluye explícitamente el historial de creaciones. Aplicar una instantánea a un documento local que lleva historial conserva su estado de trabajo anterior como un borrador de recuperación aparte e invalida el token de escritura de cualquier editor abierto. Sus puntos de control inmutables permanecen en el dispositivo. Esto protege el historial local durante la sustitución de la instantánea; no fusiona los historiales concurrentes de varios dispositivos.

Las referencias históricas a recursos se conservan, mientras que el renderizado sigue resolviendo los recursos a través de la biblioteca existente del destino. Este archivo todavía no garantiza los bytes exactos de recursos antiguos ni los renderizados antiguos de herramientas. Los bytes de versión de recurso y de resultado de archivo siguen viajando a través de su parte de copia de seguridad separada existente.

Versiones guardadas y resultados de archivo (v2)

La parte opcional de historial contiene { version: 2, assetVersions: [...], operations: [...], batches: [...] }; los lectores también aceptan la forma anterior de historial-v1 sin batches. Cada instantánea identifica el id de recurso estable y la versión exacta, su hora de guardado, la longitud en bytes y el SHA-256 hexadecimal, más un registro de recurso cuyo _file y opcional _credentialFile apuntan a partes binarias. Las operaciones llevan los datos originales del archivo, la solicitud, el informe, las marcas de tiempo y un _file de resultado opcional; los nombres de backend de almacenamiento, los identificadores OPFS y los arrendamientos de ejecución no viajan. Los lectores antiguos que solo admiten historial-v1 rechazan la nueva versión de historial antes de importar, en lugar de descartar en silencio la pertenencia a un lote.

Los manifiestos de lote registran cada fuente seleccionada antes del procesamiento, incluidos los archivos nunca leídos, los miembros cancelados, los fallos al reservar espacio de resultado y el trabajo interrumpido. Cada miembro tiene un id de operación estable, una referencia/datos de fuente, un nombre de salida solicitado y un informe final. Una fuente no leída tiene datos declarados, no un resumen inventado. La importación valida la identidad del miembro y su coherencia con cualquier informe de operación llevado consigo. Los informes de lote siguen disponibles cuando los resultados individuales se han eliminado explícitamente, pero un recibo no implica que sus bytes de salida sigan almacenados.

Los lectores más antiguos siguen aceptando el sobre v2 (minReader: 1) y restauran las partes conocidas, contando las partes de historial no admitidas como omitidas. La recuperación completa del historial requiere un shell con el adaptador fileHistory; esto es una costura interna del shell, no una nueva capacidad HostV1 de cara a las herramientas. La restauración real entre dos dispositivos está cubierta por la puerta local de Chromium; la aceptación de recuperación instalada en Tauri/iOS/Android sigue siendo aparte.

Qué no viaja

El medidor de almacenamiento desglosa la misma división. Las sesiones guardadas, Mis imágenes y los resultados y versiones de archivo viajan en un paquete. La caché de recursos, las vistas previas de herramientas y los anclajes sin conexión debajo de ellas son todos re-derivables, así que se quedan atrás.

El medidor de almacenamiento desglosando los datos de este dispositivo en categorías con nombre, con Sesiones guardadas y Mis imágenes rastreadas por separado de la Caché de recursos, aquí en una instalación nueva donde cada categoría sigue vacía signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBCompruébalo tú mismoDescarga el archivo firmadoEl medidor de almacenamiento desglosando los datos de este dispositivo en categorías con nombre, con Sesiones guardadas y Mis imágenes rastreadas por separado de la Caché de recursos, aquí en una instalación nueva donde cada categoría sigue vacía signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBCompruébalo tú mismoDescarga el archivo firmado

Garantía entre shells

data-transfer.ts lee y escribe exclusivamente a través del puente de capacidades (host.profile, host.state, host.assets) y las preferencias compartidas de localStorage. El mismo módulo lee y escribe el sobre común en web y en Tauri, sobre IndexedDB o almacenamiento del sistema de archivos. Las partes opcionales de historial aparecen solo donde el adaptador correspondiente está disponible; una parte no admitida se reporta como omitida al importar. La batería de pruebas sin interfaz gráfica ejercita las partes comunes contra un puente en memoria, mientras que las transacciones de historial también tienen pruebas en navegador real.

Dos shells quedan fuera de esa garantía, por razones distintas:

Puntos de extensión reservados

El sobre es, por diseño, un manifiesto más un conjunto de partes con nombre, para que nuevos tipos de datos portables puedan viajar en él más adelante sin un cambio incompatible. Se incorporan como partes aditivas (nuevo formatVersion, mismo minReader), y el lector actual omite lo que no reconoce. Esto todavía no está implementado. Los nombres se reservan aquí para que el formato siga siendo coherente cuando lleguen.

Cualquier cosa fuera de estos nombres reservados y de las partes anteriores es, para un lector, una parte desconocida: se deja intacta y se cuenta en skipped.

Referencia