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-editor477/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— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
1594677359b610e22fc5886db02f66bc3c943322git log --all --format='%H %T' | grep 1594677359b6 - Вердикт конвейера:
green· High 0