diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 46683302..e036c90c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,7 +19,10 @@ atomically on their first structural edit or through **Optimize plans**; Resize, Undo/Redo and repeated edits preserve the same identities without accumulating off-grid coordinates. The backend Optimize transaction also - enforces this upgrade when invoked directly. Ambiguous legacy geometry is + enforces this upgrade when invoked directly. An authored wall chain remains + valid when it becomes a room or independent walls, while a rejected + transaction stores no partial result and no longer reports false success. + Ambiguous legacy geometry is left unchanged with a recovery prompt. No Plan editor controls or zero-thickness-wall behaviour changed ([#282](https://github.com/Matysh/houseplan-card/issues/282)). diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index 899c1601..23165d03 100755 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -24,7 +24,9 @@ изменении или через **«Оптимизировать планы»**; Resize, отмена/повтор и последующие правки сохраняют те же ID без накопления координат вне сетки; серверная транзакция Optimize также гарантирует это обновление при прямом - вызове. + вызове. Принятая пользователем цепочка остаётся допустимой при преобразовании + в комнату или независимые стены, а отклонённая транзакция не записывает + частичный результат и не показывает ложное сообщение об успехе. Неоднозначная старая геометрия остаётся без изменений с подсказкой по восстановлению. Кнопки редактора плана и поведение стен нулевой толщины не менялись diff --git a/src/houseplan-card.ts b/src/houseplan-card.ts index 7c96054f..ef32adaa 100755 --- a/src/houseplan-card.ts +++ b/src/houseplan-card.ts @@ -7353,7 +7353,15 @@ class HouseplanCard extends LitElement { let safe = false; try { const afterSpace = committedCandidate.spaces.find((space: any) => space.id === before.spaceId); - safe = wallModelOffGridValueCount(afterSpace) <= wallModelOffGridValueCount(historyBefore) + // A completed point of the active, grid-snapped chain is authored input, + // even before its first draft record exists in `before`. Include that + // transient carrier in the growth baseline; later conversion from draft + // to room/partition must not count the same coordinate as newly derived. + const authoredPoints = this._path.length >= 2 + ? this._path.map((point) => [point[0] / NORM_W, point[1] / NORM_W]) + : []; + safe = wallModelOffGridValueCount(afterSpace) + <= wallModelOffGridValueCount(historyBefore, authoredPoints) && this._checkSpacePhysicalGeometry(committedCandidate, before.spaceId).ok; } catch { safe = false; @@ -13692,7 +13700,7 @@ class HouseplanCard extends LitElement { else delete sp.walls; } - this._commitPhysicalGeometry(this._t('history.wall_face_batch'), before); + if (!this._commitPhysicalGeometry(this._t('history.wall_face_batch'), before)) return; delete this._resumeDraftBySpace[this._space]; this._path = []; this._activeDraftId = null; diff --git a/src/wall-segment-model.ts b/src/wall-segment-model.ts index c1f0e133..e4444675 100644 --- a/src/wall-segment-model.ts +++ b/src/wall-segment-model.ts @@ -80,7 +80,9 @@ const canonicalSpan = (a: number[], b: number[]): [Point, Point] => ( const lengthOf = (a: number[], b: number[]): number => Math.hypot(b[0] - a[0], b[1] - a[1]); /** Unique authored/derived contour coordinates that are materially off-grid. */ -export const wallModelOffGridValueCount = (space: any): number => { +export const wallModelOffGridValueCount = ( + space: any, additionalPoints: readonly (readonly number[])[] = [], +): number => { const values = new Set(); const inspect = (value: unknown): void => { const number = Number(value); @@ -101,6 +103,9 @@ export const wallModelOffGridValueCount = (space: any): number => { inspect(segment?.b?.[0]); inspect(segment?.b?.[1]); } } + for (const additionalPoint of additionalPoints) { + inspect(additionalPoint?.[0]); inspect(additionalPoint?.[1]); + } return values.size; }; const projectT = (p: number[], a: number[], b: number[]): number => { diff --git a/test/wall-segment-model.test.mjs b/test/wall-segment-model.test.mjs index e63acc0e..18379d92 100644 --- a/test/wall-segment-model.test.mjs +++ b/test/wall-segment-model.test.mjs @@ -144,6 +144,11 @@ test('off-grid contour guard counts values once across compatibility projections wall_segments: [{ id: 'wall', a: [point, 0], b: [point, 1], cm: 20 }], walls: [{ key: 'legacy', a: [point, 0], b: [point, 1], cm: 20 }], }), 1); + assert.equal( + wallModelOffGridValueCount({ rooms: [] }, [[point, 0], [point, 1]]), + 1, + 'a transient authored path contributes its coordinates to the baseline once', + ); }); test('room openings acquire a stable wall host while partition hosts stay untouched', () => {