Files
houseplan-card/docs/reviews/CODE-REVIEW-485-r4.md
T
2026-09-09 01:12:38 +00:00

23 KiB
Raw Blame History

CODE-REVIEW-485-r4

Issue: #485 — presence-radar Stage 1. Этап: code (PROCESS.md §2.7). Заход r4, блокирующих циклов израсходовано 3 из 4 до этого раунда (r1 — красный, High 6/Medium 6; r2 — жёлтый, High 0/Medium 3; r3 — жёлтый, High 0/Medium 1).

Материал: ветка issue/485-radar-presence, git log --oneline origin/dev..HEAD / git diff origin/dev...HEAD, HEAD = ca25e910876067a067b303497022d6a9a988b39f ("test: keep radar bundle ratchet outside gzip noise") — рабочая копия уже на этом SHA, git fetch/checkout не выполнялись.

Почему разбор полный, а не по дельте (PROCESS §2.10)

Вердикт r3 (комментарий issue, 2026-09-09T00:32:39Z) называет HEAD f474793d. Этот SHA мёртв в текущей истории: git cat-file -t f474793d9b5f... → fatal: could not get object info. Проверка показала тот же паттерн, что r3 уже описала для перехода r2→r3:

  • committer-время всей цепочки коммитов ветки, от 78afb689 (спецификация) до ca25e910 (правки этого раунда), сбито в узкий интервал 03:50:50–04:01:00 +0300 09.09 — сквозная переигровка ветки, а не точечный amend;
  • git merge-base origin/dev HEAD = 3434747d ("build: prepare v1.73.0-beta.7 candidate") — это САМ ТЕКУЩИЙ tip origin/dev. Прежний merge-base на момент r3 был 687056f3, и 687056f3 — предок 3434747d (git merge-base --is-ancestor подтверждает), то есть между r3 и этим раундом dev продвинулся ещё дальше: влился #493 (13a0bfdf/578cae52/260435e4) и был вырезан кандидат v1.73.0-beta.7 (3434747d);
  • пересечение путей git diff --name-only 687056f3..3434747d с путями ветки радара (git diff --name-only origin/dev...HEAD) непустое и включает продуктовый код, не только бандл/доки: custom_components/houseplan/websocket_api.py, custom_components/houseplan/validation.py, src/houseplan-card.ts, src/houseplan-editor-runtime.ts, tests_backend/test_ha_websocket.py.

Это прямо названный в процессе триггер («ребейз на ушедший вперёд dev — после ребейза это другой код»), причём websocket_api.py — тот самый файл находки M-2 из r2/r3. Поэтому ниже — полный повторный разбор пересечённых файлов плюс построчная перепроверка присутствия всех прежних находок, а не только диффа по единственной находке r3.

Как проверялось

