18 KiB
SPEC-REVIEW-396-r2
- Issue: https://github.com/Matysh/houseplan-card/issues/396
- ТЗ под ревью:
docs/specs/396-camera-transition-fixes.md, коммитd5fd5926faab12ec0883458b1d4a932ea9344bb3(HEAD,docs: #396 spec revision 2 per SPEC-REVIEW-396-r1) - Этап: spec (PROCESS.md §2.4)
- Трек: обычный (без метки
small/trivial) - Заход: r2 · блокирующих циклов ревью ТЗ израсходовано 1 из 4 (до этого вердикта)
Скоуп ревью
Раунд r1 вернул красный вердикт с одной High-находкой: раздел «(1) B1» ТЗ
утверждал, что переход отменяют семь конкретных мест
(:1267,:1401,:1551,:4188,:4216,:6089,:6206), а фактическая сверка показала
десять мест плюс отдельный .dispose() на disconnect — при этом ни настоящий
_stagePointerDown (:6387, буквальный сценарий issue), ни настоящий
_zoomAt (:6283) в семёрке не фигурировали, а раздел «Скоуп» не называл
_stagePointerDown затрагиваемой функцией. Владелец ответил ревизией 2:
заменил список семи чисел таблицей из одиннадцати мест с классификацией
«пользовательская/структурная», добавил _stagePointerDown в «Скоуп»,
переформулировал AC1/AC2.
Это ревью — по дельте (PROCESS.md §2.9): единственный правленый раздел — «(1) B1» плюс «Скоуп/не-скоуп» плюс AC1/AC2 (см. диф ниже). B2, M2, AC3–AC7, UX, модель данных, i18n, риски, откат, release-артефакты дельту не задевают и наследуются из r1 без повторной проверки. Причина полного нового прочтения таблицы вместо точечной сверки одной строки: сама таблица — это ровно тот артефакт, для которого r1 объявил высокий риск фактической ошибки, и инструкция раунда прямо требует не доверять заявлению автора на слово, а сверять «строку кода или текста».
git diff 4e0a30a78161a4ff59679454d1f8b5c471e10008..HEAD -- docs/specs/396-camera-transition-fixes.md
(коммит 4e0a30a7 — HEAD ветки на момент вердикта r1).
Продуктового кода в ветке по-прежнему нет: git diff --stat 4e0a30a7..HEAD
показывает только docs/reviews/SPEC-REVIEW-396-r1.md (публикация прошлого
вердикта) и docs/specs/396-camera-transition-fixes.md. src/** не тронут.
Как проверялось
- Прочитан весь диф правленых разделов ТЗ (раздел «(1) B1», «Скоуп/не-скоуп», AC1, AC2, строка «Ревизия»).
- Каждая строка новой таблицы одиннадцати мест сверена с фактическим кодом на
HEAD той же командой, что и в r1:
grep -n "_cancelCameraTransition(false)\|_cancelCameraTransition(true)\|_cameraTransition\.\(cancel\|dispose\)" src/houseplan-card.ts— 14 строк (13 точек вызова + строка-определение самого_cancelCameraTransitionна:1242, которая не точка вызова и в таблицу не входит). - Каждая из 13 точек вызова прочитана в контексте содержащей функции:
:1159(_onMotionChange),:1267(_startCameraTransition, no-op ветка),:1401(_cancelModeTransition),:1551(_commitSpace),:2363(_pageVisibility),:2745(disconnectedCallback→.dispose()напрямую),:4188и:4216(обе — внутри_adoptStructuralResponses,:4167-4224),:6089(_applyView),:6206(_refitView),:6283(_zoomAt),:6360(_restoreZoom),:6387(_stagePointerDown). - Отдельно перепроверены оба вызывающих контекста
_zoomAt(:6517— pinch-move,:6897— touch multitouch move): оба вызывают_saveZoom()сразу после_zoomAt(...)— заявление таблицы «правки не требует» верно. - Сверено, что
_stagePointerDown(:6387) действительно первой строкой вызывает_cancelCameraTransition(false)и не содержит гарантированного_saveZoom()на пути простого клика — это подтверждает и контракт AC1, и то, что High-1 из r1 закрыт по существу (строка теперь в «Скоуп» и в AC1). - Гейты (
typecheck/test/build) не прогонялись — продуктового кода нет (только doc-коммиты), это вне скоупа этапа spec (PROCESS.md §2.4/§8), как и в r1.
Находки
Medium-1 — таблица приписывает строки :4188/:4216 несуществующей функции _load
Файл: docs/specs/396-camera-transition-fixes.md, раздел «(1) B1»,
строка таблицы | \:4188`, `:4216` `_load` | adoption конфига и layout | структурная |`.
Фактическая проверка: в src/houseplan-card.ts идентификатора _load не
существует (grep -n "\b_load\b" src/houseplan-card.ts — пусто). Обе строки
:4188 и :4216 лежат внутри одной и той же функции
_adoptStructuralResponses (:4167-4224 — сигнатура на :4167, config-adopt
ветка на :4188, layout-adopt ветка на :4216), которую вызывают
_loadFromServer (:4247, вызов на :4284) и второй caller на :4460
(config-only reload). Ни один из этих реальных идентификаторов не совпадает
с _load: ближайшие по написанию — _loadFromServer, _loadOk, _loading,
_loadTries, _loadRetryTimer — все разные функции/поля.
Это ровно тот класс дефекта, который r1 квалифицировал как High (таблица
называет несуществующее/неверное имя функции, хотя визуально оформлена как
проверенный факт — «сверено grep'ом… каждое прочитано»), и он новый:
в тексте r1 (и в собственном ответе ревьюера, и в разборе владельца) эти же
строки корректно названы _adoptStructuralResponses — ревизия 2, поправляя
High-1, сама внесла эту неточность взамен верного имени.
Почему Medium, а не High. У этой строки нет требуемой правки кода — класс
«структурная», и правка по контракту B1 нужна только в _stagePointerDown
(:6387), которая в «Скоуп» названа явно и корректно. Ложное имя _load не
рискует тем, что разработчик пропустит обязательную правку (в отличие от
High-1 r1, где отсутствие _stagePointerDown в списке грозило оставить
буквальный сценарий issue незачиненным) — оно рискует ввести в заблуждение
читателя, сверяющего полноту классификации (найдёт _loadFromServer, не
найдёт _load, потратит время на поиск несуществующего имени), и подрывает
доверие к самому заявлению «каждое прочитано» в этой же таблице.
Что нужно поправить. Заменить _load на _adoptStructuralResponses в
строке таблицы для :4188/:4216.
Low-1 (waived) — вторая функция в строке :1159/:2363 не названа
Строка таблицы | \:1159` `_onMotionChange`, `:2363` | ... |называет функцию только для первого числа;:2363фактически лежит в_pageVisibility (:2357-2363, поле-стрелка класса), но таблица этого не пишет. В отличие от Medium-1 здесь нет ложного утверждения — просто пропуск имени, классификация («cancel(true), цель коммитится, уже сохраняется через settled`») верна для
обеих строк. Не блокирует; можно поправить попутно при правке Medium-1, а
можно оставить — снимаю без требования правки.
Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
High-1: список из семи чисел не совпадает с реальными вызовами; _stagePointerDown (:6387) и _zoomAt (:6283) отсутствуют в списке; «Скоуп» не называет _stagePointerDown |
Список заменён таблицей из одиннадцати мест (все 13 точек вызова, включая .dispose() на :2745), с явной классификацией «пользовательская/структурная»; _stagePointerDown добавлен в «Скоуп» жирным и назван «буквальный сценарий issue» с явной пометкой «сохранять»; _zoomAt явно помечен «правки не требует» с обоснованием |
docs/specs/396-camera-transition-fixes.md, раздел «(1) B1» (новая таблица) и раздел «Скоуп / не-скоуп» (**\_stagePointerDown`**); проверено построчной сверкой с кодом в этом раунде (см. «Как проверялось» п.2–5) — таблица теперь фактически верна за единственным исключением Medium-1 (имя _loadвместо_adoptStructuralResponsesдля:4188/:4216`) |
| Риск «поймает только код-ревью, лишний цикл» | Снят тем же исправлением: _stagePointerDown теперь явно в скоупе и в AC1, реализация не сможет случайно пропустить это место, читая раздел «Скоуп» как границу работы |
AC1: «После пользовательской отмены перехода (_stagePointerDown — касание плана...) сохранённый зум пространства равен показанному» |
Унаследовано из r1
Не проверялось повторно в этом раунде — дельта их не касается, приняты по
документу docs/reviews/SPEC-REVIEW-396-r1.md на коммите 4e0a30a78161a4ff59679454d1f8b5c471e10008:
- B2 (якорь от отстающего кадра) — контракт, математическое обоснование
(
_cameraTargetAtстроит якорь от_cameraState(), presented, независимо отtarget), правка §10docs/specs/082-smooth-zoom.md(AC6) — раздел «(2) B2» ТЗ не входит в диф r1→r2, не перечитывался. - M2 (feather не заморожен) — цитата
resolveGlowFeather(..., !this._pinchStart && !this._panStart)(houseplan-card.ts:10845-10847) сверена дословно в r1; в этом раунде дополнительно подтверждено, что строка не сдвинулась (см. «Как проверялось», п. проверки M2 в контексте неизменного дифа) — раздел «(3) M2» ТЗ тоже вне дифа r1→r2. - AC3–AC7 — формулировки не менялись (вне дифа), контракты и способы доказательства признаны верными в r1.
- Структура ТЗ / обязательные разделы PROCESS.md §7.1 — все на месте, проверено в r1 (сценарий, до/после, скоуп, UX, модель данных, i18n, план автотестов, риски, откат, release-артефакты).
- Правка §10/§13
docs/specs/082-smooth-zoom.md(AC6) — обоснованность и то, что она не требует продуктового решения владельца, подтверждены в r1. - Трек (обычный, не
small) — обоснован в r1 и не оспаривается. - Открытых продуктовых вопросов владельцу нет — подтверждено в r1; Medium-1 этого раунда — тоже чисто технический вопрос (имя функции в таблице), не продуктовый, решается вердиктом, а не владельцем.
- Мутанты названы конкретно и подтверждают правило «тест умеет падать» — раздел «Мутанты» вне дифа r1→r2.
Что проверено и корректно (в этом раунде, по дельте)
- Новая таблица из одиннадцати мест фактически верна для 12 из 13 точек
вызова (все, кроме
:4188/:4216) — построчно сверено с кодом на HEAD. _stagePointerDownкорректно классифицирован как единственное пользовательское место, требующее правки, и корректно добавлен в «Скоуп»._zoomAtкорректно исключён из мест, требующих правки: оба его реальных вызывающих контекста (:6517,:6897) действительно вызывают_saveZoom()сразу после — проверено чтением обоих мест.- AC1 и AC2 переформулированы в соответствии с новой таблицей, без изменения
сути контракта B1/B2/M2 из r1; AC2 корректно охватывает
_restoreZoomкак структурное место, которое ничего не пишет вlocalStorage(сам_restoreZoomи вызываемый им_applyViewне содержат_saveZoom()). - Раздел «Ревизия» корректно ссылается на SPEC-REVIEW-396-r1 и называет High-1 явно — соответствует ожиданию «показать, чем именно закрыта находка предыдущего раунда» текстом, а не только заявлением.
Чего не проверял
- Разделы «(2) B2», «(3) M2», UX, модель данных/миграция, i18n, план автотестов, риски, откат, release-артефакты — не входят в диф r1→r2, унаследованы из r1 (см. раздел выше), повторно не перечитывались построчно против кода в этом раунде.
- Гейты
typecheck/test/build/check-docs/invariants/смоки/golden:verify— неприменимы: продуктового кода в ветке нет (только doc-коммиты), как и в r1. - Содержимое внешнего аудита
AUDIT-2026-08-31-v1700beta1.md— по-прежнему не в репозитории, не перепроверялось (не входит в дельту этого раунда). docs/specs/082-smooth-zoom.mdцеликом — не перечитывался повторно в этом раунде, так как правки §10/§13 вне дифа r1→r2; принято на веру из r1.
Вердикт
Жёлтый. High: 0. Medium: 1 (Medium-1 — строка таблицы раздела «(1) B1»
приписывает :4188/:4216 несуществующей функции _load вместо
фактической _adoptStructuralResponses; в скоупе этого же раунда, чинится в
нём же, без отдельного issue). Low: 1, waived (пропуск имени функции для
:2363 — не блокирует, можно поправить попутно).
High-1 из r1 закрыт по существу: _stagePointerDown (буквальный сценарий
issue) теперь явно в «Скоуп» и в AC1, _zoomAt корректно исключён из мест,
требующих правки, таблица одиннадцати мест фактически верна за единственным
исключением Medium-1. После правки Medium-1 (заменить _load на
_adoptStructuralResponses в одной строке таблицы) — заход по дельте
(PROCESS.md §2.9): проверить только эту строку, всё остальное наследуется без
повторной проверки.
Вердикт: жёлтый · заход r2 · блокирующих циклов 2/4 · High: 0 · Medium: 1 → в задаче · Документ: docs/reviews/SPEC-REVIEW-396-r2.md