# Единая индикация состояния и активности устройств Статус: **утверждено владельцем и реализовано локально 2026-08-05**. ## Цель Заменить четыре пересекающиеся механики — цвет подложки, пользовательскую пульсацию, motion/presence-эффекты и движение штор — одной семантической системой. Визуал должен отвечать на три разных вопроса: 1. Доступно ли устройство. 2. В каком устойчивом состоянии оно находится. 3. Что оно делает прямо сейчас или что только что произошло. ## Режимы отображения В UI остаются: 1. **Значок** — иконка, морфинг и статусная подложка без обычных эффектов активности. 2. **Значок + активность** — то же плюс семантический эффект активности. 3. **Значение вместо значка** — числовая плашка со статусным цветом; обычная активность не рисуется. Опция **«Только пульсация» удаляется**. Legacy `display: ripple` читается как `icon_ripple`, а при следующем сохранении конфигурации переписывается в `icon_ripple`. Backend временно продолжает принимать старое значение. ## Модель визуального состояния Для маркера вычисляется один результат: ```ts availability: 'available' | 'unavailable' status: 'neutral' | 'open' | 'working' | 'alarm' activity: 'none' | 'event' | 'presence' | 'transition' | 'running' ``` - `availability` отвечает за приглушение недоступного устройства; - `status` управляет подложкой и морфингом; - `activity` управляет кольцом или волнами; - тревога имеет высший приоритет; - недоступность подавляет обычную активность. ## Визуальный язык | Смысл | Подложка | Эффект в «Значок + активность» | |---|---|---| | Доступно, простаивает | нейтральная | нет | | Выполняет основную функцию | жёлтая | медленное дыхание | | Открыто / разблокировано | оранжевая | только событие/переход, не постоянный эффект | | Разовое срабатывание | текущая статусная | три расходящиеся волны, 3,3 с | | Присутствие | нейтральная | спокойное статичное кольцо | | Механическое движение | текущая статусная | дыхание до конца движения | | Недоступно | приглушённая | нет | | Тревога (протечка, дым, газ, CO, safety/tamper/problem, сирена, сработавшая охрана) | красная | быстрая красная пульсация при любом display | `prefers-reduced-motion` заменяет анимацию статичным кольцом. ## Значение жёлтой подложки Жёлтый означает только **фактическое выполнение основной функции**: - `light/switch/fan/humidifier = on`; - climate: `hvac_action = heating/cooling/drying/fan`, но не просто выбранный режим `heat/cool/auto`; - vacuum: `cleaning` (и работающий механизм при `returning`); - бытовая техника: `running/washing/rinsing/spinning/drying/heating/cooking`; - маркер с `controls`: работает хотя бы одна управляемая цель. Не являются работой: открытая дверь, разблокированный замок, открытые шторы, presence/motion, `enabled`, `home`, `unknown`, `unavailable`, climate `idle`. `media_player` — пассивный медиатранспорт, а не исполнительный механизм: `on/idle/playing/paused/standby` остаются нейтральными и не получают running- активность, а явный `off` использует то же приглушённое отображение, что `unknown/unavailable`. Правило действует на весь домен (телевизоры, ресиверы, колонки, саундбары), не на конкретные модели, и не вводит отдельного статуса. Если у маркера несколько `media_player`, он бледнеет только когда среди них нет доступной и включённой сущности. В glow-режиме у реального источника света жёлтая подложка может быть скрыта: его устойчивым индикатором служит световое пятно. Эффект активности остаётся. ## Семантика активности ### Event, 3,3 секунды - motion: только засвидетельствованный `off -> on`, не весь cooldown; - vibration/shock/sound; - открытие двери/окна (`off -> on`), но не всё время открытого состояния; - button/event; - успешный запуск script/scene/automation без устойчивого состояния. Первое состояние после загрузки и восстановление из `unknown/unavailable` не создают ложного события. Повторный детект перезапускает окно и анимацию. ### Presence `occupancy/presence = on` даёт статичное кольцо до исчезновения присутствия. ### Transition - cover: `opening/closing`; - lock: `locking/unlocking`; - valve: `opening/closing`; - vacuum: `returning`. Если промежуточного состояния нет, прямой конечный переход `closed <-> open` / `locked <-> unlocked` показывает transition 3,3 секунды. ### Running Пока устройство фактически работает, его подложка жёлтая, а в режиме «Значок + активность» вокруг неё медленно дышит кольцо. ## Источник состояния Приоритет: 1. Явный специализированный источник (в будущей расширенной настройке). 2. Cover, когда tap action явно задан как «Открыть/закрыть». 3. `controls` — агрегированное состояние всех управляемых целей. 4. Функциональная сущность устройства / включённый light. 5. Primary entity. Критические сущности устройства проверяются независимо и не могут быть скрыты менее важным primary state. ## Приоритет вывода 1. alarm; 2. unavailable; 3. event; 4. transition; 5. presence; 6. running; 7. open; 8. neutral. ## Инварианты - Одна функция вычисляет подложку и activity; они не смотрят на разные сущности. - Обычная активность появляется только в `icon_ripple`. - Alarm отображается при любом display и не зависит от пользовательского цвета. - Hidden/ghost не получает live-чисел, морфинга, подложки или активности. - Static `houseplan-space-card` остаётся статической и не рисует live effects. - Пользовательские цвет и размер применяются к обычной активности, но не к красной тревоге. ## Реализация - Чистая классификация сущностей, агрегация и распознавание переходов: `src/device-visual.ts`. - Выбор эффективных источников маркера, runtime-окно 3,3 с, миграция legacy display и рендер: `src/houseplan-card.ts`. - Общий слой колец и reduced-motion варианты: `src/styles.ts`. - UI оставляет три режима; backend сохраняет `ripple` только как входное legacy-значение до завершения миграционного периода. - Unit-спецификация классификатора подготовлена в `test/device-visual.test.mjs`; браузерные smokes переведены на новые классы.