mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 06:59:46 +00:00
@@ -0,0 +1,709 @@
|
||||
# #486 — Панель House Plan в боковом меню Home Assistant
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/486
|
||||
- **Тип / приоритет:** feature / P1
|
||||
- **Трек:** полный; задача вводит новую пользовательскую поверхность и меняет
|
||||
backend lifecycle, frontend host, bundle graph, responsive/touch-контракт и
|
||||
первый запуск
|
||||
- **Оценка:** пользовательская ценность 10/10; ценность для разработки 9/10;
|
||||
сложность 8/10; риск 8/10
|
||||
- **Связано:** #40, #210, #437, #462; `docs/SCOPE.md`,
|
||||
`docs/UX-MODES.md`, `docs/TOUCH-SUPPORT.md`,
|
||||
`docs/CONFIG-COMPATIBILITY.md`
|
||||
|
||||
## 1. Сценарий
|
||||
|
||||
Персона — новый Home admin, который установил House Plan через HACS и добавил
|
||||
интеграцию, либо действующий пользователь, которому нужно изменить план. Момент
|
||||
— сразу после setup интеграции или при следующем обслуживании дома.
|
||||
|
||||
Пользователь ожидает увидеть House Plan как приложение в боковом меню Home
|
||||
Assistant. Сейчас ему нужно отдельно догадаться создать dashboard, добавить
|
||||
custom card, найти редакторы внутри неё и затем работать в ширине одной колонки
|
||||
Sections/Masonry. На большом мониторе такая колонка может оставлять редактору
|
||||
около 400 CSS px и обрывать первый пользовательский путь ещё до первой комнаты.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** успешная установка интеграции не даёт очевидной точки входа. Редактор
|
||||
доступен только после ручного добавления карточки; по умолчанию карточка в
|
||||
Sections может оказаться узкой.
|
||||
|
||||
**После:** после setup в стандартном боковом меню HA появляется пункт
|
||||
**House Plan** с иконкой `mdi:floor-plan`. Он открывает `/houseplan`: обычную HA
|
||||
страницу, где тот же план занимает всю доступную ширину и высоту. В верхней
|
||||
строке страницы остаётся название продукта и стандартный доступ к меню HA, а
|
||||
ниже — пространства, редакторы и действия House Plan. Dashboard-card продолжает
|
||||
работать как раньше; в Sections HA предлагает ей полную ширину по умолчанию.
|
||||
|
||||
Первое создание плана выполняется через уже существующий onboarding House Plan.
|
||||
Пользователь без права записи видит тот же read-only View и не получает ложных
|
||||
редакторских действий.
|
||||
|
||||
## 3. Подтверждённое текущее состояние
|
||||
|
||||
На исходной вершине `dev` задачи:
|
||||
|
||||
1. `custom_components/houseplan/__init__.py` регистрирует HTTP/WS глобально,
|
||||
загружает server stores и подключает `houseplan-card.js`, но не регистрирует
|
||||
HA panel.
|
||||
2. `frontend_registration.py` надёжно регистрирует Lovelace resource, fallback,
|
||||
versioned URL, одноразовое reload-уведомление и System Health для карточки.
|
||||
Этот lifecycle следует переиспользовать, а не создавать второй resource.
|
||||
3. `HouseplanCard` уже получает server-side `can_write`, соблюдает `admin_only`,
|
||||
хранит последнее пространство и при уходе с HA route сбрасывает редактор в
|
||||
View. Пустой writable-план уже запускает onboarding #40, но текущая empty
|
||||
ветка ошибочно не проверяет `_canEdit`: read-only пользователь тоже видит
|
||||
Add space, а runtime может автоматически открыть onboarding. Панель для всех
|
||||
пользователей требует исправить этот разрыв в рамках #486.
|
||||
4. Высота обычной карточки выводится из `100dvh` и измеренного HA chrome. Для
|
||||
собственного полноэкранного host это неоднозначно: app bar и safe area могут
|
||||
быть вычтены дважды. Нужен неперсистентный container-owned режим.
|
||||
5. У full card есть `getCardSize()`, но нет `getGridOptions()`.
|
||||
6. Rollup, bundle manifest и stale-entry fallback предполагают один entry и
|
||||
выбирают первый `isEntry`. Простое добавление второго entry может поменять
|
||||
authority, initial graph или оставить старый panel bundle в release.
|
||||
7. Публичный совместимый API существует в HA 2024.6:
|
||||
`panel_custom.async_register_panel`. Более новые `async_panel_exists`,
|
||||
`handle_safe_area`, `sidebar_default_visible`, `show_in_sidebar` и keyword
|
||||
`warn_if_unknown` у `async_remove_panel` нельзя считать доступными на
|
||||
минимальной версии.
|
||||
|
||||
## 4. Продуктовые решения
|
||||
|
||||
1. Каноническая точка входа для создания и обслуживания плана — панель House
|
||||
Plan. Dashboard-card остаётся необязательным способом встроить план в
|
||||
пользовательский dashboard.
|
||||
2. Панель и все dashboard-card используют одну server-side модель. У панели нет
|
||||
отдельного плана, пространства, layout либо набора настроек.
|
||||
3. Панель общедоступна всем аутентифицированным пользователям
|
||||
(`require_admin=False`). Видимость редакторов и право записи определяются
|
||||
только существующим `can_write`/`admin_only`; sidebar visibility не является
|
||||
авторизацией.
|
||||
4. Панель создаёт full card с чистыми defaults и никогда не включает card
|
||||
`kiosk`. HA kiosk/скрытый sidebar может менять только shell страницы, но не
|
||||
права или режим карточки.
|
||||
5. В app bar показывается бренд **House Plan**. Дублирующий product title внутри
|
||||
card header скрывается только внутренним panel-host признаком; tabs
|
||||
пространств, редакторы, zoom/settings и остальные действия не скрываются.
|
||||
6. Панель не заменяет #437: настраиваемая сводная боковая панель рядом с планом
|
||||
остаётся отдельной будущей функцией.
|
||||
7. `getGridOptions() -> { columns: "full" }` входит в #486. Это fallback для
|
||||
пользователя, который начинает с dashboard-card, и не меняет ручной выбор
|
||||
ширины.
|
||||
8. Empty state с Add space и автоматический onboarding доступны только при
|
||||
подтверждённом праве записи. Read-only empty state не показывает активный
|
||||
CTA и не загружает onboarding/editor runtime; правило одинаково в панели и
|
||||
dashboard-card.
|
||||
|
||||
## 5. Принятые предположения
|
||||
|
||||
Эти решения приняты предположительно и могут быть свободно изменены владельцем
|
||||
до реализации без признания ТЗ заблокированным:
|
||||
|
||||
- route — `/houseplan`, custom element — `houseplan-panel`, sidebar icon —
|
||||
`mdi:floor-plan`;
|
||||
- собственный app bar использует публичное событие `hass-toggle-menu`, а не
|
||||
внутренний API конкретной версии `ha-top-app-bar-fixed`/`ha-menu-button`;
|
||||
- высота app bar следует HA theme и touch target не меньше 44×44 CSS px; точное
|
||||
число пикселей не является модельным контрактом;
|
||||
- пользователю с правом записи и пустым планом сразу доступен существующий
|
||||
onboarding, без нового промежуточного welcome-screen;
|
||||
- существующее одноразовое frontend-уведомление #462 получает новый текст,
|
||||
вместо создания второго persistent notification;
|
||||
- HA-native пользовательские настройки порядка/видимости/sidebar title после
|
||||
регистрации не перезаписываются интеграцией.
|
||||
|
||||
Открытых продуктовых вопросов нет.
|
||||
|
||||
## 6. Термины
|
||||
|
||||
- **Panel shell** — новый `<houseplan-panel>`, которому HA передаёт `hass`,
|
||||
`narrow`, `route` и `panel`; он владеет app bar и одним full card.
|
||||
- **Panel-host mode** — неперсистентный runtime-признак full card: контейнер
|
||||
задаёт доступную высоту, внешний card chrome становится плоским и не
|
||||
дублируется product title. Это не `CardConfig` и не YAML option.
|
||||
- **Card entry** — стабильный `houseplan-card.js`, Lovelace resource и authority
|
||||
существующей dashboard-card.
|
||||
- **Panel entry** — отдельный стабильный `houseplan-panel.js`, который HA
|
||||
загружает лениво только при открытии панели.
|
||||
- **Owned panel** — exact registry object, созданный текущим успешным setup
|
||||
generation House Plan. Только его разрешено удалять при unload.
|
||||
- **Foreign collision** — существующая панель другого владельца на route
|
||||
`houseplan`.
|
||||
|
||||
## 7. Скоуп
|
||||
|
||||
1. Зарегистрировать sidebar panel через публичный HA API и безопасно обслуживать
|
||||
setup, reload, disable, unload и remove.
|
||||
2. Добавить отдельный frontend entry и manifest/release tooling для двух entry
|
||||
без роста initial graph обычной карточки.
|
||||
3. Реализовать panel shell, container-owned layout и внутренний panel-host mode
|
||||
full card.
|
||||
4. Сохранить существующие права, onboarding, состояние пространств и правило
|
||||
сброса editor при уходе с route.
|
||||
5. Исправить empty-state permission gap: Add space и onboarding только writer.
|
||||
6. Добавить `getGridOptions()` только full card.
|
||||
7. Обновить config-flow completion, существующее reload-уведомление, System
|
||||
Health и переводы EN/RU/DE/FR.
|
||||
8. Добавить автоматические backend/unit/browser/golden доказательства и
|
||||
пользовательскую/архитектурную документацию.
|
||||
|
||||
## 8. Не входит
|
||||
|
||||
- отдельный server config для title, icon size, стартового пространства или
|
||||
других настроек панели;
|
||||
- перенос CardConfig dashboard-card на сервер или синхронизация нескольких
|
||||
разных YAML-конфигов карточек;
|
||||
- новый onboarding, конвертер, sample plan или изменение #40;
|
||||
- kiosk mode панели, новый fixed-floor route либо deep routing внутри панели;
|
||||
- редизайн редакторов, card header, навигации пространств или инструментов;
|
||||
- полная поддержка редакторов на touch: остаётся сознательная деградация из
|
||||
`docs/TOUCH-SUPPORT.md`;
|
||||
- изменение пользовательского размера уже размещённых dashboard-card;
|
||||
- изменение #437 либо добавление сводной панели рядом с планом;
|
||||
- запись в private `.storage/frontend.panels` и принудительное восстановление
|
||||
удалённого пользователем пункта sidebar;
|
||||
- изменение внешнего landing-сайта или `llms.txt`: таких поверхностей в
|
||||
текущем репозитории нет; README и User Guide являются канонической
|
||||
пользовательской документацией #486, а внешний сайт при его появлении
|
||||
получает отдельную задачу в своём репозитории;
|
||||
- отдельный Lovelace resource или `extra_module_url` для panel entry;
|
||||
- секреты, plan/config или instance-specific данные внутри публичного JS.
|
||||
|
||||
## 9. Backend-контракт
|
||||
|
||||
### 9.1 Регистрация
|
||||
|
||||
После успешной загрузки stores, миграций, repair и housekeeping
|
||||
`async_setup_entry`:
|
||||
|
||||
1. убеждается, что `houseplan-panel.js` существует в установленном frontend;
|
||||
2. регистрирует exact static URL `/houseplan_files/houseplan-panel.js` тем же
|
||||
compatibility helper, что используется для card: async API на новых HA и
|
||||
synchronous fallback на HA 2024.6;
|
||||
3. вызывает:
|
||||
|
||||
```python
|
||||
await panel_custom.async_register_panel(
|
||||
hass=hass,
|
||||
frontend_url_path="houseplan",
|
||||
webcomponent_name="houseplan-panel",
|
||||
sidebar_title="House Plan",
|
||||
sidebar_icon="mdi:floor-plan",
|
||||
module_url=f"/houseplan_files/houseplan-panel.js?v={VERSION}",
|
||||
embed_iframe=False,
|
||||
trust_external=False,
|
||||
require_admin=False,
|
||||
)
|
||||
```
|
||||
|
||||
4. не передаёт `config_panel_domain`, `handle_safe_area`,
|
||||
`sidebar_default_visible`, `show_in_sidebar` или другие параметры новее
|
||||
minimum HA;
|
||||
5. после успеха получает exact созданный registry object и только тогда
|
||||
фиксирует ownership текущего generation;
|
||||
6. регистрирует синхронный cleanup через `entry.async_on_unload`.
|
||||
|
||||
Панель регистрируется последней, чтобы поздний отказ миграции/repair не оставил
|
||||
sidebar entry от неуспешного setup. Отказ panel setup, напротив, fail-soft: он
|
||||
не отменяет уже рабочие stores, WS/HTTP и dashboard-card.
|
||||
|
||||
### 9.2 Static path
|
||||
|
||||
Process-wide состояние static registration становится per-URL set/map.
|
||||
Переход в памяти с legacy boolean безопасен: `True` означает, что зарегистрирован
|
||||
только `FRONTEND_URL` карточки; panel URL всё равно проверяется и регистрируется.
|
||||
|
||||
Static routes невозможно снять публичным HA API и они намеренно живут до
|
||||
restart Core. Повторный reload entry не регистрирует тот же exact URL ещё раз.
|
||||
Каталог интеграции целиком публично не публикуется.
|
||||
|
||||
### 9.3 Ownership, collision и generations
|
||||
|
||||
- `panel_custom.async_register_panel()` в HA 2024.6 возвращает `None`, а
|
||||
публичный remove API умеет удалять только по route. Ради fail-closed cleanup
|
||||
разрешено одно узкое чтение private in-memory registry
|
||||
`hass.data[frontend.DATA_PANELS]`: объект не мутируется напрямую, private
|
||||
`.storage/frontend.panels` не читается и не пишется. Эта зависимость явно
|
||||
покрывается compatibility tests на minimum/current HA.
|
||||
- Если сразу после собственной успешной регистрации registry/identity нельзя
|
||||
прочитать, setup в том же синхронном участке снимает только что созданный
|
||||
route публичным `async_remove_panel`, фиксирует `ownership_error` и оставляет
|
||||
интеграцию без панели. Неидентифицируемая панель не остаётся до unload.
|
||||
- Коллизия route поднимает/нормализуется в статус `collision`; чужая панель не
|
||||
обновляется, не скрывается и не удаляется.
|
||||
- Cleanup сравнивает текущий runtime generation и identity registry object с
|
||||
сохранённым owned object. Старый callback не может снять новую House Plan
|
||||
generation, а внешняя замена route не может быть удалена как наша.
|
||||
- Используется двухаргументный `frontend.async_remove_panel(hass, "houseplan")`,
|
||||
совместимый с HA 2024.6. `warn_if_unknown` не передаётся.
|
||||
- Reload: owned panel снимается один раз, затем новая generation регистрируется
|
||||
один раз. Disable/unload: sidebar entry исчезает. Uninstall после unload
|
||||
идемпотентен и не делает второе слепое удаление.
|
||||
- Missing bundle, static registration error, `ValueError` collision и иное
|
||||
исключение panel API записываются безопасно, но setup интеграции возвращает
|
||||
success.
|
||||
|
||||
### 9.4 Наблюдаемость
|
||||
|
||||
Runtime panel state минимум содержит:
|
||||
|
||||
- `panel_file_present`;
|
||||
- `panel_static_path_registered`;
|
||||
- `panel_status`: `not_attempted`, `registered`, `missing_asset`, `collision`,
|
||||
`static_error`, `registration_error`, `ownership_error`, `removed`;
|
||||
- `panel_url` — `/houseplan` либо `unavailable`;
|
||||
- `panel_module_url` — versioned local URL либо `unavailable`;
|
||||
- `panel_error` — безопасный fingerprint `phase:ExceptionType` либо `none`;
|
||||
- internal generation/owned identity, не выводимые как сериализованные данные.
|
||||
|
||||
System Health добавляет эти поля к существующему frontend/resource состоянию.
|
||||
Логи не включают exception message, пользовательский config, filesystem paths,
|
||||
токены или содержимое плана. Collision получает понятный warning с route и
|
||||
советом открыть System Health; отдельный Repairs issue в #486 не вводится.
|
||||
|
||||
## 10. Доступ и безопасность
|
||||
|
||||
`require_admin=False` определяет только присутствие пункта в списке панелей HA.
|
||||
Для каждого пользователя full card получает обычный `hass` и server response:
|
||||
|
||||
- read-only пользователь видит View, но не editor tabs/изменяющие действия;
|
||||
- администратор при `admin_only=true` получает существующее право записи;
|
||||
- разрешённый non-admin при `admin_only=false` получает ровно существующий
|
||||
`can_write`;
|
||||
- backend WebSocket/HTTP guards остаются authority и не полагаются на скрытый
|
||||
UI.
|
||||
|
||||
Panel JS и card JS публичны как обычные frontend resources. В них запрещены
|
||||
access tokens, план, entity states и server config; всё instance-specific
|
||||
приходит только через аутентифицированный HA runtime.
|
||||
|
||||
## 11. Bundle и release-контракт
|
||||
|
||||
### 11.1 Две именованные точки входа
|
||||
|
||||
Rollup получает именованные entries:
|
||||
|
||||
- card → стабильный `houseplan-card.js`;
|
||||
- panel → стабильный `houseplan-panel.js`.
|
||||
|
||||
Конфигурация root не оставляется на порядок enumeration:
|
||||
|
||||
```js
|
||||
input: {
|
||||
'houseplan-card': 'src/houseplan-card.ts',
|
||||
'houseplan-panel': 'src/houseplan-panel.ts',
|
||||
},
|
||||
entryFileNames: '[name].js',
|
||||
```
|
||||
|
||||
`src/houseplan-panel.ts` статически подключает card entry и регистрирует только
|
||||
`houseplan-panel`. Rollup должен переиспользовать один card graph, а не собрать
|
||||
его вторую копию. Editor/onboarding/locale/isometric/PDF chunks сохраняют
|
||||
существующую ленивость.
|
||||
|
||||
### 11.2 Manifest authority
|
||||
|
||||
Схема расширяется аддитивно и сохраняет действующие поля карточки:
|
||||
|
||||
- `entry: "houseplan-card.js"` остаётся authority для всех старых consumers;
|
||||
- `panelEntry: "houseplan-panel.js"` явно задаёт второй root;
|
||||
- `initialViewFiles`/`initialViewGzipBytes` считаются только от card entry;
|
||||
- `initialPanelFiles`/`initialPanelGzipBytes` считаются от panel entry;
|
||||
- `initialPanelOnlyFiles = initialPanelFiles \\ initialViewFiles`, а
|
||||
`initialPanelOnlyGzipBytes` — сумма только этой разницы;
|
||||
- все output files, включая оба entries и общие hashed chunks, присутствуют в
|
||||
manifest inventory с digest/bytes/gzip и `isEntry:true` ровно у двух
|
||||
объявленных стабильных roots.
|
||||
|
||||
`initialViewFiles` является подмножеством `initialPanelFiles`: panel shell
|
||||
переиспользует card graph, а не дублирует его. Выбор «первый `isEntry`»
|
||||
запрещён. Root определяется по exact filename и `facadeModuleId`.
|
||||
|
||||
### 11.3 Синхронизация и stale build
|
||||
|
||||
- На успешной сборке оба стабильных entry получают fail-loud stale-load
|
||||
поведение: недоступный hashed implementation chunk даёт видимую просьбу
|
||||
перезагрузить страницу, а не пустую поверхность. Compiler/Rollup failure
|
||||
завершается non-zero; неполная сборка не синхронизируется и не публикуется.
|
||||
- `bundle-sync` материализует полное дерево для репозитория, demo и release
|
||||
package: hashed dependencies, оба стабильных entry, manifest и удаление
|
||||
старого inventory. Эта последовательность **не объявляется live-atomic** для
|
||||
уже обслуживаемой HA: в коротком окне замены entry новый chunk может ещё не
|
||||
входить в старый manifest allowlist, и именно stale-load UI является
|
||||
допустимой fail-loud деградацией до завершения sync/reload.
|
||||
- freshness/tree/release/zip проверки валидируют оба roots и запрещают orphaned
|
||||
либо missing panel files. Unlisted root-level `houseplan-*.js` также считается
|
||||
orphan, а не только файл в `houseplan-assets/`.
|
||||
- Panel entry не создаёт Lovelace resource и загружается HA лениво только при
|
||||
открытии `/houseplan`.
|
||||
- `houseplan-panel.js` входит в HACS zip. Самостоятельный GitHub asset
|
||||
`houseplan-card.js` остаётся card-only; второй standalone asset в #486 не
|
||||
вводится.
|
||||
|
||||
### 11.4 Бюджет
|
||||
|
||||
Действующий ceiling initial View карточки не увеличивается ради panel shell и
|
||||
не ослабляется. `initialViewFiles` не содержит panel entry. Ceiling **8 KiB
|
||||
gzip** применяется к `initialPanelOnlyGzipBytes`, а не ко всему panel closure:
|
||||
общий panel graph включает тот же card graph, но не дублирует его.
|
||||
|
||||
Изменения самой full card (`getGridOptions`, panel-host branch) остаются внутри
|
||||
действующего card budget. Если он исчерпан, код уменьшается или выносится, а
|
||||
бюджет не повышается в #486.
|
||||
|
||||
## 12. Frontend panel shell
|
||||
|
||||
### 12.1 Lifecycle и свойства HA
|
||||
|
||||
`houseplan-panel` принимает property setters `hass`, `narrow`, `route`, `panel`.
|
||||
Порядок присваивания произвольный. Shell:
|
||||
|
||||
1. создаёт ровно один `<houseplan-card>` за своё подключение;
|
||||
2. до первого `hass` вызывает `setConfig({ type: "custom:houseplan-card" })`
|
||||
ровно один раз;
|
||||
3. передаёт каждое новое `hass` тому же child, не перемонтируя plan;
|
||||
4. применяет `narrow`/`route`/`panel` к shell без повторного `setConfig`;
|
||||
5. после disconnect не оставляет document/window listeners, observers или
|
||||
timers; reconnect того же элемента не размножает child/listeners.
|
||||
|
||||
Panel config не преобразуется в публичный CardConfig. `hass.kioskMode`, HA
|
||||
sidebar state или `narrow` никогда не превращаются в `config.kiosk`.
|
||||
|
||||
### 12.2 App bar и меню
|
||||
|
||||
App bar содержит:
|
||||
|
||||
- кнопку меню на всех ширинах; обычная пользовательская активация кнопки
|
||||
(trusted исходный click/tap или keyboard activation) отправляет синтетическое,
|
||||
поэтому само по себе неизбежно `isTrusted=false`, но bubbling + composed
|
||||
событие `hass-toggle-menu`. Поэтому доступ к drawer не зависит от того,
|
||||
передаёт ли конкретная версия HA отдельный desktop-collapsed flag; `narrow`
|
||||
меняет только responsive presentation shell;
|
||||
- заголовок **House Plan**;
|
||||
- семантический heading/toolbar без фальшивой навигационной ссылки.
|
||||
|
||||
Accessible name кнопки берётся из `hass.localize` с English fallback. Shell не
|
||||
зависит от private methods внутренних HA components. Если HA сам скрывает
|
||||
sidebar в kiosk, это не включает card kiosk и не открывает редакторы.
|
||||
|
||||
### 12.3 Full-page layout
|
||||
|
||||
Shell — grid/flex из `appbar auto` и `content minmax(0, 1fr)`:
|
||||
|
||||
- занимает доступные `width:100%` и `height:100%` HA panel viewport;
|
||||
- не создаёт page/horizontal scroll;
|
||||
- content и child имеют `min-width:0`, `min-height:0`, `overflow:hidden`;
|
||||
- HA-owned safe-area не вычитается вручную второй раз;
|
||||
- outer card border/radius/shadow убираются только в panel-host mode.
|
||||
|
||||
Panel-host mode считает stage от **измеренного контейнера**, вычитая только
|
||||
собственный card header/editor chrome. Он не использует `100dvh − HA chrome`.
|
||||
Существующий ResizeObserver получает итоговый stage rect и выполняет обычный
|
||||
refit. Смена space, View ↔ editor, narrow, sidebar, split-view, orientation и
|
||||
virtual keyboard не дают отрицательной/нулевой высоты, snap или постоянного
|
||||
resize-loop.
|
||||
|
||||
Обычные dashboard/kiosk/space-card размеры остаются неизменными.
|
||||
|
||||
### 12.4 Card header
|
||||
|
||||
В panel-host mode скрывается только product-title fragment. Если header
|
||||
содержит tabs пространств, editors, device count, zoom/settings или контекстные
|
||||
действия, они остаются и занимают измеряемую строку. В empty/fixed/error ветках
|
||||
не остаётся пустой title-row: app bar уже является видимым и доступным
|
||||
заголовком страницы.
|
||||
|
||||
## 13. Состояния и навигация
|
||||
|
||||
| Состояние | Ожидаемое поведение панели |
|
||||
|---|---|
|
||||
| План существует, writer | Последнее доступное пространство, View; editors доступны |
|
||||
| План существует, read-only | То же пространство и View; editors отсутствуют |
|
||||
| Пустой план, writer | Существующий onboarding/создание первого пространства |
|
||||
| Пустой план, read-only | Read-only empty state без Add space, автодиалога и onboarding/editor runtime |
|
||||
| Последнее пространство удалено | Существующий deterministic fallback пространства |
|
||||
| Уход `/houseplan` → другой HA route | Editor/dialog/draft завершаются по действующему route-departure contract; сохраняется только пространство |
|
||||
| Возврат на `/houseplan` | Новая panel/card instance открывает сохранённое пространство в View |
|
||||
| Same-route технический remount | Действующий короткий warm-remount может сохранить непрерывность; это не считается уходом пользователя |
|
||||
| Entry reload | Панель снимается и возвращается; browser может потребовать уже существующий hard reload после смены frontend version |
|
||||
| Foreign collision | Чужая панель остаётся; House Plan dashboard-card и backend работают |
|
||||
| Missing/broken panel bundle | Sidebar panel не регистрируется; dashboard-card и backend работают |
|
||||
|
||||
Panel route не вводит новое хранилище навигации. Действующий last-space browser
|
||||
state общий с full card; editor mode не записывается.
|
||||
|
||||
## 14. Grid contract dashboard-card
|
||||
|
||||
`HouseplanCard` получает instance method:
|
||||
|
||||
```ts
|
||||
getGridOptions(): { columns: 'full' }
|
||||
```
|
||||
|
||||
Ограничения:
|
||||
|
||||
- метод только у `custom:houseplan-card`, не у `houseplan-space-card`;
|
||||
- `rows`, `min_rows`, `max_rows` и принудительная высота не задаются;
|
||||
- `getCardSize(): 12` сохраняется для Masonry и старых HA;
|
||||
- HA, не знающая метод, его игнорирует;
|
||||
- существующий вручную выбранный размер Sections не перезаписывается.
|
||||
|
||||
## 15. Первый запуск и i18n
|
||||
|
||||
### 15.1 Config flow
|
||||
|
||||
Успешный `async_create_entry` использует `description="panel_ready"` — этот
|
||||
публичный контракт присутствует в HA 2024.6. Текст EN/RU/DE/FR:
|
||||
|
||||
- предлагает открыть House Plan в sidebar, если пункт появился;
|
||||
- называет desktop рекомендуемым местом для редактирования;
|
||||
- не обещает панель безусловно: если пункта нет, предлагает dashboard-card и
|
||||
System Health, что покрывает collision/fail-soft. Безусловной ссылки на
|
||||
`/houseplan` нет: при foreign collision этот route принадлежит другому panel.
|
||||
|
||||
### 15.2 Одноразовое уведомление #462
|
||||
|
||||
Стабильный ID и persisted flag не меняются. Текст существующего уведомления во
|
||||
всех четырёх backend locales теперь сообщает, что:
|
||||
|
||||
1. House Plan доступен в боковом меню;
|
||||
2. после update всё ещё нужен полный reload страницы;
|
||||
3. dashboard-card остаётся доступна и при ручном управлении resource проверяется
|
||||
Settings → Dashboards → Resources.
|
||||
|
||||
Существующий пользователь, уже получивший уведомление #462, не получает его
|
||||
повторно только ради #486. Нового флага или второго уведомления нет.
|
||||
|
||||
Sidebar brand **House Plan** не локализуется. Текст карточки/onboarding следует
|
||||
языку browser HA как сейчас; menu accessible label использует HA localization.
|
||||
|
||||
Точные ключи, обязательные одновременно в источнике и EN/RU/DE/FR:
|
||||
|
||||
- новый backend `config.create_entry.panel_ready`;
|
||||
- существующие, но с обновлённым текстом
|
||||
`issues.frontend_reload_notice.title` и
|
||||
`issues.frontend_reload_notice.description`;
|
||||
- новые System Health labels `system_health.info.panel_file`,
|
||||
`system_health.info.panel_static_path`, `system_health.info.panel_status`,
|
||||
`system_health.info.panel_url`, `system_health.info.panel_module_url`,
|
||||
`system_health.info.panel_error`;
|
||||
- новый frontend `empty.read_only`, объясняющий, что первое пространство может
|
||||
создать пользователь с правом редактирования.
|
||||
|
||||
## 16. Модель, миграция и совместимость
|
||||
|
||||
- `CardConfig`, plan schema, layout, entities, markers, spaces и storage format
|
||||
не меняются; config migration отсутствует.
|
||||
- Существующие dashboard-card, kiosk, fixed-floor и space-card не меняются.
|
||||
- После обновления существующая установка получает панель при следующем
|
||||
setup/reload интеграции; план и browser last-space остаются.
|
||||
- HA 2024.6 поддерживается без новых kwargs. Compatibility unit/stub должен
|
||||
падать, если implementation начнёт их передавать.
|
||||
- Browser с уже определёнными старыми custom elements может применить новый
|
||||
frontend только после hard reload; действующий version/reload-контракт #462
|
||||
остаётся authority.
|
||||
- Нативные HA overrides sidebar title/order/visibility переживают unregister по
|
||||
правилам HA и не чистятся через private storage.
|
||||
|
||||
## 17. Accessibility, touch и responsive
|
||||
|
||||
- Panel shell не ухудшает существующие pointer, keyboard и touch-контракты View
|
||||
из `docs/TOUCH-SUPPORT.md`; #486 не подменяет отдельный аудит #31.
|
||||
- Menu button и все app-bar действия имеют hit target не меньше 44×44 CSS px,
|
||||
видимый focus и accessible name.
|
||||
- App bar — landmark/header; title — доступный page heading. Скрытый внутренний
|
||||
title не создаёт дублирующее объявление.
|
||||
- Tab order начинается с menu и продолжается существующими House Plan controls;
|
||||
retained header controls и уже доступные stage interactives сохраняют
|
||||
focus-visible, accessible name и keyboard activation. Shell не делает
|
||||
autofocus и не крадёт focus при `hass` update/refit.
|
||||
- При 320 CSS px, tablet portrait/landscape, desktop wide, sidebar collapse и
|
||||
mobile safe area отсутствуют horizontal scroll, обрезанная app bar и
|
||||
недоступные View controls.
|
||||
- Редакторы на touch остаются best-effort. Документация прямо рекомендует
|
||||
desktop и не обещает полноценную touch-редактуру из-за новой панели.
|
||||
- `prefers-reduced-motion` использует существующие правила карточки; shell не
|
||||
добавляет самостоятельную декоративную анимацию.
|
||||
|
||||
## 18. Производительность
|
||||
|
||||
1. Регистрация panel entry не меняет cold-load dashboard-card и её Lovelace
|
||||
resource.
|
||||
2. До первого открытия `/houseplan` browser не загружает panel entry.
|
||||
3. Populated panel View загружает panel shell + существующий initial card graph;
|
||||
lazy editor/onboarding/locale/isometric/PDF chunks не становятся eager.
|
||||
4. `hass` updates не пересоздают child, не повторяют `setConfig` и не создают
|
||||
второй WebSocket subscription/render tree.
|
||||
5. Container observer не образует write/read loop и не добавляет постоянный
|
||||
layout read на каждый HA state tick.
|
||||
6. Card ceiling не повышается; panel-only ceiling из §11.4 проверяется CI.
|
||||
|
||||
## 19. Acceptance criteria
|
||||
|
||||
- **AC1 (`backend` + mutation):** setup на поддерживаемом HA регистрирует ровно
|
||||
одну `/houseplan` custom panel с exact element/title/icon/versioned module URL,
|
||||
`require_admin=False`, без kwargs новее HA 2024.6; мутанты path, permissions,
|
||||
module URL и API обязаны падать.
|
||||
- **AC2 (`backend` + mutation):** unload/reload/disable/remove удаляют только
|
||||
owned exact registry object и не оставляют duplicate; старая generation и
|
||||
foreign collision не могут удалить/переписать текущую чужую или новую панель.
|
||||
- **AC3 (`backend`):** missing panel file, static-path failure, collision и API
|
||||
exception не роняют setup/card/WS; System Health честно различает все статусы
|
||||
§9.4 и не раскрывает exception message/path/data.
|
||||
- **AC4 (`unit` + bundle gate):** manifest явно различает card/panel entries,
|
||||
inventories и graphs; card `entry`/initial semantics обратно совместимы;
|
||||
оба stable-entry stale-load поведения, sync materialization, freshness, tree
|
||||
и release zip проверяются отрицательными fixtures, включая unlisted
|
||||
root-level `houseplan-*.js`.
|
||||
- **AC5 (`unit` + bundle gate + performance + mutation):** panel entry
|
||||
отсутствует в card initial graph,
|
||||
`initialViewFiles ⊆ initialPanelFiles`, действующий card ceiling не повышен,
|
||||
`initialPanelOnlyGzipBytes ≤ 8 KiB` и один card graph не продублирован;
|
||||
eager-import/duplicate-graph mutant обязан уронить проверку.
|
||||
- **AC6 (`unit` + smoke):** panel shell создаёт один child, делает один
|
||||
`setConfig`, прокидывает все `hass` updates и реагирует на
|
||||
`narrow`/`route`/`panel` без remount; disconnect/reconnect не размножает
|
||||
listeners/child.
|
||||
- **AC7 (`smoke` + golden):** wide View `1280×800`, wide Plan editor и narrow
|
||||
read-only/empty `320×720` имеют `scrollWidth-clientWidth ≤ 1 CSS px`; stage
|
||||
отличается от измеренного content-slot не более чем на 1 CSS px, остаётся
|
||||
положительным и после одного resize/orientation intent стабилизирует rect в
|
||||
пределах 0.5 CSS px за два последовательных animation frame. Нет двойного
|
||||
title; fit envelope целиком внутри stage с действующим отступом. Эталоны
|
||||
принимаются только независимым Linux capture/review.
|
||||
- **AC8 (`smoke` + code review):** пользовательская активация menu на
|
||||
wide/narrow dispatches bubbling + composed `hass-toggle-menu`; его
|
||||
focus/ARIA/hit target корректны,
|
||||
retained header и representative existing stage interactive остаются
|
||||
keyboard-reachable; HA kiosk не превращается в card kiosk.
|
||||
- **AC9 (`backend` + smoke):** admin и read-only пользователь видят panel;
|
||||
editor visibility и реальные write calls по-прежнему следуют
|
||||
`can_write`/`admin_only`, включая разрешённого non-admin.
|
||||
- **AC10 (`unit` + smoke + mutation):** populated/empty writer/empty read-only и
|
||||
missing last-space дают состояния §13; read-only branch не показывает CTA,
|
||||
не открывает диалог и не импортирует onboarding/editor runtime; существующий
|
||||
writer onboarding не дублируется.
|
||||
- **AC11 (`unit` + smoke):** уход с panel route и возврат сохраняют только space
|
||||
и открывают View; same-route technical remount сохраняет действующий короткий
|
||||
continuity contract.
|
||||
- **AC12 (`unit` + code review):** full card возвращает только
|
||||
`{columns:"full"}`, сохраняет `getCardSize()`, space-card не получает новый
|
||||
контракт. Метод объявляет только default; код House Plan не читает и не
|
||||
мутирует сохранённый HA Sections layout, поэтому ручной размер остаётся
|
||||
ответственностью host HA.
|
||||
- **AC13 (`backend` + i18n gate):** config-flow completion и существующее
|
||||
reload notice имеют согласованные EN/RU/DE/FR keys/text; существующий persisted
|
||||
flag не создаёт повторное уведомление при upgrade.
|
||||
- **AC14 (`unit` + backend + code review):** plan/config/layout schema и
|
||||
`CardConfig` не меняются; публичные panel assets не содержат secrets/user data;
|
||||
HA 2024.6 compatibility stub отвергает использование новых API/kwargs.
|
||||
- **AC15 (`docs` + code review):** README EN/RU, User Guide EN/RU,
|
||||
Architecture, UX Modes, Touch Support, Testing и Status описывают panel как
|
||||
основной вход, dashboard-card как optional, desktop-first editing,
|
||||
lifecycle/fail-soft диагностику и Sections default.
|
||||
|
||||
## 20. Test plan
|
||||
|
||||
### Backend HA harness / pure compatibility
|
||||
|
||||
1. Successful register + current panel registry contents.
|
||||
2. Unload, reload, disable, remove and stale-generation callback.
|
||||
3. Pre-existing foreign panel; external replacement after our register.
|
||||
4. Missing panel bundle and failing static/register API.
|
||||
5. `get_panels` visibility for admin/read-only and server write denial.
|
||||
6. System Health matrix and safe error fingerprint.
|
||||
7. Config flow `description=panel_ready`, notification persisted flag and all
|
||||
locales.
|
||||
8. HA 2024.6-shaped stubs without newer parameters/methods.
|
||||
|
||||
### Unit/tooling
|
||||
|
||||
1. Exact Rollup input `{ 'houseplan-card': 'src/houseplan-card.ts',
|
||||
'houseplan-panel': 'src/houseplan-panel.ts' }`, `entryFileNames:'[name].js'`
|
||||
and deterministic exact filename/facade root selection.
|
||||
2. Card/panel dependency graphs, no duplication and budgets.
|
||||
3. Both stable-entry stale-load behaviors; missing/wrong digest/orphan or
|
||||
unlisted managed root-level entry fixture must fail.
|
||||
4. Sync order and packaged frontend inventory.
|
||||
5. Panel property/lifecycle/menu contract.
|
||||
6. `getGridOptions` exact return and space-card absence.
|
||||
|
||||
### Browser smoke/golden
|
||||
|
||||
1. Cold `/houseplan` populated View writer, then first editor intent.
|
||||
2. Empty writer onboarding and empty read-only state.
|
||||
3. Permission matrix, no `config.kiosk` propagation.
|
||||
4. Desktop wide, narrow 320 px, orientation/resize/sidebar collapse.
|
||||
5. View → editor → editor swap → View; stage rect and scroll probes.
|
||||
6. Route departure/return and same-route technical remount.
|
||||
7. Menu event, keyboard focus and touch target probes.
|
||||
8. Focused panel-host goldens from AC7 plus unchanged representative bare-card
|
||||
golden.
|
||||
|
||||
## 21. Затронутые файлы и модули
|
||||
|
||||
Ожидаемый набор (имена helper/test могут уточняться без изменения контракта):
|
||||
|
||||
- `src/houseplan-panel.ts`, `src/houseplan-card.ts`, panel/card styles;
|
||||
- `rollup.config.mjs`, `scripts/bundle-manifest.mjs`,
|
||||
`scripts/bundle-sync.mjs`, `scripts/bundle-tree.mjs`, bundle budget/freshness,
|
||||
`scripts/release-prerelease.mjs`, `.github/workflows/release.yml` и
|
||||
`.github/workflows/publish-prerelease.yml` там, где они предполагают один
|
||||
entry;
|
||||
- `custom_components/houseplan/__init__.py`, `frontend_registration.py` либо
|
||||
отдельный `panel_registration.py`, `frontend_asset_manifest.py`,
|
||||
`system_health.py`, `config_flow.py`, `manifest.json`;
|
||||
- `custom_components/houseplan/strings.json` и translations EN/RU/DE/FR;
|
||||
- targeted `test/*.test.mjs`, `tests_backend/test_ha_*.py`, browser smoke и
|
||||
golden matrix/baselines;
|
||||
- `README.md`, `README.ru.md`, `docs/USER-GUIDE.md`,
|
||||
`docs/USER-GUIDE.ru.md`, `docs/ARCHITECTURE.md`, `docs/UX-MODES.md`,
|
||||
`docs/TOUCH-SUPPORT.md`, `docs/TESTING.md`, `docs/STATUS.md`;
|
||||
- `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md` в пользовательском implementation
|
||||
commit.
|
||||
|
||||
## 22. Риски и защита
|
||||
|
||||
| Риск | Вероятность / ущерб | Защита |
|
||||
|---|---|---|
|
||||
| Multi-entry меняет «первый entry» и initial budget | high / high | explicit roots, negative manifest fixtures, unchanged card ceiling |
|
||||
| Release получает stale panel entry или missing chunk | medium / high | complete offline materialization, fail-loud stale-load behavior, zip integrity |
|
||||
| Unload снимает чужую/новую panel | medium / high | generation + registry object identity, collision tests |
|
||||
| App bar/safe area вычитаются дважды | high / high | container-owned sizing, responsive smoke/golden |
|
||||
| Shell пересоздаёт card на каждом `hass` | medium / high | one-child/one-setConfig contract and subscription probe |
|
||||
| `require_admin=False` ошибочно открывает writes | low / critical | existing backend auth authority + permission matrix |
|
||||
| Внутренний HA component/API меняется | medium / medium | public panel helper/event only, minimum-HA stub |
|
||||
| Двойная шапка съедает высоту | high / medium | panel app bar + hide only duplicated product title |
|
||||
| Editor возвращается после ухода с route | medium / medium | existing route-departure integration smoke |
|
||||
| Panel недоступна из-за пользовательского sidebar override | medium / low | direct `/houseplan`, docs; private storage не трогаем |
|
||||
|
||||
## 23. Rollback
|
||||
|
||||
Если panel lifecycle или frontend host даёт критическую регрессию:
|
||||
|
||||
1. перестать вызывать panel registration и снять owned `/houseplan` при unload;
|
||||
2. оставить card entry, dashboard-card, server stores и plan schema без отката;
|
||||
3. panel entry можно сохранить неиспользуемым один цикл или убрать вместе с
|
||||
manifest field/tooling после проверки package inventory;
|
||||
4. `getGridOptions` можно откатить независимо без изменения сохранённых cards;
|
||||
5. frontend reload notification вернуть к нейтральному тексту без изменения
|
||||
persisted flag.
|
||||
|
||||
Откат не требует миграции данных и не изменяет пользовательские планы.
|
||||
|
||||
## 24. Release-артефакты
|
||||
|
||||
- по одной значимой записи со ссылкой #486 в `docs/CHANGELOG.ru.md` и
|
||||
`docs/CHANGELOG.md`;
|
||||
- README EN/RU и User Guide EN/RU с первичным sidebar flow и optional card;
|
||||
- внешний landing и `llms.txt` явно отложены по §8, потому что не принадлежат
|
||||
этому репозиторию;
|
||||
- Architecture/UX/Touch/Testing/Status по AC15;
|
||||
- принятые panel-host golden и, если меняется документационный кадр, пересъёмка
|
||||
через Linux workflow + `docs:accept -- --reviewed`;
|
||||
- bundle manifest/tree/budget/freshness и HACS zip с обоими entries;
|
||||
- targeted backend/unit/smoke/golden gates, затем обычный full-track S7 review;
|
||||
- security evidence: public bundle inventory и неизменные server write guards;
|
||||
- performance evidence: неизменный card ceiling и отдельный panel-only budget.
|
||||
@@ -28,6 +28,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#486](https://github.com/Matysh/houseplan-card/issues/486) Панель House Plan в боковом меню HA | [486-house-plan-panel.md](486-house-plan-panel.md) |
|
||||
| [#484](https://github.com/Matysh/houseplan-card/issues/484) Внешняя размерная цепь ступенчатого фасада в PDF | [484-pdf-exterior-dimension-chain.md](484-pdf-exterior-dimension-chain.md) |
|
||||
| [#482](https://github.com/Matysh/houseplan-card/issues/482) Доводка экспорта пространства в PDF | [482-pdf-export-polish.md](482-pdf-export-polish.md) |
|
||||
| [#471](https://github.com/Matysh/houseplan-card/issues/471) Убрать белые raised plates вокруг маркеров и названий комнат | [471-isometric-overlay-white-plates.md](471-isometric-overlay-white-plates.md) |
|
||||
|
||||
Reference in New Issue
Block a user