Files
houseplan-card/docs/reviews/SPEC-REVIEW-531-r2.md
T
claude[bot] d21797dc66
Проверка (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
docs: review document for #531
Issue: #531
User-Visible: no
2026-09-11 10:02:27 +00:00

150 lines
14 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-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