Files
houseplan-card/docs/reviews/CODE-REVIEW-578-r1.md
T
claude[bot] 856a2c6e12
Проверка (CI) / Классификация изменённых файлов (push) Successful in 21s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / HACS: валидация репозитория (push) Failing after 20s
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 1m4s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 1m32s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 4m1s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Successful in 5m12s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 13m42s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 10m18s
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 17s
docs: review document for #578
Issue: #578
User-Visible: no
2026-09-14 18:47:36 +00:00

16 KiB
Raw Blame History

CODE-REVIEW-578-r1

Issue: #578 — «Touch: второй палец pinch на устройстве запускает long-press и HA more-info» Материал ревью: SHA 5790abaa1f3e8701e3de190968178e63156e3fe5 (рабочая копия зафиксирована на нём, git fetch/checkout не выполнялись) Заход: r1 · блокирующих циклов израсходовано 0/4 Трек: полный (обоснован в ТЗ, спор-ревью ТЗ зелёное — комментарий пайплайна в issue)

Скоуп

Диапазон git diff origin/dev...HEAD — один продуктовый коммит:

  • src/houseplan-card.ts — на bubble-обработчике маркера _pointerDown добавлена проверка _touchSequenceMultitouch перед вооружением 600-мс _holdTimer; capture-guard _guardTouchGesture расширен на contextmenu и keydown и делегирует активационные события в новый метод TouchGestureClickGuard.handleActivation.
  • src/touch-gesture-click-guard.ts — добавлен handleActivation(event, suppressClick): единая точка подавления click/contextmenu, пока clickBlocked; keydown с ContextMenu/Shift+F10 при отсутствии активных touch-контактов снимает _postGestureClickBlocked.
  • test/touch-gesture-click-guard.test.mjs — два новых unit-теста (#578) на активную/завершённую multi-touch-блокировку и на keyboard re-arm.
  • demo/smoke_editor_gestures.mjs — существенно расширенный browser-smoke: оба порядка начала pinch (stage→marker, marker→stage), churn с третьим контактом, все терминальные варианты, touch- и mouse-contextmenu, keyboard Shift+F10, следующий одиночный tap и long-press.
  • scripts/mutation-registry.mjs — два именованных мутанта: touch-pinch-marker-hold-rearmed, touch-pinch-contextmenu-guard-removed, оба со свидетелем smoke_editor_gestures.mjs.
  • docs/TOUCH-SUPPORT.md, docs/TESTING.md — контракт и таблица покрытия обновлены под новую формулировку.
  • docs/CHANGELOG.md + docs/CHANGELOG.ru.md — правка в том же коммите, User-Visible: yes.
  • dist/**, custom_components/houseplan/frontend/**, demo/srv/assets/** (класс D) — пересобранный бандл, синхронность подтверждена локально (см. ниже).

Не в диффе: конфигурация, backend/Python, i18n-строки, geometry/layout/marker.space/open_spans — инварианты модели не применимы к этой задаче.

Как проверялось (таблица гейтов)

Гейт Команда Результат
typecheck npx tsc --noEmit green
unit npm test 2706 pass, 0 fail, 1 skipped (совпадает с хендоффом)
build + bundle sync npm run bundle:sync (build + bundle-sync.mjs --check) green, дерево custom_components/houseplan/frontend и demo/srv/assets byte-stable
no-new-any node scripts/no-new-any.mjs --base origin/dev --head HEAD 33 добавленные строки в 2 файлах, новых any нет
check-docs node scripts/check-docs.mjs green (7 файлов, 12 внешних ссылок) — обязателен, диф трогает src/**
smoke-select node scripts/smoke-select.mjs --base origin/dev --head HEAD 4 прямых совпадения по _suppressClick: smoke_editor_gestures, smoke_furniture, smoke_linked_virtual_light, smoke_plan_snap_overlay; 32 слабые связи по _mode (одно распространённое имя — не прогонялись, см. ниже)
browser smoke (прямые) node demo/smoke_editor_gestures.mjs green, все 15 полей true, включая новые stageThenDeviceHoldBlocked, stageThenDeviceContextMenuBlocked, nextDeliberateDeviceLongPressWorks, mouseContextMenuStillWorks, keyboardContextMenuStillWorks
browser smoke (прямые) node demo/smoke_furniture.mjs green
browser smoke (прямые) node demo/smoke_linked_virtual_light.mjs green
browser smoke (прямые) node demo/smoke_plan_snap_overlay.mjs green
browser smoke (по теме бага, не в выборке) node demo/smoke_tap_ctx.mjs green
browser smoke (по теме бага, не в выборке) node demo/smoke_long_press_gesture.mjs green
mutation witness node scripts/mutation-gate.mjs --id=touch-pinch-marker-hold-rearmed green: чистый прогон зелёный, мутант красный (поймано 1/1)
mutation witness node scripts/mutation-gate.mjs --id=touch-pinch-contextmenu-guard-removed green: чистый прогон зелёный, мутант красный (поймано 1/1)

Валидация в CI на этом SHA не запрашивалась: гейты дешёвые, прогнаны локально самим ревьюером, потому что готового зелёного Validate на 5790abaa в контексте задачи не подтверждён отдельной ссылкой для этого раунда — прогон занял единицы минут.

Чего не проверял и почему

  • golden:verify — не прогонялся. Диф не меняет геометрию, стили, слои и видимую разметку; правка — событийная (какие обработчики активации подавляются), рендер маркера и стадии не тронут. Golden — предрелизный гейт (§8), не гейт ревью для этой задачи.
  • python -m pytest tests_backend — не прогонялся, диф не касается custom_components/**/*.py.
  • Модельные инварианты (npm run model-invariants) — не прогонялись, диф не трогает geometry/layout/marker.space/open_spans.
  • Perf-профили — не прогонялись; AC9 не называет perf-влияние, влияние на touch-путь ограничено O(1)-проверками поверх уже существующей Map/Set, что подтверждено чтением (pointerDown/handleActivation не создают новых структур, не сканируют DOM).
  • 32 «слабые связи» по _mode (smoke_decor*, smoke_modes, smoke_kiosk и т.д.) — не прогонялись. _mode упоминается в диффе только как условие входа (this._mode !== 'view' && this._mode !== 'devices') return, не изменено по существу; логика самих режимов не тронута. Просмотр списка не выявил смока, специфичного именно для touch-активации маркера вне уже прогнанных четырёх прямых совпадений и двух тематических (smoke_tap_ctx, smoke_long_press_gesture).
  • Полный набор demo/smoke_*.mjs — не прогонялся целиком; задача не задевает поверхность плана/редакторов вне touch-guard контура.
  • Full pre-release набор (golden/smoke/performance/E2E на реальном HA) — предрелизный гейт, не гейт код-ревью; хендофф-комментарий сам это отметил в «НЕ сделано», это корректно.

AC → доказательство

AC Доказано Как Чем краснеет
AC1 stage→marker, удержание >600мс да smoke_editor_gestures: stageThenDeviceHoldBlocked=true, stageThenDeviceZooms=true (внутри pinchZoomsFromDevice) мутант touch-pinch-marker-hold-rearmed красит именно этот путь
AC2 marker→stage (регресс #563) да тот же smoke: pinchIntermediateClickBlocked, pinchDelayedClickBlocked, pinchCancelsDeviceLongPress = true покрыт существующим мутантом #563 (не тронут)
AC3 churn, терминальные варианты, третий контакт да smoke: terminalGesture ×2 + отдельный блок с 3 контактами → pinchTerminalVariantsBlocked=true косвенно через noActivation(); отдельного мутанта на «третий контакт» нет, но код пути общий с AC1/AC2
AC4 contextmenu (touch блокируется, mouse проходит) да smoke: stageThenDeviceContextMenuBlocked, mouseContextMenuStillWorks=true (moreInfoCalls строго считается) мутант touch-pinch-contextmenu-guard-removed красит touch-путь
AC5 новая одиночная последовательность (tap + long-press) да smoke: nextDeliberateDeviceTapWorks, nextDeliberateDeviceLongPressWorks=true, без искусственной задержки (сразу после terminal) заявленный тест умеет падать — проверено чтением: без early-return на 7131 _holdTimer не отличит новую последовательность (не мутировалось отдельно, покрыто тем же мутантом AC1)
AC6 mouse/keyboard совместимость да smoke: mouseContextMenuStillWorks, keyboardContextMenuStillWorks=true; unit #563 a new mouse sequence... чтением: pointerDown() в guard сбрасывает _postGestureClickBlocked для любого pointerType, включая mouse, при отсутствии активных touch-контактов
AC7 чистая машина состояний unit да test/touch-gesture-click-guard.test.mjs, 6 тестов (4 старых #563 + 2 новых #578) покрывают single→multi→post-blocked→fresh, оба порядка release, cancel/lost-capture, keyboard re-arm unit явно ассертит false/true на каждом переходе
AC8 отрицательные mutation witnesses (раздельно) да mutation-gate.mjs --id=touch-pinch-marker-hold-rearmed и --id=touch-pinch-contextmenu-guard-removed, оба «поймано 1 из 1» сам гейт и есть доказательство: чистый прогон зелёный → мутант красный
AC9 стандартные гейты на точном SHA да таблица выше: typecheck/test/build/check-docs/подобранные smoke/оба mutation witness — все зелёные на 5790abaa —

Находки

Нет High. Нет Medium. Одна Low, снятая ревьюером без правки:

  • Low. AC3 («churn и terminal events») не получил отдельного именованного mutation witness — оба существующих мутанта (#563-й на терминалы и новые #578-е) покрывают смежные, но не тождественные пути; регресс именно в порядке «третий контакт поверх уже блокированной последовательности» ловится теми же двумя мутантами лишь косвенно (через общий early-return). Не блокирует: browser smoke явно воспроизводит сценарий с третьим контактом и проверяет noActivation(), а требование ТЗ (АC8) называло ровно два witness — «marker hold guard» и «contextmenu capture guard», оба присутствуют. Дополнительный мутант не заявлен ни ТЗ, ни AC — снимаю без правки.

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

  • Порядок исполнения pointerdown подтверждён структурно и эмпирически: guard навешан с capture: true на корневой ha-card, маркер — потомок без капчура, поэтому TouchGestureClickGuard.pointerDown() гарантированно успевает выставить _sequenceMultitouch=true до того, как _pointerDown() маркера проверит _touchSequenceMultitouch на строке src/houseplan-card.ts:7131.
  • contextmenu от touch внутри/сразу после pinch гасится handleActivation (preventDefault + stopImmediatePropagation) до того, как событие дойдёт до @contextmenu маркера (_ctxDevice, src/houseplan-card.ts:12553), а настоящий mouse-контекстное меню не задето, потому что clickBlocked ложно вне активного/только что завершённого multi-touch.
  • Keyboard Shift+F10/ContextMenu восстанавливает контекстное меню сразу после завершения всех старых контактов, но не раньше — юнит и smoke по отдельности проверяют оба состояния.
  • «Одно число — один источник» неприменимо: диф не добавляет и не меняет видимую пользователю величину.
  • Оба changelog правлены тем же коммитом, что и поведение; терминология («долгое нажатие», «внутренняя карточка House Plan», «HA more-info», «правый клик») совпадает с docs/USER-GUIDE.ru.md, а не изобретена.
  • Изменение не расширяет скоуп: правка ограничена activation-контуром pointerdown/contextmenu/keydown, не трогает pan/pinch-скорость, конфиг, i18n, назначаемые действия — согласно «Вне scope» ТЗ.
  • Продуктовая рамка (docs/SCOPE.md): закрывает J3 («tap-to-act безопасно») для touch-персон View/kiosk — ложные открытия карточек/more-info во время pinch нарушали именно этот контракт; правка не добавляет нового поведения, только убирает ложные срабатывания — риска выхода за J1–J7 нет.

Материал раунда

  • SHA: 5790abaa1f3e8701e3de190968178e63156e3fe5
  • Дерево: см. git diff origin/dev...HEAD --stat выше
  • ТЗ: тело issue #578, раздел ## ТЗ; ревью ТЗ зелёное, комментарий пайплайна (SPEC-REVIEW-578-r1)

Вердикт

Зелёный. AC1–AC9 доказаны исполняемыми свидетелями (unit + browser smoke + два отдельных mutation witness), гейты соразмерны диффу и все зелёные, документация и changelog в том же коммите, терминология из docs/USER-GUIDE.ru.md. Единственная Low-находка снята без правки.


Материал раунда

  • Ветка: issue/578-touch-pinch-longpress, коммит 5790abaa1f3e — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
  • Дерево материала: ad6f177adb21efe046fe4e88a6ef7dfede174f32
    git log --all --format='%H %T' | grep ad6f177adb21
    
  • Тело issue: ec0f61cba0a9ebb0afa611f400145755e567714b91b307e18928aed57bbb61be
  • Вердикт конвейера: green · High 0