B3: _persistDevicePlacement sent canonicalizePosition(...) to the server
and left the raw value in _layout, then recorded the fingerprint over
that raw snapshot. Canonicalization is not identity — it snaps to the
lattice — so 39 of 115 pixel-derived coordinates differ, and the next
_reloadLayoutOnly or _adoptStructuralResponses saw its own write as a
remote edit: history cleared, _layout replaced. The old _persistLayout
wrote the canonical value back; the per-device path introduced by #74
lost that line.
M1: the smoke that was supposed to prove AC10 assigned
serverLayout = structuredClone(c._layout) right before the reload —
erasing by hand the very divergence it existed to catch, so it could not
fail. The fake WS already stores what went over the wire; the
assignment is gone and the check now reddens on the unfixed code
(verified: three checks red without the fix, including this one).
Also proven, because the fix touches their neighbourhood: the echo of a
DELETE keeps the history (the branch removes a key rather than replacing
a value), and an in-flight write still wins the merge against a server
answer holding the old position.
One existing assertion was loosened deliberately: undo now restores a
position that may differ from the raw one by the lattice snap (<1e-9 of
the plan). That is the point of the fix — local and server agree — so the
equality is stated to that precision, with the snap size pinned
separately so a real drift would still fail.
User-Visible: yes
Issue: #397
A fully fictional house (plans, devices, states) used for README screenshots,
the demo GIF and headless smoke tests — so no real home data ever appears in
public materials.
srv/demo.html — self-contained host page: <ha-icon>/<ha-card> stubs and a
fake hass (registries, states, callWS, callService, floors).
srv/assets/ — generated plan SVGs and icons.js (node demo/gen_icons.mjs,
needs the repo's devDependencies). The card bundle is copied from dist/:
npm run bundle:sync (копия стенда не коммитится, #255).
serve.mjs — playwright launcher (route interception, no web server).
smoke_*.mjs — feature smoke tests; run with a Chromium installed via
PLAYWRIGHT_BROWSERS_PATH=<dir> npx playwright install chromium-headless-shell.
Note for sandboxed sessions: /tmp does not survive; this directory is the
persistent home of the harness (docs/DEVELOPMENT.md has the LD_LIBRARY_PATH recipe).