fix: preserve wall-hosted openings in static view

Issue: #282
User-Visible: yes
This commit is contained in:
Matysh
2026-08-26 02:21:20 +03:00
parent c75c77d571
commit 20f5c39699
4 changed files with 19 additions and 5 deletions
+3 -1
View File
@@ -23,7 +23,9 @@
valid when it becomes a room or independent walls, while a rejected
transaction stores no partial result and no longer reports false success.
An invalid open passage also retains its specific actionable error instead
of being masked by the generic wall-model migration blocker.
of being masked by the generic wall-model migration blocker. The compact
space card keeps wall-hosted doors, windows, gates and passages visible after
the migration as well.
Ambiguous legacy geometry is
left unchanged with a recovery prompt. No Plan editor controls or
zero-thickness-wall behaviour changed
+2 -1
View File
@@ -28,7 +28,8 @@
в комнату или независимые стены, а отклонённая транзакция не записывает
частичный результат и не показывает ложное сообщение об успехе. Невалидный
открытый проход по-прежнему получает своё точное сообщение об ошибке, а не
общий блокер миграции стен.
общий блокер миграции стен. Компактная карточка пространства также не теряет
после миграции двери, окна, ворота и проходы, привязанные к стенам.
Неоднозначная старая геометрия остаётся без изменений с подсказкой по
восстановлению. Кнопки редактора плана и поведение стен нулевой толщины не
менялись
+5 -2
View File
@@ -215,7 +215,10 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
const walls: WallEntry[] = Array.isArray(spCfg.walls) ? spCfg.walls : [];
const cellCm = Number(spCfg.cell_cm) > 0 ? Number(spCfg.cell_cm) : 5;
const resolvedHosted = (spCfg.openings || []).flatMap((opening: OpeningCfg) => {
if (!opening.host) return [];
// Contour-wall hosts keep the same saved x/y/angle projection as legacy
// room openings. Only independent partitions need to be materialised from
// their host because that geometry is stored separately from the opening.
if (opening.host?.kind !== 'partition') return [];
const resolved = resolvePartitionOpeningCompat(
opening, space.partitions, NORM_W, cellCm, GRID_PITCH,
).resolved;
@@ -273,7 +276,7 @@ export function renderSpaceStatic(o: StaticRenderOpts): TemplateResult | null {
// Static intentionally keeps the historical door/window/gate wall output.
// Only the new negative-space type participates in its masonry fingerprint.
const resolvedRawOpenings = (spCfg.openings || []).flatMap((opening: OpeningCfg) => {
if (!opening.host) return [opening];
if (!opening.host || opening.host.kind === 'wall') return [opening];
const resolved = resolvedHosted.find((item) => item.opening.id === opening.id);
return resolved ? [materializePartitionOpening(opening, resolved, NORM_W)] : [];
});
+9 -1
View File
@@ -47,6 +47,10 @@ test('passage bindings cannot reach subscriptions, locks or the info card', () =
test('static passage cuts and tunnels are passage-only additions', () => {
assert.match(staticRender, /staticPassageOpenings\(resolvedRawOpenings, NORM_W\)/);
assert.match(staticRender, /opening\.host\?\.kind !== 'partition'/,
'only independent-wall hosts go through the partition resolver');
assert.match(staticRender, /!opening\.host \|\| opening\.host\.kind === 'wall'/,
'stable contour-wall hosts retain the legacy raw-opening projection');
assert.match(staticRender, /passages: staticPassages\.map/);
assert.match(staticRender, /renderOpeningTunnelFills/);
assert.match(staticRender, /passageDataTunnels/);
@@ -69,7 +73,11 @@ test('placement preview adds passage-only cut geometry without changing saved sy
});
test('all write/import paths invoke the semantic passage validator', () => {
assert.equal((backend.match(/validate_opening_passages\(/g) || []).length, 2);
assert.equal((backend.match(/validate_opening_passages\(/g) || []).length, 3);
assert.match(backend, /validate_opening_passages\(candidate_config, config_data\.get\("config"\)\)/,
'Optimize validates the submitted v7 model before the identity barrier');
assert.match(backend, /validate_opening_passages\(msg\["config"\], config_data\.get\("config"\)\)/,
'Optimize validates the committed v8 model before persistence');
assert.match(importer, /validate_opening_passages\(merged_config, current_config\)/);
assert.match(importer, /validate_opening_passages\(incoming_config, validate_all=True\)/);
});