24 KiB
CODE-REVIEW-192-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/192
- ТЗ: лёгкий трек (
small) — живёт в теле issue #192 (ревью —docs/reviews/SPEC-REVIEW-192-r1.md, вердикт зелёный, Low-1 снят с записью, Medium: 0) - Диапазон:
git log --oneline origin/dev..HEAD/git diff origin/dev...HEAD— один коммит508d38c«Show spectrum on hue slider» (Issue: #192,User-Visible: yes) - Роль: ревьюер кода (не автор), этап
S7-code-review - Цикл: r1/2 (лёгкий трек)
Скоуп ревью
Проверялось соответствие реализации:
- контракту из тела issue #192 (§«Контракт поведения и UX», AC1–AC5, план автотестов, риски);
docs/SCOPE.md— задача не расширяет продукт за пределы editor usability polish (J4/J6), View/kiosk не затронуты;AGENTS.md/PROCESS.md— классы файлов, трейлеры, оба changelog в одномUser-Visible: yesкоммите, изоляция (только.hue-range, не общийinput[type='range']);- фактическому поведению
src/hp-color-opacity.tsв браузере (Chromium, Playwright), а не только по чтению CSS; - реально выполненным гейтам (см. ниже), включая полный
npm run golden:verify— diff меняет видимый рендер открытого picker, что подпадает под критерий «рендер/геометрия/стили» из инструкции к этому ревью.
Как проверялось
- Прочитан весь diff (
git diff origin/dev...HEAD --statи файлы по отдельности):src/hp-color-opacity.ts(+23/-0 в стилях),test/color- picker.test.mjs(+15 строк, новый тест),demo/smoke_color_picker.mjs(+5 строк),demo/golden/matrix.mjs(+3, новая сценаdecor-color- popover-desktop-en),test/golden-matrix.test.mjs(+10, contract-тест на список сцен),docs/{CHANGELOG.md,CHANGELOG.ru.md,USER-GUIDE.md, USER-GUIDE.ru.md,TESTING.md}, сгенерированныеdist/houseplan-card.js/custom_components/houseplan/frontend/houseplan-card.js/demo/srv/assets/houseplan-card.js. - Прочитано тело issue #192 целиком и все четыре комментария (аналитика
владельца, зелёное SPEC-ревью, хендофф реализации) — сверено, что
реализация не расширяет и не сужает скоуп относительно принятого ТЗ
(только
.hue-range, S/V/opacity не тронуты). - Прочитан
src/hp-color-opacity.ts:225-312построчно:input[type='range'](общий, высота 40px,margin: -5px 0) не изменён;.hue-rangeдобавляет--hp-picker-hue-track(linear-gradient, 7 стопов, циклический — 0%/100% оба#f00) иaccent-colorне тронут;::-webkit-slider- runnable-trackи::-moz-range-track— одинаковый контракт (высота 10px,border,border-radius: 999px,background: var(--hp-picker- hue-track));::-moz-range-progressобнулён вtransparent, чтобы Gecko не закрашивал левую часть трека сплошным accent-цветом поверх градиента;@media (forced-colors: active)переопределяет оба трека наCanvas/ButtonText— системный безопасный fallback без градиента. - Подтверждено, что новый CSS не покидает
.hue-range: точечный grep по файлу —::-webkit-slider-thumb/::-moz-range-thumbдля.hue-rangeотсутствуют (совпадает с тем, что ТЗ не заявляло новый thumb-стиль, только «остаётся различимым»); общийinput[type='range']не содержитlinear-gradient/hp-picker-hue-track(подтверждено также unit-тестомcommonRange). - Дисциплина falsifiability применена к обоим прогнанным тестам:
- unit: воссоздан pre-#192 файл во временном
git worktreeнаorigin/devи прогнан новый тестtest/color-picker.test.mjs(«the hue range exposes one cyclic spectrum...») именно против старого.hue-range(толькоaccent-color, без градиента) — тест корректно падает на первой же проверке (--hp-picker-hue-track:\s*linear-gradientне находит совпадения). Worktree удалён после проверки. - browser smoke:
node demo/smoke_color_picker.mjsпрогнан на текущем дереве — все 15 проверокtrue, включая новуюhueTrackContract(class,min/max/step,--hp-picker-hue-trackсодержитlinear-gradient,height >= 40px); падение этой же проверки на pre-#192 бандле подтверждено тем же временным worktree (переменная--hp-picker-hue-trackне существует на старом.hue-range, так что проверка.includes('linear-gradient')даётfalse).
- unit: воссоздан pre-#192 файл во временном
- Собран собственный бандл (
npm run build) и сверен SHA-256 всех трёх копий (dist/,custom_components/houseplan/frontend/,demo/srv/assets/) — совпадает с заявленным автором в хендоффе (8ec9aff0...). - Прогнан
npm run golden:verify(полный набор, 67 активных сценариев) — не только названные в TESTING.md сцены: подтверждён список расхождений (см. «Обязательные гейты» и «Гейты по необходимости»). - Живая проверка в браузере сверх названных в issue способов
доказательства (см. «Находки»): открыт picker через тот же харнесс,
что использует
smoke_color_picker.mjs(demo/serve.mjs+card._decorTool='line'+.triggerclick), hue программно выставлялся в 0°, 55°, 205°, 210°, 220°, 235°, 250°, 260°, каждый раз делался точныйpage.screenshot({clip})поgetBoundingClientRect()самого.hue-range, чтобы увидеть реальный растровый результат, а не полагаться на чтение CSS. Аналогичная проверка повторена на независимо собранном pre-#192 бандле (тот же временный worktree) для сравнения «было/стало». - Проверены трейлеры и class-принадлежность:
git show 508d38c --stat— класс A (src/hp-color-opacity.ts), B (test/,demo/), C (docs/), D (все три копии бандла) — все в одном коммите;Issue: #192,User-Visible: yes; оба changelog редактируются в этом же коммите. - Проверено отсутствие новых runtime dependencies:
git diff origin/dev...HEAD -- package.json package-lock.json— пусто.
Обязательные гейты (всегда)
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
чисто, без вывода |
| Unit-тесты | npm test |
889/889, 0 fail (совпадает с заявленным автором) |
| Build + сверка бандлов | npm run build + sha256sum dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js demo/srv/assets/houseplan-card.js |
все три идентичны, 8ec9aff054216bc8df5df9bf6410213254a352c7b742b4b28231e0a0048171b2 — совпадает с хендоффом автора |
Falsifiability (тест должен уметь падать), применена к обоим прогнанным наборам — см. п.5 выше («Как проверялось»): unit и browser-smoke оба корректно падают на pre-#192 коде.
Гейты по необходимости
| Гейт | Почему запускался | Результат |
|---|---|---|
node demo/smoke_color_picker.mjs |
назван в AC2 issue как способ доказательства, единственная затронутая поверхность | зелёный, все 15 проверок true, включая новую hueTrackContract; falsifiability подтверждена (см. выше) |
npm run golden:verify (полный набор, 67 активных сценариев) |
diff меняет видимый рендер открытого picker (новый градиент трека) — подпадает под критерий «рендер/геометрия/стили/слои» из инструкции к ревью | 65 passed, 1 different (decor-color-popover-mobile-ru — ожидаемо, картинка просмотрена, см. «Находки»), 1 missing-baseline (decor-color-popover-desktop-en — новая сцена, ожидаемо). Изоляция (AC4) подтверждена: ни один из 65 остальных сценариев, включая все прочие поверхности с input[type=range] (тряй-панели, диалоги устройств), не изменился |
python -m pytest tests_backend -q |
не запускался | diff не трогает ни одного файла custom_components/**/*.py (подтверждено git diff --stat), гейт неприменим |
| performance-профили | не запускались | AC явно называет причину не запускать: «статичный CSS gradient, runtime вычислений нет»; diff не трогает canvas/рендер-цикл плана, только статичный CSS двух input[type=range] |
Что проверено и корректно
- AC1 — спектр.
.hue-rangeполучает циклический 7-стопныйlinear-gradient(to right, #f00 0% … #f00 100%), применённый через::-webkit-slider-runnable-trackи::-moz-range-trackс одинаковыми стопами (общая CSS-переменная--hp-picker-hue-track) — подтверждено чтением и unit source-contract тестом. Живой рендер в Chromium (скриншоты hue=0/55/205/235/250) показывает корректный порядок цветов: красный → жёлтый → зелёный → циан → синий → маджента → красный, совпадает со стандартным hue-кругом. - AC2 — управление.
min=0/max=359/step=1не изменены; mouse (через.value+inputevent), touch (pointer-путьsv-fieldне тронут, сам hue остаётся нативным range), Arrow/Shift+Arrow(shiftArrowUsesTenStep), focus/Escape иhp-color-opacity-change— все существующие smoke-проверки остались зелёными без изменений ожиданий, кроме одной новой добавленной (hueTrackContract); 120° по-прежнему даёт#00ff00(hueUpdatesSharedDraft). - AC4 — изоляция. Полный
golden:verify(67 сценариев) подтверждает: из всех поверхностей сinput[type='range'](decor picker, диалоги устройств, тряй-панели) изменился рендер только у самого открытого color popover; общийinput[type='range']блок не содержит градиентных деклараций (unit-тестcommonRange+ собственноручный grep). - AC5 — совместимость.
package.json/package-lock.jsonне менялись; API компонента (color/opacity/showOpacity/disabled,hp-color-opacity-change) не тронуто; i18n-строк не добавлено — сверено чтением diff. - forced-colors fallback (часть AC3).
@media (forced-colors: active)корректно переопределяет оба трека на системныеCanvas/ButtonTextбез градиента — безопасный вариант деградации, соответствует общей практике WCAG для forced-colors. - Gecko progress не перекрывает градиент.
::-moz-range-progressобнулён вbackground: transparent— без этого Firefox залил бы левую часть трека сплошным accent-цветом поверх спектра, что было прямо названо риском в хендоффе автора; проверено чтением (Gecko недоступен в среде ревью, как и заявлено в «Принятых предположениях» issue). - Изоляция gradient от текущего выбранного цвета (п.3 контракта).
--hp-picker-hue-track— статическая константа, не зависит от_hue; подтверждено чтением и тем, что unit-тест матчит буквальные hex-стопы. - prefers-reduced-motion (п.4 контракта). Новый CSS не добавляет
transition/animation; существующий@media (prefers-reduced-motion: reduce)блок не тронут — подтверждено diff'ом. - Trailers/классы файлов/changelog. Коммит
508d38cнесётIssue: #192,User-Visible: yes; оба changelog (docs/CHANGELOG.md,docs/CHANGELOG.ru.md) редактируются в этом же коммите; формулировки («шкала «Оттенок» показывает полный спектр») соответствуютdocs/USER-GUIDE.ru.md, термины не изобретены. - Golden-сценарии (план автотестов). Новая light-сцена
decor-color-popover-desktop-enдобавлена и покрыта отдельным contract-тестом (test/golden-matrix.test.mjs), проверяющим ровно два сценарияdialog === 'decor-color'с ожидаемымиtheme/language/width— соответствует плану автотестов issue.
Находки
Medium-1 — thumb hue-шкалы теряет контраст с треком в диапазоне циан/синий/индиго (~195°–260°)
Файл: src/hp-color-opacity.ts:234-259 (новый градиент трека без
компенсирующего стиля thumb).
AC3 issue дословно требует: «Spectrum/thumb читаемы в light/dark... thumb
остаётся различимым». Риск-таблица самого issue заранее называла этот
сценарий («Thumb теряется на ярком секторе») и предлагала меру
(«контрастная border/shadow без уменьшения focus indicator») — эта мера не
реализована: в diff нет ни ::-webkit-slider-thumb, ни ::-moz-range-thumb
для .hue-range.
Воспроизведение (выполнено, не предположение). Открыт picker тем же
харнессом, что и demo/smoke_color_picker.mjs (demo/serve.mjs, decor →
Line → .trigger), hue выставлялся программно через input.value +
input-event, затем снят page.screenshot({ clip }) строго по
getBoundingClientRect() элемента .hue-range:
- при hue 205° (то самое значение, что используется в обеих golden-сценах
decor-color-popover-mobile-ruиdecor-color-popover-desktop-en, просмотренных автором как «контраст корректен») граница между thumb и треком уже заметно слабее, чем на жёлтом/красном участке; - при 210°, 220°, 235°, 250° thumb почти сливается с треком — узнаваемого контрастного контура нет, различим только по едва заметному перепаду яркости.
Причина: thumb рисуется фиксированным системным цветом браузера (в
тестируемом Chromium — solid blue), который не меняется с _hue
(независимый от #192 давний баг: accent-color: var(--hp-picker-hue) не
красит сам thumb ни до, ни после этого коммита — проверено тем же способом
на независимо собранном pre-#192 бандле, thumb там тоже фиксированно-синий,
но на равномерно-сером треке это никогда не создавало проблемы контраста).
До #192 нейтральный серый трек гарантировал контраст с любым
фиксированным цветом thumb; #192 заменяет этот трек радугой, и ровно в той
части круга, что близка по тону к цвету самого thumb, декларированное в
AC3 «thumb остаётся различимым» перестаёт быть верным для реального
Chromium-рендера — единственного движка, фактически исполняемого в CI/
smoke/golden этого проекта (Gecko/WebKit заявлены как source-contract-only
по «Принятым предположениям» issue).
Почему Medium, не Low. Находка не гипотетическая: воспроизведена прямым
запуском в том же харнессе, что использует официальный smoke, на реальном
исполняемом движке проекта, включая ровно те hue-значения, что приняты в
golden baseline. Диапазон ~65° из 359° (≈18% круга) — это не край случай, а
частый выбор (синие/циановые акценты в HA-конфигурациях освещения).
Функциональность не ломается (клавиатура, числовая подпись 205°,
drag из любой точки трека работают), поэтому не High; но декларированный
критерий приёмки (AC3, «thumb остаётся различимым») в этом диапазоне не
выполняется, что не позволяет закрыть находку как Low с одной лишь записью.
Действие: заведён отдельный issue
#194 (bug, P3,
S1-new), со ссылкой на #192 и на этот документ. Обнаруженный смежный
давний баг «accent-color не красит thumb вообще» описан в issue как явно
не-скоуп — самостоятельный вопрос, а не часть этой находки.
Чего не проверял
- Реальный Firefox/Gecko и Safari/WebKit. Смотрел только Chromium
(единственный движок, доступный в среде ревью и в CI проекта). Gecko/
WebKit-декларации (
::-moz-range-track,::-moz-range-progress) проверены исключительно чтением CSS и совпадением стопов с webkit-версией — это прямо принято issue как достаточное доказательство («Принятые предположения»: «WebKit/Safari и Gecko declarations доказываются source-contract и code review»), не пытаюсь выдать это за исполненный тест. - Реальный screen-reader / forced-colors режим ОС.
@media (forced-colors: active)прочитан построчно (валидный синтаксис, системные ключевые словаCanvas/ButtonText), но не исполнялся в браузере с реально включённым forced-colors — то же ограничение среды, что уже было зафиксировано как Low в SPEC-ревью и признано доказуемым только чтением кода. prefers-reduced-motion: reduceживьём — подтверждено чтением diff (блок не тронут, новый CSS не содержитtransition/animation), не отдельным browser-прогоном с эмуляцией медиа-фичи.npm run golden:accept— не запускал и не должен: принятие baseline (в т.ч. новой сценыdecor-color-popover-desktop-en) принадлежит пре-бета этапу на полном Linux CI-артефакте, не циклу код-ревью.- Полный
node demo/smoke_*.mjsнабор (127 файлов) — не запускался целиком: diff касается ровно одной поверхности (hp-color-opacity), прогнан только относящийся к нейsmoke_color_picker.mjsплюс golden целиком (гейт «по необходимости» — рендер/стили). - Bundle size budget — issue не называет числовой бюджет (в отличие от #57); не оценивал raw/gzip дельту отдельно, только сверил байт-в-байт идентичность трёх копий и совпадение SHA-256 с хендоффом автора.
Вердикт
Жёлтый. High: 0, Medium: 1 (→ #194), Low: 0. AC1, AC2, AC4, AC5 выполнены и доказаны исполнением, с личной проверкой falsifiability для обоих прогнанных тестов. AC3 выполнена частично: light/ dark читаемость трека и forced-colors fallback подтверждены, но задекларированное в этом же AC «thumb остаётся различимым» не выполняется в диапазоне hue ~195°–260° на Chromium — единственном исполняемом в этом проекте движке — что прямо воспроизведено скриншотами в ходе ревью, а не предположено. Это ровно тот сценарий, который сама задача предвидела в своей риск-таблице и не закрыла кодом. Находка не блокирует (функциональность и три остальных AC не страдают, диапазон — суб-часть одного критерия), поэтому не High; заведена отдельным issue #194 согласно процессу. Жёлтый, а не зелёный — потому что реализация не полностью решает заявленный в AC3 сценарий, даже при том что формальные шаги плана автотестов пройдены.