Экспорт: ключ архива из секрета аккаунта через HKDF, заголовок ровно 65 байт (magic, версия, соль, 32 сырых байта отпечатка владельца, iv) и он же целиком AAD шифротекста. Ника владельца в файле нет. Импорт сверяет отпечаток до расшифровки, сливает идемпотентно по id, а записи peers берёт только для ников, которых в локальном TOFU ещё нет: архивом доверие к ключу не перебить. Настройки: устройства с датой и пометкой «это устройство», «занято N МБ», кнопка «экспортировать» в подтверждении выхода и в подтверждении входа под другим ником — долг этапа 1 и обещание ADR-029 закрыты. Лента: страницы по 50 с подгрузкой вверх без прыжка прокрутки; новая страница вставляется, а не пересобирает ленту. ADR-050: импорт не перезаписывает лежащую запись — у своей есть состояние отправки, которого в архиве нет. ADR-054: архив — недоверенный ввод. Ревью собрало архивы с ts вне диапазона Date, мусорным lastId, ником с bidi-переопределением и roomId с обходом пути: каждый из них навсегда ломал ленту или счётчик. Теперь форма ника, roomId, id, ts и автора проверяется, а lastId из файла не читается вовсе. ADR-051, 052, 053: тексты и кнопки, устройства и место, страницы ленты без виртуализации. Приёмка: формат сверен побайтно на модулях, скачанных с боевого сервера, — смещения заголовка, отпечаток сырыми байтами, новые соль и iv на каждый экспорт, подмена любого байта заголовка ломает расшифровку, чужой секрет не открывает, семь видов битых файлов отвергнуты. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
26 lines
3.9 KiB
Markdown
26 lines
3.9 KiB
Markdown
# ADR-052: Своё устройство из настроек не удаляется; занятое место — оценка браузера
|
|
|
|
## Контекст
|
|
|
|
`docs/ui.md` описывает раздел «устройства» одной строкой: список `id` (первые 8 символов), дата, «это устройство», «удалить». Три вещи в ней не решены, а решить их надо в коде.
|
|
|
|
Первая — что делает «удалить» у своей строки. `DELETE /api/devices/{id}` уносит очередь, подписку и сессии устройства (`docs/protocol.md`). На своём это означает: следующий же запрос получает `401 unauthenticated` и по `docs/ui.md` («Сеть и состояния») уводит на экран входа с целой IndexedDB. Выходом это не является: «выйти» стирает историю и сначала предлагает её сохранить (ADR-051). Получается третье состояние, которого в документе нет, — выход без вопроса и без стирания, с прежним `deviceId` в `meta`, который при следующем входе заведёт устройство заново.
|
|
|
|
Вторая — какая дата. Сервер отдаёт две: `createdAt` и `lastSeen`.
|
|
|
|
Третья — что такое `N` в «занято N МБ». Байты `storage.estimate()` в мегабайтах дают дробь с десятком знаков, а браузер, который `estimate()` не умеет, не даёт и её.
|
|
|
|
## Решение
|
|
|
|
- Своё устройство из раздела не удаляется. У своей строки вместо кнопки стоит пометка «это устройство». Отцепляет текущее устройство «выйти»: там и вопрос про историю, и стирание базы.
|
|
- Сервер не меняется: `DELETE /api/devices/{id}` принимает любое своё устройство, включая текущее. Запрет — правило экрана, а не протокола: устройство, потерявшее сессию с чужой руки, обязано оставаться рабочим сценарием.
|
|
- Дата в строке — дата появления устройства (`createdAt`), в местной зоне, цифрами: `22.08.2026`. `lastSeen` не показывается: список нужен, чтобы узнать своё среди чужих и отцепить лишнее.
|
|
- «занято N МБ» — `usage` из `navigator.storage.estimate()`, МБ равен 1024×1024 байтам. Число человеческое: до десятых, пока меньше десяти, дальше целое; десятые округляются вверх, потому что пара сотен килобайт — это не «0 МБ». Браузер без `estimate()` строки не получает: писать в неё нечего.
|
|
- Записано в `docs/ui.md`, «Настройки».
|
|
|
|
## Следствия
|
|
|
|
- Потерянное устройство отцепляется с любого другого; текущее — выходом.
|
|
- Кнопки «удалить» у своей строки нет никогда, даже когда устройство одно.
|
|
- Занятое место — оценка происхождения целиком, а не сумма длин записей: индексы и служебные страницы IndexedDB тоже место. Она же намеренно грубая у самого браузера, и точнее показывать нечего.
|