Compare commits

...
Author SHA1 Message Date
claude[bot] a984d479fd docs: review document for #192
Issue: #192
User-Visible: no
2026-08-19 07:27:52 +00:00
Sergey Matyunin 0d58f77ced Keep hue slider thumb visible
Issue: #192
User-Visible: yes
2026-08-19 10:14:42 +03:00
claude[bot] c27c1e44f3 docs: review document for #192
Issue: #192
User-Visible: no
2026-08-19 07:09:09 +00:00
Sergey Matyunin 508d38ca71 Show spectrum on hue slider
Issue: #192
User-Visible: yes
2026-08-19 09:54:44 +03:00
15 changed files with 808 additions and 143 deletions
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -1,7 +1,7 @@
import { fixtureWallKey } from '../fixtures/visual-matrix.mjs';
/** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */
export const GOLDEN_MATRIX_VERSION = 26;
export const GOLDEN_MATRIX_VERSION = 27;
const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } };
const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } };
@@ -269,6 +269,9 @@ export const GOLDEN_SCENARIOS = Object.freeze([
{ id: 'decor-color-popover-mobile-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'decor-color', language: 'ru', theme: 'dark',
viewport: { width: 390, height: 760 }, ...page },
{ id: 'decor-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'decor-color', language: 'en', theme: 'light',
viewport: { width: 760, height: 820 }, ...page },
{ id: 'backup-full-preview-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'backup-full', language: 'en', theme: 'dark',
viewport: { width: 1000, height: 900 }, ...page },
+8
View File
@@ -48,6 +48,14 @@ const result = await page.evaluate(async () => {
&& surface().scrollWidth <= surface().clientWidth;
const hue = ranges()[0];
const hueStyle = getComputedStyle(hue);
out.hueTrackContract = hue?.classList.contains('hue-range')
&& hue.min === '0' && hue.max === '359' && hue.step === '1'
&& hueStyle.getPropertyValue('--hp-picker-hue-track').includes('linear-gradient')
&& hue.getBoundingClientRect().height >= 40;
const thumbShadow = hueStyle.getPropertyValue('--hp-picker-hue-thumb-shadow');
out.hueThumbContrastContract = thumbShadow.includes('0 0 0 2px')
&& thumbShadow.includes('0 0 0 3px');
ranges()[1].value = '100';
ranges()[1].dispatchEvent(new Event('input', { bubbles: true, composed: true }));
ranges()[2].value = '100';
File diff suppressed because one or more lines are too long
+95 -45
View File
File diff suppressed because one or more lines are too long
+5
View File
@@ -2,6 +2,11 @@
## v1.65.0-beta.3 — 2026-08-19
- The House Plan color picker now draws the full spectrum directly on the Hue
track, so the target colour family is visible before moving the slider. Mouse,
touch and keyboard values remain unchanged, and a contrasting ring keeps the
slider visible over every hue
([#192](https://github.com/Matysh/houseplan-card/issues/192)).
- Editor color controls now open a single House Plan picker with saturation,
brightness, hue, exact HEX and opacity together. The nested browser color
dialog is gone, while existing room, space, decor and Glow values keep their
+5
View File
@@ -8,6 +8,11 @@
## v1.65.0-beta.3 — 2026-08-19
- Палитра House Plan теперь показывает полный спектр прямо на шкале «Оттенок»,
поэтому нужную группу цветов видно ещё до движения ползунка. Значения и
управление мышью, касанием и клавиатурой не изменились, а контрастный контур
сохраняет видимость ползунка на любом оттенке
([#192](https://github.com/Matysh/houseplan-card/issues/192)).
- Выбор цвета в редакторах теперь сразу открывает единую палитру House Plan с
насыщенностью, яркостью, оттенком, точным HEX и прозрачностью. Вложенный
системный диалог браузера больше не нужен, а сохранённый формат цветов комнат,
+9 -3
View File
@@ -85,9 +85,15 @@
- [ ] The color-only Glow consumer keeps the same picker without an opacity row,
and native/fallback floating surfaces remain mutually exclusive with help
[auto: `smoke_help_affordance.mjs`].
- [ ] The existing dark mobile decor color golden is reviewed from the complete
Linux artifact before a beta; baseline acceptance is not part of the
implementation loop [golden: `decor-color-popover-mobile-ru`].
- [ ] The Hue range keeps `0…359`, step 1 and its existing input events while its
WebKit/Blink and Gecko tracks expose the same cyclic spectrum. A dual
theme-aware ring keeps the native thumb distinct from every hue, while
forced-colors falls back to system track/thumb rendering [unit:
`color-picker.test.mjs`, auto: `smoke_color_picker.mjs`].
- [ ] The dark mobile and light desktop open-picker goldens are reviewed from the
complete Linux artifact before a beta; baseline acceptance is not part of
the implementation loop [golden: `decor-color-popover-mobile-ru`,
`decor-color-popover-desktop-en`].
## Open passage (#157)
+3 -1
View File
@@ -180,7 +180,9 @@ the tray scrolls horizontally instead of shrinking the plan.
Where an editor offers a color sample, one click opens the House Plan color
picker. Hue, saturation, brightness, an exact HEX value and opacity (when the
setting supports it) are available together; there is no second browser color
dialog. Changes remain a draft until the owning properties dialog is saved.
dialog. The Hue track shows the full colour spectrum at a glance, with a
contrasting ring keeping its slider visible. Changes remain a draft until the
owning properties dialog is saved.
<!-- docs-section: input -->
+3 -2
View File
@@ -189,8 +189,9 @@ title: План дома
Если редактор показывает образец цвета, один клик открывает палитру House Plan.
Оттенок, насыщенность, яркость, точное значение HEX и прозрачность (если она
поддерживается этой настройкой) доступны вместе, без второго системного диалога
браузера. Изменения остаются черновиком до сохранения родительского диалога
свойств.
браузера. Шкала «Оттенок» сразу показывает полный цветовой спектр, а контрастный
контур сохраняет видимость ползунка. Изменения остаются черновиком до сохранения
родительского диалога свойств.
<!-- docs-section: input -->
+275
View File
@@ -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 сценарий, даже при том что формальные шаги плана
автотестов пройдены.
+132
View File
@@ -0,0 +1,132 @@
# CODE-REVIEW-192-r2
Issue: [#192](https://github.com/Matysh/houseplan-card/issues/192) — «Колор-пикер:
шкала «Оттенок» показывает реальную радугу вместо одноцветного слайдера».
Лёгкий трек (`small`); ТЗ живёт в теле issue. Лимит цикла код-ревью для
лёгкого трека — 2; это второй и последний цикл.
Диапазон: `origin/dev..HEAD` = `508d38c` (Show spectrum on hue slider),
`c27c1e4` (docs: review document for #192 — документ r1),
`0d58f77` (Keep hue slider thumb visible — исправление по итогам r1).
Диапазон соответствует ветке `issue/192-hue-gradient`.
## Скоуп проверки
r1 (см. [CODE-REVIEW-192-r1.md](CODE-REVIEW-192-r1.md)) уже подтвердил AC1,
AC2, AC4, AC5 исполнением и дал жёлтый вердикт из-за частично проваленной
AC3: заявленное «thumb остаётся различимым» не выполнялось на hue ≈195°–260°
(включая hue=205° — значение по умолчанию в обеих golden-сценах), Medium
заведён как [#194](https://github.com/Matysh/houseplan-card/issues/194).
r2 — это review исправляющего коммита `0d58f77`. Задача этого цикла: не
повторять весь r1 с нуля, а (а) подтвердить, что исправление действительно
закрывает найденный дефект, (б) убедиться, что оно не расширило скоуп и не
задело остальное, (в) держать общую картину AC1/2/4/5 в поле зрения, раз они
не менялись со времён r1.
## Как проверялось
| Гейт | Прогнан | Результат |
|---|---|---|
| `npx tsc --noEmit` | да | зелёный, без вывода |
| `npm test` | да | 889 passed, 0 failed — совпадает с хендоффом |
| `npm run build` + сверка 3 копий бандла | да | `cmp` тождественны; SHA-256 `d66b779574aebd9222ddf432b746cdf5195cae3030b2cc8ba149dad6f98ce89d` — совпадает с указанным в хендоффе |
| `node demo/smoke_color_picker.mjs` | да | 16/16 true, включая новый `hueThumbContrastContract` |
| Фальсифицируемость unit-теста | да | во временном worktree на pre-fix коммите `508d38c` с тестовыми файлами из HEAD: `hue-range exposes one cyclic spectrum...` падает ровно на новой ассерции (box-shadow/thumb-shadow), остальные 4 подтеста того же теста проходят |
| Фальсифицируемость smoke-проверки | да | тот же pre-fix коммит, пересобранный бандл: `hueThumbContrastContract: false` (единственный провал), после применения `0d58f77` — `true` |
| Визуальная проверка контраста thumb | да, вручную через Playwright/`demo/serve.mjs` | скриншоты открытого picker на hue=205° (dark, mobile-профиль) и hue=235° (тот же профиль) — кольцо чётко видно на голубом/синем участке трека, ранее проблемном по r1 |
| `npm run golden:capture -- --scenario=decor-color-popover-desktop-en` | да | `missing-baseline` (ожидаемо, эталона ещё нет); candidate открыт — кольцо видно и в light-теме на hue=205° |
| `node scripts/process-gate.mjs` (офлайн) | да | «гейт пройден», 1 ожидаемое WARN (ТЗ в теле issue при `small`, а не в `docs/specs/`) |
| Трейлеры коммитов | да, чтением `git log` | все три коммита несут `Issue: #192` и корректный `User-Visible:`; `User-Visible: yes` на `508d38c` и `0d58f77` сопровождается правками обоих changelog в том же коммите |
**Не прогонялось и почему:**
- `npm run golden:verify` (полный набор, 67 сценариев) — не гонял повторно.
r1 уже прогонял полный набор и получил ожидаемую картину (65 passed + 2
ожидаемых расхождения ровно в двух `decor-color-popover-*` сценах). Диапазон
правки `0d58f77` строго ограничен селекторами `.hue-range::-webkit-slider-thumb`
и `.hue-range::-moz-range-thumb` — то есть тем же самым hue-thumb внутри тех
же двух сцен, что уже были единственными расхождениями в r1. Полный прогон
здесь не добавил бы уверенности к тому, что дают точечные `golden:capture`
двух затронутых сцен (сделаны) плюс unit-контракт изоляции (`commonRange`
не содержит `linear-gradient|hp-picker-hue-track`, не тронут). Это гейт,
соразмерный задаче (PROCESS.md §8): полный набор — предрелизный, не ревью.
- `python -m pytest tests_backend -q` — не гонял: `custom_components/**/*.py`
не тронут ни в одном из трёх коммитов (diffstat это подтверждает).
- performance-профили — не гонял: в AC не названы, правка — один статичный
CSS `box-shadow`, без новых вычислений в рантайме.
- Firefox/Safari реальным движком — недоступно в этом окружении (только
Chromium), это принятое предположение самого ТЗ («Принятые предположения»,
автоматизированный smoke остаётся Chromium; Gecko/WebKit declarations
доказываются source-contract и код-ревью). Прочитаны декларации
`::-moz-range-thumb` — синтаксически корректны и симметричны webkit-версии.
## Находки
Нет ни одной High- или Medium-находки в этом цикле.
Low (не блокирует, снимается с записью, дальнейшая правка не нужна):
корректирующий коммит не добавил отдельного smoke/unit-теста, который бы
проверял именно *визуальный* контраст (пиксельное сравнение thumb/track) —
только наличие CSS custom property. Единственное реальное доказательство
контраста на конкретном hue — это golden-эталон (проверяется людьми при
приёмке) и ручной скриншот, сделанный в этом ревью. Это соответствует
принятому в ТЗ способу доказательства AC3 (golden + source-contract/code
review), поэтому не поднимаю до Medium — но фиксирую, что «различимость
thumb» как факт живёт исключительно в golden-baseline review, а не в
автотесте, который умеет упасть сам по себе на будущей регрессии этого рода.
Информационно, не находка: описанный в
[#194](https://github.com/Matysh/houseplan-card/issues/194) дефект
воспроизводится именно на pre-`0d58f77` коде и не воспроизводится после —
проверено выше и unit-тестом, и smoke, и скриншотами на hue=205° и hue=235°.
Закрытие #194 — не в полномочиях этого ревью (§12: закрывает issue
релиз-менеджер/владелец, не ревьюер кода), фиксирую здесь как факт для того,
кто будет разбирать #194 дальше.
## Что проверено и корректно
- **AC1 (спектр).** Без изменений с r1: `linear-gradient` с ключевыми stops
на `.hue-range`, эквивалентные `::-webkit-slider-runnable-track` и
`::-moz-range-track`. Подтверждено исполнением (unit + smoke,
`hueTrackContract: true`).
- **AC2 (управление).** Без изменений с r1 и без затронутого кода в r2:
`min=0/max=359/step=1`, mouse/touch/keyboard/`Shift+Arrow`/emit не тронуты.
889/889 unit + 16/16 smoke, включая `shiftArrowUsesTenStep` и
`hueUpdatesSharedDraft`.
- **AC3 (темы и accessibility) — предмет этого цикла.** Ранее проваленная
часть («thumb остаётся различимым») теперь выполняется: двухконтурное
`box-shadow` (`--card-background-color` внутренний контур,
`--primary-text-color` внешний) не зависит от hue и поэтому контрастен на
любом участке трека — проверено чтением реализации и визуально на двух
ранее проблемных значениях (205°, 235°) в обоих затронутых golden-сценах
(dark/mobile, light/desktop). `forced-colors: active` корректно отключает
декоративную тень (`box-shadow: none` внутри того же media-блока,
подтверждено чтением diff) — системный рендеринг thumb остаётся. Native
размер/цвет/hit area бегунка не переопределены, значит риск нарушить
hit-area или fallback без color отсутствует. `prefers-reduced-motion`
блок не тронут.
- **AC4 (изоляция).** Правка `0d58f77` меняет ровно 4 селектора внутри уже
существующего scoped-блока `.hue-range`/forced-colors; unit-тест явно
проверяет, что `input[type='range']` (общий блок) не содержит новых
свойств. Изменённых golden-сцен по-прежнему ровно две — те же, что в r1.
- **AC5 (совместимость).** Public API, данные, i18n, стили остальных range
не изменены — diffstat и unit source-contract это подтверждают.
- **Трейлеры и changelog.** `Issue: #192` на каждом коммите; `User-Visible:
yes` на `508d38c` и `0d58f77` — в обоих оба changelog правятся в том же
коммите (проверено чтением `git show`). `USER-GUIDE.md`/`USER-GUIDE.ru.md`
обновлены той же формулировкой («contrasting ring» / «контрастный контур»)
синхронно с кодом.
- **Гейты.** Все обязательные (typecheck/test/build+сверка) зелёные и
воспроизведены самостоятельно, не только со слов хендоффа; SHA-256
бандла совпал.
## Вердикт
Дефект AC3, из-за которого r1 получил жёлтый вердикт, исправлен и
верифицирован независимым исполнением (unit, smoke, ручной визуальный
скриншот на конкретных ранее проблемных hue-значениях), а не только
прочитан. Новых High/Medium-находок нет. Скоуп не расширен: правка
ограничена thumb-декорацией того же `.hue-range`.
**Вердикт: зелёный · цикл r2/2 · High: 0 · Medium: 0 → нет**
+50
View File
@@ -233,6 +233,40 @@ export class HpColorOpacity extends LitElement {
}
.hue-range {
accent-color: var(--hp-picker-hue, #f00);
--hp-picker-hue-track: linear-gradient(to right,
#f00 0%, #ff0 16.667%, #0f0 33.333%, #0ff 50%,
#00f 66.667%, #f0f 83.333%, #f00 100%);
--hp-picker-hue-thumb-shadow:
0 0 0 2px var(--card-background-color, #202126),
0 0 0 3px var(--primary-text-color, #fff);
}
.hue-range::-webkit-slider-runnable-track {
height: 10px;
box-sizing: border-box;
border: 1px solid var(--divider-color, #666);
border-radius: 999px;
background: var(--hp-picker-hue-track);
}
.hue-range::-moz-range-track {
height: 10px;
box-sizing: border-box;
border: 1px solid var(--divider-color, #666);
border-radius: 999px;
background: var(--hp-picker-hue-track);
}
.hue-range::-moz-range-progress {
height: 10px;
border: 0;
border-radius: 999px;
background: transparent;
}
.hue-range::-webkit-slider-thumb {
border-radius: 50%;
box-shadow: var(--hp-picker-hue-thumb-shadow);
}
.hue-range::-moz-range-thumb {
border-radius: 50%;
box-shadow: var(--hp-picker-hue-thumb-shadow);
}
input[type='text'],
input[type='number'] {
@@ -276,6 +310,22 @@ export class HpColorOpacity extends LitElement {
@media (prefers-reduced-motion: reduce) {
.picker { transition: none !important; }
}
@media (forced-colors: active) {
.hue-range::-webkit-slider-runnable-track {
border-color: ButtonText;
background: Canvas;
}
.hue-range::-moz-range-track {
border-color: ButtonText;
background: Canvas;
}
.hue-range::-webkit-slider-thumb {
box-shadow: none;
}
.hue-range::-moz-range-thumb {
box-shadow: none;
}
}
`;
connectedCallback(): void {
+18
View File
@@ -48,3 +48,21 @@ test('the shared component keeps its API and contains no nested native color pic
assert.equal((card.match(/<hp-color-opacity/g) || []).length, 8);
assert.equal((card.match(/\.pickerLabels=\$\{this\._colorPickerLabels\}/g) || []).length, 8);
});
test('the hue range exposes one cyclic spectrum without restyling other ranges', () => {
const component = readFileSync(new URL('../src/hp-color-opacity.ts', import.meta.url), 'utf8');
const spectrum = component.match(/\.hue-range\s*\{[\s\S]*?\n\s*\}/)?.[0] || '';
assert.match(spectrum, /--hp-picker-hue-track:\s*linear-gradient\(to right/);
for (const stop of ['#f00 0%', '#ff0 16.667%', '#0f0 33.333%', '#0ff 50%',
'#00f 66.667%', '#f0f 83.333%', '#f00 100%']) assert.ok(spectrum.includes(stop), stop);
assert.match(component, /\.hue-range::\-webkit-slider-runnable-track\s*\{[\s\S]*?height:\s*10px;[\s\S]*?background:\s*var\(--hp-picker-hue-track\);/);
assert.match(component, /\.hue-range::\-moz-range-track\s*\{[\s\S]*?height:\s*10px;[\s\S]*?background:\s*var\(--hp-picker-hue-track\);/);
assert.match(component, /\.hue-range::\-moz-range-progress\s*\{[\s\S]*?background:\s*transparent;/);
assert.match(component, /\.hue-range::\-webkit-slider-thumb\s*\{[\s\S]*?border-radius:\s*50%;[\s\S]*?box-shadow:\s*var\(--hp-picker-hue-thumb-shadow\);/);
assert.match(component, /\.hue-range::\-moz-range-thumb\s*\{[\s\S]*?border-radius:\s*50%;[\s\S]*?box-shadow:\s*var\(--hp-picker-hue-thumb-shadow\);/);
assert.match(spectrum, /--hp-picker-hue-thumb-shadow:[\s\S]*?card-background-color[\s\S]*?primary-text-color/);
assert.match(component, /@media \(forced-colors:\s*active\)[\s\S]*?background:\s*Canvas;/);
assert.match(component, /class="hue-range" type="range" min="0" max="359" step="1"/);
const commonRange = component.match(/input\[type='range'\]\s*\{[\s\S]*?\n\s*\}/)?.[0] || '';
assert.doesNotMatch(commonRange, /linear-gradient|hp-picker-hue-track/);
});
+11 -1
View File
@@ -209,7 +209,7 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity
assert.ok(scenario);
const fixture = prepareGoldenFixture(scenario);
const space = fixture.config.spaces.find((item) => item.id === scenario.space);
assert.equal(GOLDEN_MATRIX_VERSION, 26);
assert.equal(GOLDEN_MATRIX_VERSION, 27);
assert.equal(space.settings.sun_rays, true);
assert.equal(scenario.northDeg, 90,
'the sign-sensitive golden must keep a non-zero north direction');
@@ -227,6 +227,16 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity
assert.equal(scenario.sunRayPixels.minChannelDelta >= 4, true);
});
test('the open color picker golden covers dark mobile and light desktop themes', () => {
const scenarios = GOLDEN_SCENARIOS.filter((item) => item.dialog === 'decor-color');
assert.deepEqual(scenarios.map(({ id, language, theme, viewport }) => ({
id, language, theme, width: viewport.width,
})), [
{ id: 'decor-color-popover-mobile-ru', language: 'ru', theme: 'dark', width: 390 },
{ id: 'decor-color-popover-desktop-en', language: 'en', theme: 'light', width: 760 },
]);
});
test('value badge golden covers four positions and bottom badge with separate LQI', () => {
const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'device-value-badge-positions-dark');
assert.ok(scenario);