Files
houseplan-card/docs/reviews/CODE-REVIEW-640-r1.md
T
claude[bot] 284644acc8
Проверка (CI) / Классификация изменённых файлов (push) Successful in 39s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 1m9s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 25s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 1m0s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 41s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 42s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 57s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 8m11s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 19s
docs: review document for #640
Issue: #640
User-Visible: no
2026-09-23 16:03:39 +00:00

68 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` (генерируется штатным скриптом, не редактируется руками).
## Как проверялось
Ревью читал диф по коммитам и проверял утверждения кодом и внешними данными, а не текстом хендофа:
1. **Геометрия происходит из архива, не выдумана.** Скачал `dishwasher.zip` по ссылке из тела issue, посчитал `sha256sum` — `1a9b1bdaf490b35812f51e4dd3cb1c425224ec768219c31b979dd6a84e890203`, совпадает с ТЗ и README пакета побайтово (без учёта регистра hex). Извлёк `dishwasher.svg`, сравнил атрибут `d` с committed-файлом программно (Python, посимвольно) — **идентичен**, 606 символов с обеих сторон. Отличия только в нормализации: убраны `width`/`height`/root `fill`, `stroke="black"` → `stroke="currentColor"`, добавлен `stroke-linejoin="round"`. Это ровно то, что требует ТЗ и что не разрешает содержательных изменений формы.
2. **Побочных изменений в каталоге нет (AC1).** Распарсил сгенерированный `GENERATED_FURNITURE_MENU` на `origin/dev` и на `HEAD` (33 записи с обеих сторон) и сравнил объекты по `id`: изменилась ровно одна запись — `dishwasher`. Это сильнее, чем unit-тест «остальные 32 не изменились», — прямая проверка сгенерированного рантайм-артефакта.
3. **Плановый контракт не тронут (AC2).** `git diff origin/dev...HEAD -- .../pack.json .../svg/plan/dishwasher.svg` — пусто. ID, группа, размеры 60×60, названия и plan-SVG отсутствуют в дифе вообще.
4. **Безопасный 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 прошёл этот контракт.
5. **Unit-свидетель ловит регресс.** Пересчитал `sha256` содержимого committed `dishwasher.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` не запускалась мной отдельно (см. «чего не проверял»), но сама логика теста и хеш проверены напрямую.
6. **Правка 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 «ряд техники в палитре».
7. **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`. Не считаю это находкой вне скоупа: это не правка поведения, а синхронизация записи истины о уже смёрженном коде.
8. **Правка smoke — не маскировка регресса.** Прочитал `demo/smoke_dialog_config_parity.mjs` целиком вокруг правки: смок сравнивает `JSON.stringify(_serverCfg)` после правки через UI и после прямой записи состояния диалога; путь «state» задавал только `tempMax`/`northDeg`, но не `tempMaxInput`/`northDegInput` — сырые поля черновика, добавленные уже слитым #614 («поле сохраняет введённый черновик»). Без этих полей два пути расходились бы в сохранённом конфиге, и после #614 смок должен был покраснеть при следующем полном прогоне; коммит чинит только тестовый путь, продуктовый код диалогов в дифе отсутствует.
9. **Трейлеры и changelog.** Каждый нетривиальный коммит несёт `Issue: #640` и корректный `User-Visible`; `feat(furniture)` (`User-Visible: yes`) в одном коммите правит оба `docs/CHANGELOG.md`/`.ru.md` — проверено `git show --stat`. Формулировки changelog корректно называют видимое: «новая иконка категории, размещённые предметы и рисунок на плане не изменились».
10. **`docs/images/screenshots.json`.** Правка — только `sourceFingerprint`/`sourceSha256` (пересчитан из-за смены `src/**`), ни один `imageSha256` не изменился — палитра мебели не входит в документационные скриншоты, штатный `check-docs` синхронизирован корректно.
11. **Дешёвые гейты.** Validate на точном материале `6a905d9a` зелёный (workflow_dispatch `35884429047`, `conclusion: success`); проверил через `gh run view` — job «Переиспользование: это дерево уже проверено» сработал, потому что единственная разница с ранее полностью провалидированным деревом — коммит документации ревью. Полный прогон с браузерными смоками и golden состоялся раньше, на материале `2c9ee641` (push run `35872104807`): все три browser-smoke шарда и `golden` (175/175) зелёные; сверил через `gh api repos/.../commits/2c9ee641`, что этот коммит правит ровно те же три golden-файла, что и текущий `08ff4fbe` — то есть полный green run состоялся на содержимом, идентичном нынешнему product-диффу, до косметического ребейза и публикации ревью-документа. Мутанты по диффу зелёные на этом же материале (`35873306289`, все 6 шардов) и повторно на финальном SHA (`35884429047`, все 6 шардов).
12. **Выбор смоков по диффу.** `node scripts/smoke-select.mjs --base origin/dev --head HEAD` → НЕОПРЕДЕЛЁННОСТЬ: единственный задетый символ проекта, `GENERATED_FURNITURE_MENU`, не назван ни в одном из 263 смоков поимённо. Решение: не гонять smoke-матрицу заново вручную — она уже прогонялась зелёной на идентичном product-содержимом в run `35872104807` (см. п.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 на точном SHA `6a905d9a` зелёный (см. п.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 на содержимом, идентичном текущему продуктовому диффу (run `35872104807`, п.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.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/640-dishwasher-menu-icon`, коммит `6a905d9ac87d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `fc8674b16271690bd68fd016f979a045379f04ce`
```
git log --all --format='%H %T' | grep fc8674b16271
```
- Тело issue: `1336c4649192ea657779f92aeaf1daff7c7f23c2b85316d89d401bcd9f06101f`
- Вердикт конвейера: `green` · High 0