23 KiB
CODE-REVIEW-602-r1
Issue: #602 · Этап: code · Заход: r1 · блокирующих циклов израсходовано 0 из 4
Материал: c69d73f1f26b3277fde938a4eb08195c561e46ba (issue/602-dialog-polish), рабочая копия ревью — на этом SHA, без отклонений (git status чист на всём протяжении разбора).
Скоуп
ТЗ (принято владельцем на этапе spec, зелёный вердикт r2) устраняет визуальные дефекты общего form-kit после редизайна #591/#600/#601 в четырёх диалогах (Space, General settings, Room settings, Device) плюс общий confirm-dialog: подписи-дубли, центрирование тумблера, обрезание числовых полей, выравнивание подписей шкалы, однострочные футеры, порядок climate-настройки и замена кнопки объявления радара на inline-тумблер с сохранением черновика между Off/On в рамках сессии.
Диапазон разбора — git diff origin/dev...HEAD (82 файла: src/**,
demo/smoke_*, test/**, локали, docs/**, dist/** и зеркала бандла).
Полный код-ревью, поскольку это первый заход по задаче.
Как проверялось
| Гейт | Статус | Как проверено |
|---|---|---|
typecheck / npm test / npm run build + сверка 3 копий бандла |
зелёный, не перегонял | Validate на точном материале c69d73f1 — прогон 35535791268, conclusion: success, headSha совпадает. Подтверждено запросом gh run view. Мутанты по диффу (6/6 шардов) там же зелёные. |
node scripts/check-docs.mjs --strict |
зелёный, перегонял сам | node scripts/check-docs.mjs --strict → «Documentation checks passed (7 files, 12 external links)». Отпечаток src/** актуален после npm run bundle:sync на чистом дереве. |
npm run bundle:sync (пересборка для последующих шагов) |
выполнялся трижды (перед смоками, после каждой мутации) | git status --short пуст после каждого прогона — рабочее дерево не отклонилось от c69d73f1. |
Смоки — 7 файлов, изменённых в этом диффе (smoke_device_settings_form, smoke_space_settings_form, smoke_dialog_footer_width, smoke_radar_setup, smoke_climate_temp, smoke_general_settings_form, smoke_room_settings_form) |
зелёные, перегонял сам | Каждый запущен node demo/smoke_<name>.mjs, все напечатали OK без FAILED. scripts/smoke-select.mjs --base origin/dev --head HEAD вернул одно прямое совпадение (smoke_radar_setup.mjs ← _markerDialog, isActive, уже в списке выше) и 39 «слабых связей» по одному распространённому имени _markerDialog — по прочтении диффа это ожидаемо (символ есть почти в каждом смоке, трогающем диалог устройства), решил не гонять весь список: ни один из 39 не адресует изменённые CSS-контракты специфично. |
| Мутационная проверка «чем краснеет» для двух защитных AC | выполнил вручную (см. таблицу ниже) | См. раздел «Защитные AC». |
npm run golden:verify |
красный, перегонял сам | См. находку High/Medium ниже — 8 из 150 сцен different. |
npm run inventory, python -m pytest tests_backend |
не гонял | diff не трогает custom_components/**/*.py; нет причины запускать. |
| Performance-профили | не гонял | Не названы в AC, диффом не задеты чувствительные к перфу пути (ТЗ §13 подтверждает). |
Находки
Medium (в скоупе) — golden:verify красный на материале ревью, обновление golden-сцен из плана тестирования не сделано
ТЗ, план тестирования, п.4: «Обновить/добавить golden scenes минимум для
Space desktop/mobile, Device desktop/mobile с radar off/on, discard confirm
mobile…». AC15 прямо требует: «…golden:verify зелёные». Ни один файл под
demo/golden/baselines/** в диффе не изменился (git diff origin/dev...HEAD --stat -- demo/golden — пусто).
Автор сам отметил риск в комментарии реализации r1: «golden:verify на
Windows выполнен диагностически и baseline не менял… канонический кандидат
должен снять Linux CI… Прошу проверить… Linux visual artifacts» — то есть
явно передал этот пункт на сторону код-ревью. Ревью выполняется на Linux, я
запустил канонический прогон сам:
npm run golden:verify
Результат — 8 из 150 сцен different (не passed): device-dialog-desktop-en,
device-dialog-mobile-ru, device-dialog-desktop-de, device-help-popover-light-ru,
settings-help-zoom-200-en-light, settings-help-zoom-200-ru-dark,
device-ripple-color-popover-mobile-ru, space-room-color-popover-desktop-ru.
По коду demo/golden/policy.mjs:63 (goldenRunFailed) mode==='verify' и
любой статус !== 'passed' переводит process.exitCode = 1 — гейт красный,
а не «прошёл с примечаниями».
Диф-артефакт artifacts/golden/diff/device-dialog-desktop-en.png показывает
ожидаемые различия: убранный текст «Unsaved changes» в футере, изменённая
раскладка поля/шкалы «Brightness», сдвиг блока «Glow radius» и всего, что
ниже — то есть разница закономерно объясняется именно этим диффом (CSS
.hpf-range, .hpf-toggle, footerStatus), а не посторонней регрессией.
Значит правки корректны по существу, но обещанный тестовый артефакт
(обновлённые/принятые baseline-кадры) отсутствует.
Чем краснеет: сам факт — npm run golden:verify красный на предъявленном
материале; это не гипотетическая мутация, а фактический прогон канонического
гейта, который AC15 требует зелёным. Артефакты прогона удалены после
проверки (rm -rf artifacts/golden), рабочее дерево осталось чистым на
c69d73f1.
Что нужно: принять новые кадры через npm run golden:accept -- --reviewed (плюс scripts/golden-accept.mjs --expect-change=<8 id>) на
канонической Linux-сборке этого материала, с коммитом, несущим Release: и
Baseline-Reviewed: (путь уже отработан на этой же ветке коммитом
c69d73f1 для отдельного, независимого от demo/golden/baselines
docs-скриншотного механизма — check-docs.mjs/docs/images/**, который сам
по себе закрыт корректно и трейлеров не требовал, поскольку не трогает
demo/golden/baselines/**; это подтверждено чтением
scripts/validate-commit-provenance.mjs:64-77, где триггер — только
demo/golden/baselines/*.{png,json}).
Серьёзность: Medium, в скоупе задачи (сам ТЗ называет это условием приёмки AC15 и явно перечисляет затронутые сцены в плане тестирования) — без High это жёлтый вердикт, фикс проходит следующий цикл в этом же issue (#202).
Проверено и корректно
- AC1/AC8 (лишние подписи и заголовок убраны).
space.title_hint,space.scale_hint,marker.name_hint, заголовок «Tap action» удалены из разметки и всех 4 locale-файловi18n/settings/*.jsonсинхронно (RU/EN/DE/FR);marker.card_tapсинхронно вычищен изi18n/{ru,en,de,fr}.json. Подтверждено смокамиredundantBasicsHintsRemoved,nameFieldWithoutRedundantHint,tapActionCardHasNoLargeHeading— все зелёные при собственном прогоне. - AC2 (центрирование тумблера).
top: 50%; transform: translateY(-50%)вform-kit.styles.ts; смокswitchPartsAreGeometricallyCenteredсравнивает computed::before/::afterс серединой контрола (допуск 0.6px). Чем краснеет: вручную вернулtop: 14px, пересобрал (npm run bundle:sync), прогналsmoke_space_settings_form.mjs— упал именно на этом поле (switchPartsAreGeometricallyCentered: expected true, got false), затем восстановил файл и пересобрал;git statusпосле — чист. - AC3/AC5 (числовые поля не обрезаются/не перекрывают слайдер). Ширина
.hpf-unitдля прозрачности увеличена до4.8em,.hpf-rangeпереведён на grid с фиксированной колонкой значения (--hpf-range-value-width: 84px). Смоки проверяют реальныеgetBoundingClientRect():opacity100FitsTheNumberField,sizeAndAngleFieldsDoNotCoverSliders(slider.right < unit.left, no overflow) — зелёные. - AC4 (подписи шкалы совпадают с концами трека).
rangeEnds()теперь оборачивает подписи в.hpf-range-ends-track, выровненныйgrid-column: 1с тем же84pxрезервом под значение, что и сам слайдер — единый источник ширины вместо независимогоpadding-right: 122px. СмокfontScaleEndsMatchTheSliderсверяет пиксельные левый/правый края трека и подписей (допуск 1px) — зелёный. - AC6/AC7/AC14 (однострочные футеры, mobile icon-only, confirm в одну
строку, 320/360/560).
dialogsStyles:.hpf-footer-space— CSS grid (auto auto minmax(0,1fr) auto) с явными позициямиdialog-action-copy/dialog-action-danger/dialog-action-commit; на ≤480px колонки сжимаются до44px 44px minmax(0,1fr) auto,.hpf-mobile-iconпрячет текстовую метку и оставляетaria-label+title..danger-confirm-footerполучил тот жеnowrap/grid-на-двух-кнопках паттерн. Смокиsmoke_dialog_footer_width.mjs(oneRow,iconOnlyDestructive, ширины 320/360/560, RU/DE) иsmoke_space_settings_form.mjs(discardActionsShareOneRow) — зелёные при собственном прогоне. - AC9 (climate-toggle после Additional actions). Блок
toggleRowдляuseClimateTempфизически перенесён вmarker-dialog.tsпод_renderRadarSection(..., 'additional')внутри карточки Details. СмокclimateToggleFollowsAdditionalActionsсравниваетNode.DOCUMENT_POSITION_FOLLOWINGотносительно заголовка «Additional actions» — зелёный. Значение/dirty-detection/сохранениеuseClimateTempне тронуты (тот же биндингd.useClimateTemp). - AC10 (нейтральный
dialog.unsavedубран). Убран из всех четырёхfooterStatus(...)вызовов (space-form.ts,general-settings-dialog.ts,room-settings-dialog.ts,marker-dialog.ts) и из всех 4 locale JSON.dialog.review_fields(ошибки полей) и dirty-controlledSaveне тронуты — смокиsaveEnabledWhenDirtyWithoutDuplicateStatus(4 файла) зелёные. - AC11–AC13 (радар: единая секция-тумблер, восстановление черновика,
virtual/saved-unsupported).
renderRadarSectionтеперь всегда рендерит одинtoggleRowв.radaradditionalвнутри Details; конфигурация раскрывается:scope > .radargroupстрого под тумблером, вторая секция в «Основных параметрах» отсутствует (проверено смокамиradarEntryIsAToggleInAdditionalActions,noAutomaticSection). Ключевое изменение поведения —remove()теперь сохраняетradar: d.radarвместоradar: null, так что Off не теряет черновик. Чем краснеет: вручную вернулradar: nullвremove(), пересобрал, прогнал оба смока:smoke_device_settings_form.mjsупал наradarOffKeepsSessionDraftиradarOnRestoresSessionDraft;smoke_radar_setup.mjsупал наsavedOffKeepsOriginal/savedOnRestoresOriginal; восстановил файл, пересобрал,git status— чист.savedUnsupported/virtualветки кода структурно не тронуты (тот жеif (savedUnsupported || ...)), только обёрнуты в общий.radaradditionaldiv с тем же тумблером сверху — подтверждено смокомvirtualHasNoEntry. - AC16 (release artifacts).
docs/CHANGELOG.md+docs/CHANGELOG.ru.mdправлены в том же коммите746147d0(User-Visible: yes), формулировки не содержат внутренних CSS-классов/имён state-полей/номеров golden-сцен — прочитано текстом, ссылка на #602 есть в обеих версиях.docs/USER-GUIDE.mdи.ru.mdсинхронно обновлены по описанным разделам (Basics/Tap action/Details/Presence radars) — терминология («Additional actions», «This is a presence radar», «Review N fields») совпадает с уже используемой в остальном руководстве, не изобретена заново. - Трейлеры.
746147d0:Issue: #602,User-Visible: yes— оба changelog в этом же коммите (провереноgit show --stat).c69d73f1:Issue: #602,User-Visible: no, плюсRelease:/Baseline-Reviewed:— эти два трейлера не были обязательны для этого конкретного коммита (он не трогаетdemo/golden/baselines/**, толькоdocs/images/**, аscripts/validate-commit-provenance.mjs:64триггерит их только на первый путь), но лишние трейлеры не нарушают провенанс и отражают реальный Linux-прогон 35534819172.
Отдельно проверенный риск, оказавшийся не дефектом
Заподозрил, что новая формула active = !!d.radar && !d.radarRemove
(radar-section.ts:88) включает тумблер «Это радар присутствия» сразу при
открытии диалога не только для уже сохранённого радара (что явно
разрешено §6.6 ТЗ), но и для любого распознанного, но никогда не
сохранённого устройства (LD2450-профиль) — поскольку radarDraft()
(radar-editor.ts:154-165) возвращает ненулевой драфт уже при
recognition.eligible === true, независимо от наличия .original, а
_openMarkerDialog (houseplan-editor-runtime.ts:7586) вызывает эту функцию
безусловно при каждом открытии.
Проверил исполнением: поднял демо-стенд (npm run bundle:sync +
demo/serve.mjs), сконструировал синтетическое устройство с моделью
HLK-LD2450 и entity-парой ..._target_1_x/_y, зарегистрированным вне
HA-registry (чтобы recognizeRadar не подобрала настоящую модель из
реестра), открыл _openMarkerDialog. Результат: radarEligible: true,
radar.enabled: false, radarRemove: false, тумблер checked: true, форма
раскрыта — то есть подозрение подтвердилось техническим фактом.
Но дальше проверил, было ли это поведением, привнесённым этим диффом:
_openMarkerDialog и radarDraft() не входят в диапазон
git diff origin/dev...HEAD — они не менялись. Прочитал версию
radar-section.ts на origin/dev (git show origin/dev:...): условие
if (!d.radar || d.radarRemove) { if (recognition.eligible) {…fieldset 'Настроить'…} return html''; } const radar = d.radar; …полная форма… при
уже ненулевом d.radar (тот же факт, та же функция) точно так же пропускало
ветку «detected, click Настроить» и сразу показывало полную форму — то есть
поведение «распознанный радар раскрывается сразу» уже существовало в dev
до #602, просто было завёрнуто во внутренний чекбокс radar.enabled вместо
внешнего именованного тумблера. Формулировка §6.6 «для нового/ещё не
настроенного радара повторное включение… возвращает draft» описывает цикл
Off→On внутри сессии, а не начальное состояние впервые открытого диалога, и
не противоречит найденному факту. И docs/USER-GUIDE.md/.ru.md (раздел
«Presence radars»), поставленные этим же диффом, прямым текстом описывают
именно это как задуманное поведение: «For a recognized presence radar,
Additional actions contains the enabled This is a presence radar
switch…» — не гипотеза, а согласованный текст руководства. Вывод: не
регрессия и не находка, а унаследованное и теперь корректно
задокументированное поведение; фиксирую как проверенный и закрытый риск.
Чего не проверял
npm test/npm run typecheck/npm run build— не перегонял сам, положился на зелёный Validate на точном SHAc69d73f1(см. таблицу). Дешёвые гейты §10.2/§2.10 разрешают это при неизменном материале.- Полная матрица
demo/smoke_*.mjs(258 файлов) — не гонял. Инструмент выбора дал одно прямое совпадение (прогнано) и 39 слабых связей по общему имени_markerDialog; полный прогон — предрелизная обязанность (§8), не ревью. python -m pytest tests_backend— не гонял, diff не касаетсяcustom_components/**/*.py.npm run invariants— не гонял: diff не трогает геометрию комнат/стен,layout,marker.spaceилиopen_spans.- Performance-профили — не гонял, не названы в AC и не задеты диффом (ТЗ §13 подтверждает отсутствие новых циклов рендера/таймеров).
- Полный
npm run golden:verifyc последующимaccept— гейт я выполнил (см. находку), но фактическое принятие новых baseline-кадров — действие автора, не ревьюера (роль «ревьюер не правит материал»). - Ручная проверка в браузере HA — не проводилась (среды HA нет); доказательство — DOM/computed-style смоки плюс golden-диф-изображение, прочитанное визуально.
Материал раунда
- SHA ветки:
c69d73f1f26b3277fde938a4eb08195c561e46ba(issue/602-dialog-polish) - Дерево материала: идентично рабочей копии на момент вывода вердикта
(
git status --shortпуст непосредственно перед выводом). - ТЗ: тело issue #602, зелёный вердикт спецификации r2
(
Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0).
Материал раунда
- Ветка:
issue/602-dialog-polish, коммитc69d73f1f26b— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
ce2edc2506f2f52730a2fa576341f7eabf2dfccfgit log --all --format='%H %T' | grep ce2edc2506f2 - Тело issue:
8f8081ec29db1c104189682530643bde421d91127170b2c635107c595950a5a9 - Вердикт конвейера:
yellow· High 0