Files
houseplan-card/docs/reviews/CODE-REVIEW-211-r1.md
T
2026-08-19 21:56:08 +00:00

202 lines
16 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.
# CODE-REVIEW-211-r1 — визуальное соответствие маркеров дизайн-пакету #179
- **Issue:** https://github.com/Matysh/houseplan-card/issues/211
- **ТЗ:** `docs/specs/211-device-icons-visual-parity.md` (принято `SPEC-REVIEW-211-r2`, зелёный)
- **Диапазон реализации:** `6feb018..270cf63`
- `4e82976` fix: match device icons to designer package · `Issue: #211` · `User-Visible: yes`
- `270cf63` test: strengthen unavailable hover mutant · `Issue: #211` · `User-Visible: no`
- **Цикл:** r1/4
## Скоуп проверки
Диапазон реализации ограничен двумя коммитами: `src/styles.ts` (geometry/theme/state
tokens), новый независимый reference-fixture (`demo/srv/reference/device-icons/**`),
расширенный `demo/smoke_device_icon_design.mjs`, новый
`demo/capture_device_icon_reference.mjs`, точечная правка мутационного теста
`scripts/mutation-gate.mjs`, три копии бандла, оба changelog, `docs/TESTING.md` и
шесть скриншотов `docs/images/*`. Ни `device-visual.ts`, ни `device-presentation.ts`,
ни `device-pulse.ts`, ни `device-face.ts` не тронуты — совпадает с §10 ТЗ
(«не должны меняться без доказанного отсутствующего renderer fact»). Конфиг,
i18n, `custom_components/**/*.py` не задеты.
## Как проверялось
Проверка велась не по описанию хендоффа, а самостоятельным прогоном на чистом
дереве и собственным чтением диффа/скриншотов.
### Гейты — прогнаны
```
npx tsc --noEmit → чисто, без ошибок
npm test → 939 pass, 0 fail, 0 skipped
npm run build → OK
cmp dist vs custom_components/.../houseplan-card.js → идентичны
cmp dist vs demo/srv/assets/houseplan-card.js → идентичны
node scripts/check-docs.mjs → "7 files, 10 external links" — OK
node demo/smoke_device_icon_design.mjs → все 29 фактов true, OK
node demo/smoke_device_preview_parity.mjs → все 15 фактов true, OK
node demo/smoke_static_icon.mjs → все 12 фактов true, OK
node demo/smoke_disabled_device.mjs → все 7 фактов true, OK
node scripts/mutation-gate.mjs --id=device-unavailable-hover-restored
→ чистый прогон зелёный; мутант красный, «поймано 1 из 1»
node demo/capture_device_icon_reference.mjs → сгенерирован
artifacts/device-icon-reference/device-icons-reference-runtime.{html,png}
node demo/golden/run.mjs --mode=capture --scenario=device-icon-state-table-light
node demo/golden/run.mjs --mode=capture --scenario=device-icon-state-table-dark
→ оба "different" (ожидаемо, старый baseline зафиксировал баг)
```
Независимая проверка «тест умеет падать» (дисциплина применена к самому
дорогому и самому content-heavy тесту, `smoke_device_icon_design.mjs`): в
отдельном `git clone` собран бандл со **старым** `src/styles.ts` (коммит
`6feb018`, до фикса) и **новым** смоком/reference-фикстурой (`270cf63`).
Результат: **13 фактов красные** — `geometryMatchesAt32_56_96`,
`referenceStateMatrixMatches`, `referenceHoverMatches`,
`virtualUsesThemeDefaultWithDashedShell`, `iconCoreIsCircle`,
`valueBadgeIsPill`, `activeUsesPackageAmber`,
`lightActiveUsesWhiteGlyphAndAmberShell`, `darkDefaultShellUsesPackageStroke`,
`darkHoverChangesCoreOnly`, `selectionDecoratesCoreNotShell`,
`focusDecoratesCoreNotShell`, `darkLockUsesWhiteGlyphAndDarkShell`. Это больше,
чем девять фактов, названных в хендоффе (хендофф перечислял по смыслу, не по
имени предиката) — расхождение не в пользу автора, а в сторону более сильного
доказательства: тест ловит регрессию с запасом. AC9 подтверждён независимо, не
на слово.
### Визуальная проверка «глазами» (обязательна по §11 ТЗ)
Просмотрен `artifacts/device-icon-reference/device-icons-reference-runtime.png`
целиком (28 строк: Default/Hover/Active/Lock/Unlock/Selected/Focus/Alert/
Virtual/Unavailable/Text/Double Right для Light и Dark, плюс Default на
32/56/96 px). Круглая геометрия, размер MDI-глифа, value-pill радиус, theme
stroke и цвета состояний в runtime визуально совпадают с колонкой Reference
SVG построчно, включая Lock/Unlock (проверены отдельным 4×-кропом — форма и
пропорция глифа замка совпадают с эталоном в обеих темах).
Также открыт и прочитан `artifacts/golden/actual/device-icon-state-table-{light,dark}.png`
целиком. Комбинация `alarm + sel` с `focusDevice` на одном маркере
(«golden-climate») отрендерена так: core и shell — красные (alert выигрывает
покраску, как требует §7.3), а кольцо декорации — синее (focus), не янтарное
(selected). Это прямое следствие принятого в #179 приоритета
`Alert > Focus > Selected > Hover > semantic > Default`: кольцо — теперь
отдельный CSS-слой (`--device-ring-color`/`--device-ring-width` на
`.device-core`), и правило `:focus-visible` в каскаде идёт позже `.sel`, то
есть более приоритетное состояние перекрывает кольцо менее приоritetного —
ровно то поведение, которого требует таблица приоритета. До этого коммита
кольцо было частью `box-shadow` на `.device-shell` и `.dev.alarm` (более
специфичное правило ниже по каскаду) стирало декорацию selection/focus
полностью — то есть alert раньше «съедал» кольцо целиком, а не выигрывал
только цвет core/shell. Новое поведение (alert красит core/shell, но не
гасит более высокоприоритетный focus-ring) точнее реализует буквальное
требование §7.1 «Selected/Focus ring — отдельный круглый слой; не заменяет
внешний shell» и не описано ни одним найденным мной артефактом как
регрессия. Технически это расширение видимой информации (можно увидеть, что
элемент одновременно и alert, и focused), не потеря её.
Проверены также обновлённые документационные скриншоты
(`docs/images/01-view-desktop.png` и другие) — маркеры на них уже круглые, а
не скруглённо-квадратные; `sourceFingerprint` в `screenshots.json` пересчитан
и совпадает с проверкой `check-docs.mjs`.
### Коммиты и трейлеры
Оба коммита несут `Issue: #211`. `4e82976` (`User-Visible: yes`) в одном
коммите правит оба changelog (`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`) —
условие AGENTS.md выполнено. `270cf63` (`User-Visible: no`) правит только
`scripts/mutation-gate.mjs` — чистый class B, трейлер соответствует.
## Что проверено и корректно
- **AC1 (геометрия).** Круглый core (`border-radius: 50%` вместо `28%`),
shell/core `1.26875` сохранён через `box-sizing: border-box` и
компенсирующий `--device-shell-pad` независимо от переменной ширины
stroke — геометрия не плывёт между состояниями с разной толщиной обводки.
Value-pill радиус — `height/2` (`0.39375`), а не `0.18`. `geometryMatchesAt32_56_96`
подтверждает допуск на 32/56/96 px, включая painted bbox настоящего
`mdi:lightbulb-spot` (реальный SVG-путь, не bounding box `ha-icon`).
- **AC2 (theme parity).** Light/Dark default core/glyph/stroke/shadow и
запрет `backdrop-filter` подтверждены смоком и визуальной таблицей;
`darkDefaultShellUsesPackageStroke` сверяет буквальный `rgba(37,37,37,0.75)`.
- **AC3 (state parity).** Default/Hover/Active/Lock/Unlock/Alert сверены
байт-в-байт с прямым SVG (цвет **и** ширина stroke) в обеих темах;
Selected/Focus/Virtual/Unavailable подтверждены отдельными вычисляемыми
фактами и визуально. Тёмный Unlock корректно использует утверждённый
владельцем янтарный override, а не архивный зелёный.
- **AC4 (combinations).** Golden-сценарий `device-icon-state-table-*`
содержит маркер с `alarm+sel+focus` одновременно; результат просмотрен и
соответствует принятому в #179 приоритету слоёв (см. выше).
- **AC5 (layouts).** Text/Double/third section/LQI не сломаны
(`doubleUsesOneShell`, `textIsComplete`, `lqiBandsProjected`).
- **AC6 (surface parity).** `smoke_device_preview_parity` и `smoke_static_icon`
зелёные — план, preview и static card используют общий `renderDeviceFace()`
без расхождений.
- **AC7 (interaction regression).** 44×44 hit area, unavailable
click/tap без hover, keyboard/tab order, secure-путь — все факты зелёные,
`smoke_disabled_device` не задет.
- **AC8 (data/semantics).** Диапазон реализации не касается
`device-visual.ts`/`device-presentation.ts`/`device-pulse.ts`, i18n или
`custom_components/**`; unit-suite (939) зелёный без исключений.
- **AC9 (failing-before-fix).** Независимо подтверждено (13 фактов красные на
старом CSS, все зелёные после фикса) — см. раздел «Как проверялось».
- **AC10 (release artifacts).** Оба changelog, `docs/TESTING.md`, обновлённые
скриншоты — всё в user-visible коммите; golden не принят вручную (верно —
ждёт полного reviewed Linux CI artefact).
## Находки
Нет находок уровня High или Medium. Два Low, оба не блокируют и не требуют
правки в этом цикле:
- **L1.** `virtualUsesThemeDefaultWithDashedShell` в смоке проверяет только
состояние `virtual` в покое; «virtual hover идентичен ordinary hover»
(§7.3 ТЗ) не покрыт отдельным вычисляемым фактом — только логическим
чтением каскада (правило `.dev:not(.unavail):hover` не имеет исключения
для `.virtual` и не переопределяется ничем более специфичным). Риск
минимален и ограничен одним недостающим предикатом в уже большом смоке.
Снимаю без правки: поведение не изменилось этим диапазоном (правила
`.virtual`/`.dashed` не тронуты диффом), это пробел покрытия, а не дефект
поведения.
- **L2.** Комбинации `Alert+Focus+Selected` не сверены с одноимённым SVG
архива буквально (§7.3 формально требует «сверяются с одноимёнными Light
SVG»), потому что такого экспортированного файла в переданном архиве нет —
собственный AC4 ТЗ признаёт это, требуя доказательство именно
«combination golden», которое выполнено и просмотрено. Снимаю без правки:
доказательство соответствует явно заявленному в ТЗ уровню, более сильного
источника истины не существует.
Отдельно фиксирую техническую неточность в хендоффе, не влияющую на вердикт:
он сообщает «938 pass, 1 skipped», а независимый прогон на этом же дереве
даёт «939 pass, 0 skipped» (совпадает с `npm run inventory`). Не поднимаю как
находку — расхождение не воспроизводится и не меняет результат ни одного
гейта.
## Чего не проверял и почему
- **Полный набор из 152 browser smokes** — не запускал; диапазон реализации
ограничен общим рендерером маркера и его собственным smoke/preview/static
покрытием, названным в AC и хендоффе. Остальные 148 не имеют отношения к
геометрии/theme/state маркера.
- **`npm run golden:verify` по полному набору сценариев** — не запускал;
прогнал `capture` только по двум сценариям, прямо относящимся к задаче
(`device-icon-state-table-{light,dark}`), и открыл получившиеся PNG.
Полный golden-прогон и оценка остальных ~90 сценариев на предмет побочных
визуальных отличий из-за глобального изменения CSS маркера — предрелizный
гейт (PROCESS.md §8), а не гейт код-ревью; риск того, что какой-то другой
golden-сценарий с устройствами теперь тоже "different", реален, но это
ожидаемое и штатно обрабатываемое следствие правки, а не повод возвращать
задачу на этом этапе.
- **`python -m pytest tests_backend -q`** — не запускал; диапазон не
затрагивает `custom_components/**/*.py`.
- **Performance-профили** — не запускал; правка не добавляет DOM-узлов,
layout read или подписок (`device-face.ts` не тронут), а AC не называет
performance-смок явно.
- **Полный Linux CI-прогон** — не запускал локально; это гейт CI при выходе
из код-ревью, не задача рецензента.
## Вердикт
Все десять AC выполнены и подтверждены либо автотестом, который доказанно
умеет падать, либо непосредственным визуальным просмотром, как того требует
§11 ТЗ. Регрессий в интеракции, данных, конфиге и семантике не найдено.
**Зелёный.**