Files
2026-09-28 17:26:30 +00:00

21 KiB
Raw Permalink Blame History

CODE-REVIEW-689-r1

Issue: #689 «Размытие плана после зума и белое мигание при навигации на дне/ночи (день/ночь) — откат #685» Материал: f2b66ad67758aa6ee391b6bb14d1f803fd93eedb (ветка issue/689-daycycle-layers) Заход: r1 · блокирующих циклов израсходовано 0 из 4

Скоуп

Диапазон origin/dev..HEAD (c1e05e46 → f2b66ad6), 4 коммита, 51 файл:

  • 600330e1 fix: .plan-svg в день/ночь получает will-change: opacity вместо transform (не фиксирует растр); .hp-paper-outline-svg полноразмерной карточки — overflow: hidden и метка data-hp-live-overflow="clip"; live-viewport.ts ограничивает экспозицию [data-hp-live-viewbox] инлайн clip-path: inset(-25%), снимаемым вместе с overflow; откат штриховки #685 (linearGradient → единственный <pattern>). User-Visible: yes, оба CHANGELOG в этом же коммите.
  • 87195c2b — тесты/смоуки под новую композицию, Release: v1.78.0-beta.9.
  • 836ef771 — приёмка 29 golden-кадров из Linux-артефакта CI, Baseline-Reviewed.
  • f2b66ad6 — отпечаток скриншотов документации (пиксели не менялись).

Продуктовые файлы: src/styles/plan.styles.ts, src/live-viewport.ts, src/houseplan-card.ts (+12/-12 строк, откат штриховки внутри render()). Трейлеры Issue:/User-Visible: на месте на каждом коммите; Release: появляется с Baseline-Reviewed на golden-коммите, как требует §10.1.

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

Дешёвые гейты — не перегонял, зачтён Validate на этом SHA. Run 36453600665 — success, но это workflow_dispatch с heavy=false: реально исполнились и прошли только «Предполёт» (docs/check-docs/провенанс), «Фронтенд: типы, юниты, мутанты, синхрон бандла» (= tsc --noEmit, npm test, npm run build + bundle-policy --verify) и «Мутанты по диффу» (6/6). Golden/смоки/perf/geometry/backend в этом прогоне skipped, не «переиспользованы» — я проверил это по логам job «Переиспользование: это дерево уже проверено»: smoke/golden там нашли кэш-маркер именно с source_run=36440159452 (тот же SHA), а performance_smoke — «Cache not found» (в 36440159452 перф красный, маркер успеха не пишется), и сам job performance_smoke не запустился вовсе, потому что needs.changes.outputs.heavy в этом workflow_dispatch оказался false — это НЕ доказательство перфа, только подтверждение tsc/test/build/mutants-by-diff. Авторитетное golden+smoke evidence для этого SHA — прогон 36440159452 (красный только перф, остальное зелёное).

Гейты, прогнанные мной лично на этом SHA:

Гейт Команда Результат
Node-юниты по диффу node --test test/daycycle-layers.test.mjs test/live-viewport.test.mjs test/paper-scene-contract.test.mjs test/core-file-budget.test.mjs 24/24 pass; core-file-budget: houseplan-card.ts 12890 строк ≤ потолка 12891
Мутанты (AC7), каждый лично применён и погашен через mutation-gate.mjs --id= daycycle-scene-transform-hint-back, daycycle-outline-overflow-visible, live-exposure-unbounded, live-exposure-opens-clipped-scene, hatch-stroke-not-scaled, hatch-zoom-compensation-back 6/6 — «заявленный тест покраснел на мутанте»
Смоки, названные в АС/ТЗ (после npm run build && bundle-sync) smoke_daycycle_zoom_layers, smoke_daycycle_layer_budget, smoke_static_zoom_sharpness, smoke_wall_hatch_density, smoke_daycycle_raster, smoke_live_pan_coverage 6/6 OK, все внутренние проверки true
npm run golden:verify (полный набор, не только сдвинутые) — 188/188 passed, 0 failed
node scripts/smoke-select.mjs --base origin/dev --head HEAD — 0 прямых/зарегистрированных совпадений, 26 «слабых» по _zoom (см. таблицу выше — все явно названные в ТЗ прогнаны), НЕОПРЕДЕЛЁННОСТЬ по HATCH_BASE_STEP_UNITS, LIVE_SCENE_EXPOSURE_CLIP, SCENE_OVERFLOW_ATTRIBUTE и др. — все они прямо покрыты новым test/daycycle-layers.test.mjs/test/live-viewport.test.mjs и смоками из строки выше
git diff полноразмерных <pattern>/linearGradient по всему дереву grep -rn linearGradient src demo test ни одного linearGradient#hp-wall-hatch не осталось (полный откат #685 подтверждён)
Docs-фингерпринт скриншотов сверка imageSha256 в docs/images/screenshots.json построчно не изменился ни один из 11, только sourceFingerprint/sourceSha256 — «pixels unchanged» подтверждено

