mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 12:18:51 +00:00
docs: specify selectable value face source
Issue: #378 User-Visible: no
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
# 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` добавляется:
|
||||
|
||||
```ts
|
||||
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-флаг; публичный результат нормативен,
|
||||
внутренняя форма нет.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Спецификации задач
|
||||
|
||||
Актуально на 2026-08-25.
|
||||
Актуально на 2026-08-29.
|
||||
|
||||
GitHub Issues и GitHub Projects (v2) остаются единственным каноническим backlog проекта. Этот каталог содержит развёрнутые ТЗ: каждое ТЗ ссылается на issue, а issue — на соответствующий файл. Статус, приоритет и факт завершения меняются только в GitHub.
|
||||
|
||||
@@ -113,6 +113,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#75](https://github.com/Matysh/houseplan-card/issues/75) + [#76](https://github.com/Matysh/houseplan-card/issues/76) Единый поток размещения проёмов | [075-076-opening-placement-flow.md](075-076-opening-placement-flow.md) |
|
||||
| [#84](https://github.com/Matysh/houseplan-card/issues/84) Источник «Всегда» без сущности HA + [#88](https://github.com/Matysh/houseplan-card/issues/88) ведущая сущность | [084-passive-forced-light-sources.md](084-passive-forced-light-sources.md) |
|
||||
| [#90](https://github.com/Matysh/houseplan-card/issues/90) Управляемый бейдж со значением | [090-device-value-badge.md](090-device-value-badge.md) |
|
||||
| [#378](https://github.com/Matysh/houseplan-card/issues/378) Выбираемый источник для режима «Значение + состояние» | [378-value-face-source.md](378-value-face-source.md) |
|
||||
| [#94](https://github.com/Matysh/houseplan-card/issues/94) Универсальное действие «Переключить состояние» | [094-universal-state-toggle.md](094-universal-state-toggle.md) |
|
||||
| [#101](https://github.com/Matysh/houseplan-card/issues/101) Плавный переход View ↔ редакторы | [101-view-editor-transition.md](101-view-editor-transition.md) |
|
||||
| [#107](https://github.com/Matysh/houseplan-card/issues/107) Переключение виртуального источника света «Всегда» | [107-virtual-light-toggle.md](107-virtual-light-toggle.md) |
|
||||
|
||||
Reference in New Issue
Block a user