docs: review document for #464
Проверка (CI) / Классификация изменённых файлов (push) Successful in 29s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 1m9s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 42s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 26s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 31s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 8m56s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 11m56s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped

Issue: #464
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-05 22:44:26 +00:00
parent 87cab133ac
commit afba49a3fa
+153
View File
@@ -0,0 +1,153 @@
# CODE-REVIEW-464-r2
- **Issue:** https://github.com/Matysh/houseplan-card/issues/464
- **Ветка:** `issue/464-zigbee-topology-layer`
- **Материал:** `git diff origin/dev...HEAD` на SHA `87cab133ac345c2283050bbea27ce73534393794`
(= текущий `HEAD` детач-чекаута ревьюера, совпадение проверено `git rev-parse HEAD`)
- **Трек:** полный (не менялся с r1)
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4 (потрачен в r1, жёлтым;
зелёный вердикт этого раунда цикл не тратит — §4)
## Скоуп раунда
Разбор **по дельте** (PROCESS.md §2.9): предыдущий раунд (`CODE-REVIEW-464-r1.md`,
жёлтый, SHA `276bf193`) закрыт одной находкой High-1. Дельта этого раунда —
`git diff 276bf193..HEAD`, 2 коммита (`0f7b0dff` продукт+тесты+доки,
`87cab133` только `docs/images/screenshots.json`), локальна: правит один CSS-селектор,
расширяет один smoke и добавляет один mutation-gate мутант под уже найденный
дефект. Ребейза на ушедший вперёд `dev` не было, новая подсистема не задета,
контракт поведения не сменился (AC1 из ТЗ не переписан, только доказан) — полный
разбор не требуется, разбираю дельту плюс всё, до чего она дотягивается.
Файлы дельты (без `dist/**` и `custom_components/houseplan/frontend/**`,
класс D — сверены пересборкой, см. гейты):
- `src/styles/devices.styles.ts` — продуктовый фикс (класс A)
- `demo/smoke_zigbee_topology_hover.mjs`, `scripts/mutation-gate.mjs` — тесты (класс B)
- `docs/specs/464-zigbee-topology-layer-order.md`, `docs/TESTING.md`,
`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`, `docs/images/screenshots.json` —
документация (класс C)
## Закрытие раунда r1
| Находка | Чем закрыта | Где это видно |
|---|---|---|
| **High-1**: реальный курсор мыши над hovered-source endpoint матчит старое правило `:host([data-pointer-hover]) .dev:hover { z-index: 5 }` (специфичность `(0,4,0)`) сильнее нового `.dev[data-hp-zigbee-topology-endpoint] { z-index: 8 }` (специфичность `(0,2,0)`); оверлей стоит на `z-index: 7` → `5 < 7`, линия рисуется поверх маркера-источника | Добавлено правило той же специфичности что и конфликтующее плюс атрибут endpoint плюс `:hover` — `(0,5,0)`, что строго выше `(0,4,0)` и решает каскад в пользу endpoint независимо от порядка объявления | `src/styles/devices.styles.ts:345` — `:host([data-pointer-hover]) .dev[data-hp-zigbee-topology-endpoint]:hover { z-index: 8; }`. Доказано исполнением: smoke добавил блок с настоящим `page.mouse.move` (не `dispatchEvent`) и `result.realPointerEndpointWins` (`demo/smoke_zigbee_topology_hover.mjs:555-579`); лично прогнан `node demo/smoke_zigbee_topology_hover.mjs` → `OK`, `"realPointerEndpointWins": true`. Тест умеет падать: новый мутант `zigbee-topology-hovered-endpoint-elevation-removed` (`scripts/mutation-gate.mjs:6067-6076`) занижает z-index новой строки до 1; лично прогнан `node scripts/mutation-gate.mjs --id=zigbee-topology-hovered-endpoint-elevation-removed` → «покраснел, как обязан» |
Других находок в r1 не было (Medium: 0).
## Унаследовано из r1
Принято без повторной проверки — делта их не задевает:
- **AC2 (ownership/lifecycle/single-projection)** и **AC3 (casing неизвестного
LQI)** — доказаны чтением и исполнением в `CODE-REVIEW-464-r1.md` на SHA
`276bf193a2bec40a3c2fc823d364cbda00ac10d5`; дельта r2 не трогает
`hp-zigbee-topology-overlay.ts`, `zigbee-topology-overlay-bridge.ts`,
`houseplan-card.ts` — файлы, где эти AC реализованы.
- **Ревью ТЗ** — `SPEC-REVIEW-464-r1.md`, зелёное, SHA спеки `aae9efb9`.
- **Гейты `typecheck`/`test`/`build`/`bundle`/`no-new-any`/`process-gate`/
`benchmark:zigbee-topology` на SHA `276bf193`** — зелёный Validate
(run 33994548645), зафиксировано в `CODE-REVIEW-464-r1.md`.
- **i18n, трейлеры структуры коммитов, cleanup-матрица (pointerleave/touch/
pen/mode/space/setting/disconnect)** — проверены в r1, дельта их не касается.
## Как проверялось в этом раунде
| Гейт | Статус | Как |
|---|---|---|
| `typecheck` / `npm test` / `npm run build` + 3 копии бандла | ✅ зелёный, не перегонял отдельно | Validate на точном SHA `87cab133`: https://github.com/Matysh/houseplan-card/actions/runs/33996034405 (success) — «дешёвые гейты на этом SHA уже подтверждены». Дополнительно лично прогнал `npm run bundle:sync` (`tsc --noEmit && rollup -c` внутри) для собственного воспроизведения — зелёный, `git status` после чист: пересборка байт-в-байт совпала с закоммиченным `dist/**`/`custom_components/houseplan/frontend/**` |
| `node scripts/check-docs.mjs` | ✅ зелёный | Прогнал сам: `Documentation checks passed (7 files, 12 external links)`; diff трогает `src/**`, отпечаток скриншотов обязателен — коммит `87cab133` обновляет только `sourceFingerprint`/`sourceSha256`, все `imageSha256` побайтно не изменились (сверено чтением диффа `docs/images/screenshots.json`) |
| `node demo/smoke_zigbee_topology_hover.mjs` (прямое совпадение `smoke-select.mjs`) | ✅ зелёный | Прогнал сам после `bundle:sync`: `OK`, включая новый `realPointerEndpointWins: true` |
| Мутант `zigbee-topology-hovered-endpoint-elevation-removed` | ✅ красный при повреждении, как обязан | Прогнал сам: `node scripts/mutation-gate.mjs --id=zigbee-topology-hovered-endpoint-elevation-removed` → «поймано 1 из 1» |
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | ✅ выполнен | Прямое совпадение: только `smoke_zigbee_topology_hover.mjs` (уже прогнан выше). 29 слабых связей — все по одному распространённому символу `_mode`; дельта не трогает переключение режимов (только CSS-селектор hover/endpoint и тесты/доки) — решил не гонять, риска регрессии не вижу |
| `npm run golden:verify` | не гонял | В `demo/golden/**` нет Zigbee-topology-сценария (проверено чтением `demo/golden/matrix.mjs` и содержимого `demo/golden/`); дельта не расширяет видимый в golden рендер, а обновлённый скриншот-фингерпринт уже подтверждён `check-docs.mjs` и байт-в-байт сверкой `imageSha256` |
| Остальные 6 из 7 старых мутантов задачи + полный `mutation-gate.mjs --check` | не гонял | Не задеты дельтой (файлы мутантов для AC2/AC3 не менялись); наследую зелёный статус r1/хендоффа автора; полный `--check` — предрелизный объём, несоразмерен точечной правке |
| `npm run invariants` | не гонял | Дельта не трогает геометрию комнат/стен, `layout`, `marker.space`, `open_spans` — только экранный z-index topology-оверлея |
| `python -m pytest tests_backend` | не гонял | Диапазон не трогает `custom_components/**/*.py` |
| `npm run benchmark:zigbee-topology`, `bundle:budget`, `no-new-any`, `process-gate --issues` | не гонял | Не задеты дельтой; зелёный статус наследую из Validate на `87cab133` и хендоффа автора r2 (перечислены поимённо в его комментарии) |
## Что проверено и корректно
- **Специфичность CSS разрешает конфликт правильно.** Новое правило
`:host([data-pointer-hover]) .dev[data-hp-zigbee-topology-endpoint]:hover`
весит `(0,5,0)` — строго больше конфликтующего `:host([data-pointer-hover])
.dev:hover` `(0,4,0)` — независимо от порядка объявления в файле. Проверено
и расчётом, и (сильнее) реальным исполнением через `page.mouse.move`.
- **Нет скрытых конфликтов той же природы.** Перепроверил чтением все
`z-index`-правила `devices.styles.ts` (`grep`) — единственная другая
hover-зависимая пара (`.dev:focus-visible { z-index: 5 }`) реагирует на
клавиатурный фокус, а не на реальный курсор; топология активируется только
реальным mouse-hover (зафиксировано ещё в аналитике issue: «touch и kiosk не
меняются, топология доступна только admin + mouse hover»), так что этот путь
не достижим для endpoint-элементов и не относится к AC1.
- **Мутант защищает именно новую строку**, а не дублирует старый: `find`/
`replace` бьёт по селектору с `:hover`, отличному от базового
`zigbee-topology-endpoint-elevation-removed`; `id` уникален
(`grep -c` = 1 по всему файлу).
- **Регрессионный smoke использует настоящий курсор**, а не синтетический
`dispatchEvent` — то же средство, которым сам дефект был найден в r1;
ждёт до 5 c появления `:hover` + атрибута + отрисованной линии перед
сравнением `getComputedStyle(...).zIndex`, что исключает гонку с отрисовкой.
- **Документация и трейлеры.** `CHANGELOG.md`/`CHANGELOG.ru.md` в одном
коммите с `User-Visible: yes` (`0f7b0dff`), формулировка «including the one
under the pointer» / «включая маркер прямо под курсором» точно описывает
зафиксированное поведение и не расширяет сам контракт. `docs/TESTING.md` и
ТЗ `docs/specs/464-...md` получили по абзацу, описывающему именно этот
инвариант (реальный `:hover` vs синтетический `pointerover`) — согласуется
с находкой r1 дословно. `87cab133` несёт `User-Visible: no` — корректно,
видимых кадров это не меняет (imageSha256 не тронут).
- **Инварианты модели вне периметра** — дельта не касается геометрии,
подтверждено чтением диффа: единственный продуктовый файл — CSS-стили.
## Чего не проверял
- Полный `mutation-gate.mjs --check` (весь проект) и оставшиеся 6 старых
мутантов задачи — несоразмерно точечной правке, не задеты дельтой.
- `npm test` целиком и `npm run build` отдельным прогоном юнит-сьюта — положился
на зелёный Validate точного SHA `87cab133`; лично воспроизвёл только
`tsc --noEmit && rollup -c` через `bundle:sync`.
- 29 смоков со слабой связью по символу `_mode` — не гонял, дельта не трогает
переключение режимов; решение зафиксировано в таблице гейтов.
- Реальное touch/pen устройство за пределами эмуляции `pointerType` — тот же
довод, что и в r1: различие смок/реальное устройство здесь не той природы,
что в закрытой находке (`pointerType` — явное поле события, не завязано на
hit-testing браузера, в отличие от `:hover`).
- Визуальный скриншот итоговой сцены — числовой `getComputedStyle(...).zIndex`
уже является единственным источником, управляющим порядком отрисовки;
дополнительный кадр ничего не добавил бы.
## Единый источник числа
Дельта не вводит и не меняет пользователем видимую величину (числовое
значение, дублируемое в двух местах интерфейса) — правится исключительно
порядок отрисовки (`z-index`), самого числа/значения пользователю не
показывается. `test/single-source-numbers.test.mjs` не затрагивается дельтой
(файл не менялся, диапазон не относится к value-badge/подписям).
## Вывод
High-1 из r1 закрыт: причина (специфичность CSS `:hover` выше специфичности
endpoint-правила) устранена добавлением правила ещё большей специфичности,
доказано личным исполнением smoke с настоящим курсором и личным исполнением
нового защитного мутанта, который краснеет при откате правки. Других находок
не внесено. AC2/AC3 наследуются из r1 без повторной проверки — дельта их не
касается. Документация и трейлеры в порядке.
**Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0**
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/464-zigbee-topology-layer`, коммит `87cab133ac34` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `445cc106c18942021aa8209aeac33ac47f1ccbda`
```
git log --all --format='%H %T' | grep 445cc106c189
```
- ТЗ `docs/specs/464-zigbee-topology-layer-order.md`, блоб `043ada287e048ed9750080c342b2a2fdb869d9f0`
```
git log --all --find-object=043ada287e048ed9750080c342b2a2fdb869d9f0 -- docs/specs/464-zigbee-topology-layer-order.md
```