Files
houseplan-card/docs/specs/132-partition-openings.md
T
2026-08-19 00:35:59 +03:00

32 KiB
Raw Blame History

Issue #132 — проёмы в независимых стенах

1. Сценарий

Администратор рисует незамкнутую цепочку инструментом «Стены», завершает её сменой инструмента, редактора или этажа и размещает в одном сохранённом сегменте дверь, окно, ворота либо открытый проём тем же инструментом «Проём», которым работает со стенами комнат. Домочадцы затем видят корректный разрыв, а для stateful-типов — тот же live state и действия в View/kiosk.

2. Что человек увидит до и после

До изменения завершённая независимая стена остаётся сплошной и не принимает проём; после изменения выбранные дверь, окно, ворота или открытый проём вырезают её тело, перемещаются вместе с выбранным отрезком стены и ведут себя как тот же тип проёма в обычной стене. При этом стена с проёмом по #185 остаётся полноценным ребром для распознавания замкнутой комнаты.

3. Проблема и связь со scope

partitions[] — канонические независимые физические стены, но current opening placement/index принимает только derived room walls. Physical union специально добавляет partitions после opening cuts, поэтому проём не может вырезать их.

#173 заменил отдельный публичный инструмент «Перегородка» одной непрерывной цепочкой «Стены». Активные сегменты crash-safe живут в room_drafts; явное завершение превращает каждый сегмент в отдельный partition со stable id. Face graph использует сохранённые partitions и может создать room wall на той же оси, не удаляя исходный объект. Это делает composite room-wall/partition overlap штатным, а не ошибочным случаем.

#185 фиксирует более новый продуктовый контракт: проём является свойством стены, а не разрывом её структурного ребра. Поэтому opening cuts влияют на физическую и световую геометрию, но room-face detection должен уметь замыкать область через room wall или partition с любым поддержанным проёмом. open_spans остаются реальным отсутствием стены и в topology не возвращаются.

Функция закрывает J4: администратор может без внешнего SVG точно воспроизвести план. Правильный итоговый View также поддерживает J1/J2/J3: физическая геометрия, contact/lock status и безопасные actions не расходятся с домом.

4. Решения владельца

  1. На независимой стене разрешены все существующие типы: door, window, gate, passage. #132 не добавляет новый opening type.
  2. Проём следует за host при движении перегородки.
  3. Удаление перегородки с проёмами требует confirmation со списком и удаляет их только после явного согласия.
  4. Contact, lock, badges и actions идентичны одноимённому проёму в room wall; меняется только host geometry.
  5. Физический passage в перегородке участвует в световой геометрии по тем же type-specific правилам, что passage в стене.
  6. При точном совпадении saved partition со стеной комнаты они считаются одним составным физическим барьером без дополнительного host chooser. Explicit host остаётся partition; hosted interval прорезает оба совпадающих тела. После движения partition вычисляемый room-wall cut исчезает, room config не переписывается.
  7. По #185 проём любого поддержанного типа не разрывает структурное ребро для room-face detection. Контур через стену с проёмом замыкается, а проём и его host сохраняются.

5. Scope

  • явная persisted host identity для partition opening;
  • placement/hover/drag/edit/delete door, window, gate и passage на partition;
  • full-depth cut, jamb/tunnel/symbol geometry с толщиной partition;
  • composite cut совпадающих partition + derived room wall без persisted room provenance;
  • движение/удаление host и единая Undo/Redo команда;
  • structural wall axes для #185 отдельно от physical opening gaps; regression контракта #173 для endpoint/T/X/collinear faces и реальных open_spans;
  • Flat Plan/View/kiosk/static и hidden Isometric renderers;
  • clean floor, Glow barrier/source guard и действующие sun semantics;
  • existing opening HA state/actions/security;
  • backend validation, import/export/optimization compatibility;
  • i18n, user/canonical docs, unit/backend/smoke/golden/performance evidence.

