The ±4 bound from v1.50.2 measured view_box[2:4] and room w/h with the same
ruler as coordinates, so zero and negative sizes still passed the schema —
and viewBox='0 0 0 0' draws nothing on every client, with the static card
computing aspect-ratio: 0 / 0 on top. _EXTENT now requires strictly positive
sizes with a floor of one thousandth of the canvas (1 render unit — far
below any real room, keeps the maths finite); coordinates stay allowed to be
negative, a crop origin legitimately sits past the edge.
Defensive layer for stores that already hold a broken viewport: spaceModels
falls back to the whole canvas — both cards render from that model, so both
get the fallback — and a legacy rectangle with a negative size reads as the
same rectangle drawn from the other corner.
Also: the room settings button is the bottom row of the room card, and the
room name renders in the same spot in view and plan modes (owner's request,
committed earlier on dev).
- 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.
- HP-1500-02: the stage budget was the absolute document coordinate, so any
tall dashboard content before the card was billed as header and the stage
collapsed to 0px. Measure our own chrome relative to the card plus a
bounded (<=120px) allowance for what the viewport keeps above us; re-measure
on window resize, remove the listener in disconnectedCallback.
- HP-1500-03, both layers: contentBounds opens a near-zero axis (< ~an icon)
up to a 200-unit floor and ignores extra points outside a canvas envelope
(-25%..125%) for FRAMING purposes only; the server bounds layout coordinates
to +-4 — any finite float used to pass, and one 1e100 hid the plan from
every viewer. A thin real room keeps its tight frame; the gate sensor past
the edge still stretches it.
- HP-1500-01: no automatic double-transform — a correct layout and a stranded
one are indistinguishable, and guessing wrong corrupts good data. Explicit
admin command houseplan/geometry/repair: dry_run previews, the backup rides
the same store write, undo restores, and routine layout writes now preserve
unrelated store keys instead of eating the backup.
Tests: contentBounds guards (unit), layout coordinate bounds + repair
lifecycle (harness), card-below-content smoke. Inventory: 139 / 49 / 42 / 64.
Three owner reports:
- The content frame behind the default zoom only looked at rooms, and devices
are allowed to stand outside every one of them — a gate sensor by the fence
was left outside the opening view. contentBounds() takes the marker positions
now, and they count as content even on a space with no rooms at all.
- Entering an editor 'strangely shifted' the plan. The stage height was
100dvh minus a hard-coded 118px of header, and the editor header is ~90px
taller than that: the whole scene slid down by the difference and its bottom
went below the fold. The card now measures where the stage actually starts
(HA toolbar, margins and our header included) and gives it the rest of the
viewport; the measurement is deferred a frame because setting state straight
from a ResizeObserver callback trips the 'undelivered notifications' error,
which smoke_dialog_zombie rightly counts as a page error.
- Zoom stopped at the base fit. The floor is 0.4× now, and zoomed out the
clamp centres the content instead of pinning it to the top-left corner —
with the view larger than the plan there is nothing to clamp against.
New smoke: smoke_zoom_out.mjs (editor keeps the stage inside the viewport,
0.4 floor, centring, the device-stretched frame); a unit test for the extra
points of contentBounds. Not released — the next release goes out after the
v1.49.0 audit.
Owner's batch:
- zoom now opens on what is DRAWN (rooms + 5% margin) for spaces with no
background image; with one the image is the plan and still fits whole. A small
plan on the square canvas no longer opens as a speck.
- swiping between spaces, and the kiosk carousel, slide sideways; honours
prefers-reduced-motion.
- the room settings button reads 'Room settings' and lightens on hover.
- 'curation' is filtering everywhere: UI strings, docs, code.
Checked the yard while I was there: its drawing sits off-centre because it was
drawn that way — before the migration x spanned 0.12..0.54 with 0.12 and 0.46 of
margin. The migration added 0.1465 on each side, symmetrically. Content-fit zoom
makes it moot anyway.
From the v1.47.0 review:
- HP-1470-02: the picker let you delete the plan you had just selected — it is
not in the stored config yet, so the server rightly called it free, and the
save then stored a url with no file. The button is disabled, and since two
clients can do this in either order, config/set now verifies every internal
plan url against the disk under the write lock and answers .
External and legacy urls are not ours to police.
- HP-1470-01: growth is bounded at the door rather than by deleting old files —
that mistake cost real plans twice. check_quota refuses an upload that would
push the store past 256 MB / 200 plans (1 GB / 1000 attachments) or leave less
than 512 MB free. The plan list is capped at 60 newest with a total, and
thumbnails load lazily.
- HP-1470-03: picking a saved plan waited for nothing and stored a fallback
ratio when the signature had not arrived — a square plan came out stretched.
It waits for the signature, binds the result to the dialog that asked, and the
dialog preview is signed too.
- report §5: the last lifecycle comments still described age-based collection.
Not released yet — the owner asked for a release once the batch is done.
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.