mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 21:28:59 +00:00
docs: specify unified color picker coverage
Issue: #180 User-Visible: no
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
# #180 — Единый picker во всех местах выбора цвета
|
||||
|
||||
Статус: готово к ревью ТЗ
|
||||
Issue: [#180](https://github.com/Matysh/houseplan-card/issues/180)
|
||||
Связано: [#57](https://github.com/Matysh/houseplan-card/issues/57)
|
||||
|
||||
## 1. Сценарий пользователя
|
||||
|
||||
Администратор настраивает цвета плана в общих настройках, свойствах пространства
|
||||
или диалоге устройства. В любом из этих мест он нажимает образец и получает один
|
||||
и тот же House Plan picker, уже знакомый по decor, custom fill и Glow.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** часть цветов открывается единым picker из #57, а оставшиеся поля вызывают
|
||||
системный color dialog; там, где есть прозрачность, она по-прежнему вынесена в
|
||||
отдельный slider рядом.
|
||||
|
||||
**После:** каждый образец цвета открывает одну и ту же поверхность House Plan.
|
||||
Если значение уже имеет прозрачность, цвет и opacity находятся внутри неё; если
|
||||
поле color-only, строка opacity отсутствует.
|
||||
|
||||
## 3. Проблема
|
||||
|
||||
#57 заменила вложенный native picker внутри `hp-color-opacity`, но не перевела
|
||||
параллельные native call sites. В результате одинаковое действие зависит от
|
||||
конкретного диалога, а общий компонент не является настоящей единственной точкой
|
||||
выбора цвета.
|
||||
|
||||
На текущем `dev` осталось пять source-шаблонов `<input type="color">`, которые
|
||||
представляют 15 пользовательских полей. Такое расхождение усложняет touch,
|
||||
keyboard, темы, локализацию и дальнейшее сопровождение.
|
||||
|
||||
## 4. Инвентаризация и scope
|
||||
|
||||
В задачу входят все оставшиеся native call sites:
|
||||
|
||||
| Поверхность | Поля | Новый режим |
|
||||
|---|---:|---|
|
||||
| Общие настройки, `_renderColorRow()` | 11: light on/off/none; temp cold/ok/hot; LQI low/high; Glow base/light; wall fill | color + existing opacity |
|
||||
| Общие настройки → статичный фон | 1 | color-only + прежний Default/theme |
|
||||
| Устройство → activity/ripple color | 1 | color-only |
|
||||
| Пространство → цвет комнат | 1 | color + existing room opacity |
|
||||
| Пространство → статичный фон | 1 | color-only + прежний Inherit |
|
||||
|
||||
Дополнительно входят:
|
||||
|
||||
- переиспользование существующего `hp-color-opacity` API и labels #57;
|
||||
- сохранение live draft, Save/Cancel, default/inherit и nullable semantics;
|
||||
- запрет native `input[type=color]` во всём продуктовом TypeScript;
|
||||
- keyboard/touch/theme/accessibility regression coverage;
|
||||
- smoke и golden для затронутых dialog families;
|
||||
- актуализация пользовательской и тестовой документации.
|
||||
|
||||
## 5. Не в scope
|
||||
|
||||
- redesign самого picker, HSV/RGB panels, presets, history или eyedropper;
|
||||
- новое значение opacity для color-only полей;
|
||||
- изменение палитр, default-цветов либо визуальных правил light/temp/LQI/Glow;
|
||||
- изменение marker ripple animation, размера либо alarm semantics;
|
||||
- изменение day/night background, наследования space или room fill algorithms;
|
||||
- новый формат конфига, backend schema либо migration;
|
||||
- массовая перестройка layout диалогов вне необходимой замены controls;
|
||||
- полная touch parity родительских редакторов.
|
||||
|
||||
## 6. Общий interaction contract
|
||||
|
||||
Каждый из 15 controls использует существующий `hp-color-opacity`:
|
||||
|
||||
- click/tap/Enter/Space по swatch открывает единственную picker surface;
|
||||
- внутри нет native `input[type=color]` и второго системного dialog;
|
||||
- изменения сразу обновляют только draft текущего родительского диалога;
|
||||
- Save сохраняет draft прежним путём, Cancel/close его отбрасывает;
|
||||
- Escape сначала закрывает picker и возвращает focus на trigger, второй Escape
|
||||
принадлежит родительскому dialog;
|
||||
- открытие другого transient overlay либо picker закрывает предыдущий;
|
||||
- одновременно в одном dialog видима не более чем одна picker surface;
|
||||
- disabled/busy parent не получает обходного write через picker.
|
||||
|
||||
Все interaction, floating surface, focus, viewport и pointer semantics остаются
|
||||
контрактом #57. #180 добавляет consumers, а не второй вариант компонента.
|
||||
|
||||
## 7. Контракт значений
|
||||
|
||||
### 7.1 Поля с opacity
|
||||
|
||||
`_renderColorRow()` и room color передают текущую пару `{color, opacity}` в
|
||||
`hp-color-opacity` с `showOpacity=true`. Событие
|
||||
`hp-color-opacity-change: {color, opacity}` атомарно обновляет оба draft-поля.
|
||||
|
||||
Существующие отдельные alpha slider и процент рядом удаляются: opacity доступна
|
||||
внутри picker при первом открытии. Значение `[0,1]`, шаг/округление и сохранение
|
||||
остаются контрактом #57; открытие/закрытие без изменения ничего не округляет.
|
||||
|
||||
### 7.2 Color-only поля
|
||||
|
||||
Общий фон, space background и ripple используют `showOpacity=false`. Они не
|
||||
получают alpha в UI, config или event handling; передаваемая техническая opacity
|
||||
не сохраняется.
|
||||
|
||||
### 7.3 Default и inheritance
|
||||
|
||||
Nullable background сохраняет прежнюю модель:
|
||||
|
||||
- общий `bgColor=null` показывает эффективный theme/stage color и прежний текст
|
||||
`Theme`; выбор цвета создаёт explicit draft;
|
||||
- кнопка `Default` снова ставит `null`;
|
||||
- space `bgColor=null` показывает эффективный inherited/global color и прежний
|
||||
текст `Inherited`; выбор создаёт explicit draft;
|
||||
- кнопка `Inherit` снова ставит `null`.
|
||||
|
||||
Picker не получает собственную reset/inherit команду. Действующие кнопки рядом
|
||||
с ним сохраняются и закрывают открытую surface через обычный parent update.
|
||||
|
||||
Пустой `rippleColor` означает действующий accent/default. Открытие picker без
|
||||
изменения не материализует fallback; первое валидное изменение создаёт explicit
|
||||
hex draft. Сброс ripple color в #180 не добавляется, потому что его не было в
|
||||
текущем UI и владелец запросил унификацию picker, а не новый default-action.
|
||||
|
||||
## 8. Layout, theme и accessibility
|
||||
|
||||
- На каждой строке пользовательское название отображается ровно один раз; можно
|
||||
убрать внешний label либо внутренний label component, но trigger получает
|
||||
непустой локализованный accessible name.
|
||||
- У 11 строк общих настроек не остаётся пустого места от удалённого alpha slider;
|
||||
swatch выравнивается с labels и соседними controls без горизонтального overflow.
|
||||
- Смешанная marker-строка сохраняет ripple-size slider и его значение; picker
|
||||
trigger не уменьшает touch target и не перекрывает соседний control.
|
||||
- Общий/space default-inherit button остаётся keyboard-доступным и не попадает
|
||||
внутрь floating surface.
|
||||
- Light/dark theme, forced colors, 390 CSS px и 200% zoom используют уже
|
||||
проверенный visual contract #57.
|
||||
- Picker остаётся локализован через `.pickerLabels=${this._colorPickerLabels}` и
|
||||
`.opacityLabel`; hardcoded English в новых call sites запрещён.
|
||||
|
||||
## 9. Модель данных и совместимость
|
||||
|
||||
Новых полей нет. Без изменений сохраняются:
|
||||
|
||||
- `FillColors[key] = { c: #RRGGBB, a: number }`;
|
||||
- global `settings.bg_color?: #RRGGBB`;
|
||||
- `marker.ripple_color?: #RRGGBB`;
|
||||
- `space.settings.room_color` и `room_opacity`;
|
||||
- `space.settings.bg_color?: #RRGGBB`.
|
||||
|
||||
Backend, export/import, config compatibility registry и migration не меняются.
|
||||
Старые значения открываются и сохраняются в прежнем формате. `safeStoredColor`
|
||||
и normalizer #57 остаются единственной границей допустимого hex; #180 не вводит
|
||||
новый parser.
|
||||
|
||||
## 10. i18n
|
||||
|
||||
Новых ключей не требуется. Переиспользуются существующие:
|
||||
|
||||
- `color_picker.*` и `space.opacity` для общей picker surface;
|
||||
- текущие labels `gs.*`, `marker.activity_color`, `space.room_color` и
|
||||
`space.bg_color` для конкретного trigger;
|
||||
- текущие Default/Theme/Inherit/Inherited strings.
|
||||
|
||||
EN/RU parity scanner должен остаться зелёным.
|
||||
|
||||
## 11. Touch
|
||||
|
||||
**Touch editor: supported для самого picker;** родительские редакторы остаются
|
||||
best effort / intentionally degraded по `docs/TOUCH-SUPPORT.md`.
|
||||
|
||||
Tap по любому новому trigger открывает ту же surface #57; pointer capture,
|
||||
multi-touch, `pointercancel`, viewport rotation и outside tap не создают
|
||||
дополнительного parent write. Родительский Save/Cancel остаётся safety boundary.
|
||||
|
||||
## 12. Критерии приёмки
|
||||
|
||||
### AC1 — полное source-покрытие
|
||||
|
||||
Все 15 полей из §4 используют `hp-color-opacity`; в `src/**/*.ts` отсутствует
|
||||
`input[type=color]` в любой допустимой Lit-форме. Уже мигрированные consumers #57
|
||||
продолжают использовать тот же component.
|
||||
|
||||
**Доказательство:** source-contract unit со scan всего `src/**/*.ts`, точным
|
||||
перечнем пяти новых template instances и regression count/allowlist всех
|
||||
component call sites.
|
||||
|
||||
### AC2 — opacity перенесена внутрь единой surface
|
||||
|
||||
Каждая из 11 FillColors и room color открывает picker с текущим color/opacity;
|
||||
изменение любого из них обновляет правильный draft. Отдельные alpha slider и
|
||||
процент этих строк отсутствуют, но сохранённое значение совпадает с picker.
|
||||
|
||||
**Доказательство:** unit/source contracts + browser smoke общего dialog и space
|
||||
dialog, включая две разные FillColors keys и room opacity 0/37/100%.
|
||||
|
||||
### AC3 — color-only не получает alpha
|
||||
|
||||
Ripple, global background и space background скрывают opacity row, меняют только
|
||||
hex color и не добавляют alpha в config/event projection.
|
||||
|
||||
**Доказательство:** DOM smoke `showOpacity=false`, save round-trip и negative
|
||||
schema/source assertions.
|
||||
|
||||
### AC4 — nullable/default/inherit остаются прежними
|
||||
|
||||
При `bgColor=null` trigger показывает effective color, но одно открытие/закрытие
|
||||
не материализует его. Первое изменение создаёт explicit value; Default/Inherit
|
||||
возвращает `null`. Parent Cancel не сохраняет ни один draft.
|
||||
|
||||
**Доказательство:** browser smoke global + space dialog с config snapshot до/
|
||||
после open, change, reset и Cancel/Save.
|
||||
|
||||
### AC5 — ripple и соседний size control независимы
|
||||
|
||||
Ripple picker меняет только `rippleColor`; ripple size slider остаётся видимым,
|
||||
keyboard/touch-доступным и сохраняет прежнее значение. Пустой fallback не
|
||||
материализуется без изменения.
|
||||
|
||||
**Доказательство:** marker-dialog browser smoke и config projection unit.
|
||||
|
||||
### AC6 — один overlay, keyboard и touch
|
||||
|
||||
В dialog с 11 swatches открытие второго picker закрывает первый; Escape/focus,
|
||||
outside tap, pointercancel, 390px/200% zoom и fallback portal соответствуют #57.
|
||||
Ни один trigger не вызывает системный picker.
|
||||
|
||||
**Доказательство:** расширенный `smoke_color_picker.mjs`, включая general
|
||||
settings multi-picker и representative mobile marker/space cases.
|
||||
|
||||
### AC7 — visual/theme contract
|
||||
|
||||
Общие настройки, marker activity и space appearance не имеют layout overflow
|
||||
или двойных labels в light/dark themes; default/inherit и ripple-size controls
|
||||
остаются читаемыми.
|
||||
|
||||
**Доказательство:** reviewed Linux golden минимум для general settings desktop,
|
||||
marker activity mobile и space appearance в противоположных темах.
|
||||
|
||||
### AC8 — совместимость и производительность
|
||||
|
||||
Persisted schema/values не меняются; новых dependencies/fetch нет. Открытие
|
||||
general settings с 11 component instances не создаёт long task и закрытый dialog
|
||||
не оставляет document listeners/overlays.
|
||||
|
||||
**Доказательство:** round-trip units, dependency diff, целевой smoke после
|
||||
close/reopen; existing performance smoke без нового regression threshold.
|
||||
|
||||
## 13. План автотестов
|
||||
|
||||
### Unit/source
|
||||
|
||||
1. Рекурсивный scan `src/**/*.ts` запрещает native `input[type=color]`.
|
||||
2. Все новые `hp-color-opacity` передают localized `pickerLabels`.
|
||||
3. Matrix фиксирует `showOpacity=true` только для FillColors/roomColor и `false`
|
||||
для ripple/global-bg/space-bg.
|
||||
4. Event handlers обновляют только ожидаемые draft keys и сохраняют alpha.
|
||||
5. Nullable effective color не материализуется без change event.
|
||||
6. Existing `color-picker.test.mjs` conversions/API остаются зелёными.
|
||||
|
||||
### Browser smoke
|
||||
|
||||
Расширить `demo/smoke_color_picker.mjs` либо добавить узкий companion, который
|
||||
проверяет:
|
||||
|
||||
- две строки general FillColors, alpha внутри picker и переключение overlays;
|
||||
- global background effective/default lifecycle;
|
||||
- marker ripple color-only + неизменный size;
|
||||
- space room color+opacity;
|
||||
- space background inherited/explicit/inherit;
|
||||
- Save/Cancel snapshots, keyboard/Escape, touch/pointercancel;
|
||||
- native/fallback floating surface и отсутствие системного input.
|
||||
|
||||
### Golden
|
||||
|
||||
- general settings с открытым picker, desktop light;
|
||||
- marker activity picker, mobile dark;
|
||||
- space appearance с room/background controls, desktop dark либо mobile light;
|
||||
- все расхождения принимаются только по полному reviewed Linux artifact перед
|
||||
бетой; существующие baselines #57 не обновляются автоматически.
|
||||
|
||||
### Реализационные гейты
|
||||
|
||||
В цикле: `npm run typecheck`, `npm test`, `npm run build`.
|
||||
|
||||
Перед `S7-code-review`: затронутый color-picker smoke и целевые golden capture/
|
||||
verify. Полные golden, smoke и performance остаются pre-beta gate. Backend tests
|
||||
не нужны, если diff действительно не затронет Python/schema.
|
||||
|
||||
## 14. Риски и меры
|
||||
|
||||
| Риск | Мера |
|
||||
|---|---|
|
||||
| Alpha теряется при замене двух controls одним | атомарный `{color, opacity}` handler + boundary tests |
|
||||
| Inherited fallback случайно сохраняется explicit | отсутствие change при open/close + snapshot smoke |
|
||||
| 11 pickers открываются одновременно | общий exclusive overlay controller #57 |
|
||||
| Label дублируется или trigger остаётся без имени | ровно один visible label + accessible-name assertion |
|
||||
| Marker row ломается из-за более крупного trigger | mobile golden + независимый size smoke |
|
||||
| Native picker возвращается новым call site | recursive source scan, не count одного файла |
|
||||
| Много component listeners остаётся после dialog | disconnect/reopen smoke и existing cleanup contract |
|
||||
|
||||
Производительность: одновременно существует максимум содержимое открытого
|
||||
родительского dialog; floating surface создаётся только у активного picker.
|
||||
Новых observers, subscriptions, animation loops и network requests нет.
|
||||
|
||||
## 15. Откат
|
||||
|
||||
Откат возвращает пять native templates и отдельные alpha slider. Поскольку
|
||||
модель данных и API сохранения не меняются, migration/downgrade действий нет.
|
||||
Сам picker #57 и уже существующие consumers остаются работоспособными.
|
||||
|
||||
## 16. Release-артефакты
|
||||
|
||||
User-visible implementation commit одновременно обновляет:
|
||||
|
||||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` со ссылкой на #180;
|
||||
- `docs/USER-GUIDE.ru.md` — единый picker во всех общих, space и marker color
|
||||
settings, включая opacity/default/inherit;
|
||||
- `docs/TESTING.md` — source invariant, smoke matrix и golden scenarios;
|
||||
- синхронные `dist`, integration frontend и demo bundle.
|
||||
|
||||
Перед бетой нужны reviewed golden artifacts для трёх новых dialog families и
|
||||
зелёный targeted smoke. Новых screenshots вне golden, security artifact,
|
||||
backend artifact или отдельный performance profile не требуется.
|
||||
|
||||
## 17. Принятые технические предположения
|
||||
|
||||
1. Пять новых source instances `hp-color-opacity` — один в
|
||||
`_renderColorRow()` и четыре прямых — являются предпочтительной структурой;
|
||||
helper можно выделить, если source-invariant и AC сохраняются.
|
||||
2. `opacity=1` у color-only consumer является presentation input и не должен
|
||||
попадать в parent draft/config.
|
||||
3. Layout-классы можно уточнить локально; публичными считаются поведение,
|
||||
accessible names и отсутствие overflow, а не DOM nesting.
|
||||
4. Existing `hp-color-opacity` не расширяется reset callback: default/inherit
|
||||
остаются parent-owned actions.
|
||||
5. Точные golden scenario names и pixel thresholds выбираются по Linux artifact,
|
||||
но обязаны отдельно доказать general settings, marker и space surfaces.
|
||||
@@ -111,6 +111,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#57](https://github.com/Matysh/houseplan-card/issues/57) Единый выбор цвета и прозрачности | [057-color-opacity-picker.md](057-color-opacity-picker.md) |
|
||||
| [#180](https://github.com/Matysh/houseplan-card/issues/180) Единый picker во всех местах выбора цвета | [180-all-color-call-sites.md](180-all-color-call-sites.md) |
|
||||
|
||||
## Правило актуализации
|
||||
|
||||
|
||||
Reference in New Issue
Block a user