# CODE-REVIEW-584-r2 — #584: физический габарит мебели равен заявленным размерам - Issue: [#584](https://github.com/Matysh/houseplan-card/issues/584) - Этап: `code` (PROCESS.md §2.7) - Диапазон материала раунда: `9bf41ec3..62d86e0f` - Материал ревью — ровно `62d86e0f8361a0ed4cc7b9af184252ab5ee63dd0`, рабочая копия уже на нём; `git fetch`/`git checkout`/`git pull` на другой коммит не делал. - Предыдущий раунд: [`CODE-REVIEW-584-r1.md`](CODE-REVIEW-584-r1.md), вердикт **жёлтый**, материал `9bf41ec3`, комментарий-вердикт `github-actions @ 2026-09-16T21:12:07Z`. - Заход: **r2** — разбор по дельте (PROCESS.md §2.9, issue #214) - Блокирующих циклов израсходовано: 1/4 (бюджет тратят жёлтый/красный; r1 был жёлтым) ## Дельта раунда ``` git diff 9bf41ec3..62d86e0f --stat docs/reviews/CODE-REVIEW-584-r1.md | 260 +++++++++++++++++++++++++++++++++++++ 1 file changed, 260 insertions(+) ``` Между материалом r1 (`9bf41ec3`) и материалом r2 (`62d86e0f`) в дереве изменился **ровно один файл** — сам документ ревью r1, положенный шагом публикации конвейера. Продуктового кода, тестов, паков, генераторов, changelog — нулевая дельта. `origin/dev` за это время продвинулся `7c5fd32a → e0b16c76`, но `e0b16c76` уже был предком материала `9bf41ec3` (это чужой докс-коммит, влившийся в `dev`), а не переписыванием истории — проверено `git merge-base --is-ancestor e0b16c76 HEAD` (истина) и `git merge-base origin/dev HEAD == e0b16c76`. Значит это не ребейз на ушедший вперёд `dev` (§7.2), ветка ветки просто линейно впереди; полный повторный разбор по этой причине не требуется. Единственное действие раунда — комментарий автора `Matysh @ 2026-09-16T21:13:28Z` «M1 закрыт: число названо», закрывающий единственную блокирующую находку r1 (M1) без изменения кода. ## Закрытие раунда r1 | Находка r1 | Чем закрыта | Где это видно | |---|---|---| | **M1** (Medium, в скоупе) — раздел «Производительность»/«Риски» ТЗ требует назвать рост ленивого чанка `furniture-plan-art.generated` числом; число не было названо ни в хендоффе, ни в коммитах, ни в CHANGELOG | Комментарий `Matysh @ 2026-09-16T21:13:28Z` называет число по официальному источнику — полю `bundle-manifest.mjs`, а не по блобам исходника: `lazyFurnitureArtGzipBytes` 10 254 → 23 338 Б (+13 084 Б, +128 %); заодно назван эффект на холодный старт (`initialViewGzipBytes` +47 Б, +0,016 %) и на весь ленивый набор (`lazyGzipBytes` +13 136 Б, +3,1 %) | Issue #584, комментарий 2026-09-16T21:13:28Z. **Числа проверены мной независимо, не на слово** — см. «Как проверялось» | ## Как проверялось Материал ревью — ровно `62d86e0f`; `git checkout`/`fetch` на другой коммит не делал. Дешёвые гейты на этом SHA уже подтверждены Validate (упомянуто в задании ревью: прогон `35151230014`, `success`) — это тот же код, что и на `9bf41ec3` (дельта раунда не трогает исходники), поэтому `npx tsc --noEmit`, `npm test`, `npm run build`+сверка бандлов, `check-docs.mjs`, browser-смоки, `golden:verify`, мутанты — повторно не гонял. Причина не «доверие», а «нулевая дельта кода»: r1 эти гейты уже проверил на идентичном дереве (`9bf41ec3`), а r2 к дереву ничего не добавил кроме доксового файла, который эти гейты не касается. Единственное, что требовало самостоятельной проверки в этом раунде — достоверность чисел из закрывающего комментария M1. Числа не входят ни в один автоматический гейт как утверждение соответствия ТЗ (сам факт наличия числа в отчёте гейтом не проверяется), поэтому свериться нужно было вручную: ``` python3 -c " import json d = json.load(open('custom_components/houseplan/frontend/houseplan-assets.json')) for k in ['initialViewGzipBytes','lazyGzipBytes','lazyFurnitureArtGzipBytes']: print(k, d.get(k)) " # HEAD (62d86e0f): initialViewGzipBytes 291158, lazyGzipBytes 433657, lazyFurnitureArtGzipBytes 23338 git show origin/dev:custom_components/houseplan/frontend/houseplan-assets.json > /tmp/dev-manifest.json python3 -c "... тот же код на /tmp/dev-manifest.json ..." # dev (e0b16c76, тот же код мебели что и 7c5fd32a): initialViewGzipBytes 291111, lazyGzipBytes 420521, lazyFurnitureArtGzipBytes 10254 ``` Результат совпадает с комментарием **побитно**: | поле | `dev` (было) | `HEAD` (стало) | дельта в комментарии | дельта, посчитанная мной | |---|---|---|---|---| | `lazyFurnitureArtGzipBytes` | 10 254 | 23 338 | +13 084 Б, +128 % | +13 084 Б, +127,6 % → округление до +128 % корректно | | `lazyGzipBytes` | 420 521 | 433 657 | +13 136 Б, +3,1 % | +13 136 Б, +3,124 % → округление корректно | | `initialViewGzipBytes` | 291 111 | 291 158 | +47 Б, +0,016 % | +47 Б, +0,0161 % → округление корректно | Источник чисел — реальный закоммиченный файл манифеста (`custom_components/houseplan/frontend/houseplan-assets.json`, поле пишет `scripts/bundle-manifest.mjs`, тот же источник, которым руководствуется `scripts/bundle-budget.mjs` при проверке бюджета `INITIAL_VIEW_GZIP_BUDGET = 301 066`), а не отдельный ручной подсчёт по blob'ам, как было у меня в r1. Значит число теперь официальное и воспроизводимое любым будущим ревьюером той же командой, а не разовый расчёт ревьюера. Дополнительно проверено: `291 158 < 301 066` — initial-View бюджет не задет (и не мог быть задет: арт мебели лежит в ленивом чанке, `initialViewFiles` его не включает — подтверждено ещё в r1 чтением `lazyFurnitureArtFiles`). ## Унаследовано из r1 Без повторной проверки в этом раунде принято всё, что установил [`CODE-REVIEW-584-r1.md`](CODE-REVIEW-584-r1.md) на материале `9bf41ec3`, поскольку дельта `9bf41ec3..62d86e0f` не касается ни одного из этих доказательств (единственный изменённый файл — сам r1-документ): - **AC1** (границы `[0,artW]×[0,artH]` ±0.1) — `test/furniture-visual-bounds.test.mjs`, мутант `furniture-symbol-may-keep-inner-padding` логически прослежен, красит тест. - **AC2** (одинаковые `w/h` → одинаковый габарит) — наследуется от AC1 транзитивно. - **AC3** (трансформ сохраняет бокс при угле/зеркалировании/масштабе) — прямые углы точное равенство, 15°/37° вложенность и покрытие >97 %. - **AC4** (склейка путей сохраняет относительные команды) — `test/furniture-path-join.test.mjs`, мутант `furniture-paths-joined-without-reset` логически прослежен; попутная находка про `stairs`/`tv` подтверждена в исходном сгенерированном коде. - **AC5** (`furniture:check` падает на нарушении пп.1/2/5) — прочитан код `svgArt()`/`loadPack()`, три `fail()`-условия соответствуют контракту. - **AC6** (golden пересняты, диффы объяснены) — 9 кадров, каждый объяснён в коммите `9bf41ec3`, `Baseline-Reviewed` присутствует. - **AC7** (скоуп ограничен паком/генератором/`furniture.ts`/тестами) — `git diff --stat` не содержит редактора/hit-area/конфига/i18n. - **AC8** (гейты зелёные на точном SHA) — два прогона Validate на `9bf41ec3` проверены по `headSha` (`35149944216` light, `35145083038` full). - Математика legacy `plant` (растяжение `1/0.96`), независимая сверка `svg-path-bounds.mjs` с продакшен-парсером `parseSvgPath` до `1e-6`, добросовестность приёмки дизайнерского пака, трейлеры и changelog обоих коммитов, «одно число — один источник» (не применимо), touch/security (без изменений) — все пункты раздела «Что проверено и корректно» r1. - Раздел «Чего не проверял» r1 (визуальный осмотр PNG глазами, `model-invariants`/`pytest` — не применимо, полный перф-профиль `large-house-*`) остаётся в той же силе: дельта раунда их не касается. Новое в этом раунде, что r1 не мог проверить (числа появились только сейчас): воспроизведение и проверка чисел M1 — выполнено выше, не наследуется, а проверено заново. ## Находки Не найдено. M1 (единственная блокирующая находка r1) закрыта комментарием с числами, которые я сверил с закоммиченным манифестом бандла независимо и получил точное побитное совпадение — не принято на слово. Нового кода, который нужно было бы разбирать, дельта не вносит, поэтому новых находок по построению взяться не может (AC не пересматривались, потому что дельта их не задевает). ### Low — не найдено ## Что проверено и корректно - **Точность чисел M1** — см. таблицу выше, совпадение до последнего байта между комментарием и полем `houseplan-assets.json` на `dev` (`e0b16c76`, тот же код мебели, что и старый `7c5fd32a`) и на `HEAD` (`62d86e0f`). - **Источник чисел — официальный, не разовый расчёт.** Комментарий ссылается на поле, которое реально существует в производственном манифесте и используется гейтом `bundle-budget.mjs`, а не на постороннюю метрику. - **Смысл цифр в комментарии корректен**: холодный старт (`initialViewGzipBytes`) практически не меняется (+47 Б — это запись имени нового чанка, не сам чанк), рост сосредоточен в ленивом чанке мебели, который загружается только при открытии мебели на плане — соответствует утверждению ТЗ «решение — за владельцем, если рост окажется заметным» и корректно передаёт масштаб (+128 % локально в ленивом чанке, но всего +3,1 % во всём ленивом наборе и +0,016 % в критичном для UX холодном старте). - **Отсутствие рецидива рабейза/дивергенции**: `origin/dev` продвинулся, но остался предком материала — не §7.2 «другой код после ребейза». - Всё унаследованное из r1 (см. раздел выше) остаётся в силе — дельта его не подрывает: правка M1 не касается ни одного файла, участвующего в AC1–AC8. ## Чего не проверял - **`npx tsc --noEmit`, `npm test`, `npm run build`+сверка бандлов, `check-docs.mjs`, browser-смоки, `golden:verify`, mutation-gate** — не гонял повторно: дельта раунда не меняет ни одного файла, участвующего в этих гейтах (единственный новый файл — `docs/reviews/CODE-REVIEW-584-r1.md`, сам по себе документация ревью, не код, не тесты, не арт). Эти гейты уже зелены на идентичном дереве кода — на `9bf41ec3` в r1 (проверено там по `headSha`) и на `62d86e0f` в актуальном Validate (`35151230014`, `success`, указан в задании ревью). - **Полный визуальный осмотр 9 golden-кадров глазами** — не открывал заново; это уже было явным пунктом «чего не проверял» в r1 и дельта раунда кадры не трогает. - **`model-invariants.mjs`, `pytest tests_backend`** — не применимо, как и в r1: дельта не трогает геометрию комнат/стен/backend, а в этом раунде вообще не трогает исходный код. ## Вердикт **Зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0 → в задаче.** Единственная блокирующая находка r1 (M1 — рост ленивого чанка мебели не был назван числом, как того требует раздел «Производительность»/«Риски» ТЗ) закрыта комментарием автора с числами по официальному источнику (`bundle-manifest.mjs`/`houseplan-assets.json`). Числа проверены мной независимо по закоммиченному манифесту на обоих концах диапазона и совпадают с комментарием побитно: `lazyFurnitureArtGzipBytes` 10 254 → 23 338 Б (+128 %), `lazyGzipBytes` +3,1 %, `initialViewGzipBytes` (холодный старт, критичный для бюджета `bundle-budget.mjs`) +47 Б (+0,016 %) — бюджет `301 066` Б не приближается. Продуктового кода в дельте раунда нет, `origin/dev` продвинулся, но остался предком материала (не ребейз, §7.2 не применяется). AC1–AC8, скоуп, трейлеры и changelog наследуются из r1 без повторной проверки — дельта их не касается. Новых High/Medium/Low находок не появилось. --- --- ## Материал раунда - Ветка: `issue/584-furniture-physical-bounds`, коммит `62d86e0f8361` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала: `894d037879b76147721b484c5c769dd32eba7383` ``` git log --all --format='%H %T' | grep 894d037879b7 ``` - Тело issue: `2b6d41955420844101142131ce4f789615f0ac788609ae654f4b416448b03b9b` - Вердикт конвейера: `green` · High 0