docs: review document for #582

Issue: #582
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-15 10:25:24 +00:00
parent 226f323073
commit a0a108cf13
+297
View File
@@ -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()`.
- Новый `<svg class="hp-paper-outline-svg">` теперь рендерится ВСЕГДА при активном 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-размерный корневой `<svg>`), а `.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-подобной строки шаблона (`<div class="stage ...">`), в которой лежат десятки не изменившихся по смыслу свойств — слабая связь по 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 `<g>` 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-карточке нет, потому что там нет второго `<svg>`-соседа вообще) — риск ровно тот, ради
устранения которого пункт 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).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/582-webview-large-filter-layers`, коммит `226f323073af` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `dca5f4042deec2196302371f3ba4ffd6ab59c242`
```
git log --all --format='%H %T' | grep dca5f4042dee
```
- Тело issue: `4c1e1ecfe8ce205f3cfb3c44eb6028a35d0bb4358323e05ba60c6fd85b3b40ba`
- Вердикт конвейера: `red` · High 1