Прочитан весь diff origin/dev...HEAD (см. состав ниже) и построчно — пересечение путей ребейза с веткой радара, и оба новых коммита раунда: 3f0b4aee ("test: cover radar radial calibration guard" — закрывает M-1 из r3) и ca25e910 ("test: keep radar bundle ratchet outside gzip noise" — калибровка bundle:budget после ребейза на #493 + пересборка бандла/отпечатка скриншотов).

Пересечение путей ребейза с веткой радара проверено построчно, конфликтов смысла нет:

  • custom_components/houseplan/websocket_api.py:1459 — фикс M-2 из r2/r3 (**({"radar_stage1_api": 1} if rt.radar_coordinator is not None else {})) на месте, не затронут посторонними правками #493 в этом же файле.
  • custom_components/houseplan/validation.py — упоминания radar (vol.Optional ("radar"), строки 1818-1820, 2183) не изменились по смыслу; правки #493 в этом файле относятся к summary-panel, не пересекаются построчно.
  • src/houseplan-editor-runtime.ts — radarAfterBindingChange (фикс H6 r1) и оба места вызова (13015, 13071 в r3; строки сместились из-за вставки summary-panel-кода, но логика та же — ...radarAfterBindingChange(d.radar, d.radarTouched, d.radarRemove)) не тронуты.
  • src/houseplan-card.ts — синхронное закрытие диалога маркера (фикс d433b64c из r2, регрессия общего пути) на месте: _closeMarkerDialog() в houseplan-editor-runtime.ts:7331-7335 по-прежнему разветвляется на isDirty(), синхронный путь не стал асинхронным повторно.
  • tests_backend/test_ha_websocket.py — тест M-2 (test_config_get_advertises_radar_only_while_coordinator_is_ready, строки 40-58) присутствует, не переписан слиянием с #493-тестами (test_summary_panel.py — отдельный файл, не тот же диапазон строк).

Прогнанные гейты:

Гейт Результат
npx tsc --noEmit зелёный
node --test test/*.test.mjs (полный фронтенд-набор) 2378 passed, 1 skipped, 0 failed (2379 всего, npm test)
npm run build собрано (rollup, 19.1s)
npm run bundle:sync dist → custom_components/houseplan/frontend → demo/srv/assets; git status чист и до, и после — бандл воспроизводим побайтно
npm run bundle:budget 0 — initial View 298 523 Б gzip / потолок 299 100 (±2000), запас до потолка 577 Б, до бюджета 301 066 Б — 2543 Б (предупреждение о малом запасе — унаследованный долг #367→#474, не новый)
node scripts/check-docs.mjs «Documentation checks passed» — отпечаток скриншотов актуален после пересборки в ca25e910
node scripts/smoke-select.mjs --base origin/dev --head HEAD 60 прямых совпадений + 1 зарегистрированная связь — та же картина, что в r3 (артефакт полного диффа branch-vs-dev, не нового кода раунда: см. обоснование ниже)
node demo/smoke_radar_setup.mjs OK, все поля отчёта true
node demo/smoke_radar_live.mjs OK, все поля отчёта true
python3 -m pytest tests_backend/test_radar_geometry.py tests_backend/test_radar_validation.py -q (системный Python 3.12 + pip install pytest voluptuous) 57 passed (14 geometry + 43 validation — на один больше r3: новый тест M-1)
Ручная мутация нового гварда-теста M-1 (backend): radial_ok and rms<=20… → rms<=20… в radar_geometry.py:199 новый тест test_two_point_fit_rejects_hidden_radial_mismatch красный (DID NOT RAISE); откат подтверждён (git diff --stat чист)
Ручная мутация того же гварда (TS): radialOk && rmsCm<=20… → rmsCm<=20… в radar-geometry.ts:79, пересборка test-build (tsc -p tsconfig.test.json && fix-test-build.mjs) новый тест «rejects a reference with a hidden radial mismatch» красный (1 fail из 7); откат подтверждён
git diff --name-only 687056f3..3434747d ∩ git diff --name-only origin/dev...HEAD непустое пересечение, разобрано построчно выше — не находка, конфликтов смысла нет
Merge-conflict маркеры (<<<<<<</=======/>>>>>>>) по всему дереву не найдены
Трейлеры новых коммитов раунда (git show -s --format=full) 3f0b4aee/ca25e910 — Issue: #485, User-Visible: no на обоих (тест-фикс и внутренняя калибровка бюджета — без видимого пользователю поведения, changelog не требуется)
npm run invariants не запускался — diff не трогает рёбра комнат/толщину/layout/marker.space/open_spans (grep по всем изменённым backend/frontend/тестовым файлам раунда и по пересечению с ребейзом — 0 совпадений)
CI Validate на точном SHA ca25e910 (34297455739) «Бэкенд: pytest в Home Assistant» — completed/success, 823 passed, 2 skipped (полный HA-харнесс, включая test_ha_radar*.py/test_ha_websocket.py); «Фронтенд: типы, юниты, мутанты, синхрон бандла» — success; HACS/hassfest/preflight — success; «Мутанты по диффу» (3 job) — ещё in_progress на момент публикации; смоки/perf/golden — skipped (heavy-гейт не по расписанию для обычного пуша, ожидаемо)

Зелёного полного Validate на этом SHA пока нет (мутационные job не завершены) — как и предупреждает задание, гейты выше прогнаны самостоятельно. В отличие от r3, здесь уже есть завершённый успешный прогон job «Бэкенд: pytest в Home Assistant» на точном SHA — это закрывает главный пробел r3 («Чего не проверял»: HA-харнесс недоступен в песочнице ревью) фактическим результатом, а не предположением автора.

Находки

High — нет

Medium — нет

Единственная Medium-находка r3 (M-1: четвёртый гвард решателя radial_ok/ radialOk не покрыт тестом) закрыта предметно — см. таблицу ниже.

Low (унаследовано, не растёт)

  • Нет golden-сцены для радара (find demo/golden -iname "*radar*" — по-прежнему пусто). Не эскалирую третий раунд подряд по той же логике r2/r3: защитные утверждения (частичное здоровье, отрисовка зон, гейт допустимости калибровки, отказ неподдерживаемых команд) уже доказаны прицельными негативными тестами.
  • Планка 4 кадра/с общая на координатор, а не «на радар» буквально (radar.py:236-256, поля _pending/_last_publish_monotonic — координатора). Не новый риск, не изменился в этом раунде.

Закрытие раунда r3

Находка r3 Чем закрыта Где видно
M-1 — четвёртый числовой гвард решателя (radial_ok/radialOk, согласованность радиальной дистанции) не покрыт ни одним тестом ни в backend, ни в TS-двойнике tests_backend/test_radar_geometry.py::test_two_point_fit_rejects_hidden_radial_mismatch (коммит 3f0b4aee) — референс с верным направлением, но радиальной ошибкой 25 см (RMS 17.68/индив. ошибка 25,0 — оба проходят), требует ValueError(invalid_selection); симметричный тест «rejects a reference with a hidden radial mismatch» в test/radar-geometry.test.mjs Подтверждено ручной мутацией на обеих сторонах в этом раунде (таблица гейтов выше): снятие radial_ok/radialOk из условия красит ровно эти новые тесты и ничего больше (DID NOT RAISE на backend, 1 fail из 7 на TS)

Унаследовано из r3 (без повторного разбора по существу — только подтверждение присутствия после ребейза)

Документ: docs/reviews/CODE-REVIEW-485-r3.md, SHA материала r3 f474793d9b5f (мёртв после ребейза — см. «Почему разбор полный» выше; дерево 33297f22…).

  • Все 6 High из r1 (partial-health, teardown/await race, LD2450 same-device guard, no-contour warning vs refusal, zones render, radar binding-change wipe) — подтверждены присутствующими построчным чтением тех же мест кода, что называл r3 (radar.py:552-554, radar.py:106-151, radar_validation.py:147-180, radar-render.ts:18-24, radar-editor.ts:65-75/houseplan-editor-runtime.ts вызовы radarAfterBindingChange). Ни один из этих файлов не входит в пересечение путей ребейза этого раунда (687056f3..3434747d), кроме houseplan-editor-runtime.ts — который перепроверен заново в этом раунде (раздел «Как проверялось»), не унаследован вслепую.
  • M-2 (капабилити radar_stage1_api условна на radar_coordinator) и M-3 (radar.bad_references реально достижим и промаршрутизирован отдельно от radar.bad_fit) из r2 — оба файла (websocket_api.py, radar-setup.ts/radar-geometry.ts) перепроверены заново в этом раунде (не унаследованы вслепую), потому что websocket_api.py входит в пересечение путей ребейза — см. «Как проверялось».
  • i18n-структура (en/ru/de/fr, 1363/1363 ключей без расхождений) — принято без повторного построчного обхода: diff этого раунда не касается src/i18n/*.json, а полный фронтенд-набор npm test (2378/2379, включает test/i18n.test.mjs/test/i18n-dead-keys.test.mjs) прогнан заново и зелёный.
  • Права и лимиты координатора (ACL на подписку/инспекцию, MAX_RADARS=32, лимиты подписок) — приняты без повторного чтения: radar.py/ radar_websocket.py не входят в пересечение путей ребейза этого раунда и не менялись диапазоном коммитов 3f0b4aee/ca25e910.
  • single-source-numbers (радар не создаёт вторых источников чисел) — принято по тому же аргументу: код рендера/диагностики не менялся, тест прогнан заново в составе полного набора и зелёный (3/3 внутри 2378).

Что проверено сверх таблиц

  • Легитимность повышения потолка INITIAL_VIEW_GZIP_CEILING (299 000→299 100, коммит ca25e910). Не тихое ослабление гейта: фактическая пересборка в этой песочнице дала 298 523 Б — под новым потолком на 577 Б, что и обосновывает комментарий коммита («восстановить >500 Б запаса шума по обе стороны существующей 2 кБ ratchet-полосы» после того, как ребейз на #493 сдвинул gzip-размер до 298 527 Б — почти вплотную к старому потолку 299 000). Проверено самостоятельной пересборкой, а не принято на слово из текста коммита.
  • Трейлеры и changelog. e9fb255a («fix: close radar review gaps», предок обоих новых коммитов раунда, User-Visible: yes) правит оба changelog в себе же — подтверждено git show --stat. Оба новых коммита этого раунда (3f0b4aee, ca25e910) — User-Visible: no, Issue: #485 — корректно: ни один не меняет видимое пользователю поведение.
  • Инварианты геометрии неприменимы: ни пересечённые с ребейзом файлы, ни два новых коммита раунда не касаются рёбер комнат/толщины/layout/ marker.space/open_spans.

Чего не проверял

  • Полный demo/smoke_*.mjs (234 файла) — не гонял. smoke-select вернул 60 прямых совпадений, но это артефакт того же полного диффа origin/dev...HEAD, что видели r2/r3 (символы _markerDialog/ _editorRuntime/_mode/_cfgRev/cellCm — из файлов, которые фактический дифф этого раунда (3f0b4aee/ca25e910) не трогает вовсе: они меняют только test/radar-geometry.test.mjs, tests_backend/test_radar_geometry.py, бандл-артефакты, scripts/bundle-budget.mjs и docs/images/screenshots.json). Два прямых радар-смока (smoke_radar_setup.mjs, smoke_radar_live.mjs) прогнаны и зелёные; остальные 58 — код диалога/навигации, не тронутый этим раундом и уже прогнанный прицельно в r2/r3.
  • Мутационный CI-гейт «Мутанты по диффу» (3 job) — не завершён на момент публикации документа (in_progress). Не жду: ручная мутация конкретно тех строк, что добавил этот раунд (radial_ok/radialOk), выполнена лично и задокументирована в таблице гейтов — это прямая, а не косвенная замена той же роли для нового кода. Если по завершении job «Мутанты по диффу» на этом SHA окажется красным на не рассмотренной мной строке — это отменяет вердикт независимо от прочтения кода.
  • python -m pytest tests_backend -q (полный набор, без HA) — не гонял весь модуль, только test_radar_geometry.py+test_radar_validation.py (57 тестов, прямо относящихся к дельте и к M-1). Полный backend-набор вне HA харнесса r3 уже прогоняла целиком (451 passed) на код, который этот раунд не меняет за пределами двух перечисленных файлов.
  • npm run golden:verify — не прогонял: diff не трогает рендер; r2/r3 уже зафиксировали отсутствие golden-сцен для радара как Low, не растущий третий раунд подряд.
  • Perf-профили — не запускал: diff не касается iso-*/live-*/ render-*/houseplan-render-lifecycle.ts, AC стадии не требуют perf-профиль отдельным доказательством.
  • node scripts/mutation-gate.mjs локально — не запускал; см. пункт про CI-джобы «Мутанты по диффу» выше — сам гейт ещё выполняется в CI на этом SHA.

Итог

0 High, 0 Medium. Единственная Medium-находка r3 (четвёртый числовой гвард solve_two_point/solveRadarTwoPoint без теста) закрыта точечным тестом на обеих сторонах, подтверждена ручной мутацией (гвард снят → новый тест красный на backend и на TS). Ребейз на ушедший дальше dev (влился #493, вырезан кандидат v1.73.0-beta.7) потребовал полного, а не дельта-разбора; пересечение путей с продуктовым кодом (websocket_api.py, houseplan-editor-runtime.ts, houseplan-card.ts и др.) проверено построчно — конфликтов смысла ребейз не внёс, все находки r1/r2 на месте. CI Validate на точном SHA уже подтвердил полный HA-харнес бэкенда (823 passed/2 skipped) — это закрывает главный пробел предыдущего раунда независимым прогоном, а не заявлением автора. Вердикт — зелёный.


Материал раунда

  • Ветка: issue/485-radar-presence, коммит ca25e9108760 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: ca12394f18186c16362c75919099036c3213a5f2
    git log --all --format='%H %T' | grep ca12394f1818
    
  • ТЗ docs/specs/485-radar-presence-stage1.md, блоб 322f040bfcfdba41802caad302da99188ab9d710
    git log --all --find-object=322f040bfcfdba41802caad302da99188ab9d710 -- docs/specs/485-radar-presence-stage1.md
    
  • ТЗ docs/specs/485-radar-presence-stage2.md, блоб b403e73fc26ba0ffb97be4ffa73be067d179880f
    git log --all --find-object=b403e73fc26ba0ffb97be4ffa73be067d179880f -- docs/specs/485-radar-presence-stage2.md
    
  • ТЗ docs/specs/485-radar-presence-stage3.md, блоб c65e28389d762216c47f00249bdb751f3c9d5082
    git log --all --find-object=c65e28389d762216c47f00249bdb751f3c9d5082 -- docs/specs/485-radar-presence-stage3.md
    
  • ТЗ docs/specs/485-radar-presence.md, блоб 3297ed0140265ad86f2072a40cb9bf8097c0674d
    git log --all --find-object=3297ed0140265ad86f2072a40cb9bf8097c0674d -- docs/specs/485-radar-presence.md
    
  • Вердикт конвейера: green · High 0