docs: review document for #564
Проверка (CI) / Классификация изменённых файлов (push) Successful in 14s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 32s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 16s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 20s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Failing after 46s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 6m11s
Проверка (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
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 14s

Issue: #564
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-14 01:14:06 +00:00
parent 032616f223
commit 60f93a10ed
+182
View File
@@ -0,0 +1,182 @@
# CODE-REVIEW-564-r2
**Issue:** #564 — «В узкой колонке нажатие достаётся соседнему маркеру: круг 44 px без разрешения перекрытий»
**Заход:** r2 (второй заход код-ревью; циклов ревью кода потрачено 1/4 — потрачены на жёлтый вердикт r1, зелёный вердикт бюджет не тратит, #227)
**Материал:** зафиксирован на точном SHA `032616f2237a8de5c5cbc2c125267a09902c2594`. Рабочая копия уже на нём; `git fetch`/`checkout` не выполнялись.
## Предыдущий раунд и дельта
Вердикт r1 (документ `docs/reviews/CODE-REVIEW-564-r1.md`, материал зафиксирован на SHA
`0af582daa56632736560df0bff6c78f778618c5a`) — **жёлтый**, единственная блокирующая находка
**M1 (Medium, в скоупе)**: AC2 требовал обязательным доказательством, помимо чистой unit-матрицы,
ещё и browser smoke для Icon/Text/Double/legacy capsule во всех четырёх направлениях; в диффе
r1 такого прогона не было — только синтетические прямоугольники (unit) и один реальный
браузерный прогон на обычных Icon-маркерах без direction-вариаций.
Дельта раунда: `git diff 0af582da..032616f2` — ровно один продуктовый коммит:
```
032616f2 test: покрыть реальные капсулы маркеров (#564)
```
(коммит `27126ad3` между ними — публикация документа ревью r1, не код). Диапазон
`origin/dev..HEAD` не менялся между раундами (`git merge-base origin/dev HEAD` = `a65c88bc`,
то же самое дерево `dev`, что и на r1) — ребейза не было, дельта локальна.
Изменённые файлы дельты:
```
demo/smoke_device_hit_capsules.mjs | 252 ++++++++++++++++++++++++++
docs/TESTING.md | 10 +-
scripts/mutation-registry.mjs | 12 ++
```
Ни один файл дельты не лежит в `src/**` — `node scripts/smoke-select.mjs --base 0af582da --head
032616f2` подтверждает: «Исполняемого frontend-диффа нет… Browser-smoke этим диффом не
выбираются». Это ожидаемо: правка добавляет тест и мутанта, не трогая резолвер.
**Разбор этого раунда ограничен дельтой** (§2.10): полноценно перепроверено только то,
до чего дельта дотягивается — доказательство AC2 и связанный с ним мутационный виджет AC9.
Остальные AC (AC1, AC3–AC8, AC10, AC11) не имеют изменившегося доказательства в дельте и
наследуются из r1 (раздел ниже), кроме дешёвых гейтов, которые прогнаны заново, как требует
правило «дешёвые гейты — каждый раунд».
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| **M1** (Medium, в скоупе) — AC2 не имел browser-smoke доказательства painted-priority для Icon/Text/Double/legacy capsule во всех четырёх направлениях | Новый `demo/smoke_device_hit_capsules.mjs`: рендерит реальные Icon (`static_icon`), Text (`display: value`), Double (`badge` + явный `value_badge`) и legacy (`badge` без `value_badge` → авто-производный двухзначный legacy-режим, `legacySupplementalMetrics` в `src/device-face.ts:35-48`) маркеры, по каждому — 4 кардинальных направления (`pos-right/bottom/left/top` для with-values типов, все 4 стороны painted core для Icon/Text). Соседа `hit-neighbour` кладёт раньше в DOM/index order и позиционирует так, что тестовая точка лежит строго в invisible 44 px floor соседа и вне его painted capsule, но внутри painted capsule цели. Проверяет одновременно `elementFromPoint` (нативный DOM hit-test), `_deviceHitOwnerAt` (семантический owner) и настоящий `page.mouse.click` с открытием info card. Плюс именованный мутант `dense-device-hit-browser-skips-painted-priority` (`scripts/mutation-registry.mjs`), который отключает `if (painted.length) return nearest(painted, point)` в `src/device-hit-owner.ts:77`. | Прогнано в этом раунде: `node demo/smoke_device_hit_capsules.mjs` → `{"realFaceKinds":true,"allSixteenCasesRan":true,"everyPaintedPointOwnsNativeAndSemanticHit":true,"everyPaintedPointOwnsClick":true}` / `OK`. `node scripts/mutation-gate.mjs --id=dense-device-hit-browser-skips-painted-priority` → «чистый прогон» зелёный, мутант «покраснел» → «поймано 1 из 1». `docs/TESTING.md:552-557` теперь называет обе строки доказательства AC2 явно. |
Других блокирующих находок в r1 не было (High: 0).
## Унаследовано из r1
Документ: `docs/reviews/CODE-REVIEW-564-r1.md`, материал зафиксирован на SHA
`0af582daa56632736560df0bff6c78f778618c5a`. Принято без повторной проверки в этом раунде, потому
что дельта (`032616f2`) не касается их доказательств:
- **AC1** — J7 390/780 px, реальный click → dense1..dense5 (`demo/smoke_household_journeys.mjs`,
не тронут дельтой);
- **AC3** — минимум 44×44 не уменьшен (`.dev::before` не изменён дельтой r2 по значению);
- **AC4** — латч владельца pointerdown→pointerup/cancel (`src/device-hit-owner.ts`,
`test/device-hit-owner.test.mjs` — не тронуты дельтой);
- **AC5** — hover/tooltip у ближайшего owner, touch/pen без ложного mouse-hover;
- **AC6** — pinch/#563-совместимость (`src/touch-gesture-click-guard.ts` не тронут);
- **AC7** — детерминированный tie-break, изоляция карточек, фильтр hidden/removed/other-space;
- **AC8** — отсутствие per-frame layout scan, кэш с инвалидацией по контракту;
- **AC10** — typecheck/unit/build в цикле реализации, smoke/golden/perf — до беты;
- **AC11** — схема/координаты/action settings/i18n/backend API не менялись;
- трейлеры и changelog пяти коммитов диапазона `origin/dev..0af582da`;
- `check-docs`, `no-new-any`, байтовая идентичность бандла и скриншотов на момент r1.
Два из трёх именованных мутантов AC9, унаследованных из r1 (`dense-device-hit-falls-back-to-input-order`,
`device-touch-hover-gate-removed`, `zigbee-topology-hovered-endpoint-elevation-removed`), не
изменены дельтой — тем не менее перепроверены заново в этом раунде вместе с дешёвыми гейтами
(см. ниже), а не приняты слепо, поскольку это стоит недорого и дельта физически меняет тот же
файл `scripts/mutation-registry.mjs`, в который они прописаны.
## Как проверялось
Дешёвые гейты — гоняются в каждом раунде независимо от дельты:
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | `npx tsc --noEmit` | зелёный |
| Build + синхронизация трёх копий бандла | `npm run bundle:sync` (запускает `build`), затем `cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` и `cmp … demo/srv/assets/houseplan-card.js` | зелёный, все три копии побайтово идентичны; `git status` после — чисто, новых diff'ов не осталось |
| `node scripts/no-new-any.mjs --base 0af582da --head 032616f2` | ручной прогон | «Новых any нет» (0 добавленных строк в `src/**/*.ts` — дельта не трогает `src/**`) |
| `node scripts/check-docs.mjs` | ручной прогон | `Documentation checks passed (7 files, 12 external links)` |
| `npm test` (2691 unit) | **не перегонялся** | подтверждён зелёным Validate на точном SHA `032616f2` (см. ниже) — правило «дешёвые гейты не перегонять, если Validate зелёный на этом SHA» |
CI Validate на точном SHA подтверждён напрямую, не только по ссылке автора:
`gh run view 34794194982` → `{"conclusion":"success","headSha":"032616f2237a8de5c5cbc2c125267a09902c2594","name":"Проверка (CI)","status":"completed"}`.
https://github.com/Matysh/houseplan-card/actions/runs/34794194982
Гейты по дельте (M1/AC2/AC9):
| Гейт | Команда | Результат |
|---|---|---|
| Новый browser smoke | `node demo/smoke_device_hit_capsules.mjs` | зелёный, все 16 случаев (`allSixteenCasesRan`), painted-приоритет и click подтверждены |
| Новый мутант | `node scripts/mutation-gate.mjs --id=dense-device-hit-browser-skips-painted-priority` | «поймано 1 из 1» |
| Унаследованные мутанты AC9, перепроверены т.к. общий файл реестра изменён | `node scripts/mutation-gate.mjs --id=dense-device-hit-falls-back-to-input-order` / `--id=device-touch-hover-gate-removed` / `--id=zigbee-topology-hovered-endpoint-elevation-removed` | все три — «поймано 1 из 1» |
| `smoke-select` по дельте | `node scripts/smoke-select.mjs --base 0af582da --head 032616f2` | «Исполняемого frontend-диффа нет… Browser-smoke этим диффом не выбираются» — корректно, дельта не трогает `src/**` |
| Трейлеры/changelog коммита дельты | `git show -s --format=full 032616f2` | `Issue: #564`, `User-Visible: no` — оба класс-B файла (`demo/**`, `scripts/**`) плюс `docs/TESTING.md`; changelog не требовался и не тронут (`git diff 0af582da..032616f2 -- docs/CHANGELOG.md docs/CHANGELOG.ru.md` пуст) |
**Не прогонялись в этом раунде** (осознанно): `golden:verify`, полный `performance_smoke`,
`pytest tests_backend`, полный набор из 248+ смоков и 15 смоков, выбранных под r1 (household
journeys, drag/modes/align/history/grid-snap/pan-zoom/static-icon/wireless-parity/touch-
tips/feedback/room-tooltip/room-fit, linked-light) — они уже прогнаны в r1 на SHA `0af582da`,
дельта их файлы не трогает, и `smoke-select` по дельте не выбрал ни одного из них. По AC10/ТЗ
§10 golden/perf/pytest остаются пред-релизным гейтом.
## Находки
Новых находок нет. High: 0, Medium: 0.
## Проверено и корректно
- **AC2 закрыт полностью**: painted core/capsule реально выигрывает у невидимого floor соседа
для всех четырёх типов лица маркера (Icon/Text/Double/legacy) и всех четырёх направлений
капсулы, подтверждено настоящим DOM (`getBoundingClientRect`, `elementFromPoint`) и настоящим
кликом мыши, не только семантическим резолвером. Отображение типов в `demo/smoke_device_hit_
capsules.mjs` сверено с реальной логикой `renderDeviceFace` (`src/device-face.ts:92-101`,
`legacySupplementalMetrics` строки 35-48) — `badge.configured !== false` действительно даёт
auto-derived двухзначный `with-legacy` путь, что подтверждает `setup.realFaceKinds: true` в
выводе смока.
- Мутант `dense-device-hit-browser-skips-painted-priority` бьёт именно по строке приоритета
painted-областей (`src/device-hit-owner.ts:77`, `if (painted.length) return nearest(painted,
point)` → `if (false && painted.length) …`) и красит новый смок, не другие тесты — точечность
подтверждена независимым прогоном.
- Дешёвые гейты (typecheck, build+3 копии бандла, no-new-any, check-docs) — зелёные при
собственном прогоне на этом SHA; unit-набор из 2691 теста подтверждён зелёным Validate на
точном SHA, факт green-конклюжна проверен напрямую через `gh run view`, а не только со слов
автора.
- Трейлеры и класс изменённых файлов корректны: `demo/**`, `scripts/**`, `docs/TESTING.md` —
класс B/C, `Issue: #564` присутствует, `User-Visible: no` оправдан (тестовая инфраструктура,
без изменения поведения) и changelog не требовался.
- Дельта не расширяет scope и не трогает `src/**`, `custom_components/**`, схему конфигурации,
координаты маркеров или action-контракты — согласовано с ТЗ §4 «Не входит в задачу».
## Чего не проверял
- `npm test` целиком не перегонялся — заменён подтверждённым зелёным Validate на точном SHA
`032616f2` (см. таблицу выше), что соответствует явному разрешению для этой задачи: дешёвые
гейты не дублируются, если green Validate уже назван на этом SHA.
- Golden-эталоны, полный `performance_smoke`, `pytest tests_backend`, полный набор browser-
смоков и 15 смоков r1 — не гонялись повторно: делта их не касается (`smoke-select` подтверждает
«выбирать нечего»), а по ТЗ §10/AC10 golden и perf в любом случае пред-релизные гейты.
- Кросс-браузерное поведение `click instanceof PointerEvent` (риск, отмеченный в r1 как контекст,
не находка) — не переисследовалось, дельта его не касается.
- Ручного тестирования в браузере поверх automated smoke не проводилось — весь браузерный
прогон в этом раунде выполнен через `demo/smoke_device_hit_capsules.mjs` (Playwright/Chromium),
что и есть требуемое AC2 browser-доказательство.
## Материал раунда
- Ветка: `issue/564-dense-marker-hit` (по комментариям issue); рабочая копия — detached HEAD на
`032616f2237a8de5c5cbc2c125267a09902c2594`.
- `git rev-parse HEAD` = `032616f2237a8de5c5cbc2c125267a09902c2594`.
- `git merge-base origin/dev HEAD` = `a65c88bc11db09c65afe1d59ab1c51fdd01a6f30` — то же дерево
`dev`, что и на r1; ребейза между раундами не было.
- Материал r1 для сравнения: SHA `0af582daa56632736560df0bff6c78f778618c5a`, документ
`docs/reviews/CODE-REVIEW-564-r1.md`.
## Вердикт
Зелёный. Единственная блокирующая находка r1 (M1) закрыта исполняемым browser-smoke и именованным
мутантом, оба перепроверены в этом раунде и умеют падать. Новых находок нет, High: 0, Medium: 0.
Дешёвые гейты зелёные (частично перепрогнаны вручную, частично подтверждены Validate на точном
SHA). Issue готова к переходу в `S8-merged` по завершении промоушена.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/564-dense-marker-hit`, коммит `032616f2237a` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `621d422b3a6cfc589a8659e8c6e9dc55c94b6b13`
```
git log --all --format='%H %T' | grep 621d422b3a6c
```
- Тело issue: `f958e178616c6d48277f99106f9d84e76de72762c0c7b3e6f315231ff1cfec08`
- Вердикт конвейера: `green` · High 0