diff --git a/docs/reviews/CODE-REVIEW-582-r2.md b/docs/reviews/CODE-REVIEW-582-r2.md new file mode 100644 index 00000000..6045dbcf --- /dev/null +++ b/docs/reviews/CODE-REVIEW-582-r2.md @@ -0,0 +1,297 @@ +# CODE-REVIEW-582-r2 + +Issue: [#582](https://github.com/Matysh/houseplan-card/issues/582) · этап: code · заход r2 · +блокирующих циклов израсходовано 2 из 4 · материал: `226f323073afd79a1eef9e2e5979684c9ae628f7` +(`origin/dev` merge-base = `4208ca81fe3516200a5bd80c1221597492d8fa7b`, дерево материала = +`dca5f4042deec2196302371f3ba4ffd6ab59c242`). + +## Скоуп + +Красный r1 (`CODE-REVIEW-582-r1.md`, материал `0a6592f4`) нашёл два High: AC4 (перф-свидетель +`smoke_daycycle_raster` красный 8/8, `plan-svg` продавливался в отдельный composited-слой из-за +`"Overlaps other composited content"`) и AC6 (все 4 day-cycle golden-сцены `different`, ~3.3–3.5% +пикселей). Между r1 и r2 ветку дважды двигали: «Повторный хендофф» переписал сам механизм +контура, затем ребейз на `dev` (конфликт с #581, материал стал `2a8b5f1d`), затем красный +Validate с мутантами на `2a8b5f1d` (ревью не запускалось, «код никто не читал» — не цикл), и +наконец стабилизация шумного raster-свидетеля коммитом `226f3230` — это и есть материал r2. + +**SHA r1 (`0a6592f4112f`) и его дерево (`9f4d95168fdd`) не резолвятся в этом чекауте** — +`git cat-file -t` возвращает «could not get object info», в `git ls-remote origin` его тоже нет. +Это ожидаемо по PROCESS.md §2.10 (ребейз осиротил коммит, находкой не считается) — данные о r1 +взяты не из живого диффа, а из текста `docs/reviews/CODE-REVIEW-582-r1.md` (коммит `728c564e`, +который *остался* в истории, так как он был закоммичен уже после материала r1) плюс из текущего +кода. Раз architecture между r1 и r2 поменялась содержательно (не «одна строка в фикстуре», а +новый conditional-механизм переключения слоя), разбор ниже — полный по AC1–AC7, а не только по +находкам r1 (§2.9: «дельта не локальна» — сменился сам механизм, которым AC1–AC6 доказываются). + +## Материал и что сделано в коде (дельта r1→r2) + +- `src/houseplan-card.ts`: новое поле `_safeDayCycleOutline` и метод + `_activateSafeDayCycleOutline()` (строка ~2068), вызываемый из трёх точек входа жеста — + двухпальцевый pinch (`_pinchStart`+`_pointers.size>=2`), обнаружение `_panLock==='pan'` + (мышь и палец через общий pointer-путь) и multitouch-старт pinch. При активации кладёт класс + `hp-safe-daycycle-outline` на `_stageEl` напрямую (минуя цикл Lit — сохраняет compositor + lifecycle #579) и персистентно на весь срок жизни инстанса через `render()`. +- Новый `` теперь рендерится ВСЕГДА при активном day-cycle + (не только после жеста), но `visibility: hidden` до появления `.hp-safe-daycycle-outline` — + то есть до первого движения камеры карточка сохраняет байт-в-байт прежнюю (r1-довоенную, + #532) однослойную композицию: `.hp-paperg` держит `filter`+`will-change:filter` напрямую, а + скрытый сосед не создаёт собственного promoted-слоя (у него нет фильтра до активации). +- После активации: `.hp-paperg` получает `filter:none; will-change:auto` (перестаёт быть + большим координатным слоем), фильтр и `will-change:filter` переезжают на + `.hp-paper-outline-svg` (stage-размерный корневой ``), а `.plan-svg` получает явный + `will-change: transform` — это и есть исправление находки №1 r1: явная причина промоушена + не даёт Chromium придумать implicit `"Overlaps other composited content"` для всего плана. +- `src/render/paper-scene.ts` — новый общий `renderPaperShapes()` (вынесен из r1, без + изменений сигнатуры), используется в `houseplan-card.ts` и `space-render.ts`. +- `src/space-render.ts`/`src/space-card.ts` — **не получили** `hp-paper-outline-svg`/ + `hp-safe-daycycle-outline` вообще (в r1 они были, симметрично основной карточке). Это разбирается + как находка №1 ниже. +- `demo/smoke_daycycle_layer_budget.mjs` (новый в r1, здесь без изменений сигнатуры проверок) — + свидетель AC1–AC3, теперь дополнительно проверяет, что до жеста контур `visibility:hidden`, + а после — `.plan-svg` получает `will-change:transform` явным слоем. +- `demo/smoke_daycycle_raster.mjs` — переписан в r2 (комментарий issue «Исправление + CI-свидетеля»): три чередующиеся замкнутые static/day-cycle пары, медиана парных отношений + вместо одиночного замера — устраняет ложный красный на шумном раннере, порог 2.0 не менялся. +- `scripts/mutation-registry.mjs` — новый мутант `daycycle-outline-promoted-on-inner-paper` + (возвращает `will-change:filter` на `.hp-paperg` в safe-режиме), старый + `daycycle-outline-not-promoted` адаптирован (снимает `will-change:filter` с общего блока). +- Документация (`ARCHITECTURE.md`, `SUN.md`, `TOUCH-SUPPORT.md`, `TESTING.md`) переписана под + новый двухфазный механизм и **прямо признаёт**, что статическая карточка в фолбэк не входит + («the non-interactive static space card keeps its historical inner outline because it has no + camera gesture (#582)» — `docs/SUN.md`). +- Changelog (`docs/CHANGELOG.md`/`.ru.md`) не менялся в этой дельте — запись сделана ещё в + `a68e4c6b` (`User-Visible: yes`) и по-прежнему точно описывает финальное поведение основной + карточки (перепроверено golden/smoke-прогонами ниже). + +## Как проверялось + +Дешёвые гейты подтверждены зелёным Validate на точном материале +(https://github.com/Matysh/houseplan-card/actions/runs/34954286064) — предполёт, «Фронтенд: +типы/юниты/бандл», мутанты по диффу: `success`. Тяжёлые джобы в этом прогоне не гейтили пуш +(обычный push, не `Release:`/PR/`full=true`) — именно их r1 не хватило, поэтому здесь они +прогнаны мной лично. + +| Гейт | Результат | Почему прогнан/не прогнан | +|---|---|---| +| `npm run bundle:sync` (typecheck+build+rollup+bundle-tree) | зелёный | нужен как основа для браузерных смоков; дёшево | +| `npm run bundle:budget` | зелёный, 291058 Б / потолок 291700±2000, запас 10008 Б (предупреждение о низком общем запасе — преэкзистентный долг #367/#474, не находка этой задачи) | обязателен после билда | +| `node scripts/check-docs.mjs` | зелёный, 7 файлов, 12 внешних ссылок | diff трогает `src/**` — обязателен всегда | +| `node scripts/process-gate.mjs --issues` | зелёный, 5 коммитов, 0 предупреждений | офлайн-проверка трейлеров/веток | +| `node demo/smoke_daycycle_layer_budget.mjs` (AC1/AC2/AC3) | **зелёный**, все 12 заявленных инвариантов true, 12/12 presented-кадров `nearWhiteRatio:0` | прямое совпадение — это и есть свидетель находок r1 | +| `node demo/smoke_daycycle_raster.mjs` (AC4) | **зелёный**, медиана парных отношений 1.55 (1.39/1.55/1.69) при потолке 2.0 | прямое совпадение — это и есть красный тест r1 | +| `npm run golden:verify` (AC6, полная матрица 172 сцены) | **зелёный, 172/172 `passed`**, все 4 `day-cycle-{dawn,day,dusk,night}-dark` в их числе | diff меняет рендер; это и есть красная проверка r1 | +| `node demo/smoke_bg_color.mjs` | зелёный (все проверки, включая `staticCardLayersStayOrdered`) | зарегистрированная связь `renderPaperShapes` | +| `node demo/smoke_live_pan_coverage.mjs` | зелёный | зарегистрированная связь `renderPaperShapes` | +| `node demo/smoke_pan_any_zoom.mjs` | зелёный (pinch/pan/kiosk-swipe инварианты) | прямое совпадение по `_panLock`, тот же код-путь, что получил новый вызов `_activateSafeDayCycleOutline()` | +| `node demo/smoke_isometric_contract.mjs` | зелёный | прямое совпадение — `plan-svg` класс стал безусловным, iso-обёртка контура повторяет `isoFloorMatrixCss()` | +| `node scripts/mutation-gate.mjs --id=daycycle-outline-promoted-on-inner-paper` | **зелёный**: чистый прогон ok, мутант поймал (1 из 1) | новый мутант AC2, дорогой гейт — обязателен по §2.7 (защита живёт в продуктовом коде) | +| `node scripts/mutation-gate.mjs --id=daycycle-outline-not-promoted` | **зелёный**: чистый прогон ok, мутант поймал (1 из 1) | существующий мутант r1, адаптирован под новый общий блок — перепроверен, что не сломался | +| `npx tsc --noEmit`, `npm test`, `npm run build` (сверка копий) | не гонял отдельно — подтверждены зелёным Validate на этом же SHA | дешёвые гейты, разрешено ссылаться на зелёный прогон (`npm run build` я всё равно выполнил как часть `bundle:sync`) | +| `python -m pytest tests_backend`, HACS, Hassfest, geometry-parity | не гонял | diff не трогает `custom_components/**/*.py`, манифесты, `layout`/`marker.space`/толщину стен — только SVG-рендер и стили | +| `node scripts/model-invariants.mjs` | не гонял | diff не меняет геометрическую модель/рёбра/толщину — только композитинг и разметку SVG | +| Остальные 40+ «прямое совпадение» смоков из `smoke-select.mjs` (`_mode`, `_modeTransitionBusy`, `_decorTool`, `_stageEl`, `_booting` и т.п.) | не гонял, кроме двух выше | это артефакт одной длинной изменённой JSX-подобной строки шаблона (`
`), в которой лежат десятки не изменившихся по смыслу свойств — слабая связь по PROCESS.md §8; прогнал два наиболее правдоподобных (`smoke_pan_any_zoom`, `smoke_isometric_contract`) точечно | +| `performance_smoke` (полный, вне `smoke_daycycle_raster`) | не гонял | AC4 уже проверен целевым, более дешёвым свидетелем; полный профиль не добавляет решающей информации на этом раунде | + +## Закрытие раунда r1 + +| Находка r1 | Чем закрыта | Где это видно | +|---|---|---| +| High №1 — AC4 не держится: `plan-svg` продавлен в отдельный слой (`"Overlaps other composited content"`), raster ratio 2.16–2.50 против потолка 2.0 | Явный `will-change: transform` на `.plan-svg` в safe-режиме убирает implicit-промоушен; контур скрыт до жеста, поэтому до-жестовый idle-рендер не платит вообще | `src/styles/plan.styles.ts:111-113` (`.stage.daycycle.hp-safe-daycycle-outline .plan-svg { will-change: transform; }`); мой прогон `node demo/smoke_daycycle_raster.mjs` — медиана 1.55 при потолке 2.0 | +| High №2 — AC6 не проходит: все 4 day-cycle golden `different`, ~3.3–3.5% пикселей | Контур не рендерится видимо (`visibility:hidden`) и не меняет `.hp-paperg` до первого жеста — golden снимаются на idle-карточке, которая теперь байт-в-байт прежняя композиция | `src/styles/plan.styles.ts:264-270` (`.hp-paper-outline-svg{visibility:hidden}` / `.stage.hp-safe-daycycle-outline .hp-paper-outline-svg{visibility:visible}`); мой прогон `npm run golden:verify` — 172/172 `passed`, все 4 day-cycle сцены включены | +| Low №3 — комментарий про «the external day-cycle outline... is composited» на `.hp-paperg`, хотя фильтр туда больше не крепится | Комментарий переписан под новую архитектуру | `src/houseplan-card.ts`, блок над `renderPaperShapes(paperShapes)`: «One `` keeps the visible sheet and filtered silhouette free of room seams.» — прочитано, соответствует коду | + +## Находки + +### Находка №1 (High) — Контракт п.6 не выполнен: `houseplan-space-card` не получил ограниченный экранными размерами механизм + +**Файлы:** `src/styles/plan.styles.ts:93-95` (правило `.hp-static-stage.daycycle .hp-paperg`), +`src/space-render.ts`, `src/space-card.ts`. + +ТЗ (раздел «Контракт поведения», пункт 6, зелёное спек-ревью r1, без замечаний по этому пункту): + +> Тот же ограниченный экранными размерами механизм применяется в `houseplan-space-card`, чтобы +> статическая карточка не сохраняла скрытый координатно-зависимый риск. Внешний вид и +> отсутствие интерактивных жестов этой карточки не меняются. + +Текущий код `.hp-static-stage.daycycle .hp-paperg` **не тронут дельтой r1→r2 вообще** — та же +самая строка `will-change: filter` на координатно-большой `.hp-paperg`, что и до всей задачи +#582/#532. Ни `hp-paper-outline-svg`, ни `hp-safe-daycycle-outline` в `space-render.ts`/ +`space-card.ts` не существует (в r1 они были — см. «Что проверено и корректно» r1: «hp-static-stage +получил аналогичное правило»; в r2 это убрано полностью). + +**Это не домысел ревьюера — это задокументированное и осознанное решение автора.** Прямая цитата +из диффа `docs/SUN.md` (r2): «the non-interactive static space card keeps its historical inner +outline because it has no camera gesture (#582)»; из `docs/ARCHITECTURE.md` (r2): «the static +space card, which has no camera gesture, never enters the fallback». Тест +`test/paper-scene-contract.test.mjs` кодифицирует это же самое как требование: +`assert.doesNotMatch(staticRender, /class="hp-paper-outline-svg"/)` и +`assert.doesNotMatch(staticCard, /\.hp-static-stage \.hp-paper-outline-svg/)` — то есть попытка +исправить это в будущем сломает собственный тест задачи. + +**Почему это High, а не молчаливое наследование.** Формулировка контракта явно и без условий: +«скрытый» риск назван так именно потому, что он не требует жеста — `houseplan-space-card` +рендерит тот же `canonicalWallGeometry.paperD`/`paperRoomShapes(space.rooms)` из того же +пространства с тем же `cell_cm`, то есть ровно тот же 4700×4200-координатный диапазон при +`cell_cm:1`, что и основная карточка (тот самый пример из тела issue). `houseplan-space-card` — +read-only карточка для встраивания (`docs/ARCHITECTURE.md:1577`), доступная в тех же дашбордах +HA Companion/kiosk, где и воспроизведён исходный дефект (`docs/SCOPE.md`: View — продукт для двух +из трёх персон, touch/View — release-blocking по `docs/TOUCH-SUPPORT.md`). Ни один из AC1–AC9 +явно не тестирует компоситорную безопасность статической карточки (AC5 проверяет только +визуальную целостность контура и отсутствие лишней outline-сцены у статичного ФОНА/редакторов/ +изометрии — это другой параметр, не про `houseplan-space-card`; AC7 проверяет только переиспользование +общего рендерера форм, не компоситинг) — то есть пробел в контракте образовался и не пойман +собственным тест-планом задачи. + +Технически это не тот же самый регресс, что нашёл r1 (никакого overlap-промоушена `plan-svg` в +static-карточке нет, потому что там нет второго ``-соседа вообще) — риск ровно тот, ради +устранения которого пункт 6 был написан: постоянный координатно-большой `will-change:filter`. +Раз пункт 6 сформулирован как безусловное требование, принятое зелёным спек-ревью, и раз +реализация сознательно его не выполняет без вынесения вопроса владельцу (ТЗ §7.1 требует +эскалации продуктовых развилок, а не тихого решения в одиночку) — блокирует. + +Проверено чтением (селекторы CSS, вызовы `_activateSafeDayCycleOutline`, текст документации и +теста), не браузерным прогоном: демонстрация самого белого-тайла на статической карточке +потребовала бы отдельного HA Companion WebView стенда, которого нет ни у одного участника этого +раунда; риск подтверждён структурно (тот же координатный размер, тот же непрерывный +`will-change:filter`), а не количественно замерен. + +### Находка №2 (Medium, в скоупе) — Анимированные переходы камеры (double-tap, «Вписать всё», клик по комнате, колесо/кнопки zoom) не активируют safe-режим + +**Файлы:** `src/houseplan-card.ts` — `_onWheel` (6727), `_stepZoom` (6744), `_resetZoom`/`_fitAll` +(6764/6396, включая `reason:'double-tap'` из `_doubleFit.pointerUp` на строке 7044), переход по +клику на комнату (`reason:'room'`, строка 6381) — все они вызывают `_startCameraTransition(...)`, +которая не вызывает `_activateSafeDayCycleOutline()` нигде. + +`_activateSafeDayCycleOutline()` вызывается только из трёх точек прямого pointer-move — двухпальцевый +pinch, `_panLock==='pan'` (drag) и multitouch-старт. Контракт п.3 ТЗ говорит «во время всей +pinch/pan-сессии», и AC1–AC4/AC9 буквально описывают только pinch — так что это не прямое +нарушение контракта (в отличие от находки №1), но double-tap-to-fit и room-fit — такие же +многокадровые анимированные изменения `viewBox` (`CAMERA_FIT_MS`), достижимые касанием на +планшете-киоске (тот же класс устройства, где воспроизведён #582), и физический механизм риска +(большой координатный `will-change:filter` на `.hp-paperg` во время многокадрового изменения +`viewBox`) не привязан семантически именно к жесту «два пальца» — он привязан к тому, что слой +вообще движется на экране несколько кадров подряд. Пользователь, который на планшете только +дважды тапает «показать всё» или нажимает кнопку зума и никогда не щипает/не тащит план одним +пальцем, весь сеанс сохраняет исходную #532-риск-топологию. + +Не проверено запуском (нет прямого свидетеля double-tap/room-fit в новом смоке — +`smoke_daycycle_layer_budget.mjs` покрывает только явный touch-pinch путь); проверено чтением +всех точек входа `_startCameraTransition`. В скоупе задачи (тот же файл, тот же механизм) — +чинится добавлением вызова `_activateSafeDayCycleOutline()` в `_startCameraTransition` (или во все +её вызывающие точки) либо явным решением сузить контракт п.3 до буквально pinch/pan с вынесением +вопроса владельцу, почему double-tap/room-fit исключены. + +## Что проверено и корректно + +- **AC1–AC3** (layer budget, mutant AC2, presented-frame screencast) — подтверждены свежим + запуском `smoke_daycycle_layer_budget.mjs`: 0 слоёв больше 4096, суммарная площадь экранного + порядка, 12/12 presented-кадров без белых пикселей, `.hp-paper-outline-svg` + `visibility:hidden`→`visible` ровно при активации `hp-safe-daycycle-outline`. +- **AC4** — перепроверен на новом, медианно-стабилизированном свидетеле: 1.55 при потолке 2.0 + (мой собственный прогон, три чередующиеся замкнутые пары); мутант `daycycle-outline-not-promoted` + ловит регрессию (`node scripts/mutation-gate.mjs --id=... ` — поймано 1 из 1). +- **AC6** — все 172 сцены golden-матрицы, включая 4 day-cycle, `passed` на каноничном Linux + (мой собственный прогон `npm run golden:verify`). +- **AC2** — новый мутант `daycycle-outline-promoted-on-inner-paper` зарегистрирован на верном + guard-файле и реально ловит возврат `will-change:filter` на `.hp-paperg` в safe-режиме (мой + запуск, поймано 1 из 1). +- **Единый живой viewport (#579).** `data-hp-live-viewbox` стоит на `.hp-paper-outline-svg` тем + же значением, что на `plan-svg`; активация safe-режима идёт напрямую через `classList.add` на + `_stageEl`, минуя ожидание Lit re-render — сохраняет контракт п.4 (бюджетированные обновления + `viewBox` без полного re-render на каждый pointer-кадр). Проверено чтением плюс + `smoke_daycycle_layer_budget` (`cameraActivatesSafeFallback`, `settledPlanLayerIsExplicitAndBounded`). +- **Изометрия.** `plan-svg` стал безусловным классом ещё в r1 (принято тогда же, не новая + находка); `smoke_isometric_contract.mjs` зелёный на этом материале. +- **Мутация/тест-план.** Оба мутанта #582 (старый и новый) реально ловятся своими guard-смоками, + не только заявлены. +- **Changelog/трейлеры.** `Issue: #582`+`User-Visible: yes` на `a68e4c6b`, оба changelog правлены + в нём же и по-прежнему точно описывают финальное поведение основной карточки (перепроверено — + ни pinch-путь, ни визуальный контур с r1 не изменились содержательно для пользователя, + описание не вводит в заблуждение относительно находки №1, поскольку не упоминает static-карточку + явно). Коммиты `2a8b5f1d`/`226f3230` — `User-Visible: no`, соответствует правилу (продуктовое + поведение основной карточки не меняется относительно уже описанного, правится только + устойчивость реализации/свидетеля). +- **Документация.** `ARCHITECTURE.md`/`SUN.md`/`TOUCH-SUPPORT.md`/`TESTING.md` построчно + соответствуют новому коду, включая честное признание про статическую карточку (которое и стало + находкой №1 — документация не скрывает пробел, она его называет). +- **`node scripts/check-docs.mjs`, `node scripts/process-gate.mjs --issues`, `npm run + bundle:budget`** — зелёные на материале. + +## Унаследовано из r1 + +Документ: `docs/reviews/CODE-REVIEW-582-r1.md` (коммит `728c564e`), материал r1 — +`0a6592f4112fd7ceef68cbd3be7c44eeb8a7ef3e` (SHA осиротел после ребейза, не резолвится в этом +чекауте — по PROCESS.md §2.10 это не находка). Принято без повторной проверки, так как дельта +r1→r2 этого не касается: + +- **AC7 (unit/structure, переиспользуемый рендерер бумаги).** `renderPaperShapes()` — тот же + API, что в r1, используется в обеих поверхностях без изменений сигнатуры; существующее + покрытие «пустого и составного плана» в `test/logic.test.mjs`/`test/wall-thickness.test.mjs` + (найдено `grep`, не запускалось повторно отдельно от `npm test`, зелёного на Validate). +- **Спек-ревью r1** (`SPEC-REVIEW-582-r1.md`, зелёный, High:0/Medium:0) — ТЗ с тех пор не + менялось (тело issue не редактировалось после спек-ревью; проверено визуально, хэш якоря + спек-ревью не пересчитывался в этом раунде за неимением инструмента конвейера в среде ревью — + риск низкий, раздел «Контракт поведения» текстуально совпадает с тем, что цитирует r1). +- **AC8 (quality gates состав)** — перечень необходимых гейтов не изменился между раундами. +- **AC9 (полевая приёмка)** — вне этапа код-ревью по определению самого AC, не переоценивается. +- **i18n/миграция/откат/риски раздела ТЗ** — не затронуты дельтой, содержательно не изменились. + +## Чего не проверял и почему + +- `python -m pytest tests_backend`, HACS, Hassfest, geometry-parity — diff не трогает + `custom_components/**/*.py`, манифесты, `layout`/`marker.space`/толщину стен. +- `node scripts/model-invariants.mjs` — diff не меняет геометрическую модель/рёбра решётки/ + записи толщины, только SVG-композитинг и разметку. +- Полная матрица `demo/smoke_*.mjs` (250 файлов) — прогнаны 6 (два «зарегистрированная связь», + два прямых по `renderPaperShapes`-цепочке и два точечно выбранных из 44 «прямых» слабых + совпадений). Остальные 42 «прямых» совпадения — артефакт одной длинной изменённой строки + шаблона со множеством не изменившихся по смыслу свойств (`_mode`, `_booting`, + `_modeTransitionBusy`, `_decorTool`, `_stageEl` и т.п.); не прогнаны сознательно, см. таблицу + выше. +- Полный `performance_smoke`/профили вне `smoke_daycycle_raster.mjs` — AC4 уже подтверждён на + целевом, более дешёвом свидетеле. +- `npx tsc --noEmit`/`npm test`/`npm run build` отдельно от `bundle:sync` — приняты по зелёному + Validate на этом же материале (https://github.com/Matysh/houseplan-card/actions/runs/34954286064). +- Реальный HA Companion WebView на живом устройстве (AC9) — вне этапа код-ревью; это же + ограничение не позволило количественно подтвердить находку №1 на статической карточке + (подтверждена структурно, не количественно). + +## Вывод + +Оба High r1 закрыты предметно: AC4 (raster ratio 1.55 против потолка 2.0, воспроизведено +самостоятельно) и AC6 (172/172 golden, включая все 4 day-cycle сцены, воспроизведено +самостоятельно) — новый двухфазный механизм (`hp-safe-daycycle-outline`, активируемый только +жестом) решает находки r1 без регрессии производительности контура из #532. Но при этом дельта +r1→r2 предметно убрала защиту статической карточки, которую r1 реализовывал (там она была +симметрична основной), и пункт 6 контракта ТЗ («тот же ограниченный экранными размерами механизм +применяется в houseplan-space-card») сейчас не выполнен — задокументировано самим автором как +осознанное решение, не вынесенное владельцу как продуктовая развилка. Это High, в скоупе задачи, +чинится в этой же ветке. Дополнительно найден Medium: анимированные (не pinch/pan) переходы +камеры не активируют safe-режим, что оставляет часть touch-путей (double-tap-to-fit, room-fit, +кнопки/колесо зума) в исходной риск-топологии — тоже в скоупе, тоже чинится здесь. Возврат +автору. + +--- + +## Материал раунда + +- Ветка: `issue/582-webview-large-filter-layers`, HEAD `226f323073afd79a1eef9e2e5979684c9ae628f7`. +- Дерево материала: `dca5f4042deec2196302371f3ba4ffd6ab59c242`. +- `origin/dev` на момент ревью: `4208ca81fe3516200a5bd80c1221597492d8fa7b` (merge-base с HEAD). +- Предыдущий документ: `docs/reviews/CODE-REVIEW-582-r1.md` (материал `0a6592f4112f`, вердикт + `red`, High 2 — SHA осиротел после ребейза, не резолвится в этом чекауте, см. §2.10). + +--- + + + +## Материал раунда + +- Ветка: `issue/582-webview-large-filter-layers`, коммит `226f323073af` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `dca5f4042deec2196302371f3ba4ffd6ab59c242` + ``` + git log --all --format='%H %T' | grep dca5f4042dee + ``` +- Тело issue: `4c1e1ecfe8ce205f3cfb3c44eb6028a35d0bb4358323e05ba60c6fd85b3b40ba` +- Вердикт конвейера: `red` · High 1