Perf (AC6) — не зелёный на точном SHA, разобрано отдельно ниже.

Чего не проверял: python -m pytest tests_backend (диф не касается custom_components/**/*.py), npm run invariants (геометрия стен/проёмов как данные не тронута — тронут только способ растеризации и композиция слоёв), полный npm test (3210+, отдельной командой) — положился на зелёный job «Фронтенд» на этом SHA (36440159452 и повторно 36453600665), ручную визуальную приёмку 29 golden-кадров глазами — доверился Baseline-Reviewed трейлеру и Linux-артефакту CI, как предписывает процесс для этого гейта. HA Companion и «встроенный браузер» реального устройства не проверял — недоступно в среде ревью; для AC1/AC4 это оставлено как визуальное свидетельство владельца (сам автор пометил их так же: «НЕ сделано»).

По контракту и AC

  • AC1 (нет зафиксированного растра). test/daycycle-layers.test.mjs, smoke_daycycle_zoom_layers, smoke_daycycle_raster, smoke_daycycle_layer_budget — везде will-change: opacity после осевшего кадра, will-change: transform нигде не остаётся. Прочитан код: .stage.daycycle.hp-safe-daycycle-outline .plan-svg (plan.styles.ts:122) — единственное место, где раньше стоял transform. Владелец подтвердил в реальном Chrome (issue-комментарий), что пробный CSS даёт чёткий кадр; полная бандл-версия владельцем ещё не перепроверена (сам автор фиксирует это как «не сделано») — доказательство контракта в CI/юнитах полное, финальная визуальная приёмка вне headless остаётся открытой по объективной причине (сам продукт задачи).
  • AC2 (бюджет слоёв на 800%). smoke_daycycle_zoom_layers (CDP LayerTree, DPR 2, день/ночь, тёмная заливка, колесо+кнопки+pinch+перезагрузка+навигация) — прогнан лично: gestureLayersWithinBudget (≤1.5× сцены), settledLayersWithinBudget (≤1× сцены + 240px), noWhiteFrames (screencast, порог 0.25 доли почти-белых пикселей) — все true. Пороги смока строже ТЗ (1.5×/1× против 2.5×/1.5×).
  • AC3 (экспозиция ограничена и снимается). test/live-viewport.test.mjs — два новых теста: инлайн overflow: visible + clip-path: inset(-25%) во время проекции, оба свойства уходят при терминальном кадре (idle-DOM совпадает с состоянием до жеста), сцена с data-hp-live-overflow="clip" никогда не экспонируется. Прогнано лично, 2/2 pass; оба мутанта, которые снимают эту защиту (live-exposure-unbounded, live-exposure-opens-clipped-scene), лично применены и погашены смоком/юнитом.
  • AC4 (нет белых кадров). smoke_daycycle_zoom_layers.noWhiteFrames — 0 почти-белых кадров в центральной выборке после перезагрузки на 800% и навигации колесом. Владелец подтвердил в реальном Chrome 152 (issue). Как и AC1, финальная бандл-проверка владельцем на полном билде — открытый пункт по признанию автора, не находка ревью (headless объективно не видит симптом, это прямо оговорено в ТЗ).
  • AC5 (откат #685). smoke_wall_hatch_density (10/10) и smoke_static_zoom_sharpness (все режимы) проверяют единственный <pattern id="hp-wall-hatch">; grep по всему дереву не находит linearGradient#hp-wall-hatch. golden:verify 188/188, включая 21 junction-*/large-house-zoom-0{40,250} кадр, вернувшийся к виду до 139dceaa, и 4 static-hatch-openings-* с восстановленным паттерном. 4 кадра stairs-* из #688 приняты тем же коммитом с явной атрибуцией и ссылкой на #690 — не скрытое поглощение чужого дифа, а зафиксированный долг по рекомендации самого #690.
  • AC7 (защитные мутанты). 6/6 лично погашены (таблица выше); реестр браузерных гвардов остаётся на потолке 200/200 (одна запись удалена, одна добавлена — grep -c подтверждает).
  • К6 (idle-DOM). smoke_live_pan_coverage обновлён: экспонированная сцена открывается только в рамках clip-path, помеченная clip сцена не открывается никогда, снятие включает и clipPath. День/ночь golden не сдвинулись нигде, кроме мест, которые меняет К1/К3/К5 по самому ТЗ.
  • Не-скоуп соблюдён. .hp-static-stage (пространственная статичная карточка, space-card.ts/space-render.ts) — отдельный компонент с собственными стилями; правило .stage .hp-paper-outline-svg { overflow: hidden } на неё не действует (разная специфичность/разный корневой класс, не пересекаются) — прочитано и подтверждено, .hp-paperg-композиция (нетронутая карточка) не менялась.

AC6 (перформанс) — разобрано отдельно, вердикт ревьюера: не блокирует

Точный SHA #689 на авторитетном Linux CI-прогоне (36440159452) красный ровно на одной метрике из 51:

❌ timing.firstStableRenderMs.median   3002.4 / 3000   (+2.4 мс, +0.08 %)

hardMaxMs в evaluate.mjs — абсолютный потолок (Math.min(hardMaxMs, regressionLimit)), не смягчается noiseAllowanceMs; формально AC6 не доказан зелёным прогоном на этом SHA. Автор сам не прогнал его повторно (последующий «зелёный» Validate 36453600665 не покрывает perf — см. «Как проверялось» выше) и явно передал вывод ревьюеру («вывод о бюджете firstStableRenderMs после #691 — за ревьюером»).

Я разобрал вопрос по коду и данным, а не принял решение владельца/автора на слово:

  1. Профиль не исполняет код #689. large-house-interaction-v1 не включает день/ночь — .stage.daycycle...-правило и .hp-paper-outline-svg-клип не участвуют вовсе (элемент не рендерится без dayCycle). Единственная всегда-активная часть диффа — экспозиция clip-path в setLayerProjection — исполняется во время panSeriesMs/cameraSeriesMs; оба прошли с большим запасом (433.2/500, 199.9/500) — не похоже, что механизм экспозиции заметно давит именно на firstStableRenderMs (окно, которое гейтов жестов не включает).
  2. #691, после которого впервые измерен объединённый dev, докс-онли — не мог изменить рантайм-код. Значит превышение — свойство текущего dev HEAD, впервые измеренное на этом прогоне: performance_smoke у #691 самого не запускался ни разу (heavy=false во всех 4 прогонах его ветки).
  3. Локальный A/B, прогнанный лично (одна и та же песочница, --samples=3 --warmups=1, диагностика, не гейт-evidence): dev @ c1e05e46 — 2936.0 / 2926.8 / 2961.1; ветка @ f2b66ad6 — 2961.0 / 2939.4 / 2962.0. Разница между ветками (~25 мс) того же порядка, что разброс сэмплов внутри одного dev (~34 мс) — на трёх сэмплах отличить регрессию от шума нельзя.

Вывод: превышение бюджета не приписывается диффу #689 — правдоподобнее, что large-house-interaction-v1/firstStableRenderMs уже стоит на пределе hardMaxMs у текущего dev и это впервые проявилось именно на этом рибейзе. Это Medium-находка вне скоупа #689 (соседний, общий perf-бюджет, а не поведение задачи) — заведена отдельно: #692 «Перф-гейт: large-house-interaction-v1 firstStableRenderMs на пределе hardMaxMs=3000 независимо от #689», с полным разбором и предложенным планом (авторитетный 7-сэмпловый прогон dev HEAD, решение — расширять бюджет или чинить деградацию).

Число, видимое дважды

HATCH_BASE_STEP_UNITS = 8 — используется и в wall-thickness.ts (шаг штриховки на эталонной клетке), и в новом stripe = 2 * (hatchStep / HATCH_BASE_STEP_UNITS) (houseplan-card.ts). Один источник — константа импортирована, не задублирована числом; проверено алгебраически: на cell_cm=5 (hatchStep=8) stripe = 2, на cell_cm=25 (hatchStep=1.6) stripe = 0.4 — совпадает с historical stroke-width и с smoke_wall_hatch_density (referenceStrokeIsTwo, coarseGridScalesTheStroke).

Что проверено и корректно

  • Откат #685 полный и чистый: ни одного linearGradient#hp-wall-hatch не осталось нигде в дереве; штриховка регрессионно идентична допо-#685 состоянию (golden 21/21 кадр вернулся к виду до 139dceaa).
  • Композиция слоёв день/ночь и статичной полноразмерной карточки читается и исполняется согласованно с ТЗ К1–К4, К6, К7; не-скоуп (.hp-static-stage, .hp-paperg) не задет — прочитано и подтверждено отдельно.
  • Все 6 заявленных мутантов лично применены к рабочей копии и погашены соответствующим гейтом — не принято на слово из хендоффа.
  • Golden (188/188) и все явно названные в ТЗ/АС смоки лично прогнаны и зелёные.
  • Трейлеры, changelog (оба языка, в одном коммите с User-Visible: yes), канонические документы (SUN.md, ARCHITECTURE.md, WALL-THICKNESS.md) — обновлены точно там, где меняется контракт, лишнего/недостающего не найдено.
  • core-file-budget потолок houseplan-card.ts не поднят (12890 ≤ 12891) — решение «инлайн вместо метода» не увеличивает монолит, при этом сохраняет откат #685 буквально (атрибуты паттерна, запасной цвет).
  • Golden-долг #688 (stairs-*) явно назван в коммите, не поглощён молча — соответствует собственной рекомендации #690 п.3.

Находки

Medium (вне скоупа #689) — заведён отдельный issue, не блокирует.

  • large-house-interaction-v1/firstStableRenderMs красный на точном SHA (+0.08 % над hardMaxMs); по анализу выше — свойство dev, не этой задачи → #692.

High: 0. Medium в скоупе: 0. Low: 0.

Итог

AC1–AC5, AC7 доказаны исполнением (юниты, смоки, golden, мутанты — лично прогнаны, не приняты на веру из хендоффа); AC1/AC4 сохраняют объективно открытым только финальный шаг «владелец смотрит на полный бандл в реальном браузере/Companion» — то же ограничение, что и в самой задаче (headless не видит симптом), не находка ревью. AC6 красный на точном SHA, но по независимому разбору кода и диагностическому A/B — не является следствием диффа #689; вынесен отдельным issue #692 согласно правилу «Medium вне скоупа». Задача решает заявленный сценарий (резкость после зума, отсутствие мигания, откат #685) без регрессии в touch-контракте, бюджете монолита или golden-корпусе.

Вердикт: зелёный.


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

  • Ветка: issue/689-daycycle-layers, коммит f2b66ad67758 — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: a14a6fb6b30afe13f9cf7ad1b11b343be3e8a0c6
    git log --all --format='%H %T' | grep a14a6fb6b30a
    
  • Тело issue: 57b2345003d4b8f80b2c00dcc327b4ae2832e3de6524d1ac404eb5a32d6c84ab
  • Вердикт конвейера: green · High 0