Files
houseplan-card/docs/reviews/CODE-REVIEW-508-r1.md
T
2026-09-09 19:49:18 +00:00

14 KiB

CODE-REVIEW-508-r1

Issue: #508 — «Сводная панель: в диалоге настроек не работает прокрутка — колесом мыши и touch на мобильных» Материал: 768e1281f9119b12d018b416372984ff17eae62d (ветка issue/508-summary-dialog-scroll, ребейзнута конвейером на dev: 7e5d394a → 768e1281, +2 коммита dev). Заход: r1 · блокирующих циклов израсходовано 0 из 2. Ребейз довёл ветку до другого дерева (§7.2) → разбор полный.

Скоуп

Один коммит, класс A (src/hp-dialog.ts, src/summary-panel-editor.ts) + класс B (новый смок, контрактный юнит-тест, два мутанта) + класс C (docs/ARCHITECTURE.md, оба CHANGELOG) + класс D (пересобранный бандл во всех трёх копиях, docs/images/screenshots.json — только фингерпринт/sourceSha256, imageSha256 не изменились, что соответствует заявленному «11/11 попиксельно»).

Правка ровно та, что описана в ТЗ (docs зафиксированы в теле issue, SPEC-REVIEW-508-r1.md → SPEC-REVIEW-508-r2.md, вердикт зелёный): hp-dialog получает opt-in булев атрибут flex-content (flexcontent на ha-dialog, обе HA-ветки рендера — с described-by и без), диалог настроек сводной панели его объявляет. Нативная ветка (<dialog>) не тронута ни строкой.

J1 (docs/SCOPE.md) — обзорный диалог сводной панели входит в «show the whole home at a glance»; это чистый баг-фикс регресса после #505, никакой новой функциональности и расширения скоупа нет.

Как проверялось

