Files
houseplan-card/docs/specs/378-value-face-source.md
T
2026-08-29 22:17:11 +03:00

23 KiB
Raw Blame History

Issue #378 — выбираемый источник для режима «Значение + состояние»

  • Issue: https://github.com/Matysh/houseplan-card/issues/378
  • Статус документа: первая редакция, готова к ревью
  • Приоритет / тип: P2 · feature / polish
  • Область: frontend presentation + editor, marker config, backend validation, import/export, документация и QA
  • Связи: #90 (канонические источники value badge), #321 (пользовательский сценарий position only)
  • Ревизия: 1 (2026-08-29)

Сценарий

Администратор дома на desktop открывает диалог уже размещённой маркизы, выбирает режим «Значение + состояние», а затем источник «Положение · Маркиза». Preview сразу показывает 42 %. После сохранения то же значение видят на полном плане и в static space card владелец, член семьи и гость; нажатие по маркеру по-прежнему управляет маркизой по существующей настройке действия.

Что человек увидит до и после

До: режим показывает слово Open, потому что умеет брать только state автоматически выбранной сущности. После: пользователь одним селектором выбирает полезный показатель и видит 42 % вместо иконки, без дополнительного слова и без template sensor.

Проблема

resolveValue() в src/device-presentation.ts выбирает одну visual entity и передаёт её в validStateValue(). Атрибут выбрать нельзя. В то же время src/device-value-badge.ts уже содержит:

  • канонический набор ValueBadgeSource;
  • построение кандидатов текущего устройства;
  • allowlist поддерживаемых атрибутов;
  • преобразования brightness/volume/percentage/temperature;
  • unavailable-контракт и стабильные ключи источников.

Создание второй таблицы или второго formatter приведёт к расхождению двух селекторов. Простого добавления поля в диалог тоже недостаточно: конфиг проходит backend delta-validation, ссылки marker:* переписываются при rebind и переносятся между пространствами через import/export.

Скоуп

  • Отдельный селектор источника лица значения в диалоге устройства при display: value.
  • Явный источник state, поддерживаемого attribute либо derived value из ровно того же набора и в том же порядке, что value badge.
  • Общий resolver и formatter для value badge и внутреннего лица значения.
  • Live preview и одинаковый результат в основном плане и static space card.
  • Опциональное поле marker config, lossless delta-validation, rebind и полный import/export reference seam.
  • Обновление документации режима и decision table.

Не-скоуп

  • Новые атрибуты, единицы, формулы, шаблоны либо произвольный entity picker.
  • Изменение внешнего value badge или положения его капсулы.
  • Новый display mode.
  • Изменение визуального состояния подложки, activity, pulse, alarm, Glow, room metrics или LQI palette.
  • Изменение tap/click target и действий info, toggle, run, cover.
  • Выбор нескольких значений либо prefix/suffix пользователя.

Контракт поведения

1. Выбор источника

  1. При display: value сразу под подсказкой режима отображается поле «Источник значения».
  2. Первая строка — «Автоматически (как раньше)». Она соответствует отсутствию явного источника. Далее идут кандидаты valueBadgeCandidates() в том же порядке, с теми же названиями, техническими подписями и текущими значениями, что в selector value badge.
  3. Оба потребителя используют общий ValueSource/ValueSourceCandidate, общий source-key codec и общий formatter/resolver. Совпадение списков доказывается тестом на одну и ту же ссылку/проекцию данных, а не сравнением двух копий allowlist.
  4. Отсутствие кандидатов не запрещает legacy auto: строка «Автоматически» остаётся доступной. Для virtual marker selector disabled с существующим объяснением value_virtual; сохранённый явный источник не удаляется.
  5. Переключение на другой display mode скрывает поле, но сохраняет выбор. Возврат в value восстанавливает его.
  6. При явной смене HA binding в диалоге старый value_source сбрасывается в auto: перенос источника прежнего устройства был бы ложной настройкой.

