From d21797dc669889b8d0361eeac52ef074e9b2289c Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:02:27 +0000 Subject: [PATCH] docs: review document for #531 Issue: #531 User-Visible: no --- docs/reviews/SPEC-REVIEW-531-r2.md | 149 +++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 docs/reviews/SPEC-REVIEW-531-r2.md diff --git a/docs/reviews/SPEC-REVIEW-531-r2.md b/docs/reviews/SPEC-REVIEW-531-r2.md new file mode 100644 index 00000000..64cc8ba3 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-531-r2.md @@ -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`). + +--- + + + +## Материал раунда + +- Ветка: `dev`, коммит `b4a16ad7043d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `9ac2a0132e8bb5d8db8eaab5fa14e1a714063caf` + ``` + git log --all --format='%H %T' | grep 9ac2a0132e8b + ``` +- Тело issue: `89e75138a50434ac52f42cef606d8af741a8ae3b022dba382c4b62c1f4887e8a` +- Вердикт конвейера: `green` · High 0