Files
houseplan-card/docs/CHANGELOG.md
T
2026-08-13 22:11:02 +03:00

3833 lines
236 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
## Unreleased
## v1.63.0-beta.2 — 2026-08-13
- Splitting a room from an existing corner no longer deforms the exterior wall
or pulls a thick internal divider through the facade. Plan, View, kiosk,
static cards, hidden isometric rendering and light obstacles now use the same
preserved exterior geometry, including already saved plans
([#123](https://github.com/Matysh/houseplan-card/issues/123)).
- Small fixes and improvements.
## v1.63.0-beta.1 — 2026-08-13
- Deleting a standalone sensor or lock marker no longer breaks its explicit
door, window or gate binding: the entity stays available in opening settings
and continues to drive its state without restoring the marker on the plan
([#104](https://github.com/Matysh/houseplan-card/issues/104)).
- Fixed a crash after deleting the last space: the supported empty state now
stays usable so a new space can be added ([#111](https://github.com/Matysh/houseplan-card/issues/111)).
- Small fixes and improvements.
## v1.62.0 — 2026-08-13
- Added portable full-plan and single-space backups with server-validated
preview, safe import and one-step undo, plus reusable contextual help for
complex settings ([#50](https://github.com/Matysh/houseplan-card/issues/50),
[#68](https://github.com/Matysh/houseplan-card/issues/68)).
- Added passive light markers, explicit leading-entity selection and
user-controlled value badges, while unifying device actions and activity
presentation across the plan, preview and static cards
([#84](https://github.com/Matysh/houseplan-card/issues/84),
[#88](https://github.com/Matysh/houseplan-card/issues/88),
[#90](https://github.com/Matysh/houseplan-card/issues/90),
[#94](https://github.com/Matysh/houseplan-card/issues/94),
[#98](https://github.com/Matysh/houseplan-card/issues/98)).
- Reworked opening placement with a Window / Door / Gate submenu and a
full-symbol preview on thick walls. Fixed room Split wall thickness, opening
tunnel seams and Glow sources embedded in walls or exterior openings
([#75](https://github.com/Matysh/houseplan-card/issues/75),
[#76](https://github.com/Matysh/houseplan-card/issues/76),
[#81](https://github.com/Matysh/houseplan-card/issues/81),
[#91](https://github.com/Matysh/houseplan-card/issues/91),
[#92](https://github.com/Matysh/houseplan-card/issues/92)).
- Added coherent transitions between View and editors, restored reliable
window sun rays, and hardened editor lifecycle, composite-device selection,
previews and regression coverage
([#93](https://github.com/Matysh/houseplan-card/issues/93),
[#95](https://github.com/Matysh/houseplan-card/issues/95),
[#101](https://github.com/Matysh/houseplan-card/issues/101),
[#102](https://github.com/Matysh/houseplan-card/issues/102)).
- Small fixes and improvements.
## v1.62.0-rc.1 — 2026-08-13
- Republished the reviewed beta.10 candidate under an RC identifier so HACS
discovers it after beta.9; the application code and user-visible behaviour
are unchanged.
## v1.62.0-beta.10 — 2026-08-13
- Fixed composite device presentation and actions after the beta review:
an explicitly selected light entity now remains the tap target, a primary
cover keeps the cover face, and registry-backed state selection is identical
in the full plan, preview and static card. Strengthened Split, Glow,
contextual-help and opening-preview regression coverage.
- A light source placed inside a physical wall, window tunnel or exterior
door/gate opening intentionally produces no Glow; move its marker onto the
room floor to restore illumination ([#92](https://github.com/Matysh/houseplan-card/issues/92)).
- Small fixes and improvements
([#108](https://github.com/Matysh/houseplan-card/issues/108)).
## v1.62.0-beta.9 — 2026-08-12
- Fixed post-review inconsistencies between device state and tap behaviour:
exact bindings no longer toggle a sibling entity, a passive relay controls
its leading entity, and incidental cover or humidity entities no longer
take over a composite device's presentation.
- Hardened opening placement with shared defaults, a stable geometry cache,
correct centre snapping and a stricter visual preview gate. Restored the
public activity and preview-dot CSS hooks, improved help accessibility and
strengthened internal process checks.
- Small fixes and improvements
([#108](https://github.com/Matysh/houseplan-card/issues/108)).
## v1.62.0-beta.8 — 2026-08-12
- Reworked opening placement in the Plan editor. **Opening** now opens a compact
Window / Door / Gate sub-menu; choosing a type arms its 120 / 90 / 300 cm
preset and shows the complete future architectural symbol at 50% opacity on
top of physical walls. Thick-wall targeting, junction selection, click
without prior hover and repeated placement are now deterministic, while the
persisted opening model remains unchanged
([#75](https://github.com/Matysh/houseplan-card/issues/75),
[#76](https://github.com/Matysh/houseplan-card/issues/76)).
- Hardened the beta after the full review: editor close/navigation can no
longer be swallowed by an active transition or context tray; device taps,
passive light state and value badges now use the same resolved sources on
every surface; legacy temperature/humidity satellites are preserved; help,
paired import/export and split-wall geometry gained the missing guards and
regression coverage. Small fixes and internal improvements
([#50](https://github.com/Matysh/houseplan-card/issues/50),
[#68](https://github.com/Matysh/houseplan-card/issues/68),
[#84](https://github.com/Matysh/houseplan-card/issues/84),
[#90](https://github.com/Matysh/houseplan-card/issues/90),
[#91](https://github.com/Matysh/houseplan-card/issues/91),
[#92](https://github.com/Matysh/houseplan-card/issues/92),
[#94](https://github.com/Matysh/houseplan-card/issues/94),
[#95](https://github.com/Matysh/houseplan-card/issues/95),
[#98](https://github.com/Matysh/houseplan-card/issues/98),
[#101](https://github.com/Matysh/houseplan-card/issues/101)).
## v1.62.0-beta.7 — 2026-08-12
- Fixed the first opening of device settings: **Display**, **Room** and
**Leading light entity** now immediately show the value that is actually
persisted instead of the first option. The form and its preview can no
longer report different settings.
- Added a shared native-select contract and browser checks that reopen a device
with non-first persisted choices. Future selectors can no longer silently
reintroduce the unsafe binding pattern.
## v1.62.0-beta.6 — 2026-08-12
- Fixed the remaining cause of invisible window sun rays in real Home Assistant
installations. Core/runtime entities such as `sun.sun` may have a live state
without an Entity Registry row; House Plan now preserves that state while
continuing to exclude explicitly disabled entities and devices
([#102](https://github.com/Matysh/houseplan-card/issues/102)).
- Added a deterministic visual regression crop of an exterior window and its
ray. The browser now compares the reviewed image with the same frame after
hiding only the sun layer and fails unless the ray paints a material number
of pixels, so an empty screenshot cannot be accepted as success.
## v1.62.0-beta.5 — 2026-08-12
- Unified all device activity around one predictable pulse system. Alerts use
the red alarm pulse, witnessed events use three finite waves, and ongoing
work, movement or presence uses one continuous pulse. Static rings are gone;
reduced-motion users get a compact dot for ordinary activity. The full plan,
device preview and static space card now share the same resolver and renderer
([#98](https://github.com/Matysh/houseplan-card/issues/98)).
- Made transitions between View and all three editors coherent. Toolbar height,
usable plan area, camera, background and editor presentation now move on one
short timeline; editor-to-editor switches fade their incoming controls, rapid
choices retarget from the visible frame, and reduced motion remains atomic
([#101](https://github.com/Matysh/houseplan-card/issues/101)).
- Fixed the device editor's **Value** selector visually showing its first row
instead of the source that was actually saved and rendered on the plan.
Saved source and position are now selected explicitly when the dialog opens,
including temporarily missing sources ([#100](https://github.com/Matysh/houseplan-card/issues/100)).
- Fixed contextual help temporarily adding a vertical scrollbar to its owning
dialog. The accessibility-only description no longer participates in the
dialog's scroll geometry in either native Popover or fallback mode
([#99](https://github.com/Matysh/houseplan-card/issues/99)).
- Fixed a transition regression that could leave window sun rays invisible in
View. The ray layer again owns its lifecycle independently from editor
transitions, without changing sun position, window geometry or the existing
threshold fade ([#102](https://github.com/Matysh/houseplan-card/issues/102)).
## v1.62.0-beta.4 — 2026-08-12
- Fixed contextual help showing a dead icon when its explanation was absent.
Incomplete help content is now omitted entirely, and the plain `?` glyph was
replaced with the consistent outlined circled-question icon
([#68](https://github.com/Matysh/houseplan-card/issues/68)).
- Hardened universal **Toggle state** after the beta code review. Climate,
water-heater, siren, camera, media-player and legacy-vacuum actions now
require the exact HA entity capability bits as well as a live service;
clicks resolve current controls instead of a retained visual frame, and a
disabled legacy cover keeps its original, explainable target
([#94](https://github.com/Matysh/houseplan-card/issues/94)).
- Fixed the device editor showing **Device card** for a lamp whose effective
default action is **Toggle state**. Untouched actions now follow the current
preview entity in real time, while explicit choices and lossless legacy
storage remain unchanged
([#97](https://github.com/Matysh/houseplan-card/issues/97)).
- Fixed **Device card** taps doing nothing on compound curtain/cover devices.
The local House Plan card now opens independently of a momentary HA registry
revalidation, while HA more-info and service actions retain their safety gate
([#96](https://github.com/Matysh/houseplan-card/issues/96)).
- Fixed an editor-close race after a same-route technical remount. A visible
editor now closes on the first press even while write permission is still
resolving; the late server response can no longer reopen it
([#95](https://github.com/Matysh/houseplan-card/issues/95)).
## v1.62.0-beta.3 — 2026-08-12
- Replaced the separate Toggle and cover actions with one universal **Toggle
state** action. The device editor now shows the exact target, current/next
effect and any skipped or unsupported entities; covers and valves retain
open/close/stop behaviour, secure devices remain no-op, and configured
no-target actions never fall back to an info card. Existing `cover` records
remain lossless until deliberately edited
([#94](https://github.com/Matysh/houseplan-card/issues/94)).
- Returning to House Plan now restores only the last space and always opens
View. Editor mode and dialogs no longer survive page reloads or navigation
through other Home Assistant pages; same-route technical remounts still
protect unfinished work ([#93](https://github.com/Matysh/houseplan-card/issues/93)).
- Fixed Glow starting from inside a thick wall or an exterior opening and
lighting one half of an exterior door or gate tunnel. Sources embedded in
the final opaque masonry geometry now produce no Glow at all, while real
interior passages remain transparent
([#92](https://github.com/Matysh/houseplan-card/issues/92)).
## v1.62.0-beta.2 — 2026-08-11
- Added a user-controlled value badge beside each device. Users can select the
exact reading and place it on the right, bottom, left or top; preview, the
main plan and the static space card consume one resolver, while untouched
legacy markers retain their previous automatic behaviour
([#90](https://github.com/Matysh/houseplan-card/issues/90)).
- Fixed room Split losing the thickness of adjoining walls. Split now
materializes exact intervals from the legacy wall profile before changing
geometry, so the new thickness applies only to the divider
([#91](https://github.com/Matysh/houseplan-card/issues/91)).
- Hardened portable backups: full import retains the model version, write
endpoints and preview tokens have negative coverage, and the test harness
clears temporary plans and attachments.
- Removed the contextual-help scroll-listener leak and made the plan-wide light
graph resolve once per frame instead of once for every marker.
- Strengthened golden, smoke and repository gates: the seam scenario now
crosses a stepped wall, baseline inventory is exact, and temporary-file plus
browser-quiescence flakes are eliminated.
## v1.62.0-beta.1 — 2026-08-11
- Fixed layout maintenance silently discarding the one-deep Optimize/Import
undo snapshot. Explicit geometry repair and its undo now preserve all store
metadata and keep the plan snapshot valid for the resulting layout revision
([#87](https://github.com/Matysh/houseplan-card/issues/87)).
- Added passive plan light sources and explicit leading-entity selection. A
virtual dumb lamp can now own its Glow position, colour, brightness and
radius while following a smart relay; multiple controllers use OR, and
`marker:*` links are validated without ever reaching Home Assistant as entity
IDs. Multi-channel Always sources can select the meaningful `light.*` or
`switch.*` instead of silently using registry order
([#84](https://github.com/Matysh/houseplan-card/issues/84),
[#88](https://github.com/Matysh/houseplan-card/issues/88)).
- Added portable House Plan backups: editors can export the complete model or
one space, inspect a server-validated JSON import before any write, safely
handle duplicate HA bindings and undo a full replacement until the next plan
edit. Internal plans and attachments remain local references rather than
being silently copied between Home Assistant instances
([#50](https://github.com/Matysh/houseplan-card/issues/50)).
- Fixed recurring one-pixel SVG seams in room-coloured door, window and gate
tunnels. Equal and stepped wall profiles now render as continuous contours
without translucent overlaps or gaps at fractional zoom ([#81](https://github.com/Matysh/houseplan-card/issues/81)).
- Added reusable localized help buttons for complex settings. They work with
mouse, keyboard and touch, stay inside the viewport, and share the dialog's
Escape and focus lifecycle ([#68](https://github.com/Matysh/houseplan-card/issues/68)).
## v1.61.0 — 2026-08-11
- Expanded robot-vacuum integration coverage with deterministic source
selection, explicit capability and health diagnostics, gap-preserving trails,
safer map arbitration and stricter calibration checks.
- Reworked room colour and lighting controls. Glow is an independent additive
overlay, room fills keep their exact configured colour, and each source can
use automatic, always-on or disabled behaviour with manual colour, brightness
and radius where applicable.
- Light propagation now follows the same physical wall bodies drawn on the
plan, including wall thickness, jambs, partitions and columns. Doorways and
virtual boundaries transmit light without seams, while walls and corners cast
stable clipped shadows.
- Returning to a tab, reconnecting Home Assistant or remounting the card keeps
the last coherent plan frame. Devices, rooms, Glow, sun, openings, vacuums and
live decorative text update atomically instead of flashing partial state.
- Small fixes and improvements to dialogs, room hover, device markers, editor
stability, validation and release tooling.
## v1.61.0-beta.8 — 2026-08-11
- Plan recovery now has one bounded two-second barrier: a stalled signature or
protected-backdrop decode can no longer leave an already complete plan below
an opaque layer forever. The card retains its last coherent frame, while a
cold failure exposes an explicit retryable error.
- Added an immutable per-frame data snapshot. Devices, positions, room fills,
Glow, sun, openings, vacuums and HA variables in decorative text now update
together instead of mixing old and new state during reconnect.
- Connection readiness is treated as recovery only after a confirmed loss;
ordinary HA ticks no longer alter the structural frame fingerprint. Forced
config refreshes, signed-URL caching and late image load events are now
protected from their respective races.
- Strengthened deterministic continuity, protected-backdrop and screencast
coverage. The browser scenario now exercises the production visibility
listener while checking geometry, devices, Glow, sun and decor together.
## v1.61.0-beta.7 — 2026-08-11
- Fixed rare but severe Glow geometry failures: a clipping exception can no
longer paint outside the house, a grid-snapped source on a wall stays dark,
and the angular seam no longer cuts a wedge from an otherwise valid pool.
Light barriers now invalidate for every changed body point, wall endpoint,
scale or space.
- Delete/Cancel/Save in opening and space dialogs now use the same responsive
grouped footer as physical-object properties, keeping localized actions
inside narrow dialogs.
- Returning to a dashboard no longer hides or rebuilds an already complete
plan. Full and compact cards keep the last coherent frame through tab sleep,
reconnect, remount and transient resize; identical config/layout echoes are
adopted without geometry churn, and protected backdrops share a bounded
loaded cache. A localized recovery layer is reserved for the rare case where
no complete frame can be retained. ([#73](https://github.com/Matysh/houseplan-card/issues/73))
- Restored the room-hover contract without bringing back the Glow flash: a
neutral clean-floor wash now darkens the resolved room fill while its late
outline remains above walls; Glow, sun, openings and devices stay visually
independent. ([#79](https://github.com/Matysh/houseplan-card/issues/79))
- Working device markers keep their yellow resting plate with the ordinary
neutral elevation shadow, use the same hover as other interactive markers,
and no longer add an unrelated yellow outer glow.
([#80](https://github.com/Matysh/houseplan-card/issues/80))
## v1.61.0-beta.6 — 2026-08-11
- Fixed shared dialog layout for localized content: multi-line titles now grow
the Home Assistant header instead of being clipped, and physical-object
footers keep Delete inside the left inset while Cancel/Save wrap together on
narrow dialogs. ([#77](https://github.com/Matysh/houseplan-card/issues/77),
[#78](https://github.com/Matysh/houseplan-card/issues/78))
- Light now works one way, everywhere: a lamp lights the floor it can see.
Walls block it with their real thickness — the same bodies the plan draws —
as do columns and free-standing partitions; doorways, gates and virtual
boundaries simply are not walls, so light crosses them, and a thick wall's
jambs narrow the beam exactly as they would in the house. Everything
else follows from that single rule — a doorway carries one continuous light
instead of an unlit bar and a detached blob, a beam ends where the receiving
room's own walls end, a column and a wall corner cast crisp shadows (also
when they belong to a room further away), and shadow edges are lines with a
hairline penumbra rather than smears. A window and an outside door stay solid
for light — there is no floor behind a front door to light, so it no longer
glows halfway. Pools also fade over their whole radius instead of holding a
flat plateau to 70%, so distant floor is faint because it is distant. As a
side effect the light layer became a single clipped circle per source. Cold
geometry calculation on a complex plan became dramatically faster; the warm
render path remains broadly comparable with beta.5 and is tracked separately.
([#71](https://github.com/Matysh/houseplan-card/issues/71))
- Fixed a one-frame Glow brightness flash when hovering any room. Room hover
now uses a plain SVG wash and double outline without compositor filters.
([#72](https://github.com/Matysh/houseplan-card/issues/72))
- Prerelease publishing now accepts an intentionally skipped announcement job,
so recovery after a partial workflow run no longer rejects a valid release.
## v1.61.0-beta.5 — 2026-08-10
- Device settings now offer Auto / Always / Never light-source roles. Per-source
Glow can follow Home Assistant, use a custom colour with live brightness, or
fix both colour and brightness; dim lights remain visibly readable through a
perceptual intensity curve. ([#65](https://github.com/Matysh/houseplan-card/issues/65),
[#66](https://github.com/Matysh/houseplan-card/issues/66),
[#67](https://github.com/Matysh/houseplan-card/issues/67))
- Compatibility note: an explicitly stored `is_light: false` now means Never,
rather than behaving like Auto. UI-authored historical configs normally used
`null`, so this primarily affects hand-written YAML.
- Prerelease validation now uses a short candidate-only 60-source Glow
performance smoke. The expensive same-runner baseline comparison remains
mandatory for stable releases and runs on `main`, weekly and on demand.
([#69](https://github.com/Matysh/houseplan-card/issues/69))
- Telegram release announcements are now sent only for stable releases; betas
and release candidates publish silently.
([#70](https://github.com/Matysh/houseplan-card/issues/70))
## v1.61.0-beta.4 — 2026-08-10
- Performance comparison now keeps newly introduced private members optional
for older supported baselines while validating every present field's runtime
type. Rewritten-push baselines prefer the candidate parent and emit a warning
instead of silently jumping to an older release. ([#15](https://github.com/Matysh/houseplan-card/issues/15),
[#16](https://github.com/Matysh/houseplan-card/issues/16))
- In Glow spaces, LQI/Light/Temperature modes without usable HA data now fall
back to the dark room base instead of exposing a bright unfilled paper hole.
A genuinely resolved data or custom fill still keeps its exact color and
opacity. ([#61](https://github.com/Matysh/houseplan-card/issues/61))
- In space settings, Custom color is now the ordinary first room-fill mode and
replaces the separate None choice. Historical spaces with `fill_mode: none`
open as a zero-opacity custom color, preserving the same floor and Glow
appearance through Preview and Save. New spaces also start at zero opacity,
so replacing the choice does not change their previous initial appearance.
An individual room can still suppress an inherited dynamic fill. ([#64](https://github.com/Matysh/houseplan-card/issues/64))
- Prerelease publication can now be completed with one fail-closed local
command or a manual GitHub Actions run. Version files, bilingual release
notes, the exact-SHA Validate result, annotated tag, both release assets and
HACS discovery are checked before completion. Downloaded JS/ZIP contents are
bound to the candidate hash and manifest version; per-tag concurrency rejects
parallel publication. ZIP inspection is portable across Windows and Linux
without a system `tar`, while standalone JS and hashes come from exact Git
blobs rather than CRLF-sensitive worktree bytes; stale assets are repaired
automatically and handled
interruptions release the local lock. The public release is staged as a draft
first and partial failures are safe to retry. Existing release workflows remain
available as a fallback. ([#63](https://github.com/Matysh/houseplan-card/issues/63))
## v1.61.0-beta.3 — 2026-08-10
- Performance tooling now fails before measurement when a candidate/base card
no longer exposes a private API consumed by the benchmark, documents the
safe two-revision rename path, and falls back from an unreachable force-push
comparison SHA to the latest reachable release. The bilingual release-body
headings now have one explicit canonical template. ([#15](https://github.com/Matysh/houseplan-card/issues/15),
[#16](https://github.com/Matysh/houseplan-card/issues/16),
[#17](https://github.com/Matysh/houseplan-card/issues/17))
- Rooms and spaces can now use a persistent custom fill color and opacity,
independently of border/name color and Home Assistant state. Room overrides
inherit safely from the space and can be reset. ([#56](https://github.com/Matysh/houseplan-card/issues/56))
- Glow no longer darkens or tints LQI, light, temperature, or custom room
fills: those colors stay exact while live radial light pools remain visible.
([#61](https://github.com/Matysh/houseplan-card/issues/61))
- The global Glow radius now sits with the other Glow settings. Small code
review cleanups also remove an unreachable legacy branch and empty static
SVG layers. ([#60](https://github.com/Matysh/houseplan-card/issues/60))
## v1.61.0-beta.2 — 2026-08-10
- Light-source Glow is now an independent overlay that can be combined with
temperature, Zigbee signal, light-state or no room fill. Existing plans that
used the legacy Glow fill keep the same effective appearance and migrate
losslessly on a normal save or Optimize Plans.
- Overlapping light pools now blend additively, producing brighter mixed-colour
intersections. The card verifies real SVG support at runtime and falls back
safely to the previous normal composition on unsupported engines.
- Closing a device card opened by mouse long-press no longer leaves the plan
attached to the cursor or reopens the dialog from a stale gesture.
## v1.61.0-beta.1 — 2026-08-09
- Stored plan colours now use one strict `#RRGGBB` contract at both API and
render boundaries. Malformed legacy/imported values safely fall back instead
of being able to add CSS declarations; Home Assistant RGB light colours stay
supported through a separately generated numeric form.
- Vacuum integration coverage is now explicit and diagnosable. One sticky,
order-independent source resolver supports same-device discovery and a lazy
picker for registry-less map cameras, distinguishes missing, disabled,
unavailable and limited-permission states, and never leaks stale disabled
telemetry onto the plan.
- Xiaomi Cloud Map Extractor multi-subpath trails retain real gaps; path
arbitration, point budgets and map IDs are deterministic across frontend and
backend. Room calibration uses area centroids on both sides, keeps bbox-only
integration dialects through a final bbox-centre fallback, and a physical
error above 40 cm now requires Apply or manual fitting before config changes.
- Server trail health reports one deduplicated warning per missing/disabled
source incident and records recovery. Documentation now states the verified
Dreame/XCME/Valetudo capability matrix; Roomba remains Stage 2.
## v1.60.3 — 2026-08-09
- Editor toolbars now keep their working area stable: selection actions,
transient tool settings and the furniture palette open in one accessible
context tray over the stage, while Close remains pinned. Adaptive wide,
medium and narrow layouts are covered by deterministic golden scenarios.
- Returning to a browser tab no longer causes the plan to flash or briefly
reset its zoom/day state. Two-finger gestures cannot activate an underlying
marker, and vacuum-map fitting isolates its own gesture surface.
- Thick-wall openings use one wall association for their symbol, cut and
room-coloured tunnel. Detached/T-junction walls no longer steal a side,
overlapping openings do not compound opacity, and adjacent atomic tunnel
strips no longer expose thin SVG seams.
- Furniture previews stay inside their palette buttons, the editor Close icon
is centred, and dismissing an unarmed palette no longer leaves click
suppression behind.
- Maintenance improvements include deterministic golden-image coverage,
same-runner performance gates and migration of the active backlog to GitHub
Issues + Project v2.
- Small fixes and improvements.
## v1.60.3-beta.2 — 2026-08-09
- Editor toolbars now keep a stable height while selecting objects or changing
active-tool context. Properties, Delete, drawing parameters, operation hints
and the furniture palette use one translucent tray over the stage, so the
plan no longer shrinks/refits and Close stays pinned. The same accessible
surface is prepared for future explicitly designed tool groups without
grouping any current buttons automatically.
- Two-finger gestures no longer activate an interactive plan item when a pinch
starts over it, and robot-map calibration no longer zooms the plan underneath
its overlay. Furniture palettes and future tool groups now dismiss reliably
outside the card without swallowing an unrelated following click.
- Small fixes and improvements: reduced-motion navigation is respected again,
stale contextual actions are rejected immediately after local edits, and the
editor/performance regression checks now exercise their real UI and state.
## v1.60.3-beta.1 — 2026-08-08
- Returning to a briefly backgrounded browser tab no longer forces a one-frame
day/night/hover reset that could make the whole plan flash. Long suspended
tabs keep the protected settle and immediate sun catch-up.
- Thick-wall openings now use one cached wall association for their symbol,
wall cut and room-coloured tunnel. Nearby detached walls and perpendicular
T-junction arms can no longer steal a tunnel side, while overlapping openings
no longer darken the room fill twice. A legacy opening whose saved angle or
offset no longer actually aligns with a wall is no longer allowed to cut or
shift that wall; re-snap the opening in the Plan editor to restore it.
- Expanded always-static device coverage now verifies RGB suppression,
size/rotation, lifecycle priority, mode round trips, plan/preview/static-card
parity and live-vacuum overlay removal.
- Maintenance groundwork adds an offline compatibility-field registry/config
audit, a schema-valid large-house performance baseline with stale-bundle and
precise-memory guards, deterministic golden-image capture/review
infrastructure and the first render-only extraction from the root card.
These changes do not alter saved data or the plan's SVG/interaction contract.
## v1.60.2 — 2026-08-08
- Door, window and gate tunnels in thick walls now continue the effective room
fill instead of exposing a white paper strip. An exterior opening uses its
room colour through the full wall depth; a shared opening splits cleanly on
the wall centreline when its rooms use different fills. Glow, window sun
rays and opening symbols keep their existing behaviour.
- Device display now has an **Always static icon** option. It keeps the normal
dark icon unchanged by work, open, alarm, unavailable, RGB, values or sensor
badges, and hides live vacuum puck/trail overlays. Hover, actions, controls
and the device's separate contribution to room light remain unchanged. The
former **Icon** option is now named **Icon + dynamic plate**.
- A controller no longer becomes a physical Glow origin merely because it has
managed light targets. `controls` still drives group toggling, room light
state and statistics, while the actual lamp marker owns the light pool. A
marker's own legacy `switch.*` self-control is no longer inferred as a light;
relay-driven fixtures require the explicit “This device is a light source” flag.
## v1.60.2-beta.3 — 2026-08-08
- Saved devices disabled in Home Assistant are now forced off the plan and all
plan calculations until re-enabled, while the Device editor keeps a labelled
service ghost. Registry refresh now also recovers from unavailable update
subscriptions, and preview/static-card status hooks and explanations stay in
sync. Small accessibility, routing and preview-parity fixes are included.
- Openings now include Gate. It keeps the same contact, lock, wall cut and Glow
behaviour as a door, but defaults to 300 cm and uses two half-width leaves
opening only 10° outwards, without a plan-obscuring full swing arc.
- Sunlight through windows no longer depends on a weather entity: clouds,
rain and snow cannot dim or hide geometrically valid rays. The obsolete
weather selector is removed; legacy stored values are safely ignored.
- Device settings now include a live display preview with the actual current
state, effective source entities, the providing Home Assistant integration,
an explanation of the resulting marker, and a safe local activity demo.
The full plan, preview and read-only space card now share one presentation
resolver and one marker-face renderer.
- Existing `Value instead of icon` markers can now show localized text states,
not only numeric measurements. Long valid values are ellipsized without
changing the marker hit area, while their full text remains accessible.
- When several equally valid visual sources exist, `Value instead of icon` no
longer picks the first registry entity arbitrarily: it falls back to the
device icon and the preview explains the ambiguity.
- Decorative lines can now be switched between Solid and Dashed in the
properties dialog opened by double-clicking a finished line. Existing and
newly drawn lines stay solid by default, and dashed gaps retain a comfortable
selection target in the Background editor.
- The Plan editor now has one contextual Boundary tool instead of separate
Virtual wall and Physical wall buttons. Two points on a solid shared wall
open a virtual stretch; one click on a dashed stretch restores its complete
physical body with the inherited thickness previewed before the click.
Screen-space hit zones stay usable at every zoom, ambiguous junctions and
independent masonry block unsafe edits, and an unfinished first point is
cancelled by Esc, Undo/Redo, navigation, reconnect or multi-touch.
- The shared `hp-dialog` now wraps long translated titles and device names
instead of clipping them horizontally. Numeric angle fields across editors
display at most three decimal places without rounding stored geometry.
## v1.60.2-beta.2 — 2026-08-08
- Composite switch-only devices now use one representative lifecycle entity
instead of treating every uncategorised feature toggle as whole-device
activity. A dedicated Power switch is selected from generic Home Assistant
metadata: powered-on appliances stay neutral, while off/unavailable ones use
the existing faded presentation. Standalone relays retain their normal
yellow working state.
- Device information uses the wide responsive dialog shell and a wrapping
footer which keeps Edit, Open in HA and Close aligned on narrow screens.
- New Room has one save path: choosing “No area” in the area list and entering
a name enables the regular Save action and stores `area: null`; the duplicate
“No area” footer button has been removed.
- Erase in the Background editor gives thin lines and outlined shapes an
invisible 16 px screen-space target without changing their appearance. The
target remains usable at every zoom level.
## v1.60.2-beta.1 — 2026-08-07
- The final review follow-up makes geometry cache invalidation root-safe and
LRU-bounded, keeps pan/pinch available over saved outline hit targets,
rejects partially numeric editor values, preserves absent column angles in
Optimize Plans and gives rotation handles a full 24 px touch target. Boolean
geometry now has a lossless sequential fallback, and draft joining plus
editor-to-editor height animation are covered by realistic browser smokes.
- The Plan editor now supports persistent unfinished room outlines,
room-independent partitions and square/circular columns. They stay on the
grid, participate in clean floor area, Glow and window-ray occlusion, and
share the geometry Undo/Redo stack. Select provides drag, properties and a
rotation handle for square columns; saved outlines can be resumed or joined
endpoint-to-endpoint. Client and backend limits are aligned and invalid
thicknesses are rejected visibly instead of being silently clamped.
- Audit hardening adds cached clean-floor/light geometry, complete blocking for
a light embedded in masonry, leak-free point shadows behind long partitions,
strict column validation, reconnect gesture cancellation and browser smoke
coverage for the new physical objects.
- While drawing a room outline, Shift now locks the new segment to the nearest
45° direction. The preview and committed vertex match, and the endpoint
remains strictly grid-bound.
- The Plan editor toolbar now uses the compact labels Merge, Virtual wall,
Physical wall and Thickness. Undo and Redo are icon-only while retaining
localized tooltips and accessible names.
- The Background editor now de-emphasizes physical, thick and dashed virtual
walls to the same 35% opacity as every other non-decor plan layer.
- Switching between editors on the same space now visibly fades in the new
toolbar and interpolates its measured height. Different wrapping and tool
composition no longer make the plan jump; rapid switches continue smoothly
from the current intermediate height.
- Audit hardening makes editor history unambiguous on QWERTZ/AZERTY and
non-Latin layouts, gives SVG text an atomic Gecko/WebKit hit-test fallback,
and fully resets or disables hidden navigation/editor chrome after lifecycle
changes. Exact header sizing no longer leaves a stale viewport.
- Opening and saving a marker no longer drops ordered, duplicate or temporarily
unknown external controls. Climate recognizes preheating/defrosting and
ignores vendor pseudo-actions before using its documented mode fallback;
Optimize Plans repairs zero, null and negative grid scales to the 5 cm default.
## v1.60.1-beta.1 — 2026-08-07
- Climate markers whose integrations omit `hvac_action` now fall back to the
current non-off HA mode advertised in `hvac_modes`, so enabled air
conditioners regain their yellow working plate. A present action remains
authoritative, therefore `hvac_action: idle` stays neutral.
- Room settings now opens at the medium dialog width. Long radio labels and
source names wrap or ellipsize within the content area, eliminating the
horizontal scrollbar while retaining responsive mobile sizing.
- Erase now treats a text label as one atomic decor object across its full
bounding box. Clicking between glyphs or on its selection glow can no longer
remove only the apparent outline; an empty-canvas miss is a true no-op.
- Space tabs, View/editor changes and switches between editors now use one
short, subtle transition. Editor controls expand and collapse smoothly, and
reduced-motion preferences still disable all navigation motion.
- While drawing a room outline, Ctrl/Cmd+click now closes the last point back
to the first without adding another vertex. Closure requires at least two
existing edges and refuses degenerate or self-intersecting contours.
- Editor Undo/Redo shortcuts now use layout-independent keyboard codes, so
Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z and Ctrl/Cmd+Y work with non-Latin layouts too.
Focused text and number fields retain their native browser history.
- Closed the v1.60.0 follow-up audit: reconnecting during resume settle can no
longer strand the plan veil; legacy self-controlled switches retain their
light-source intent and migrate to `is_light`; colour/opacity pickers use the
browser top layer and flip/clamp inside small dialogs; Optimize repairs
out-of-range `cell_cm` while preserving unknown/duplicate control targets;
entity-bound legacy relays discard only their exact self-control and keep
explicitly added sibling light targets; wall SVG fallbacks now use valid
literal colours.
## v1.60.0 — 2026-08-07
- **Audit hardening.** Deleting one virtual marker no longer removes its
neighbours; real `v_*` ids remain positionable; deletion tombstones are
hidden for old clients and binding-scoped for entity members of live HA
devices. Runtime-filtered controls are no longer destructively saved. Trail
deletion is transactional. Optimisation clamps migrated physical styles,
validates `cell_cm`, preserves invalid legacy transforms for manual repair
and reports model-version bookkeeping honestly. Flat shapes are rejected,
furniture selectors initialise reliably, rotated resize keeps its fixed
corner, mode switches cancel live transforms, and the demo bundle is rebuilt
from the same source as production. English device names containing
`thermometer` now resolve as temperature sensors instead of matching the
broader meter rule.
- Auto icons now update immediately after rebinding and can be explicitly
pinned; imperial stroke-field bounds use inches; decor magnet geometry is
memoised; Undo and Redo share the same live-gesture boundary.
- A marker can no longer list its own bound entity (or an entity of its bound
device) as an external light control. Legacy self-controls are ignored immediately;
marker Save removes them, and plan optimisation also cleans a directly bound
entity self-reference. The explicit `is_light` switch
remains the only way to classify that bound relay as a light source. Active
fans/hoods still use the yellow working-state plate without entering Glow,
Light fill or room light statistics.
- **The Background editor now has one coherent transform and style system.**
Every decor kind can be selected, moved, resized proportionally (Shift for
independent axes), rotated and edited numerically; lines expose endpoint
handles. Stroke/text and fill have independent colour/opacity; stroke width
and text size are canonical physical cm/in values. Rectangle drafts show area and ovals show radii. Drawing
magnetises only to decor and room geometry. Decor and the plan image keep
both dimensions on the grid even during proportional resize and use
the named 50-step Undo/Redo stack. The image has an exclusive tool, fades to
0.5 elsewhere in this editor, supports independent axes/rotation and remains
fully opaque outside it. Legacy styles/transforms remain visually stable and
migrate only via explicit plan optimisation.
- **Devices can now be deleted from the plan, not merely hidden.** Delete sits
beside Hide/Show and requires confirmation. A deleted HA binding is excluded
from rendering, Show-hidden ghosts, LQI, climate, room sources, light
resolution/Glow/statistics, controls, openings and live text, while remaining
available in Add. Re-adding replaces its minimal discovery tombstone and
starts with a fresh position. Virtual markers are removed outright; layout,
attachments, activity runtime and vacuum trails are cleaned, and stale-tab
drag updates cannot resurrect the old position.
- **Device-dialog footer spans the dialog again.** The shared `hp-dialog`
footer is now a full-width slot item instead of exposing its action row as a
shrink-to-fit flex child. The divider no longer starts halfway across the
modal, Hide stays at bottom-left, and Cancel/Save stay at bottom-right.
- **Auto device icons keep their label in the editor.** When a marker has no
explicit icon override, the HA icon picker now receives the effective
auto-derived icon as its display value, so the field shows both the glyph
and `mdi:*` name instead of a nameless glyph. The stored override remains
empty until the user actually selects an icon.
- **Media-player power is no longer mistaken for work.** `media_player`
markers now stay neutral for `on`, `idle`, `playing`, `paused` and other
transport states; an explicit `off` uses the same faded presentation as
`unknown` / `unavailable`.
This is a domain-level rule for TVs, receivers, speakers and soundbars, not
a model exception. Mixed media entities fade only when none of them is
currently available and powered.
- **Returning to a long-suspended View is stable.** In normal View (not kiosk),
transient zero/partial ResizeObserver boxes no longer overwrite the saved
viewport. After a genuinely long background sleep the plan is revealed only
after the stage size is measurable and quiet, then zoom and centre are
applied in one frame. Critical wall fill/stroke attributes also live directly
on the SVG paths, so a warm Lovelace re-mount cannot show the browser's black
default paint while component styles are being restored. Editors, kiosk and
quick tab switches keep their existing immediate behaviour.
- **Device-info actions stay inside narrow dialogs.** The Edit, Open in HA and
Close actions now wrap as complete buttons when the small HA dialog cannot
fit one row; reduced mobile padding preserves useful width. The leading Edit
action can no longer be pushed beyond the left edge of the modal.
## v1.59.2 — 2026-08-07
- **A11Y-02: one accessible dialog shell.** Every House Plan modal now uses
`hp-dialog`: Home Assistant supplies the modal surface and focus trap through
`ha-dialog`, while the standalone demo uses a native `<dialog>` fallback.
Dialogs have an announced title, deterministic initial focus, Escape
handling, contained Tab navigation and focus restoration to the control that
opened the modal, including nested and dialog-to-dialog flows.
## v1.59.1 — 2026-08-06
This maintenance release makes device status and every light-related view use
shared semantic resolvers, softens Glow, and fixes the last compacted-wall
T-junction artifact.
- **Systemic device-state resolver instead of a “first entity”.** HA devices
have no single state, so House Plan now excludes `config`/`diagnostic`
entities first, resolves the device's functional role, then semantic binary
signals, and uses generic switches only as a fallback. Passive readings are
aggregated, so one unavailable entity no longer makes the whole marker
unavailable. The same rule fixes X50/Customized Cleaning, presence/Anti
interference, TRV service switches and equivalent integration mistakes. A
real cover now also outranks its reverse-direction option, while a mixed
lamp+cover remains a lamp unless the cover action is explicitly selected.
- **UX-12: one resolved light-source set.** Glow, Light fill, room light stats,
marker indication and group controls now consume `resolvedLightSources(room)`.
Its precedence is `controls` -> an explicit `is_light` source -> automatic
`light.*`; hidden markers are excluded, entity ids are de-duplicated, and an
explicit `room_id` outranks an HA area. Relays and controlled groups therefore
mean the same thing in every light presentation.
- **Softer source glow.** The complete glow layer now renders at `0.7`
opacity; room darkness and the yellow working-state plate are unchanged.
- **Clean real/virtual T-junctions after wall compaction.** A maximal real wall
may cross several shorter collinear room sides. Exact endpoint containment
now restores its thickness on every covered side even when the compacted
wall's midpoint lies outside that room. Room hover therefore stays on the
inner face and the adjoining real wall no longer exposes a false end cap at
a virtual continuation.
## v1.59.0 — 2026-08-06
Stable 1.59 turns House Plan into a substantially more complete floor-plan
editor while keeping the card's live Home Assistant view coherent. It includes
every beta/RC change below and closes the line with clean-floor hover geometry
and robust wall unions.
- **Real walls, virtual boundaries and openings form one geometry system.** A
room can mix wall thicknesses, a wall can contain real and virtual stretches,
adjacent equal fragments compact again, and Resize preserves exact interval
endpoints. Partial openings, virtual T-junctions, thick-wall door tunnels and
sunlight from the inner corners of windows are handled consistently.
- **Plan edits are explicit and reversible.** Room outline, Split, Close
boundary, Merge rooms, Delete room, Resize, wall thickness and opening edits
have one result each and share a named 50-command Undo/Redo stack. Delete
removes only the selected object.
- **One mandatory grid invariant.** Rooms, openings, markers, decor, furniture
and backdrops always land on the grid or stay quantised along their wall.
Optimize plans previews and applies all current lossless migrations, repairs
legacy/off-grid data and compacts wall fragments with a safe one-step undo.
- **Editors gained production workflows.** Background objects open their
properties on double click; furniture has real dimensions and wall snapping;
live labels accept arbitrary text mixed with manually typed or picker-inserted
Home Assistant tokens; layer and object visibility controls are explicit.
- **Device feedback uses one semantic language.** Yellow means actual work,
orange means open/unlocked, alarms remain dominant, unavailable devices are
muted, motion is a short event and presence lasts while detected. The marker
plate, pulse/activity effect and source-light room fill now agree, including
covers, climate, fans, relays, media players and other working devices.
- **Rooms explain themselves in View.** Glow and toasts are pointer-transparent;
room hover and tooltips work through light pools, the tooltip reports clean
floor area, and device dialogs expose an explicit Hide/Show action. The hover
accent now follows the same inner wall faces used for area; nested-room walls
use the face exposed to the parent floor, while doors, windows and virtual
spans remain gaps.
- **Final wall-body correction.** Wall bodies are formed as the union of each
room's own outset-minus-inset ring. A parent clean-floor contour can no longer
erase half of a nested 15 cm wall or leave a white sliver at a complex
multi-wall crossing.
- **Documentation was rebuilt from the product.** The complete Russian user
guide, setting/device behaviour tables and improvement plan describe the
shipped code; obsolete audits and one-off debug material live under
`legacy/`.
## v1.59.0-rc.2 — 2026-08-06
The second release candidate makes plan editing predictable and reversible,
enforces one grid invariant, and completes the documentation refresh.
- **One tool — one result.** Closing a virtual boundary, merging rooms and
deleting a room are now three separate Plan actions. Delete removes only the
room that was explicitly clicked; the opening tool no longer closes an
existing dashed stretch. The drawing action is renamed Room outline to state
that it creates a closed room rather than a free wall.
- **One named geometry history.** Create, Split, Merge, Resize, room deletion,
wall thickness, virtual-boundary edits and door/window add/edit/move/delete
all share a 50-command Undo/Redo stack. Toolbar buttons and Ctrl+Z,
Ctrl+Shift+Z/Ctrl+Y expose the same named steps; a new branch clears Redo.
Echoes of the card's own server writes keep the history, while a genuinely
newer external revision safely resets it.
- **The grid is mandatory.** Shift can no longer place rooms, markers, labels,
decor, furniture, backdrops or openings between grid positions. Free points
land on nodes; wall-bound objects stay projected and quantised along their
wall. Shift remains only for explicitly angular controls. Optimize plans
repairs old/imported off-grid data.
- **Clearer interaction feedback.** Glow is pointer-transparent, so room hover
and tooltips work through a light pool. The View hover overlay follows the
complete physical perimeter, including shared thick walls. Existing device
dialogs now expose an explicit bottom-left Hide/Show action. The static
space-card editor no longer offers the unused `aspect_ratio` setting. Toasts
are pointer-transparent and cannot block a tool handle underneath them.
- **Documentation and repository hygiene.** A new Russian user guide and a
code-grounded product audit/roadmap document current behaviour, setting and
device matrices. Obsolete audits, rejected design notes and one-off debug
scripts moved under `legacy/`; active docs and automated scenarios now match
the grid, Undo and split-action contracts.
## v1.59.0-rc.1 — 2026-08-06
First release candidate of the 1.59 line: safe whole-plan optimization,
clearer object editing and room feedback, plus the beta.10 audit follow-ups.
- **Whole-plan optimizer with safe undo.** The former Align-to-grid action is
now Optimize plans. One preview runs every current lossless model migration,
aligns grid- and wall-bound geometry, materialises legacy open boundaries,
merges touching virtual pieces and compacts equal-thickness real-wall
fragments. Config and layout are committed through a durable two-store
intent; one undo remains available until the next plan edit. Backdrop
calibration, saved views, unattached layout entries and user files are left
alone. Interrupted optimization/undo completes on the next integration setup.
- **Background-object properties on double click.** In the Background editor's
Select mode, double-clicking any object now opens its properties: labels keep
their text form, while lines, rectangles, ellipses and furniture get a compact
colour / line-width dialog (plus fill for closed shapes). The new dialog is
included in warm-remount draft recovery.
- **Rooms identify themselves in View.** Hover now highlights the room under the
pointer, including rooms with no configured fill or HA area. The room tooltip
also shows its formatted clean-floor area; thick walls use the same inner
contour as room labels and resize measurements.
- **Yellow working state remains universal in source-light fill.** A device that
is semantically on/working keeps its yellow marker plate when the space uses
the per-source glow fill. The light pool supplements the marker state instead
of replacing it.
- **beta.10 audit fixes.** The first real device transition after load is no
longer consumed while rebuilding the registry, and changing a marker's
effective source synchronously clears an old source's short effect. Editing a
legacy live label preserves an explicit unit or a non-representable attribute
instead of silently dropping it; the old `attr: state` selector migrates to a
bare state token instead of a nonexistent attribute. Wall thickness fragments
now persist exact normalised endpoints (legacy midpoint keys still read),
survive resize, and
compact into maximal equal-thickness solid runs after virtual pieces are
closed. Virtual-wall dashes remain visible in all editors even with borders
hidden. Release CI now reports GitHub/HACS prerelease-order trouble instead of
silently publishing a beta that HACS cannot discover.
## v1.59.0-beta.10 — 2026-08-05
Tenth pre-release of the 1.59 line: one coherent device-state language and a
focused set of wall, doorway-light and background-label refinements.
- **One device-state language.** Marker plates and effects now come from one
semantic resolver: yellow means actual work, orange means open/unlocked,
unavailable is faded, and alarms are always red. The display list is reduced
to Icon, Icon + activity and Value; the removed Ripple-only value migrates to
Icon + activity on the next save. Activity distinguishes short events,
persistent presence, mechanical travel and actual running, with
reduced-motion fallbacks and no false event on first load or reconnect.
Short effects also reset when a marker's effective source changes, while a
cover's real opening/closing state takes precedence over the tap fallback.
- **Clearer wall-drawing toolbar.** The Plan editor's former “Add” tool is now
labelled “Walls”, and its new-wall thickness field sits immediately to the
right of that button instead of after the rest of the toolbar.
- **View-mode virtual walls sit behind thick wall bodies.** Their stored
geometry still reaches the physical centreline, but in View the hatch masks
the dash ends inside adjoining thick walls. All three editors keep saved
dashes and live previews above the wall body so the complete span remains
visible while editing.
- **Door light respects thick-wall reveals.** Light now crosses a doorway only
through the clear width of its physical wall tunnel. The near and far inner
face spans jointly clip the spill, so the two jamb returns cast the expected
cut-offs for an off-centre source; zero-thickness walls retain the previous
doorway sector.
- **Inline HA variables in decor text.** A label can now mix ordinary copy and
multiple `{entity}` / `{entity:attribute}` references. Choosing a state or
attribute inserts its token at the textarea caret. The separate unit field,
one-slot hint, and preview are removed; old linked labels remain readable and
migrate to inline tokens when edited.
- **Canonical wall fragments.** Touching or overlapping virtual stretches on
the same boundary and room pair now collapse into one `open_span`; Split
pieces belonging to different room pairs remain separate. When removing the
last virtual stretches leaves an original wall solid and uniformly thick,
its atomic thickness entries collapse back to one whole-wall key.
## v1.59.0-beta.9 — 2026-08-05
Ninth pre-release of the 1.59 line: mixed virtual/thick resize integrity and
clean, visible virtual T-junctions.
- **Resize keeps mixed virtual/thick walls intact.** A live edge drag now moves
`open_spans` and whole/atomic thickness keys together with the room polygons;
commit no longer drops the thickness of the solid remainders, and Undo
restores the complete transaction.
- **Clean virtual T-junctions.** Real wall arms owned by different room
contours receive the missing mitre at an open-span endpoint instead of a
stepped corner. Virtual dashes and the two-click drawing preview paint above
real wall bodies, so they stay visible right up to the junction centreline.
## v1.59.0-beta.8 — 2026-08-05
Eighth pre-release of the 1.59 line: beta.7 audit follow-ups, a fail-closed
release path, full/static wall-render parity, and sunlight anchored to the
room-side corners of thick window openings.
- **Sun rays start at the inner window corners.** With wall thickness, the
wedge's full source span now sits on the room-side face of the opening, so
both crisp side edges begin at its two inner corners even at an oblique sun
angle. The clean-floor contour still clips the light at the wall body.
- **Beta.7 audit follow-up.** A Split crossing one `open_span` now keeps every
valid room-pair piece and derives `open_to` for both children instead of
silently turning one half back into a solid wall (`AUD-159B7-01`).
- **Release safety.** `release.yml` resolves the published tag to its exact
commit and waits for all matching Validate runs to finish green; missing,
failed, cancelled or timed-out validation withholds the asset
(`AUD-159B7-02`). The same fail-closed policy has automated negative tests.
- **Test/render parity.** General-settings smoke includes the Walls group,
inventory counts class-based and trail backend tests, and the static space
card uses the same 3 px thin-wall hatch fallback as the full card
(`AUD-159B7-03`, `AUD-159B7-04`).
## v1.59.0-beta.7 — 2026-08-05
Seventh pre-release of the 1.59 line: audit fixes for partial-wall geometry and
lifecycle, plus wall fill colour under the hatch.
- **Wall fill + hatch.** General setting `fill_colors.wall_fill` (default opaque
white) paints under the diagonal hatch; the hatch stays. Thin-on-screen bodies
keep the solid fill alone so the hatch does not collapse into noise.
- **Atomic wall intervals (AUD-159B6-01).** Thickness and open cuts follow
shared-boundary / open-span endpoints, not whole polygon edges — a partial
shared stretch no longer leaks thickness onto the outer remainder, and an open
span away from the edge midpoint clears only its own piece.
- **Open-span geometry transaction (AUD-159B6-02).** `open_spans` ride in the
resize snapshot/Undo; Split / Merge / Delete rekey, clip and sync `open_to` in
one step without resurrecting a legacy stretch mid-mutation.
- **Backend `open_spans` schema (AUD-159B6-03)** with cap / finite points /
dedupe; frontend fail-soft sanitises malformed entries.
- **Warm owner vs pending nav (AUD-159B6-04):** adopting a warm viewport clears
the global LS pending mode so a neighbour card cannot overwrite the owner's
editor and draft.
- **Smoke / inventory hygiene (AUD-159B6-06):** two-click openwall fixtures,
island-by-id, render-perf hook on `_openPairs` / `_buildModel`, inventory
counts `test` and `it`.
## v1.59.0-beta.6 — 2026-08-05
Sixth pre-release of the 1.59 line: partial open-wall spans and wall-centric Delete.
- **Partial open (virtual) wall spans** (`space.open_spans`): two-click openwall
(anchor → second point on a shared wall, clamped to nearest corners); crosshair
cursor; openings on virtual stretches are removed and cannot be placed;
thickness clears on open and restores from neighbour / 15 cm on close. Legacy
`open_to` still expands to the full shared boundary on read.
- **Wall-centric Delete**: virtual → solid; shared solid → confirm merge (whole
shared boundary); outer wall or click inside room → confirm delete room.
## v1.59.0-beta.5 — 2026-08-05
Fifth pre-release of the 1.59 line: wall-thickness redesign (seamless ±½ rings, inner floor/area/sun), draw-with-thickness, new-space opens the plan editor, and audit hygiene from the beta.4 recheck.
- **Draw with wall thickness.** The Plan Draw toolbar has a thickness field
(default 15 cm / HA inches). New rooms get that thickness on commit; shared
walls keep the neighbour's value; empty clears. Live thick preview while
drawing.
- **New space opens the plan editor.** Saving a newly created space switches to
Plan with the draw tool armed, so an empty floor is not left in View with
nothing to look at (same as the first-space onboard path).
- **Wall thickness redesign (docs/WALL-THICKNESS.md).** Walls grow ±½ from the
centreline (outer and shared); bodies are outset−inset rings unioned into one
hatch (seamless L/T as in the reference plan); fills/glow/sun clip to the
inner contour; displayed m² is the clean floor; sun wedges narrow through the
opening tunnel. Resize/undo/scale keep and re-key `walls`; openings resolve by
wall angle; plan-editor mode restores after `can_write`. Hatch colour matches
the wall outline.
## v1.59.0-beta.4 — 2026-08-05
Fourth pre-release of the 1.59 line: wall thickness in the plan editor, and a
fix so editors keep a white drawing sheet under the grid even when a backdrop
image is loaded.
- **Wall thickness in the plan editor (docs/WALL-THICKNESS.md).** A wall carries
one thickness (`space.walls: [{ key, cm }]`), entered in cm or inches from HA's
unit system. Shared walls grow half into each room; outer walls grow inward;
open boundaries refuse thickness. Hatched bodies follow `show_borders` (always
visible in the plan editor); openings cut the slab full-depth and door swings
start at the inner face. Area, glow and sun stay on the polygon. The static
space card draws the same bodies. Unit + smoke: `test/wall-thickness.test.mjs`,
`demo/smoke_wall_thickness.mjs`.
- **Editors keep a white sheet under the grid with a backdrop.** With an uploaded
plan image, the plan / devices / decor editors used the theme card colour under
the grid instead of the white drawing sheet of a hand-drawn plan. They now stay
white; View is unchanged (theme under an image, white without one).
## v1.59.0-beta.3 — 2026-08-05
The third pre-release of the 1.59 line: you can furnish the plan with
top-view symbols at real size, hide the decor layer or the openings from
View without losing them, aim card-mod at stable `data-*` hooks, and let
Home Assistant format entity values the way its own more-info does. A
handful of editor polish lands with it — bead-sized corner handles, the
backdrop editor opening on its own tool, virtual walls following the
room-border switch — plus the audit follow-ups (write policy, README
differentiation, tighter validation). Wall thickness is approved as a
written spec only (docs/WALL-THICKNESS.md); it is not coded yet.
- **Two new switches in a space's settings: «Hide the decorative layer» and
«Hide doors and windows».** Both only hide. The shapes, labels, furniture
and openings stay in your config, and each stays visible in the editor that
owns it — the backdrop editor always draws decor, the plan editor always
draws openings — because a layer you cannot see is a layer you cannot edit.
What an opening *means* is untouched either way: light still spills through
it, the sun still comes in at a window, a contact sensor still opens it; only
the symbol is gone. Both are off by default and store nothing when off, so
every existing plan reads back unchanged.
- **A space that does not draw room borders no longer draws virtual walls.**
With «Always show room borders» off, the dashed stretches of an open boundary
used to survive on their own, leaving a plan with no walls except a few
floating dashes. They now follow the same switch. The plan editor still shows
them — the Open-boundary tool has to show what it edits.
- **The backdrop editor opens on the tool it is named after.** With a picture
in the space, «Backdrop image» is armed the moment the editor opens, so
dragging the picture works straight away instead of after finding the tool.
The frame was already drawn on open, which promised a draggable picture; the
promise is now kept. Select is still one click away and still leaves the
picture's body to the one-finger pan.
- **Corner handles are beads again, not blobs.** Every corner handle in the
card — the backdrop frame, the room-resize frame and the robot-map fit —
paints a circle a **quarter** of its old radius while its clickable area is
unchanged, matching the text block's handles. A handle the size of a room
hides the thing it is there to adjust.
- **You can furnish the plan (docs/FURNITURE.md).** The background editor
gains a seventh tool, **Furniture**: a grouped palette of ~30 top-view
symbols — furniture, appliances, plumbing and a few odds like stairs and a
rug — that are placed at their **real size**. Pick a sofa, correct the
2.2 × 0.9 m in the two fields if your sofa is a different one, click the
plan, and 2.2 m of *your* plan is what it takes, because the size goes
through the space's `cell_cm` like every other length in this card. While
you place or drag it, the nearest wall within ~30 cm claims it: the piece's
back lands flat on that wall and turns to it, so "put the bed against that
wall" is one click. Shift suspends the magnet, as Shift suspends every snap
here. A placed piece is selected in the **Select** tool and wears the text
block's frame — the corner handles now set **width and depth independently**
(a bed is not made deeper by being made wider), with live badges showing
both in metres or feet, and the handle above it turns the piece in 5° steps.
The symbols are **drawn by us, in code**: an icon set draws a sofa seen from
the front in a 24 × 24 square, and stretching that into a 2.2 × 0.9 m
rectangle gives an icon lying on the floor rather than a plan. That also
settles the licence question outright — nothing third-party ships in the
bundle. Every piece carries `data-kind="furniture"` and `data-symbol`, so
card-mod can colour all the plumbing in one rule. New kind, nothing
migrated: a plan written before this reads back byte-for-byte.
- **The plan has stable hooks for card-mod (docs/STYLING-HOOKS.md).** Every
object the plan draws now carries the same identity — `data-hp` says what it
is (`device`, `room`, `room-label`, `opening`, `decor`, `space-tab`),
`data-id` is its id in your config, and where it applies `data-entity`,
`data-area` and `data-kind` (door/window, line/rect/ellipse/text) come with
it. That is all this is: we do not ship a CSS field, do not add a theme
editor and do not support user stylesheets — we simply stop getting in the
way of a power user who has already installed card-mod and only needed
something stable to aim at. The names are a contract now: renaming one is a
breaking change. Everything NOT in that table — editor chrome, boot classes,
layer wrappers, dialog markup — is explicitly not the contract. The doc
carries the table, three worked examples, the shadow-DOM limits (you can
style an `ha-icon` host, never its insides) and a plain disclaimer that
card-mod is not ours to support.
- **Values are formatted by Home Assistant, not by us.** Wherever the card
prints one entity's state — the value badge of a «value instead of icon»
marker, a live decor label, the device info card — it now goes through
`hass.formatEntityState` (and `hass.formatEntityAttributeValue` for an
attribute), the same call HA's own more-info makes. So a sensor's
`display_precision` is honoured, the decimal separator is the one your
locale uses, and `on` finally reads as *Включено* instead of `on`. The unit
lands exactly once — the formatter usually appends it, and where it does not
we still do — and your own unit on a decor label replaces the entity's
rather than piling on after it. An older Home Assistant without those
methods behaves exactly as before. The small °/% plates next to an icon keep
their own compact form on purpose: they are a derived reading (an average
over the area's sensors, a climate device's `current_temperature`), not an
entity state, and the plan reads as one instrument panel because they all
look alike.
- **The text block's handles are a quarter of the size** (owner's request).
The corner circles and the rotate handle above a selected label are now ink
the size of a bead instead of a button, so the frame stops covering the very
words it frames. The area you can grab is unchanged — an invisible
finger-sized circle still owns the gesture at the old 1.8 % of the visible
view, exactly the split the wall-resize handles use.
- **Wall thickness — the approved spec (docs/WALL-THICKNESS.md).** Not
implemented yet; the document captures the model (thickness on a wall, not
a room edge), segment keys that survive resize, hatching that does not
affect area, and how openings render in a thick wall.
Audit follow-ups from `legacy/docs/audit-v1.58.0/AUDIT-RECOMMENDATIONS.md` (2026-08-05):
- **Write policy aligned (P0-4).** `admin_only` now defaults to **on** for new
installs and when the option key is missing; `houseplan/config/get` returns
`can_write` from `may_write`, and the card's editor chrome follows that flag.
Missing `hass.user` no longer fails open into the editors.
- **README differentiation (P0-3).** Comparison table now positions House Plan
against both YAML/SVG incumbents and GUI draw cards (e.g. easy-floorplan):
shared `.storage` map + area-bound rooms, not furniture CAD.
- **Validation tighten (P3-4).** Marker `binding` must be `device:…` /
`entity:…` / `virtual`; `ripple_color` is `#rrggbb`; decor rect/ellipse
extents are strictly positive; space `id` matches `SPACE_ID_RE`.
- **Hygiene (P3-5).** `quality_scale.yaml` points at the real config-flow test
file; card-level `tap_action` documented as deprecated/ignored.
## v1.59.0-beta.2 — 2026-08-04
The second pre-release of the 1.59 line, and it is about the words you put
on the plan. A decor label can now read a live value off an entity, so a
caption becomes a readout without becoming a template language. The text
block itself lost the choice between three font sizes: it is scaled by its
corners and turned by its handle, like every other object on the plan, and
it may have more than one line. Under that, the three findings of the
beta.1 audit are closed — two identical cards no longer share one warm
memo, a rapid double re-mount no longer eats the draft, and an expired
dialog no longer holds a plan file in memory.
- **A decor label can show a live value (docs/LIVE-TEXT.md).** A text shape
gains three optional fields — `entity`, `attr`, `unit` — and its `text`
becomes a template whose `{}` is where the value lands: `Бак {}` reads
*Бак 68 %*. Without a placeholder the value is appended; without an entity
the label is byte-for-byte the static one it always was. The unit comes
from the entity unless you type your own; a dead, unknown or missing
entity shows an em dash instead of quietly vanishing. Nothing is rounded
or reformatted — the value is what Home Assistant reports, because
rounding belongs to the sensor's `display_precision`. Not a template
language: one value, one place, no syntax to get wrong.
- **The text dialog gained an entity picker, an attribute picker and a live
preview.** The attribute list is the chosen entity's own attributes, the
unit field shows the entity's unit as its placeholder, and the preview is
rendered through the very same substitution the plan uses.
- **A text block is sized by its corners and turned by its handle.** The
choice between three font sizes is gone: select a label and pull a corner
to scale it, or use the handle above it to rotate in 5° steps (Shift for
any angle) — the same mechanics as the backdrop frame. Labels drawn with
the old Small/Medium/Large come back at exactly their old size, and the
first drag replaces that setting with the scale it meant.
- **A label can have more than one line.** The text field is a textarea now:
your line breaks are stored and drawn as line breaks, centred, with the
block growing around its anchor. Nothing wraps by itself — a caption that
reflows on a state change is a caption that jumps around the plan.
- **The text tool edits the label you press on.** Drawing tools own the
canvas (a new line must be able to start on the end of an old one), and
that stays true — with one exception: pressing an existing label with the
text tool opens ITS form instead of starting a second label on top of it.
Empty canvas and non-text shapes still create a new label.
- **Two identical cards on one page no longer share a warm memo
(AUD-159B1-01).** The memo key was the window size and the card config,
which cannot tell two placements of the same config apart: the newer of
them was the last writer, and a card re-created in the OTHER placement
woke up with its neighbour's floor, editor mode and zoom — while the
draft of its real predecessor was eaten by the mode guard. The memo now
keeps one entry per card PLACEMENT: `location.pathname` (the dashboard
view) joins the key, and inside a key the entry is claimed by DOM slot —
the parent element and the index in it — with a live neighbour's entry
never adoptable. When two placements are genuinely indistinguishable
only the settled header height is adopted (it is the same for both),
never the viewport and never the dialog.
- **A rapid double re-mount no longer destroys the draft
(AUD-159B1-02).** `disconnectedCallback()` cleared the «I still owe my
predecessor a dialog» flag BEFORE taking its snapshot, so a middle
instance in an A→B→C rebuild wrote `dlg: null` over a draft it had not
yet restored, and the third instance got nothing. The snapshot now runs
while the flag is still set: an unsaved dialog simply travels down the
chain until one instance lives long enough to reopen it.
- **An expired dialog no longer holds its payload (AUD-159B1-03).** The
10-second revive TTL was only a rule checked at revive time; the entry
itself kept the dialog — for a space dialog that is a whole plan file
as base64 — until the page reloaded. Detaching now arms a guarded
eviction that frees the payload the moment it stops being revivable
(and drops the stale slot, so the next claim is unambiguous again).
## v1.59.0-beta.1 — 2026-08-04
Minor pre-release: the card survives a Lovelace re-mount bit-for-bit —
your pan, your zoom and even the dialog you had open come back with it —
sun rays get a hairline edge so they read on white paper, the «+» that
adds a space leaves the Plan editor for the tab row, and the list of
plans already uploaded to the server stops collapsing into a stripe.
- **The card no longer twitches — and no longer loses your open dialog —
when you come back to the tab (docs/WARM-REMOUNT.md).** Lovelace
re-creates the card element on a websocket reconnect; v1.58.0 removed
the preloader flash, but the new instance still had to guess what the
dead one had been looking at. Measured: the PAN never left the instance
(a view parked in a corner came back re-centred: x=50 → x=250 at
zoom 2.2), and the EDITOR zoom is deliberately not persisted while the
editor MODE is — so a re-mount inside an editor came back at 1.0
instead of 3.0. The warm memo now carries the whole viewport (space,
mode, zoom, the `_view` rect itself, the view-mode snapshot, the
«show far objects» frame, the selected tool and selection, the local
«show hidden» toggle), so the restore is the same RECTANGLE, not the
same zoom number — bit-for-bit, verified frame by frame.
- **An open dialog survives the re-creation with its draft.** The memo is
module state and is never serialised, so the live draft object — a
half-filled device dialog with its uploaded PDFs included — moves over
for free. The rule is «revive the draft, never revive the decision»:
the confirmations for «Align everything to the grid» and for a room
merge are deliberately NOT restored (a modal whose whole content is
«press OK to rewrite your plan» must not greet a returning user), nor
is a tap confirmation (it closes over the dead instance), nor the floor
import wizard (it reopens itself), nor any dialog with a save in flight.
Revival requires the same space and the same mode, happens at most once
(the snapshot is consumed), and only if the previous instance died
within the last 10 s. A dialog closed on purpose — Esc, Cancel or
Save — writes `null` into the memo on the very next render, so it can
never come back (smoke_warm_dialogs).
- **Sun rays get an edge (docs/SUN.md, «The rim»).** On a white plan a
wedge of light was nearly invisible, and no amount of opacity could fix
it: painting light means adding luminance, and white paper has none
left to give. Every lit wedge now carries a 1 px black hairline along
its two SIDE edges — the ones running inward from the ends of the
window. It fades to nothing on exactly the same axis, the same curve
and the same 85 % threshold as the fill (a second gradient built on the
fill's own stops), stays one screen pixel at any zoom
(`non-scaling-stroke`), is cut by the room like the wedge itself, and
lives in the same layer — so the 3° threshold, the two-second fade,
cloud cover, night and the editors govern it without a line of extra
logic. Peak opacity 0.42, picked against a white sheet and the dark
glow canvas alike. The «shade instead of light» model of
legacy/docs/SUN-CONTRAST.md was rejected in favour of this; that file now
records the decision and keeps the analysis behind it.
- **The «+» that adds a space is not an editor tool.** The button next to
the floor names only existed inside the Plan editor, so adding a second
floor meant first opening an editor you did not want. Adding a space is
navigation, not markup: the «+» now sits in the tab row in every mode —
View and all three editors — exactly where the per-space gear already
lives, and under the same admin rule. The kiosk is a shop window: the
button is not rendered there at all (its header is `display:none`, and a
hidden-but-present node is still clickable from script). The tab row
wraps as before at 390 px (smoke_gear_tabs measures the overflow).
- **«Already uploaded» shows the plans again.** In both space dialogs
(new space and space settings) the list of plans stored on the server
collapsed into a thin rounded stripe: the rows were rendered, the box
itself was 14 px tall. A scrolling box is a flex item whose automatic
minimum size is zero, and the dialog body is a flex column with a
66 vh cap — so the picker was the one child that could be squeezed to
nothing. It no longer shrinks and keeps a floor of its own; up to five
thumbnails are visible at once and the rest scroll. The empty and
loading states stay readable instead of clipping their own text
(smoke_plan_picker measures the heights — the old smoke only counted
DOM nodes and passed).
## v1.58.0 — 2026-08-04
Minor release: the backdrop picture becomes a movable, scalable object,
the opaque plan sheet is redefined as the room contours, the decor
drawing tools stop stealing clicks, and «Align everything to the grid»
tells the truth about what it is going to do.
### The backdrop picture can be moved and scaled (docs/BACKDROP.md)
- **The plan image is no longer nailed down.** In the Background
editor the picture gets a transform frame: drag it by its body to
move it, pull a corner to resize it evenly (proportions kept, the
opposite corner stays put). A live badge states the picture's real
size in metres (or feet) while you drag, through the space's
`cell_cm`. Position and size land on the grid; Shift steps off it.
Nothing else moves — rooms, doors, devices and decor stay put — and
there is no rotation.
- Three new optional space fields: `plan_x`, `plan_y`, `plan_scale`.
Their absence is exactly the old behaviour, so **existing plans
render bit-identically and nothing is migrated**. «Вернуть картинку»
in the toolbar clears them.
- **The opaque plan sheet is now always the room contours.** It used
to be the image rectangle whenever a picture was attached; now the
picture is drawn ON the sheet — above it, below the walls. The
scene colour (`bg_color`, the `daynight` sky) therefore reaches the
exterior walls on an image plan too. Consequence, deliberate: a
transparent picture over a space with no rooms drawn shows the
scene background through itself.
- «Вписать всё» counts the moved and scaled picture, so it can no
longer be left off screen.
### Drawing tools no longer grab the shape under the cursor
- **A drawing tool owns the canvas.** With Line, Rectangle, Oval or
Text picked in the decor editor, clicking on an existing figure now
starts a NEW figure instead of selecting the old one — so a line can
begin exactly on the end of another line, and a rectangle can be
drawn on top of a filled one. Double-clicking a text under a drawing
tool no longer opens it for editing either. The same inertness
applies in the «Картинка-подложка» tool, where a decor shape lying
over the plan must not block the picture's own drag.
- «Выбрать» and «Стереть» are unchanged: shapes stay clickable there,
which is where selecting and deleting belong.
### «Align everything to the grid» keeps its promise (AUD-158B1-01)
The action has no undo, so its confirmation dialog is the only safety
there is — and it was understating the damage.
- **The maximum shift is measured on the geometry that is actually
written**, not on an intermediate one. A rect was measured by its
origin and its far corner only — so the minimum-size correction that
widens a too-thin box afterwards went unmeasured, and the two corners
that carry the X error of one side together with the Y error of the
other were never looked at at all. An ordinary box could move √2
times further than promised; a box thinner than one grid step, much
further.
- **Each space is converted through its own `cell_cm`.** The dialog
used to take one normalised maximum and multiply it by the cell size
of the FIRST space: a plan whose ground floor is drawn at 5 cm per
cell and whose attic is at 100 cm promised 2.5 cm for a vertex that
moved 50 cm. The report now carries the maximum in centimetres and
the space it belongs to, and the dialog names that space when there
is more than one.
- The last tenth of a centimetre is rounded **up**: the promise can
never be smaller than the deed.
### An opening whose only error is its angle can now be fixed (AUD-158B1-02)
- A window sitting exactly on its wall but holding a wrong `angle` used
to come back as `changed: false` — the dialog said there was nothing
to move and offered no button, while the returned plan differed from
the one given. Such openings could never be aligned. The angle is
part of the diff now; the dialog counts them separately, and the
displacement is measured on the opening's ENDS, so turning it in
place is not free in the report either. Turning an opening end over
end (180°) is still counted as a correction but displaces nothing.
## v1.57.0 — 2026-08-04
Minor release: the canvas becomes infinite — no more "plan size", no
more edge to run past — plus a tap action for curtains and blinds,
a polish pass over the sun, live rulers while placing openings, and a
warm re-mount that no longer flashes.
### The infinite canvas (docs/CANVAS.md)
The idea of a "grid size" or "plan size" is gone. The canvas is
conceptually unbounded — any coordinate is legal, with a `+/-5000`
limit kept only as garbage insurance (about 60 km of plan at the
card's own scale).
- **If your plan ran off the edge, it now just works.** Some plans
grew past the old square and devices simply could not be placed
outside it; the only workaround was to redraw everything. That
limit no longer exists: draw, drag and place devices anywhere, and
nothing needs to be redrawn. Your stored plan is untouched — the
coordinates mean exactly what they always meant, and there is no
migration.
- **Existing plans open exactly as before.** Verified on a real
three-floor config: the opening frames come out bit-for-bit
identical to v1.56.0.
- **The opening view is derived from what is actually drawn.**
`view_box` is now an optional hint for the very first frame only;
after that the starting view is always computed from the real
content, with outliers rejected so one stray room cannot shrink the
whole plan.
- **Pan at any zoom, in every editor.** Dragging the plan used to work
only above 100% zoom; now it works at any zoom and in all editing
modes, with one screen of slack around the content.
- **Zoom out to 3x the content**, so you can see the whole plan and
the space around it.
- **"Fit everything" button** — one tap frames all the content again.
- **A "home is that way" arrow** appears when you have panned away
from the plan, pointing back to it.
- **An adaptive grid.** The grid picks its step from the zoom
(1 / 2 / 5 / 10 ... 1000) instead of a fixed cell, and the dots stay
a muted hint rather than a pattern that competes with the plan.
### Curtains and blinds
- **New "Open/close" tap action for covers.** Closed opens, open
closes, and a cover in motion stops. Offered only for the
unprotected device classes — garage doors, doors and gates stay on
the info dialog, on purpose.
- **The icon itself tells you the state**: it morphs between the open
and closed shape (a table of cover classes, plus aliases resolved
from the base icon), and pulses with a soft ring while the cover is
travelling.
- **A curtain never wears a coloured plate.** No state fill behind a
cover marker, ever.
- **The cover is found among all of the marker's entities**, not just
the primary one — which is the common case for Aqara and
zigbee2mqtt devices — and an explicit "Open/close" choice always
wins the indication.
### Sun polish
- **Brighter, shorter rays.** Opacity up to 0.30, length down 30%,
with crisp sides again.
- **Light fades only along the ray**, and is guaranteed to reach zero
before the wedge ends — the wedge is now clipped by distance along
the ray, so the "bright rim" that appeared with a low, oblique sun
is gone.
- **A hard 3 degree threshold.** The rays appear and disappear at 3
degrees of solar elevation over a two-second fade, instead of
creeping in.
- **The day/night sky catches up with the sun** when you come back to
the tab: a gap of more than 3 degrees is repainted at once, while
normal movement keeps breathing smoothly.
### Everything else
- **Live rulers and the centre magnet while PLACING an opening**, not
only while dragging one — with the distances measured along the
owning room's own edge.
- **A warm re-mount.** When Lovelace recreates the card on reconnect
there is no preloader and no flash of the plan any more, and a
dropped WebSocket never blanks a plan that is already on screen.
- **Room borders and names default to dark grey `#55606c`** instead of
the accent blue. If you picked a colour explicitly, nothing changes.
- **An "About" block at the end of the general settings** — card
version, GitHub and Telegram links.
- **The icon angle now steps by 5 degrees.**
- Audit fixes: the sun-ray cache survives local edits, hidden devices
no longer stretch the frame, the editor's grown frame does not leak
into view mode, an outlier room no longer inflates icon sizes, and a
kiosk pan stays a pan instead of flipping to the next floor.
## v1.56.0 — 2026-08-03
Minor release: the sun comes to the floor plan — a compass, a day/night
backdrop and real sunlight through the windows — plus room temperature
from climate devices, a design-token pass over the UI, live rulers for
opening drags, and a sun-cache fix.
- **The sun on your floor plan** (docs/SUN.md). Tell the plan where
north is — a compass dial in the general settings, with a per-space
override — and the card starts living by Home Assistant's own
`sun.sun`:
- *Day/night backdrop* (`bg_mode: daynight`): the scene behind the
plan follows the sun's elevation — white at full day, warm gold
through the golden hour, cooling dusk, deep night. The plan itself
dims only ~10% at night, so the rooms stay readable.
- *Light through the windows* (`sun_rays`): every window on an
exterior wall casts a soft wedge of light into its room — clipped
by the room's own contour, warmer near the horizon, stretching
long at sunrise and sunset, short at noon, gone after dark.
- *Clouds, if you want them* (`weather_entity`): point at a weather
entity and an overcast sky mutes the wedges; rain or snow puts
them out. A dead sensor never kills the sun.
- The plan now sits on an *opaque sheet of paper* traced along the
room contours: the scene background — `bg_color` or the daynight
sky — is visible only around the walls and never bleeds through
the rooms; in day/night mode the sheet casts a soft shadow.
- The whole feature is silent until the compass is set; without
`sun.sun` the settings dialog explains why.
- **Room temperature from climate devices.** A new checkbox in the
device dialog puts the AC's or thermostat's `current_temperature` on
the badge next to its icon and into the room's average temperature.
- **UI modernization.** A design-token pass over `styles.ts` — 209
hard-coded values unified into spacing/radius/font/shadow scales —
and native `ha-switch` / `ha-slider` controls in the dialogs, with a
hard fallback to the old plain inputs where HA components are
unavailable.
- **Live rulers for opening drags.** Dragging a door or window now
shows the live distances to both ends of its wall, measured along
the owning room's edge; a dashed guide and a soft magnet snap the
opening to the wall's center (hold Shift to disable the magnet).
- **Fix DEV-B701-01** — the sun-ray cache is now invalidated by local
geometry edits too, so a wedge can no longer go stale after you move
a wall locally before the server confirms the change.
## v1.55.3 — 2026-08-02
Patch release: two fixes for the v1.55.2 first-open veil, found by an
adversarial lifecycle audit (AUD-1552-01/02).
- **A dashboard rebuild during the first open can no longer leave the
plan hidden forever** (AUD-1552-01). Disconnecting the card while the
boot veil was up (Lovelace recreates its DOM, a view switch remounts
the card) killed the settle timers but kept their ids, so nothing ever
lifted the veil again. The veil lifecycle now restarts from every
reconnect — with a fresh clock and an unconditional hard cap — and the
fade-out also survives a mid-fade remount.
- **The veil no longer opens early, right before a late panel lands**
(AUD-1552-02). Two equal height reads at 200/400 ms used to reveal the
plan at ~400 ms, so Home Assistant chrome arriving at 450+ ms jumped
on a visible plan — exactly what the veil was meant to prevent. The
veil now holds for a full protective window with trailing quiescence
(height changes near the cap extend the wait), and for a short grace
after the reveal any later shift glides via a height transition
instead of snapping. Deliberate height changes (entering an editor)
still apply instantly.
## v1.55.2 — 2026-08-02
Patch release: a calmer first open — no zoom flash and no layout jumps
while Home Assistant is still settling — plus a new background color
setting and two bits of editor polish.
- **Opening a plan no longer flashes the default view before your saved
zoom** (HP-1551). The saved zoom used to be applied a beat after the
first render, so the card painted the default fit for a frame and then
snapped to your position. It is now applied synchronously, before the
first paint — the plan appears exactly where you left it.
- **A first-open veil hides the plan until the layout settles**
(HP-1552). While Home Assistant is still loading its side panels, the
stage height can change several times, and the plan visibly jumped
along with it. The card now waits under a dark veil with a pulsing
house outline until the height stops moving, then reveals the plan
already in place. Kiosk mode skips the veil entirely.
- **New setting: background color around the plan** (HP-1554). The area
around the plan can now be any color — set it in general settings for
the whole card, or override it per space. The backend validates the
value as a #rrggbb hex color.
- **Round caps and joins on background-editor lines** (HP-1553). Strokes
used to end and meet in square cuts; line ends and corners are rounded
now, so traced walls and outlines look clean.
- **The Resize tool's wall handles now look the part.** They are half
the previous size, show a wall-with-arrows glyph that rotates to
follow the wall, and use the grab cursor — while the hit area stays
exactly as large as before.
## v1.55.1 — 2026-08-01
Patch release: the four findings of the v1.55.0 audit, all in the new
Room resize tool (HP-1550-01..04).
- **A pending save can no longer leak a live drag to the server**
(HP-1550-01). The resize preview used to be written straight into the
shared config object, and a debounced save still queued from a
previous edit could snapshot it mid-drag — so a resize you then
cancelled with Esc quietly survived on the server and came back after
a reload. The live geometry now stays in a separate overlay that only
the renderer sees; it reaches the config exactly once, when the
handle is released.
- **An interrupted drag is cancelled, not committed** (HP-1550-03).
When the system cuts a drag short — switching apps, palm rejection on
a tablet — the tool used to treat it as a release and save the
half-finished geometry. Such interruptions now take the same path as
Esc: the original geometry comes back, no undo step, no save.
- **A door in the middle of a wall no longer blocks resizing it**
(HP-1550-04). The door's invisible hit area used to sit on top of the
wall handle, making such a wall ungrabbable for both rooms. In the
resize tool the handles now own the hit test (openings are not
editable there anyway — they simply travel with the wall), while all
other editor tools keep openings clickable exactly as before.
- **The ~30 cm minimum size now holds for any room shape**
(HP-1550-02). The old check only measured parallel opposite walls, so
a triangular room could be squeezed into a sliver, and a rotated
room could scale its real short side below the floor unnoticed. Both
measures are orientation-independent now: wall drags respect every
obstacle in the wall's path (a triangle's apex, a slanted wall), and
the scale frame uses the room's true minimum width. Rooms already
thinner than the floor may still be improved, never made worse.
## v1.55.0 — 2026-08-01
Minor release: rooms are no longer set in stone — a dedicated Resize
tool in the plan editor changes their size by dragging walls; plus the
two findings of the v1.54.3 audit (HP-1543-01/02).
- **Room resize — a new «Размер» tool in the plan editor** (spec:
docs/RESIZE.md). Every wall gets a handle at its midpoint; drag it
and the whole wall moves, staying parallel to itself, with grid
snap. Works for polygons (L-shapes included) and for legacy
rectangles alike.
- **Shared walls always move together.** If the dragged wall coincides
with a neighbour's boundary, the neighbour follows: your room grows,
the neighbour shrinks — gaps and overlaps cannot appear by
construction. At T-junctions only the coinciding stretch follows,
inserting new corners into the neighbour's outline where needed.
- **Live numbers while you drag.** The dragged wall and its two
adjacent walls show their lengths in real meters/feet, and the room
area in m² updates live at the room centre — for a shared wall, the
areas of both rooms.
- **The wall stops where it must.** Rooms cannot get thinner than
~30 cm (neither yours nor the neighbour's), an outline never crosses
itself, a growing wall stops at foreign rooms and island rooms, and
doors and windows are anchors: an opening travels with its wall, and
a wall carrying openings can never get too short for them.
- **A scale frame for the whole room.** Click a room in the resize
tool to select it: dragging a corner of the dashed frame scales the
outline proportionally, with the same stops applied.
- **Esc and Ctrl+Z.** Esc cancels the current drag and puts the
original geometry back; one released drag is one undo step, and
Ctrl+Z walks back up to 30 of them while the tool is active.
- **Fix: room overlap detection missed equal-height rectangles slid
over each other.** Found while building the resize stops; the
slide-over case now counts as an overlap everywhere the check is
consulted, including the draw tool.
- **Fix (HP-1543-01): editor zoom stayed on screen after a floor
switch made inside the editor.** Exiting an editor on a different
floor than the one you started on skipped the viewport restore and
left the editor working zoom in view mode. Such an exit now falls
back to the current floor's saved view zoom.
- **Fix (HP-1543-02): a motion sensor re-tripped mid-flash stayed
invisible.** A rapid off→on before the current flash ended kept the
old CSS animation timeline, so the second detection played nothing.
Every witnessed trip now gets a fresh animation identity and the
flash restarts; under reduced motion the static ring stays, as
before.
## v1.54.3 — 2026-08-01
Patch release: three quality-of-life rounds — icon satellites now follow
the per-device size multiplier, editor zoom stopped leaking into view
mode, and motion sensors got a visual language of their own.
- **The per-device size multiplier now scales the icon's satellites
too.** Reported by an owner with a screenshot: shrinking a device left
the value badge, the temperature/humidity plates, the LQI label, the
«new» dot and the alarm ring at full size, towering over the tiny
icon. Every satellite now follows the multiplier along with the icon.
- **Editor zoom is a working tool, not a saved setting.** Zooming to
500% to place a marker precisely and then closing the editor used to
drop you into a 500% view mode. Leaving any editor now restores the
viewport you had in view mode before you started editing.
- **Fix: editor zoom could still resurrect when switching floors.** The
editor viewport raced the per-floor viewport store and won: switch
floors while editing and the 500% was written down as that floor's
saved view, greeting you on the next visit (steps to reproduce came
from the owner). Editor zoom is never written to the store anymore.
- **Tripped motion and presence sensors signal with a yellow ring.** The
rule stays «fill = on»: motion is a one-shot flash — three pulses at
the moment of detection, with no pulsing during the cool-down tail —
while occupancy/presence hold a static ring for as long as they report
someone present.
- Stand only: a `services.yaml` for the demo stand's `demo_guard` to
keep hassfest green; nothing of it ships in the integration.
## v1.54.2 — 2026-07-31
Patch release: the one remaining finding of the v1.54.1 re-audit
(HP-1541-01), pinned by regression tests on both sides of the contract.
- **Fix: a vacuum whose own `selected_map` is `0` split calibration and
trail between two map ids.** The v1.54.1 map-id contract («the first value
that exists wins, and zero is a value») was applied to the source entity
but not to the card's fallback on the vacuum's `selected_map`: the
frontend still judged it by truthiness and turned `0` into `default`,
while the server recorder stored the run under `0`. Calibration was saved
under a key the recorder never used, and the recorded trail never rendered
after a reload. The fallback now follows the same not-nullish rule on both
sides (`vacMapIdWithFallback` in the card, `resolve_map_id` on the
server), with cross-runtime regressions for `selected_map` = `0`, `"0"`
and `""`.
## v1.54.1 — 2026-07-31
Patch release: everything the adversarial audit of v1.54.0 found
(HP-1540-01..06), each fix pinned by a regression test that fails on the
old code.
- **Fix: the first calibration of a freshly discovered vacuum silently did
nothing.** Until the device dialog was saved once, the robot had no config
marker — yet the «Живая позиция» section was fully interactive, and every
handler quietly bailed out while auto-calibration still announced success.
Any vacuum edit now materialises the marker itself, and the success toast
only appears after the matrix has verifiably landed in the config.
- **Fix: a robot whose first map is `map_index: 0` lost its server-side
trail.** The backend picked the map id by truthiness and dropped the zero,
so the recorded run was stored under a key the card never looked up. Both
sides now share one explicit rule — the first value that exists wins, and
zero is a value.
- **Fix: one robot on two floors recorded history for the last floor only.**
The recorder kept a single marker per source entity, so the second
placement silently evicted the first. Every marker fed by a source now
gets its own copy of the run.
- **Fix: auto-calibration ignored plans drawn with rectangle rooms.** The
room matcher only accepted polygon outlines and then blamed the room
names. Legacy rectangles count like everywhere else in the card.
- **Fix: overlapping recorder refreshes leaked state subscriptions.** Two
config saves racing each other could both subscribe and strand one
callback until restart; refreshes are serialised now and teardown wins
over any refresh still in flight.
- The «no rooms» / «no match» toasts and docs/VACUUM.md no longer send you
to the three-point calibration that no longer exists — they point at
«Подогнать вручную», which does.
## v1.54.0 — 2026-07-31
### Live robot vacuums
The plan now shows the robot while it works. The device marker stays where
you put it — that is the dock — and a round puck drives the plan in real
time, pouring its path out from under itself. Everything is display only:
the card never commands the robot.
- **Calibration without arithmetic.** «Настроить автоматически» matches the
robot's room list against your rooms by name and solves the transform in
one click. When names do not match, the fit panel lays the robot's rooms
over the plan as a dashed ghost: drag it into place, stretch it by the
corner handles, quarter-turn and mirror with two buttons. Mirror is on by
default — every robot map we have measured flips Y versus the screen.
One calibration per robot map, so two floors stay independent.
- **The path is recorded server-side.** The integration watches the robot
itself, so the trail records with no card open, survives page reloads, and
every screen sees the same line. The current run and one previous run are
kept — cleaned versus not-yet-cleaned at a glance.
- **«Показывать путь робота»**: never / while cleaning (default) / always.
Only the last mode also draws the previous run, faded.
- The trail never runs ahead of the icon: drawn segments lag one point and
the growing tip is glued to the puck's animated centre every frame. It is
drawn as a dark halo under a light core, so it stays readable over any
room fill. The puck teleports instead of gliding when the view changes —
zoom, floor switch or a return to the browser tab.
- Adapters: Xiaomi Cloud Map Extractor, dreame-vacuum (Tasshack), Valetudo.
Verified against a live Dreame X50 Master.
### Also
- **«Свет по источникам» is the default fill for new spaces** and leads the
options list. Existing plans are untouched: a space whose owner never
chose a fill still renders as before.
- **Fixed: the "what to run" search showed no results.** The list is a
scrollable box and, as a flex item, collapsed into a 1px sliver — the
matches were there, rendered into nothing.
## v1.53.1 — 2026-07-30
- **Fix: the "what to run" search showed no results.** The results were
there — the list is a scrollable box, and as a flex item inside the dialog
body it collapsed into a 1px sliver, so 26 matching automations rendered
into nothing visible. Reported within minutes of v1.53.0 by the owner. The
list keeps its height now; the smoke measures that height instead of
merely counting DOM rows, which is why it passed the broken build.
## v1.53.0 — 2026-07-30
**A tap can run your automation** (owner's spec)
- **New tap action: "Run automation/script/scene".** We cannot know every
exotic device, but you know what a tap on it should do: pick any
automation, script or scene from a searchable list, and the tap runs it —
`automation.trigger`, `script.turn_on` or `scene.turn_on` per kind, with a
"Started" toast. A script is the idiomatic HA "action", so all three
runnable kinds are offered — no trigger-less dummy automations needed.
Saving refuses a run action without a target; a target deleted later warns
in the dialog and toasts safely on tap.
- **"Ask for confirmation" checkbox** — the accidental-tap guard, available
for any state-changing action: toggle and run alike, wall-switch markers
with bound targets included. The dialog is the card's own (Esc, backdrop
or Cancel = nothing happens), so it works on a wall tablet.
- **Curtains toggle natively:** covers and valves joined the card-wide
toggle domains — with one deliberate exception: garage doors, doors and
gates (cover device classes garage/door/gate) stay OUT of the default
toggle, an accidental tap must not open the driveway. An explicit
per-device toggle remains the owner's conscious choice, and locks/alarm
panels stay untouchable from the plan, as always.
## v1.52.2 — 2026-07-29
**From the v1.52.1 review** (no runtime changes — test and wording quality)
- The plan-editor regression check now targets THE lamp (HP-1521-01): the
old assertion accepted any yellow badge, and the lit socket in the same
fixture would have satisfied it with the lamp fix removed. Verified by
mutation: reverting the v1.52.1 gate makes the smoke fail.
- The one remaining "yellow in every fill mode" wording — the checklist
entry and the _stateClass comment — now states the actual contract: the
lit-source state is computed by the glow-pool condition, and the yellow
badge shows only where the glow spot is not drawn (HP-1521-02).
## v1.52.1 — 2026-07-29
**From the v1.52.0 review**
- **A lit lamp always has exactly one indicator (HP-1520-01).** The glow
layer is hidden in the plan editor, but the yellow suppression still
applied there — a lit lamp showed neither the spot nor the badge. The
suppression gate now equals the layer's actual visibility: wherever the
spot is not drawn, the yellow badge returns.
- **The static card honours a marker's size and rotation (HP-1513-01).** The
same stored marker rendered at base size, unrotated, on the read-only
card. It now mirrors the full card's --dev-scale and angle — geometry
only, still a schematic.
- Documentation caught up with the v1.52.0 colour contract (HP-1520-02):
the RGB-tint expectations in TESTING/UX-MODES and a stale inline comment.
## v1.52.0 — 2026-07-29
**One look for light sources, whatever flipped them** (owner's rule)
- **A lamp's colour lives only in its glow.** The RGB tint of the icon,
border and shadow is gone: depending on whether the state carried colour
data, the same lamp used to land in the "coloured icon on a dark badge" or
the "plain yellow badge" branch — turning one lamp off by tap and the rest
by the wall switch produced visibly different results. The branch is gone.
- **In glow fill the indicator IS the glow spot.** A light source's badge
stays standard, lit or not — the pool of light around it says everything.
A lit socket, fan or kettle keeps its yellow even in glow fill: they cast
no light, the rule is for sources only.
- **In every other fill a lit source is plain yellow**, like a heating
radiator valve — RGB and white lamps alike.
- Icon morphing (the shining-bulb outline) stays in every mode, and the
ripple colour still falls back to the lamp's light colour.
## v1.51.3 — 2026-07-29
- **The icon size multiplier scales the glyph, not just the badge.** Changing
a device's size grew the badge, the ripple and the value badges but left
the icon itself at its default size — a big empty box around a small glyph
(user report). The glyph now derives from the same per-device size as
everything else and keeps its proportion at any multiplier and zoom.
## v1.51.2 — 2026-07-29
**From the v1.51.1 review**
- **The auto grid is the same on both cards (HP-1511-01).** The full card
reserves grid cells for hidden devices (their ghosts keep a place in the
device editor); the static card compacted the grid over visible ones only,
so a freshly discovered marker with no saved position landed in different
spots on the two cards. The static card now feeds the full roster to the
same grid and still draws only the visible.
- **A ripple-display ghost keeps its base icon (HP-1511-02).** Hidden markers
with the "ripple" presentation rendered as an icon-less inactive pulse —
unrecognisable in the editor. A ghost now drops the display dressing
entirely: base icon and name, whatever the display mode.
## v1.51.1 — 2026-07-29
**From the v1.51.0 review**
- **The static card counts hidden devices in room LQI again (HP-1510-01).**
Its visibility filter had quietly become the aggregation filter: the same
room showed different Zigbee health on the two cards. Aggregation and
rendering use separate lists now — hidden devices count toward signal on
both cards, are drawn on neither, and still cast no light.
- **A ghost shows no live numbers (HP-1510-02).** A hidden device in "Show
hidden" suppressed the state colors but still painted its value text,
temperature, humidity, LQI badge and state-morphed icon. All of that is
gone: the ghost keeps only the base icon and name — enough to recognise it
and open the dialog.
## v1.51.0 — 2026-07-29
**Hiding is an explicit flag now** (docs/FILTERING.md)
- **Every device dialog — virtual ones included — has a "Hide device from
plan" checkbox.** The old on-the-fly filter survives only as the SEEDER of
those flags: on first load by an editing client the config is materialised
once — non-physical devices (bridges, scenes, service integrations, lamps
folded into a light group) get the flag, and from then on the flag belongs
to you. Unticking it is final: the seeder never revisits a device you have
decided about. New non-physical devices hide silently; physical ones keep
the red-dot flow.
- **"Show all" became "Show hidden"** — a local tool of the device editor
(nothing flips on the wall tablets), showing hidden devices as translucent
BLUE dashed ghosts: clearly apart from a grey unavailable icon, and with no
live-state paint at all — a ghost is configuration, not status. Click one
to untick the box. "Remove from plan" is gone for bound devices (the
checkbox is the way); a virtual device's Delete still deletes.
- Hidden devices still count toward the room's Zigbee signal, but cast no
glow and no light fill — an invisible device casts no visible light. Room
climate is unchanged. Old configs behave exactly as before until an
editing client materialises them.
**Yellow means working right now**
- One principle for the glowing icon: a light is shining, a socket is
powering, a fan is spinning, media is playing, a vacuum is cleaning — or a
radiator valve is ACTUALLY heating (hvac_action), not merely enabled for
the winter. Previously a TRV could glow yellow because its anti-scaling
service switch was on while the actually-heating one stayed dark: the
primary-entity search let a vendor's config switch outrank the visible
climate entity. Fixed — a service entity never beats the device's visible
main function (this also fixes tap-toggle and icon morphing on such
devices).
- The glow pool and the icon color now ask the same question: a lit light
yellows its icon in every fill mode, by exactly the condition that lights
its glow spot. The README (en+ru) documents the color language.
**The editors, on a phone**
- Pinch zoom and pan gestures now work in every editor: drawing is
click-based, so the two coexist — a moving finger pans, two fingers pinch,
releasing after a gesture never draws a point, a clean tap still does.
**The room settings button**
- Detached from the (movable) room name: it sits at the VISUAL centre of the
room — the centre of the largest inscribed circle with a pull toward the
area centroid, so an elongated room centres it on both axes and an L-shaped
one keeps it in the middle of its widest part, never down a thin limb.
- Half its former size, sized from the device icon (70% of the icon box) and
zooming WITH the plan instead of keeping a constant screen size.
- The small metric rows under the room name (temperature, humidity, signal,
lights) now show in the plan editor too, and the name renders in exactly
the same spot in view mode and in the editor.
## v1.50.4 — 2026-07-29
**From the v1.50.3 review**
- **Both cards build their model with the same code now (HP-1503-01).** The
full card carried a hand-copied twin of the shared model builder, and the
twin missed the legacy-store fallbacks v1.50.3 added — the same broken
store rendered fine in the static card and as a blank `viewBox="0 0 0 0"`
in the main one. The duplicate is gone: the full card calls the shared
builder and only swaps in the raw plan url its signing flow needs. A new
smoke runs the audit's exact legacy vector through both models and both
DOM trees and asserts parity.
## v1.50.3 — 2026-07-29
**From the v1.50.2 review**
- **A size is not a coordinate (HP-1502-01).** The ±4 bound from v1.50.2
treated all four view_box elements and room w/h alike, so `[0, 0, 0, 0]`
and negative sizes still passed — and a zero axis serialises into
`viewBox="0 0 0 0"`, a blank plan on every client, with the static card
computing `aspect-ratio: 0 / 0` on top. Sizes now get their own validator:
strictly positive, floored at one thousandth of the canvas; coordinates may
still be negative, because a crop origin legitimately sits past the edge.
And since a store may already hold a broken viewport from before, both
cards fall back to the whole canvas instead of a blank screen, and a legacy
rectangle with a negative size is read as the same rectangle drawn from the
other corner.
**Also in this release**
- The room settings button moved to the bottom of the room card, and the room
name renders in exactly the same spot in view mode and in the plan editor —
the button and the metrics no longer take part in the label's centring.
## v1.50.2 — 2026-07-29
**From the v1.50.1 review**
- **Geometry magnitudes are bounded on both layers (HP-1501-01).** v1.50.1
bounded layout positions, but room rectangles, polygon vertices, view_box
and opening coordinates still took any finite float — one schema-valid 1e100
vertex framed the space so wide the plan was a dot, for every client, and
the server stored it as a perfectly good configuration. The config schema
now bounds geometry to ±4 (angles to ±360°), and the content frame applies
the same canvas envelope to room vertices it already applied to device
positions — so a store that already holds an absurd coordinate from before
this door existed still renders: the point draws wherever it is, it just no
longer commands the frame. A vertex a bit past the canvas edge keeps
working.
- **A no-op repair no longer eats the undo backup (HP-1501-02).** A typo'd
space id "succeeded" with moved: 0 — and its empty result replaced the
one-deep backup, destroying the only way back exactly when it was needed
most: right after repairing the wrong space. Matching nothing is an error
now (`nothing_to_repair`); nothing is written, the revision does not move,
and the previous repair stays undoable.
## v1.50.1 — 2026-07-29
**From the v1.50.0 review**
- **A card below other dashboard content gets its stage back (HP-1500-02).**
The v1.50.0 height measurement used the absolute document coordinate, so a
tall card before this one was billed as "header" and the stage collapsed to
zero. The card now measures only its own chrome plus a bounded allowance for
what the dashboard keeps above it, and re-measures on window resize; the
listener is removed on teardown.
- **The content frame can no longer be degenerate or absurd (HP-1500-03).**
A lone marker in an empty space produced a zero-area viewBox — a blank
scene; a single stored coordinate like 1e100 (any finite float passed
validation) stretched the frame until the plan was a dot, for every viewer
of the space. A near-zero axis now opens up to a floor of canvas around the
marker, points far outside the canvas envelope no longer command the frame
(they still render where they are), and the server refuses layout
coordinates outside ±4 — generous slack for an icon dragged past an edge,
not an envelope for absurdity. A real thin room keeps its tight frame, and
the gate sensor slightly past the edge still counts.
- **A repair path for installs stranded by the v1.48 migration window
(HP-1500-01).** If the old migration crashed between its two writes, the
markers of a space are left in the old coordinates with nothing in the data
able to prove it — and re-transforming a correct layout would corrupt it, so
nothing automatic is safe. `houseplan/geometry/repair {space_id, aspect}` is
the explicit answer: `dry_run` previews the exact moves, the previous
positions ride the same store write as a one-deep backup, `undo` restores
them, and routine drags no longer erase that backup. The v1.50.0
`geom_pending` protocol already protects every future migration; this covers
the installs it was too late for.
## v1.50.0 — 2026-07-28
**Owner's batch**
- **The default zoom counts devices as content.** They are allowed to stand
outside every room — a gate sensor by the fence, a camera on a pole — and the
opening view now includes them, even on a space with no rooms at all.
- **Entering an editor no longer shifts the plan.** The stage height assumed a
fixed 118px of header, and the editor header is taller: the scene slid down
by the difference and its bottom went below the fold. The card measures where
the stage actually starts and gives it the rest of the viewport.
- **The zoom goes out as well as in.** Down to 0.4×, and zoomed out the plan
floats centred instead of being pinned to a corner.
**From the v1.49.0 review**
- **The square-canvas migration survives a crash between its two writes
(HP-1490-01).** Config and layout live in separate stores, written one after
the other, and the first write deleted the very fields the second needed — a
failure between them stranded markers in the old coordinates for good. The
migration intent is durable now, saved before anything moves and cleared by
the layout write itself; whichever half is missing after a crash, the next
start finishes exactly that half, once.
- **Parallel uploads cannot slip past the store quota together (HP-1490-02).**
N uploads all measured the store before any of them wrote, and all passed a
limit only one of them fit under. The measure-and-write pair is one atomic
step under its own lock — separate from the config lock, so a slow directory
scan does not stall saves.
- **The editors see the whole canvas again (HP-1490-03).** The content frame
also bounded pan, zoom and pointer maths, so after the first room there was
nowhere left to draw the second one. Edit modes now measure from the full
square; the view keeps its content fit, and switching modes refits instead of
carrying a view clamped against the wrong base.
- **Save waits for the proportions of a picked plan (HP-1490-04).** Saving
before the image had answered used to ship the PREVIOUS file's ratio, and the
new plan kept the old shape for good. Picking a plan clears the old ratio at
once, and Save awaits the bounded read; if it fails, "unknown" is stored —
a square fallback is honest, an inherited ratio is not.
- Release hygiene from §5: package-lock.json caught up with the package
version, and a duplicated comment in space-geometry.ts is gone.
## v1.49.0 — 2026-07-28
**The canvas is square** (see v1.48.0, released together with this one).
- **Zoom opens on what is drawn, not on the whole canvas.** A space without a
background image now fits its rooms with a 5% margin, so a small plan on a big
canvas fills the screen instead of sitting in the middle of it as a speck.
With a background image nothing changes: the image is the plan, and cropping
to the rooms would hide the parts nobody has outlined yet.
- **Switching spaces by swipe, or on the kiosk carousel, slides.** The plan
leaves the way the finger went and the next one arrives from the other side.
Respects "reduce motion".
- The room settings button says "Room settings" rather than just "Room", and
lightens slightly under the cursor.
- "Curation" is called filtering everywhere — the interface, the documentation
and the code.
**From the v1.47.0 review**
- **A plan you have just picked can no longer be deleted from the same dialog
(HP-1470-02).** It was not saved yet, so the server correctly considered it
free — and the save then stored a url with no file behind it. The button is
disabled now, and, because two clients can do the same in either order, the
server checks every internal plan url a configuration adds against the disk
and refuses a new reference that is already broken. A url the stored
configuration already carries is let through — a file can vanish from outside
Home Assistant, and refusing then would block the very edit that detaches it.
Urls that are not ours are left alone.
- **Uploads are bounded (HP-1470-01).** Nothing is deleted for being old — that
cost real plans twice — so the limit sits where a decision is being made
anyway: an upload is refused if the store would pass 256 MB or 200 plans
(1 GB / 1000 for attachments), or if the disk would drop below 512 MB free.
The plan list is capped at the 60 newest and its thumbnails load lazily.
- **Picking a saved plan reads its real proportions (HP-1470-03).** The card
waited for nothing and, when the signature for the protected url had not
arrived yet, saved a fallback ratio — a square plan came out stretched. It now
waits for the signature, ties the result to the dialog that asked, and the
preview in the dialog is signed like everything else.
## v1.48.0 — 2026-07-28 (the canvas is always square)
- **A space no longer has proportions of its own.** The drawing area is a square;
a plan image keeps its own shape and is centred inside it, so a wide plan gets
margins above and below and a tall one gets them at the sides. There is
nothing left to choose — the canvas orientation setting for hand-drawn spaces
is gone with it.
- **Existing plans are migrated once, on upgrade.** Nothing about a drawing
changes: the box is padded out to a square and every coordinate is
re-expressed against it — rooms, doors and windows, decor, marker positions
and the saved viewport. Angles, room proportions and relative positions are
preserved exactly. For a tall plan the scale in centimetres per grid cell is
adjusted along with it, because the grid is tied to the width; without that a
wall would silently measure less than it does.
## v1.47.0 — 2026-07-28 (pick a plan you already uploaded)
- **The space dialog can now show the plans stored on the server.** Detaching a
plan keeps the image on disk — that has been the rule since v1.46.4, but until
now the only way back was to find the original file on your computer and
upload it again. "Already uploaded" lists what is there, with a thumbnail, the
file size and which space uses it. One click attaches it; the aspect ratio is
read from the image, exactly as on upload.
- **And it is where you delete one.** A plan file is never removed automatically
— not for being detached, not for being old — which is only a sensible policy
if you can see what is being kept and get rid of it deliberately. The trash
button does that, and refuses while a space still uses the plan: the answer to
"may this go" comes from the stored configuration, not from the browser.
- Documentation caught up with the code: several comments still described the
age-based collection that v1.46.6 removed.
## v1.46.6 — 2026-07-28 (the detach promise, actually kept this time)
- **Switching a space to "draw" no longer deletes its image.** v1.46.4 and
v1.46.5 said it did not, and the scheduled cleanup indeed left detached plans
alone — but the save itself deleted the file the moment the reference was
cleared, before any of those guards were reached. The cause: a file that left
the configuration was called "superseded", and from that difference alone
replacing a plan, detaching one and deleting its space are indistinguishable.
Only the first is a deletion anybody asked for. The transition is now
classified by the space that owned the file, and the same distinction applies
to attachments: dropping one from a device that still exists removes it,
deleting the device keeps its manuals.
- **A plan whose space was deleted is kept**, rather than the thirty days
v1.46.5 promised — thirty days measured from the file's age is meaningless
anyway, since it was usually uploaded months earlier.
- **Nothing is deleted for being old any more**, except a per-dialog staging
folder. The rule that aged out "rejected uploads" turned out to race a retry:
the cleanup removed the file from a failed save while the next attempt was
committing a reference to it. A rule that can delete a file somebody is about
to point at is not worth the disk it reclaims. Files therefore go when an
action says so, and otherwise stay.
## v1.46.5 — 2026-07-28 (audit of every automatic deletion)
- **A detached plan is never deleted, at any age.** v1.46.4 gave it a month;
this makes it permanent and writes the reason down where the next change will
see it. The rule, now in docs/SCOPE.md: the component may delete a file only
when a user action says so — replacing a plan, removing an attachment,
deleting a device. "Nothing points at this any more" is not such an action.
The errors are not symmetrical: wasted disk is visible, cheap and reversible;
a deleted file is none of those.
- **`houseplan/files/cleanup` no longer takes a folder on the client's word.**
After a device is rebound its files are copied to the new id and the old
folder is dropped — with `rmtree`, on whatever id the card sent. Two ways that
ends badly: a partial copy leaves some urls still pointing into that folder
(the migration deliberately does not rewrite those, so they were live links to
files being deleted), and a wrong or stale id from any client would destroy a
live device's manuals. The server now checks the stored configuration itself,
under the config lock, and removes only files nothing references.
- **A plan of a space that was deleted waits thirty days instead of an hour.**
Deleting a space is deliberate, but an hour is a short window in which to
notice it was a misclick.
## v1.46.4 — 2026-07-28 (data loss: detached plans were collected as garbage)
- **A plan you detach is no longer deleted an hour later.** Switching a space to
"draw" clears the reference and, as the editor has always said, leaves the
image on disk so you can put it back. The collection added in v1.46.0 did not
make that distinction: it treated "nothing points at this right now" as
abandoned and applied a one-hour rule. On the author's own instance the
scheduled pass then removed two floor plans that had been detached weeks
earlier, with no way to get them back. If you have detached a plan since
v1.46.0 and your instance restarted or ran for a day, check
`config/houseplan/plans/` before updating anything else — and please report it
in the Telegram chat if a file is missing.
The rule now: **a commit still removes exactly what it replaced**, because
that it knows for certain. Beyond that the question is whether "unreferenced"
means "abandoned", and the answer depends on the case. A space with no plan at
all has had one detached and may want it back — its files are never collected.
A space that does have a plan can only be holding rejected uploads of its own,
so those still go after an hour. Attachments outside a per-dialog staging
folder wait a month; a staging folder, which by construction only ever holds
an upload from a dialog that was never saved, keeps the one-hour rule.
## v1.46.3 — 2026-07-28 (re-check of v1.46.2: HP-1462-01)
- **The cleanup at startup now actually cleans up.** It looked its own runtime
data up by domain, and during startup Home Assistant does not yet consider
the integration loaded — so the lookup came back empty and the pass quietly
degraded to removing half-finished transfers, leaving the real work to a timer
24 hours away. Restart more often than that and it never ran at all. It uses
the object it was given at startup now.
- **The test that was supposed to prove this was passing for the wrong
reason.** It created the stray files *before* saving the configuration — and
saving collects too, so everything was already gone by the time the restart
happened. Rewritten to seed after the save, plus a second test that fires the
scheduled timer on its own, and a third that runs a restart and a save at the
same time and asserts the accepted configuration never points at a file the
cleanup removed.
## v1.46.2 — 2026-07-28 (re-check of v1.46.1: HP-1461-01, -02)
- **A file nobody ended up using is now cleaned up even if nothing is ever
saved again (HP-1461-01).** Collection is tied to a configuration write,
which is right for what a write supersedes but leaves a gap: cancel a dialog
after the file has already uploaded, lose the connection just after, or call
the upload API directly, and nothing references the file and no future write
notices it. The daily sweep added in v1.46.1 only removed half-finished
transfers, so the promise that a cancelled attachment disappears after an hour
did not hold on an instance nobody edits. The sweep now compares against the
stored configuration — under the same lock a write uses — and collects aged
unreferenced attachments and plans as well.
- **A drag is no longer undone by someone else's move (HP-1461-02).** When the
full card learned to follow position changes in v1.46.1, it protected the
positions you had moved but not yet sent — except it read that list *after*
flushing the pending write, and flushing empties it first. In a real drag,
where a write is already scheduled, the list was therefore empty and the
server's older position was painted over your move. The card now takes the
snapshot before flushing and also holds on to positions that are sent but not
yet acknowledged: until the server confirms a position, the card that moved it
is the authority on it.
- Two tests grew up to their docstrings: the upload test now actually cancels
the request task instead of only exercising error paths, and the position-sync
smoke schedules a real debounced write and delays it, which is the ordering
that lost the drag.
## v1.46.1 — 2026-07-28 (re-check of v1.46.0: HP-1460-01 … -03)
- **Two uploads of the same file name can no longer collide (HP-1460-01).**
v1.46.0 stopped overwriting attachments, but choosing a free name and taking
it were two steps: two uploads racing between them agreed on the same name,
both reported success, and one set of bytes replaced the other. The name is
now claimed atomically as it is chosen — twenty simultaneous uploads of
`manual.pdf` produce twenty files. The same helper is used when rebinding
moves files, which had the same gap.
Also fixed there: a name at the length limit lost its extension, and the
collision suffix pushed it past the limit, so the attachment was stored under
a name the server would not serve back — a permanent 404 on a file the UI
reported as attached.
- **An interrupted upload no longer leaves a temporary file forever
(HP-1460-02).** Cleanup ran in an `except Exception`, which a cancelled
request walks straight past, and the collector only ever looks inside marker
folders — so an aborted transfer left a `.upload-*` in place with nothing able
to remove it. Every exit path now cleans up, a request carrying two files is
refused outright, and abandoned temporaries are swept at startup and daily.
Uploads also write in 1 MB batches instead of one disk task per 64 KB.
- **Two full cards side by side keep the same positions (HP-1460-03).** v1.46.0
taught the static card to follow position changes and left the full one
behind, so dragging an icon in one window did not move it in another until a
reload. It follows now, without disturbing a drag of its own: a revision
arriving mid-drag is merged rather than applied over the top, and a card does
not re-read what it just wrote itself.
## v1.46.0 — 2026-07-28 (full external audit of v1.45.4: HP-1454-01 … -10)
**Security**
- **An uploaded SVG plan is no longer a live document of your Home Assistant
origin (HP-1454-01, high — release blocker).** Inside the card a plan is
referenced by `<image>`, where scripts never run; but the same url opened
directly became a top-level document of HA's own origin, and a `<script>` in
it could read the session's `localStorage` and call the API. Uploading needs
write access, which by default every authenticated user has, and a signed url
is easy to hand to an administrator. Plan responses for SVG now carry a
`sandbox` Content-Security-Policy, which drops the document into an opaque
origin. Only SVG gets it — a CSP on a PDF can break the browser's built-in
viewer, and a raster image cannot execute anything. Nothing changes for
existing plans: the card renders them exactly as before.
**Data integrity**
- **A manual attached to a device no longer overwrites the previous one
(HP-1454-02).** The upload wrote straight to `<marker>/<filename>`, outside
the configuration transaction: cancelling the dialog, or a rejected save, left
the stored url serving the new bytes. And every new icon uploaded into one
shared folder, so two of them attaching `manual.pdf` ended up pointing at the
same physical file. Uploads now take a free name and never overwrite, a new
icon gets its own staging folder whose files move to the real icon when the
save is accepted, and an upload nobody saved is collected an hour later. The
name a collision falls back to changed from `manual (2).pdf` to `manual-2.pdf`
— the old one was sanitised on the way back in, so a renamed attachment was
written and then never served (found by the new test, and it applied to
rebind collisions before this release too).
- **Two quick edits can no longer lose the second one (HP-1454-03).** The
debounce spaced out the starts of a save, not the saves themselves. If one
took longer than half a second — a busy instance, a slow link — the next edit
went out with the same revision, the server accepted the first and rejected
the second, and the conflict handler reloaded the server copy over the local
one. The edit was gone, with a message blaming another window when there was
none. Writes are now serialized: one at a time, each carrying the revision the
previous one returned.
**Correctness and limits**
- **Open boundaries follow the geometry again (HP-1454-04).** Their cache was
keyed on room ids and links only, so changing a space's aspect ratio or
dragging a vertex left the open boundaries — and the light spilling through
them — at their old coordinates until a reload. The cache is now keyed on the
rendered model itself, which is exactly what it is computed from.
- **The configuration can no longer be made arbitrarily heavy (HP-1454-05).**
The outer collections were capped; the ones inside them were not. A polygon
with 150 000 points, or a list of 100 000 device ids, validated fine and then
made every render walk it. There are now limits on polygon vertices, open-to
links, controls, attachments, text and url lengths, plus a cap on the whole
serialized configuration. The obsolete `segments` field is dropped by the
server instead of trusting the card to strip it.
- **Large files stream instead of being held in memory (HP-1454-06).** A 50 MB
manual was read whole into memory on the way in and again on the way out; a
couple of parallel downloads were real pressure on a small Home Assistant
host. Uploads stream to a temporary file, downloads stream from disk.
**Consistency**
- **The static space card honours per-room fill settings (HP-1454-07).** It
builds its model with a different function, and room settings were not carried
into it, so a room you had set to "no fill" was still painted.
- **Moving an icon updates the static card immediately (HP-1454-08).** Layout is
separate state with no revision and no event: a drag on the full card left a
static card next to it showing the old position until the configuration
changed or the page was reloaded. Layout writes now keep a revision, return
it, and announce themselves — which also makes the optimistic locking on a
wholesale layout write mean something, since a point-wise write used to reset
the counter.
- **A repair warning about a missing plan disappears with its space
(HP-1454-09).** The cleanup only looked at spaces that still exist, so
deleting or renaming one left its warning in Repairs with nothing able to
clear it.
- Build chain: `serialize-javascript` pinned past two advisories (HP-1454-10).
Not reachable at runtime and production dependencies were already clean, but
it is one line.
## v1.45.4 — 2026-07-28 (review of v1.45.3: R5-1, R5-2)
- **A partly successful signing answer no longer skips the backoff (R5-1).**
The backend signs each path independently: one it cannot sign is logged,
skipped, and the call still succeeds with the remaining urls. The card took
any successful call as "the whole batch is done", cleared the backoff for
every path in it, and then wrote only the urls that came back — so a path the
backend kept skipping was requested again on every single render, which is
exactly the amplification v1.45.2 added the backoff to prevent. A path is now
counted as signed only if the answer actually carries a url for it; the rest
back off individually, keys nobody asked for are ignored, and a re-render is
only triggered when at least one new signature arrived.
- **The status snapshot no longer contradicts the repository (R5-2).** It still
described `main` as carrying releases up to v1.40.1 and quoted test counts
from several releases back, while the version line right beside them was kept
current — a maintainer or an agent reading it for handoff got a wrong branch
model and a smaller picture of the coverage than exists. The branch roles are
described accurately, and the counts are gone: `npm run inventory` prints them
from the tree, so there is nothing left to go stale.
## v1.45.3 — 2026-07-27
- **"Value instead of an icon" could not be saved (issue #3).** The option was
added to the device editor in v1.26.0, but the server-side schema only ever
accepted `badge`, `ripple` and `icon_ripple`. Choosing it produced
`not a valid value for dictionary value @ data['config']['markers'][n]['display']`
— and because a single rejected marker fails the whole configuration write,
the plan could not be saved at all until the setting was undone. Thanks to
@RemyRoux for the report and the exact error text.
- **The option lists now live in one place and are checked across languages.**
`DISPLAY_MODES`, `TAP_ACTIONS`, `SPACE_FILL_MODES` and `ROOM_FILL_MODES` are
exported from the card and read by a backend test that asserts the schema
accepts every value a user can actually pick. Adding an option to an editor
and forgetting the schema now fails the test suite instead of surfacing
through somebody's error message.
## v1.45.2 — 2026-07-27 (hardening from the v1.45.1 review: R4-1, R4-2)
- **A failed cleanup no longer reports an accepted save as an error (R4-1).**
Collecting superseded plan files runs after the configuration is already
stored, but an error while listing the directory — it can vanish or turn
unreadable between the check and the walk — propagated out of `config/set`.
The client then saw a failure for a revision the server had committed, and its
retry came back as a conflict. The collector now reports "nothing collected"
instead of raising, and `config/set` logs and proceeds: the event fires and
the new revision is returned.
- **One signing request per url instead of one per render (R4-2).** The pending
set was cleared when the batch went out rather than when it came back, so
while a `content/sign` call was in flight every re-render queued another one —
six calls where one was needed, and far worse on a socket that is slow rather
than merely busy. Queued and in-flight are now separate states, a failure
backs off (2 s doubling to 60 s) instead of retrying on the next frame, and a
request that never settles stops blocking retries after 15 s. A late answer
arriving after the card was torn down no longer triggers a render.
- Tests: eight unit tests for the signer with hand-settled promises (four fail
on v1.45.1), a backend test asserting a broken collector still yields a
successful save with a usable revision, and the pure-collector test extended
to a disappearing directory.
## v1.45.1 — 2026-07-27 (follow-up review of v1.45.0: R3-1, R3-2)
- **Collecting old plan files moved into the config transaction (R3-1, high).**
v1.45.0 made the upload safe but handed the deletion to the client: after a
successful save the card asked the backend to remove everything except the
file it had just committed. Two open editors could not be ordered — a delayed
request from one client deleted the plan the other had just saved, and the
accepted configuration was left pointing at nothing, which is the exact damage
copy-on-write was added to prevent. The `houseplan/plan/cleanup` command is
gone. `config/set` now collects inside its own write lock, comparing the
configuration it replaced with the one it accepted: a file the old revision
referenced and the new one does not is removed, and any other unreferenced
upload is left alone until it is an hour old, because a fresh one may belong
to a transaction that has not committed yet.
- **The static space card shows its plan background again (R3-2).** It signed
the url and then threw the result away — `getCardSize()` mutated a throwaway
model while `render()` rebuilt its own from the config — so the `<image>` kept
requesting the protected path and got a 401 on every render. Both cards now
share one signer, which also gives the static card the batching, the
expiry handling and the periodic re-signing the main card already had. Its
pending set is released in `finally`, so a single failed request no longer
wedges a url for the life of the page.
- New tests: five backend cases for the two-client interleavings from the report
(late commit, uncommitted upload, aged orphan, foreign files, rejected save),
the collector extracted to a pure module and unit-tested, and
`smoke_space_card_bg` for the signed background — it fails on v1.45.0 with the
raw url in the DOM.
## v1.45.0 — 2026-07-27 (external review of v1.44.8: R2-1, R2-2, R2-3)
- **A rejected save can no longer damage a working plan (R2-1, high).** The
plan file was written to its final name — deleting the previous extension on
the way — *before* the revision-checked config write. If that write was then
rejected (revision conflict, validation, lost connection), the live plan had
already been replaced, or the stored config was left pointing at a file that
no longer existed. Uploads now go to a versioned name
(`<space>.<token>.<ext>`) and nothing is deleted; the card asks the backend to
drop the superseded files only after the config write is accepted. A crash in
between leaves one orphan, which the next successful upload collects.
- **Signed urls no longer expire for good on long-lived screens (R2-2).** The
backend signs at most 200 paths per request and silently ignores the rest,
while the card sent its whole cache in one call and treated any cached entry
as valid forever. Past 200 attachments the later ones stopped being refreshed
and, 24 hours in, quietly broke. Requests are now batched to the shared limit,
entries carry their age (aging urls keep working while a replacement is
fetched, expired ones are never served), and the cache is pruned to the urls
the current config still references.
- **Room climate is computed once per update instead of once per room (R2-3).**
Each room asked for temperature and humidity separately, and every ask
rescanned the entire entity registry: with 60 rooms and 2000 entities that is
~120 traversals per render, enough to spend a whole frame on metadata that had
not changed. One pass now builds a map for all areas, keyed on the Home
Assistant snapshot, so fresh states are always observed while unrelated
re-renders cost nothing. Measured in the smoke: 133 registry scans per update
before, 2 after — and no longer growing with the number of rooms.
- `smoke_ux_fixes` wrote its screenshot to a hard-coded `/tmp` path and could
not run on Windows; it uses the OS temp directory now.
- New tests: `smoke_plan_upload_reject` (cleanup happens only after an accepted
save), `smoke_sign_cap` (201 urls, batching, pruning, expiry),
`smoke_climate_once` (scan count does not grow with rooms), plus backend
coverage for the versioned plan names and unit tests for the new helpers.
## v1.44.8 — 2026-07-27
- **An uploaded plan is actually attached to the space.** `_saveSpaceDialog`
held a reference to the space object across the `await` that uploads the
image. Every `houseplan_config_updated` event runs `_reloadConfigOnly()`,
which *replaces* `_serverCfg` — so the reference became an orphan and
`plan_url`, `aspect`, the title and all display settings were written into a
detached object while the save shipped the untouched config. The file landed
on disk, the plan never appeared, and re-saving could not help. Creating a
space in that window lost the space entirely.
The upload now happens *before* the config is touched, and nothing is held
across an await.
- **`_saveConfigNow` marks the write in flight** (`_cfgWriting`), like the
debounced writer already did, so a remote revision arriving mid-save defers
its reload instead of replacing the config underneath it (audit L2 extended
to this path).
- Regression test `demo/smoke_plan_upload_race.mjs` fails on v1.44.7 and passes
here.
## v1.44.7 — 2026-07-27
- **Plan backgrounds are visible again (regression from v1.44.5).** Since the
content endpoint requires authentication, the card asks the backend to sign
the plan's url — but the signing happened inside the *memoized* space model,
which is cached on the config fingerprint. The unsigned url froze in that
cache, so the signature never reached the `<image>` element and the plan never
loaded. The url is now resolved at render time, outside the cache. (PDF links
were unaffected — they already resolved at render time.)
- **No more "failed login attempt" from your own IP.** While the plan was
broken the browser kept requesting the unsigned path, which returns 401 and
makes Home Assistant raise a login-attempt warning for the viewer's own
address. The card now renders nothing until the signature is in hand, so an
unsigned request is never made.
- **Long-lived screens no longer blink.** The 12-hour re-signing used to drop
every signature and wait for new ones; it now keeps the current urls until the
replacements arrive, so a wall tablet never shows an empty plan.
- Regression test `demo/smoke_plan_signed.mjs` fails on v1.44.6 and passes here.
## v1.44.6 — 2026-07-27
- **Only room *air* counts as room climate.** After v1.44.5 started reading the
area registry instead of the visible icons, every hidden temperature entity in
the area became a candidate — including ones that measure something other than
the air. Three guards now run before averaging: entities marked
diagnostic/config are skipped, entities from curated-out integrations are
skipped, and entity ids naming a non-air medium are skipped
(`water`, `coolant`, `flow_temp`, `return_temp`, `target`, `setpoint`, `chip`,
`cpu`, `processor`, `board`, `device_temp`, `batter`, `freezer`, `fridge`,
`oven`, `kettle`, `boiler`).
On a live 60-area install this removed four real false positives: a NAS
processor temperature, the water in a smart kettle, a 90 °C sauna heater and a
virtual `better_thermostat` duplicating the real sensor.
- **New icon rules:** kettles/thermopots get `mdi:kettle`, saunas
(`sauna`, `harvia`, `парная`) get `mdi:hot-tub` — previously both fell through
to the generic thermometer rule, which is also what made them count as room
climate.
## v1.44.5 — 2026-07-27
- **Room climate now counts every sensor in the area**, including devices that
are not placed on the plan (hidden by curation or by you). Previously the
average was taken over the visible icons only, so hiding a thermometer
silently removed it from the room card, the tooltip and the temperature fill.
Curation still applies (fridges, TRVs and chip-temperature plugs stay out),
and an explicit per-room source still wins.
- The room tooltip no longer says "open the area" — clicking a room stopped
navigating in v1.40.1; the link icon on the room card does that.
## v1.44.4 — 2026-07-27 (audit follow-up: B2, B5, L4)
- **One authorization policy (B2).** The HTTP upload view still failed **open**
when the config entry was unavailable while the WebSocket path failed closed —
the two had drifted apart. Both now call the same `may_write` helper, which
denies non-admins whenever the policy cannot be read.
- **NaN/Infinity refused on every coordinate (B5).** The finite-number check
guarded only layout positions; room rects, polygon vertices, `view_box` and
opening coordinates accepted `"NaN"`, which serializes to `null` and corrupts
the stored geometry permanently. The `MAX_OPENINGS` cap was defined but never
wired in — the openings list was unbounded.
- **Drag hardening (L4 sub-item).** The tolerant `setPointerCapture` wrapper is
now used by every drag pipeline (device, label, resize), not just openings —
an inactive pointer id could kill a drag outright. Decor shapes gained a
bounds clamp: they can no longer be dragged far outside the plan and saved
there.
## v1.44.3 — 2026-07-27 (fix: plans and manuals load again)
- **The authenticated content endpoint had no working browser path.** v1.43.0
closed the security hole correctly, but Home Assistant authenticates HTTP
requests by a Bearer header or an `authSig` signed path — and an SVG
`<image href>` or a plain `<a href>` sends neither. Plan backgrounds and PDF
links returned **401** on a real dashboard (reproduced live before the fix).
The card now asks the backend to sign what it displays
(`houseplan/content/sign`, 24 h, bound to the session's refresh token, only
for our own endpoint), re-renders when signatures arrive, and refreshes them
every 12 hours so wall tablets keep working. A backend test fetches a signed
url **without** an Authorization header and asserts 200, and 401 without the
signature.
> 🇷🇺 Русская версия: [CHANGELOG.ru.md](CHANGELOG.ru.md) (записи с v1.42.0).
## v1.44.2 — 2026-07-27 (external code review: CR-1…CR-3)
A second, adversarial review (of v1.44.0) produced three findings; all are
addressed.
- **The lock invariant is now precise and enforced (CR-1).** The reviewer was
right that "locks can never be actuated from the plan" was too absolute a
claim: the door card's Unlock button does call the service. That button is a
deliberate product decision, so the invariant is restated where it belongs
("never by an accidental tap; exactly one labeled surface"), unlocking now
**asks for confirmation**, and a new smoke exercises all five actuation paths
to prove icons, `controls[]` and the device card still refuse locks outright.
- **Attachment migration became transactional (CR-2).** Rebinding a marker used
to MOVE its files before the revision-checked config save — if that save was
rejected, the stored config kept the old urls while the files had already
left. Now the server **copies**, the config is committed, and only then the
old folder is removed (`houseplan/files/cleanup`).
- **Failed or partial migrations no longer rewrite urls (CR-3).** The copy
reports an exact `{source: written}` mapping; only confirmed copies are
rewritten, name collisions get a unique name instead of silently linking a
pre-existing file, and a failed migration surfaces as a toast with the links
left pointing at the still-existing originals.
## v1.44.1 — 2026-07-27
- Added the community chat everywhere users look: **https://t.me/ha_houseplan**
(badge and header line in both READMEs, a "Getting help" section, the issue
template contact links, CONTRIBUTING, STATUS and SCOPE).
## v1.44.0 — 2026-07-27 (user feedback: control first)
- **The device card is now a control surface.** It opens with the device's
controllable entities: lights, switches and fans toggle straight from the
card with finger-sized buttons, covers/locks/climate open Home Assistant's
own more-info. Model, links and PDF manuals moved below — on a wall tablet
this card is for running the home, not for reading documentation (field
report). Config and diagnostic entities are not listed; locks still never
toggle from a card tap.
- **"This device is a light source"** — a new per-device flag. A smart switch
driving ordinary (dumb) fixtures now casts a glow in the "Light sources"
fill without inventing a light-group helper: the glow follows the switch, or
the lights bound under "Controls light sources" when they are set.
## v1.43.3 — 2026-07-27 (user feedback: discoverability and touch)
- **Room settings were unfindable.** The gear added in v1.42.0 lived inside the
room label at 0.9em of its font and 60% opacity — a few pale pixels on a
normal plan. It is now a pill button "⚙ Room" of a fixed, readable size that
does not shrink with the card font, and it appears on **unnamed rooms too**
(that is where you name them). This also unblocks the font-size sliders,
which nobody could reach.
- **Metrics line enlarged** from 0.62 to 0.75 of the room name — the reporter
could scale the name but the sensor line stayed unreadable on a tablet. The
per-room and per-space multipliers still apply on top.
- **Touch tooltips, take two.** The `(hover: none)` guard was not enough: some
devices, skins, styluses and paired mice report `hover: hover`, so tips still
stuck under the finger. The card now also latches on the first touch/pen
pointer event and drops any open tooltip on touch.
## v1.43.2 — 2026-07-27 (external audit: the test layer)
- **The smoke suite can finally fail (T1).** All 48 headless-browser smokes used
to print booleans and exit 0 — a regression was visible in their own output
and still reported success. `demo/serve.mjs` now exports `check`/`checkAll`/
`finish`: every fact is asserted by name, mismatches and uncaught exceptions
inside the card set a non-zero exit code. Verified by deliberately breaking
the kiosk editor guard: the matching smoke went red.
- **The suite runs in CI (T2)** as a `smoke` job gated on `frontend`, against a
freshly built bundle (the committed `demo/srv/assets` copy is a snapshot and
would have tested stale code), uploading per-file logs on failure.
- **`docs/TESTING.md` reconciled (T3).** `[auto]` now means "a named check
exists that fails when this breaks", and each such line names it; 72 lines
whose automation was aspirational are honestly marked `[manual]`. Two
long-standing contradictions fixed: the "ZERO edit buttons in View" line
(wrong since v1.30.1) and the opening-click line (true again since v1.43.1).
- Three smokes carried expectations that predate v1.39.0/v1.25 and quietly
described old behaviour; they now test the current contract.
## v1.43.1 — 2026-07-27 (external audit: P1 fixes)
- **Render cost (L1).** Home Assistant replaces `hass` on every state change in
the home, and each of those renders recomputed the whole plan geometry —
`_openPairs()` ran once per room (O(rooms³) collinear-overlap math) and the
space model was rebuilt twice. Both are now memoized on the config's
structural fingerprint and hoisted out of the per-room loop; cache
invalidation happens synchronously at mutation time, not inside the debounce.
- **Opening tap vs drag (L4).** Dragging a door/window had no movement
threshold, so any pointer jitter counted as a drag: the properties dialog
never opened and an unchanged config was written (which then fed the L2 race).
Now the same 3 px threshold as every other drag pipeline, and the write only
happens when the geometry actually changed.
- **Concave rooms (G2).** Containment used the arithmetic mean of the vertices
as an "interior point" — which lies OUTSIDE U- and L-shaped rooms, so island
rooms in them were rejected as overlaps and their holes never rendered. A
real interior point is computed instead (`interiorPoint`).
- **Wall dedup (G3).** `segKey` ordered endpoints by raw floats but printed
rounded ones, so one shared wall could produce two keys and be drawn twice.
Rounds first, then orders.
- **Backend hardening (B2–B5).** The write-authorization check now fails
**closed** when the config entry is unavailable (it used to allow writes
during a reload); `layout/set` supports `expected_rev` and conflicts like the
config store; a `config/set` without `expected_rev` over a non-empty store
logs a warning; coordinates reject NaN/Infinity, and spaces/rooms/markers/
decor/layout have generous size caps.
## v1.43.0 — 2026-07-27 (external audit: P0 fixes)
An external code audit of v1.41.1 found four critical issues. All four are fixed
and covered by regression tests.
- **Silent data loss on save (L2).** A debounced config write read the config at
fire time, so a `houseplan_config_updated` event arriving in between replaced
it and the user's edit vanished with no error — reproducible in a single tab.
The debounce now supports `flush()`/`pending()`, a reload flushes the pending
write first and defers while a write is in flight, and a failed reload finally
reports instead of staying silent.
- **Split corrupted room geometry (G1).** A cut starting and ending on the SAME
wall (carving a niche — a natural action) produced two overlapping,
self-intersecting rooms whose areas summed to twice the original, and the
overlap guard did not catch it. Same-edge cuts now carve the niche correctly,
and a partition invariant (parts must sum to the original) rejects anything
else.
- **Plans and uploaded files were served without authentication (B1).** Anyone
who could reach the HA endpoint could fetch floor plans and attached manuals
without logging in. They are now served by an authenticated view; stored
legacy URLs are rewritten on read, so nothing breaks. **The old public paths
disappear after a Home Assistant restart.**
- **Dialogs could resurrect and blank the card (L3).** Closing a dialog while
its save was in flight, on a failed save, spread `null` into a truthy husk;
the renderer then threw and the card went blank until reload. Guarded in all
four save routines; the error toast still fires.
## v1.42.2 — 2026-07-26
- Touch devices no longer pop hover tooltips on every tap (field feedback:
"extra labels appear and get in the way on a tablet"). Hover tooltips are
desktop-only now; on touch the same data lives in room cards and the
long-press device card.
## v1.42.1 — 2026-07-26 (room-card font sizes)
- Closing the "can't change the font size" feedback: **three sliders**.
Space settings gained a base room-card font size for the whole space;
Room settings gained independent sizes for the room NAME and the METRICS
line (50–300% each). Effects multiply — and stack with the card's corner
resize and the kiosk per-screen multiplier as before.
- Both dialogs show a **live sample card** that follows the sliders as you
drag them.
## v1.42.0 — 2026-07-26 (room settings — the third tier)
- **Settings now have four tiers**: global → space → room → device; the more
specific tier overrides the more general one (owner's principle, fixed in
ARCHITECTURE). This release adds the ROOM tier.
- Every room card in the Plan editor gained a **gear**: rename the room,
change its HA area, override the **fill type** for this room only (works
in glow spaces too — 'none' pulls the room out of the darkness), and pick
an explicit **temperature / humidity source** — any HA device or entity —
instead of the default room average. The source feeds the room card, the
tooltip and the temperature fill, and works for rooms without an HA area
(the user-feedback case: a custom template sensor bound to a room).
- The same settings section appears in the room dialog right after closing a
contour.
## v1.41.2 — 2026-07-26
- Fixed attached PDF manuals breaking after a marker is rebound to another
device: the id changes, but the uploaded files stayed in the OLD id's
folder — orphaned and eventually lost (that is exactly how the sauna
heater's manuals died). Rebinding now moves the files server-side
(`houseplan/files/migrate`) and rewrites the attached urls.
## v1.41.1 — 2026-07-24 (docs)
- README (en/ru) reworked for discoverability: keyword-rich hero ("interactive
floor plan card for Home Assistant"), badges, a feature-highlights list
covering glow, controls, kiosk, virtual walls and room cards; the kiosk
recipe moved next to Installation. Demo GIF slot ready for the new capture.
## v1.41.0 — 2026-07-24 (kiosk mode for wall devices)
- New card option **`kiosk: true`** (also in the GUI editor): the full View
experience — live states, glow, lamp taps, info cards, locks — with no
header and no editors at all, sized for a wall tablet or TV.
- **Swipe** left/right switches spaces at 1:1 zoom (wrap-around, dots
indicator); while zoomed the gesture pans as usual; **double tap** resets
zoom. **`cycle: N`** auto-advances spaces every N seconds (pauses for a
minute after any touch) — for TVs and against OLED burn-in.
- **Per-screen sizes**: long-press an empty spot (3 s) to open a popover with
icon and room-card-font multipliers, stored in this device's localStorage —
every tablet/TV tunes itself once.
- Recipe for a full-screen wall dashboard (panel view + kiosk-mode/companion
settings) added to the README.
## v1.40.2 — 2026-07-24
- Default icon rules: smart speakers (Yandex/Alice stations, «колонка»,
generic speakers) now get **mdi:speaker**; mdi:soundbar stays for actual
soundbars. Applies where the icon rules haven't been customized.
## v1.40.1 — 2026-07-23
- Rooms are no longer clickable in View (default cursor, empty space does
nothing). Instead the room card shows a small **open-in-new icon** after
the name — clicking it opens the HA area. Rooms without an area (and all
editors) have no icon.
## v1.40.0 — 2026-07-23 (smart guides)
- **Alignment helper in every editor**: while drawing an outline, a cut or a
decor shape, and while dragging icons, room cards or decor, thin dashed
guides appear from the nearest object sharing your X and/or Y (one per
axis, with a marker dot at the source) — lamps line up, lines end exactly
above the end of a parallel line. Candidates follow the context: room
vertices and path points in the Plan editor, other icons in the Device
editor, decor endpoints/corners plus room vertices in the Background
editor, other room cards while dragging one.
- The cursor badge now shows **length · angle** and turns green when the
segment's angle is a multiple of 45°. Guides are pure indication — the
grid keeps owning the actual position.
## v1.39.0 — 2026-07-23 (lights toggle by default)
- Pure light sources — devices whose primary entity is a `light` (bulbs,
chandeliers, night lights, light groups) — now **toggle on click by
default**, right from auto-placement, no per-device setting needed.
Devices where light is a side function (a kettle's backlight: its primary
is a sensor) keep the Device-card default. An explicit per-device choice
always wins. The device dialog shows the effective default.
## v1.38.4 — 2026-07-23
- Plan editor: the DERIVED wall segments (the markup layer's solid lines)
are now trimmed under open boundaries as well — v1.38.3 only trimmed the
room outlines, so the virtual wall still looked solid in the editor.
(`cutSegments` extracted as the shared workhorse; outlineWithout reuses it.)
## v1.38.3 — 2026-07-23
- The Plan editor now shows open boundaries as a true dash as well: the blue
markup outlines are trimmed under the open stretches (rooms picked for
merge/split keep their full amber highlight).
## v1.38.2 — 2026-07-23
- The card now **remembers where you were**: the selected space and the active
editor survive navigation and closing the tab (localStorage; edit modes are
restored for admins only). A `#space=` deep link still wins over the saved
space. This reverses the earlier "always start in View" rule — the owner's
call.
## v1.38.1 — 2026-07-23 (tap action cleanup, right-click more-info)
- The per-device action is now one of three: **Device card** (renamed from
"Info card", the default), HA more-info, Toggle. The confusing "As the card
default" option is gone — along with the card editor's global tap setting
(it is ignored if present in old configs). Explicit per-device choices are
untouched. RU wording: «по нажатию» instead of «по тапу».
- **Right click** on a device icon in View mode always opens HA's more-info
dialog (editors keep the native browser menu; a virtual marker without an
entity opens its device card).
## v1.38.0 — 2026-07-23 (binding section redesign)
- The device dialog's binding section is compact now: two radio buttons —
**Virtual device** and **Pick from the HA list** — with a **Show entities**
checkbox (adds every entity of the devices to the list; groups and helpers
are always listed). The searchable dropdown appears only in HA mode and
collapses once you pick. Save is disabled until a binding is chosen.
The binding logic itself is unchanged.
## v1.37.3 — 2026-07-23
- Open boundaries now render as a **true dash**: the rooms' solid outlines are
trimmed under the open stretch (outlineWithout) instead of dashes being
painted over a solid line, and the dashed layer moved **above the glow
pools** so light never covers it.
## v1.37.2 — 2026-07-23
- Glow falloff tuned: full brightness for the inner 70% of the radius,
gradient on the outer 30% (was 80/20).
## v1.37.1 — 2026-07-23
- Open-boundary tool polish: the cursor stays default and only turns into a
pointer near a wall shared by two rooms; hovering previews the exact
stretch that would become open (amber dashed) — or red solid when the
boundary is already open and the click would close it.
## v1.37.0 — 2026-07-23 (open boundaries — virtual walls)
- Rooms divided only by zoning can now share an **open boundary**: the new
"Open boundary" tool in the Plan editor toggles it with a click on the wall
two rooms share. The stretch renders dashed; while the tool is active open
boundaries highlight amber. Stored as `room.open_to` links by room id, so
redrawing/merging neighbours doesn't break them.
- In the light-sources fill, light flows through open boundaries freely —
transitively across the whole connected zone (kitchen ↔ living ↔ hall as
one open space), still limited by the glow radius. Door sectors now work
from any outer wall of the zone.
## v1.36.4 — 2026-07-23
- Glow: sharper light edge — the pool is fully lit for the inner 80% of the
radius, the gradient falloff lives only in the outer 20%.
## v1.36.3 — 2026-07-23
- Glow: fixed dark wedges appearing INSIDE a lit room near some doorways.
The room outline and the door sectors were subpaths of a single clip path;
with opposite winding directions the nonzero fill rule cancelled their
overlap. Each contour is now its own clipPath child (children always
union), so sectors only ever ADD light.
## v1.36.2 — 2026-07-23
- **Glow radius is now per source**: every device dialog gained a "Glow
radius" field (in your HA units; empty = the global default from general
settings, shown as the placeholder). A kettle's night light can glow half
a meter while the ceiling lamp floods the room. Door sectors use the same
per-source radius.
## v1.36.1 — 2026-07-23
- Fixed tap-toggle "doing nothing" on lamps whose individual `light.*` entity
is **hidden in the registry** (the usual setup when lamps are folded into a
light group): the primary entity fell through to a visible config switch
(do-not-disturb) or an identify button, and the click toggled THAT.
Primary selection now works in tiers — domain priority beats hiddenness,
so a hidden light still wins over a visible config switch, while visible
entities of the same domain keep winning over hidden ones.
## v1.36.0 — 2026-07-23 (wall switches that really switch)
- Markers gained **"Controls light sources"**: bind any set of `light.*` /
`switch.*` entities to an icon. With tap action **Toggle**, a click flips
them all with HA-group semantics — any on → all off, all off → all on — in
one service call. Covers stateless remotes, one-switch-many-lights and
dumb wall switches (place a virtual marker; no HA entity needed).
- The icon mirrors its targets: on when any target is on, tinted by the first
lit RGB light. The info card lists every target with its state. Controls
fire only on the explicit per-marker Toggle (owner's decision); locks and
other domains can never be group-controlled.
## v1.35.0 — 2026-07-23 (glow fill: dark house, glowing lamps)
- New fill mode **"Light sources"**: the whole house is painted with a single
configurable darkness color, and every lit lamp casts a radial pool of light
around itself. The pool color comes from the lamp's `rgb_color`, else its
color temperature (blackbody conversion), else a configurable default;
brightness scales the intensity.
- Pools are clipped by the source's room — **plus the sector through each
doorway** (rays from the source to the door edges, out to the glow radius),
so light spills into neighbouring rooms through doors. Entrance doors (no
room behind) spill nothing; windows don't spill. No shadow casting: islands
and furniture do not block light (deliberate limitation).
- The glow radius is configured in General settings in your HA unit system
(meters or feet; stored in cm, default 3 m). The palette gained a "glow"
group: house darkness + default light color/intensity.
## v1.34.0 — 2026-07-22 (island rooms)
- **Nested rooms are now legal**: draw a contour fully inside an existing room
(or around one) — a column in a ring-shaped room, an inner room, a wardrobe
island. The parent room's fill is rendered with an evenodd hole, so a ring
paints as a ring; the island itself stays clickable and can carry its own
area, fill and devices. Partial overlaps and duplicate outlines are still
rejected (`roomsOverlap` reworked; `polyContainsPoly`/`islandsOf` helpers,
unit-tested). The per-click "point inside a room" rejection is gone —
validation happens once, when the outline closes.
## v1.33.5 — 2026-07-22
- Editor tabs got extended tooltips explaining what each editor is for (plan
geometry vs device icons vs visual decor).
## v1.33.4 — 2026-07-22
- Editing a device no longer makes its icon jump. Changing the HA binding
(which changes the marker id) migrates the saved position to the new id;
changing the room within the same space keeps the icon exactly where it
stands (previously it re-centered in the new room). Only a brand-new icon,
or a move to a room in a different space, is centered.
## v1.33.3 — 2026-07-22
- Device dialog: when no icon is set explicitly, the icon picker no longer
looks empty — it shows the **auto-derived icon** (from the icon rules /
device class) as a placeholder, with an "Auto: mdi:…" hint line underneath.
Picking an explicit icon replaces it as before; clearing returns to auto.
## v1.33.2 — 2026-07-22
- Removed the **Reset** button from the Device editor. It wiped the entire
layout — positions of all devices, room cards and their scales across every
space — behind a single confirm. Low value, high blast radius.
## v1.33.1 — 2026-07-22
- The dot grid is now shown in **every editor** (Plan, Devices, Background),
not just Plan — an instant visual cue that you are editing.
- In the Background editor, rooms, devices, openings and labels fade to 35%
opacity so the decor you are drawing stands out; decor itself stays fully
opaque. Other modes are unaffected.
## v1.33.0 — 2026-07-22 (background editor)
- New third mode: **Background editor**. Draw purely visual decor on the plan —
lines, rectangles, ovals and text labels that never interact with rooms,
devices or fills. Shapes are drag-drawn with grid snap and a live preview;
text is placed via a small dialog (size S/M/L, color; double-click to edit).
Toolbar: Select (move, Delete key), Erase, color, three line widths and an
optional 25% fill for rects/ovals. Esc walks back: draft → selection →
Select tool → View.
- The decor layer renders **under rooms** (a true underlay), is visible in all
modes and completely click-transparent outside the editor. Stored per space
in the server config (`space.decor`, validated on the backend, shared across
clients with the usual rev/optimistic locking).
## v1.32.1 — 2026-07-22
- Opening tool: hovering near a wall now shows a **dashed preview** of where
the opening would land — snapped onto the wall, default door length (90 cm),
with a center dot. No preview far from walls or over an existing opening
(a click there edits it instead).
## v1.32.0 — 2026-07-22 (split polyline, tool cursors, Esc)
- **Split can now cut along a polyline**, not just a straight chord: start on
a wall, click intermediate points inside the room, finish on another wall.
The path is validated (no wall crossings, no self-intersection) and drawn
live with vertices and a preview segment. Two clicks still work as before.
- **Tool cursors**: Merge and delete-room show a pointer; Split shows a
pointer while picking the room, then a crosshair while cutting.
- **Esc walks back out of Merge/Split** step by step: last cut point → first
point → room selection → back to the Draw tool. Merge: selection → tool.
## v1.31.2 — 2026-07-22
- Plan editor: the room picked with the **Merge** tool (and the room selected
for **Split**) is highlighted amber again. The `.outlined` markup style,
added later in the stylesheet, was silently overriding the `.picked`
highlight at equal specificity (source-order gotcha #4 — rule order fixed
and documented in the stylesheet).
## v1.31.1 — 2026-07-22
- Plan editor: interacting with a room card (drag, corner-resize or a plain
click) no longer leaks into the active markup tool — previously the click
after a resize could add an outline point, pick a merge/split room or even
prompt to delete the room under the card.
## v1.31.0 — 2026-07-22 (room cards)
- Room labels grew into **room cards**: the name on top, and an optional
smaller metrics line below — temperature, humidity, average Zigbee signal
and lights, each behind its own checkbox in the space settings (all off by
default). Lights show On/Off, or **"1 of 3"** when only part of the room is
lit. Rooms without an HA area keep showing just the name.
- Cards are **resizable** in the Plan editor: hovering shows corner handles;
dragging one scales the whole card uniformly (0.5×–3×). The scale is stored
in the layout next to the card position, and dragging keeps it.
- Fixed a latent v1.25 regression: room labels were not rendered as draggable
HTML in the Plan editor at all (only a static SVG name), so moving them was
impossible. The Plan editor now renders real cards (name only) that can be
dragged and resized.
- Two markup smokes (merge/split) still called a method removed in v1.25 —
repaired.
## v1.30.4 — 2026-07-22
- **Escape now closes every dialog** (general settings, icon rules, device
editor, space dialog, opening editor, info cards), topmost first when
stacked. Closing the space dialog with Esc abandons a floor-import queue,
same as its Cancel button. Esc while drawing an outline still undoes the
last point (dialogs take priority).
## v1.30.3 — 2026-07-22
- The **General settings** (fill palette) gear in the header is now visible in
every mode for users who can edit, not just in the Plan editor.
## v1.30.2 — 2026-07-22 (editor tabs redesign)
- Mode tabs renamed and reduced to two: **"Plan editor"** and **"Device
editor"**. View is no longer a tab — it is the implicit default state.
- The Device editor now has its own bottom toolbar (add / show all / reset
layout / icon rules moved out of the header), mirroring the Plan toolbar.
- Both toolbars and the active tab itself got an **X** button that closes the
editor and returns to View. Re-clicking the active tab does nothing;
switching Plan↔Devices is direct.
## v1.30.1 — 2026-07-22 (space gear polish)
- The gear icon next to the space name is now visible in **every mode** (not
just Plan) for users who can edit, so space settings are always one click
away. The "+" (add space) tab remains Plan-only.
- Fixed the gear's vertical alignment — it sat noticeably lower than the space
name (baseline-aligned web component); tabs are now flex-centered.
## v1.30.0 — 2026-07-22 (lock action in the opening info card)
- The door/window info card (View mode) now offers an explicit **Unlock/Lock
button** when a lock entity is bound and available. Unlock is styled red as a
security-sensitive action; the button is disabled during locking/unlocking and
hidden when the lock is unavailable.
- The security rule is untouched: **tapping a lock icon on the plan still never
toggles it** — the action lives only behind a deliberate, clearly labeled
button, same interaction contract as HA's more-info dialog.
## v1.29.0 — 2026-07-22 (the "new device" flag)
- **Devices that appear in HA after installation no longer show up silently**:
an auto-placed device (or light group) gets a big red dot at the top-right of
its icon. The flag is stored server-side (`settings.new_device_ids`), so every
client sees it — and it disappears everywhere the first time someone opens
that device's editor.
- The baseline of known devices is seeded silently on the first run after the
update, so **existing devices never flood the plan with dots**. Hand-made
markers (virtual/rebindings) are never flagged — the user just created them.
(Pure `diffNewDevices`, unit-tested; backend schema for the two new settings.)
## v1.28.1 — 2026-07-22 (View-mode polish: cursors and inert openings)
- **Device icons in View no longer show the grab cursor** (drag lives in the
Devices mode); they show a pointer — clicking still works exactly as before.
- **Doors and windows in View are pure drawings**: no grab cursor, no hover
outline, no hit target, no click — regardless of what is bound to them.
- **The lock badge is the one exception**: when a lock is bound, the badge is
shown and clickable in View (pointer cursor, click opens the door/lock info
card). It stays inert in Plan so it does not fight editing.
- In Plan an opening remains fully interactive: grab cursor + hover outline,
dragging along walls, and a click with ANY tool opens its properties dialog.
## v1.28.0 — 2026-07-21 (sub-area rooms: manual placement without an HA area; issue #3)
- **Devices can now be placed into rooms that have no Home Assistant area.**
The marker dialog's room list includes area-less rooms (marked "no area,
manual"); pick one and the device/virtual marker lands at its centre. The
marker stores `room_id`, so the choice survives edits and re-opens. The
laundry-cupboard case from issue #3: a decorative room inside a larger area
can now hold its own door sensor and light.
- Room reference parsing extracted as pure `parseRoomRef` (`space#area` /
`space#@roomId`), unit-tested; backend schema for `room_id`.
## v1.27.0 — 2026-07-21 (RGB light colors + alarm pulse; issue #3)
- **RGB lights show their actual color**: an "on" light with a color tints its
bulb icon, glow and — unless a custom ripple color is set — its presence ripple.
Brightness is deliberately ignored (a dim red bulb still reads red); off,
white-only and unavailable lights look as before. (Pure `lightColorOf`.)
- **Emergencies pulse red**: leak / smoke / gas / CO / safety / tamper / problem
binary sensors and sirens in `on` get a red pulsing ring over any display mode.
`unavailable`/`unknown` never alarm — an outage is not a fire. Honours
`prefers-reduced-motion`. (Pure `isAlarmState`.)
## v1.26.0 — 2026-07-21 (state-reflecting icons + value display; issue #3)
- **Auto icons now reflect live state**, like core HA: door/window/garage sensors
swap open↔closed variants, locks show locked/unlocked, a bulb lights up as
`lightbulb-on`. Conservative by design: only well-known pairs, never when the
user picked a custom icon, and `unavailable`/`unknown` keep the base icon.
Gated by the existing "live states" card option. (Pure `stateIcon`, unit-tested.)
- **New marker display mode "Value instead of an icon"**: the measurement itself
(temperature °, humidity %, or any numeric state with its unit) becomes the
marker body — the small corner badges disappear for such markers. Direct
request from issue #3. Non-numeric entities fall back to the icon.
## v1.25.0 — 2026-07-21 (three interaction modes: View / Plan / Devices)
The approved UX redesign (docs/UX-MODES.md), confirmed by user feedback (#3):
- **View** (default, and the only mode after every load): display and device
interaction only — tap/long-press/tooltips/pan/zoom. Nothing can be dragged or
edited; panning may start on top of an icon and never displaces it. The header
carries only space tabs, the counter, zoom and the mode switcher.
- **Plan**: everything about geometry and appearance — room outline/delete/merge/
split tools, openings (placement, drag along walls, click-to-edit), room-label
dragging, per-space gear dialog, add space, the ⚙ fill palette. Orange stage frame.
- **Devices**: marker work — icon dragging lives ONLY here, a click opens the
editor directly; add device, show-all curation, reset layout, icon rules. Accent frame.
- The mode switcher is a segmented control shown to administrators; the standalone
markup toggle button is gone, "drag anywhere" (v1.9) is consciously reversed,
and the v1.23.1 view-mode opening drag/double-click moved into Plan.
## v1.24.2 — 2026-07-16 (lights fill: a color for rooms with no light sources)
- The "Fill: lights" group in General settings gained a third color — **"No light
sources"**. Its default opacity is 0, so rooms without any lights stay unfilled
exactly as before; give it an opacity and such rooms get their own tint,
distinguishable from "all lights off".
## v1.24.1 — 2026-07-16 (space tab: gear instead of pencil)
- The small icon next to a space name in the tabs is now a gear (was a pencil) —
the dialog it opens is space *settings* (plan, display, scale), not just renaming.
## v1.24.0 — 2026-07-16 (general settings: fill palette; per-space LQI toggle)
- **New "General settings" dialog** (⚙ in the header): the fill colors used by every
space, grouped by mode — lights (on / all off), temperature (cold / comfortable /
hot) and zigbee signal (weak / strong endpoints of the gradient). Every color has
its **own opacity slider**; the zigbee fill interpolates between the two configured
endpoint colors. Stored server-side in `settings.fill_colors` (defaults are not
persisted); the static `houseplan-space-card` uses the same palette.
- **Per-space "Show zigbee signal (LQI)" toggle** in the space dialog: hides or shows
the LQI badges next to zigbee devices (and the signal line in room tooltips) for
that space; when never touched, the card-level `show_signal` option applies as before.
- Fill opacity is now governed by the per-color setting; the space "Opacity" slider
keeps controlling borders and names.
- New pure helpers `fillColorsOf` / `lerpColor` / `roomFillStyle` (+4 tests: 77 → 81);
backend schema for `fill_colors` and `show_lqi`; smoke `smoke_general_settings`.
## v1.23.2 — 2026-07-16 (manual upload limit raised to 50 MB)
- The per-file limit for attached manuals (PDF etc.) is now **50 MB** (was 25).
The limit is still enforced while the multipart body streams in, so an oversized
upload is cut off early rather than buffered whole; the error toast reads the
actual limit from the server response.
## v1.23.1 — 2026-07-17 (openings: hover, drag along walls, double-click properties)
- **Hover affordance**: an accent outline hugs the opening's wall strip on hover, with a
grab cursor — placed openings now look grabbable.
- **Drag along walls** (view mode): an opening re-snaps continuously to the nearest derived
wall while dragged; too far from any wall → it stays put. `snapToWall` now normalizes the
angle to [-90, 90) — two rooms share a wall with OPPOSITE edge directions, and without this
a drag across segment boundaries flipped the hinge side back and forth. Saved on release.
- **Click / double click**: a single click still opens the status card (now via a 250 ms
timer); a **double click opens the properties dialog** right from view mode. The markup
"Opening" tool behaves as before. Hit zone made slightly thicker and is now a strip along
the wall (previously it covered the whole swing square, causing accidental hovers).
- (+1 test: 76 → 77.)
## v1.23.0 — 2026-07-17 (doors & windows with live open/lock state)
Visual language after easy-floorplan (MIT); the placement model is ours.
- **New markup tool "Opening"**: click next to a wall → the opening snaps onto the nearest
DERIVED room wall (walls have no independent existence — v1.19.0) and takes its angle, then a
dialog asks for type (door/window), **length in real cm** (defaults: door 90, window 120 —
the per-space scale makes this honest), an open/close sensor (door/window-class
`binary_sensor`/`cover`, invertible) and, for doors, a **lock entity**. The opening keeps
absolute coordinates, so editing/merging/deleting rooms never breaks it. Click an existing
opening with the tool to edit or delete it.
- **Live rendering**: a door is a leaf hinged at the jamb with a quarter-circle swing arc that
"draws on" (stroke-dashoffset) as it opens; a window is two casement leaves meeting in the
middle. Открыто → the moving parts take the accent colour and animate (CSS transitions,
`prefers-reduced-motion` honoured). No sensor → the classic static plan: doors drawn open,
windows closed. `unavailable`/`unknown` freeze that default — an outage must not fake motion
(pure `openingAmount`, unit-tested). Hinge side / swing side via flip toggles.
- **Locks**: a padlock badge beside the door — green closed when `locked`, orange open when
unlocked, grey question when unknown. The lock is NEVER toggled from the plan (the card's
standing security rule); clicking the opening or the badge shows an **info card** with both
states.
- New pure helpers `snapToWall` (projection + wall angle over derived edges) and
`openingAmount`; `space.openings[]` validated server-side. (+2 tests: 74 → 76.)
## v1.22.0 — 2026-07-17 (presence ripples, per-device icon size/rotation, one-click install)
Ideas borrowed from [easy-floorplan](https://github.com/nicosandller/easy-floorplan) — the visuals,
not the model.
- **Presence ripples**: `display: badge | ripple | icon_ripple` per marker, with `ripple_color`
and `ripple_size`. Active → pulsing rings; idle → a faint dot. Gated by the pure
`isActiveState` — independent of the card-wide live_states toggle, and `unavailable`/`unknown`
count as idle so an outage never leaves a ring pulsing. Honours `prefers-reduced-motion`.
- **Per-device icon `size` (×0.5–3) and `angle`** — sizing now hangs off `--dev-size`, so value
badges scale with the device.
- **One-click install badge** (My Home Assistant → HACS) in both READMEs.
- (+1 test.)
## v1.21.3 — 2026-07-16 (room labels: no text shadow)
- Room name labels no longer carry a text shadow — crisper look on both the white
hand-drawn canvas and plan images.
## v1.21.2 — 2026-07-16 (space-dialog polish)
- The **"Scale (cm per cell)" input is compact** again — a generic `width:100%`
dialog rule was stretching it across the row.
- **A space with no background image now gets a white "paper" canvas** instead of
the dark stage, so hand-drawn rooms read like a floor plan on paper.
## v1.21.1 — 2026-07-16 (audit: split snaps to the wall, docs for merge/split)
- **Fix (found in audit):** Split required each click to land on a grid node, so it
silently refused rooms whose walls are not grid-aligned (imported or older polygons)
— the "pick a wall" toast fired no matter where you clicked. The click now snaps to
the room's nearest wall (`closestPointOnBoundary`) instead of the grid, with the pull
capped at ~6 grid cells so an accidental click in the middle of a room stays a miss
rather than becoming a wall point the user never meant; `splitRoom()` still rejects
a cut that is not a clean wall-to-wall chord. Also makes aiming easier on the fine
(240-cell) grid.
- **Docs:** README (en + ru) now documents room Merge, Split, the drawing ruler and the
per-space scale — these v1.18–v1.21 features were shipped without user-facing docs.
`docs/TESTING.md` gained merge/split rows and a fresh self-run record.
- New smokes `demo/smoke_merge_split.mjs` and `smoke_split_nonsnap.mjs`;
`closestPointOnBoundary` unit-tested. (+1 test: 72 → 73.)
## v1.21.0 — 2026-07-16 (merge and split rooms)
- **Merge** (toolbar "Merge"): click a room, then a neighbour. Only rooms that **share a wall**
can merge — and that is decided by the result rather than a heuristic: `mergeRooms` unions the
outlines and accepts the pair only when they collapse into ONE hole-free outline. A corner
touch, rooms apart, or a union enclosing a hole are refused. A dialog picks which name and
area survive; the kept room keeps its id, so its label position and devices stay put. The
dialog warns that the other area is released.
- **Split** (toolbar "Split"): click the room, then two points on its walls — the chord cuts it
in two, with the live ruler on the cut. **The bigger part stays the room it was** (name, area,
devices); the smaller becomes a new room and its dialog asks for name/area. Cancelling the
dialog leaves the room whole — the cut is applied only on confirm. Cuts that do not run
wall-to-wall inside the room (ends off the wall, chord leaving a concave room, a chord along a
wall) are refused.
- Boolean geometry via **polyclip-ts** (proper ESM + native types; `polygon-clipping` ships named
types but a default-only ESM build, which breaks either tsc or the runtime). Verified against
the real plan, where neighbouring walls overlap collinearly instead of matching exactly —
the case a hand-rolled union gets wrong. Bundle: 151 KB → 202 KB.
- New pure helpers: `polygonArea`, `mergeRooms`, `splitRoom`. (+5 tests: 67 → 72.)
## v1.20.0 — 2026-07-16 (rooms may not overlap)
- **A click strictly inside an existing room is refused** while drawing (toast names the room).
Being *on* a wall stays legal — neighbouring rooms share walls, and real walls overlap
collinearly rather than match exactly, so new vertices land on existing outlines mid-span
all the time. `pointStrictlyInside` excludes the boundary explicitly (ray casting alone is
unreliable exactly on an edge).
- **Closing an outline that overlaps an existing room is refused** — vertex checks alone are not
enough: an outline drawn *around* a room has every vertex outside it. The outline stays open
so it can be corrected. Nesting one room inside another counts as an overlap.
- New pure geometry in logic.ts: `roomPoly`, `pointOnBoundary`, `pointStrictlyInside`,
`segmentsProperlyCross` (touching/collinear is deliberately not a crossing), `roomsOverlap`
(edge crossings + containment probe, which also catches duplicate outlines). (+4 tests: 63 → 67.)
## v1.19.0 — 2026-07-16 (a line is never a thing of its own)
**Model change.** A wall can only exist as an edge of a closed room. Consequences:
- **Walls are derived from room outlines** (`roomEdges` in logic.ts), not stored. A wall
shared by two rooms is emitted once, so **deleting a room keeps the borders its neighbours
still contribute** — and drops the walls nobody else uses. This falls out of the model
instead of needing bookkeeping.
- **An abandoned outline leaves nothing behind.** Previously every click pair was written to
`space.segments` immediately, so a contour you never closed left orphan lines on the plan.
Now nothing is persisted until the room is saved.
- **The "Erase line" tool is gone** — there is no standalone line to erase. Mis-clicks are
undone with Esc / Ctrl+Z as before.
- **`space.segments` is dropped on every save** (legacy configs shed it on first write).
Validation still tolerates the field so a stale browser tab cannot fail a save; diagnostics
no longer reports it. Lines that belonged to no room disappear on upgrade — by design.
- Dead code removed: `_addSegment`, `_removeSegmentByKey`, `_distToSeg`, `_pathSegs`, `_segKey`.
`segKey(a, b, prec)` gained a precision argument (normalized coords need more than render
units). (+2 tests: 61 → 63.)
## v1.18.1 — 2026-07-16 (fix: the drawing ruler was invisible)
- Fix on top of v1.18.0: the length badge never showed up while drawing. It was rendered
inside `.devlayer`, and `.stage.markup .devlayer { display: none }` hides that whole layer
in markup mode (so icons do not get in the way) — the badge was in the DOM but invisible.
It now lives in its own `.measurelayer` (absolute, `pointer-events: none`), which markup
mode does not hide. Verified visually on a real drawn segment ("3.60 m" on screen).
- Testing lesson (see docs/TESTING.md): asserting on `textContent` is not enough — a DOM
query passes on elements hidden by an ancestor. Check `offsetParent`/rect or look at a
screenshot.
## v1.18.0 — 2026-07-14 (live measurements while drawing rooms + per-space scale)
- **Ruler while drawing.** In room-markup "draw" mode, a badge follows the cursor showing the
length of the current segment (last placed vertex → cursor). Units come from the HA unit
system: metric → metres ("1.25 m"), imperial → feet+inches ("4′ 1″").
- **Per-space scale.** New "Scale (grid cell size)" field in the space dialog — cm represented
by one grid cell (default **5 cm**, i.e. 240 cells ≈ 12 m). Stored as `space.cell_cm`; each
plan can have its own real-world size.
- Pure helpers `segmentCm` / `formatLength` in logic.ts (unit-tested); `_fmtLen` +
`_renderMeasureLabel` in the card; `.measurelabel` style; i18n `space.scale_label`/`scale_unit`.
(+3 tests: 58 → 61 frontend.)
## v1.17.2 — 2026-07-11 (humidity badge: gate on the sensor, not the icon)
- Fix on top of v1.17.1: the humidity `%` badge is now shown whenever the marker's primary
entity is a humidity sensor (`device_class: humidity`), regardless of the resolved icon.
Previously it required the `mdi:water-percent` icon, so a humidity sensor whose name matched
another icon rule (e.g. a "Myheat Влажность …" sensor → boiler icon) showed no value.
Verified live (45.2 → 45%). (+1 test: 57 → 58.)
## v1.17.1 — 2026-07-11 (humidity value next to the icon, like temperature)
- **Humidity sensors now show their value (%) next to the icon**, mirroring the temperature
badge. Any marker resolved to the humidity icon (`mdi:water-percent`) — a humidity device or a
humidity entity placed on its own (v1.17.0) — gets an integer `%` badge and the value in its
tooltip. New `isHumEntity`/`humFor` (diagnostic entities excluded), `DevItem.hum`, `.hval`
badge, gated by the same "sensor values" option as temperature. (+2 tests: 55 → 57.)
## v1.17.0 — 2026-07-11 (place individual entities, not just whole devices — issue #1)
- **You can now put a single entity on the plan as its own icon** — e.g. a climate sensor
exposes temperature AND humidity; add the device (shows temperature) and separately add the
humidity entity as a second icon. In the "add device" dialog, start typing in the binding
search and individual entities now appear alongside devices/helpers (surfaced only while
searching, so the default list stays clean); the sub-label shows the domain and parent device.
- Entity markers now get a **sensible auto icon** (name rules → `device_class` → chip) instead of
the generic shape, and a **temperature value** when the entity is a thermometer/air-monitor.
The `entity:<eid>` binding already existed (used by helpers/light groups); this exposes it for
any entity. (+1 test: 54 → 55.)
## v1.16.2 — 2026-07-11 (docs+log: correct card resource URL — fixes "Custom element doesn't exist")
- **Support issue #2**: users adding a Lovelace resource that points at the on-disk path
`/custom_components/houseplan/frontend/houseplan-card.js` get a `text/plain` MIME error and
"Custom element doesn't exist: houseplan-card" — HA does not serve `custom_components/` over
HTTP. The integration serves the card at **`/houseplan_files/houseplan-card.js`** (verified:
`200 text/javascript`) and auto-registers it as a Lovelace resource in storage mode.
- README (en+ru) now documents the correct URL and the common mistake, incl. a YAML-mode
`resources:` snippet.
- On setup the integration logs (INFO) the exact served URL and, when Lovelace resources are
YAML-managed, how to add it manually — so the fix is discoverable from the logs.
## v1.16.1 — 2026-07-08 (space-card shows room fills as configured)
- **The static `houseplan-space-card` now renders room fills exactly as configured on the
full card** (temperature / signal / lights coloring), as a snapshot of the states passed
in via `hass` — reverting the v1.16.0 omission. The card still does not subscribe to state
changes itself and stays non-interactive (`pointer-events:none`); fills refresh when HA
hands the card an updated `hass`. Added shared `areaLqi()` in devices.ts.
## v1.16.0 — 2026-07-08 (new: houseplan-space-card + deep-link)
- **New second card `custom:houseplan-space-card`** — a READ-ONLY, static schematic of a
single space, embeddable on any dashboard. Draws the configured plan + room borders/names +
device markers at their saved positions, with **no interactivity** (the schematic layer is
`pointer-events:none` — no clicks/hover/tooltips/drag/more-info) and **no live states**
(no state subscription, no status/temperature fills). A footer button opens the space in the
full component via a **deep-link** (`#space=<id>`). Config: `space` (required), `title`,
`show_button`, `button_label`, `button_target`, `aspect_ratio`, `icon_size`; unknown space → a
tidy error card. GUI editor with a space dropdown from the integration config.
- **Deep-link in the full card**: on load it reads `#space=<id>` (valid id wins over
`default_floor`) and listens to `hashchange`, without blocking manual space switching.
- **Shared rendering**: pure geometry in `space-geometry.ts` (unit-tested), the static drawer in
`space-render.ts`, and a **module-level config cache** (`config-store.ts`, rev-keyed, seeded from
the full card's localStorage snapshot, invalidated on `houseplan_config_updated`) so N embedded
cards on one board share a single WS request. Both cards ship in the one bundle.
- Tests: 48 → 54 frontend (space geometry) + demo smokes `smoke_space_card` and `smoke_deeplink`.
- Note: status/temperature fills are intentionally omitted from the static card (they are live);
it shows configured room borders/names + neutral icons. Marker/position edits reflect after the
config event or a reload.
## v1.15.6 — 2026-07-08 (room hover also reveals the border)
- Hovering a room now **shows its outline** even when borders are turned off. The stroke
colour (`--room-stroke`) is now always set to the room colour and only hidden via
`--room-stroke-op` — so the existing `stroke-opacity: 1` on hover reveals a crisp border
(previously the stroke was `transparent` when borders were off, so hover showed nothing).
## v1.15.5 — 2026-07-08 (fix: room hover was always grey even when filled)
- Room hover now **darkens the current fill** for filled rooms and only greys **unfilled**
ones (as intended since v1.15.1). The legacy `.room.overlay:hover` / `.room.yard:hover`
grey rules were still matching styled rooms and, being applied to `fill` directly, beat
the `--room-fill` variable — so a temperature/light/zigbee-filled room turned grey on
hover. Scoped those legacy rules with `:not(.styled)`; styled rooms are now governed only
by `.styled.filled:hover` (brightness 0.78) and `.styled:not(.filled):hover` (grey).
## v1.15.4 — 2026-07-08 (fix: device icon vertical centering — proper root cause)
- **Root cause of the off-centre icon, found in the live app** (the demo stub hid it):
HA's real `<ha-icon>` host is `display:block` with a large inherited `line-height`
(~22 px for a ~12 px glyph), so the SVG sat ~1.8 px **below** the badge centre.
Fix: `.dev ha-icon` is now a zero-line-height flex box — the glyph centres exactly.
- Reverted the v1.15.3 `box-sizing: border-box` (it shrank the badge by 2 px and made the
vertical offset *more* visible — "worse"). The 1 px anchor drift is instead corrected by
the centering margin (`-(size/2 + 1px)`), keeping the original badge size.
- Verified in the real dashboard: anchor offset 0, glyph offset 0, badge size unchanged.
- Demo `ha-icon` stub made faithful to HA (block + line-height) so `smoke_icon_center`
now actually reproduces and guards this; the smoke also asserts glyph-in-badge centering.
## v1.15.3 — 2026-07-08 (fix: device icon 1px off its anchor point)
- Device icon badges were sitting **1 px down-and-right of their true point**: `.dev`
used the default `content-box`, so the 1 px border made the rendered square 2 px
wider than the width the centering margin assumed. Added `box-sizing: border-box` —
the badge centre now lands exactly on the device coordinate (verified in the demo:
anchor offset 1 px → 0). The glyph itself was already centred within the square.
## v1.15.2 — 2026-07-08 (fix: room average temperature counted non-thermometers)
- **Average room temperature now uses only devices the card treats as thermometers**
(`mdi:thermometer` / `mdi:air-filter`). Previously `areaTemp` swept every device in the
area through `tempFor`, so fridges, TRV heads and smart plugs leaked their readings into
the average (e.g. a 8.3 °C valve/appliance temperature dragging a room down).
- **`isTempEntity` now excludes chip/diagnostic temperatures**: `*_device_temperature`
sensors and any entity in the `config`/`diagnostic` category are no longer treated as a
room temperature — so even a genuine thermometer no longer reports its chip temperature.
- Affects the temperature room fill and the room tooltip average. (+3 frontend tests: 46 → 48.)
## v1.15.1 — 2026-07-06 (display-settings UX round from live usage)
- **Comfort-bounds input hardening**: clearing a temperature field can no longer
collapse a bound to 0 (`Number('') === 0` — this silently turned "comfort from
25" into a 0–25 range after the auto-swap, showing green at 24°). Inputs now
parse with `parseFloat` + `isFinite` guard and the save path falls back to the
defaults for non-finite values.
- Room tooltip now shows the **average room temperature** (what the temperature
fill actually uses — averages every thermometer in the area, including TRVs).
- **Hover no longer recolors rooms blue**: filled rooms darken their current fill
(`brightness(0.78)`), unfilled rooms get a light grey tint.
- Fill mode selector is a **radio group** with short labels (no color legend);
the comfort bounds sit compactly inline on the temperature row (56 px inputs).
- The space dialog is wider (500 px) — the settings no longer feel cramped.
## v1.15.0 — 2026-07-06 (temperature room fill)
- New room fill mode **"Temperature"**: light blue below the comfort range, green
inside it, warm yellow above. The comfort bounds (default 20–25 °C) are editable
right in the space dialog and appear only when the mode is selected; bounds
entered in the wrong order are swapped automatically. A room's temperature is
the average of its devices' temperature readings; rooms without a reading stay
unfilled. (+`areaTemp` helper, 3 new frontend tests, backend schema fields.)
## v1.14.0 — 2026-07-06 (per-space display settings, hand-drawn spaces, testing checklist)
- **Per-space "Display" settings** (space dialog): always-visible room borders,
room name labels, a border/name color picker with an opacity slider, and a room
fill mode — none / by zigbee signal (red→green) / by lights (yellow = something
is on, grey = all lights off; rooms without lights stay unfilled).
- **Room name labels are draggable** like device icons; positions persist server-side
(layout keys `rl_<roomId>`), defaults to the room centre; hidden in markup mode.
- **Hand-drawn spaces**: the space dialog got a "No image — I'll outline rooms by
hand" option with a canvas orientation choice (landscape/portrait/square). Such
spaces default to visible borders and names; switching an existing space to this
mode detaches its background image. The plan image is no longer mandatory.
- Backend: explicit validation schema for the new per-space settings (+test).
- **`demo/` harness moved into the repository** (synthetic home, host page, capture
and smoke scripts, icon-map generator) — public materials and smoke tests no
longer depend on a perishable sandbox.
- **`docs/TESTING.md`**: a comprehensive manual-testing checklist (environments
matrix, every feature, edge cases); policy — updated in the same commit as any
functional change. The first self-run found and fixed two bugs:
`plan_url` not detached on image→draw switch, and a `_stateClass` crash on
state objects without `entity_id`.
- Tests: 43 frontend + 11 pure backend; new smokes `smoke_space_settings` and
`smoke_edge_cases` (empty install, XSS names, legacy layout entries, 150-device perf).
## v1.13.3 — 2026-07-06 (privacy: drop legacy real-house plan sources)
- Removed the legacy `assets/` directory (real floor-plan sources from the pre-v1.3
bundled-data era). Nothing in the build referenced it; instance data lives in
server-side config. Note: the files remain in old git history and release archives.
## v1.13.2 — 2026-07-05 (audit round 3: fixes + buildDevices test suite)
- **buildDevices finally has a direct unit-test suite** (12 tests on a fake hass):
area filtering, curation incl. show-all, duplicate numbering, light-group folding
and its `group_lights=false` inverse, marker claim/metadata/hidden/virtual/entity
paths, custom icon rules + the deliberate lock override, device_class fallback,
primary-entity priority, LQI/temperature extraction. Frontend tests: 28 → 41.
- Fix: `t()` now substitutes **every** occurrence of a placeholder (extracted as the
pure `subst()` helper with a regression test).
- Fix: `_saveConfigNow` refreshes the local config on a rev conflict before
rethrowing — a retry no longer hits the same conflict (the debounced path already
did this; the immediate path did not).
- Fix: `pointercancel` on a device icon clears the long-press timer — no phantom
info card after an aborted touch gesture.
- Repairs check moved to `repairs.py` and now **re-runs after every config save**,
so a missing/restored plan file is reflected in the Repairs UI without a restart.
- Documented the deliberate `mdi:lock` override in `devices.ts` (wins over custom
rules — a mislabeled lock icon is safety-relevant confusion).
- Test infra: `tsconfig.test.json` also compiles `devices.ts`/`types.ts`;
`scripts/fix-test-build.mjs` appends `.js` to tsc's extensionless ESM imports.
## v1.13.1 — 2026-07-05 (distribution materials)
- **Demo GIF** in the README — recorded on a fully synthetic demo home (no real
floor plans): live states, tap-to-toggle, drag, zoom, info card, space tabs.
- GitHub issue templates (bug report with diagnostics hint, feature request),
`CONTRIBUTING.md` (5-minute setup, ground rules, architecture pointers),
Discussions enabled.
## v1.13.0 — 2026-07-05 (universality: floors import, icon rules, tap actions)
- **Floors import wizard**: on first run, if the HA registry has floors, the card
offers to create a space per floor — names prefilled, the (mandatory) plan image is
requested step by step, any floor can be skipped; after the last one the room-markup
mode opens automatically. No floors / old HA → the old single-dialog onboarding.
- **Editable icon rules**: the built-in "name pattern → MDI icon" rules are now data
(`settings.icon_rules`) with an in-card editor (⬡ in the header): reorder, delete,
add, live test field, invalid-regex highlighting, one-click reset to the bilingual
(EN/RU) defaults. Fallback chain: rules → entity `device_class` → generic chip.
Invalid user regexes are skipped safely.
- **Tap actions**: `tap_action` card option (`info` default | `more-info` | `toggle`)
plus a per-device override in the device dialog. Safety model: a card-wide toggle
only affects lights/switches/fans/humidifiers; covers/valves need a conscious
per-device toggle; locks and alarm panels never toggle from the plan. A long press
(600 ms) always opens the info card.
- **i18n dictionaries moved to JSON** (`src/i18n/{en,ru}.json`) — new languages can be
contributed without touching TypeScript; tests enforce key and placeholder parity.
- Light-theme pass: hardcoded dark badge backgrounds replaced with theme variables.
- Tests: 28 frontend (was 15) — tap-action resolver incl. security cases, icon-rule
compilation/overrides/device-class fallback, floors sorting, i18n parity.
## v1.12.0 — 2026-07-05 (Quality Scale: Bronze + selected Silver/Gold)
Backend brought to Integration Quality Scale patterns (custom integrations are not
formally graded; progress is tracked in `custom_components/houseplan/quality_scale.yaml`):
- **`entry.runtime_data`** (typed `HouseplanData`: both stores + the write lock) replaces
`hass.data[DOMAIN]` for entry data; WS handlers resolve it per call and answer
`not_ready` while no entry is loaded. New `store.py` common module.
- **test-before-setup**: storage readability is verified in `async_setup_entry`
(`ConfigEntryNotReady` on failure). **Unloading** is supported; WS commands and
static paths are global by design (documented).
- **`single_config_entry: true`** in the manifest replaces the manual flow check.
- **Store versioning**: stores now carry `minor_version` and a migration hook
(`HouseplanStore._async_migrate_func`) — schema changes get a single upgrade path.
- **Diagnostics** (`diagnostics.py`): redacted dump (options, rev, per-space stats,
markers with personal fields redacted, layout size).
- **Repairs**: a missing plan file raises a repair issue (`broken_plan`) with
en/ru translations; issues clear automatically when resolved.
- **System health** (`system_health.py`): rev, spaces/rooms/markers/layout counters.
- **Uninstall cleanup**: `async_remove_entry` deletes our Lovelace resource entry.
- **Tests**: config flow, WS API (layout ops, rev conflict, not_ready, plan upload
validation), HTTP upload (ok/bad ext/traversal) on `pytest-homeassistant-custom-component`
— run in CI on Python 3.13; pure validation tests still run anywhere.
- `strings.json` added; translations updated.
## v1.11.2 — 2026-07-05 (device dialog: usable Description height)
- The Description textarea in the device edit dialog was squeezed to ~2 lines by
the dialog body's flex column. Now `min-height: 92px`, `flex-shrink: 0`, `rows=4`;
still resizable vertically.
## v1.11.1 — 2026-07-05 (brand images shipped inside the integration)
- Brand icon and logo (256/512, transparent background) now live in
`custom_components/houseplan/brand/` — the native mechanism for custom
integrations since Home Assistant 2026.3 (served via `/api/brands/...`,
local images take priority over the brands CDN). The former root `brand/`
directory is gone, and no home-assistant/brands PR is needed (theirs bot
closes such PRs as obsolete).
## v1.11.0 — 2026-07-05 (full English translation + UI localization)
- **UI localization (en/ru)**: every card string moved to `src/i18n.ts` dictionaries.
The language follows the HA user profile (`hass.locale`) automatically; a new
`language: en|ru` card option forces it. The GUI editor got the option and its own
localized labels. Generated device names (light group, unnamed, virtual device)
are localized via a `loc` callback in `BuildCtx`.
- **English-only codebase**: all comments, docstrings, section banners, JSDoc, test
names, backend WS/HTTP error messages and log lines translated to English.
Russian remains only where it is functional or content: the `ru` i18n dictionary,
`translations/ru.json` (config flow), `iconFor` regex patterns matching Russian
device names (with their test fixtures), and the Russian documentation copy.
- **Docs**: README is now English-first with a full Russian copy in `README.ru.md`;
`docs/ARCHITECTURE.md`, `DEVELOPMENT.md`, `ROADMAP.md` and the entire CHANGELOG
history translated to English. `translations/en.json` had Russian strings — fixed.
- Removed obsolete `RELEASE_NOTES_v1.9.3.md` and `scripts_publish.sh` (publication
is done; the repo lives at github.com/Matysh/houseplan-card).
## v1.10.0 — 2026-07-05 (audit: write races, XSS, modularity)
**Backend**
- **Write race eliminated**: all load→modify→save cycles (`layout/set|update|delete`,
`config/set`) are serialized by a shared `asyncio.Lock` — concurrent WS calls no longer lose
changes, and the `expected_rev` check became atomic.
- New WS command `houseplan/layout/delete` — cleans up a position when a marker is deleted
(previously orphans accumulated in `.storage/houseplan.layout`).
- Removed the dead WS command `houseplan/file/set` — files are uploaded over HTTP only
(`/api/houseplan/upload`), as the card already did.
- HTTP upload: the 25 MB limit is enforced WHILE reading the multipart stream (abort at the limit),
not after reading the whole file into memory; file `stat()` moved into the executor.
- `request.app[KEY_HASS]` instead of the deprecated `request.app["hass"]` (with a fallback for older HA).
**Frontend**
- **The god-component was broken up**: `houseplan-card.ts` 3023 → ~1990 lines.
Styles → `styles.ts`; types → `types.ts`; building devices from the HA registries
(curation, light groups, markers, LQI/temperature) → `devices.ts` (pure functions).
- **Last-writer-wins in the layout eliminated**: `_saveMarker` writes the position pointwise
(`layout/update`), not the whole layout (`layout/set`) — it no longer wipes positions from other
windows (the regression class of the v1.4.4 incident). Rebinding/deletion clean up the old position.
- **XSS closed**: marker `link` and `pdfs[].url` go through `safeUrl()` (only http(s)
and relative paths; `javascript:`/`data:` are rejected). +12 tests.
- File uploads via `hass.fetchWithAuth` (auto-refresh of an expired token),
fallback to the raw token.
- **The dacha hardcode removed from the GUI editor**: the "default space" list is built
from the server config (WS `config/get`), not the baked-in f1/f2/yard.
- `rules.ts`: removed the dead export `GROUP_TITLES`; the `iconFor` regexes are precompiled.
- A single `_errText()` in all error handlers (never "[object Object]").
## v1.9.3 — 2026-07-05 (audit + refactoring + tests)
- The pure functions `fitView` (viewport contain rectangle) and `declump` (icon push-apart)
were extracted from the card into `logic.ts` — covered by unit tests (was 9 → now 14 tests).
- `_lqiFor` and `_roomLqi` now use the shared `averageLqi` (duplicate averaging removed).
- Removed dead code left after dropping the separate edit mode: the `_edit` field (written but
never read), the methods `_renderEditbar` and `_applyXY` (never called).
- No behavior change — only structure and test coverage. Verified: tsc, build,
14 frontend tests + 10 backend tests green, headless smoke test (render/zoom/devices/declump) clean.
## v1.9.2 — 2026-07-05 (grid twice as fine)
- The markup/snapping grid step was halved: `GRID_N` 120 → 240. Device positions and room
outlines are NOT recomputed and do not shift: coordinates are stored as normalized fractions and snap
to grid nodes, and the old nodes (multiples of 1/120) are an exact subset of the new ones (multiples of 1/240).
Verified on the live layout (65 positions — all already on the new grid). Dragging now
allows more precise positioning.
## v1.9.1 — 2026-07-05 (Zigbee signal: reading from the attribute + ZHA)
- The signal level is now taken not only from the dedicated `*_linkquality` sensor (Z2M), but also:
from `*_lqi` sensors (ZHA), and from the `linkquality`/`lqi` ATTRIBUTE on any entity of the device.
This covers devices whose dedicated signal sensor is disabled but the value is available in an attribute.
- NOTE: if a device's `*_linkquality` sensor is disabled (disabled_by=integration) AND the value
is not published anywhere as an attribute — the signal cannot be shown; enable the sensor in the HA registry.
## v1.9.0 — 2026-07-05 (UX: always-on dragging, declumping, show all, responsive)
- **Icon dragging is available at all times** — the separate "edit mode" is gone (the ✥ button removed).
Clicking an icon opens the device card (metadata editing — via the "Edit" button
inside it), dragging works at any moment. `_pointerDown` no longer requires `_edit`.
- **Icons no longer clump together** (`_declump`): after the automatic grid layout a push-apart pass
runs within the room so that icons do not overlap each other.
- **Positions are pinned when a room is saved**: the auto-positions of the area's devices are written
into the layout once, so icons do not get reshuffled when the order in the HA registry changes.
- **Duplicates are numbered instead of hidden**: devices with the same "name|area" get a suffix
(" 2", " 3"…) instead of silently disappearing.
- **The 👁 "Show all devices" button** in the header: temporarily disables curation and shows
all devices of the area (bridges, service records, duplicates) — in case a needed one is hidden. The `show_all`
flag in the config settings.
- **Markup continuation hint**: after saving a room a toast shows the room counter and
suggests outlining the next one or leaving markup mode.
- **Responsive header**: the toolbar wraps and shrinks on narrow screens (media query ≤620px).
## v1.8.4 — 2026-07-04 (onboarding: required fields + user guidance)
- Initial setup brought to the target scenario: install → space dialog →
rooms → auto device icons.
- **The background is mandatory** when creating a space: "Save" stays disabled without an uploaded
plan (previously only the name was required — an empty space could be created).
- **The room area is mandatory**, with an explicit escape hatch: the main "Save" requires binding to an HA area;
for decorative rooms without devices (hall, sauna) there is a separate "No area" button
(only a name is needed). Previously saving was possible with "name OR area".
- **User guidance:** with an empty server config the space dialog opens
automatically; after the first space is added the card enters room markup mode
by itself with an "outline the rooms" hint.
- When a room with an area is saved, the icons of that area's devices are auto-placed inside the outline
(the curated set: without bridges/service records/duplicates/individual lamps from groups), and a toast shows
how many devices were added.
## v1.8.3 — 2026-07-04 (instant start: config cache, data in the background)
- Icons and the plan appeared with a delay — the card waited for the server response (WS `config/get`
+ connection warm-up retries), and until then the space model was empty.
- A "stale-while-revalidate" cache was introduced: a snapshot of the server config + rev + layout
is saved in `localStorage['houseplan_card_cfg_v1']`. In `setConfig` (synchronously, before
hass arrives) it is restored — the plan and icons are drawn immediately from the cache (verified: setConfig
populates the model in 0.2 ms without the server), while fresh data loads in the background and updates the
card. Live states/temperatures/signal come in as hass warms up.
- The cache is refreshed after every successful load, live resync and icon position save,
so on the next open the current snapshot is visible. When the server is unavailable the
last known plan is shown instead of the empty onboarding.
## v1.8.2 — 2026-07-04 (vector-crisp zoom via viewBox + full-width stage)
- Zoom moved from CSS `transform: scale()` to manipulating the SVG `viewBox`. Previously the layer
was rasterized and then scaled — everything "blurred" when zooming in. Now the browser
redraws the vector at the target resolution: icons, room labels, lines, hatching and the
vector background remain crisp at any scale (verified at 274%).
- The icon layer (HTML) is positioned and scaled by the same `view`, not a global
transform — the icons are crisp and grow together with the plan.
- The stage now occupies the full viewport width (a "contain" model by the stage aspect): when
zoomed in the content fills the width without black bars on the sides. Fully zoomed out the
whole plan is visible (both width and height), the margins around it are filled with the card
background, not black.
- All coordinate math (markup click `_svgPoint`, icon drag, pan, pinch) was rewritten
in unified `view` coordinates — editing is correct at any zoom. A ResizeObserver on the stage
recomputes the `view` when dimensions change (sidebar, rotation). Pan/zoom are constrained to `fit`.
## v1.8.1 — 2026-07-04 (fit-to-viewport + per-space zoom persistence)
- Zooming out to the full view: the lower zoom limit = 100% coincides with the fully
visible plan. `.stage` is now constrained by the viewport height
(`width:min(100%, calc((100dvh − 118px) × aspect))`, `max-width:100%`, centering),
so on reset/zoom-out the whole layout is visible both in width and height without clipping.
- The zoom level is remembered per space and locally per device
(`localStorage['houseplan_card_zoom_v1']`, `_zoomBySpace`): switching floors
restores each floor's zoom; on return the pan is centered and constrained.
- Verified on live HA: f1→196%, f2→140%, the yard untouched — after switching around the
values are restored; reset gives 100% and the full plan (874≤992 px in height).
## v1.8.0 — 2026-07-04 (plan zooming)
- Zoom and pan of the layout: mouse wheel (towards the cursor), two-finger pinch on a touch screen,
one-finger background drag when zoomed in, −/reset/+ buttons in the toolbar, a zoom badge.
- Implemented via a CSS transform on `.zoomwrap` (translate+scale); the coordinate math
(markup clicks, icon drag, tooltips) was recomputed with zoom/pan in mind — editing
works when zoomed in too. Pan is constrained so the content always covers the stage. Zoom 100–800%.
- `.stage`: overflow hidden + touch-action none (custom gestures instead of the browser's).
## v1.7.4 — 2026-07-04 (PDF over HTTP instead of WebSocket)
- THE ROOT CAUSE of being unable to upload a PDF: the file was sent as base64 in a single WebSocket
message, and WS has a message-size limit — a real manual (>2–3 MB) exceeded it, the connection dropped
("Connection lost"), which both failed the upload and closed the dialog (WS drop → reconnect
→ re-render). Confirmed by test: 1 MB over WS is fine, 3 MB → Connection lost.
- Manual files are now uploaded through the HTTP endpoint `POST /api/houseplan/upload` (multipart,
HomeAssistantView, requires_auth, admin_only check) — like media in HA itself. Verified:
3 MB (182 ms) and 10 MB (446 ms) upload without a hitch.
- Readable errors everywhere (`_errText`): no more "[object Object]"; clear texts for
too_large / bad_ext / unauthorized.
- The WS command houseplan/file/set is kept for compatibility, but the frontend uses HTTP.
## v1.7.3 — 2026-07-04 (device dialog fixes)
- PDF manuals failed to upload: the manual base64 (`btoa(String.fromCharCode(...subarray))`) crashed
on real files (RangeError when spreading large arrays), and when the dialog was closed during an
await it threw a null access. Replaced with `FileReader.readAsDataURL` (reliable for any size);
the results are accumulated and added only if the dialog is still open (no exceptions).
- The device/space/room edit dialog closed on an outside click and "by itself"
(an accidental click on the background overlay, including after closing the system file picker). Removed
background-click closing for editor dialogs — only explicit Cancel/Save/Esc.
The info card (read-only) still closes on an outside click.
- Verified on live HA: PDFs attach; a background click and dialog data refresh do not close the dialog.
## v1.7.2 — 2026-07-04 (fix for the mobile "configuration error")
- THE CAUSE: the card was hooked up only via `add_extra_js_url` (extra_module_url), whose loading the
frontend does NOT wait for — on a cold start of the mobile app the dashboard was drawn
before the element was registered → "configuration error". (All working HACS cards are Lovelace resources.)
- FIX 1: the integration registers the card as a **Lovelace resource** (module) — the frontend waits for
those before rendering. Idempotent (updates the URL on a version change, no duplicates); a fallback to
extra_module_url for Lovelace YAML mode. `_register_lovelace_resource` in __init__.py.
- FIX 2: `_loadFromServer` retries (up to 8 attempts on hass updates) — if hass arrived before
WS readiness, the card does not get stuck on the onboarding but waits for the config.
- Verified: the houseplan-card.js resource is registered, extra_module_url is empty (a single clean
module), the card renders the plan on a fresh tab without the error.
## v1.7.0 — 2026-07-04 (audit + refactoring + tests)
- Removed the sample house baked into the bundle (the dacha, ~245 KB of base64 plans + ROOMS/FLOOR_*): the bundle
went 293 KB → 83 KB. A fresh install shows the "Add a space" onboarding, not someone else's house.
- Removed the legacy→server migration code (the dacha is already on the server config) and the dead
device_overrides/virtual_devices paths (replaced by markers[]).
- Pure logic extracted into src/logic.ts (lqiColor, snapToGrid, segKey, pointInPolygon,
markerIdForBinding, averageLqi) — covered by unit tests (node:test, 9 tests + iconFor).
- Backend: validation/sanitizers extracted into validation.py (no HA imports) — pytest, 10 tests.
- SECURITY: a test revealed that the file/marker name sanitizer preserved leading dots
(a directory traversal risk via ".."); leading-dot stripping added, empty → misc/file.
- Fixed real bugs previously masked by the truncated rules.ts: DOMAIN_PRIORITY was
cut short (breaking the more-info entity selection); _defaultPositions crashed on polygon rooms
(now bbox). tsc --noEmit added to the build and CI — strict typing passes.
- Removed the junk duplicates src/data/{editor,rules}.ts; versions synchronized (the manifest was stuck at 1.4.0).
- CI: added typecheck, frontend and backend unit tests.
## v1.6.2 — 2026-07-04
- The "Room" field in the device dialog is now for ALL icons (not only virtual ones):
for bound ones it "overrides the placement" (by default, the device's area); changing the
room moves the icon to its center. Stored in marker.space/area and wins over the HA area.
- An "Edit" button was added to the info card — it opens the device edit dialog.
## v1.6.0 — 2026-07-04 (Phase 3: device editor)
- The "markers" model (config.markers[]): a hybrid — auto-discovered HA devices appear by themselves, markers
edit/rebind/augment them and describe manual/virtual icons. The marker id
preserves the position (device→device_id, entity→lg_<eid>, virtual→v_<rand>).
- Clicking an icon: normal mode → the info card (name, model, state, link, description,
PDF manuals, an "Open in HA" button); edit mode → the edit dialog.
- The device dialog: name, binding (a picker of all HA+Z2M devices/groups+helpers with a text
filter, minus already-placed ones and duplicates by name|area, plus "Virtual device"), MDI icon
(ha-icon-picker), model, link, description, manual file attachment (PDF to the server),
room (for virtual ones), Remove/Cancel/Save.
- Toolbar: a "+" button (tooltip "Add device") — an empty dialog.
- Backend: MARKER_SCHEMA in the config; WS houseplan/file/set (files in /config/houseplan/files/,
≤25 MB, served from /houseplan_files/files/); the files static path registered.
- Virtual icons are now draggable and positioned like normal ones (layout by id).
## v1.6.1 — 2026-07-04
- The binding picker hides device duplicates by "name|area" (Tuya/LocalTuya), same as the dedup on the plan.
## v1.5.1 — 2026-07-04 (space management)
- A pencil icon to the right of each space name in the top toolbar + a "+" button
(only with a server config).
- The space dialog: a "Name" field, a preview of the current background + "Upload/Replace…"
(SVG/PNG/JPG/WebP, base64 → houseplan/plan/set, the aspect ratio is detected from the file),
Delete / Cancel / Save buttons. Creating an empty space and deletion (together with all its
rooms and markup, with a confirmation). Saved via config/set with a revision.
## v1.5.0 — 2026-07-04 (light groups = entities)
- Lamps replaced by group entities: HA light groups (platform=group, area from the entity registry)
and Z2M groups (device model=Group) are rendered with the mdi:lightbulb-group icon; a click → the group's
more-info (controlling the whole group and its members natively in HA).
- Individual lamps are hidden in rooms that have a light group (settings.group_lights=false
brings back the per-lamp mode).
- The old visual grouping and the custom member menu were removed (code simplification).
- The positions of the old groups (grp_<area>) were migrated to the new ids (lg_<entity_id>) in the dacha layout.
## v1.4.4 — 2026-07-04 (CRITICAL fix: configuration race)
- INCIDENT: the config was saved wholesale on a last-writer-wins basis — an open client with a
stale copy wiped other clients' edits (the first iteration of the user's markup was lost).
- Fix: optimistic locking — the config carries a `rev`; `config/set` accepts `expected_rev`
and returns a `conflict` error on mismatch; the client re-reads the config.
- Live synchronization: `config/set` broadcasts a `houseplan_config_updated` event; all open
cards are subscribed and re-read the config automatically (windows no longer diverge).
- Positions during drag are saved pointwise via `layout/update` (dirty set), not a full
`layout/set` — the layout is also no longer wiped between windows.
- A config backup before deployment: .storage/houseplan.config.bak-*.
## v1.4.3 — 2026-07-04
- Device icons snap to the same grid as the markup (the icon center = a node, snapping
on drag and on X/Y input). The existing 57 positions in the dacha DB were snapped to the nodes.
## v1.4.2 — 2026-07-04 (markup grid)
- The grid step was halved: GRID_N 60 → 120 (step 8.33 render units ≈ 0.83% of the plan width).
- Grid points are rendered ON TOP of the plan and rooms (the grid-rect was moved from below the plan
into the top markup layer); the points are higher-contrast (r=0.14g, outline).
- Dacha data: the boundaries of all 13 rooms were snapped to grid nodes (edge snapping, shifts ≤ half a step;
only the instance config was edited via houseplan/config/set, the code was untouched).
- Card cache: the module URL ?v= is taken from const.VERSION — when updating the frontend, bump
const.py and restart HA, otherwise browsers keep the old module in memory cache.
## v1.4.1 — 2026-07-04 (markup UX)
- Esc / Ctrl+Z while drawing remove the last point (and its line, if it was added
by that step; reused walls belonging to others are untouched).
- The editing panel (markup and layout editing) was moved to the top under the card header
and pinned together with it (a shared sticky container .hdr).
- Closing the outline automatically opens the "New room" modal dialog: the display
name, a dropdown of ONLY the free HA areas (not assigned to any room of the config),
Save/Cancel. Cancel (or Esc) removes the closing point — the outline can be continued.
Choosing an area with an empty name fills in the area's name.
## v1.4.0 — 2026-07-04 (Phase 2: room markup editor)
- The "Markup" mode in the card (the mdi:vector-square-edit button, only with a server config):
a grid of points (60 nodes across the width), lines drawn by pairs of clicks with node snapping, a polyline preview.
- A closed outline (a click on the first point) activates "Save": choosing an HA area from a
dropdown + a name; the room is saved as a polygon (poly, normalized vertices).
- Tools: Add (drawing), Erase line (a click on a line), Delete room
(a click inside + confirm). The "wall" lines are stored in space.segments and reused by
neighboring rooms (shared walls — via clicks on existing nodes).
- Room rendering: polygons on par with rectangles (hover, click into the area, LQI tooltip,
the label at the centroid); in markup mode the outlines and names of all rooms are visible.
- Backend: ROOM_SCHEMA accepts poly (≥3 vertices) or x/y/w/h; SPACE_SCHEMA — segments.
## v1.3.0 — 2026-07-04 (Phase 1: server-side configuration)
- The `houseplan.config` Store: spaces (plan, aspect, view_box, rooms), device_overrides
(hidden/icon/name), virtual_devices, settings (exclude_integrations, group_lights).
- WS: `houseplan/config/get|set`, `houseplan/plan/set` (plan file upload as base64 →
`<config>/houseplan/plans/`, served through /houseplan_files/plans/).
- The card: a resolve model — the server config (coordinates NORMALIZED 0..1, render 1000×1000/aspect)
or the legacy bundle (canvas 1489×1053). Layout v2: {device_id: {s, x, y}} normalized.
- The "To server" button in edit mode — a one-click migration of the legacy config (plans, rooms,
view_box, layout). Performed at the dacha: .storage/houseplan.config + plans/f1.svg,f2.svg.
- Read-side support for device overrides and virtual devices (the management UI — phases 3–4).
## v1.2.2 — 2026-07-04
- The card toolbar (floor tabs) pins under the HA header while scrolling
(`position: sticky; top: var(--header-height)`; ha-card overflow: visible).
- Incident: an intermediate build on the unstable mount produced a broken bundle that crashed the
dashboard rendering; the rule "build only in /tmp + md5 control" was locked into DEVELOPMENT.md.
## v1.2.1 — 2026-07-04
- Icons no longer enlarge on hover/drag (transform: scale removed).
## v1.2.0 — 2026-07-03
- Room boundaries snapped to the walls of the vector plan.
- Zigbee LQI: the value under the icon, the room average in the tooltip, a red→green gradient,
the show_signal option.
- `mdi:lock` for any devices with a lock.* entity (TTLock).
- The hallway switch: the device's area in the HA registry was fixed (it was in detskaia_elina).
## v1.1.0 — 2026-07-03
- Vector backgrounds (SVG from REMPLANNER), automatic scale/offset alignment via raster correlation.
- Icon size as a % of the plan width (container queries, default 2.5%).
## v1.0.1 — 2026-07-03
- fix: nested SVG fragments via lit svg`` (the background/rooms were not rendered).
- fix: the version taken from const instead of a blocking manifest.json read in the event loop.
## v1.0.0 — 2026-07-03
- First release: the Lovelace card (TS+Lit, no token, driven by hass) + the houseplan integration
(WS layout storage, JS serving). The prototype's model carried over: curation, lamp groups,
iconFor, temperature, more-info, area navigation, drag layout.