# SPEC-REVIEW-212-r1 - **Issue:** [#212](https://github.com/Matysh/houseplan-card/issues/212) — «Правки по новым иконкам» - **ТЗ:** `docs/specs/212-device-icons-polish.md` (обычный трек, не `small`) - **Этап:** ревью ТЗ (PROCESS.md §2.4) - **Вердикт:** зелёный - **Цикл:** r1/4 - **High:** 0 · **Medium (в скоупе):** 0 · **Medium (вне скоупа):** 0 · **Low:** 1 (снят с записью) ## Скоуп ревью Issue #212 — консолидация трёх открытых задач (#22 отклик на нажатие, #154 sticky touch-hover, #181 неверный адрес документации) плюс собственные пункты владельца: уменьшение маркера устройства на 10% и исправление формы капсулы `display: value`. ТЗ живёт файлом `docs/specs/212-device-icons-polish.md` (трек «обычный», не `small`) — формат файла корректен для этого трека. Проверялось: тело и комментарии #212, тела #22/#154/#181 (полностью, без усечения), #179 и #211 (нормативный дизайн-пакет и его принятое исправление), `docs/SCOPE.md`, `docs/TOUCH-SUPPORT.md`, `docs/UX-MODES.md`, `docs/CONFIG-COMPATIBILITY.md`, сам текст ТЗ целиком (§1–§22) и — построчно — код на `dev`, который ТЗ цитирует как «подтверждённую причину» в §3 и как основание архитектурных решений в §16. ## Как проверялось Ревью на этапе `spec` не запускает гейты сборки/тестов — предмет проверки это ambiguity, доказуемость AC и отсутствие выданных за факт догадок. Вместо прогона тестов я построчно сверил каждое фактическое утверждение ТЗ о текущем коде с самим кодом на `dev` (`ec6f77b`), поскольку §3 ТЗ прямо заявляет «аудит текущего dev подтвердил четыре независимых дефекта» — такое заявление обязано быть проверяемым, а не риторическим. Сверено чтением (`grep`/`sed` по `src/styles.ts`, `src/houseplan-card.ts`, `src/space-card.ts`, `src/hp-dialog.ts`, `src/hp-help.ts`, `src/hp-color-opacity.ts`, `src/vacuum.ts`): | Утверждение ТЗ | Где | Результат сверки | |---|---|---| | §3.1: shell = `1.26875 × core`, нет отдельного коэффициента 0.9 | `styles.ts:1877` | подтверждено; `grep` на `* 0.9` / `--global-icon-factor` — ноль совпадений | | §3.2: `.device-core` держит `border-radius: 50%` глобально, `valonly` растит только ширину | `styles.ts:1812–1816, 1991–2001` | подтверждено — эллипс на широком value неизбежен | | §3.2: shell использует постоянный радиус `device-shell-size/2` | `styles.ts:1939, 1955` | подтверждено | | §3.3: `_clickDevice()` не создаёт transient feedback | `houseplan-card.ts` (нет отдельного feedback-owner рядом с dispatch) | подтверждено отсутствием | | §3.4: `_touchSeen` — статический (глобальный) флаг, `_notePointer()` закрывает только `_tip` | `houseplan-card.ts:5765,5776–5779` | подтверждено | | §3.4: `_hoverRoom` пишется из `mouseenter`/`mouseleave` | `houseplan-card.ts:15837–15864` | подтверждено | | §3.5/#181: hover-контракт View описан в `UX-MODES.md`, не в `CANVAS.md` | `UX-MODES.md` «View — display and device interaction only» | подтверждено дословно | | §7.3: Double-value pill уже имеет константу `0.39375 × core` | `styles.ts:2248` | подтверждено | | §7.2: vacuum puck зависит от `--dev-size` | `styles.ts:2556` (`--puck-size: calc(var(--dev-size)…)`) | подтверждено | | §16: перечисленные файлы (`device-face.ts`, `space-render.ts`, `hp-device-preview.ts`, `hp-dialog.ts`, `hp-help.ts`, `hp-color-opacity.ts`, `space-card.ts`) существуют; `pointer-modality.ts` — новый | `ls src/` | подтверждено | | §13: единственный `:hover` в `space-card.ts` — `.hp-static-btn:hover` | `space-card.ts:903` | подтверждено | | §13: по одному `:hover` в `hp-dialog.ts`/`hp-help.ts`/`hp-color-opacity.ts` (`.close`, `.trigger`) | построчный `grep` | подтверждено | | §19.1: названные smoke-файлы существуют, «device action feedback smoke» — новый, не переиспользование `smoke_feedback_v2.mjs` | `ls demo/`, чтение `smoke_feedback_v2.mjs` | подтверждено; `smoke_feedback_v2.mjs` вообще не про device-маркеры (комнатная gear-кнопка) — ТЗ верно не сослалось на него | | §4: версия пакета «1.1.1» | `docs/specs/179-…md`, `docs/specs/211-…md`, `docs/TESTING.md:342` | подтверждено предыдущими принятыми ТЗ, не изобретено | | 44×44 CSS px как уже существующий пол, а не новое требование | `styles.ts:1939` (`max(44px, var(--device-shell-size))`) | подтверждено | Все проверенные фактические утверждения ТЗ о текущем состоянии кода и документации оказались точными. Раздел §3 «Проблема и подтверждённая причина» не содержит ни одной догадки, выданной за факт, — каждый пункт воспроизводится в реальном коде по указанным строкам. Дополнительно сверены структурные требования PROCESS.md §7.1: сценарий, «что человек увидит», проблема, скоуп/не-скоуп, поведенческий контракт, UX, данные/миграция/i18n, AC1…AC16 с указанием способа доказательства, план тестов, риски, откат, release-артефакты — все разделы присутствуют, ни один не сведён к формальной заглушке. Блок §22 «принятые технические предположения» оформлен явно и содержит только технические (не продуктовые) решения — ни один из семи пунктов не подменяет продуктовое решение техническим языком. ## Находки ### Low-1 — межевой случай «plan-wide setting» из #22 закрыт молча, без явной пометки - **Файл:** `docs/specs/212-device-icons-polish.md`, §6 «Не входит в задачу» - **Что происходит:** оригинальный текст #22 (полностью поглощаемого этим ТЗ) перечисляет ожидаемое поведение как «a subtle scale dip or flash, **plan-wide setting**, only on devices that actually act, reduced-motion fallback». Формулировка «plan-wide setting» синтаксически допускает два прочтения: (a) эффект применяется единообразно по всему плану, без per-marker переопределения — то, что фактически описывает ТЗ в §10; или (b) запрос на новый пользовательский переключатель уровня плана (вкл/выкл или интенсивность). ТЗ выбирает (a) молча и одновременно явно исключает (b) в §6 («новый пользовательский toggle интенсивности/ длительности feedback»), не фиксируя, что здесь вообще была развилка. - **Почему не Medium.** Это ровно тот тип вопроса, который по PROCESS.md §7.1 адресуется владельцу («какой объём видимых изменений входит в этот issue»), но: (1) §4 ТЗ прямо и обоснованно ставит текст #212 выше текста #22 по приоритету, а сам #212 (написанный позже, тем же владельцем) перечисляет пять конкретных пунктов и ни словом не упоминает настройку; (2) §15 ТЗ уже держит открытым предохранитель — если реализация всё же потребует новую строку/переключатель, это автоматически трактуется как расширение скоупа с отдельным решением владельца, то есть риск «тихо потерять требование навсегда» отсутствует. - **Решение ревьюера:** снимается с записью. Автору стоит одной строкой в §22 зафиксировать, что «plan-wide setting» прочитано как «единая политика на весь план, не per-marker», а не как новый UI-переключатель — это ничего не меняет по существу, но убирает тихое разночтение источника, которое иначе выглядит как то самое «оставили в тексте, а не решили». Других находок — ни High, ни Medium, ни дополнительных Low — не выявлено. ## Что проверено и корректно - **Формат трека.** Issue не помечен `small`/`trivial` (сложность и риск 8/10 по аналитике владельца) — файл в `docs/specs/` обязателен и создан корректно, `small`-путь неприменим. - **Продуктовые разделы.** §1 «Сценарий» называет персону из `docs/SCOPE.md` (домочадец на телефоне/панели/компьютере, админ в редакторе устройств) и момент («нажимает на устройство»); §2 «Что человек увидит» — одной парой фраз, без терминов реализации (не упоминает `--dev-size`, resolver, pointermodality и т.п.). - **Обоснование скоупа по SCOPE.md.** Задача закрывает J1/J2/J7 (спойлер плашки для длинного значения читается хуже — J1; отклик на нажатие — J3; честный touch-контракт — обязательное требование `TOUCH-SUPPORT.md`, а не отдельная строка SCOPE, но сам документ называет touch-only дефект View продуктовым багом). Ничего из «Out of scope» SCOPE.md не задето: цвета, lock-инвариант, glyph viewport, LQI threshold — везде явно исключены в §6. - **Lock-инвариант не тронут.** §6 явно исключает изменение lock security; §9 явно исключает feedback для «secure/no-target/unavailable/missing/ unsupported no-op»; confirmation-путь (§9) требует повторной валидации target при подтверждении — согласуется с параграфом «The lock invariant» `docs/SCOPE.md` (единственная санкционированная поверхность — кнопка Unlock/Lock внутри открытой карточки, а не тап по плану). - **Правильный канонический адрес документации.** §19.4 обновляет `docs/UX-MODES.md` (не `docs/CANVAS.md`) для hover/pressed-владения — именно то, что требует #181, с явной фразой «`docs/CANVAS.md` не обновляется ради hover ownership». - **AC1…AC16.** Каждый критерий сформулирован как проверяемое числовое или структурное утверждение (допуски `±0.005`, `±20 ms`, `≤0.5 CSS px`, `44×44 CSS px`) и несёт способ доказательства (`unit`/`smoke`/`golden`/ `code review`/`performance profile`), как того требует DoR (PROCESS.md §2.5). Ни один AC не сформулирован через реализацию так, что критерий нельзя было бы фальсифицировать независимо от кода. - **Данные/миграция/i18n.** §15 корректно фиксирует отсутствие новых config/localStorage/backend-полей и отсутствие миграции; совместимость с `docs/CONFIG-COMPATIBILITY.md` не требует новой записи, так как round-trip не меняется — проверено чтением раздела «Status meanings» в этом документе. - **Touch-контракт.** Трейлер `Touch editor: best effort / intentionally degraded` относится к редакторам (Device/Plan/Background остаются desktop-first, §14), тогда как View/kiosk-touch — обязательная, release-blocking часть скоупа (AC8–AC12) в полном соответствии с `docs/TOUCH-SUPPORT.md` («A touch-only failure in View is a product defect»). Формат трейлера соответствует одному из трёх канонических значений документа. - **Откат.** §20 указывает на одиночный revert реализации без миграции данных и без storage rollback — согласуется с «Migration не нужна» (§15). - **Никаких искусственно вычисленных «Fresh»-цифр.** Числа 0.90/0.95/200 ms трассируются либо к телу #212 (5%, 0.2s), либо к уже принятому ТЗ #22 (перенос контракта eligibility/confirmation/reduced-motion, §4), а не придуманы автором ТЗ. - **Единственный открытый вопрос не требуется.** Явных продуктовых развилок, которые нельзя было бы разрешить чтением #212/#22/#154/#181 и уже принятых #179/#211, не найдено — комментарий аналитики владельца прямо закрывает этот путь («Продуктовых вопросов не осталось»), а единственная найденная двусмысленность (Low-1) не поднимается до уровня, требующего его участия. ## Чего не проверял - Не запускал `npx tsc --noEmit`, `npm test`, `npm run build` — на этапе `spec` кода ещё нет, эти гейты относятся к код-ревью (PROCESS.md §2.7) и реализации (§2.6). - Не запускал browser-смоки/golden — они тоже принадлежат циклу реализации и код-ревью, а не ревью ТЗ. - Не проверял `custom_components/**/*.py` — ТЗ прямо заявляет отсутствие изменений бэкенда и новых config-полей (§15), backend-путей в «Ожидаемые файлы» (§16) нет. - Не оценивал реализуемость 200 ms/0.95 анимации в конкретном браузерном движке (WAAPI vs CSS transition) — это явно помечено в §22 как «принято предположительно, ревьюер вправе изменить свободно» и будет предметом код-ревью, а не ревью ТЗ. ## Итог `docs/specs/212-device-icons-polish.md` — самодостаточное, добросовестно проверяемое ТЗ: каждое фактическое утверждение о текущем коде подтверждено построчной сверкой, каждый AC проверяем и несёт способ доказательства, скоуп и не-скоуп аккуратно очерчены и не задевают lock-инвариант или другие ограничители `docs/SCOPE.md`, канонический адрес документации (#181) исправлен верно. Единственная находка — Low, снимается с запиской автору, не блокирует переход в «Готово к разработке». **Вердикт: зелёный · цикл r1/4 · High: 0 · Medium: 0 в задаче**