Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
51 KiB
#462 — Надёжная регистрация frontend-ресурса и восстановление после обновления
- Issue: https://github.com/Matysh/houseplan-card/issues/462
- Тип / приоритет: bug / P1
- Трек: полный; меняются backend lifecycle, frontend recovery-controller и обязательный View/kiosk UX
- Оценка: пользовательская ценность 10/10; ценность для разработки 8/10; сложность 7/10; риск 8/10
- Связано: #2, #295, #353, #412;
docs/SCOPE.md,docs/TOUCH-SUPPORT.md,docs/UX-MODES.md,docs/CONFIG-COMPATIBILITY.md
1. Сценарий
Администратор устанавливает House Plan через HACS, добавляет интеграцию и перезапускает Home Assistant. Либо обновляет уже установленную интеграцию. В открытой вкладке карточка отсутствует в picker или продолжает исполнять старый frontend bundle, хотя backend и файл новой версии уже доступны.
Пользователь должен получить три независимых уровня помощи:
- корректную инструкцию для своего режима Lovelace;
- видимую диагностику способа подключения ресурса на стороне HA;
- понятное восстановление при несовпадении уже загруженной карточки и backend.
На настенной панели в kiosk обновление применяется само, но только в момент, когда перезагрузка не уничтожит действие пользователя и не создаст reload-loop.
2. Что человек увидит до и после
До: документация предлагает невалидный верхнеуровневый resources:,
storage-пользователь применяет YAML, который его dashboard игнорирует, отказ
авторегистрации скрыт в логе, а старая вкладка выглядит как актуальная. Иногда
помогает только угаданный Ctrl+F5.
После: документация отдельно описывает storage и YAML; после первой доступной регистрации HA один раз сообщает, что нужно полностью перезагрузить frontend; System Health показывает файл, статический путь, способ регистрации и URL. Уже загруженная full card при несовпадении версий показывает компактную плашку с перезагрузкой. Kiosk выполняет не более одной тихой перезагрузки для конкретной целевой backend-версии и при неуспехе показывает ту же плашку.
3. Подтверждённая причина
На dev 16b4c2d6 (v1.72.0-beta.4) подтверждены все исходные разрывы:
- README и оба User Guide показывают плоский
resources:безlovelace:и не разделяют storage/YAML mode; _register_lovelace_resource()возвращает одинFalseдля отсутствующего реестра, YAML mode и исключения, поэтому lifecycle не может отличить устойчивый fallback от временной гонки;- fallback
add_extra_js_url()невидим в UI и не ожидается Lovelace перед построением dashboard; manifest.jsonне содержитafter_dependencies: ["lovelace"], повторной попытки нет;- System Health содержит только статистику плана;
- backend уже отдаёт
integration_version, а full card уже знаетCARD_VERSION, но сравнение живёт в lazy editor runtime и не защищает View или kiosk; - браузер не может переопределить уже зарегистрированный custom element без
reload документа. Кэш-бастинг
?v=<VERSION>исправен и не является причиной.
Полевой сценарий #462 завершился после Ctrl+F5, поэтому задача не меняет путь
раздачи или стратегию кэш-бастинга: она делает существующий механизм надёжным и
наблюдаемым.
Гипотеза о штатной startup-race не подтверждена: и в минимально
поддерживаемом HA 2024.6, и в актуальном HA 2026.8 hard dependency
houseplan → frontend → lovelace должна подготовить hass.data["lovelace"] до
setup entry. Явный after_dependencies документирует намерение и страхует
изменение upstream-графа; one-shot retry восстанавливает искусственный transient
отказ/исключение, но ни один release note не называет гонку установленной
причиной полевого случая.
Версионная граница YAML resources подтверждена первичными upstream-источниками, а не экстраполяцией по HA 2025.x:
- Home Assistant Core PR
#161816, merged в milestone
2026.2.0 коммитом
190fe10, прямо «introduce a new key:resource_modeto replacemode» и отделяет загрузку ресурсов от режима dashboard; - исходник тега
2026.2.0
содержит
CONF_RESOURCE_MODE, отдельное полеLovelaceData.resource_modeи fallback на legacymode; - актуальная официальная документация HA
требует
resource_mode: yamlдляlovelace.resourcesи описывает его отдельно отdashboards.*.mode.
Поэтому HA до 2026.2 и HA 2026.2+ намеренно получают разные snippets; это не противоречит исходному репорту, который описывает storage mode старого HA.
4. Решения владельца
- В обычном режиме автоматической перезагрузки нет.
- В kiosk разрешена тихая перезагрузка только при безопасном состоянии.
- Минимальные обязательные guards переиспользуют смысл
_cycleTick:Date.now() >= _cyclePausedUntilи_zoom <= 1.001; дополнительно запрещены editor, dialog и незавершённые physical writes. - На одну целевую
integration_versionдопустима ровно одна автоматическая перезагрузка в рамках browser-tab session. Отметка записывается до reload; смена/чередование frontend bundle при той же backend-версии не даёт новую попытку. - Если после неё версии всё ещё различаются, повтор запрещён и показывается ручная плашка.
5. Термины и границы
- Storage mode — ресурсы управляются UI HA. Интеграция может создавать и обновлять запись Lovelace resource registry.
- YAML resources mode — ресурсы объявляются под
lovelace.resourcesвconfiguration.yaml; backend не пишет их в registry. В HA 2026.2+ режим выбирается независимымlovelace.resource_mode: yamlи не переводит сами dashboards из storage в YAML. - Legacy full-YAML dashboard mode — совместимый с поддерживаемыми HA
2024.6–2026.1 вариант
lovelace.mode: yaml; он управляет не только ресурсами, но и самим dashboard, поэтому не предлагается пользователю storage dashboard как равнозначная замена современномуresource_mode. - Fallback — подключение через
add_extra_js_urlпри недоступном для записи registry. Это поддерживаемая деградация, но она требует reload документа. - Совпадение версий — точное равенство непустых строк
CARD_VERSIONиintegration_version. Порядок semver не угадывается: frontend может быть как старее, так и новее backend. - Неизвестная версия — одна из сторон не предоставила корректную непустую строку. Она не считается mismatch и не запускает notice/reload.
- Full card —
custom:houseplan-card. Runtime-плашка и kiosk auto-reload относятся к ней.custom:houseplan-space-cardзагружается тем же bundle, но отдельный runtime banner/kiosk-контроллер в этой задаче не получает.
6. Скоуп
- Исправление README EN/RU и английского/русского User Guide: установка, storage/YAML развилка и hard reload.
- Типизированный результат регистрации ресурса вместо boolean.
after_dependencies: ["lovelace"], одна lifecycle-bound повторная попытка после старта HA для временно недоступного registry и корректная отмена при unload.- Взаимоисключающий финальный loader: Lovelace registry либо
extra_module_url; fallback удаляется, если retry успешно перешёл на registry. - Локализованное одноразовое persistent notification после первой доступной регистрации frontend-ресурса.
- Состояние frontend-регистрации в System Health.
- Runtime version mismatch controller в initial full-card bundle, без импорта lazy editor runtime.
- Ручная плашка и безопасная одноразовая kiosk-перезагрузка.
- i18n EN/RU/DE/FR, backend/unit/browser/mutation проверки, документация и два changelog.
7. Не входит
- изменение
/houseplan_files/houseplan-card.jsили query cache busting; - горячая замена custom element без reload документа;
- поддержка одиночного JS без установленной интеграции;
- автоматическое изменение пользовательского
configuration.yaml; - отдельная update-плашка и kiosk-режим для
houseplan-space-card; - общий менеджер обновлений HACS/HA и проверка наличия новой версии в сети;
- повторные бесконечные polling/backoff попытки Lovelace registry;
- перестройка picker или dashboard UI Home Assistant.
8. Backend-контракт регистрации
8.1 Типизированный результат
Одна функция регистрации возвращает структурированный outcome, достаточный для диагностики и решения lifecycle, минимум со следующими состояниями:
| Outcome | Смысл | Финальный loader |
|---|---|---|
created |
запись resource создана | lovelace_resource |
updated |
URL существующей записи обновлён до текущей версии | lovelace_resource |
existing |
точный URL уже существовал | lovelace_resource |
registry_pending |
registry ещё не появился/не загрузился | временно fallback, затем один retry |
yaml_fallback |
registry сознательно недоступен для записи | extra_module_url |
transient_error |
первая попытка упала на load/create/update | временно fallback, затем один retry |
error_fallback |
повторная попытка также упала | extra_module_url |
Outcome включает безопасный короткий last_error только для диагностики; stack
trace, токены и пути вне config не попадают в System Health.
8.2 Lifecycle и retry
- Статический путь регистрируется как сейчас, один раз на HA run.
- При наличии файла выполняется первая попытка registry.
created/updated/existingзавершают путь безextra_module_url.registry_pendingили первыйtransient_errorвключает fallback немедленно и ставит ровно одну отложенную попытку: штатный HA start helper дожидается running-state, после чего cancellable lifecycle timer даёт registry ещё одну фиксированную bounded паузу в 1 секунду. Если HA уже running, start helper вызывается сразу, но секундная пауза всё равно сохраняется — retry не должен повторять transient отказ в том же tick.- Отмена start-listener, timer и уже запущенной retry task регистрируется через
entry.async_on_unload. Дополнительный cancellation/generation guard перед каждым поздним side effect гарантирует, что unload/remove не воскресит удалённую интеграцию даже при гонке с уже начавшейся coroutine. - Успешный retry удаляет через штатный frontend helper только тот exact
versioned URL, который этот setup сам добавил в
extra_module_url, и фиксирует registry как финальный loader. Чужие URL не затрагиваются. - Неуспешный retry превращает состояние в устойчивый
error_fallback. Нового timer, tight loop и накопления listeners нет. yaml_fallbackиerror_fallbackне создают бесконечный retry. Повторный setup остаётся идемпотентным.- Несколько найденных legacy resource entries не размножаются: authority — одна каноническая запись; существующее best-effort удаление при uninstall очищает все записи с base URL.
Если HA API текущей минимально поддерживаемой версии не предоставляет удаление
extra_module_url, реализация не создаёт два разных URL: это фиксируется как
lovelace_resource_with_session_fallback в диагностике до следующего reload, а
registry остаётся authority для будущих документов. Предпочтителен доступный
штатный remove_extra_js_url.
8.3 Наблюдаемое runtime-состояние
В hass.data[DOMAIN] хранится только состояние текущего run:
card_file_present;static_path_registered;resource_status— последний outcome;loader—lovelace_resource,extra_module_url,lovelace_resource_with_session_fallbackилиnone;module_url— точный versioned URL;retry_pendingиretry_attempted;- безопасный
last_errorлибоnull.
Отсутствующий bundle не валит setup: loader=none, файл false, warning и
честный System Health. Ложное значение static_path_registered=true до
успешного вызова HA API запрещено.
9. Одноразовое уведомление после первой регистрации
Используется локализованное persistent_notification со стабильным
namespaced ID. Это информационный onboarding, а не сохраняемая неисправность:
backend не пытается определять, какую из вкладок пользователь уже обновил, и не
создаёт Repairs issue.
- Уведомление создаётся ровно один раз за жизнь config entry после первой
доступной регистрации frontend:
created,updated,existingили поддерживаемый fallback при наличии файла и зарегистрированного static path. - Флаг «уведомление уже создано» записывается в служебное поле config entry
сразу после того, как синхронный callback
persistent_notification.async_createвернулся без исключения. Он не зависит от backendVERSION. - Существующая установка без поля получает одно уведомление при первом setup версии с #462. После этого update/downgrade/reload entry его не возвращают.
- Dismiss уведомления не влияет на интеграцию. После restart HA оно может исчезнуть по правилам persistent notification и не создаётся повторно: это намеренно одноразовая инструкция, а не контроль прочтения.
- Отсутствующий frontend-файл либо неуспешный static path не устанавливает флаг: уведомление появится при первом последующем setup, где frontend доступен.
- Повторный setup, параллельное завершение retry и несколько entries не создают дубли: стабильный notification ID и persisted flag являются authority.
- Uninstall dismiss-ит уведомление best effort; служебное поле исчезает вместе с entry.
- Текст сообщает: карточка подключена; полностью перезагрузите страницу
(
Ctrl+F5/Cmd+Shift+R); при ручной настройке storage dashboard ресурс находится в Settings → Dashboards → Resources.
Текст берётся через HA async_get_translations из совместимой с HA 2024.6
категории issues backend translation catalog для языка HA с fallback на
English; EN/RU/DE/FR поставляются одновременно. Использование переводов из
issues не превращает сообщение в Repairs issue: показ выполняет только
persistent_notification. Это глобальное системное уведомление и поэтому
использует язык экземпляра HA, а не язык конкретной открытой вкладки.
10. System Health
system_health_info() сохраняет существующую статистику и добавляет
локализованные ключи:
| Поле | Значение |
|---|---|
card_file |
present / missing |
static_path |
registered / not_registered |
resource_status |
последний типизированный outcome либо not_attempted |
resource_loader |
финальный loader из §8.3 |
resource_url |
точный FRONTEND_URL?v=VERSION либо unavailable |
resource_retry |
pending / attempted / not_needed |
resource_error |
безопасная причина либо none |
first_reload_notice |
created / already_created / pending_frontend |
Ключи system_health.info.* добавляются в strings.json и EN/RU/DE/FR
translations по контракту HA. Значения остаются короткими и пригодными для
копирования в support report. System Health не обещает, что конкретная вкладка
браузера уже перезагружена: одноразовый флаг подтверждает только создание
инструкции, а фактическое состояние показывает runtime-сравнение.
11. Frontend version controller
11.1 Источник истины
Контроллер находится в initial graph full card, использует только
CARD_VERSION и последний корректный integration_version из успешного
config/get. Lazy editor runtime не загружается ради проверки.
Каждый успешный config/get авторитетен. Версия считается известной только если
поле имеет тип string и после trim() не пусто; сравнивается нормализованная
trimmed string без предположений о SemVer. Любой другой тип, пустая/whitespace
строка или отсутствующее поле очищает ранее принятое значение до unknown, а
не оставляет stale mismatch от предыдущего ответа/reconnect.
Матрица:
| Frontend | Backend | Обычный режим | Kiosk |
|---|---|---|---|
| unknown | любой | ничего | ничего |
A |
A |
ничего | ничего |
A |
B, target B не пытался |
плашка, только ручной reload | без плашки; при safe state одна auto-попытка |
A |
B, target B уже пытался |
плашка | плашка, auto запрещён |
Mismatch симметричен. Текст не утверждает, какая сторона новее, и предлагает завершить restart HA, затем reload страницы.
11.2 Плашка
- Компактная overlay-плашка располагается в card-level overlay над сценой и не
изменяет высоту editor/header или fit viewport. Она остаётся доступна и в
ранних full-card состояниях без готовой сцены (fixed-floor pending/invalid,
пустая модель), если успешный
config/getуже подтвердил mismatch; отдельные копии разметки в render-ветках не становятся независимыми состояниями. - Содержит понятный direction-neutral текст, версии frontend/backend и кнопку
«Перезагрузить страницу» /
Reload page. - Кнопка вызывает
window.location.reload()только из trusted click/tap. - Плашка доступна с клавиатуры, имеет
role=statusили эквивалентный live-region без повторного объявления на каждый render; touch-target кнопки не менее 44×44 CSS px. - Она не перехватывает pan/zoom вне своей поверхности, не закрывает основной navigation и не меняет focus самопроизвольно.
- В обычном режиме отображается при любом известном mismatch, включая editor и открытый dialog; reload остаётся осознанным действием пользователя.
- Известный mismatch не блокирует вход в редактор. Существующий recovery #353 для lazy editor/onboarding runtime остаётся независимой проверкой fingerprint, но его terminal-тост с тем же советом о reload подавляется, пока уже видна version-mismatch плашка. Network/non-terminal toast с советом повторить действие сохраняется; terminal toast также сохраняется, если mismatch неизвестен и плашки нет. Одновременно два сообщения с одной просьбой о reload не показываются.
- В kiosk плашка до первой разрешённой auto-попытки не показывается: это осознанное исключение из обычного mismatch UX ради тихого обновления настенной панели. Если safe state долго не наступает, карточка сохраняет текущий кадр; после первой попытки и сохранившегося mismatch появляется обычная плашка.
- Motion — короткое opacity-появление/исчезновение; при
prefers-reduced-motion: reduceбез анимации.
11.3 Ровно одна тихая kiosk-перезагрузка
Attempt хранится в sessionStorage под namespaced key и содержит целевую
integration_version. Один module-level helper разделяет его между всеми full
card на странице.
- Перед
location.reload()target синхронно записывается вsessionStorage. - Та же target после reload, при другом
CARD_VERSIONили в другом card instance не получает вторую auto-попытку. - Новая backend target может получить одну новую попытку.
- Manual button не очищает guard и не обещает auto retry.
- Если
sessionStorageчитать или писать нельзя, fail-safe — banner и никакой автоматической перезагрузки. - Совпадение версий не очищает сохранённую target: иначе reverse proxy, чередующий старый и новый frontend при том же backend, снова разрешит reload. Хранится одна небольшая строка без плана, entity ID и других данных.
Auto-reload разрешён только при одновременном выполнении:
config.kiosk === trueи компонент connected;- initial server load завершён, есть полный settled frame, нет recovery/loading;
- режим
view,_editing === false; - ни один принадлежащий карточке first-class dialog/confirm/menu overlay не
открыт; единый predicate обязан включать как минимум editor-secondary dialogs,
room/partition delete, import, backdrop guard, danger confirm и незавершённый
_vacFit. Нативный HA more-info не включается по ненадёжному stale-полю: отдельного достоверного сигнала его закрытия у карточки нет. Вместо этого единая точка открытия more-info обязана продлить_cyclePausedUntilи для pointer, и для keyboard, и для внутренних программных путей; одной паузы из stage pointerdown недостаточно; _pendingPhysicalWrites.size === 0,_writesPending === 0и нет незавершённой config write chain;- нет pending layout debounce/отправки и грязных несохранённых позиций устройств;
- нет активного pointer/pinch/swipe/drag gesture или mode transition;
Date.now() >= _cyclePausedUntil;_zoom <= 1.001.
Проверка идёт независимым bounded controller/timer: cycle: 0 не отключает
механизм. Одновременно существует не более одного timer на card; disconnect,
совпадение версий и смена target его отменяют. Проверка не чаще одного раза за
animation frame/разумный timer и не добавляется в HA state render hot path.
12. Документация установки
README.md, README.ru.md, docs/USER-GUIDE.md и
docs/USER-GUIDE.ru.md содержат одну и ту же развилку:
Storage mode (HA default)
Обычно ручная запись не нужна. Если автоматическое подключение не сработало:
Settings → Dashboards → меню ⋮ → Resources → Add resource; URL
/houseplan_files/houseplan-card.js, type JavaScript module. После установки или
изменения ресурса полностью reload страницы; hard reload shortcuts названы.
YAML resources mode в HA 2026.2+
Современный канонический snippet управляет только источником ресурсов и оставляет сами dashboards в выбранном пользователем storage/YAML режиме. Его синтаксис подтверждён Home Assistant Core #161816, исходником HA 2026.2.0 и официальной документацией dashboards:
lovelace:
resource_mode: yaml
resources:
- url: /houseplan_files/houseplan-card.js
type: module
Legacy HA 2024.6–2026.1
Для уже YAML-managed dashboard показан отдельно помеченный legacy snippet:
lovelace:
mode: yaml
resources:
- url: /houseplan_files/houseplan-card.js
type: module
Документация явно предупреждает, что mode: yaml меняет режим самого dashboard.
Пользователь старого HA со storage dashboard должен применять UI Resources из
предыдущего раздела, а не переключать dashboard ради карточки. Нельзя утверждать,
что lovelace.resources всегда игнорируется storage-managed dashboard: в HA
2026.2+ именно resource_mode: yaml поддерживает YAML-ресурсы независимо от
режима dashboard. UI Resources, в свою очередь, не заменяет YAML declaration,
когда выбран YAML resource mode. Путь /custom_components/... остаётся явно
запрещён.
13. Модель данных, compatibility и миграция
- Формат плана, layout, storage schema и card config не меняются.
- Websocket-протокол не меняется:
integration_versionостаётся существующим ответным compatibility-полем, новых request/response полей нет. - Служебный boolean-флаг показа первого уведомления в config entry читается как optional; отсутствие/невалидное значение означает «ещё не создавалось». Schema migration и перепись планов не нужны.
sessionStorage— transient browser-tab metadata, не экспортируется и не синхронизируется.- Downgrade безопасен: старый backend без
integration_versionдаётunknown, backend с иной валидной версией даёт symmetric mismatch. Запрос остаётся тем же и не требует compatibility retry.
14. i18n
Новые строки добавляются одновременно:
- frontend EN/RU/DE/FR: заголовок/текст version mismatch, подписи обеих версий, кнопка reload;
- backend
strings.json+ EN/RU/DE/FR: текст одноразового notification и ключиsystem_health.info.*; - документация EN/RU синхронна по смыслу.
Тексты не говорят «backend новее» и не обещают, что один reload всегда исправит незавершённое обновление. Значения status enum в System Health не требуют перевода для машинной диагностики, но их названия полей локализованы.
15. Критерии приёмки
AC1 — документация режима (unit/docs contract)
Оба README и оба User Guide содержат отдельную storage UI-инструкцию, современный
HA 2026.2+ snippet с lovelace.resource_mode: yaml, явно помеченный legacy
2024.6–2026.1 snippet с lovelace.mode: yaml только для full-YAML dashboard и
hard reload shortcuts. Возврат плоского top-level resources:, современного
mode: yaml вместо resource_mode: yaml или совет переключить storage dashboard
в legacy YAML ради карточки делает docs contract красным.
AC2 — честный outcome регистрации (backend)
Backend различает created, updated, existing, registry_pending,
transient_error, yaml_fallback, error_fallback; существующая запись
обновляется без дубля, а отсутствующий файл не сообщает успешный loader.
AC3 — lifecycle retry без двойного loader (backend + mutation)
Registry отсутствует на первой попытке и появляется после HA started: один retry через фиксированный секундный lifecycle delay регистрирует канонический resource и снимает fallback либо честно маркирует его остаток. Уже running HA также не делает retry в том же tick. Unload до event, во время delay и во время task отменяет callback/side effects; повторный setup не накапливает listeners. Мутанты «удалить retry», «убрать delay при already-running» и «не привязать callback/task к unload» краснеют.
AC4 — одноразовое notification (backend + mutation)
Первая доступная регистрация frontend создаёт локализованное persistent notification и сохраняет boolean-флаг. Повторный setup, retry completion и новая версия его не создают; missing file/static failure не расходуют право на показ; uninstall dismiss-ит его. Мутант «не сохранять флаг» краснеет повторным setup.
AC5 — System Health (backend)
Матрица missing file / registry success / pending→success / YAML fallback / exception возвращает правдивые поля §10, точный versioned URL и безопасную ошибку. Существующая статистика плана не исчезает.
AC6 — точное frontend-сравнение (unit + mutation)
Pure controller покрывает equal, symmetric mismatch, unknown и смену target.
Успешный ответ без корректной integration_version очищает stale значение.
В обычном режиме banner существует iff известен mismatch; kiosk-исключение до
первой auto-попытки соответствует матрице §11.1. Отключение сравнения или
принятие unknown за mismatch ловится unit/mutation gate.
AC7 — обычный режим никогда не reload сам (unit + browser + mutation)
При mismatch full card показывает плашку, но любое ожидание без click оставляет
location.reload невызванным. Trusted button вызывает один reload. Мутант,
разрешающий timer reload при kiosk !== true, краснеет.
AC8 — kiosk safety (unit + browser + mutation)
Каждый отдельный unsafe guard из §11.3 блокирует auto-reload; после перехода в полностью safe state target записывается до ровно одного reload. Минимум мутанты «игнорировать pause», «игнорировать dialog/editor», «игнорировать pending write» и «помечать attempt после reload» детерминированно краснеют. Открытие native HA more-info мышью/тапом, клавиатурой и внутренней карточкой продлевает ту же pause, поэтому ни один из этих путей не допускает немедленный kiosk reload.
AC9 — защита от reload-loop (unit + browser + mutation)
После simulated reload тот же backend target показывает banner и не вызывает reload повторно; это сохраняется при чередовании разных frontend-версий и во втором card instance той же вкладки. Новая backend target получает одну попытку. Исключение storage переводит в manual-only. Мутант «игнорировать сохранённую target» краснеет.
AC10 — граница full/space UX (unit/browser)
Runtime banner/auto-reload рендерит только full card. Space card продолжает штатно загружать тот же bundle и config, но не получает скрытого timer или нового UI этой задачи.
AC11 — downgrade и lifecycle compatibility (unit + backend)
Старый backend без integration_version даёт unknown; валидная отличающаяся
версия не скрывается. Setup/unload/remove, старый websocket request и
существующие test_ha_setup.py остаются зелёными.
AC12 — View/touch/a11y и визуальная стабильность (browser + golden)
Плашка не меняет stage bounds/fit, доступна клавиатурой и touch, не блокирует жесты сцены вне себя, не двигает focus. Desktop, узкий touch viewport и kiosk after-failed-attempt имеют принятый screenshot/golden; reduced-motion вариант не анимируется. При видимой version-mismatch плашке terminal fingerprint failure lazy runtime не добавляет второй toast с просьбой reload; non-terminal failure и terminal failure без плашки продолжают показывать соответствующий toast.
AC13 — сборка и синхронные артефакты (build + ревью кода)
Typecheck/unit/backend/build/bundle parity/no-new-any/check-docs и целевые smoke
зелёные; EN/RU/DE/FR ключи полны; оба changelog и пользовательская документация
обновлены. Три runtime-копии bundle синхронны; две committed trees (dist и
integration frontend) побайтно совпадают.
16. План тестов и отрицательные доказательства
Backend
- расширить
tests_backend/test_ha_setup.pyматрицей outcomes, delayed registry, listener cleanup, idempotent reload, missing bundle и uninstall cleanup; - отдельные тесты
system_health_infoдля §10; - one-time notification: first available setup, repeated setup/version, missing file/static failure и uninstall;
- mutation witnesses для retry, unload-bound callback и persisted notice flag.
Frontend unit
- pure mismatch/session guard controller без DOM;
- exact backend target, чередование frontend versions, storage exception, multiple instances и mark-before-reload;
- каждый safe predicate независимо false/true;
- authoritative очистка stale
integration_versionдо unknown.
Browser smoke/golden
- full card mismatch в обычном View: banner, стабильный stage bbox, no auto, click reload spy;
- попытка открыть editor при известном mismatch: terminal fingerprint failure не дублирует видимую плашку toast-ом; non-terminal failure остаётся видимым; без version banner terminal toast #353 сохраняется;
- kiosk mismatch: unsafe→safe без предварительной плашки, one auto; remount/reload с той же backend target — banner и zero auto;
- editor/dialog/pending-write/zoom/recent interaction отдельными probes;
- native HA more-info через pointer, keyboard и внутренний программный путь одинаково продлевает interaction pause без reliance на stale open-marker;
- narrow touch + keyboard focus + reduced motion;
- space card загружается без runtime banner/timer.
Docs/mutation
- статический parser проверяет вложенность
resources, современныйresource_mode: yaml, отдельно версионированный legacymode: yaml, запрет рекомендовать legacy mode для storage dashboard и отдельную UI-инструкцию; scripts/mutation-gate.mjsхранит дорогие backend/browser mutants из AC3, AC4, AC7–AC9 с точными target test commands;- в документе code review каждый защитный AC получает строку «чем краснеет» по
§2.7
PROCESS.md.
17. Затронутые файлы и модули
Ожидаемый минимум:
custom_components/houseplan/__init__.py,manifest.json,system_health.pyи runtime/store typing;custom_components/houseplan/strings.jsonи translations EN/RU/DE/FR;src/houseplan-card.ts, небольшой pure version controller, frontend i18n EN/RU/DE/FR и CSS;README.md,README.ru.md,docs/USER-GUIDE.md,docs/USER-GUIDE.ru.md,docs/ARCHITECTURE.md,docs/DEVELOPMENT.md,docs/TESTING.md, оба changelog;- backend/unit/browser/docs/mutation tests и синхронные generated bundles.
Точные helper names и разбиение pure-модулей остаются за реализацией; границы поведения и доказательства менять нельзя без актуализации ТЗ.
18. Производительность и security
- Registry retry — максимум один callback на entry setup; polling отсутствует.
- Version comparison — O(1) только после config response/update, не на каждый HA state tick и не внутри geometry/render hot path.
- Kiosk использует один bounded timer только пока существует непредпринятый mismatch; после match/attempt/disconnect timer уничтожается.
- Banner добавляет постоянный DOM только во время mismatch. Geometry/Glow/device pipelines не меняются; новый performance capture не нужен, но штатные budget gates обязательны.
- В System Health не попадают traceback, секреты, внешние filesystem paths или пользовательская конфигурация.
- Одноразовый notice-флаг не меняет план или permission boundary; resource registration/removal остаётся серверной операцией.
19. Риски
- Двойное исполнение bundle: fallback и resource могут сосуществовать после retry. Закрывается единым exact URL, удалением fallback и AC3.
- Повторяющееся notification: закрывается persisted boolean и AC4.
- Потерянное первое notification: флаг пишется только после доступного frontend/static path и успешного создания.
- Reload-loop kiosk: sessionStorage backend target до reload и AC8/AC9.
- Потеря правки: полный safe predicate, обычный режим manual-only.
- Frontend новее backend: direction-neutral copy и symmetric matrix.
- Old backend не отдаёт version: authoritative
unknown, без ложного banner/reload. - Несколько cards/tabs: attempt общий в пределах вкладки, но не между
вкладками; это намеренная граница
sessionStorage. - HA startup race/uninstall: after dependency, one-shot lifecycle callback, unload cancellation и removal tests.
- Заблуждающий System Health: отдельные file/static/loader/outcome/error поля, без обещания browser state.
- Дублирующиеся просьбы reload: terminal toast #353 подавляется только при уже видимой version-mismatch плашке; остальные ошибки lazy runtime не скрываются, что проверяет AC12.
20. Rollback
Откат — один revert продуктового коммита и синхронных тестов/документации/ bundles. План и layout не мигрируют. Старый backend игнорирует служебный boolean в config entry; он не требует ручного вмешательства.
После отката вернутся невидимый fallback и необходимость ручного reload; зарегистрированный Lovelace resource с прежним корректным URL останется работоспособным. Удаление интеграции продолжает убирать ресурс.
21. Release-артефакты
Поскольку исправление пользовательски видимо:
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.mdполучают короткий пункт со ссылкой на #462;- README и User Guide EN/RU меняются по §12;
docs/TESTING.mdбольше не утверждает старый reload-контракт; docs/ARCHITECTURE.mdфиксирует authority resource loader, notification и full-card version controller;docs/DEVELOPMENT.mdфиксирует backend/frontend version test seam и lifecycle retry;- принимаются три новых product golden из AC12. Отдельно изменение
src/**требует актуального LinuxDocs screenshotsartifact для всех десяти документационных кадров и fingerprint acceptance; неизменившиеся пиксели не переснимаются вручную; - release performance/security отдельных отчётов не требует; штатные gates и pre-beta runbook остаются обязательными;
- generated bundle trees обновляются только через штатный build.
22. Принятые технические предположения — можно менять на ревью
- Выбран
persistent_notification, потому что контракт владельца — одна информационная подсказка после первой регистрации, а не сохраняемая неисправность Repairs. - Boolean «notification уже создавалось» хранится в
entry.data, а не в плане или layout; точное имя поля внутреннее. - One-shot retry использует
homeassistant.helpers.start.async_at_started, а после running-state — cancellable delay через штатный event/loop helper; listener, timer, task и поздние side effects привязаны к unload. - Loader outcome может быть dataclass/enum/typed dict; публичны только значения System Health.
- Banner в kiosk появляется только после уже предпринятой auto-попытки для этой backend target; до неё сохраняется тихий текущий кадр, как решил владелец.
- Одна сохранённая backend target достаточна: новая target заменяет старую, а смена frontend при неизменной target не сбрасывает guard.
- Проверка всех dialog/gesture состояний может быть выделена в общий pure snapshot helper; перечисление §11.3 является обязательным контрактом.
- Минимальный touch-target 44×44 CSS px выбран как локальный accessibility-порог для этой плашки; общего числового порога в канонических UX-документах проекта пока нет.