Files
houseplan-card/docs/specs/204-space-create-display-defaults.md
T
2026-08-19 17:44:48 +00:00

183 lines
13 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.
# 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.