Files
houseplan-card/docs/specs/377-decor-default-persist.md
T
2026-08-29 21:00:00 +03:00

143 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ #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` штатно, отдельной обработки нет.
## Контракт поведения
1. Ключ отсутствует → поведение сегодняшнее: `_decorStyle = DEFAULT_DECOR_STYLE`.
2. Ключ присутствует → `_decorStyle` = мердж ключа поверх `DEFAULT_DECOR_STYLE`
(неизвестные/отсутствующие поля берутся из дефолта; лишние поля игнорируются).
3. Изменение любого поля стиля из UI (пикер тулбара или контекстный трей —
оба уже пишут в один `_decorStyle`) планирует запись settings с дебаунсом
**1000 мс** от последнего изменения: драг по палитре даёт одну запись.
4. Запись идёт существующим сериализованным путём `_writeConfig`
(`houseplan-editor-runtime.ts:1764`): expected_rev (#340), очередь записей,
штатный conflict при гонке вкладок. Новый канал не создаётся.
5. Если текущий стиль равен `DEFAULT_DECOR_STYLE` по значению — ключ из settings
удаляется (паттерн `bg_color`/`fill_colors` в `_saveSettingsDialog`:
дефолт хранится отсутствием ключа).
6. Запись возможна только из editor-runtime; холодный View ключ читает, но
никогда не пишет (класс #357 не задевается).
7. Формат ключа настроек — 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: не задеты (ключ не влияет на хуки).