Issue: #474 User-Visible: no
17 KiB
CODE-REVIEW-474-r1
Issue: #474 — «Стартовый граф: арт мебели уходит в ленивый чанк»
Этап: code · заход r1 · блокирующих циклов израсходовано 0 из 4
Ветка: issue/474-lazy-furniture-art, HEAD 605a9a1c (после ребейза на origin/dev = ab01586b)
Спецификация принята зелёным на r3 (docs/reviews/SPEC-REVIEW-474-r3.md); это первый заход кодового ревью — авторская попытка ранее вернулась не по коду, а по конфликту ребейза (docs/specs/README.md), решённому без содержательных изменений реализации, поэтому разбор полный, не дельта.
Скоуп
Диапазон git diff origin/dev...HEAD (62 файла): разрез генератора мебели на
каталог (eager) и арт (ленивый чанк), новый FurnitureArtRuntime
(src/furniture-art-runtime.ts), три точки готовности арта (приём конфига,
редакторный adopt, бут-гейт), перевод магнита furniture-placement.ts на
каталог, бюджет бандла (потолок 300 500 → 290 500), семь мутантов-свидетелей,
новый смок smoke_furniture_lazy_art, unit-тесты, golden/harness.mjs-гейт на
.dfurn, i18n-тост в четырёх языках, docs/ARCHITECTURE.md/docs/FURNITURE.md.
Не в скоупе диффа и не проверялось повторно: сам арт/каталог мебели (не
менялся), редактор мебели вне точки adopt, бэкенд (.py не тронут).
Как проверялось
Гейты, прогнанные лично на этом SHA (список честности):
| Гейт | Команда | Результат |
|---|---|---|
| Типы | npx tsc --noEmit |
зелёный |
| Unit | npm test |
2139 тестов, 2138 pass, 1 skipped (пред-существующий), 0 fail |
| Сборка | npm run build |
зелёная, tsc+rollup |
| Бюджет бандла | node scripts/bundle-budget.mjs |
initial View 289 760 Б (потолок 290 500±2000, стена 301 066, запас 11 306 Б); предупреждение о низком запасе (#367) — известный, не этой задачи долг |
| Дерево бандла | npm run bundle:sync + git status |
пусто — committed dist/, custom_components/.../frontend, demo/srv/assets побайтово совпадают со свежей сборкой |
| Документация | node scripts/check-docs.mjs |
зелёный, 7 файлов, отпечаток скриншотов свежий |
| Смок задачи | node demo/smoke_furniture_lazy_art.mjs |
OK, все 15 проверок (AC2–AC5, AC11) |
| Смоки, названные в AC7 | smoke_furniture, smoke_furniture_polish, smoke_decor, smoke_decor_default_persist, smoke_lazy_editor_chunk, smoke_preloader_lifecycle |
все OK |
smoke-select.mjs --base origin/dev --head HEAD |
инструмент отбора | 9 прямых совпадений — все на _showToast (общее имя, слабая связь: свежий вызов composeUnsub — аддитивный, реализацию _showToast не трогает — не гонял все девять); 4 «зарегистрированные связи» — EditorRuntimeLoader/LanguageRuntime не изменялись в этом диффе, smoke_lazy_editor_chunk уже прогнан по AC7, остальные три (smoke_entry_stale, smoke_french_locale, smoke_german_locale) не гонял — связь по аналогии контракта, не по изменённому модулю |
| Мутанты §4.6 (7 шт.) | node scripts/mutation-gate.mjs --id=<each> по одному (флаг --id= берёт первое вхождение) |
все семь: «поймано 1 из 1» — furniture-art-eager-import, furniture-art-fallback-never-settles, furniture-art-no-retry-nonce, furniture-art-boot-gate-ignored, furniture-placement-needs-art, furniture-art-fingerprint-unchecked, furniture-art-editor-adopt-skipped |
| Golden | npm run golden:verify (полный набор — diff меняет рендер мебели и бут-гейт) |
15 сцен «different», identical к прогону на чистом origin/dev в отдельном worktree (см. ниже) — 0 расхождений от диффа; все furniture-*/decor-*-мебельные сцены — passed |
| Инварианты модели | не гонял | diff не трогает рёбра комнат, записи толщины, layout, marker.space, open_spans — магнит мебели проверяет только каталог размеров, геометрию стен не меняет |
pytest tests_backend |
не гонял | custom_components/**/*.py не тронут |
npm run inventory |
прогнан | 2138 unit / 310 pure-backend / 211 HA-harness / 227 browser smokes — числа не копировал вручную |
Golden: изоляция пред-существующего дрейфа
golden:verify не даёт прогон по одному сценарию (policy.mjs требует полный
набор для verify), поэтому гонял целиком. Список из 15 «different» —
исключительно isometric-* (10) и *-color-popover-* (5) — не пересекается с
темой задачи. Чтобы не приписать их этому диффу, поднял origin/dev
(ab01586b) в отдельном git worktree, собрал и прогнал тот же
golden:verify: результат — те же самые 15 сцен «different», побуквенно то же
множество id. Дрейф пред-существующий (среда/Chromium), не regressии.
Worktree убран (git worktree remove --force).
Находки
Low — scripts/bundle-budget.mjs: печать не называет новый ленивый граф
Файл: scripts/bundle-budget.mjs, функция сборки lines для CLI-вывода и
блок GITHUB_STEP_SUMMARY (около строк 322–344).
Манифест и assertBundleBudget уже возвращают lazyFurnitureArtGzipBytes
(добавлено этим диффом, покрыто тестом в test/bundle-assets.test.mjs), но
печатаемые строки CLI перечисляют только lazy editor/lazy locale/
lazy isometric — новый граф мебели не появляется ни в консоли, ни в таблице
GITHUB_STEP_SUMMARY, хотя структурно стоит рядом с остальными тремя.
Сценарий отказа: это не влияет на корректность гейта — assertBundleBudget
по-прежнему бросает исключение, если lazyFurnitureArtFiles пуст или
пересекается с initial (проверено чтением и тестом). Cтрадает только
наблюдаемость: инженер, читающий вывод bundle:budget в CI-сводке при
следующей правке бюджета, не увидит вес чанка мебели рядом с остальными тремя
графами и должен будет открыть JSON-манифест вручную.
Не блокирует; можно поправить в этой же задаче (одна строка в lines и в
таблице GITHUB_STEP_SUMMARY) или снять с явной пометкой «не печатаем — граф
редко меняется» — оставляю решение автору.
Что проверено и корректно
- Разрез генератора (§4.1).
scripts/generate-furniture-assets.mjsпишетfurniture-plan-catalog.generated.ts(eager, id/группа/категория/размеры) иfurniture-plan-art.generated.ts(GENERATED_FURNITURE_ART+ отпечаток), импортируемый только динамически изfurniture-art-runtime.tsи статически изhouseplan-editor-runtime.ts. Инвариант «id каталога == ключи арта» — тест вtest/furniture-assets.test.mjs, проверен запуском. FurnitureArtRuntime(§4.2). Контрактready|pending|fallbackзеркалитLanguageRuntime:art()синхронный и не запускает загрузку,ensure()идемпотентен (один in-flight промис — тест «only a single in-flight load»),adopt()синхронный и терминально отвергает чужой отпечаток,fallbackосевший (второй провал не откатывает раньше_failed, не ретраится дальше — проверено и мутантом, и unit-тестом). Нонс на attempt 1 — через content-hashed токенbundle-manifest.mjsподставляет так же, как дляde/fr/iso(проверено чтением диффаbundle-manifest.mjs— счётчик замен теперь1/1/1/1/1/1, шестой — новый).- Три точки готовности (§4.3). Приём конфига —
ensureFurnitureArtForв_seedDecorStyle, вызывается на всех путях, где в_serverCfgможет появиться новая мебель: первичный кэш (LS_CFG),_adoptStructuralResponses(оба live-sync входа —_syncLiveConfig-подобные вызовы на строках 4314 и 4491 — заведены через один и тот же метод). Пути, где_serverCfgпереписывается напрямую (_setAreaLifecycleConfig,_syncNewDevices,_ackNewDevice,_writeConfig's canonicalize) намеренно не вызывают_seedDecorStyle— они правят толькоsettings/marker_area_snapshot, неspaces[].decor, проверено чтением каждого из четырёх сайтов. Редактор — статический импорт +FURNITURE_ART_RUNTIME.adopt(...)на уровне модуляhouseplan-editor-runtime.ts, которое само является лениво импортируемым корнем (houseplan-card.ts:697), то естьadoptгарантированно выполняется до любого рендера палитры/призрака. Бут-гейт —furnitureArtBootPendingв условии_bootWatch,BOOT_MAX_MSостаётся безусловным потолком (проверено чтением:elapsed >= BOOT_MAX_MS ||стоит первым слагаемым||). - Магнит (AC6).
furniture-placement.tsпроверяетfurnitureSymbol(каталог), неfurnitureGraphic(арт) — размещение designer-предмета работает вpending, подтверждено unit-тестом и мутантомfurniture-placement-needs-art. - Отказ (§4.4, AC4/AC5). Смоком подтверждено: два провала →
fallback, вуаль снята, устройства живы, только legacy-предмет рисуется, ровно один тост; чужой отпечаток → терминальныйfallbackза один запрос без ретрая. - Бюджет (AC1). Измерено лично: initial View 289 760 Б, потолок 290 500,
стена не тронута,
lazyFurnitureArtFilesнепуст и не пересекается с initial —assertBundleBudgetбросает при нарушении (проверено мутантомfurniture-art-eager-import). - Golden (AC8). Гейт в
demo/golden/harness.mjsтребует все[data-kind="furniture"]активного пространства после снятия вуали — если бы фолбэк не различался от пустого кадра, гейт сам бросил бы; полныйgolden:verifyподтвердил 0 диффа от задачи (см. раздел про изоляцию выше). - Мутанты (AC9). Семь строк в §4.6 = семь id в
mutation-gate.mjs= семь «поймано 1 из 1» при личном прогоне каждого по отдельности. - Ядро (AC10).
houseplan-card.ts— 13 656 строк, тестcore-file-budget.test.mjsзелёный (потолок не превышен). - i18n.
toast.furniture_art_load_failed— во всех четырёх словарях (en/ru/de/fr), формулировка по образцуtoast.locale_load_failed. - Трейлеры. Все три продуктовых коммита несут
Issue: #474иUser-Visible: no; changelog не тронут — соответствует §7 спецификации (визуальных изменений нет). - Документация.
docs/ARCHITECTURE.mdиdocs/FURNITURE.mdописывают новую ленивую границу; отпечаток скриншотов обновлён в том же коммите (605a9a1c),check-docs.mjsзелёный. - Единственный источник числа. Диф не добавляет и не меняет ни одной
видимой пользователю величины (UX не меняется по AC2/§6.1) — гейт
single-source-numbers.test.mjsпрогнан в составеnpm test, не задет.
Чего не проверял
python -m pytest tests_backend— бэкенд не тронут этим диффом.npm run invariants— диф не касается геометрии стен/комнат/layout; единственная геометрическая точка соприкосновения (furniture-placement.ts) теперь читает только каталог размеров, ключи решётки не затронуты.- Три «зарегистрированные связи» инструмента отбора смоков —
smoke_entry_stale,smoke_french_locale,smoke_german_locale— оставлены непрогнанными: связь идёт по аналогии архитектурного контракта (EditorRuntimeLoader/LanguageRuntime), а не по изменённому файлу; ниeditor-runtime-loader.ts, ниlanguage-runtime.tsв диффе нет. - Девять «прямых совпадений» на
_showToast— общее имя, использованное по всей карточке; прогнан только тематически релевантный (входит в AC7-список), остальные восемь не гонял — правка добавляет вызовcomposeUnsub, не трогая саму_showToast. - Performance-профили — не названы в AC и не задет чувствительный к перфу
путь (бут-гейт ограничен тем же
BOOT_MAX_MS, что и раньше). - Ручное открытие карточки в браузере вне смоков/golden — не делал; браузерные смоки и golden воспроизводят все сценарии AC через настоящий Chromium.
Вердикт
Все 11 AC доказаны исполняемыми гейтами, которые я прогнал лично и которые
умеют падать (проверено мутантами на защитных контрактах и сверкой golden
против чистого dev для исключения пред-существующего дрейфа). Единственная
находка — Low, чисто наблюдательная, не по AC и не по корректности.
Зелёный.
Материал раунда
- Ветка:
issue/474-lazy-furniture-art, коммит605a9a1c786c— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
780f546ed4d2a9be72796f222d3d8bb0331c0268git log --all --format='%H %T' | grep 780f546ed4d2 - ТЗ
docs/specs/474-lazy-furniture-art.md, блобb4e8a03e921dd45220d33cd55afe95fd60440bbegit log --all --find-object=b4e8a03e921dd45220d33cd55afe95fd60440bbe -- docs/specs/474-lazy-furniture-art.md