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

10 KiB
Raw Blame History

ТЗ #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: не задеты (ключ не влияет на хуки).