fix: keep a stored zero draft thickness and guard both thickness writers (#313)

По находкам CODE-REVIEW-313-r1:

High — резолвер больше не превращает сохранённый 0 сегмента драфта в 15:
ноль — легитимное значение (docs/WALL-THICKNESS.md §6), к дефолту 15 падает
только ОТСУТСТВУЮЩАЯ запись. Смок дополнен: hit нулевого сегмента несёт 0,
диалог показывает пустое поле, Apply без правки отказывает и не портит
данные.

Medium — гвард #278 усилен: паттерн допускает перенос строки после скобки,
и счётчик требует РОВНО ДВЕ точки коммита wall_thickness — мутация любой из
них (включая новую независимую) красит юнит; краснота второго патча мутанта
проверена изолированным исполнением.

Issue: #313
User-Visible: no
This commit is contained in:
Codex
2026-08-26 08:35:58 +03:00
parent d34e56ba78
commit ed126d6a43
6 changed files with 42 additions and 18 deletions
File diff suppressed because one or more lines are too long
+11 -1
View File
@@ -20,7 +20,8 @@ const out = await page.evaluate(async () => {
// exact duplicate over the room's top edge — the #308 layout
{ id: 'overlay', a: [0.1, 0.1], b: [0.5, 0.1], cm: 30 },
],
room_drafts: [{ id: 'saved-draft', points: [[0.6, 0.5], [0.9, 0.5]], segments: [{ cm: 12 }] }],
room_drafts: [{ id: 'saved-draft', points: [[0.6, 0.5], [0.9, 0.5], [0.9, 0.7]],
segments: [{ cm: 12 }, { cm: 0 }] }],
}], markers: [], settings: {} };
card._layout = {}; card._space = 'wt'; card._modelCache = null; card._frame = null;
card._cfgEpoch++; card._setMode('plan'); card._tool = 'wallthick'; await update();
@@ -52,6 +53,15 @@ const out = await page.evaluate(async () => {
card._wallDialog = { ...card._wallDialog, value: '18' };
card._wallThickApply(false); await update();
result.draftWritten = sp().room_drafts[0].segments[0].cm === 18;
// CODE-REVIEW-313-r1 High: a STORED zero must surface as zero (empty field),
// and Apply without editing must refuse — never silently turn 0 into 15.
const zeroSegHit = card._wallThickHit([0.9 * NORM_W, 0.6 * NORM_W]);
result.zeroSegmentSurvives = zeroSegHit?.source?.kind === 'draft' && zeroSegHit.cm === 0;
card._wallThickClick([0.9 * NORM_W, 0.6 * NORM_W]); await update();
result.zeroSegmentFieldEmpty = card._wallDialog?.value === '';
card._wallThickApply(false); await update();
result.zeroSegmentNotCorrupted = sp().room_drafts[0].segments[1].cm === 0;
card._wallDialog = null; await update();
// 4) #308 overlap: the independent wall owns the hit
const overlapHit = card._wallThickHit([0.3 * NORM_W, 0.1 * NORM_W]);
result.overlapPrefersIndependent = overlapHit?.source?.kind === 'partition'
+2 -2
View File
File diff suppressed because one or more lines are too long
+11 -11
View File
@@ -2,7 +2,7 @@
"version": 1,
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"sourceFingerprint": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceFingerprint": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"captureScriptSha256": "ce2e9542fed9dade3085be87d16f69adb2ac8262893ad78ad966b1b9673f2983",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -14,7 +14,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "36223106c073f07d8cc3ecf8eaab37192ebb2687daba65c5c21047d0b7890de0"
},
"view-touch": {
@@ -25,7 +25,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "41e3ba67f8db0e98f26f484293af83ef937c369ca5ca6a59a3350d8954c906f4"
},
"space-create": {
@@ -36,7 +36,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "c33a7279165a4cec6fa6fadb6fd08cd967e082a17fe101ef442d27d36ae59b6b"
},
"room-contour-close": {
@@ -47,7 +47,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "b4777162eae89e0d95801721330bcd74ba761624b82b3362069d7e2d38317e08"
},
"plan-context-tray": {
@@ -58,7 +58,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "c0e28edf82f45ccc6df568d3023b681e9e4394262ad34c1605fe9eacdb57a390"
},
"device-editor": {
@@ -69,7 +69,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "d0ffd31ce80bfde21ab75da356a5fc1af38246f2b301030880320620c228d89d"
},
"device-display-preview": {
@@ -80,7 +80,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "2cdabae1f89c3286e4fac0ce30f757ee1690b707ab8a5488748b7cd420626160"
},
"background-editor": {
@@ -91,7 +91,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "30147bb00a90eea7136b4cee30995f6e6a9217b5132f3e8d3ad7471413b1af8a"
},
"room-card": {
@@ -102,7 +102,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "029a3e69ec647a8a370d99e6bb7f9225833c526739076022f6b52ba54bff30ea"
},
"device-info": {
@@ -113,7 +113,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "5fd7e29594eae558c770dc79fd15551d251cfcf7c8e5e335526f8bed3e517e07",
"sourceSha256": "0a6db3a8e3e3cb63b28f1152f37a4a2dc0f9dba155d4e933ac90410eb578e56c",
"imageSha256": "dd492f53150b7149085daada5cce9eeae9bde9e7ea1d86679a54b3041f72f517"
}
}
+6 -1
View File
@@ -11971,9 +11971,14 @@ class HouseplanCard extends LitElement {
for (let i = 0; i + 1 < draft.points.length; i++) {
const a = [draft.points[i][0], draft.points[i][1]];
const b = [draft.points[i + 1][0], draft.points[i + 1][1]];
// Zero is a legitimate stored value («Empty / 0 leaves the new wall
// thin», docs/WALL-THICKNESS.md §6) — only a MISSING record falls
// back to the drawing default. `|| 15` would silently turn a stored
// 0 into 15 on Apply (CODE-REVIEW-313-r1 High).
const rawCm = Number(draft.segments[i]?.cm);
offer({
a, b, roomId: '', segs: [[a[0], a[1], b[0], b[1]]],
open: false, cm: Number(draft.segments[i]?.cm) || 15,
open: false, cm: Number.isFinite(rawCm) ? rawCm : 15,
source: { kind: 'draft', id: draft.id, segment: i },
}, distToSegment(raw, [a[0], a[1], b[0], b[1]]), true);
}
+10 -1
View File
@@ -104,8 +104,17 @@ test('#278 production source routes physical writers through one barrier and dec
'physical_move', 'resize_room', 'close_boundary', 'open_boundary', 'wall_thickness',
'move_opening', 'delete_opening', 'merge_rooms', 'contour_to_partitions',
]) {
assert.match(source, new RegExp(`_commitPhysicalGeometry\\(this\\._t\\('history\\.${historyKey}'`), historyKey);
// The pattern tolerates a line break after the opening parenthesis: a
// wrapped call must not slip past the barrier check (CODE-REVIEW-313-r1).
assert.match(source,
new RegExp(`_commitPhysicalGeometry\\(\\s*this\\._t\\('history\\.${historyKey}'`), historyKey);
}
// #313 introduced a second thickness commit point (independent masonry).
// BOTH must go through the barrier: replacing either with _recordGeometry
// reduces the count and reddens this line.
assert.equal(
(source.match(/_commitPhysicalGeometry\(\s*this\._t\('history\.wall_thickness'/g) || []).length,
2, 'both thickness writers route through the common barrier');
assert.match(source, /_commitPhysicalGeometry\([\s\S]{0,160}history\.edit_opening/);
assert.match(source, /_commitPhysicalGeometry\([\s\S]{0,160}history\.split_room/);
assert.match(source, /_recordGeometry\(this\._t\('history\.decor_edit'/);