mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
committed by
claude[bot]
parent
e894ce2986
commit
3b0b9eea50
@@ -0,0 +1,481 @@
|
||||
# Issue #141 — бесшовные стыки перегородок и открытых контуров
|
||||
|
||||
Статус: **ТЗ на ревью**
|
||||
Дата: 2026-08-14
|
||||
Тип: `bug` · приоритет: `P2` · оценка ценности: 8/10 · сложность/риск: 7/10
|
||||
|
||||
Issue: [#141](https://github.com/Matysh/houseplan-card/issues/141)
|
||||
Ветка: `issue/141-wall-joints`
|
||||
Канонические документы: [SCOPE](../SCOPE.md),
|
||||
[WALL-THICKNESS](../WALL-THICKNESS.md), [CANVAS](../CANVAS.md),
|
||||
[UX-MODES](../UX-MODES.md), [LIGHT](../LIGHT.md), [SUN](../SUN.md),
|
||||
[ISOMETRIC](../ISOMETRIC.md), [TOUCH-SUPPORT](../TOUCH-SUPPORT.md).
|
||||
|
||||
Решения владельца Q1–Q3 и defaults аналитики приняты 2026-08-14 в issue #141.
|
||||
|
||||
## 1. Сценарий и продуктовый контекст
|
||||
|
||||
Основная персона — домашний администратор, который в desktop Plan editor с нуля
|
||||
рисует план либо позднее добавляет к нему независимые перегородки. После второго
|
||||
клика он должен оценивать уже финальную форму стены, а не ждать замыкания комнаты
|
||||
или перехода в View, чтобы обнаружить клин, щель либо зуб в стыке.
|
||||
|
||||
Это часть J4/J6 из `docs/SCOPE.md`: GUI должен доводить администратора от пустого
|
||||
холста до правдивого плана без внешнего редактора и сохранять этот план правдивым
|
||||
при дальнейших изменениях. View и киоск не получают новых действий, но обязаны
|
||||
показывать ту же физическую кладку и те же световые препятствия.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** два соединённых толстых сегмента выглядят как наложенные прямоугольники:
|
||||
у прямого угла остаётся ступень/зуб, у непрямого — клиновидный разрыв; открытая
|
||||
комнатная стена исправляется только после замыкания контура.
|
||||
|
||||
**После:** намеренно соединённые сегменты сразу, включая live rubber-band,
|
||||
образуют одно ровное тело с тем же ограниченным mitre/bevel, которое человек
|
||||
видит у готовой комнаты; после клика, замыкания или перехода в View форма не
|
||||
прыгает.
|
||||
|
||||
## 3. Проблема и подтверждённая причина
|
||||
|
||||
1. `drawWallPreviewD()` использует `outset − inset` только для закрытого
|
||||
контура. Открытый путь создаётся как несколько независимых прямоугольников.
|
||||
2. `partitionBody()` правильно создаёт один сегмент с плоскими торцами, но
|
||||
`physicalBodies()` передаёт набор таких прямоугольников без топологии узлов.
|
||||
3. Polygon union удаляет перекрытие прямоугольников, но не может догадаться,
|
||||
какой отсутствующий сектор следует достроить в endpoint↔endpoint углу.
|
||||
Поэтому прямой и косой угол показывают один механизм в разном масштабе.
|
||||
4. Закрытая комната проходит другой путь: смежные рёбра одного polygon получают
|
||||
offset-line intersection с `MITRE_LIMIT`, поэтому её углы нормализованы.
|
||||
5. Full/static/isometric render уже объединяют независимые тела с кладкой, но
|
||||
Glow и солнце местами обходят исходные прямоугольники отдельно. В результате
|
||||
визуальная щель может стать реальным световым просветом, а внутренняя грань
|
||||
перекрытия — ложным окклюдером.
|
||||
6. Предположенный в исходном описании отдельный дефект свободного торца по
|
||||
приложенному скриншоту не подтверждён: видимый нижний левый зуб находится в
|
||||
стыке двух сегментов. Одиночный корректный сегмент уже имеет плоский cap.
|
||||
|
||||
## 4. Решения владельца
|
||||
|
||||
1. Исправление охватывает точные endpoint↔endpoint и endpoint↔line (T)
|
||||
соединения, которые могут создать инструменты «Контур» и «Перегородка» после
|
||||
#137, между active/saved draft, partition и готовой комнатной стеной.
|
||||
2. Rubber-band до клика использует тот же join, что сохранённое тело; клик не
|
||||
меняет внешнюю форму стыка.
|
||||
3. Свободный конец остаётся плоским. Отдельные round/square cap, настройка cap
|
||||
или новый UX не вводятся.
|
||||
4. Существующие сегменты не дробятся и сохранённый config не переписывается ради
|
||||
вычисляемого T-узла.
|
||||
5. Случайное X-пересечение без endpoint сохраняет текущую union-семантику и не
|
||||
становится новым persisted node.
|
||||
|
||||
## 5. Scope
|
||||
|
||||
В задачу входят:
|
||||
|
||||
1. вычисляемая топология точных узлов у `room_drafts` и `partitions`;
|
||||
2. бесшовные endpoint↔endpoint углы: прямые, острые и тупые;
|
||||
3. endpoint↔line T-соединения с partition, saved draft и комнатной стеной;
|
||||
4. open-draft preview после размещения сегмента и rubber-band до следующего
|
||||
клика/замыкания;
|
||||
5. собственная толщина каждого уже размещённого сегмента и текущая толщина
|
||||
rubber-band;
|
||||
6. одна joined geometry для full View, Plan, static card, hidden isometric,
|
||||
clean floor/area, Glow, sun и проверки источника внутри кладки;
|
||||
7. сохранение per-object geometry для выбора, hit testing, drag, properties,
|
||||
удаления и Undo/Redo;
|
||||
8. unit, targeted production-bundle smoke, golden и performance regression
|
||||
coverage;
|
||||
9. пользовательская и архитектурная документация плюс RU/EN changelog.
|
||||
|
||||
## 6. Non-scope
|
||||
|
||||
В задачу не входят:
|
||||
|
||||
- изменение snap tolerance, приоритетов или overlay из #137;
|
||||
- автоматическое дробление room/draft/partition в persisted config;
|
||||
- превращение partition в комнатную границу, разбиение комнаты или HA area;
|
||||
- новый тип persisted node/junction и миграция старых планов;
|
||||
- новый cap/join selector, round cap, декоративные окончания и материалы;
|
||||
- изменение opening/open-span, wall thickness, Split или Resize semantics;
|
||||
- исправление #138 про замыкание контура по углам существующей комнаты;
|
||||
- изменение furniture magnet, opening placement либо selection UX;
|
||||
- touch parity редактора сверх действующего safety floor;
|
||||
- новые backend API, HA service calls, зависимости или i18n-тексты.
|
||||
|
||||
## 7. Контракт геометрии и поведения
|
||||
|
||||
### 7.1. Что считается соединением
|
||||
|
||||
1. Узел вычисляется только из координат, уже совпавших по действующему
|
||||
grid/wall-bound snap и geometry epsilon. Визуально близкие, но разные точки
|
||||
автоматически не стягиваются.
|
||||
2. Endpoint↔endpoint — общий конец двух или более segment centrelines.
|
||||
3. Endpoint↔line — endpoint одного segment точно лежит на сплошном интервале
|
||||
другого. Для вычисления тела проходящий segment можно временно представить
|
||||
двумя incident rays, но его persisted запись остаётся одной.
|
||||
4. Opening и open-span gap не является физической линией комнатной стены и не
|
||||
создаёт join с ней. Independent partition/draft не получает проём только
|
||||
потому, что совпал с room opening.
|
||||
5. Пересечение двух внутренних точек без endpoint не создаёт node metadata;
|
||||
наложившиеся физические объёмы по-прежнему соединяются обычным boolean union.
|
||||
|
||||
### 7.2. Endpoint↔endpoint corner
|
||||
|
||||
1. Исходные segment bodies растут на `½ thickness` по обе стороны centreline.
|
||||
2. Внешние offset faces смежных incident rays пересекаются в mitre point.
|
||||
3. Если расстояние до точки пересечения не превышает
|
||||
`MITRE_LIMIT × max(adjacent half-depth)`, отсутствующий сектор включается в
|
||||
joined body.
|
||||
4. Если предел превышен либо устойчивого пересечения нет, применяется bounded
|
||||
bevel. Бесконечный spike, self-intersection, щель и незаполненный зуб
|
||||
недопустимы.
|
||||
5. Для коллинеарных продолжений результат — один прямой wall run без внутреннего
|
||||
торца. Для разворота назад/нулевой длины новая неоднозначная кладка не
|
||||
создаётся.
|
||||
|
||||
### 7.3. T-соединения и комнатные стены
|
||||
|
||||
1. Branch, endpoint которого лежит на другом solid segment, входит в тело
|
||||
проходящей стены без щели и без видимой внутренней butt-face.
|
||||
2. Для готовой комнатной стены authoritative room ring/exterior shell не
|
||||
перестраивается по правилам partition. Joined independent geometry
|
||||
объединяется с уже готовой `wallBodiesGeometry()` после opening cuts, как и
|
||||
сейчас; partition не пробивается совпавшим room opening.
|
||||
3. Подключение к exterior corner не меняет наружный фасад комнаты за пределами
|
||||
реального independent body и не возвращает регрессию #123.
|
||||
4. Разная толщина incident segments не усредняется и не записывается обратно.
|
||||
Joined contour переходит между их реальными offset faces без прозрачной щели.
|
||||
|
||||
### 7.4. Свободные торцы и сложные узлы
|
||||
|
||||
1. Узел степени 1 заканчивается текущим плоским cap на исходном endpoint.
|
||||
2. В узле степени 3+ объединяется volume всех incident bodies и только
|
||||
ограниченные join patches. Алгоритм не должен заполнять произвольный круг
|
||||
вокруг узла или расширять кладку дальше bounded mitre/bevel envelope.
|
||||
3. Zero-length, non-finite или уже отклонённый schema segment не создаёт body или
|
||||
join patch и не ломает остальные валидные тела.
|
||||
4. Порядок records, id и направление `a↔b` не меняют результат.
|
||||
|
||||
### 7.5. Live preview
|
||||
|
||||
1. После каждого законченного segment открытый контур показывает joined body
|
||||
немедленно; замыкание не является первым моментом нормализации.
|
||||
2. Если есть cursor candidate, rubber-band присоединяется к предыдущему segment
|
||||
и к точному target endpoint/line тем же bounded join.
|
||||
3. Предыдущие segment используют сохранённые `room_drafts[].segments[].cm`, а
|
||||
rubber-band — текущее session thickness. Изменение поля толщины не
|
||||
перерисовывает уже сохранённые segment новой толщиной.
|
||||
4. При клике outer contour rubber-band становится committed contour без скачка;
|
||||
допустимо только изменение preview/editor styling.
|
||||
5. При закрытии простой комнаты все уже существовавшие вершины совпадают с
|
||||
нормализованной формой готовой комнаты в пределах geometry epsilon.
|
||||
|
||||
### 7.6. Единая физическая семантика
|
||||
|
||||
Для валидного сохранённого плана один structural result определяет:
|
||||
|
||||
- full Plan/View wall-body path;
|
||||
- `houseplan-space-card` wall-body path;
|
||||
- hidden isometric wall footprint/faces;
|
||||
- subtraction из clean floor и вычисляемой площади;
|
||||
- Glow barriers и fail-dark source placement;
|
||||
- sun occluders.
|
||||
|
||||
Ни один consumer не должен заново обходить raw segment rectangles как будто их
|
||||
внутренние butt-faces являются наружными стенами. Raw per-object body остаётся
|
||||
доступным только там, где требуется identity редактируемой записи: hit,
|
||||
selection frame, drag/properties/delete и history snapshot.
|
||||
|
||||
### 7.7. Ошибки вычисления
|
||||
|
||||
1. На валидных schema/snap inputs joined pass обязан быть детерминированным.
|
||||
2. Malformed legacy input не должен превращать видимую стену в прозрачность:
|
||||
fallback остаётся conservative/opaque и не пишет config.
|
||||
3. Boolean failure не разрешается маскировать исчезновением кладки, световым
|
||||
проходом или миграцией данных. Диагностический fallback может вернуть raw
|
||||
bodies с прежним визуальным дефектом, но plan и light должны fail consistently
|
||||
opaque.
|
||||
|
||||
## 8. Архитектурный контракт реализации
|
||||
|
||||
### 8.1. Structural physical frame
|
||||
|
||||
Реализация вводит один чистый вычисляемый frame (точное имя свободно), который
|
||||
получает room walls/open cuts/openings, partitions, room drafts, columns и scale
|
||||
inputs и возвращает как минимум:
|
||||
|
||||
- raw bodies с source identity для редактора;
|
||||
- joined independent geometry;
|
||||
- объединённую room + independent masonry geometry для presentation/occlusion;
|
||||
- стабильный structural fingerprint либо данные для существующего cache key.
|
||||
|
||||
Join topology строится из centreline segments и их half-depth, а не из SVG DOM,
|
||||
stroke-linejoin или raster measurement. Columns остаются обычными closed bodies
|
||||
и участвуют в union, но не становятся incident wall rays.
|
||||
|
||||
### 8.2. Canonical consumers
|
||||
|
||||
`wallBodiesGeometry()` остаётся канонической точкой room masonry либо получает
|
||||
эквивалентный единый wrapper; full/static/isometric не создают разные join
|
||||
алгоритмы. `_lightBarriers` получает outer/hole rings объединённой opaque geometry,
|
||||
а не внутренние грани перекрывающихся raw rectangles. Clean-floor и source guard
|
||||
используют тот же joined result.
|
||||
|
||||
Preview использует тот же pure node/join primitive, но может строить только
|
||||
активный path плюс локально необходимые target segments. Он не мутирует и не
|
||||
инвалидирует saved structural frame на каждый pointermove.
|
||||
|
||||
### 8.3. Cache и invalidation
|
||||
|
||||
1. Saved topology пересчитывается только при structural geometry change:
|
||||
coordinates, cm, room/opening/open-span topology, `cell_cm` или grid/scale.
|
||||
2. HA state, theme, hover, cursor, Glow brightness и animation tick не входят в
|
||||
saved geometry key.
|
||||
3. Live rubber-band допускает дешёвый локальный расчёт на pointermove; полный
|
||||
boolean union всего дома на каждый move запрещён.
|
||||
4. Full/static/isometric/light consumers переиспользуют structural result либо
|
||||
эквивалентный immutable geometry, а не копируют O(N²) node search.
|
||||
5. Cache остаётся bounded и очищается существующими lifecycle hooks.
|
||||
|
||||
## 9. Модель данных, compatibility и миграция
|
||||
|
||||
- `rooms`, `walls`, `room_drafts`, `partitions`, `wall_columns`, `openings` и
|
||||
`open_spans` не меняют schema.
|
||||
- Backend validation, storage version, import/export и Python model не меняются.
|
||||
- Старые планы получают исправленную вычисляемую форму при чтении без записи.
|
||||
- Hover, preview, render и cache warm-up не вызывают config/layout/storage write.
|
||||
- Undo/Redo продолжает хранить существующие geometry snapshots; одна partition
|
||||
остаётся одной записью и одним history action.
|
||||
- Прямой и обратной миграции нет.
|
||||
|
||||
## 10. UX, i18n, accessibility и touch
|
||||
|
||||
Новых controls, диалогов, toasts, focus/keyboard semantics и текстов нет;
|
||||
следовательно, новые RU/EN i18n keys не требуются.
|
||||
|
||||
Plan editor остаётся desktop-first. На touch/coarse pointer новый hover parity не
|
||||
обещается, но если существующий tap создаёт segment, его сохранённая geometry
|
||||
обязана быть той же; pinch, pointercancel и synthetic click не могут сохранить
|
||||
лишний segment. View/киоск остаются полностью поддержанными и получают ту же
|
||||
исправленную форму без новых взаимодействий.
|
||||
|
||||
Selection frame и handles могут показывать границу конкретной редактируемой
|
||||
записи поверх общего wall body; это editor chrome, а не альтернативная физика.
|
||||
Forced colours и `prefers-reduced-motion` не получают новой ветки поведения.
|
||||
|
||||
## 11. Производительность и безопасность
|
||||
|
||||
**Производительность:** large-house fixture уже содержит 60 partitions. Новая
|
||||
топология не должна пересчитываться на HA state tick, создавать unbounded cache
|
||||
или ослаблять действующие budgets. Перед бетой обязательны exact-SHA performance
|
||||
smoke и Full Performance; изменение budget допускается только отдельным решением
|
||||
процесса, не в #141.
|
||||
|
||||
**Безопасность:** HA permissions, services, locks, network, HTML и внешние данные
|
||||
не меняются. Главный safety invariant — нарисованная кладка, clean floor и
|
||||
окклюдер совпадают. При ошибке вычисления приоритет у непрозрачного fail-closed
|
||||
поведения, а не у сохранения декоративной картинки.
|
||||
|
||||
## 12. Acceptance criteria
|
||||
|
||||
- **AC1 (`unit` + `golden`; разработчик):** open draft из двух segment с общим
|
||||
endpoint при 90°, остром и тупом угле образует один body без gap, tooth,
|
||||
overlap seam или неограниченного spike; до замыкания и после замыкания уже
|
||||
размещённые вершины геометрически совпадают в пределах epsilon.
|
||||
- **AC2 (`unit` + `smoke` + `golden`; разработчик):** rubber-band до клика
|
||||
использует тот же bounded mitre/bevel к предыдущему segment и к выбранному
|
||||
endpoint/line target; commit не меняет outer contour, кроме preview styling.
|
||||
- **AC3 (`unit`; разработчик):** каждый segment сохраняет собственную толщину,
|
||||
`MITRE_LIMIT = 4` ограничивает spike, excess angle переходит в bevel,
|
||||
collinear continuation не имеет внутреннего cap, а degree-1 endpoint остаётся
|
||||
плоским.
|
||||
- **AC4 (`unit` + `smoke`; разработчик):** две и более saved partitions с точным
|
||||
endpoint↔endpoint соединением дают одинаковую joined geometry для прямого и
|
||||
косого угла независимо от record order, id, направления и допустимой разной
|
||||
толщины.
|
||||
- **AC5 (`unit` + `smoke`; разработчик):** endpoint↔line соединение с partition,
|
||||
saved draft и solid room wall образует бесшовный T-body; target record не
|
||||
дробится, room/partition/draft config до и после render/hover побайтно
|
||||
эквивалентен.
|
||||
- **AC6 (`unit`; разработчик):** near-miss вне geometry epsilon остаётся
|
||||
раздельным, opening/open-span gap не создаёт room-wall join, incidental X без
|
||||
endpoint сохраняет union semantics, zero-length/non-finite segment не портит
|
||||
валидные neighbours.
|
||||
- **AC7 (`unit` + `smoke` + `golden`; разработчик):** full Plan/View,
|
||||
`houseplan-space-card` и hidden iso получают один joined footprint: крупный
|
||||
прямой/косой стык не показывает внутреннюю butt-face, зуб или второй hatch.
|
||||
- **AC8 (`unit` + `smoke`; разработчик):** clean-floor area, Glow barriers,
|
||||
source-inside-body guard и sun occlusion используют joined geometry: свет не
|
||||
проходит через corner/T join и не блокируется внутренней гранью бывшего
|
||||
overlap.
|
||||
- **AC9 (`smoke`; разработчик):** hit/selection/drag/properties/delete и Undo/Redo
|
||||
сохраняют identity отдельных partitions/draft segments; joined render не
|
||||
объединяет записи в config и не меняет их history granularity.
|
||||
- **AC10 (`unit` + code review; разработчик/ревьюер):** room wall openings,
|
||||
virtual-T, nested/partial walls, exterior shell #123, columns и одиночные
|
||||
partitions сохраняют действующую геометрию; совпавший room opening не режет
|
||||
independent wall.
|
||||
- **AC11 (`performance` + code review; разработчик/ревьюер):** structural joined
|
||||
pass cached/bounded, HA state и pointer hover не запускают full-house topology,
|
||||
60-partition fixture проходит существующие budgets без их ослабления.
|
||||
- **AC12 (`unit` + backend/schema review; разработчик/ревьюер):** schema,
|
||||
serialized config, backend, import/export, i18n, HA/network calls и зависимости
|
||||
не меняются; старый plan исправляется без migration/write.
|
||||
- **AC13 (`typecheck` + `unit` + `build` + documentation review; разработчик):**
|
||||
implementation-loop gates зелёные; три bundle-копии после build побайтно
|
||||
одинаковы; RU/EN changelog и перечисленная документация обновлены в том же
|
||||
user-visible implementation commit.
|
||||
|
||||
## 13. План автотестов
|
||||
|
||||
### 13.1. Unit
|
||||
|
||||
Добавить geometry regression tests в `test/physical-geometry.test.mjs`,
|
||||
`test/wall-thickness.test.mjs` либо отдельный узкий файл:
|
||||
|
||||
1. сравнить joined L-body с ожидаемым offset envelope для 90°, acute и obtuse;
|
||||
2. проверить bounded mitre и bevel threshold вокруг `MITRE_LIMIT`;
|
||||
3. проверить equal/unequal thickness, reversed endpoints и permutation records;
|
||||
4. проверить collinear run, flat free caps, zero-length и near-miss;
|
||||
5. проверить T partition→partition, draft→partition и partition→solid room wall;
|
||||
6. проверить gap/open span и X-crossing contract;
|
||||
7. сравнить open-preview committed vertices с closed-room ring;
|
||||
8. доказать, что предыдущие draft segment сохраняют свой `cm`, когда current
|
||||
session thickness меняется;
|
||||
9. проверить clean-floor boolean area и united opaque rings для Glow/source/sun;
|
||||
10. regression: openings do not cut extras, columns unchanged, #123 exterior,
|
||||
virtual-T и nested wall remain green;
|
||||
11. проверить immutability inputs и structural fingerprint/cache invalidation.
|
||||
|
||||
Минимум один новый test должен быть запущен/проверен ревьюером на `origin/dev` и
|
||||
краснеть на старой прямоугольной geometry, а не только подтверждать новый helper.
|
||||
|
||||
### 13.2. Targeted production-bundle smoke
|
||||
|
||||
Добавить `demo/smoke_wall_junctions.mjs` либо эквивалент:
|
||||
|
||||
1. открыть Plan editor и нарисовать open draft с прямым и косым join;
|
||||
2. проверить rubber-band path до click и отсутствие geometry jump после click;
|
||||
3. создать две partitions endpoint↔endpoint и новую partition через line-snap #137
|
||||
к середине существующей wall/partition;
|
||||
4. проверить единый body path, отсутствие split target record и no-write hover;
|
||||
5. переключить Plan → View → static card → hidden iso и сравнить footprint/bbox
|
||||
signatures;
|
||||
6. проверить selection/drag/Undo/Redo отдельных records;
|
||||
7. поставить Glow source и sun case по разные стороны junction и подтвердить
|
||||
opaque/no-false-edge result;
|
||||
8. проверить pointercancel/pinch/suppressed click — новых records нет.
|
||||
|
||||
По процессу smoke пишется вместе с кодом, но полный browser-suite запускается
|
||||
только перед бетой.
|
||||
|
||||
### 13.3. Golden
|
||||
|
||||
Добавить deterministic joined-wall scenario минимум с кадрами:
|
||||
|
||||
- Plan/light: committed 90° и oblique open-draft joins плюс active rubber-band;
|
||||
- Plan/dark: endpoint-to-line T-preview поверх существующей partition/room wall;
|
||||
- View/static/iso: saved 90° и oblique partitions с крупно читаемым outer contour;
|
||||
- lighting при необходимости: junction рядом с Glow pool, чтобы щель не стала
|
||||
световой полосой.
|
||||
|
||||
Не связанные View baselines с одиночными partitions должны остаться
|
||||
pixel-identical. Любые затронутые эталоны принимаются только из полного
|
||||
просмотренного Linux artifact через
|
||||
`npm run golden:accept -- --reviewed`; локальное принятие ради зелёного CI
|
||||
запрещено.
|
||||
|
||||
### 13.4. Performance и backend
|
||||
|
||||
Перед бетой прогнать существующий `large-house-v1`/performance smoke с 60
|
||||
partitions и Full Performance на точном SHA. Если для локального join-preview
|
||||
нужен отдельный профиль, он получает новый id и не переопределяет старый budget.
|
||||
|
||||
Backend не меняется; отдельный backend test не требуется. Полный Linux Validate
|
||||
остаётся release gate.
|
||||
|
||||
## 14. План реализации
|
||||
|
||||
1. Выделить чистое представление linear physical segments и exact junction
|
||||
topology с source identity.
|
||||
2. Построить bounded mitre/bevel node patches и joined independent geometry.
|
||||
3. Разделить raw editable bodies и canonical joined presentation/physics frame.
|
||||
4. Подключить joined frame к full/static/isometric, clean floor, Glow/source guard
|
||||
и sun, сохранив opening ordering.
|
||||
5. Перевести open-draft/rubber-band preview на тот же join primitive и per-segment
|
||||
thickness.
|
||||
6. Добавить unit и targeted smoke; подготовить golden/performance fixtures.
|
||||
7. Обновить документы, оба changelog и поставляемые bundle-копии.
|
||||
|
||||
Точные имена helpers/files не являются продуктовым контрактом.
|
||||
|
||||
## 15. Release-артефакты
|
||||
|
||||
Изменение пользовательское. Implementation-коммит имеет `User-Visible: yes` и в
|
||||
том же коммите обновляет:
|
||||
|
||||
- `docs/CHANGELOG.md`;
|
||||
- `docs/CHANGELOG.ru.md`;
|
||||
- `docs/USER-GUIDE.ru.md` — рисование открытых контуров и partitions;
|
||||
- `docs/WALL-THICKNESS.md` — independent joined-body/cap/mitre contract;
|
||||
- `docs/ARCHITECTURE.md` — raw editable bodies против canonical joined frame;
|
||||
- `docs/LIGHT.md` и при необходимости `docs/SUN.md`/`docs/ISOMETRIC.md` — единый
|
||||
opaque geometry consumer;
|
||||
- `docs/TESTING.md` — unit/smoke/golden coverage;
|
||||
- `docs/STATUS.md` — релизная линия после фактической реализации;
|
||||
- три поставляемые bundle-копии, создаваемые build/release-процессом.
|
||||
|
||||
Перед бетой обязательны exact-SHA Linux Validate, полный smoke-suite, просмотр и
|
||||
reviewed acceptance Linux golden artifact, performance smoke и Full Performance,
|
||||
а также зелёный code review. Отдельный security report не требуется: новых
|
||||
внешних данных, вызовов и разрешений нет. Изменение проходит beta до stable.
|
||||
|
||||
## 16. Риски и снижение
|
||||
|
||||
| Риск | Вероятность / ущерб | Снижение |
|
||||
|---|---|---|
|
||||
| Pairwise patch переполнит узел степени 3+ | средняя / высокий | radial/node envelope units, T/Y matrix, bounded area assertion |
|
||||
| Разная толщина усреднится или даст self-intersection | средняя / высокий | per-ray half-depth и boolean difference tests |
|
||||
| Plan исправится, а Glow/sun сохранят raw butt-faces | средняя / высокий | один canonical joined frame и occlusion smoke |
|
||||
| Opening случайно прорежет coincident partition | средняя / высокий | сохранить union extras after cuts, regression unit |
|
||||
| Selection потеряет identity после union | средняя / высокий | raw bodies отдельным editor-only channel, drag/Undo smoke |
|
||||
| Room exterior изменится при attached partition | низкая / высокий | #123 exterior regression и room-ring authority |
|
||||
| Live preview начнёт делать full union на pointermove | средняя / высокий | local target subset, performance/code review AC11 |
|
||||
| Старый near-miss внезапно соединится | средняя / средний | exact epsilon contract и explicit near-miss test |
|
||||
| Boolean failure сделает стену прозрачной | низкая / высокий | conservative opaque fallback и malformed fixture |
|
||||
| Golden изменятся шире joined cases | средняя / средний | baseline diff audit; unrelated single partitions pixel-identical |
|
||||
|
||||
## 17. Откат
|
||||
|
||||
Откат — revert user-visible implementation commit #141 вместе с тестами,
|
||||
документацией, changelog и bundle-копиями. Schema и persisted data не меняются,
|
||||
поэтому migration/data rollback не нужны; после отката вернётся прежний
|
||||
визуальный дефект прямоугольных bodies.
|
||||
|
||||
Красный golden/performance gate перед бетой блокирует выпуск. Feature flag,
|
||||
ослабление budget или принятие baseline без review не используются как обход.
|
||||
|
||||
## 18. Принятые технические предположения — можно менять без продуктового ревью
|
||||
|
||||
1. Junction epsilon переиспользует существующий grid/geometry tolerance и не
|
||||
вводит пользовательскую настройку.
|
||||
2. Рекомендуется immutable `PhysicalGeometryFrame` либо эквивалент с raw и
|
||||
joined projections; точное имя и раскладка файлов свободны.
|
||||
3. Node patches могут строиться вручную через offset intersections или через
|
||||
другую deterministic boolean decomposition, если все AC выполняются.
|
||||
4. Room-wall geometry может оставаться внутри `wallBodiesGeometry()` либо быть
|
||||
обёрнута общим helper; второй независимый renderer запрещён.
|
||||
5. Preview может добавлять локальный patch поверх existing saved body либо
|
||||
строить небольшой joined subset; DOM decomposition не важна при отсутствии
|
||||
seam/jump.
|
||||
6. Cache может использовать `_cfgEpoch` плюс structural fingerprint или полный
|
||||
fingerprint; HA state/theme/cursor не входят в saved key.
|
||||
7. Selection frame вправе показывать raw body выбранной записи, если base wall
|
||||
body остаётся joined и hit order не меняется.
|
||||
8. Имена smoke/golden scenarios и точные test files не являются продуктовым
|
||||
контрактом.
|
||||
9. Для malformed legacy fallback допустимы raw opaque bodies; валидные schema
|
||||
inputs обязаны проходить joined path без fallback.
|
||||
10. Нет открытых продуктовых вопросов: Q1–Q3 и предложенные 8/10 · P2 приняты
|
||||
владельцем 2026-08-14.
|
||||
@@ -82,6 +82,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#122](https://github.com/Matysh/houseplan-card/issues/122) Изометрический режим Stage 2: скрытый режим и визуальная полировка | [122-isometric-stage2.md](122-isometric-stage2.md) |
|
||||
| [#123](https://github.com/Matysh/houseplan-card/issues/123) Split из вершины не меняет наружную геометрию стен | [123-corner-split-wall.md](123-corner-split-wall.md) |
|
||||
| [#137](https://github.com/Matysh/houseplan-card/issues/137) Узлы и линии привязки в редакторе Плана | [137-plan-snap-overlay.md](137-plan-snap-overlay.md) |
|
||||
| [#141](https://github.com/Matysh/houseplan-card/issues/141) Бесшовные стыки перегородок и открытых контуров | [141-wall-junctions.md](141-wall-junctions.md) |
|
||||
|
||||
## Правило актуализации
|
||||
|
||||
|
||||
Reference in New Issue
Block a user