Усе, що накопичує користувач Lolly, зберігається на його пристрої - без облікового запису, без хмари. Пакет передавання даних - це спосіб перенести це: експортуйте його на одній інсталяції, перенесіть файл будь-яким способом (USB, AirDrop, лист собі на пошту, мережевий спільний ресурс) і імпортуйте на іншій. Файл і є транспорт. Цільовий пристрій може бути офлайн або онлайн. Це не має значення, оскільки нічого й ніколи не звертається до сервера.
signed by Lollyvector SVGПеревірте саміGet the signed file12 paths~5.1k nodes12 groups60 KB
signed by Lollyvector SVGПеревірте саміGet the signed file12 paths~5.1k nodes12 groups60 KB
Ця сторінка - специфікація формату. Покроковий опис для кінцевого користувача - у Using Lolly → Moving to another device. Реалізація - shells/web/src/data-transfer.ts, а tests/data-transfer.test.ts фіксує контракт двостороннього обміну.
Обсяг. Пакет переносить дані користувача, а не інструменти. Інструменти та ресурси каталогу синхронізуються окремо, і передбачається, що вони вже присутні на цільовому пристрої (у гіршому випадку - вищої версії). Імпорт ніколи не встановлює й не оновлює інструмент.
Цілі
- Один формат, кожна оболонка. Ті самі байти створюються й споживаються веб-PWA, застосунками Tauri для настільних і мобільних пристроїв та будь-якою майбутньою оболонкою. Пакет - це контракт. Міст можливостей кожної оболонки - це адаптер для конкретної платформи за ним.
- Переживає перенесення. Пакет, пошкоджений або обрізаний під час передавання, помітно завершується невдачею при імпорті, ніколи не відновлюється наполовину.
- Переживає цю версію. Старіший застосунок усе одно може імпортувати розпізнані частини новішого пакета. Дійсно несумісний формат відхиляється чисто.
- Безпечний для злиття. Імпорт в інсталяцію, яка вже використовується, ніколи не стирає нічого, чого не було в пакеті.
Конверт
Пакунок - це звичайний .zip. Завантажений файл названо на честь особи, якій він належить, - LollyTools-<First>-<Last>-<YYYY-MM-DD>-<n>.zip (наприклад, LollyTools-Ada-Lovelace-2026-06-26-1.zip) - тож папка Downloads із резервними копіями лишається зрозумілою. Частини First і Last беруться з профілю і опускаються, якщо не задані. Без профілю виходить LollyTools-2026-06-26-1.zip, а лише з іменем - LollyTools-Ada-2026-06-26-1.zip. Кожна частина очищається до безпечного для імені файлу токена (літери й цифри Unicode зберігаються, пробіли й розділові знаки прибираються, обмеження - 32 символи). <n> - це послідовність на день і на пристрій, тож повторні експорти того самого дня не конфліктують і зберігають порядок. Ім'я формує backupFilename() у файлі shells/web/src/data-transfer.ts. Вміст zip-архіву однаковий незалежно від імені. Всередині:
| Шлях | Обов'язково | Вміст |
|---|---|---|
manifest.json | так | Ідентифікатор формату, версії, кількість і цілісність по кожній частині. Перше, на що дивиться читач. |
profile.json | якщо задано | Запис me користувача (ім'я, контакти, посилання на фото, прапорці). Читається через host.profile. |
sessions.json | так | Кожен збережений сеанс: слот, ідентифікатор/версія інструмента, мітка, мініатюра (data-URL) і повні дані введення. Читається через host.state. |
assets.json | так | Метадані для кожного завантаженого ресурсу (зображення, шрифти, токени бренду), кожне вказує на свої байти під assets/blobs/. |
assets/blobs/<n>.<ext> | для кожного ресурсу | Сирі байти ресурсу (файли зображень і шрифтів). Зберігаються нестиснутими (уже стиснуті формати). Розширення - косметичне. Авторитетний джерелом є MIME в assets.json. |
prefs.json | так | Локальні налаштування, що належать користувачу: theme, sidebarWidth та лічильник активності ct-metrics. |
lolly.txt | так | Читабельний людиною підсумок пакета (кількості, профіль, назва файлу) для будь-кого, хто відкриє zip без Lolly. Перегенеровується під час кожного експорту і розпізнається при імпорті, тому ніколи не рахується як пропущена частина. Записується після карти цілісності, тому лишається поза нею. |
Пакет навмисно є звичайним zip-архівом: він неушкоджено переживає будь-який транспорт, і будь-який засіб розпакування може його оглянути.
profile.json - найменша частина і та, яку читач бачить першою в застосунку: дані, які виробник заповнює один раз, разом зі згодою, що дозволяє інструментам їх використовувати.
signed by Lollyvector SVGПеревірте саміGet the signed file18 paths~2.0k nodes41 groups30 KB
signed by Lollyvector SVGПеревірте саміGet 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-…"
}
}
| Поле | Значення |
|---|---|
format | Завжди lolly-backup. Файл без нього відхиляється як «не резервна копія Lolly». |
formatVersion | Компонування, з яким записано цей пакет. Збільшується при будь-якій зміні набору чи форми частин. Читачі не орієнтуються на нього. |
minReader | Мінімальна версія читача, необхідна для безпечного імпорту цього пакета. Саме на це поле орієнтуються читачі. |
app | Ідентифікатор застосунку, що створив пакет, для діагностики. |
exportedAt | ISO-мітка часу створення пакета. |
counts | Що записувач поклав у пакет, для відображення та перевірки коректності. |
integrity | Необов'язково. Зіставляє кожну частину, крім manifest.json, з дайджестом у стилі SRI sha256-<base64> її нестиснутих байтів. |
Політика версій (пряма сумісність)
Розділення між formatVersion і minReader дозволяє формату зростати, не залишаючи старіші інсталяції осторонь:
- Читач імпортує пакет, коли
manifest.minReader ≤за його власну версію читача. Він відмовляє (з повідомленням «потрібна новіша версія застосунку») лише тоді, коли пакет явно вимагає новішого читача. - Адитивна зміна - нова необов'язкова частина або нове необов'язкове поле маніфесту - збільшує
formatVersion, але залишаєminReaderнезмінним. Старіші застосунки все одно імпортують кожну розпізнану частину. Нерозпізнані частини пропускаються (див. нижче), а не відкидаються мовчки. - Критична зміна - коли неправильний імпорт частини псує дані або коли раніше необов'язкова частина стає обов'язковою - підвищує
minReader. Тоді старіші застосунки чисто відмовляють замість того, щоб імпортувати те, з чим не можуть впоратись. - Якщо майбутній пакет задає
formatVersion, але пропускаєminReader, читачі обережно орієнтуються наformatVersion(розглядають зміну як критичну).
Правило для авторів: якщо кожен наявний читач усе одно поводитиметься правильно, просто ігноруючи ваше доповнення, - це адитивна зміна: збільшуйте
formatVersion, залишайтеminReader. Інакше підвищуйтеminReader.
Цілісність
Коли присутнє manifest.integrity, читач перевіряє SHA-256 кожної переліченої частини перед тим, як щось записати. Невідповідність («не пройшла перевірку цілісності») або відсутня частина («неповний») перериває весь імпорт - часткового відновлення немає. Це вловлює пошкодження, яке може внести транспорт файлу (обрізаний AirDrop, поштовий шлюз, що перекодував вкладення, поганий сектор USB).
Цілісність за задумом реалізована як найкраще можливе: вона записується лише там, де доступний Web Crypto (кожен захищений контекст браузера й сучасний Node), і перевіряється лише коли присутні і карта, і Web Crypto. Пакет без карти - наприклад, створений до появи цілісності - імпортується без змін. «Неможливо перевірити» ніколи не трактується як «пошкоджено».
Маніфест не перелічує ні себе, ні перегенерований README lolly.txt. Дайджести охоплюють ті частини, за які маніфест ручається.
Семантика імпорту
Імпорт - це злиття з перезаписом, ніколи не повна заміна:
- Наявні дані на цільовому пристрої лишаються на місці.
- Будь-який ключ, що конфліктує, - профіль, слот сеансу, ідентифікатор завантаженого зображення - замінюється імпортованою копією.
- Ніщо, чого не було в пакеті, не зачіпається. Сеанс, який був на цільовому пристрої, але якого не було в пакеті, переживає імпорт.
Збережені сеанси автоматично пере-прив'язуються до своїх зображень: посилання на ресурси зберігаються за ідентифікатором, і міст повторно розв'язує їх після відновлення завантажених зображень (це необхідно в будь-якому разі, оскільки URL-адреси blob: не переживають перезавантаження).
Підсумок імпорту повідомляє { profile, sessions, userAssets, prefs, skipped, failedAssets }. failedAssets рахує завантажені ресурси, які не вдалося відновити (наприклад, сховище пристрою заповнене). Це відрізняється від skipped, яке рахує частини від сумісного з майбутнім новішого записувача, які ця збірка не розпізнала. Інтерфейс показує skipped («… · N новіших елементів пропущено»), тож відновлення чесно повідомляє, що воно залишило поза увагою.
Що не переноситься
- Кеші каталогу (завантажені метадані ресурсів і бінарні дані, індекс інструментів) - безкоштовно повторно синхронізуються на цільовому пристрої.
- Інструменти та ресурси бренду - поза межами охоплення, і передбачається, що вже присутні на цільовому пристрої.
- URL-адреси
blob:/ об'єктні URL - перегенеровуються мостом при завантаженні. - Лічильник послідовності експорту - лічильник іменування завантажень за день (ключ
localStoragelolly-export-seq) - це локальна зручність іменування. Він виведений за межіPREF_KEYS, тому ніколи не потрапляє в пакет.
Індикатор сховища деталізує той самий розподіл. Збережені сеанси та My images потрапляють у пакет. Кеш ресурсів, попередні перегляди інструментів та офлайн-закріплення нижче них усі відновлювані заново, тому лишаються осторонь.
signed by Lollyvector SVGПеревірте саміGet the signed file25 paths~4.8k nodes46 groups59 KB
signed by Lollyvector SVGПеревірте саміGet the signed file25 paths~4.8k nodes46 groups59 KB
Гарантія міжоболонкової сумісності
data-transfer.ts читає та записує виключно через міст можливостей (host.profile, host.state, host.assets) і спільні налаштування localStorage. Оскільки міст - єдина точка з'єднання, той самий модуль дає побайтово ідентичний пакет на кожній оболонці, навіть попри те, що сховище під ним відрізняється - IndexedDB у вебі, файлова система в Tauri. Оболонки Tauri використовують цей модуль без змін. Відрізняється лише їхня реалізація host.state. Безголовий тест перевіряє повний цикл туди-й-назад проти сховища в пам'яті, тому він репрезентує їх усі.
Дві оболонки виходять за межі цієї гарантії, з різних причин:
- Одноразовий CLI не має що переносити - його стан перебуває в пам'яті і зникає після кожного виклику.
- TUI справді зберігає стан (
~/.lolly: сесії, теки, профіль), і його представлення Profile може створювати резервну копію, але записує простіший власний архів:sessions/<slot>.jsonдля кожної сесії плюсprofile.jsonіfolders.json, без маніфесту, безformatVersion/minReaderі без карти цілісності. Він не імпортується в цьому форматі - зчитувач відхиляє його як "не резервна копія Lolly" - і, заплутуючи, використовує схожу назву (lolly-backup-<stamp>.zip). Об'єднання цих двох - відома прогалина.
Зарезервовані точки розширення
Конверт спроєктовано як маніфест плюс набір іменованих частин, щоб нові види портованих даних могли долучитися пізніше без критичних змін. Вони вбудовуються як додаткові частини (нова formatVersion, той самий minReader), і сьогоднішній зчитувач пропускає те, чого не розпізнає. Це є в дорожній карті, але ще не реалізовано. Назви зарезервовано тут, щоб формат лишався узгодженим, коли вони з'являться.
tokens.json- токени дизайну. Документ токенів дизайну W3C DTCG (формат, який імпортує й експортує Penpot - токени з$value/$type/$description, організовані в групи, набори та теми). Набір токенів у пакеті дозволяє користувачу переносити примітиви свого бренду між інсталяціями разом із сесіями. У перспективі імпортований набір токенів стане повноцінним джерелом, проти якого інструменти й активи палітри виконуватимуть звірку.penpot/- імпортовані файли Penpot. Зарезервований каталог для файлу Penpot (або його вилученої, релевантної для Lolly підмножини), імпортованого й представленого як інструмент. Пакет нестиме імпортоване визначення, тож воно подорожуватиме разом з рештою даних користувача.
Усе поза цими зарезервованими назвами та частинами вище є для зчитувача невідомою частиною: залишається незміненим і враховується в skipped.
Довідка
- Модуль:
shells/web/src/data-transfer.ts(exportBackup,importBackup,BACKUP_FORMAT,BACKUP_FORMAT_VERSION,BACKUP_READER_VERSION- іменувачbackupFilename()внутрішній). - Контрактний тест:
tests/data-transfer.test.ts- випадки циклу туди-й-назад, злиття, цілісності, зворотної сумісності та шлюзу зчитувача. - Використана поверхня моста:
host.profile,host.state,host.assets- див. Host API.