Files
houseplan-card/demo/smoke_plan_upload_race.mjs
T
Matysh 94b298962a v1.48.0: the canvas is always square, the plan is centred inside it
A space carried an aspect ratio, and coordinates were normalised against it: x
by the width, y by the height. Every geometric question therefore depended on a
per-space number, and picking a canvas orientation was a decision the user had
no reason to make. The render space is now NORM_W x NORM_W and a plan image is
fitted into it by its OWN ratio, centred — wide plans get margins above and
below, tall ones at the sides.

Migration (geometry_migration.py, pure and unit-tested) runs once at setup under
the write lock. Nothing about a drawing changes: the old box is padded out to a
square and every coordinate re-expressed against it — rooms as rects and
polygons, openings and their lengths, decor, view_box, and the marker positions
in the separate layout store. In render units it is a uniform scale plus an
offset, so angles and proportions are exact. cell_cm is scaled for tall plans,
because the grid pitch is a fraction of the width: without it a wall would
measure less than it does.

 is now dropped by the schema rather than accepted — a stale tab sending
it would be sending coordinates from the old normalisation too, and honouring
the field would not make them right.

The demo fixture was migrated with the same transform, so the smokes exercise
the new geometry rather than a square-native fake; six of them needed their
render-space helpers updated and one its click coordinates.

Not released — dev only, per the owner's instruction.
2026-07-28 22:20:59 +03:00

71 lines
3.6 KiB
JavaScript

// Загрузка подложки: ссылка обязана долететь до конфига.
// Баг 2026-07-27 (найден на боевой установке): _saveSpaceDialog держал ссылку
// на объект пространства через await загрузки файла. Любое событие
// houseplan_config_updated в этот момент вызывает _reloadConfigOnly(), которое
// ЗАМЕНЯЕТ _serverCfg — и plan_url/aspect/settings уезжали в осиротевший
// объект, а на сервер уходил нетронутый конфиг. Симптом: файл на диске есть,
// подложки нет, пересохранение не помогает.
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const base = c.hass.callWS;
let reloadDuringUpload = 0;
c.hass = { ...c.hass, callWS: async (m) => {
if (m.type === 'houseplan/plan/set') {
// пока файл «загружается», прилетает чужая ревизия конфига
reloadDuringUpload++;
await c._reloadConfigOnly(true);
return { ok: true, url: '/api/houseplan/content/plans/_/' + m.space_id + '.png?v=42' };
}
if (m.type === 'houseplan/config/set') { c.__sent = m.config; return { ok: true, rev: 99 }; }
if (m.type === 'houseplan/config/get') {
// сервер отдаёт СВЕЖИЙ объект, а не тот же самый — как в реальном HA
const r = await base(m);
return { ...r, config: JSON.parse(JSON.stringify(r.config)) };
}
return base(m);
} };
// редактирование существующего пространства: подложка + новый заголовок
c._openSpaceDialog('edit', 'f1'); await c.updateComplete;
c._spaceDialog = { ...c._spaceDialog, title: 'Ground', source: 'file',
planFile: { ext: 'png', b64: 'AAAA', aspect: 1.6 } };
await c._saveSpaceDialog(); await c.updateComplete;
out.reloadHappened = reloadDuringUpload === 1;
const sentF1 = (c.__sent?.spaces || []).find((s) => s.id === 'f1');
const liveF1 = (c._serverCfg?.spaces || []).find((s) => s.id === 'f1');
out.sentPlanUrl = sentF1?.plan_url;
out.sentPlanAspect = sentF1?.plan_aspect; // the IMAGE's ratio; the canvas is square
out.sentTitle = sentF1?.title;
out.livePlanUrl = liveF1?.plan_url;
out.dialogClosed = c._spaceDialog === null;
// создание пространства при том же сбое: оно должно доехать целиком
c._openSpaceDialog('create'); await c.updateComplete;
c._spaceDialog = { ...c._spaceDialog, title: 'Attic', source: 'file',
planFile: { ext: 'png', b64: 'BBBB', aspect: 0.8 } };
await c._saveSpaceDialog(); await c.updateComplete;
const attic = (c.__sent?.spaces || []).find((s) => s.title === 'Attic');
out.atticSaved = !!attic;
out.atticHasPlan = !!attic && typeof attic.plan_url === 'string' && attic.plan_url.includes('/content/plans/');
out.atticPlanAspect = attic?.plan_aspect;
return out;
});
// зафиксировано прогоном на v1.44.8 и сверено с кодом
checkAll(res, {
reloadHappened: true,
sentPlanUrl: '/api/houseplan/content/plans/_/f1.png?v=42',
sentPlanAspect: 1.6,
sentTitle: 'Ground',
livePlanUrl: '/api/houseplan/content/plans/_/f1.png?v=42',
dialogClosed: true,
atticSaved: true,
atticHasPlan: true,
atticPlanAspect: 0.8,
});
await finish(browser);