Files
houseplan-card/docs/reviews/SPEC-REVIEW-381-r1.md
T
2026-08-30 07:48:16 +00:00

20 KiB

SPEC-REVIEW-381-r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/381 — «Действие по нажатию: добавить "Ничего не делать"»
  • Этап: ТЗ на ревью (PROCESS.md §2.4), полный трек (владелец: нарушен критерий §5 «нет нового UX-контракта» — новое наблюдаемое действие и новое сохраняемое значение публичной конфигурации)
  • ТЗ: docs/specs/381-no-op-tap-action.md
  • Материал ревью: ТЗ на ветке issue/381-no-op-tap-action, ревизия автора 7a260e8f; git diff --stat origin/dev...HEAD подтверждает, что изменены только docs/specs/381-no-op-tap-action.md и docs/specs/README.md — продуктовый код не тронут (закономерно для этапа spec)
  • Заход: r1 · блокирующих циклов израсходовано 0 из 4 до этого вердикта

Скоуп ревью

Первый заход — предыдущего вердикта нет, разбор полный. Проверялось:

  • соответствие docs/SCOPE.md (job, персона, поверхность);
  • корректность выбора трека (полный, не small/trivial) по критериям §5;
  • наличие всех обязательных разделов ТЗ по §7.1, включая «риски» и «откат»;
  • однозначность и доказуемость каждого AC1…AC9, указание способа доказательства;
  • отсутствие догадок, выданных за факт: каждое утверждение ТЗ о текущем поведении кода сверено с исходниками, а не принято на слово;
  • согласованность терминологии с docs/USER-GUIDE.ru.md и TOUCH-SUPPORT.md;
  • полнота списка «Затронутые файлы и модули» — не пропущен ли реальный потребитель tap_action.

Как проверялось

  1. Прочитаны docs/SCOPE.md, PROCESS.md (§2, §4, §5, §7, §8), AGENTS.md.
  2. Прочитано тело issue #381 и все три комментария (аналитика, claim, публикация ТЗ) через gh issue view --json.
  3. Прочитан ТЗ docs/specs/381-no-op-tap-action.md целиком.
  4. Каждое фактическое утверждение ТЗ о текущем коде сверено с dev:
    • src/logic.ts:910 — TAP_ACTIONS = ['info', 'more-info', 'toggle', 'run'] — подтверждено, ровно четыре текущих действия.
    • src/device-toggle.ts:463-475 (projectedTapAction) — подтверждено: любой нераспознанный непустой токен возвращает 'info' («fail closed»), проекция света по умолчанию срабатывает только при реальном отсутствии значения.
    • src/houseplan-card.ts:5187-5313 (_clickDevice) — подтверждено: после веток toggle/info/run/more-info функция заканчивается безусловным this._infoCard = actionDevice; — реальный fallback на локальную карточку, который контракт §2.2 обязан обойти до него, а не после (проверен соответствующий пункт мутантов).
    • src/houseplan-card.ts:5325-5330 (_keyDevice) — подтверждено: Enter/Space вызывают тот же _clickDevice() после preventDefault().
    • src/houseplan-card.ts:5178-5185 (_ctxDevice) и long-press на 600 мс (:6360-6364) — подтверждено: оба жеста не читают tap_action вообще, независимы от выбора действия, как заявляет контракт §3.
    • src/houseplan-editor-runtime.ts:12346 — маппинг селектора TAP_ACTIONS.map((v) => [v, 'tap.' + v.replace('-', '_')]) — подтверждено, что none автоматически получит ключ tap.none без спецкода.
    • src/houseplan-editor-runtime.ts:12337,12352,12388,12394,12423 — подтверждено: Toggle-hint, target chooser и confirmation-чекбокс скрыты условием effectiveTapAction === 'toggle'/'run'; для none они погаснут автоматически, без новой ветки видимости.
    • src/houseplan-editor-runtime.ts:7934-7940 (_markerTapActionFields) — подтверждено: untouched-путь (!tapActionTouched) пишет originalTapAction как есть, включая legacy/неизвестный литерал — lossless-контракт AC5 уже существует для этого поля, задача его не ломает.
    • src/houseplan-editor-runtime.ts:8034 — tap_target пишется null для любого effectiveTapAction !== 'run' — подтверждает пункт «Принятых предположений» про очистку tap_target.
    • custom_components/houseplan/validation.py:1693 — MARKER_SCHEMA принимает vol.Any("info", "more-info", "toggle", "run", "cover", None) — подтверждено, backend действительно отклонит седьмой (none) без schema-правки.
    • custom_components/houseplan/import_export.py:1386 — подтверждён список полей, которые virtualization дубликата стирает вместе с tap_action.
    • src/device-presentation.ts:264-338 — подтверждено, что визуальная роль cover-устройства (coverOwnsFace) не зависит от выбора none: код уже форсирует tapAction: 'cover' для presentation independent от персистентного действия, поэтому AC4 (presentation parity) не требует здесь правок.
    • src/i18n/{en,ru,de,fr}.json — подтверждено, что это ровно четыре существующих словаря (пятого/шестого локале нет) и что паттерн ключей tap.<action> уже используется (tap.info, tap.toggle, tap.run).
    • tests_backend/test_validation.py — подтверждено существование cross-language parity теста TAP_ACTIONS, на который ссылается план автотестов (это не выдуманная будущая инфраструктура).
  5. Сверена терминология: docs/USER-GUIDE.ru.md:812 подтверждает, что «Действие по нажатию» — существующее название поля редактора, не изобретённое ТЗ.
  6. docs/TOUCH-SUPPORT.md — подтверждено, что View/kiosk — гарантированно touch-поверхность; ТЗ явно требует паритет mouse/touch/keyboard в AC2, что закрывает блокирующее требование §2.5 без отдельного раздела «Touch».
  7. Найден прямой прецедент калибровки строгости в этом же репозитории: docs/reviews/SPEC-REVIEW-376-r1.md (M1 «раздел откат отсутствует полностью» — классифицирован как Medium, не High) и docs/reviews/SPEC-REVIEW-377-r1.md (тот же тип находки, вердикт жёлтый · High: 0 · Medium: 1). Severity ниже откалибрована по этому прецеденту, а не назначена заново.
  8. Сравнены с шаблоном двух последних full-track ТЗ этого же автора — docs/specs/377-decor-default-persist.md и docs/specs/378-value-face-source.md — оба содержат отдельные разделы ## Риски и ## Откат; это подтверждает, что раздел ожидается предметно, а не просто присутствует в §7.1 формально.

