Files
houseplan-card/docs/reviews/SPEC-REVIEW-579-r1.md
T
2026-09-14 19:01:08 +00:00

256 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-579-r1
- Issue: #579 — «HA Companion: при pinch-zoom стены и пол мигают белым или прозрачным»
- Этап: spec (PROCESS.md §2.4), заход r1, блокирующих циклов израсходовано 0 из 4
- Материал: тело issue #579 целиком (аналитика → `## ТЗ`, 14 разделов) плюс один
комментарий-аналитика (`S2`), метка `S4-spec-review`; предыдущих раундов
ревью в issue нет — это первый заход, разбор полный, раздела «Унаследовано»
не требуется
- Проверено на `dev` @ `856a2c6e1238d5cb735f0c4676af2f2d8edfc838` (рабочая копия
уже на этом SHA — сверено `git rev-parse HEAD` и `git log -1`); тот же SHA
назван автором в комментарии-аналитике как точка проверки
## Скоуп ревью
Ревью ТЗ, не кода: `src/live-viewport.ts` ещё не менялся (issue открыт,
продуктового коммита по #579 нет). Вопрос — выполнимо ли и однозначно ли ТЗ,
доказуем ли каждый AC, не выдана ли догадка за факт, верно ли выбран полный
трек, и нет ли продуктового вопроса, спрятанного в тексте под видом
технического.
## Как проверялось
1. Прочитан `docs/SCOPE.md`: задача чинит поломку J1 («Show the whole home and
what's happening right now») ровно в той поверхности, которую SCOPE.md
называет решающей для двух из трёх персон — «View mode is the product»
(Household members, Guests/kiosk). Новой job не открывает, рамку не
расширяет.
2. Прочитаны `AGENTS.md` и PROCESS.md §2.4, §2.9/2.10 (не применимо — r1),
§4, §7.1, §7.2.
3. Прочитано тело issue #579 целиком: аналитический блок до `## ТЗ` (причина,
почему тесты зелёные, связанные issue, диагностическая развилка, границы
исправления, покрытие будущей реализации) и сам `## ТЗ` (14 разделов).
Прочитан единственный комментарий — S2-аналитика владельца, подтверждающая
механизм на этом же SHA и явно снимающая продуктовые вопросы.
4. Прочитан `docs/TOUCH-SUPPORT.md`: pinch/pan в View и kiosk — гарантированная,
release-blocking поверхность («support convenient pan, pinch zoom and space
switching», «A touch-only failure in View is a product defect, not an
accepted limitation»). Задача лежит строго внутри этого контракта, не
пытается его расширить или сузить.
5. Прочитан `docs/CANVAS.md` §5 (Zoom and pan): «Pinch and pan remain direct
1:1 gestures», модель zoom/pan/anchor не входит в скоуп задачи и прямо
исключена в её не-скоупе — согласовано.
6. Прочитан `docs/ARCHITECTURE.md`, раздел «Live viewport: a transform per
frame, a `viewBox` on a budget (#531, 2026-09-11)» — канонический текст
подсистемы. Он же документирует ныне действующее поведение, которое ТЗ
собирается менять: «A budget refresh or the terminal commit removes the
inline overflow together with the transform» — совпадает с описанием
дефекта в ТЗ дословно, не расходится.
7. **Технический механизм дефекта сверен построчно с кодом на названном SHA**,
а не принят на веру:
- `src/live-viewport.ts:190-205` (`paintLiveViewport`): на каждом кадре
сцена-SVG проецируется от `next.frame` к `current` через
`liveLayerProjection`; при бюджетном refresh (`needsViewBoxRefresh`,
`:93-103`, 100 мс либо сдвиг/масштаб 15 %) `next.frame` становится равен
`current` → проекция тождественна → `isIdentityLiveLayerProjection`
возвращает `true` → в `setLayerProjection` передаётся `null`
(`:192-205`), что физически снимает `transform`/`transform-origin`/
`will-change` и (при `exposeSceneOverflow`) `overflow` (`:112-146`,
ветка `!projection`). Следующий кадр жеста снова получает нетождественную
проекцию и заново промотирует слой. Это ровно тот churn
промотирования/демотирования, который ТЗ называет причиной дефекта —
подтверждено чтением, а не предположением.
- `commitHouseplanViewport` (`:255-270`) вызывается из
`LiveInteractionRuntime.commit()` (`src/live-interaction-runtime.ts:79`),
который, в свою очередь, вызывается из `protected updated()`
(`src/houseplan-card.ts:4202-4203`) — то есть **на каждом полном Lit-цикле
обновления**, включая конец программной камеры (переход анимации меняет
реактивные `_view`/`_zoom`, что вызывает `requestUpdate` → `updated()`).
Это подтверждает контрактный пункт 4 и 9 ТЗ («тот же lifecycle для
pointerup/pointercancel/lostpointercapture/программной камеры/обычного
Lit-коммита, не по user-agent») без необходимости заводить новые
обработчики событий — он уже общий по архитектуре.
- `setLayerProjection` (`:112-146`) уже содержит write-only-on-change
защиту (`if (style.transform === text) return;`, `:142`, и аналог для
`viewBox` в `setViewBox`, `:149-151`) — то есть требование АС1/АС2/АС9
«держать identity transform явно установленным без лишних записей на
неизменный кадр» технически совместимо с существующим примитивом: нужно
не убирать значение, а один раз записать identity-текст, дальше сравнение
строк само не даст повторных записей.
- Селектор `data-hp-live-viewbox="camera"/"floor"` (`houseplan-card.ts`,
множественные точки: iso underlay/shadows/walls/overlays, flat/iso
floor-svg, `vactrail`, `radar-ranges`) уже общий (`querySelectorAll`, без
перечисления по имени) — значит формулировка ТЗ «каждый scene-SVG с
`data-hp-live-viewbox=camera/floor`» реализуется тем же общим
механизмом, не требует адресного списка и не рискует пропустить один из
iso-слоёв.
- Риск ТЗ «изометрия имеет несколько SVG с разными camera/floor
проекциями; применение одной матрицы ко всем запрещено» — подтверждён:
в коде камера и floor действительно проецируются раздельно
(`sceneCamera`/`sceneFloor`, `:190-191`, отдельные querySelectorAll на
`:192` и `:199`), общий рефакторинг рискует их случайно смешать, и ТЗ
этот риск называет явно, а не молчит о нём.
8. Сверены названные в плане автотестов файлы/инструменты — все существуют,
ни один не выдуман: `test/live-viewport.test.mjs`,
`demo/smoke_live_pan_coverage.mjs`, `demo/screencast_visual_continuity.mjs`
(CDP screencast harness, npm-скрипт `continuity:screencast`),
`demo/smoke_editor_gestures.mjs`, `demo/smoke_smooth_zoom.mjs`,
`demo/smoke_tap_ctx.mjs`, `demo/smoke_long_press_gesture.mjs`,
`demo/smoke_isometric_live_touch.mjs`, `scripts/mutation-gate.mjs`.
9. Попытка воспроизвести приведённый в issue вывод
`node demo/smoke_live_pan_coverage.mjs` (`OK — 37/37 checks green`) —
локально упала на старте (`Failed to fetch dynamically imported module`,
таймаут `page.waitForFunction`), потому что нет собранного `dist/**`
(продуктовый код по #579 не менялся, бандл собирался в другой раз). Это
ожидаемо для стадии spec (гейты и сборка сюда не относятся, см. ниже) и не
ставит под сомнение вывод: сам механизм дефекта я подтвердил прямым чтением
исходников (п.7) — более надёжным способом, чем повторный прогон уже
процитированного зелёного смока.
10. Проверено согласование трека: `small` (PROCESS.md §5) требует одну
поверхность и отсутствие нового touch/производительного контракта —
задача одновременно трогает compositor lifecycle, кросс-браузерную
производительность (Chrome/Firefox) и touch-контракт View/kiosk; владелец
в аналитике явно пишет «лёгкий трек: нет» с обоснованием. Полный трек
выбран верно.
Гейты (`typecheck`/`test`/`build`/`golden`) на этой стадии не прогонялись и не
нужны: продуктового кода по #579 ещё нет, предмет проверки — текст ТЗ, а не
бинарник (см. п.9 выше — попытка была сделана ровно для проверки
воспроизводимости цитаты из issue, а не как обязательный гейт стадии).
## Находки
Ни одной High или Medium находки.
- **Проверено и не является находкой: возможная догадка о причине.** Аналитика
(до `## ТЗ`) прямо помечает локализацию как «подтверждённая, но не доказанный
конкретный фикс» и требует A/B-развилку из 5 вариантов до выбора реализации.
Раздел «Принятые предположения» отдельно и честно выделяет «причина — churn
promotion/demotion, а не конкретный фильтр/геометрия» как предположение,
которое можно поменять на ревью. Я сам подтвердил именно этот механизм прямым
чтением кода (см. «Как проверялось», п.7) — то есть то, что в ТЗ помечено как
предположение, на самом деле уже доказано чтением; это не ухудшает ТЗ (лишняя
осторожность автора не вредит), поэтому не является дефектом текста.
- **Проверено и не является находкой: чем закрывается AC10 без физического
устройства.** ТЗ прямо возлагает финальную полевую проверку на владельца
перед закрытием beta candidate и разрешает честно отметить «невыполненная
полевая приёмка» вместо подмены Chromium-скриншотом. Это не новый прецедент:
`docs/reviews/CODE-REVIEW-544-r1.md` («физическое устройство — не локальное
доказательство») ровно так же выносит полевую проверку за пределы
автоматического доказательства того же самого подкласса дефекта (Companion
WebView). Формулировка AC10 не создаёт скрытого продуктового вопроса и не
блокирует «Готово к разработке» — критерий доказуем в том виде, в каком
сформулирован.
- **Проверено и не является находкой: смешение технического и продуктового
вопроса.** Раздел «Продуктовые вопросы владельцу: нет» обоснован —
пользовательский контракт («ни один показанный кадр не теряет пол/стены»)
уже зафиксирован в `docs/TOUCH-SUPPORT.md` как release-blocking для View, а
выбор конкретного compositor-механизма — техническое решение авторов
(§7.1: «где хранится состояние… агенты решают сами»), корректно вынесенное
в раздел «Технический механизм требует A/B до реализации», а не владельцу.
## Проверка обязательных разделов (PROCESS.md §7.1)
Все обязательные разделы на месте и в правильном порядке: сценарий · что
человек увидит до и после · проблема · скоуп и не-скоуп · контракт поведения
(9 пунктов) · UX · модель данных и миграция (эфемерное состояние
`LiveViewportState`, явно «новых полей/ключей/миграций нет») · i18n (явно
«новых строк нет») · критерии приёмки AC1–AC10, каждый со способом
доказательства · план автотестов · риски · откат · release-артефакты. Плюс
полезные необязательные разделы: аналитика по коду, диагностическая развилка,
предварительные границы, требуемое покрытие будущей реализации, приоритет и
трек, принятые предположения.
- **Сценарий/что человек увидит** — персона-центричны («пользователь Home
Assistant… в Companion на телефоне»), без терминов реализации; «промежуточный
кадр», «pinch», «zoom», «View», «kiosk» — уже канонические термины подсистемы
(`CANVAS.md`, `TOUCH-SUPPORT.md`, `USER-GUIDE.ru.md`), не изобретены задачей.
- **AC однозначны и проверяемы.** Каждый из AC1–AC10 называет способ
доказательства (unit/browser smoke/screencast/mutation witness/полевая
проверка), большинство — с отрицательным свидетелем (AC1 и AC6 явно требуют
красного mutation witness; AC10 явно требует красного текущего поведения на
реальном устройстве до фикса — это условие «тест умеет падать» будущего
код-ревью, зафиксированное уже в самом ТЗ).
- **Скоуп/не-скоуп** согласован с `SCOPE.md` (J1) и `TOUCH-SUPPORT.md`
(View/kiosk — гарантированная поверхность); не расползается: явно исключены
геометрия плана, диапазон/якорь/инерция zoom, UA-sniffing, отключение
эффектов, рефакторинг Companion/Chromium, публикация изометрии.
- **Контракт поведения** (9 пунктов) внутренне непротиворечив и совместим с
существующим кодом: п.7 («частота записей viewBox не возрастает») не
конфликтует с п.2/п.3 («identity transform остаётся явно установленным») —
это разные атрибуты (`viewBox` vs `style.transform`), и существующий
write-on-change примитив (`:142`, `:149-151`) их не смешивает.
- **Принятые предположения** оформлены отдельным блоком «можно менять на
ревью» с явным обоснованием каждого пункта — ровно то разделение
«продуктовое решает владелец, техническое — авторы», которого требует §7.1.
Открытых продуктовых вопросов, замаскированных под технические, не найдено.
- **Риски** называют конкретные точки провала предположения (GPU-память
постоянно промотированного слоя; WebView может мигать и внутри уже
стабильного layer — с названным следующим техническим вариантом на этот
случай; identity transform может дать другой settled raster — с названным
контролем через golden/cleanup-unit; смешение iso camera/floor проекций) —
не декоративные, а операционные.
- **Откат** — ревертом единственного продуктового коммита, без данных/миграции;
единственная оговорка (возврат к покадровому `viewBox` как временная мера при
доказанной GPU-регрессии) явно помечена как повторное открытие исходного
дефекта, не как тихий компромисс.
## Что проверено и корректно
- Технический механизм дефекта (churn промотирования/демотирования scene-SVG
на каждом budget refresh) подтверждён построчным чтением `live-viewport.ts`
и `houseplan-card.ts` на названном SHA, совпадает и с текстом ТЗ, и с
действующим описанием в `docs/ARCHITECTURE.md`.
- Существующая архитектура (write-on-change примитивы, общий селектор
`data-hp-live-viewbox`, generic `updated()`-коммит) технически допускает
предложенный фикс без новых обработчиков событий или специальных случаев —
«Техническая реализация» не описывает невозможную или противоречивую схему.
- Ни одно техническое утверждение ТЗ, не помеченное как предположение, не
разошлось с кодом или каноническими документами при проверке.
- Названные в плане автотестов файлы и npm-скрипты существуют, инфраструктура
для AC1–AC9 реальна, а не гипотетична.
- Продуктовых вопросов владельцу нет обоснованно: контракт «ни один показанный
кадр не теряет пол/стены» уже зафиксирован `TOUCH-SUPPORT.md` как
release-blocking, новых продуктовых решений задача не вводит.
## Чего не проверял
- Гейты `typecheck`/`test`/`build`/`golden` — не прогонялись: продуктового
кода ещё нет, предмет этапа — текст ТЗ, а не бинарник.
- Полный `node demo/smoke_live_pan_coverage.mjs` — попытка запуска сделана
только для проверки воспроизводимости цитаты из issue (см. п.9), упала на
отсутствии собранного `dist/**`; пересборка бандла на этой стадии не
требуется и не выполнялась.
- Реализация будущего mutation witness и содержимое расширенного
`test/live-viewport.test.mjs`/`smoke_live_pan_coverage.mjs` — не существуют
на этой стадии, их проверит код-ревью.
- Ручного/физического Companion-теста не проводилось и не требовалось: ТЗ
прямо и корректно относит его к AC10 как отдельный релизный гейт перед
закрытием issue, а не как доказательство этого раунда.
- Perfomance-профиль большого плана (AC9) — назван «перед бетой», вне скоупа
ревью ТЗ.
## Вердикт
Зелёный. High — 0, Medium — 0. Все обязательные разделы §7.1 присутствуют,
каждый AC однозначен, проверяем и снабжён доказательством и (где применимо)
отрицательным свидетелем. Технический механизм дефекта подтверждён прямым
чтением кода на названном SHA, а не принят на веру. Единственное
предположение, обозначенное как таковое, на практике уже доказано чтением
кода. Продуктовых вопросов владельцу нет обоснованно. Задача может переходить
в «Готово к разработке».
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/579-webview-pinch-compositor`, коммит `856a2c6e1238` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `35e12ab83a88ef61b8284ef386341e4e71f40b89`
```
git log --all --format='%H %T' | grep 35e12ab83a88
```
- Тело issue: `4545e4010a0a0d13588d41c73912f543b83f5091653de15127390b77630498a0`
- Вердикт конвейера: `green` · High 0