Lolly
Abrir o app

Data Transfer - o pacote lolly-backup

Tudo que um usuário do Lolly acumula vive no seu dispositivo - sem conta, sem nuvem. O pacote de transferência de dados é como esse valor se move: exporte-o em uma instalação, leve o arquivo por qualquer meio (USB, AirDrop, e-mail para si mesmo, um compartilhamento de rede) e importe-o em outra. O arquivo é o transporte. O destino pode estar offline ou online. Não faz diferença, porque nada nunca fala com um servidor.

Os dois botões que movem uma instalação inteira: Export my data grava um zip, Import data o lê de volta signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBVerifique você mesmoBaixar o arquivo assinadoOs dois botões que movem uma instalação inteira: Export my data grava um zip, Import data o lê de volta signed by Lollyvector SVG9 paths~4.9k nodes11 groups56 KBVerifique você mesmoBaixar o arquivo assinado

Esta página é a especificação do formato. Para o passo a passo do usuário final, veja Encontre e recupere seu trabalho → Mova seu trabalho para outro dispositivo. A implementação é shells/web/src/data-transfer.ts, e tests/data-transfer.test.ts fixa o contrato de ida e volta.

Escopo. Um pacote carrega dados do usuário, não ferramentas de catálogo. Ferramentas de catálogo e ativos de catálogo são sincronizados separadamente e presume-se que já estejam presentes no destino (no pior caso, em uma versão mais alta); ferramentas que o próprio usuário fez viajam dentro de profile.json. Importar nunca instala nem atualiza uma ferramenta de catálogo.

Objetivos

O envelope

Um pacote é um .zip simples. O download recebe o nome da pessoa a quem pertence - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip (por exemplo LollyTools-Ada-Lovelace-2026-06-26-1.zip) - para que uma pasta de Downloads cheia de backups continue legível. As partes de primeiro e último nome vêm do perfil e são omitidas quando não definidas. Sem perfil, o resultado é LollyTools-2026-06-26-1.zip, e apenas um primeiro nome dá LollyTools-Ada-2026-06-26-1.zip. Cada parte é sanitizada para um token seguro para nome de arquivo (letras/dígitos Unicode mantidos, espaços/pontuação removidos, limitado a 32 caracteres). <n> é uma sequência por dia, por dispositivo, então exportações repetidas no mesmo dia não colidem e permanecem em ordem. backupFilename() em shells/web/src/data-transfer.ts monta o nome. O conteúdo do zip é idêntico independentemente do nome. Dentro:

CaminhoObrigatórioConteúdo
manifest.jsonsimId do formato, versões, contagens e integridade por parte. A primeira coisa que um leitor observa.
profile.jsonquando definidoTodo o registro me do usuário: nome, contato, referência de foto de perfil e flags, mais pastas, Lixeira, modelos de projeto, modelos do usuário e ferramentas feitas pelo usuário, favoritos, ferramentas ocultas, escolha de idioma e de emoji. Lido via host.profile.
sessions.jsonsimCada sessão salva: slot, id/versão da ferramenta, rótulo, miniatura (data-URL) e todos os dados de entrada. Lido via host.state.
assets.jsonsimMetadados de cada ativo enviado (imagens, fontes, tokens de marca, logos, cópias salvas de downloads), cada um apontando para seus bytes em assets/blobs/.
assets/blobs/<n>.<ext>por ativoOs bytes brutos do ativo (arquivos de imagem e de fonte). Armazenados sem compressão (formatos já comprimidos). A extensão é cosmética. O MIME em assets.json é a autoridade.
assets/blobs/<n>.c2paquando presenteContent Credentials extraídas como bytes binários exatos, referenciadas por _credentialFile no registro do ativo. Não são chaves de assinatura do dispositivo.
design-systems.jsonquando presenteOs design systems criados ou adicionados nesta instalação, como { active, records }. Mesclado por id na importação; a escolha ativa do pacote se aplica só quando o destino não tem design system próprio.
file-history.jsonopcionalSnapshots de ativos versionados, relatórios de operação de arquivo do terminal e manifestos de lote completos. A parte de histórico tem versão própria; fornecida pelo adaptador de backup interno fileHistory do shell.
revision-history.jsonopcional, backups manuaisIDs de criação estáveis, checkpoints retidos, miniaturas e rascunhos rotativos de recuperação. Fornecida por host.state.history.backup onde suportado.
file-history/versions/por snapshotBytes anteriores do ativo e credenciais extraídas, independente de o ativo atual ainda existir.
file-history/results/por operação concluídaBytes de saída exatos. Nenhum arquivo original selecionado para conversão é retido ou incluído.
prefs.jsonsimPreferências locais do próprio usuário: theme, sidebarWidth e a contagem de atividade ct-metrics.
lolly.txtsimUm resumo legível do pacote (contagens, perfil, nome do arquivo) para quem abrir o zip sem o Lolly. Regenerado a cada exportação e reconhecido na importação, então nunca conta como uma parte pulada. É escrito depois do mapa de integridade, então fica fora dele.

