mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
@@ -0,0 +1,545 @@
|
||||
# ТЗ #457 — направление Zigbee-связей к координатору
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/457
|
||||
- Базовый контракт топологии: [ТЗ #54](054-zigbee-topology-overlay.md)
|
||||
- Приоритет: P2
|
||||
- Тип: feature
|
||||
- Сложность: 4/5
|
||||
- Трек: полный — новый визуальный контракт, производный граф, i18n и browser smoke
|
||||
- Продуктовые решения владельца: 2026-09-05
|
||||
|
||||
## 1. Сценарий
|
||||
|
||||
Персона — администратор дома из `docs/SCOPE.md`, работающий в полной карточке
|
||||
House Plan на компьютере с мышью. В общих настройках уже включено отображение
|
||||
Zigbee-связей и явно загружен snapshot ZHA или Zigbee2MQTT.
|
||||
|
||||
Администратор наводит курсор на проблемное устройство и хочет не только увидеть
|
||||
его радиососедей, но и понять, через какое следующее устройство путь ведёт к
|
||||
координатору и какие устройства, наоборот, используют наведённый router как
|
||||
следующий шаг.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
До: при наведении House Plan показывает одинаковые линии ко всем доступным
|
||||
соседям; направление к координатору по ним определить нельзя.
|
||||
|
||||
После: те же линии и LQI-цвета остаются, но на рёбрах выбранного дерева
|
||||
маршрутов появляются стрелки к координатору. У обычного устройства одна
|
||||
исходящая стрелка, у router также могут быть входящие стрелки от детей, а
|
||||
резервные соседские связи остаются без стрелок. Если следующий узел нельзя
|
||||
показать на текущем плане, короткая стрелка ведёт в поясняющий bubble рядом с
|
||||
наведённым устройством.
|
||||
|
||||
## 3. Проблема и текущее состояние
|
||||
|
||||
`ZigbeeTopologyLink` уже хранит направленные наблюдения `aToB` и `bToA`, но
|
||||
`ZigbeeHoverLine` несёт только `neighborMarkerId` и `lqi`.
|
||||
`resolveMappedTopologyHover()` классифицирует локальные, удалённые и
|
||||
неотрисованные endpoints, а `hp-zigbee-topology-overlay` рисует обычные SVG
|
||||
`line`. Поэтому существующая картинка отвечает на вопрос «кого видит это
|
||||
устройство», но не отвечает на вопрос «куда отсюда двигаться к координатору».
|
||||
|
||||
Нельзя ставить стрелку непосредственно по `relationship` каждого neighbour
|
||||
record. Снимки могут быть неполными или взаимно противоречивыми: два узла могут
|
||||
одновременно назвать друг друга родителем, sibling не задаёт направление, а
|
||||
устаревшие записи способны образовать цикл. Требование владельца сильнее
|
||||
отдельного provider-наблюдения: двигаясь по стрелкам, пользователь должен
|
||||
приходить к координатору. Это свойство должно обеспечиваться построением
|
||||
производного дерева.
|
||||
|
||||
Дополнительный дефект нормализации: Zigbee2MQTT передаёт `relationship` числом,
|
||||
а текущий `observation()` принимает только строку. В результате полезное
|
||||
предпочтение родителя теряется, хотя сам граф и LQI сохраняются.
|
||||
|
||||
## 4. Скоуп
|
||||
|
||||
В #457 входят:
|
||||
|
||||
- детерминированное дерево аплинков отдельно для каждой topology instance;
|
||||
- построение дерева по полному нормализованному графу, включая узлы без marker;
|
||||
- стрелки только на рёбрах дерева, всегда в направлении к координатору;
|
||||
- сохранение всех существующих прямых соседских линий, halo и LQI-семантики;
|
||||
- parent bubble со стрелкой, если следующий узел находится в другом
|
||||
пространстве или не имеет drawable marker;
|
||||
- нормализация числовых `relationship` Zigbee2MQTT;
|
||||
- локализованные тексты для отсутствующего устройства и координатора;
|
||||
- пояснение семантики в руководстве пользователя;
|
||||
- unit-проверка дерева и инварианта, расширение topology hover smoke;
|
||||
- сохранение lazy boundary и существующих performance budgets.
|
||||
|
||||
## 5. Не-скоуп
|
||||
|
||||
В #457 не входят:
|
||||
|
||||
- фактическая трассировка каждого Zigbee-пакета или гарантия реального текущего
|
||||
маршрута между router-узлами;
|
||||
- постоянный полный mesh-граф, режим «показать все связи» или легенда;
|
||||
- изменение способа и частоты получения ZHA/Zigbee2MQTT snapshots;
|
||||
- автоматическое сканирование, история маршрутов и анимация пакетов;
|
||||
- новая визуализация асимметрии LQI;
|
||||
- изменение цветов, толщины или состава существующих соседских линий;
|
||||
- отдельный topology-жест для touch/pen, keyboard focus, kiosk,
|
||||
`houseplan-space-card` или редакторов;
|
||||
- автоматическое размещение координатора либо других отсутствующих устройств;
|
||||
- переход в другое пространство по bubble;
|
||||
- новые настройки или изменение сохранённого конфига.
|
||||
|
||||
## 6. Контракт производного дерева
|
||||
|
||||
### 6.1. Изоляция provider-сетей
|
||||
|
||||
Дерево строится отдельно для каждого `ZigbeeTopology`, то есть для пары
|
||||
`provider + instanceId`. Узлы и рёбра разных ZHA/Zigbee2MQTT instances никогда
|
||||
не объединяются в один маршрут.
|
||||
|
||||
Ровно один узел с `role: coordinator` становится корнем. Если координатора нет
|
||||
или нормализованный instance содержит несколько координаторов, дерево для этого
|
||||
instance не строится: соседские линии продолжают работать как сейчас, но
|
||||
стрелок и parent bubble нет. Неоднозначность не исправляется выбором по имени,
|
||||
порядку или IEEE.
|
||||
|
||||
### 6.2. Дистанция и выбор родителя
|
||||
|
||||
От координатора выполняется обход в ширину по ненаправленному набору
|
||||
нормализованных links. Для каждого достижимого узла фиксируется минимальное
|
||||
число рёбер до координатора.
|
||||
|
||||
Родитель узла с дистанцией `d > 0` выбирается только среди его соседей с
|
||||
дистанцией `d - 1`. Это ограничение обязательно: оно по построению исключает
|
||||
циклы и гарантирует продвижение к корню на каждом шаге.
|
||||
|
||||
Если допустимых родителей несколько, применяется стабильный порядок:
|
||||
|
||||
1. сначала сосед, которого направленное наблюдение **от дочернего узла к
|
||||
соседу** называет `parent`;
|
||||
2. затем большее известное LQI того же направления; известное значение выше
|
||||
отсутствующего;
|
||||
3. при полном равенстве — лексикографически меньший `node.key`.
|
||||
|
||||
Обратное LQI не подменяет отсутствующее прямое наблюдение и не усредняется с
|
||||
ним. `relationship`, указывающий на соседа той же или большей дистанции,
|
||||
игнорируется как устаревшее предпочтение, потому что нарушил бы инвариант.
|
||||
|
||||
Недостижимые от координатора компоненты не получают parent. Их прямые связи
|
||||
видны как сейчас, без стрелок; отсутствие стрелки и есть согласованный сигнал,
|
||||
что путь по snapshot неизвестен.
|
||||
|
||||
### 6.3. Нормализация relationship
|
||||
|
||||
Строковые значения нормализуются без учёта регистра, пробелов, дефисов и
|
||||
подчёркиваний. Числа Zigbee2MQTT преобразуются в канонические значения:
|
||||
|
||||
| Значение | Каноническая семантика |
|
||||
|---:|---|
|
||||
| 0 | `parent` |
|
||||
| 1 | `child` |
|
||||
| 2 | `sibling` |
|
||||
| 3 | `none` |
|
||||
| 4 | `previous_child` |
|
||||
|
||||
Неизвестное число или строка не отбрасывает link и LQI, а означает отсутствие
|
||||
предпочтения. Только `parent` влияет на tie-break выбора родителя; остальные
|
||||
значения сохраняются для диагностики, но не задают стрелку напрямую.
|
||||
|
||||
### 6.4. Инварианты
|
||||
|
||||
Для каждого построенного дерева должны выполняться свойства:
|
||||
|
||||
1. у координатора нет parent;
|
||||
2. у каждого другого включённого узла ровно один parent;
|
||||
3. дистанция parent всегда на единицу меньше дистанции ребёнка;
|
||||
4. циклов нет;
|
||||
5. повторный переход по parent за конечное число шагов достигает координатора;
|
||||
6. одинаковый snapshot всегда даёт одинаковое дерево независимо от порядка
|
||||
`nodes` и `links` во входном массиве.
|
||||
|
||||
## 7. Контракт hover
|
||||
|
||||
### 7.1. Локальные endpoints
|
||||
|
||||
Hover по-прежнему берёт только incident links наведённого node.
|
||||
|
||||
- если сосед выбран parent наведённого node, стрелка идёт от наведённого marker
|
||||
к соседу;
|
||||
- если наведённый node выбран parent соседа, стрелка идёт от marker соседа к
|
||||
наведённому marker;
|
||||
- если link не входит в дерево, линия остаётся без стрелки;
|
||||
- координатор не имеет исходящей стрелки, но может принимать входящие;
|
||||
- цвет, пунктир и LQI линии по-прежнему берутся из наблюдения от наведённого node
|
||||
к соседу, независимо от направления стрелки;
|
||||
- существующий halo каждого drawable локального соседа сохраняется.
|
||||
|
||||
Если одна и та же физическая пара неожиданно пришла из двух независимых
|
||||
instances, результаты instances не смешиваются в одно дерево. Совпадающие
|
||||
отрисованные линии могут быть дедуплицированы по marker endpoints; при
|
||||
противоречащих направлениях наконечник fail-closed не показывается.
|
||||
|
||||
### 7.2. Parent в другом пространстве
|
||||
|
||||
Если parent имеет drawable marker, но находится в другом пространстве, линия к
|
||||
его неизвестной экранной позиции не рисуется. Возле наведённого marker
|
||||
появляется один короткий parent bubble с названием пространства, и стрелка от
|
||||
marker направлена к bubble.
|
||||
|
||||
Название берётся из текущего server config и передаётся overlay как явная
|
||||
`spaceId -> title` проекция. Пустой или отсутствующий title не заменяется raw
|
||||
space id: используется локализованный безопасный fallback «другое
|
||||
пространство».
|
||||
|
||||
Parent, представленный отдельным bubble, исключается из старого агрегата
|
||||
`+{n} в других пространствах`, чтобы одна связь не считалась дважды. Остальные
|
||||
drawable remote соседи, не являющиеся parent наведённого node, продолжают
|
||||
участвовать в агрегате по контракту #54.
|
||||
|
||||
### 7.3. Parent без drawable marker
|
||||
|
||||
Если parent присутствует в полном графе, но не сопоставлен с drawable marker,
|
||||
показывается bubble со стрелкой:
|
||||
|
||||
| Роль parent | Текст bubble |
|
||||
|---|---|
|
||||
| `coordinator` | «координатора нет на плане» |
|
||||
| любая другая | «устройства нет на плане» |
|
||||
|
||||
Одинаковый текст «устройства нет на плане» применяется к отсутствующему,
|
||||
hidden/removed, ambiguous, HA-disabled, unmatched и отмеченному provider как
|
||||
недоступный parent. Эти технические причины не раскрываются в View.
|
||||
|
||||
Bubble создаётся только для parent наведённого устройства. Неразмещённые дети,
|
||||
для которых наведённый router является parent, не получают ни bubble, ни
|
||||
подпись. Остальные omitted endpoints остаются невидимыми, как в #54.
|
||||
|
||||
### 7.4. Нет известного пути
|
||||
|
||||
Если дерево не построено либо наведённый node не достижим от координатора,
|
||||
parent bubble не показывается и маршрут не выдумывается. Существующие прямые
|
||||
соседские линии, remote count и lifecycle hover продолжают работать.
|
||||
|
||||
## 8. UX и визуальная геометрия
|
||||
|
||||
- Стрелка — заполненный треугольный наконечник цвета своей линии; новый цвет или
|
||||
legend не вводятся.
|
||||
- Геометрия наконечника рассчитывается в экранных пикселях, а не в процентах
|
||||
растянутого SVG. Изменение aspect ratio контейнера не сплющивает и не
|
||||
поворачивает стрелку неверно.
|
||||
- Наконечник локальной связи располагается перед halo целевого marker и не
|
||||
уходит под marker/halo. Линия остаётся визуально соединённой с endpoint.
|
||||
- Для входящей стрелки целью является halo наведённого marker; для исходящей —
|
||||
halo соседнего marker.
|
||||
- Bubble имеет screen-space отступ от наведённого marker, не перекрывает его
|
||||
hit area, остаётся `pointer-events:none` и не участвует в fit/bounds.
|
||||
- Короткая линия к bubble оканчивается у границы bubble, а наконечник направлен
|
||||
в bubble, не в произвольную точку плана.
|
||||
- При недостатке места bubble может сместиться на противоположную сторону
|
||||
marker, оставаясь внутри видимой области overlay; текст не обрезается сырой
|
||||
идентификационной строкой.
|
||||
- Arrow/bubble находятся в существующем topology layer: над архитектурой и
|
||||
decor, под device markers и tooltip.
|
||||
- Light/dark themes сохраняют существующую контрастность bubble. В
|
||||
`forced-colors` линия, наконечник и граница bubble используют системные
|
||||
`Highlight` / `CanvasText` / `Canvas`.
|
||||
- Hover cleanup, pointer ownership и действия устройств не меняются. Весь новый
|
||||
слой pointer-transparent.
|
||||
|
||||
## 9. Режимы, touch и доступность
|
||||
|
||||
Функция остаётся mouse-hover диагностикой полной карточки в View для
|
||||
администратора.
|
||||
|
||||
- Touch/pen не получают стрелок, bubble или нового жеста; их событие немедленно
|
||||
очищает активный mouse hover по контракту #54.
|
||||
- Keyboard focus не имитирует hover.
|
||||
- В Plan, Devices, Background, kiosk и `houseplan-space-card` topology overlay
|
||||
не создаётся.
|
||||
- Новые элементы имеют `aria-hidden` и не образуют интерактивных tab stops,
|
||||
потому что дублируют временную визуальную диагностику без нового действия.
|
||||
- Существующие tap, long press, click, tooltip, pan/pinch и fit не меняются.
|
||||
|
||||
Это сознательно не добавляет touch-путь к административной radio-диагностике и
|
||||
не ослабляет touch-first контракт обычного View: сама функция остаётся
|
||||
выключенной по умолчанию и недоступна non-admin.
|
||||
|
||||
## 10. Модель данных, миграция и совместимость
|
||||
|
||||
Сохранённая конфигурация и topology provider payload не меняются. Новые данные
|
||||
существуют только в памяти:
|
||||
|
||||
```ts
|
||||
type ZigbeeRouteParent = {
|
||||
parentNodeKey: string;
|
||||
distance: number;
|
||||
};
|
||||
|
||||
type ZigbeeRouteDirection = 'toward-neighbor' | 'toward-origin' | 'none';
|
||||
|
||||
type ZigbeeParentTarget =
|
||||
| { kind: 'local'; markerId: string }
|
||||
| { kind: 'remote-space'; spaceId: string }
|
||||
| { kind: 'unplaced-device' }
|
||||
| { kind: 'unplaced-coordinator' };
|
||||
```
|
||||
|
||||
Имена типов допускается уточнить без изменения поведения. Route projection
|
||||
вычисляется рядом с `mapTopologies` и мемоизируется на тот же snapshot + devices
|
||||
+ registry lifecycle; она не пересчитывается на каждый render или HA state tick.
|
||||
|
||||
`ZigbeeHoverLine` расширяется направлением стрелки, а
|
||||
`ZigbeeHoverResolution` — максимум одним parent target для каждого обработанного
|
||||
instance. Raw IEEE, provider payload и внутренний node key не передаются в DOM,
|
||||
тексты или console.
|
||||
|
||||
Миграции нет. Старый config читается и сохраняется без изменений, downgrade
|
||||
игнорирует отсутствие runtime-only полей. Compatibility-поля не добавляются.
|
||||
|
||||
## 11. i18n
|
||||
|
||||
В `src/i18n/topology/{en,ru,de,fr}.json` добавляются одинаковые ключи:
|
||||
|
||||
- `route_device_not_on_plan`;
|
||||
- `route_coordinator_not_on_plan`;
|
||||
- `route_other_space` — безопасный fallback, если title пространства пуст.
|
||||
|
||||
Название пространства остаётся пользовательским title и не переводится. Новые
|
||||
строки не включают IEEE, device id, space id или provider error text. Parity и
|
||||
dead-key проверки обязательны для четырёх тематических словарей.
|
||||
|
||||
## 12. Производительность и lazy boundary
|
||||
|
||||
- Полный обход ограничен действующими лимитами: не более 1000 nodes и 6000
|
||||
links на instance.
|
||||
- Построение расстояний и parent map имеет сложность `O(V + E)` и выполняется
|
||||
один раз на изменение topology/mapping, а не на hover.
|
||||
- Hover остаётся incident-only; он не запускает повторный BFS.
|
||||
- `benchmark:zigbee-topology` расширяется построением дерева и сохраняет текущие
|
||||
потолки `normalize 80 ms`, `map 160 ms`, `first hover 180 ms`, `20 repeated
|
||||
hovers 120 ms` на 500-node fixture. Поднимать бюджеты в рамках задачи нельзя
|
||||
без отдельного измеренного решения.
|
||||
- Topology model/overlay/i18n остаются в существующем lazy chunk. Выключенная
|
||||
опция не загружает их и не выполняет route work.
|
||||
- Initial View graph остаётся в действующем bundle budget и ceiling.
|
||||
|
||||
## 13. Безопасность и privacy
|
||||
|
||||
Новых backend/API/service-call поверхностей нет. Стрелки используют только уже
|
||||
загруженный memory-only snapshot и не инициируют scan или fetch. Права остаются
|
||||
admin-only на существующем bridge.
|
||||
|
||||
IEEE, raw topology, внутренние ids и `relationship` не попадают в saved config,
|
||||
export, backup, diagnostics, support report, localStorage, DOM-текст или console.
|
||||
Пользовательский title пространства проходит существующий Lit text binding, не
|
||||
вставляется через `unsafeHTML` и не становится CSS.
|
||||
|
||||
## 14. Критерии приёмки и доказательства
|
||||
|
||||
### AC1. Дерево всегда ведёт к координатору
|
||||
|
||||
Для каждого корректного instance ровно с одним координатором parent map
|
||||
ациклична, уменьшает дистанцию на каждом шаге и приводит каждый достижимый узел
|
||||
к координатору. Порядок входных arrays результат не меняет.
|
||||
|
||||
Доказательство: pure unit на цепочке, ромбе, циклическом mesh, disconnected
|
||||
component и перестановках nodes/links.
|
||||
|
||||
### AC2. Parent выбирается детерминированно
|
||||
|
||||
Среди соседей предыдущего BFS-уровня соблюдается порядок: direct
|
||||
`relationship=parent`, затем direct LQI, затем stable node key. Противоречивый
|
||||
relationship не создаёт цикл и не уводит на тот же/следующий уровень.
|
||||
|
||||
Доказательство: table-driven unit, включая one-way observations и ties.
|
||||
|
||||
### AC3. Z2M relationship сохраняется
|
||||
|
||||
Числа 0…4 реальной Z2M network-map fixture нормализуются в заявленные значения;
|
||||
неизвестное значение сохраняет link/LQI и не становится parent preference.
|
||||
|
||||
Доказательство: unit на обезличенной fixture и snake/camel-case payloads.
|
||||
|
||||
### AC4. Локальные стрелки имеют правильное направление
|
||||
|
||||
При hover parent-связь наведённого устройства показывает стрелку от него к
|
||||
соседу; child-связь — от соседа к наведённому; non-tree link остаётся без
|
||||
наконечника. У координатора нет исходящей стрелки.
|
||||
|
||||
Доказательство: resolver unit + `smoke_zigbee_topology_hover.mjs` с DOM-атрибутом
|
||||
направления и pixel-geometry assertions.
|
||||
|
||||
### AC5. Соседские линии не регрессируют
|
||||
|
||||
Количество существующих drawable incident links, их hovered-side LQI-цвет,
|
||||
unknown-пунктир и halo не меняются из-за route tree. Линия без известного пути
|
||||
остаётся обычной линией.
|
||||
|
||||
Доказательство: существующие unit/smoke assertions #54 плюс regression cases.
|
||||
|
||||
### AC6. Remote parent заканчивается названием пространства
|
||||
|
||||
Parent в другом пространстве получает короткую стрелку к bubble с title
|
||||
пространства; не создаёт линию к выдуманной позиции и не дублируется в
|
||||
`remote_count`. Остальные remote соседи продолжают учитываться агрегатом.
|
||||
|
||||
Доказательство: resolver unit + browser smoke с двумя пространствами.
|
||||
|
||||
### AC7. Unplaced parent объяснён без утечки деталей
|
||||
|
||||
Неразмещённый coordinator показывает «координатора нет на плане», другой parent
|
||||
— «устройства нет на плане». Hidden, ambiguous, unmatched, HA-disabled и
|
||||
provider-unavailable parent используют тот же безопасный device-текст.
|
||||
Неразмещённые дети bubble не создают.
|
||||
|
||||
Доказательство: classification unit + browser smoke + DOM privacy assertions.
|
||||
|
||||
### AC8. Неоднозначный или неизвестный корень fail-closed
|
||||
|
||||
При нуле/нескольких координаторах и для disconnected component стрелки и parent
|
||||
bubble не выдумываются, а обычные incident lines продолжают работать.
|
||||
|
||||
Доказательство: pure unit + smoke fallback case.
|
||||
|
||||
### AC9. Arrow geometry остаётся экранной
|
||||
|
||||
На широком и высоком контейнере, при min/default/max zoom наконечник сохраняет
|
||||
размер и угол, располагается перед target halo; bubble line заканчивается у
|
||||
границы bubble. Layer остаётся pointer-transparent.
|
||||
|
||||
Доказательство: pure pixel-geometry unit + browser smoke на двух aspect ratios и
|
||||
трёх zoom; computed styles для pointer ownership.
|
||||
|
||||
### AC10. Themes, forced colors и lifecycle сохранены
|
||||
|
||||
Light/dark/forced-colors читаемы; pointerleave, mouse→touch/pen, mode/space
|
||||
change, setting off и disconnect очищают стрелки и bubble вместе с линиями.
|
||||
|
||||
Доказательство: browser theme/modality/lifecycle smoke и CSS audit.
|
||||
|
||||
### AC11. Производительность и lazy boundary не ухудшены
|
||||
|
||||
Route tree строится один раз на snapshot/mapping, benchmark проходит без
|
||||
увеличения бюджетов, disabled topology не грузит lazy chunks, bundle budget и
|
||||
initial View ceiling зелёные.
|
||||
|
||||
Доказательство: benchmark, call-count unit, browser resource spy,
|
||||
`npm run bundle:budget`.
|
||||
|
||||
### AC12. Документация и локализация полны
|
||||
|
||||
Четыре topology-словаря имеют одинаковые ключи, руководства RU/EN объясняют
|
||||
семантику стрелки, approximation и случаи без стрелки; changelog RU/EN содержит
|
||||
пользовательское изменение со ссылкой на #457.
|
||||
|
||||
Доказательство: i18n tests, `node scripts/check-docs.mjs`, review diff.
|
||||
|
||||
## 15. План автотестов
|
||||
|
||||
1. Расширить `test/zigbee-topology.test.mjs` pure suites для normalised
|
||||
relationship, BFS distances, parent tie-breaks, invariant/path traversal,
|
||||
input-order stability и zero/multiple coordinator fail-closed.
|
||||
2. Добавить hover projection cases: local parent/child/peer, remote parent с
|
||||
title, unplaced device/coordinator, hidden/ambiguous parent, unplaced child,
|
||||
disconnected node и remote-count deduplication.
|
||||
3. Вынести расчёт screen-space наконечника/обрезания линии в pure helper и
|
||||
проверить горизонталь, вертикаль, диагональ, короткую линию, разные marker
|
||||
размеры и границу bubble.
|
||||
4. Расширить `demo/smoke_zigbee_topology_hover.mjs`: direction attributes,
|
||||
наконечники, parent bubble, две формы контейнера, три zoom, light/dark,
|
||||
forced-colors emulation, pointer transparency и существующий cleanup.
|
||||
5. Расширить `demo/benchmark_zigbee_topology.mjs` route-build measurement в
|
||||
существующем `mapMs` либо отдельной метрикой внутри тех же суммарных потолков.
|
||||
6. Добавить mutation witnesses для удаления BFS-level guard, инверсии стрелки и
|
||||
повторного включения parent в remote count.
|
||||
7. Перед 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`, выбранные topology/device-action smokes,
|
||||
`npm run benchmark:zigbee-topology` и `node scripts/no-new-any.mjs --base
|
||||
origin/dev --head HEAD`.
|
||||
8. Golden baselines не добавляются: topology отсутствует в текущей golden
|
||||
matrix и появляется только после реального hover. `npm run golden:verify`
|
||||
должен подтвердить отсутствие изменений существующих эталонов. Поскольку
|
||||
меняется `src/**`, docs screenshots и их fingerprint обновляются штатным
|
||||
capture workflow по общему процессу.
|
||||
|
||||
## 16. Затронутые файлы и модули
|
||||
|
||||
Ожидаемые продуктовые поверхности:
|
||||
|
||||
- `src/zigbee-topology.ts` — relationship normalization, route parent map,
|
||||
hover direction и parent-target classification;
|
||||
- `src/hp-zigbee-topology-overlay.ts` — screen-space arrowheads, bubble и
|
||||
forced-colors;
|
||||
- `src/zigbee-topology-overlay-bridge.ts`, `src/houseplan-card.ts` — проекция
|
||||
названий пространств без загрузки topology в initial graph;
|
||||
- `src/i18n/topology/{en,ru,de,fr}.json` — три новых строки.
|
||||
|
||||
Доказательные и документальные поверхности:
|
||||
|
||||
- `test/zigbee-topology.test.mjs` и при необходимости отдельный pure geometry
|
||||
test;
|
||||
- `demo/smoke_zigbee_topology_hover.mjs`;
|
||||
- `demo/benchmark_zigbee_topology.mjs`;
|
||||
- `scripts/mutation-gate.mjs` и smoke-selection registry при необходимости;
|
||||
- `docs/USER-GUIDE.md`, `docs/USER-GUIDE.ru.md`;
|
||||
- `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`;
|
||||
- manifest-driven `dist/**` и integration frontend после `bundle:sync`;
|
||||
- docs screenshot fingerprint/artifacts по обязательному `check-docs` flow.
|
||||
|
||||
Backend Python не меняется. Если реализация потребует новый House Plan API или
|
||||
provider fetch, это расширение скоупа и возврат в `S3-spec`.
|
||||
|
||||
## 17. Риски и меры
|
||||
|
||||
| Риск | Мера |
|
||||
|---|---|
|
||||
| Provider relationship противоречит графу | Parent только на предыдущем BFS-уровне; relationship лишь tie-break |
|
||||
| Стрелка сплющивается `preserveAspectRatio=none` | Вся arrow geometry считается в screen pixels |
|
||||
| Наконечник скрывается marker/halo | Обрезать линию по фактическому target radius и проверять geometry smoke |
|
||||
| Неразмещённый router искажает путь | BFS использует полный graph, mapping применяется только при проекции |
|
||||
| Стрелки прыгают между hover | Stable node-key tie-break и permutation unit |
|
||||
| Несколько provider instances смешиваются | Parent map scoped by topology instance |
|
||||
| Parent считается дважды как remote | Явное исключение parent из `remote_count` и regression test |
|
||||
| Текст раскрывает внутренний id | Только space title или локализованный fallback, DOM privacy test |
|
||||
| Hover начинает делать дорогой BFS | Memo на snapshot/mapping, benchmark и call-count witness |
|
||||
| Lazy-код протекает в initial View | Bundle graph/budget gate и disabled resource spy |
|
||||
|
||||
## 18. Откат
|
||||
|
||||
Пользовательский немедленный откат — выключить существующую опцию «Показывать
|
||||
связи Zigbee при наведении на устройство»: lazy overlay исчезает целиком и не
|
||||
выполняет route work.
|
||||
|
||||
Кодовый откат удаляет runtime-only parent projection, arrow/bubble rendering и
|
||||
три i18n-ключа, возвращая обычные линии #54. Сохранённый config и provider data
|
||||
не мигрируются, поэтому обратного преобразования данных нет.
|
||||
|
||||
## 19. Release-артефакты
|
||||
|
||||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`: одна пользовательская запись со
|
||||
ссылкой на #457 без публикации внутренних ключей/алгоритма;
|
||||
- `docs/USER-GUIDE.md` и `docs/USER-GUIDE.ru.md`: стрелка означает следующий
|
||||
шаг производного пути к координатору, а не фактический маршрут пакета;
|
||||
описать parent bubble и честное отсутствие стрелки;
|
||||
- golden: новых baselines нет; `golden:verify` подтверждает отсутствие
|
||||
регрессий существующей matrix;
|
||||
- docs screenshots: обновить штатным reviewed capture после изменения `src/**`;
|
||||
- performance: зелёные `benchmark:zigbee-topology` и `bundle:budget`;
|
||||
- security/privacy: DOM/serialization audit без новых backend surfaces;
|
||||
- generated bundles: синхронизировать только через `npm run bundle:sync`.
|
||||
|
||||
## 20. Принятые предположения
|
||||
|
||||
Ниже технические решения, не меняющие утверждённый владельцем пользовательский
|
||||
контракт; ревьюер может уточнить их без нового продуктового вопроса.
|
||||
|
||||
1. Только сосед предыдущего BFS-уровня может стать parent; это формализует
|
||||
обязательный инвариант и безопаснее прямого доверия `relationship`.
|
||||
2. Если instance содержит не ровно одного coordinator, стрелки fail-closed не
|
||||
показываются, а существующие линии сохраняются.
|
||||
3. Parent в другом пространстве исключается из общего remote count, остальные
|
||||
remote-соседи остаются в нём.
|
||||
4. Пустой title пространства не заменяется raw id, а получает локализованный
|
||||
fallback.
|
||||
5. Новых golden baselines нет, потому что текущая матрица не активирует
|
||||
mouse-hover topology; screen-space геометрию защищает целевой browser smoke.
|
||||
6. Размер и точная форма наконечника определяются общими визуальными токенами
|
||||
overlay при условии соблюдения AC9, без новой пользовательской настройки.
|
||||
@@ -179,6 +179,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#449](https://github.com/Matysh/houseplan-card/issues/449) Двойной клик/тап по свободному фону вписывает весь план | [449-double-fit-all.md](449-double-fit-all.md) |
|
||||
| [#451](https://github.com/Matysh/houseplan-card/issues/451) Фильтрация render и лёгкий live-слой взаимодействий | [451-render-performance.md](451-render-performance.md) |
|
||||
| [#456](https://github.com/Matysh/houseplan-card/issues/456) Копирование пространства без комнат и устройств | [456-copy-space.md](456-copy-space.md) |
|
||||
| [#457](https://github.com/Matysh/houseplan-card/issues/457) Направление Zigbee-связей к координатору | [457-zigbee-route-arrows.md](457-zigbee-route-arrows.md) |
|
||||
|
||||
## P3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user