5.6 KiB
ТЗ #21 — безопасные пользовательские цвета
- Issue: https://github.com/Matysh/houseplan-card/issues/21
- Приоритет: P2
- Статус: реализовано; целевой gate v1.61.0-beta.1 пройден, ожидается публикация
- Блокирует цветовые части #50 и #56; SVG sanitization из #51 остаётся отдельной задачей
Проблема
Новые записи проходят backend-валидацию, но старый, импортированный или вручную
изменённый store может попасть во frontend без повторной проверки. Подтверждённый
разрыв: marker.ripple_color доходил до CSS custom property как часть строки
inline-style. Lit не позволяет выйти из HTML-атрибута, но строка вида
red;position:fixed могла добавить соседние CSS declarations.
Единый контракт
Все сохраняемые цвета Houseplan имеют только вид #RRGGBB:
- ровно
#и шесть шестнадцатеричных цифр; - регистр букв любой;
- без пробелов, короткого
#RGB, имени цвета,rgb(),var(),url()и других CSS-функций; - неверное значение не исправляется и не записывается автоматически: при чтении используется безопасный штатный fallback.
Это соответствует уже существующим color picker и backend-схеме. Расширять форматы цвета в рамках #21 не требуется.
Реализация
Frontend
Один pure helper safeStoredColor(value, fallback) является общей границей для
всех цветов из сохранённой конфигурации:
- цвет и фон пространства;
- глобальный фон и палитра заливок, включая стены и Glow;
- обводка и заливка декоративных объектов и текста;
marker.ripple_color;- компонент выбора цвета.
Проверка выполняется при проекции данных для рендера, поэтому защищает и старый store, записанный до этого правила. Невалидный пользовательский ripple не должен перекрывать безопасный динамический цвет лампы.
HA может отдавать rgb_color. Это не сохраняемый пользовательский цвет:
приложение принимает три конечных числа, ограничивает каждый канал диапазоном
0–255, округляет и само создаёт каноническую строку rgb(R, G, B). На последней
границе inline-style разрешены только строгий #RRGGBB и именно такой
сгенерированный rgb().
Backend
Все поля цвета используют один _COLOR = ^#[0-9a-fA-F]{6}$. Поведение API не
меняется: корректный hex принимается, остальные формы отклоняются.
Совместимость
- валидные планы визуально не меняются;
- невалидный старый цвет заменяется только в runtime на штатный fallback;
- исходный config не мутируется при чтении;
- при следующем обычном редактировании color picker показывает безопасный fallback, а сохранение записывает валидный цвет.
Обязательные проверки перед пре-релизом
- Unit corpus: валидный mixed-case hex; short hex, whitespace, named/rgb,
;, braces, comment, backslash, newline,url(), overlong и non-string. - Проекция устройства: hostile
ripple_colorзаменяется динамическим цветом включённой RGB-лампы либоnull, если такого цвета нет. - Последняя style-граница не выводит hostile ripple declaration.
- Decor/space/fill resolvers используют fallback и не меняют входной объект.
- Backend одинаково отклоняет hostile значение во всех цветовых полях.
- Browser smoke на импортированном hostile config: нет overlay, внешнего запроса, новой style declaration и ошибки рендера.
Критерии приёмки
- Ни одно значение цвета из store не попадает в CSS/SVG без общей проверки.
- Все новые записи принимают только
#RRGGBB. - Динамический цвет HA остаётся рабочим и не может содержать произвольный CSS.
- Визуальное поведение валидных существующих планов не изменено.
- Целевые frontend/backend тесты и браузерные regression smokes выполнены в gate v1.61.0-beta.1; issue закрывается после зелёного exact-SHA CI и проверки release assets.