Issue: #640 User-Visible: no
17 KiB
CODE-REVIEW-640-r1
Issue: #640 · Этап: code · Заход: r1 · Материал: 6a905d9ac87d419d45d668ca821c1a1d2f39d1a1 (ветка issue/640-dishwasher-menu-icon, база origin/dev = 007efaee2854aa8b9b4a063db8e037ecfc5d3f98).
Скоуп
Замена только menu-иконки категории «Посудомоечная машина» в палитре мебели: assets/furniture/houseplan-0.4.1/svg/menu/dishwasher.svg, соответствующий сгенерированный src/furniture-menu-art.generated.ts, синхронизированные копии бандла, обновление golden-сцены furniture-categories-light, точечные unit/golden/smoke свидетели и оба changelog. ТЗ получило зелёный вердикт спека r1 (docs/reviews/SPEC-REVIEW-640-r1.md), High/Medium нет, единственная Low-находка (AC3 называет browser-smoke «unit») не блокирует и правки не требовала.
Полный диф origin/dev...HEAD — 59 файлов: продуктовый SVG + README пакета, генератор-артефакт src/furniture-menu-art.generated.ts, обе копии бандла (class D), два golden PNG + индекс, demo/golden/harness.mjs/matrix.mjs, точечная правка demo/smoke_dialog_config_parity.mjs, unit test/furniture-assets.test.mjs, оба CHANGELOG, docs/images/screenshots.json (только фингерпринт), docs/reviews/INDEX.md и сам документ спек-ревью. Класс A — только src/furniture-menu-art.generated.ts (генерируется штатным скриптом, не редактируется руками).
Как проверялось
Ревью читал диф по коммитам и проверял утверждения кодом и внешними данными, а не текстом хендофа:
- Геометрия происходит из архива, не выдумана. Скачал
dishwasher.zipпо ссылке из тела issue, посчиталsha256sum—1a9b1bdaf490b35812f51e4dd3cb1c425224ec768219c31b979dd6a84e890203, совпадает с ТЗ и README пакета побайтово (без учёта регистра hex). Извлёкdishwasher.svg, сравнил атрибутdс committed-файлом программно (Python, посимвольно) — идентичен, 606 символов с обеих сторон. Отличия только в нормализации: убраныwidth/height/rootfill,stroke="black"→stroke="currentColor", добавленstroke-linejoin="round". Это ровно то, что требует ТЗ и что не разрешает содержательных изменений формы. - Побочных изменений в каталоге нет (AC1). Распарсил сгенерированный
GENERATED_FURNITURE_MENUнаorigin/devи наHEAD(33 записи с обеих сторон) и сравнил объекты поid: изменилась ровно одна запись —dishwasher. Это сильнее, чем unit-тест «остальные 32 не изменились», — прямая проверка сгенерированного рантайм-артефакта. - Плановый контракт не тронут (AC2).
git diff origin/dev...HEAD -- .../pack.json .../svg/plan/dishwasher.svg— пусто. ID, группа, размеры 60×60, названия и plan-SVG отсутствуют в дифе вообще. - Безопасный SVG-контракт (АС1 «нормализовать под контракт»). Прочитал
svgArt()вscripts/generate-furniture-assets.mjs(строки 69–113): жёсткий allow-list тегов (svg,g,path), allow-list атрибутов пути, обязательныеfill=none/stroke=currentColor/stroke-linecap/stroke-linejoin=round, запрет<script>/<style>/DOCTYPE. Генератор — тот же самый гейт, что прогоняетсяnpm run furniture:check; раз сборка прошла (см. ниже), committed-SVG прошёл этот контракт. - Unit-свидетель ловит регресс. Пересчитал
sha256содержимого committeddishwasher.svg(с\r\n→\n) —dd8bceaf329e0c78f9f5c3d7684f69cecc1adbfb6f2cd108306bf70b7c483048, совпадает со значением, зашитым вtest/furniture-assets.test.mjs. Тест не тавтология: старое имя теста «изменяет три плановых рисунка из #606» переименовано и логика расширена — цикл поsvg/menuтеперь явно пропускает толькоdishwasher.svg, остальные 32 сравниваются побайтово со старым пакетом, плюс отдельноassert.notEqualи точныйassert.equalпо SHA нового файла. Компиляция тестов черезtsc -p tsconfig.test.jsonне запускалась мной отдельно (см. «чего не проверял»), но сама логика теста и хеш проверены напрямую. - Правка golden-гейта осмысленна, не подгонка.
demo/golden/harness.mjs: новая проверкаscenario.furnitureCategoryArtтребует<path d>у указанной плитки и что еёgetBoundingClientRect()целиком внутри границ палитры — это именно то, что нужно, чтобы плиткаdishwasherне оказалась обрезанной футером и еёdреально был непустым.demo/golden/matrix.mjs: сценарийfurniture-categories-lightполучилfurnitureCategoryArt: 'dishwasher'и увеличенныйviewport.height(820→1050) с комментарием, почему («ряд техники, включая Dishwasher, должен быть виден целиком») — соответствует формулировке AC1 «ряд техники в палитре». - Golden-приёмка не расширяет скоуп бесконтрольно.
demo/golden/baselines/baselines-index.jsonменяет ровно два хеша сцен:furniture-categories-light(ожидаемо, новая иконка) иroom-discard-dialog-mobile-ru. Проверил происхождение второго: коммит1dba8185(«fix: clarify unsaved-settings confirmation (#610)») уже слит вorigin/devдо база-коммита этой ветки (git merge-base=007efaee, коммит #610 — предок), т.е. дрейф эталона существовал вdevнезависимо от #640 и был подхвачен только потому, что полный прогон матрицы (175 сцен) не даёт принять частично — это соответствует правилу «никогда не принимать эталон частично». Коммит08ff4fbe— отдельный от продуктового измененияchore, называет обе сцены в сообщении и несётRelease/Baseline-Reviewedтрейлеры на артефакт35870750208. Не считаю это находкой вне скоупа: это не правка поведения, а синхронизация записи истины о уже смёрженном коде. - Правка smoke — не маскировка регресса. Прочитал
demo/smoke_dialog_config_parity.mjsцеликом вокруг правки: смок сравниваетJSON.stringify(_serverCfg)после правки через UI и после прямой записи состояния диалога; путь «state» задавал толькоtempMax/northDeg, но неtempMaxInput/northDegInput— сырые поля черновика, добавленные уже слитым #614 («поле сохраняет введённый черновик»). Без этих полей два пути расходились бы в сохранённом конфиге, и после #614 смок должен был покраснеть при следующем полном прогоне; коммит чинит только тестовый путь, продуктовый код диалогов в дифе отсутствует. - Трейлеры и changelog. Каждый нетривиальный коммит несёт
Issue: #640и корректныйUser-Visible;feat(furniture)(User-Visible: yes) в одном коммите правит обаdocs/CHANGELOG.md/.ru.md— провереноgit show --stat. Формулировки changelog корректно называют видимое: «новая иконка категории, размещённые предметы и рисунок на плане не изменились». docs/images/screenshots.json. Правка — толькоsourceFingerprint/sourceSha256(пересчитан из-за сменыsrc/**), ни одинimageSha256не изменился — палитра мебели не входит в документационные скриншоты, штатныйcheck-docsсинхронизирован корректно.- Дешёвые гейты. Validate на точном материале
6a905d9aзелёный (workflow_dispatch35884429047,conclusion: success); проверил черезgh run view— job «Переиспользование: это дерево уже проверено» сработал, потому что единственная разница с ранее полностью провалидированным деревом — коммит документации ревью. Полный прогон с браузерными смоками и golden состоялся раньше, на материале2c9ee641(push run35872104807): все три browser-smoke шарда иgolden(175/175) зелёные; сверил черезgh api repos/.../commits/2c9ee641, что этот коммит правит ровно те же три golden-файла, что и текущий08ff4fbe— то есть полный green run состоялся на содержимом, идентичном нынешнему product-диффу, до косметического ребейза и публикации ревью-документа. Мутанты по диффу зелёные на этом же материале (35873306289, все 6 шардов) и повторно на финальном SHA (35884429047, все 6 шардов). - Выбор смоков по диффу.
node scripts/smoke-select.mjs --base origin/dev --head HEAD→ НЕОПРЕДЕЛЁННОСТЬ: единственный задетый символ проекта,GENERATED_FURNITURE_MENU, не назван ни в одном из 263 смоков поимённо. Решение: не гонять smoke-матрицу заново вручную — она уже прогонялась зелёной на идентичном product-содержимом в run35872104807(см. п.11), а не по имени символа, а по факту прогона всех 3 шардов. Отдельно проверил вручную, чтоdemo/smoke_furniture.mjs(гипотетически «релевантный smoke палитры» из AC3) не завязан на конкретныйdishwasher, а на «sofa» как образец — так что даже точечный перезапуск не добавил бы нового доказательства сверх уже пройденного полного прогона и golden-теста харнесса (п.6), который проверяет именно плиткуdishwasher.
Находки
Нет. High: 0, Medium: 0, Low: 0 (новых). Low-находка спек-ревью r1 (неточная терминология свидетеля в AC3) не требовала правок и не относится к коду.
Что проверено и корректно
- AC1 — геометрия совпадает с архивом, только заявленная нормализация; сгенерированный каталог меняет ровно одну запись; golden-сцена расширена так, чтобы плитка была видима и её path непуст.
- AC2 — plan-SVG, ID, группа/категория, названия, размеры 60×60 не задеты (пустой diff).
- AC3 —
furniture:generate/furniture:check-контракт (allow-list вsvgArt()) соблюдён; менюарт остаётся вlazyEditorFilesбандла (не переехал в initial view); полный Linux Validate (typecheck/unit/build/bundle-sync/smoke×3/golden 175/175/мутанты×6) зелёный на идентичном по продуктовому содержимому материале. - Provenance — SHA-256 архива подтверждён независимой загрузкой и пересчётом, README и unit ссылаются на #640/JustBusiness корректно.
- Трейлеры, оба changelog,
Release/Baseline-Reviewedна golden-коммите — на месте и корректны. - Побочный golden-дрейф (
room-discard-dialog-mobile-ru, #610) прослежен до уже смёрженного, независимого от #640 коммита вdev— не побочный эффект этой ветки, принят той же дисциплиной (полный артефакт, явная запись причины). - Smoke-фикс — точечная правка теста под уже слитый #614, продуктовый код не затронут.
Чего не проверял
- Не запускал
npx tsc --noEmit,npm test,npm run buildсам — Validate на точном SHA6a905d9aзелёный (см. п.11), гейты дешёвые и уже подтверждены явной ссылкой на прогон. - Не запускал
node scripts/check-docs.mjsсам — фингерпринт вdocs/images/screenshots.jsonуже пересчитан в дифе и совпадает с ожидаемым пересчётом после правкиsrc/**; отдельный прогон не добавил бы нового доказательства. - Не гонял
npm run invariants— диф не касается геометрии комнат/стен/layout/marker.space/open_spans, инварианты модели не относятся к этой задаче. - Не перезапускал browser-smoke матрицу и
npm run golden:verifyлокально — оба уже зелёные на Linux CI на содержимом, идентичном текущему продуктовому диффу (run35872104807, п.11);smoke-select.mjsдал НЕОПРЕДЕЛЁННОСТЬ, точечный перезапускdemo/smoke_furniture.mjsне добавил бы нового доказательства сверх golden-харнесса, который проверяет именно плиткуdishwasher(см. п.12). - Не запускал
python -m pytest tests_backend— диф не касаетсяcustom_components/**/*.py. - Не проверял performance-профили — в AC не названы, диф не касается чувствительных к перфу путей.
Вердикт
Зелёный. AC1–AC3 доказаны кодом и внешней сверкой архива, побочных изменений вне скоупа нет (единственный сосед — независимо смёрженный дрейф #610 в golden-приёмке, прослежен и объяснён), гейты подтверждены на точном SHA.
Материал раунда
- Ветка:
issue/640-dishwasher-menu-icon, коммит6a905d9ac87d— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
fc8674b16271690bd68fd016f979a045379f04cegit log --all --format='%H %T' | grep fc8674b16271 - Тело issue:
1336c4649192ea657779f92aeaf1daff7c7f23c2b85316d89d401bcd9f06101f - Вердикт конвейера:
green· High 0