# CODE-REVIEW-532-r1 Вердикт: **жёлтый** · заход r1 · блокирующих циклов израсходовано 0 из 4 · High: 0 · Medium: 2 (в скоупе, возврат автору) Материал: `bb04f8605f9003c0ec6643359fa47af9319247bc` (ветка приведена к `dev` конвейером; `origin/dev..HEAD` содержит один коммит, диплист диффа полный, не по дельте — цикл первый). ## Скоуп Issue #532: тройной `drop-shadow` внешнего контура плана (`.hp-paperg`) живёт в общем композиционном слое с планом, поэтому любая перерисовка плана (ховер, панорама) заново прогоняет три прохода размытия по габариту всего листа — на машине владельца (Firefox) это ~9 к/с и 23 МБ текстур/кадр. Правка: `will-change: filter` на правиле `.stage.daycycle .hp-paperg, .hp-static-stage.daycycle .hp-paperg`, уводящее отфильтрованную бумагу в свой слой. Побочный эффект контракта (К2, принят на спек-ревью r2): промоушен делит SVG на два слоя, из-за чего диагональная штриховка стен антиалиасится иначе — 4 golden-кадра `day-cycle-{dawn,day,dusk,night}-dark` расходятся и подлежат пересъёмке на кандидате беты, а не в этом коммите. Продуктовая рамка: View-режим — продукт для двух из трёх персон (`SCOPE.md`), настенный планшет и телефон читают план именно там; ускорение растеризации дневного цикла закрывает именно этот сценарий, палитру/геометрию/слои окружения/блендинг не трогает (К3, К4 — вне скоупа, подтверждено кодом, см. ниже). Продукт кода вне `plan.styles.ts` не тронут. Спек-ревью прошло два раунда (r1, r2 — оба зелёные); r2 переписал К2/AC3 после того, как автор сам обнаружил на реализации, что «golden останется зелёным» не подтвердилось, и вернул ТЗ на переработку вместо подгонки факта под старый текст. Код-ревью (этот документ) — первый заход, полный разбор. ## Как проверялось Дешёвые гейты (`tsc`, `npm test`, `npm run build` + сверка 3 копий бандла, `check-docs.mjs`) подтверждены зелёным прогоном Validate на этом самом SHA (https://github.com/Matysh/houseplan-card/actions/runs/34622197915) — повторно не гонял, кроме случаев ниже, где перегон был нужен как побочный эффект. Гейты, которые я прогнал сам (а не поверил хендоффу): | Гейт | Команда | Результат | |---|---|---| | Смок-свидетель | `node demo/smoke_daycycle_raster.mjs` (после `npm run bundle:sync`, локальный ratio 0.73 — другая машина, тот же порядок, что у автора 0.76/0.82) | OK, 8/8 | | Мутант | `node scripts/mutation-gate.mjs --id=daycycle-outline-not-promoted` | `тест покраснел, как обязан` — поймано 1 из 1 | | Селектор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | НЕОПРЕДЕЛЁННОСТЬ (0 символов на изменённой строке) — совпадает с заявленным риском 5 | | Полная golden-матрица | `node demo/golden/run.mjs --mode=verify` (169 сценариев) | ровно 4 `different`: `day-cycle-{dawn,day,dusk,night}-dark`; всё остальное `passed`, включая 7 сценариев, которые в хендоффе автора были помечены как «шум песочницы» — здесь (пиновый Chromium) они все зелёные | | Мой собственный замер среднего цвета кадра по всем 4 кадрам (не только `night-dark`, как в хендоффе) | одноразовый скрипт через `createImageBitmap`/`getImageData`, baseline vs `artifacts/golden/actual/*`, удалён после прогона | `dawn` Δ−0.001, `day` Δ−0.014, `dusk` Δ+0.047, `night` Δ+0.080 из 255 — все четыре внутри заявленного допуска «не дальше 0.1» | | Ограничивающий прямоугольник расхождения по всем 4 кадрам | тот же одноразовый прогон | `[120‑121, 36, 867, 757‑758]` — идентичен по всем четырём кадрам и совпадает с рамкой, которую автор привёл для `night-dark`; расхождение не задевает ореол снаружи плана | | Три копии бандла | `diff -q dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` (+ `houseplan-assets.json`) | идентичны | Не прогонял: `pytest tests_backend` (бэкенд не задет), `npm run invariants` (диффа геометрии/модели/`layout`/`marker.space`/`open_spans` нет — правка чисто в CSS), performance-профили Validate (не названы в AC), полный `npm test`/`tsc`/`build` заново как отдельный гейт (уже зелёные на этом SHA по Validate; `npm run build`+`bundle:sync` я всё равно выполнил как побочный эффект, чтобы поднять локальный смок/golden — красных мест не дали). `smoke_glow*`/`smoke_discovery_filters` не гонял: слабое совпадение по имени («свет», «фильтр»), но К3/К4 прямо объявляют слои окружения и блендинг вне скоупа, а код-факт (`svgScreenBlendSupported` не читает `bg_mode`) это подтверждает — смотреть не было необходимости. Рабочая копия оставлена чистой: все временные скрипты и `artifacts/golden/` удалены после использования, `git status` пуст. ## AC · чем доказан · чем краснеет (проверено самостоятельно) | # | AC | Чем доказан | Чем краснеет | Проверено | |---|---|---|---|---| | AC1 | Подсказка `will-change: filter` стоит только при `daynight`, статичный фон — без неё | `demo/smoke_daycycle_raster.mjs`, часть 1 | мутант `daycycle-outline-not-promoted` | сам прогнал: смок зелёный на коде, красный на мутанте | | AC2 | Отношение растеризации на панораме ≤ 2.0 | тот же смок, часть 2 (CDP `RasterTask`) | тот же мутант (без декларации отношение ~15) | сам прогнал: 0.73 на коде, мутант красный | | AC3 | Изменились ровно 4 кадра `day-cycle-*-dark`; расхождение — антиалиасинг штриховки, не изменение вида (ореол цел, средний цвет кадра сдвинут ≤0.1/255) | полная golden-матрица + разбор пикселей | 5-й кадр либо сдвиг среднего цвета | сам прогнал полную матрицу (169 сценариев): ровно 4 different; сам посчитал средний цвет и bbox расхождения для **всех четырёх** кадров (не только `night-dark`, как в хендоффе) — все внутри допуска, bbox идентичен по всем четырём | | AC4 | Статичный фон не платит: ни `filter`, ни `will-change` | тот же смок | декларация без `.daycycle` | сам прогнал вместе с AC1 | | AC5 | `docs/SUN.md` описывает промоушен как часть контракта; оба чейнджлога на месте | построчная сверка ревьюером | расхождение документа и кода | сверил построчно — обе строки чейнджлога и абзац SUN.md на месте, но см. находки ниже | AC3 закрыт по существу мной лично, а не на веру: спек-ревью r2 прямо отметило, что автор экстраполировал порог сдвига цвета с одного разобранного кадра (`night-dark`) на оставшиеся три и делегировало код-ревью обязанность проверить это «на реальном прогоне». В хендоффе автора эта проверка по-прежнему покрывала только `night-dark` количественно (три другие только помечены `different` в таблице). Я прогнал полную матрицу и лично посчитал средний цвет и bbox расхождения для `dawn`/`day`/`dusk`-dark — все три ведут себя идентично `night-dark` (тот же bbox, тот же порядок сдвига цвета), так что риск экстраполяции снят фактом, а не доверием. ## Находки ### Medium-1 (в скоупе) — чейнджлог утверждает то, что опровергает сам контракт задачи `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` (новая запись в `## Unreleased`): > The picture is unchanged, to the pixel ([#532]). > Картинка не изменилась ни на пиксель ([#532]). Это буквально неверно и противоречит К2/AC3 этой же задачи: 3.3–3.5 % пикселей на каждом из четырёх кадров дневного цикла расходятся (максимальное отклонение канала 81 из 255) — именно поэтому эталоны `day-cycle-*-dark` пересматриваются отдельным коммитом на кандидате беты. Сообщение самого коммита формулирует это точнее («the diagonal wall hatch anti-aliases differently and the four day-cycle baselines are re-taken»), а публичный чейнджлог — нет. **Воспроизведение:** сравнить текст `docs/CHANGELOG.md` с любым из четырёх диффов golden-матрицы (`node demo/golden/run.mjs --mode=verify`, `day-cycle-night-dark`: 26 442 / 770 640 пикселей выше `maxChannelDelta: 10`). **Правка:** перефразировать обе строки чейнджлога так, чтобы не утверждать побайтовую идентичность — например, в духе текста коммита («картинка читается так же; антиалиасинг штриховки стен смещается на несколько пикселей, эталоны дневного цикла пересматриваются отдельно, см. #532»). ### Medium-2 (в скоупе) — `docs/SUN.md` приписывает измерение в Chromium браузеру Firefox `docs/SUN.md`, новый абзац (AC5): > …without the hint every repaint of the plan re-ran three blur passes over > the whole sheet, which cost **a Firefox window** about **fifteen times** > the rasterization of a static background (#532). Отношение «пятнадцать раз» — это измерение мутанта/смока через CDP-трассировку в headless **Chromium** (см. хендофф: «без правки… отношение 15.06»; тот же порядок в моём собственном прогоне мутанта). Профиль владельца в Firefox дал качественные цифры (23 МБ текстур/кадр, ~9 к/с, вклад drop-shadow 72–75 % от стоимости растеризации), но никогда не давал коэффициент «×15» — сам документ ТЗ и ревью r1/r2 явно фиксируют, что Gecko не профилировался этим свидетелем («Chromium — не Gecko… приговор по Gecko даёт профиль владельца»). Формулировка SUN.md смешивает два источника числа под одной меткой, что противоречит собственной дисциплине проекта «одно число — один источник». **Воспроизведение:** сверить абзац SUN.md с хендоффом/комментарием аналитики — цифра 15× нигде не связана с Firefox, только с Chromium/CDP. **Правка:** заменить «a Firefox window» на «our Chromium/CI measurement» (или эквивалент), не приписывая коэффициент профилю владельца. ## Что проверено и корректно - Селектор и объявление `will-change: filter` в `src/styles/plan.styles.ts` дословно совпадают с К1 (в т.ч. вторая ветка селектора `.hp-static-stage`); комментарий над правилом даёт числа Chromium/CDP корректно (852 мс / 28 мс / 108 мс) — в отличие от SUN.md, здесь атрибуция источника числа верна. - `.hp-paperg` действительно группа только бумажных силуэтов (`src/houseplan-card.ts:11534`, `_paperShapes(space.rooms)`), не меняется при ховере/панораме — механика из ТЗ подтверждена чтением, не только словом аналитика. - `scripts/mutation-gate.mjs`: патч `daycycle-outline-not-promoted` находит ровно ту строку, которая добавлена в диффе (сверено побайтово через `grep`), мутация подтверждена прогоном. - `demo/golden/matrix.mjs` содержит ровно 4 сценария с `bgMode: 'daynight'` — структурно подтверждает и «К2 живёт только под `.daycycle`», и «ровно четыре кадра» из AC3. - `svgScreenBlendSupported` (`src/glow-blend.ts`) — рантайм-проба, не читающая `bg_mode` — подтверждает К4 (блендинг света вне скоупа) кодом. - `docs/images/screenshots.json`: изменился только `sourceFingerprint` (отпечаток `src/**`), все 11 `imageSha256` не изменились — соответствует заявлению «`npm run docs:accept -- --identical`», документация не поехала. - Три копии бандла (`dist/`, `custom_components/houseplan/frontend/`) побайтово идентичны; сборка воспроизводится (`npm run build` + `bundle:sync` на этом дереве прошли чисто). - Коммит несёт `Issue: #532` и `User-Visible: yes`; обе строки чейнджлога — в том же коммите, что и поведение (не в отдельном). - Продуктовый вопрос автора («приемлемо ли платить пересъёмкой 4 эталонов за скорость») не новый: это ровно К2/AC3, уже разобранные и принятые зелёным спек-ревью r2 с полным раскрытием факта (byte-for-byte ореол, сдвиг среднего цвета, план пересъёмки с `Release:`/`Baseline-Reviewed:`). Код реализует то, что было одобрено; переоткрывать это как продуктовое решение на этапе код-ревью нет оснований — я лишь перепроверил цифру на всех четырёх кадрах вместо одного (см. AC3 выше). - Риск 5 из ТЗ (`smoke-select` не находит связь) подтверждён независимо: тот же вывод «НЕОПРЕДЕЛЁННОСТЬ» на моём прогоне, что и в хендоффе. ## Чего не проверял - `pytest tests_backend` — диффа в `custom_components/**/*.py` нет. - `npm run invariants` — диффа геометрии/модели (рёбра, `layout`, `marker.space`, `open_spans`) нет, только CSS-свойство. - Performance-профили Validate (`performance_smoke` и т.п.) — не названы в AC, диффа в этих путях нет. - Приговор по настоящему Firefox (профиль владельца до/после) — вне возможностей код-ревью; ТЗ явно оставляет это отдельным шагом суждения владельца, а не AC этой задачи. - Полный `smoke_*` матрица (244 файла) — не прогонял целиком; `smoke-select` честно вернул «неопределённость», просмотрел список смоков с похожими именами (`glow*`, `discovery_filters`) и решил не гонять — К3/К4 прямо выводят эти поверхности из скоупа, и код (`svgScreenBlendSupported`) это подтверждает. --- **Материал раунда:** SHA `bb04f8605f9003c0ec6643359fa47af9319247bc`, `origin/dev..HEAD` = 1 коммит, диапазон диффа `origin/dev...HEAD` (53 файла, включая 3 копии бандла и golden-fingerprint документации). Первый заход — раздела «Унаследовано из r0» нет. --- ## Материал раунда - Ветка: `issue/532-daycycle-raster`, коммит `bb04f8605f90` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `959371418fa5d512b6314804fb4d9cf13e86efe4` ``` git log --all --format='%H %T' | grep 959371418fa5 ``` - Тело issue: `12e66588332b94c78100156f01e4832881b8163fb6f8fdca83acfcb8e90f4545` - Вердикт конвейера: `yellow` · High 0