mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
362 lines
24 KiB
Markdown
362 lines
24 KiB
Markdown
# 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 сравнению, не меняя ТЗ.
|