feat v1.11.0: full English translation + en/ru UI localization

- All card UI strings moved to src/i18n.ts (en/ru); language follows the HA
  profile automatically, new 'language: en|ru' card option forces it; GUI
  editor localized and got the language dropdown; generated device names
  localized via BuildCtx.loc.
- English-only codebase: comments, docstrings, test names, backend error
  messages and logs. Russian remains only in the ru dictionary, ru.json,
  iconFor regexes matching Russian device names (+their fixtures) and README.ru.md.
- Docs English-first: README (EN) + README.ru.md, ARCHITECTURE/DEVELOPMENT/
  ROADMAP/CHANGELOG fully translated; translations/en.json had Russian - fixed.
- Removed obsolete RELEASE_NOTES_v1.9.3.md and scripts_publish.sh.
This commit is contained in:
Matysh
2026-07-05 21:43:58 +03:00
parent f1b372f0e6
commit 94fb4af14c
29 changed files with 1692 additions and 1210 deletions
+89 -89
View File
@@ -1,101 +1,101 @@
# Архитектура House Plan
# House Plan architecture
Обновлено: 2026-07-04 (v1.2.2). Репозиторий = HACS-интеграция (категория **Integration**),
которая содержит и бэкенд (`custom_components/houseplan`), и Lovelace-карточку (`src/``dist/`).
Updated: 2026-07-04 (v1.2.2). The repository = a HACS integration (category **Integration**)
that contains both the backend (`custom_components/houseplan`) and the Lovelace card (`src/``dist/`).
## Состав
## Layout
```
houseplan-card/
├─ src/ # исходники карточки (TypeScript + Lit 3)
│ ├─ houseplan-card.ts # карточка: рендер, состояния, drag, tooltip, sticky-шапка
│ ├─ editor.ts # GUI-редактор конфига (ha-form + селекторы)
│ ├─ rules.ts # правила иконок (iconFor), курирование, группы, приоритет доменов
├─ src/ # card sources (TypeScript + Lit 3)
│ ├─ houseplan-card.ts # the card: rendering, states, drag, tooltip, sticky header
│ ├─ editor.ts # GUI config editor (ha-form + selectors)
│ ├─ rules.ts # icon rules (iconFor), curation, groups, domain priority
│ └─ data/
│ ├─ house.ts # геометрия: ROOMS (комнаты→area), FLOOR_VB (viewBox), названия
│ └─ backgrounds.ts # ВЕКТОРНЫЕ планы (SVG base64) + FLOOR_BG_RECT (позиционирование)
├─ dist/houseplan-card.js # сборка (rollup+terser), ~290 КБ, планы внутри
├─ custom_components/houseplan/ # интеграция HA
│ ├─ __init__.py # setup: Store, WS-команды, раздача JS (add_extra_js_url)
│ ├─ house.ts # geometry: ROOMS (rooms→area), FLOOR_VB (viewBox), names
│ └─ backgrounds.ts # VECTOR plans (SVG base64) + FLOOR_BG_RECT (positioning)
├─ dist/houseplan-card.js # build (rollup+terser), ~290 KB, plans embedded
├─ custom_components/houseplan/ # the HA integration
│ ├─ __init__.py # setup: Store, WS commands, JS serving (add_extra_js_url)
│ ├─ websocket_api.py # houseplan/layout/get|set|update
│ ├─ config_flow.py # одна запись; опция admin_only (правка только админам)
│ ├─ config_flow.py # single entry; admin_only option (editing restricted to admins)
│ ├─ const.py # DOMAIN, STORAGE_KEY, VERSION, FRONTEND_URL
│ └─ frontend/houseplan-card.js # копия dist, раздаётся как /houseplan_files/houseplan-card.js
├─ assets/ # исходники планов: f1_plan.svg, f2_plan.svg (РЕМПЛАННЕР), *_bg.png (старые растровые)
├─ hacs.json # манифест HACS
└─ docs/ # эта документация
│ └─ frontend/houseplan-card.js # copy of dist, served as /houseplan_files/houseplan-card.js
├─ assets/ # plan sources: f1_plan.svg, f2_plan.svg (REMPLANNER), *_bg.png (old raster versions)
├─ hacs.json # HACS manifest
└─ docs/ # this documentation
```
## Ключевые решения
## Key decisions
1. **Один репозиторий — интеграция + карточка.** Интеграция раздаёт JS
(`async_register_static_paths`) и регистрирует его как **Lovelace-ресурс** (module) —
фронтенд дожидается ресурсов перед рендером, поэтому карточка работает и на холодном старте
мобильного приложения (в отличие от `add_extra_js_url`, который остаётся фолбэком для
YAML-режима). Пользователю не нужно вручную добавлять ресурс.
2. **Раскладка иконок — на сервере.** `helpers.storage.Store(1, "houseplan.layout")`
`.storage/houseplan.layout`. Карточка читает/пишет через `hass.callWS`
(`houseplan/layout/get|set|update`). Fallback — localStorage (если интеграции нет).
3. **Без токена.** Всё из объекта `hass` фронтенда: `hass.states` (реактивно),
`hass.devices/entities/areas` (реестры). Никаких прямых WS-подключений.
4. **Реактивность.** Каждое изменение состояния в HA приводит к set hass → re-render.
Температуры/LQI/вкл-выкл live по определению (проверено подменой state).
1. **One repository — integration + card.** The integration serves the JS
(`async_register_static_paths`) and registers it as a **Lovelace resource** (module) —
the frontend waits for resources before rendering, so the card also works on a cold start
of the mobile app (unlike `add_extra_js_url`, which remains a fallback for
YAML mode). The user does not need to add the resource manually.
2. **Icon layout lives on the server.** `helpers.storage.Store(1, "houseplan.layout")`
`.storage/houseplan.layout`. The card reads/writes via `hass.callWS`
(`houseplan/layout/get|set|update`). Fallback — localStorage (when the integration is absent).
3. **No token.** Everything comes from the frontend `hass` object: `hass.states` (reactive),
`hass.devices/entities/areas` (registries). No direct WS connections.
4. **Reactivity.** Every state change in HA leads to set hass → re-render.
Temperatures/LQI/on-off are live by definition (verified by substituting state).
## Координатная система
## Coordinate system
- Базовое пространство: **1489×1053** («пиксели» старого PNG-рендера, 1 ед. = 1 px).
Все комнаты, позиции иконок и viewBox этажей — в нём. НЕ менять без миграции раскладки.
- Векторные планы вставляются `<image href=svg>` в прямоугольник `FLOOR_BG_RECT`:
- Base space: **1489×1053** ("pixels" of the old PNG render, 1 unit = 1 px).
All rooms, icon positions and floor viewBoxes live in it. DO NOT change without a layout migration.
- Vector plans are inserted as `<image href=svg>` into the `FLOOR_BG_RECT` rectangle:
- f1: scale **0.647**, offset **(490, 27)** → rect [490, 27, 774.2, 949.3]
- f2: scale **0.896**, offset **(351, 21)** → rect [351, 21, 1048.4, 961.4]
- вычислено растровой корреляцией (cv2.matchTemplate по бинаризованным картам темноты)
рендера SVG с эталонным PNG; точность ~1 px. Скрипты воспроизводимы (docs/DEVELOPMENT.md).
- Комнаты (`ROOMS`) сняппены к внутренним граням стен (полуавто: поиск ближайшей «тёмной линии»
по профилю + ручная доводка по overlay-рендерам).
- computed via raster correlation (cv2.matchTemplate on binarized darkness maps) of the
SVG render against the reference PNG; accuracy ~1 px. The scripts are reproducible (docs/DEVELOPMENT.md).
- Rooms (`ROOMS`) are snapped to the inner faces of walls (semi-automatic: search for the nearest
"dark line" along the profile + manual fine-tuning against overlay renders).
## Модель данных карточки (runtime)
## Card data model (runtime)
`DevItem`: id (device_id), name, model, area, floor, icon, entities[], primary (сущность для
more-info по приоритету доменов), temp, members[] (группа ламп), link/linkPrimary (Z2M-группа).
`DevItem`: id (device_id), name, model, area, floor, icon, entities[], primary (the entity used for
more-info by domain priority), temp, members[] (light group), link/linkPrimary (Z2M group).
Построение из реестров (`_buildDevices`), правила 1-в-1 из прототипа:
- показываются только устройства с area из списка комнат;
- скрываются: entry_type=service, интеграции из EXCLUDED_DOMAINS, model=Group, сцены, мосты,
под-устройства myheat, дубли по «имя|area»;
- **устройство с сущностью `lock.*` всегда получает `mdi:lock`** (TTLock-замки в реестре
называются «Дом»/«Терраса»/«Кладовка» — по имени не распознать);
- лампы (mdi:lightbulb) ≥2 в комнате схлопываются в группу `mdi:lightbulb-group`
(клик → меню: вся группа + отдельные).
Built from the registries (`_buildDevices`), rules carried over 1-to-1 from the prototype:
- only devices with an area from the room list are shown;
- hidden: entry_type=service, integrations from EXCLUDED_DOMAINS, model=Group, scenes, bridges,
myheat sub-devices, duplicates by "name|area";
- **a device with a `lock.*` entity always gets `mdi:lock`** (TTLock locks in the registry
are named "Dom"/"Terrasa"/"Kladovka" [House/Terrace/Storeroom] — unrecognizable by name);
- lamps (mdi:lightbulb) with ≥2 in a room collapse into a group `mdi:lightbulb-group`
(click → menu: the whole group + individual lamps).
## Живые данные
## Live data
- Температура: сущность с device_class=temperature / °C / `_temperature$`метка справа.
- LQI (zigbee): среднее по `*_linkquality`-сущностям → метка под иконкой; цвет
`lqiColor()`: ≤40 красный → ≥180 зелёный (hsl-градиент). Среднее по комнате — в тултипе комнаты.
- Классы состояния иконки: on (жёлтый), open (оранжевый: cover/valve/lock/binary_sensor
проблемных классов), unavail (прозрачность).
- Temperature: an entity with device_class=temperature / °C / `_temperature$`label on the right.
- LQI (zigbee): the average over `*_linkquality` entities → label under the icon; color via
`lqiColor()`: ≤40 red → ≥180 green (hsl gradient). The room average is shown in the room tooltip.
- Icon state classes: on (yellow), open (orange: cover/valve/lock/binary_sensor
of problem classes), unavail (transparency).
## Размеры
## Sizes
`icon_size` в конфиге = **% ширины видимой области плана** (дефолт 2.5). Реализация:
`.stage { container-type: inline-size }` + размеры в `cqw`. Легаси px-значения (>8) игнорируются.
`icon_size` in the config = **% of the visible plan area width** (default 2.5). Implementation:
`.stage { container-type: inline-size }` + sizes in `cqw`. Legacy px values (>8) are ignored.
## Sticky-шапка
## Sticky header
`.head { position: sticky; top: var(--header-height, 56px) }`; ОБЯЗАТЕЛЬНО
`ha-card { overflow: visible }``overflow: hidden` ломает sticky.
`.head { position: sticky; top: var(--header-height, 56px) }`; it is MANDATORY that
`ha-card { overflow: visible }``overflow: hidden` breaks sticky.
## Маркеры устройств (v1.6.0+)
## Device markers (v1.6.0+)
`config.markers[]`: `{id, binding:'device:<id>'|'entity:<eid>'|'virtual', space?, area?, hidden?,
name?, icon?, model?, link?, description?, pdfs:[{name,url}]}`. Гибрид: авто-устройства HA
появляются сами; маркер с `binding=device:<id>` их перекрывает (метаданные/перепривязка/скрытие),
`entity:<eid>`для групп/хелперов, `virtual` — ручной значок без HA. id маркера = device_id /
`lg_<eid>` / `v_<rand>` (сохраняет позицию в layout). Пикер привязки исключает уже размещённые
ссылки и дубли по имя|area. Файлы-инструкции: `houseplan/file/set``/config/houseplan/files/<id>/`,
отдача `/houseplan_files/files/`.
name?, icon?, model?, link?, description?, pdfs:[{name,url}]}`. A hybrid: auto-discovered HA devices
appear on their own; a marker with `binding=device:<id>` overrides them (metadata/rebinding/hiding),
`entity:<eid>`for groups/helpers, `virtual` — a manual icon without HA. The marker id = device_id /
`lg_<eid>` / `v_<rand>` (preserves the position in the layout). The binding picker excludes already-placed
references and duplicates by name|area. Manual files: `houseplan/file/set``/config/houseplan/files/<id>/`,
served from `/houseplan_files/files/`.
## Серверная конфигурация (v1.3.0+)
## Server-side configuration (v1.3.0+)
`.storage/houseplan.config` (Store):
```json
@@ -104,31 +104,31 @@ name?, icon?, model?, link?, description?, pdfs:[{name,url}]}`. Гибрид: а
"virtual_devices": [{"id","space","name","icon","x","y","note?","entity_id?"}],
"settings": {"exclude_integrations":[],"group_lights":true} }
```
Все координаты **нормированные (0..1 от плана пространства)**; рендер-пространство
1000 × 1000/aspect. Раскладка v2: `{device_id: {"s": space, "x", "y"}}` (нормир.).
Файлы планов: `<config>/houseplan/plans/<space>.<ext>` → URL `/houseplan_files/plans/…`.
Если server config пуст — карточка работает от legacy-бандла (дача) и показывает кнопку
миграции «На сервер» в режиме правки. Дача мигрирована 2026-07-04.
All coordinates are **normalized (0..1 of the space plan)**; the render space is
1000 × 1000/aspect. Layout v2: `{device_id: {"s": space, "x", "y"}}` (normalized).
Plan files: `<config>/houseplan/plans/<space>.<ext>` → URL `/houseplan_files/plans/…`.
If the server config is empty, the card falls back to the legacy bundle (the dacha) and shows a
"To server" migration button in edit mode. The dacha was migrated on 2026-07-04.
## Редактор разметки (v1.4.0+)
## Markup editor (v1.4.0+)
Состояние в карточке: `_markup` (режим), `_tool` (draw/erase/delroom), `_path` (текущий контур,
вершины по сетке GRID_N=60). Клики по stage → `_svgPoint``_snap`. Каждая пара точек добавляет
сегмент в `space.segments` (дедуп по ключу, сохранение config/set с дебаунсом). Контур замкнут
= клик по первой вершине → селект зоны (hass.areas) + имя → room {poly}. Комнаты-полигоны и
прямоугольники рендерятся единообразно (hit-test: point-in-polygon / rect).
State inside the card: `_markup` (mode), `_tool` (draw/erase/delroom), `_path` (the current outline,
vertices on the GRID_N=60 grid). Clicks on the stage → `_svgPoint``_snap`. Each pair of points adds a
segment to `space.segments` (dedup by key, saved via config/set with debounce). The outline is closed
= a click on the first vertex → area select (hass.areas) + name → room {poly}. Polygon rooms and
rectangles are rendered uniformly (hit-test: point-in-polygon / rect).
## WS API интеграции
## Integration WS API
| Команда | Параметры | Ответ |
| Command | Parameters | Response |
|---|---|---|
| `houseplan/layout/get` | — | `{layout: {device_id: {x,y}}}` |
| `houseplan/layout/set` | `layout` | `{ok}` (admin_only опционально) |
| `houseplan/layout/set` | `layout` | `{ok}` (admin_only optional) |
| `houseplan/layout/update` | `device_id`, `pos` | `{ok}` |
| `houseplan/config/get` | — | `{config, rev}` |
| `houseplan/config/set` | `config`, `expected_rev?` | `{ok, rev}` / err `conflict`; событие `houseplan_config_updated` |
| `houseplan/plan/set` | `space_id`, `ext` (svg/png/jpg/webp), `data` (b64, ≤8МБ) | `{ok, url}` |
| `houseplan/file/set` | `marker_id`, `filename`, `data` (b64) | `{ok,url,name}` (legacy, лимит WS) |
| `houseplan/config/set` | `config`, `expected_rev?` | `{ok, rev}` / err `conflict`; event `houseplan_config_updated` |
| `houseplan/plan/set` | `space_id`, `ext` (svg/png/jpg/webp), `data` (b64, ≤8 MB) | `{ok, url}` |
| `houseplan/file/set` | `marker_id`, `filename`, `data` (b64) | `{ok,url,name}` (legacy, WS limit) |
**Загрузка файлов — HTTP** (не WS, у него лимит размера сообщения): `POST /api/houseplan/upload`
(multipart: marker_id + file), HomeAssistantView, requires_auth. Отдача — `/houseplan_files/files/`.
**File uploads go over HTTP** (not WS, which has a message-size limit): `POST /api/houseplan/upload`
(multipart: marker_id + file), HomeAssistantView, requires_auth. Served from `/houseplan_files/files/`.
+281 -263
View File
@@ -1,310 +1,328 @@
# Changelog
## v1.10.0 — 2026-07-05 (аудит: гонки записи, XSS, модульность)
**Бэкенд**
- **Гонка записи устранена**: все циклы load→modify→save (`layout/set|update|delete`,
`config/set`) сериализованы общим `asyncio.Lock` — параллельные WS-вызовы больше не теряют
изменения, проверка `expected_rev` стала атомарной.
- Новая WS-команда `houseplan/layout/delete` — чистка позиции при удалении маркера
(раньше в `.storage/houseplan.layout` копились сироты).
- Удалена мёртвая WS-команда `houseplan/file/set` — файлы грузятся только по HTTP
(`/api/houseplan/upload`), как и было в карточке.
- HTTP-загрузка: лимит 25 МБ применяется ПО МЕРЕ чтения multipart (обрыв на лимите),
а не после чтения всего файла в память; `stat()` файлов ушёл в executor.
- `request.app[KEY_HASS]` вместо устаревшего `request.app["hass"]` (с фолбэком для старых HA).
## v1.11.0 — 2026-07-05 (full English translation + UI localization)
- **UI localization (en/ru)**: every card string moved to `src/i18n.ts` dictionaries.
The language follows the HA user profile (`hass.locale`) automatically; a new
`language: en|ru` card option forces it. The GUI editor got the option and its own
localized labels. Generated device names (light group, unnamed, virtual device)
are localized via a `loc` callback in `BuildCtx`.
- **English-only codebase**: all comments, docstrings, section banners, JSDoc, test
names, backend WS/HTTP error messages and log lines translated to English.
Russian remains only where it is functional or content: the `ru` i18n dictionary,
`translations/ru.json` (config flow), `iconFor` regex patterns matching Russian
device names (with their test fixtures), and the Russian documentation copy.
- **Docs**: README is now English-first with a full Russian copy in `README.ru.md`;
`docs/ARCHITECTURE.md`, `DEVELOPMENT.md`, `ROADMAP.md` and the entire CHANGELOG
history translated to English. `translations/en.json` had Russian strings — fixed.
- Removed obsolete `RELEASE_NOTES_v1.9.3.md` and `scripts_publish.sh` (publication
is done; the repo lives at github.com/Matysh/houseplan-card).
**Фронтенд**
- **God-component разобран**: `houseplan-card.ts` 3023 → ~1990 строк.
Стили → `styles.ts`; типы → `types.ts`; построение устройств из реестров HA
(курирование, группы света, маркеры, LQI/температура) → `devices.ts` (чистые функции).
- **Last-writer-wins в раскладке устранён**: `_saveMarker` пишет позицию точечно
(`layout/update`), а не всей раскладкой (`layout/set`) — не затирает позиции из других
окон (регресс-класс инцидента v1.4.4). Перепривязка/удаление подчищают старую позицию.
- **XSS закрыт**: `link` и `pdfs[].url` маркеров проходят `safeUrl()` (только http(s)
и относительные пути; `javascript:`/`data:` отбрасываются). +12 тестов.
- Загрузка файлов через `hass.fetchWithAuth` (автообновление протухшего токена),
фолбэк на сырой токен.
- **Хардкод дачи убран из GUI-редактора**: список «пространство по умолчанию» строится
из серверного конфига (WS `config/get`), а не зашитых f1/f2/yard.
- `rules.ts`: удалён мёртвый экспорт `GROUP_TITLES`; регэкспы `iconFor` прекомпилированы.
- Единый `_errText()` во всех обработчиках ошибок (никогда «[object Object]»).
## v1.10.0 — 2026-07-05 (audit: write races, XSS, modularity)
**Backend**
- **Write race eliminated**: all load→modify→save cycles (`layout/set|update|delete`,
`config/set`) are serialized by a shared `asyncio.Lock` — concurrent WS calls no longer lose
changes, and the `expected_rev` check became atomic.
- New WS command `houseplan/layout/delete` — cleans up a position when a marker is deleted
(previously orphans accumulated in `.storage/houseplan.layout`).
- Removed the dead WS command `houseplan/file/set` — files are uploaded over HTTP only
(`/api/houseplan/upload`), as the card already did.
- HTTP upload: the 25 MB limit is enforced WHILE reading the multipart stream (abort at the limit),
not after reading the whole file into memory; file `stat()` moved into the executor.
- `request.app[KEY_HASS]` instead of the deprecated `request.app["hass"]` (with a fallback for older HA).
## v1.9.3 — 2026-07-05 (аудит + рефакторинг + тесты)
- Чистые функции `fitView` (contain-прямоугольник вьюпорта) и `declump` (расталкивание значков)
вынесены из карточки в `logic.ts` — покрыты юнит-тестами (было 9 → стало 14 тестов).
- `_lqiFor` и `_roomLqi` теперь используют общую `averageLqi` (убрано дублирование усреднения).
- Удалён мёртвый код после отказа от отдельного режима правки: поле `_edit` (писалось, но не
читалось), методы `_renderEditbar` и `_applyXY` (не вызывались).
- Поведение не изменилось — только структура и покрытие тестами. Проверено: tsc, сборка,
14 фронт-тестов + 10 бэк-тестов зелёные, headless-дымтест (рендер/зум/устройства/declump) чист.
**Frontend**
- **The god-component was broken up**: `houseplan-card.ts` 3023 → ~1990 lines.
Styles → `styles.ts`; types → `types.ts`; building devices from the HA registries
(curation, light groups, markers, LQI/temperature) → `devices.ts` (pure functions).
- **Last-writer-wins in the layout eliminated**: `_saveMarker` writes the position pointwise
(`layout/update`), not the whole layout (`layout/set`) — it no longer wipes positions from other
windows (the regression class of the v1.4.4 incident). Rebinding/deletion clean up the old position.
- **XSS closed**: marker `link` and `pdfs[].url` go through `safeUrl()` (only http(s)
and relative paths; `javascript:`/`data:` are rejected). +12 tests.
- File uploads via `hass.fetchWithAuth` (auto-refresh of an expired token),
fallback to the raw token.
- **The dacha hardcode removed from the GUI editor**: the "default space" list is built
from the server config (WS `config/get`), not the baked-in f1/f2/yard.
- `rules.ts`: removed the dead export `GROUP_TITLES`; the `iconFor` regexes are precompiled.
- A single `_errText()` in all error handlers (never "[object Object]").
## v1.9.2 — 2026-07-05 (сетка вдвое мельче)
- Шаг сетки разметки/привязки уменьшен вдвое: `GRID_N` 120 → 240. Позиции устройств и контуры
комнат НЕ пересчитываются и не сдвигаются: координаты хранятся нормированными долями и снапятся
к узлам сетки, а старые узлы (кратные 1/120) — точное подмножество новых (кратных 1/240).
Проверено на живой раскладке (65 позиций — все уже на новой сетке). Перетаскивание теперь
позволяет более точное позиционирование.
## v1.9.3 — 2026-07-05 (audit + refactoring + tests)
- The pure functions `fitView` (viewport contain rectangle) and `declump` (icon push-apart)
were extracted from the card into `logic.ts` — covered by unit tests (was 9 → now 14 tests).
- `_lqiFor` and `_roomLqi` now use the shared `averageLqi` (duplicate averaging removed).
- Removed dead code left after dropping the separate edit mode: the `_edit` field (written but
never read), the methods `_renderEditbar` and `_applyXY` (never called).
- No behavior change — only structure and test coverage. Verified: tsc, build,
14 frontend tests + 10 backend tests green, headless smoke test (render/zoom/devices/declump) clean.
## v1.9.1 — 2026-07-05 (сигнал Zigbee: чтение из атрибута + ZHA)
- Уровень сигнала теперь берётся не только из выделенного сенсора `*_linkquality` (Z2M), но и:
из сенсоров `*_lqi` (ZHA), и из АТРИБУТА `linkquality`/`lqi` на любой сущности устройства.
Это покрывает устройства, у которых отдельный сенсор сигнала отключён, но значение есть в атрибуте.
- ПРИМЕЧАНИЕ: если у устройства сенсор `*_linkquality` отключён (disabled_by=integration) И значение
нигде не публикуется как атрибут — сигнал показать нельзя, нужно включить сенсор в реестре HA.
## v1.9.2 — 2026-07-05 (grid twice as fine)
- The markup/snapping grid step was halved: `GRID_N` 120 → 240. Device positions and room
outlines are NOT recomputed and do not shift: coordinates are stored as normalized fractions and snap
to grid nodes, and the old nodes (multiples of 1/120) are an exact subset of the new ones (multiples of 1/240).
Verified on the live layout (65 positions — all already on the new grid). Dragging now
allows more precise positioning.
## v1.9.0 — 2026-07-05 (UX: перетаскивание всегда, расталкивание, показать все, адаптив)
- **Перетаскивание значков доступно всегда** — отдельный «режим правки» убран (кнопка ✥ удалена).
Клик по значку открывает карточку устройства (правка метаданных — через кнопку «Редактировать»
в ней), перетаскивание работает в любой момент. `_pointerDown` больше не требует `_edit`.
- **Значки не скучиваются** (`_declump`): после авто-раскладки по сетке выполняется расталкивание
в пределах комнаты, чтобы иконки не налезали друг на друга.
- **Позиции фиксируются при сохранении комнаты**: авто-позиции устройств зоны один раз пишутся
в раскладку, поэтому при смене порядка в реестре HA значки не перетасовываются.
- **Дубли не скрываются, а нумеруются**: устройства с одинаковым «имя|зона» получают суффикс
(« 2», « 3»…) вместо тихого исчезновения.
- **Кнопка 👁 «Показать все устройства»** в шапке: временно отключает курирование и показывает
все устройства зоны (мосты, служебные, дубли) — на случай, если нужное скрыто. Флаг `show_all`
в настройках конфига.
- **Подсказка продолжения разметки**: после сохранения комнаты тост показывает счётчик комнат и
предлагает обвести следующую или выйти из разметки.
- **Адаптивная шапка**: тулбар переносится и ужимается на узких экранах (media-query ≤620px).
## v1.9.1 — 2026-07-05 (Zigbee signal: reading from the attribute + ZHA)
- The signal level is now taken not only from the dedicated `*_linkquality` sensor (Z2M), but also:
from `*_lqi` sensors (ZHA), and from the `linkquality`/`lqi` ATTRIBUTE on any entity of the device.
This covers devices whose dedicated signal sensor is disabled but the value is available in an attribute.
- NOTE: if a device's `*_linkquality` sensor is disabled (disabled_by=integration) AND the value
is not published anywhere as an attribute — the signal cannot be shown; enable the sensor in the HA registry.
## v1.8.4 — 2026-07-04 (онбординг: обязательные поля + ведение пользователя)
- Первичная инициализация приведена к целевому сценарию: установка → диалог пространства →
комнаты → авто-значки устройств.
- **Подложка обязательна** при создании пространства: «Сохранить» неактивна без загруженного
плана (раньше требовалось только название — можно было создать пустое пространство).
- **Зона комнаты обязательна** с явным выходом: основная «Сохранить» требует привязку к зоне HA;
для декоративных комнат без устройств (холл, сауна) есть отдельная кнопка «Без зоны»
(нужно только имя). Раньше сохранить можно было по «имя ИЛИ зона».
- **Ведение пользователя:** на пустом серверном конфиге диалог пространства открывается
автоматически; после добавления первого пространства карточка сама входит в режим разметки
комнат с подсказкой «обведите комнаты».
- При сохранении комнаты с зоной значки устройств этой зоны авто-расставляются внутри контура
(курируемый набор: без мостов/служебных/дублей/одиночных ламп из групп), тост показывает,
сколько устройств добавлено.
## v1.9.0 — 2026-07-05 (UX: always-on dragging, declumping, show all, responsive)
- **Icon dragging is available at all times** — the separate "edit mode" is gone (the ✥ button removed).
Clicking an icon opens the device card (metadata editing — via the "Edit" button
inside it), dragging works at any moment. `_pointerDown` no longer requires `_edit`.
- **Icons no longer clump together** (`_declump`): after the automatic grid layout a push-apart pass
runs within the room so that icons do not overlap each other.
- **Positions are pinned when a room is saved**: the auto-positions of the area's devices are written
into the layout once, so icons do not get reshuffled when the order in the HA registry changes.
- **Duplicates are numbered instead of hidden**: devices with the same "name|area" get a suffix
(" 2", " 3"…) instead of silently disappearing.
- **The 👁 "Show all devices" button** in the header: temporarily disables curation and shows
all devices of the area (bridges, service records, duplicates) — in case a needed one is hidden. The `show_all`
flag in the config settings.
- **Markup continuation hint**: after saving a room a toast shows the room counter and
suggests outlining the next one or leaving markup mode.
- **Responsive header**: the toolbar wraps and shrinks on narrow screens (media query ≤620px).
## v1.8.3 — 2026-07-04 (мгновенный старт: кэш конфига, данные в фоне)
- Иконки и план появлялись с задержкой — карточка ждала ответа сервера (WS `config/get`
+ ретраи прогрева соединения), до этого модель пространств была пуста.
- Внедрён кэш «stale-while-revalidate»: снимок серверного конфига + rev + раскладки
сохраняется в `localStorage['houseplan_card_cfg_v1']`. В `setConfig` (синхронно, до прихода
hass) он восстанавливается — план и значки рисуются сразу из кэша (проверено: setConfig
наполняет модель за 0.2 мс без сервера), а свежие данные догружаются в фоне и обновляют
карточку. Живые состояния/температуры/сигнал подтягиваются по мере прогрева hass.
- Кэш обновляется после каждой успешной загрузки, live-ресинка и сохранения позиций иконок,
поэтому при следующем открытии виден актуальный снимок. При недоступном сервере показывается
последний известный план вместо пустого онбординга.
## v1.8.4 — 2026-07-04 (onboarding: required fields + user guidance)
- Initial setup brought to the target scenario: install → space dialog →
rooms → auto device icons.
- **The background is mandatory** when creating a space: "Save" stays disabled without an uploaded
plan (previously only the name was required — an empty space could be created).
- **The room area is mandatory**, with an explicit escape hatch: the main "Save" requires binding to an HA area;
for decorative rooms without devices (hall, sauna) there is a separate "No area" button
(only a name is needed). Previously saving was possible with "name OR area".
- **User guidance:** with an empty server config the space dialog opens
automatically; after the first space is added the card enters room markup mode
by itself with an "outline the rooms" hint.
- When a room with an area is saved, the icons of that area's devices are auto-placed inside the outline
(the curated set: without bridges/service records/duplicates/individual lamps from groups), and a toast shows
how many devices were added.
## v1.8.2 — 2026-07-04 (векторно-чёткий зум через viewBox + полная ширина сцены)
- Зум переведён с CSS `transform: scale()` на манипуляцию `viewBox` SVG. Раньше слой
растрировался и потом масштабировался — при приближении всё «мылилось». Теперь браузер
перерисовывает вектор в целевом разрешении: иконки, подписи комнат, линии, штриховка и
векторная подложка остаются чёткими на любом масштабе (проверено на 274%).
- Слой иконок (HTML) позиционируется и масштабируется по тому же `view`, а не общим
transform — иконки чёткие и растут вместе с планом.
- Сцена теперь занимает всю ширину вьюпорта (модель «contain» по аспекту сцены): при
зум-ине контент заполняет ширину без чёрных полей по бокам. При полном отдалении виден
весь план (по ширине и высоте), поля вокруг залиты фоном карточки, а не чёрным.
- Вся координатная математика (клик разметки `_svgPoint`, drag иконок, pan, pinch) переписана
в единых `view`-координатах — редактирование корректно на любом зуме. ResizeObserver сцены
пересчитывает `view` при изменении размеров (сайдбар, поворот). Пан/зум ограничены `fit`.
## v1.8.3 — 2026-07-04 (instant start: config cache, data in the background)
- Icons and the plan appeared with a delay — the card waited for the server response (WS `config/get`
+ connection warm-up retries), and until then the space model was empty.
- A "stale-while-revalidate" cache was introduced: a snapshot of the server config + rev + layout
is saved in `localStorage['houseplan_card_cfg_v1']`. In `setConfig` (synchronously, before
hass arrives) it is restored — the plan and icons are drawn immediately from the cache (verified: setConfig
populates the model in 0.2 ms without the server), while fresh data loads in the background and updates the
card. Live states/temperatures/signal come in as hass warms up.
- The cache is refreshed after every successful load, live resync and icon position save,
so on the next open the current snapshot is visible. When the server is unavailable the
last known plan is shown instead of the empty onboarding.
## v1.8.1 — 2026-07-04 (fit-to-viewport + сохранение зума по пространствам)
- Уменьшение масштаба до полного вида: нижний предел зума = 100% совпадает с полностью
видимым планом. `.stage` теперь ограничена по высоте вьюпорта
(`width:min(100%, calc((100dvh 118px) × aspect))`, `max-width:100%`, центрирование),
поэтому при сбросе/зум-ауте вся схема видна и по ширине, и по высоте без обрезки.
- Масштаб запоминается для каждого пространства отдельно и локально на устройстве
(`localStorage['houseplan_card_zoom_v1']`, `_zoomBySpace`): переключение этажей
восстанавливает свой зум; при возврате пан центрируется и ограничивается.
- Проверено на живом HA: f1→196%, f2→140%, двор нетронут — после переключений
значения восстанавливаются; сброс даёт 100% и полный план (874≤992 px по высоте).
## v1.8.2 — 2026-07-04 (vector-crisp zoom via viewBox + full-width stage)
- Zoom moved from CSS `transform: scale()` to manipulating the SVG `viewBox`. Previously the layer
was rasterized and then scaled — everything "blurred" when zooming in. Now the browser
redraws the vector at the target resolution: icons, room labels, lines, hatching and the
vector background remain crisp at any scale (verified at 274%).
- The icon layer (HTML) is positioned and scaled by the same `view`, not a global
transform — the icons are crisp and grow together with the plan.
- The stage now occupies the full viewport width (a "contain" model by the stage aspect): when
zoomed in the content fills the width without black bars on the sides. Fully zoomed out the
whole plan is visible (both width and height), the margins around it are filled with the card
background, not black.
- All coordinate math (markup click `_svgPoint`, icon drag, pan, pinch) was rewritten
in unified `view` coordinates — editing is correct at any zoom. A ResizeObserver on the stage
recomputes the `view` when dimensions change (sidebar, rotation). Pan/zoom are constrained to `fit`.
## v1.8.1 — 2026-07-04 (fit-to-viewport + per-space zoom persistence)
- Zooming out to the full view: the lower zoom limit = 100% coincides with the fully
visible plan. `.stage` is now constrained by the viewport height
(`width:min(100%, calc((100dvh 118px) × aspect))`, `max-width:100%`, centering),
so on reset/zoom-out the whole layout is visible both in width and height without clipping.
- The zoom level is remembered per space and locally per device
(`localStorage['houseplan_card_zoom_v1']`, `_zoomBySpace`): switching floors
restores each floor's zoom; on return the pan is centered and constrained.
- Verified on live HA: f1→196%, f2→140%, the yard untouched — after switching around the
values are restored; reset gives 100% and the full plan (874≤992 px in height).
## v1.8.0 — 2026-07-04 (зумирование плана)
- Зум и панорама схемы: колёсико мыши (к курсору), щипок двумя пальцами (pinch) на тач-экране,
перетаскивание фона одним пальцем при увеличении, кнопки −/сброс/+ в тулбаре, бейдж масштаба.
- Реализовано через CSS-transform на `.zoomwrap` (translate+scale); координатная математика
(клик разметки, drag иконок, тултипы) пересчитана с учётом зума/пана — редактирование
работает и при увеличении. Пан ограничен, чтобы контент всегда покрывал сцену. Зум 100800%.
- `.stage`: overflow hidden + touch-action none (свои жесты вместо браузерных).
## v1.8.0 — 2026-07-04 (plan zooming)
- Zoom and pan of the layout: mouse wheel (towards the cursor), two-finger pinch on a touch screen,
one-finger background drag when zoomed in, /reset/+ buttons in the toolbar, a zoom badge.
- Implemented via a CSS transform on `.zoomwrap` (translate+scale); the coordinate math
(markup clicks, icon drag, tooltips) was recomputed with zoom/pan in mind — editing
works when zoomed in too. Pan is constrained so the content always covers the stage. Zoom 100800%.
- `.stage`: overflow hidden + touch-action none (custom gestures instead of the browser's).
## v1.7.4 — 2026-07-04 (PDF по HTTP вместо WebSocket)
- КОРНЕВАЯ ПРИЧИНА невозможности загрузить PDF: файл слался как base64 одним WebSocket-сообщением,
а у WS есть лимит размера — реальный мануал (>2–3 МБ) превышал его, соединение рвалось
(«Connection lost»), из-за чего и падала загрузка, и закрывался диалог (обрыв WS → переподключение
ре-рендер). Подтверждено тестом: 1 МБ по WS ок, 3 МБ → Connection lost.
- Файлы-инструкции теперь грузятся через HTTP-эндпоинт `POST /api/houseplan/upload` (multipart,
HomeAssistantView, requires_auth, admin_only-проверка) — как медиа в самом HA. Проверено:
3 МБ (182мс) и 10 МБ (446мс) загружаются на ура.
- Читаемые ошибки везде (`_errText`): больше никаких «[object Object]»; понятные тексты для
## v1.7.4 — 2026-07-04 (PDF over HTTP instead of WebSocket)
- THE ROOT CAUSE of being unable to upload a PDF: the file was sent as base64 in a single WebSocket
message, and WS has a message-size limit — a real manual (>23 MB) exceeded it, the connection dropped
("Connection lost"), which both failed the upload and closed the dialog (WS drop → reconnect
re-render). Confirmed by test: 1 MB over WS is fine, 3 MB → Connection lost.
- Manual files are now uploaded through the HTTP endpoint `POST /api/houseplan/upload` (multipart,
HomeAssistantView, requires_auth, admin_only check) — like media in HA itself. Verified:
3 MB (182 ms) and 10 MB (446 ms) upload without a hitch.
- Readable errors everywhere (`_errText`): no more "[object Object]"; clear texts for
too_large / bad_ext / unauthorized.
- WS-команда houseplan/file/set оставлена для совместимости, но фронт использует HTTP.
- The WS command houseplan/file/set is kept for compatibility, but the frontend uses HTTP.
## v1.7.3 — 2026-07-04 (фиксы диалога устройства)
- PDF-инструкции не загружались: ручной base64 (`btoa(String.fromCharCode(...subarray))`) падал
на реальных файлах (RangeError при спреде больших массивов), а при закрытии диалога во время
await бросал null-доступ. Заменено на `FileReader.readAsDataURL` (надёжно для любого размера),
результаты аккумулируются и добавляются, только если диалог ещё открыт (без исключений).
- Диалог редактирования устройства/пространства/комнаты закрывался по клику мимо и «сам»
(случайный клик по фон-оверлею, в т.ч. после закрытия системного выбора файла). Убрано
закрытие по клику на фон у диалогов-редакторов — только явные Отмена/Сохранить/Esc.
Инфо-карточка (только чтение) по-прежнему закрывается кликом мимо.
- Проверено на живом HA: PDF прикрепляется; клик по фону и обновление данных диалог не закрывают.
## v1.7.3 — 2026-07-04 (device dialog fixes)
- PDF manuals failed to upload: the manual base64 (`btoa(String.fromCharCode(...subarray))`) crashed
on real files (RangeError when spreading large arrays), and when the dialog was closed during an
await it threw a null access. Replaced with `FileReader.readAsDataURL` (reliable for any size);
the results are accumulated and added only if the dialog is still open (no exceptions).
- The device/space/room edit dialog closed on an outside click and "by itself"
(an accidental click on the background overlay, including after closing the system file picker). Removed
background-click closing for editor dialogs — only explicit Cancel/Save/Esc.
The info card (read-only) still closes on an outside click.
- Verified on live HA: PDFs attach; a background click and dialog data refresh do not close the dialog.
## v1.7.2 — 2026-07-04 (фикс мобильного «ошибка конфигурации»)
- ПРИЧИНА: карточка подключалась только через `add_extra_js_url` (extra_module_url), загрузку
которого фронтенд НЕ дожидается — на холодном старте мобильного приложения дашборд рисовался
раньше регистрации элемента → «ошибка конфигурации». (Все рабочие HACS-карточки — Lovelace-ресурсы.)
- ФИКС 1: интеграция регистрирует карточку как **Lovelace-ресурс** (module) — их фронтенд ждёт
перед рендером. Идемпотентно (обновляет URL при смене версии, без дублей); фолбэк на
extra_module_url для YAML-режима Lovelace. `_register_lovelace_resource` в __init__.py.
- ФИКС 2: `_loadFromServer` ретраит (до 8 попыток по обновлениям hass) — если hass пришёл раньше
готовности WS, карточка не застревает на онбординге, а дожидается конфига.
- Проверено: ресурс houseplan-card.js зарегистрирован, extra_module_url пуст (единый чистый
модуль), карточка рендерит план на свежей вкладке без ошибки.
## v1.7.2 — 2026-07-04 (fix for the mobile "configuration error")
- THE CAUSE: the card was hooked up only via `add_extra_js_url` (extra_module_url), whose loading the
frontend does NOT wait for — on a cold start of the mobile app the dashboard was drawn
before the element was registered → "configuration error". (All working HACS cards are Lovelace resources.)
- FIX 1: the integration registers the card as a **Lovelace resource** (module) — the frontend waits for
those before rendering. Idempotent (updates the URL on a version change, no duplicates); a fallback to
extra_module_url for Lovelace YAML mode. `_register_lovelace_resource` in __init__.py.
- FIX 2: `_loadFromServer` retries (up to 8 attempts on hass updates) — if hass arrived before
WS readiness, the card does not get stuck on the onboarding but waits for the config.
- Verified: the houseplan-card.js resource is registered, extra_module_url is empty (a single clean
module), the card renders the plan on a fresh tab without the error.
## v1.7.0 — 2026-07-04 (аудит + рефакторинг + тесты)
- Удалён вшитый в бандл дом-образец (дача, ~245 КБ base64 планов + ROOMS/FLOOR_*): бандл
293 КБ → 83 КБ. Свежая установка показывает онбординг «Добавить пространство», а не чужой дом.
- Удалён код миграции legacy→сервер (дача уже на server config) и мёртвые пути
device_overrides/virtual_devices (заменены markers[]).
- Чистая логика вынесена в src/logic.ts (lqiColor, snapToGrid, segKey, pointInPolygon,
markerIdForBinding, averageLqi) — покрыта юнит-тестами (node:test, 9 тестов + iconFor).
- Бэкенд: валидация/санитайзеры вынесены в validation.py (без HA-импортов) — pytest, 10 тестов.
- БЕЗОПАСНОСТЬ: тест выявил, что санитайзер имён файлов/маркеров сохранял ведущие точки
(риск обхода каталогов через «..»); добавлено срезание ведущих точек, пустое → misc/file.
- Исправлены реальные баги, ранее замаскированные обрезанным rules.ts: DOMAIN_PRIORITY был
усечён (ломало выбор more-info-сущности); _defaultPositions падал на полигон-комнатах
(теперь bbox). tsc --noEmit добавлен в сборку и CI — строгая типизация проходит.
- Убраны дубли-мусор src/data/{editor,rules}.ts; версии синхронизированы (manifest застрял на 1.4.0).
- CI: добавлены typecheck, юнит-тесты фронта и бэка.
## v1.7.0 — 2026-07-04 (audit + refactoring + tests)
- Removed the sample house baked into the bundle (the dacha, ~245 KB of base64 plans + ROOMS/FLOOR_*): the bundle
went 293 KB → 83 KB. A fresh install shows the "Add a space" onboarding, not someone else's house.
- Removed the legacy→server migration code (the dacha is already on the server config) and the dead
device_overrides/virtual_devices paths (replaced by markers[]).
- Pure logic extracted into src/logic.ts (lqiColor, snapToGrid, segKey, pointInPolygon,
markerIdForBinding, averageLqi) — covered by unit tests (node:test, 9 tests + iconFor).
- Backend: validation/sanitizers extracted into validation.py (no HA imports) — pytest, 10 tests.
- SECURITY: a test revealed that the file/marker name sanitizer preserved leading dots
(a directory traversal risk via ".."); leading-dot stripping added, empty → misc/file.
- Fixed real bugs previously masked by the truncated rules.ts: DOMAIN_PRIORITY was
cut short (breaking the more-info entity selection); _defaultPositions crashed on polygon rooms
(now bbox). tsc --noEmit added to the build and CI — strict typing passes.
- Removed the junk duplicates src/data/{editor,rules}.ts; versions synchronized (the manifest was stuck at 1.4.0).
- CI: added typecheck, frontend and backend unit tests.
## v1.6.2 — 2026-07-04
- Поле «Комната» в диалоге устройства теперь для ВСЕХ значков (не только виртуальных):
для привязанных — «переопределить размещение» (по умолчанию по зоне устройства); смена
комнаты переставляет значок в её центр. Сохраняется в marker.space/area и выигрывает у зоны HA.
- В инфо-карточке добавлена кнопка «Редактировать» — открывает диалог редактирования устройства.
- The "Room" field in the device dialog is now for ALL icons (not only virtual ones):
for bound ones it "overrides the placement" (by default, the device's area); changing the
room moves the icon to its center. Stored in marker.space/area and wins over the HA area.
- An "Edit" button was added to the info card — it opens the device edit dialog.
## v1.6.0 — 2026-07-04 (Фаза 3: редактор устройств)
- Модель «маркеров» (config.markers[]): гибрид — авто-устройства HA появляются сами, маркеры
правят/перепривязывают/дополняют их и описывают ручные/виртуальные значки. id маркера
сохраняет позицию (device→device_id, entity→lg_<eid>, virtual→v_<rand>).
- Клик по значку: обычный режим → инфо-карточка (имя, модель, состояние, ссылка, описание,
PDF-инструкции, кнопка «Открыть в HA»); режим правки → диалог редактирования.
- Диалог устройства: имя, привязка (пикер всех устройств/групп HA+Z2M+хелперов с текстовым
фильтром, минус уже размещённые и дубли по имя|area, + «Виртуальное устройство»), MDI-иконка
(ha-icon-picker), модель, ссылка, описание, прикрепление файлов-инструкций (PDF на сервер),
комната (для виртуальных), Убрать/Отмена/Сохранить.
- Тулбар: кнопка «+» (тултип «Добавить устройство») — пустой диалог.
- Бэкенд: MARKER_SCHEMA в config; WS houseplan/file/set (файлы в /config/houseplan/files/,
≤25 МБ, отдача /houseplan_files/files/); статика files зарегистрирована.
- Виртуальные значки теперь перетаскиваются и позиционируются как обычные (layout по id).
## v1.6.0 — 2026-07-04 (Phase 3: device editor)
- The "markers" model (config.markers[]): a hybrid — auto-discovered HA devices appear by themselves, markers
edit/rebind/augment them and describe manual/virtual icons. The marker id
preserves the position (device→device_id, entity→lg_<eid>, virtual→v_<rand>).
- Clicking an icon: normal mode → the info card (name, model, state, link, description,
PDF manuals, an "Open in HA" button); edit mode → the edit dialog.
- The device dialog: name, binding (a picker of all HA+Z2M devices/groups+helpers with a text
filter, minus already-placed ones and duplicates by name|area, plus "Virtual device"), MDI icon
(ha-icon-picker), model, link, description, manual file attachment (PDF to the server),
room (for virtual ones), Remove/Cancel/Save.
- Toolbar: a "+" button (tooltip "Add device") — an empty dialog.
- Backend: MARKER_SCHEMA in the config; WS houseplan/file/set (files in /config/houseplan/files/,
≤25 MB, served from /houseplan_files/files/); the files static path registered.
- Virtual icons are now draggable and positioned like normal ones (layout by id).
## v1.6.1 — 2026-07-04
- Пикер привязки прячет дубли устройств по «имя|area» (Tuya/LocalTuya), как дедуп на плане.
- The binding picker hides device duplicates by "name|area" (Tuya/LocalTuya), same as the dedup on the plan.
## v1.5.1 — 2026-07-04 (управление пространствами)
- Иконка-карандаш справа от названия каждого пространства в верхнем тулбаре + кнопка «+»
(только при server config).
- Диалог пространства: поле «Название», превью текущей подложки + «Загрузить/Заменить…»
(SVG/PNG/JPG/WebP, base64 → houseplan/plan/set, пропорции определяются из файла),
кнопки Удалить / Отмена / Сохранить. Создание пустого пространства и удаление (со всеми
комнатами и разметкой, с подтверждением). Сохранение через config/set с ревизией.
## v1.5.1 — 2026-07-04 (space management)
- A pencil icon to the right of each space name in the top toolbar + a "+" button
(only with a server config).
- The space dialog: a "Name" field, a preview of the current background + "Upload/Replace…"
(SVG/PNG/JPG/WebP, base64 → houseplan/plan/set, the aspect ratio is detected from the file),
Delete / Cancel / Save buttons. Creating an empty space and deletion (together with all its
rooms and markup, with a confirmation). Saved via config/set with a revision.
## v1.5.0 — 2026-07-04 (группы света = сущности)
- Лампы заменены сущностями групп: HA light-groups (platform=group, area из реестра сущности)
и Z2M-группы (device model=Group) рендерятся иконкой mdi:lightbulb-group; клик → more-info
группы (управление всей группой и участниками штатно в HA).
- Одиночные лампы скрываются в комнатах, где есть группа света (settings.group_lights=false
возвращает поштучный режим).
- Выпилена старая визуальная группировка и кастомное меню участников (упрощение кода).
- Позиции старых групп (grp_<area>) перенесены на новые id (lg_<entity_id>) в раскладке дачи.
## v1.5.0 — 2026-07-04 (light groups = entities)
- Lamps replaced by group entities: HA light groups (platform=group, area from the entity registry)
and Z2M groups (device model=Group) are rendered with the mdi:lightbulb-group icon; a click → the group's
more-info (controlling the whole group and its members natively in HA).
- Individual lamps are hidden in rooms that have a light group (settings.group_lights=false
brings back the per-lamp mode).
- The old visual grouping and the custom member menu were removed (code simplification).
- The positions of the old groups (grp_<area>) were migrated to the new ids (lg_<entity_id>) in the dacha layout.
## v1.4.4 — 2026-07-04 (КРИТИЧЕСКИЙ фикс: гонка конфигураций)
- ИНЦИДЕНТ: config сохранялся целиком по принципу last-writer-wins — открытый клиент со
старой копией затирал чужие правки (потеряна первая итерация пользовательской разметки).
- Фикс: оптимистичная блокировка — конфиг несёт `rev`; `config/set` принимает `expected_rev`
и возвращает ошибку `conflict` при несовпадении; клиент перечитывает конфиг.
- Live-синхронизация: `config/set` рассылает событие `houseplan_config_updated`; все открытые
карточки подписаны и перечитывают конфиг автоматически (окна больше не расходятся).
- Позиции при drag сохраняются точечно через `layout/update` (dirty-set), а не полным
`layout/set`раскладка тоже больше не затирается между окнами.
- Бэкап конфига перед деплоем: .storage/houseplan.config.bak-*.
## v1.4.4 — 2026-07-04 (CRITICAL fix: configuration race)
- INCIDENT: the config was saved wholesale on a last-writer-wins basis — an open client with a
stale copy wiped other clients' edits (the first iteration of the user's markup was lost).
- Fix: optimistic locking — the config carries a `rev`; `config/set` accepts `expected_rev`
and returns a `conflict` error on mismatch; the client re-reads the config.
- Live synchronization: `config/set` broadcasts a `houseplan_config_updated` event; all open
cards are subscribed and re-read the config automatically (windows no longer diverge).
- Positions during drag are saved pointwise via `layout/update` (dirty set), not a full
`layout/set`the layout is also no longer wiped between windows.
- A config backup before deployment: .storage/houseplan.config.bak-*.
## v1.4.3 — 2026-07-04
- Иконки устройств привязываются к той же сетке, что и разметка (центр иконки = узел, снап
при перетаскивании и вводе X/Y). Существующие 57 позиций в БД дачи снапнуты к узлам.
- Device icons snap to the same grid as the markup (the icon center = a node, snapping
on drag and on X/Y input). The existing 57 positions in the dacha DB were snapped to the nodes.
## v1.4.2 — 2026-07-04 (сетка разметки)
- Шаг сетки уменьшен в 2 раза: GRID_N 60 → 120 (шаг 8.33 рендер-ед. ≈ 0.83% ширины плана).
- Точки сетки рендерятся ПОВЕРХ плана и комнат (grid-rect перенесён из под-плана в верхний
markup-слой); точки контрастнее (r=0.14g, обводка).
- Данные дачи: границы всех 13 комнат привязаны к узлам сетки (снап рёбер, сдвиги ≤ полшага;
правка только конфига инстанса через houseplan/config/set, код не менялся).
- Кэш карточки: URL модуля ?v= берётся из const.VERSION — при обновлении фронтенда бампать
const.py и рестартовать HA, иначе браузеры держат старый модуль в memory-cache.
## v1.4.2 — 2026-07-04 (markup grid)
- The grid step was halved: GRID_N 60 → 120 (step 8.33 render units ≈ 0.83% of the plan width).
- Grid points are rendered ON TOP of the plan and rooms (the grid-rect was moved from below the plan
into the top markup layer); the points are higher-contrast (r=0.14g, outline).
- Dacha data: the boundaries of all 13 rooms were snapped to grid nodes (edge snapping, shifts ≤ half a step;
only the instance config was edited via houseplan/config/set, the code was untouched).
- Card cache: the module URL ?v= is taken from const.VERSION — when updating the frontend, bump
const.py and restart HA, otherwise browsers keep the old module in memory cache.
## v1.4.1 — 2026-07-04 (UX разметки)
- Esc / Ctrl+Z при рисовании убирают последнюю точку (и её линию, если она была добавлена
этим шагом; переиспользованные чужие стены не трогаются).
- Панель редактирования (разметка и правка раскладки) перенесена наверх под шапку карточки
и закреплена вместе с ней (общий sticky-контейнер .hdr).
- Замыкание контура автоматически открывает модальный диалог «Новая комната»: отображаемое
имя, выпадающий список ТОЛЬКО свободных зон HA (не назначенных ни одной комнате конфига),
Сохранить/Отмена. Отмена (или Esc) снимает замыкающую точку — контур можно продолжить.
Выбор зоны при пустом имени подставляет название зоны.
## v1.4.1 — 2026-07-04 (markup UX)
- Esc / Ctrl+Z while drawing remove the last point (and its line, if it was added
by that step; reused walls belonging to others are untouched).
- The editing panel (markup and layout editing) was moved to the top under the card header
and pinned together with it (a shared sticky container .hdr).
- Closing the outline automatically opens the "New room" modal dialog: the display
name, a dropdown of ONLY the free HA areas (not assigned to any room of the config),
Save/Cancel. Cancel (or Esc) removes the closing point — the outline can be continued.
Choosing an area with an empty name fills in the area's name.
## v1.4.0 — 2026-07-04 (Фаза 2: редактор разметки комнат)
- Режим «Разметка» в карточке (кнопка mdi:vector-square-edit, только при server config):
сетка точек (60 узлов по ширине), линии парами кликов со снапом к узлам, полилиния-превью.
- Замкнутый контур (клик по первой точке) активирует «Сохранить»: выбор зоны HA из
выпадающего списка + название; комната сохраняется полигоном (poly, нормированные вершины).
- Инструменты: Добавить (рисование), Стереть линию (клик по линии), Удалить комнату
(клик внутри + confirm). Линии-«стены» хранятся в space.segments и переиспользуются
соседними комнатами (общие стены — кликами по существующим узлам).
- Рендер комнат: полигоны наравне с прямоугольниками (hover, клик в зону, LQI-тултип,
подпись в центроиде); в режиме разметки контуры и названия всех комнат видимы.
- Бэкенд: ROOM_SCHEMA принимает poly (≥3 вершин) или x/y/w/h; SPACE_SCHEMA — segments.
## v1.4.0 — 2026-07-04 (Phase 2: room markup editor)
- The "Markup" mode in the card (the mdi:vector-square-edit button, only with a server config):
a grid of points (60 nodes across the width), lines drawn by pairs of clicks with node snapping, a polyline preview.
- A closed outline (a click on the first point) activates "Save": choosing an HA area from a
dropdown + a name; the room is saved as a polygon (poly, normalized vertices).
- Tools: Add (drawing), Erase line (a click on a line), Delete room
(a click inside + confirm). The "wall" lines are stored in space.segments and reused by
neighboring rooms (shared walls — via clicks on existing nodes).
- Room rendering: polygons on par with rectangles (hover, click into the area, LQI tooltip,
the label at the centroid); in markup mode the outlines and names of all rooms are visible.
- Backend: ROOM_SCHEMA accepts poly (≥3 vertices) or x/y/w/h; SPACE_SCHEMA — segments.
## v1.3.0 — 2026-07-04 (Фаза 1: серверная конфигурация)
- Store `houseplan.config`: spaces (план, aspect, view_box, комнаты), device_overrides
## v1.3.0 — 2026-07-04 (Phase 1: server-side configuration)
- The `houseplan.config` Store: spaces (plan, aspect, view_box, rooms), device_overrides
(hidden/icon/name), virtual_devices, settings (exclude_integrations, group_lights).
- WS: `houseplan/config/get|set`, `houseplan/plan/set` (загрузка файла плана base64 →
`<config>/houseplan/plans/`, отдача через /houseplan_files/plans/).
- Карточка: резолв-модель — server config (координаты НОРМИРОВАННЫЕ 0..1, рендер 1000×1000/aspect)
или legacy-бандл (холст 1489×1053). Раскладка v2: {device_id: {s, x, y}} нормированная.
- Кнопка «На сервер» в режиме правки — миграция legacy-конфига в один клик (планы, комнаты,
view_box, раскладка). Выполнена на даче: .storage/houseplan.config + plans/f1.svg,f2.svg.
- Read-side поддержка оверрайдов устройств и виртуальных устройств (UI управления — фазы 34).
- WS: `houseplan/config/get|set`, `houseplan/plan/set` (plan file upload as base64 →
`<config>/houseplan/plans/`, served through /houseplan_files/plans/).
- The card: a resolve model — the server config (coordinates NORMALIZED 0..1, render 1000×1000/aspect)
or the legacy bundle (canvas 1489×1053). Layout v2: {device_id: {s, x, y}} normalized.
- The "To server" button in edit mode — a one-click migration of the legacy config (plans, rooms,
view_box, layout). Performed at the dacha: .storage/houseplan.config + plans/f1.svg,f2.svg.
- Read-side support for device overrides and virtual devices (the management UI — phases 34).
## v1.2.2 — 2026-07-04
- Тулбар карточки (вкладки этажей) закрепляется при скролле под шапкой HA
- The card toolbar (floor tabs) pins under the HA header while scrolling
(`position: sticky; top: var(--header-height)`; ha-card overflow: visible).
- Инцидент: промежуточная сборка на нестабильном mount дала битый бандл, роняющий рендер
дашбордов; правило «собирать только в /tmp + md5-контроль» закреплено в DEVELOPMENT.md.
- Incident: an intermediate build on the unstable mount produced a broken bundle that crashed the
dashboard rendering; the rule "build only in /tmp + md5 control" was locked into DEVELOPMENT.md.
## v1.2.1 — 2026-07-04
- Иконки не увеличиваются при наведении/перетаскивании (убран transform: scale).
- Icons no longer enlarge on hover/drag (transform: scale removed).
## v1.2.0 — 2026-07-03
- Границы комнат сняппены к стенам векторного плана.
- Zigbee LQI: значение под иконкой, среднее по комнате в тултипе, градиент красный→зелёный,
опция show_signal.
- `mdi:lock` для любых устройств с сущностью lock.* (TTLock).
- Выключатель прихожей: исправлена зона устройства в реестре HA (был в detskaia_elina).
- Room boundaries snapped to the walls of the vector plan.
- Zigbee LQI: the value under the icon, the room average in the tooltip, a red→green gradient,
the show_signal option.
- `mdi:lock` for any devices with a lock.* entity (TTLock).
- The hallway switch: the device's area in the HA registry was fixed (it was in detskaia_elina).
## v1.1.0 — 2026-07-03
- Векторные подложки (SVG РЕМПЛАННЕР), автосовмещение масштаба/сдвига растровой корреляцией.
- Размер иконок в % ширины плана (container queries, дефолт 2.5%).
- Vector backgrounds (SVG from REMPLANNER), automatic scale/offset alignment via raster correlation.
- Icon size as a % of the plan width (container queries, default 2.5%).
## v1.0.1 — 2026-07-03
- fix: вложенные SVG-фрагменты через lit svg`` (подложка/комнаты не рендерились).
- fix: версия из const вместо блокирующего чтения manifest.json в event loop.
- fix: nested SVG fragments via lit svg`` (the background/rooms were not rendered).
- fix: the version taken from const instead of a blocking manifest.json read in the event loop.
## v1.0.0 — 2026-07-03
- Первый релиз: Lovelace-карточка (TS+Lit, без токена, от hass) + интеграция houseplan
(WS-хранилище раскладки, раздача JS). Перенос модели прототипа: курирование, группы ламп,
iconFor, температура, more-info, навигация в зоны, drag-раскладка.
- First release: the Lovelace card (TS+Lit, no token, driven by hass) + the houseplan integration
(WS layout storage, JS serving). The prototype's model carried over: curation, lamp groups,
iconFor, temperature, more-info, area navigation, drag layout.
+53 -53
View File
@@ -1,77 +1,77 @@
# Разработка и деплой
# Development and deployment
## Окружение (cowork-сессии)
## Environment (cowork sessions)
- Эталон кода — **git-репозиторий** (в сессии живёт в `/tmp/hpc`, восстанавливается из
`houseplan-card.git.bundle`: `git clone houseplan-card.git.bundle hpc`).
- Папка пользователя `houseplan/houseplan-card/` — зеркало репо (rsync после каждого коммита)
+ актуальный `houseplan-card.git.bundle`.
- The source of truth for the code is the **git repository** (lives in `/tmp/hpc` during a session,
restored from `houseplan-card.git.bundle`: `git clone houseplan-card.git.bundle hpc`).
- The user's folder `houseplan/houseplan-card/` is a mirror of the repo (rsync after every commit)
+ an up-to-date `houseplan-card.git.bundle`.
### ⚠️ Грабли файловой синхронизации (критично)
1. Сетевой mount иногда отдаёт файлы **обрезанными/перемешанными** — правки через Edit-tool
с Windows-стороны ненадёжны. Правило: **править python-патчами по чистой копии в /tmp,
писать через bash**, assert на count(old)==1.
2. **Сборку rollup вести ТОЛЬКО в /tmp/hpc** (`npm ci` уже сделан). Сборка на mount однажды
дала синтаксически валидный, но битый бандл («wi is not defined»), который валил рендер
ВСЕХ дашбордов HA (карточка грузится как extra_module на каждой странице!).
3. `.git` на mount не создаётся («Operation not permitted» на dot-каталогах) — поэтому bundle.
### ⚠️ File-sync pitfalls (critical)
1. The network mount sometimes serves files **truncated/scrambled** — edits via the Edit tool
from the Windows side are unreliable. Rule: **apply python patches against a clean copy in /tmp,
write via bash**, with an assert that count(old)==1.
2. **Run the rollup build ONLY in /tmp/hpc** (`npm ci` is already done). A build on the mount once
produced a syntactically valid but broken bundle ("wi is not defined") that crashed the rendering
of ALL HA dashboards (the card is loaded as an extra_module on every page!).
3. `.git` cannot be created on the mount ("Operation not permitted" on dot-directories) — hence the bundle.
## Тесты
## Tests
- Фронт: `npm test`компилит src/logic.ts+rules.ts (tsconfig.test.json) и гоняет node:test
(test/*.test.mjs). Строгая типизация: `npm run typecheck` (tsc --noEmit, входит в `npm run build`).
- Бэк: `python -m pytest tests_backend/`чистая валидация custom_components/houseplan/validation.py
(грузится по пути, без импорта HA-пакета).
- ВАЖНО (аудит-урок): rollup-плагин typescript выдаёт синтаксическую ошибку как WARNING и всё равно
собирает бандл — обрезанный файл может «пройти». Поэтому в сборке первым идёт `tsc --noEmit`,
который падает на таких ошибках. Всегда собирать `npm run build`, не голый `rollup -c`.
- Frontend: `npm test`compiles src/logic.ts+rules.ts (tsconfig.test.json) and runs node:test
(test/*.test.mjs). Strict typing: `npm run typecheck` (tsc --noEmit, part of `npm run build`).
- Backend: `python -m pytest tests_backend/`pure validation of custom_components/houseplan/validation.py
(loaded by path, without importing the HA package).
- IMPORTANT (audit lesson): the rollup typescript plugin reports a syntax error as a WARNING and still
builds the bundle — a truncated file can "pass". That is why the build starts with `tsc --noEmit`,
which fails on such errors. Always build with `npm run build`, never bare `rollup -c`.
## Сборка
## Build
```bash
cd /tmp/hpc && npm ci # один раз
cd /tmp/hpc && npm ci # once
npx rollup -c # → dist/houseplan-card.js
node --check dist/houseplan-card.js
cp dist/houseplan-card.js custom_components/houseplan/frontend/
```
## Деплой на дачу (ha.jbstudio.pro)
## Deployment to the dacha (ha.jbstudio.pro)
- SSH: порт **323**, root, ключ `ha_jb` (пользователь загружает в чат; в песочнице /tmp/ha_jb, chmod 600).
- SSH: port **323**, root, key `ha_jb` (the user uploads it to the chat; in the sandbox /tmp/ha_jb, chmod 600).
- JS: `scp -P 323 -i /tmp/ha_jb dist/houseplan-card.js root@ha.jbstudio.pro:/config/custom_components/houseplan/frontend/`
- Интеграция целиком: tar c custom_components/houseplan (--exclude __pycache__) → tar x на сервере.
- **Проверка обязательна**: `md5sum` локально == на сервере == `curl http://homeassistant:8123/houseplan_files/houseplan-card.js | md5sum`
(внутри SSH-аддона `localhost` — НЕ HA, использовать хост `homeassistant`).
- Изменения Python требуют рестарта HA (`ha core restart`, держит соединение до конца, HTTP
поднимается через 1–3 мин). Изменения JS — только обновление страницы (static path отдаётся
с no-cache).
- После деплоя JS — проверить в браузере (Ctrl+F5) и console (не должно быть ошибок из
houseplan-card.js; битый бандл роняет все дашборды).
- The whole integration: tar c custom_components/houseplan (--exclude __pycache__) → tar x on the server.
- **Verification is mandatory**: `md5sum` locally == on the server == `curl http://homeassistant:8123/houseplan_files/houseplan-card.js | md5sum`
(inside the SSH add-on `localhost` is NOT HA, use the host `homeassistant`).
- Python changes require an HA restart (`ha core restart`, holds the connection until it finishes, HTTP
comes back up in 13 min). JS changes — just a page refresh (the static path is served
with no-cache).
- After deploying JS — check in the browser (Ctrl+F5) and the console (there must be no errors from
houseplan-card.js; a broken bundle takes down all dashboards).
## Кэш фронтенда и «пустой вид»
## Frontend cache and the "empty view"
- URL модуля карточки содержит `?v=<VERSION из const.py>`. Браузеры держат ES-модуль в
memory-cache: после деплоя нового JS **бампните VERSION в const.py и рестартуйте HA**,
иначе обычный F5 оставит старую версию.
- После reload страницы HA-фронтенд (с kiosk-mode) иногда оставляет вид пустым
(«InvalidStateError: Transition was aborted», hui-view не создаётся 12 мин).
Лечится повторной SPA-навигацией: pushState + событие location-changed, или подождать.
- The card module URL contains `?v=<VERSION from const.py>`. Browsers keep the ES module in
memory cache: after deploying new JS **bump VERSION in const.py and restart HA**,
otherwise a plain F5 will keep the old version.
- After a page reload the HA frontend (with kiosk-mode) sometimes leaves the view empty
("InvalidStateError: Transition was aborted", hui-view is not created for 12 min).
Cured by repeating the SPA navigation: pushState + a location-changed event, or just waiting.
## Релиз
## Release
Тег `vX.Y.Z` + GitHub Release → workflow `.github/workflows/release.yml` собирает и прикладывает
`houseplan-card.js`. Версию бампать синхронно: `src/houseplan-card.ts` (CARD_VERSION),
Tag `vX.Y.Z` + GitHub Release → the workflow `.github/workflows/release.yml` builds and attaches
`houseplan-card.js`. Bump the version everywhere in sync: `src/houseplan-card.ts` (CARD_VERSION),
`package.json`, `custom_components/houseplan/manifest.json`, `custom_components/houseplan/const.py`.
## Воспроизводимые скрипты (данные)
## Reproducible scripts (data)
- Извлечение геометрии/подложек из прототипа и генерация `src/data/*` — см. историю коммитов
и docs/ARCHITECTURE.md (трансформации SVG→базовое пространство: f1 0.647/(490,27), f2 0.896/(351,21)).
- Подгонка комнат: рендер плана с прямоугольниками поверх (cv2) → снап к стенам → ручная доводка.
- Extracting the geometry/backgrounds from the prototype and generating `src/data/*` — see the commit
history and docs/ARCHITECTURE.md (SVG→base-space transforms: f1 0.647/(490,27), f2 0.896/(351,21)).
- Room fitting: render the plan with rectangles overlaid (cv2) → snap to walls → manual fine-tuning.
## Прод-объекты в HA (дача)
## Production objects in HA (the dacha)
- Дашборд `plan-doma`, панельный вид, карточка `custom:houseplan-card` (icon_size 2.5).
- Интеграция houseplan: entry загружен, `.storage/houseplan.layout`раскладка (сервер).
- Старый прототип `/config/www/houseplan/` (iframe) сохранён как запасной, не трогать.
- Бэкапы configuration.yaml: `.bak-avgtemp` (до правки среднего датчика).
- Dashboard `plan-doma`, panel view, card `custom:houseplan-card` (icon_size 2.5).
- The houseplan integration: entry loaded, `.storage/houseplan.layout`the layout (server-side).
- The old prototype `/config/www/houseplan/` (iframe) is kept as a fallback, do not touch.
- configuration.yaml backups: `.bak-avgtemp` (before the average-temperature sensor edit).
+56 -56
View File
@@ -1,73 +1,73 @@
# Роадмап: от «карты дачи» к публикуемой универсальной интеграции
# Roadmap: from a "dacha map" to a publishable universal integration
Цель: опубликовать в HACS (сначала custom repository, затем PR в default) универсальную
интеграцию «интерактивный план дома»: свои планы на пространство, ручная разметка комнат,
полуавтоматическое размещение устройств, скрытие/переименование/смена иконок, виртуальные
устройства. Никакого хардкода конкретного дома в коде.
Goal: publish to HACS (first as a custom repository, then a PR into default) a universal
"interactive house plan" integration: custom plans per space, manual room markup,
semi-automatic device placement, hiding/renaming/changing icons, virtual
devices. No hardcoding of a specific house in the code.
## Принципы (зафиксировано)
- Пишем полноценный компонент по паттернам HA dev docs, не захардкоженную фичу.
- Все данные конкретного дома — это **конфигурация инстанса** (server-side Store),
а не код/бандл. Текущие данные дачи станут первым мигрированным инстансом.
- Документировать всё сразу в docs/ (контекст сессий теряется).
- Версионирование хранилищ (Store minor_version + async_migrate) с первого дня.
## Principles (locked in)
- We write a proper full component following HA dev docs patterns, not a hardcoded feature.
- All data of a specific house is **instance configuration** (server-side Store),
not code/bundle. The current dacha data will become the first migrated instance.
- Document everything immediately in docs/ (session context gets lost).
- Storage versioning (Store minor_version + async_migrate) from day one.
## Фаза 0 — Гигиена публикации (быстро, без новых фич)
- [x] hacs.json, manifest с обязательными ключами, структура custom_components/*
- [x] CI: hacs/action + hassfest (workflow validate.yml) — добавлено, проверить на GitHub
## Phase 0 — Publication hygiene (quick, no new features)
- [x] hacs.json, manifest with the required keys, custom_components/* structure
- [x] CI: hacs/action + hassfest (workflow validate.yml) — added, verify on GitHub
- [x] brand/icon.png
- [ ] Публичный GitHub-репозиторий: description, topics, issues on; первый Release v1.2.x
- [ ] README EN (основной) + README.ru.md; скриншоты/GIF (обязательны для витрины HACS)
- [ ] Заменить codeowners/documentation/issue_tracker на реальные URL после создания репо
- [ ] Public GitHub repository: description, topics, issues on; first Release v1.2.x
- [ ] README EN (primary) + README.ru.md; screenshots/GIF (mandatory for the HACS storefront)
- [ ] Replace codeowners/documentation/issue_tracker with real URLs after the repo is created
## Фаза 1 — Конфиг на сервере (декаплинг от дачи) ← ✅ СДЕЛАНО в v1.3.0 (кроме выпиливания бандл-данных — оставлены как fallback до фазы 2)
Новое хранилище `houseplan.config` (Store v1):
## Phase 1 — Server-side config (decoupling from the dacha) ← ✅ DONE in v1.3.0 (except removing the bundle data — kept as a fallback until phase 2)
New store `houseplan.config` (Store v1):
```json
{ "spaces": [ { "id": "f1", "title": "1 этаж", "plan": {"media_id": "...", "type": "svg"},
{ "spaces": [ { "id": "f1", "title": "1st floor", "plan": {"media_id": "...", "type": "svg"},
"view_box": [x,y,w,h], "rooms": [{"id","name","area_id","x","y","w","h"}] } ],
"device_overrides": { "<device_id>": {"hidden":bool,"icon":str,"name":str} },
"virtual_devices": [ {"id","space","name","icon","x","y","note"?, "entity_id"?} ],
"settings": {"exclude_integrations": [...], "group_lights": bool, ...} }
```
- WS API v2: `houseplan/config/get|set`, `houseplan/plan/upload` (файл плана
`<config>/houseplan/` через process-executor, отдача через static path), layout как сейчас.
- Карточка: при наличии server-config использует его; бандл-данные дачи становятся
**fallback-примером** и затем выпиливаются (миграционный скрипт зальёт их в Store).
- Единицы координат: нормированные (0..1 от плана) для новых конфигов — независимость от
разрешения исходника; миграция дачи пересчитает 1489×1053 → нормированные.
- WS API v2: `houseplan/config/get|set`, `houseplan/plan/upload` (plan file
`<config>/houseplan/` via the process executor, served through a static path), layout as today.
- The card: when a server config exists it uses it; the dacha bundle data becomes a
**fallback example** and is then removed (a migration script will push it into the Store).
- Coordinate units: normalized (0..1 of the plan) for new configs — independence from the
source resolution; the dacha migration will convert 1489×1053 → normalized.
## Фаза 2 — Редактор разметки в карточке ← ✅ ЯДРО СДЕЛАНО в v1.4.0 (осталось: загрузка плана из UI, правка view_box, редактирование существующих комнат)
- Режим «Настройка» (отдельно от drag-раскладки): рисование/ресайз прямоугольников комнат
поверх плана, привязка к area (селектор ha-area-picker), редактирование viewBox (кадр).
- Позже: полигональные комнаты (SVG path), повороты планов.
- Загрузка плана из UI (file upload → WS) + выбор существующего media.
## Phase 2 — Markup editor in the card ← ✅ CORE DONE in v1.4.0 (remaining: plan upload from the UI, view_box editing, editing existing rooms)
- A "Setup" mode (separate from drag layout): drawing/resizing room rectangles
on top of the plan, binding to an area (ha-area-picker selector), viewBox (frame) editing.
- Later: polygonal rooms (SVG path), plan rotations.
- Plan upload from the UI (file upload → WS) + picking existing media.
## Фаза 3 — Управление устройствами ← ✅ СДЕЛАНО в v1.6.x (скрытие, иконка, имя, модель, ссылка, описание, PDF, перепривязка, «+», виртуальные)
- Панель устройств в режиме настройки: список неразмещённых (с фильтрами), drag из панели
на план; авто-раскладка «сеткой по комнате» кнопкой.
- Оверрайды per-device: скрыть, своя иконка (ha-icon-picker), своё имя. Хранение в config.
- Настраиваемое курирование: исключения интеграций/доменов в options flow вместо хардкода.
## Phase 3 — Device management ← ✅ DONE in v1.6.x (hiding, icon, name, model, link, description, PDF, rebinding, "+", virtual)
- A device panel in setup mode: a list of unplaced devices (with filters), drag from the panel
onto the plan; auto-layout "grid over the room" by button.
- Per-device overrides: hide, custom icon (ha-icon-picker), custom name. Stored in config.
- Configurable curation: integration/domain exclusions in the options flow instead of hardcode.
## Фаза 4 — Виртуальные устройства ← ✅ базово в v1.6.x (CRUD виртуальных маркеров в общем диалоге; отдельная доработка заметок/иконок по мере надобности)
- CRUD виртуальных маркеров (имя, иконка, координаты, заметка; опционально ссылка на
entity/URL): септик, кран, счётчик без датчика и т.п. Рендер как обычные иконки,
клик → карточка с заметкой или more-info привязанной сущности.
## Phase 4 — Virtual devices ← ✅ basics in v1.6.x (CRUD of virtual markers in the shared dialog; further note/icon polish as needed)
- CRUD of virtual markers (name, icon, coordinates, note; optionally a link to an
entity/URL): septic tank, valve, a meter without a sensor, etc. Rendered like normal icons,
click → a card with the note or more-info of the bound entity.
## Фаза 5 — Полировка UX/фич
- Клик-действия по настройке: toggle для света/розеток, long-press → more-info.
- Тюнинг live-индикации (цвета по теме, badge), light-тема.
- Тултипы на тач-устройствах (long-press), доступность (клавиатура, aria).
- Опция LQI: порог «плохого» сигнала, скрытие меток на не-zigbee инстансах.
## Phase 5 — UX/feature polish
- Configurable click actions: toggle for lights/outlets, long-press → more-info.
- Live-indication tuning (theme-aware colors, badges), light theme.
- Tooltips on touch devices (long-press), accessibility (keyboard, aria).
- LQI option: "bad signal" threshold, hiding the labels on non-zigbee instances.
## Фаза 6 — Качество и публикация
- Тесты: pytest (config_flow, websocket_api, миграции Store) + hassfest/hacs action в CI;
фронт: vitest на rules/geometry-утилиты.
- Типизация: strict TS-интерфейсы hass (custom-card-helpers или свои), mypy для python.
- Переводы integration+card: en + ru (translations/, локализация строк карточки).
- Quality scale bronze → silver чек-лист; PR в hacs/default; опционально PR в
home-assistant/brands (пока хватает brand/ в репо).
## Phase 6 — Quality and publication
- Tests: pytest (config_flow, websocket_api, Store migrations) + hassfest/hacs action in CI;
frontend: vitest for rules/geometry utilities.
- Typing: strict TS interfaces for hass (custom-card-helpers or our own), mypy for python.
- Translations for integration+card: en + ru (translations/, card string localization).
- Quality scale bronze → silver checklist; PR into hacs/default; optionally a PR into
home-assistant/brands (the brand/ in the repo suffices for now).
## Открытые вопросы
- Имя для публикации: «House Plan Card»? домен `houseplan` не занят в HACS default — проверить.
- Лицензия MIT (в package.json уже MIT) — добавить LICENSE файл.
- Формат планов: SVG предпочтителен (вектор, вес), PNG поддержать обязательно.
## Open questions
- Publication name: "House Plan Card"? the `houseplan` domain is not taken in HACS default — verify.
- MIT license (package.json is already MIT) — add a LICENSE file.
- Plan formats: SVG preferred (vector, size), PNG support is mandatory.