25 KiB
CODE-REVIEW-600-r1
Issue: #600 · Этап: code · Заход: r1 · Материал: fa12045271f47bac64db153b60737bc7bb4e5336 (детач, диапазон origin/dev..HEAD, origin/dev = 6cee9504)
Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 4 → в задаче
Скоуп и что проверялось
Задача переписывает внутренности четырёх диалогов настроек (Space, General
settings, Room settings, Device on the plan) под референс дизайнера,
сохраняя запись состояния (К1–К10 ТЗ). Диапазон: 15 коммитов поверх dev,
199 файлов, из них по существу — 35 файлов src/** (3554+/2191-), 64 файла
тестов/смоков/докс/чейнджлога. Референс, SPEC.md, IMPLEMENTATION-GUIDE.md
уже разложены в docs/design/600-settings-dialogs/ (AC0, принят на спек-ревью).
Спек-ревью r1 по этой же задаче — зелёный (комментарий issue от 20.09, 06:18), одна Low-находка снята без последствий для кода. Это первый заход код-ревью, предыдущего кода-ревью раунда не было — раздела «Унаследовано из r0» нет, разбор ниже полный.
Методология
- Прочитан
git diff origin/dev...HEADцеликом по файлам (частями дляmarker-dialog.ts, 1247 строк диффа) и структураdocs/design/.... - Запущено четыре параллельных фоновых агента с точными предписаниями (файлы, строки ТЗ, конкретные критерии) на AC1/AC4/AC8 (по диалогам), AC3/AC5/AC6/AC7 и мутанты, AC9–AC13 + трейлеры/changelog. Их находки перепроверены выборочно (see below), а не приняты на слово.
- Самостоятельно: визуальное попарное сравнение референс/продукт по всем
4 диалогам × 2 темы (
docs/design/600-settings-dialogs/pairs/*.pngпротивscreenshots/*.png, с покадровым zoom через Pillow) — так была найдена находка №1 ниже, которую не поймал ни один смок и не назвал сам автор. - Проверка полноты CI-подтверждения на точном материале: dispatch-прогон
35506392847наfa120452не тяжёлый (full=trueне задан) и дляsmoke/goldenне нашёл кэша переиспользования («Cache not found for input keys: reuse-smoke-…», «reuse-golden-…» — seeПереиспользованиеjob лога), то есть ни smoke-матрица, ни golden не подтверждены CI на самом материале ревью — только на промежуточном504c1609(там же — одна красная браузерная проверка, исправленная позже в4bd80e46без последующего CI-подтверждения). Это закрыто мной лично (см. «Гейты» ниже), а не принято на слово автора.
Гейты — что прогнано и чем
Дешёвые гейты (tsc --noEmit, npm test, npm run build) уже зелёные на
fa120452 по ссылке из задания (Validate 35506392847, success) — не
перегонялись.
Прогнано лично или агентами, с результатом:
| Гейт | Кто | Результат |
|---|---|---|
npm run build && npm run bundle:sync |
я | чисто, дерево не изменилось |
npm run golden:verify (полный, 173 сцены) |
я | 173/173 passed — закрывает пробел CI (см. выше); подтверждает AC10 на самом материале, включая 13 новых и все старые сцены побайтово |
node demo/smoke_room_tooltip_toggle.mjs |
я | OK, все 25 ключей true — закрывает пробел CI: у фикса из 4bd80e46 не было ни одного зелёного CI-прогона на финальном дереве |
node demo/smoke_dialog_config_parity.mjs |
агент A | OK (AC3, побайтовое сравнение конфига через UI-путь и state-путь) |
node demo/smoke_{space,general,room,device}_settings_form.mjs |
агенты A/B/C | все OK; проверяют собственный ключ + нетронутость соседнего для каждого нового контрола, role=radiogroup+aria-label на сегментах, цели ≥44px |
node demo/smoke_color_picker_consumers.mjs |
агент A | OK, nativeColors()===0 держится |
node demo/smoke_esc_dialogs.mjs |
агент A | OK, включая переспрос при закрытии с изменениями |
node demo/smoke_help_affordance.mjs, node --test test/i18n-dead-keys.test.mjs |
агент A | OK |
node --test test/form-kit.test.mjs |
агент D | 5/5 pass, включая байтовое сравнение CSS панели с замороженной test/fixtures/summary-panel-editor.css (файл не тронут диапазоном — AC11 не фиктивен) |
npm run bundle:budget |
агент D | потолок LAZY_EDITOR_GZIP_CEILING 222900→243900 с построчным обоснованием по сериям; факт 243125, зазор 775/1225 Б (оба >500) |
node scripts/check-docs.mjs |
агент D | зелёный на HEAD (скриншоты пересняты в fa120452) |
npm test (полный) |
агенты A/D независимо | 2794/2793 pass, единственный флак — не связанный с #600 тест по времени сборки одного этажа (#509), не влияет на вердикт |
Чего не проверял и почему
- Мутанты из
scripts/mutation-registry.mjsне исполнены живьём (patch → guard → RED → revert) — только статическая проверка, что anchor-строки существуют один раз и обоснование соответствует коду. Полный mutation-run дорог и не назван в AC как обязательный отдельно от диффовых мутантов, которые уже зелёные в CI на этом SHA («Мутанты по диффу 1–6/6» — success). - Не все 88 «прямых совпадений» из
smoke-select.mjsперепрогнаны поштучно моей рукой. Полная матрица уже прогнана CI дважды за время задачи (workflow_dispatch full=trueна504c1609, шарды 1/3 и 3/3 — success целиком, шард 2/3 — одна красная, исправленная в4bd80e46и лично перепроверенная мной на финальном дереве, см. таблицу выше). Дельта между504c1609иfa120452— сам смок-фикс, два golden-коммита и один docs-тест, ни один из них не переиграл более широкий смок. Прогон всех 88 заново не пропорционален размеру этой дельты. python -m pytest tests_backendне запускался — диапазон не трогаетcustom_components/houseplan/**/*.pyпо существу (только собранныйfrontend/**, класс D).- Живые ha-dialog (реальный Home Assistant) не поднимались — только
демо-харнесс с нативным
<dialog>; это и есть причина, по которой находка №3 ниже (поповер «?») осталась неподтверждённой в обе стороны, а не закрытой.
Находки (все — Medium, в скоупе задачи, к исправлению в этом же заходе)
1. Room settings: подпись «Temperature» в 5-сегменте переносится посреди слова
docs/design/600-settings-dialogs/pairs/room-light.png и room-dark.png —
кадры, которые сам автор снял и приложил как доказательство AC1 — показывают
сегмент «Fill mode» (None | Zigbee | Lights | Temperature | Custom), где
активный пункт отображается как Temperatur / e двумя строками внутри
кнопки. Референс (тот же SPEC.md §6.1, тот же список из пяти пунктов)
рассчитан на одну строку на пункт.
Причина — src/styles/form-kit.styles.ts:295-312: .hpf-seg label держит
flex: 1 1 0; min-width: 0; overflow-wrap: anywhere — при пяти равных
колонках слово Temperature (11 симв.) не помещается и рвётся в
произвольном месте вместо аккуратного переноса или уменьшения кегля.
src/editors/room-settings-dialog.ts:197-200 документирует, что автор был в
курсе тесноты пяти сегментов («Короткие подписи там, где полные не
помещаются…») и сократил lqi→«Zigbee», custom→«Custom», но не тронул
fill.temp — то самое слово, которое рвётся. RU-вариант («По температуре»,
14 символов) длиннее английского и пострадает не меньше; DE/FR — сопоставимо.
docs/design/600-settings-dialogs/ACCEPTANCE.md:69 при этом утверждает
«Подписи сегмента заливки … то же» и не называет это расхождение —
единственная строка ACCEPTANCE.md про подписи segment на самом деле не
покрывает пять из пяти пунктов честно. AC2 («без переноса, читаемо при
длинных RU/DE/FR») формально проверяет только отсутствие горизонтального
скролла контейнера (demo/smoke_room_settings_form.mjs:192,
content.scrollWidth <= content.clientWidth), а перенос внутри кнопки
scrollWidth контейнера не трогает — поэтому смок зелёный, а дефект есть.
Сценарий: открыть Room settings любой комнаты с выключенным «As the space», посмотреть на сегмент Fill mode — на активном/любом широком языке пункт «Temperature» отображается разорванным словом. Это ровно тот класс дефекта («ложный паритет», «сырая раскладка»), ради которого заведён #600.
2. Device dialog: у Glow radius появилась не согласованная владельцем блокировка Save
src/editors/marker-form-state.ts:41-58 вводит glowRadiusValid() и новый
MarkerProblem marker.error_glow_radius, который через
marker-dialog.ts (canSave = ... && problems.length === 0 ...) блокирует
кнопку Save, если поле Glow radius непусто и не является положительным
числом.
На dev это поле никогда не блокировало Save: запись конфига
(houseplan-editor-runtime.ts:7991-7995, код не изменился в этом диапазоне)
молча приводит любое непустое невалидное или неположительное значение к
null (= общий радиус) прямо на сохранении. К10 в теле issue перечисляет
закрытым списком, какие условия остаются ошибками — «пустое имя, режим файла
без изображения, невалидный диапазон температур, отсутствие привязки при
выборе из HA» — Glow radius в списке нет. Ни OPEN-POINTS.md, ни решения
владельца от 20.09, ни один комментарий issue не называют эту новую
проверку. Это функциональное расширение контракта Save (AC6/К10) за пределы
переразметки, не решённое владельцем.
Сценарий: диалог устройства → «Light and glow» → Glow radius → ввести
0 или -1 → кнопка Save становится неактивной без объяснения, почему это
стало ошибкой, — на dev то же действие сохраняло маркер с общим радиусом.
Это не потеря данных и не крах, но это несогласованное расширение
поведения контракта Save, а не просто вёрстка. Fix: либо снять валидацию
(вернуть прежнее молчаливое приведение к null, как и было), либо получить
явное решение владельца о пятом условии ошибки Save и внести его в текст
К10, если задача снова пойдёт на спек-ревью, либо (раз это в рамках
code-review light track) явно задокументировать это расширение как
осознанное отступление в ACCEPTANCE.md/IMPLEMENTATION-GUIDE.md — сейчас
это выглядит как незамеченный побочный эффект.
3. Дефект №5 (поповер «?» обрезается границей диалога комнаты) не имеет свидетеля закрытия
AC8 требует «девять прямых дефектов… закрыты поимённо», «по свидетелю на каждый». Для дефекта №5 (Room settings) свидетеля нет:
- в диапазоне нет ни одной строки, адресующей клиппинг поповера (нет правки
overflow, нет промоушена.overlay-portalв top layer,hp-help.tsне тронут); ACCEPTANCE.mdне упоминает дефект №5 ни разу — только №1 (Zigbee links);- в демо-харнессе (нативный
<dialog>) клиппинг не воспроизводится ни в одну, ни в другую сторону — харнесс использует другой путь модальности, чем реальныйha-dialog(src/hp-dialog.ts:553-609,_usesHaDialog()), так что харнесс не может ни подтвердить, ни опровергнуть исходный дефект; - ни
demo/smoke_room_settings_form.mjs, ниdemo/smoke_help_affordance.mjsне проверяют клиппинг попапа границей диалога.
Не факт, что дефект жив (структура вокруг hp-help не менялась этим PR), но
AC8 требует именованного доказательства закрытия для каждого из девяти
пунктов, а не «скорее всего не мой код» — сейчас его нет ни для одного, ни
для другого исхода. Нужно либо воспроизвести на реальном ha-dialog
(методика #505) и показать, что не режется, либо признать дефект
непроверенным и решить его в этом же заходе.
4. ACCEPTANCE.md: заявлено 11 эталонных сцен, фактически принято 13
docs/design/600-settings-dialogs/ACCEPTANCE.md:28 (правился в c3e64dc6,
до принятия эталонов) утверждает «Эталоны одиннадцати сцен». Фактически
ed2af1b1 («эталоны 13 сцен диалогов настроек») меняет хэши 13 сцен в
demo/golden/baselines/baselines-index.json — расхождение 11 vs 13 нигде не
объяснено, документ не синхронизирован после приёмки эталонов. AC1/AC10
прямо требуют, чтобы расхождения были «названы», а не «примерно посчитаны» —
это тот самый принцип, ради которого заведён #600 (не доверять цифре на
глаз). Фикс — одна строка в ACCEPTANCE.md.
5. docs/USER-GUIDE.ru.md:529 — испорченная строка таблицы
| Карточки комнат | Сигнал Zigbee рядом с устройствами | Переопределяет базовую настройку карточки для этого пространства |ожки они остаются видимыми |
Хвост «ожки они остаются видимыми» — остаток удалённой строки про
декоративный слой, слипшийся с текущей ячейкой без переноса строки: лишняя
| создаёт для парсера Markdown лишний столбец, и в отрендеренной таблице
пользователь увидит обрывок чужого предложения. Английская версия
(docs/USER-GUIDE.md) аналогичной порчи не содержит — расхождение только в
RU. Раз User-Visible: yes требует правку USER-GUIDE как часть DoD (и она
входит в явный скоуп задачи), это Medium-находка внутри скоупа, а не
придирка к стилю — текст, который увидит пользователь, буквально сломан.
Что проверено и корректно (по AC)
- AC0 — референс/докс/PNG на месте, отпечаток архива сверен ранее на
спек-ревью; ничего из
docs/design/**не попадает в бандл (bundle-treeзелёный в CI). - AC1 (кроме находки №1) — прямое попарное сравнение восьми кадров
(4 диалога × 2 темы) с референсом показывает подлинный, а не косметический
паритет: сегменты, строки-тумблеры, плитки значений/цвета, компактная
плашка цвета, футер со статусом, единый скролл — всё воспроизведено по
структуре, а не только по обёртке карточек. Дублирующийся заголовок Zigbee
links (дефект №1) закрыт и подтверждён смоком (
zigbeeHeadingOnce). - AC3 — доказано исполнением на двух независимых уровнях: побайтовое
сравнение сохранённого конфига (
smoke_dialog_config_parity.mjs) и поконтрольные unit/смок-проверки «свой ключ меняется — соседний нет» для каждого нового примитива. Тесты способны падать (проверено по формулировке ассертов, не только по названию). - AC4 — все ветки К4 (Entity to toggle, What to run, Leading light
entity, Value source, температура климата, радар/пылесос, баннеры
привязки,
ha_registry_limited) сверены построчно сorigin/dev— условия видимости не изменились, изменена только обёртка в новые примитивы. - AC5 —
hp-color-opacityне переписан по семантике (диф — 6 строк, толькоhideLabel),nativeColors() === 0держится и подтверждено смоком; вокруг него ровно новая раскладка (свотч/hex/Opacity/Reset). - AC6 — dirty-tracking и confirm-on-close переиспользуют существующий
_confirmDanger(не новый механизм), Save заблокирован приbusyи ошибках, «Review N fields» реально переводит фокус на первое проблемное поле (провереноactiveElement.id, не только текстом). - AC7 —
rhintв четырёх целевых диалогах отсутствует (0 совпадений); оставшиеся вхождения (vacuum-maps-section.ts,import-диалог онбординга) — вне скоупа четырёх диалогов и не регрессия этого PR (были такими же наdev). Сообщения-состояния (К5) остаются открытыми callout-блоками. - AC9 —
role="radiogroup"/role="group"сaria-label,label[for],aria-invalid,aria-live, reduced-motion/forced-colors — присутствуют в коде и подтверждены геометрией/атрибутами в реальном DOM через Playwright (не заглушка по названию класса). - AC10 — 173/173 сцен golden проходят на материале ревью (прогнано
лично, см. «Гейты»); трейлеры
Release/Baseline-Reviewedна коммите с эталонами на месте. - AC11 — независимый замороженный фикстур панели (
summary-panel-editor.css) не тронут диапазоном, тест сравнивает его побайтово с реальным собранным CSS — доказательство не фиктивно, рост диалоговых фикстур это отдельный, явно расширяемый набор. - AC12 — потолок поднят с построчным обоснованием по сериям, факт держит запас >500 Б с обеих сторон полосы.
- Трейлеры/changelog — все 15 коммитов несут
Issue/User-Visible, единственныйUser-Visible: yesправит оба changelog в этом же коммите.
Итог
Ни одной High-находки: данные не теряются, инвариантов геометрии/замков не касается, полный набор автотестов и golden зелёные на материале ревью. Четыре находки Medium — все внутри объявленного скоупа задачи (Room dialog, Device dialog, собственная документация задачи, USER-GUIDE) — по правилу #202 не заводят отдельный issue и чинятся в этом же заходе. Возврат автору: жёлтый.
Материал раунда
- Ветка:
issue/600-settings-dialogs, коммитfa12045271f4— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
437733cefa9f7febee3f9edfed4e5290760d517agit log --all --format='%H %T' | grep 437733cefa9f - Тело issue:
2871bca5cd192e74cd862e2a4db109f96d559aecf77c8c2032369475e76aa1c6 - Вердикт конвейера:
yellow· High 0