fix: address room draft review findings

Issue: #478
User-Visible: no
This commit is contained in:
Matysh
2026-09-06 18:46:50 +03:00
parent b754d362ca
commit 38abe78c9a
47 changed files with 892 additions and 591 deletions
+33 -7
View File
@@ -2517,6 +2517,7 @@ public _markupClick(ev: MouseEvent): void {
const sp = this.host._curSpaceCfg as any;
if (!sp) return;
const beforePath = this.host._path.map((point) => [...point]);
const beforeChainId = this.host._activeWallChainId;
const beforeIds = [...this.host._activeWallChainPartitionIds];
const beforeCms = [...this.host._wallChainSegmentCms];
const beforeGraphSources = this._wallGraphSources([]);
@@ -2534,7 +2535,7 @@ public _markupClick(ev: MouseEvent): void {
this.host._activeWallChainPartitionIds = [...beforeIds, id];
this.host._wallChainRedo = [];
if (!commitWallChainSegmentGeometry(this, this.host._t('history.wall_segment'), before)) {
this.host._activeWallChainId ||= `chain-${Date.now().toString(36)}`;
this.host._activeWallChainId = beforeChainId;
this.host._activeWallChainPartitionIds = beforeIds;
this.host._path = beforePath;
this.host._wallChainSegmentCms = beforeCms;
@@ -6764,6 +6765,14 @@ public _applyWallFaceBatch(): void {
return carriers[0]?.id || '';
}));
const before = this._geometrySnapshot();
if (!before) {
abort('toast.geometry_unsafe');
return;
}
const abortMutation = (): void => {
this._restoreGeometryStateLocal(before);
abort('toast.geometry_unsafe');
};
if (repairs[0] && !this._applyWallRepair(repairs[0], batch)) {
abort('toast.wall_repair_changed');
return;
@@ -6781,7 +6790,7 @@ public _applyWallFaceBatch(): void {
if (!split) continue;
const main = sp.rooms.find((room) => room.id === split.roomId);
if (!main) {
abort('toast.contour_cannot_close');
abortMutation();
return;
}
main.poly = split.mainPoly.map((point) => [point[0] / NORM_W, point[1] / this.host._spaceH]);
@@ -6817,7 +6826,10 @@ public _applyWallFaceBatch(): void {
this.host._cfgEpoch++;
const openCuts = this.host._openCuts();
const updatedModel = this.host._spaceModel();
if (!updatedModel) return;
if (!updatedModel) {
abortMutation();
return;
}
let walls = sp.walls;
for (const { room, decision } of newRooms) {
const ring = decision.candidate.ring;
@@ -6853,7 +6865,10 @@ public _applyWallFaceBatch(): void {
// canonical room-wall representation in this same transaction.
this.host._cfgEpoch++;
const reconciledModel = this.host._spaceModel();
if (!reconciledModel) return;
if (!reconciledModel) {
abortMutation();
return;
}
const reconciled = reconcileCoincidentPartitions(
sp, reconciledModel, sp.walls, openCuts,
{
@@ -6861,7 +6876,7 @@ public _applyWallFaceBatch(): void {
cellCm: this.host._cellCm,
gridPitch: this.host._gridPitch,
coordScale: NORM_W,
allowCoincidentPartitions: true,
allowCoincidentPartitions: true, // #478 room acceptance consumes its wall carriers.
},
);
if (reconciled.walls.length) sp.walls = reconciled.walls;
@@ -6916,6 +6931,11 @@ public _commitRoom(): void {
const space = this.host._spaceModel();
if (!sp || !space) return;
const before = this._geometrySnapshot();
if (!before) return;
const abortMutation = (): void => {
this._restoreGeometryStateLocal(before);
this.host._showToast(this.host._t('toast.geometry_unsafe'));
};
const H = this.host._spaceH;
const wasSplit = !!this.host._pendingSplit;
// Split changes the two source-wall edges into shorter children. Preserve
@@ -6979,7 +6999,10 @@ public _commitRoom(): void {
this.host._cfgEpoch++; // the new room must be in the model before keying
const openCuts = this.host._openCuts();
const updatedSpace = this.host._spaceModel();
if (!updatedSpace) return;
if (!updatedSpace) {
abortMutation();
return;
}
let next = applyWallThicknessToNewRoom(
sp.walls, updatedSpace.rooms, newRoom.id, cm,
this.host._wallKeyPitch, openCuts, NORM_W,
@@ -7007,7 +7030,10 @@ public _commitRoom(): void {
else delete sp.walls;
const reconciledModel = this.host._spaceModel();
if (!reconciledModel) return;
if (!reconciledModel) {
abortMutation();
return;
}
const reconciled = reconcileCoincidentPartitions(
sp, reconciledModel, sp.walls, openCuts,
{
+30 -13
View File
@@ -720,6 +720,24 @@ type LegacyRoomDraftSpace = {
wall_segments?: WallSegmentEntry[];
};
const LEGACY_DRAFT_POINT_EPSILON = 0.001;
const LEGACY_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
const legacyFiniteNumber = (value: unknown): number | null => {
if (typeof value !== 'number' && typeof value !== 'string') return null;
if (typeof value === 'string' && !LEGACY_NUMBER.test(value.trim())) return null;
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
};
const legacyDraftPoint = (value: unknown): Point | null => {
if (!Array.isArray(value) || value.length < 2) return null;
const x = legacyFiniteNumber(value[0]), y = legacyFiniteNumber(value[1]);
return x == null || y == null ? null : [x, y];
};
const legacyIdentity = (value: unknown): string => typeof value === 'string' ? value : '';
const migrateRoomDraftsToPartitions = (
space: LegacyRoomDraftSpace, initialMigration: boolean,
): { drafts: number; segments: number } => {
@@ -745,25 +763,24 @@ const migrateRoomDraftsToPartitions = (
const partitions = Array.isArray(space.partitions) ? [...space.partitions] : [];
let converted = 0;
for (const draft of drafts) {
const draftIdentity = legacyIdentity(draft?.id);
const points = Array.isArray(draft?.points) ? draft.points : [];
const segments = Array.isArray(draft.segments) ? draft.segments : [];
const segments = Array.isArray(draft?.segments) ? draft.segments : [];
if (points.length < 2 || segments.length !== points.length - 1)
throw new WallSegmentModelError('zero-length', String(draft?.id || ''));
throw new WallSegmentModelError('zero-length', draftIdentity);
for (let index = 0; index + 1 < points.length; index++) {
const a = points[index], b = points[index + 1];
const a = legacyDraftPoint(points[index]), b = legacyDraftPoint(points[index + 1]);
const segment = segments[index];
if (!finitePoint(a) || !finitePoint(b) || samePoint(a, b))
throw new WallSegmentModelError('zero-length', String(draft?.id || ''));
const cm = Number(segment?.cm);
if (!Number.isFinite(cm) || cm < 0 || cm > 100)
throw new WallSegmentModelError('thickness-conflict', String(draft?.id || ''));
if (!a || !b || samePoint(a, b, LEGACY_DRAFT_POINT_EPSILON))
throw new WallSegmentModelError('zero-length', draftIdentity);
const cm = legacyFiniteNumber(segment?.cm);
if (cm == null || cm < 0 || cm > 100)
throw new WallSegmentModelError('thickness-conflict', draftIdentity);
let id = typeof segment?.id === 'string' && segment.id ? segment.id : '';
if (id) {
if (used.has(id)) throw new WallSegmentModelError('duplicate-id', id);
} else {
if (!id || used.has(id)) {
const base = deterministicWallSegmentId(
String(space.id || ''), a, b,
[`draft:${String(draft?.id || '')}:${index}`],
legacyIdentity(space.id), a, b,
[`draft:${draftIdentity}:${index}`],
);
id = base;
for (let suffix = 2; used.has(id); suffix++) id = `${base}-${suffix}`;