24 KiB
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 Package1.1.1, экспорт 2026-08-19, SHA-25663670C73E25D1E59DDAF1BE236F3D7F2FAC827B9B5D6DD4B77125EA9BC012025
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:
.device-coreиспользуетborder-radius: 28%, тогда как core каждого нормативного Icon-состояния — круг80 × 80,rx=40.ha-iconполучает viewport0.62 × core, из-за чего общий reference glyph заметно крупнее SVG. Архив не задаёт универсальный viewport для всех MDI: измеренный painted bounding box общего glyph равен примерно0.417 × core, а Lock/Unlock —0.333…0.4375 × core.- value-section высотой
0.7875 × coreимеет радиус лишь0.18 × core, а нормативный badge — pill с радиусом, равным половине его высоты. - shell всегда имеет светлый stroke
#BCBCBC; Dark Default No Blur требует#252525с opacity0.75и сохраняет dark outer/inner shadows безbackdrop-filter. - Active, hover, lock/unlock, selected и focus меняют не те слои либо неверные цвета: например hover/selected перекрашивают внешний shell, а active не переносит semantic stroke на shell.
- текущий smoke проверяет отношение shell/core и часть цветов, но не проверяет круглую форму, размер glyph viewport, value pill, theme-specific stroke, слой selection/focus и полную матрицу состояний.
4. Нормативные источники и разрешение расхождений
Порядок приоритета:
- решения владельца, уже записанные в #179;
SPECIFICATION.md,DEVELOPER_HANDOFF.mdиACTIVE_ANIMATION_SPEC.mdархива #179;- соответствующий SVG архива для темы, состояния и layout;
docs/specs/179-device-icons-redesign.md;- текущая реализация — только для поведения, которое источники выше не меняют.
Решения владельца #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:
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. План тестирования
Цикл реализации
npm run typecheck
npm test
npm run build
Перед S7-code-review после fresh build и синхронизации трёх bundle-копий:
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. Принятые технические предположения
Эти решения пользователь не наблюдает как отдельный продуктовый контракт и могут быть изменены ревьюером:
- Репрезентативные reference SVG хранятся только в
demo/и не попадают в production bundle. - Exact CSS projection может использовать custom properties, псевдоэлементы либо явный decoration span, если один общий renderer остаётся источником истины.
- Expected facts теста транскрибируются из package 1.1.1 отдельно от production tokens; тест не импортирует реализацию, которую проверяет.
- Для визуального pixel diff допустим anti-aliasing threshold, но проверка width/height/radius/color/stroke остаётся точной и не маскируется threshold.
- Существующие ошибочные baselines не удаляются до появления полного нового reviewed Linux artefact; локальный actual используется для ревью, не для принятия эталона.
0.5 × core— стартовый технический размер viewport для common MDI, потому что он воспроизводит painted bbox около0.417 × coreу reference glyph. Это не нормативная константа архива: реализация и код-ревью вправе скорректировать viewport по прямому SVG/runtime сравнению, не меняя ТЗ.