docs: review document for #531
Проверка (CI) / Классификация изменённых файлов (push) Successful in 26s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 52s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 24s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 25s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 43s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 4m59s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 7m4s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped

Issue: #531
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-11 10:02:27 +00:00
parent b4a16ad704
commit d21797dc66
+149
View File
@@ -0,0 +1,149 @@
# SPEC-REVIEW-531-r2
**Issue:** #531 — «Панорама плана в Firefox: `viewBox` переписывается на каждый
кадр жеста, композитор ждёт 13 vsync-тиков»
**Этап:** spec (ревью ТЗ, PROCESS.md §2.4)
**Трек:** полный (не изменился со времени r1 — критерий §5, который задача не
проходит, назван и подтверждён в r1: «нет влияния на производительность»)
**Заход:** r2 · блокирующих циклов израсходовано 1 из 4
**Материал:** тело issue #531 на текущем HEAD, `dev` `b4a16ad7`. Продуктовый
код не тронут: `git diff af2081cb..b4a16ad7 -- src/live-viewport.ts
src/live-interaction-runtime.ts src/houseplan-card.ts docs/ARCHITECTURE.md
scripts/mutation-gate.mjs test/live-viewport.test.mjs
demo/benchmark_large_house.mjs` пуст. Единственный коммит между материалом r1
и этим раундом — `b4a16ad7 docs: review document for #531` (класс C, публикация
самого документа r1), никакого влияния на технические утверждения r1.
## Разбор по дельте (PROCESS.md §2.10)
Предыдущий раунд закончился жёлтым вердиктом с единственной находкой Medium
(в скоупе), остальное ТЗ принято. Автор ответил комментарием
(`2026-09-11T09:58:44Z`), назвав ровно три правки текста ТЗ: переписан К2,
добавлен AC2а, переформулирован риск 1. Код между раундами не менялся (см.
выше) — делать полный повторный разбор нет предмета: дельта строго локальна
(один контрактный пункт, один новый AC, одна строка риска), не является
ребейзом, не меняет контракт поведения за пределами уже поднятого вопроса и не
затрагивает новую подсистему. Разбор в этом раунде ограничен дельтой и всем, до
чего она дотягивается: К2, АС2/АС2а как пара, риск 1 и их согласованность с
остальным контрактом (К1, К5) и остальными AC (AC1, AC3-AC9), которые дельта не
меняет текстуально, но с которыми новый текст обязан не конфликтовать.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| Medium — второй триггер К2 (сдвиг) не имел ни ориентировочной константы, ни собственного AC; ни один из AC1-AC9 не мог покраснеть, если реализация вообще не построит сдвиговую ветку | К2 переписан как два явных условия с названными константами-ориентирами (`LIVE_VIEWBOX_REFRESH_MS` ≈100 мс, `LIVE_VIEWBOX_REFRESH_SHIFT` ≈0.15) и объяснением, зачем нужен второй; добавлен отдельный **AC2а** (`unit`), который с подменёнными часами проверяет сдвиговый путь независимо от временного: кадр со смещением выше порога обязан переписать `viewBox` немедленно, хотя `LIVE_VIEWBOX_REFRESH_MS` ещё не истёк, а кадр ниже порога — не должен переписывать | Тело issue #531, раздел `### Контракт`, пункт К2 (два подпункта «по времени» / «по сдвигу», абзац «Оба порога — константы модуля»); раздел `### AC`, новый пункт **AC2а** |
| Low — обязательные разделы §7.1 присутствуют по содержанию, но не оформлены отдельными заголовками | Снято ревьюером r1 без правки, автор текст не трогал (подтверждено комментарием «Low ... снят ревьюером без правки — текст не трогал») | Не требует проверки — решение уже зафиксировано в SPEC-REVIEW-531-r1.md |
Проверка не ограничилась чтением заявления автора: ниже — построчная сверка,
что новый текст АС2а действительно ловит отсутствие сдвиговой ветки, а не
просто существует.
**АС2а умеет покраснеть.** Если реализация не построит путь «накопленный сдвиг
→ досрочная перезапись» и оставит только временной бюджет — кадр со смещением
выше `LIVE_VIEWBOX_REFRESH_SHIFT`, но с ещё не истёкшим
`LIVE_VIEWBOX_REFRESH_MS`, при отсутствующей сдвиговой ветке не перепишет
`viewBox`; AC2а прямо требует, чтобы он его переписал — assertion провалится.
Симметрично: если реализация триггерит по сдвигу слишком рано (например,
использует долю от неверной оси или не выполняет сброс накопленного смещения
после перезаписи), негативная часть AC2а («кадр со смещением ниже порога — не
переписывает») ловит и это. Это ровно тот сценарий, который в r1 не мог
провалиться ни на одном из AC1-AC9 — пробел закрыт предметно, а не
декларативно.
**Согласованность с остальным контрактом не нарушена.** К1 («каждый кадр — это
только трансформ») и новое К2 не конфликтуют: кадр, на котором истёк один из
двух бюджетов (временной или сдвиговый), — явно описанное исключение, а не
нарушение К1, и после него «трансформ этих узлов возвращается в единицу в том
же кадре» для обоих путей одинаково. АС2 (временной путь) и АС2а (сдвиговый
путь) размечены как независимые сценарии и не пересекаются: АС2 говорит «по
истечении бюджета», АС2а — «пока временной бюджет ещё не истёк». Риск 1
(«пустая полоса на набегающем крае») теперь корректно ссылается на «оба
порога К2» вместо одного, и вывод риска («пороги — константы модуля, их
достаточно уменьшить») остаётся верным для обоих. AC5 (смок, суммарный счётчик
перезаписей за 20 кадров) не переписан и не должен был: у него по-прежнему
нет обязанности различать триггер — это ровно то, для чего существует АС2а.
AC8 (таблица мутантов на К1/К4) не получил новой строки под сдвиговый триггер;
это не пробел этого раунда — §2.7 (таблица «AC · чем доказан · чем краснеет»)
требование к код-ревью для защитных AC, а не к тексту спецификации, и АС2а сам
по себе уже задаёт проверяемый негативный сценарий текстом.
## Унаследовано из r1
Без повторной проверки в этом раунде приняты все выводы
`docs/reviews/SPEC-REVIEW-531-r1.md` (материал: тело issue #531 на dev
`af2081cb`), поскольку продуктовый код и остальной текст ТЗ, от которых эти
выводы зависят, между раундами не менялись (подтверждено пустым `git diff`
выше):
- продуктовая рамка и выбор полного трека (docs/SCOPE.md, J1; критерий §5);
- построчная сверка технических утверждений с `src/live-viewport.ts`,
`src/live-interaction-runtime.ts`, `src/houseplan-card.ts` (`paintLiveViewport`
пишет `viewBox` безусловно; `isIdentityLiveLayerProjection` уже существует у
трансформа и отсутствует у `viewBox`; `_floorView(view) === view` в
неизометрическом режиме — факт, не приближение; теги
`data-hp-live-viewbox`/`data-hp-live-layer` существуют и используются как
описано, включая изометрическую ветку);
- проверяемость AC1, AC3, AC4, AC6, AC9 и их привязка к существующей
инфраструктуре (`test/live-viewport.test.mjs`, `scripts/mutation-gate.mjs`,
`docs/ARCHITECTURE.md:1908`);
- честность разделения «AC внутри репозитория доказывают механизм» / «приговор
по скорости — профиль владельца» (AC7 golden, раздел «Приёмка по скорости»);
- риски 2-4, откат, touch-вывод («единственная точка входа `_liveVp()`,
Pointer Events унифицируют мышь и тач — отдельного контракта не требуется»);
- статус трёх «Принятых технических предположений» как действительно
технических, не подменяющих продуктовое решение;
- Low-находка о неформальной структуре разделов §7.1 — снята r1, автор
текст, к которому она относится, не менял.
## Находки
Нет. Единственная находка r1 (Medium) закрыта по существу — новый текст не
просто добавляет константу, а даёт конкретный, способный провалиться сценарий
для триггера, который раньше мог остаться нереализованным незамеченно. Новых
находок дельта не вносит: правка локальна, не противоречит соседним пунктам
контракта и не расширяет скоуп.
## Чего не проверял
- Гейты `npx tsc --noEmit`/`npm test`/`npm run build`/`node
scripts/check-docs.mjs` не запускались — в материале этого раунда нет
изменений класса A/B/D (диапазон `origin/dev..HEAD` пуст, `src/**` не
менялся), прогонять их значило бы проверять пустое множество; та же логика,
что в r1.
- Реализуемость числовых ориентиров `LIVE_VIEWBOX_REFRESH_MS≈100 мс` и
`LIVE_VIEWBOX_REFRESH_SHIFT≈0.15` на живом железе владельца — по тексту ТЗ
(«Приёмка по скорости — отдельно и не здесь») это предмет профиля на этапе
код-ревью/пре-релиза, не текста спецификации; унаследовано из r1 без
повторной оценки.
- Изометрический путь и поведение при одновременном зуме и панораме —
унаследовано из r1 («детальная проверка изо-пути остаётся код-ревью по факту
диффа»), дельта этого раунда К6 и зум не касается.
## Вердикт
**Зелёный · заход r2/4 · High: 0 · Medium: 0.**
Единственная находка r1 закрыта предметно: К2 теперь называет константу для
обоих триггеров перезаписи `viewBox`, и новый AC2а даёт сценарий, который
красится именно на отсутствии сдвиговой ветки (проверено построчно выше, а не
принято на слово автора). Согласованность нового текста с остальным
контрактом (К1, К5, AC2, AC5, риск 1) проверена и дефектов не выявила. Код
между раундами не менялся, поэтому весь остальной корпус выводов r1
наследуется без повторной проверки (раздел «Унаследовано из r1»).
Следующий статус — «Готово к разработке» (`S5-ready`).
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `b4a16ad7043d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `9ac2a0132e8bb5d8db8eaab5fa14e1a714063caf`
```
git log --all --format='%H %T' | grep 9ac2a0132e8b
```
- Тело issue: `89e75138a50434ac52f42cef606d8af741a8ae3b022dba382c4b62c1f4887e8a`
- Вердикт конвейера: `green` · High 0