2. Разрешение и форматирование

  1. Если marker.value_source отсутствует или null, вызывается существующий auto-path resolveValue() без изменения порядка климатической legacy- эвристики, power-gate и проверки неоднозначности.
  2. Если источник задан явно, auto-path не выбирает и не подставляет другую сущность. State, attribute, derived_lqi и derived_marker_state разрешаются тем же кодом, что value badge.
  3. cover.current_position = 42 отображается 42 %; brightness, volume, humidity, battery и temperature получают ровно те же преобразования и единицы, что внешний badge. valueText — компактный текст, valueFullText — доступное полное значение/tooltip.
  4. Временно отсутствующий, unknown/unavailable или переставший быть скалярным явный источник сохраняется и отображает — внутри лица значения. Он не заменяется иконкой и не переключается на auto. Presentation сохраняет диагностический код value_no_state или value_non_scalar, но explicit- ветка не трактует этот код как разрешение сменить лицо.
  5. Для auto-path прежняя семантика остаётся: value_no_state, value_ambiguous_sources и value_non_scalar откатывают лицо к иконке. Явный источник по определению снимает value_ambiguous_sources.
  6. value_virtual имеет приоритет над сохранённым источником: virtual marker остаётся с иконкой, как сейчас.
  7. Ключ выбранного источника входит в sourceSignature даже в unavailable- состоянии, чтобы смена выбора/восстановление данных инвалидировала snapshot.
  8. Если внешний badge показывает тот же source key, существующая подсказка о дублировании остаётся и сравнивает уже явный источник лица.

3. Preview и действия

  • Preview строит draft marker с текущим display и value_source и вызывает тот же resolveDevicePresentation(), что оба plan renderer; отдельной mock- строки 42 % нет.
  • Смена selector обновляет preview без Save.
  • Сохранение не требуется для preview, но Cancel не пишет draft в config.
  • Выбор источника не меняет primary, visual sources, tap_action, tap_target, toggle_entity, controls или more-info entity.
  • Pointer и touch hit area не меняются; на touch результат и действие идентичны desktop. View/киоск считаются release-blocking поверхностями.

UX

Новый selector расположен рядом с причиной выбора режима, до блока внешнего value badge. В нём переиспользуются строки кандидатов #90. Новые строки нужны только для собственной подписи/help и auto-опции:

  • «Источник значения»;
  • «Выберите, что заменит иконку. Действие по нажатию не изменится»;
  • «Автоматически (как раньше)»;
  • missing-hint, который говорит, что лицо покажет —, пока источник не восстановится или пользователь не выберет другой.

Сохранённый источник, которого нет среди текущих кандидатов, добавляется одной выбранной disabled-looking, но сохраняемой строкой «Источник недоступен» по паттерну value badge. Пользователь может оставить её, выбрать другой источник или auto. Само открытие и сохранение диалога без изменения selector не материализует рекомендацию и не удаляет неизвестное значение.

Модель данных и миграция

Frontend

В Marker добавляется:

value_source?: ValueBadgeSource | null;

Имя техническое и не обещает связь с tap target. Тип источника извлекается из badge-модуля в нейтральный общий контракт либо реэкспортируется без дублирования.

  • absence/null = legacy auto;
  • object = explicit source;
  • выбор auto удаляет поле при записи нового frontend-конфига;
  • неизвестный untouched literal losslessly round-trips согласно docs/CONFIG-COMPATIBILITY.md; после явной правки поля записывается только канонический source либо отсутствие.

Миграции и массовой материализации нет: существующие marker остаются без поля и выглядят как до #378.

Backend validation

MARKER_SCHEMA принимает value_source как lossless object/null, а семантическая delta-validation применяет к изменённому значению тот же набор kind/полей/entity-id/attribute, что value_badge.source. Общая функция валидации источника должна исключить второй backend allowlist.

Для derived_marker_state ref обязан вести на существующий не-removed marker с is_light: true, как у badge. Полный импорт валидирует все новые данные; обычная запись сохраняет untouched future literal.