6. Не входит в задачу

  • новый opening type или новая семантика существующего passage;
  • проёмы в unfinished room drafts или columns;
  • несколько host segments на один opening;
  • автоматическая конверсия старого room-wall opening в partition opening;
  • новый способ завершения Walls chain либо auto-resume finished partition;
  • новая light model либо изменение window/door/gate semantics;
  • sun rays, источником которых становится внутреннее partition window;
  • полная touch parity Plan editor;
  • свободное удаление host с неявной потерей его openings.

7. Модель данных и host identity

OpeningCfg получает optional host discriminator:

host?: {
  kind: 'partition';
  id: string;
  t: number;
}
  • host отсутствует — current legacy/current room-wall association по x/y/ angle/length; старые конфиги не мигрируют.
  • kind='partition' — id обязан ссылаться на partition того же space.
  • Один finished Walls segment создаёт один partition id. Бывшая multi-segment chain не становится составным host: соседние segments двигаются и удаляются независимо.
  • t — нормализованное положение центра вдоль направленного a→b, [0,1].
  • x, y, angle остаются materialized compatibility projection и обновляются атомарно из host; новый frontend считает valid host+t authority.
  • length остаётся normalized physical length и существующий dialog показывает cm/in по cell_cm.

Если implementation выбирает flat fields вместо object, semantics и compatibility остаются теми же. Host kind расширяем discriminated union, но #132 добавляет только partition.

Backend new writes проверяет: existing partition id, finite t, finite geometry, length fits host с jamb safety margin и total limits. Referentially invalid full config write отклоняется, чтобы старый client не мог тихо удалить host и оставить opening.

8. Canonical resolved opening

Один pure resolver строит immutable ResolvedOpeningHost для всех consumers:

  • kind/id;
  • directed centreline and normalized unit vector;
  • center from a + t(b-a);
  • angle modulo current opening convention;
  • full length and host depth from partition cm;
  • derived room-wall intervals, collinear with and covering the hosted opening interval within the canonical wall epsilon;
  • validity/orphan reason;
  • adjacent floor samples/type-specific passage policy.

Placement preview, symbol, body/composite cut, tunnel, hit test, light barriers, Iso panel, move/delete и tests используют этот resolver. Запрещён render-only nearest-wall fallback для explicit partition host. Coincident room-wall coverage — вычисляемая projection valid partition host, а не второй persisted host.

Если endpoints хоста представлены в обратном порядке при canonical rewrite, rewrite одновременно заменяет t → 1-t, сохраняя physical center и hinge/flip orientation. Простое rigid translation сохраняет t, length и flip.

9. Placement UX

Активный инструмент «Проём» рассматривает два host kind:

  • solid derived room-wall intervals;
  • saved independent partition segments.

Hover preview показывает тот же translucent door/window/gate и привязывается к partition axis; passage использует свою действующую symbol-less preview. Center квантуется по действующему wall-bound grid step, length целиком помещается между endpoints. Click повторно разрешает candidate и открывает существующий opening dialog.

При близких candidates выбирается минимальная perpendicular distance, затем полное покрытие opening length. Точное collinear overlap room-wall/partition, покрывающее reserved interval, считается одним composite barrier и выбирает explicit partition host без дополнительного dialog. Не совпадающая геометрически tie остаётся неразрешимой и отклоняется с локализованной причиной. Existing opening hit имеет приоритет над placement.

Toast opening_no_wall обновляется до «стены или перегородки». Virtual room span, room draft и column остаются невалидными host. Touch placement — best effort; pointercancel/multi-touch не сохраняют draft по safety floor.

10. Толщина, cut и symbol

