mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 20:29:00 +00:00
@@ -0,0 +1,123 @@
|
||||
# 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` исключён и тестом, и
|
||||
построчным чтением кода. Находок нет.
|
||||
|
||||
---
|
||||
|
||||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||||
|
||||
## Материал раунда
|
||||
|
||||
- Ветка: `issue/508-summary-dialog-scroll`, коммит `7e5d394a936a` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||||
- Дерево материала: `1594677359b610e22fc5886db02f66bc3c943322`
|
||||
```
|
||||
git log --all --format='%H %T' | grep 1594677359b6
|
||||
```
|
||||
- Вердикт конвейера: `green` · High 0
|
||||
Reference in New Issue
Block a user