Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
48 KiB
Issue #228 — надёжное рисование стен и операции с готовым контуром
Пункты этого исследования об отдельном редакторе границ (#148) являются историческими и заменены #306: отдельного инструмента и сущности виртуальной стены больше нет, используется обычная стена
cm:0.
- Дата: 2026-08-22
- Тип: bug · приоритет P1
- Оценка: пользовательская ценность 9/10 · ценность для разработки 8/10 · сложность 9/10 · риск 9/10
- Issue: #228
- Ветка:
issue/228-plan-drawing-problems
Канонические документы: docs/SCOPE.md, docs/USER-GUIDE.ru.md,
docs/ARCHITECTURE.md, docs/CANVAS.md, docs/WALL-THICKNESS.md,
docs/TOUCH-SUPPORT.md, docs/CONFIG-COMPATIBILITY.md, а также принятые
контракты #137, #141 и #173.
1. Сценарий, персона и момент
Домашний администратор рисует или исправляет план в desktop-редакторе Плана
инструментом «Стены». В этот момент ему нужно видеть точную ось активного
отрезка, однозначно понимать будущий узел, сохранять строгий угол с Shift,
создать комнату внутри уже готового контура и удалить старую комнату, не теряя
нужную кладку.
Задача закрывает J4 — точный план без внешнего SVG-редактора — и J6 — безопасное обслуживание плана. View и киоск не получают новых действий, но обязаны без регрессии показывать результат созданной или удалённой геометрии.
2. Что человек увидит до и после
До: активная толстая стена скрывает собственную ось и конечный узел;
неразличимые близкие endpoints молча разрешаются в один из них; snap может
сломать зафиксированный Shift угол, а подпись 90,1° остаться зелёной; готовый
контур нельзя превратить в комнату без нового отрезка; удаление комнаты всегда
удаляет её эксклюзивные стены.
После: активный отрезок показывает ось и точный будущий узел; неоднозначный
узел не записывается; Shift означает действительно строгий угол; клик внутри
готовой пустой области предлагает комнату и при подтверждении может исправить
единственный разрыв до 2 см; при удалении комнаты человек явно выбирает,
оставить её физические стены или удалить их.
3. Подтверждённое текущее состояние
- В
_renderMarkupLayer()толстыйpreviewDстроится поpath + cursor, но.pathlineи.vertex— только по уже поставленному_path. Пунктирный.previewсуществует, однако теряется на штриховке и не даёт конечного узла. resolvePlanSnap()возвращает ближайший endpoint внутри 12 CSS px и детерминированно разрешает равенство. Два разных узла могут быть корректными кандидатами, но визуально сливаться на текущем масштабе; commit всё равно молча выбирает один.wall-face-graph.tsсоединяет только точные endpoint/T/X/collinear узлы с малым geometry epsilon. Промах 1,2 см из отчёта не образует face, даже когда тела стен визуально соприкасаются._deleteRoomClick()использует nativeconfirm(), удаляетroomи клипует open spans. Эксклюзивные room edges не материализуются вpartitions.- #173 ищет только faces, появившиеся после последнего принятого сегмента. Существующий замкнутый partition/draft ring намеренно не вызывает пассивного предложения.
_resolvePlanDrawPoint()безусловно предпочитает любой snap candidate_snapDrawPoint(raw, lock45).is45(deg, 0.5)затем считает 90,1° зелёным. Это старый контракт #137, который настоящая задача заменяет решением владельца.
4. Решения владельца
22.08.2026 владелец подтвердил все defaults:
- Визуально неразличимые разные endpoints не выбираются автоматически: commit блокируется и предлагает увеличить масштаб.
- Только при явном создании комнаты допустимо точно исправить единственный промах до 2 см; исправление и комната входят в один Undo. Больший разрыв не чинится и подсвечивается.
- Основное действие удаления — «Удалить комнату, оставить стены»; отдельное опасное действие — «Удалить комнату и стены»; общие стены остаются всегда. Сохраняемые эксклюзивные стены удерживают свою толщину и проёмы.
- Одиночный клик пустой цепочкой внутри наименьшей подходящей области предлагает
комнату;
Shift+clickначинает новую цепочку и обходит предложение; клик по оси или узлу сохраняет обычный Walls flow. Shiftстрогий: принимается только совместимый endpoint либо точное пересечение зафиксированного луча с линией. Зелёная подпись означает фактически точный кратный угол; 90,1° зелёным не считается.
5. Scope
- Ось и конечный узел активного Walls rubber-band поверх тела стены.
- Явное состояние неоднозначных близких endpoints, блокирующее commit.
- Новый strict-Shift resolver для endpoint и line snap без изменения обычного no-modifier поведения.
- Строгая семантика зелёной угловой подписи для Walls/Split preview.
- Поиск наименьшей подходящей уже существующей bounded face по клику внутри неё.
Shift+clickкак desktop-bypass предложения комнаты до первой точки.- Вычисляемый repair proposal endpoint↔endpoint или endpoint↔solid-line до 2 физических сантиметров, его preview, revalidation и атомарное применение только вместе с подтверждённым созданием комнаты.
- Диагностика единственного большего/неоднозначного разрыва без мутации.
- Диалог удаления комнаты с Keep walls / Delete walls / Cancel.
- Материализация эксклюзивных физических room-wall intervals в независимые
partitions, сохранение толщины и перепривязка проёмов при Keep walls. - Каскадное удаление проёмов эксклюзивных удаляемых стен при Delete walls; сохранение общих стен и их проёмов в обоих вариантах.
- Один named Undo/Redo и один config transaction на создание комнаты с repair либо удаление комнаты с выбранным вариантом.
- RU/EN i18n, документация, unit, targeted smoke, visual/performance coverage.
6. Non-scope
- глобальное автоматическое выравнивание, чтение или сохранение старых планов;
- соединение обычного snap с допуском 2 см: вне явного room-create обычная геометрия остаётся точной;
- слияние разных близких узлов на pointermove, reload, Optimize, View или HA tick;
- repair разрывов больше 2 см либо нескольких неоднозначных разрывов;
- автоматическое перемещение вершин существующих комнат;
- новый общий инструмент редактирования узлов/стен, изменение toolbar #148;
- пассивное предложение комнат при загрузке, Undo/Redo, Resize, move, delete, thickness, Optimize или внешнем config update;
- изменение 12 CSS px общей snap-зоны, радиусов 5/10 см либо endpoint-first порядка в однозначном no-Shift случае;
- удаление уже существующих совпадающих partitions по inference;
- новая настройка, feature flag, persisted
node,face,repairили draft; - touch-паритет desktop hover и modifier gestures;
- изменение рендера стыков #141, общей face-модели #173 или задачи #232 за пределами необходимых точек расширения.
7. Термины и инварианты
- Активная ось — центрline от последней поставленной точки до текущей resolved cursor point.
- Визуально неразличимые endpoints — два разных endpoint-кандидата внутри общей snap-зоны, расстояние между центрами которых на текущем view меньше технического CSS-порога различимости. Точное значение порога не хранится.
- Existing face — simple bounded face текущего structural wall graph, существовавшая до клика и не занятая комнатой.
- Repair proposal — immutable список точных endpoint moves, необходимый для получения одной face и ограниченный 2 см на каждое движение.
- Физическая стена комнаты — solid room-wall interval с эффективной
положительной толщиной. Intentional
open_spanи нулевая boundary-линия не материализуются как новая masonry partition. - Эксклюзивный interval — часть границы удаляемой комнаты, которую после её удаления не продолжает ни одна другая room boundary.
Инварианты:
- Hover, face hit-test, ambiguity и repair preview не мутируют config/layout.
- Обычный click/tap всегда повторно решает актуальный snapshot; hover не авторитетен.
- Неоднозначность fail-closed: нет точки, segment, history или save.
- Repair не остаётся вычисляемой «почти связью»: после Save persisted endpoints точно совпадают с выбранным endpoint/линией.
- Repair применяется только после явного подтверждения комнаты; Keep walls, Cancel, X, Escape и ошибка оставляют исходную геометрию bit-equivalent.
- Общую стену нельзя удалить удалением одной комнаты.
- Проём не остаётся orphan по результату обеих веток удаления.
- Любая успешная операция является одной атомарной geometry-командой.
8. UX-контракт
8.1 Активная ось и узел
После первой точки и до commit следующей:
- тонкая ось продолжается до resolved cursor point поверх толстого preview;
- на свободном конце виден один контрастный узел;
- при endpoint/line snap роль конечного узла выполняет уже существующий активный snap marker, второй совпадающий круг не рисуется;
- при miss рисуется обычный live endpoint;
- committed path и active axis выглядят как одна непрерывная конструкция;
- существующий thick fill, bounded join patch, length/angle badge и hit-testing не меняют источник геометрии.
Layer pointer-inert и aria-hidden; анимации нет. Light, dark, forced-colours и
reduced-motion остаются читаемыми.
8.2 Неоднозначные endpoints
Resolver получает три исхода endpoint-ветки: none, resolved, ambiguous.
- Exact-dedup endpoints остаются одним endpoint и не создают ambiguity.
- Если два разных endpoint внутри 12 CSS px визуально неразличимы на текущем view, все конфликтующие точки получают контрастный conflict-state, а активный resolved endpoint отсутствует.
- Pointermove только показывает конфликт. Click/tap не добавляет точку и один раз показывает локализованный toast «Увеличьте масштаб, чтобы выбрать узел».
- После zoom, когда центры различимы, обычное nearest-endpoint правило #137 снова действует; tie остаётся детерминированным.
- Endpoint ambiguity сильнее line snap: resolver не падает сквозь конфликт к более удобной линии.
8.3 Строгий Shift
При наличии anchor ближайший октант определяется из anchor → raw pointer.
- Совместимый endpoint принимается только если лежит на выбранном ray в canonical geometry epsilon и не даёт нулевой segment.
- Для line candidate вычисляется пересечение solid segment с выбранным ray. Оно принимается, только если лежит на segment, направлено от anchor и само остаётся в текущей 12 CSS px зоне pointer.
- Для collinear ray/line используется ближайшая ненулевая wall-bound точка на ray; для parallel/non-intersecting или несовместимого endpoint кандидат игнорируется.
- При отсутствии совместимого snap результат — действующий strict 45°
grid-bound point из
snapSegment45(). - Без
Shiftendpoint-first/line-snap #137 не меняется, кроме ambiguity guard.
First-point Shift+click не имеет anchor и поэтому использует обычную grid point,
но служит bypass из §8.5.
8.4 Угловая подпись
Для текущего Walls/Split segment зелёное состояние вычисляется по вектору, а не
по широкому допуску ±0,5°:
- горизонталь, вертикаль и диагональ с
|dx| = |dy|в geometry epsilon зелёные; - неизбежный floating-point шум точного пересечения допускается;
- 89,9°/90,1° и другие реально некратные значения красные;
- отображаемое число продолжает округляться до 0,1° и совпадает с цветовым обещанием.
8.5 Создание комнаты из готовой области
При Walls, пустой _path и обычном click/tap применяется такой порядок:
- room card, physical editor chrome и другие существующие владельцы события сохраняют приоритет;
- ambiguity либо клик в snap-зоне endpoint/line обрабатывается обычным Walls flow и не предлагает комнату;
Shift+clickна desktop всегда начинает новую цепочку;- иначе structural graph ищет existing face, содержащую raw click;
- exact room duplicate, partial overlap и face, чья внутренняя точка уже принадлежит любой комнате, исключаются;
- из оставшихся выбирается наименьшая площадь, затем canonical key;
- открывается стандартный room dialog/queue #173 с Create room, Keep walls и Cancel; existing partitions/drafts не удаляются после Create.
Keep walls и Cancel для existing face ничего не пишут. Следующий обычный click может снова предложить ту же face; новый persisted dismiss-state не вводится.
8.6 Допуск 2 см и явный repair
Exact graph проверяется первым. Если exact face под click/последним segment не найдена, разрешён поиск face с одним однозначным repair proposal:
- endpoint→endpoint либо endpoint→внутренняя точка solid structural line;
- физическое расстояние вычисляется через
cell_cm/gridPitchи не превышает 2,0 см включительно; - endpoint room boundary является неподвижным target; двигается endpoint independent partition/draft/active chain;
- room vertex никогда не двигается;
- если обе стороны independent, выбирается deterministic target по геометрии и grid-canonicality, не по record order/id;
- несколько равноценных target, несколько необходимых разрывов, zero-length, self-intersection, open-span bridge или конфликт проёма делают repair недопустимым;
- opening types не разрывают structural axis, а
open_spanостаётся настоящим gap и никогда не чинится; - preview подсвечивает исходный разрыв и точку будущего соединения.
Room dialog не применяет repair заранее. Create повторно проверяет config revision, face, 2 см, limits, overlap и hosted openings, затем одним commit:
- двигает только предложенные independent endpoints;
- обновляет compatibility-проекцию
x/y/angleпроёмов изменённого partition, сохраняя host id/t, если strict jamb validation остаётся зелёной; - создаёт room по уже точному repaired graph;
- применяет обычный consumed/unconsumed contract #173;
- записывает одну named Undo-команду.
Если gap больше 2 см, но около click существует единственный диагностируемый разрыв в screen snap-range, он подсвечивается и room dialog не открывается. Неоднозначный или недиагностируемый открытый набор остаётся обычным Walls flow; House Plan не строит комнату из произвольного почти-цикла.
8.7 Удаление комнаты
Клик инструментом «Удалить комнату» открывает hp-dialog, не native confirm:
- title называет комнату;
- основная кнопка «Удалить комнату, оставить стены»;
- danger-кнопка «Удалить комнату и стены»;
- Cancel/X/Escape ничего не меняют.
Keep walls:
- до удаления материализуется effective room-wall profile;
- solid positive-thickness exclusive intervals становятся
partitionsс теми же endpoints иcm; - совпадающая уже существующая partition переиспользуется, а не дублируется;
- room-wall opening эксклюзивного interval получает явный partition host и сохраняет type, length, flips, contact/lock и положение; strict jamb margin валидируется до commit;
- shared intervals остаются производными стенами соседей и не получают partition;
- virtual/open и zero-thickness boundary intervals не материализуются как physical partition;
- target room удаляется, walls/open spans нормализуются действующими helpers.
Delete walls:
- target room удаляется без новых partitions;
- openings, чья resolved structural ассоциация была только с эксклюзивной стеной target room, удаляются тем же commit;
- opening на shared wall сохраняется и после re-resolution принадлежит surviving room wall;
- уже существующая совпадающая partition и её hosted opening не удаляются: это отдельный объект, а не стена комнаты.
Обе ветки сохраняют neighbour rooms/metadata/devices, пересчитывают open spans, wall intervals и device projection и дают один Undo. Capacity/validation failure показывается до мутации и оставляет исходный snapshot.
9. Модель данных и архитектурный контракт
9.1 Snap result
PlanSnapCandidate либо соседний pure type расширяется до результата,
различающего resolved и ambiguous. Список конфликтующих endpoint содержит
только immutable coordinates/keys; CSS threshold передаётся из live view и не
входит в cache key static geometry.
Strict-Shift projection выделяется в pure helper. Card orchestrator передаёт raw point, anchor, ray, candidate и geometry epsilon; SVG DOM не является источником координат.
9.2 Existing face и repair proposal
wall-face-graph.ts получает read-only face-at-point query поверх уже построенного
exact graph. Она не меняет правило #173 «новые faces только после segment»:
новый query вызывается только явным idle click.
Near-junction repair строится отдельным pure planning pass или расширением graph API с provenance:
- input — room axes после
open_spans, partitions, inactive drafts и active chain, physical conversion и click/added-segment context; - output — candidate face, exact endpoint moves и diagnostics;
- no output при неоднозначности либо необходимости двигать room vertices;
- record order, ids, direction и winding не меняют repaired geometry;
- input objects не мутируются.
9.3 Room deletion plan
До показа/commit строится immutable deletion plan:
- exact old room-wall intervals и ownership после atomization;
- shared/exclusive classification;
- partitions to create/reuse;
- room-wall openings to rehost, keep or delete;
- next walls/open spans и capacity/validation result.
Commit revalidates room id и structural fingerprint. UI не повторяет геометрию планировщика самостоятельно.
9.4 Кэши и lifecycle
- Static snap geometry cache сохраняется; ambiguity зависит от event/view и не создаёт новый O(E) DOM на каждый pointermove.
- Exact face graph использует действующий bounded LRU. Existing-face и repair query запускаются только по click/accepted segment, не по hover/render/HA tick.
- Mode/tool/space change, pointerleave, Cancel и external config adoption очищают active/conflict/repair preview.
- Никаких timer, websocket, service, fetch или localStorage путей не добавляется.
10. Совместимость и миграция
Новых persisted полей и write-on-read нет.
rooms,walls,room_drafts,partitions,openings,open_spansи layout сохраняют текущую schema;- repair записывает только допустимые текущей schema endpoints и обновлённые compatibility-поля hosted openings;
- Keep walls создаёт обычные
partitionsсcm1–100; нулевая boundary-линия не превращается в новый тип данных; - import/export, model version и backend storage API не меняются;
- backend semantic validation должен принять обе ветки удаления и repaired partition; для новых schema keys Python-изменений нет;
- downgrade читает созданные rooms/partitions как обычные существующие данные;
- старые планы не меняются до явного Create/Delete;
- no-op/Cancel/Keep existing face возвращают bit-equivalent config.
11. UX, i18n, accessibility и touch
11.1 RU/EN i18n
Добавляются или уточняются ключи для:
- ambiguous endpoints / рекомендация увеличить масштаб;
- gap больше 2 см либо неоднозначный repair;
- room-delete dialog title/body;
- «Удалить комнату, оставить стены»;
- «Удалить комнату и стены»;
- validation/capacity/opening failure при сохранении стен;
- history «Создать комнату и соединить стены» и две ветки удаления, если для понятного Undo нужны разные названия.
Термины следуют docs/USER-GUIDE.ru.md: Стены, комната,
независимая стена, проём, увеличить масштаб.
11.2 Accessibility
- активная/конфликтная геометрия декоративна,
aria-hidden, pointer-inert; - toast получает действующий status/announcement path;
- delete dialog использует
hp-dialog: title, focus trap, Escape и restore focus; - primary/danger/Cancel имеют явные текстовые labels и логичный tab order;
- forced colours различает normal active node и conflict;
- новой клавиатурной навигации по plan nodes нет.
11.3 Touch
Touch editor: best effort / intentionally degraded.
- tap без pointermove повторно решает ambiguity, existing face и repair;
- hover preview не обещается;
- на touch нет modifier-bypass: tap внутри eligible empty face предлагает комнату; свободную цепочку внутри неё начинают с axis/node либо на desktop;
- pinch, pan, second pointer, pointercancel и suppressed synthetic click не создают point/room, не применяют repair и не подтверждают delete;
- View/kiosk остаются fully supported и не получают editor DOM/actions.
Эта deliberate degradation описывается в user guide/release notes.
12. Производительность и безопасность
- pointermove остаётся O(E) resolver по cached geometry и обновляет только ограниченное active/conflict состояние;
- face traversal/repair/delete planning не запускаются на pointermove, render или HA state;
- large-house budgets не ослабляются; отдельные accepted-click measurements покрывают exact face, one-repair и ambiguity;
- conflict set/repair proposal/deletion plan ограничены текущим числом архитектурных рёбер и существующими schema limits;
- нет HTML от пользователя, новых URL, network, HA action или permission paths;
- delete/repair mutation проходят существующий config write/CAS и Undo boundary;
- secure opening contact/lock references только сохраняются либо удаляются как данные; сервисы HA не вызываются.
13. Acceptance criteria
- AC1 (
unit+smoke+ pre-betagolden; разработчик): после первой точки active Walls segment показывает непрерывную ось поверх thick preview и ровно один конечный узел для miss/endpoint/line snap в light/dark/forced colours; pointer/ARIA и committed geometry не меняются. - AC2 (
unit+smoke; разработчик): два exact-dedup endpoints остаются одним, а разные visually-indistinguishable endpoints дают conflict state; click/tap не пишет point/history/config и показывает zoom toast. После zoom nearest endpoint снова принимается; line fallback при конфликте отсутствует. - AC3 (
unit+smoke; разработчик): безShift#137 остаётся pixel/coordinate-compatible; сShiftпринимаются только endpoint на strict ray либо точное line/ray intersection в solid interval и hit zone; несовместимый snap даёт grid-bound strict-45 point. - AC4 (
unit+smoke; разработчик): angle badge зелёный для точных 0/45/90° vectors с численным epsilon и красный для 89,9°/90,1°; показанное значение и committed endpoint совпадают. - AC5 (
unit+smoke; разработчик): idle ordinary click внутри наименьшей existing empty bounded face открывает room dialog без нового segment; snap-zone click рисует стену,Shift+clickbypass начинает цепочку, occupied/duplicate/partial face не предлагаются. - AC6 (
unit+smoke; разработчик): unique endpoint↔endpoint и endpoint↔line gap<=2,0 cmсоздаёт repair proposal; только Create атомарно записывает exact junction + room и один Undo. Keep/Cancel/X/Escape/error оставляют config bit-equivalent. - AC7 (
unit+smoke; разработчик):>2 cm, two-gap, equal-target, room-vertex move, open-span bridge, zero/self-intersection и invalid hosted opening не ремонтируются; доступный больший single gap подсвечивается без room/save. - AC8 (
unit+smoke; разработчик): room, созданная из existing/repaired face, соблюдает #173 eligibility/order/limits; existing room walls, drafts и partitions не удаляются, active consumed/unconsumed cm/provenance сохраняются. - AC9 (
smoke; разработчик): Delete room открываетhp-dialogс primary Keep walls, danger Delete walls и Cancel; focus/Escape/restore работают, nativeconfirmне используется. - AC10 (
unit+smoke; разработчик): Keep walls материализует только positive solid exclusive intervals как partitions с exact endpoints/cm, reuses coincident partition, rehosts valid openings и не дублирует shared, virtual или zero-thickness intervals. - AC11 (
unit+smoke; разработчик): Delete walls удаляет только target room и openings эксклюзивных room walls; shared walls/openings, neighbour rooms и explicit coincident partitions/hosted openings остаются. - AC12 (
unit+smoke; разработчик): обе delete ветки и room+repair — один named Undo/Redo/config save; capacity, opening validation, stale room и conflict fail before mutation; Redo детерминированно восстанавливает результат. - AC13 (
unit+ targeted smoke + code review; разработчик/ревьюер): Plan/View/static/hidden Iso, clean floor, Glow, sun и junction rendering используют existing canonical consumers; no-op hover/dialog/render не меняют config, а shared masonry не получает double wall/tooth/gap. - AC14 (
smoke+ code review; разработчик/ревьюер): tap выполняет authoritative click resolution, но pinch/pan/second pointer/pointercancel/ suppressed click ничего не сохраняют; View/kiosk pixels/actions не меняются. - AC15 (
performance+ code review; разработчик/ревьюер): 60-room/ 60-partition profile проходит pointer ambiguity, existing-face click и one-repair query без ослабления timing/heap/cache/DOM budgets; graph не запускается на pointermove/HA ticks. - AC16 (
unit+ backend/schema review; разработчик/ревьюер): current schema/import/export/storage/API/permissions остаются совместимыми; старые планы не мигрируют, новые rooms/partitions/openings валидны для backend; no network/security paths. - AC17 (
typecheck+unit+build+ documentation review; разработчик): fast gates зелёные, три bundle-копии побайтно одинаковы, named smokes зелёные, RU/EN i18n, оба changelog и перечисленные документы обновлены в одномUser-Visible: yesimplementation commit.
14. План автотестов и гейтов
14.1 Unit
Расширить test/plan-snap-overlay.test.mjs, test/wall-face-graph.test.mjs и
добавить pure room-deletion suite:
- active-axis projection: miss/resolved/duplicate-node suppression;
- ambiguity по screen threshold, exact dedup, zoom separation, endpoint-first;
- strict ray endpoint, diagonal/vertical/horizontal intersection, collinear, parallel, outside interval/hit zone, immutability;
- exact-octant badge против 89,9/90,1 и floating epsilon;
- exact existing face hit, smallest/nested/order, room occupancy exclusion;
- 1,2 см endpoint/endpoint и endpoint/line repair при
cell_cm:1; - physical invariance threshold at other
cell_cm, exact 2,0 / 2,01 boundary; - ambiguous/multi-gap/open-span/room-vertex/hosted-opening negative matrix;
- repair revalidation, Keep/Cancel immutability, one history snapshot;
- delete classification for exclusive/shared/partial-collinear/open spans;
- positive cm preservation, coincident partition reuse, limits;
- room opening rehost with t/x/y/angle and delete cascade;
- shared opening and explicit hosted partition survival;
- walls/open spans normalisation, Undo/Redo and record-order invariance.
Каждая новая ветка имеет mutation test: удаление ambiguity guard, поднятие порога выше 2 см, движение room vertex либо удаление shared opening делает соответствующий тест красным.
14.2 Targeted production-bundle smoke
Добавить demo/smoke_plan_drawing_repairs.mjs либо эквивалент и при
необходимости расширить demo/smoke_plan_snap_overlay.mjs /
demo/smoke_unified_wall_tool.mjs:
- active thick segment показывает axis/node и сохраняет exact preview point;
- два близких endpoint блокируют click, zoom разрешает выбранный endpoint;
- strict Shift endpoint/line и красный 90,1°;
- existing closed partition ring → click → room dialog → Create/Keep/Cancel;
Shift+clickначинает chain, snap-zone click не открывает room;- 1,2 см gap → preview → Create → exact persisted repair + Undo/Redo;
- 2,01 см/ambiguous/open-span/invalid-opening negative paths;
- Keep walls: exclusive thick walls/room openings становятся hosted partitions, shared wall/opening не дублируются;
- Delete walls: exclusive opening удалён, shared/explicit partition сохранены;
- pan/pinch/pointercancel/suppressed tap и View/kiosk absence.
Перед S7-code-review обязательны точные команды:
npm run typecheck
npm test
npm run build
node demo/smoke_plan_snap_overlay.mjs
node demo/smoke_unified_wall_tool.mjs
node demo/smoke_plan_drawing_repairs.mjs
После build три bundle-копии синхронизируются и сравниваются побайтно.
14.3 Golden и performance
- pre-beta golden review проверяет light/dark Plan с active axis, endpoint conflict, repair preview и delete dialog;
- существующие View/static/hidden-Iso baselines не меняются только из-за
editor chrome; baseline принимается исключительно из полного reviewed Linux
artifact через
golden:accept -- --reviewed; - large-house profile получает accepted-click cases exact/ambiguous/repair и доказывает отсутствие graph work на pointermove;
- бюджеты не ослабляются; полный golden/performance suite остаётся pre-beta gate на exact SHA.
14.4 Backend
Backend schema не меняется. Pure frontend unit + backend/schema code review
доказывают действующие partition/host limits. Полный HA harness выполняется в
Linux CI/WSL; native Windows из-за fcntl каноном не является.
15. План реализации
- Расширить pure snap result ambiguity и добавить strict-ray projection.
- Подключить active axis/node render без дублирования snap marker.
- Заменить wide angle colour check на exact-vector predicate для editor badge.
- Добавить exact face-at-point query и idle-click routing.
- Реализовать provenance-aware near-junction repair planner и atomic apply.
- Реализовать pure room deletion plan с partition/opening consequences.
- Заменить native confirm на
hp-dialogи связать оба commits с history/save. - Добавить unit, targeted smokes и performance diagnostics.
- Обновить i18n, docs, changelogs и три bundle-копии.
Точные helper/file names свободны при соблюдении границ модулей и AC.
16. Затронутые файлы и модули
Ожидаемо:
src/plan-snap-overlay.ts— ambiguity и candidate metadata;src/wall-face-graph.ts— exact face-at-point / repair planning extensions;- новый pure helper room deletion/repair либо эквивалент;
src/houseplan-card.ts— orchestration, dialog, history, lifecycle;src/styles.ts— active/conflict/repair visuals;src/i18n/en.json,src/i18n/ru.json;test/plan-snap-overlay.test.mjs,test/wall-face-graph.test.mjs, новый deletion/repair suite;demo/smoke_plan_snap_overlay.mjs,demo/smoke_unified_wall_tool.mjs, новый targeted smoke и при необходимости performance fixture;docs/USER-GUIDE.ru.md,docs/CANVAS.md,docs/ARCHITECTURE.md,docs/WALL-THICKNESS.md,docs/TOUCH-SUPPORT.md,docs/TESTING.md,docs/STATUS.md, оба changelog;dist/houseplan-card.js,custom_components/houseplan/frontend/houseplan-card.js,demo/srv/assets/houseplan-card.jsкак один generated build.
Изменение Python/schema не ожидается. Если реализация потребует новый persisted
field либо расширение допустимых cm, работа останавливается и возвращается в
S3-spec: это изменение контракта вне утверждённого ТЗ.
17. Release-артефакты
Изменение пользовательское. Implementation commit имеет User-Visible: yes и
в том же коммите обновляет:
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.mdсо ссылкой на #228;docs/USER-GUIDE.ru.md— active preview, ambiguity, strict Shift, existing face, 2 см repair, room delete choices и touch limitation;docs/CANVAS.md— обновлённый snap/Shift/face contract;docs/ARCHITECTURE.md— repair/deletion plans и atomic transactions;docs/WALL-THICKNESS.md— materialized exclusive positive wall intervals;docs/TOUCH-SUPPORT.md— modifier bypass отсутствует на touch;docs/TESTING.mdиdocs/STATUS.mdпосле реализации;- RU/EN i18n;
- named smokes и performance diagnostics;
- три одинаковые bundle-копии.
Перед бетой: полный smoke suite, reviewed Linux golden, performance smoke/Full Performance, Linux backend harness и Validate на exact candidate SHA. Изменение проходит beta/RC до stable.
18. Риски и снижение
| Риск | Вероятность / ущерб | Снижение |
|---|---|---|
| Ambiguity заблокирует обычный endpoint | средняя / высокий | screen-threshold matrix, zoom smoke, exact dedup |
| Strict Shift потеряет полезный line snap | средняя / средний | exact ray/segment intersection и no-Shift parity |
| Repair соединит намеренный разрыв | средняя / высокий | 2 см, one-gap, explicit Create, open-span/ambiguity guards |
| Repair изменит room polygon | низкая / высокий | room vertices immutable, provenance units |
| Hosted opening перестанет помещаться | средняя / высокий | preflight strict validation, fail before mutation |
| Existing face click мешает начать стену | средняя / средний | snap-zone priority + desktop Shift bypass + docs |
| Keep walls создаст двойную кладку | средняя / высокий | interval ownership и coincident partition reuse |
| Delete walls удалит shared opening | низкая / высокий | old/new association plan, shared regression smoke |
| Multi-action commit сохранится частично | средняя / высокий | immutable plan + revalidation + one history/save |
| Graph попадёт в pointermove | средняя / высокий | click-only API и performance diagnostics |
| Touch tap сохранит лишнюю геометрию | низкая / высокий | gesture safety smoke |
| Визуальные golden изменятся шире scope | средняя / средний | targeted frames, reviewed Linux acceptance only |
19. Откат
Откат — revert одного user-visible implementation commit вместе с tests, docs, i18n, changelogs и bundles. Новых persisted полей нет; rooms, partitions и hosted openings, уже явно созданные новой версией, остаются валидными для старой. Автоматическая data rollback не нужна.
Красный targeted smoke/golden/performance/backend gate блокирует бету. Ослабление budget, удаление теста или silent fallback к приблизительной связи не являются откатом.
20. Принятые технические предположения — можно менять без продуктового ревью
- Порог screen-различимости endpoints рекомендуется держать около 8 CSS px; точное значение свободно при выполнении zoom/ambiguity AC.
- Exact-octant лучше проверять по
dx/dy, а не по degrees; helper/name свободны. - Repair target priority: immutable room axis → более grid-canonical independent endpoint → stable geometric tie. Persisted id не определяет координату результата.
- Диагностика gap больше 2 см может искать один virtual bridge только в текущей 12 CSS px event-zone; произвольный open-chain solver не требуется.
- Existing face может переиспользовать
_wallFaceBatchс новым origin либо отдельный pending object, если Create/Keep/Cancel остаются эквивалентны. - Delete planner может atomize old room edges через
wallIntervals()и canonical segment helpers; DOM measurement запрещён. - Совпадающая partition переиспользуется только при exact collinearity/coverage и совместимой толщине; иначе создаётся отдельный interval в пределах limits.
- Нулевая room boundary — контур/бордер, а не physical masonry partition. Настоящая поддержка zero-cm independent wall требует отдельного model review.
- Точные i18n/history keys, названия helper/test/smoke и раскладка файлов свободны.
- Все пять продуктовых вопросов закрыты defaults владельца 22.08.2026; открытых продуктовых вопросов нет.