Partition body строится из centreline и cm, half-depth с каждой стороны. Hosted opening:

  • вырезает body на полную глубину ровно по своему reserved interval;
  • получает два flat jamb returns на концах cut;
  • door swing начинается с выбранной face по flip_v;
  • window glass располагается в середине partition depth;
  • gate leaves используют current compact gate geometry;
  • passage остаётся symbol-less full-depth cut по контракту #157;
  • overlapping hosted openings не могут резервировать один interval дважды;
  • junction patches после cut не должны заново закрыть passage.

Для zero/invalid thickness new partition невозможна по current validation (1–100 cm). Malformed legacy host fail-dark: cut не применяется. Partition cut выполняется по explicit host identity до единого physical union. Он дополнительно вырезает только derived room-wall bodies, которые collinear и покрывают тот же hosted interval в пределах canonical epsilon. Случайное пересечение, nearby parallel wall или второй independent body не прорезаются.

11. Room topology и bug #185

Room-face graph получает два разных представления одной архитектуры:

  • physical/presentation: opening interval вырезан из кладки и остаётся проходом для изображения, hit geometry и type-specific света;
  • structural room topology: axis валидной стены непрерывен через door/window/gate/passage, потому что проём не отменяет принадлежность стены контуру комнаты.

buildPlanSnapGeometry() либо следующий pure collector обязан сохранить structural room/partition segment через opening interval для face traversal, не возвращая этот interval в physical body. Действующий opening cut может оставаться snap boundary/measure input, но не удаляет connectivity для room detection.

Контракт применяется одинаково к legacy room-wall opening и новому partition-hosted opening. Контур, который последним segment опирается на такую стену endpoint/T/X/collinear-примыканием, предлагает room по обычному #173 flow; opening config, host, entities и materialized geometry не мутируют. open_spans и отсутствующий/invalid host остаются настоящими gaps. Пассивные HA state ticks, редактирование opening state и render не запускают room dialog.

12. Floor fill и tunnel

Partition не является границей room ownership. После вычитания hosted opening видна уже существующая clean-floor/fill geometry под body. Дополнительный нейтральный paper tunnel не рисуется.

Если partition пересекает две room fills, каждый side сохраняет свою canonical fill ownership; cut не смешивает alpha и не создаёт новую room. Glow/sun layers остаются выше base fill. Opening symbol скрывается по hide_openings, но physical cut, HA state и barrier semantics остаются активны, как у room-wall opening.

13. Glow и light barriers

Решение владельца «проём пропускает свет» реализуется через type-specific существующий контракт:

  • door/gate/passage — transparent passage только там, где по обе стороны host есть floor; passage не получает state или створку;
  • exterior/no-floor с одной стороны остаётся opaque, чтобы Glow не уходил наружу;
  • window сохраняет текущую политику: glass виден, но indoor Glow через него не проходит;
  • virtual boundary к partition не относится;
  • source внутри valid interior door/gate/passage допустим; source внутри window tunnel или invalid/orphan cut fail-dark.

Barrier geometry получает joined partition body после вычитания только его hosted passages. Cache fingerprint включает host id/t, partition endpoints/cm, opening length/type и geometry validity. HA-only state tick не rebuild-ит barriers.

14. Sun semantics

Partition window не является exterior room-boundary window и не создаёт новый sun wedge. Existing exterior windows и their wedges сохраняются. Если canonical sun/physical clipping использует independent body set, hosted cut не должен восстанавливать opaque partition поверх уже разрешённого passage; никаких новых direction/source rules #132 не вводит.

15. HA state, actions и security

Contact/invert, lock, badges, open amount, info card, lock/unlock confirmation и resolveHaBindingStatus() не зависят от host kind.

  • Door/gate lock action остаётся единственной sanctioned opening surface.
  • Marker tombstone не отключает exact opening entity reference.
  • Disabled/orphaned/unverified entity не выполняет service call.
  • Window не получает lock control, если current type contract его не допускает.
  • Passage остаётся inert: без contact/lock/invert/flip, badge, info card и service call, по тому же контракту #157 на room wall.
  • Перемещение host не меняет entity ids и runtime state.

