22 KiB
CODE-REVIEW-132-r2
- Issue: https://github.com/Matysh/houseplan-card/issues/132
- Связанный bug в том же scope: #185 (закрывается тем же кодом, по решению владельца)
- Диапазон:
origin/dev...HEADна веткеissue/132-partition-openings-v2, но предметно этот цикл разбирает только новый коммит с момента r1 —3fe0f8c fix: address partition opening review regressions(16 файлов, +306/−244), поверх уже принятого без изменений9f77e3e(r1 покрыл его целиком). - ТЗ:
docs/specs/132-partition-openings.md, зелёное ревьюdocs/reviews/SPEC-REVIEW-132-r1.md - Предыдущий цикл:
docs/reviews/CODE-REVIEW-132-r1.md— красный, High: 1 (build_space_merge()не ремапилopening.host.id), Medium: 3 (#186, #187, #188, заведены отдельно, не входят в этот фикс) - Роль: ревьюер кода (не автор), этап
S7-code-review - Цикл: r2/4
Скоуп ревью
Коммит 3fe0f8c заявлен как исправление ровно High-1 из r1. Фактически несёт
два независимых изменения:
- Ремап
opening.host.idвbuild_space_merge()(custom_components/houseplan/import_export.py:845-854) — прямое исправление High-1, плюс регрессионный тест вtests_backend/test_ha_import_export.py. - Разделение presentation/structural снапшотов
buildPlanSnapGeometry()вsrc/houseplan-card.ts(новый_planStructuralGeometrySnapshot(),_planSnapGeometrySnapshot()восстанавливает opening-cuts,_wallGraphSourcesпереключён на структурный снапшот) плюс правкиsrc/plan-snap-overlay.ts(только докстринг),docs/CANVAS.md, changelog RU/EN, doc-screenshots.
Второе не было прямо потребовано r1 (High-1 касался только backend), но
устраняет регрессию, которую r1 обнаружил и не смог объяснить (golden-diff
plan-snap-line-gaps-dark, зафиксирован в r1 как «Чего не проверял», не как
находка) — это восстановление поведения _planSnapOpeningCuts(), которое
существовало на origin/dev до правки #132 и было потеряно в 9f77e3e. Не
считаю это расширением скоупа: правка находится в файлах и подсистеме, которые
сам r1 разбирал по AC5/AC6, и не добавляет пользователю ничего вне контракта
из ТЗ §11.
Прочие 11 AC (AC1–AC4, AC6–AC12) не затронуты диапазоном 3fe0f8c — сам файл-состав
диффа (backend id-ремап + presentation/structural split) не пересекается с их
кодовыми путями placement/lifecycle/light/HA-state/compatibility-schema/i18n,
что подтверждено построчным чтением диффа. Повторный разбор по каждому из них
не производился — r1 их разобрал, вердикт по этому циклу ограничен тем, что
изменилось.
Как проверялось
Дешёвые гейты (прогнаны лично в этой сессии, точные команды и результат):
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
зелёный, без вывода |
| Unit | npm test |
870 passed / 0 failed, совпадает с заявленным |
| Build + сверка бандлов | npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js |
сборка ок, обе копии побайтово совпадают, git status --short после сборки пуст (закоммиченный бандл идентичен свежей сборке) |
Смоки, прогнанные локально (по diff — только затронутые этим коммитом поверхности):
| Смок | Результат | Почему выбран |
|---|---|---|
node demo/smoke_partition_openings.mjs |
OK, все 12 полей true |
использует переименованный _planStructuralGeometrySnapshot, регрессия по AC1/AC3/AC5 |
node demo/smoke_room_autoclose.mjs |
OK, все 9 полей true |
openingKeepsStructuralAutoClose: true — #185 не пострадал от разделения снапшотов |
node demo/smoke_plan_snap_overlay.mjs |
OK, все 32 поля true, включая openingGapHasNoLine: true |
прямая проверка presentation-cut; не входил в список из 12 смоков r1, хотя diff трогает именно эту поверхность — восполняю здесь |
Остальные смоки из целевого набора r1 (smoke_opening_preview, smoke_glow,
smoke_inert_openings, smoke_opening_binding, smoke_opening_tunnel_fill,
smoke_wall_junctions, smoke_unified_wall_tool, smoke_isometric_contract,
smoke_openwall, smoke_sun) не перегонялись: 3fe0f8c не меняет код на их
путях (placement/light/HA-binding/junction geometry/iso/#173 regression —
подтверждено чтением диффа), а r1 уже доказал их зелёными на предшествующем
коммите. Не весь набор из 141 — избыточно для 16-файлового фикса.
Backend. В окружении этого ревью нет homeassistant/.venv-backend (то же
ограничение, что у r1). Установил pytest+voluptuous и прогнал чистое
подмножество: python -m pytest tests_backend -q → 139 passed — тот же
результат, что у r1, test_ha_import_export.py (файл с фиксом High-1 и новым
регрессионным тестом) в этом прогоне не участвовал (conftest.py молча
игнорирует test_ha_*.py без homeassistant).
Вместо предположения проверил через реальный CI на этом SHA (Linux,
homeassistant установлен): job backend для 3fe0f8c —
completed / success, лог оканчивается 280 passed in 4.30s, команда
в логе — python -m pytest tests_backend/ -q. Для контраста: тот же job на
предыдущем коммите 9f77e3e (до фикса) — completed / failure. Это прямое
подтверждение, что регрессионный тест test_space_merge_remaps_every_space_owned_id_and_room_link
(теперь с host-фикстурой) действительно запускается на Linux CI и проходит
после фикса, а до фикса — ломался. Не «verified» без команды: команда и результат
процитированы из фактического лога джобы
(https://github.com/Matysh/houseplan-card/actions/runs/32193375736/job/95892362527).
Golden. job golden на 3fe0f8c — completed / success, в логе все
перечисленные сцены passed, включая ранее «different»/нерасследованную в r1
plan-snap-line-gaps-dark — теперь passed. Это лучше, чем 60/62 у r1:
второй компонент фикса (presentation-снапшот) действительно устранил тот
golden-diff, который r1 оставил неразобранным.
Прочитано построчно, не исполнено:
custom_components/houseplan/import_export.py:845-854— ремапopening.host.idчерез тот жеid_map, что и id перегородки; порядок операций корректен: цикл ремапа id (:830-838) выполняется раньше,id_mapуже содержитold_partition_id → new_partition_id, когда цикл надopenings(:849-854) читаетhost.id.src/houseplan-card.ts:6156-6198—_planSnapGeometrySnapshotвосстановилroomCuts: [...openCuts, ...this._planSnapOpeningCuts(space, openCuts)](буквально то же выражение, что было наorigin/devдо9f77e3e, см.git show origin/dev:src/houseplan-card.ts— метод_planSnapOpeningCutsсуществовал там же). Новый_planStructuralGeometrySnapshotиспользуетroomCuts: this._openCuts()— ровно то, что_planSnapGeometrySnapshotвычислял в промежуточном (r1) коде. Все потребители разделены корректно и без остатка:_wallGraphSources(:6900-6901, единственный вызывающий_wallFaceGraph/#185-путь) — на структурный; четыре презентационных сайта (:6213, 6236, 11771, 17598, 17688) — на исходный. Grep по всему файлу подтверждает отсутствие смешанных вызовов.
Находки
High
Нет. High-1 из r1 исправлен корректно (см. «Как проверялось» — фикс, тест и зелёный CI).
Medium (заведена отдельным issue)
Medium-1 → #189 — presentation snap-overlay не режет ось независимой перегородки в месте её собственного проёма
Второй компонент этого коммита восстанавливает presentation-cut для legacy room-wall openings и для composite partition/room-wall случая, но не для основного сценария #132 — проёма на независимой перегородке, не совпадающей ни с какой стеной комнаты.
Почему это не тривиальная догадка, а воспроизведённый дефект. Прочитано
и эмпирически проверено (headless Chromium, demo/serve.mjs, тот же харнесс,
что у смоков): пространство с одной перегородкой {a:[0.25,0.5], b:[0.75,0.5]}
без комнат и дверью host:{kind:'partition', id, t:0.5} даёт
card._planSnapGeometrySnapshot().value.segments один сегмент partition
от [250,500] до [750,500] — без разрыва в точке проёма (x=500). Причина
прослеживается по коду:
_roomWallOpeningInputs()(src/houseplan-card.ts:7834-7855) для partition-hosted проёма возвращает вход в выборку только когдаpartitionOpeningHasCompositeRoomWall(...)истинно — т.е. только для composite-случая (решение по Q4);_planSnapOpeningCuts()(:6141-6154) строит cuts исключительно из этой выборки — для обычного (не composite) partition-hosted проёма cut не создаётся вообще;buildPlanSnapGeometry()(src/plan-snap-overlay.ts:118-156) в любом случае передаёtcuts: []дляkind: 'partition'безусловно (roomCutsприменяется только кkind: 'room',:123-131) — так что даже если бы cut был вычислен, для partition-источника он никуда не попал бы без изменения этой функции.
Почему это противоречит контракту, а не просто пробел evidence.
ТЗ §11 буквально: «Контракт применяется одинаково к legacy room-wall opening
и новому partition-hosted opening» — про presentation/snap boundary cut.
Этот же коммит переписал docs/CANVAS.md («Door, window, gate and
intentionally open-span intervals are cut from presentation axes», без
оговорки про host kind) и оба changelog («the editor's visual snap guide
keeps its physical gap across the opening» / «визуальная направляющая
привязки по-прежнему показывает физический разрыв в месте проёма») —
универсально, без оговорки о composite-случае. Заявленное в документации и
реализованное в коде расходятся именно для главного, а не краевого сценария
#132 (проём на независимой, не совпадающей с комнатой перегородке).
Почему Medium, не High. Не портит сохранённые данные, не ломает
physical/light геометрию (та режется верно — hostBodyHasFullDepthOpeningGap
в smoke_partition_openings.mjs зелёный) и не ломает structural room-face
граф (#185 — _planStructuralGeometrySnapshot намеренно и корректно
игнорирует cuts для обоих host kind, что и требуется). Затрагивает только
inline snap-подсказку инструмента «Стены» в Plan-редакторе (admin-only
поверхность): пользователь может получить снап на точку, физически лежащую
внутри проёма, как если бы там была сплошная кладка — ровно то поведение,
которое контракт «opening gap remains a gap» (#173) должен исключать, только
для нового host kind. Не покрыто ни одним из 12 AC ТЗ буквально (все они
описывают geometry/light/HA/render — не snap-guide), поэтому не проваливает
формальный AC, но нарушает явный текст §11 и текст только что обновлённой
документации/changelog в этом же коммите.
Заведено: #189 (bug, P2, S1-new), со ссылкой на #132 и точной репродукцией.
Low
Нет новых Low в этом цикле. Три Low из SPEC-REVIEW-132-r1 были закрыты до
начала кода (запись автора «Начало реализации»); не пересматривались здесь,
диапазон 3fe0f8c их не касается.
AC — статус после r2
AC1–AC4, AC6–AC8, AC10–AC12: без изменений относительно r1 (пройдены,
диапазон 3fe0f8c их кода не касается — подтверждено чтением diff-состава).
AC5 (#185 room closure): пройден, подтверждено заново для этого коммита
(smoke_room_autoclose.mjs: openingKeepsStructuralAutoClose: true;
smoke_partition_openings.mjs: openingKeepsRoomFaceAxisContinuous: true
через переименованный, но не изменённый по семантике метод). Разделение
снапшотов не меняет вход _wallFaceGraph — структурный снапшот вычисляется
идентично тому, что использовался в r1.
AC9 (compatibility): пройден — это ровно то, что чинил High-1. Backend
export/import/duplicate одного space с partition-hosted openings теперь
сохраняет referential integrity host.id; подтверждено новым регрессионным
кейсом в test_ha_import_export.py и зелёным backend job на точном SHA
3fe0f8c в Linux CI (280 passed, 0 failed).
Ни один из 12 AC не описывает presentation snap-overlay буквально (см. Medium-1/#189) — находка не проваливает формальный AC, но является нарушением §11 ТЗ вне списка AC1–AC12.
Что проверено и корректно
- High-1 из r1 фактически исправлен: код читается корректно (порядок ремапа,
тот же
id_map, что у остальных ссылок), новый regression-тест целенаправленно бьёт по сценарию High-1 (фикстура сhost), Linux CI backend job зелёный на точном SHA с командой и результатом в логе. - Presentation/structural разделение снапшотов реализовано чисто: ни одного
оставшегося смешанного вызова (
_wallGraphSources— единственный потребитель структурного, четыре презентационных сайта — исходного; проверено grep по всему файлу). - Golden CI зелёный полностью (все перечисленные сцены
passed, включая ранее неразобраннуюplan-snap-line-gaps-dark), лучше результата r1. - Три Medium из r1 (#186, #187, #188) корректно остаются отдельными
открытыми issue, не включены и не спрятаны в этом фиксе — проверено
gh issue viewпо каждому: все три открыты,S1-new, ссылаются на #132. - Трейлеры коммита
3fe0f8c:Issue: #132,User-Visible: yes, оба changelog (RU+EN) правлены в этом же коммите — провереноgit show --stat. - Bundle freshness: три копии (
dist/,custom_components/houseplan/frontend/,demo/srv/assets/) побайтово идентичны после чистой пересборки в этой сессии;git status --shortпосле сборки пуст.
Чего не проверял
- Полный набор из 141 браузерного смока — не запускал, diff 16-файлового
фикса не касается большинства поверхностей (обоснование выбора трёх
целевых смоков — выше). CI job
smokeна момент завершения этого документа ещё выполнялся (in_progress) — полный прогон относится к предрелизному гейту, не к гейту этого код-ревью, и не блокирует вердикт. tests_backend/test_ha_import_export.pyне выполнялся мной локально (нетhomeassistantв этом окружении) — заменено проверкой факта и результата прогона на Linux CI на точном SHA (см. «Как проверялось»), а не предположением.performance_smoke— не запускал, diff не касается кеш-инвалидации/hot-path (только backend id-ремап и разделение уже кешируемых снапшотов по тому же шаблону, что был).- Composite (coincident partition + room-wall) presentation-cut сценарий — не
проверял отдельно эмпирически в этом цикле; логика
_roomWallOpeningInputsне менялась в3fe0f8cи была прочитана r1 как корректная для этого случая (см. CODE-REVIEW-132-r1, разбор AC2). Мой репродукшн для #189 намеренно взял не-composite случай, чтобы изолировать дефект. - Точную причину, почему исходный golden-diff
plan-snap-line-gaps-darkиспользовал именно room-wall, а не partition-сценарий (и поэтому не вскрыл #189 в r1/r2 через golden) — не расследовал; вне golden-набора нет сцены с независимой перегородкой и проёмом без совпадения с комнатой, что и объясняет, почему CI не поймал #189.
Вердикт
Зелёный · цикл r2/4 · High: 0 · Medium: 1 → #189
High-1 из r1 исправлен и подтверждён (код + новый тест + зелёный Linux CI backend job на точном SHA). Остальные 11 AC не затронуты этим диапазоном и остаются в состоянии r1. Новая Medium-находка (#189) — presentation snap-overlay не режет ось независимой перегородки в месте собственного проёма, вопреки §11 ТЗ и тексту этого же changelog — не проваливает ни один из 12 сформулированных AC буквально, не портит данные, не затрагивает structural/#185-путь и не расширяется на View/kiosk; заведена отдельным issue и не блокирует переход, как и три предыдущих Medium из r1.