docs: specify frontend resource recovery

Issue: #462
User-Visible: no
This commit is contained in:
Matysh
2026-09-05 16:00:54 +03:00
parent fcc4125c6e
commit 6b77d598fd
2 changed files with 625 additions and 0 deletions
@@ -0,0 +1,624 @@
# #462 — Надёжная регистрация frontend-ресурса и восстановление после обновления
- **Issue:** https://github.com/Matysh/houseplan-card/issues/462
- **Тип / приоритет:** bug / P1
- **Трек:** полный; меняются backend lifecycle, websocket-протокол и обязательный
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 и файл новой версии уже доступны.
Пользователь должен получить три независимых уровня помощи:
1. корректную инструкцию для своего режима Lovelace;
2. видимую диагностику способа подключения ресурса на стороне HA;
3. понятное восстановление при несовпадении уже загруженной карточки и backend.
На настенной панели в kiosk обновление применяется само, но только в момент,
когда перезагрузка не уничтожит действие пользователя и не создаст reload-loop.
## 2. Что человек увидит до и после
**До:** документация предлагает невалидный верхнеуровневый `resources:`,
storage-пользователь применяет YAML, который его dashboard игнорирует, отказ
авторегистрации скрыт в логе, а старая вкладка выглядит как актуальная. Иногда
помогает только угаданный `Ctrl+F5`.
**После:** документация отдельно описывает storage и YAML; HA сообщает, что
нужно перезагрузить frontend, пока совпадающий bundle действительно не
подключился; System Health показывает файл, статический путь, способ регистрации
и URL. Уже загруженная full card при несовпадении версий показывает компактную
плашку с перезагрузкой. Kiosk выполняет не более одной тихой перезагрузки для
конкретной пары версий и при неуспехе показывает ту же плашку.
## 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 не называет гонку установленной
причиной полевого случая.
## 4. Решения владельца
1. В обычном режиме автоматической перезагрузки нет.
2. В kiosk разрешена тихая перезагрузка только при безопасном состоянии.
3. Минимальные обязательные guards переиспользуют смысл `_cycleTick`:
`Date.now() >= _cyclePausedUntil` и `_zoom <= 1.001`; дополнительно запрещены
editor, dialog и незавершённые physical writes.
4. На одну пару несовпадающих версий допустима ровно одна автоматическая
перезагрузка в рамках browser-tab session. Отметка записывается до reload.
5. Если после неё версии всё ещё различаются, повтор запрещён и показывается
ручная плашка.
## 5. Термины и границы
- **Storage mode** — ресурсы управляются UI HA. Интеграция может создавать и
обновлять запись Lovelace resource registry.
- **YAML mode** — ресурсы объявляются под `lovelace.resources` в
`configuration.yaml`; backend не пишет их в registry.
- **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 и
подтверждает его версию backend, но отдельный runtime banner/kiosk-контроллер
в этой задаче не получает.
## 6. Скоуп
1. Исправление README EN/RU и английского/русского User Guide: установка,
storage/YAML развилка и hard reload.
2. Типизированный результат регистрации ресурса вместо boolean.
3. `after_dependencies: ["lovelace"]`, одна lifecycle-bound повторная попытка
после старта HA для временно недоступного registry и корректная отмена при
unload.
4. Взаимоисключающий финальный loader: Lovelace registry либо
`extra_module_url`; fallback удаляется, если retry успешно перешёл на
registry.
5. Локализованная Repairs-подсказка о необходимости загрузить новый frontend;
она снимается только после подтверждения совпадающего bundle.
6. Опциональный `card_version` в `houseplan/config/get`; его передают full и
space card. Старые клиенты без поля полностью совместимы.
7. Состояние frontend-регистрации в System Health.
8. Runtime version mismatch controller в initial full-card bundle, без импорта
lazy editor runtime.
9. Ручная плашка и безопасная одноразовая kiosk-перезагрузка.
10. 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` |
| `error_fallback` | безопасно перехвачен неожиданный отказ | `extra_module_url` |
Outcome включает безопасный короткий `last_error` только для диагностики; stack
trace, токены и пути вне config не попадают в System Health.
### 8.2 Lifecycle и retry
1. Статический путь регистрируется как сейчас, один раз на HA run.
2. При наличии файла выполняется первая попытка registry.
3. `created/updated/existing` завершают путь без `extra_module_url`.
4. `registry_pending` включает fallback немедленно и ставит ровно один callback
через штатный HA start helper. Helper выполняется после старта либо сразу,
если HA уже running.
5. Callback зарегистрирован через `entry.async_on_unload`; unload/remove до его
выполнения делает его no-op и не может воскресить удалённую интеграцию.
6. Успешный retry удаляет через штатный frontend helper только тот exact
versioned URL, который этот setup сам добавил в `extra_module_url`, и
фиксирует registry как финальный loader. Чужие URL не затрагиваются.
7. Неуспешный retry превращает состояние в устойчивый fallback. Нового timer,
tight loop и накопления listeners нет.
8. `yaml_fallback` и `error_fallback` не создают бесконечный retry. Повторный
setup остаётся идемпотентным.
9. Несколько найденных 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. Уведомление и подтверждение загрузки frontend
Используется локализованный Repairs issue с постоянным ID
`frontend_reload_required`, severity `warning`, `is_fixable=false`. Это
технический выбор в рамках разрешённого владельцем «Repairs или persistent
notification»: Repairs даёт клиентскую локализацию и может автоматически
исчезнуть после доказанного результата.
### 9.1 Публикация
- При доступном frontend-файле backend создаёт/обновляет issue для текущего
`VERSION`, если эта версия ещё не подтверждена браузером.
- Подтверждённая версия хранится в служебном поле config entry, отдельно от
плана. Отсутствие поля у старой установки означает «не подтверждено» и не
требует миграции данных плана.
- Повторный setup той же уже подтверждённой версии не создаёт кратковременное
ложное issue. Для новой или downgraded версии issue создаётся снова.
- Текст сообщает: интеграция готова; перезапустите HA после незавершённого
обновления, затем полностью перезагрузите страницу (`Ctrl+F5` /
`Cmd+Shift+R`); в storage mode при ручной настройке ресурс находится в
Settings → Dashboards → Resources.
### 9.2 Подтверждение
`houseplan/config/get` принимает необязательное поле `card_version` — непустую
строку с разумным пределом длины. Full и space card передают `CARD_VERSION` при
каждом штатном initial/reconnect запросе.
- `card_version === VERSION`: backend записывает acknowledgement текущей
версии и удаляет Repairs issue.
- поле отсутствует, malformed либо не равно `VERSION`: конфигурация всё равно
возвращается как раньше; acknowledgement и issue не меняются.
- side effect не требует write permission: загрузить совпадающий публичный
bundle может любой аутентифицированный пользователь, а изменение касается
только служебной подсказки, не плана.
- одновременные совпадающие запросы идемпотентны и не вызывают reload config
entry.
- uninstall удаляет Repairs issue; служебное поле исчезает вместе с entry.
Первый релиз механизма намеренно покажет issue существующим установкам, пока
хотя бы один браузер не загрузит новый bundle. Это закрывает переходный случай,
когда старый frontend ещё не умеет показать runtime-плашку.
## 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` |
Ключи `system_health.info.*` добавляются в `strings.json` и EN/RU/DE/FR
translations по контракту HA. Значения остаются короткими и пригодными для
копирования в support report. System Health не обещает, что конкретная вкладка
браузера уже перезагружена: это показывает Repairs acknowledgement и runtime
сравнение.
## 11. Frontend version controller
### 11.1 Источник истины
Контроллер находится в initial graph full card, использует только
`CARD_VERSION` и последний корректный `integration_version` из успешного
`config/get`. Lazy editor runtime не загружается ради проверки.
Каждый успешный `config/get` авторитетен: отсутствующее или malformed
`integration_version` очищает ранее принятое значение до `unknown`, а не
оставляет stale mismatch от предыдущего ответа/reconnect.
Матрица:
| Frontend | Backend | Обычный режим | Kiosk |
|---|---|---|---|
| unknown | любой | ничего | ничего |
| `A` | `A` | ничего | ничего |
| `A` | `B`, пара не пыталась | плашка, только ручной reload | без плашки; при safe state одна auto-попытка |
| `A` | `B`, пара уже пыталась | плашка | плашка, auto запрещён |
Mismatch симметричен. Текст не утверждает, какая сторона новее, и предлагает
завершить restart HA, затем reload страницы.
### 11.2 Плашка
- Компактная overlay-плашка располагается внутри full card над сценой и не
изменяет высоту editor/header или fit viewport.
- Содержит понятный 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 остаётся осознанным действием пользователя.
- В kiosk плашка до первой разрешённой auto-попытки не показывается: это
осознанное исключение из обычного mismatch UX ради тихого обновления настенной
панели. Если safe state долго не наступает, карточка сохраняет текущий кадр;
после первой попытки и сохранившегося mismatch появляется обычная плашка.
- Motion — короткое opacity-появление/исчезновение; при
`prefers-reduced-motion: reduce` без анимации.
### 11.3 Ровно одна тихая kiosk-перезагрузка
Attempt хранится в `sessionStorage` под namespaced key и содержит точную пару
`CARD_VERSION + integration_version`. Один module-level helper разделяет его
между всеми full card на странице.
1. Перед `location.reload()` pair синхронно записывается в `sessionStorage`.
2. Та же pair после reload или в другом card instance не получает вторую
auto-попытку.
3. Новая pair может получить одну новую попытку.
4. Manual button не очищает guard и не обещает auto retry.
5. Если `sessionStorage` читать или писать нельзя, fail-safe — banner и никакой
автоматической перезагрузки.
6. Совпадение версий не требует очищать историю; хранится только одна небольшая
pair, без плана, 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`;
- `_pendingPhysicalWrites.size === 0`, `_writesPending === 0` и нет
незавершённой config write chain;
- нет активного pointer/pinch/swipe/drag gesture или mode transition;
- `Date.now() >= _cyclePausedUntil`;
- `_zoom <= 1.001`.
Проверка идёт независимым bounded controller/timer: `cycle: 0` не отключает
механизм. Одновременно существует не более одного timer на card; disconnect,
совпадение версий и смена pair его отменяют. Проверка не чаще одного раза за
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 mode
Показан валидный top-level snippet:
```yaml
lovelace:
mode: yaml
resources:
- url: /houseplan_files/houseplan-card.js
type: module
```
Явно сказано, что `lovelace.resources` не подключает resource к storage-managed
dashboard, а UI Resources не заменяет YAML для YAML-managed dashboard. Путь
`/custom_components/...` остаётся явно запрещён.
## 13. Модель данных, compatibility и миграция
- Формат плана, layout, storage schema и card config не меняются.
- Websocket-поле `card_version` опционально; старые frontend продолжают получать
прежний ответ. `integration_version` остаётся ответным compatibility-полем.
- Служебное acknowledgement в config entry читается как optional string.
Отсутствующее/невалидное значение означает «не подтверждено»; schema migration
и перепись планов не нужны.
- `sessionStorage` — transient browser-tab metadata, не экспортируется и не
синхронизируется.
- Downgrade безопасен: старый backend игнорирует невозможный для него новый
request field только если его schema это допускает; поэтому новый frontend
должен при `invalid_format` один раз повторить `config/get` без
`card_version`, принять ответ и перейти в состояние unknown вместо load-loop.
Этот compatibility retry выполняется не чаще одного раза на load.
- Возврат к backend, где поле уже поддержано, снова включает handshake.
## 14. i18n
Новые строки добавляются одновременно:
- frontend EN/RU/DE/FR: заголовок/текст version mismatch, подписи обеих версий,
кнопка reload;
- backend `strings.json` + EN/RU/DE/FR: Repairs title/description и ключи
`system_health.info.*`;
- документация EN/RU синхронна по смыслу.
Тексты не говорят «backend новее» и не обещают, что один reload всегда исправит
незавершённое обновление. Значения status enum в System Health не требуют
перевода для машинной диагностики, но их названия полей локализованы.
## 15. Критерии приёмки
### AC1 — документация режима (`unit`/docs contract)
Оба README и оба User Guide содержат `lovelace:` перед YAML `resources:`,
отдельную storage UI-инструкцию и hard reload shortcuts. Возврат плоского
top-level `resources:` делает docs contract красным.
### AC2 — честный outcome регистрации (`backend`)
Backend различает `created`, `updated`, `existing`, `registry_pending`,
`yaml_fallback`, `error_fallback`; существующая запись обновляется без дубля, а
отсутствующий файл не сообщает успешный loader.
### AC3 — lifecycle retry без двойного loader (`backend` + mutation)
Registry отсутствует на первой попытке и появляется к HA started: один retry
регистрирует канонический resource и снимает fallback либо честно маркирует его
остаток. Unload до event отменяет callback; повторный setup не накапливает
listeners. Мутанты «удалить retry» и «не привязать callback к unload» краснеют.
### AC4 — Repairs handshake (`backend` + mutation)
Неподтверждённая текущая версия создаёт локализованный Repairs issue; совпадающий
`card_version` сохраняет acknowledgement и удаляет issue. Missing/malformed/
different version не блокирует чтение config и не снимает issue. Повторный setup
подтверждённой версии не создаёт его снова; uninstall очищает. Мутант
«подтверждать любую версию» краснеет.
### 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 и смену pair.
В обычном режиме 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 pair записывается до ровно одного reload. Минимум мутанты
«игнорировать pause», «игнорировать dialog/editor», «игнорировать pending write»
и «помечать attempt после reload» детерминированно краснеют.
### AC9 — защита от reload-loop (`unit` + browser + mutation)
После simulated reload тот же mismatch показывает banner и не вызывает reload
повторно; второй card instance той же вкладки также не вызывает. Новая pair
получает одну попытку. Исключение storage переводит в manual-only. Мутант
«игнорировать сохранённую pair» краснеет.
### AC10 — full/space handshake и граница UX (`unit`/browser)
Обе карточки посылают `CARD_VERSION` и могут подтвердить загрузку bundle.
Runtime banner/auto-reload рендерит только full card; space card не падает и не
теряет config при новом ответном поле.
### AC11 — downgrade и lifecycle compatibility (`unit` + backend)
Старый backend, отклонивший request с `card_version`, получает один fallback
request без поля; загрузка завершается без retry storm. Setup/unload/remove и
существующие `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 вариант
не анимируется.
### AC13 — сборка и синхронные артефакты (`build` + ревью кода)
Typecheck/unit/backend/build/bundle parity/no-new-any/check-docs и целевые smoke
зелёные; EN/RU/DE/FR ключи полны; оба changelog и пользовательская документация
обновлены. Три коммитящихся bundle-копии побайтно совпадают.
## 16. План тестов и отрицательные доказательства
### Backend
- расширить `tests_backend/test_ha_setup.py` матрицей outcomes, delayed registry,
listener cleanup, idempotent reload, missing bundle и uninstall cleanup;
- отдельные тесты `system_health_info` для §10;
- websocket schema/handshake: equal, missing, malformed, mismatch, concurrent
equal и старый client;
- mutation witnesses для retry, unload-bound callback и exact acknowledgement.
### Frontend unit
- pure mismatch/session guard controller без DOM;
- exact pair, storage exception, multiple instances, mark-before-reload;
- каждый safe predicate независимо false/true;
- compatibility request fallback на old backend.
### Browser smoke/golden
- full card mismatch в обычном View: banner, стабильный stage bbox, no auto,
click reload spy;
- kiosk mismatch: unsafe→safe без предварительной плашки, one auto;
remount/reload с той же pair — banner и zero auto;
- editor/dialog/pending-write/zoom/recent interaction отдельными probes;
- narrow touch + keyboard focus + reduced motion;
- space card успешно отправляет handshake без runtime banner.
### Docs/mutation
- статический parser проверяет не просто строку `lovelace:`, а вложенность
`resources` в каждом canonical snippet и отдельную storage-инструкцию;
- `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`, `repairs.py`, `websocket_api.py`, runtime/store typing;
- `custom_components/houseplan/strings.json` и translations EN/RU/DE/FR;
- `src/houseplan-card.ts`, shared websocket/config client для обеих cards,
небольшой 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 обязательны.
- `card_version` — недоверенная bounded строка, не используется как URL/path и
не даёт write capability. В System Health не попадают traceback, секреты,
внешние filesystem paths или пользовательская конфигурация.
- Repairs acknowledgement не меняет план и доступен обычному authenticated
клиенту; resource registration/removal остаётся серверной операцией.
## 19. Риски
1. **Двойное исполнение bundle:** fallback и resource могут сосуществовать после
retry. Закрывается единым exact URL, удалением fallback и AC3.
2. **Ложное вечное Repairs issue:** закрывается matching handshake и persistent
acknowledgement текущей версии.
3. **Ложное снятие issue старым frontend:** exact equality и malformed tests.
4. **Reload-loop kiosk:** sessionStorage pair до reload и AC8/AC9.
5. **Потеря правки:** полный safe predicate, обычный режим manual-only.
6. **Frontend новее backend:** direction-neutral copy и symmetric matrix.
7. **Old backend rejects new request key:** один compatibility retry без поля.
8. **Несколько cards/tabs:** attempt общий в пределах вкладки, но не между
вкладками; это намеренная граница `sessionStorage`.
9. **HA startup race/uninstall:** after dependency, one-shot lifecycle callback,
unload cancellation и removal tests.
10. **Заблуждающий System Health:** отдельные file/static/loader/outcome/error
поля, без обещания browser state.
## 20. Rollback
Откат — один revert продуктового коммита и синхронных тестов/документации/
bundles. План и layout не мигрируют. Старый backend игнорирует служебное поле
acknowledgement в config entry; при необходимости оно безопасно удаляется
следующим setup/removal, но не требует ручного вмешательства.
После отката вернутся невидимый 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, handshake и
full-card version controller;
- `docs/DEVELOPMENT.md` фиксирует backend/frontend version test seam и
lifecycle retry;
- принимаются три целевых golden/screenshot из AC12; изменение `src/**` требует
актуального Linux `Docs screenshots` artifact и fingerprint acceptance;
- release performance/security отдельных отчётов не требует; штатные gates и
pre-beta runbook остаются обязательными;
- generated bundle trees обновляются только через штатный build.
## 22. Принятые технические предположения — можно менять на ревью
1. Repairs выбран вместо `persistent_notification`, потому что даёт HA-native
локализацию и доказанное автоматическое закрытие; пользовательский результат
остаётся тем же.
2. Acknowledgement хранится в `entry.data`, а не в плане или layout; точное имя
поля внутреннее.
3. One-shot retry использует `homeassistant.helpers.start.async_at_started` либо
эквивалентный lifecycle helper, привязанный к unload.
4. Loader outcome может быть dataclass/enum/typed dict; публичны только значения
System Health.
5. Banner в kiosk появляется только после уже предпринятой auto-попытки для этой
pair; до неё сохраняется тихий текущий кадр, как решил владелец.
6. One-pair `sessionStorage` достаточно: новая target pair заменяет старую, а не
создаёт неограниченный список.
7. Проверка всех dialog/gesture состояний может быть выделена в общий pure
snapshot helper; перечисление §11.3 является обязательным контрактом.
+1
View File
@@ -86,6 +86,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
| [#306](https://github.com/Matysh/houseplan-card/issues/306) Нулевые стены вместо виртуальных границ | [306-zero-thickness-walls.md](306-zero-thickness-walls.md) |
| [#348](https://github.com/Matysh/houseplan-card/issues/348) Полная немецкая локализация | [348-german-localization.md](348-german-localization.md) |
| [#428](https://github.com/Matysh/houseplan-card/issues/428) Round-trip экспорта с отсутствующей картинкой декора | [428-missing-decor-asset-roundtrip.md](428-missing-decor-asset-roundtrip.md) |
| [#462](https://github.com/Matysh/houseplan-card/issues/462) Надёжная регистрация frontend-ресурса и восстановление после обновления | [462-card-resource-registration.md](462-card-resource-registration.md) |
## P2