Files
houseplan-card/docs/specs/294-wall-esc-detach.md
T
2026-08-24 18:16:46 +00:00

18 KiB
Raw Blame History

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.