17 KiB
CODE-REVIEW — issue #51 «Custom decor images» · заход r4
- Issue: https://github.com/Matysh/houseplan-card/issues/51
- Материал раунда:
git diff 043aa0d826cf1e1490a02feb4cc28f50cb96245b..HEAD(материал r3 объявлен вdocs/reviews/CODE-REVIEW-51-r3.md). - SHA материала:
f7dfc6cab2dd02b19e19e222b981c85fd7c9b66b(сверено сgit rev-parse HEAD— совпадает). - SHA предыдущего раунда (r3):
043aa0d826cf1e1490a02feb4cc28f50cb96245b— жив,git cat-file -t→commit,git merge-base --is-ancestor 043aa0d8 HEADподтверждает: прямой предокHEAD, ребейза не было. - Трейлеры обоих коммитов дельты (
6bfa42ca,f7dfc6ca) —Issue: #51,User-Visible: no. Корректно: ни один не меняет продуктовый код или наблюдаемое пользователем поведение (толькоtests_backend/**иdemo/smoke_gear_tabs.mjs) — правок вdocs/CHANGELOG*не требуется и не сделано.
Скоуп раунда
Единственная предметная находка r3 (Medium, в скоупе: backend coverage
ratchet красный из-за недопокрытого decor_assets.py, 71%) закрыта одним
коммитом (6bfa42ca). Второй коммит раунда (f7dfc6ca) — попутная
стабилизация ранее упавшего в CI demo/smoke_gear_tabs.mjs, тест-инфраструктура,
не связанная с #51 по существу, но входящая в тот же материал.
Дельта — 2 файла (без бандлов, без учёта опубликованного docs/reviews/CODE-REVIEW-51-r3.md,
который положил конвейер, а не автор): tests_backend/test_decor_assets.py
(+71 строка, новые тесты) и demo/smoke_gear_tabs.mjs (+12/-4, замена
таймаута на page.waitForFunction). Продуктовый код (src/**,
custom_components/houseplan/** за пределами тестов) не тронут вообще.
Ребейза на ушедший вперёд dev не было, контракт поведения не менялся,
новая подсистема не задета, объём дельты (2 файла, только тесты) на
порядок меньше исходной задачи — критерий «разбор остаётся полным» не
выполняется, сокращение объёма правомерно. Разбирал дельту построчно
(diff показан целиком выше), не декларативно.
Закрытие раунда r3
| Находка r3 | Чем закрыта | Где это видно |
|---|---|---|
Finding 1 (Medium, в скоупе): pytest tests_backend с coverage-ratchet красный на 043aa0d8 — decor_assets.py покрыт на 71%, ниже среднего проекта; coverage: 86.9% при baseline 87.2% |
6bfa42ca добавил 8 новых тестов в test_decor_assets.py: реальные валидные JPEG/WebP(VP8/VP8L/VP8X)-фикстуры для полного декодирования через Pillow (test_supported_raster_headers_and_full_decode), граничные условия размера/расширения upload (test_upload_size_and_extension_guards), SVG dimensions/viewBox/text edge cases (test_svg_dimension_and_text_boundaries_fail_closed), пустой каталог и путь метаданных (test_catalog_empty_directory_and_metadata_path), malformed spaces/asset-id в reference scan (test_reference_scan_skips_malformed_spaces_and_asset_ids) |
Независимо перепроверил на реальном CI-логе job'а «Бэкенд: pytest в Home Assistant» прогона 33685974303 (SHA 6bfa42ca, backend job success): custom_components/houseplan/decor_assets.py 292 32 132 22 87% (было 71% на 043aa0d8), TOTAL … 87%, coverage: 87.5% (baseline 87.2%) — ratchet зелёный, 534 passed, 2 skipped. Дополнительно прогнал сам локально: python3 -m pytest tests_backend/test_decor_assets.py -q → 35 passed (совпадает с заявленным автором числом), ruff check tests_backend/test_decor_assets.py → чисто |
Прогон 33685974303 в целом завершился failure не из-за backend-а, а
из-за независимого падения Смоки в браузере (шард 3 из 3) — это и есть
триггер второго коммита раунда (f7dfc6ca, smoke_gear_tabs). Проверил
отдельно: на итоговом HEAD (прогон 33686693823,
уже названный в контексте задачи как зелёный Validate) шард 3 содержит
ok smoke_gear_tabs; job «Бэкенд: pytest в Home Assistant» в этом прогоне
skipped, потому что джоб «Переиспользование: это дерево уже проверено»
законно переиспользовал побайтово идентичный backend-результат прошлого
зелёного прогона (диапазон коммита f7dfc6ca не трогает backend-релевантные
пути) — это не пропуск гейта, а корректное переиспользование того же
самого прогона, который я сверил выше напрямую по логу.
Проверено и корректно
- Новые тесты не декларативны. Построчно сверил каждый новый тест с
реализацией в
custom_components/houseplan/decor_assets.py(validate_asset,_validate_svg,asset_refs,asset_meta_path,read_catalog): всеpytest.raises(..., match=...)соответствуют реальным сообщениям об ошибках и реальным веткам кода (_check_size,ext not in ASSET_EXTENSIONS,viewBox-парсинг на 4 значения,_validate_dimensionssafety limit, SVG text-branch,root.is_dir()false-branch,isinstance/regex-guard вasset_refs). Проверил сам, что тест умеет проходить не «вхолостую»: локальный прогон зелёный (35/35), плюс независимое подтверждение по логу реального CI на backend job с ростом покрытия именно целевого модуля. - Coverage ratchet — монотонный, не занижен.
scripts/backend-coverage-baseline.txtне менялся в этой дельте (git diff 043aa0d8..HEAD -- scripts/backend-coverage-baseline.txtпуст), остаётся87.2; фактическое покрытие выросло до87.5%за счёт реальных тестов, а не понижения планки — соответствует монотонному контракту гейта (docs/specs/042-backend-engineering-quality.md §4). smoke_gear_tabs.mjs-правка не ослабляет проверку. Сравнил diff: утверждениеout.kioskNoAddButton = !sr.querySelector('.tab.tabadd')не изменилось; заменён только механизм ожидания —page.waitForFunctionна появлениеha-cardв shadow root вместо гонкиsetTimeout(350)+ форсированного переприсваиванияhass. Т.к. Lit рендерит шаблон компонента одним синхронным проходом, появлениеha-cardв DOM означает завершение того же render()-прохода, в котором решается наличие.tab.tabadd— проверка становится детерминированной, а не слабее. Остальная часть файла (res-блок с выравниванием шестерёнок, диалогом создания, mobile-блок) не тронута.- Трейлеры и провенанс. Оба коммита дельты несут
Issue: #51,User-Visible: no— корректно, видимое поведение не меняется. Job «Предполётные проверки» (провенанс коммитов, процессный гейт) в прогоне 33686693823 — success. - User-Visible. Нет изменений в
docs/CHANGELOG*— и не требовалось: диффаsrc/**/custom_components/**продуктового кода нет.
Унаследовано из r1–r3 (без повторной проверки)
Дельта раунда не касается ничего из перечисленного ниже — переносится по
документам предыдущих раундов, SHA которых подтверждены как прямые предки
HEAD (см. цепочку merge-base выше и в CODE-REVIEW-51-r3.md):
- AC1–AC4, AC6, AC8–AC13, AC16 (продуктовый контракт: upload/palette,
furniture-parity transforms без wall magnet, raster pipeline, reusable
lifecycle, missing-repair, import/export, i18n, unified projection) —
из
docs/reviews/CODE-REVIEW-51-r1.md, SHA38205d87, доказаны автотестом либо разобраны чтением. - High r1 (SVG DTD/entity billion-laughs, UTF-16-обход байтового
префильтра) — закрыт
encoding-aware expat.ParserCreate(), адверсариально перепроверен на 6 кодировках/вариантах вdocs/reviews/CODE-REVIEW-51-r2.md, SHA042af520. - Medium r1 (3 шт.) —
smoke_decor.mjsна актуальном runtime API, единаяprojectDecorImage(устраняет «одно число — два источника» между full/static-рендером), positive/negative resolve-кэш — все три закрыты и перепроверены вCODE-REVIEW-51-r2.md, SHA042af520. - Low r1 (2 шт.) — touch pointerType для snap, SVG per-attribute bounds — закрыты там же; третий Low (data:/javascript: substring) сознательно оставлен как принятый риск, запись сохраняется.
- Medium r2 (устаревший
scripts/config-schema.json) — закрыт, перепроверен вdocs/reviews/CODE-REVIEW-51-r3.md, SHA043aa0d8(git diffпосле регенерации пуст). - Гейты, зелёные на
043aa0d8и не тронутые этой дельтой:tsc --noEmit,npm test(1777 passed/1 skipped),npm run build,bundle:sync,bundle:budget(291018/300000, известный долг #367),no-new-any,check-docs.mjs --external(отпечаток документационных скриншотов),ruff check custom_components/houseplan, golden/model-invariants (дельта их не касается — ни разу не тронуты с r1, продуктовый код без изменений). - Model invariants (
npm run invariants) не нужны ни в этом раунде, ни накопительно — diff всей задачи не создаёт новых ссылок на геометрию комнат/стен сверх уже проверенного в r1.
Что проверил лично в этом раунде
git diff 043aa0d8..HEAD— весь, построчно (2 файла, не считая опубликованного документа r3).python3 -m pytest tests_backend/test_decor_assets.py -q— 35 passed (локально, послеpip install pytest defusedxml Pillow).python3 -m ruff check tests_backend/test_decor_assets.py— чисто.- Реальные логи CI: job «Бэкенд: pytest в Home Assistant» прогона
33685974303 (SHA
6bfa42ca) — построчная таблица покрытия,decor_assets.py87%,TOTAL87.5% vsbaseline 87.2%,534 passed, 2 skipped; job «Смоки в браузере (шард 3 из 3)» прогона 33686693823 (SHAf7dfc6ca, тот самый Validate) —ok smoke_gear_tabs. node scripts/smoke-select.mjs --base 043aa0d8 --head HEAD→ «Исполняемого frontend-диффа нет (src/**/*.tsне тронут). Browser-smoke этим диффом не выбираются — выбирать нечего».git diff 043aa0d8..HEAD -- scripts/backend-coverage-baseline.txt— пусто, планка не занижена.git merge-base --is-ancestor 043aa0d8 HEAD— подтверждён прямой предок, ребейза не было.
Что не проверял и почему
- Полный
tsc --noEmit/npm test/npm run build/ сверка трёх копий бандла заново — не прогонял: диффаsrc/**в этом раунде нет, а Validate на точномHEAD(f7dfc6ca, прогон 33686693823) уже зелёный по этим шагам (см. контекст задачи) — повторный прогон не добавляет информации. - Полный
pytest tests_backend/HA-harness заново — не перезапускал весь набор (нет пиновой версии Python 3.14/HA в песочнице ревьюера); вместо этого прочитал реальный лог зелёного backend job на точном SHA фикса (6bfa42ca) и убедился по построчной таблице покрытия, что находка r3 закрыта числом, а не декларацией. Backend job в финальном Validate-прогоне (f7dfc6ca) законно skipped как переиспользование идентичного backend-результата — проверил это переиспользование, а не принял на слово. golden:verify, model invariants, performance-профили — не прогонял: дельта раунда не трогаетsrc/**, рендер, геометрию комнат/стен или производительные пути; этот вывод не новый — он наследуется от всех предыдущих раундов, где diff этих областей тоже не касался.check-docs.mjs— не перезапускал: диффаsrc/**нет, отпечаток документационных скриншотов от этого раунда зависеть не может.smoke-selectпо остальным ~65 smoke-файлам — не прогонял: сам инструмент подтвердил «выбирать нечего» для этой дельты (frontend-код не тронут);smoke_gear_tabs.mjs, единственный изменённый smoke-файл, подтверждён напрямую логом реального CI, а не инструментом выбора.node scripts/no-new-any.mjs— не перезапускал: дельта не содержит TypeScript.
Вывод
Единственная находка r3 закрыта предметно и подтверждена числом (не
заявлением автора): покрытие decor_assets.py выросло с 71% до 87%,
общий coverage ratchet зелёный (87.5% vs baseline 87.2%, планка не
занижена) — подтверждено чтением реального CI-лога на точном SHA фикса.
Побочная стабилизация smoke_gear_tabs.mjs не ослабляет проверяемый
инвариант и подтверждена тем же реальным CI на финальном SHA. Новых
находок в дельте r4 нет. High: 0, Medium: 0.
Материал раунда
- Ветка:
issue/51-custom-decor-images, коммитf7dfc6cab2dd— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
0fdd0e0043cec7cd4015dbcae6bc7949c4b64167git log --all --format='%H %T' | grep 0fdd0e0043ce