fix: stop resize before lossy metadata rekey

Issue: #277
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-24 07:10:27 +03:00
parent cd0a682ad7
commit 2e22ed7938
5 changed files with 34 additions and 10 deletions
+2 -2
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -8,7 +8,8 @@
diagonal and ambiguous walls remain visible but explain why they are
disabled. Preview and commit use the same fixed-topology result, so resizing
can no longer split thickness records, leave wall fragments behind or make
the space masonry disappear
the space masonry disappear. If persisted thickness or virtual-span metadata
cannot be moved losslessly, the wall visibly stops at the last safe position
([#277](https://github.com/Matysh/houseplan-card/issues/277)).
- “Optimize plans” now repairs an exact redundant independent wall drawn over
one solid shared room boundary. Its hosted door/window/gate is reattached to
+2 -1
View File
@@ -15,7 +15,8 @@
остаются видимыми, но объясняют причину блокировки. Preview и сохранение
используют один результат с неизменной топологией, поэтому ресайз больше не
разрезает записи толщины, не оставляет фрагменты стен и не заставляет кладку
пространства исчезнуть
пространства исчезнуть. Если записи толщины или виртуальных отрезков нельзя
перенести без потерь, стена видимо останавливается в последней безопасной точке
([#277](https://github.com/Matysh/houseplan-card/issues/277)).
- «Оптимизировать планы» теперь исправляет точную лишнюю перегородку,
нарисованную поверх одной сплошной общей стены комнат. Её дверь, окно или
+21 -6
View File
@@ -8434,9 +8434,12 @@ class HouseplanCard extends LitElement {
}
// Rekeying may change coordinates/keys, never the number or physical
// thicknesses of persisted records. A lossy mapping is a stopped preview.
const beforeWallCms = new Set((s.walls || []).map((wall: any) => Number(wall.cm)));
const afterWallCms = new Set((sp.walls || []).map((wall: any) => Number(wall.cm)));
if ([...beforeWallCms].some((cm) => !afterWallCms.has(cm))
const wallThicknesses = (walls: any[]) => walls
.map((wall: any) => Number(wall.cm))
.sort((a: number, b: number) => a - b);
const beforeWallCms = wallThicknesses(s.walls || []);
const afterWallCms = wallThicknesses(sp.walls || []);
if (JSON.stringify(beforeWallCms) !== JSON.stringify(afterWallCms)
|| (s.open_spans || []).length !== ((sp as any).open_spans || []).length) return false;
this._rszPreview = { space: this._space, sp };
this._cfgEpoch++;
@@ -8510,11 +8513,23 @@ class HouseplanCard extends LitElement {
const wanted = (sn[0] - plan.a[0]) * plan.n[0] + (sn[1] - plan.a[1]) * plan.n[1];
let d = clampSafeResize(g.rooms, g.openings, plan, wanted, this._gridPitch, g.opts);
if (d === g.d && g.moved) return;
const previousPreview = this._rszPreview;
const previousLive = this._rszLive;
const previousD = g.d;
let res = applySafeResize(g.rooms, g.openings, plan, d);
if (!this._rszApplyPreview(res.polys, res.openings)) {
// Persistence metadata is part of the geometry transaction. If wall or
// virtual-span rekeying would be lossy, keep the last complete preview:
// the pointer visibly stops there and pointerup can commit only that
// already-rendered safe candidate.
this._rszPreview = previousPreview;
this._rszLive = previousLive;
g.d = previousD;
this.requestUpdate();
return;
}
g.moved = true;
g.changed = [...plan.roomIds];
this._rszPreview = null;
let res = applySafeResize(g.rooms, g.openings, plan, d);
this._rszApplyPreview(res.polys, res.openings);
g.d = d;
this._rszLive = this._rszEdgeLabels(res, plan);
this.requestUpdate();
+7
View File
@@ -42,3 +42,10 @@ test('#277 every stable disabled reason and commit failure is localized RU/EN',
assert.ok(en['resize.commit_failed']);
assert.ok(ru['resize.commit_failed']);
});
test('#277 a lossy persistence rekey stops at the last complete preview', () => {
assert.match(card, /JSON\.stringify\(beforeWallCms\) !== JSON\.stringify\(afterWallCms\)/);
assert.match(card, /if \(!this\._rszApplyPreview\(res\.polys, res\.openings\)\)/);
assert.match(card, /this\._rszPreview = previousPreview/);
assert.match(card, /g\.d = previousD/);
});