mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 06:38:57 +00:00
460 lines
35 KiB
Markdown
460 lines
35 KiB
Markdown
# Issue #138 — автозамыкание комнаты по существующей стене
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/138
|
||
- **Редакция:** r2 после High из `docs/reviews/SPEC-REVIEW-138-r1.md`;
|
||
статус определяется только метками issue
|
||
- **Тип / приоритет:** bug / P1
|
||
- **Оценка:** пользовательская ценность 9/10; ценность для разработки 7/10;
|
||
сложность 5/10; риск 7/10
|
||
- **Область:** редактор Плана, инструмент «Контур комнаты», архитектурная
|
||
endpoint/line-привязка #137, замыкание и сохранение room draft
|
||
- **Модель данных:** без новых полей, миграции и backend-изменений
|
||
- **Связано:** #137, `docs/SCOPE.md`, `docs/ARCHITECTURE.md`,
|
||
`docs/CANVAS.md`, `docs/WALL-THICKNESS.md`, `docs/TOUCH-SUPPORT.md`
|
||
|
||
## 1. Сценарий и продуктовый контекст
|
||
|
||
**Персона:** администратор дома, который дорисовывает соседнее помещение в
|
||
редакторе Плана.
|
||
|
||
**Поверхность:** desktop browser с мышью или точным pointer. Touch editor остаётся
|
||
best effort по `docs/TOUCH-SUPPORT.md`.
|
||
|
||
**Момент:** пользователь начинает новый контур на углу либо середине стены
|
||
существующей комнаты, обходит новое помещение и заканчивает контур на другом месте
|
||
той же непрерывной стены.
|
||
|
||
Задача поддерживает:
|
||
|
||
- **J4:** план можно точно нарисовать встроенным GUI без ручного редактирования SVG;
|
||
- **J6:** соседние помещения используют общую границу без микрозазоров и лишних
|
||
обходных действий.
|
||
|
||
## 2. Что человек увидит до и после
|
||
|
||
**До:** #137 точно привязывает финальный клик к существующему углу или линии, но
|
||
редактор сохраняет его как ещё одну точку открытого draft. Диалог новой комнаты не
|
||
открывается, хотя последний и первый узлы уже однозначно соединены существующей
|
||
сплошной стеной.
|
||
|
||
**После:** если первая и последняя точки нового контура лежат на одном непрерывном
|
||
сплошном интервале существующей стены, финальный клик сразу использует участок этой
|
||
стены как замыкающее ребро и открывает обычный диалог новой комнаты.
|
||
|
||
## 3. Причина дефекта
|
||
|
||
`_resolvePlanDrawPoint()` уже возвращает точную endpoint- или line-snap координату,
|
||
но `_markupClick()` считает контур замкнутым только при клике в собственную первую
|
||
точку либо при `Ctrl`/`Cmd` + click. При клике в другую точку существующей стены
|
||
результат snap добавляется в `_path` и сохраняется в `room_drafts`, после чего
|
||
рисование остаётся открытым.
|
||
|
||
Это не ошибка точности #137: отсутствует продуктовый контракт, связывающий два узла
|
||
одного существующего сплошного wall interval с неявным замыкающим ребром.
|
||
|
||
## 4. Решения владельца
|
||
|
||
Владелец подтвердил 2026-08-14 все предложенные defaults:
|
||
|
||
1. Автозамыкание действует только тогда, когда первая и последняя точки лежат на
|
||
одном прямом каноническом ребре существующей комнаты. Допустимы исходные
|
||
endpoints/углы и промежуточные line-snap точки. Разные рёбра одной комнаты не
|
||
подходят.
|
||
2. Door/window/gate и `open_span` разрывают допустимую стену. Первая и последняя
|
||
точки должны принадлежать одному непрерывному видимому сплошному интервалу #137.
|
||
3. Подходящий финальный клик сразу добавляет существующий интервал как неявное
|
||
замыкающее ребро и открывает стандартный диалог новой комнаты, без отдельного
|
||
подтверждения.
|
||
4. Новая визуальная индикация и новые строки не нужны: используются увеличенная
|
||
snap-точка, live-preview и существующий диалог.
|
||
5. Приоритет задачи — P1.
|
||
|
||
## 5. Scope
|
||
|
||
В issue входят:
|
||
|
||
1. Распознавание подходящего финального клика инструмента «Контур комнаты» после
|
||
актуального endpoint/line resolution #137.
|
||
2. Проверка принадлежности первой и финальной точек одному room-owned solid
|
||
interval из общего архитектурного snap snapshot.
|
||
3. Автозамыкание валидного контура существующим участком стены и открытие обычного
|
||
диалога комнаты.
|
||
4. Сохранение существующих validation, draft, per-segment thickness, history,
|
||
Cancel/Save и Undo/Redo контрактов.
|
||
5. Unit и production-bundle smoke для положительных, отрицательных и touch-safety
|
||
сценариев.
|
||
6. Обновление пользовательской и архитектурной документации.
|
||
|
||
## 6. Non-scope
|
||
|
||
В issue не входят:
|
||
|
||
- автозамыкание между разными рёбрами одной или разных комнат;
|
||
- поиск пути по нескольким коллинеарным либо угловым стенам;
|
||
- автозамыкание по `room_drafts`, `partitions`, колоннам, декору или подложке;
|
||
- проведение замыкающего ребра через дверь, окно, ворота или виртуальную границу;
|
||
- автоматическое дробление, переписывание или удаление существующей стены;
|
||
- новая кнопка, настройка, toast, анимация, цвет или i18n-ключ;
|
||
- изменение endpoint/line hit radius, приоритета и визуального слоя #137;
|
||
- исправление старой off-grid геометрии;
|
||
- новая schema, backend API, storage key, импорт/экспорт или миграция;
|
||
- полный hover-паритет редактора на coarse pointer.
|
||
|
||
## 7. Контракт распознавания
|
||
|
||
### 7.1 Порядок разрешения клика
|
||
|
||
Финальный click/tap сначала повторно получает авторитетную точку через действующий
|
||
resolver #137. Hover-кандидат не используется как сохранённое обещание. Проверка
|
||
автозамыкания выполняется до обычного добавления точки в draft и до `_draftEndAt()`,
|
||
чтобы существующий endpoint draft не перехватил подходящий клик.
|
||
|
||
Существующие явные варианты имеют приоритет и не меняются:
|
||
|
||
1. клик в собственную первую точку замыкает контур как сейчас;
|
||
2. `Ctrl`/`Cmd` + click выполняет существующее быстрое замыкание;
|
||
3. только отличный от первой точки обычный клик, после добавления которого в
|
||
prospective path будет не меньше трёх вершин, проверяется как новое
|
||
автозамыкание по существующей стене.
|
||
|
||
Повторный клик в текущий anchor остаётся no-op и не создаёт нулевой сегмент.
|
||
Если в path пока находится только первая точка `A`, второй клик `B` не считается
|
||
попыткой автозамыкания даже при общем solid interval `A—B`: он без validation
|
||
toast добавляется в draft обычным способом. Это тот же минимальный gate, который
|
||
уже защищает ручное замыкание кликом в первую точку.
|
||
|
||
### 7.2 Допустимый существующий интервал
|
||
|
||
После прохождения gate минимального числа вершин автозамыкание разрешено, если
|
||
существует хотя бы один сегмент текущего архитектурного snap snapshot, который
|
||
одновременно:
|
||
|
||
- имеет `sourceKind: room` и происходит из завершённого контура комнаты;
|
||
- является одним прямым сплошным интервалом после канонических cuts #137;
|
||
- содержит первую точку нового контура;
|
||
- содержит финальную resolved-точку нового контура;
|
||
- имеет положительную длину, а две точки на нём различны.
|
||
|
||
«Содержит» означает коллинеарность и положение внутри закрытого интервала с
|
||
геометрической точностью существующих pure helpers, а не визуальную близость в CSS
|
||
pixels. Если угол принадлежит двум стенам, достаточно одного общего подходящего
|
||
интервала для первой и финальной точки. Стабильный порядок решает технические ties,
|
||
не меняя полученную линию замыкания.
|
||
|
||
`room_draft` и `partition` остаются видимыми snap-кандидатами #137, но не могут
|
||
стать неявной границей новой комнаты.
|
||
|
||
### 7.3 Проёмы и виртуальные участки
|
||
|
||
Используется уже разрезанная геометрия #137:
|
||
|
||
- door/window/gate и `open_span` удаляются из room axis до проверки;
|
||
- граница cut не становится новым постоянным endpoint;
|
||
- точки по разные стороны любого cut не принадлежат одному solid interval и не
|
||
запускают автозамыкание;
|
||
- точка внутри cut не получает line-snap, как и до этой задачи.
|
||
|
||
Новый код не вводит второй resolver проёмов или виртуальных границ.
|
||
|
||
## 8. Контракт замыкания и валидации
|
||
|
||
### 8.1 Успешный финальный клик
|
||
|
||
Пусть `A` — первая точка draft, `P` — его текущий конец, `B` — отличный от `A`
|
||
resolved-финал на общем solid interval `A—B`.
|
||
|
||
Проверка автозамыкания начинается только когда до клика path уже содержит минимум
|
||
две вершины, поэтому добавление `B` создаёт prospective path минимум из трёх
|
||
вершин. Для подходящего клика редактор рассматривает prospective polygon
|
||
`[..., P, B] + B—A` и требует минимум три различные вершины. До пользовательского
|
||
диалога он применяет те же проверки минимального размера, нулевой площади,
|
||
self-intersection, overlap и общих geometry limits, что и обычное замыкание.
|
||
|
||
Если контур валиден, одним пользовательским действием:
|
||
|
||
1. `B` становится настоящей последней вершиной нового контура;
|
||
2. нарисованный сегмент `P—B` сохраняется как обычный завершённый draft segment с
|
||
выбранной для него толщиной;
|
||
3. `B—A` считается замыкающим ребром комнаты;
|
||
4. открывается существующий диалог имени/HA-зоны комнаты.
|
||
|
||
Отдельного confirm, toast об успехе или промежуточного UI нет.
|
||
|
||
### 8.2 Невалидный prospective contour
|
||
|
||
Если при достаточном количестве вершин подходящий клик дал невалидный prospective
|
||
polygon:
|
||
|
||
- диалог комнаты не открывается;
|
||
- `B`, `P—B` и `B—A` не записываются;
|
||
- draft остаётся в том же редактируемом состоянии, что до клика;
|
||
- показывается существующий подходящий validation toast;
|
||
- config, layout и history не получают частично применённой операции.
|
||
|
||
Клик не превращается после ошибки в обычное добавление `B`: подходящая точка уже
|
||
означает намерение замкнуть контур, как клик в собственную первую точку.
|
||
Недостаточное количество вершин не является такой ошибкой: оно отсекается до
|
||
eligibility и обрабатывается как обычный клик по §8.3 без toast.
|
||
|
||
### 8.3 Обычный клик
|
||
|
||
Если общего room-owned solid interval нет либо после добавления resolved-точки в
|
||
path всё ещё будет меньше трёх вершин, поведение остаётся полностью текущим:
|
||
resolved-точка добавляется в открытый draft, сегмент сохраняется, а диалог не
|
||
открывается. Это относится в том числе к:
|
||
|
||
- второму клику `B` после единственной первой точки `A`, даже если `A` и `B` —
|
||
разные endpoints одной существующей стены;
|
||
- разным рёбрам одной комнаты;
|
||
- разным комнатам;
|
||
- точкам по разные стороны opening/open-span cut;
|
||
- `room_draft` и `partition`;
|
||
- обычной точке сетки рядом со стеной.
|
||
|
||
После такого клика пользователь по-прежнему может явно замкнуть контур первой
|
||
точкой или `Ctrl`/`Cmd` + click.
|
||
|
||
## 9. Draft, диалог, история и толщина
|
||
|
||
- Каждый явно нарисованный сегмент до и включая `P—B` сохраняет текущую толщину в
|
||
`room_drafts` по существующему контракту.
|
||
- Неявный `B—A` не создаёт independent partition или отдельную физическую запись.
|
||
Он становится room-boundary edge только при сохранении комнаты.
|
||
- **Save** удаляет соответствующий draft и создаёт обычную комнату через текущий
|
||
commit/history boundary.
|
||
- **Cancel** стандартного диалога снимает состояние замкнутого контура, но оставляет
|
||
открытый draft с последней настоящей точкой `B` и сегментом `P—B`, как при
|
||
существующем ручном замыкании.
|
||
- **Оставить замкнутыми стенами** продолжает использовать текущий путь конвертации
|
||
рёбер в independent partitions.
|
||
- Undo/Redo, смена инструмента, reload/resume, external config adoption и лимит
|
||
50 команд не получают отдельной ветки поведения.
|
||
- На общей границе `B—A` сохраняется уже существующая толщина соседней комнаты.
|
||
Выбранная толщина рисования применяется только к новым внешним участкам по
|
||
действующему `applyWallThicknessToNewRoom`-контракту.
|
||
- Room-boundary walls остаются производными и дедуплицированными. Автозамыкание не
|
||
создаёт вторую физическую стену поверх общей границы.
|
||
|
||
## 10. Touch, accessibility и визуальная деградация
|
||
|
||
**Touch editor: best effort / intentionally degraded.**
|
||
|
||
- Tap без предварительного pointermove повторно решает snap и выполняет тот же
|
||
контракт автозамыкания.
|
||
- Hover или увеличенная точка до tap на no-hover устройстве не обещаются.
|
||
- Pinch, pan, pointercancel, второй touch и suppressed synthetic click не должны
|
||
замыкать или менять draft.
|
||
- Новый DOM, focus target, ARIA-содержимое или клавиатурная команда не создаются.
|
||
- View и kiosk не меняют pixels, gestures или действия.
|
||
|
||
## 11. Модель данных, совместимость и миграция
|
||
|
||
Новых данных нет.
|
||
|
||
- `rooms[].poly`, `room_drafts`, `walls`, `openings` и `open_spans` сохраняют
|
||
текущую схему;
|
||
- старые и импортированные планы читаются без миграции;
|
||
- backend validation и integration API не меняются;
|
||
- downgrade не требует data rollback: созданная комната является обычным polygon;
|
||
- новая версия меняет только момент интерпретации подходящего финального клика.
|
||
|
||
## 12. Архитектурный и performance-контракт
|
||
|
||
1. Проверка общего интервала является чистой геометрической операцией над
|
||
существующим immutable snap snapshot; SVG DOM не является источником данных.
|
||
2. Canonical opening/open-span cuts переиспользуются из `plan-snap-overlay.ts` или
|
||
эквивалентного общего helper, без дублирования wall topology.
|
||
3. Проверка выполняется только на click/tap. Она не добавляет работу в pointermove,
|
||
не пересобирает статическую геометрию и не меняет O(E) overlay DOM #137.
|
||
4. Линейный O(S) просмотр room-owned solid segments на click допустим. Новый
|
||
постоянно растущий cache или spatial index не требуется.
|
||
5. Prospective validation завершается до mutation. Успех использует существующие
|
||
draft/save/history границы; ошибка не требует rollback частичного состояния.
|
||
6. Решение не создаёт websocket, HA service, fetch, timer, storage key или внешнюю
|
||
зависимость.
|
||
|
||
## 13. Acceptance criteria
|
||
|
||
- **AC1 (`unit` + `smoke`; разработчик):** контур, начатый в endpoint `A`
|
||
завершённой room wall, после обхода нового помещения заканчивается в другом
|
||
endpoint `B` того же непрерывного solid interval; финальный click сохраняет
|
||
`P—B`, замыкает polygon через `B—A` и сразу открывает стандартный room dialog.
|
||
- **AC2 (`unit` + `smoke`; разработчик):** тот же результат получается, когда `A`,
|
||
`B` или обе точки являются line-snap точками внутри одного solid room interval;
|
||
сохранённые координаты остаются точно на существующей стене.
|
||
- **AC3 (`unit` + `smoke`; разработчик):** если path содержит только первую точку
|
||
`A`, второй click в отличный endpoint/line-node `B` того же solid room interval
|
||
не запускает автозамыкание и validation: `B` добавляется как обычная точка
|
||
открытого draft без dialog и toast, после чего контур можно продолжить.
|
||
- **AC4 (`unit` + `smoke`; разработчик):** разные рёбра одной комнаты, стены разных
|
||
комнат, saved draft и partition не запускают автозамыкание: финальная точка
|
||
остаётся обычной точкой открытого draft, а явное замыкание первой точкой и
|
||
`Ctrl`/`Cmd` продолжают работать.
|
||
- **AC5 (`unit` + `smoke`; разработчик):** door/window/gate или `open_span` между
|
||
`A` и `B` разрывает общий interval и не допускает автозамыкание; cut boundary не
|
||
становится новым endpoint и отдельный resolver проёмов не появляется.
|
||
- **AC6 (`unit` + `smoke`; разработчик):** при достаточном числе вершин zero-area,
|
||
self-intersecting, overlapping, слишком маленький или выходящий за лимиты
|
||
prospective contour не
|
||
открывает диалог, показывает существующий validation toast и оставляет draft,
|
||
config и history в состоянии до финального клика.
|
||
- **AC7 (`unit` + `smoke`; разработчик):** успешный финальный `P—B` сохраняет
|
||
выбранную толщину; Cancel оставляет открытый draft с `B`, Save создаёт комнату и
|
||
удаляет draft, а secondary action создаёт замкнутые стены по текущему контракту.
|
||
- **AC8 (`unit` + wall-thickness regression; разработчик):** `B—A` использует
|
||
существующую толщину общей стены, новые внешние рёбра сохраняют свои per-segment
|
||
значения, room geometry не создаёт duplicate partition или двойное физическое
|
||
wall body.
|
||
- **AC9 (`unit` + code review; разработчик/ревьюер):** собственная первая точка и
|
||
`Ctrl`/`Cmd` имеют прежний приоритет, current anchor остаётся no-op, а eligibility
|
||
решается до resume/draft endpoint handling.
|
||
- **AC10 (`smoke`; разработчик):** tap без hover выполняет тот же результат; pan,
|
||
pinch, pointercancel и suppressed synthetic click не меняют geometry. View,
|
||
kiosk, остальные Plan tools и другие editors не меняются.
|
||
- **AC11 (`unit` + performance review; разработчик/ревьюер):** общий interval
|
||
определяется из уже кэшированного snapshot чистым O(S) helper только на click;
|
||
pointermove, cache size, DOM count и network/storage activity #137 не растут.
|
||
- **AC12 (`typecheck` + `unit` + `build` + documentation review; разработчик):**
|
||
implementation-loop gates зелёные, три bundle-копии побайтно одинаковы,
|
||
пользовательская документация и оба changelog обновлены в том же видимом
|
||
коммите.
|
||
- **AC13 (`schema/security review`; ревьюер):** backend, schema, import/export,
|
||
i18n, HA permissions/calls и зависимости не меняются; старые планы совместимы.
|
||
|
||
## 14. План автотестов
|
||
|
||
### 14.1 Unit
|
||
|
||
Добавить pure-helper покрытие:
|
||
|
||
1. оба endpoints одного room segment дают общий interval;
|
||
2. endpoint + interior point и две interior line-snap точки дают общий interval;
|
||
3. при path `[A]` второй click `B` на том же room interval не вызывает eligibility,
|
||
dialog или toast, а добавляет `B` и `A—B` в открытый draft;
|
||
4. угол, принадлежащий двум рёбрам, соединяется только с точкой на одном из них;
|
||
5. разные рёбра/rooms, draft и partition возвращают отсутствие eligibility;
|
||
6. opening/open-span cut разделяет исходную прямую на разные intervals;
|
||
7. reversed segment, floating tolerance и stable tie дают тот же результат;
|
||
8. одинаковые `A/B`, zero-length и current anchor не подходят;
|
||
9. prospective polygon валидируется до mutation для success и каждого класса
|
||
существующей ошибки;
|
||
10. shared-wall thickness и внешние per-segment thickness сохраняются.
|
||
|
||
Каждый тест должен падать отдельно при ослаблении `sourceKind`, игнорировании cut,
|
||
смешивании разных рёбер, добавлении точки до validation или перезаписи толщины.
|
||
|
||
### 14.2 Targeted browser smoke
|
||
|
||
Расширить `demo/smoke_plan_snap_overlay.mjs` либо добавить отдельный
|
||
production-bundle smoke:
|
||
|
||
1. создать существующую комнату, opening/open span, draft и partition;
|
||
2. начать `A→B` по двум точкам одной существующей стены и проверить, что второй
|
||
click добавляет `B` без dialog/toast и оставляет открытый draft;
|
||
3. нарисовать соседний контур endpoint→внешние точки→endpoint той же стены и
|
||
проверить немедленное открытие room dialog;
|
||
4. повторить с mid-line point и diagonal room edge;
|
||
5. доказать отрицательные случаи different edge, cut, draft и partition;
|
||
6. доказать отсутствие partial write при self-intersection/overlap;
|
||
7. проверить Cancel, Save, secondary action, reload/resume и Undo/Redo;
|
||
8. проверить inherited shared thickness и разные толщины внешних сегментов;
|
||
9. повторить tap без pointermove и gesture-safety случаи.
|
||
|
||
Targeted smoke пишется вместе с кодом; полный smoke-suite запускается перед бетой.
|
||
|
||
### 14.3 Golden и performance
|
||
|
||
Новых pixels, стилей и состояний overlay нет, поэтому новый golden baseline не
|
||
нужен. Существующие editor/View golden должны остаться без изменений; любое
|
||
изменение baseline требует отдельного объяснения и review.
|
||
|
||
Отдельный performance fixture не нужен: новый O(S) поиск выполняется только на
|
||
click. Перед бетой обязательны существующие performance smoke и Full Performance
|
||
на точном SHA без ослабления budgets.
|
||
|
||
### 14.4 Backend
|
||
|
||
Backend не меняется. Нового backend-теста не требуется; полный Linux Validate
|
||
остаётся release gate.
|
||
|
||
## 15. План реализации
|
||
|
||
1. Добавить чистый helper принадлежности двух точек одному room-owned solid
|
||
segment в snap geometry #137.
|
||
2. Отделить prospective validation от mutation либо дать текущему close helper
|
||
безопасно проверить путь с новым терминальным `B`.
|
||
3. Подключить eligibility в `_markupClick()` после click re-resolution и до
|
||
обычного draft/resume handling.
|
||
4. На success сохранить `P—B` обычным draft-механизмом и открыть существующий
|
||
dialog с `B—A`; на failure не менять state.
|
||
5. Добавить unit и production-bundle smoke.
|
||
6. Обновить `docs/ARCHITECTURE.md`, `docs/CANVAS.md`,
|
||
`docs/USER-GUIDE.ru.md`, оба changelog и три bundle-копии.
|
||
|
||
Имена helper-функций и приватных полей не являются продуктовым контрактом.
|
||
|
||
## 16. Release-артефакты
|
||
|
||
Изменение пользовательское: implementation-коммит имеет `User-Visible: yes` и в
|
||
том же коммите обновляет:
|
||
|
||
- `docs/CHANGELOG.md`;
|
||
- `docs/CHANGELOG.ru.md`;
|
||
- `docs/USER-GUIDE.ru.md` — автозамыкание соседней комнаты;
|
||
- `docs/CANVAS.md` и `docs/ARCHITECTURE.md` — внутренний snap/markup контракт;
|
||
- три поставляемые bundle-копии.
|
||
|
||
Новый screenshot/golden не требуется, потому что визуал #137 не меняется.
|
||
|
||
Перед бетой обязательны:
|
||
|
||
- exact-SHA Linux Validate;
|
||
- полный smoke-suite;
|
||
- существующие golden без необъяснённого diff;
|
||
- performance smoke и Full Performance на точном SHA;
|
||
- code review с отрицательным security/network/schema verdict.
|
||
|
||
Отдельный security report не нужен: новых внешних данных, HTML input, HA calls,
|
||
network или storage путей нет. Публикация проходит через бету до stable.
|
||
|
||
## 17. Риски и меры
|
||
|
||
| Риск | Вероятность / влияние | Мера |
|
||
|---|---|---|
|
||
| Разные рёбра ошибочно считаются одной стеной | средняя / высокий | проверять один canonical segment, unit на corner/different edge |
|
||
| Автозамыкание проходит через проём | средняя / высокий | использовать cut snapshot #137, unit + smoke opening/open-span |
|
||
| Второй узел общей стены ошибочно трактуется как невалидное замыкание | средняя / высокий | minimum-vertex gate до eligibility, отдельные unit + smoke |
|
||
| Невалидный click частично сохраняет draft | средняя / высокий | prospective validation до mutation, state/history assertions |
|
||
| Resume endpoint перехватывает финальный click | средняя / высокий | eligibility до `_draftEndAt()`, integration smoke |
|
||
| Общая стена получает новую толщину | средняя / высокий | действующий wall inheritance helper + regression matrix |
|
||
| Cancel теряет финальный нарисованный сегмент | средняя / средний | явный Cancel contract и smoke snapshot draft |
|
||
| Поведение touch расходится с click | низкая / средний | re-resolve на tap, gesture-safety regression |
|
||
| Click начинает пересобирать overlay | низкая / средний | использовать cached snapshot, performance/code review |
|
||
|
||
## 18. Откат
|
||
|
||
Откат — revert implementation-коммита #138 вместе с тестами, документацией,
|
||
changelog и bundle-копиями. Persisted schema не меняется, поэтому data rollback и
|
||
миграция не нужны. Комнаты, сохранённые новой версией, являются обычными polygons и
|
||
полностью читаются предыдущей версией.
|
||
|
||
## 19. Принятые технические предположения — можно менять без продуктового ревью
|
||
|
||
1. Eligibility helper рекомендуется разместить в `src/plan-snap-overlay.ts` и
|
||
передавать ему готовый snapshot; точное имя и сигнатура свободны.
|
||
2. Геометрическая точность использует существующий `samePoint`/segment epsilon или
|
||
более строгий эквивалент, совместимый с grid- и wall-bound координатами. Она не
|
||
превращается в новую CSS hit tolerance.
|
||
3. При нескольких одинаково подходящих room segments применяется стабильный ключ
|
||
#137; persisted id результата не нужен.
|
||
4. Prospective validation может быть вынесена из `_closeRoomContour()` в pure helper
|
||
либо параметризовать существующий путь, если success/error contract не меняется.
|
||
5. Успешное добавление `B` может использовать текущий `_persistActiveDraftSegment()`;
|
||
отдельной транзакции backend не требуется.
|
||
6. При достаточном числе вершин geometry validation error не добавляет `B`. Если
|
||
же `B` была бы только второй вершиной, eligibility не запускается и `B`
|
||
добавляется обычным способом без ошибки; это воспроизводит существующий
|
||
minimum-vertex gate ручного замыкания.
|
||
7. Никаких открытых продуктовых вопросов нет: Q1–Q5 и технические defaults приняты
|
||
владельцем 2026-08-14.
|