Новый action resolver или отдельный partition status model запрещён.

16. Move, edit, delete и Undo

Move/edit

Rigid drag одного partition segment обновляет endpoints и materialized x/y/angle всех его hosted openings в одном preview/commit. t, length, entities and flips сохраняются. Соседние segments бывшей Walls chain не двигаются. Одна Undo/Redo command восстанавливает partition и все hosted projections, включая computed composite cut.

Editor operation, которая сократила бы host меньше opening+jamb margin, блокирует commit с причиной. Import/legacy invalid geometry не clamped молча.

Delete

Если hosted openings нет, действует current delete path. Если есть:

  1. dialog перечисляет openings в порядке t: локализованный type + length;
  2. Cancel ничего не меняет;
  3. Confirm атомарно удаляет partition и ровно его hosted openings;
  4. одна Undo восстанавливает host и весь список с entities/geometry;
  5. attachments отсутствуют у opening model, file cleanup не вызывается.

Удалять hosted openings без confirmation через generic cleanup запрещено.

17. Orphan и failure policy

Explicit partition opening с отсутствующим/invalid host:

  • не вырезает physical body и не создаёт transparent light path;
  • не рендерится как рабочий opening в View/kiosk/static/Iso;
  • в Plan editor показывается диагностический orphan affordance в materialized x/y, позволяющий удалить или перепривязать объект;
  • entity action из orphan affordance не выполняется;
  • production console не спамится; diagnostic reason доступна test/support hook.

Boolean failure по одному cut возвращает непрорезанный opaque partition (fail-dark), не удаляет config и не делает все independent bodies прозрачными.

18. Migration и compatibility

  • Старые openings без host читаются и пишутся по current room-wall contract.
  • Existing room-wall opening около partition не начинает прорезать partition.
  • Room-wall opening, существовавший до #132, сохраняет implicit host и участвует в structural room topology по #185 без migration/write-on-read.
  • New optional fields добавляются в frontend/backend schema и docs/CONFIG-COMPATIBILITY.md/field registry.
  • Export/import/backup сохраняют host object и referential order; counts остаются в общем числе openings.
  • Align/optimize переносит partition и hosted opening согласованно, не re-snaps explicit host к nearby room wall.
  • Older frontend may preserve unknown host fields, but backend referential validation prevents destructive orphan write; backward visual support не обещается до версии, реализующей #132.

Schema migration существующих данных не нужна: capability появляется только у новых/явно перепривязанных openings.

19. i18n и accessibility

Новые en/ru strings:

  • toast.opening_no_wall — обновлённая guidance/error «стена или независимая стена»;
  • opening.host_partition — host kind «Независимая стена» в properties/diagnostic;
  • confirm.delete_partition_openings_title, confirm.delete_partition_openings_body и confirm.delete_partition_openings_item — confirmation и строки списка с параметрами {count}, {type}, {length};
  • opening.partition_orphan и opening.rebind_partition — orphan reason и доступное действие перепривязки по §17.

Opening dialog, list и confirmation используют существующий hp-dialog, focus trap, Escape и restore focus. List доступен screen reader; confirmation сообщает точное количество. Canvas preview не является единственным объяснением host: properties/diagnostic имеет текстовое accessible name.

Touch editor: best effort / intentionally degraded. Desktop остаётся reference surface. На touch обязательны только safety floor: tap повторно решает candidate на commit, а pinch/second pointer/pointercancel не создают opening, не удаляют host и не завершают Walls chain. Hover parity не обещается; View и kiosk не получают новых interactions.

