Files
houseplan-card/docs/specs/179-device-icons-redesign.md
T
2026-08-19 22:06:25 +03:00

564 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Issue #179 — новый визуальный язык маркеров устройств
- **Статус:** ревизия 2 после жёлтого ревью r1; готово к повторному ревью ТЗ
- **Issue:** https://github.com/Matysh/houseplan-card/issues/179
- **Приоритет:** P1
- **Тип:** feature / polish / accessibility
- **Область:** frontend, общий renderer маркера, preview, статическая карточка,
документация и visual QA
- **Нормативный дизайн-пакет:** `House Plan Icons — Developer Package`,
версия 1.1.1, экспорт 2026-08-18
- **Архив:** https://github.com/user-attachments/files/31235094/House.plan.Icons.-.Developer.Package.2026-08-19.zip
- **SHA-256 архива:**
`63670C73E25D1E59DDAF1BE236F3D7F2FAC827B9B5D6DD4B77125EA9BC012025`
- **Figma frames:** Light `99:1290`, Dark `104:1539`
## 1. Пользовательский сценарий и персона
**Персона:** пользователь House Plan, который одновременно контролирует
устройства разных доменов, читает значения датчиков и оценивает состояние
связи на светлой либо тёмной теме Home Assistant.
**Сценарий:** пользователь открывает план и за один взгляд различает обычное,
активное, тревожное, заблокированное, разблокированное, виртуальное и
недоступное устройство; видит полное значение и Zigbee LQI; получает тот же
результат в полном плане, preview редактора и статической карточке пространства.
## 2. Проблема и результат
### До реализации
- plate маркера является одним заполненным rounded-square без общего внешнего
shell из дизайн-пакета;
- hover применяется и к unavailable-маркеру;
- selected обозначается amber-цветом и конфликтует с семантикой активности;
- value и legacy secondary value рисуются отдельными спутниками, длинные
значения обрезаются ellipsis;
- LQI использует непрерывный HSL-gradient, а не три читаемых диапазона;
- ordinary continuous pulse длится 2,4 с и по умолчанию разрастается до 3×;
- маркер не является клавиатурной целью и не активируется Enter/Space;
- light/dark, focus и semantic states не образуют единой дизайн-системы.
### После реализации
Все поверхности используют один runtime-renderer нового shell/core,
компоновок Text/Double, LQI и motion из пакета. Семантика устройств и действий
остаётся прежней; меняется её единое визуальное и доступное представление.
## 3. Нормативный источник и порядок разрешения расхождений
Единственный дизайн-источник этой задачи — архив #179. Нормативны его
`README.md`, `SPECIFICATION.md`, `ACTIVE_ANIMATION_SPEC.md`,
`DEVELOPER_HANDOFF.md`, `PACKAGE_ANNOTATION.txt`, `manifest.json` и SVG-примеры.
Порядок приоритета:
1. решения владельца в §4;
2. текстовые документы архива;
3. SVG-примеры архива;
4. текущая реализация House Plan — только для поведения, которое пакет не
переопределяет.
Статические SVG являются эталонами геометрии и состояний, а не готовыми
production-иконками: runtime продолжает использовать динамические MDI glyphs,
HA-значения и локализованный текст.
## 4. Принятые решения владельца
1. **Unavailable.** Визуальный hover и motion отсутствуют. Click/tap остаётся
доступен и выполняет текущее действие поверхности (More Info либо открытие
настроек).
2. **Пользовательские pulse-настройки.** Если `ripple_color` или `ripple_size`
явно сохранены, они сохраняют приоритет. При отсутствии поля используются
геометрия и цвет пакета.
3. **Zigbee LQI.** `0…40` — red, `41…179` — amber, `180+` — green.
4. **Клавиатура.** Интерактивный маркер получает focus; Enter и Space выполняют
то же действие, что click на этой поверхности.
5. Специальная кнопка либо новый action для клавиатуры не вводятся.
## 5. Цели
1. Реализовать shell/core, цвета, состояния, focus и motion пакета #179.
2. Сохранить единую проекцию на полном плане, в editor preview и static card.
3. Показывать полное динамическое значение без ellipsis в Text/Double layout.
4. Сделать маркер читаемым в light/dark и на цветном фоне плана.
5. Обеспечить минимум 44×44 CSS px для интерактивной цели и keyboard parity.
6. Сохранить существующую HA-семантику, пользовательские настройки, координаты
и безопасный pipeline действий.
## 6. Не входит в задачу
- новые правила определения `on`, `working`, `open`, alarm и activity;
- новые HA service calls, actions или обход secure confirmation;
- новые display modes и новые пользовательские настройки дизайна;
- смена MDI-иконок либо поставка собственного icon font;
- настройка порогов LQI пользователем;
- автоматическое устранение пересечений соседних маркеров;
- изменение комнатной LQI-заливки, Glow, света, комнатных fills или isometry;
- миграция сохранённых координат и marker config;
- перенос каждого example SVG из архива в production bundle.
## 7. Визуальная система
### 7.1. Геометрия
Маркер состоит из независимых слоёв:
1. прозрачная hit area минимум 44×44 CSS px;
2. внешний shell: прозрачный fill, stroke и внешние shadows;
3. внутренний core с MDI glyph либо динамическим значением;
4. semantic/focus/selection decoration;
5. pulse layer с `pointer-events: none`;
6. LQI и дополнительные секции, если включены.
Для Icon-эталона shell имеет диаметр `101.5/127` viewBox, core — `80/127`;
отношение core/shell равно `0.788` с допустимым расхождением не более 0,5 CSS
px на QA-размерах 32, 56 и 96 px. Координата сохранённого маркера остаётся
центром icon core, поэтому включение Text/Double и смена стороны не двигает
устройство на плане.
Light shell: stroke `#BCBCBC`; core по умолчанию white. У light-варианта
запрещены backdrop blur и inner shadows. Внешние shadows масштабируются от
референса 56 px: `0 1px 2px rgb(37 40 45 / 12%)` и
`0 4px 8px -1.07px rgb(37 40 45 / 18%)`.
Dark использует core `#252525`, stroke и внешние shadows неизменённой
dark-ревизии пакета, но production renderer нормативно следует поставленному
варианту `Dark/Icon Default No Blur.svg`: `backdrop-filter: blur(20px)` на
каждом маркере запрещён. Это сохраняет дизайн-пакетный fallback и исключает
дорогой отдельный backdrop-composite layer для 20–200 устройств в View/kiosk.
Тема берётся из актуальных HA theme tokens; переключение темы не требует
remount и не меняет semantic state.
### 7.2. Цвета
Нормативные semantic colors:
| Смысл | Цвет |
|---|---|
| hover / focus / neutral activity | `#0C82F0` |
| active / working / unlocked | `#F0A00C` |
| alert / low LQI | `#F0410C` |
| unavailable core | `#B5BAC1` |
| locked glyph | black |
| presence activity / high LQI | `#1DC21D` |
Тема не подменяет semantic colors. Контраст glyph/core и читаемость текста
проверяются в light/dark и на светлом, тёмном и насыщенном фоне плана.
### 7.3. Состояния и приоритет
Нормативный визуальный приоритет:
```text
Alert > Focus > Selected > Hover > semantic state > Default
```
- **Default:** theme-default shell/core и обычный glyph.
- **Hover:** blue decoration пакета; не меняет semantic state и не
перезапускает pulse.
- **Focus:** blue keyboard-focus decoration, видимая без hover.
- **Selected:** отдельная selection decoration пакета, не amber semantic fill.
- **Active/working:** amber.
- **Lock:** locked glyph black; **Unlock:** unlocked glyph amber.
- **Alert:** red и всегда выше остальных состояний.
- **Unavailable:** gray core/glyph по текущему правилу прозрачности; без
визуальной реакции на hover и без motion.
- **Virtual:** пунктирный внешний shell. У обычного virtual default/hover
меняется только цвет оформления. Реальная HA unavailable и active pulse для
HA-less virtual device не синтезируются.
Текущая semantic-модель `neutral/open/working/alarm`, activity resolver и
domain-specific HA rules сохраняются. Для lock presentation дополнительно
различает locked/unlocked, не меняя secure action. Generic physical `open`
для cover/contact/valve не переименовывается в lock state и продолжает
существующую отдельную semantic-проекцию.
`display: static_icon` получает новую theme-default shell/core геометрию, но
остаётся нереактивным к HA state, values и pulse. На интерактивной поверхности
он сохраняет разрешённые hover/focus/click.
## 8. Text, Double и дополнительные секции
### 8.1. Text
В `display: value` динамический текст находится внутри общего shell. Полная
строка всегда доступна визуально: CSS ellipsis, clipping и скрытие хвоста
запрещены. Сначала шрифт равномерно уменьшается до `0.25 × marker size`
(8 CSS px при размере 32); если этого недостаточно, text section расширяет
shell. Измерение выполняется детерминированно и кэшируемо, без layout-loop и
ResizeObserver на каждый маркер.
### 8.2. Double
Иконка и value badge входят в один общий shell. Поддерживаются сохранённые
позиции `right`, `bottom`, `left`, `top`; icon core остаётся anchor. Высота
внутренней value-секции равна `0.7875 × icon core`, как в референсе.
Если у нетронутой legacy-конфигурации одновременно разрешены два значения,
второе становится третьей секцией того же shell, а не отдельным спутником.
Новая пользовательская модель второго бейджа не вводится.
Длинное значение проходит тот же auto-fit, что Text: полная строка без
ellipsis, а shell при необходимости расширяется. Динамический текст остаётся
текстом DOM, а не SVG path; шрифт — HA/system Roboto с weight 600 и безопасным
system fallback.
## 9. Zigbee LQI
Числовое LQI располагается под shell по геометрии пакета и окрашивается
категориально:
- `<= 40` — `#F0410C`;
- `41…179` — `#F0A00C`;
- `>= 180` — `#1DC21D`.
Границы применяются к текущему resolved average LQI без изменения источника,
агрегации, форматирования и флагов `show_signal`. Значение `0` валидно. Для
missing/unavailable LQI сохраняется текущая политика отсутствия подписи.
Категориальная функция применяется только к marker LQI. Непрерывный
HSL-gradient комнатной LQI-заливки остаётся неизменным.
В `Light/Zigbee LQI Low.svg` архива low ошибочно окрашен amber. Текстовая
спецификация архива и решение владельца имеют приоритет: production low — red.
## 10. Motion
### 10.1. Continuous
- одно кольцо;
- duration `3.6s`, infinite;
- easing `cubic-bezier(.45,.05,.55,.95)`;
- scale `1 → 1.5`, opacity `.55 → 0`;
- цвет соответствует resolved ordinary activity: `presence` — green;
running/working/open/unlocked — amber; neutral transition — blue. Явный
пользовательский либо валидный live-light color сохраняет приоритет по
§10.4.
### 10.2. Short
- три кольца;
- каждое длится `1.1s`;
- delays `0`, `1.1s`, `2.2s`;
- easing `cubic-bezier(.22,.61,.36,1)`;
- общий цикл `3.3s`;
- новое событие перезапускает цикл через существующий generation/runtime.
### 10.3. Alert
- red независимо от custom pulse color;
- wave stroke 3 reference units;
- scale до `1.5`;
- два wave-starts с интервалом `1.2s`, цикл `2.4s`;
- easing `cubic-bezier(.22,.61,.36,1)`;
- alert имеет приоритет над ordinary short/continuous.
### 10.4. Пользовательские настройки и fallback
Явно сохранённые `ripple_color` и `ripple_size` сохраняются без миграции.
Отсутствующий `ripple_size` использует package scale `1.5`, отсутствующий
`ripple_color` разрешается в порядке:
1. валидный live RGB контролируемого light, если он уже является текущим
источником pulse color;
2. semantic green для presence;
3. semantic amber для running/working/open/unlocked;
4. package blue для neutral transition/activity.
UI-default размера меняется на `1.5`, но Open → Save не материализует поле и
не переписывает существующее явно сохранённое значение. Backend допустимый
диапазон и import/export round-trip не меняются.
### 10.5. Reduced motion
При `prefers-reduced-motion: reduce` animated rings отсутствуют. Ordinary
activity обозначается статической semantic point из существующего единого
pulse renderer; alert остаётся красным статическим состоянием. Marker не
создаёт собственную media-query subscription: используется одна подписка на
surface/card host.
## 11. Интерактивность и доступность
- Интерактивные маркеры в View/kiosk и Device editor получают `tabindex="0"`
и button semantics.
- Enter/Space вызывают тот же существующий click handler текущей поверхности:
action/More Info в View и открытие настроек в Device editor.
- Space предотвращает прокрутку только когда активирует marker.
- Pointer/touch поведение, long-press/context action и drag в редакторе не
меняются; keyboard path не создаёт прямой HA service call.
- Background/Plan, read-only static card и неинтерактивный preview не входят в
tab order и не получают ложный button role.
- Minimum hit area 44×44 CSS px центрирована по icon core и не влияет на
visual bounds, координату, pulse либо collision geometry.
- `aria-label` включает локализованное имя, semantic state, activity/alert и,
если LQI показан, число и понятный диапазон «низкий/средний/высокий».
- Цвет и motion не являются единственным носителем alert, availability, lock
или LQI band.
- Focus после закрытия открытого marker dialog возвращается на исходный
marker по существующему dialog contract.
Secure lock/cover/valve actions продолжают проходить через текущий action
resolver и confirmation policy. Новый DOM-shell и keyboard handler не имеют
отдельного пути обхода подтверждения.
**Touch editor: best effort / intentionally degraded.** Device editor
сохраняет текущие touch drag/tap/pointer-cancel guarantees и увеличенную hit
area, но его полный editor workflow остаётся desktop-first. View/kiosk touch —
блокирующий и полностью поддерживаемый контракт.
## 12. Совместимость и данные
- config schema version не меняется;
- `display`, `value_badge`, `value_badge_position`, `ripple_color`,
`ripple_size`, `show_signal` и legacy fields сохраняют формат и round-trip;
- существующие координаты относятся к icon core и не мигрируют;
- явно сохранённые pulse color/size воспроизводятся как раньше, кроме нового
shell и motion geometry;
- отсутствующие поля не материализуются при Open → Save;
- старые версии карточки читают тот же config; downgrade меняет только
внешний вид;
- runtime не зависит от наличия в bundle статических SVG-примеров;
- ru/en i18n parity обязательна.
## 13. Архитектурный контракт
```text
HA/device registry + marker config
→ existing semantic/activity resolvers
→ resolveDevicePresentation
→ resolveDevicePulse
→ renderDeviceFace (shell/core/text/LQI/motion)
→ full plan | editor preview | static space card
```
Один resolved presentation и один `renderDeviceFace()` остаются источником
истины всех поверхностей. Surface передаёт только interaction/theme context и
не вычисляет semantic state повторно.
Рекомендуемые зоны изменений:
- `src/device-visual.ts` — lock presentation facet без смены action semantics;
- `src/device-presentation.ts` — theme/semantic projection, marker-only LQI
band, defaults pulse;
- `src/device-pulse.ts` — package timings/scale/reduced motion;
- `src/device-face.ts` — единый shell и Text/Double/third section DOM;
- `src/styles.ts` — tokens, geometry, states, focus, auto-fit и motion;
- `src/houseplan-card.ts` и общие surface adapters — role/tabindex/keydown;
- i18n, unit, smoke, golden fixtures и документация.
Нельзя вводить surface-specific копию state mapping, formatter либо motion
runtime. Text auto-fit не должен читать layout на каждом render/animation
frame. Pulse использует transform/opacity и не меняет layout.
## 14. Эдж-кейсы
- light/dark hot switch во время continuous/short pulse;
- colored room background и минимальный marker size 32;
- marker sizes 32/56/96, user scale 0.5…3 и explicit ripple size 1…20;
- alarm во время hover/focus/selected и снятие alarm;
- unavailable во время active pulse, затем reconnect baseline без false short;
- locked, unlocked, locking и unlocking с secure action;
- virtual default/hover без HA entity и linked virtual with resolved controller;
- `static_icon` при реальном alarm/working/unavailable;
- `0`, `false`, very long localized string, unit symbol и third legacy value;
- Double на четырёх сторонах вместе с LQI;
- LQI exactly `0`, `40`, `41`, `179`, `180`, missing;
- rapid short retrigger, short поверх continuous, reduced-motion hot switch;
- keyboard activation после drag, click confirmation и dialog focus return;
- несколько cards одного плана с независимыми runtime и theme context;
- hidden/HA-disabled/orphaned marker и read-only static card.
## 15. Acceptance criteria
- [ ] **AC1 — shell/theme.** Icon, Text и Double используют package shell/core
geometry и Light/Dark tokens; light не содержит blur/inner shadow.
**Доказательство:** unit token assertions + reviewed golden 32/56/96.
- [ ] **AC2 — states.** Default, Hover, Focus, Selected, Active, Lock, Unlock,
Alert, Virtual и Unavailable соблюдают §7.3 и нормативный приоритет.
**Доказательство:** presentation unit matrix + light/dark state-table
golden.
- [ ] **AC3 — unavailable.** Unavailable не получает visual hover/motion, но
click/tap по-прежнему открывает текущее действие поверхности.
**Доказательство:** unit + browser smoke.
- [ ] **AC4 — secure locks.** Locked black, unlocked amber, locking/unlocking
continuous; pointer и keyboard проходят один action/confirmation path.
**Доказательство:** unit + secure-action smoke.
- [ ] **AC5 — virtual/static.** Virtual имеет dashed shell; HA-less virtual не
синтезирует unavailable/activity. `static_icon` использует theme-default
shell и не реагирует на live state/value/pulse.
**Доказательство:** unit + existing static-icon smoke.
- [ ] **AC6 — Text/Double.** Полные строки видимы без ellipsis; четыре стороны,
anchor, auto-fit и третья legacy section соответствуют §8.
**Доказательство:** unit layout facts + browser smoke + long-value golden.
- [ ] **AC7 — LQI.** Marker LQI использует red/amber/green thresholds на
границах, а room fill сохраняет continuous gradient.
**Доказательство:** boundary unit tests + room-fill regression test.
- [ ] **AC8 — motion.** Continuous/short/alert timings, ring counts, retrigger,
easing, reason→color mapping, scale и colors соответствуют §10; explicit
color/size сохраняются.
**Доказательство:** pure resolver tests + DOM/style smoke.
- [ ] **AC9 — reduced motion.** Rings отсутствуют, ordinary point и static red
alert остаются; одна subscription на host.
**Доказательство:** unit + reduced-motion browser smoke.
- [ ] **AC10 — keyboard/a11y.** Только интерактивные surfaces получают focus;
Enter/Space эквивалентны click, hit area >=44×44, aria-label содержит
state/LQI, focus возвращается после dialog.
**Доказательство:** keyboard/touch/browser smoke.
- [ ] **AC11 — parity.** Full plan, editor preview и static card используют
один presentation/face renderer и дают одинаковую read-only projection.
**Доказательство:** existing preview/static parity smokes.
- [ ] **AC12 — совместимость.** Schema и saved config не мигрируют; Open → Save
не материализует defaults; explicit pulse settings round-trip.
**Доказательство:** frontend/backend config tests.
- [ ] **AC13 — производительность.** Нет per-frame JS, per-marker media query,
ResizeObserver/layout loop или SVG example assets в production bundle;
transform/opacity motion не вызывает layout; ни Light, ни Dark marker не
создаёт per-marker `backdrop-filter`/backdrop-composite layer.
**Доказательство:** code assertion + `performance_smoke.mjs` перед beta.
- [ ] **AC14 — release artifacts.** ru/en copy, guides, architecture/testing
docs, changelogs, targeted smokes и golden review актуальны.
## 16. Тестирование
### 16.1. Цикл реализации
После каждого логического блока:
```bash
npm run typecheck
npm test
npm run build
```
Unit coverage:
- полная state/priority/theme/display matrix;
- lock/unlock и generic open не смешиваются;
- LQI boundaries и неизменность room gradient;
- pulse kinds, timings, fallback/explicit settings и reduced motion;
- Text/Double/third section facts, long values и anchor;
- keyboard surface policy и accessible labels;
- config/default/round-trip compatibility.
### 16.2. Targeted browser smoke перед S7
Добавить `demo/smoke_device_icon_design.mjs` и прогнать его вместе с:
- `demo/smoke_device_preview_parity.mjs`;
- `demo/smoke_static_icon.mjs`;
- `demo/smoke_state_value.mjs`;
- `demo/smoke_disabled_device.mjs`;
- релевантным secure-action/keyboard smoke из существующего набора.
Новый smoke проверяет light/dark, все состояния, Text/Double, четыре позиции,
long value, LQI boundaries, unavailable click, 44×44 hit area, keyboard и
reduced motion.
### 16.3. Mutation gates
Зарегистрировать targeted mutants, которые обязаны быть пойманы тестами:
1. generic hover снова применяется к `.unavail`;
2. LQI `40/180` уходит в соседний диапазон либо меняет room gradient;
3. Text/Double снова получает ellipsis;
4. keyboard activation вызывает отдельный action path.
### 16.4. Golden и pre-beta
Golden matrix: desktop/mobile, light/dark, neutral/hover/focus/selected,
active/lock/unlock/alert/virtual/unavailable, Text, четыре Double, third section,
LQI low/mid/high, reduced motion, sizes 32/56/96 и цветной фон.
Комбинации `Selected + Hover/Working/Green/Alert` и `Focus + Alert` проверяются
в обеих темах: Light сверяется с прямыми SVG-эталонами, Dark — с тем же
нормативным приоритетом слоёв, поскольку отдельных Dark combo-assets в архиве
нет.
Golden принимается только по Linux CI artifact согласно HP-QA-01. Перед beta
запускаются полный golden, smoke и performance наборы по release runbook;
Windows не является каноном полного HA harness из-за `fcntl`.
## 17. Риски и откат
### Риски
- рост shell у длинных значений может чаще пересекаться с соседями;
- old explicit ripple size 3+ визуально заметно больше package default;
- Dark No-Blur отличается от основного Dark example отсутствием
backdrop-filter, но сохраняет core/stroke/shadow geometry пакета;
- новый focus/tab order увеличивает число клавиатурных остановок на больших
планах;
- ошибочная привязка общего `lqiColor()` изменит комнатную заливку;
- новый DOM может изменить snapshot/golden fingerprint всех поверхностей.
### Митигации
- центр icon core и saved coordinates неизменны;
- explicit settings не переписываются скрыто;
- marker-only LQI helper отделён и покрыт regression test;
- интерактивность добавляется только существующим actionable surfaces;
- один renderer и одна token table исключают drift;
- No-Blur Dark не создаёт 20–200 дорогих backdrop-composite layers;
- targeted golden содержит контрастные backgrounds и dense layout.
### Откат
Откат выполняется одним revert implementation commit. Миграции данных нет,
поэтому rollback возвращает старый renderer без преобразования config. ТЗ и
архив остаются диагностическим контрактом.
## 18. Release-артефакты
В том же user-visible implementation commit обновить:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` со ссылкой на #179;
- `docs/USER-GUIDE.md` и `docs/USER-GUIDE.ru.md`: состояния, Text/Double,
LQI, keyboard и static mode;
- `docs/ARCHITECTURE.md`: общий presentation/face pipeline и theme context;
- `docs/TESTING.md`: новый smoke, mutation и golden matrix;
- ru/en i18n для доступных state/LQI descriptions и изменённого static hint;
- screenshot/golden fingerprints и review manifest по HP-QA-01.
Release note: единая новая визуальная система устройств на светлой и тёмной
теме, полные значения, читаемый LQI и клавиатурное управление. Поставка сначала
в beta; stable — только после release gates.
## 19. План реализации
1. Зафиксировать package tokens и pure projection facts unit-тестами.
2. Расширить presentation lock/LQI/theme facts без изменения semantic/action
resolvers.
3. Перевести `renderDeviceFace()` на shell/core и общие Text/Double sections.
4. Реализовать package states, focus, unavailable и light/dark CSS.
5. Обновить pulse timings/defaults/reduced motion.
6. Подключить keyboard/hit area/accessibility к интерактивным surfaces.
7. Обновить preview/static parity, settings default и i18n/docs.
8. Добавить unit, mutation, targeted smoke и golden fixtures.
9. Прогнать typecheck, unit, build и named smokes; отправить в S7.
10. Перед beta выполнить полный golden/smoke/performance gate в Linux CI.
## 20. Принятые технические предположения
1. Package SVGs — visual reference; runtime DOM/SVG строится из тех же ratios и
tokens, потому что MDI glyph, HA text и theme динамические.
2. `Light/Zigbee LQI Low.svg` содержит ошибочный amber; red из textual spec и
решения владельца нормативен.
3. Package не задаёт minimum font size для бесконечно длинного значения:
используется floor `0.25 × marker size`, затем расширяется shell.
4. Generic physical `open` сохраняет текущую отдельную semantic-проекцию;
специальные black/amber Lock/Unlock применяются только к lock domain.
5. `static_icon` остаётся нереактивным, но default plate становится
theme-aware, потому что package задаёт разные Light/Dark defaults.
6. Отсутствующий pulse color сохраняет существующий validated live-light RGB
раньше semantic fallback; это единственный package-compatible способ не
потерять текущий пользовательский light-color effect.
7. Marker LQI меняет только цвет подписи; room LQI fill остаётся непрерывным.
8. Preview и static card показывают новый дизайн, но не получают ложную
интерактивность либо tab stop.
9. Green continuous variant нормативно означает `presence`; amber означает
running/working/open/unlocked, blue — neutral transition. Это явный mapping
трёх package assets, который архив сам не задаёт.
10. Production Dark использует поставленный `Icon Default No Blur` fallback:
визуально дорогой `backdrop-filter: blur(20px)` на каждом marker запрещён
ради блокирующих View/kiosk поверхностей с 20–200 устройствами.