diff --git a/docs/specs/457-zigbee-route-arrows.md b/docs/specs/457-zigbee-route-arrows.md new file mode 100644 index 00000000..2c8350e3 --- /dev/null +++ b/docs/specs/457-zigbee-route-arrows.md @@ -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, без новой пользовательской настройки. diff --git a/docs/specs/README.md b/docs/specs/README.md index 4e72f547..e42f4882 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -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