v1.59.0-rc.1: optimize plans and polish editor feedback
Validate / hacs (push) Failing after 7s
Validate / hassfest (push) Failing after 6s
Validate / frontend (push) Successful in 3m12s
Validate / backend (push) Failing after 8m53s
Validate / smoke (push) Failing after 13m51s

This commit is contained in:
Matysh
2026-08-06 10:14:52 +03:00
parent d2bec266ed
commit e0f6746d7f
43 changed files with 2052 additions and 568 deletions
+9 -1
View File
@@ -72,8 +72,12 @@ Built from the registries (`_buildDevices`), rules carried over 1-to-1 from the
- Temperature: an entity with device_class=temperature / °C / `_temperature$` → label on the right.
- LQI (zigbee): the average over `*_linkquality` entities → label under the icon; color via
`lqiColor()`: ≤40 red → ≥180 green (hsl gradient). The room average is shown in the room tooltip.
The same tooltip includes the formatted clean-floor area (inner contour for
thick walls), and View hover gives every room an accent/brightness highlight.
- Icon state classes: on (yellow), open (orange: cover/valve/lock/binary_sensor
of problem classes), unavail (transparency).
of problem classes), unavail (transparency). Yellow remains on the marker in
source-glow fill mode: a light pool is spatial information, not a replacement
for the universal working-state plate.
## Sizes
@@ -92,6 +96,8 @@ resolver (`src/device-visual.ts`) for availability, steady status and activity.
shows the icon/morph and status plate; `icon_ripple` additionally shows a finite event,
static presence, mechanical transition or actual-work ring; `value` replaces the icon
with the HA-formatted numeric value. A critical alarm is red in every presentation.
Runtime baselines are seeded as soon as a rebuilt registry becomes authoritative, before
the next HA snapshot is classified; source-key changes reset any finite effect immediately.
Legacy `display: ripple` is read as `icon_ripple` and rewritten on the next config save;
the backend accepts it only for compatibility. `ripple_color` and `ripple_size` remain the
stored names for the ordinary activity effect. `size` (icon multiplier via the
@@ -201,6 +207,8 @@ double click → properties dialog. In markup mode the "Opening" tool handles cl
| `houseplan/config/get` | — | `{config, rev}` |
| `houseplan/trail/get` | — | `{trails: {marker: {current, previous}}}` — vacuum runs, raw robot coords |
| `houseplan/config/set` | `config`, `expected_rev?` | `{ok, rev}` / err `conflict`; event `houseplan_config_updated` |
| `houseplan/plan/optimize` | `config`, `layout`, both expected revisions | crash-resumable two-store commit + one-deep backup |
| `houseplan/plan/optimize_undo` | both expected revisions | restores backup only before any later edit |
| `houseplan/plan/set` | `space_id`, `ext` (svg/png/jpg/webp), `data` (b64, ≤8 MB) | `{ok, url}` — writes `<space>.<token>.<ext>`, deletes nothing |
| `houseplan/plans/list` | — | `{plans: [{name, url, size, modified, used_by}], total}` (newest 60) |
| `houseplan/plans/delete` | `name` | `{ok, removed}` / err `in_use` |
+32 -12
View File
@@ -398,13 +398,13 @@ Three things were fixed here besides the new coverage:
older meanings too — it opts out of the opening's centre magnet, and it
turns the compass to coarse 15° steps.
### 9.5 «Выровнять всё по сетке» — an ACTION, not a migration
### 9.5 «Оптимизировать планы» — explicit whole-plan maintenance
Existing plans may hold coordinates between the nodes. The card does
**not** round them on update. Instead, general settings grow a **Grid**
group with one button; it opens a confirmation that states how many
elements will move and by how much at most, warns that there is no undo,
and only then writes.
**not** round them on update. General settings contain a **Plan
maintenance** group whose action previews and then runs all current
lossless maintenance passes: model upgrades, grid alignment, exact
open-span canonicalisation and wall-interval compaction.
Why an action rather than a silent migration:
@@ -416,15 +416,35 @@ Why an action rather than a silent migration:
3. A silent migration is unattributable. When a room looks 3 cm wrong
the owner cannot tell whether the card did it or they did.
4. An update that touches stored geometry cannot be rolled back by
downgrading the card. An action can simply not be pressed.
downgrading the card. The explicit action has a one-deep snapshot and
can also simply not be pressed.
`optimizePlans(config, layout)` (`src/plan-optimizer.ts`) is the pure
orchestrator. It converts only legacy fields with an exact lossless
mapping, materialises legacy `open_to`, calls the grid projection,
rekeys exact wall/open-span endpoints onto the moved rooms, merges
touching virtual spans per room pair, compacts consecutive real-wall
intervals of equal thickness and stamps `model_version`. Unknown fields
are preserved and every pass is idempotent.
The optimizer deliberately does **not** alter backdrop calibration or
saved view boxes, delete unattached layout entries (a device may only be
temporarily unavailable), deduplicate markers, or delete files. File
collection remains the backend's reference-aware scheduled job.
`alignAllToGrid(spaces, layout)` (`src/align-grid.ts`) is pure: it
copies its input, never mutates it, and returns the new spaces, the new
layout and the report. The dialog therefore measures and commits the
**same object** — the numbers it promises cannot differ from what it
does. The write is one `config/set` plus the layout updates, in one go.
does. The resulting config+layout pair is sent to
`houseplan/plan/optimize`; the backend persists a durable intent before
either store changes, commits both revisions, and retains one snapshot.
`houseplan/plan/optimize_undo` restores it only while neither revision
has changed since the optimization. A crash between store writes is
completed from the intent on the next integration setup.
Guarantees, all covered by `test/align-grid.test.mjs`:
Guarantees are covered by `test/align-grid.test.mjs` and the orchestration/
idempotence case in `test/plan-optimizer.test.mjs`:
* every grid-bound element ends on a node; a rect's FAR corner too (a
snapped *size* on an off-grid origin leaves the other side between
@@ -442,7 +462,7 @@ Guarantees, all covered by `test/align-grid.test.mjs`:
### The report is a promise
The confirmation is the only gate in front of an action with no undo, so
The confirmation is the decision gate in front of a geometry rewrite, so
`maxShift`/`maxShiftCm` must never be smaller than what the run does:
* displacement is measured on the geometry **actually written back** —
@@ -461,9 +481,9 @@ The confirmation is the only gate in front of an action with no undo, so
says which space the maximum is in; openings corrected in angle alone
are counted on a line of their own.
There is no undo, and the dialog says so: the card keeps no snapshot of
the previous geometry. Re-running the action does not undo it either —
it is a projection, and a projection is not invertible.
One undo is available until the next config or layout edit. It restores
the stored snapshot; re-running optimization itself is never treated as
undo because a grid projection is not invertible.
## Every place that assumed the unit square
+39
View File
@@ -2,6 +2,45 @@
## Unreleased (dev)
## 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
+41
View File
@@ -8,6 +8,47 @@
## Не выпущено (dev)
## v1.59.0-rc.1 — 2026-08-06
Первый release candidate линейки 1.59: безопасная оптимизация всех планов,
понятнее редактирование объектов и обратная связь по комнатам, а также правки
по результатам аудита beta.10.
- **Оптимизация всех планов с безопасной отменой.** Бывшее действие
«Выровнять по сетке» заменено на «Оптимизировать планы». В одном preview
выполняются все актуальные без потерь миграции модели, привязка геометрии к
сетке и стенам, материализация старых открытых границ, объединение соседних
виртуальных частей и одинаковых по толщине реальных фрагментов. Config и
layout записываются через устойчивое намерение для двух хранилищ; одна отмена
доступна до следующей правки плана. Калибровка подложки, сохранённые виды,
непривязанные позиции и пользовательские файлы не изменяются. Прерванная
оптимизация или отмена завершается при следующем запуске интеграции.
- **Свойства объекта подложки по двойному клику.** В режиме «Выбрать» редактора
подложки двойной клик по любому объекту открывает его свойства: для надписи
остаётся форма текста, а для линии, прямоугольника, овала и мебели появляется
компактный диалог цвета и толщины линии (для замкнутых фигур — ещё и заливка).
Черновик нового диалога переживает тёплый ре-маунт карточки.
- **Комната обозначает себя в Просмотре.** При наведении подсвечивается именно
текущая комната, в том числе без настроенной заливки или HA area. В тултипе
также показывается форматированная чистая площадь пола; при толстых стенах
используется тот же внутренний контур, что в подписях и измерениях Resize.
- **Жёлтая подложка работающего устройства остаётся универсальной.** Устройство
в семантическом состоянии «включено / работает» остаётся жёлтым и при заливке
комнаты «Свет по источникам». Световое пятно дополняет состояние маркера, а
не заменяет его.
- **Исправления по аудиту beta.10.** Первое настоящее событие устройства после
загрузки больше не поглощается перестроением реестра, а смена фактического
источника маркера сразу сбрасывает короткий эффект старого источника. При
редактировании старой живой надписи явная единица или непредставимый inline-
грамматикой атрибут сохраняются без потерь; старый выбор `attr: state`
превращается в токен состояния, а не в несуществующий атрибут. Фрагменты
толщины стен теперь хранят точные нормализованные концы (старые midpoint-ключи
продолжают читаться), переживают Resize и после закрытия виртуальных частей объединяются
в максимальные сплошные отрезки одинаковой толщины. Пунктир виртуальных стен
виден во всех редакторах даже при скрытых границах. Release CI теперь явно
сообщает о проблеме порядка prerelease в GitHub/HACS, а не молча публикует
beta, которую HACS не может обнаружить.
## v1.59.0-beta.10 — 2026-08-05
Десятый pre-release линейки 1.59: единая система состояния устройств и набор
+47 -2
View File
@@ -19,6 +19,47 @@
- GitHub pushes: classic PAT (repo+workflow scopes), created via the user's Chrome;
stored in `~/.git-credentials` for the session.
## Local Windows workstation
The CI contract is **Node.js 22 + Python 3.13**. Do not use Codex's bundled
Node 24 or the machine's default Python 3.14 as proof that a release will pass.
Minimal native setup (PowerShell):
```powershell
winget install --id OpenJS.NodeJS.22 --source winget
winget install --id GitHub.cli --source winget
gh auth login
Set-Location 'C:\Users\Sergey\Downloads\dev\houseplan-dev\houseplan-card-src'
uv python install 3.13
uv venv --python 3.13 .venv
uv pip install --python '.venv\Scripts\python.exe' pytest voluptuous pytest-asyncio
npm ci
npx playwright install chromium
```
Open a new Windows Terminal after installing Node/GitHub CLI so their PATH
changes are visible. Keep the Playwright browser in its normal shared Windows
cache; downloading it inside every repository wastes time and disk space.
WSL2 is optional for the ordinary frontend and pure-backend loop. It is required
only when running the full HA harness locally: current Home Assistant imports
the Unix-only `fcntl` module and cannot start its pytest plugin on native
Windows. Keep a WSL clone inside the Linux ext4 filesystem rather than under
`/mnt/c`, otherwise dependency installs become slower. The release CI always
runs this harness on Ubuntu and gates the exact tagged commit. Docker Desktop is
not currently required. Do not install the full Home Assistant pytest stack
natively just for this repository: its pinned `lru-dict==1.3.0` first requires
Visual Studio Build Tools to compile, but the resulting plugin still cannot run
without `fcntl`.
Useful repo-local Git settings on NTFS (optional for this small repository):
```powershell
git config core.fsmonitor true
git config core.untrackedCache true
```
### ⚠️ File-sync pitfalls (critical)
1. The network mount sometimes serves files **truncated/scrambled** — edits via the Edit tool
from the Windows side are unreliable. Rule: **apply python patches against a clean copy in /tmp,
@@ -32,8 +73,12 @@
- Frontend: `npm test` — compiles src/logic.ts+rules.ts (tsconfig.test.json) and runs node:test
(test/*.test.mjs). Strict typing: `npm run typecheck` (tsc --noEmit, part of `npm run build`).
- Backend: `python -m pytest tests_backend/` — pure validation of custom_components/houseplan/validation.py
(loaded by path, without importing the HA package).
- Pure backend on native Windows (with no HA plugin autoload):
`$env:PYTEST_DISABLE_PLUGIN_AUTOLOAD='1'; .\.venv\Scripts\python.exe -m pytest
-p pytest_asyncio.plugin tests_backend/test_validation.py
tests_backend/test_trails.py tests_backend/test_trail_recorder.py -q`.
- Full backend (including `test_ha_*.py`): `python -m pytest tests_backend/ -q`
in CI or WSL/Linux only.
- IMPORTANT (audit lesson): the rollup typescript plugin reports a syntax error as a WARNING and still
builds the bundle — a truncated file can "pass". That is why the build starts with `tsc --noEmit`,
which fails on such errors. Always build with `npm run build`, never bare `rollup -c`.
+7 -3
View File
@@ -1,6 +1,6 @@
# The text block — a decor label that can show an entity's state
Status: **implemented (dev, unreleased).** Code: `src/logic.ts`
Status: **released in v1.59.0-rc.1.** Code: `src/logic.ts`
(`liveText`, `liveTextReference`, `liveTextToken`, `liveTextValue`,
`decorTextScale`, `decorTextLines` — pure, unit-tested),
`src/houseplan-card.ts` (`_renderDecorLayer`,
@@ -12,7 +12,9 @@ Smokes: `demo/smoke_live_text.mjs`, `demo/smoke_decor_text.mjs`;
The shape: `{kind:'text', x, y, text, color, scale?, angle?}` — plus legacy
`size?`, `entity?`, `attr?`, and `unit?` fields that older plans may still
carry. New live references are stored only inside `text`; existing linked
labels render unchanged and migrate to inline references when edited.
labels render unchanged and migrate to inline references when that conversion
is lossless. A legacy explicit `unit`, or an attribute name that cannot be
represented by the inline grammar, stays in the legacy fields when edited.
## 1. Why a live label
@@ -168,7 +170,9 @@ click opens its editor, and the corner/rotate handles appear.
references included; `scale` optional, finite, `0.15…20`; `angle` optional,
finite, `-360…360`. Legacy `entity`/`attr`/`unit` remain accepted and bounded
so old saved plans continue to validate and render. The frontend writes none
of them after the label has been edited. Tests: `tests_backend/test_validation.py`
of them after an ordinary representable label has been edited. It deliberately
retains them when dropping an explicit unit or non-representable attribute would
change what the label says. Tests: `tests_backend/test_validation.py`
(`test_decor_text_live_fields`, `test_decor_text_block_scale_and_angle`).
## 7. What this is not
+11 -4
View File
@@ -11,15 +11,15 @@
> (versions, publication, infrastructure), DEVELOPMENT.md for new gotchas,
> ARCHITECTURE.md for design changes, ROADMAP.md when plans move.
## Snapshot (2026-08-05)
## Snapshot (2026-08-06)
| Item | State |
|---|---|
| Version | **v1.59.0-beta.10** everywhere (manifest, const.py, package.json, CARD_VERSION) — **pre-release**, tag `v1.59.0-beta.10` on **`dev`**, GitHub Release with `prerelease=true`; `main` is not touched and nothing is copied to the home instance by hand — HACS delivers it on the beta channel. On top of beta.9: unified device status/activity visuals; inline HA variables in decor text; doorway-light clipping by thick-wall reveals; view-only virtual-wall masking; a clearer wall toolbar; and canonical merging/compaction of wall fragments. Previous pre-release: v1.59.0-beta.9; previous stable: v1.58.0 |
| Version | **v1.59.0-rc.1** everywhere (manifest, const.py, package.json, CARD_VERSION) — **pre-release**, tag `v1.59.0-rc.1` on **`dev`**, GitHub Release with `prerelease=true`; `main` is not touched. The rc line deliberately replaces the planned beta.11: GitHub lists beta.9 before beta.10/beta.11 lexicographically and HACS 2.0.x consumes that order without SemVer sorting, while rc.1 is both newer by SemVer and discoverable first. Previous pre-release: v1.59.0-beta.10; previous stable: v1.58.0 |
| Workflow | Owner's rule since 2026-08-05: ordinary fixes/features are made **locally, without tests and without commits**. Tests/build/smokes run only when the owner asks for a pre-release; then bump to `X.Y.Z-beta.N`, commit/tag the tested `dev` state and publish a GitHub Release with `prerelease=true`. `main` is not touched and nothing is copied to the home instance by hand; HACS delivers it on the beta channel |
| Workflow | Owner's rule since 2026-08-05: ordinary fixes/features are made **locally, without tests and without commits**. Tests/build/smokes run only when the owner asks for a pre-release; then bump the version, commit/tag the tested `dev` state and publish a GitHub Release with `prerelease=true`. `main` is not touched and nothing is copied to the home instance by hand. The release workflow now reports a failed HACS-discovery check when GitHub does not return the new tag as its first prerelease |
| GitHub | https://github.com/Matysh/houseplan-card — `main` carries stable releases; pre-release tags may point directly at `dev`. Work lands on `dev` and is merged into `main` for a stable release, so `dev` is normally equal to or ahead of `main`, never behind. Push via SSH key `ha_jb` (remote git@github.com:…); API releases via the fine-grained PAT in `~/.git-credentials` (Contents R/W, issued 2026-07-23) |
| CI | beta.10 passes the full local frontend, pure-backend and browser-smoke gate. Exact-SHA release gating remains active: `release.yml` withholds the asset until every matching Validate run finishes green; GitHub Validate and the release asset are verified after the tag is pushed |
| CI | rc.1 passes the local Node 22 typecheck/build, all 386 frontend tests, all 93 pure-backend tests and all 113 browser smokes. Native Windows cannot start the current HA pytest plugin (`fcntl` is Unix-only), so the exact-SHA Ubuntu Validate remains mandatory: `release.yml` withholds the asset until every matching run finishes green; GitHub Validate and the release asset are verified after the tag is pushed |
| HACS | Custom repository works. **Inclusion PR: hacs/default#9004** — open, valid, labeled, mergeable clean, never drafted. Queue: 1212 open, 835 older than ours. Merge rate COLLAPSED: 75 in July but almost all in the first decade, 0 in the last week (checked 2026-07-29) — maintainers process in rare bursts; ETA unknowable, months at best. Nothing actionable on our side |
| Home instance | ha.jbstudio.pro (SSH port **22222**, key `ha_jb`; HA config root is `/mnt/data/supervisor/homeassistant` — `/config` does NOT exist in this SSH environment), last direct copy was **v1.57.0**; from v1.58.0 on it updates itself through HACS by tag (no scp) |
| Localization | UI en/ru (src/i18n/*.json), everything user-visible localized incl. kiosk popover |
@@ -74,6 +74,13 @@
actual work, orange open/unlocked, unavailable and always-red alarms;
activity distinguishes a short event, presence, mechanical travel and
running. Legacy Ripple-only migrates to Icon + activity on the next save.
- **v1.59.0-rc.1** (2026-08-06): whole-plan lossless optimization with an
atomic config+layout commit and safe undo; the yellow actual-work plate is
retained alongside source glow; all Background objects have Select-mode
double-click properties; View hover highlights every room and reports its
clean-floor area. Audit fixes add eager activity baselines/source resets,
lossless legacy live-text editing, exact wall-fragment endpoints/compaction,
editor-visible virtual walls and prerelease-discovery reporting in CI.
## Recent milestones (details in CHANGELOG.md)
+41 -28
View File
@@ -85,9 +85,11 @@
placed on the plan (hidden by filtering or by the user) still feeds the
room card, the tooltip and the temperature fill; fridges/TRVs still do
not; an explicit per-room source still wins [auto: unit devices.test]
- [ ] Room tooltip wording (v1.44.5): hovering a room shows its name (plus
temperature/signal when available) and no longer claims "open the area" —
room clicks were removed in v1.40.1 [manual]
- [ ] Room hover + tooltip: in View, hovering any room visibly highlights it
(filled, transparent and area-less alike) and shows its name plus clean-
floor area; temperature/signal follow when available. Thick walls reduce
the area to the inner contour. Editors do neither [auto: smoke_ux_fixes;
manual visual]
## Environments matrix
@@ -283,9 +285,9 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
at 70% of a device icon and zooming WITH the plan; the small metric rows
under the room name now show in the plan editor too
[auto: smoke_room_cards gearDetached/plainInPlan]
- [ ] One indicator always (v1.52.1, HP-1520-01): in a glow space the plan
editor (no glow layer) shows the lit lamp yellow again; the devices
editor draws the layer and keeps the badge standard
- [ ] Working plate remains universal: in a source-glow space a lit lamp or
other actually working device stays yellow in View and in every editor;
the glow pool is an additional spatial indicator, not a replacement
[auto: smoke_light_badges]
- [ ] Size/angle parity (v1.52.1, HP-1513-01): a marker with size 3 / angle 37
scales x3 and rotates on BOTH cards [auto: smoke_size_angle_parity]
@@ -362,10 +364,10 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
the explicit action is yellow again, a wall switch still mirrors its
controls, and an «Открыть/закрыть» marker whose device has no `cover.*`
at all falls back to its primary [auto: smoke_cover_plate_precedence]
- [ ] Light-source badges (v1.52.0): in glow fill a lit lamp's badge stays
standard (the spot is the indicator) and a lit socket stays yellow; in
other fills a lit lamp is plain yellow with no RGB tint; morphing and
the activity colour fallback survive [auto: smoke_light_badges +
- [ ] Light-source badges (current contract): in glow fill a lit lamp's badge
stays yellow just like a lit socket; the pool keeps the source's RGB while
the marker keeps semantic yellow. Other fills behave identically;
morphing and the activity-colour fallback survive [auto: smoke_light_badges +
smoke_rgb_alarm]
- [ ] Icon size multiplier scales the glyph (dev): set a marker's size to 3 —
the icon inside grows with the badge instead of staying default
@@ -394,10 +396,13 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
- [ ] Yellow means working (dev): a TRV whose hvac_action is heating glows
yellow; one that is merely enabled (idle) or has a service switch on
(anti-scaling, child lock) stays dark; a lit light turns its state on by
the same condition that lights the glow pool — and shows the yellow
badge only where the glow spot is NOT drawn: with the glow layer
visible the spot is the one indicator (v1.52.0)
the same condition that lights the glow pool and shows the yellow badge
even where that pool is drawn
[auto: smoke_yellow_principle + smoke_light_badges]
- [ ] Activity baseline (beta.10 audit): rebuilding the device registry seeds
the current snapshot immediately, so the very first later motion/event
transition is detected. Rebinding a marker's effective source clears the
old source's finite flash in the same update [auto: smoke_motion_sense]
- [ ] Editor gestures on touch (dev): in the plan editor on a phone, pinch
zooms and a moving finger pans; releasing after a gesture does not draw
a point, a clean tap still does [auto: smoke_editor_gestures]
@@ -718,9 +723,11 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
while decor shapes stay fully opaque; no fade in the other editors [auto: smoke_decor / smoke_grid_fade]
- [ ] Background editor (v1.33.0): third tab with its own toolbar (select /
line / rect / oval / text / erase + color, width, fill, X); shapes drag-
drawn with grid snap and live preview; degenerate shapes dropped; text
via dialog (S/M/L, color; dblclick re-edits); Select moves (snap), Delete
removes, Erase deletes on click; Esc: draft → selection → select tool →
drawn with grid snap and live preview; degenerate shapes dropped. In
Select, double click always opens properties: text/HA variables for a
label, colour + line width for every other object, and Fill for rectangles
or ovals. Select moves (snap), Delete removes, Erase deletes on click;
Esc: draft → selection → select tool →
View; decor renders under rooms, visible everywhere, inert outside the
editor; stored in space.decor (rev/lock, backend schema) [auto: smoke_decor / smoke_grid_fade]
- [ ] Opening hover preview (v1.32.1): with the Opening tool, hovering near a
@@ -820,7 +827,8 @@ Run the *core flows* (marked ★ below) in each environment at least once per mi
- [ ] HA instance with zero devices/areas → onboarding works, rooms can be drawn, no crashes [manual]
- [ ] Space with zero rooms → renders; markup hint visible
- [ ] Room without area + borders ON → drawn, click does nothing, no area tooltip signal
- [ ] Room without HA area + borders OFF → still has a transparent View hit
surface, highlights on hover, reports geometric floor area, click does nothing
- [ ] No zigbee devices anywhere → no LQI badges, lqi fill leaves all rooms unfilled [manual]
- [ ] 100+ devices in one space → build under ~50 ms [manual], drag stays smooth
- [ ] Very long device/room names → ellipsis/wrap, no layout explosion
@@ -1177,9 +1185,11 @@ require hands on real hardware — they remain for the human pass.
- [ ] **Nothing is rounded**: a sensor reporting `23.94781` shows `23.94781`.
Rounding is the sensor's `display_precision`, not ours [auto:
smoke_live_text]
- [ ] **Old links migrate on edit**: a stored beta.9 `text + entity/attr/unit`
label still renders unchanged. Open it: the inline token is visible in
the textarea; save it and only the new text template remains
- [ ] **Old links migrate without loss**: a stored beta.9 `text + entity/attr`
label still renders unchanged. A representable link moves into the
textarea token and drops legacy fields on save; an explicit `unit` or an
attribute name outside the inline grammar remains legacy and survives an
otherwise unrelated text/colour edit
[auto: smoke_live_text]
- [ ] **The same everywhere**: the label reads identically in View, in the
editors and on a kiosk screen [auto: smoke_live_text]
@@ -1490,8 +1500,9 @@ require hands on real hardware — they remain for the human pass.
the whole wall; click opens the cm/in field; empty/0 clears; Esc closes
without applying; «Apply to all walls of this room» fills every allowed
edge [auto: smoke_wall_thickness]
- [ ] **Hatched body, area unchanged**: after setting thickness a `.wallbody`
path appears; the room card's m² is identical before and after
- [ ] **Hatched body, clean-floor area**: after setting thickness a `.wallbody`
path appears; room-card and tooltip m² both decrease to the same inner-
contour area
[auto: smoke_wall_thickness]
- [ ] **Openings cut the slab**: a door/window on a thick wall leaves a gap in
the body; the door swing is offset toward the inner face; with
@@ -1516,9 +1527,10 @@ require hands on real hardware — they remain for the human pass.
[auto: test/wall-thickness.test.mjs + smoke_resize_virtual_thick]
- [ ] **Fragment normalisation**: draw adjacent/overlapping virtual stretches
along the same pair of rooms — they persist and select as one span. Close
the last virtual stretch on a uniformly thick wall — its saved `walls`
data returns to one whole-edge key. Repeat across a Split boundary: spans
owned by different room pairs must stay separate
the last virtual stretch — consecutive solid fragments merge into maximal
runs of equal thickness; equal neighbours become one, a thickness change
remains an exact endpoint. Resize transforms those endpoints. Repeat
across a Split boundary: spans owned by different room pairs must stay separate
[auto: test/open-spans.test.mjs + test/wall-thickness.test.mjs +
smoke_resize_virtual_thick]
- [ ] **Unit + backend**: inset/mitre/bevel, key from either end, degrade,
@@ -1598,12 +1610,13 @@ require hands on real hardware — they remain for the human pass.
still comes in at the window, a door with a contact sensor still reports
open/closed in the room card, and the resize tool still refuses to
shorten a wall past its opening [auto: smoke_hide_layers, smoke_glow]
- [ ] **Virtual walls follow the borders switch** (owner 2026-08-05): make an
- [ ] **Virtual walls follow the borders switch only in View** (owner 2026-08-05): make an
open boundary between two rooms (Plan → «Открытая граница»), then turn
**«Всегда отображать границы комнат» OFF**. The dashed stretch goes with
the borders — no floating dashes on a plan that draws no walls. Turn the
borders back on and it returns. In the Plan editor the dashes are always
visible, whatever the switch says [auto: smoke_hide_layers, smoke_openwall]
borders back on and it returns. In Plan, Devices and Background editors
the dashes are always visible, whatever the switch says
[auto: smoke_hide_layers, smoke_openwall]
- [ ] **Nothing is stored when nothing is hidden**: with both boxes unticked,
the space's config carries no `hide_decor` / `hide_openings` at all, and
a plan saved by an older card still opens here unchanged
+11 -5
View File
@@ -35,7 +35,8 @@ Allowed: pan/zoom (wheel, pinch, buttons), switching spaces, device tap
(info / more-info / toggle per settings), long-press → info card, opening tap →
door/lock info card (with an explicit Unlock/Lock button when a lock is bound —
the only way to operate a lock from the card; plan-icon taps never toggle locks),
room-card link icon → HA area (room taps do nothing since v1.40.1), hover tooltips (name, temperature, signal).
room-card link icon → HA area (room taps do nothing since v1.40.1), room hover
highlight, hover tooltips (name, clean-floor area, temperature, signal).
Removed from this mode (they move, not die):
- icon dragging ("drag anywhere", v1.9 — consciously reversed),
@@ -68,13 +69,15 @@ layer you cannot see is a layer you cannot edit.
| Setting | Off (default) | On | Always drawn in |
|---|---|---|---|
| `show_borders` — «Всегда отображать границы комнат» | borders (and the dashed **virtual walls**) are hidden | both are drawn | the Plan editor |
| `show_borders` — «Всегда отображать границы комнат» | in View, borders and dashed **virtual walls** are hidden | both are drawn | room borders: Plan; virtual walls: all editors |
| `hide_decor` — «Скрыть декоративный слой» | decor is drawn | lines, shapes, labels and furniture are hidden | the Background editor |
| `hide_openings` — «Скрыть проёмы» | doors and windows are drawn | their symbols are hidden | the Plan editor |
- **Virtual walls follow `show_borders`** (owner, 2026-08-05). They are walls —
dashed ones. Drawing them on a space with no borders left a plan whose only
walls were a few floating dashed stretches.
- **Virtual walls follow `show_borders` in View** (owner, 2026-08-05). They are
walls — dashed ones. Drawing them on a View with no borders left a plan whose
only walls were a few floating dashed stretches. Every editor deliberately
shows them regardless of the switch: hiding geometry while editing a plan,
device placement or its underlay makes those modes visually ambiguous.
- **Their geometry and their presentation are separate.** Every virtual span
still ends on the real wall centreline. In View the thick wall body is
painted over the dash ends, so they visually stop at its faces; editors paint
@@ -102,6 +105,9 @@ layer you cannot see is a layer you cannot edit.
- Toolbar tools: select / line / rect / oval / text / erase, plus colour, width
and fill. Shapes are drag-drawn with grid snap and a live preview.
- In **Select**, double click always means “edit this object”. Text opens its
text/HA-variable form; a line, rectangle, oval or furniture symbol opens the
common colour and line-width form, with Fill where the shape supports it.
- **Live size badge** (owner 2026-08-04): while a LINE is being dragged out, the
same `.measurelabel` the Plan editor puts on a wall shows «length · angle» —
`segmentCm` over the space's `cell_cm`, metres or feet per the HA unit system,
+14 -8
View File
@@ -15,17 +15,23 @@ Code: `src/wall-thickness.ts`, render in `src/houseplan-card.ts` /
## 1. Model
Per space: `walls: [{ key, cm }]`. Key = quantised midpoint + direction
(modulo 180°). Config always stores centimetres. Open boundaries refuse
thickness. One physical stretch has one thickness (atomic collinear spans when
neighbours overlap only partially). Atomic keys are storage only while a real
geometric break requires them: once an original edge is entirely solid with
one thickness, normalisation writes its single whole-edge key again. Likewise,
Per space: `walls: [{ key, cm, a?, b? }]`. `key` remains the quantised midpoint
and direction (modulo 180°) compatibility lookup; new or rewritten entries also
carry exact endpoints `a` / `b` in normalised plan coordinates. Config always
stores centimetres. Old `{key, cm}` data remains readable and is upgraded when
the affected boundary is edited. Open boundaries refuse thickness. One physical
stretch has one thickness (atomic collinear spans when neighbours overlap only
partially). Exact endpoints make a thickness boundary independent of whichever
room topology later happens to split the same straight line. Normalisation
merges consecutive solid pieces into each maximal run of equal thickness; a
different thickness or a virtual gap remains a real break. Likewise,
touching/overlapping `open_spans` of the same room pair are stored as one span;
pair ownership remains a hard boundary so Split can derive exact `open_to` links.
Degrade unmatched keys silently on write. Resize / undo / scale re-key all
touched spans in the same transaction and keep `walls` in the resize snapshot.
Degrade unmatched keys silently on write. Resize / undo / scale transform exact
endpoints and re-key all touched spans in the same transaction; legacy entries
without endpoints keep the midpoint fallback. `walls` stays in the resize
snapshot.
## 2. Growth (centreline ±½)
+4 -3
View File
@@ -169,9 +169,10 @@ Lovelace **пересоздаёт элемент карточки** — выбр
`_spaceDialog` (настройки/создание пространства), `_markerDialog` (устройство),
`_settingsDialog` (общие настройки), `_rulesDialog` (правила иконок),
`_openingDialog` (проём), `_decorTextDialog` (надпись), `_roomDialog` вместе со
всей своей обвязкой (`_roomEditId`, `_roomFill`, источники температуры и
влажности, масштабы подписей, `_areaSel`/`_nameSel`, `_pendingSplit`, `_path`).
`_openingDialog` (проём), `_decorTextDialog` (надпись), `_decorShapeDialog`
(цвет / толщина линии объекта подложки), `_roomDialog` вместе со всей своей
обвязкой (`_roomEditId`, `_roomFill`, источники температуры и влажности,
масштабы подписей, `_areaSel`/`_nameSel`, `_pendingSplit`, `_path`).
Информационные попапы — `_infoCard` (карточка устройства) и `_openingInfo` —
восстанавливаются **по id**, а не по объекту: конфиг мог перезагрузиться под