mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 11:49:16 +00:00
fix: preserve resize undo through write repair
Issue: #293 User-Visible: yes
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -137,6 +137,15 @@ if (target) {
|
||||
window.__card._geometryHistory.size), 1);
|
||||
await page.waitForTimeout(650);
|
||||
check('resize_pointer.one_atomic_write', await page.evaluate(() => window.__resizeWrites.length), writesBefore + 1);
|
||||
check('resize_pointer.undo_ready_after_write_ack', await page.evaluate(() => {
|
||||
const card = window.__card;
|
||||
return {
|
||||
history: card._geometryHistory.size,
|
||||
mode: card._mode,
|
||||
tool: card._tool,
|
||||
canCommit: card._canCommitSpace('real-second-floor'),
|
||||
};
|
||||
}), { history: 1, mode: 'plan', tool: 'resize', canCommit: true });
|
||||
check('resize_pointer.wall_metadata_preserved', await page.evaluate(() => {
|
||||
const walls = window.__card._serverCfg.spaces[0].walls || [];
|
||||
return { count: walls.length, cms: walls.map((wall) => wall.cm).sort((a, b) => a - b) };
|
||||
@@ -144,7 +153,15 @@ if (target) {
|
||||
|
||||
await page.keyboard.press('Control+z');
|
||||
await settle();
|
||||
check('resize_pointer.undo_keyboard_consumed_history', await page.evaluate(() => ({
|
||||
history: window.__card._geometryHistory.size,
|
||||
canRedo: window.__card._geometryHistory.canRedo,
|
||||
})), { history: 0, canRedo: true });
|
||||
check('resize_pointer.undo_byte_exact', await persistedGeometry(), before);
|
||||
await page.waitForTimeout(650);
|
||||
check('resize_pointer.undo_one_atomic_write', await page.evaluate(() =>
|
||||
window.__resizeWrites.length), writesBefore + 2);
|
||||
const stableAfterUndo = await persistedGeometry();
|
||||
|
||||
// The second gesture leaves the circle by much more than its hit radius.
|
||||
// Pointer capture must keep the real browser stream alive; Esc then cancels
|
||||
@@ -158,7 +175,7 @@ if (target) {
|
||||
await page.keyboard.press('Escape');
|
||||
await page.mouse.up();
|
||||
await settle();
|
||||
check('resize_pointer.escape_restores_config', await persistedGeometry(), before);
|
||||
check('resize_pointer.escape_restores_config', await persistedGeometry(), stableAfterUndo);
|
||||
await page.waitForTimeout(650);
|
||||
check('resize_pointer.escape_zero_extra_write', await page.evaluate(() => window.__resizeWrites.length), writesBefore + 2);
|
||||
|
||||
@@ -193,7 +210,7 @@ if (target) {
|
||||
await page.mouse.up();
|
||||
await settle();
|
||||
check('resize_pointer.capture_loss_restores_dom', await domHasSharedX(400), true);
|
||||
check('resize_pointer.capture_loss_restores_config', await persistedGeometry(), before);
|
||||
check('resize_pointer.capture_loss_restores_config', await persistedGeometry(), stableAfterUndo);
|
||||
await page.waitForTimeout(650);
|
||||
check('resize_pointer.capture_loss_zero_extra_write', await page.evaluate(() => window.__resizeWrites.length), writesBefore + 2);
|
||||
}
|
||||
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
+3
-1
@@ -7,7 +7,9 @@
|
||||
from the press point, preserves one continuous side-wall thickness record,
|
||||
keeps working outside the handle through pointer capture, and stops at the
|
||||
last renderable preview with an explicit message instead of silently doing
|
||||
nothing ([#293](https://github.com/Matysh/houseplan-card/issues/293)).
|
||||
nothing. Undo remains available after the write acknowledgement, including
|
||||
for an older plan that the storage boundary repairs while saving
|
||||
([#293](https://github.com/Matysh/houseplan-card/issues/293)).
|
||||
- Every saved plan coordinate now converges to the exact shared `1/240` grid
|
||||
value when it differs only by an invisible floating-point tail. Authored
|
||||
off-grid/diagonal geometry is preserved. “Optimize plans” reports the number
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
смещение от точки нажатия, сохраняет одну непрерывную запись толщины боковой
|
||||
стены, продолжает работать за пределами ручки благодаря pointer capture и
|
||||
останавливается на последнем отображаемом preview с явным сообщением вместо
|
||||
молчаливого бездействия
|
||||
молчаливого бездействия. Undo остаётся доступным после подтверждения записи,
|
||||
в том числе для старого плана, который storage-барьер ремонтирует при
|
||||
сохранении
|
||||
([#293](https://github.com/Matysh/houseplan-card/issues/293)).
|
||||
- Все сохраняемые координаты плана теперь приводятся к точному общему узлу
|
||||
сетки `1/240`, если отличаются от него только невидимым хвостом вычислений.
|
||||
|
||||
@@ -298,6 +298,17 @@ export const MUTANTS = [
|
||||
replace: ' safe = true;',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'resize-history-boundary-repair-removed',
|
||||
guard: 'node demo/smoke_resize_pointer_real_plan.mjs',
|
||||
because: 'write-time wall degradation must not erase the one Resize Undo command; '
|
||||
+ 'the strict outbound barrier remains authoritative after the local restore (#293)',
|
||||
patches: [{
|
||||
file: 'src/houseplan-card.ts',
|
||||
find: " && check?.reason === 'wall-degraded-extra');",
|
||||
replace: ' && false);',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'resize-pointer-delta-zeroed',
|
||||
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
|
||||
|
||||
+18
-6
@@ -2727,7 +2727,7 @@ class HouseplanCard extends LitElement {
|
||||
this._undoPoint();
|
||||
return;
|
||||
}
|
||||
if (!this._applyGeometryState(command.before)) {
|
||||
if (!this._applyGeometryState(command.before, true)) {
|
||||
this._geometryHistory.clear();
|
||||
return;
|
||||
}
|
||||
@@ -7289,7 +7289,9 @@ class HouseplanCard extends LitElement {
|
||||
if (viewportGestureEnded && this._pointers.size === 0) this.requestUpdate();
|
||||
}
|
||||
|
||||
private _applyGeometryState(state: SpaceGeometryState): boolean {
|
||||
private _applyGeometryState(
|
||||
state: SpaceGeometryState, allowHistoryBoundaryRepair = false,
|
||||
): boolean {
|
||||
if (!this._canCommitSpace(state.spaceId)) return false;
|
||||
const before = this._geometrySnapshot(state.spaceId);
|
||||
if (!before || !this._restoreGeometryStateLocal(state)) return false;
|
||||
@@ -7297,8 +7299,18 @@ class HouseplanCard extends LitElement {
|
||||
!== spacePhysicalGeometryFingerprint(state);
|
||||
if (physicalChanged) {
|
||||
let safe = false;
|
||||
try { safe = !!this._serverCfg
|
||||
&& this._checkSpacePhysicalGeometry(this._serverCfg, state.spaceId).ok; } catch { safe = false; }
|
||||
try {
|
||||
const check = this._serverCfg
|
||||
? this._checkSpacePhysicalGeometry(this._serverCfg, state.spaceId)
|
||||
: null;
|
||||
// A history snapshot can predate the write-time wall degradation that
|
||||
// canonicalized its command. Restore that one repairable baseline so
|
||||
// Undo remains byte-exact immediately; _writeConfig still degrades and
|
||||
// strictly validates the outbound candidate before it can leave the
|
||||
// card. Every other preflight failure stays fail-closed.
|
||||
safe = !!check?.ok || !!(allowHistoryBoundaryRepair
|
||||
&& check?.reason === 'wall-degraded-extra');
|
||||
} catch { safe = false; }
|
||||
if (!safe) {
|
||||
this._restoreGeometryStateLocal(before);
|
||||
this._showToast(this._t('toast.geometry_unsafe'));
|
||||
@@ -7342,7 +7354,7 @@ class HouseplanCard extends LitElement {
|
||||
if (this._rszDrag) { this._rszCancelDrag(); return; }
|
||||
const command = this._geometryHistory.undo();
|
||||
if (!command) return;
|
||||
if (!this._applyGeometryState(command.before)) {
|
||||
if (!this._applyGeometryState(command.before, true)) {
|
||||
this._geometryHistory.clear();
|
||||
return;
|
||||
}
|
||||
@@ -7365,7 +7377,7 @@ class HouseplanCard extends LitElement {
|
||||
if (this._rszDrag) { this._rszCancelDrag(); return; }
|
||||
const command = this._geometryHistory.redo();
|
||||
if (!command) return;
|
||||
if (!this._applyGeometryState(command.after)) {
|
||||
if (!this._applyGeometryState(command.after, true)) {
|
||||
this._geometryHistory.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ test('#278 production source routes physical writers through one barrier and dec
|
||||
assert.doesNotMatch(source, /_commitPhysicalGeometry\(this\._t\('history\.decor_/);
|
||||
assert.match(source, /this\._rszSpaceCandidateRenderable\(preview\.space, preview\.sp\)/);
|
||||
assert.match(source, /this\._checkSpacePhysicalGeometry\(candidate, spaceId\)\.ok/);
|
||||
assert.match(source, /if \(physicalChanged\)[\s\S]{0,900}_pendingPhysicalWrites\.set\(state\.spaceId/,
|
||||
assert.match(source, /if \(physicalChanged\)[\s\S]{0,1500}_pendingPhysicalWrites\.set\(state\.spaceId/,
|
||||
'physical Undo/Redo must retain the deferred-write barrier');
|
||||
assert.match(source, /if \(configChanged\)[\s\S]{0,300}_pendingPhysicalWrites\.clear\(\)/,
|
||||
'an external baseline must invalidate pending local approvals');
|
||||
|
||||
Reference in New Issue
Block a user