From 15dc8adc4f5272953342975a098e00c81470d7a6 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Wed, 19 Aug 2026 20:11:53 +0300 Subject: [PATCH] docs: specify space creation display defaults Issue: #204 User-Visible: no --- .../204-space-create-display-defaults.md | 182 ++++++++++++++++++ docs/specs/README.md | 1 + 2 files changed, 183 insertions(+) create mode 100644 docs/specs/204-space-create-display-defaults.md diff --git a/docs/specs/204-space-create-display-defaults.md b/docs/specs/204-space-create-display-defaults.md new file mode 100644 index 00000000..f0416360 --- /dev/null +++ b/docs/specs/204-space-create-display-defaults.md @@ -0,0 +1,182 @@ +# Issue #204 — честные defaults границ и имён при создании пространства + +- Дата: 2026-08-19 +- Тип: bug · приоритет P2 +- Оценка: пользовательская ценность 7/10 · ценность для разработки 6/10 · сложность 4/10 · риск 5/10 +- Issue: [#204](https://github.com/Matysh/houseplan-card/issues/204) +- Ветка: `issue/204-space-create-display-defaults` + +Канонические документы: `docs/SCOPE.md`, `docs/UX-MODES.md`, +`docs/USER-GUIDE.ru.md`, `docs/TESTING.md`, `docs/specs/040-floor-area-onboarding.md`. + +## 1. Сценарий и персона + +Администратор создаёт пространство вручную либо последовательно заводит этажи +через Floors/Areas onboarding. В диалоге он выбирает источник «Рисовать комнаты +вручную», проверяет настройки отображения и сохраняет. Значения на экране должны +быть теми значениями, которые попадут в config и будут показаны при повторном +открытии. + +## 2. До и после + +**До:** оба переключателя — «Всегда отображать границы комнат» и «Отображать +названия комнат» — видимо выключены. Для `create + draw` Save игнорирует их и +принудительно записывает `true/true`. Повторное открытие показывает включённое +состояние, которого пользователь не выбирал. + +**После:** при выборе ручного пространства оба действующих product defaults +сразу видимо включены и именно они сохраняются. Для пространства с файлом +исходный default остаётся `false/false`. Если пользователь изменил хотя бы один +из двух переключателей, дальнейшие File ↔ Draw в том же открытом диалоге не +перезаписывают ни один из его выборов. + +## 3. Подтверждённая причина + +`_openSpaceDialog('create')` и `_openNextImport()` создают dialog state с +`source:'file'`, `showBorders:false`, `showNames:false`. Радиокнопки источника +меняют только `source`. `_saveSpaceDialog()` отдельно содержит скрытую ветку: + +```ts +show_borders: draw && d.mode === 'create' ? true : d.showBorders +show_names: draw && d.mode === 'create' ? true : d.showNames +``` + +Поэтому только `create + draw` нарушает round-trip. Create с файлом и Edit уже +сохраняют показанные значения; backend принимает явные boolean без подмены. +#203 исправляет runtime-смысл `show_names`, но не dialog defaults и не Save. + +## 4. Scope + +- сделать default-пару источника частью create-dialog state: File `false/false`, + Draw `true/true`; +- применять пару при переключении источника, пока пользователь не тронул ни + один из этих двух controls; +- после первого пользовательского изменения сохранять оба текущих значения при + любых следующих File ↔ Draw; +- сохранять в config ровно `d.showBorders` и `d.showNames` без скрытой ветки; +- одинаково применить контракт в обычном Create и Floors/Areas onboarding; +- проверить Save/reopen, Cancel и source-switch round-trip. + +## 5. Non-scope + +- изменение runtime resolver `spaceDisplayOf()` или render поведения #203; +- миграция уже созданных spaces и переписывание explicit/implicit settings; +- изменение defaults Edit, legacy spaces либо space с уже сохранёнными values; +- изменение `hide_decor`, `hide_openings`, fill/Glow, background, масштаба или + других полей диалога при source switch; +- добавление предупреждения, текста, reset-кнопки или отдельных preferences на + каждый source; +- изменение HA Floors/Areas registry или backend schema/API. + +## 6. Контракт dialog state + +Create state получает внутренний ephemeral признак `displayTouched` (имя может +быть другим). Он не сохраняется в config. + +1. Новый обычный Create и каждый новый шаг onboarding открываются как File с + видимой парой `false/false`, `displayTouched:false`. +2. Пока `displayTouched:false`, переход на Draw атомарно ставит `true/true`, а + переход на File — `false/false`. Повторные переходы воспроизводят default + выбранного source и не влияют на прочие поля. +3. Изменение любого из двух переключателей ставит `displayTouched:true`. Второй + control не меняется автоматически. +4. При `displayTouched:true` смена source меняет только `source`; текущая пара + сохраняется целиком. Это даёт в том числе намеренные `true/false` и + `false/true`. +5. Save для Create и Edit всегда записывает текущие dialog booleans. Значение + не выводится заново из наличия background. +6. Cancel/close уничтожает ephemeral state. Следующий Create снова начинает с + File `false/false`; следующий этаж onboarding получает собственный чистый + state и не наследует touch предыдущего. + +Выбор source и изменение control происходят синхронно в одном новом object +state, поэтому Lit не может отрисовать промежуточную ложную пару. + +## 7. Данные и совместимость + +Persisted schema не меняется: `settings.show_borders` и `settings.show_names` +уже boolean. Новые hand-drawn spaces по default по-прежнему получают +`true/true`; меняется только честность UI и возможность до Save выбрать другое. +Новые file spaces по default по-прежнему получают `false/false`. + +Existing spaces и imports/backups не мигрируются. Edit читает effective values +и сохраняет их как раньше; ephemeral touch нужен только create-mode. Downgrade +читает те же booleans. Нет нового model/version marker. + +## 8. UX, accessibility, touch и i18n + +Состав, порядок и тексты controls не меняются. Обновляются checked states двух +существующих HA switches. Keyboard, pointer и touch проходят один и тот же +`change` handler; focus не переносится, live announcement не требуется. Цвет не +является единственным сигналом — состояние остаётся нативно доступным через +checked/ARIA контракт control. Новых locale keys нет. + +## 9. Acceptance criteria + +| AC | Критерий | Доказательство | +|---|---|---| +| AC1 | Новый Create открывается как File с `false/false`; первый переход Draw показывает `true/true`. | Targeted dialog smoke + state assertions. | +| AC2 | Save сразу после выбора Draw сохраняет показанные `true/true`; reopen читает `true/true`. | Production-bundle smoke. | +| AC3 | Пользовательские `false/false`, `true/false` и `false/true` для Draw сохраняются без подмены и совпадают после reopen. | Table-driven smoke/unit seam. | +| AC4 | До touch File ↔ Draw переключает `false/false` ↔ `true/true`; после touch source меняется, а оба booleans остаются. | Source-switch matrix. | +| AC5 | Новый этап Floors/Areas onboarding следует той же матрице и не наследует touch/values предыдущего этажа. | Extended onboarding smoke. | +| AC6 | Create с файлом по default сохраняет `false/false`; Edit существующего пространства не меняет values при open/save. | Regression smoke. | +| AC7 | Cancel не пишет config; новый Create возвращает чистый default. | Save-call/config snapshot assertions. | +| AC8 | Остальные dialog fields побайтно сохраняются при source transitions; runtime #203 остаётся зелёным. | Focused state regression + existing smoke. | +| AC9 | Старый hidden Save override ловится мутантом. | Mutation gate. | +| AC10 | Рабочие gates зелёные. | typecheck, unit, build, targeted smokes; bundle copies identical. | + +## 10. План реализации и тестов + +Создание начального state и source transition следует вынести в маленький pure +helper либо эквивалентную общую функцию, чтобы обычный Create и `_openNextImport` +не расходились. Handlers двух переключателей помечают pair touched. Save удаляет +ветку `draw && create ? true : ...`. + +Основной targeted browser сценарий может расширить `demo/smoke_space_settings.mjs` +или быть выделен как `smoke_space_create_display_defaults.mjs`. Он проходит +default, source matrix, mixed values, Save/reopen, Cancel и onboarding. Unit +допустим для pure state helper; DOM/Save round-trip остаётся обязательным. + +Mutation entry восстанавливает принудительные `true` в Save либо отключает +touched guard и обязан падать на mixed-value/source-switch проверке. Golden не +нужен: checked и persisted booleans являются точным доказательством. Полный +smoke/golden/performance остаётся предрелизным gate. + +## 11. Риски и меры + +| Риск | Мера | +|---|---| +| Source switch затирает осознанный mixed choice | Один touched guard на пару, AC3/AC4. | +| Onboarding расходится с обычным Create | Общий initializer/transition и AC5. | +| Touch предыдущего этажа протекает дальше | Новый state на каждый `_openNextImport()`. | +| Исправление меняет существующий product default | Persisted default pairs остаются прежними. | +| Edit получает create-only reset | Mode matrix и AC6. | + +Производительность и security/privacy boundary не меняются: локальные boolean +transitions не добавляют I/O, registry access или HA service calls. + +## 12. Release-артефакты и rollback + +Изменение пользовательское. Implementation-коммит имеет `User-Visible: yes` и +включает: + +- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` со ссылкой на #204; +- `docs/USER-GUIDE.ru.md` — defaults File/Draw и сохранение выбора; +- `docs/TESTING.md` — source/touch/onboarding matrix; +- `docs/STATUS.md` — фактическую release-линию; +- targeted smoke, mutation entry и три синхронные bundle copies. + +Отдельные backend, schema/migration, i18n, screenshot/golden, security и +performance artifacts не нужны. Rollback — revert implementation-коммита; +данные остаются валидными, но вернётся скрытая Save-подмена для create+draw. + +## 13. Принятые предположения + +1. Один touched flag намеренно защищает пару: изменение одного control означает, + что dialog больше не вправе автоматически сбрасывать и второй. +2. Пока пользователь не менял controls, каждый source switch показывает default + текущего source; отдельная память values по источникам не требуется. +3. Internal helper/field names и выбор smoke-файла являются техническими + решениями при сохранении §6. +4. Продуктовых вопросов больше нет: Q1–Q2 приняты владельцем по defaults. diff --git a/docs/specs/README.md b/docs/specs/README.md index d4e28aa1..5d1a038e 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -100,6 +100,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#174](https://github.com/Matysh/houseplan-card/issues/174) Связанный виртуальный источник следует реальному контроллеру | [174-linked-virtual-light-controller.md](174-linked-virtual-light-controller.md) | | [#178](https://github.com/Matysh/houseplan-card/issues/178) Выбор сущности для действия «Переключить состояние» | [178-toggle-entity.md](178-toggle-entity.md) | | [#197](https://github.com/Matysh/houseplan-card/issues/197) Один junction-патч не гасит кладку всего плана | [197-junction-patch-fail-dark.md](197-junction-patch-fail-dark.md) | +| [#204](https://github.com/Matysh/houseplan-card/issues/204) Честные defaults границ и имён при создании пространства | [204-space-create-display-defaults.md](204-space-create-display-defaults.md) | | [#201](https://github.com/Matysh/houseplan-card/issues/201) Наследование толщины для атомарного участка стены | [201-atomic-thickness-lookup.md](201-atomic-thickness-lookup.md) | | [#203](https://github.com/Matysh/houseplan-card/issues/203) Выключение названий скрывает все подписи комнат | [203-hide-room-names.md](203-hide-room-names.md) | | [#186](https://github.com/Matysh/houseplan-card/issues/186) Безопасный остаток стены у торцов партиционного проёма | [186-partition-opening-jamb-margin.md](186-partition-opening-jamb-margin.md) |