docs: specify contextual zigbee links

Issue: #54
User-Visible: no
This commit is contained in:
Sergey Matyunin
2026-09-04 13:53:07 +03:00
parent ec9824f227
commit a6a3df9310
+510 -51
View File
@@ -1,74 +1,533 @@
# ТЗ #54 — Диагностический Zigbee topology overlay
# ТЗ #54 — контекстные связи Zigbee на плане
- Issue: https://github.com/Matysh/houseplan-card/issues/54
- Приоритет: P2
- Статус ТЗ: research + adapter contract; provider APIs проверяются до кода
- Приоритет: P3
- Тип: feature
- Трек: полный — новый UX-контракт, провайдеры, настройки, privacy и performance
- Stage 0: capability research завершён, решение **GO с условиями**
- Контракты проверены: 2026-08-31
- UX скорректирован решением владельца: 2026-09-04
## Цель
## 1. Сценарий
По явному запросу показать связи ZHA/Zigbee2MQTT между уже размещёнными
устройствами поверх реальной геометрии, не превращая House Plan в редактор сети.
Персона — Enthusiast/Power User из `docs/SCOPE.md`. Поверхность — полная
карточка House Plan в режиме View на устройстве с мышью. Момент — пользователь
ищет пространственную причину нестабильной работы конкретного Zigbee-устройства
и хочет увидеть его наблюдаемых соседей относительно комнат и стен, не переходя
в отдельный сетевой граф.
## Stage 0 — capability research
## 2. Что человек увидит до и после
На поддерживаемых версиях HA зафиксировать официальные/фактические API:
До: House Plan показывает LQI устройства и комнаты, но не показывает, с какими
Zigbee-узлами связано выбранное устройство и где они находятся на плане.
- ZHA topology/neighbour source, permissions, direction и LQI semantics;
- Zigbee2MQTT networkmap request/response через HA MQTT integration либо
документированную exposed entity;
- соответствие IEEE ↔ HA device registry `connections`;
- timeout/rate limits и поведение sleeping end devices.
После: если пользователь заранее включил диагностику в общих настройках и навёл
мышь на размещённое Zigbee-устройство, House Plan временно показывает только его
непосредственные связи; без наведения план выглядит как раньше.
Неподтверждённые command/topic не hardcode-ятся. Каждый provider имеет fixture
с обезличенными ids и дату/версию проверенного контракта.
## 3. Проблема
## Normalized model
Полный mesh-граф поверх жилого плана быстро превращается в нечитаемую паутину:
число линий растёт вместе с сетью, связи закрывают геометрию и устройства, а
пользователь всё равно диагностирует один проблемный endpoint за раз. При этом
ZHA и Zigbee2MQTT дают разные по свежести и стоимости snapshots, а их neighbor
records нельзя честно называть фактическим маршрутом каждого пакета.
Нужен opt-in диагностический слой с progressive disclosure: сеть не видна в
обычном состоянии, а непосредственное окружение одного устройства появляется
только на время hover.
## 4. Scope и non-scope
### 4.1 Входит в #54
- одна выключенная по умолчанию опция в «Общих настройках»;
- provider-neutral topology model и точное сопоставление узлов с размещёнными
маркерами;
- ZHA cached snapshot через штатный admin-only WebSocket API;
- Zigbee2MQTT raw network map через штатные HA MQTT surfaces и явно заданные
base topics;
- ручное получение/обновление provider data из «Общих настроек»;
- только непосредственные связи наведённого устройства;
- локальные линии внутри текущего пространства и временный счётчик связей в
другие пространства;
- состояния загрузки, устаревшего snapshot, partial data, unsupported и error;
- lazy loading, общий memory cache, privacy guards, i18n, unit/backend/browser,
golden и performance coverage.
### 4.2 Не входит в #54
- постоянный слой или кнопка «показать весь граф»;
- вычисление либо изображение «маршрута до координатора»;
- tap, long press или другой topology-жест на touch/pen;
- интерактивная панель cross-space links и переход к удалённому marker;
- линии между независимыми пространствами, этажами или экземплярами карточки;
- принудительный ZHA radio scan без completion-aware upstream API;
- background/periodic scan, scan по hover, HA state tick, смене пространства или
созданию второй карточки;
- pairing, reconfigure, bind/unbind, удаление устройств и изменение сети;
- автоматическое создание маркеров, matching по имени/model/friendly name;
- RF-прогноз по стенам, причинные утверждения, history и графики;
- topology в kiosk, редакторах и `houseplan-space-card`;
- доступ non-admin в обход штатных прав Home Assistant.
## 5. Контракт поведения
### 5.1 Общая настройка
В «Общих настройках» появляется переключатель
«Показывать связи Zigbee при наведении на устройство».
- missing/invalid/`false` означает выключено;
- значение сохраняется вместе с общими настройками и действует для всех
пространств полной карточки;
- выключенное состояние не загружает topology runtime, не вызывает provider API
и не строит topology/mapping;
- включение разрешает отображение, но само по себе не запускает radio scan;
- выключение немедленно убирает hover-слой. Уже начатый явный Z2M scan не
прерывается небезопасным unsubscribe: ответ может завершить shared cache, но
UI остаётся скрыт;
- non-admin не видит активный control и не инициирует provider requests, даже
если настройку ранее сохранил admin.
### 5.2 Получение данных
Под переключателем, только в раскрытом включённом состоянии, показываются
provider controls и статус последнего snapshot.
- ZHA: действие «Прочитать данные ZHA» читает cached `zha/devices`. Оно не
обещает свежий radio scan и не показывает фиктивный момент сканирования;
- Zigbee2MQTT: для каждой настроенной instance задаётся base topic и доступно
действие «Обновить карту Zigbee2MQTT»;
- перед Z2M scan показывается предупреждение: операция занимает от 10 секунд до
2 минут и временно может снизить отзывчивость сети;
- provider actions явные и независимые. Ошибка одного provider не скрывает
пригодный snapshot другого;
- статус сообщает provider, время получения House Plan, stale/partial и
человекопонятную ошибку. IEEE, raw payload и внутренние identifiers не
показываются;
- snapshot старше пяти минут помечается устаревшим, но не обновляется сам.
### 5.3 Hover одного устройства
Связи разрешены только если одновременно выполняются условия:
1. настройка включена;
2. текущая поверхность — View полной карточки, не kiosk;
3. последняя pointer modality — реальная мышь с fine/hover capability;
4. существует пригодный snapshot;
5. курсор находится над drawable live marker, точно сопоставленным Zigbee node.
Тогда House Plan берёт только incident links выбранного node — пары, в которых
этот node является одним из концов. Весь остальной граф не строится и не
рисуется. Это наблюдаемые непосредственные соседи, а не вычисленный путь до
координатора.
Если второй endpoint точно сопоставлен видимому marker текущего пространства,
рисуется одна связь между маркерами, а marker-сосед получает лёгкое временное
выделение. Если второй endpoint находится в другом пространстве, линия не
рисуется; рядом с исходным marker появляется компактный неинтерактивный счётчик
«ещё N в других пространствах». Unmatched, ambiguous, hidden, removed и
HA-disabled endpoints не рисуются и в cross-space count не входят.
Hover-слой очищается при pointerleave marker, переходе на touch/pen, смене
пространства или режима, remount/disconnect и выключении настройки. Он не
остаётся закреплённым после клика. Existing click, long-press, hover tooltip,
device action и dispatched-action animation не меняются.
### 5.4 Touch, keyboard и другие режимы
- Touch/pen topology не показывают и не получают нового жеста. Tap и long press
продолжают действовать по существующему контракту устройства.
- Keyboard focus не имитирует mouse hover и не вводит новую навигацию по плану;
это соответствует текущей accessibility-границе `docs/SCOPE.md`.
- Plan, Devices и Decor не показывают линии, соседнее выделение или cross-space
count независимо от сохранённой настройки.
- Kiosk и `houseplan-space-card` не загружают runtime и не делают запросы.
- Несколько полных карточек могут переиспользовать один snapshot, но каждая
самостоятельно владеет своим mouse-hover состоянием.
## 6. UX и визуальная семантика
- В обычном View при включённой функции нет постоянной легенды, badges или
линий. Визуальный шум равен выключенному состоянию.
- Связи находятся в отдельном pointer-transparent layer: над архитектурой и
decor, но под device markers и их tooltips.
- Линия соединяет фактические центры двух уже спроецированных маркеров, не
участвует в fit/bounds и не меняет геометрию плана.
- Цвет использует каноническую непрерывную шкалу House Plan LQI. Unknown LQI —
нейтральный пунктир. Provider error никогда не изображается как «плохая»
красная связь.
- Для двух directional observations визуал линии использует наблюдение от
наведённого node к соседу; обратное значение не усредняется и остаётся в
нормализованной модели для диагностики/тестов.
- Толщина/opacity допускают ограниченное усиление по LQI, но сохраняются в
читаемом screen-space диапазоне при любом zoom.
- Coordinator/router/end не получают постоянных новых обозначений. Во время
hover сосед может получить один нейтральный halo, не похожий на alert,
selection или dispatched-action animation.
- Cross-space count живёт только пока активен тот же hover, имеет
`pointer-events:none`, не становится частью hit area и не предлагает tap.
- При отсутствии incident links или drawable соседей план не показывает
ложную линию или toast. Причина и summary доступны в provider status общих
настроек.
## 7. Подтверждённые provider-контракты Stage 0
### 7.1 ZHA
Проверены Home Assistant Core `2026.8.3` и
`dev@6ad726ba56d517e56536cdd6fa2ba9f358bbc0ef`, Home Assistant Frontend
`dev@6b7d3871754dada4d599e4fecc0d5ca0442cf22b`, а также zigpy topology.
1. Admin-only `zha/devices` возвращает `device_reg_id`, IEEE, роль,
`neighbors`, `routes` и directional LQI.
2. `device_reg_id` даёт exact HA Device Registry mapping.
3. Штатный HA frontend строит граф из `neighbors`/`routes`, сохраняет два
directional LQI пары и использует route только вместе с реальным neighbor
next hop.
4. zigpy обычно сканирует topology раз в четыре часа. `zha/devices` читает
кешированный snapshot, который может быть старым или неполным.
5. `zha/topology/update` в проверенном контракте запускает background scan, но
не возвращает completion/result. Поэтому #54 его не вызывает.
### 7.2 Zigbee2MQTT
Проверены Zigbee2MQTT `2.13.0` и
`master@fcbb7ff44bdc05a16e95d3472a81d110646cc17b`.
1. Документированный request
`BASE/bridge/request/networkmap` с
`{"type":"raw","routes":false,"transaction":"…"}` отвечает на
`BASE/bridge/response/networkmap` и возвращает nodes/links.
2. Node содержит IEEE, friendly name, роль и scan failures. Link содержит
source, target, directional LQI и relationship.
3. Scan занимает 10 секунд — 2 минуты и может временно снизить отзывчивость
Zigbee-сети; только явное действие пользователя вправе его запускать.
4. `routes:false` достаточно для непосредственных neighbor edges и уменьшает
нагрузку.
5. HA MQTT предоставляет admin-only WebSocket subscription, а `mqtt.publish` —
штатное HA action. Браузер не подключается к broker напрямую.
6. Base topic конфигурируем и не выводится надёжно из HA registry. Для каждой
instance пользователь задаёт его явно; default `zigbee2mqtt` проверяется по
retained `bridge/info` до scan.
## 8. Модель данных и миграция
### 8.1 Сохраняемая настройка
Концептуальная форма общих настроек:
```ts
type ZigbeeTopology = {
provider: 'zha' | 'z2m'; capturedAt: number;
nodes: Array<{ key: string; deviceId?: string; role: 'coordinator'|'router'|'end' }>;
links: Array<{ from: string; to: string; lqi?: number; direction: 'one'|'both'|'unknown' }>;
warnings: string[];
type ZigbeeTopologySettings = {
enabled?: boolean; // missing/invalid => false
z2mBaseTopics?: string[]; // normalized exact base topics
};
```
Provider adapters живут backend-side либо в изолированном HA adapter; render
не знает API. Browser не подключается к MQTT напрямую и не хранит topology в
House Plan config.
Точное внутреннее имя поля вправе изменить реализация без изменения UX.
Настройка принадлежит плану целиком, а не пространству. Повторное сохранение
несвязанных общих настроек не удаляет её. Дубликаты/пустые/невалидные base topic
нормализуются fail-closed.
## Fetch/cache/security
Миграции существующих планов нет: отсутствие объекта означает выключенную
функцию. Downgrade игнорирует неизвестное поле. Выключение функции не удаляет
base topics, чтобы повторное включение не требовало настройки заново.
- Overlay off по умолчанию; первый toggle запускает fetch с progress/cancel.
- Shared per-provider cache TTL 60 s (уточняется Stage 0), in-flight dedupe и
hard timeout. Toggle off отменяет UI ожидание, но безопасный backend request
может завершить cache.
- Refresh только явный или после TTL; HA state ticks не сканируют topology.
- Read permission проверяется backend. Ошибка/unsupported не ломает plan и
показывает localized status.
### 8.2 Runtime model
## Mapping и визуал
Provider payload не проходит в render:
Node сопоставляется marker только через device registry/explicit entity owner.
На plan рисуются links, у которых оба конца имеют видимые live markers текущего
space. Остальные nodes доступны в summary «Не размещено N», но не рисуются.
```ts
type ZigbeeProvider = 'zha' | 'z2m';
Edges — pointer-transparent отдельный layer. Цвет/opacity/thickness отражают
нормализованный LQI только при сопоставимой шкале provider; unknown — нейтральный
пунктир. Direction optional arrow только при достоверных данных. Coordinator
и router имеют legend. Geometry стен не интерпретируется как причина сигнала.
type ZigbeeTopology = {
provider: ZigbeeProvider;
instanceId: string;
obtainedAt: number;
freshness: 'provider-cache' | 'fresh-scan';
nodes: ZigbeeTopologyNode[];
links: ZigbeeTopologyLink[];
warnings: ZigbeeTopologyWarning[];
};
## Multiple spaces и lifecycle
type ZigbeeTopologyNode = {
key: string; // provider instance + normalized IEEE
ieee: string; // private runtime value
deviceId?: string;
role: 'coordinator' | 'router' | 'end' | 'unknown';
available?: boolean;
};
Cross-space link не рисуется как линия через разные планы; endpoints получают
badge/count и список другого пространства. Hidden/removed/disabled marker не
рисуется. Rebind refreshes mapping без нового network scan.
type ZigbeeDirectionalObservation = {
lqi?: number; // provider-native 0..255
relationship?: string;
activeRoute?: boolean;
};
## Performance и проверки
type ZigbeeTopologyLink = {
a: string;
b: string;
aToB?: ZigbeeDirectionalObservation;
bToA?: ZigbeeDirectionalObservation;
};
- 20/100/500 nodes, dense mesh; edge cap/viewport culling после Stage 0;
- provider fixtures, timeout, malformed/cyclic/duplicate links;
- mapping IEEE/device/entity, cross-space, hidden lifecycle;
- lazy request/in-flight cache and no fetch on ordinary HA ticks;
- golden LQI/unknown legend и performance budget;
- network layer полностью исчезает при toggle off и не меняет config.
type ZigbeeTopologyWarning = {
code:
| 'invalid_payload'
| 'duplicate_link'
| 'self_link'
| 'unmatched_device'
| 'ambiguous_placement'
| 'provider_scan_failure';
nodeKey?: string;
};
```
Snapshot остаётся memory-only и не входит в экспорт, backup, diagnostics,
support report, localStorage или console. Shared cache key включает HA connection,
provider и instance; concurrent requests дедуплицируются.
## 9. Нормализация и mapping
- IEEE нормализуется до одного canonical representation; raw/friendly name не
используется как identity.
- Одинаковая unordered pair хранится один раз; directional observations не
усредняются и не выдумываются из отсутствующей стороны.
- Self links и exact duplicates отбрасываются с warning.
- Route destination не создаёт прямую линию: route может только пометить
существующий neighbor next hop.
- Некорректный/неизвестный LQI становится `undefined`, а не `0`.
- Payload ограничивается по bytes, nodes и links до дорогостоящей обработки.
Для каждого node сначала определяется exact HA `deviceId`, затем marker:
1. единственный видимый live marker с binding `device:<deviceId>`;
2. если его нет — единственный видимый entity marker, чья entity принадлежит
тому же HA device;
3. несколько равноправных markers дают `ambiguous_placement` и не рисуются;
4. hidden, removed и HA-disabled markers не являются drawable endpoints;
5. rebind, перемещение и смена пространства пересчитывают mapping по тому же
snapshot без provider request.
Для Z2M обычный identifier `zigbee2mqtt_<ieee>`, а bridge/coordinator —
`zigbee2mqtt_bridge_<ieee>`. Exact Entity Registry owner допускается как
fallback. User-overridden identifiers могут оставить node unmatched; matching
по friendly name, entity name или model запрещён.
## 10. Fetch, cache, lifecycle и permissions
- Runtime загружается динамически только после сохранённого `enabled:true` на
доступной admin View/full-card поверхности.
- Provider data загружается только явными controls в общих настройках; hover
никогда не вызывает fetch или scan.
- Shared cache — per HA connection + provider + instance, memory-only, с
in-flight dedupe. Snapshot старше пяти минут stale, но пригоден до явного
обновления или конца connection lifecycle.
- Z2M transaction обязателен; timeout — 150 секунд. Retained ответ до request,
ответ с чужой transaction и late response игнорируются; subscription всегда
очищается после success/error/timeout.
- Hide/disable, изменение binding, удаление marker, смена режима/пространства и
disconnect синхронно инвалидируют draw mapping/hover, но не запускают scan.
- Provider/API failure не ломает план, device actions, LQI badges/fill и
остальные режимы.
- Если HA отказывает в admin API, adapter fail-closed и не пытается обойти права
через backend House Plan.
## 11. Ограничения достоверности
- Neighbor tables — наблюдаемый snapshot, а не лог текущих packet routes.
- Спящие end devices могут отсутствовать или иметь устаревшую связь.
- Пара может наблюдаться только с одной стороны; это нормальный результат.
- Coordinator/router failures могут оставить частичный граф.
- Диапазон LQI 0..255 общий, но providers не гарантируют одинаковую методику и
момент измерения. Межпровайдерный рейтинг качества не строится.
- Геометрия стен используется только как визуальный контекст. House Plan не
утверждает, что конкретная стена вызвала слабый сигнал.
## 12. i18n
Новые пользовательские строки добавляются синхронно в `en`, `ru`, `de`, `fr`:
- название и help переключателя;
- названия ZHA/Z2M provider controls;
- статус «данные не загружены / получены / устарели / частичные»;
- предупреждение о длительности и нагрузке Z2M scan;
- ошибки permission, unsupported, timeout, invalid base topic/payload;
- cross-space count с pluralization;
- нейтральное summary «связи не найдены / часть узлов не показана».
Тексты используют «наблюдаемые связи» и «полученные данные», а не «текущий
маршрут». Internal identifiers и raw provider errors локализованную границу не
пересекают.
## 13. Критерии приёмки
- **AC1 — default/off.** Новый и существующий план без валидного `enabled:true`
визуально и по поведению совпадает с текущим View; topology runtime/API не
вызываются. Доказательство: unit config tests + browser request spy, Codex.
- **AC2 — persistence.** Admin может включить/выключить настройку в «Общих
настройках»; она переживает reload, относится ко всем spaces и не теряется при
сохранении соседнего поля. Доказательство: config/store unit + browser smoke,
Codex; backend suite на Linux CI при изменении store schema.
- **AC3 — permissions.** Non-admin не получает активного control, не вызывает
ZHA/MQTT API и не видит слой при сохранённом `true`. Доказательство: unit
permission matrix + browser smoke, Codex.
- **AC4 — contextual only.** При включённой функции и пригодном snapshot без
hover нет линий, постоянных badges или легенды; hover одного mapped Zigbee
marker рисует только incident links этого node. Доказательство: resolver unit
+ browser smoke + golden light/dark, Codex.
- **AC5 — no inferred route.** Реализация не строит путь до coordinator и не
добавляет несуществующие edges из route destinations. Доказательство:
provider fixtures + mutation witness, Codex.
- **AC6 — same-space mapping.** Рисуются только links с двумя exact drawable
endpoints текущего space; сосед получает transient neutral highlight.
Доказательство: mapping unit + browser smoke + golden, Codex.
- **AC7 — cross-space truth.** Remote endpoint не создаёт линию; рядом с
hovered marker временно показан неинтерактивный count только drawable remote
endpoints, без направления «выше/ниже». Доказательство: classification unit +
browser smoke + golden, Codex.
- **AC8 — ambiguous/hidden lifecycle.** Unmatched, ambiguous, hidden, removed и
HA-disabled endpoints не рисуются и не входят в cross-space count; rebind и
перемещение пересчитывают mapping без scan. Доказательство: unit + browser
smoke, Codex.
- **AC9 — modality and modes.** Touch/pen, keyboard focus, editors, kiosk и
`houseplan-space-card` не показывают topology и не получают нового действия;
существующие tap/long-press/click работают без изменений. Доказательство:
browser mouse/touch/mode matrix + existing device-action smokes, Codex.
- **AC10 — cleanup.** Pointerleave, mouse→touch/pen, mode/space change,
disconnect/remount и setting off очищают линии, highlight и count в том же
lifecycle turn. Доказательство: state-machine unit + browser smoke, Codex.
- **AC11 — pointer ownership.** Линии, highlight и count pointer-transparent,
не меняют device hit area, hover tooltip, click action, pan/zoom или fit.
Доказательство: DOM/computed-style assertions + browser gestures smoke, Codex.
- **AC12 — LQI visual.** Hover→neighbor observation использует каноническую
continuous LQI colour; unknown — нейтральный пунктир; reverse observation не
усредняется. Stroke остаётся читаемым в screen space на min/default/max zoom.
Доказательство: pure resolver unit + golden zoom/theme matrix, Codex.
- **AC13 — ZHA contract.** Явное действие читает admin-only `zha/devices`,
нормализует fixtures и не вызывает `zha/topology/update`; UI не заявляет
неизвестную scan freshness. Доказательство: adapter unit/contract fixture +
request spy, Codex.
- **AC14 — Z2M contract.** Только подтверждённая instance публикует raw
`routes:false` request после явного предупреждения; transaction, 150 s timeout,
retained/foreign/late response и cleanup соблюдены. Доказательство: adapter
unit with fake clock/MQTT + backend/HA contract smoke on Linux CI, Codex/CI.
- **AC15 — no implicit work.** HA state ticks, hover, space switch, rebind и
второй экземпляр карточки не запускают provider fetch/scan; concurrent explicit
requests дедуплицируются. Доказательство: call-count unit + browser smoke +
mutation witnesses, Codex.
- **AC16 — failures.** Unsupported API, permission denial, invalid payload,
partial provider failure и timeout дают локализованный status и не ломают plan
либо snapshot другого provider. Доказательство: unit/error fixtures + browser
smoke, Codex.
- **AC17 — privacy.** IEEE/raw topology не попадают в saved config, exports,
backup, diagnostics, support report, localStorage или console. Доказательство:
serialization/privacy tests + reviewer code audit, Codex/Claude.
- **AC18 — bounds.** Payload limits, edge cap и incident-only render сохраняют
установленный performance budget на fixtures 20/100/500 nodes и dense
malformed graph. Доказательство: benchmark + performance smoke, Codex/CI.
- **AC19 — lazy boundary.** Выключенный initial View не включает topology runtime
в initial graph и сохраняет действующий bundle ceiling; включённый путь
загружает отдельный chunk один раз. Доказательство: chunk/import unit + bundle
budget + browser network spy, Codex.
- **AC20 — i18n.** Все новые видимые состояния имеют parity en/ru/de/fr,
корректные plurals и не показывают raw provider text. Доказательство: i18n
parity/dead-key tests + golden, Codex.
## 14. План автотестов
1. Добавить pure-unit suites для settings normalization, normalized graph,
directional pair merge, incident-edge selection, exact registry/marker
mapping, cross-space classification, payload bounds и cache/in-flight dedupe.
2. Добавить обезличенные ZHA и Z2M fixtures: one-way/two-way/unknown LQI,
sleeping end device, coordinator, multiple instances, duplicates/self links,
partial router failure, wrong base topic и malformed/dense payload.
3. Adapter tests с fake clock/transport доказывают ZHA read-only path и полную
Z2M transaction/timeout/retained/late/cleanup матрицу. Для защит implicit scan,
matching и cleanup зарегистрировать mutation-gate entries.
4. Новый browser smoke `smoke_zigbee_topology_hover.mjs`: off/no requests;
admin enable/persistence; explicit load; mouse hover incident-only; leave и
lifecycle cleanup; same/cross-space; touch/pen/mode/kiosk/static absence;
click/pan/zoom regression; errors и second-card dedupe.
5. Golden-сценарии light/dark: local strong/weak/unknown links, cross-space count,
min/default/max zoom и partial-data status в общих настройках. Baselines
принимаются только через штатный Linux reviewed workflow.
6. Performance fixture 20/100/500 nodes измеряет normalize/map, first hover,
repeated hover и dense-invalid rejection; обычный HA tick сохраняет нулевую
topology работу. Бюджеты фиксируются до S7 на измеренном dev-стенде.
7. Если реализация добавляет House Plan backend surface, выполнить полный HA
harness на Linux CI; Windows native результат не считать backend-доказательством.
8. Перед S7 обязательны `npm run typecheck`, `npm test`, `npm run build`,
`npm run bundle:sync`, `npm run bundle:budget`, `node scripts/check-docs.mjs`,
`node scripts/smoke-select.mjs --base origin/dev --head HEAD`, выбранные smoke,
`node scripts/no-new-any.mjs --base origin/dev --head HEAD` и относящиеся
mutation runs. Golden/performance полным набором — перед бетой, targeted
witnesses — до код-ревью.
## 15. Риски
- ZHA snapshot может быть старым, а completion-aware refresh отсутствует.
- Z2M scan дорогой и зависит от корректного base topic/MQTT permissions.
- Один HA device может иметь несколько маркеров, а custom discovery identifiers
могут разрушить exact mapping.
- Пользователь может принять neighbor snapshot за реальный маршрут трафика.
- Dense/ошибочный payload способен перегрузить normalization или DOM без ранних
limits и incident-only resolver.
- Hover-only UX недоступен на touch; это осознанное ограничение диагностической
функции, а не скрытая попытка переопределить device gestures.
## 16. Откат
Функция fail-closed и выключена по умолчанию. Операционный откат — отключить
настройку: runtime перестаёт загружаться/рисоваться, provider requests не
выполняются, текущий план и LQI остаются прежними. Каждый provider adapter можно
отключить независимо без изменения данных плана. Snapshot memory-only, поэтому
cleanup пользовательских данных и миграция назад не требуются; сохранённые base
topics безопасно игнорируются старой версией.
## 17. Release-артефакты
- запись со ссылкой на #54 в `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` в том
же user-visible implementation commit;
- актуализация `docs/UX-MODES.md`, `docs/SCOPE.md`/J7 и пользовательского
руководства: default-off, mouse-only, incident-only, manual provider refresh;
- обновление описания общих настроек и privacy/support boundary;
- reviewed golden screenshots для topology hover и настроек;
- release note прямо сообщает ограничения: admin, mouse hover, snapshot, без
ZHA forced scan и без touch interaction.
## 18. Предположения автора — можно менять на ревью без решения владельца
- Внутреннее поле настроек группируется в `settings.zigbee_topology`; точное имя
не является продуктовым контрактом.
- Provider-neutral runtime и adapters живут в отдельном lazy chunk; House Plan
backend не добавляется, пока штатных HA WebSocket/MQTT surfaces достаточно.
- Default Z2M base topic — `zigbee2mqtt`; несколько instances представлены
списком exact base topics.
- Stale threshold — пять минут; он меняет только status, не запускает refresh.
- Z2M hard timeout — 150 секунд, `routes:false`.
- Line visual использует LQI направления hovered→neighbor; reverse observation
хранится, но не требует второго параллельного stroke.
- Cross-space detail ограничен неинтерактивным count; список и навигация могут
стать отдельной задачей только после полевого запроса.
## 19. Источники Stage 0
- Home Assistant Core ZHA WebSocket API:
https://github.com/home-assistant/core/blob/dev/homeassistant/components/zha/websocket_api.py
- Home Assistant Frontend ZHA data contract:
https://github.com/home-assistant/frontend/blob/dev/src/data/zha.ts
- Home Assistant Frontend graph normalization:
https://github.com/home-assistant/frontend/blob/dev/src/panels/config/integrations/integration-panels/zha/zha-network-data.ts
- zigpy topology scanner:
https://github.com/zigpy/zigpy/blob/dev/zigpy/topology.py
- Home Assistant MQTT WebSocket subscription:
https://github.com/home-assistant/core/blob/dev/homeassistant/components/mqtt/__init__.py
- Home Assistant `mqtt.publish` action:
https://www.home-assistant.io/actions/mqtt.publish/
- Zigbee2MQTT networkmap API:
https://www.zigbee2mqtt.io/guide/usage/mqtt_topics_and_messages.html#zigbee2mqttbridgerequestnetworkmap
- Zigbee2MQTT implementation/model:
https://github.com/Koenkk/zigbee2mqtt/blob/master/lib/extension/networkMap.ts
and https://github.com/Koenkk/zigbee2mqtt/blob/master/lib/types/api.ts