Files
houseplan-card/docs/specs/211-device-icons-visual-parity.md
T
2026-08-20 00:21:49 +03:00

362 lines
24 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 #211 — визуальное соответствие маркеров дизайн-пакету #179
- **Issue:** https://github.com/Matysh/houseplan-card/issues/211
- **Исходная задача:** https://github.com/Matysh/houseplan-card/issues/179
- **Первая затронутая версия:** `v1.65.0-beta.6`
- **Приоритет:** P1
- **Тип:** bug / polish
- **Область:** frontend, общий renderer маркера, preview, статическая карточка,
browser smoke и visual QA
- **Нормативный пакет:** `House Plan Icons — Developer Package` 1.1.1,
экспорт 2026-08-19, SHA-256
`63670C73E25D1E59DDAF1BE236F3D7F2FAC827B9B5D6DD4B77125EA9BC012025`
## 1. Сценарий и персона
**Персона:** обычный пользователь или владелец настенной панели, который в
View/киоске за один взгляд читает тип и состояние устройства; администратор
встречает тот же маркер в редакторе устройств и его preview.
**Поверхность и момент:** полный план, киоск, preview настроек устройства и
`houseplan-space-card` сразу после загрузки либо смены темы/состояния.
## 2. Что человек увидит до и после
**До:** круглый дизайн из #179 выглядит как большой скруглённый квадрат с
переразмеренным глифом, а внешний контур и несколько состояний не совпадают с
эталоном, особенно в тёмной теме.
**После:** на всех поверхностях маркер сохраняет круглую геометрию, правильный
масштаб глифа и точные theme/state-слои нормативных SVG при размерах 32, 56 и
96 px.
## 3. Проблема и подтверждённая причина
Реализация #179 прошла проверки внутренней согласованности, но visual QA не
сравнил runtime рядом с исходными SVG. Ошибочный runtime затем стал новым
golden, поэтому golden оказался зелёным, не доказывая дизайн.
Подтверждённые расхождения `v1.65.0-beta.6`:
1. `.device-core` использует `border-radius: 28%`, тогда как core каждого
нормативного Icon-состояния — круг `80 × 80`, `rx=40`.
2. `ha-icon` получает viewport `0.62 × core`, из-за чего общий reference glyph
заметно крупнее SVG. Архив не задаёт универсальный viewport для всех MDI:
измеренный painted bounding box общего glyph равен примерно `0.417 × core`,
а Lock/Unlock — `0.333…0.4375 × core`.
3. value-section высотой `0.7875 × core` имеет радиус лишь `0.18 × core`, а
нормативный badge — pill с радиусом, равным половине его высоты.
4. shell всегда имеет светлый stroke `#BCBCBC`; Dark Default No Blur требует
`#252525` с opacity `0.75` и сохраняет dark outer/inner shadows без
`backdrop-filter`.
5. Active, hover, lock/unlock, selected и focus меняют не те слои либо неверные
цвета: например hover/selected перекрашивают внешний shell, а active не
переносит semantic stroke на shell.
6. текущий smoke проверяет отношение shell/core и часть цветов, но не проверяет
круглую форму, размер glyph viewport, value pill, theme-specific stroke,
слой selection/focus и полную матрицу состояний.
## 4. Нормативные источники и разрешение расхождений
Порядок приоритета:
1. решения владельца, уже записанные в #179;
2. `SPECIFICATION.md`, `DEVELOPER_HANDOFF.md` и
`ACTIVE_ANIMATION_SPEC.md` архива #179;
3. соответствующий SVG архива для темы, состояния и layout;
4. `docs/specs/179-device-icons-redesign.md`;
5. текущая реализация — только для поведения, которое источники выше не меняют.
Решения владельца #179, обязательные и для исправления:
- Unlock янтарный в обеих темах; зелёный Dark/Unlock из старой dark-ревизии не
используется.
- Virtual hover такой же, как ordinary hover: меняется core, а единственное
постоянное отличие virtual — пунктирный внешний shell. HA-less virtual не
получает active, unavailable или pulse.
- Unavailable сохраняет прежнее правило и величину прозрачности House Plan,
не получает hover/motion, но сохраняет обычный click/tap.
- LQI, pulse-семантика, explicit `ripple_color`/`ripple_size`, действия,
подтверждения и порядок приоритетов остаются по принятому ТЗ #179.
- production Dark использует No Blur: `backdrop-filter` на маркере запрещён,
но остальные dark shadows не удаляются.
Нормативные SVG служат эталоном геометрии, цвета и слоёв. Production продолжает
использовать динамический MDI glyph и HA-текст; примерный glyph из SVG не
подменяет иконку устройства.
## 5. Скоуп
В задачу входят:
- точная shell/core/value geometry для Icon, Text и Double;
- theme-specific default stroke, opacity, outer shadows и допустимый Dark inner
shadow без blur;
- точная проекция Default, Hover, Active/working, Lock, Unlock, Selected,
Focus, Alert, Virtual и Unavailable с уже принятым приоритетом;
- одинаковый результат общего `renderDeviceFace()` в полном плане, киоске,
Device preview и `houseplan-space-card`;
- сохранение icon-core центра как saved anchor, 44×44 hit area, Text/Double
сторон и полного текста;
- независимый от production CSS visual-contract fixture и расширение targeted
smoke, чтобы исходный дефект падал до исправления;
- обновление затронутых screenshots/golden только по каноническому процессу.
## 6. Не-скоуп
- новые состояния, glyphs, display modes или настройки пользователя;
- изменение semantic/activity resolvers, HA entity selection, LQI-порогов,
actions, confirmation/security, Glow, vacuum lifecycle или isometry;
- изменение сохранённого config, координат либо миграция данных;
- устранение пересечений соседних маркеров;
- публикация либо удаление `v1.65.0-beta.6`;
- принятие новых golden без полного Linux CI artefact и ручного сравнения.
## 7. Визуальный контракт
### 7.1. Геометрия
Все коэффициенты измеряются относительно diameter icon core.
| Элемент | Контракт |
|---|---|
| Icon core | круг; width = height = `1.0`; radius = `0.5` |
| Icon shell | круг; diameter `101.5 / 80 = 1.26875`; core и shell концентричны |
| MDI glyph | painted bounding box того же reference glyph совпадает с соответствующим SVG; единого ratio для разных MDI нет |
| Text core | pill высотой `1.0`; radius = `0.5`; ширина зависит от полного текста |
| Double value core | pill высотой `0.7875`; radius = `0.39375`; ширина по полному значению и padding SVG |
| Selected/Focus ring | отдельный круглый слой; не заменяет внешний shell и не превращает core в rounded-square |
| Hit area | минимум 44×44 CSS px, центрирована по icon core и не меняет визуальные bounds |
Допуск для измеримых width/height/radius на 32/56/96 px — не более 0.5 CSS px.
Для glyph сравнивается painted path одного и того же MDI/reference glyph, а не
bounding box элемента `ha-icon`; anti-aliasing оценивается side-by-side.
Сохранённая координата остаётся центром icon core при любом layout и стороне
value badge.
### 7.2. Базовые theme-токены
| Тема | Core default | Glyph default | Shell default |
|---|---|---|---|
| Light | `#FFFFFF` | `#000000` / эквивалентный package black | `#BCBCBC`, `1.5` reference units |
| Dark | `#252525` | `#FFFFFF` | `#252525` opacity `0.75`, `1.5` reference units |
Light использует только package outer shadows. Dark No Blur сохраняет outer
shadows и белый inner highlight из `Icon Default No Blur.svg`, но не содержит
`backdrop-filter`, `foreignObject` или отдельный backdrop-composite layer.
Stroke/shadow масштабируются вместе с marker так, чтобы 32/56/96 сохраняли
одинаковую пропорцию и не расходились с прямым SVG-рендером более допустимой
визуальной погрешности.
### 7.3. Состояния и слои
| Состояние | Core | Glyph | Shell / decoration |
|---|---|---|---|
| Default Light | white | black | базовый Light shell |
| Default Dark | `#252525` | white | базовый Dark shell |
| Hover Light | blue `#0C82F0` | white | базовый Light shell не синеет |
| Hover Dark | blue `#0C82F0` | `#252525` | базовый Dark shell не синеет |
| Active Light | amber `#F0A00C` | white | amber semantic stroke как в `Light/Icon Active.svg` |
| Active Dark | amber `#F0A00C` | `#252525` | amber semantic stroke как в `Dark/Icon Active.svg` |
| Lock Light | black | white | black semantic stroke |
| Lock Dark | `#252525` | white | dark lock stroke |
| Unlock | amber | Light: white; Dark: `#252525` | amber semantic stroke; owner override для Dark |
| Selected | текущий semantic core не теряется | по semantic core | amber selection ring отдельным слоем; base/semantic shell остаётся видимым |
| Focus | текущий semantic core не теряется | neutral SVG может стать blue | blue focus ring отдельным слоем; не shell-shadow substitute |
| Alert | red `#F0410C` | white | red semantic stroke/alert; выше hover/selection/focus по принятому alert-контракту |
| Virtual | ordinary state core/glyph | ordinary | тот же shell, но dashed; hover меняет только core/glyph |
| Unavailable | по принятой серой проекции #179 | по принятой проекции | прежняя opacity House Plan; без hover и motion |
Комбинации `Selected + Hover/Working/Green/Alert` и `Focus + Alert` сверяются с
одноимёнными Light SVG; для Dark применяется тот же порядок слоёв с dark base
tokens. Generic physical `open` остаётся существующей отдельной оранжевой
семантикой и не превращается в Lock/Unlock.
### 7.4. Text, Double, LQI и motion
Исправление геометрии применяется к Text/Double без изменения уже принятого
контракта полного текста, auto-fit, четырёх сторон, third legacy section и LQI.
Value и его section остаются настоящим DOM-текстом Roboto/System 600.
LQI bands, pulse kinds/timings/colors, reduced motion и explicit ripple
настройки не меняются. Исправление не добавляет layout-read, per-marker
subscription, ResizeObserver или per-frame JS.
## 8. UX, touch и доступность
Это исправление внешнего вида, а не взаимодействия. View и киоск остаются
touch-first: click/tap, pan/pinch, long press, keyboard Enter/Space, secure
confirmation и dialog focus return должны пройти без регрессий.
**Touch editor: best effort / intentionally degraded.** Device editor остаётся
desktop-first. Static card и preview не получают интерактивность или tab-stop.
Форма/decoration не меняют pointer bounds. Unavailable остаётся кликабельным,
несмотря на отсутствие визуального hover.
## 9. Данные, совместимость и i18n
- schema version и wire/config model не меняются;
- `display`, `value_badge`, `value_badge_position`, `show_signal`,
`ripple_color`, `ripple_size`, coordinates и defaults round-trip без записи;
- новых i18n-ключей и пользовательских строк нет;
- downgrade меняет только внешний вид и не требует обратной миграции.
## 10. Архитектурный контракт и файлы
Остаётся один pipeline:
```text
existing semantic/activity resolvers
→ ResolvedDevicePresentation / ResolvedDevicePulse
→ renderDeviceFace
→ full plan | kiosk | Device preview | houseplan-space-card
```
Ожидаемые зоны изменений:
- `src/styles.ts` — geometry/theme/state tokens и отдельные decoration layers;
- `src/device-face.ts` — только если для selection/focus нужен явный общий слой;
- `test/device-face.test.mjs` и/или новый pure visual-contract test;
- `demo/smoke_device_icon_design.mjs` — computed geometry/theme/state matrix;
- `demo/golden/*` и demo-only reference assets — независимая side-by-side таблица;
- `docs/images/*`, `docs/TESTING.md`, оба changelog.
`src/device-visual.ts`, `src/device-presentation.ts` и `src/device-pulse.ts` не
должны меняться без доказанного отсутствующего renderer fact: эта задача не
переопределяет semantic state.
## 11. Независимая visual QA
Новый fixture не должен получать ожидаемые значения импортом из production
CSS/TypeScript. Он хранит отдельно измеренный контракт package 1.1.1 и прямые
reference SVG для репрезентативных состояний.
Таблица сравнения содержит два соседних столбца — **Reference SVG** и
**Runtime** — для Light/Dark и размеров 32/56/96. Минимальный набор строк:
Default, Hover, Active, Lock, Unlock, Selected, Focus, Alert, Virtual,
Unavailable, Text и Double Right. Дополнительно runtime state-table сохраняет
четыре Double sides, LQI bands и комбинации состояний из §7.3.
Code review обязан посмотреть side-by-side результат глазами и записать это в
вердикте; успешный `golden:verify` без такого сравнения AC не закрывает.
## 12. Acceptance criteria
- [ ] **AC1 — круглая геометрия.** Icon core круглый, painted bounding box
reference glyph совпадает с прямым SVG, shell/core ratio `1.26875`, value
section является pill; 32/56/96 укладываются в допуск §7.1.
**Доказательство:** computed-style browser smoke + side-by-side golden.
- [ ] **AC2 — theme parity.** Light/Dark Default используют точные core/glyph,
shell stroke/opacity и package shadows; ни один marker не создаёт
backdrop-filter.
**Доказательство:** smoke token/geometry assertions + reviewed golden.
- [ ] **AC3 — state parity.** Hover, Active, Lock, Unlock, Selected, Focus,
Alert, Virtual и Unavailable соответствуют §7.3 и прямым SVG; decoration
применяется к правильному слою.
**Доказательство:** light/dark computed-style matrix + side-by-side golden.
- [ ] **AC4 — combinations.** Alert, Focus, Selected, Hover и semantic state
сохраняют принятый приоритет и отдельные слои; generic open не становится
lock state.
**Доказательство:** unit/presentation regression + combination golden.
- [ ] **AC5 — layouts.** Icon, Text, Double на четырёх сторонах, third section
и LQI сохраняют anchor, полный текст и нормативную круглую/pill форму.
**Доказательство:** existing layout units + targeted smoke/golden.
- [ ] **AC6 — surface parity.** Полный план, киоск, Device preview и static card
используют общий face и одинаково проецируют read-only состояние.
**Доказательство:** preview/static parity smokes + code inspection.
- [ ] **AC7 — interaction regression.** 44×44 hit area, unavailable click/tap,
hover suppression, Enter/Space и secure action path не изменились.
**Доказательство:** targeted browser smokes.
- [ ] **AC8 — data and semantics.** Нет config/i18n/migration изменений; LQI,
pulse, activity, actions, Glow и vacuum semantics не изменены.
**Доказательство:** unit suite + diff inspection.
- [ ] **AC9 — failing-before-fix guard.** Новые geometry/state assertions
доказанно падают на `v1.65.0-beta.6` как минимум из-за `28%`,
переразмеренного reference glyph при `0.62`, value radius и Dark shell.
**Доказательство:** запись mutation/before-fix результата в handoff.
- [ ] **AC10 — release artifacts.** Оба changelog описывают исправление #211;
screenshots/visual matrix актуальны, а golden принимаются только по
полному reviewed Linux CI artefact.
**Доказательство:** docs/provenance checks и prerelease gate.
## 13. План тестирования
### Цикл реализации
```bash
npm run typecheck
npm test
npm run build
```
Перед `S7-code-review` после fresh build и синхронизации трёх bundle-копий:
```bash
node demo/smoke_device_icon_design.mjs
node demo/smoke_device_preview_parity.mjs
node demo/smoke_static_icon.mjs
node demo/smoke_disabled_device.mjs
```
Если secure keyboard path затронут diff'ом, дополнительно запускается его
существующий named smoke. `golden:capture`/`golden:verify` выполняются для
визуальной диагностики; обновлённые baselines не принимаются на Windows.
Перед следующей бетой обязательны полный Linux golden, browser smoke и
performance gate по release runbook. Полный HA harness остаётся каноничным в
Linux CI из-за `fcntl`.
## 14. Производительность, риски и откат
**Бюджет:** DOM и число marker layers не должны расти без необходимости;
рендер 200 устройств не получает layout read, backdrop layer, per-frame JS или
per-marker media subscription. Исправление CSS geometry само по себе не должно
ухудшать performance budget.
**Риски:** theme-specific shadows могут расходиться между WebView; MDI glyph
имеет собственные пустые поля; длинные values расширяют shell; неверный
specificity снова смешает semantic core и selection/focus.
**Митигации:** отдельные tokens/layers, computed assertions на 32/56/96,
реальные MDI glyphs в runtime column, контрастные backgrounds и прямые SVG в
reference column.
**Откат:** один revert implementation commit возвращает CSS/fixture; данных и
миграций нет. Возврат к ошибочным beta.6 golden не является допустимым способом
отката эталона.
## 15. Release-артефакты
В user-visible implementation commit обновить:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` со ссылкой на #211;
- `docs/TESTING.md` — independent reference/runtime matrix и named smoke;
- затронутые документационные screenshots после визуальной проверки.
Golden baseline меняется отдельным допустимым коммитом только через
`npm run golden:accept -- --reviewed` по полному Linux CI artefact с обязательными
`Release:` и `Baseline-Reviewed:` trailers. Поставка — только следующей beta по
прямой команде владельца.
## 16. Принятые технические предположения
Эти решения пользователь не наблюдает как отдельный продуктовый контракт и
могут быть изменены ревьюером:
1. Репрезентативные reference SVG хранятся только в `demo/` и не попадают в
production bundle.
2. Exact CSS projection может использовать custom properties, псевдоэлементы
либо явный decoration span, если один общий renderer остаётся источником
истины.
3. Expected facts теста транскрибируются из package 1.1.1 отдельно от
production tokens; тест не импортирует реализацию, которую проверяет.
4. Для визуального pixel diff допустим anti-aliasing threshold, но проверка
width/height/radius/color/stroke остаётся точной и не маскируется threshold.
5. Существующие ошибочные baselines не удаляются до появления полного нового
reviewed Linux artefact; локальный actual используется для ревью, не для
принятия эталона.
6. `0.5 × core` — стартовый технический размер viewport для common MDI, потому
что он воспроизводит painted bbox около `0.417 × core` у reference glyph.
Это не нормативная константа архива: реализация и код-ревью вправе
скорректировать viewport по прямому SVG/runtime сравнению, не меняя ТЗ.