# CODE-REVIEW-597-r1 Issue: #597 · Этап: код-ревью · Заход: r1 · блокирующих циклов израсходовано 0/2 (лёгкий трек, лимит 2) Материал: `2b89f699618a3fa3e9bafc07dfa71e07a8e19960` (рабочая копия), диапазон `git diff origin/dev...HEAD` Ветка приведена конвейером к актуальному `dev` до ревью (`45dac1c0` → `2b89f699`, +2 коммита `dev` сверху) — разбор проведён полностью (§7.2), не по дельте. ## Скоуп Шаг 2 эпика #591 (шаг 1 — #594, в `dev`). Генератор общего набора контролов (`src/styles/form-kit.styles.ts`) разрезан на пять именованных фрагментов (`formKitCardsCss`, `formKitFocusCss`, `formKitDisabledCss`, `formKitSwitchRowCss`, `formKitSwitchCaptionCss`); редактор боковой панели (`src/summary-panel-editor-style.ts`) подставляет каждый на своё место вместо собственной копии тех же правил. Ни одна строка `src/summary-panel-editor.ts` (разметка) не меняется. Изменение задумано как «ноль видимых изменений» — К1–К5 ТЗ. Диапазон диффа (без сгенерированного класса D): `src/styles/form-kit.styles.ts`, `src/summary-panel-editor-style.ts`, `scripts/mutation-registry.mjs`, `test/form-kit.test.mjs`, три новые фикстуры в `test/fixtures/`. Класс A задет — полный флоу обязателен и пройден (ТЗ в теле issue, ревью ТЗ зелёное на r2). ## Как проверялось Дешёвые гейты (`typecheck`, `npm test`, `npm run build` со сверкой бандла) на этом SHA уже подтверждены зелёным Validate (https://github.com/Matysh/houseplan-card/actions/runs/35446978510) — не перегонялись. Вместо этого бюджет раунда потрачен на чтение кода и на гейты, которые Validate в этом прогоне **не покрывает**: в нём `smoke`, `golden` и `performance_smoke` стоят `skipped` (обычный push в задачную ветку, не кандидат беты и не PR — эти джобы включаются только на `heavy`). | Гейт | Прогнан | Результат | |---|---|---| | `npx tsc --noEmit`, `npm test`, `npm run build` + сверка 3 копий бандла | нет — подтверждено зелёным Validate на этом SHA | success (ссылка выше) | | `npm run bundle:sync` (пересборка + сверка дерева) | да, лично | `dist/houseplan-card.js` побайтово совпал с закоммиченным после чистой пересборки — `git status` пуст | | `npm run bundle:budget` (AC7) | да, лично | initial View 290843/291400, lazy editor 222359/222900, lazy furniture art 16942/17900 — совпало цифра в цифру с хендоффом | | `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | да | «Новых any нет» | | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | да | одна зарегистрированная связь — `smoke_summary_panel_polish.mjs` ← `summaryPanelEditorCss`; остальных совпадений нет (диф — 2 файла, 9 символов на изменённых строках) | | `node demo/smoke_summary_panel.mjs` (AC4) | да, лично | `OK` | | `node demo/smoke_summary_panel_polish.mjs` (AC4) | да, лично | `OK` | | `node demo/smoke_room_settings.mjs` (AC5) | да, лично | `OK` | | `npm run golden:verify` (AC6) | да, лично (в песочнице есть Chromium — у автора не было) | 80/80 сцен `passed`, exit 0 — расхождений ноль | | `node scripts/check-docs.mjs` (обязателен — diff трогает `src/**`) | да, лично | **ERROR: отпечаток скриншотов документации устарел** — см. находку M1 | | `npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test test/form-kit.test.mjs` | да, лично | 5/5 тестов зелёные | | Мутант `form-kit-fragment-drifts-from-panel` (padding 14px→13px) | да, лично, дважды (с восстановлением) | оба теста `#597` краснеют: `AssertionError` на побайтовом сравнении | | Проба M1 ревью ТЗ (литерал фрагмента карточки-группы в блочном комментарии рядом с листом панели) | да, лично | тест 1 краснеет ровно заявленным текстом: `карточка-группа: правило осталось литералом...`; после снятия — снова зелёный | | Проба на стык фрагментов диалогов (лишний `\n` в `sharedCss`) | да, лично | тест 2 (`#597 лист диалогов карточки не изменился ни на байт`) краснеет | | Независимая пересборка фикстур с кода `origin/dev` (не доверяя хендоффу) | да, лично | панель: 14585 байт, побайтово совпало с `test/fixtures/summary-panel-editor.css`; диалоги без переключателя — 1885 байт, с ним — 2399 байт, оба побайтово совпали со своими фикстурами | | `python -m pytest tests_backend -q` | нет | диф не касается `custom_components/**/*.py` | | Perf-профили | нет | не названы в AC, диф не трогает чувствительные пути (`src/iso-*`, `src/live-*`, `src/render-*`) | | `npm run docs:accept -- --identical` (диагностика находки M1) | да, лично (изменения отменены, не коммитились) | «Все 11 кадров попиксельно совпали с закоммиченными: принят только отпечаток исходников» | Не гонял полный набор смоков (250 в матрице) — задача узкая (2 файла CSS, один потребитель), `smoke-select` называет одну связь, остальные не относятся к диффу; прогон всей матрицы — предрелизная обязанность, не гейт ревью. ## Находки ### M1 (Medium, в скоупе задачи) — устаревший обязательный гейт `check-docs` не закрыт `node scripts/check-docs.mjs` на материале ревью красит: ``` ERROR screenshot source fingerprint is stale; run npm run docs:capture and accept before the beta candidate (#479) ``` PROCESS.md §8 называет этот гейт безусловно обязательным при любом диффе по `src/**`: «отпечаток скриншотов документации считается по всему `src/**`, поэтому любая правка фронтенда делает его устаревшим... выборка «по diff и AC» здесь не работает — diff всегда попадает, и решать нечего». Диф задачи меняет `src/styles/form-kit.styles.ts` и `src/summary-panel-editor-style.ts` — значит гейт применим и обязан быть закрыт в рамках задачи, а не оставлен следующей. Это не гипотетический риск: ровно тот же паттерн уже стоил продукту инцидента — «скриншоты не пересняли в #230 и #234, и `dev` стоял с красным job `docs`, пока это не нашли при следующей задаче (#237)» (PROCESS.md §8). Непосредственный предшественник этой ветки — коммит `fc4d55c7 docs: отпечаток скриншотов после правки комментария рантайма (#593)` — обновил отпечаток сразу после последней правки `src/**` перед этой задачей (`0565c7ad`, #593); других правок `src/**` между `fc4d55c7` и материалом ревью нет, кроме самой задачи #597. То есть именно этот коммит впервые за долгое время сделал отпечаток снова устаревшим и не закрыл это следующим шагом, хотя предыдущая задача сделала это ровно так. Проверил, что правка дешёвая и не открывает новый спор с ТЗ: прогнал `npm run docs:accept -- --identical` — «Все 11 кадров попиксельно совпали с закоммиченными: принят только отпечаток исходников» (0 расхождений, как и обещает К1/AC1 задачи — вывод CSS не изменился ни на пиксель). Изменения не коммитил (ревьюер не правит материал); откатил рабочую копию. Раздел ТЗ «Release-артефакты» («Документация не меняется») отвечает только за прозу (`docs/*.md`, `USER-GUIDE.ru.md`) и не называет отпечаток скриншотов отдельно — в этом смысле находка и к ТЗ (пропущенный release-артефакт), и к реализации (гейт не прогнан и не закрыт до хендоффа). Ни AC1–AC8, ни хендофф не упоминают `check-docs`, хотя PROCESS.md называет его обязательным именно по факту диффа `src/**`, независимо от AC. **Чем краснеет:** сам гейт — `node scripts/check-docs.mjs` без флагов (умолчание `--screenshots=strict`) на материале ревью. В обычном пуше в задачную ветку CI джоб «Предполёт» использует `--screenshots=warn` и поэтому зелёный на этом SHA — это маскирует находку в Validate, но не отменяет требование PROCESS.md §8 и не меняет исход на кандидате беты, где режим строгий. **Правка:** `npm run docs:accept -- --identical` и коммит обновлённого `docs/images/screenshots.json` (только отпечаток, без изменения PNG) в этой же задаче — ровно то, что сделал непосредственный предшественник (`fc4d55c7`). Находка в скоупе задачи (диф сам её и вызвал) — по §2.7/§7.2 чинится в текущем issue, отдельный issue не заводится. ## Что проверено и корректно - **К1/AC1 (ноль видимых изменений в листе панели).** Независимо от хендоффа пересобрал `formKitCss`-composition на коде `origin/dev` (через временный откат `src/styles/form-kit.styles.ts` к версии `dev` и компиляцию) и сравнил результат с закоммиченной фикстурой: 14585 байт, побайтовое совпадение. Тест `test/form-kit.test.mjs` подтверждает это же на актуальном коде. - **К2/AC2 (одна копия).** Прочитал `src/summary-panel-editor-style.ts` — ни один из пяти литералов `PANEL_FRAGMENTS` не встречается в файле, только вызовы `formKit*Css(SUMMARY_PANEL_FORM_KIT)`. Мутационная проба (литерал карточки-группы в блочном комментарии рядом с листом) краснит тест ровно словами хендоффа; без правки M1 прежняя проверка (`min-height: 54px`) её бы пропустила — это подтверждено чтением истории обеих находок ревью ТЗ r1/r2. - **К3/AC3 (генератор не меняет смысла листа диалогов).** Пересобрал `formKitCss(CARD_DIALOG_FORM_KIT)` в обеих формах на коде `origin/dev` и сравнил с новыми фикстурами: без переключателя 1885 байт, с ним 2399 — побайтовое совпадение в обоих случаях, независимо от заявленного в хендоффе. Проба с лишним переносом строки на стыке фрагментов (`sharedCss`) краснит именно этот тест — свидетель реален, а не декларативен. - **К4 (граф загрузки).** Ни `formKitCardsCss`/`formKitFocusCss`/... не экспортированы вовне лениво загружаемых модулей за пределы прежних; `git diff --stat` подтверждает изменения только в тех же двух файлах, что и раньше несли этот CSS. `npm run bundle:budget` — initial View / lazy editor / lazy furniture art в пределах прежних потолков, цифры совпали с хендоффом. - **К5 (разметка панели не тронута).** `src/summary-panel-editor.ts` вне диффа (`git diff --name-only -- src/`). - **AC4/AC5 (смоки панели и диалога комнаты).** Прогнал все три смока лично: `smoke_summary_panel.mjs`, `smoke_summary_panel_polish.mjs`, `smoke_room_settings.mjs` — все `OK`. - **AC6 (эталоны golden).** Автор не прогонял (в песочнице нет Chromium) — в этой среде Chromium доступен, прогнал сам: 80/80 сцен `passed`, exit 0. Расхождений нет ни в одной сцене, включая те, что рендерят диалоги на общем наборе контролов (диалог комнаты и т.п.). - **AC7 (бюджет бандла).** Числа совпали цифра в цифру с хендоффом. - **AC8 (полный набор).** `npm test` и `npm run gate:small` подтверждены зелёным Validate на этом SHA; отдельно прогнал целевой файл `test/form-kit.test.mjs` (5/5) и `no-new-any` (чисто). - **Мутант `form-kit-fragment-drifts-from-panel`.** Зарегистрирован в `scripts/mutation-registry.mjs`, гард — сам целевой тест. Применил мутацию (`padding: 14px` → `13px`) дважды независимо — оба раза оба теста `#597` падают с `AssertionError`; без мутации — зелёные. Мутант реален, не декоративен. - **Провенанс коммита.** Один коммит, трейлеры `Issue: #597` и `User-Visible: no` на месте; `User-Visible: no` не требует changelog, диф не трогает пользовательский текст ни в одном из двух changelog-файлов. - **i18n.** Диф не касается i18n-файлов — согласуется с ТЗ («i18n: нет»). - **Классы риска §2.6.** async/данные и права/геометрия — неприменимо (чистый CSS-рефакторинг без сети, без геометрии). Визуал — главный риск, закрыт двойным свидетелем: побайтовым текстовым сравнением (сильнее кадра, ловит перестановку правил, которую пиксель не отличит) и личным прогоном `golden:verify` на полной матрице. Объём/perf — AC7 подтверждён. Host/input — разметка и обработчики не менялись (К5), поведение то же. - **Факт из ТЗ, использованный как опора доказательства («у панели нет golden-кадра»).** Проверил `demo/golden/matrix.mjs` — сцены с `dialog: 'summary'` действительно нет. Утверждение не является догадкой. ## Чего не проверял - Полный набор из 250 браузерных смоков — не оправдано диффом (`smoke-select` называет одну прямую связь, диф — 2 CSS-файла с нулевым изменением собранного вывода для панели и диалогов карточки). - `python -m pytest tests_backend` — диф не касается Python. - Performance-профили — не названы в AC, диф не в чувствительных путях (`src/iso-*`, `src/live-*`, `src/render-*`, `houseplan-card.ts`). - Полный HA-харнесс (`fcntl`, недоступен вне Linux+HA) — не относится к CSS. - `process-gate.mjs --issues` — не имеет значения для решения ревьюера, статус и лейблы issue уже видны напрямую через `gh`. - `npm run docs:accept -- --identical` — прогнал только для диагностики находки M1 (подтвердить, что правка дешёвая), результат не коммитил: правку делает автор в задаче. ## Вердикт Жёлтый. Единственная находка (M1, Medium) — в скоупе задачи, вызвана самим диффом и чинится одной командой плюс коммитом отпечатка; отдельный issue не заводится. Все восемь AC доказаны свидетелями, которые я лично проверил на умение краснеть (мутант + две пробы совпали с заявленными в хендоффе текстами ошибок); AC6 закрыт мной вместо автора, у которого не было среды. Технический план задачи (разрезание генератора на фрагменты, подстановка вместо редизайна) корректен и полностью соответствует контракту К1–К5. --- ## Материал раунда - Ветка: `issue/597-panel-on-form-kit`, коммит `2b89f699618a` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `94639a89f4617d5498d0a2c63027db0fa2dab139` ``` git log --all --format='%H %T' | grep 94639a89f461 ``` - Тело issue: `8a56815128285d56f2c2f9f63eb6d8ae211525202f38e8bdd75159aa7c55412c` - Вердикт конвейера: `yellow` · High 0