20. Acceptance criteria

  1. AC1 — Walls workflow и placement. После завершения multi-segment цепочки «Стены» каждый saved segment является отдельным candidate host. На partition размещаются door/window/gate/passage; active draft, column и virtual span не принимаются. Доказательство: resolver/lifecycle unit + desktop smoke.
  2. AC2 — geometry и composite overlap. Opening cut full-depth с корректными jamb/symbol для 1/15/100 cm и diagonal partition. При точном покрывающем room-wall overlap partition выбирается explicit host и весь composite barrier прорезается; crossing/nearby/второй independent body не затрагиваются. Доказательство: geometry units + reviewed golden.
  3. AC3 — host lifecycle. Rigid move одного segment переносит его openings и убирает computed room-wall cut; delete показывает точный список и атомарно удаляет после Confirm; Undo/Redo восстанавливает host/openings/composite. Доказательство: command units + browser smoke.
  4. AC4 — light. Interior door/gate/passage пропускают Glow через полный composite cut; exterior/no-floor варианты и window остаются opaque по current policy; invalid host/boolean failure fail-dark. Доказательство: light visibility units + smoke/golden.
  5. AC5 — #185 room closure. Room-face detection считает wall axis непрерывным через door/window/gate/passage как на legacy room wall, так и на partition host. Endpoint/T/X/collinear closure предлагает room и сохраняет opening bit-equivalent; open_span остаётся gap. Доказательство: pure topology unit + production-bundle smoke, который краснеет на origin/dev.
  6. AC6 — no passive topology mutation. HA state, opening edit/render, reload и existing closed face не показывают room dialog и не меняют geometry; только последний accepted Walls segment может инициировать предложение по #173. Доказательство: wall-face graph unit + smoke.
  7. AC7 — render parity. Plan/View/kiosk/static/Iso используют один resolved host и не расходятся по center/angle/depth/open state; passage остаётся symbol-less. Доказательство: cross-render smoke + golden.
  8. AC8 — HA/security parity. Door/window/gate contact/lock/badge/actions совпадают с room-wall contract, secure/disabled guards не меняются; passage остаётся inert. Доказательство: unit matrix + smoke.
  9. AC9 — compatibility. Legacy openings unchanged; host fields survive save/export/import/optimize; invalid reference rejected or fail-dark; schema принимает ровно четыре уже существующих type. Доказательство: frontend/backend round-trip tests.
  10. AC10 — accessibility/touch safety. Dialog/confirmation keyboard доступны, touch cancel/multi-touch не создают opening и не завершают Walls chain. Доказательство: browser smoke.
  11. AC11 — cache/performance. HA tick и pointermove не rebuild-ят host, composite или face topology; caches bounded и инвалидируются geometry host / opening type/length. Доказательство: counters + performance smoke.
  12. AC12 — regression #173/#157. Walls finish/no-auto-resume, room queue, passage field restrictions, room-wall opening placement и планы без hosted openings остаются совместимыми. Доказательство: existing targeted suites
    • named #132 smoke.

21. План автотестов

Unit

  • host serialization/resolution, t and reversed endpoints;
  • room-wall vs partition candidate, composite/non-composite tie and length fit;
  • horizontal/vertical/diagonal, 1/15/100 cm cut and jambs;
  • overlap reservation, junction patch, coincident derived wall vs independent body;
  • move/delete/Undo atomic snapshots;
  • interior/exterior/window/passage light policy and fail-dark;
  • structural-vs-physical collector: all four openings preserve room-face axes, while open_span remains a gap; endpoint/T/X/collinear #185 matrix;
  • only the latest accepted segment may cause room proposal; opening/HA updates do not;
  • orphan, missing host and cache fingerprint;
  • HA state/action host-kind parity.

Backend

  • host discriminator/id/t and referential validation;
  • config size/count limits, export/import/unknown fields;
  • canonical passage field restrictions from #157 with partition host present;
  • malformed/missing partition rejection;
  • full HA harness authority — Linux CI/WSL.

