18 KiB
Issue #381 — действие по нажатию «Ничего не делать»
- Issue: https://github.com/Matysh/houseplan-card/issues/381
- Статус документа: первая редакция, готова к ревью
- Приоритет / тип: P2 · feature / polish
- Область: marker config, Редактор устройств, View/киоск, frontend action projection, backend validation, i18n, документация и QA
- Связи: #94 (универсальный Toggle), #178 (точная Toggle-сущность)
- Ревизия: 1 (2026-08-30)
Сценарий
Администратор дома размещает на плане датчик или виртуальный маркер, который должен только показывать место и состояние. В Редакторе устройств он выбирает для маркера действие «Ничего не делать» и сохраняет настройки. После этого член семьи, гость или киоск может коротко нажать маркер, не открывая карточку и не отправляя команду устройству. Долгое нажатие и правый клик сохраняют свои отдельные информационные действия.
Что человек увидит до и после
До: короткое нажатие всегда открывает информационную поверхность либо пытается выполнить команду. После: в селекторе появляется явный вариант «Ничего не делать», и короткое нажатие выбранного маркера не даёт никакого результата, пока сам маркер продолжает показывать состояние на плане.
Проблема
TAP_ACTIONS предлагает только info, more-info, toggle и run.
projectedTapAction() проецирует неизвестный сохранённый токен в info, а
_clickDevice() после известных команд открывает локальную карточку. Backend
MARKER_SCHEMA также принимает только эти четыре значения, legacy cover и
null. Поэтому строка в селекторе без сквозного контракта либо будет отклонена
сервером, либо после reload превратится в открытие карточки.
Использовать неработоспособный toggle как замену нельзя: это зависимый от
текущей привязки safe no-op с диагностикой, который может начать отправлять
команду после восстановления цели. Здесь требуется явное и стабильное намерение
пользователя.
Скоуп
- Новая строка «Ничего не делать» в селекторе «Действие по нажатию» для существующих и новых реальных/виртуальных маркеров.
- Канонический persisted literal
marker.tap_action: "none". - Одинаковый no-op для короткого mouse click, touch tap и keyboard activation
Enter/Spaceв View и киоске. - Сквозное чтение, preview, запись, backend validation, full/space export/import и downgrade-описание нового literal.
- Паритет переводов EN/RU/DE/FR и обновление пользовательской документации.
- Регресс-проверки остальных действий и default-проекции.
Не-скоуп
- Изменение долгого нажатия, правого клика или жестов редактора.
- Отдельная настройка hold/right-click action.
- Изменение hit area, hover/focus, внешнего вида, состояния, pulse, Glow, LQI, value badge, room aggregates или доступности маркера.
- Изменение defaults: свет без явного значения по-прежнему получает Toggle, остальные устройства — карточку House Plan.
- Изменение семантики
info,more-info,toggle,runи legacycover. - Новая HA service/WebSocket-команда, подтверждение или toast для
none. - Интерактивность
houseplan-space-card, которая остаётся отдельной статической поверхностью по своему существующему контракту.
Контракт поведения
1. Выбор и сохранение
TAP_ACTIONSсодержит каноническийnone; селектор показывает его строкой «Ничего не делать» после существующих четырёх действий.- Выбор
noneнемедленно меняет draft/effective action диалога. Toggle- и Run-зависимые поля скрываются по тем же правилам, что дляinfoиmore-info; блокcontrolsостаётся доступен, поскольку он участвует также в light/presentation graph. - Save записывает точный literal
tap_action: "none". Повторное открытие диалога и reload сохраняют выбранную строку. - Cancel не меняет конфиг. Простое Open → Save маркера с отсутствующим,
legacy либо неизвестным
tap_actionсохраняет прежний lossless-контракт и не материализуетnone. noneне равенnull, отсутствию или пустой строке. Только явный literal отключает короткое действие; прежняя light/non-light default-проекция не меняется.
2. Короткое нажатие
projectedTapAction("none", domain)возвращаетnoneдля любого domain и binding kind. Неизвестные токены по-прежнему fail closed вinfo.- В View/киоске
_clickDevice()после разрешения актуального marker по id и до binding/service веток завершаетnoneкак quiet no-op. - No-op означает одновременно: не открывать карточку House Plan или HA more-info, не создавать confirmation, не показывать toast, не вызывать HA service/WebSocket, не запускать press feedback/activity и не менять локальное/серверное состояние.
- Click/tap по маркеру остаётся поглощённым маркером и не превращается в клик по комнате, pan либо другому объекту под ним.
EnterиSpaceидут тем же_clickDevice()-путём, предотвращают браузерный default как сейчас и также завершаются без побочного действия.
3. Независимые жесты и presentation
- Long press 600 ms по-прежнему открывает внутреннюю карточку House Plan.
- Правый клик по-прежнему открывает HA more-info основной сущности либо существующий fallback для marker без primary.
- В Редакторе устройств короткий клик по маркеру по-прежнему открывает его
настройки;
tap_actionотносится только к View/киоску. - Marker остаётся видимым, focusable и получает прежние hover/focus стили, tooltip, state, pulse, badge, Glow и LQI. Отдельного disabled-вида нет.
- Preview диалога меняет только effective action; рисунок маркера не меняется.
UX и i18n
Добавить одинаковый ключ tap.none в четыре словаря:
- RU:
Ничего не делать; - EN:
Do nothing; - DE:
Nichts tun; - FR:
Ne rien faire.
Новая строка находится в существующем native select после Run; новая
подсказка, иконка либо секция диалога не нужны. Toggle-hint, target chooser и
confirmation checkbox при none не показываются. Live region Toggle не
анонсирует цель.
Модель данных, миграция и совместимость
Frontend
- Возвращаемый тип
projectedTapAction()и все effective-action consumers расширяются literalnone. Marker.tap_actionостаётся forward-compatiblestring | null; отдельная миграция типа или model/store version не требуется.devicesfingerprint уже включает исходныйtap_action, поэтому смена наnoneинвалидирует presentation/action snapshot без нового cache key.
Backend
MARKER_SCHEMA принимает none рядом с текущими canonical literals. Значение
не несёт target/ref и не требует semantic validator. Cross-language parity
test обязан доказать, что backend принимает каждый literal из frontend
TAP_ACTIONS, включая none, и продолжает отдельно принимать legacy cover.
Import/export и downgrade
- Full и space export/import сохраняют
noneштатным копированием marker; remap ссылок не требуется. - Виртуализация duplicate marker при space import продолжает удалять
tap_actionвместе с HA-dependent marker fields по существующему правилу. - Старый frontend прочитает неизвестный ему
noneкакinfo; старый backend отклонит попытку записать изменённый marker с новым literal. Это известная граница downgrade для нового enum value, а не основание маскироватьnoneпод отсутствующее значение. - Новый frontend/backend не переписывают старые
cover, absent,nullили неизвестные untouched значения при редактировании другого поля.
Затронутые файлы и модули
src/logic.ts,src/device-toggle.ts,src/houseplan-card.ts.src/houseplan-editor-runtime.ts;src/types.tsтолько если потребуется уточнить локальный effective-action type без сужения forward compatibility.src/i18n/{en,ru,de,fr}.json.custom_components/houseplan/validation.py.test/device-toggle.test.mjs, action/click contract tests,tests_backend/test_validation.pyи целевой browser smoke диалога/маркера.docs/USER-GUIDE.md,docs/USER-GUIDE.ru.md,docs/CONFIG-COMPATIBILITY.md,docs/ARCHITECTURE.md, при необходимостиdocs/TESTING.md, оба changelog.
Критерии приёмки
- AC1 — selector и persisted value (unit + smoke). Селектор содержит пять
canonical options с
noneпоследним; выбор сохраняетtap_action: "none", reopen/reload восстанавливает его, Cancel не пишет. - AC2 — quiet no-op (unit + smoke). Mouse click, touch tap,
EnterиSpaceпо marker сnoneне открывают поверхности, не вызывают service/WS, toast, confirmation, press animation или activity stamp. - AC3 — независимые жесты (smoke). Long press всё ещё открывает карточку House Plan, right click — HA more-info, а клик по тому же marker в Редакторе устройств — настройки marker.
- AC4 — presentation parity (unit + visual assertion). До и после выбора совпадают DOM/classes/face/state/pulse/badge/LQI/Glow/tooltip/hit area и hover/focus; новый disabled-стиль не появляется.
- AC5 — defaults и legacy (unit). Absent/
null/""оставляют Toggle для primary light и Info для остальных;coverпроецируется в Toggle; неизвестный token проецируется в Info; untouched Open → Save сохраняет исходный literal. - AC6 — соседние действия (unit + smoke).
info,more-info,toggleиrunвыполняют прежние ветки, а Toggle/Run fields и confirmation видны только при соответствующих effective actions. - AC7 — backend и transfer (backend). Backend принимает
noneи всеTAP_ACTIONS, отклоняет произвольный новый изменённый token; full/space transfer сохраняетnone, duplicate virtualization удаляет его как раньше. - AC8 — i18n и docs (unit + docs gate). Четыре словаря имеют паритет,
обе версии руководства описывают
noneи неизменные long/right gestures, compatibility/architecture фиксируют literal и downgrade. - AC9 — гейты и бюджет (commands). В цикле реализации зелёные
npx tsc --noEmit,npm test,npm run buildи целевой backend pytest;no-new-any, i18n/docs checks и browser smoke проходят на обязательном предрелизном прогоне. Существенного роста initial/editor bundle нет.
План автотестов
- Расширить таблицу
projectedTapAction: explicitnoneдля light/switch и неизменные absent/legacy/unknown варианты. - Добавить целевой тест click dispatcher с шпионами на
_infoCard,_openMoreInfo,callService,callWS,_tapConfirm, toast,_startDevicePressFeedbackи_stampActivity. - Проверить keyboard reuse того же dispatcher и сохранение
stopPropagation/preventDefaultна соответствующих путях. - Проверить editor draft/save/reopen/Cancel и отсутствие Toggle/Run dependent
controls для
none; отдельно — lossless untouched legacy/unknown action. - Расширить cross-language backend test
TAP_ACTIONSи marker validation. - Добавить import/export fixtures для full, space и duplicate virtualization.
- Целевой Playwright smoke: выбрать
none, Save, reopen, reload, выполнить click/tap/keyboard no-op, long press и contextmenu; убедиться, что marker presentation не изменился.
Мутанты: не добавить none в backend → AC7; спроецировать none в info →
AC1/AC2; поставить no-op после fallback-карточки → AC2; сделать ранний return до
stopPropagation → AC2; связать none с unavailable Toggle → AC2/AC5; скрыть
marker или изменить face → AC4.
Производительность и безопасность
No-op — одна константная ветка до capability resolution и сетевых операций.
Новых подписок, таймеров, cache entries, DOM-узлов и данных HA нет. Вызовов
service/WS для none быть не может; backend принимает только точный allowlisted
literal. Full performance/golden harness не требуется из-за отсутствия
геометрической/визуальной дельты, но штатные предрелизные проверки проекта не
отменяются.
Release-артефакты
- Пользовательская запись в
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.mdв том жеUser-Visible: yesкоммите, что реализация. - Обновлённые EN/RU user guides, compatibility и architecture.
- Новый plan golden не требуется: marker pixels и геометрия не меняются. Открытое native select не является стабильной кроссплатформенной golden- поверхностью; наличие/текст option доказывают DOM unit и Playwright smoke.
- Docs screenshot не требуется: существующие изображения не утверждают полный список tap actions; текстовые разделы являются каноническими.
- Release/performance/security waiver не требуется; перед beta выполняется общий golden/smoke/performance набор по runbook.
Принятые предположения
noneрасположен последним в текущем порядке selector; порядок можно свободно поменять до реализации без изменения контракта.- Controls остаются редактируемыми, потому что используются presentation/light graph независимо от короткого действия.
- Существующая политика соседних полей сохраняется: смена с
runна любое другое действие очищаетtap_targetпри записи, а общие настройки marker не сбрасываются только из-за выбораnone. - Marker остаётся focusable и визуально интерактивным ради доступных hover, tooltip, long-press и context-menu путей; отдельный disabled appearance не вводится.