38 KiB
Всегда статичный значок устройства
Статус: ТЗ актуализировано по ревью, реализовано и проверено в v1.60.2 Дата: 2026-08-08 Область: редактор устройств, все поверхности отображения marker, единый resolver визуального состояния, backend-валидация и пользовательская документация
1. Краткое продуктовое решение
В поле «Отображение» добавляется четвёртый режим:
«Всегда статичный значок».
В этом режиме видимый marker устройства всегда имеет один и тот же нейтральный вид:
- стандартная тёмная подложка marker;
- базовая выбранная или автоматически определённая иконка;
- заданные пользователем размер и угол поворота;
- без изменения подложки, прозрачности, цвета и иконки из-за состояния Home Assistant;
- без activity/pulse-эффекта;
- без RGB-окраски от источника света;
- без подмены иконки по состоянию;
- без живых дополнительных значений температуры, влажности и качества связи.
- без отдельного движущегося puck, следа и других state-driven overlays живого пылесоса.
Режим является только способом отображения marker. Он не изменяет привязку, действия по нажатию, принадлежность комнате, участие устройства в управлении, источниках света, Glow, заливке комнаты или расчётах плана.
Текущий режим «Значок» переименовывается в:
- RU: «Значок + динамическая подложка»;
- EN: «Icon + dynamic plate».
Его внутренняя модель и текущее поведение не меняются. Он остаётся режимом по умолчанию.
2. Причина изменения
Сейчас минимальный режим badge, показанный пользователю как «Значок», всё равно является динамическим:
- работающие устройства получают жёлтую подложку;
- открытые объекты получают отдельное состояние;
- тревоги становятся красными;
off,unknownиunavailableиспользуют приглушённое представление согласно действующей общей логике;- некоторые классы устройств меняют саму иконку по состоянию;
- активный источник света может передавать marker цвет;
- при включённых общих настройках рядом появляются температура, влажность или LQI.
Название «Значок» не объясняет эту динамику, а отдельного способа намеренно оставить устройство визуально нейтральным нет. Это особенно заметно у устройств с большим числом вспомогательных сущностей, у декоративных marker и у объектов, состояние которых не нужно акцентировать на плане.
3. Цели
- Дать пользователю предсказуемый режим, в котором HA state не меняет вид marker.
- Явно назвать существующий режим
badgeдинамическим. - Сохранить текущий режим по умолчанию для новых и существующих устройств.
- Обеспечить одинаковый результат на интерактивном плане, в карточке пространства и в предпросмотре настроек.
- Не смешивать отображение marker с логикой света, управления и данных плана.
- Не требовать миграции существующих конфигураций.
4. Не входит в задачу
- изменение правил, по которым определяется состояние устройства;
- изменение
resolvedLightSources(room); - изменение Glow, заливки «Свет» или управления источниками света;
- изменение tap action, more-info или диалога устройства;
- создание отдельной пользовательской палитры статичного marker;
- фиксация автоматически выбранной MDI-иконки как ручного override;
- изменение правил скрытых, удалённых и деактивированных в HA устройств;
- изменение поведения остальных трёх режимов, кроме переименования
badgeв UI.
Touch editor: best effort / intentionally degraded согласно docs/TOUCH-SUPPORT.md. Native select обязан оставаться сохранённым и работоспособным на узком экране, но мобильная компоновка редактора не является отдельной feature-parity целью. View и kiosk полностью поддерживаются.
5. Список режимов и порядок в UI
Порядок в выпадающем списке обязателен:
| № | Persisted value | RU | EN | Краткое поведение |
|---|---|---|---|---|
| 1 | badge или null |
Значок + динамическая подложка | Icon + dynamic plate | Иконка и текущая динамическая подложка |
| 2 | icon_ripple |
Значок + активность | Icon + activity | Динамическая подложка плюс activity-эффект |
| 3 | value |
Значение вместо иконки | Value instead of an icon | Значение, когда его можно однозначно получить, иначе иконка |
| 4 | static_icon |
Всегда статичный значок | Always static icon | Всегда нейтральная подложка и базовая иконка |
Legacy-значение ripple по-прежнему принимается только для чтения и нормализуется в icon_ripple; в UI оно не показывается.
6. Значение по умолчанию и совместимость
6.1. Новые устройства
Новый marker по умолчанию получает UI-режим badge. При сохранении значение может, как и сейчас, быть записано как null/отсутствующее поле. Это означает «Значок + динамическая подложка», а не статичный значок.
6.2. Существующие устройства
- отсутствующий
display→badge; display: null→badge;display: "badge"→badge;display: "ripple"→icon_ripple;display: "icon_ripple"→icon_ripple;display: "value"→value;display: "static_icon"→ новый статичный режим;- неизвестное значение во frontend должно безопасно давать
badge, а backend продолжает отклонять его при записи.
Автоматически переводить существующие устройства в static_icon запрещено.
6.3. Изменение режима
Переключение режима в открытом диалоге сразу обновляет предпросмотр, но не меняет server config до нажатия «Сохранить». Отмена диалога возвращает сохранённый режим.
7. Нормативный визуальный контракт static_icon
7.1. Что остаётся видимым
- базовая иконка
d.icon; - ручная иконка, если пользователь её выбрал;
- автоматически определённая иконка, если ручного override нет;
- пользовательский размер marker;
- пользовательский угол поворота;
- обычная рамка и тень нейтрального marker согласно теме House Plan.
«Чёрная подложка» в требованиях означает текущую стандартную нейтральную подложку marker, а не жёстко заданный #000000. Реализация обязана использовать действующие theme tokens (--hp-bg, --hp-line, --hp-txt или их актуальные преемники), чтобы сохранить контраст и совместимость с темами HA.
7.2. Что принудительно отключается
Для static_icon renderer-ready presentation всегда получает:
visual.availability = "available";visual.status = "neutral";visual.activity = "none";activity = "none";valueText = null;valueFullText = null;fallbackReason = null;tempText = null;humText = null;lqiText = null;lqiColor = null;lightColor = null;- базовую, а не state-morphed иконку;
- отсутствие классов
on,open,alarm,unavailиactivity-*.
Отдельный live-vacuum renderer также обязан скрыть движущийся puck, текущий/предыдущий след и room-highlight для этого marker. Runtime/серверная запись следа может продолжаться независимо: смена режима отображения не удаляет историю уборки.
Ручные ripple_color и ripple_size, оставшиеся в старой конфигурации, не используются и не показываются в форме, пока выбран static_icon. Их сохранённые значения не должны неожиданно влиять на статичный marker.
7.3. Состояния, которые не меняют marker
| Условие | Результат в static_icon |
|---|---|
| Устройство включено | Нейтральный marker |
| Устройство выключено | Тот же нейтральный marker |
| Устройство работает | Тот же нейтральный marker |
| Устройство остановлено | Тот же нейтральный marker |
| Движение/присутствие обнаружено | Тот же нейтральный marker |
| Cover открывается/закрывается | Тот же нейтральный marker и базовая иконка |
| Дверь/замок открыт | Тот же нейтральный marker |
Сущность unknown/unavailable |
Тот же нейтральный marker, без fade |
| Тревога/протечка/дым | Тот же нейтральный marker, без красной подложки |
| Свет включён | Тот же нейтральный marker, без жёлтой/RGB-подложки |
| Краткое event-событие | Тот же marker, без activity ring |
| Общая настройка live states включена/выключена | Тот же marker |
| Включены temperature/LQI badges | Для этого marker badges не показываются |
| Живой пылесос движется или имеет сохранённый след | Puck, след и room-highlight не рисуются |
Подавление тревоги является осознанным следствием режима. Предпросмотр и диалог Home Assistant всё ещё могут показывать фактическое тревожное состояние, но marker на плане не сигнализирует о нём.
8. Граница между отображением и поведением устройства
static_icon меняет только лицо marker. Следующие возможности продолжают работать без изменений:
- клик, more-info, toggle, cover action, run action и подтверждение действия;
controlsи управление связанными источниками света;is_lightи автоматическое определение светового источника;- Glow и заливка «Свет по источникам»;
- световой вклад устройства в карточку комнаты и controls;
- принадлежность пространству/комнате;
- температура устройства как источник комнатной температуры, если это настроено;
- данные в tooltip/more-info и предпросмотре настроек;
- автоматическое обновление HA state внутри модели данных.
Если статичный marker выглядит доступным, но его active binding фактически имеет state unavailable, действие по нажатию проходит по существующему безопасному пути. Ошибка service call обязательно показывается существующим toast/HA feedback и не должна превращаться в молчаливый no-op. Новый режим не симулирует доступность сервиса и не подавляет ошибки действий.
Следствие: источник света в режиме static_icon может освещать комнату и создавать Glow, оставаясь сам визуально чёрным. Если пользователь не хочет, чтобы устройство влияло на освещение, для этого используется существующая настройка «Это устройство — источник света» и/или список управляемых источников, а не режим отображения.
9. Приоритеты и исключения видимости
Режим static_icon действует только на marker, который по общим правилам разрешено показывать.
| Ситуация | Приоритет над static_icon |
|---|---|
| Marker удалён с плана | Не отображается и снова доступен к добавлению |
| Пользователь нажал «Скрыть» | Не отображается в просмотре |
| В редакторе включено «Показать скрытые» | Показывается как служебный blue ghost, а не как обычный чёрный marker |
| Устройство деактивировано в HA | Принудительно скрыто в просмотре; в разрешённом design-preview показывается HA-disabled ghost |
| Привязка orphaned | Обрабатывается по отдельному lifecycle-контракту disabled-devices; static_icon не возвращает marker на поверхность, где общий контракт запрещает его рендер, и не маскирует диагностику потерянной привязки |
| Marker выбран в редакторе | Допускается внешняя рамка выделения |
| Keyboard focus | Обязателен focus-visible outline для доступности |
| Новый marker | Допускается служебная точка «новое устройство» |
Служебные editor/diagnostic overlays не считаются реакцией на состояние устройства. Они не должны перекрашивать внутреннюю подложку обычного видимого статичного marker.
10. Hover, focus и действия
Интерактивные состояния интерфейса не входят в понятие статичности устройства и сохраняют текущее общее поведение marker:
- hover продолжает использовать существующую accent-подсветку;
- pointer cursor, tooltip и действие по клику сохраняются;
- keyboard focus обозначается существующим focus-состоянием;
- выбор marker в редакторе обозначается существующей рамкой/тенью;
- drag feedback сохраняется, но не должен имитировать HA-состояния
on,open,alarmилиunavailable.
После завершения hover/focus/selection/drag marker возвращается к своей нейтральной статичной подложке. Новый режим подавляет только визуальные реакции на данные и состояния устройства, но не обратную связь на действия пользователя.
11. Предпросмотр устройства
hp-device-preview обязан использовать тот же ResolvedDevicePresentation, что и план и houseplan-space-card.
При выборе static_icon:
- stage сразу показывает нейтральную подложку и базовую иконку;
- кнопка демонстрации activity не показывается;
- строка «Текущее состояние» продолжает показывать фактический state HA;
- строка «Источник отображения» продолжает показывать сущность/сущности;
- строка интеграции продолжает показывать provider;
- строка «Результат» показывает локализованное объяснение: RU: «Статичный режим: состояние устройства не меняет значок»; EN: «Static mode: device state does not change the icon»;
- тревога, недоступность и активность могут быть видны в фактах/технических подробностях, но не меняют stage;
- режим не создаёт и не изменяет activity runtime.
Если привязка содержит alarm-capable сущность (smoke, gas, carbon_monoxide, moisture, safety, tamper, problem, siren или alarm panel), под select показывается неблокирующее предупреждение: статичный режим скроет визуальную тревогу marker. Предупреждение не запрещает Save и не зависит от того, активна ли тревога прямо сейчас.
В PresentationReason добавляется отдельная причина static_icon. Она имеет приоритет после ha_disabled/orphaned, но до alarm/availability/state причин для обычного renderable marker.
12. Автоматическая и ручная иконка
Статичный режим использует базовую effective icon:
- ручной
marker.icon, если он задан; - иначе иконку, определённую текущими правилами автоматического выбора.
State morph запрещён: например, cover не меняет mdi:...-closed на mdi:...-open при изменении state.
Автоматическая иконка не превращается в ручной override только из-за выбора static_icon. Поэтому она может измениться после реального изменения метаданных устройства или правил иконок. Если пользователю нужна полностью зафиксированная MDI-иконка и при изменении конфигурации HA, он должен нажать существующее действие фиксации автоматической иконки.
13. Модель данных и backend
13.1. Frontend
Канонический тип:
type DeviceDisplayMode = 'badge' | 'icon_ripple' | 'value' | 'static_icon';
ripple остаётся только legacy input и нормализуется в icon_ripple.
DISPLAY_MODES:
export const DISPLAY_MODES = ['badge', 'icon_ripple', 'value', 'static_icon'] as const;
Обязательно использовать единый экспортируемый DeviceDisplayMode и normalizer вместо повторения union и локальных _displayOf() в нескольких файлах. Неизвестное runtime-значение нормализуется в badge; ripple нормализуется в icon_ripple.
13.2. Persisted marker
display: static_icon
Поле display не переименовывается. Версия общей модели данных не меняется: это расширение уже существующего enum.
13.3. Backend
MARKER_SCHEMA принимает:
badge | ripple | icon_ripple | value | static_icon | null
Cross-language test обязан подтверждать, что каждое значение из frontend DISPLAY_MODES принимается backend-схемой.
14. Архитектурный план реализации
14.1. Единая точка поведения
Основная логика реализуется в resolveDevicePresentation(), а не отдельно в каждом renderer.
Предпросмотр продолжает вычислять sources и фактические HA states для объяснения результата. Плановым поверхностям разрешён явный short-circuit source/value resolution для static_icon, поскольку этим данным нечего менять в renderer-ready лице. Short-circuit не должен затронуть отдельные агрегаты света/климата/controls, которые вызывают свои resolver независимо от marker presentation.
14.2. Поверхности-потребители
Должны быть проверены:
- интерактивный план в
houseplan-card; houseplan-space-cardчерезspace-render;hp-device-preview;- режим устройства в editor и design preview;
- kiosk/view mode;
- скрытые и HA-disabled ghosts.
Ни одна поверхность не должна самостоятельно повторно классифицировать состояние или добавлять live badges после resolver.
14.3. CSS
Renderer должен иметь стабильный hook для статичного режима, например класс static-icon или data-display="static_icon", чтобы:
- отключить перекраску внутренней подложки от HA state, не изменяя общий hover;
- сохранить существующие hover, focus и editor selection;
- не зависеть от порядка классов
on/open/alarm/unavail; - дать одинаковый результат в основном плане, предпросмотре и статической карточке.
Не допускается решать задачу только более специфичным CSS поверх динамических классов: semantic projection тоже обязана быть нейтральной, чтобы tooltip, preview explanation и будущие renderers не получали ложное состояние.
14.4. Activity runtime
static_iconникогда не отображает уже активный event window;- preview demo останавливается при переключении на
static_icon; - накопленный runtime не должен менять статичный marker;
- после перехода обратно в
icon_rippleне следует искусственно запускать activity только из-за смены режима; - реальные последующие события после возврата в
icon_rippleработают обычно.
15. Локализация и текстовая подсказка
Обновить RU/EN ключи:
display.badge
display.static_icon
marker.display_hint
marker.preview.reason.static_icon
Рекомендуемая RU-подсказка:
Динамическая подложка показывает работу, открытие, тревоги и недоступность. «Значок + активность» дополнительно показывает события и движение. «Значение вместо иконки» выводит однозначное состояние HA. Статичный значок не меняется от состояний устройства.
Рекомендуемая EN-подсказка:
The dynamic plate shows work, open, alarm and unavailable states. Icon + activity also shows events and motion. Value instead of an icon shows one unambiguous HA state. A static icon does not react to device states.
Длинный ярлык display.badge проверяется в узком native select. Обрезка закрытого системного select допустима только если после открытия ОС показывает полный текст option; горизонтальный scroll или нарушение ширины диалога недопустимы.
16. Accessibility
- Названия режимов должны полностью озвучиваться native select.
- Контраст нейтральной иконки и подложки соответствует текущему marker contract.
static_iconне отменяетfocus-visible.- Нельзя передавать состояние только через скрытую подложку: фактический state остаётся доступен в tooltip/more-info и в текстовом предпросмотре.
- Для тревожных устройств пользователь осознанно отказывается от визуальной тревоги на marker; это должно быть понятно из предпросмотра и подсказки режима.
prefers-reduced-motionне требует отдельной ветки: в статичном режиме движения нет изначально.
17. Edge cases
17.1. Источник света
Свет включён, marker имеет display: static_icon и is_light: true: marker остаётся нейтральным, но Glow и заливка комнаты продолжают работать.
17.2. Critical alarm среди вторичных сущностей
Resolver продолжает находить critical source для диагностики, но лицо marker не становится красным. В предпросмотре current state/details тревога остаётся видна.
17.3. Нет HA state при загрузке
Marker сразу отображается нейтральным, без промежуточного unavail fade и без последующего визуального скачка после прихода states.
17.4. Виртуальное устройство
Виртуальный marker использует тот же статичный контракт. Его обычная dashed-рамка как признак типа объекта сохраняется, поскольку это не HA state.
17.5. Value → static
Текстовое значение немедленно заменяется базовой иконкой. Fallback notice value-режима исчезает.
17.6. Activity → static
Текущее кольцо исчезает немедленно. Возврат в activity-режим не создаёт новое событие сам по себе.
17.7. Автоматическая state icon
Открытие cover, lock или door не меняет glyph. При ручной смене icon в форме предпросмотр меняется — это конфигурационное действие пользователя, а не реакция на HA state.
17.8. Размер и поворот
Ручные size/angle применяются как обычно. «Статичный» не означает «не редактируемый».
17.9. Custom CSS/card-mod
Встроенный контракт гарантирует нейтральный вид без пользовательских overrides. Намеренный custom CSS может изменить marker; задача не должна пытаться перебить все внешние !important-правила.
18. Тестовый план для этапа реализации
По действующему правилу проекта тесты запускаются перед пре-релизом; ниже перечислен обязательный набор тестов, который должен быть добавлен вместе с реализацией.
18.1. Unit: presentation resolver
Для static_icon проверить минимум:
onиoffдают одинаковую visual projection;unknownиunavailableне даютunavail;alarmне даётalarm;- presence/event/transition/running не дают activity;
- cover state не меняет базовую иконку;
- RGB light не задаёт
lightColor/динамическую окраску; - value/temp/hum/LQI отсутствуют;
- size и angle сохраняются;
- source facts/signature остаются доступными;
- hidden/HA-disabled precedence соблюдается;
- explanation reason равен
static_iconдля обычного marker; - переход между display modes не создаёт activity.
- live vacuum projection помечается как подавленная, а plan renderer не рисует puck/след в
static_icon.
18.2. Backend
static_iconпринимаетсяMARKER_SCHEMA;- неизвестное значение отклоняется;
- cross-language enum test проходит;
- legacy
rippleиnullпо-прежнему принимаются.
18.3. UI/component
- в select четыре пункта в заданном порядке;
badgeвыбран по умолчанию;- новое название
badgeлокализовано RU/EN; static_iconпоследним в RU/EN;- activity settings скрыты для
static_icon; - preview сразу становится нейтральным;
- activity demo отсутствует;
- hover работает так же, как у остальных marker;
- focus-visible и editor selection остаются видимыми;
- сохранение/повторное открытие сохраняет
static_icon. - alarm-capable binding получает неблокирующее предупреждение;
- tap по
unavailablebinding вstatic_iconпоказывает видимую ошибку service call, а не молчит; - длинный RU/EN ярлык не создаёт горизонтальный scroll на узком экране.
18.4. Поверхностный parity test
Один и тот же draft/config marker при одинаковых входных данных имеет одинаковые icon/classes/badges в:
- основном плане;
hp-device-preview;houseplan-space-card.
Parity-набор включает отдельную vacuum-фикстуру: base marker статичен, а puck/след отсутствуют. Минимальный browser smoke проверяет alarm-устройство в static_icon (нет alarm, unavail, activity-*), мгновенное переключение режима туда и обратно, preview parity и отсутствие vacuum overlay.
19. Документация реализации
В той же локальной серии изменений обновляются:
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.mdотдельной user-visible записью;docs/USER-GUIDE.ru.md— четыре режима и их матрица;docs/ARCHITECTURE.mdиdocs/STATUS.md— enum и единая static projection;docs/FILTERING.md— исключение из общей state-матрицы;docs/VACUUM.md— подавление puck/следа;docs/TESTING.mdиdocs/TESTING-DEMO.md— unit/backend/browser/manual сценарии;- существующие скриншоты редактора с прежним списком режимов, если такие актуальные скриншоты обнаружены.
20. Критерии приёмки
- В списке «Отображение» четыре режима,
static_iconрасположен последним. - Текущий «Значок» называется «Значок + динамическая подложка».
- Новые и старые устройства остаются в динамическом
badgeпо умолчанию. - Устройство в
static_iconвыглядит одинаково приon,off, working, open, alarm, unknown и unavailable. - Статичный marker не меняет иконку по state, не получает activity, RGB, value, temperature, humidity или LQI.
- Размер, угол и базовая иконка применяются.
- Световое и управляющее поведение устройства не меняется.
- Скрытые, удалённые и HA-disabled устройства не возвращаются на план из-за
static_icon. - Hover, focus и selection работают так же, как у остальных marker, и не изменяются этой задачей.
- Предпросмотр точно объясняет статичный результат, сохраняя фактический HA state в текстовых фактах.
- Backend принимает новый enum, существующие конфигурации не мигрируют и не ломаются.
- Все три renderer-поверхности используют единый resolver и показывают одинаковый результат.
- Живой пылесос в
static_iconне показывает puck, след или room-highlight, но сохранённая история не удаляется. - Ошибка действия по недоступной сущности остаётся видимой пользователю.
- Alarm-capable binding получает честное неблокирующее предупреждение до Save.
21. Подтверждённые продуктовые решения
Владелец продукта подтвердил 2026-08-08:
- Статичность касается только внешнего вида marker. Устройство продолжает влиять на Glow, заливку «Свет» и controls согласно своим отдельным настройкам и фактическому состоянию.
- У статичного marker полностью скрываются живые satellite badges температуры, влажности и LQI.
- Hover не изменяется этой задачей и сохраняет текущее общее поведение marker, включая существующую accent-подсветку. Статичность относится к HA state и данным устройства, а не к интерактивной обратной связи UI.
Тревоги, unknown/unavailable, state icon morph и RGB-цвет также безусловно подавляются, поскольку статичный marker должен иметь одинаковый вид при любых состояниях устройства.