16 KiB
CODE-REVIEW · issue #639 · заход r1
Материал: 0ac08434b5cd12848ce8b18aa17a6343b83b6653 (рабочая копия на нём).
Диапазон: git diff origin/dev...HEAD — 3 файла, 16 добавлений / 4 удаления,
2 коммита (71e8dc29, 0ac08434). Правок продуктового кода нет: только
demo/smoke_room_settings_form.mjs, demo/smoke_device_settings_form.mjs,
scripts/smoke-links.mjs.
Предыдущего раунда не было: 71e8dc29 получил метку S7-code-review, но
Validate упал на preflight-сверке process.yml/mutation-gate.yml между
main и dev (несинхронный мердж #636) до того, как код кто-либо прочитал —
автор сам это фиксирует в комментарии 06:46. Раздел «Унаследовано из r0» не
нужен: наследовать нечего.
Скоуп задачи
Ночной мутационный прогон 2026-09-23 упал пятью шардами из шести не на
сбежавшем мутанте, а на двух гардах, красных и на чистом dev:
smoke_room_settings_form.mjs (numberWritesNameScaleOnly) и
smoke_device_settings_form.mjs (brightnessNumberWrites,
sizeAndAngleNumbersWrite). Причина — #608 перенёс запись числа range-line
(слайдер+число, rangeLine() в form-kit.ts) с события input на атомарный
change (committedRangeLineValue), а оба смока-гарда продолжали задавать
поле одним input. Правка: смоки шлют input+change, как пользователь,
отпустивший поле. Вторым коммитом — попутная (не обязательная для гейта)
рекомендация из разбора: scripts/smoke-links.mjs получил запись
rangeLine/unitInput → smoke_room_settings_form.mjs,
smoke_device_settings_form.mjs, smoke_space_settings_form.mjs, чтобы
будущая правка контролов form-kit гоняла эти три смока на кандидате
ревью, а не дожидалась ночного реестра.
Класс B, User-Visible: no в обоих коммитах — верно: правка не меняет
поведение карточки, только тестовую инфраструктуру. docs/SCOPE.md здесь не
применяется впрямую (это не продуктовая фича), но и не нарушается.
Как проверялось
Валидация уже выполнена платформой на этом SHA — Validate success,
https://github.com/Matysh/houseplan-card/actions/runs/35829479664. npx tsc --noEmit, npm test, npm run build со сверкой копий бандла на этом
основании не перегонялись. Дельта не трогает src/**, геометрию,
custom_components/**/*.py, рендер/стили/слои и не заявляет perf-профили в
AC — поэтому node scripts/check-docs.mjs, npm run invariants,
npm run golden:verify, python -m pytest tests_backend тоже не запускались:
выбирать здесь нечего, это не пропуск, а несовпадение диффа с условием гейта.
Дешёвые гейты, которые я всё же прогнал сам (Validate их не покрывает — это адресные смоки/тесты по теме диффа, что PROCESS.md требует делать ревьюеру всегда):
node scripts/smoke-select.mjs --base origin/dev --head HEAD→ «Browser-smoke этим диффом не выбираются» — ожидаемо: инструмент читает исполняемыйsrc/**фронтенд, а диффа там нет. Значит выбор смоков для ручной проверки я делал по теме задачи (сами два гарда, названные в разборе автора, плюс третий смок из новой записи реестра), а не по инструменту.npx mocha test/smoke-select.test.mjs— 7/7 зелёных, включая тест «каждая запись реестра объясняет себя и указывает на существующий смок» — механически подтверждает, что новая запись ссылается на реальные файлы, но НЕ проверяет правдивость текстаbecause(см. находку M1).npm run bundle:sync(build + копия вdemo/srv/assets—demo/srvне коммитится, без этого браузерные смоки не запускаются) — прошёл без ошибок, попутно повторно подтвердилtsc --noEmitиrollup -cчистыми на этом SHA.node demo/smoke_room_settings_form.mjsиnode demo/smoke_device_settings_form.mjsна HEAD — обаOK.- Тест умеет падать — отдельно проверил регрессионную ветвь: временно
вернул оба файла к версии
origin/dev(git show origin/dev:... >) и перезапустил те же два смока. Получил ровно то падение, которое описал автор:Затем восстановил файлы командойsmoke_room_settings_form: FAILED (1): numberWritesNameScaleOnly smoke_device_settings_form: FAILED (2): brightnessNumberWrites, sizeAndAngleNumbersWritegit checkout HEAD -- ...; рабочее дерево чистое (git status --shortпусто), в репозиторий ничего не писал. - Прочитал по коду (не исполнением) все четыре исправленных поля в
src/editors/marker-dialog.ts(marker-glow-brightness,marker-size,marker-angle) иsrc/editors/room-settings-dialog.ts(room-${key}-scale) — все четыре действительно обёрнуты вrangeLine(), где числовое поле коммитит наonChange, аonInput— no-op (form-kit.ts:470-489). Значит фикс задел ровно те поля, для которых он нужен. - Прочитал по коду соседние необновлённые поля в тех же файлах —
#room-temp-min/max,#marker-glow-radius— это обычныйunitInput()сonInput, коммитящий наinputнапрямую (не черезrangeLine), т.е. их не нужно было трогать. Автор не задел лишнего и не пропустил нужное.
Находки
Medium (в скоупе — чинится в этой же ветке)
M1. Новая запись smoke-links.mjs для smoke_space_settings_form.mjs
описывает проверку, которой смок не делает.
scripts/smoke-links.mjs (диф, добавленная запись):
{
symbols: ['rangeLine', 'unitInput'],
smokes: ['smoke_room_settings_form.mjs', 'smoke_device_settings_form.mjs', 'smoke_space_settings_form.mjs'],
because: '#639: the three settings-form smokes drive range-line numbers (room name/label scale, '
+ 'device brightness/size/angle, space card font) through the DOM and assert the host draft; ...',
}
Утверждение — «space card font» проверяется «through the DOM». Но
demo/smoke_space_settings_form.mjs:132-141 не отправляет на
#space-card-font ни input, ни change: значение слайдера/числа
изменяется напрямую присвоением c._spaceDialog = { ...d, cardFontScale: 1.5 },
после чего проверяется только кнопка «Сбросить к 100 %». Поле #space-card-font
— это как раз настоящий rangeLine() (src/editors/space-form.ts:427-431,
min:50, max:300, step:5, число коммитит на onChange, см. form-kit.ts:479-487)
— то есть ровно тот же класс поля, который сломался в #608. Если бы commit-семантика
rangeLine сломалась именно для этого экземпляра, smoke_space_settings_form
никак не заметил бы: он не проходит через DOM-обработчик, который сломался.
Файл scripts/smoke-links.mjs сам формулирует требование к своим записям
(шапка файла, строки 10-15): «Каждая запись обязана объяснять, ЧТО именно
проверяет смок — иначе реестр превращается в список суеверий, который никто
не решается почистить». Эта запись именно так и врёт: она обещает DOM-покрытие
третьего смока, которого нет. Практический риск смягчён тем, что диапазон
50/300/5 у space.card-font совпадает с room-name-scale/room-label-scale,
уже покрытыми смоком smoke_room_settings_form, — но именно поэтому вредно и
дальше опираться на неверный текст: он создаёт иллюзию третьей независимой
проверки там, где её нет, и следующий, кто аудирует реестр (как в #234), потратит
цикл на то же самое расследование, которое я проделал сейчас.
Воспроизведение: demo/smoke_space_settings_form.mjs:132-141 — нет
dispatchEvent на #space-card-font.
Как чинить (любое из двух, решает автор):
- либо убрать
smoke_space_settings_form.mjsиз этой записи и оставить два реально DOM-управляемых смока; - либо дописать в
smoke_space_settings_form.mjsDOM-коммит на#space-card-font(input+change, как в остальных двух смоках) и тогда текст записи станет верным.
Medium, в скоупе — задача именно про честность smoke-links.mjs для
range-line/unitInput; без High это жёлтый вердикт, отдельный issue не заводится.
Что проверено и корректно
- Оба фикса (
smoke_room_settings_form.mjs:133,smoke_device_settings_form.mjs:102,144,145) добавляютchangeровно на тех полях, что реально используютrangeLine()и коммитят поchange— подтверждено чтением исходников формы и воспроизведением падения/починки. commit-хелпер вsmoke_device_settings_form.mjs:25и уже существовавшийchange-хелпер вsmoke_room_settings_form.mjs:23(не добавлен этим диффом, был в файле раньше) корректно шлютinputзатемchange— соответствует контрактуrangeLine(onInputдля числа — no-op, коммит наonChange).- Соседние поля той же формы (
unitInputбезrangeLine) фиксом не тронуты — верно, у них коммит наinputне менялся. - Трейлеры обоих коммитов:
Issue: #639,User-Visible: no— верно, правка не меняет видимое поведение карточки; изменений в user-facing changelog не требуется и не делалось. test/smoke-select.test.mjsзелёный (7/7), включая структурную проверку новой записи реестра (существование файлов/символов).- Второй комментарий в issue объяснил и закрыл красный Validate на
71e8dc29как инфраструктурный шум (несинхронный мердж #636 междуmain/devна preflight), не имеющий отношения к коду ветки — согласуется с тем, что preflight сверяетprocess.yml/mutation-gate.ymlмежду ветками, а не диффом задачи; отдельно перепроверять эту причину не требовалось.
Чего не проверял и почему
npx tsc --noEmit,npm test,npm run build(сверка 3 копий бандла) — не перегонял: зелёный Validate уже есть на этом SHA (run 35829479664), а дельта тестовая, не продуктовая.node scripts/check-docs.mjs— не запускал:src/**не тронут.npm run invariants— не запускал: диф не трогает геометрию/layout/ толщины/marker.space/open_spans.npm run golden:verify— не запускал: диф не может изменить рендер (это событийная семантика smoke-теста, не код карточки).python -m pytest tests_backend— не запускал:custom_components/**/*.pyне тронут.- Остальные 259 смоков из
demo/smoke_*.mjs, не поднятые ни автором, ниsmoke-select— не гонял: диф не трогает исполняемый фронтенд, темы не пересекаются. - Perf-профили — не запускал: не названы в задаче, дифф не затрагивает чувствительные к перфу пути.
Вывод
Основная причинно-следственная цепочка разобрана верно, фикс двух гардов
точен и проверен на умение падать. Единственная находка — Medium, в скоупе:
новая запись реестра для smoke_space_settings_form.mjs заявляет DOM-проверку,
которой в файле нет. Практический риск снижен перекрытием с
smoke_room_settings_form, но запись должна быть либо исправлена, либо
урезана до двух реально DOM-управляемых смоков — это именно тот класс ошибки,
против которого сам файл предупреждает в своей шапке.
Вердикт: жёлтый.
Материал раунда
- Ветка:
issue/639-range-line-smokes, коммит0ac08434b5cd— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
f7de276fa6df89e7f8b53499143a2ff0905e2f9agit log --all --format='%H %T' | grep f7de276fa6df - Тело issue:
7ad41f5686d99bfd44e274349b50adaec808bc8169c0e0ea2f31df52671bf0b3 - Вердикт конвейера:
yellow· High 0