# SPEC-REVIEW-358-r1 **Issue:** #358 — «Холодная вкладка + пылесос (реальная телеметрия): `_vacMapId` бросает в `willUpdate` — карточка перестаёт обновляться» **Этап:** spec (PROCESS.md §2.4) · **Трек:** small (§5, метка `small`, подтверждено владельцем: комментарий S2→S3 от 2026-08-28) · **Заход:** r1 · блокирующих циклов израсходовано 0 из 2. ## Скоуп Лёгкий трек: ТЗ живёт в теле issue #358 (раздел «## ТЗ (small-трек, S3)»), файла в `docs/specs/` нет — соответствует §5, проверено `ls docs/specs/`. ТЗ описывает три пункта контракта: - **К1** — перенос реализации `_vacMapId` с `houseplan-editor-runtime.ts` на `houseplan-card.ts` (владелец полей — карта), runtime делегирует в host. Тот же приём, что #357 применил к `_toggleIntent`. - **К2** — два защитных гарда: `_decorShapeDown` (парный гард к `_decorShapeDbl` из #337) и рендер диалога `_vacCalConfirm` за `this._editorRuntime ?`. - **К3** — новый холодный смок `demo/smoke_cold_view_vacuum.mjs`, закрывающий слепое пятно демо-фикстуры (`vacuum.mower` без атрибутов позиции). AC1–AC4 повторяют issue-AC1…4 с указанием способа доказательства (smoke / smoke + мутант / smoke + чтение кода). Откат — один revert, без миграций. ## Как проверялось Ревью ТЗ этапа `spec` не гоняет гейты (§8 относится к код-ревью) — предмет проверки этого этапа: выполнимость и однозначность ТЗ, и то, что автор не выдал догадку за факт. Проверка велась чтением действующего исходника на `61911b86` (текущий `HEAD`, тот же SHA, на котором зелёный CI Validate уже подтверждён — #343) и сопоставлением каждого утверждения ТЗ с кодом. Прочитано и сверено: 1. `docs/SCOPE.md` — сценарий issue закрывает J1 («живой обзор дома»): баг останавливает весь цикл обновления Lit, то есть бьёт по ядру продукта (кухня/дача с киоском), а не по периферийной функции. 2. `PROCESS.md` §2.4, §5, §7.1 — обязательные разделы лёгкого трека (проблема · контракт · AC с доказательством · откат) присутствуют. 3. Тело issue #358 целиком + единственный комментарий (владелец, S2→S3). 4. `docs/VACUUM.md` — цепочка фолбэка `map_name → current_map → map_index → selected_map → default` совпадает с описанием К1. 5. Код: - `src/houseplan-card.ts:11707-11709` — сегодняшняя жёсткая заглушка `_vacMapId`, вызываемая без гарда из `_captureRenderDeviceSnapshot` (`:4471`, внутри `willUpdate`) и `_renderVacuums` (`:11807`) — подтверждён ровно тот баг, который описывает issue; - `src/houseplan-editor-runtime.ts:9979-9985` — реализация, которую К1 предлагает перенести. Текст в ТЗ (`const sel = ve ? planHass?.states?.[ve] ?.attributes?.selected_map : null; return vacMapIdWithFallback(tele.mapId, sel);`) и комментарий HP-1541-01 процитированы буквально, без искажений; - `src/vacuum.ts:252` — `vacMapIdWithFallback` действительно экспортируется оттуда и реализует «не-nullish» правило, о котором говорит комментарий; - `src/houseplan-editor-runtime.ts:11633-11648` — прецедент того же приёма на `_toggleIntent` из #357: карта владеет реализацией, runtime держит делегирующего двойника (`return this.host._toggleIntent(...)`), внутренние вызовы runtime (`this._toggleIntent(...)` на `:11929`, `:11648`) продолжают работать без изменений. К1 в точности повторяет эту схему — не догадка, а проверенный шаблон; - `src/houseplan-card.ts:7630-7632` — `_decorShapeDown` действительно жёсткая заглушка без гарда; `:7639-7642` — `_decorShapeDbl` действительно уже несёт `if (!this._editorRuntime) return;` (гард из #337, на который К2 ссылается как на образец); - `src/styles/plan.styles.ts:664` — `.decorlayer .dshape { pointer-events: none; }` вне `mode-decor` подтверждает, что реальный указатель не достаёт фигуру в View, а значит только синтетический `pointerdown` (как в плане смока К3-г) может воспроизвести падение — соответствует помеченной в issue пометке «проверено» (исполнением, не гипотеза); - `src/houseplan-card.ts:11346-11361` — ряд редакторских диалогов действительно рендерится по шаблону `${this._X ? this._editorRuntime ? html\`…\` : nothing : nothing}`, а `_vacCalConfirm` (`:11351`) — единственный из них без внешнего гарда `this._editorRuntime ?`. К2 описывает точное целевое выражение, совпадающее с этим шаблоном; - `src/houseplan-editor-runtime.ts:10038,10052` — `_vacCalConfirm` присваивается truthy-значение только внутри runtime; в `houseplan-card.ts` встречается лишь сброс в `null`. Значит утверждение AC4 «кейс недостижим исполнением по построению» — не догадка, а факт, подтверждённый полным grep всех присваиваний; - `src/houseplan-card.ts:11730-11731` — `_vacApplyCalibrationProposal` (кнопки диалога) — тоже жёсткая заглушка, как и указано в issue. 6. `demo/smoke_vacuum.mjs`, `demo/smoke_cold_view_toggle.mjs`, `demo/serve.mjs` (`launchColdView`) — механика инъекции сущности с телеметрией через `card.hass`/`card._serverCfg`/`card._layout` и трекинг `page.on('request'/'pageerror')` уже существует и покрывает ровно то, что требует план К3 (несколько кадров `hass`, отсутствие запроса `houseplan-editor-runtime-*.js`, ноль pageerror, синтетический pointerdown). Смок, описанный в К3, технически реализуем без новой инфраструктуры. 7. `scripts/mutation-gate.mjs` — реестр мутаций, шаблон записи `cold-view-toggle-delegated-to-runtime` (добавлен в #357) — ревертит `_toggleIntent` карты назад на делегацию в runtime и проверяется тем же холодным смоком. AC3 просит зеркальную запись для `_vacMapId`; формулировка ТЗ («вернуть делегацию `_vacMapId` в runtime») однозначно отображается на эту механику — проверяемый, а не расплывчатый критерий. 8. `demo/smoke_static_icon.mjs`, `demo/smoke_vacuum_firstuse.mjs` — подтверждён слепое пятно демо-фикстуры: `vacuum.mower` заведён без `vacuum_position`, значит ни один существующий смок (включая холодные) не проходит через ветку `telemetry ? this._vacMapId(...) : null`. ## Находки Нет. High: 0, Medium: 0, Low: 0. Отдельно проверено на «догадку, выданную за факт» (главный риск по инструкции ревью): каждое утверждение К1/К2/К3/AC1-4, сформулированное как факт о текущем коде, сверено с исходником построчно (см. таблицу выше) и подтвердилось без исключений. Технические решения, оставленные без явного обсуждения (имя файла смока, точный id мутанта, порядок гардов), относятся к классу «агенты решают сами» (§7.1) и не требуют пометки «принято предположительно» — они не затрагивают видимое пользователю поведение. ## Что проверено и корректно - Соответствие лёгкому треку (§5): ТЗ в теле issue, файла в `docs/specs/` нет. - Обязательные разделы ТЗ лёгкого трека присутствуют и однозначны. - Каждый AC называет способ доказательства (smoke / smoke+мутант / smoke+чтение кода) и не содержит открытых вопросов. - К1 текстуально и по логике совпадает с действующим кодом-источником (`houseplan-editor-runtime.ts`) и с прецедентом #357. - К2 описывает ровно два реальных гэпа (найдены построчным grep всех присваиваний/рендеров) и предлагает решение по образцу уже принятого кода. - К3 реализуем на существующей инфраструктуре демо-стенда без новых примитивов. - Продуктовая рамка (сценарий, кого касается, почему это выше по тяжести, чем #357) присутствует в теле issue и совпадает с J1 из `docs/SCOPE.md`. - Открытых продуктовых вопросов к владельцу нет — и по факту не должно быть: ожидаемое поведение («карточка живёт на холодной вкладке») уже зафиксировано как баг, а не как решение с вариантами. - Откат описан («один revert, конфиг/контракты не меняются») и достаточен для этого класса правки (без миграций, без нового UX-контракта). ## Чего не проверял - Гейты `npx tsc --noEmit` / `npm test` / `npm run build` не гонялись: на этапе `spec` кода ещё нет (issue не покидал `S3/S4`), гонять их не над чем. Дешёвые гейты на `61911b86` (последнем коммите `dev`) уже зелёные — Validate: https://github.com/Matysh/houseplan-card/actions/runs/33198366583. - Исполнение самого смока `demo/smoke_cold_view_vacuum.mjs` — он ещё не написан, это предмет реализации, не ТЗ. Оценивалась только его осуществимость на существующей инфраструктуре (`launchColdView`, паттерны инъекции сущности), а не его будущий текст. - `scripts/smoke-links.mjs` / реестр обнаружения смоков — не проверял, требует ли новый файл смока отдельной регистрации там; это техническая деталь реализации, а не предмет ТЗ, и её увидит код-ревью. - Golden/визуальные эталоны, backend-тесты (`tests_backend`), performance-профили — вне скоупа: правка не трогает `custom_components/**`, не меняет визуальный результат и не названа в AC как влияющая на производительность. ## Вердикт Зелёный. ТЗ выполнимо, каждый AC проверяем и снабжён способом доказательства, фактические утверждения о коде подтверждены построчно, догадок, выданных за решения, не найдено. Готово к разработке.