Гейты (tsc/test/build/invariants/smoke/golden) не прогонялись: этап — ревью ТЗ, продуктовый код в этой ветке не менялся (см. git diff --stat выше). Они относятся к этапу код-ревью (§2.7) и будут актуальны в следующем цикле.

Находки

M1 (Medium, в скоупе) — отсутствуют обязательные разделы «Риски» и «Откат» (§7.1)

docs/specs/381-no-op-tap-action.md не содержит ## Риски и ## Откат. Оба раздела прямо названы обязательными в PROCESS.md §7.1 («Обязательные разделы ТЗ: … план автотестов · риски · откат · release-артефакты») и входят в чек-лист DoR §2.5 («риски перечислены», «откат: как выключить или вернуть назад»). Оба непосредственно предшествующих full-track ТЗ этого же автора — docs/specs/377-decor-default-persist.md и docs/specs/378-value-face-source.md — оба раздела содержат с предметным наполнением (конкретные риски со снимающим механизмом; git revert + анализ потери данных для отката). В 381 часть содержания фактически рассеяна по тексту («Известная граница downgrade» в разделе Import/export, раздел «Мутанты» как список технических рисков тестирования), но ни то, ни другое не отвечает на вопрос §2.5 буквально: как откатить именно этот выпуск, если none после релиза окажется проблемным (флага Labs нет, отдельной миграции нет — значит ответ, вероятно, тривиален: git revert реализационных коммитов, уже записанный tap_action: "none" остаётся в конфиге и безопасно читается как info старым фронтом — но это нужно написать явно, а не оставлять читателю выводить самому), и не перечисляет предметные риски конкретно этого контракта (например: неполный список потребителей tap_action в «Затронутые файлы» — риск того, что найдётся шестой потребитель уже после реализации; путаница none vs null в ручном редактировании YAML/API в обход UI).

Воспроизведение: grep -n "^## Риски\|^## Откат" docs/specs/381-no-op-tap-action.md не находит ни одного совпадения; grep -n "^##" (полный список выше в разделе «Как проверялось» не повторяю) показывает переход прямо от «План автотестов» к «Производительность и безопасность» и «Release-артефакты».

Серьёзность откалибрована по прецеденту. SPEC-REVIEW-376-r1.md нашёл структурно идентичную находку («раздел «откат» отсутствует полностью») и классифицировал её как Medium, а не High; красный вердикт того раунда был вызван отдельной, не связанной High-находкой. Здесь High-находок нет, поэтому по правилу §2.4 («без High это жёлтый вердикт») этот Medium один определяет вердикт «жёлтый».

