Files
houseplan-card/docs/reviews/SPEC-REVIEW-402-r1.md
T
2026-08-31 13:19:34 +00:00

223 lines
20 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-402-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/402
- Артефакт ТЗ: `docs/specs/402-confirm-outside-main-branch.md` (полный трек, класс A)
- Заход: r1 · лимит циклов ревью ТЗ для полного трека — 4 (§4), израсходовано 0
- Ревьюер: Claude (роль «ревьюер ТЗ»), независимая сессия, без устных пояснений автора
- База сравнения: HEAD `d94db87e` (коммит, добавивший спецификацию)
## Скоуп ревью
Первый раунд ревью ТЗ для #402: регресс подтверждения опасного действия
(`hp-confirm`), которое рендерится только в финальной ветке `render()`
(`src/houseplan-card.ts`) и поэтому недоступно/теряется в остальных ветках
(онбординг «нет пространств», `fixed_floor` pending/invalid, `!space`).
Задача полного трека (P1, bug, класс A) — файл ТЗ обязателен, что и сделано.
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` целиком (разделы §2.4,
§2.5, §5, §7.1, §7.2), тело issue #402 и единственный комментарий (аналитика
S2).
2. Прочитан весь текст ТЗ `docs/specs/402-confirm-outside-main-branch.md`.
3. Каждое фактическое утверждение ТЗ о коде сверено с текущим деревом на SHA
`d94db87e`:
- структура `render()` (`src/houseplan-card.ts:11165-11884`) — все семь
ранних веток и их точки возврата;
- `HpConfirmController` (`src/danger-confirm.ts`) — поведение `confirm` /
`resolve` / `cancel`;
- вызов `_confirmDanger` из `_deleteServerPlan`
(`src/houseplan-onboarding-runtime.ts:218-224`, клик на `:273`);
- все восемь call site'ов `_confirmDanger` (`houseplan-card.ts`,
`houseplan-editor-runtime.ts` ×5, `houseplan-onboarding-runtime.ts` ×2);
- расположение `_tapConfirm` (`:11852-11868`) и `_vacCalConfirm`
(`:11802-11812`) относительно `_dangerConfirm` (`:11872-11878`);
- импорт `hp-confirm` — проверено, что он не ленивый (`houseplan-card.ts:14`,
верхнеуровневый `import`), что подтверждает заявление ТЗ об отсутствии
влияния на бюджет (AC7);
- использование `noChange` во всём `src/**` — подтверждено, что сентинел
нигде не оборачивается в шаблон (`grep noChange`), это делает техническое
утверждение границы ТЗ («noChange нельзя обернуть») фактом, а не догадкой.
4. Проверено соответствие терминологии `docs/USER-GUIDE.ru.md` (раздел про
общий диалог подтверждения) — видимых текстов ТЗ не меняет, конфликта нет.
5. Проверено `docs/TOUCH-SUPPORT.md` и DoR-чек-лист §2.5 на предмет
обязательного пункта про touch/kiosk — см. находку H1.
6. Проверены обязательные разделы §7.1 — присутствуют все (сценарий · что
человек увидит · проблема/контракт · скоуп/не-скоуп · UX · модель данных ·
i18n · AC1-7 с доказательством · план автотестов · риски · откат ·
release-артефакты).
7. Гейты кода (`tsc`, `test`, `build`) не гонялись: на этапе ТЗ продуктового
диффа ещё нет, гонять их не над чем (см. «Чего не проверял»).
## Находки
### H1 (High, блокирует). ТЗ не называет влияние на touch/kiosk — обязательный, объявленный блокирующим пункт DoR
**Где**: весь файл `docs/specs/402-confirm-outside-main-branch.md` — ни разу
не упоминает touch, kiosk или `TOUCH-SUPPORT.md` (`grep -i touch|kiosk` по
файлу — ноль совпадений).
**Почему это находка, а не формальность**. PROCESS.md §2.5 перечисляет пункты
«Готово к разработке» и явно помечает один из них как блокирующий отдельно от
остальных: «влияние на touch по `docs/TOUCH-SUPPORT.md` (**View и киоск —
блокирующие**)», и там же: «Если хоть один пункт не выполнен — статус не
«Готово к разработке»». `TOUCH-SUPPORT.md` со своей стороны требует того же
прямым текстом: «New editor feature specifications … must state one of:
`Touch editor: supported` / `best effort / intentionally degraded` / `not
exposed`».
Затронутая этим ТЗ поверхность — не абстрактный служебный код: это ровно
диалог `<hp-confirm>` в состоянии «нет пространств» (онбординг) и в
`fixed_floor` pending/invalid, то есть экран, который согласно
`TOUCH-SUPPORT.md` относится к категории «View dialogs and safe device
actions» — «Fully supported» на touch, без исключений best-effort. Часть
затронутых call site'ов (`houseplan-editor-runtime.ts`, 5 мест) при этом
принадлежит редакторам, для которых допустима best-effort деградация — но
только если она **явно объявлена**, а не подразумевается молчанием.
**Симптом отсутствия анализа**: без явного утверждения нельзя отличить
«автор проверил — влияния нет» от «автор не думал про touch вовсе». Это
особенно значимо здесь, потому что рефакторинг двигает `hp-confirm` из
внутреннего дочернего узла одной `<ha-card>` в узел, рендерящийся отдельно от
конкретной ветки разметки (см. Риск «двойной рендер диалога» в самом ТЗ) —
у смены места крепления DOM-узла есть техническая возможность задеть
touch-специфичные вещи (portal/DOM-scope для `dialog.showModal()`,
`pointer-events`, `:focus-trap`), даже если по факту не заденет.
**Фактическая оценка (для экономии цикла)**: содержательно последствий,
скорее всего, нет — `<hp-confirm>` остаётся тем же кастомным элементом с тем
же shadow DOM и той же логикой модального диалога независимо от того, чьим
прямым потомком в основном дереве он является; ни один AC1-7 не описывает
изменение верстки, жестов или поведения `hp-confirm` самого по себе. Но это
вывод ревьюера, а не факт, зафиксированный автором в ТЗ — а фиксировать его
обязан автор (правило #163 «New editor feature specifications … must state
one of»).
**Требуемая правка**: добавить в ТЗ явную строку по образцу
`Touch: View/kiosk — fully supported, без изменений (тот же <hp-confirm>,
маршрут рендера не меняет разметку/жесты диалога)`, и отдельно —
`Touch editor: supported` для пяти call site'ов из `houseplan-editor-runtime.ts`,
если они действительно не деградируют. Это правка одной-двух строк текста, не
кода.
### M1 (Medium, в скоупе задачи). «Не-скоуп»-обоснование для `_tapConfirm`/`_vacCalConfirm` содержит фактическую неточность
**Где**: `docs/specs/402-confirm-outside-main-branch.md`, раздел «Скоуп /
не-скоуп»: «Не в скоупе: … `_tapConfirm` и `_vacCalConfirm` — у них своя
механика и свои ветки.»
**Проверено чтением кода**: `_tapConfirm` рендерится в
`src/houseplan-card.ts:11852-11868`, `_vacCalConfirm` — в `:11802-11812`,
`_dangerConfirm` — в `:11872-11878`. Все три блока лежат в **одной и той же**
финальной ветке `render()` (последний `<ha-card>`, тот же самый шаблон, между
ними нет ни одного `return`). Утверждение «свои ветки» в буквальном
прочтении неверно: у них нет собственных веток `render()` — они делят ровно
ту же ветку, что и `_dangerConfirm` до этого фикса, и подвержены тому же
классу дефекта («открытое подтверждение исчезает при смене ветки», вторая
половина дефекта из аналитики S2), например если многоклиентская
синхронизация (J6, `docs/SCOPE.md`) уводит карточку в `fixed_floor` pending
или обнуляет `model.length` **пока** открыт `_tapConfirm`/`_vacCalConfirm`.
Отличие от `_dangerConfirm`, которое у ревьюера НЕ вызывает вопросов и,
похоже, и есть настоящая причина исключения: `_tapConfirm`/`_vacCalConfirm`
не используют `HpConfirmController` и не отдают вызывающему `Promise` —
`exec()` вызывается синхронно по клику, поэтому «вечно висящего промиса» у
них в принципе не бывает (при потере диалога они не блокируют await
вызывающего, а просто становятся недоступны до следующего рендера основной
ветки). А сами точки входа (`marker.tap_confirm`, календарь пылесоса)
физически недостижимы из веток онбординга/`fixed_floor`/`!space` — там нет
устройств на плане, значит буквальный сценарий issue их не касается.
Это делает исключение из скоупа **разумным по существу**, но
**обоснование в тексте — неверным**: «свои ветки» вместо настоящей причины
(«синхронный exec без промиса» + «недостижимость точки входа из веток вне
основной»). Ложное обоснование опасно не абстрактно: тот, кто будет
реализовывать вынос `hp-confirm` из финальной ветки, должен точно знать, что
`_tapConfirm`/`_vacCalConfirm` физически стоят в той же ветке и их нельзя
случайно утащить вместе с `_dangerConfirm` при выносе блока — а формулировка
«у них свои ветки» наводит на обратное представление.
**Требуемая правка**: заменить формулировку на техническую причину исключения
(синхронный `exec`, недостижимость входа) и явно предупредить реализацию не
трогать расположение этих двух блоков при выносе `_dangerConfirm`. Опционально
(не обязательно для этого issue) — упомянуть остаточный риск «уже открытый
`_tapConfirm`/`_vacCalConfirm` теряется при смене ветки во время
многоклиентской синхронизации» как кандидата в отдельный issue, если владелец
сочтёт его достаточно вероятным; в скоупе #402 фиксировать не обязательно —
issue именно про мёртвую кнопку и висящий промис, которых у этих двух объектов
нет.
## Что проверено и признано корректным
- **Диагноз дефекта** — точен и воспроизводим по строкам: `render()` действительно
возвращает `hp-confirm` только в финальной ветке (`:11872`), все более ранние
`return` (`:11166`, `:11170`, `:11171`, `fixed_floor` pending/invalid,
`!model.length`, `!space`) реально существуют и реально не содержат
`hp-confirm`.
- **`HpConfirmController` описан верно**: `cancel()` действительно резолвит
`false` через `resolve()`, `resolve()` сверяет токен, новый `confirm()`
отменяет предыдущий запрос (`src/danger-confirm.ts:47-72`) — контроллер не
участвует в дефекте, претензия ТЗ обоснована.
- **Буквальный сценарий issue** (`_deleteServerPlan` из онбординга) сверен
построчно: `src/houseplan-onboarding-runtime.ts:218-224` вызывает
`this.host._confirmDanger`, кнопка в `:273` — реальная причина «мёртвой
корзины» из issue.
- **Техническое утверждение о `noChange`** («нельзя обернуть в шаблон») —
подтверждено: во всём `src/**` `noChange` возвращается только как значение
`render()` целиком (`houseplan-card.ts`, `editor.ts`, `space-card.ts`,
`space-editor.ts`), ни разу не встречается внутри `${…}` — граница ТЗ не
придумана, а списана с уже действующего паттерна кодовой базы.
- **AC1-AC4, AC6** — проверяемы, у каждого назван способ доказательства
(смок), и способ реалистичен: `demo/smoke_danger_confirmation.mjs` уже
существует и уже покрывает контракт диалога, дополнение под новые ветки —
органичное расширение того же файла.
- **AC5** (иммедиат-отказ в `!_config||!hass` и `warm`) — продуман
корректно: `!hass`/`!_config` не могут стать `false→true→false` в течение
жизни примонтированной карточки (Lovelace не сбрасывает `hass`/`config` в
falsy после первичной установки), то есть эта ветка достижима только до
первого рендера — отказывать там немедленно безопасно и не теряет открытый
диалог (терять нечего). Ветка `warm` реально достижима в течение сессии
(смена языка), и там `noChange` **сохраняет** уже показанный диалог
нетронутым (Lit не трогает DOM) — то есть AC5 верно ограничивает
немедленный отказ только НОВЫМИ запросами, не описывая (и не должен
описывать) отмену уже открытых.
- **AC7 (бюджет)** — `hp-confirm` импортируется в шапке файла обычным
(не ленивым) `import` (`houseplan-card.ts:14`), поэтому перемещение точки
рендера не добавляет новый чанк/зависимость — заявление «правка структурная,
бюджет не растёт» подтверждается фактическим импортом.
- **Скоуп/не-скоуп в остальном** — граница с #32 (содержимое диалога,
ревалидация после `await`) и с #406 (`alertdialog`/`aria-describedby`)
названа явно и не пересекается с контрактом этого ТЗ.
- **Обязательные разделы §7.1** — все присутствуют, включая «откат» и
«release-артефакты» (changelog RU+EN).
- **Соответствие `docs/SCOPE.md`**: чинит J4 (онбординг, «zero to a working
plan») и снимает риск для J6 (multi-client sync может вызвать смену ветки
под открытым диалогом) — задача не расширяет продукт, а восстанавливает
ранее рабочее поведение (регресс против до-#32).
- Метки issue (`bug`, `P1`, `S4-spec-review`, без `small`/`trivial`)
согласуются с заявленным в ТЗ полным треком.
## Чего не проверял
- **Гейты кода** (`npx tsc --noEmit`, `npm test`, `npm run build`,
`check-docs`, смоки, `bundle:budget`) — не гонялись: на этапе ТЗ
продуктового кода ещё нет, диффа для гейтов не существует. Это будет
предметом код-ревью после реализации.
- **`scripts/mutation-gate.mjs` / `demo/smoke_danger_confirmation.mjs`** —
не запускал, только убедился, что оба файла существуют и их формат
(реестр мутантов, browser-smoke на реальной карточке) совместим с планом
автотестов ТЗ.
- **Таблицу `docs/specs/README.md`** — строка для #402 в неё не добавлена,
но это не регрессия этого ТЗ: специфика #395-#401 туда тоже не занесена
(таблица не поддерживается систематически, известный долг §7.3 п.1
документа процесса), поэтому не поднимаю отдельной находкой.
- **Реальный рендер в браузере** — на этапе ТЗ кода нет, воспроизведение
дефекта в описании ТЗ и issue взято на веру как «воспроизведено исполнением»
автором аналитики; независимо не перепроверял (это будет предметом
смок-доказательства в код-ревью).
- **`_tapConfirm`/`_vacCalConfirm` как отдельный дефект** — не завожу
отдельный issue: по результату разбора (см. M1) это не тот же класс
дефекта (нет висящего промиса, нет недостижимости из веток issue), поэтому
это не «Medium вне скоупа» по #202, а неточность формулировки внутри
текущего ТЗ — чинится правкой текста в этом же документе.