# CODE-REVIEW-211-r2 — коррекция compatibility-смока после полного Linux CI - **Issue:** https://github.com/Matysh/houseplan-card/issues/211 - **ТЗ:** `docs/specs/211-device-icons-visual-parity.md` (принято `SPEC-REVIEW-211-r2`, зелёный) - **Предыдущий цикл:** `docs/reviews/CODE-REVIEW-211-r1.md` — зелёный, реализация (`4e82976`, `270cf63`) уже слита в `dev` до того, как полный Linux smoke вскрыл устаревшую проверку; поэтому временный `S8-merged` был гонкой двух событий, а не итоговым статусом (см. комментарии владельца в issue). - **Диапазон этого цикла:** `origin/dev..HEAD` = один коммит `4d1b62b` — `test: align locked marker smoke with design package` · `Issue: #211` · `User-Visible: no` - **Цикл:** r2/4 ## Скоуп проверки Весь диапазон `git diff origin/dev...HEAD` — это 11 строк в одном файле, `demo/smoke_cover_no_plate.mjs` (7 добавлено, 4 удалено). Ничего в `src/**`, `custom_components/**`, конфиге, i18n или changelog не тронуто — класс B (гейт/тест), трейлер `User-Visible: no` соответствует. Изменение правит один устаревший compatibility-факт (`lockedLockIsNeutral` → `lockedLockKeepsPackageFace`) внутри смока, который сам по себе не входит в диапазон реализации #211, но проверяет побочный контракт («у замка есть рамка, замок не становится нейтральным»), задетый переходом на дизайн-пакет #179/#211. Продуктовый код в этом цикле не проверяется повторно — он уже был предметом `CODE-REVIEW-211-r1` (зелёный) и в этом диапазоне не менялся. ## Как проверялось ### Гейты — прогнаны ``` npx tsc --noEmit → чисто npm test → 939 pass, 0 fail, 0 skipped npm run build → OK cmp dist/houseplan-card.js custom_components/.../houseplan-card.js → идентичны cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js → идентичны node demo/smoke_cover_no_plate.mjs → все факты true, OK node demo/smoke_device_icon_design.mjs → все факты true, OK (доп. проверка основного AC3-смока лежащей рядом поверхности; продукт не менялся с r1, но это самый дешёвый способ убедиться, что коррекция теста не разошлась с уже принятым CSS) ``` ### Проверка «тест умеет падать» (обязательная дисциплина, применена к единственному изменённому предикату) Правило `.dev.lock-locked` в `src/styles.ts` вернул временно к состоянию до основного фикса `4e82976` (`background: var(--device-core-bg); color: #000`, без `theme-light`/`theme-dark` расщепления — то есть ровно то поведение, которое проверял старый `lockedLockIsNeutral`), пересобрал `dist` и синхронизировал `demo/srv/assets/houseplan-card.js` (демо-сервер отдаёт именно эту копию, не `dist/` напрямую — без этого шага мутация не была бы видна смоку). Результат: ``` "lockedLockKeepsPackageFace": false FAILED (1): lockedLockKeepsPackageFace: expected true, got false ``` Все остальные факты смока остались зелёными — падение точечное, как и должно быть для правки одного предиката. После проверки `src/styles.ts` восстановлен из копии, `npm run build` прогнан повторно, `dist` синхронизирован обратно в `demo/srv/assets` и в `custom_components/houseplan/frontend`; `git status` — чистое дерево, диффа не осталось. Дополнительно проверено чтением: новые ожидаемые значения (`rgb(0, 0, 0)`/`rgb(255, 255, 255)` для Light, `rgb(37, 37, 37)`/ `rgb(255, 255, 255)` для Dark) — это буквальный computed-style результат правил `.dev.lock-locked`, `.dev.theme-light.lock-locked`, `.dev.theme-dark.lock-locked` (`src/styles.ts:2035-2048`), которые сами уже были зафиксированы и приняты в `CODE-REVIEW-211-r1` как соответствующие §7.3 ТЗ (`Lock Light: core black / glyph white`, `Lock Dark: core #252525 / glyph white`). `--device-face-bg`/`--device-face-fg` транслируются в `background`/`color` `.device-core` напрямую (`src/styles.ts:1988-1989`), так что тест меряет ровно то, что видит пользователь, а не побочный токен. ### Оценка ветвления по теме в самом тесте Смок не переключает `hass.themes.darkMode`, а демо-фикстура (`demo/srv/demo.html`) не задаёт его вовсе, поэтому `device-face.ts:24` (`hass.themes.darkMode ? 'theme-dark' : 'theme-light'`) в этом прогоне детерминированно даёт `theme-light` — веткой `rgb(37, 37, 37)` тест здесь фактически не исполняется. Это не дефект: тёмная ветка того же контракта (`darkLockUsesWhiteGlyphAndDarkShell`) уже отдельно и явно проверяется вычисляемым фактом в `demo/smoke_device_icon_design.mjs` и была подтверждена в r1 независимым before/after прогоном. Тернарное выражение здесь написано корректно и симметрично на случай, если смок когда-нибудь начнёт переключать тему, но не создаёт для этого дублирующего покрытия — фиксирую как наблюдение, не как находку. ### Коммит и трейлеры `4d1b62b`: `Issue: #211`, `User-Visible: no` — верно, диапазон не содержит изменений продукта, изменений в changelog не требуется. ## Что проверено и корректно - Правка ограничена одним файлом гейта (`demo/smoke_cover_no_plate.mjs`), относится к предмету #211 (AC3, Lock state parity) и устраняет расхождение между устаревшим pre-#179 предположением теста («заблокированный замок — нейтральный, глиф всегда чёрный») и принятым дизайн-пакетом («core черный/ `#252525`, глиф всегда белый»). - Новый предикат `lockedLockKeepsPackageFace` измеряет именно тот CSS, который производит видимый пиксель (`background`/`color` на `.device-core` через `--device-face-bg`/`--device-face-fg`), не побочный токен. - Предикат доказанно умеет падать: воспроизведена красная линия на до-фиксовом правиле, падение точечное (1 из ~60 фактов файла), после восстановления кода все факты снова зелёные. - Остальные факты того же файла (cover plate/morph/valve/window/unlocked-lock) не задеты диапазоном и остались зелёными — регрессии в соседних, не относящихся к #211 проверках нет. - `tsc`, unit-suite и build чистые; три копии бандла идентичны после восстановления дерева. - Коммит несёт корректный `Issue:`/`User-Visible:` трейлер для class B. ## Находки Нет находок уровня High, Medium или Low. Единственное отмеченное выше наблюдение (тернарная dark-ветка предиката не исполняется в этом конкретном прогоне) не поднимаю как находку: поведение для Dark уже покрыто отдельным, явно исполняемым фактом в другом смоке, а сам тернар написан правильно и не вводит риск регрессии. ## Чего не проверял и почему - **Полный набор из 127 browser smokes** — не запускал; диапазон правит один предикат в одном уже существующем файле, не относящийся к нему smoke не может быть задет текстовой правкой ассерта. - **`npm run golden:verify`** — не запускал; диапазон не меняет ни один файл, влияющий на рендер (только тестовый ассерт), видимый результат не может измениться. - **`python -m pytest tests_backend -q`** — не запускал; `custom_components/**/*.py` не тронут. - **Performance-профили** — не запускал; диапазон не касается DOM/CSS/JS продукта. - **Полный Linux CI-прогон** — не запускал локально; это гейт CI на выходе из код-ревью, а не задача рецензента. (Именно такой прогон и обнаружил устаревший факт, который правит этот коммит — второй такой же прогон на этом SHA пройдёт штатно через пайплайн после зелёного вердикта.) ## Вердикт Единственное изменение цикла — точечная и доказанная (падает на старом поведении, зелёное на новом) коррекция стороннего compatibility-теста, приводящая его в соответствие с уже принятым в r1 дизайн-контрактом #211/#179. Продуктовый код не менялся, регрессий нет, трейлеры корректны. **Зелёный.**