docs: code review document for #211

Issue: #211
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-19 21:56:08 +00:00
parent 270cf634e6
commit 303f710e03
+201
View File
@@ -0,0 +1,201 @@
# 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 ТЗ. Регрессий в интеракции, данных, конфиге и семантике не найдено.
**Зелёный.**