21 KiB
CODE-REVIEW-689-r1
Issue: #689 «Размытие плана после зума и белое мигание при навигации на дне/ночи (день/ночь) — откат #685»
Материал: f2b66ad67758aa6ee391b6bb14d1f803fd93eedb (ветка issue/689-daycycle-layers)
Заход: r1 · блокирующих циклов израсходовано 0 из 4
Скоуп
Диапазон origin/dev..HEAD (c1e05e46 → f2b66ad6), 4 коммита, 51 файл:
600330e1fix:.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:verify188/188, включая 21junction-*/large-house-zoom-0{40,250}кадр, вернувшийся к виду до139dceaa, и 4static-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 — за
ревьюером»).
Я разобрал вопрос по коду и данным, а не принял решение владельца/автора на слово:
- Профиль не исполняет код #689.
large-house-interaction-v1не включает день/ночь —.stage.daycycle...-правило и.hp-paper-outline-svg-клип не участвуют вовсе (элемент не рендерится безdayCycle). Единственная всегда-активная часть диффа — экспозицияclip-pathвsetLayerProjection— исполняется во времяpanSeriesMs/cameraSeriesMs; оба прошли с большим запасом (433.2/500, 199.9/500) — не похоже, что механизм экспозиции заметно давит именно наfirstStableRenderMs(окно, которое гейтов жестов не включает). - #691, после которого впервые измерен объединённый
dev, докс-онли — не мог изменить рантайм-код. Значит превышение — свойство текущегоdevHEAD, впервые измеренное на этом прогоне:performance_smokeу #691 самого не запускался ни разу (heavy=falseво всех 4 прогонах его ветки). - Локальный 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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
a14a6fb6b30afe13f9cf7ad1b11b343be3e8a0c6git log --all --format='%H %T' | grep a14a6fb6b30a - Тело issue:
57b2345003d4b8f80b2c00dcc327b4ae2832e3de6524d1ac404eb5a32d6c84ab - Вердикт конвейера:
green· High 0