Что делать: добавить ## Риски (2–4 пункта: неполный список потребителей tap_action, путаница none/null в ручном редактировании конфига в обход UI, что ещё именно автор считает риском этого контракта) и ## Откат (git revert реализационных коммитов; судьба уже записанного tap_action: "none" на старом/новом фронте и бэкенде — эта часть уже фактически написана в разделе «Import/export и downgrade», её нужно только процитировать/сослаться под правильным заголовком, а не сочинять заново).

L1 (Low, снимается) — AC4 называет способ доказательства словом вне канонического словаря

AC4 помечен «unit + visual assertion»; §2.5 перечисляет канонические ярлыки unit/backend/smoke/golden/«ревью кода». «Visual assertion» не входит в этот список и по названию мог бы намекать на golden-скриншот, которого раздел «Release-артефакты» этого же ТЗ прямо исключает («Новый plan golden не требуется…»). Снимаю без правки: раздел «Release-артефакты» и формулировка AC4 («совпадают DOM/classes/…») однозначно указывают, что имеется в виду DOM/class-assertion в unit-тесте, а не отдельный вид гейта — противоречия по существу нет, только словарь. Если автор всё равно правит документ по M1, имеет смысл переименовать в «unit (DOM assertion)» заодно, но отдельного цикла это не требует.

Что проверено и корректно

  • Продуктовая рамка. Job J3 docs/SCOPE.md — точный якорь; пассивный маркер как явный выбор администратора не противоречит ни одному пункту «Out of scope» и не расширяет action-контракт за пределы короткого клика. Сценарий и «что человек увидит» — конкретные, без терминов реализации, отвечают на оба обязательных вопроса §7.1.
  • Трек выбран верно. Аргумент «новый UX-контракт» из аналитики подтверждён кодом (новый persisted enum literal, новое наблюдаемое поведение) — не small и не trivial.
  • Все технические утверждения о текущем коде подтверждены чтением исходников (полный список — раздел «Как проверялось», пункт 4); догадок, выданных за факт, не найдено.
  • Контракт поведения однозначен и без домыслов, включая точку вставки no-op в _clickDevice() (до fallback-карточки, а не после) — контракт явно называет актуальный порядок веток и совпадает с реальным кодом.
  • AC1…AC9 проверяемы, у каждого указан способ доказательства; AC7 (backend) корректно требует cross-language parity test, который уже существует и будет расширен, а не написан с нуля.
  • План автотестов и раздел «Мутанты» называют конкретные ошибки реализации (спроецировать none в info; вставить no-op после fallback-карточки; связать none с недоступным Toggle) — это ровно те ошибки, которые нашлись бы при поверхностном прочтении текущего кода, что показывает, что план тестов написан против реального риска, а не формально.
  • i18n и терминология — «Действие по нажатию» и паттерн ключей tap.* взяты из существующего кода/документации, не изобретены; ровно 4 словаря названы и ровно 4 существуют.
  • Touch и defaults решены и не оставлены открытыми: паритет mouse/touch/keyboard явно в AC2, defaults явно не меняются (Скоуп/Не-скоуп).
  • Release-артефакты называют оба changelog в скоупе одного коммита с реализацией — соответствует политике User-Visible: yes (§2.6).
  • Открытых продуктовых вопросов действительно нет: единственная зона неоднозначности («марker остаётся видимым, без disabled-вида») напрямую закрыта формулировкой самого issue («Выбранный маркер остаётся видимым и показывает свои состояния») — это не догадка автора ТЗ, а прямая цитата владельца.

Чего не проверял

  • Не прогонялись npx tsc --noEmit, npm test, npm run build, node scripts/check-docs.mjs, npm run invariants, браузерные смоки и golden:verify — на этапе ревью ТЗ они неприменимы: продуктовый код не менялся (см. git diff --stat в шапке документа). Они станут обязательны на этапе код-ревью (§2.7).
  • Не проверялась реализация (её нет) — весь разбор выше про код относится к текущему, немодифицированному dev и служит только проверке того, что фактические утверждения ТЗ о текущем поведении верны.
  • Не проверялась орфография/стиль переводов DE/FR по существу (нет статуса носителя языка) — только структурное наличие всех четырёх ключей и соответствие паттерну именования.

Вердикт

Один Medium в скоупе, High нет → по §2.4 жёлтый вердикт, возврат автору на доработку двух обязательных разделов; остальной контракт (AC, модель данных, i18n, touch, defaults, release-артефакты) готов и не требует повторного разбора, если правка ограничится добавлением «Риски»/«Откат» (это будет проверено по дельте в r2, PROCESS.md §2.9/§2.10).

Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 → в задаче · Документ: docs/reviews/SPEC-REVIEW-381-r1.md