24 KiB
CODE-REVIEW-598-r1
Issue: #598 · Заход r1 · Материал: c22a1f43db8d4916b4bfdc054408e05e1d0954df (HEAD, рабочая копия на нём же, git status чист)
Скоуп
Диалоги «Общие настройки», «Пространство» и «Устройство на плане» переведены на
карточки-группы общего набора (form-kit.ts, введённого в #594). Шесть
пояснений-абзацев переехали под кнопку «?»; два сообщения о состоянии
(gs.sun_missing, marker.run_target_gone) остаются видимыми callout'ами
(решение владельца). gs.hint перенесён из ленивого support-словаря в основной
как gs.card_fills.help. Потолок стартового графа поднят 291 400 → 292 500 под
рост словаря. Диапазон файлов подтверждён git diff origin/dev...HEAD --stat:
68 файлов, из них продуктовые — src/editors/{general-settings,space,marker}-dialog.ts,
четыре основных i18n/*.json, четыре i18n/support/*.json, scripts/bundle-budget.mjs,
scripts/mutation-registry.mjs, два смока, тесты i18n, changelog×2, USER-GUIDE.ru.md,
плюс сгенерированный бандл (класс D, синхронизирован).
Как проверялось
Дешёвые гейты для этого SHA уже подтверждены зелёным Validate
(https://github.com/Matysh/houseplan-card/actions/runs/35450469410), но
поскольку диалоги — визуальная поверхность, а Validate на обычный пуш не
гоняет golden (heavy-гейт, #479), golden:verify я прогнал сам — и не зря
(находка H1). Также перепрогнал typecheck/test/build локально для собственной
уверенности перед тем, как опираться на них далее.
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
✅ 0 ошибок |
| Unit-тесты | npm test |
✅ 2788 pass / 0 fail / 1 skip (2789 всего) |
| Build + сверка 3 копий бандла | npm run build && npm run bundle:sync + cmp dist↔custom_components↔demo/srv/assets |
✅ все три копии побайтово совпадают, рабочее дерево чистое после синка |
no-new-any |
node scripts/no-new-any.mjs --base origin/dev --head HEAD |
✅ новых any нет (284 добавленных строки в 4 файлах, 56 — дословный перенос) |
check-docs (диф трогает src/**) |
node scripts/check-docs.mjs |
✅ 7 файлов, 12 внешних ссылок, отпечаток обновлён |
bundle:budget (AC10) |
npm run bundle:budget |
✅ initial View 291 522 Б (потолок 292 500±2000), lazy editor 222 190 Б (потолок 222 900±2000) — оба в полосе |
golden:verify, полный набор (AC8, визуальный риск — главный по самому ТЗ) |
npm run golden:verify |
❌ 11 из 11 объявленных сцен "different", 0 расхождений вне них — находка H1 |
Смоки, названные в АС / прямое совпадение smoke-select |
node demo/smoke_{general_settings,tap_run,help_affordance,color_picker_consumers,backup_transfer}.mjs |
✅ все зелёные |
Девять .srcrow-смоков (AC2) |
node demo/smoke_{discovery_filters,ha_controls,hide_layers,room_settings,room_tooltip_toggle,space_create_display_defaults,sun,tap_run,ux_fixes}.mjs |
✅ все зелёные — но smoke_tap_run.mjs из этого списка изменён в диффе (находка M1) |
| Смоки панели/комнаты (AC9) | node demo/smoke_{summary_panel,summary_panel_polish,summary_first_paint,summary_dialog_scroll}.mjs |
✅ все зелёные, файлы вне диффа |
| Мутанты, добавленные диффом | node scripts/mutation-gate.mjs --id=state-callout-hidden-under-help и --id=dialog-card-loses-its-heading |
✅ оба «покраснели на мутанте» — тест умеет падать |
| Существующий генерик-мутант сегмента | node scripts/mutation-gate.mjs --id=form-kit-segment-drops-radio-semantics |
✅ покраснел |
smoke-select.mjs (для решения по выборке) |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
Вывод учтён при выборе смоков выше |
Не прогонял и почему:
python -m pytest tests_backend— диф не трогаетcustom_components/**/*.py.node scripts/model-invariants.mjs— диф не трогает геометрию,layout,marker.space,open_spans; изменения чисто разметочные.- Junction-parity зеркало — не затронуто.
- Полный browser-смоук-набор (250 файлов) — задача не задевает всё, точечный набор выше покрывает АС и
.srcrow-опору. - Performance-профили — не названы в AC,
iso-*/live-*/render-*не тронуты.
Находки
H1 — AC8 (эталоны) не выполнен: golden:verify красный на этом SHA
Файлы: demo/golden/baselines/** (не изменены), src/editors/general-settings-dialog.ts, src/editors/space-settings-dialog.ts, src/editors/marker-dialog.ts.
ТЗ AC8 требует: «Эталоны трёх диалогов пересняты и объявлены поимённо:
device-dialog-{desktop-en,mobile-ru,desktop-de}, toggle-entity-dialog-{desktop-en,mobile-ru},
device-help-popover-light-ru, device-ripple-color-popover-mobile-ru,
general-color-popover-desktop-en, settings-help-zoom-200-{en-light,ru-dark},
space-room-color-popover-desktop-ru. Все остальные сцены — побайтово прежние»,
доказывается npm run golden:verify на полном линуксовом артефакте.
Я собрал бандл (npm run build && npm run bundle:sync, рабочее дерево после
этого чистое — совпадает с закоммиченным) и прогнал npm run golden:verify без
--only на полной матрице. Результат:
different device-dialog-desktop-en
different device-dialog-mobile-ru
different device-dialog-desktop-de
different toggle-entity-dialog-desktop-en
different toggle-entity-dialog-mobile-ru
different device-help-popover-light-ru
different general-color-popover-desktop-en
different settings-help-zoom-200-en-light
different settings-help-zoom-200-ru-dark
different device-ripple-color-popover-mobile-ru
different space-room-color-popover-desktop-ru
Ровно те же 11 сцен, что названы в AC8 — и ни одной больше (хорошая новость:
изменение геометрически ограничено ровно заявленной поверхностью, регрессии в
соседних сценах нет). Плохая новость: git diff --stat не содержит ни одного
файла под demo/golden/baselines/** — новые эталоны не переснимались и не
принимались вовсе. AC8, как написан, сейчас проваливается буквально: это не
«не прогнан», а «прогнан и красный».
Почему High, а не «на пре-релизе поймают». AC8 — пронумерованный критерий
приёмки этой же задачи, с названной командой доказательства; критерий не
выполнен на предъявленном SHA. Смержить это в dev — значит на первом же
workflow_dispatch full=true/PR/бета-кандидате получить красный golden
job, повторяя цену #230/#234 (тогда это стоило dev дня в красном docs).
Раздел ТЗ «Release-артефакты» отдельно называет «эталоны одиннадцати
объявленных сцен» как то, что должно появиться в этом же коммите — их нет.
Чем закрывается: пересъёмка npm run golden:capture (или через полный
Linux CI артефакт) и npm run golden:accept -- --reviewed по одиннадцати
названным сценам, коммит baseline с трейлерами Release:/Baseline-Reviewed:
как требует AGENTS.md. Это не переделка кода — сама разметка отделена ровно
по границе 11 сцен, что и подтверждает мой прогон.
M1 — AC2/AC3 нарушены: изменён один из «неприкосновенных» .srcrow-смоков
Файл: demo/smoke_tap_run.mjs (+22 строки).
ТЗ фиксирует ограничение явно: «девять смоков с .srcrow... зелёные без единой
правки» (AC2, «чем краснеет» — сам смок; «git diff --stat demo/ по ним пустой»),
и «единственный изменённый смок — smoke_general_settings» (AC3). .srcrow
встречается в девяти файлах (проверено grep -l "\.srcrow" demo/smoke_*.mjs):
smoke_discovery_filters, smoke_ha_controls, smoke_hide_layers,
smoke_room_settings, smoke_room_tooltip_toggle,
smoke_space_create_display_defaults, smoke_sun, smoke_tap_run,
smoke_ux_fixes. Фактический дифф (git diff origin/dev...HEAD --stat)
показывает изменения в двух смоках демо-каталога: smoke_general_settings.mjs
(36 строк — объявленное исключение) и smoke_tap_run.mjs (22 строки —
не объявленное).
Сам коммит утверждает обратное: «Изменён ровно один смок —
smoke_general_settings» — при факте двух изменённых файлов это не
формальность, а неточное свидетельство ровно того рода, от которого
предостерегает PROCESS.md («Verified» без сверки не доказательство). Добавки
в smoke_tap_run.mjs сами по себе не вредны и обоснованны (проверяют AC5 —
видимость marker.run_target_gone — и состав карточек устройства), но they
принадлежат какому-то другому файлу, не одному из одиннадцати
объявленных неприкосновенными.
Чем закрывается: перенести три новых блока (missingRunTargetStaysVisible,
markerCardsInOrder, everyMarkerCardHasContent) в новый файл — например,
demo/smoke_marker_dialog_cards.mjs — либо в уже существующий несмежный смок
устройства, и вернуть smoke_tap_run.mjs к состоянию origin/dev побайтово.
M2 — Сегментированный переключатель обещан в семи местах ТЗ, реализован в одном
Файлы: src/editors/general-settings-dialog.ts (сделано), src/editors/space-settings-dialog.ts (не сделано, segmented импортирован и не используется), src/editors/marker-dialog.ts (не сделано).
Раздел «UX» ТЗ: «Сегментированный переключатель применяется... стиль нулевых
стен (2), режим фона (2–3), режим привязки устройства (2), роль света (3),
режим свечения (3), положение бейджа значения (4)» — шесть точек плюс уже
упомянутый gs.bg_mode (в разделе «Общие настройки» той же таблицы) — семь
конверсий. Раздел «Что человек увидит после» формулирует это как часть
контракта: «выбор из двух-пяти вариантов — сегментированный переключатель
вместо селекта или столбика радиокнопок».
Фактически grep -n "segmented(" src/editors/*.ts находит ровно один
вызов — gs-bg-mode в «Общих настройках». space-settings-dialog.ts
импортирует segmented из form-kit и ни разу не вызывает (мёртвый импорт,
tsc --noEmit его не ловит — noUnusedLocals, судя по всему, выключен).
space-zero-wall-style и space-bg-mode остались <select>; в
marker-dialog.ts все восемь <select> (marker-room, marker-tap-action,
marker-toggle-entity, marker-light-entity, marker-display,
marker-value-source, marker-value-badge-source,
marker-value-badge-position) остались как были — ни одного segmented(.
CHANGELOG честно называет только один переход («The plan background choice became a segmented control instead of a dropdown» — единственное число), то есть сокращение не выдаётся за большее, чем есть. Но само сокращение нигде не объявлено: ни в issue, ни в блоке «Принято предположительно» ТЗ, ни в тексте коммита. Раздел УX — часть проревьюенного и принятого ТЗ (обязательный по §7.1), и «ревьюер вправе оспорить любую из этих развилок» подразумевает явную развилку на столе, а не тихое исполнение одной из семи строк таблицы.
Дополнительно: AC6 («каждый новый сегмент обходится стрелками и Tab... блок в
смоке каждого диалога») не имеет автотеста даже для той единственной
gs-bg-mode, что появилась — grep по smoke_general_settings.mjs на
ArrowRight|ArrowLeft|activeElement|getBoundingClientRect пуст. Единственная
проверка 44 px — статический CSS-assert в test/form-kit.test.mjs
(min-height: 44px в правиле), не привязанный к конкретному диалогу и не
исполняющий реальное нажатие стрелки.
Чем закрывается: либо реализовать оставшиеся шесть конверсий (и убрать мёртвый импорт при провале одной), либо — если разработчик и ревьюер сходятся, что дело не в этом раунде — явно сузить ТЗ (комментарий в issue, а не молчание) и добавить AC6-смок хотя бы для уже добавленного сегмента.
M3 — AC5 доказан мутантом только наполовину
Файл: scripts/mutation-registry.mjs; отсутствующая пара — src/editors/general-settings-dialog.ts.
AC5 защищает два сообщения о состоянии, называя единственный мутант-свидетель:
«gs.sun_missing и marker.run_target_gone... чем краснеет: мутант
state-callout-hidden-under-help». Фактический патч этого мутанта (проверено
чтением scripts/mutation-registry.mjs, id state-callout-hidden-under-help)
патчит только src/editors/marker-dialog.ts (строку с marker.run_target_gone).
Для gs.sun_missing в general-settings-dialog.ts патча нет —
grep -n "sun_missing" scripts/mutation-registry.mjs не находит ничего.
Смок-проверка sunMissingStaysVisible: true в smoke_general_settings.mjs
существует и в моём прогоне зелёная, но по правилу §2.7 «мутант обязателен,
когда защита живёт в продуктовом коде и проверяется дорогим гейтом (смок...)» —
а smoke_general_settings.mjs именно такой гейт. Пустой третий столбец —
находка Medium по тому же правилу, а не примечание.
Чем закрывается: добавить второй патч (или отдельный id, например
state-callout-hidden-under-help-gs) на строку
html\
вgeneral-settings-dialog.ts, guard node demo/smoke_general_settings.mjs`.
L1 — AC1 не имеет обещанного автотеста для диалога «Пространство» (закрыто чтением)
Файл: src/editors/space-settings-dialog.ts.
AC1 называет доказательством «demo/smoke_general_settings.mjs,
demo/smoke_space_create_display_defaults.mjs, новый блок в смоке устройства».
demo/smoke_space_create_display_defaults.mjs не тронут (0 строк в
git diff --stat) — притом что это тот же файл, который AC2 требует оставить
неприкосновенным (девятый .srcrow-смок), так что у ТЗ здесь внутреннее
противоречие между AC1 и AC2 для одного файла, а не только пробел исполнителя.
Я сверил построчно старую и новую версию space-settings-dialog.ts
(git show origin/dev:src/editors/space-settings-dialog.ts против рабочей
копии): каждый перемещённый контрол (цвет/прозрачность комнаты, радиокнопки
режима заливки, чекбоксы скрытия декора/проёмов, кнопка сброса кастомной
заливки) несёт дословно тот же обработчик (@input/@change/_boolInput
замыкание не переписано, только переставлен внутри другого formCard).
Функционального риска для AC1 в диалоге «Пространство» не вижу — проверено
чтением, не исполнением. Отмечаю как Low, потому что claim в AC1 не
соответствует факту диффа, а не потому что запись в конфиг под угрозой.
Что проверено и корректно
- К1 (запись не меняется). Прочитаны все три диалога построчно;
каждый перенесённый контрол сохраняет свой обработчик байт-в-байт, меняется
только обёртка
formCard(...). Подтверждено экспериментально смоками для «Общих» (saved,customFillUsed,lqiBefore/Afterи т.д. — зелёные) и «Устройства» (markerSaved,runCalled,automationTriggered— зелёные). - К3 (цвет).
smoke_color_picker_consumers.mjsзелёный без правок —hp-color-opacityне подменялась, нативных пикеров не появилось. - К4/К6 (пояснения под «?», классы-опоры). i18n-дифф последователен по
всем четырём локалям (ru/en/fr/de: 6 удалённых текстов из основных
словарей +
gs.hintиз четырёхsupport/*.json, 24 новых ключа во всех четырёх основных словарях, тексты идентичны исходным — сверено посимвольно дляgs.card_fills.helpпротив прежнегоsupport.gs.hint)..srcrow/.dispsection/.gsrow/.colorrow/.namein/.areasel/.tempinостаются на прежних узлах — все девять протестированных.srcrow-смоков иsmoke_backup_transferзелёные. - К5 (сообщения о состоянии). Оба текста остаются абзацами
(
class="rhint"), не переехали подhp-help; смоки это подтверждают (sunMissingStaysVisible,missingRunTargetStaysVisible); мутантstate-callout-hidden-under-helpпокраснел на попытке спрятатьmarker.run_target_gone(см. M3 — вторая половина без мутанта). - К8 / AC10 (граф загрузки).
npm run bundle:budget: initial View и lazy editor в полосе; форма набора как была в ленивом графе, новых листов нет. - AC9 (панель и комната не задеты).
git diff --statне содержитsrc/summary-panel-*иroom-settings-dialog.ts; их смоки зелёные без правок. - AC11 (полный набор).
npm test— 2788/2789 (1 skip), 0 failures. - Дублирование чисел. Диф не вводит новых видимых пользователю величин
(только текст и разметка),
test/single-source-numbers.test.mjsв составеnpm testзелёный — правило неприменимо по существу. - Классы риска §2.6. async — не применимо (синхронная разметка). Данные/права — см. К1 выше. Геометрия — не применимо. Визуал — главный риск, и именно здесь найден H1. Объём/perf — AC10 зелёный, DOM не растёт сверх карточек. host/input — К7/AC6 частично не подтверждён (см. M2).
Материал раунда
- Ветка/материал:
c22a1f43db8d4916b4bfdc054408e05e1d0954df(HEAD на момент ревью и на момент публикации —git statusчист, коммитов после старта разбора не было). - Диапазон:
git diff origin/dev...HEAD. - Это первый заход код-ревью данного issue — раздела «Унаследовано из r0» и «Закрытие раунда r0» не требуется.
Материал раунда
- Ветка:
issue/598-three-dialogs-form-kit, коммитc22a1f43db8d— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
97c7efe2a5b46a514092385d77c9a762dca0c5c3git log --all --format='%H %T' | grep 97c7efe2a5b4 - Тело issue:
a81e7c46b347ae3eb2a0de35e30606c335c901733d52a04e1019a937a6d737fb - Вердикт конвейера:
red· High 1