O pacote é um zip simples de propósito: sobrevive a qualquer transporte intacto, e qualquer ferramenta de descompactação consegue inspecioná-lo.

profile.json é a menor parte e a primeira que um leitor vê no app: os detalhes que uma produtora preenche uma vez, mais o opt-in que permite às ferramentas usá-los.

O formulário de detalhes do Perfil que se torna profile.json: nome, dados de contato e foto de perfil signed by Lollyvector SVG15 paths~1.2k nodes30 groups1 image40 KBVerifique você mesmoBaixar o arquivo assinado

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-…"
  }
}
FieldMeaning
formatSempre lolly-backup. Um arquivo sem isso é rejeitado como "not a Lolly backup".
formatVersionO layout com que este pacote foi escrito. Incrementado a cada mudança no conjunto de partes ou formas. Os leitores não se baseiam nele.
minReaderA versão mínima de leitor necessária para importar este pacote com segurança. É neste campo que os leitores se baseiam.
appId do app produtor, para diagnóstico.
exportedAtTimestamp ISO de quando o pacote foi criado.
countsO que o gravador colocou, para exibição e verificação de sanidade.
integrityOpcional. Mapeia cada parte, exceto manifest.json, a um digest no estilo SRI sha256-<base64> dos seus bytes não compactados.

Política de versão (compatibilidade futura)

A separação entre formatVersion e minReader é o que permite ao formato crescer sem deixar instalações antigas órfãs:

Regra prática para autores: se todo leitor existente ainda se comportaria corretamente ao ignorar sua adição, ela é aditiva - incremente formatVersion, deixe minReader. Caso contrário, eleve minReader.

Integridade

Quando manifest.integrity está presente, um leitor verifica o SHA-256 de cada parte listada antes de escrever qualquer coisa. Uma divergência ("failed its integrity check") ou uma parte ausente ("incomplete") aborta toda a importação - não há restauração parcial. Isso captura a corrupção que um transporte de arquivo pode introduzir (um AirDrop truncado, um gateway de e-mail que recodificou o anexo, um setor de USB ruim).

A integridade é best-effort por design: só é escrita onde a Web Crypto está disponível (todo contexto seguro de navegador e Node moderno), e só é verificada quando tanto o mapa quanto a Web Crypto estão presentes. Um pacote sem o mapa - por exemplo, um de antes de a integridade existir - é importado sem alteração. "Não é possível verificar" nunca é tratado como "corrompido".

O manifesto não lista nem a si mesmo nem o README lolly.txt regenerado. Os digests cobrem as partes que o manifesto atesta.

Semântica de importação

A importação é uma mesclagem, nunca substituição total:

Sessões salvas se reconectam automaticamente às suas imagens: as referências de ativos são mantidas por id, e a ponte as resolve novamente depois que as imagens enviadas são restauradas (ela precisa fazer isso de qualquer forma, porque URLs blob: não sobrevivem a uma recarga).

O resumo de importação reporta { profile, sessions, userAssets, prefs, skipped, failedAssets }. failedAssets conta os ativos enviados que não puderam ser restaurados (armazenamento do dispositivo cheio, por exemplo). É distinto de skipped, que conta partes de um gravador mais novo e compatível para trás que esta build não reconheceu. A interface exibe skipped ("… · N newer items skipped"), então a restauração é honesta sobre o que deixou para trás.

Quando o histórico de arquivo está presente, o resumo também carrega assetVersions, fileOperations e failedHistory. Esgotamento de armazenamento ou conflitos de ID imutável podem causar uma restauração parcial; a interface diz ao usuário para manter o backup de origem. A sincronização na nuvem não avança sua revisão aplicada depois de uma restauração parcial ou não suportada, então o snapshot continua disponível para nova tentativa. Restaurar não é uma única transação em todos os armazenamentos de perfil/sessão/ativo/histórico.

Histórico de criação (v3)

Backups manuais de um host web com capacidade de histórico incluem revision-history.json com seu próprio esquema { version: 1, documents, revisions, recoveries }. Ele carrega os IDs retidos, snapshots canônicos de entrada, marcas de versão, prévias raster e rascunhos de redator separados. O adaptador de histórico captura as sessões atuais e suas pontas em uma única transação de leitura; sessions.json usa esses mesmos snapshots atuais para leitores mais antigos.

A restauração verifica o SHA-256 e as contagens de bytes do payload, identidades únicas, relações documento/head, ancestralidade, timestamps, tipos de prévia e limites antes de confirmar o arquivo em uma única transação. Referências de pai compactadas podem estar ausentes. O trabalho atual existente nunca é substituído silenciosamente: quando uma criação está presente nos dois lados, o lado que não é mantido como atual se torna um rascunho protegido. O limite de transferência de 384 MiB do arquivo é verificado explicitamente, e os limites de armazenamento são aplicados sem truncar checkpoints retidos. O backup geral ainda usa uma implementação de ZIP em memória e não é um arquivo em streaming.