Reference seam и import/export

  • rewriteMarkerControlReferences() переписывает value_source.ref вместе с controls и value_badge.source.ref при смене marker id.
  • Full export/import сохраняет поле штатно.
  • Space export оставляет derived ref только если target входит в переносимый набор; иначе удаляет value_source (возврат в auto) и увеличивает счётчик dropped marker links.
  • Space import remap-ит живой target на новый marker id. При skip/virtualize или отсутствующем target поле удаляется, сырой marker:old-id не протекает.
  • Виртуализация дубликата удаляет HA-dependent value_source, как value_badge, controls и tap fields.

i18n

Добавить с паритетом EN/RU/DE/FR:

  • marker.value_source
  • marker.value_source.help и .aria
  • marker.value_source_auto
  • marker.value_source_missing_hint

Имена state/attribute/derived-кандидатов и marker.value_badge_missing переиспользуются. Тексты marker.display_hint_value во всех четырёх языках обновляются: значение может быть выбранным, а не только «однозначным» auto.

Затронутые файлы и модули

  • src/types.ts, src/device-value-badge.ts, src/device-presentation.ts, src/device-presentation-policy.ts (только explicit-dash gate), src/devices.ts.
  • src/houseplan-card.ts, src/houseplan-editor-runtime.ts.
  • src/i18n/{en,ru,de,fr}.json.
  • custom_components/houseplan/validation.py, custom_components/houseplan/import_export.py, websocket validation call sites при переименовании validator.
  • Frontend/backend unit tests и целевой browser smoke.
  • docs/DEVICE-PRESENTATION.md, docs/ARCHITECTURE.md, docs/CONFIG-COMPATIBILITY.md, docs/USER-GUIDE.md, docs/USER-GUIDE.ru.md, оба changelog.

Критерии приёмки

  • AC1 — список и сохранение (unit + smoke). В display: value selector содержит auto и ровно те же кандидаты/порядок, что value badge; выбор cover.current_position сохраняет value_source, повторное открытие восстанавливает выбор, выбор auto удаляет поле.
  • AC2 — результат на всех renderer (unit + smoke + golden). При position 42 preview, полный план и static space card показывают 42 % вместо иконки; desktop/touch tap вызывает тот же action/target, что до выбора.
  • AC3 — паритет formatter (unit). Все поддерживаемые attribute kinds, entity state, LQI и marker state дают одинаковые text/fullText/availability для лица и value badge.
  • AC4 — legacy compatibility (unit + backend). Marker без поля и с null проходит прежние F09–F12 сценарии: auto state, ambiguity/no-state/non-scalar fallback и virtual fallback не меняются; простое Save не материализует поле.
  • AC5 — fail explicit (unit + smoke). Сохранённый явный источник, временно исчезнувший либо unavailable, остаётся выбранным, на лице показывает —, даёт корректный диагностический reason и не подменяется иконкой/другим state; возврат source восстанавливает значение без повторного выбора.
  • AC6 — независимость (unit). Выбор не меняет внешний badge, visual/activity state, room aggregates, Glow, LQI palette, primary и tap resolver; duplicate- hint срабатывает при совпадении source keys.
  • AC7 — конфиг и ссылки (backend + unit). Delta-validation режет каждый некорректный новый kind/attribute/entity/ref, сохраняет untouched future literal, rebind переписывает derived ref, full transfer сохраняет, space transfer remap-ит либо явно удаляет dangling ref с отчётом.
  • AC8 — preview/Cancel/binding (smoke). Selector меняет draft preview сразу; Cancel ничего не пишет; смена binding сбрасывает старый source в auto.
  • AC9 — локализация и документация (unit + docs gate). Четыре словаря имеют паритет, обновлены guide/presentation/architecture/compatibility, docs screenshots соответствуют точному SHA.
  • AC10 — гейты и бюджет (commands). npx tsc --noEmit, npm test, npm run build, backend pytest, no-new-any, целевой smoke, golden:verify и check-docs зелёные; bundle budget без существенной дельты.

