# SPEC-REVIEW-402-r2 - Issue: https://github.com/Matysh/houseplan-card/issues/402 - Артефакт ТЗ: `docs/specs/402-confirm-outside-main-branch.md` (полный трек, класс A) - Заход: r2 · лимит циклов ревью ТЗ для полного трека — 4 (§4), блокирующих циклов израсходовано 1/4 (зелёные вердикты бюджет не тратят, #227) - Ревьюер: Claude (роль «ревьюер ТЗ»), независимая сессия - Раунд r1: вердикт красный, `docs/reviews/SPEC-REVIEW-402-r1.md`, база `d94db87e` (коммит, добавивший спецификацию) - Раунд r2: правка ТЗ — коммит `11959e4c` («docs: #402 spec revision 2 per SPEC-REVIEW-402-r1»), HEAD ревью — `11959e4c` ## Скоуп ревью (§2.10, по дельте) r1 закончился красным (High: 1, Medium: 1). Дельта этого раунда — `git diff d94db87e..11959e4c -- docs/specs/402-confirm-outside-main-branch.md`: правка ровно двух мест ТЗ (текст «Touch и kiosk» + переформулировка обоснования не-скоупа для `_tapConfirm`/`_vacCalConfirm`), плюс AC8, AC9 и два пункта плана автотестов. Никакого продуктового кода в дереве нет (этап — ТЗ), контракт поведения не изменился, новая подсистема не задета, объём дельты явно меньше исходной задачи → разбор ограничен дельтой плюс проверкой, что она не сломала уже принятое (§2.10 п.4-5). ## Как проверялось 1. Прочитан весь диапазон изменений r1→r2 (`git diff d94db87e..HEAD`), построчно, для файла ТЗ. 2. Найден вердикт r1 и SHA, на котором он получен (`d94db87e`, назван в шапке `SPEC-REVIEW-402-r1.md` — не пришлось восстанавливать). 3. По каждой находке r1 (H1, M1) сверено текстом дельты, чем именно она закрыта — раздел «Закрытие раунда r1» ниже. 4. Технические утверждения новой дельты сверены с текущим деревом на `11959e4c` (код не менялся с `d94db87e`, но проверка не наследуется автоматически — новый текст мог сослаться на код неточно): - структура `render()` (`src/houseplan-card.ts:11165-11878`) — семь ранних `return`, отсутствие `return` между `:11248` и `:11872` (`awk`-проверка по диапазону) — подтверждает утверждение M1, что `_tapConfirm` (`:11852`), `_vacCalConfirm` (`:11802`) и `_dangerConfirm` (`:11872-11878`) лежат в одной и той же финальной ветке без разрыва; - `docs/TOUCH-SUPPORT.md` § «Safety floor that still applies to touch editors» (`:69-79`) — сверено число пунктов и их содержание против фразы ТЗ «запрещает best effort в трёх вещах» — см. находку L1; - `docs/TOUCH-SUPPORT.md` § «Testing and release gates» (`:139-151`) — сверено, что touch-editor safety floor входит в release-blocking гарантии (п.4), а не только View/kiosk (п.2) — это меняет оценку находки H1 (см. ниже); - `hasTouch` — паттерн существующих touch-смоков (`grep -rn hasTouch demo/smoke_*.mjs`) реально существует и используется ровно так, как описывает AC8 (`demo/smoke_color_picker.mjs`, `demo/smoke_isometric_live_touch.mjs`, `demo/smoke_touch_tips.mjs`); - смоки, на которые ссылается AC9 (тап и калибровка пылесоса), существуют: `demo/smoke_tap_run.mjs`, `demo/smoke_tap_ctx.mjs`, `demo/smoke_vacuum.mjs`, `demo/smoke_vacuum_firstuse.mjs`, `demo/smoke_cold_view_vacuum.mjs`. 5. Проверено, остаются ли §7.1-обязательные разделы на месте после правки — да, ни один не удалён, добавился один новый («Touch и kiosk»). 6. Гейты кода не гонялись — на этапе ТЗ продуктового диффа нет (см. «Чего не проверял»). ## Закрытие раунда r1 | Находка r1 | Чем закрыта | Где это видно | |---|---|---| | **H1** (High) — ТЗ не называет влияние на touch/kiosk | Добавлен раздел «Touch и kiosk»: цитирует `TOUCH-SUPPORT.md` § Safety floor, называет её блокирующей для touch, объясняет, почему перенос точки рендера не меняет touch-поведение (тот же компонент, разметка, scrim, футер не меняются), добавлен AC8 (смок в touch-эмуляции на буквальном сценарии issue) | `docs/specs/402-confirm-outside-main-branch.md`, раздел «Touch и kiosk» (строки ~112-127 текущей редакции), AC8 | | **M1** (Medium, в скоупе) — ложное обоснование «свои ветки» для `_tapConfirm`/`_vacCalConfirm` | Формулировка заменена на две настоящие причины: (1) нет промиса — синхронный `exec()` / закрытие по `hp-close`; (2) точка входа недостижима из ранних веток. Добавлено явное требование к реализации не трогать расположение этих двух блоков при выносе `_dangerConfirm`, плюс AC9 фиксирует это как критерий приёмки | `docs/specs/402-confirm-outside-main-branch.md`, раздел «Скоуп / не-скоуп» (абзац «`_tapConfirm` и `_vacCalConfirm` — не в скоупе...»), AC9 | M1 закрыта полностью и точно — построчная сверка (`:11802`, `:11852`, `:11872-11878`, отсутствие `return` между ними) подтверждает переформулировку слово в слово. H1 закрыта по существу, с одной оговоркой (см. находку L1 ниже): текст верно поднимает safety floor и даёт достаточное объяснение для блокирующей части DoR (View/kiosk), но не называет явно 5 call site'ов `_confirmDanger` в `houseplan-editor-runtime.ts`, про которые r1 просил отдельную строку `Touch editor: …`. Разобрано отдельно ниже, почему это не держит вердикт красным/жёлтым. ## Унаследовано из r1 Без повторной проверки принято всё, чего дельта не касалась — документ `docs/reviews/SPEC-REVIEW-402-r1.md`, SHA `d94db87e`: - диагноз дефекта (структура `render()`, семь ранних веток, `hp-confirm` только в финальной) — построчно сверен в r1, дельта эти строки не меняла; - корректность `HpConfirmController` (`cancel`/`resolve`/токен) — `src/danger-confirm.ts` не тронут дельтой; - буквальный сценарий issue (`_deleteServerPlan` → `houseplan-onboarding-runtime.ts:218-224,273`) — не тронут; - факт «`noChange` нигде не оборачивается в шаблон» — граница ТЗ для языкового гейта `warm`, код не менялся; - AC1-AC4, AC6 — проверяемость и реалистичность доказательства (`demo/smoke_danger_confirmation.mjs`) — текст этих AC дельта не меняла; - AC5 — рассуждение про недостижимость `!hass/!_config` после первого монтирования и про то, что `noChange` не трогает уже открытый диалог; - AC7 — `hp-confirm` импортируется не лениво (`houseplan-card.ts:14`), бюджет не растёт; - граница скоупа с #32 (содержимое диалога, ревалидация после `await`) и с #406 (`alertdialog`/`aria-describedby`) — абзац не менялся; - соответствие `docs/SCOPE.md` (J4, J6) — не затронуто дельтой; - присутствие всех обязательных разделов §7.1, кроме нового «Touch и kiosk» — остальные разделы не удалялись и не переписывались. ## Находки ### L1 (Low, наблюдение — не блокирует). Число в цитате TOUCH-SUPPORT.md неверно **Где**: `docs/specs/402-confirm-outside-main-branch.md`, раздел «Touch и kiosk»: «`docs/TOUCH-SUPPORT.md` § Safety floor запрещает «best effort» в **трёх** вещах». **Проверено чтением**: `docs/TOUCH-SUPPORT.md:71-79` перечисляет **шесть** пунктов под «"Best effort" never permits:» — потерю данных, небезопасный вызов HA, обход подтверждения, залипание карточки вне View, поломку View/ kiosk соседним редактором, случайное сохранение геометрии от неверно понятого жеста. Ни разу не три. **Последствие**: не меняет вывод ТЗ — пункт «обход подтверждения разрушающего действия» реален и процитирован верно, вывод («задача поднимает пол обратно») не зависит от точного числа. Это неточная цифра в пересказе документа, а не выданная за факт догадка о продуктовом поведении. **Решение ревьюера**: снимаю как Low с записью, не блокирует. Автору стоит поправить «трёх» на «шесть» (или убрать число вовсе) в следующей правке, которую он уже будет делать по другому поводу — заводить цикл ради одного слова нецелесообразно (тот же принцип, что «r2 по #150» в §2.10). ### L2 (Low, наблюдение — не блокирует). Явного упоминания touch-статуса пяти вызовов из `houseplan-editor-runtime.ts` по-прежнему нет **Где**: раздел «Touch и kiosk» рассуждает в общем виде («тот же компонент, разметка не меняется») и доказывает это AC8, но AC8 сформулирован только для ветки онбординга; ни разу не назван `houseplan-editor-runtime.ts` и пять его вызовов `_confirmDanger`. **Почему это не блокирует, хотя r1 просил именно это**. Я перепроверил довод по существу, а не только по форме: - `docs/TOUCH-SUPPORT.md:139-151` («Testing and release gates») перечисляет четыре release-blocking гарантии, и «touch-editor safety floor» — в их числе (п.4), не только View/kiosk (п.2). Так что для полноты формально стоило бы явно назвать и редакторские call site'ы, как просил r1. - Но по коду («Контракт» ТЗ + AC1, AC2, AC4, AC5) `hp-confirm` выносится из цепочки `render()` **как единый механизм**, не привязанный к тому, кто вызвал `_confirmDanger`. AC4 («Открытое подтверждение переживает смену ветки») сформулирован без привязки к вызывающему — он покрывает и случай, когда подтверждение открыл редакторский call site, а карточка тем временем потеряла последнее пространство (`model.length` дошёл до нуля во время открытого диалога — ровно тот сценарий, который сам текст ТЗ называет «второй половиной дефекта»). Отдельного AC для редакторских call site'ов не нужно ровно потому, что контракт написан на уровне компонента, а не вызывающего. - Поэтому вывод раздела «Touch и kiosk» («разметка, размер целей нажатия, scrim, футер не меняются ни на строку») уже покрывает и эти пять call site'ов — только не называет их явно. Это пробел в полноте текста, не в контракте: настоящей неопределённости, которая изменила бы AC или поведение, за этим не стоит. **Решение ревьюера**: снимаю как Low, не как Medium — субстанция уже доказана на уровне механизма (AC1-AC5), формальная строка `Touch editor: supported` для пяти call site'ов была бы уместна для полноты, но её отсутствие не оставляет открытого продуктового вопроса и не меняет объём реализации. Не завожу повторный цикл ради одной строки текста (тот же принцип §2.10, что и для L1). Рекомендация автору — добавить эту строку попутно при следующей правке, не обязательно сейчас. ## Что проверено и признано корректным (дельта r2) - Раздел «Touch и kiosk» по существу верно определяет, что затронутая ветка (View-диалог онбординга) относится к «Fully supported» категории `TOUCH-SUPPORT.md` и что перенос точки рендера сам по себе не меняет touch-поведение компонента. - AC8 технически обоснован: паттерн `hasTouch` реален и используется в существующих touch-смоках так же, как описано в АС. - AC9 корректно фиксирует инвариант «блоки на месте» — построчная сверка подтверждает, что все три диалога (`_tapConfirm`, `_vacCalConfirm`, `_dangerConfirm`) в текущем дереве действительно в одной ветке без `return` между ними, то есть требование реализации не тащить их за компанию — обоснованное и проверяемое. - M1 закрыта полностью и точно, без остаточных вопросов. - Новый раздел не удаляет и не противоречит ни одному из ранее принятых разделов ТЗ (§7.1 набор полон). - Пункты 6-7 плана автотестов (touch-повтор сценария 1, проверка «один `hp-confirm` в DOM») соответствуют AC8 и риску «двойной рендер», названному в разделе «Риски» ещё в r1. ## Чего не проверял - **Гейты кода** (`npx tsc --noEmit`, `npm test`, `npm run build`, `check-docs`, смоки, `bundle:budget`, инварианты модели) — не гонялись: на этапе ТЗ продуктового кода по-прежнему нет, дифф пуст. Это предмет код-ревью после реализации. - **`scripts/mutation-gate.mjs` / `demo/smoke_danger_confirmation.mjs`** — не запускал; в r2 они не менялись, наследую проверку существования файлов из r1. - **Реальный браузерный рендер** — не переисполнял; воспроизведение из аналитики issue взято на веру, как и в r1 (это будет предметом смок-доказательства AC1-AC9 в код-ревью). - **`docs/specs/README.md`** — строка для #402 по-прежнему не добавлена; унаследованное из r1 решение не поднимать это отдельной находкой (известный долг §7.3 п.1) остаётся в силе. ## Вывод Обе находки r1 закрыты по существу (таблица выше). Дельта r2 не вносит новых Medium/High — только два наблюдения Low (неверное число в цитате документа, отсутствие явной строки про touch-статус пяти редакторских call site'ов), оба сняты ревьюером с записью и не блокируют выход в «Готово к разработке». **Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0**