diff --git a/docs/reviews/CODE-REVIEW-192-r1.md b/docs/reviews/CODE-REVIEW-192-r1.md new file mode 100644 index 00000000..409ee384 --- /dev/null +++ b/docs/reviews/CODE-REVIEW-192-r1.md @@ -0,0 +1,275 @@ +# 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, что подпадает + под критерий «рендер/геометрия/стили» из инструкции к этому ревью. + +## Как проверялось + +1. Прочитан весь 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`. +2. Прочитано тело issue #192 целиком и все четыре комментария (аналитика + владельца, зелёное SPEC-ревью, хендофф реализации) — сверено, что + реализация не расширяет и не сужает скоуп относительно принятого ТЗ + (только `.hue-range`, S/V/opacity не тронуты). +3. Прочитан `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 без градиента. +4. Подтверждено, что новый CSS не покидает `.hue-range`: точечный grep по + файлу — `::-webkit-slider-thumb`/`::-moz-range-thumb` для `.hue-range` + отсутствуют (совпадает с тем, что ТЗ не заявляло новый thumb-стиль, + только «остаётся различимым»); общий `input[type='range']` не содержит + `linear-gradient`/`hp-picker-hue-track` (подтверждено также unit-тестом + `commonRange`). +5. **Дисциплина 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`). +6. Собран собственный бандл (`npm run build`) и сверен SHA-256 всех трёх + копий (`dist/`, `custom_components/houseplan/frontend/`, + `demo/srv/assets/`) — совпадает с заявленным автором в хендоффе + (`8ec9aff0...`). +7. Прогнан `npm run golden:verify` (полный набор, 67 активных сценариев) — + не только названные в TESTING.md сцены: подтверждён список расхождений + (см. «Обязательные гейты» и «Гейты по необходимости»). +8. **Живая проверка в браузере сверх названных в issue способов + доказательства** (см. «Находки»): открыт picker через тот же харнесс, + что использует `smoke_color_picker.mjs` + (`demo/serve.mjs` + `card._decorTool='line'` + `.trigger` click), hue + программно выставлялся в 0°, 55°, 205°, 210°, 220°, 235°, 250°, 260°, + каждый раз делался точный `page.screenshot({clip})` по + `getBoundingClientRect()` самого `.hue-range`, чтобы увидеть реальный + растровый результат, а не полагаться на чтение CSS. Аналогичная проверка + повторена на независимо собранном pre-#192 бандле (тот же временный + worktree) для сравнения «было/стало». +9. Проверены трейлеры и class-принадлежность: `git show 508d38c --stat` — + класс A (`src/hp-color-opacity.ts`), B (`test/`, `demo/`), C (`docs/`), D + (все три копии бандла) — все в одном коммите; `Issue: #192`, + `User-Visible: yes`; оба changelog редактируются в этом же коммите. +10. Проверено отсутствие новых 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` + `input` event), 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](https://github.com/Matysh/houseplan-card/issues/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](https://github.com/Matysh/houseplan-card/issues/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 сценарий, даже при том что формальные шаги плана +автотестов пройдены.