Files
houseplan-card/docs/reviews/SPEC-REVIEW-396-r2.md
T
2026-08-31 01:36:53 +03:00

198 lines
18 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-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. Прочитан весь диф правленых разделов ТЗ (раздел «(1) B1», «Скоуп/не-скоуп»,
AC1, AC2, строка «Ревизия»).
2. Каждая строка новой таблицы одиннадцати мест сверена с фактическим кодом на
HEAD той же командой, что и в r1:
`grep -n "_cancelCameraTransition(false)\|_cancelCameraTransition(true)\|_cameraTransition\.\(cancel\|dispose\)" src/houseplan-card.ts`
— 14 строк (13 точек вызова + строка-определение самого `_cancelCameraTransition`
на `:1242`, которая не точка вызова и в таблицу не входит).
3. Каждая из 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`).
4. Отдельно перепроверены оба вызывающих контекста `_zoomAt` (`:6517` —
pinch-move, `:6897` — touch multitouch move): оба вызывают `_saveZoom()`
сразу после `_zoomAt(...)` — заявление таблицы «правки не требует» верно.
5. Сверено, что `_stagePointerDown` (`:6387`) действительно первой строкой
вызывает `_cancelCameraTransition(false)` и не содержит гарантированного
`_saveZoom()` на пути простого клика — это подтверждает и контракт AC1, и
то, что High-1 из r1 закрыт по существу (строка теперь в «Скоуп» и в AC1).
6. Гейты (`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`), правка §10 `docs/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**