Волна 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
52 KiB
Issue #90 — управляемый бейдж со значением рядом с устройством
- Статус: реализовано локально / ревизия 2 после ревью 2026-08-11; ожидает beta-проверки
- Issue: https://github.com/Matysh/houseplan-card/issues/90
- Область: frontend, marker config, backend validation, import/export, документация и QA
- Приоритет: P2
- Тип: feature / UX
1. Резюме
В настройках каждого устройства появляется явная настройка отдельного бейджа со значением. Пользователь самостоятельно решает:
- нужен ли бейдж;
- какое конкретно значение Home Assistant он показывает;
- с какой стороны маркера он расположен: справа, снизу, слева или сверху.
Checkbox, источник и положение являются live-настройками: существующий блок предпросмотра обязан сразу показывать итоговый marker вместе с бейджем именно так, как он будет выглядеть на плане.
Новая настройка заменяет неявный выбор температуры/влажности по иконке и
primary-сущности для явно настроенных устройств. Старые нетронутые
конфигурации продолжают выглядеть как раньше. Бейдж является независимой
поверхностью и не заменяет существующий режим «Значение вместо иконки».
2. Проблема текущей реализации
Сейчас компактное значение около маркера определяется скрытыми эвристиками:
- температура появляется только у
mdi:thermometer,mdi:air-filterлибо при включённомuse_climate_temp; - влажность появляется только тогда, когда именно
primary-сущность признана датчиком влажности; - температура и влажность не имеют пользовательского выбора источника;
- два показателя потенциально претендуют на одно и то же место справа;
- LQI всегда располагается снизу и не участвует в раскладке с другими показателями;
- изменение автоматической иконки или порядка сущностей может незаметно изменить отображение;
- глобальное поле
show_temperatureфактически управляет и температурой, и влажностью, но не объясняет конкретный выбор источника; - режим «Значение вместо иконки» решает другую задачу и не позволяет оставить обычную иконку с выбранным показанием рядом.
Результат непредсказуем: пользователь видит либо случайно подходящее значение, либо не видит ничего и не может объяснить или исправить результат в UI.
3. Пользовательская ценность
3.1. Оценка
Ценность: высокая, 8/10. Это не декоративная настройка, а устранение необъяснимого поведения одного из основных элементов плана.
Функция позволяет без отдельных карточек показывать рядом с устройством:
- температуру, влажность, заряд батареи, мощность, расход, давление;
- состояние бинарного датчика или реле;
- положение шторы/клапана;
- текущую температуру climate-устройства;
- громкость media player;
- средний LQI;
- состояние явно управляемого источника.
Особенно полезны мультисенсоры и сложные HA-устройства, у которых десятки сущностей и текущая эвристика почти неизбежно выбирает не то либо ничего.
3.2. Почему это лучше автоматической эвристики
- результат объясним и воспроизводим;
- изменение иконки не меняет значение;
- один marker показывает ровно один выбранный внешний бейдж;
- пользователь может осознанно распределить бейджи вокруг плотной группы;
- preview становится достоверным инструментом настройки;
- сохранённая ссылка переживает перестановку сущностей в реестре HA.
4. Цели
- Дать пользователю прямое управление одним внешним value-бейджем marker.
- Сделать выбор источника стабильным и не зависящим от порядка registry rows.
- Обеспечить одинаковый результат на полном плане, статической карточке пространства и в preview редактора.
- Исключить перекрытие нижнего value-бейджа и LQI.
- Сохранить внешний вид существующих нетронутых конфигураций.
- Не связывать выбор бейджа с влиянием устройства на комнатную температуру, свет, controls, Glow или заливку комнаты.
5. Не входит в задачу
- несколько пользовательских value-бейджей у одного marker;
- произвольное перетаскивание бейджа мышью;
- ручной offset, размер шрифта, цвет, фон и рамка;
- формулы, шаблоны, единицы пользователя, prefix/suffix;
- изменение алгоритма режима «Значение вместо иконки»;
- автоматическое предотвращение пересечения бейджа с соседними устройствами;
- использование произвольных сложных JSON-атрибутов HA;
- изменение комнатных подписей и метрик комнаты.
6. Термины
- Внешний value-бейдж — компактная плашка рядом с marker, управляемая этой задачей.
- Внутреннее значение — содержимое marker в режиме «Значение вместо
иконки» (
display: value). Это другая поверхность. - Системный LQI — существующая небоксированная подпись качества связи под
marker, управляемая
show_signalи настройкой пространства. - Источник бейджа — сохранённая конкретная HA-сущность, её поддерживаемый атрибут либо производное значение House Plan.
- Legacy auto — существующая эвристика температуры/влажности для marker без новой явной настройки.
7. UX диалога устройства
7.1. Размещение
Новый блок располагается в настройках устройства после поля «Отображение» и его пояснения, непосредственно над предпросмотром.
В свёрнутом состоянии отображается одна строка:
[ ] Отображать бейдж со значением
Если флаг включён, ниже раскрываются два поля:
- Значение — выпадающий список доступных источников;
- Расположение — выпадающий список:
- Справа;
- Снизу;
- Слева;
- Сверху.
Порядок в списке расположений нормативный: right, bottom, left, top.
По умолчанию используется Справа.
7.2. Поведение
- Все изменения немедленно отражаются в существующем preview.
- Preview показывает не условную схему, а тот же resolved badge, текст, позицию, unavailable-состояние и LQI-layout, что полный план и статическая карточка пространства. Сохранение для обновления preview не требуется.
- Выключение чекбокса скрывает два поля, но сохраняет последний источник и позицию. Повторное включение восстанавливает их.
- Если пользователь включает чекбокс впервые, рекомендуемый источник выбирается только в локальном draft и отражается в preview. Он записывается в config лишь потому, что включение checkbox является явным действием пользователя. Простое открытие диалога рекомендацию не материализует. Если источников нет, checkbox disabled и под ним показано: «У этого устройства нет доступных значений».
- Если сохранённый источник временно отсутствует, чекбокс остаётся доступным, выпадающий список содержит отдельную выбранную строку «Источник недоступен» и предупреждение. Пользователь может сохранить настройку, отключить бейдж или выбрать другой источник.
- При явной смене HA-привязки в том же диалоге источник старой привязки не переносится: выбирается рекомендация новой привязки либо бейдж отключается, если кандидатов нет.
- В
display: static_iconсохранённый чекбокс показывается disabled с объяснением: «Статичный значок не показывает живые значения». Настройка не удаляется и восстанавливается после возврата в динамический display mode. - Для скрытого marker preview продолжает показывать его реальный дизайн по
существующему
designPreview-контракту.
7.3. Текст строк списка
Каждая опция должна показывать:
- понятное имя показателя;
- friendly name сущности, если одного имени показателя недостаточно;
- текущее форматированное значение вторичной строкой;
- entity id/attribute в tooltip либо вторичной технической строке.
Примеры:
Температура · Датчик климата—23,4 °C;Текущая температура · Кондиционер—24,1 °C;Заряд батареи · Датчик двери—78 %;Состояние · Реле света—Включено;Положение · Штора—35 %;Качество Zigbee-сигнала (среднее)—142.
Технический entity id не должен заменять friendly name, но всегда должен быть доступен для диагностики.
8. Доступные источники
8.1. Общий принцип
Dropdown строится только из источников, связанных с текущим marker:
- активные сущности его
device:/entity:binding; - прямые активные HA-сущности из
controls; - прямые
marker:*targets изcontrolsкак производное состояние; - производный собственный state пассивного/виртуального источника света;
- производный средний LQI текущего устройства.
Для marker:* бейдж не обходит controls/light graph самостоятельно: он
потребляет уже разрешённое состояние существующего канонического resolver из
#84. Второго обхода, собственного определения on/off и отдельного cycle
resolver не создаётся.
Произвольные сущности со всего Home Assistant не предлагаются. Это сохраняет смысл настройки «значение этого устройства» и не превращает dropdown в общий entity picker.
8.2. State сущности
Допускается state активной сущности, если:
- сущность принадлежит разрешённому набору выше;
- её domain не
buttonи неevent; entity_categoryне равенconfig;- state является строкой, числом или boolean либо временно отсутствует;
- сущность не отключена в HA.
Диагностические сущности допускаются: battery, power, energy, signal и другие
измерения являются ценными кандидатами. unknown/unavailable не удаляют
строку из списка — источник может восстановиться.
8.3. Поддерживаемые атрибуты
Чтобы не показывать сотни служебных полей и не сохранять сложные объекты, разрешён централизованный allowlist скалярных атрибутов:
| Domain | Атрибуты |
|---|---|
climate |
current_temperature, temperature, current_humidity, humidity |
water_heater |
current_temperature, temperature |
cover, valve |
current_position |
fan |
percentage |
humidifier |
current_humidity, humidity |
light |
brightness |
media_player |
volume_level |
vacuum, lawn_mower |
battery_level, fan_speed |
В список попадает только реально объявленный атрибут либо уже сохранённая ссылка на него. Allowlist хранится в одном frontend-модуле вместе с metadata: локализованное имя, единица/преобразование и порядок.
Преобразования:
brightness0–255 → 0–100%;volume_level0–1 → 0–100%;current_position,percentage, humidity иbattery_level→ проценты;- температуры форматируются в единицах HA;
- остальные значения используют HA formatter либо безопасный fallback.
Произвольные строковые/массивные атрибуты (entity_picture, hvac_modes,
supported_features и т. п.) не предлагаются.
8.4. Производные источники
Поддерживаются два производных типа:
- Средний LQI — тот же
lqiFor(), который использует текущий системный индикатор; - Состояние marker-источника — итоговое
on/offдля пассивного источника или прямогоmarker:*target, вычисленное существующим light/control graph.
Если внешний value-бейдж показывает средний LQI, отдельный системный LQI у этого marker не рисуется, чтобы не дублировать одно значение.
8.5. Рекомендация при первом включении
Рекомендуемый источник выбирается детерминированно:
- существующий legacy climate temperature при
use_climate_temp: true; - существующий legacy temperature;
- существующий legacy humidity;
primarystate, если он допустим;- temperature;
- humidity;
- battery;
- первый функциональный state по существующему role resolver;
- первый прочий кандидат в стабильном порядке entity id;
- средний LQI.
Порядок реестра HA не используется как семантический приоритет.
9. Форматирование значения
- Для state сначала вызывается
hass.formatEntityState(). - Для атрибута сначала вызывается HA attribute formatter, если он доступен.
- Если HA formatter отсутствует или не вернул пригодный текст, применяется нормативное преобразование §8.3.
- Последний fallback — существующий безопасный scalar formatter House Plan; отдельный параллельный formatter для badge не вводится.
- Суффикс и единица (
°C,°F,%и т. п.) входят в готовый текст resolver.device-face.tsне приклеивает°/%: иначе при переводе legacy temperature/humidity на общий badge получится двойная единица. 0,false,off,closedявляются валидными значениями и не скрывают бейдж.unknown,unavailable, отсутствующий source и невалидный scalar дают видимый бейдж—с приглушённым unavailable-стилем и полным объяснением в tooltip/accessible label. Позиция при этом не прыгает.- Длинный текст сокращается многоточием. Полное локализованное значение
остаётся в
titleи accessibility-описании. - Значение рендерится только как текст; HTML из HA не интерпретируется.
10. Взаимодействие с существующими режимами
| Режим/настройка | Результат |
|---|---|
| Значок + динамическая подложка | Внешний бейдж показывается по настройке |
| Значок + активность | Бейдж показывается; activity ring проходит позади него |
| Значение вместо иконки | Внутреннее и внешнее значения независимы; допускаются разные источники |
| Значение вместо иконки и тот же source | Допускается, preview показывает неблокирующее предупреждение о дублировании |
| Всегда статичный значок | Внешний бейдж подавлен, настройка сохранена |
live_states: false |
Явный бейдж продолжает показывать доступное HA-значение; статусная подложка остаётся нейтральной |
| Явно выключенный бейдж | Legacy temperature/humidity для marker не показывается |
| Явно включённый бейдж | Имеет приоритет над глобальным show_temperature |
Per-space label_temp, label_hum, label_lqi, label_light |
Не подавляют явно настроенный value-badge: это свойство marker. Тумблеры продолжают управлять штатными room labels |
| Показ системного LQI выключен на card/space | Системная строка LQI скрыта; явно выбранный value-badge, включая source LQI, остаётся видимым |
| Источник badge = LQI | Показывается выбранный boxed badge; отдельный LQI скрыт |
| Скрытый/HA-disabled marker | Marker и его бейдж не показываются по общему lifecycle-контракту |
Выбор бейджа не изменяет статус устройства, activity, light role, Glow, controls, комнатные метрики и click action.
11. Раскладка
11.1. Якоря
Бейдж привязывается к внешнему прямоугольнику .dev, а не к glyph и не к
activity ring:
right: центр бейджа по вертикали, начало за правой гранью marker;left: центр по вертикали, конец перед левой гранью;top: центр по горизонтали, нижняя грань над marker;bottom: центр по горизонтали, верхняя грань под marker.
Зазор и размеры вычисляются от --dev-size, поэтому масштаб конкретного
устройства, zoom, kiosk scale и статическая space-card дают одинаковые
пропорции. Поворот glyph не поворачивает бейдж.
11.2. Конфликт с LQI
Если position = bottom и системный LQI одновременно видим:
- value-бейдж располагается первым, ближе к marker;
- LQI располагается второй строкой ниже;
- общий контейнер центрируется относительно marker;
- между строками сохраняется масштабируемый gap;
- ни один элемент не меняет выбранную пользователем сторону автоматически.
При top, left и right системный LQI остаётся на штатном нижнем якоре.
Если value source сам является LQI, отдельная строка LQI подавляется.
11.3. Другие пересечения
- Activity ring может проходить под бейджем, но не сдвигает его и не вызывает layout jump.
- Бейдж имеет
pointer-events: none, поэтому не создаёт новую click target и не мешает hover/drag marker. - Автоматический flip у края плана запрещён: явно выбранная сторона стабильна.
- Соседние marker и подписи комнат автоматически не перестраиваются.
- Родительский слой не должен обрезать бейдж по прямоугольнику самого marker.
11.4. Safe area предпросмотра и текущая регрессия clipping
В текущем hp-device-preview размер preview рассчитывается преимущественно по
диаметру marker/activity ring, а .previewstage использует
overflow: hidden. В результате существующий боковой temperature/humidity
badge может быть слегка обрезан границей stage. Это подтверждённая регрессия и
часть scope issue #90, а не допустимое ограничение preview.
Новый preview обязан учитывать полный визуальный bounding box face:
- marker plate;
- activity ring;
- внешний value-бейдж в выбранной позиции;
- системный LQI;
- нижний стек value badge + LQI;
- HA-disabled/new-device служебные badges, если они присутствуют.
Алгоритм fit/центрирования использует максимальные extents всех этих элементов
и оставляет не менее одного масштабируемого gap до каждой границы previewstage.
Нормативный browser-assert сравнивает getBoundingClientRect() бейджа и stage:
badge.left >= stage.left + gap, badge.right <= stage.right - gap, аналогично
по вертикали. Проверка выполняется для четырёх позиций, минимальной ширины
диалога, максимального activity ring и legacy temperature/humidity badge.
Нельзя исправлять clipping простым overflow: visible, если это позволяет
бейджу залезть в facts-колонку или за скругление preview-карточки. Допустимы:
- внутренний safe-area wrapper с рассчитанным padding;
- вычисление fit по расширенному face bounding box;
- комбинация обоих подходов.
При смене right → bottom → left → top marker остаётся визуально центрированным в доступной области вместе со спутниками, без скачка размера stage. Длинное значение сначала сокращается до нормативной max-width, затем участвует в fit. Legacy temperature/humidity badge до явной настройки также получает этот fix.
12. Модель данных
12.1. TypeScript
type ValueBadgePosition = 'right' | 'bottom' | 'left' | 'top';
type ValueBadgeSource =
| { kind: 'entity_state'; entity_id: string }
| { kind: 'entity_attribute'; entity_id: string; attribute: string }
| { kind: 'derived_lqi' }
| { kind: 'derived_marker_state'; ref: `marker:${string}` };
interface MarkerValueBadge {
enabled: boolean;
source?: ValueBadgeSource | null;
position: ValueBadgePosition;
}
interface MarkerCfg {
// ...
value_badge?: MarkerValueBadge | null;
}
enabled: false разрешает сохранять последний source и position. Для
enabled: true отсутствие source является допустимым только как runtime
состояние старого/повреждённого конфига; UI не создаёт такую запись.
12.2. Backend validation
Backend семантически проверяет новую или изменяемую пользователем запись:
- известный
kind; - строковый
entity_idформата HA; - attribute из frontend/backend общего allowlist;
- канонический
refформатаmarker:<id>для derived marker — тот же формат и те же helper/правила broken reference, что уcontrols(#84); - position из четырёх значений;
- boolean
enabled; - согласованность discriminated union.
Произвольный attribute, пустой id/ref и несогласованная форма union
отклоняются на записи нового/изменённого badge. Неизвестные соседние ключи
в value_badge и source сохраняются (ALLOW_EXTRA) по downgrade-контракту
docs/CONFIG-COMPATIBILITY.md: чтение и round-trip конфига из будущей версии
не падают и не стирают неизвестные данные.
12.3. Ссылки, удаление и import remap
derived_marker_state.ref является внутренней marker-ссылкой наравне с
controls[] = marker:<id>:
- import/export #50 добавляет её в таблицу remap внутренних id;
- при импорте пространства target id ремапится вместе с
marker.id; - target вне импортируемого пространства снимается, badge становится
enabled: false, position сохраняется, а import preview увеличивает счётчик отброшенных внешних marker-ссылок; - атомарная очистка/удаление target marker из #84 обрабатывает badge тем же общим helper: source сохраняется как broken/missing для runtime-диагностики, но не перепривязывается молча;
- badge не добавляет ребро управления и не обходит граф, поэтому не создаёт
отдельного цикла сверх уже проверенного
controlsgraph.
13. Совместимость и миграция
13.1. Отсутствующее поле
value_badge == null/undefined означает legacy compatibility, а не
явное выключение:
- renderer выполняет текущую temperature/humidity эвристику без изменения;
show_temperatureпродолжает управлять этим legacy-результатом;- существующие планы остаются pixel-identical до явного изменения настройки.
13.2. Диалог старого marker
Для marker без value_badge UI строит эффективный draft:
- если legacy badge сейчас существует — checkbox визуально включён, выбран тот же source, position = right;
- иначе checkbox выключен, но dropdown заранее получает рекомендуемый source;
- draft содержит внутренний
valueBadgeTouched = false.
Сохранение других полей не материализует новую настройку. value_badge
записывается только после взаимодействия с checkbox/source/position. Это
защищает конфигурацию от случайной миграции при временно неполном registry.
13.3. Явная настройка
enabled: falseполностью подавляет legacy temperature/humidity этого marker независимо отshow_temperature;enabled: trueпоказывает выбранный source независимо отshow_temperature;- глобальное
show_temperatureсохраняется как compatibility-настройка для marker без явногоvalue_badgeи не удаляется из schema; - новые marker используют тот же draft/recommendation contract, но не получают произвольный generic badge без действия пользователя.
13.4. use_climate_temp
Существующее поле сохраняет ответственность за участие climate
current_temperature в средней температуре комнаты.
- У marker без новой настройки оно также сохраняет старое поведение badge.
- У явно настроенного marker оно не управляет внешним badge.
- Текст настройки меняется с «Использовать датчик температуры устройства» на «Учитывать температуру устройства в комнате»; help объясняет, что внешний badge настраивается ниже отдельно.
Так выбор показания не начинает неявно менять агрегаты комнаты и наоборот.
14. Runtime-архитектура
14.1. Один resolver
Добавляется чистый resolveDeviceValueBadge() либо эквивалентный модуль,
который получает:
hass/registry projection;DevItem;- marker config;
- effective display/global compatibility settings;
- уже вычисленные presentation/light sources при необходимости.
Результат:
interface ResolvedValueBadge {
configured: boolean;
enabled: boolean;
source: ValueBadgeSource | null;
sourceLabel: string;
text: string;
fullText: string;
position: ValueBadgePosition;
availability: 'available' | 'unavailable' | 'missing';
isLqi: boolean;
}
ResolvedDevicePresentation получает одно поле valueBadge. Старые
tempText/humText перестают быть независимыми renderer decisions: legacy
эвристика также преобразуется в этот единый результат. В одном face никогда
не рендерятся два внешних value-бейджа.
14.2. Один renderer
device-face.ts рендерит единый DOM:
<span class="value-badge pos-right available">23,4 °C</span>
Полный план, preview и static space-card используют тот же
ResolvedDevicePresentation. Ни один renderer не выбирает source и не
форматирует значение самостоятельно.
hp-device-preview не имеет права повторно вычислять источник или позицию. Он
получает готовый valueBadge, использует общий renderDeviceFace() и отдельно
решает только задачу безопасного fit полного face bounding box (§11.4).
14.3. Кандидаты редактора
Список кандидатов строится только при открытом диалоге и memoize-ится по:
- binding;
- active registry revision;
- controls;
- relevant state/attribute signature.
Plan render не строит dropdown candidates. Новых подписок к HA не добавляется.
15. Lifecycle и edge cases
| Ситуация | Нормативное поведение |
|---|---|
Значение равно 0 |
Показать 0 с единицей |
State off/false |
Показать локализованное состояние |
unknown/unavailable |
Стабильный бейдж —, unavailable style |
Сущность временно исчезла из hass.states |
Бейдж —; source не переназначать |
| Registry row удалён после сохранения | Бейдж —; editor показывает missing source |
| HA-disabled source внутри активного device | Бейдж —; не подменять другой сущностью |
| HA-disabled весь binding | Marker скрыт по lifecycle-контракту |
| Смена friendly name | Label обновляется, source id сохраняется |
| Смена unit system HA | Значение переформатируется без миграции config |
| Source меняет numeric state на text | HA-formatted text показывается, если scalar |
| Source начинает возвращать object/array | Бейдж —; HTML/JSON не выводить |
| Обычное виртуальное устройство без HA binding, light role и controls | Checkbox disabled: нет кандидатов |
| Пассивный forced-light marker без контроллеров | Доступно его каноническое derived marker state: по #84 источник имеет состояние всегда |
| Виртуальный passive light с входящим controller | Доступно уже resolved derived marker state |
| Несколько controls | Каждый прямой source — отдельная явная опция |
| Target marker удалён | Сохранённый source missing, без silent fallback |
| Controls graph изменён | Сохранённый source остаётся либо становится missing |
| Marker size изменён | Бейдж масштабируется от --dev-size |
| Glyph повёрнут | Бейдж остаётся горизонтальным |
| Нижний badge + LQI | Стек: badge ближе, LQI ниже |
| Badge source = LQI | Только boxed value badge, без дубликата LQI |
display: value |
Внутреннее и внешнее значения независимы |
display: static_icon |
Badge подавлен, config сохранён |
| User-hidden marker в device editor | Ghost marker следует существующему контракту |
| Импорт старой конфигурации | Поле отсутствует → legacy auto |
| Экспорт/импорт новой конфигурации | Source и position round-trip без изменений |
16. Accessibility и touch
- Бейдж не является отдельной интерактивной целью.
- Accessible name marker дополняется: «{имя устройства}, {имя показателя}: {полное значение}».
- Внутренний span скрывается от screen reader либо маркируется так, чтобы значение не читалось дважды.
—сопровождается текстом «значение недоступно», а не читается как необъяснимый символ.- Все label/select связаны программно; checkbox имеет описание причины disabled.
- В режиме просмотра touch hit target остаётся marker, бейдж не перехватывает tap/pinch/pan.
- Редактор на touch остаётся best-effort согласно общей политике проекта, но нативные select/checkbox должны быть доступны.
17. I18n и документация
Новые строки добавляются синхронно в RU/EN:
marker.value_badge.enabled;marker.value_badge.source;marker.value_badge.position;- четыре позиции;
- no candidates / unavailable / missing / duplicate notices;
- названия allowlisted attributes и derived sources;
- обновлённые label/help
use_climate_temp.
<hp-help> обязателен у checkbox включения, поля source и поля position. Для
них добавляются пары <key>.help и <key>.help.aria в RU/EN по контракту #68;
тест паритета help-реестра обязателен.
Обновить:
docs/USER-GUIDE.ru.mdи английский пользовательский документ;docs/ARCHITECTURE.md— marker presentation/data model;docs/TESTING.md;- пример marker config;
- changelog значимой beta.
18. Безопасность и производительность
- Все значения проходят текстовое Lit-binding;
unsafeHTMLзапрещён. - Attribute source ограничен allowlist, backend повторяет проверку.
- Runtime не сканирует весь HA registry для каждого marker.
- Сохранённый source не вызывает service calls.
- Изменение live value не должно пересоздавать marker DOM или запускать анимацию layout; меняется только текст/availability class.
- Snapshot visual continuity обязан сохранять и badge, чтобы возврат на вкладку не давал промежуточный legacy/пустой кадр.
- Фича укладывается в существующий large-house/performance профиль и его fail-closed сверку окружения; отдельный профиль и новый бюджет не заводятся. Существующие candidate/beta performance checks не ослабляются.
19. План реализации
- Добавить типы, constants/allowlist и frontend/backend schema.
- Обновить import/export и validation tests.
- Реализовать candidate discovery, recommendation и formatter.
- Реализовать
resolveDeviceValueBadgeи интегрировать вResolvedDevicePresentation. - Перевести legacy temp/humidity projection на единый resolved badge.
- Добавить draft/touched/save lifecycle в диалог marker.
- Добавить UI и live preview.
- Заменить
.tval/.hvalединым четырёхпозиционным CSS-компонентом и LQI stack. - Обновить static card, tooltip/accessibility и visual snapshot capture.
- Добавить unit, backend, DOM smoke и golden fixtures.
- Обновить документацию и выпустить через beta согласно promotion rule.
20. Тестовая матрица
20.1. Unit/frontend
- четыре позиции;
- source state: numeric/text/binary/zero/false;
- каждый allowlisted attribute и преобразование percent/temperature;
- unknown/unavailable/missing/non-scalar;
- deterministic recommendation;
- explicit on/off overrides legacy/global setting;
- legacy marker остаётся pixel/semantic compatible;
- bottom + LQI stack и LQI dedup;
- static mode suppression with config preservation;
- display=value independence;
- hidden/disabled lifecycle;
- binding change reset;
- candidate filtering and stable ordering;
- source ids survive rename/reorder.
- explicit badge не зависит от
label_temp/label_hum/label_lqi/label_light; - системный LQI продолжает зависеть от своих card/space toggles.
20.2. Backend/import-export
- все valid union variants;
- invalid kind/position/entity/attribute rejected на новой/изменённой записи;
- unknown extra keys переживают read/round-trip;
- enabled true without source rejected on write, но существующий повреждённый
config читается без падения и даёт
—; marker:<id>remap, external target drop/disable и import preview counter;- full/partial export-import round-trip;
- old config without field accepted.
20.3. Browser/visual
- preview = interactive plan = static card;
- RU/EN long labels do not create horizontal dialog scroll;
- изменение checkbox/source/position без сохранения немедленно обновляет preview;
- marker scale 0.5/1/3;
- icon rotation 0/137°;
- activity ring behind badge;
- all four positions in light/dark theme;
- bottom badge with visible LQI;
- legacy правый temperature/humidity badge целиком помещается в preview;
- ни один из четырёх badge anchors не обрезается previewstage при минимальной ширине диалога и при activity ring максимального размера;
- long value ellipsis/title;
- touch pointer events do not steal marker click or pinch;
- visual continuity after tab hide/restore.
Golden fixtures должны содержать минимум один marker для каждой позиции и отдельный нижний badge + LQI. Любой ненулевой diff существующих golden считается регрессом. Новые эталоны принимаются только из полного Linux-артефакта CI по действующему HP-QA-01 контракту.
20.4. Мутационный гейт #85
Для каждого мутанта сохраняются исполнимый patch/команда и имя краснеющего теста:
- Удалить touched gate, чтобы сохранение чужого поля материализовало
value_badge→ падаетmarker value badge untouched save preserves legacy. - Игнорировать
enabled: falseи вернуть legacy temperature/humidity → падаетexplicit disabled value badge suppresses legacy metric. - Заставить
hp-device-previewвычислять source самостоятельно вместоResolvedDevicePresentation.valueBadge→ падаетdevice preview consumes resolved value badge verbatim. - При missing source выбрать первый candidate → падает
missing value badge source never falls back silently. - Не подавлять системный LQI при source
derived_lqi→ падаетderived lqi value badge renders exactly once. - Убрать full-face safe gap/fit → browser-тест
preview value badge bounding box stays inside stageпадает хотя бы для right/left либо max activity ring.
21. Критерии приёмки
- В диалоге над preview есть checkbox «Отображать бейдж со значением».
- При включении доступны source и одна из четырёх позиций.
- Выбранный source, а не иконка/порядок entities, определяет значение.
- Full plan, preview и static card показывают одинаковый badge.
- Checkbox, source и position обновляют preview немедленно, до сохранения.
- Бейдж ни с одной стороны не обрезается в preview; текущая регрессия бокового legacy-бейджа исправлена.
- Внешний badge не перекрывает системный LQI при нижней позиции.
- Одновременно рендерится не более одного внешнего value-бейджа marker.
- Missing/unavailable source не заменяется молча другим и показывает
—. - Explicit off действительно убирает legacy temperature/humidity.
- Explicit on работает независимо от глобального
show_temperature. - Старые нетронутые markers выглядят как до изменения, кроме исправленного clipping в preview.
static_iconостаётся полностью статичным.- Badge не меняет свет, Glow, controls и комнатные агрегаты.
- Config проходит backend validation и import/export round-trip.
- RU/EN, keyboard, screen reader и touch-view контракты выполнены.
- При открытии editor нативные source/position selectors показывают именно
сохранённые effective values, даже если они не первые в динамическом списке;
открытие не помечает
value_badgeкак touched и не меняет config (#100). - Реализация выходит сначала в beta/RC.
22. Сложность, риски и оценка
22.1. Сложность
Средняя/выше средней. Сам UI прост, но корректная функция затрагивает семантический projection, schema, три renderer surface, compatibility и registry lifecycle.
Оценка инженерного объёма:
| Блок | Оценка |
|---|---|
| Модель, validation, import/export | 0,5–1 день |
| Resolver/candidates/formatting/compatibility | 1–1,5 дня |
| Dialog UX и preview | 0,5–1 день |
| Legacy temp/humidity → единый resolver с pixel parity | 0,5–1 день |
| Full-face preview fit и измеримый safe area | 0,5–1 день |
| Renderer/CSS/LQI layout/a11y | 0,5–1 день |
| Tests, golden, docs, beta hardening | 1–1,5 дня |
| Итого | 4,5–8 рабочих дней, одна beta-итерация |
22.2. Основные риски
| Риск | Вероятность/ущерб | Снижение |
|---|---|---|
| Регрессия старых temp/humidity | средняя/высокий | absence = legacy, touched gate, parity fixtures |
| Source исчезает после registry refresh | высокая/средний | stable ref, —, no silent fallback |
| Перегруженный dropdown сложного устройства | средняя/средний | scope, grouping, allowlist, ordering |
| Несовпадение plan/preview/static | средняя/высокий | единый presentation resolver и face renderer |
| Перекрытия с LQI/activity | средняя/средний | нормативный stack и golden matrix |
| Clipping спутников в узком preview | высокая/средний | full-face extents, safe area и browser matrix |
| Скрытое изменение room average | низкая/высокий | полное разделение badge и climate aggregate |
| Новая per-state работа ухудшит render | низкая/средний | memo/snapshot, candidates только в dialog |
| Слишком длинные текстовые states | высокая/низкий | max-width, ellipsis, full accessible text |
23. Принятые продуктовые решения и вопросы
Блокирующих вопросов для начала реализации нет. В этом ТЗ предложены следующие решения, которые должны считаться нормативными после принятия issue:
- Один внешний пользовательский бейдж на marker.
- Явная per-device настройка важнее глобального
show_temperature. - Нетронутые marker остаются на legacy auto без принудительной миграции.
- Системный LQI сохраняется; снизу элементы складываются в стек.
static_iconподавляет бейдж, не стирая настройку.- Неизвестное значение показывается как стабильный
—, а не исчезает. - Выбор badge source не влияет на среднюю температуру комнаты.
- Поддерживаются states связанных сущностей и ограниченный набор полезных scalar attributes; произвольные attributes не поддерживаются.