O resumo adiciona revisions e recoveryDrafts, contando só o que esta importação adicionou, e added, kept, replaced, copies e hidden para como cada criação foi mesclada. Um shell sem essa capacidade restaura sessões comuns e relata a parte de histórico como pulada. O histórico de sistema de arquivos nativo continua sem suporte até seu adaptador fornecer transações de histórico duráveis. O estado de convidado P2P não tem histórico durável nem arquivo de recuperação.

A sincronização de snapshot pessoal exclui explicitamente o histórico de criação. Aplicar um snapshot a um documento local com histórico preserva seu estado de trabalho anterior como um rascunho de recuperação separado e invalida o token de escrita de qualquer editor aberto. Seus checkpoints imutáveis permanecem no dispositivo. Isso protege o histórico local durante a substituição de snapshot; não mescla históricos de dispositivos concorrentes.

Referências históricas de ativos são retidas, enquanto a renderização ainda resolve os ativos pela biblioteca existente do destino. Este arquivo ainda não garante bytes exatos de ativos antigos nem renderizações antigas da ferramenta. Bytes de versão de ativo e de resultado de arquivo continuam viajando pela sua própria parte de backup separada.

Versões salvas e resultados de arquivo (v2)

A parte opcional de histórico contém { version: 2, assetVersions: [...], operations: [...], batches: [...] }; leitores também aceitam o formato anterior history-v1 sem lotes. Cada snapshot identifica o ID estável do ativo e a versão exata, seu horário de salvamento, o comprimento em bytes e o SHA-256 hex, mais um registro de ativo cujo _file e _credentialFile opcional apontam para partes binárias. Operações carregam os fatos originais do arquivo, requisição, relatório, timestamps e _file de resultado opcional; nomes de backend de armazenamento, handles OPFS e leases de execução não viajam. Leitores mais antigos, só de history-v1, recusam a nova versão de histórico antes de importar, em vez de descartar silenciosamente a associação de lote.

Manifestos de lote registram cada fonte selecionada antes do processamento, incluindo arquivos nunca lidos, membros cancelados, falhas ao reservar espaço de resultado e trabalho interrompido. Cada membro tem um ID de operação estável, referência/fatos de fonte, nome de saída solicitado e relatório terminal. Uma fonte não lida tem fatos declarados, não um digest inventado. A importação valida a identidade do membro e a consistência com qualquer relatório de operação carregado. Relatórios de lote continuam disponíveis quando resultados individuais foram removidos explicitamente, mas um recibo não implica que os bytes de saída ainda estejam armazenados.

Leitores mais antigos ainda aceitam o envelope v2 (minReader: 1) e restauram as partes conhecidas, contando partes de histórico não suportadas como puladas. A recuperação completa de histórico exige um shell com o adaptador fileHistory; isso é uma costura interna do shell, não uma nova capacidade HostV1 voltada para a ferramenta. A restauração real entre dois dispositivos é coberta pelo portão local do Chromium; a aceitação de recuperação instalada do Tauri/iOS/Android continua separada.

O que não viaja

O medidor de armazenamento discrimina a mesma divisão. Sessões salvas, My images e File results & versions viajam em um pacote. O cache de ativos, prévias de ferramenta e fixações offline abaixo deles são todos rederiváveis, então ficam para trás.

O medidor de armazenamento dividindo os dados deste dispositivo em categorias nomeadas, com Saved sessions e My images rastreados separadamente do Asset cache, aqui em uma instalação nova onde toda categoria ainda está vazia signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBVerifique você mesmoBaixar o arquivo assinadoO medidor de armazenamento dividindo os dados deste dispositivo em categorias nomeadas, com Saved sessions e My images rastreados separadamente do Asset cache, aqui em uma instalação nova onde toda categoria ainda está vazia signed by Lollyvector SVG37 paths~4.9k nodes85 groups65 KBVerifique você mesmoBaixar o arquivo assinado

Garantia entre shells

data-transfer.ts lê e escreve exclusivamente pela bridge de capacidade (host.profile, host.state, host.assets) e pelas preferências compartilhadas de localStorage. O mesmo módulo lê e escreve o envelope comum na web e no Tauri, sobre armazenamento IndexedDB ou de sistema de arquivos. Partes de histórico opcionais aparecem só onde o adaptador correspondente está disponível; uma parte não suportada é relatada como pulada na importação. A suíte headless exercita as partes comuns contra uma bridge em memória, enquanto as transações de histórico também têm testes em navegador real.

Dois shells ficam fora dessa garantia, por motivos diferentes:

Pontos de extensão reservados

O envelope é, por design, um manifesto mais um conjunto de partes nomeadas, então novos tipos de dado portável podem viajar nele mais tarde sem uma mudança que quebre compatibilidade. Eles se encaixam como partes aditivas (novo formatVersion, mesmo minReader), e o leitor de hoje pula o que não reconhece. Essas partes ainda não foram construídas. Os nomes são reservados aqui para que o formato continue coerente quando elas chegarem.

Qualquer coisa fora desses nomes reservados e das partes acima é, para um leitor, uma parte desconhecida: deixada intocada e contada em skipped.

Referência