19 KiB
CODE-REVIEW-579-r1
Issue: #579 — «WebView pinch compositor churn»
Материал: 216990e469f2d123a81c03e99cdcb315ed756be0 (диапазон origin/dev..HEAD, один продуктовый коммит 216990e4 + служебный 0cbc7aff документ ревью ТЗ)
Заход: r1 (первый код-ревью; ТЗ ревьюилось отдельно и получило зелёный вердикт, см. docs/reviews/SPEC-REVIEW-579-r1.md)
Блокирующих циклов израсходовано: 0 из 4
Скоуп
Задача убирает churn промотирования/демотирования сцены ([data-hp-live-viewbox="camera"|"floor"])
во время активного pinch/pan: раньше каждый budget-refresh (100 мс / 15% сдвига) и любой
полный Lit-кадр во время активного жеста переписывали viewBox и одновременно снимали
transform/transform-origin/will-change/temporary overflow, а следующий промежуточный
кадр создавал слой заново. Это совпадает с полевым дефектом HA Companion WebView (мигание/
белые кадры стен и пола при pinch).
Изменённые файлы класса A: src/live-viewport.ts, src/live-interaction-runtime.ts.
Классы B/C/D — тесты, документация и синхронизированный бандл, все в том же коммите.
Геометрия, жесты, i18n, конфигурация не затронуты (совпадает с заявленным не-скоупом ТЗ).
Как проверялось
Полный разбор (не по дельте): это первый код-ревью, отдельного r0 не было.
Дешёвые гейты подтверждены зелёным Validate на этом SHA (run 34887568637 /
34887188635 — оба на 216990e4, typecheck+unit+build+bundle-sync+no-new-any и
diff-мутанты все success); heavy-джобы (smoke, golden, backend, hacs,
hassfest, geometry_parity, performance_smoke) там ожидаемо skipped — обычный push,
не relaese-кандидат. Дополнительно к зачёту CI я лично прогнал на этом дереве:
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck+unit+build+bundle-sync | подтверждено Validate (см. выше) | зелёный, не перегонял |
no-new-any |
node scripts/no-new-any.mjs --base origin/dev --head HEAD |
новых any нет (31 строка в 2 файлах) |
check-docs |
node scripts/check-docs.mjs |
7 файлов, 12 внешних ссылок — зелёный |
unit npm test |
npm test |
2707 pass / 0 fail / 1 skip (сошлось с хендоффом) |
unit #579 |
node --test --test-name-pattern="#579" test/live-viewport.test.mjs |
1/1 зелёный |
смок, выбранный smoke-select |
node scripts/smoke-select.mjs --base origin/dev --head HEAD → 1 «зарегистрированная связь»: demo/smoke_live_pan_coverage.mjs |
см. ниже |
demo/smoke_live_pan_coverage.mjs |
прогнал напрямую | OK, все именованные проверки true, включая новые stablePromotion* |
demo/smoke_editor_gestures.mjs |
AC8 | OK, все проверки true |
demo/smoke_smooth_zoom.mjs |
AC8 | OK |
demo/smoke_tap_ctx.mjs |
AC8 | OK |
demo/smoke_long_press_gesture.mjs |
AC8 | ok:true |
demo/smoke_isometric_live_touch.mjs |
второй consumer live-viewport | OK, все проверки true |
npm run continuity:screencast |
AC3, единственный оракул на представленные (не screenshot) кадры | OK — capturedPinchPresentedFrames: true, noEmptyOrBlackPresentedFrame: true |
npm run golden:verify |
диф трогает live-viewport рендер-путь | все сценарии passed, осевших кадров byte-diff нет |
Мутант live-pinch-compositor-demoted-on-budget-refresh |
node scripts/mutation-gate.mjs --id=live-pinch-compositor-demoted-on-budget-refresh |
«заявленный тест покраснел на мутанте», поймано 1/1 |
Мутант live-pinch-compositor-demoted-on-active-lit-commit |
node scripts/mutation-gate.mjs --id=live-pinch-compositor-demoted-on-active-lit-commit |
«заявленный тест покраснел на мутанте», поймано 1/1 |
Не прогонял:
- Полный набор
demo/smoke_*.mjs(249 штук) — задача не задевает всё дерево,smoke-selectвернул одну зарегистрированную связь плюс явно названные в AC8 смоки; этого требует диф. python -m pytest tests_backend— диф не касаетсяcustom_components/**/*.py.npm run model-invariants— геометрия и ссылки на неё не менялись.- Performance-профиль как гейт (пороговое сравнение с базовым SHA) — по ТЗ и PROCESS §8 это
предрелizный, а не ревью-гейт (AC9 сам требует «профиль перед бетой», не перед ревью); я
прогнал
npm run benchmark:large-house-interactionвручную как дымовую проверку (скрипт отработал, разумные числа, без падений), но это не заменяет сравнение с бюджетом — честно фиксирую как непройденный гейт, а не как «проверено». - Физический прогон на HA Companion Android (AC10) — недоступен в этом окружении; по ТЗ и
по прежнему прецеденту (
CODE-REVIEW-544-r1) это полевая приёмка перед бетой, не перед код-ревью, и автор честно отметил её как «НЕ сделано» в хендоффе.
AC → доказательство · чем краснеет
| AC | Доказано | Чем краснеет |
|---|---|---|
| AC1 стабильная promotion-сессия | unit test/live-viewport.test.mjs (#579 a live session keeps one promoted scene layer...), я прогнал |
мутант live-pinch-compositor-demoted-on-budget-refresh — красный, поймано 1/1 (я прогнал) |
| AC2 бюджет сохранён | тот же unit-тест: root.camera.writes === viewBoxWritesBeforeRefresh + 1 через порог/15%; browser smoke stablePromotionKeepsViewBoxBudget (я прогнал, true) |
naive возврат к записи viewBox на каждый кадр красит этот assert — проверено чтением теста, отдельного именованного мутанта на этот AC нет (Low, см. ниже) |
| AC3 сцена не пропадает в представленных кадрах | npm run continuity:screencast, я прогнал: noEmptyOrBlackPresentedFrame:true, capturedPinchPresentedFrames:true |
заложенный в скрипт forbidden-фильтр по всей серии кадров, включая pinch-участок; отдельного мутанта нет — оракул сам по себе экспериментальный (пиксельный) |
| AC4 эффекты не отключаются (day cycle/Glow/room fills/hatching/фон) | не доказано отдельным тестом — см. находку M1 | — |
| AC5 слои синхронны ≤1px | demo/smoke_live_pan_coverage.mjs (isoMouseRightEdgeMarkerParity и др., pre-existing, не тронуто #579), я прогнал — зелёный |
— |
| AC6 terminal cleanup | unit-тест (settled removes all styles) + smoke stablePromotionCleansAtTerminal, я прогнал |
мутант live-pinch-compositor-demoted-on-active-lit-commit — красный, поймано 1/1 (я прогнал) |
| AC7 существующее покрытие края не потеряно | demo/smoke_live_pan_coverage.mjs coverage-проверки, я прогнал — зелёный |
существующий (до #579) mutant этого смока входит в diff-мутанты CI (6/6 success) — не прогонял лично, доверяю CI |
| AC8 нет регрессии управления | smoke_editor_gestures, smoke_smooth_zoom, smoke_tap_ctx, smoke_long_press_gesture, я прогнал все — зелёные |
— (не защитный AC в терминах §2.7, обычное сравнение) |
| AC9 производительность | не прогнан как гейт (см. «Не прогонял») | предрелизный, по ТЗ и PROCESS §8 |
| AC10 полевой Companion-контроль | явно не сделано, задокументировано в хендоффе | приёмка перед бетой, не перед код-ревью |
Находки
Medium (в скоупе задачи — жёлтый вердикт, чинится в этом issue)
M1 — AC4 (матрица day cycle/Glow/room fills/hatching/фон) не имеет тестового доказательства.
ТЗ (раздел «Критерии приёмки», AC4) прямо требует: «Матрица day cycle off/on, Glow off/on,
room fills, штриховка и пользовательский фон остаётся видимой во всех sampled кадрах.
Доказательство: браузерный smoke на контрастной fixture». Диф трогает три тестовых файла:
test/live-viewport.test.mjs, demo/smoke_live_pan_coverage.mjs, demo/screencast_visual_continuity.mjs.
Ни один не включает Glow (glow_enabled/glow_radius_cm) или day cycle в используемой fixture —
проверено чтением: дефолтный fixture demo/srv/demo.html (const CFG = {... settings: {} ...})
не задаёт ни то, ни другое, а обе смоки не трогают card._serverCfg.settings вообще (grep по
обоим файлам на glow/day_cycle — пусто). Единственная fixture-правка — уже существовавшая
до #579 «coverage-room» с fill_color, не новая для этой задачи.
Воспроизведение находки (не дефект продукта — пробел evidence): grep -n "day_cycle\|glow" demo/smoke_live_pan_coverage.mjs demo/screencast_visual_continuity.mjs — 0 совпадений.
Смягчающее обстоятельство, найденное чтением src/houseplan-card.ts: room fills, Glow
(_renderGlowBaseRooms/_renderGlowLayer, строки ~11769–11777) и hatching рендерятся внутри
того же корневого <svg data-hp-live-viewbox="floor"> (открывается на :11593), что и стены/пол —
то есть тот же самый механизм compositor-стабильности, который AC1 доказывает для стен,
структурно распространяется и на них: нет отдельного код-пути, который мог бы демотировать
Glow независимо от пола. renderDayCycleEnvironment (:11583), напротив, рендерится ДО этого
SVG как отдельный элемент без data-hp-live-viewbox — он вообще не участвует в
исправляемом lifecycle, поэтому риск для дневного цикла из этого бага структурно ниже, чем
для Glow. Это разобрано чтением, а не исполнением, и не заменяет обещанный в ТЗ смок.
Остаточный риск нельзя закрыть только чтением: Glow использует SVG-фильтры/blur-пулы, а
именно комбинации GPU-фильтров с компоновкой слоёв — типичный источник WebView-специфичных
артефактов (сам issue ссылается на это как на общий класс проблем). Обещанный оракул
(«sampled кадры» во время удержанного pinch с Glow/day-cycle включённым) технически
достижим тем же приёмом, что уже применён в runStablePromotion/screencast_visual_continuity —
включить glow_enabled/day-cycle в используемую fixture и подтвердить отсутствие потери
пикселей в held-кадрах, как уже делается для coverage-room.
Без блокирующих (High) находок это жёлтый вердикт: правка (добавить Glow/day-cycle в существующую fixture смоков и подтвердить сохранение видимости в held-кадрах, либо явно и обоснованно снять эту часть AC4 через ревью) остаётся в этом же issue, отдельный issue не заводится (#202).
Low
L1 — AC2 не имеет именованного мутанта. У AC2 («бюджет сохранён») нет специального
MUTANT_DEFINITIONS-контрпримера — доказательство ограничено прямым assert'ом в unit-тесте
(root.camera.writes === viewBoxWritesBeforeRefresh + 1), которого я прочитал и прогнал; он
действительно упал бы при регрессии к побюджетной записи, но без явной мутации это не
формализовано как «чем краснеет» по правилам §2.7. AC2 не новый в этой задаче (природа budget
refresh — из #531), находка образовательная, не блокирует: снимаю с записью, отдельной правки
не требую.
Что проверено и корректно
- Единственная точка входа
commitHouseplanViewport(host, keepSceneLayer)имеет ровно один вызывающий (LiveRuntime.commit()→this.active()), никаких других колл-сайтов с устаревшей сигнатурой не осталось (grepподтверждает). this.active()покрывает оба независимых источника continuous-режима: реальные pointer- контакты (_pointers.size, drag-состояния) и анимированные камера-переходы (_cameraTransition.active, которым идут wheel-zoom через_startCameraTransitionи toolbar-zoom) — прочитано_onWheel/_stepZoom, оба используют camera-transition, а не прямую мутацию_zoom/_viewв обход контроллера, поэтому «session properties, не user- agent» (контракт п.9) выполняется единообразно для мыши, touch и программной камеры._stagePointerUp/_stagePointerCancel(не тронуты диффом, но задействованы контрактом п.4) гарантированно вызываютrequestUpdate()при обнулении_pointers, что даёт терминальный Lit-кадр и, следовательно,commit(..., false)после отпускания.- HTML-маркеры (
[data-hp-live-layer="camera"]) не получилиkeepSceneLayer-исключения — их путь (черезpainted/current, а не через якорь сцены) не изменился, что и объясняет, почему AC5 (парity ≤1px) остался зелёным без единой правки в этой части. - Дедупликация записи стилей (сравнение по свойству, а не строкой transform целиком) строго
сильнее старой версии: старый код мог оставить
transformбезtransformOrigin/will-changeв вырожденном случае (раннийreturn), новый — нет; это не регрессия, а попутное усиление. - Изометрия использует общий
sceneCamera/sceneFloorна всеdata-hp-live-viewbox="camera"элементы, но все они делят один и тот жеview(проверено чтением шаблона:11588-11843) — риск «одна матрица на разные проекции», названный в ТЗ, не реализуется, и это поведение не менялось диффом. docs/ARCHITECTURE.md,docs/TESTING.md,docs/TOUCH-SUPPORT.mdобновлены точно и согласованно с кодом;docs/images/screenshots.json— только новыйsourceFingerprint, байты PNG (imageSha256) не изменились (путьdocs:accept --identical). Оба changelog (docs/CHANGELOG.md,docs/CHANGELOG.ru.md) в том же коммите, трейлерыIssue: #579/User-Visible: yesна месте, ветка и коммит — один продуктовый коммит.- «Одно число — один источник»: диф не добавляет и не меняет ни одной пользовательски видимой величины (zoom-бейдж не тронут) — неприменимо.
Вердикт
Единственная находка — Medium в скоупе задачи (AC4 без прямого теста), блокирующих (High) находок нет. Это жёлтый вердикт: возврат автору на добавление evidence для AC4 (или обоснованное сужение этой части AC ревьюером/владельцем), остальное подтверждено чтением и исполнением перечисленных гейтов.
Материал раунда
- Ветка:
issue/579-webview-pinch-compositor, коммит216990e469f2— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
09aab067582821f39940f59986820362cf079666git log --all --format='%H %T' | grep 09aab0675828 - Тело issue:
4545e4010a0a0d13588d41c73912f543b83f5091653de15127390b77630498a0 - Вердикт конвейера:
yellow· High 0