20 KiB
CODE-REVIEW-303-r1 — #303: подсветка «Толщины стены» по факту кладки
- Issue: #303
- Этап:
code(PROCESS.md §2.7) - Трек:
small— ТЗ живёт в теле issue (комментарий Matysh, 2026-08-25T07:48:43Z), ревью ТЗ — комментарий-вердикт зелёный, документdocs/reviews/SPEC-REVIEW-303-r1.md - Диапазон:
origin/dev...HEAD—origin/dev=0f71d86,HEAD=93cd455(веткаissue/303-wallthick-hover-width) - Коммиты:
36992d68(fix: match wall thickness hover to masonry,Issue: #303,User-Visible: yes),93cd4556(docs: refresh reviewed screenshots,Issue: #303,User-Visible: no) - Заход: r1 · блокирующих циклов ревью 0/2 (лёгкий трек — лимит 2, PROCESS.md §4)
- Вердикт: зелёный
Скоуп ревью
12 файлов, +217/−24.
Продуктовый код (класс A): src/grid-scale.ts (+16 — новая чистая функция
wallThickHoverHalfUnits), src/houseplan-card.ts (+8/−6 — _wallThickHover
переведён на неё, пол gridPitch*1.25 удалён; _wallThickHit не тронут).
Гейты/тесты (класс B): test/grid-scale.test.mjs (+29), новый браузерный смок
demo/smoke_wallthick_hover_width.mjs (+87), три новых мутанта в
scripts/mutation-gate.mjs (+46).
Документация (класс C): docs/CHANGELOG.md/docs/CHANGELOG.ru.md (+5/+5,
User-Visible: yes в том же коммите — верно), docs/TESTING.md (+7 — новая
строка чек-листа со ссылками на unit/smoke/мутанты).
Генерируемое (класс D): dist/houseplan-card.js,
custom_components/houseplan/frontend/houseplan-card.js — сверено байт-в-байт
пересборкой (см. таблицу гейтов); docs/images/09-device-info.png и
docs/images/screenshots.json — обновлённый sourceFingerprint после смены
src/**, второй коммит корректно помечен User-Visible: no.
Backend (custom_components/houseplan/**/*.py), манифесты, i18n, README,
docs/USER-GUIDE.ru.md — не тронуты (git diff --stat origin/dev...HEAD
подтверждает; § «Что не входит» ТЗ явно исключает превью рисования стены и
другие инструменты).
Прочитано до вердикта: docs/SCOPE.md, AGENTS.md, PROCESS.md §2.7/§2.10/§4,
тело issue #303 и все 4 комментария (ТЗ, зелёное ревью ТЗ, «взял в разработку»,
отчёт о готовности), docs/reviews/SPEC-REVIEW-303-r1.md, docs/WALL-THICKNESS.md
(упоминает инструмент «Толщина» и hover, не фиксирует контракт ширины полосы —
без противоречий), docs/USER-GUIDE.ru.md (терминология «Толщина стены»
совпадает с формулировкой changelog), весь изменённый продуктовый код и тесты
целиком.
Как проверялось
| Гейт | Команда | Результат |
|---|---|---|
| Типы | npx tsc --noEmit |
green (без вывода) |
| Сборка | npm run build |
green, dist/houseplan-card.js пересобран |
| Синхронность бандлов | cp dist/… custom_components/…/houseplan-card.js && npm run bundle:sync, затем git status --porcelain |
пусто — пересборка из чистого чекаута дала байт-в-байт то же содержимое, что закоммитил автор; все три копии синхронны |
| Unit | npm test |
1294 теста: 1293 pass, 1 skip, 0 fail (см. примечание ниже) |
| Docs-гейт | node scripts/check-docs.mjs |
green — «Documentation checks passed (7 files, 10 external links)» |
| Мутации, точечно (реестр §10.2, не полный прогон — по дельте) | node scripts/mutation-gate.mjs --id=wallthick-hover-floor-back, --id=wallthick-zero-strip-not-visual, --id=wallthick-hit-narrowed |
3/3 «тест покраснел, как обязан» — под каждой мутацией именно новый unit/smoke гаснет |
| Применимость патчей всех мутантов | node scripts/mutation-gate.mjs --check |
green (не дал закончиться полному прогону, см. ниже — досрочно остановлен) |
| Целевой смок AC | node demo/smoke_wallthick_hover_width.mjs |
green — все 5 проверок true |
Смоки по прямому совпадению символа (node scripts/smoke-select.mjs --base origin/dev --head HEAD) |
smoke_decor.mjs, smoke_grid_scale_invariance.mjs, smoke_space_scale_defaults.mjs (все три — совпадение по cellCm) |
все green |
Примечание по unit-тестам. Автор отчитался «1292 pass, 2 skip»; в моём
прогоне — 1293 pass, 1 skip. Разница на один тест — единственный skip в моём
прогоне (#904 issue 281 private exact fixture is not present) отмечен как
условно пропускаемый при отсутствии приватной фикстуры; он не относится к
диапазону #303 и не влияет на результат (0 fail в обоих случаях). Расхождение
в счёте — особенность окружения, не находка по этому диапазону; фиксирую как
пример того, что «verified» без точной команды/среды может на единицу
расходиться, но здесь это не заслоняет главный факт — красных тестов нет.
node scripts/mutation-gate.mjs --check без ограничения по --id прогоняет
«чистый прогон» guard-команды для каждого из ~40 существующих мутантов
реестра (пересборка/тесты на каждый) — это дублирует ежедневный npm test и
принадлежит пред-релизному гейту (комментарий в самом скрипте: «его место —
перед стабильным релизом, не на каждой бете»). Остановил его после того, как
подтвердил, что новые три мутанта в списке проверок присутствуют и «чистый»
прогон их guard проходит; предметная проверка «умеет падать» сделана точечными
--id= прогонами выше — это и есть проверка по дельте, а не по всему реестру.
Диапазон трогает геометрию?
Нет. Диапазон читает существующее поле hit.cm (толщина стены, уже посчитанная
wallIntervals) только для отрисовки; не меняет структуры walls, layout,
marker.space, open_spans и не пишет новых геометрических данных. npm run invariants не запускал — по построению диапазона гейт неприменим (нет записи/
чтения ссылок на геометрию, только визуальный проход по уже готовому значению).
Мутанты
wallthick-hover-floor-back— возвращает старый полmax(depth/2, gridPitch*1.25); гвард — точечный unit«uses the exact physical wall width»вtest/grid-scale.test.mjs. Прогнан: покраснел.wallthick-zero-strip-not-visual— убираетgridVisualUnitsиз ветки нулевой толщины (заменяет её на голыйgridPitch*1.5, ломая инвариантность к масштабу); гвард — unit«zero-thickness hover». Прогнан: покраснел.wallthick-hit-narrowed— сужаетpullв_wallThickHitсgridPitch*6доgridPitch*2; гвард —demo/smoke_wallthick_hover_width.mjs. Прогнан: покраснел.
Все три мутанта корректно нацелены (регрессия из issue, регрессия того же класса, риск смежного контракта — сужение зоны попадания) и реально ловятся названными тестами, а не проходят «случайно».
Находки
Нет находок High или Medium. Низкая — одна, ниже.
L1 — счёт unit-тестов в отчёте разработчика на единицу отличается от факта
Файл: отчёт в комментарии issue (не код). Серьёзность: Low.
Разработчик указал «1294 unit-теста (1292 pass, 2 skip)»; фактический прогон в
среде ревью — 1293 pass, 1 skip, 0 fail. Причина, скорее всего, — среда:
условный skip #904 (приватная фикстура #281) присутствует не всегда. Это не
влияет на диапазон #303 (тест не относится к нему, fail нигде нет) и не меняет
вердикт.
Диспозиция: снимается без правки — расхождение в подсчёте пропусков природы окружения, не дефект кода; ноль red в обоих случаях.
Что проверено и корректно, по AC ТЗ
- AC1 (
cell_cm:30, стена 50 см — полоса совпадает с телом, допуск 2%). Доказано исполнением:smoke_wallthick_hover_width.mjsстроит план, выставляетcell_cm:30, ставит стену 50 см, затем поперечным сканомisPointInFillмеряет ширину.wallbodyи.wallthick-hoverв одной и той же точке оси —bodyIsPhysicalWidthиhoverMatchesWallWithinTwoPercentзелёные. Дополнено чтением:wallThickHoverHalfUnitsдляcm>0вызывает тот жеwallCmToUnits, что и тело стены (_wallUnionGeometry/drawWallPreviewD) — совпадение не случайное, оба потребителя читают одну формулу, один источник числа. - AC2 (
cell_cm:5, толстые стены ≥12.5 см — поведение не меняется). Проверено арифметикой на границе:wallThickHoverHalfUnits(12.5, 5, GRID_PITCH) === GRID_PITCH*1.25— ровно то значение, которое давал старый полmax(depth/2, gridPitch*1.25)на границе применимости пола (дляcm≥12.5приcellCm=5пол никогда не был активен, значит новая формула без пола даёт то же самое для всего диапазонаcm≥12.5). Тест вtest/grid-scale.test.mjsесть и проходит. - AC3 (
cell_cm:5, стена 3 см → честные 2.5 юнита, не 10.42). UnitwallThickHoverHalfUnits(3, 5, GRID_PITCH) * 2 === 2.5— проходит; это именно тот случай, где старый пол раздувал тонкую стену, и мутантwallthick-hover-floor-backподтверждает, что тест ловит возврат старого поведения. - AC4 (стена
cm=0— видимый минимум, физически одинаковый на любомcell_cm, приcell_cm:5— байт-в-байт сегодняшний вид). Два unit-теста: первый —atFive*2 === GRID_PITCH*3(это ровно староеdepth = gridPitch*3→half = 1.5*gridPitchпри отсутствии пола наcellCm=5, т.е. вид не изменился); второй — пересчёт в физические сантиметры(half*2/gridPitch)*cellCmдаёт 15 см на обоихcellCm=5иcellCm=30, подтверждая масштабную инвариантность черезgridVisualUnits(та же конвенция, что уже используют штрихи,styles.ts:1549). Мутантwallthick-zero-strip-not-visualподтверждает, что тест реагирует именно на потерю этой инвариантности. - AC5 (зона попадания курсора не сужена).
_wallThickHitне входит в диф этого диапазона (проверено чтением всего диффа) —pull = gridPitch*6физически не менялся. Дополнительно подтверждено исполнением:generousHitAreaUnchangedв смоке бьёт на расстоянииgridPitch*5от оси и попадает; мутантwallthick-hit-narrowed(сужение доgridPitch*2) ловится тем же смоком. - AC6 (мусорный ввод:
cm<0/NaN/Infinity→ как 0;cellCm≤0/NaN→ как 5;gridPitchневалиден → 0). Unit-тест перебирает[-1, NaN, Infinity, -Infinity]дляcmи[0, -1, NaN, Infinity, -Infinity]дляcellCm, плюс отдельноcellCm=0для стены с реальной толщиной (wallThickHoverHalfUnits(50, 0, GRID_PITCH) === 5*GRID_PITCH— откат к эталонной клетке 5 см) иgridPitch=NaN(wallThickHoverHalfUnits(50, 30, NaN) === 0, посколькуwallCmToUnitsдомножает наgridPitch). Все проходят; прочитана реализация — три независимыеNumber.isFinite-проверки покрывают именно эти случаи, догадок не найдено.
Одно число — один источник
Полоса подсветки не дублирует показанное пользователю числовое значение
(диалог толщины показывает cmToField(cm,…), полоса — только визуальная
ширина); проверка неприменима как «два места показывают одно число». Но по
существу дефект #303 был именно расхождением визуальной величины с телом
стены при одном общем источнике данных (hit.cm) — новая реализация устраняет
это, вызывая ту же wallCmToUnits, что и тело стены, а не независимую
формулу. test/single-source-numbers.test.mjs (не относится напрямую к этому
диапазону) прогнан — 3/3 green, регрессий не внесено.
Соответствие ТЗ и его допущениям
ТЗ (лёгкий трек) уточнило формулировку исходного issue («минимум в
сантиметрах» → «grid-visual юниты, существующая конвенция проекта») и это
уточнение было принято зелёным ревью ТЗ. Реализация буквально следует
контракту §2 ТЗ: новая функция в точности с указанной сигнатурой и телом,
_wallThickHover не содержит собственной арифметики, _wallThickHit/стили
полосы не тронуты. Ни одного расхождения между ТЗ и кодом не найдено.
Чего не проверял
- Полный набор смоков (188 файлов) — не запускал целиком; по
scripts/smoke-select.mjsдиапазон даёт только «прямые совпадения» (4 файла, все прогнаны и зелёные), «широких» символов нет. Диапазон меняет ровно один вычисляемый геттер и одну чистую функцию без побочных эффектов — расширять выборку не увидел оснований. - Полный прогон
mutation-gate.mjsпо всем ~40 мутантам — по построению скрипта это пред-релизный гейт (дорогая пересборка бандла на каждый мутант); для этого диапазона проверил точечно три новых мутанта (все ловятся) и применимость патчей (--check, green). npm run golden:verify/ golden capture — не запускал:wallthick-hoverне участвует вdemo/golden/matrix.mjs(grep пустой), полоса подсветки не входит ни в одну golden-сцену, изменение не может дать диф golden по построению.npm run invariants— не запускал: диапазон не пишет и не трансформирует геометрические структуры (стены/layout/marker.space/open_spans), только читает уже вычисленноеhit.cmдля отрисовки.python -m pytest tests_backend -q— не запускал: диапазон не трогает ни одногоcustom_components/**/*.pyфайла (git diff --statподтверждает).- Performance-профили — не запускал: ни ТЗ, ни AC не называют perf-чувствительный путь; изменение — замена одной арифметической формулы в геттере подсветки, не затрагивает пути измерения перформанса.
git diff --check(whitespace) — не запускал отдельно; не увидел его необходимости, поскольку diff небольшой и был прочитан целиком построчно.
Вердикт
Зелёный · заход r1 · блокирующих циклов 0/2 · High: 0 · Medium: 0 → в задаче.
Все 6 AC ТЗ подтверждены — частично исполнением (целевой смок, точечные
unit-тесты, три точечных мутационных гварда), частично чтением там, где
исполнение избыточно (_wallThickHit не в диффе). Дешёвые гейты
(typecheck, test, build, синхронность бандла, check-docs) зелёные.
Единственная находка — Low, косметическая, не в коде и не требует правки.
Готово к очереди на пре-релиз (S8-merged по факту прохождения ревью,
согласно конвейеру).