12 KiB
Тёплый ре-маунт: возврат на вкладку без «перезагрузки»
Статус: реализовано (dev, DEV-B703-01…03). Код:
src/houseplan-card.ts, модульная памяткаwarmBoot. Смоки:demo/smoke_warm_remount.mjs,demo/smoke_warm_dialogs.mjs.
1. Что вообще происходит
Lovelace пересоздаёт элемент карточки — выбрасывает старый DOM-элемент и создаёт новый — когда websocket переподключается после долго свёрнутой вкладки, при перестройке дашборда и при переключении видов. Страница при этом никуда не девается, но экземпляр карточки умирает, и вместе с ним умирает всё, что жило в полях экземпляра.
Для владельца это выглядит как «карточка перезагрузилась»:
- мигал прелоадер (закрыто DEV-B703-01);
- «чуть-чуть дёргается масштаб» (DEV-B703-03, §2);
- исчезают открытые диалоговые окна (DEV-B703-03, §3) — это и есть прямое доказательство пересоздания: состояние диалога больше нигде не хранится.
Лекарство одно и то же: модульная памятка warmBoot — обычный Map,
который живёт в загруженном JS-модуле, а не в экземпляре. Ключ:
`${window.innerWidth}x${window.innerHeight}|${JSON.stringify(config)}`
Смена размера окна → промах → полный защитный бут (единственный случай, когда хром HA может реально пересобраться заново). Одинаковый конфиг дважды на одной странице неразличим — это задокументированное ограничение (высоты у них всё равно совпадают).
2. Рывок масштаба: фактическая причина
Памятка хранила только hdrH/stageH. Этого мало, потому что:
- Пан (
_view) не переживал экземпляр вообще. Единственным, что восстанавливалось, был зум — изlocalStorage(LS_ZOOM, per space). Новый экземпляр приходил с_view = null,updated()→_refitView(), а затем_loadFromServer()звал_restoreZoom(), который центрирует план (_applyView(z, центр vb)). Вид, отъеханный в угол, возвращался в центр. Измерено смоком: до пересозданияview.x=50, y=493, после —x=250, y=293при zoom 2.2. - Зум редактора не сохраняется намеренно (
_saveZoom()выходит при_mode !== 'view': рабочий зум редактора — инструмент, а не «как я хочу смотреть»), а режим редактора сохраняется (LS_NAV). Значит ре-маунт внутри редактора возвращался в тот же редактор, но на зуме ПРОСМОТРА: измерено 3.0 → 1.0. _viewModeSnap(виджет «куда вернуться при выходе из редактора») умирал вместе с экземпляром — выход из редактора после ре-маунта прыгал второй раз._showFar(«показать дальние объекты») меняет_baseVb(), то есть саму систему координат, против которой клампится вид.
Правило теперь: памятка хранит не «зум», а весь вьюпорт:
пространство, режим, зум, сам прямоугольник _view, _viewModeSnap,
_showFar, а также инструмент редактора (_tool, _decorTool), выделение
(_selId, _rszSel, _decorSel) и локальный переключатель «показать
скрытые» (_showHidden). Восстановление даёт бит-в-бит тот же прямоугольник,
а не «тот же зум». _loadFromServer() в этом случае не зовёт
_restoreZoom() — центрирующее восстановление осталось только для настоящей
навигации (хеш/LS_NAV привели на другое пространство).
Диплинк #space=<id> — явная навигация и по-прежнему сильнее памятки.
Памятка обновляется на каждом updated() (метод _warmSnapshot()), то
есть всегда отражает последний ОТРИСОВАННЫЙ кадр. Родиться запись может только
из осевшей геометрии (_bootSettled), поэтому холодный бут по-прежнему платит
полный защитный цикл.
3. Диалоги переживают пересоздание
В той же записи памятки лежит dlg: { kind, space, mode, data }, где
data — живой объект-черновик. Памятка — состояние модуля, её никто не
сериализует, поэтому наполовину заполненный диалог устройства вместе с
загруженными PDF переезжает бесплатно и без потерь.
Восстановление (_warmReviveDialog) происходит не в setConfig, а на
следующий такт после connectedCallback: Lovelace в момент setConfig может
ещё держать старый элемент, а живому владельцу диалог красть нельзя.
Условия воскрешения — все обязательны:
- запись памятки есть (тёплый ре-маунт, а не холодный старт);
dlgне пуст — то есть в последнем отрисованном кадре прошлого экземпляра диалог был открыт;- прошлый экземпляр отсоединился (
freed), и с этого момента прошло не большеWARM_REVIVE_MS(10 c). Перестройка Lovelace укладывается в один такт; ушедший на другой дашборд и вернувшийся через полчаса пользователь не должен встречать диалог, о котором он давно забыл; - то же пространство и тот же режим (
d.space/d.mode); - одноразово:
dlgсъедается в момент попытки восстановления. Третий экземпляр диалог уже не увидит, зомби невозможен.
Осознанное закрытие (Esc / Отмена / Сохранить) чистить памятку отдельно не
нужно: следующий же updated() запишет dlg: null. Это сильнее ручной
чистки — ни один путь закрытия нельзя забыть.
3.1. Что восстанавливается с черновиком
_spaceDialog (настройки/создание пространства), _markerDialog (устройство),
_settingsDialog (общие настройки), _rulesDialog (правила иконок),
_openingDialog (проём), _decorTextDialog (надпись), _roomDialog вместе со
всей своей обвязкой (_roomEditId, _roomFill, источники температуры и
влажности, масштабы подписей, _areaSel/_nameSel, _pendingSplit, _path).
Информационные попапы — _infoCard (карточка устройства) и _openingInfo —
восстанавливаются по id, а не по объекту: конфиг мог перезагрузиться под
нами, и карточка, отрисованная из устаревшего объекта, была бы враньём. Если
объекта с таким id больше нет — попап просто не открывается.
3.2. Что НЕ восстанавливается — и почему
| Не воскрешаем | Причина |
|---|---|
«Выровнять всё по сетке» (_alignDialog) |
Модалка, всё содержимое которой — «нажмите OK, и я перепишу ваш план». Воскресить подтверждение рядом с человеком, который только что вернулся на вкладку, — прямой путь к слепому клику по разрушающей записи. Открывается одним нажатием из шестерёнки. |
Подтверждение объединения комнат (_mergeDialog) |
Тот же класс: подтверждение необратимой правки геометрии. |
Подтверждение действия по тапу (_tapConfirm) |
Держит замыкание exec на мёртвый экземпляр. |
Мастер импорта этажей (_importDialog) |
updated() сам открывает его, пока конфиг пуст; воскрешение удвоило бы очередь. |
Любой диалог с busy: true |
Запись/загрузка была в полёте. Новый экземпляр не знает, доехала ли она; показать «Сохранить» ещё раз — это приглашение записать дважды. Правду покажет перезагрузка конфига. |
| Киоск | У киоска памятки нет вообще (100dvh, ничего не оседает, редактирование недоступно). |
Правило одной строкой: воскрешаем черновик, не воскрешаем решение.
4. Что осознанно отложено
Ре-маунт по-прежнему теряет мелкое промежуточное состояние жестов, у которого
нет осмысленного «продолжения» после смены экземпляра: незавершённый контур
рисования вне диалога комнаты (_path/_cursorPt), стек отмены ресайза
(_rszUndo), выбор разреза (_splitSel, _mergeSel), черновик фигуры декора
(_decorDraft), незавершённое перетаскивание подложки (_bdDrag), тост
(_toast). Все они живут внутри одного жеста; пересоздание элемента жест и так
прерывает.