mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
278 lines
18 KiB
Markdown
278 lines
18 KiB
Markdown
# Issue #294 — Esc завершает текущую цепочку стен без удаления геометрии
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/294
|
||
- **Статус:** редакция r2 после жёлтого ревью r1; канонический статус задаётся метками issue
|
||
- **Тип / приоритет:** feature + polish / P2
|
||
- **Область:** Plan editor, desktop keyboard, lifecycle активного wall draft,
|
||
подсказки RU/EN, targeted browser regression
|
||
- **Модель данных:** schema и model version не меняются
|
||
- **Связано:** #173; эта задача заменяет только прежний контракт `Esc`
|
||
|
||
## 1. Проблема
|
||
|
||
После каждого завершённого сегмента единый инструмент «Стены» сохраняет
|
||
crash-safe `room_draft`. Пока цепочка активна, её последний узел остаётся
|
||
якорем следующего сегмента. Сейчас `Esc` вызывает ту же операцию удаления
|
||
последней точки, что и локальный шаг назад: последний сегмент и соответствующая
|
||
толщина исчезают из draft и с плана.
|
||
|
||
Это не соответствует ожидаемой роли `Esc`: пользователь хочет закончить
|
||
текущий жест рисования, сохранить уже сделанное и начать следующую независимую
|
||
цепочку без переключения инструментов.
|
||
|
||
Эталон уже существует. Переход со «Стен» на «Колонну» вызывает штатный finish:
|
||
draft становится ordinary `partitions`, активный якорь очищается. Возврат на
|
||
«Стены» начинает новую цепочку. #294 даёт тот же результат одним `Esc`, но не
|
||
покидает инструмент «Стены».
|
||
|
||
## 2. Пользовательский результат
|
||
|
||
Для открытой цепочки `A–B–C` нажатие `Esc`:
|
||
|
||
1. оставляет на плане всю уже принятую геометрию `AB` и `BC` с её толщинами;
|
||
2. заканчивает текущую цепочку и снимает привязку к `C`;
|
||
3. оставляет выбранным инструмент «Стены»;
|
||
4. делает следующий клик `D` первой точкой новой цепочки;
|
||
5. создаёт новый сегмент только вторым кликом `E`, то есть `DE`, но не `C–D`.
|
||
|
||
`Ctrl/Cmd+Z` остаётся действием «убрать последнюю принятую точку/отрезок».
|
||
Таким образом, finish и undo больше не скрыты за одной клавишей.
|
||
|
||
## 3. Контракт Esc
|
||
|
||
### 3.1 Активная открытая цепочка с сегментами
|
||
|
||
Если Plan editor активен, выбран `draw`, диалог комнаты не открыт и в `_path`
|
||
есть не менее двух точек, `Esc` выполняет ровно штатное завершение wall chain:
|
||
|
||
- каждый сегмент materialize-ится как ordinary `partition` тем же кодом и с
|
||
теми же merge/limit правилами, что при смене инструмента;
|
||
- исходный активный `room_draft` удаляется, дубликат draft + partitions
|
||
недопустим;
|
||
- `_path`, `_activeDraftId`, `_draftSegmentCms`, closing state, snap-hover и
|
||
rubber-band preview очищаются;
|
||
- `_resumeDraftBySpace` не сохраняет ссылку на завершённый draft;
|
||
- `_tool` остаётся `draw`;
|
||
- используется существующая одна history/config transaction
|
||
`history.wall_chain_finish`; дополнительная команда для самого `Esc` не
|
||
создаётся.
|
||
|
||
Если штатный finish отклонён, например из-за лимита partitions, применяется
|
||
действующее сообщение об ошибке, цепочка остаётся активной и геометрия не
|
||
теряется. Визуально отцеплять не завершённую в storage цепочку запрещено.
|
||
|
||
### 3.2 Только первая точка
|
||
|
||
Если есть одна временная точка и нет завершённого сегмента, `Esc` очищает
|
||
точку, preview и snap state. Никакие `room_draft`, `partition`, history command
|
||
или config write не создаются; инструмент остаётся «Стены».
|
||
|
||
### 3.3 Нет активной цепочки
|
||
|
||
В `draw` без `_path` `Esc` не меняет геометрию, history и выбранный инструмент.
|
||
Повторное нажатие после успешного finish идемпотентно.
|
||
|
||
### 3.4 Приоритет верхних поверхностей
|
||
|
||
Действующая лестница Escape сохраняется:
|
||
|
||
- topmost dialog/editor surface обрабатывает клавишу первой;
|
||
- при открытом room proposal первый `Esc` отменяет весь ещё не применённый
|
||
batch и восстанавливает его terminal draft по контракту #173;
|
||
- этот же keydown не должен дополнительно завершить восстановленный draft;
|
||
- только следующий отдельный `Esc` вне диалога может превратить его в
|
||
partitions;
|
||
- resize/drag, physical selection и прочие инструменты сохраняют свои нынешние
|
||
обработчики Escape.
|
||
|
||
## 4. Undo, ввод и persistence
|
||
|
||
1. `Ctrl/Cmd+Z` на активной цепочке сохраняет текущий путь через
|
||
`_undoActiveDraftPoint` / `_undoPoint`: удаляет последнюю точку и сегмент,
|
||
не завершая всю цепочку.
|
||
2. `Ctrl/Cmd+Z` после `Esc` следует существующей history-семантике штатного
|
||
`wall_chain_finish`; #294 не добавляет второй undo step.
|
||
3. Завершённые по `Esc` partitions переживают save/reload как любые другие
|
||
законченные независимые стены и не возобновляются автоматически как draft.
|
||
4. Явный будущий клик по концу finished partition может использовать обычный
|
||
snap, но не является восстановлением старой chain session.
|
||
5. Pinch, pan, `pointercancel`, suppressed synthetic click и повторный выбор
|
||
уже активной кнопки «Стены» не получают семантику `Esc` и не завершают цепь.
|
||
|
||
## 5. Подсказки и документация
|
||
|
||
Новых i18n-ключей нет. Существующий `markup.hint_points` меняется синхронно:
|
||
|
||
- RU различает «Esc — завершить цепочку» и «Ctrl+Z — убрать точку»;
|
||
- EN различает “Esc — finish chain” и “Ctrl+Z — undo a point”.
|
||
|
||
`docs/USER-GUIDE.md` и `docs/USER-GUIDE.ru.md` должны явно описать тот же
|
||
контракт во всех местах, где сейчас обобщено старое поведение:
|
||
|
||
- RU, таблица поверхностей ввода «Рисование и точный drag»: отделить finish
|
||
цепочки «Стены» от отмены текущего precise drag;
|
||
- RU, таблица «Клавиши отмены»: разделить объединённый Draw/Split-контекст —
|
||
`Esc` завершает wall chain, а Split сохраняет свою действующую отмену;
|
||
- RU, «Комнаты и стены»: заменить «Esc/Ctrl+Z удаляет последнюю точку» двумя
|
||
разными действиями;
|
||
- EN, input-surface row “Draw or precise drag”: отделить Walls finish от
|
||
cancel текущего precise drag;
|
||
- EN, “Cancel and undo”: исключить wall chain из обобщения “Esc cancels an
|
||
unfinished path” и явно назвать сохранение принятой цепочки;
|
||
- EN, “Create a room”: добавить тот же явный паритет Esc/Ctrl+Z, который есть
|
||
в RU-разделе, а не оставлять новый контракт только в общей клавиатурной
|
||
памятке.
|
||
|
||
Поведение Escape у room dialog, Resize, Split и других операций остаётся
|
||
описано отдельно и не обобщается на wall chain.
|
||
|
||
Изменение пользовательское: implementation-коммит обновляет оба changelog.
|
||
|
||
## 6. Scope
|
||
|
||
### Входит
|
||
|
||
- desktop `Escape` для активной wall chain;
|
||
- повторное использование штатного finish без смены `_tool`;
|
||
- сохранение различия Esc и Ctrl/Cmd+Z;
|
||
- приоритет room dialog и безопасный отказ finish;
|
||
- RU/EN hint и обе версии руководства;
|
||
- targeted production-bundle browser smoke через настоящий `keydown`.
|
||
|
||
### Не входит
|
||
|
||
- новая кнопка «Завершить», обработка Enter или новая touch-команда;
|
||
- изменение face detection, room proposal, merge partitions или limit policy;
|
||
- изменение формата `room_drafts`, `partitions` либо history storage;
|
||
- изменение Escape у Resize, Columns, Openings, Boundary, Decor и диалогов;
|
||
- автоматическое продолжение finished partition как draft;
|
||
- переработка всех общих текстов про Escape вне затронутого Walls-контекста.
|
||
|
||
## 7. Acceptance criteria
|
||
|
||
### AC1. Esc сохраняет всю открытую цепочку (`smoke`)
|
||
|
||
Production-bundle smoke рисует `A–B–C` с различимыми толщинами `AB` и `BC`,
|
||
посылает настоящий `window` `keydown` с `key=Escape` и доказывает: обе оси и
|
||
толщины присутствуют в ordinary partitions, `room_draft` отсутствует,
|
||
`_path/_activeDraftId` очищены, preview отсутствует, `_tool === 'draw'`.
|
||
|
||
### AC2. Следующая цепочка независима (`smoke`)
|
||
|
||
После AC1 smoke кликает stage в `D`, затем `E` через production click path.
|
||
После `D` segment не появляется; после `E` появляется только `DE`. Ни
|
||
partition, ни draft с соединением `C–D` нет.
|
||
|
||
### AC3. Одна точка не создаёт геометрию (`smoke`)
|
||
|
||
На пустом плане первый клик создаёт только active anchor. Настоящий `Esc`
|
||
очищает anchor/preview, оставляет `draw`, не создаёт draft, partition, history
|
||
command или config write.
|
||
|
||
### AC4. Ctrl/Cmd+Z остаётся шагом назад (`smoke`)
|
||
|
||
На активной `A–B–C` настоящий `Ctrl/Cmd+Z` оставляет активной `A–B`, удаляет
|
||
только `BC` и одну запись толщины. Mutation, направляющая этот shortcut в finish,
|
||
обязана ломать проверку.
|
||
|
||
### AC5. Dialog precedence не регрессирует (`smoke`)
|
||
|
||
При открытом room proposal один `Esc` отменяет batch, оставляет terminal draft
|
||
активным и не создаёт partitions. Второй отдельный `Esc` завершает этот draft.
|
||
Закрытие dialog и finish — две разные keyboard transactions.
|
||
|
||
### AC6. Failure и повтор безопасны (`smoke` + ревью кода)
|
||
|
||
Если штатный `_finishWallChain()` возвращает `false`, `Esc` не очищает active
|
||
state и не меняет geometry/history. После успешного finish повторный `Esc`
|
||
ничего не добавляет, не удаляет и не возобновляет draft.
|
||
|
||
### AC7. Тексты совпадают с поведением во всех справочных местах (`unit` + ревью кода)
|
||
|
||
RU/EN `markup.hint_points`, обе user guide и оба changelog различают finish по
|
||
Esc и удаление точки по Ctrl/Cmd+Z. Ревью кода обязано проверить все шесть
|
||
конкретных поверхностей из §5:
|
||
|
||
1. `docs/USER-GUIDE.ru.md`: строка «Рисование и точный drag» не называет Esc
|
||
общей отменой; она отдельно говорит про finish «Стен» и cancel drag;
|
||
2. `docs/USER-GUIDE.ru.md`: объединённая строка «Незавершённый Draw/Split»
|
||
разделена, и у Walls больше нет текста «убирает последнюю точку» для Esc;
|
||
3. `docs/USER-GUIDE.ru.md`: «Комнаты и стены» различает `Esc` и `Ctrl+Z`;
|
||
4. `docs/USER-GUIDE.md`: строка “Draw or precise drag” не называет Esc общей
|
||
отменой, а различает Walls finish и cancel drag;
|
||
5. `docs/USER-GUIDE.md`: “Cancel and undo” исключает wall chain из “cancels an
|
||
unfinished path” и не обещает удалить принятую геометрию;
|
||
6. `docs/USER-GUIDE.md`: “Create a room” явно описывает Esc/Ctrl+Z с тем же
|
||
смыслом, что RU-раздел.
|
||
|
||
Старые противоречащие формулировки отсутствуют, i18n parity зелёный.
|
||
|
||
### AC8. Регрессии и локальные гейты (`unit` + `build`)
|
||
|
||
- `npm run typecheck`;
|
||
- `npm test`;
|
||
- `npm run build` и bundle parity;
|
||
- существующие wall-chain, room dialog, physical Escape и history тесты зелёные.
|
||
|
||
Targeted smoke пишется в реализации, но по принятому циклу исполняется вместе
|
||
со smoke-набором перед beta; golden и performance также остаются предрелизными.
|
||
|
||
## 8. Совместимость, touch, security и performance
|
||
|
||
Schema, model version и storage не меняются; миграция и compatibility-fields не
|
||
нужны. Сохранённые старые drafts читаются и завершаются штатным кодом.
|
||
|
||
Изменяется только desktop keyboard. Touch View и kiosk не затронуты, touch
|
||
editor остаётся best effort без новой команды finish; pan, pinch и
|
||
`pointercancel` сохраняют safety floor.
|
||
|
||
Новых HA actions, backend endpoint и security boundary нет. На один `Esc`
|
||
выполняется уже существующий O(n) finish активной цепочки; pointermove/render
|
||
hot path не меняется, performance budget не затрагивается.
|
||
|
||
## 9. Риски и меры
|
||
|
||
- **Потеря геометрии при ошибке finish.** AC6 требует сохранять active state при
|
||
`false` и использовать существующий limit toast.
|
||
- **Draft и partitions одновременно.** AC1 проверяет materialization и удаление
|
||
active draft одной transaction.
|
||
- **Скрытое продолжение от старой точки.** AC2 проверяет production click path,
|
||
а не только private state.
|
||
- **Двойное действие одного keydown после диалога.** AC5 фиксирует ранний return
|
||
и два отдельных нажатия.
|
||
- **Регресс Ctrl/Cmd+Z.** AC4 отдельно проверяет shortcut через реальный keydown.
|
||
- **Документация врёт после изменения.** AC7 включает обе локали и hint.
|
||
|
||
## 10. Откат
|
||
|
||
Откат — полный revert implementation-коммита #294 вместе с smoke, RU/EN hint,
|
||
руководствами и changelog. Schema и сохранённые данные не меняются, поэтому
|
||
миграция назад и восстановление конфигурации не нужны. После отката возвращается
|
||
контракт #173: Esc снова удаляет последнюю точку.
|
||
|
||
## 11. Ожидаемые файлы
|
||
|
||
- `src/houseplan-card.ts`;
|
||
- `src/i18n/en.json`, `src/i18n/ru.json`;
|
||
- `demo/smoke_unified_wall_tool.mjs`;
|
||
- `docs/USER-GUIDE.md`, `docs/USER-GUIDE.ru.md`;
|
||
- `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`.
|
||
|
||
Новый backend-код, config schema, golden и performance fixture не ожидаются.
|
||
|
||
## 12. Release
|
||
|
||
Implementation-коммит имеет терминальные трейлеры `Issue: #294` и
|
||
`User-Visible: yes`; оба changelog входят в тот же коммит. Issue не закрывается
|
||
вручную: она закрывается пачкой при выпуске beta.
|
||
|
||
## 13. Принятые предположения
|
||
|
||
1. «То же самое, что Колонна → Стены» означает тот же materialization/merge и
|
||
history contract, но без фактической смены инструмента.
|
||
2. `Esc` при единственной точке снимает пустой жест: сохранять нечего.
|
||
3. Открытый room dialog имеет больший приоритет, поэтому для его Cancel и
|
||
последующего finish нужны два нажатия.
|
||
4. Отказ из-за общего physical limit не позволяет отцепиться визуально от ещё
|
||
не завершённого draft.
|