mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 13:48:57 +00:00
The editor's grown frame stays in the editor
Audit dev@2c947f4, DEV-2C947-02 (P2). Move the only room from 0.1..0.9 to 5.1..5.9 inside the Plan editor and go back to View: the frame stayed 5880 units wide instead of the room's 880, and only a manual `_frame = null` put it right. Anything that moves, deletes or heavily resizes geometry in an editor left View looking at ground the plan no longer occupies — until some unrelated model/layout/device change happened to invalidate the memo. The growth itself is deliberate and stays (docs/CANVAS.md §4.3): inside an editor the frame bounds pan and defines what zoom 1 means, and one that shrank the instant a room was deleted would move the ground under a live gesture. The bug was that the growth was invisible to the memo — `_frame`'s key carried the space, the model, the layout, the devices and the show-far flag, but not the mode, so the accumulated union was handed straight back in View. `grow` (`_mode !== 'view'`) is now part of the key, and the union is only ever taken against a frame the same editor session produced. Leaving an editor recomputes from the content; entering one starts from the current geometry instead of resurrecting the union of a previous session. smoke_canvas_frame.mjs grows the auditor's scenario: the frame before, the union inside the editor (asserted, so the growth cannot be "fixed" by deleting it), the frame after exit — 5060..5940 — and re-entry. Two checks are red on the parent commit.
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -4,6 +4,10 @@
|
||||
// frame — hiding a marker that had wandered into the yard left the visible
|
||||
// house a dot in the corner (the full card AND the static
|
||||
// houseplan-space-card, which drew `devs` but framed `spaceDevs`).
|
||||
//
|
||||
// DEV-2C947-02: inside an editor the frame only grows (a frame that shrank
|
||||
// mid-drag would move the ground under the pointer); that union was memoised
|
||||
// without the mode, so it survived the way back into View.
|
||||
import { launch, checkAll, finish } from './serve.mjs';
|
||||
|
||||
const { page, browser } = await launch({ width: 900, height: 820 }, 1);
|
||||
@@ -114,5 +118,51 @@ Object.assign(out, await page.evaluate(async () => {
|
||||
return o;
|
||||
}));
|
||||
|
||||
// ------------------------------------------------------- 02: editor -> view
|
||||
const oneRoomAt = (x0) => ({
|
||||
spaces: [{
|
||||
id: 'e1', title: 'Editor', view_box: [0, 0, 1, 1], plan_url: null, plan_aspect: null,
|
||||
rooms: [{ id: 'r1', name: 'Room', area: 'zx_room',
|
||||
poly: [[x0, 0.1], [x0 + 0.8, 0.1], [x0 + 0.8, 0.9], [x0, 0.9]] }],
|
||||
}],
|
||||
markers: [], settings: { filter_seeded: true },
|
||||
});
|
||||
|
||||
await load(oneRoomAt(0.1), {}, 'e1');
|
||||
await raf();
|
||||
Object.assign(out, await page.evaluate(async () => {
|
||||
const c = window.__card;
|
||||
const o = {};
|
||||
const round = () => c._baseVb().map((n) => Math.round(n));
|
||||
o.viewFrameBefore = JSON.stringify(round()) === JSON.stringify([60, 60, 880, 880]);
|
||||
// move the only room 5 canvases to the right, inside the Plan editor
|
||||
c._setMode('plan');
|
||||
await c.updateComplete;
|
||||
const cfg = JSON.parse(JSON.stringify(c._serverCfg));
|
||||
cfg.spaces[0].rooms[0].poly = [[5.1, 0.1], [5.9, 0.1], [5.9, 0.9], [5.1, 0.9]];
|
||||
c._serverCfg = cfg;
|
||||
c._modelCache = null;
|
||||
c._cfgEpoch++;
|
||||
c.requestUpdate();
|
||||
await c.updateComplete;
|
||||
const inEditor = round();
|
||||
// inside the editor the frame is the UNION — that is deliberate: it bounds
|
||||
// pan and defines zoom 1, and it must not shrink under a live gesture
|
||||
o.editorFrameOnlyGrows = inEditor[2] > 5000;
|
||||
c._setMode('view');
|
||||
await c.updateComplete;
|
||||
const back = round();
|
||||
o.viewFrameFollowsTheRoomAgain = JSON.stringify(back) === JSON.stringify([5060, 60, 880, 880]);
|
||||
if (!o.viewFrameFollowsTheRoomAgain) console.log('frame after exit', back, 'editor', inEditor);
|
||||
// and back into an editor it starts from the CURRENT geometry, not the old union
|
||||
c._setMode('plan');
|
||||
await c.updateComplete;
|
||||
o.reenteringTheEditorDoesNotResurrectTheUnion =
|
||||
JSON.stringify(round()) === JSON.stringify([5060, 60, 880, 880]);
|
||||
c._setMode('view');
|
||||
await c.updateComplete;
|
||||
return o;
|
||||
}));
|
||||
|
||||
checkAll(out);
|
||||
await finish(browser, out);
|
||||
|
||||
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -139,6 +139,19 @@ When `outliers > 0` the card shows an unobtrusive inline hint (no
|
||||
modal) — "there are objects far from the plan" with a **Show** action
|
||||
that fits `all`.
|
||||
|
||||
### §4.3 The frame in an editor
|
||||
|
||||
Inside an editor the frame only ever **grows** (`unionRect` with the previous
|
||||
one): it bounds pan and defines what zoom 1 means, and a frame that shrank the
|
||||
instant a room was deleted would move the ground under a live gesture.
|
||||
|
||||
That union belongs to the editor session and to nothing else. The memo key
|
||||
carries the growth flag, so leaving for View recomputes the frame from the
|
||||
content instead of inheriting the union — otherwise a room dragged five
|
||||
canvases away in the Plan editor kept View framing the empty ground it had
|
||||
left behind, until some unrelated model change happened to invalidate the memo
|
||||
(audit DEV-2C947-02).
|
||||
|
||||
### §4.2 Degenerate frames
|
||||
|
||||
An SVG `viewBox` with a zero axis paints nothing, so a frame still has
|
||||
|
||||
@@ -998,6 +998,11 @@ require hands on real hardware — they remain for the human pass.
|
||||
frame, on the full card and on `houseplan-space-card`. Untick it and the
|
||||
frame takes it in again; room LQI counted it the whole time
|
||||
[auto: smoke_canvas_frame]
|
||||
- [ ] **The editor frame does not follow you out** (audit DEV-2C947-02): move
|
||||
the only room five canvases away in the Plan editor (the frame grows
|
||||
there, deliberately), close the editor — View frames the room where it
|
||||
is NOW, not the union with where it was; re-entering the editor starts
|
||||
from the current geometry [auto: smoke_canvas_frame]
|
||||
- [ ] **A far stray does not break the view** (§4.1): a marker dragged an
|
||||
order of magnitude away leaves the opening view alone and raises the
|
||||
inline chip «Объектов далеко от плана: N» with «Показать». No modal.
|
||||
|
||||
+12
-5
@@ -1843,7 +1843,7 @@ class HouseplanCard extends LitElement {
|
||||
*/
|
||||
private _frame:
|
||||
| { id: string; model: SpaceModel; layout: unknown; devs: unknown; far: boolean;
|
||||
rect: Rect; all: Rect; outliers: number }
|
||||
grow: boolean; rect: Rect; all: Rect; outliers: number }
|
||||
| null = null;
|
||||
/** The outlier hint's «Показать» is on: the frame takes in the far strays
|
||||
* too, so zoom, pan and the fit button all agree about what "everything"
|
||||
@@ -1857,21 +1857,28 @@ class HouseplanCard extends LitElement {
|
||||
// content changes, so this catches a marker drag and a server push alike —
|
||||
// an epoch would have to be bumped at every one of those call sites.
|
||||
const f = this._frame;
|
||||
// `grow` is part of the KEY, not just of the computation (DEV-2C947-02):
|
||||
// the union an editor accumulated is an editor's frame, and leaving for
|
||||
// View has to recompute rather than inherit it — otherwise a room moved
|
||||
// far away in the Plan editor kept View framing the empty ground it left
|
||||
// behind, until some unrelated model change happened to invalidate memo.
|
||||
const grow = this._mode !== 'view';
|
||||
if (f && f.id === m.id && f.model === m && f.layout === this._layout
|
||||
&& f.devs === this._devices && f.far === this._showFar) return f;
|
||||
&& f.devs === this._devices && f.far === this._showFar && f.grow === grow) return f;
|
||||
const cf = contentFrame(this._contentItems(m));
|
||||
let all = cf.all || spaceFrame(m);
|
||||
let rect = this._showFar ? all : (cf.core || spaceFrame(m));
|
||||
if (f && f.id === m.id && this._mode !== 'view') {
|
||||
if (f && f.id === m.id && grow && f.grow) {
|
||||
// Inside an editor the frame only GROWS: it bounds pan and defines what
|
||||
// zoom 1 means, and a frame that shrank the instant a room was deleted
|
||||
// would move the ground under the pointer mid-gesture.
|
||||
// would move the ground under the pointer mid-gesture. Only ever unions
|
||||
// with a frame the SAME editor session produced (f.grow).
|
||||
rect = unionRect(f.rect, rect);
|
||||
all = unionRect(f.all, all);
|
||||
}
|
||||
this._frame = {
|
||||
id: m.id, model: m, layout: this._layout, devs: this._devices,
|
||||
far: this._showFar, rect, all, outliers: cf.outliers,
|
||||
far: this._showFar, grow, rect, all, outliers: cf.outliers,
|
||||
};
|
||||
return this._frame;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user