Browser smoke

  • finish multi-segment Walls chain; place/edit/drag door, window, gate and passage on one resulting partition without resuming the chain;
  • host move, delete Cancel/Confirm and Undo/Redo;
  • contact opens, lock confirmation/action, hide_openings;
  • Glow across interior passage and blocked by window/exterior passage;
  • reproduce #185 on legacy room wall and hosted partition for all four types, including T closure; verify open_span still blocks closure and opening config is unchanged;
  • exact composite overlap selects partition host without an extra chooser and leaves no opaque wall layer across the opening;
  • Plan/View/kiosk/static/Iso parity;
  • touch cancel/pinch safety and keyboard confirmation;
  • import/optimize round trip.

Golden

  • thick/diagonal partition with all four types, light/dark;
  • coincident room-wall/partition composite before and after hosted cut;
  • Glow before/after door passage and opaque window;
  • hidden Iso panels/cuts and static-card parity;
  • expected diffs accepted only from reviewed full Linux artifact.

Performance

  • cold structural build within current large-house budget;
  • HA state tick geometry build count 0;
  • repeated render cache growth 0/bounded caps;
  • no per-opening repeated full wall index build.

22. Затронутые поверхности

  • OpeningCfg, backend validation and config compatibility registry;
  • opening placement/resolver, wall-thickness/physical geometry and command stack;
  • plan-snap-overlay/wall-face structural collector and #173 room queue;
  • Flat/static/Iso opening renderers;
  • Glow/light and current sun physical consumers;
  • Plan dialog/i18n, import/export/align/optimize;
  • unit/backend/smoke/golden/performance fixtures;
  • docs/ARCHITECTURE.md, docs/CANVAS.md, docs/UX-MODES.md, docs/WALL-THICKNESS.md, docs/LIGHT.md, docs/SUN.md, docs/USER-GUIDE.ru.md, docs/TESTING.md.

23. Риски и откат

Риск Мера
Host inference режет не ту стену explicit partition id+t
Composite overlap режет nearby/crossing body collinear full-interval coverage + negative units
Junction union закрывает passage cut-aware physical union tests
#185 возвращает физическую кладку в проём отдельные structural и physical projections
Opening начинает пассивно открывать room dialog topology-delta и no-passive smoke #173
Delete теряет entities atomic command snapshot + confirmation
Light проходит через window/outside existing type/floor-side policy
Older writer оставляет orphan backend referential validation
Geometry cost растёт per opening single immutable host/index snapshot

Откат запрещает создание новых partition-host openings и возвращает body union after room cuts. Уже сохранённые host objects должны либо оставаться read-preserved/diagnostic, либо требовать downgrade warning; автоматически преобразовывать их в room-wall openings нельзя.

24. Release-артефакты

Implementation commit имеет User-Visible: yes и одновременно обновляет:

  • docs/CHANGELOG.md и docs/CHANGELOG.ru.md;
  • docs/USER-GUIDE.ru.md — Walls finish, placement/types/move/delete/light и closure через стену с проёмом;
  • docs/ARCHITECTURE.md, docs/CANVAS.md, docs/UX-MODES.md, docs/WALL-THICKNESS.md, docs/LIGHT.md, docs/SUN.md, docs/CONFIG-COMPATIBILITY.md;
  • docs/TESTING.md.

Нужны reviewed Flat/Iso/Glow golden artifacts, targeted browser/performance reports and exact Linux backend evidence. Visual baselines принимаются только через golden:accept -- --reviewed по полному Linux artifact.

25. Принятые технические предположения

  • partition host хранится explicit id+t, materialized x/y/angle сохраняются;
  • room-wall openings остаются implicit для backward compatibility;
  • exact covering room-wall/partition overlap выбирает partition host как owner-approved composite; прочие ambiguous ties отклоняются;
  • structural room topology may use uncut wall axes, while all physical/light consumers continue using type-specific cuts; shared persisted geometry не создаётся;
  • partition window не становится exterior sun source;
  • invalid host/cut fail-dark and hidden from ordinary View;
  • точная форма discriminator fields может меняться на ревью без изменения persisted identity/lifecycle semantics.