From e2e4cec1335e923e809d01110dd81dd55c3b06b2 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 29 Aug 2026 20:48:44 +0300 Subject: [PATCH] docs: specify decor default style persistence (#377) User-Visible: no Issue: #377 --- docs/specs/377-decor-default-persist.md | 137 ++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/specs/377-decor-default-persist.md diff --git a/docs/specs/377-decor-default-persist.md b/docs/specs/377-decor-default-persist.md new file mode 100644 index 00000000..96e9b544 --- /dev/null +++ b/docs/specs/377-decor-default-persist.md @@ -0,0 +1,137 @@ +# ТЗ #377 — Персист цвета декора по умолчанию в серверный конфиг + +- Issue: https://github.com/Matysh/houseplan-card/issues/377 +- Приоритет: P3, polish; полный трек (компатибилити-поле `settings.decor_default_style`, + кросс-модульный путь записи — критерий §5 «без новых персистентных полей» не проходит; + выделено из #376в по SPEC-REVIEW-376-r1 H1) +- Ревизия: 1 (2026-08-29) +- Продуктовое решение владельца (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`): `color`, `opacity`, `width_cm`, `fill`, + `fill_color`, `fill_opacity`. Все опциональны. + +## 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: не задеты (ключ не влияет на хуки).