v1.50.2: the v1.50.1 review (HP-1501-01, HP-1501-02)

- HP-1501-01: v1.50.1 bounded layout positions and left room rectangles,
  polygon vertices, view_box and opening coordinates on bare _finite — the
  same absurd-magnitude failure, one schema over. _GEOM (±4) covers them all
  now, opening angles get ±360. And because a store may already hold such a
  vertex from before the door existed, contentBounds applies its canvas
  envelope to room geometry exactly as it does to device positions: the
  point renders where it is, the frame ignores it, a space of nothing but
  absurd points falls back to the whole canvas.
- HP-1501-02: a repair matching zero positions answered ok/moved:0 and
  replaced the one-deep backup with an empty one — a typo right after
  repairing the wrong space destroyed the promised way back. Empty match is
  nothing_to_repair now: no write, no revision bump, backup intact.

Old test fixtures carried view_box [0,0,100,100] from the render-unit days;
they now use the normalised box the product actually stores.
This commit is contained in:
Matysh
2026-07-29 07:30:22 +03:00
parent aa53b33dd6
commit 5392dadeaa
18 changed files with 193 additions and 36 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ PLAN_ORPHAN_TTL_S = 3600
SCHEDULED_GRACE_S = 30 * 24 * 3600
FILES_DIR = "houseplan/files"
CONF_ADMIN_ONLY = "admin_only"
VERSION = "1.50.1"
VERSION = "1.50.2"
DEFAULT_CONFIG: dict = {
"spaces": [],
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -16,5 +16,5 @@
"issue_tracker": "https://github.com/Matysh/houseplan-card/issues",
"requirements": [],
"single_config_entry": true,
"version": "1.50.1"
"version": "1.50.2"
}
+16 -9
View File
@@ -107,7 +107,14 @@ POS_SCHEMA = vol.Schema(
)
LAYOUT_SCHEMA = vol.All(vol.Schema({str: POS_SCHEMA}), vol.Length(max=MAX_LAYOUT))
POINT = vol.All([_finite], vol.Length(min=2, max=2))
# Geometry is normalised to the canvas (0..1). ±4 is generous slack for a
# vertex nudged past an edge, not an envelope for arbitrary magnitudes: any
# finite float used to pass here, and a single 1e100 room vertex stretched the
# frame until the whole space was unviewable for every client (HP-1501-01) —
# the exact failure HP-1500-03 closed for layout positions, one schema over.
_GEOM = vol.All(_finite, vol.Range(min=-4.0, max=4.0))
POINT = vol.All([_GEOM], vol.Length(min=2, max=2))
def _require_geometry(room: dict) -> dict:
@@ -136,10 +143,10 @@ ROOM_SCHEMA = vol.All(
extra=vol.ALLOW_EXTRA,
),
),
vol.Optional("x"): _finite,
vol.Optional("y"): _finite,
vol.Optional("w"): _finite,
vol.Optional("h"): _finite,
vol.Optional("x"): _GEOM,
vol.Optional("y"): _GEOM,
vol.Optional("w"): _GEOM,
vol.Optional("h"): _GEOM,
vol.Optional("poly"): vol.All([POINT], vol.Length(min=3, max=MAX_POLY_POINTS)),
},
extra=vol.ALLOW_EXTRA,
@@ -203,7 +210,7 @@ SPACE_SCHEMA = vol.Schema(
vol.Optional("plan_aspect"): vol.Any(
None, vol.All(vol.Coerce(float), vol.Range(min=0.05, max=20))
),
vol.Required("view_box"): vol.All([_finite], vol.Length(min=4, max=4)),
vol.Required("view_box"): vol.All([_GEOM], vol.Length(min=4, max=4)),
vol.Required("rooms"): vol.All([ROOM_SCHEMA], vol.Length(max=MAX_ROOMS)),
vol.Optional("decor"): vol.All([DECOR_SCHEMA], vol.Length(max=MAX_DECOR)),
vol.Optional("openings"): vol.All([
@@ -211,9 +218,9 @@ SPACE_SCHEMA = vol.Schema(
{
vol.Required("id"): str,
vol.Required("type"): vol.Any("door", "window"),
vol.Required("x"): _finite,
vol.Required("y"): _finite,
vol.Required("angle"): _finite,
vol.Required("x"): _GEOM,
vol.Required("y"): _GEOM,
vol.Required("angle"): vol.All(_finite, vol.Range(min=-360.0, max=360.0)),
vol.Required("length"): vol.All(vol.Coerce(float), vol.Range(min=0.001, max=1)),
vol.Optional("contact"): vol.Any(str, None),
vol.Optional("lock"): vol.Any(str, None),
@@ -212,6 +212,16 @@ async def ws_geometry_repair(hass: HomeAssistant, connection, msg: dict[str, Any
k: dict(v) for k, v in layout.items()
if isinstance(v, dict) and str(v.get("s")) == space_id
}
if not touched:
# A typo'd space id used to "succeed" with moved: 0 — and its
# empty result REPLACED the one-deep backup, destroying the very
# undo this endpoint promises (HP-1501-02). Nothing to move means
# nothing to save: no write, no revision bump, the backup stays.
connection.send_error(
msg["id"], "nothing_to_repair",
f"No stored positions belong to space '{space_id}'",
)
return
preview = {k: dict(v) for k, v in touched.items()}
migrate_layout(preview, {space_id: msg["aspect"]})
if msg.get("dry_run"):