mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 13:48:57 +00:00
fix: junction limits judge both sides after the same migration (#329)
The baseline for inheritance was the raw previous document, which for a legacy space carries no wall catalogue at all — so every inherited short segment of a real plan looked new and the resize smoke's legitimate write was refused (executed: two 5 cm segments against their own 30 cm thickness). Both sides now cross commitWallSegmentModel first, and inheritance is counted per rule rather than per subject, because a structural write re-keys the carriers it re-atomises. Issue: #329 User-Visible: no
This commit is contained in:
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
"version": 1,
|
||||
"fixture": "synthetic-only",
|
||||
"chromium": "151.0.7922.34",
|
||||
"sourceFingerprint": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceFingerprint": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"captureScriptSha256": "ce2e9542fed9dade3085be87d16f69adb2ac8262893ad78ad966b1b9673f2983",
|
||||
"command": "npm run build && node demo/docs/capture.mjs",
|
||||
"scenarios": {
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
|
||||
},
|
||||
"view-touch": {
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "41e3ba67f8db0e98f26f484293af83ef937c369ca5ca6a59a3350d8954c906f4"
|
||||
},
|
||||
"space-create": {
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "0dc024726327f60f4a9ceaf3044381691f81f1090af81d1812e870f22d9343ba"
|
||||
},
|
||||
"room-contour-close": {
|
||||
@@ -47,7 +47,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "44cfaa95bd51e2cd628400e28db0ad8b2f0cd904385845bd402494f3f5c0d93c"
|
||||
},
|
||||
"plan-context-tray": {
|
||||
@@ -58,7 +58,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "e0662429b423fb74151b583dcc2c8635b001b637d03bbf7a6b16aec46399c3f8"
|
||||
},
|
||||
"device-editor": {
|
||||
@@ -69,7 +69,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
|
||||
},
|
||||
"device-display-preview": {
|
||||
@@ -80,7 +80,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
|
||||
},
|
||||
"background-editor": {
|
||||
@@ -91,7 +91,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
|
||||
},
|
||||
"room-card": {
|
||||
@@ -102,7 +102,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
|
||||
},
|
||||
"device-info": {
|
||||
@@ -113,7 +113,7 @@
|
||||
},
|
||||
"theme": "dark",
|
||||
"language": "en",
|
||||
"sourceSha256": "e3e2539c7a8a6a86120cad4dd041929b27e220eee60a5efe92cabe006353dbde",
|
||||
"sourceSha256": "ab24211e8f36804dc9f508e4bae2446801cb332b91b5a75a3649a50f2376edc2",
|
||||
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -87,7 +87,7 @@ import {
|
||||
} from './wall-thickness';
|
||||
import {
|
||||
checkNodeDistances, checkNodes, checkRoomClearance, checkSegmentLengths,
|
||||
newViolations, type JunctionLimitViolation, type LimitSegment,
|
||||
increasedViolations, type JunctionLimitViolation, type LimitSegment,
|
||||
} from './junction-limits';
|
||||
import {
|
||||
pointOnOpenCut, sanitizeOpenSpans,
|
||||
@@ -7450,13 +7450,24 @@ class HouseplanCard extends LitElement {
|
||||
private _junctionLimitsIntroduced(
|
||||
candidate: any, previousConfig: any, spaceId: string,
|
||||
): JunctionLimitViolation[] {
|
||||
// The baseline must be the previous document AS THE CANDIDATE SEES IT: a
|
||||
// legacy space carries no wall catalogue at all, so comparing it raw with
|
||||
// a migrated candidate reported every inherited short segment as new and
|
||||
// refused legitimate resizes of real plans. Both sides therefore cross
|
||||
// the same identity barrier first.
|
||||
let inherited: JunctionLimitViolation[] = [];
|
||||
try { inherited = this._junctionLimitViolations(previousConfig, spaceId); }
|
||||
catch { inherited = []; }
|
||||
try {
|
||||
const migrated = commitWallSegmentModel(previousConfig).config;
|
||||
inherited = this._junctionLimitViolations(migrated, spaceId);
|
||||
} catch {
|
||||
// An unmigratable baseline proves nothing about inheritance; never
|
||||
// refuse the write on that basis.
|
||||
return [];
|
||||
}
|
||||
let next: JunctionLimitViolation[] = [];
|
||||
try { next = this._junctionLimitViolations(candidate, spaceId); }
|
||||
catch { return []; }
|
||||
return newViolations(next, inherited);
|
||||
return increasedViolations(next, inherited);
|
||||
}
|
||||
|
||||
private _commitPhysicalGeometry(
|
||||
|
||||
@@ -195,6 +195,34 @@ export function checkRoomClearance(
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Violations introduced BY THIS WRITE, counted per rule.
|
||||
*
|
||||
* Subject identity churns across a structural write (segments are re-atomised
|
||||
* and re-keyed), so matching by subject would report an inherited violation as
|
||||
* new the moment its carrier is re-keyed — that alone refused legitimate
|
||||
* resizes of a real plan. Counting per rule keeps the spec's boundary (§3)
|
||||
* without depending on identity: a write may keep existing violations, never
|
||||
* add one.
|
||||
*/
|
||||
export function increasedViolations(
|
||||
candidate: readonly JunctionLimitViolation[],
|
||||
previous: readonly JunctionLimitViolation[],
|
||||
): JunctionLimitViolation[] {
|
||||
const before = new Map<JunctionLimitRule, number>();
|
||||
for (const item of previous || []) before.set(item.rule, (before.get(item.rule) || 0) + 1);
|
||||
const after = new Map<JunctionLimitRule, JunctionLimitViolation[]>();
|
||||
for (const item of candidate || []) {
|
||||
after.set(item.rule, [...(after.get(item.rule) || []), item]);
|
||||
}
|
||||
const introduced: JunctionLimitViolation[] = [];
|
||||
for (const [rule, items] of after) {
|
||||
const grew = items.length - (before.get(rule) || 0);
|
||||
if (grew > 0) introduced.push(...items.slice(0, grew));
|
||||
}
|
||||
return introduced;
|
||||
}
|
||||
|
||||
/** Violations introduced BY THIS WRITE: inherited ones are never reported. */
|
||||
export function newViolations(
|
||||
candidate: readonly JunctionLimitViolation[],
|
||||
|
||||
@@ -135,3 +135,25 @@ test('§4: вырожденная вершина сходится в точку
|
||||
// Нулевая толщина не создаёт вырожденной зоны.
|
||||
assert.equal(isDegenerateApexCorner(spike, [0, 0, 0], 1), false);
|
||||
});
|
||||
|
||||
test('наследование считается по правилам, а не по id носителя', async () => {
|
||||
const { increasedViolations } = await import('../test-build/junction-limits.js');
|
||||
const inherited = [
|
||||
{ rule: 'length', subject: 'wall-old-1', actual: 5, limit: 20 },
|
||||
{ rule: 'length', subject: 'wall-old-2', actual: 7, limit: 20 },
|
||||
];
|
||||
// Структурная запись переатомизировала стены: те же два нарушения под
|
||||
// новыми id — запись законна (ровно кейс ресайза реального плана).
|
||||
const rekeyed = [
|
||||
{ rule: 'length', subject: 'wall-new-a', actual: 5, limit: 20 },
|
||||
{ rule: 'length', subject: 'wall-new-b', actual: 7, limit: 20 },
|
||||
];
|
||||
assert.deepEqual(increasedViolations(rekeyed, inherited), []);
|
||||
// Появилось третье — вот оно и есть новое.
|
||||
const worse = [...rekeyed, { rule: 'length', subject: 'wall-new-c', actual: 3, limit: 20 }];
|
||||
assert.equal(increasedViolations(worse, inherited).length, 1);
|
||||
// Новое правило на чистом плане.
|
||||
assert.equal(increasedViolations(
|
||||
[{ rule: 'angle', subject: 'n', actual: 9, limit: 15 }], [],
|
||||
).length, 1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user