10 KiB
ТЗ #377 — Персист цвета декора по умолчанию в серверный конфиг
- Issue: https://github.com/Matysh/houseplan-card/issues/377
- Приоритет: P3, polish; полный трек (компатибилити-поле
settings.decor_default_style, кросс-модульный путь записи — критерий §5 «без новых персистентных полей» не проходит; выделено из #376в по SPEC-REVIEW-376-r1 H1) - Ревизия: 2 (2026-08-29) — по SPEC-REVIEW-377-r1 (M1)
- Продуктовое решение владельца (29.08): цвет декора по умолчанию персистится в серверный конфиг.
Сценарий
Хозяин плана (персона «владелец дома», Background-редактор) подбирает под свой план фирменный цвет разметки — например, тёмно-красный для линий мебели-обводки — через пикер основного цвета в главном тулбаре (#360). Назавтра он открывает редактор снова, рисует новую линию — и она рисуется его тёмно-красным, а не заводским серо-синим. Тот же цвет видит второй редактор этого плана на другом устройстве.
Что человек увидит до и после
До: выбранный «цвет по умолчанию» живёт до перезагрузки страницы; после неё новые элементы снова заводского цвета. После: выбранный стиль по умолчанию переживает перезагрузку и общий для всех, кто редактирует этот план. Ничего нового в UI не появляется.
Проблема
_decorStyle — приватное in-memory поле карты (houseplan-card.ts:984,
{ ...DEFAULT_DECOR_STYLE }). Ни конфиг, ни браузерное хранилище не задействованы:
перезагрузка страницы молча выбрасывает выбор пользователя. Название #360
(«цвет по умолчанию») обещает большее.
Скоуп
- Новый опциональный ключ
settings.decor_default_styleв серверном конфиге. - Чтение при инициализации; запись при изменении стиля из UI.
- Валидация на бэкенде; юниты обеих сторон; смок; доки; ченджлоги.
Не-скоуп
- Новые элементы UI (пикеры уже существуют, #360).
- Per-space или per-user стиль (ключ один, глобальный, как
settings.bg_color). - Персист прочих сессионных предпочтений редактора (инструмент, зум и т.п.).
- Экспорт/импорт: ключ едет внутри
settingsштатно, отдельной обработки нет.
Контракт поведения
- Ключ отсутствует → поведение сегодняшнее:
_decorStyle = DEFAULT_DECOR_STYLE. - Ключ присутствует →
_decorStyle= мердж ключа поверхDEFAULT_DECOR_STYLE(неизвестные/отсутствующие поля берутся из дефолта; лишние поля игнорируются). - Изменение любого поля стиля из UI (пикер тулбара или контекстный трей —
оба уже пишут в один
_decorStyle) планирует запись settings с дебаунсом 1000 мс от последнего изменения: драг по палитре даёт одну запись. - Запись идёт существующим сериализованным путём
_writeConfig(houseplan-editor-runtime.ts:1764): expected_rev (#340), очередь записей, штатный conflict при гонке вкладок. Новый канал не создаётся. - Если текущий стиль равен
DEFAULT_DECOR_STYLEпо значению — ключ из settings удаляется (паттернbg_color/fill_colorsв_saveSettingsDialog: дефолт хранится отсутствием ключа). - Запись возможна только из editor-runtime; холодный View ключ читает, но никогда не пишет (класс #357 не задевается).
- Формат ключа настроек — snake_case; фронтовый
DecorStyle(src/editors/decor/types.ts:78) — camelCase. Явный маппинг (принято предположительно, по паттерну ручной конвертацииwidthCm → width_cmвsrc/editors/decor/geometry.ts:144):color↔color,opacity↔opacity,width_cm↔widthCm,fill↔fill,fill_color↔fillColor,fill_opacity↔fillOpacity. Все поля ключа опциональны; конвертация — единственная точка (одна пара функций toSettings/fromSettings), чтобы AC2 доказывал мердж всех шести полей.
UX
Видимых изменений нет. Единственная новая наблюдаемая вещь — сохранённый цвет после перезагрузки. Ошибка записи показывается существующим тостом конфликта ревизий (#340), нового текста нет.
Модель данных и миграция
custom_components/houseplan/validation.py, settings-схема (~:1883):vol.Optional("decor_default_style"): vol.Schema({vol.Optional("color"): _COLOR, vol.Optional("opacity"): 0..1, vol.Optional("width_cm"): vol.All(vol.Coerce(float), vol.Range(min=0.1, max=100)), vol.Optional("fill"): bool, vol.Optional("fill_color"): _COLOR, vol.Optional("fill_opacity"): 0..1}).- Миграции нет: ключ опционален, старые конфиги валидны без него, старый
бэкенд при откате терпит лишний ключ (settings-схема —
ALLOW_EXTRA, проверено SPEC-REVIEW-376-r1). import_export.py: settings переносится целиком — отдельной правки нет, но юнит это доказывает (AC6).
i18n
Новых строк нет. Проверка — существующий паритет-гейт словарей.
Критерии приёмки
- AC1 (юнит бэкенда, pytest): settings с валидным
decor_default_styleпроходит; каждое поле с мусором (цвет не-цвет, opacity 2, width_cm 0) режется ошибкой валидации. - AC2 (юнит фронта): конфиг с ключом →
_decorStyle= мердж поверхDEFAULT_DECOR_STYLE; частичный ключ (толькоcolor) наследует остальные поля дефолта. - AC3 (юнит фронта): конфиг без ключа →
DEFAULT_DECOR_STYLE(легаси). - AC4 (смок): смена цвета в тулбаре → ровно одна запись config/set после
дебаунса, несущая
decor_default_style; серия быстрых изменений (драг) → по-прежнему одна запись; выставление стиля обратно в дефолт → запись БЕЗ ключа. - AC5 (смок, продолжение AC4): пересоздание карты на конфиге из AC4 → новый нарисованный объект несёт сохранённый цвет.
- AC6 (юнит): экспорт→импорт конфига с ключом сохраняет ключ байт-в-байт.
- AC7 (гейт): полный гейт зелёный; бюджет ≈ без изменений; в холодном View (launchColdView) изменение недостижимо — записи нет (доказательство: смок AC4 работает только после входа в редактор).
План автотестов
- pytest: AC1 (валидация), AC6 (import/export) — в существующих файлах тестов валидации/импорта.
- node --test: AC2, AC3 (инициализация; файл тестов карты/декора).
- смок
demo/smoke_decor_default_persist.mjs(новый): AC4+AC5 на моковом config/set; плюс негативная ветка AC7. - Мутанты (mutation-gate): м1 — вырезать чтение ключа при инициализации → красный AC2/AC5; м2 — вырезать дебаунс (запись на каждый ввод) → красный AC4.
Риски
- Гонка двух вкладок: штатный conflict #340; дебаунс сужает окно. Риск низкий.
- Захламление settings при частой смене: дебаунс + удаление ключа при дефолте.
- Обратная совместимость: ключ опционален в обе стороны (см. «Модель данных»).
Откат
git revert реализационных коммитов. Уже записанный decor_default_style
остаётся в конфиге и игнорируется старым фронтом; бэкенд после отката схемной
правки терпит ключ через ALLOW_EXTRA (пока владелец не решит вычистить его
следующим сохранением настроек — паттерн weather_entity). Потери данных нет.
Release-артефакты
- CHANGELOG.md + CHANGELOG.ru.md: user-visible запись со ссылкой #377.
- USER-GUIDE(.ru): одно предложение в разделе Background про сохранение выбранного стиля.
- docs/STYLING-HOOKS.md / ARCHITECTURE.md: не задеты (ключ не влияет на хуки).