Дешёвые гейты на этом SHA уже подтверждены Validate (https://github.com/Matysh/houseplan-card/actions/runs/34395116095, headSha=768e1281, job «Фронтенд: типы, юниты, мутанты, синхрон бандла» — success). Тем не менее часть из них прогнана повторно вживую, потому что дёшево и диф трогает src/** (общий компонент hp-dialog, используемый девятью другими вызывающими).

Гейт Прогнал Результат
npx tsc --noEmit + rollup (npm run bundle:sync) да сборка чистая; пересобранный dist/ и обе копии (custom_components/.../frontend, demo/srv/assets) байт-в-байт совпали с закоммиченными — git status после пересборки пуст
npm test да 2437 тестов, 2436 pass, 1 skip (известный smoke_opening_measure не запускался тут — не относится к юнитам), 0 fail
node scripts/no-new-any.mjs --base origin/dev --head HEAD да «Новых any нет» (10 добавленных строк в 2 файлах)
node scripts/check-docs.mjs (диф трогает src/**) да «7 files, 12 external links» — зелёный, sourceFingerprint в screenshots.json уже обновлён коммитом
npm run bundle:budget да initial View 298979 B (потолок 299600±2000); предупреждение о запасе <15000 Б — существующий проектный долг (#367/#474), эта задача добавляет один атрибут разметки и не сдвигает бюджет заметно; не находка этой задачи
node scripts/smoke-select.mjs --base origin/dev --head HEAD да «НЕОПРЕДЕЛЁННОСТЬ» — 2 файла, 0 символов на изменённых строках, порог «широкого» не достигнут. Решение ревьюера ниже
node demo/smoke_summary_dialog_scroll.mjs (AC1/AC2, новый) да OK, все 14 проверок зелёные (desktop wheel/footer, phone touch/viewport, witness без flexcontent)
node demo/smoke_summary_panel_polish.mjs (AC3, прямое совпадение — единственный смок диалога панели) да OK; файл смока не менялся (git diff пуст) — «без правок его ожиданий» выполнено
node demo/smoke_dialog_modal_recovery.mjs, node demo/smoke_esc_dialogs.mjs, node demo/smoke_dialog_footer_width.mjs (зарегистрированная связь: общий hp-dialog, фокус/эскейп/футер) да все три OK
node scripts/mutation-gate.mjs --id=summary-dialog-drops-flex-content да «чистый прогон» → зелёный, мутация → «тест покраснел, как обязан», поймано 1 из 1
node scripts/mutation-gate.mjs --id=hp-dialog-ignores-flex-content да то же, поймано 1 из 1
in-memory негативная проба AC4 (test/hp-dialog-contract.test.mjs, добавление flex-content в hp-confirm.ts только в памяти интерпретатора, без записи в дерево) да ассерт === 1 дал бы 2 → тест обязан покраснеть

Не прогонял: полный golden:verify, python -m pytest tests_backend, полную матрицу смоков (237 шт.), performance-профили.

  • golden — диф не меняет ни один существующий рендер: атрибут opt-in, ?flexcontent= в Lit убирает атрибут целиком при false, поэтому разметка всех прочих hp-dialog не меняется ни на байт; golden снимается в demo-стенде, где вообще используется нативная ветка (<dialog>), которую диф не трогает. golden-сцен с префиксом summary в репозитории нет (снято ещё в SPEC-REVIEW r1 M1, AC3 переписан на единственный смок). Риск нулевой — не прогонял намеренно, а не пропустил.
  • pytest tests_backend — диф не касается custom_components/houseplan/**/*.py вовсе.
  • Полная матрица смоков и перф-профили — не названы в AC; smoke-select вернул «НЕОПРЕДЕЛЁННОСТЬ», выбор сузил вручную до прямого совпадения (диалог панели) и зарегистрированных связей (общий hp-dialog: восстановление модального стека, Escape-стек, ширина футера). Остальные 231 смока к этому диффу не привязаны сильнее «общий компонент» и по правилу прогона не требуют.

AC → доказательство → чем краснеет

AC Доказано Чем краснеет
AC1 (HA-ветка: колесо крутит .summary-editor, шапка/футер неподвижны) demo/smoke_summary_dialog_scroll.mjs сценарий (a), прогнан лично — OK оба мутанта summary-dialog-drops-flex-content и hp-dialog-ignores-flex-content, прогнаны лично: «покраснел, как обязан»
AC2 (touch 390×844 крутит редактор; диалог ≤ вьюпорта; футер доступен) сценарий (b), прогнан лично — OK те же два мутанта (общий guard — атрибут)
AC3 (нативная ветка не меняется, smoke_summary_panel_polish зелёный без правок ожиданий) прогнан лично — OK; git diff файла смока пуст не защитный AC (сравнение факта с ожиданием, не guard) — правило §2.7 не требует красноты
AC4 (прочие hp-dialog без flex-content не меняют раскладку) test/hp-dialog-contract.test.mjs (в составе npm test, прогнан лично) + чтение: ?flexcontent= в Lit опускает атрибут при false, значит разметка девяти прочих вызывающих идентична in-memory негативная проба (см. выше, без записи в дерево): подмена показала бы optIns.length === 2 → ассерт === 1 красный
AC5 (оба мутанта пойманы штатным раннером) сами прогоны мутантов, см. выше это и есть доказательство; вторичной пробы не нужно
AC6 (User-Visible: yes, оба changelog, i18n/модель/перф не задеты) git diff коммита: docs/CHANGELOG.md + docs/CHANGELOG.ru.md в том же коммите; ни один translations/**/i18n файл не в диффе; в src/hp-dialog.ts нет новых обработчиков — только проброс булева атрибута в существующий рендер не защитный AC (перечисление фактов), доказано чтением диффа

Что проверено и корректно

  • Обе HA-ветки рендера (describedBy и без) получили ?flexcontent=${this.flexContent} — прочитано в src/hp-dialog.ts:515,531, подтверждено юнит-тестом и мутантом.
  • Нативная <dialog>-ветка (_usesHaDialog() false) не содержит ни одного упоминания flexcontent/CSS-селектора на :host([flex-content]) — сброс атрибута не имеет побочных эффектов там, где используется golden/demo.
  • Диагноз (scroll chaining обрывается на height-unbound overflow:auto + overscroll-behavior:contain) independently подтверждён автором на живом стенде HA 2026.9.1 (ha-dialog.flexContent = true → .summary-editor 477/819, колесо работает) — это сильнее, чем воспроизведение только в стабе; стаб смока копирует контракт .body{overflow:auto} + :host([flexcontent]) .body{display:flex}, о котором говорит и уже существующий комментарий demo/capture_summary_panel_505.mjs (#505).
  • docs/TOUCH-SUPPORT.md:48 прямо называет «simple settings form» сводной панели гарантированной View- touch-поверхностью — включение touch в скоуп задачи не расширение, а обязательное соответствие контракту.
  • Терминология changelog («сводной панели», «настройки») совпадает с docs/USER-GUIDE.ru.md:264.
  • docs/images/screenshots.json: только sourceFingerprint/sourceSha256 изменились, все imageSha256 идентичны — соответствует заявленному в хендоффе docs:accept --identical (11/11 пиксель-в-пиксель, первое боевое использование #512).
  • Один коммит, трейлеры Issue: #508 и User-Visible: yes на месте; Release:/Baseline-Reviewed: не требуются — demo/golden/baselines/** не тронут.
  • Скоуп не расширен: единственный вызывающий (summary-panel-editor.ts) объявляет атрибут, остальные восемь — нет (проверено и тестом, и построчным чтением).

Находки

Нет. High: 0, Medium: 0, Low: 0.

Унаследовано из ревью ТЗ (для полноты трассировки)

Спек-ревью прошло два раунда (SPEC-REVIEW-508-r1.md → жёлтый, SPEC-REVIEW-508-r2.md → зелёный на SHA f02af9330b17a37eea6c8086f4335cbbc7265d36): M1 (AC3 ссылался на несуществующий golden-префикс summary-*) и M2 (не было раздела «откат») закрыты правкой тела issue. Код-ревью это не переоткрывает — AC3 в теле issue на момент код-ревью уже опирается только на smoke_summary_panel_polish, а раздел «Откат» присутствует и соответствует факту (opt-in атрибут, ревёрт коммита без миграций).

Вердикт

Зелёный. Все AC доказаны исполнением (кроме AC3/AC6, не заявляющих защиты — доказаны прямым сравнением), диагноз независимо подтверждён на реальном стенде HA, оба защитных мутанта лично прогнаны и красные без патча/зелёные с ним, регресс на нативную ветку и остальные восемь hp-dialog исключён и тестом, и построчным чтением кода. Находок нет.


Материал раунда

  • Ветка: issue/508-summary-dialog-scroll, коммит 7e5d394a936a — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: 1594677359b610e22fc5886db02f66bc3c943322
    git log --all --format='%H %T' | grep 1594677359b6
    
  • Вердикт конвейера: green · High 0