18 KiB
CODE-REVIEW-231-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/231
- ТЗ:
docs/specs/231-decor-layer-order.md(зелёный SPEC-REVIEW r2) - Диапазон:
git log --oneline origin/dev..HEAD→ один коммитb747316«fix: render decor above room fills» (Issue: #231,User-Visible: yes) - Заход: r1 · блокирующих циклов израсходовано 0 из 4
Скоуп
Единственное продуктовое изменение: перенос вызова _renderDecorLayer() в
src/houseplan-card.ts из позиции перед рендером комнат в позицию после
активной hover-заливки комнаты, обоих проходов тоннелей проёмов и Glow-base
комнат — то есть непосредственно перед _renderGlowLayer(), как того требует
§8.1 ТЗ. Сопутствующие изменения: новый browser-smoke
demo/smoke_decor_layer_order.mjs, расширение demo/smoke_glow.mjs
(decorLayer теперь участвует в hoverLayerOrder), новый documented mutant
decor-restored-below-room-fills в scripts/mutation-gate.mjs, две новые
golden-сцены (decor-over-opaque-hover-light, decor-over-glow-base-dark) с
семантическими пиксельными пробами в demo/golden/{matrix,harness,run}.mjs и
юнит-контракт на них в test/golden-matrix.test.mjs, документация
(docs/BACKDROP.md, docs/DECOR-EDITOR.md, docs/TESTING.md, оба
changelog), три синхронные копии бандла и пересчитанный отпечаток
docs/images/screenshots.json.
Как проверялось
Свежий чекаут ветки issue/231-decor-layer-order на b747316, origin/dev
на момент ревью — 6683e52.
Гейты — обязательная часть (прогнаны все)
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
зелёный, без вывода |
| Unit | npm test |
зелёный, 1070/1070 pass (0 skip — на CI runner’е автора один тест process-gate.test.mjs пропускался, окружение без gh; на поведение #231 не влияет) |
| Build + bundle parity | npm run build, затем cmp трёх копий и sha256sum |
зелёный, все три houseplan-card.js (dist, custom_components/houseplan/frontend, demo/srv/assets) байт-в-байт идентичны (baf60c9a…) |
| Docs fingerprint | node scripts/check-docs.mjs |
зелёный: «Documentation checks passed (7 files, 10 external links)» — diff трогает src/**, поэтому гейт обязателен, не пропущен |
Гейты — по необходимости (diff и AC)
| Гейт | Команда | Результат | Почему выбран |
|---|---|---|---|
| Mutation registry (статика) | node scripts/mutation-gate.mjs --check |
зелёный, все id, включая новый | якоря патчей мутанта существуют ровно один раз |
| Mutation run (AC5, доказательство «тест умеет падать») | node scripts/mutation-gate.mjs --id=decor-restored-below-room-fills |
зелёный: «чистый прогон: node demo/smoke_decor_layer_order.mjs» → ok, «decor-restored-below-room-fills: тест покраснел, как обязан» |
AC5 явно требует это доказательство, а не только регистрацию мутанта |
| Целевой smoke (AC1–AC4) | node demo/smoke_decor_layer_order.mjs |
зелёный, все 25 полей true, включая DOM-order и пиксельные пробы hover/tunnel/glow-base |
назван в АС1–АС4 и в плане тестов §15.1 |
| Glow layer order (AC2/AC3, hover×decor×glow) | node demo/smoke_glow.mjs |
зелёный, hoverLayerOrder: true и весь остальной Glow-контракт |
назван в плане тестов §15.1 и напрямую правится в этом diff |
| Opening tunnel fill (AC2, тоннель как продолжение пола) | node demo/smoke_opening_tunnel_fill.mjs |
зелёный | тоннели — предмет нормативной границы §8.1, смок их проверяет отдельно от decor |
| Hide/editor parity (AC4) | node demo/smoke_hide_layers.mjs |
зелёный | явно назван в AC4 |
| Decor editor поведение (AC4/AC6, не regresses) | node demo/smoke_decor.mjs |
зелёный, включая decorRestored, visibleInView, inertInView |
самый прямой существующий смок по объекту правки |
| Backdrop parity (AC4, backdrop ниже decor) | node demo/smoke_backdrop.mjs |
зелёный | явно назван в AC4 и §9 |
| Golden (AC1/AC8, диф может менять видимый результат) | npm run golden:verify |
см. раздел «Находки», M1 | обязателен по правилам гейтов — diff меняет геометрию/порядок слоёв |
| Mutation unit-контракт | node --test test/mutation-gate.test.mjs |
зелёный, 6/6 | регистр мутантов и test-build резолвятся |
Не прогонялось и почему
- Полный
demo/smoke_*.mjs(167 файлов) — не прогонялся.scripts/smoke-select.mjs, на который ссылается инструкция ревью, в репозитории отсутствует (Cannot find module .../scripts/smoke-select.mjs), поэтому автоматической выборки нет; список выше подобран вручную по AC и по diff (Glow, тоннели, hide/editor/backdrop — единственные существующие smokes, которые diff и ТЗ называют напрямую). Остальные 160+ смоков не касаются композиции SVG-слоёв. python -m pytest tests_backend -q— не прогонялся, diff не трогаетcustom_components/**/*.py.- Performance-профили — не прогонялись, AC7 требует только «DOM count, source
review и targeted performance sanity»; смок
smoke_decor_layer_order.mjsуже проверяетoneDecorLayer: true(нет второго.decorlayer), число decor-групп не растёт, что и требуется. Отдельного профиля AC не называет. - Ручного тестирования в браузере не было — не предусмотрено циклом (см. «Гейты», PROCESS §2.7); AC доказаны автотестами и разбором по коду.
Находки
M1 (Medium, в скоупе) — golden regression на трёх существующих large-house сценах не определена и не учтена
Issue #231 (п.4) прямо требует: «Любая сцена с декором изменится. Определить
список и переснять отдельным шагом с доказательством ревью». ТЗ AC8 требует
актуальное «golden evidence». Handoff-комментарий разработчика утверждает:
«Полный npm run golden:verify → ожидаемо красный: две новые сцены имеют
missing-baseline, а текущий dev уже содержит множество ранее накопленных
непринятых diff».
Я прогнал npm run golden:verify на HEAD (b747316) и получил, помимо двух
ожидаемых missing-baseline, ещё три сценария с вердиктом different:
isometric-large-warm-remount-dark, large-house-zoom-250-dark,
large-house-warm-remount-dark.
Проверил заявление о «уже накопленном dev-дрейфе» напрямую: собрал origin/dev
(6683e52) в отдельном git worktree, собрал бандл и прогнал те же три
сценария через node demo/golden/run.mjs --mode=capture --scenario=<id>
(diagnostic-режим, --mode=verify требует полный матрикс и отказывает на
--scenario) — все три passed против того же самого принятого baseline.
То есть регрессия появляется именно между origin/dev и этим коммитом, а не
существовала на dev заранее — причина утверждения в handoff неверна для этих
трёх сценариев.
Причина найдена по коду: фикстура demo/fixtures/large-house.mjs кладёт 500
decor-объектов сеткой (makeDecor, x:0.02–0.98, y:0.018–0.97) поверх
20-комнатной сетки (roomGrid, x:0.04–0.96, y:0.04–0.96) с fill_mode: 'glow'
(large-house.mjs:214) — то есть decor обширно перекрывает Glow-base заливку
комнат. Это ровно тот же эффект, что и цель #231 (decor становится видимым
поверх Glow-base), только на perf-фикстуре, а не на новой синтетической. При
zoom 0.4 (large-house-zoom-040-dark) разница остаётся под порогом
maxDiffRatio и сценарий проходит; при zoom 2.5 и в warm-remount сценариях
рендер крупнее и разница превышает порог — отсюда different.
Сам визуальный эффект — ожидаемое и корректное следствие фикса (Приложение
пруф выше: изменения соответствуют цели задачи, а не побочный дефект
рендера). Дефект не в поведении, а в полноте DoD: список затронутых golden
сцен (issue п.4, ТЗ AC8, docs/TESTING.md) называет только две новые сцены;
три существующие accepted-сцены, тоже реально изменившиеся из-за этого же
коммита, не определены, не занесены ни в docs/TESTING.md, ни в комментарий с
доказательством. Риск: на предрелизном golden:accept -- --reviewed эти три
скриншота будет легко спутать с «уже накопленным неразобранным дрейфом» (как и
произошло в handoff-комментарии) и принять непроверенными либо, наоборот,
несправедливо связать с #231 задним числом без объяснения причины.
Не High: поведенческий AC1–AC7 не нарушен, ничего не ломается для пользователя в этом коммите — golden baseline в этом issue не принимается вообще (по правилу принятия эталонов), сама заливка не тронута. Это пробел в трассируемости релизного артефакта и неточное заявление в handoff, а не неверное продуктовое решение.
Как закрыть в этой же задаче: дополнить список сцен, изменившихся из-за
переноса decor (минимум isometric-large-warm-remount-dark,
large-house-zoom-250-dark, large-house-warm-remount-dark) в
docs/TESTING.md/комментарий к задаче, и заменить формулировку «уже
накопленный dev-дрейф» на подтверждённую причину — пересечение
large-house-decor с Glow-base после переноса слоя, которое требует того же
reviewed-baseline-подтверждения перед бетой, что и две новые сцены.
Что проверено и корректно
- AC1 (decor поверх room fill).
demo/smoke_decor_layer_order.mjs:customFillKeepsDecorPixels,customTunnelKeepsDecorPixels—true; прогон против кода до фикса (мутант) даёт красный, значит проверка не тривиальна. - AC2 (decor поверх hover и пола проёма). DOM-order
hoverBeforeDecor/glowBaseBeforeDecorи пиксельныеhoverDoesNotTintDecorPixels,hoverTunnelKeepsDecorPixels,glowBaseKeepsDecorPixels,glowTunnelKeepsDecorPixels— всеtrue;smoke_glow.mjshoverLayerOrder: trueподтверждает то же на независимой fixture. M1 из SPEC-REVIEW r1 закрыта именно здесь — кодsrc/houseplan-card.ts:16720-16729ставит decor строго после_renderRoomHoverFillи обоих_renderOpeningTunnelFills. - AC3 (верхние слои сохранены).
parity-блок smoke:liveGlowAfterDecor,sunAfterDecor,wallsAfterDecor,openingSymbolsAfterDecor,htmlDevicesAndLabelsAfterDecor— всеtrue; подтверждено чтениемsrc/houseplan-card.ts:16729-16731(decor → Glow → sun rays, стены/символы/устройства дальше по файлу не переставлялись). - AC4 (hide/editor/backdrop parity).
hideDecorStillHidesView,ownEditorOverridesHide,backdropBeforeDecor—true;smoke_hide_layers.mjs,smoke_decor.mjs,smoke_backdrop.mjs— все зелёные без изменений в их собственной логике (diff их не трогает). - AC5 (mutant доказывает тест). Реально прогнан
node scripts/mutation-gate.mjs --id=decor-restored-below-room-fills: чистый прогон зелёный, с мутантом — целевой smoke красный. Не «зарегистрирован», а исполнен. - AC6 (compatibility и surfaces).
renderDoesNotRewriteDecor: true(JSON.stringify(sp.decor) === storedпосле полного цикла рендера);oneDecorLayer: true— второй.decorlayerне создан; статическая карточка и скрытая изометрия в diff не затронуты (изменений в соответствующих renderer'ах нет). - AC7 (performance/security). Diff — это перемещение одного вызова и
одного komментария, новых groups/observers/listeners не добавлено;
oneDecorLayer: trueподтверждает отсутствие дублирования слоя. - Q1 (владелец). Единый порядок без нового флага — подтверждено кодом:
disp.hideDecor && this._mode !== 'decor' ? nothing : this._renderDecorLayer()не получил новых веток/условий по объекту. - Release-артефакты. Оба changelog обновлены в том же коммите (
Issue: #231,User-Visible: yes);docs/BACKDROP.mdиdocs/DECOR-EDITOR.mdописывают новый порядок текстом, совпадающим с фактическим кодом;docs/images/screenshots.json— отпечаток пересчитан,check-docs.mjsзелёный; три копии бандла синхронны. - Golden — новые сцены.
decor-over-opaque-hover-lightиdecor-over-glow-base-darkдаютmissing-baseline, что ожидаемо (baseline не принимается в этом ревью);test/golden-matrix.test.mjsпокрывает их структуру юнитом (fill_mode, hoverRoom/glowEnabled, набор decor kinds, наличиеopening-tunnelпробы) — прогнан в составеnpm test. - Существующие golden-сцены с decor (кроме large-house).
golden-geometryиспользуетfill_mode: 'none'по умолчанию — decor-линияgeo-axis-hвизуально не перекрывалась заливкой ни до, ни после переноса, поэтому 30+ сцен на этом пространстве (geometry-view-*,day-cycle-*,isometric-geometry-view-*,hover-nested-room-darkи др.) осталисьpassed— подтверждено прогономgolden:verify, не только чтением.hover-nested-room-darkдополнительно проверен геометрически: полигонgeo-nested(y: 0.14–0.38) не пересекает decor-линию на y=0.5, поэтому hover-fill и decor не перекрываются пространственно в этом сценарии.
Чего не проверял
- Полный browser-smoke набор (167 файлов) — не прогонялся целиком, только
выбранные вручную из-за отсутствия
scripts/smoke-select.mjsв репозитории (см. «Не прогонялось и почему»). pytest tests_backend— не прогонялся, diff не касается Python.- Performance-профили (
benchmark:large-houseи т.п.) — не прогонялись; AC7 не называет отдельный профиль, аoneDecorLayer/DOM count проверены smoke'ом. - Полный Linux golden-артефакт и его reviewed-принятие — вне цикла код-ревью по правилу принятия эталонов (§13 PROCESS.md); это предрелизный шаг, не код-ревью.
- Ручной просмотр в браузере на реальном HA — не проводился (не предусмотрено циклом).