Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
23 KiB
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. Выбор источника
- При
display: valueсразу под подсказкой режима отображается поле «Источник значения». - Первая строка — «Автоматически (как раньше)». Она соответствует отсутствию
явного источника. Далее идут кандидаты
valueBadgeCandidates()в том же порядке, с теми же названиями, техническими подписями и текущими значениями, что в selector value badge. - Оба потребителя используют общий
ValueSource/ValueSourceCandidate, общий source-key codec и общий formatter/resolver. Совпадение списков доказывается тестом на одну и ту же ссылку/проекцию данных, а не сравнением двух копий allowlist. - Отсутствие кандидатов не запрещает legacy auto: строка «Автоматически»
остаётся доступной. Для virtual marker selector disabled с существующим
объяснением
value_virtual; сохранённый явный источник не удаляется. - Переключение на другой display mode скрывает поле, но сохраняет выбор.
Возврат в
valueвосстанавливает его. - При явной смене HA binding в диалоге старый
value_sourceсбрасывается в auto: перенос источника прежнего устройства был бы ложной настройкой.
2. Разрешение и форматирование
- Если
marker.value_sourceотсутствует илиnull, вызывается существующий auto-pathresolveValue()без изменения порядка климатической legacy- эвристики, power-gate и проверки неоднозначности. - Если источник задан явно, auto-path не выбирает и не подставляет другую
сущность. State, attribute,
derived_lqiиderived_marker_stateразрешаются тем же кодом, что value badge. cover.current_position = 42отображается42 %; brightness, volume, humidity, battery и temperature получают ровно те же преобразования и единицы, что внешний badge.valueText— компактный текст,valueFullText— доступное полное значение/tooltip.- Временно отсутствующий, unknown/unavailable или переставший быть скалярным
явный источник сохраняется и отображает
—внутри лица значения. Он не заменяется иконкой и не переключается на auto. Presentation сохраняет диагностический кодvalue_no_stateилиvalue_non_scalar, но explicit- ветка не трактует этот код как разрешение сменить лицо. - Для auto-path прежняя семантика остаётся:
value_no_state,value_ambiguous_sourcesиvalue_non_scalarоткатывают лицо к иконке. Явный источник по определению снимаетvalue_ambiguous_sources. value_virtualимеет приоритет над сохранённым источником: virtual marker остаётся с иконкой, как сейчас.- Ключ выбранного источника входит в
sourceSignatureдаже в unavailable- состоянии, чтобы смена выбора/восстановление данных инвалидировала snapshot. - Если внешний 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_sourcemarker.value_source.helpи.ariamarker.value_source_automarker.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: valueselector содержит 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: rebindderived_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-флаг; публичный результат нормативен, внутренняя форма нет.