План автотестов

  • Расширить unit device-value-badge общим resolver contract и таблицей всех source kinds/formatter branches.
  • Расширить device-presentation/decision fixtures: explicit position, unavailable explicit dash, restored source, legacy F09–F12 без изменений, source signature и duplicate badge.
  • Unit editor save/draft: untouched, explicit, auto, missing, binding change, Cancel.
  • Unit devices: rebind derived_marker_state для обоих потребителей.
  • Backend pytest: schema/delta-validation, future literal, renamed marker, full/space import-export keep/remap/drop/virtualize.
  • Новый либо расширенный целевой Playwright smoke диалога: preview + save + reopen + unavailable/recovery + main/static render + desktop/touch action.
  • Golden-сценарий с cover 42 % и выбранным source принимается только через штатный reviewed Linux artifact; docs screenshots — только workflow Docs screenshots на точном SHA.

Мутанты: удалить explicit branch → AC2 красный; заменить source formatter на raw attribute → AC3; fallback explicit в auto/icon → AC5; не переписать ref → AC7; материализовать auto при открытии → AC4/AC8.

Производительность и безопасность

  • Сеть, сервисы HA и частота обновлений не меняются.
  • Candidate discovery уже кэшируется; один выбранный source разрешается O(1), кроме существующего derived marker-state graph. Новый обход всех HA entities запрещён.
  • В snapshot/signature добавляется один короткий source key; заметной дельты bundle/render budget не ожидается и она проверяется штатным budget gate.
  • Backend принимает только те же ограниченные source shapes и attributes, что badge; произвольный template/code/URL не исполняется. Security artifact не требуется, кроме unit отрицательных входов.

Touch

Сам selector — desktop-поверхность редактора. Результат живёт в View и киоске: размер/hit target маркера и tap semantics не меняются. AC2 и AC6 обязаны проверить touch pointer path; расхождение desktop/touch блокирует выпуск.

Риски

  • Две настройки с похожим названием: смягчается размещением source сразу под display и сохранением отдельного заголовка «Бейдж со значением» ниже.
  • Расхождение formatter/candidates: запрещено контрактом общей функции и AC3.
  • Dangling marker:* после rebind/import: закрывается полным reference seam AC7.
  • Изменение legacy auto при рефакторинге: закрывается неизменными decision fixtures и AC4.
  • — может быть принят за значение: missing warning и preview reason объясняют восстановление; молчаливый fallback опаснее.

Откат

git revert реализационного коммита. Старый frontend/backend losslessly пропустит дополнительный marker key благодаря ALLOW_EXTRA, но проигнорирует его и покажет legacy auto; данные не теряются. Повторная установка новой версии восстановит выбор. Массовая очистка конфига не нужна.

Release-артефакты

  • docs/CHANGELOG.md и docs/CHANGELOG.ru.md: user-visible пункт со ссылкой #378 в том же коммите, что поведение.
  • docs/USER-GUIDE.md и .ru.md: как выбрать position/percentage/temperature и что tap не меняется.
  • docs/DEVICE-PRESENTATION.md: explicit/auto/dash строки decision table.
  • docs/ARCHITECTURE.md и docs/CONFIG-COMPATIBILITY.md: общий resolver, optional key, delta-validation и reference seam.
  • Golden cover 42 % + reviewed artifact; docs screenshots с точного SHA.
  • Отдельного performance/security отчёта нет: результаты budget и негативных validation tests входят в handoff.

Принято предположительно, поменять свободно

Это технические решения, а не новые продуктовые требования:

  • имя persisted-поля marker.value_source;
  • извлечь neutral source module или реэкспортировать тип/функции из текущего device-value-badge.ts — выбирается вариант с меньшим циклом зависимостей;
  • расширить существующий smoke или создать отдельный;
  • хранить diagnostic fallback рядом с explicit — отдельным полем либо снять запрет fallback в policy через explicit-флаг; публичный результат нормативен, внутренняя форма нет.