Issue: #582 User-Visible: no
17 KiB
CODE-REVIEW-582-r3
Issue: #582 — HA Companion: на плане с масштабом 1 см/точку при pinch появляются фиксированные белые области.
Материал: 54dc836deec3318e2eac2349f17945b0e6738adb (рабочая копия была на нём).
Заход r3 · блокирующих циклов ревью 2 из 4 израсходовано.
Скоуп разбора
Это третий заход, и красный вердикт r2 был получен предметно на материале
226f3230073afd79a1eef9e2e5979684c9ae628f7 (docs: review document for #582 /
a0a108cf — публикация документа r2 — легла поверх него как чистый commit
класса C, дельту кода не меняла). Автор не ребейзился на ушедший вперёд dev
и не менял контракт поведения — правка адресует ровно две находки r2 в той же
архитектуре (hp-safe-daycycle-outline), поэтому разбор ведётся по дельте:
git diff 226f3230..54dc836d
54 файла, из них продуктовый код (класс A) — src/houseplan-card.ts,
src/space-card.ts, src/space-render.ts, src/styles/plan.styles.ts;
гейты/тесты (класс B) — demo/smoke_bg_color.mjs,
demo/smoke_daycycle_layer_budget.mjs, demo/smoke_smooth_zoom.mjs,
scripts/mutation-registry.mjs, scripts/smoke-links.mjs,
test/paper-scene-contract.test.mjs; документация (класс C) —
docs/ARCHITECTURE.md, docs/SUN.md, docs/TESTING.md,
docs/images/screenshots.json (только fingerprint); остальное — сгенерированный
бандл (класс D, dist/**, custom_components/houseplan/frontend/**).
Геометрия комнат/рёбер/толщины стен не затронута — npm run invariants не
требуется.
Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
High: контракт п.6 не выполнен — .hp-static-stage.daycycle .hp-paperg не тронута дельтой r1→r2, статическая houseplan-space-card сохраняла координатно-большой will-change: filter, симметрии с полной карточкой не было. |
renderSpaceStatic теперь с первого кадра рендерит отдельный <svg class="hp-paper-outline-svg" ... pointer-events="none"> через общий renderPaperShapes (тот же helper, что и у hp-static-plan-svg); .hp-static-stage.daycycle .hp-paperg получила явные filter: none; will-change: auto; transition: none, а фильтр/will-change: filter переехал на .hp-static-stage.daycycle .hp-paper-outline-svg. |
src/space-render.ts:902-906, src/styles/plan.styles.ts:93-95,103-108, src/space-card.ts:960-963. Лично прогнан node demo/smoke_daycycle_layer_budget.mjs: staticCardUsesSeparateFilteredOutline, staticCardPaperStaysUnfiltered, staticCardHasOneOutlineLayer, staticCardOutlineLayerIsStageBounded, staticCardHasNo4096ContentLayer — все true, staticOversized: []. Мутант daycycle-static-outline-promoted-on-inner-paper (возвращает will-change: filter на .hp-static-stage.daycycle .hp-paperg) лично прогнан через node scripts/mutation-gate.mjs --id=daycycle-static-outline-promoted-on-inner-paper — заявленный guard (smoke_daycycle_layer_budget.mjs) на чистом коде зелёный, на мутанте красный (1/1 поймано). |
Medium (в скоупе): программные переходы камеры без прямого pointer-жеста — double-tap-to-fit, «Вписать всё», клик по комнате, колесо/кнопки zoom — идут через _startCameraTransition, которая нигде не вызывала _activateSafeDayCycleOutline(); планшет-киоск на одних кнопках/double-tap весь сеанс оставался в исходной топологии риска. |
_startCameraTransition() теперь вызывает this._activateSafeDayCycleOutline() первой строкой, до this._cameraTransition.start(...) — единственная точка входа для room, wheel, button, fit/home/double-tap (все они вызывают именно _startCameraTransition, прямой pinch/pan по-прежнему активирует тот же флаг из своих исходных точек на pointer-событиях). |
src/houseplan-card.ts:1273-1287 (сам вызов), вызовы _startCameraTransition на houseplan-card.ts:6381 (room), 6739 (wheel), 6756 (button), 6769 (resetZoom: fit/home/double-tap). Мутант daycycle-programmatic-camera-skips-safe-outline (удаляет строку вызова) лично прогнан через node scripts/mutation-gate.mjs --id=daycycle-programmatic-camera-skips-safe-outline — guard smoke_smooth_zoom.mjs зелёный на чистом коде, красный на мутанте (1/1 поймано). Лично прогнан сам смок: programmaticCameraActivatesSafeDayCycleOutline: true (кейс — нажатие кнопки zoom, _stepZoom, идущее через _startCameraTransition). |
Оба High/Medium из r2 были в скоупе задачи и почищены в той же ветке без нового issue — ровно так, как требует правило §12/решение 2026-08-19.
Унаследовано из r2 (и раньше)
Без повторной проверки принято то, что дельта 226f3230..54dc836d не задевает
и что уже было предметно закрыто в предыдущих раундах:
- AC1–AC3 (экранно-ограниченный слой контура основной карточки, мутант
на внутреннюю
.hp-paperg) — закрыты в r1, документdocs/reviews/CODE-REVIEW-582-r1.md, подтверждены на SHA красного r1 (8e225582→ дальше не переоткрывались); архитектураhp-safe-daycycle-outlineкак отдельный<svg>-сосед, устраняющая implicit-overlap слойplan-svg, — закрыта в r2, документdocs/reviews/CODE-REVIEW-582-r2.mdна SHA226f3230. Дельта r2→r3 не трогает.stage.daycycle(основную интерактивную карточку) в части этого механизма, только добавляет вызов активации в новой точке входа. - Item 5 контракта (один внешний контур без внутренних швов, L-формы не
превращаются в прямоугольник) — логика объединения paper-силуэтов
(
renderPaperShapes, вычислениеpaperShapes) не изменена дельтой r2→r3. - AC9 (полевая приёмка на реальном HA Companion) — вне автоматических ворот по определению ТЗ, ответственность владельца в следующей бете.
Переподтверждено самостоятельно, а не просто унаследовано, хотя дельта их прямо не трогает — потому что относятся к тому же визуальному контракту, который теперь исполняется на объединённом (главная + статическая карточка) коде:
- AC4 (свидетель #532,
smoke_daycycle_raster.mjs) — лично прогнан:ratio: 1.55при потолке2(pairedRatios: [1.53, 1.59, 1.55]),staticMedianMs: 138.4,dayCycleMedianMs: 211.2. Совпадает с числом, заявленным автором. - AC6 (4 golden-сцены day-cycle) — лично прогнан полный
npm run golden:verify(политикаdemo/golden/policy.mjsзапрещает частичный--scenarioв режиме verify, только полная матрица): 172/172passed, включаяday-cycle-dawn-dark,day-cycle-day-dark,day-cycle-dusk-dark,day-cycle-night-dark.git statusпосле прогона чист — новых/изменённых файлов вdemo/golden/baselines/**нет.
Что проверено и корректно
- Дисциплина «тест умеет падать» применена к обоим новым мутантам
(
daycycle-static-outline-promoted-on-inner-paper,daycycle-programmatic-camera-skips-safe-outline) лично, не на слово автора: оба поймали свой мутант 1/1 черезnode scripts/mutation-gate.mjs --id=.... - Структурный тест
test/paper-scene-contract.test.mjsобновлён содержательно, а не ослаблен: староеassert.doesNotMatch(staticRender, /class="hp-paper-outline-svg"/)(r1/r2-архитектура — статика без контура) заменено наassert.match(...)того же паттерна плюс новая проверка, что_startCameraTransitionсодержит вызов_activateSafeDayCycleOutline()перед_cameraTransition.start. Лично прогнан:node --test test/paper-scene-contract.test.mjs— 2/2ok. - CSS не конфликтует:
.hp-static-stageне несёt класс.stage(src/space-render.ts:898), поэтому общий селектор.stage.daycycle .hp-papergстатическую карточку не затрагивает — правилоfilter: noneдля.hp-static-stage.daycycle .hp-papergне соревнуется по специфичности с чужим правилом, оно просто в другом поддереве..hp-static-stage > .hp-paper-outline-svg { z-index: 0; overflow: visible; }вsrc/space-card.tsдублирует (не переопределяет иначе) общее правило.hp-paper-outline-svgизplan.styles.tsтеми же значениями. - Общий helper (AC7): и полная карточка (
houseplan-card.ts:11598-11603, 11626), и статическая (space-render.ts:902-906,915) строят контур и видимую бумагу через один и тот жеrenderPaperShapes(paperShapes, ...)— не два похожих, а один код. - Целевые смоки по дельте — выбраны
node scripts/smoke-select.mjs --base 226f3230 --head 54dc836d(категория «зарегистрированная связь», символrenderPaperShapes, 5 смоков). Все 5 лично прогнаны и зелёные:smoke_daycycle_layer_budget.mjs,smoke_smooth_zoom.mjs(programmaticCameraActivatesSafeDayCycleOutline: true),smoke_bg_color.mjs(staticCardLayersStayOrdered: true),smoke_daycycle_raster.mjs,smoke_live_pan_coverage.mjs. - Typecheck/build/bundle — лично прогнаны (не только по ссылке на
Validate):
npm run build(tsc --noEmit && rollup) без ошибок,node scripts/bundle-sync.mjsразложил три копии бандла,git statusпосле этого чист (собранное байт-в-байт совпадает с закоммиченным на этом SHA).npm run bundle:budget:initial View: 291143 B gzipпри потолке291700 ±2000— в бюджете (совпадает с числом автора291162, разница — шум сборки). Предупреждение о низком запасе (headroom 9923 Б < 15000) — предсуществующий долг (#367/#474/#499), этой задачей не создан и не увеличен заметно. node scripts/check-docs.mjs— лично прогнан,Documentation checks passed (7 files, 12 external links); отпечатокdocs/images/screenshots.jsonв дельте обновлён (sourceFingerprintпересчитан), что и требуется при любой правкеsrc/**.- Документация (
ARCHITECTURE.md,SUN.md,TESTING.md) в дельте описывает именно новую архитектуру — «static space card uses the same stage-sized day-cycle outline from its first frame», а не старое «has no camera gesture … keeps its historical inner outline», которое было источником находки r2. Текст согласован с кодом, а не переписан частично. - Трейлеры/changelog:
54dc836dнесётIssue: #582,User-Visible: no— корректно, т.к. запись в оба changelog уже сделана вa68e4c6b(первый user-visible фикс задачи) и описывает финальное видимое поведение («outline appearance and static backgrounds are unchanged»); эта дельта не добавляет нового видимого поведения, только достраивает механизм под капотом для двух путей, которые r2 поймал незакрытыми.
Чего не проверял и почему
- Полный
npm test(2714+ юнитов) отдельно не перегонялся — Validate на точном SHA54dc836dзелёный (https://github.com/Matysh/houseplan-card/actions/runs/34959723812), включаетnpm testцеликом; из затронутого дельтой юнит-теста прогнан адресно (test/paper-scene-contract.test.mjs, 2/2). npx tsc --noEmitотдельно не гонялся — покрыт тем же Validate-прогоном и тем жеnpm run build, который выполнялся лично.- Полный
npm run golden:verify— вопреки общему правилу «дорогие гейты не перегонять, если уже зелёные на SHA», был лично прогнан целиком, т.к. политика верификации (demo/golden/policy.mjs) не допускает частичный прогон в режимеverify, а диф правки трогает рендер. 172/172passed. - Полная матрица
demo/smoke_*.mjs(250 файлов) не прогонялась — вне скоупа делты, прогон всех уместен только когда задача трогает всё; выбор ограничен инструментомsmoke-select.mjs(5 файлов, все прогнаны). npm run invariants— не запускался: геометрия комнат/рёбер/толщины стен/layout/marker.space/open_spansдельтой не затронута.python -m pytest tests_backend— не запускался: дельта не трогаетcustom_components/**/*.py.- Perf-профили вне #532-свидетеля (
npm run benchmark:*) — не названы в AC и не затронуты дельтой, не гонялись. - AC9 (полевая приёмка в HA Companion) — по определению вне автоматических ворот код-ревью, остаётся владельцу в следующей бете.
- «Одно число — один источник» — задача не добавляет и не меняет ни одной видимой пользователю величины (числа, подписи, площади); проверка неприменима.
Находки
Находок нет. Оба блокирующих High/Medium из r2 закрыты предметно в скоупе задачи, подтверждены личным прогоном тестов и мутантов (не на слово автора), делта не вносит новых регрессий в переподтверждённые AC4/AC6.
Вердикт
Зелёный. High: 0. Medium: 0.
Материал раунда
- Ветка:
issue/582-webview-large-filter-layers, коммит54dc836deec3— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
767e57c30645bb92213f4cc67e4179bdca734646git log --all --format='%H %T' | grep 767e57c30645 - Тело issue:
4c1e1ecfe8ce205f3cfb3c44eb6028a35d0bb4358323e05ba60c6fd85b3b40ba - Вердикт конвейера:
green· High 0