Release v1.61.0-beta.2 candidate

This commit is contained in:
Matysh
2026-08-10 00:24:42 +03:00
parent 112c260314
commit fb382bfa11
49 changed files with 6342 additions and 1509 deletions
+26 -18
View File
@@ -1,6 +1,6 @@
# House Plan architecture
Updated: 2026-08-09 (v1.61.0-beta.1). The repository = a HACS integration (category **Integration**)
Updated: 2026-08-10 (v1.61.0-beta.2). The repository = a HACS integration (category **Integration**)
that contains both the backend (`custom_components/houseplan`) and the Lovelace card (`src/` → `dist/`).
## Layout
@@ -587,23 +587,31 @@ hash falls back to the default.
`plan_scale` feeds both axes. The image is interactive only in its own
Background tool, rotated corners contribute to content bounds, and the
static card uses the same model. See `DECOR-EDITOR.md` and `BACKDROP.md`.
- **Glow fill** (v1.35+): `fill_mode: 'glow'` paints every room with
`fill_colors.glow_base` and renders per-source radial gradients clipped by
a per-light `clipPath` = zone polygons + doorway sectors, each contour a
SEPARATE clipPath child (children union; subpaths of one nonzero path
cancel on opposite windings — field bug v1.36.3). Radius: global
`settings.glow_radius_cm` + per-marker `glow_radius_cm`. On a thick wall the
doorway sector is the angular intersection of its near- and far-face clear
spans, so the jamb returns clip the spill as a real opening tunnel. The
shared light resolver marks external `controls` as non-spatial: they still
vote in room state/statistics and drive group actions, but never place a Glow
pool at the controller. If the same entity also has a real lamp marker, that
physical marker owns its unique Glow position regardless of registry order.
Room-coloured opening tunnels are a separate layer below Glow/sun. Their
geometry and cache remain owned by `wall-thickness.ts`/the card orchestrator;
`render/opening-tunnels.ts` only maps resolved immutable inputs to the stable
SVG contract. This is the first render-only HP-ARCH-01 extraction and must not
become a second geometry or fill model.
- **Independent Glow overlay** (#55): `settings.glow_enabled` is orthogonal to
the data `fill_mode`; room `settings.glow` is the tri-state-compatible model
foundation for #36. Legacy `fill_mode: 'glow'` remains a permanent read
token and projects to data fill `none` plus Glow `true` unless an explicit
boolean wins. Normal settings/room saves materialise that projection in the
same write; Optimize Plans performs the equivalent idempotent model-v6
migration. Render order is paper → data room/tunnel fill → pointer-free Glow
base room/tunnel fill → radial pools → sun/interactive layers. The static
room card uses the same data/base projection but deliberately has no live
pools.
- **Glow pools and additive composition** (#19): every source retains its own
radial gradient and wall/door `clipPath`. All pools live in one flat isolated
group inside one outer `opacity=.7` frame; only the pool elements use SVG
`mix-blend-mode: screen`. Gradient-stop alpha contains source brightness and
palette alpha, so group opacity is applied exactly once. A cached per-
`Document` raster probe verifies actual SVG screen pixels rather than trusting
CSS syntax support; pending/unsupported/error/timeout states render with
deterministic normal blending and a successful probe requests one update.
Radius remains global `settings.glow_radius_cm` with optional per-marker
`glow_radius_cm`. On thick walls the doorway sector uses the near/far clear
spans so jamb returns clip the spill as a real opening tunnel. The shared
light resolver marks external `controls` as non-spatial: they vote in room
state/statistics and drive group actions but never place a pool at the
controller. `wall-thickness.ts` and the card orchestrator continue to own all
geometry/caches; `render/opening-tunnels.ts` only projects immutable inputs.
- **Open boundaries** (v1.37, revised 2026-08): `space.open_spans` hold
geometric virtual stretches; `room.open_to` remains the light-zone index
derived from spans (legacy `open_to`-only configs expand to full
+11 -1
View File
@@ -1,6 +1,16 @@
# Changelog
## Unreleased
## 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
+11 -1
View File
@@ -6,7 +6,17 @@
> **Правило проекта:** оба файла пополняются в одном коммите с самим
> изменением — как и остальная документация (см. docs/STATUS.md).
## Не выпущено
## v1.61.0-beta.2 — 2026-08-10
- Свечение источников света стало независимым слоем и может одновременно
работать с заливкой по температуре, Zigbee-сигналу, состоянию света или без
заливки. Старые планы с режимом Glow сохраняют прежний результат и без потерь
мигрируют при обычном сохранении или «Оптимизации планов».
- Пересекающиеся световые пятна теперь складываются как свет: пересечение
становится ярче и смешивает цвета. Карточка проверяет реальную поддержку SVG
в браузере и безопасно возвращается к обычному наложению, если её нет.
- После закрытия карточки устройства, открытой долгим нажатием мыши, план больше
не прилипает к курсору и диалог не открывается повторно из старого жеста.
## v1.61.0-beta.1 — 2026-08-09
+9
View File
@@ -47,3 +47,12 @@ no file it prints the current registry.
Unknown future fields remain outside this report and continue to follow the
backend's forward-compatibility policy. Absence from the report is therefore
not permission to delete a field.
## Independent Glow compatibility
The historical space and room token `fill_mode: glow` remains accepted on read
indefinitely. Runtime projects it into an ordinary inherited data fill plus an
enabled Glow overlay; explicit `glow_enabled` / room `glow` booleans always win.
A normal edit that replaces the legacy token writes the resolved boolean in the
same operation. Optimize Plans applies the same lossless, idempotent model-v6
migration while preserving unknown sibling settings.
+10 -7
View File
@@ -17,15 +17,15 @@ change must pass through a published beta/RC before stable. Stable release
commits are promotion-only (versions, generated bundles and release/changelog
metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
## Snapshot (2026-08-09)
## Snapshot (2026-08-10)
| Item | State |
|---|---|
| Version | **v1.61.0-beta.1** everywhere (manifest, const.py, package.json, CARD_VERSION) — pre-release candidate with Stage 1 vacuum integration coverage and the strict stored-colour contract |
| Current local cycle | Stable v1.60.3 is published. v1.61.0-beta.1 implements deterministic multi-subpath vacuum telemetry, sticky source resolution and XCME picker/diagnostics, physical calibration confirmation, owner-approved bbox-only final room-anchor fallback and backend source-health lifecycle. Issue #21 adds one strict persisted-colour contract and render-time protection for legacy/imported config. The targeted pre-release gate is green; publication waits for the mandatory exact-SHA GitHub Validate run. |
| Version | **v1.61.0-beta.2** everywhere (manifest, const.py, package.json, CARD_VERSION) — pre-release candidate with independent/additive Glow and the long-press gesture fix |
| Current local cycle | Stable v1.60.3 and pre-release v1.61.0-beta.1 are published. The current `dev` candidate for v1.61.0-beta.2 implements #55 (Glow independent from data fill, legacy-safe model-v6 migration and separate base/tunnel projection), #19 (flat additive pool composition with a cached real-pixel capability probe and normal fallback), and P1 #59 (a modal opened by mouse long-press now terminates the interrupted pan gesture). Deterministic fixtures, browser smokes, golden candidates and two same-runner performance profiles are included; publication waits for reviewed Linux golden baselines and the mandatory exact-SHA Validate run. |
| Workflow | Owner's rule since 2026-08-07: ordinary fixes/features are made **locally, without tests and without commits**. A requested pre-release gets a production build plus the smallest targeted unit/smoke set covering the changed surfaces, one tested `dev` commit/tag and a GitHub Release with `prerelease=true`; `main` stays untouched. The complete local frontend/backend/smoke gate runs only before a stable release, after which `main` is fast-forwarded to the exact tested `dev` SHA and the GitHub Release uses `prerelease=false`. Release bodies are short and bilingual (Russian first): only significant user changes get individual bullets, while minor/code-only work is grouped as `Мелкие исправления и улучшения` / `Small fixes and improvements`; every body ends with separate links to the Russian and English changelogs. Nothing is copied to the home instance by hand |
| GitHub | https://github.com/Matysh/houseplan-card — [Issues](https://github.com/Matysh/houseplan-card/issues) are the canonical task records and the linked [Project v2](https://github.com/users/Matysh/projects/1) is the canonical priority/status view; both must stay current. `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 | v1.61.0-beta.1 passed its targeted local gate: 198 frontend tests, 114 pure-backend tests, both vacuum browser smokes plus decor/device-preview/static-icon regression smokes, and a production build whose three bundle snapshots have the same SHA-256. Exact-SHA Ubuntu Validate remains mandatory for the full HA harness and broader matrix; `release.yml` withholds assets until every matching run finishes green |
| CI | The v1.61.0-beta.2 local targeted gate passed: typecheck, 146 focused frontend tests, 86 validation-schema backend tests, Glow/settings/tunnel/static-parity/#59 browser smokes, reviewed local golden candidates, the 7-sample additive profile and a diagnostic full-house overlay sample. Both profiles preserve exact 1/10/30/60 pool cardinality, one render per HA tick and stable caches; the mandatory exact-SHA Ubuntu Validate will run the full HA/smoke/golden matrix and both 7-sample performance profiles before publication. `release.yml` withholds the card asset unless it is green. |
| 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 |
@@ -43,9 +43,12 @@ metadata). Only an explicit owner-approved emergency hotfix may skip this gate.
across reloads (v1.38.2). **Kiosk mode** (v1.41.0): `kiosk: true` — no
header/editors, swipe between spaces, double-tap zoom reset, `cycle: N`
carousel, per-screen size multipliers in localStorage.
- **Glow fill** (v1.35–v1.37): dark house + per-source light pools (rgb/color
temp/default; per-source radius), door sectors, open boundaries
(`room.open_to`, virtual walls, dashed, transitive light zones).
- **Independent Glow overlay** (local #55/#19 over the v1.35–v1.37 model):
dark-room base and per-source pools can coexist with any data fill. Pools use
additive screen blending only after a cached real-pixel browser probe and
otherwise fall back to normal composition; legacy `fill_mode: glow` remains
losslessly readable/migratable. Existing RGB/colour-temperature/default
colour, per-source radius, door sectors and transitive open zones remain.
- **Real switches** (v1.36): `marker.controls[]` group-toggle with HA-group
semantics; icon mirrors targets; hidden grouped lamps fixed (tiered
primaryEntity). **Lights toggle by default** (v1.39): primary domain light
+4 -1
View File
@@ -253,7 +253,10 @@ registry-сценарий. Для limited/read-only нужен локальны
- Per-source радиус glow — Стенд: поле «Радиус свечения» в диалоге лампы.
- Скрытая light-primary — Стенд: лампы-заглушки световых групп скрыты в реестре — glow комнаты питается группой; смотреть, что скрытые лампы не рисуют своих маркеров.
- Marker controls (тогл связки ламп) — Стенд: у настенного выключателя или виртуального маркера прописать «Управляет источниками света» + Toggle и тапнуть: лампы комнаты разом.
- Glow fill в целом — Стенд: заливка по умолчанию на «Ground Floor»: зажечь свет — пятно света, радиус в ⚙.
- Независимый Glow — Стенд: выбрать любую заливку пространства, отдельно
включить «Свечение источников света», зажечь свет — заливка данных остаётся,
поверх появляется пятно; радиус меняется в ⚙. Пересечения нескольких пятен
должны быть ярче и смешивать цвета.
- Островные комнаты — Стенд: нарисовать остров во «Yard».
- Иконка не прыгает при перепривязке — Стенд: перепривязать маркер: позиция та же; смена комнаты в том же пространстве тоже не двигает.
- Плейсхолдер автоиконки — Стенд: диалог без явной иконки: «Auto: mdi:…» с превью.
+23 -8
View File
@@ -124,7 +124,7 @@ missing destructive confirmation or an editor exception that breaks View.
(stylus, paired mouse, vendor skins) [auto: smoke_feedback_v2]
- [ ] Light-source flag (v1.44.0, user feedback): a smart SWITCH driving dumb
fixtures glows in the "Light sources" fill only once "This device is a
fixtures creates a Glow pool only once "This device is a
light source" is ticked. External targets under "Controls" still feed
group state/statistics but never create a pool at the switch coordinates;
unticked devices without a light entity never glow [auto: smoke_glow]
@@ -816,13 +816,28 @@ separately promised workflows:
[auto: smoke_controls; unit: devices.test.mjs, plan-optimizer.test.mjs].
Repeat with a `device:*` binding whose controls contain one of that
device's child switches; the child is excluded while external targets remain
- [ ] Glow fill (v1.35.0): fill mode "Light sources" — every room painted with
one uniform darkness color; lit lamps glow with a radial gradient
(rgb_color → color temp → default color; brightness scales opacity),
clipped by the source's room plus door sectors into NEIGHBOUR rooms
(entrance doors leak nothing; windows don't spill); radius set in
general settings in HA units (m/ft, stored in cm); no shadow casting —
islands don't block light (documented limitation) [auto: smoke_glow]
- [ ] Independent Glow (#55): the space has data-fill radios
None/LQI/Light/Temperature plus a separate Glow switch; every combination
persists and renders both layers in order. Legacy space/room
`fill_mode: glow` has identical effective state, explicit booleans win,
normal Save materialises both fields atomically and Optimize Plans makes
the same idempotent model-v6 migration without deleting unknown settings.
Glow-off everywhere creates no base/tunnel/pool SVG layer; static room
cards show the data fill plus base darkness but no live pools
[unit: logic, plan-optimizer, backend validation; auto: golden matrix].
- [ ] Additive Glow (#19): 1/10/30/60-source fixture renders one flat isolated
pool group and exactly one outer opacity; brightness/palette alpha live
only in gradient stops. A real SVG raster probe is cached per Document:
pending/error/timeout/unsupported use `data-blend=normal`, success changes
mounted cards to `screen`. Warm/cool overlap, reverse DOM order,
same-colour brightening and a non-pool sector are pixel-checked
[unit: glow-blend, fixture schema; auto: smoke_glow_blending; golden and
large-light-blend-v1/large-house-glow-overlay-v1 performance profiles].
- [ ] Long-press gesture interruption (#59): mouse pointerdown on a marker,
hold until the device card opens, release over the modal and close via X;
pointermove cannot pan, all stage pointer/pan anchors are empty and the
next clean short click follows the ordinary path
[auto: smoke_long_press_gesture].
- [ ] Island rooms (v1.34.0): a contour drawn fully inside an existing room
(or around one) saves as a nested room — column in a ring, inner room;
the parent's fill renders with an evenodd hole so the ring paints
+20 -6
View File
@@ -246,7 +246,8 @@ desktop: для точного рисования, Resize, модификато
| Стиль | Цвет и прозрачность | Общий цвет границ и названий |
| Фон | Наследовать/статический/день-ночь | Управляет фоном вокруг плана |
| Солнце | Север и лучи | Локально переопределяет общие настройки |
| Заливка | Световые источники/нет/LQI/свет/температура | Выбирает модель заливки для комнат пространства |
| Заливка | Нет/LQI/свет/температура | Выбирает модель данных для заливки комнат пространства |
| Свечение источников света | Вкл/выкл | Независимо добавляет базовое затемнение и световые пятна поверх заливки |
Удаление пространства удаляет его комнаты и разметку после подтверждения. Файл подложки при этом автоматически не удаляется: им можно управлять через список уже загруженных планов.
@@ -625,21 +626,25 @@ Glow и заливка «Свет» продолжают работать по
| Режим | Источник | Цвет/поведение | Нет данных |
|---|---|---|---|
| Световые источники | Включённые лампы и отмеченные реле | Дом затемняется, вокруг источников рисуются световые пятна | Остаётся базовая темнота |
| Нет | — | Прозрачная комната/только граница | — |
| Zigbee signal | Средний LQI устройств HA-зоны | Градиент от красного (≤40) до зелёного (≥180) | Без заливки |
| Свет | Единый набор видимых источников комнаты: внешние `controls` + собственный источник по флагу «Это источник света»; автоматические `light.*` — fallback | Цвет «включено», «всё выключено» или «нет источников» | Цвет «нет источников», если его прозрачность >0 |
| Температура | Источник комнаты или среднее датчиков | Холодно ниже минимума, комфорт между границами, жарко выше максимума | Без заливки |
Новая вручную созданная область по умолчанию предлагает «Световые источники». Старые планы без сохранённого режима остаются без заливки.
«Свечение источников света» теперь включается отдельно и сочетается с любым
режимом заливки. Оно добавляет базовое затемнение и световые пятна поверх
заливки данными, не заменяя её. Новое вручную созданное пространство по
умолчанию имеет режим «Нет» и включённое свечение. Старые планы с режимом
«Световые источники» читаются без визуальной потери и при следующем обычном
сохранении переводятся в независимые настройки.
### Наследование
| Уровень | Что можно изменить |
|---|---|
| Общие настройки | Цвет и прозрачность всех состояний, радиус света, цвет стен |
| Пространство | Активный режим, температурные границы, показ LQI, фон |
| Комната | Наследовать или явно выбрать Нет/LQI/Свет/Температуру; отдельный Glow не выбирается |
| Пространство | Режим заливки, независимый переключатель Glow, температурные границы, показ LQI, фон |
| Комната | Наследовать или явно выбрать Нет/LQI/Свет/Температуру; пока наследует Glow пространства |
| Устройство | Явно стать источником света, радиус свечения, цвет эффекта |
### Что считается светом
@@ -664,7 +669,16 @@ Glow и заливка «Свет» продолжают работать по
пока пользователь явно не включит флаг источника. Ручная привязка маркера к
комнате (`room_id`) точнее HA-зоны и работает в том числе для комнаты без HA Area.
В режиме Glow свет ограничивается внутренним контуром комнаты, проходит в комнаты через двери и свободно объединяет комнаты через виртуальные границы. Наружная дверь не выпускает свет за пределы дома. У толстых стен сектор двери дополнительно обрезается внутренним тоннелем и откосами.
При включённом Glow свет ограничивается внутренним контуром комнаты, проходит
в комнаты через двери и свободно объединяет комнаты через виртуальные границы.
Наружная дверь не выпускает свет за пределы дома. У толстых стен сектор двери
дополнительно обрезается внутренним тоннелем и откосами.
Пересекающиеся световые пятна складываются как свет: пересечение двух
источников становится ярче и смешивает их цвета. Перед включением этого режима
карточка проверяет реальную поддержку SVG-смешивания браузером. Если движок его
не поддерживает, используется обычное наложение без ошибки и без изменения
сохранённых настроек.
Цвет пятна выбирается по приоритету: RGB-состояние лампы → цветовая температура → общий цвет света. Яркость влияет на интенсивность, но имеет минимальный визуальный порог 15%.
+217 -29
View File
@@ -2,54 +2,242 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/19
- Приоритет: P2
- Статус ТЗ: performance-first draft
- Связано: независимый Glow overlay #55 должен сохранить эту композицию
- Статус ТЗ: реализовано в кандидате v1.61.0-beta.2; exact-SHA verification/golden approval — release gate
- Связано: независимый Glow overlay #55 повторно использует эту композицию
## Цель
Пересекающиеся radial pools смешиваются как свет, а не перекрывают друг друга
по DOM order, без изменения tunnel glow и базового затемнения комнат.
по DOM order:
- тёплый и холодный источник дают смешанный цвет в пересечении;
- два одинаковых dim-источника дают более светлое пересечение;
- room/data fill, Glow base, tunnel sectors, backdrop и sun не участвуют в
аддитивной группе.
Модель источников, `resolvedLightSources(room)`, радиусы, clipping светом стен и
сохранённый config не меняются.
## Scope guards
- blend применяется только к radial pools;
- никакого UA sniffing и программного pixel-by-pixel polyfill;
- unsupported или сомнительный engine получает текущий normal layering;
- feature не имеет пользовательского/скрытого toggle: при провале correctness
или performance gate issue возвращается в research;
- #55 не создаёт вторую blend group и не смешивает Glow base с pools.
## Render architecture
Внутри lighting layer создаётся отдельная isolated group только для pools:
Все pools одного lighting layer переносятся в одну плоскую изолированную
группу. Нормативная структура:
```html
<g class="glow-pools-frame" opacity="0.7" pointer-events="none">
<g class="glow-pools blend-screen" data-blend="screen">
<circle class="glow-pool" clip-path="url(#per-source-clip)">…</circle>
</g>
</g>
```
При fallback группа получает `blend-normal` и `data-blend="normal"`. Одно
capability-решение действует сразу на все pools документа: смешанных
screen/normal состояний между источниками не бывает. `data-blend` находится на
группе только для диагностики и тестов, не является persisted настройкой.
```css
.glow-pools { isolation: isolate; }
.glow-pool[data-blend='screen'] { mix-blend-mode: screen; }
.glow-pools.blend-screen > .glow-pool { mix-blend-mode: screen; }
.glow-pools.blend-normal > .glow-pool { mix-blend-mode: normal; }
```
`glow_base`, room/data fill, opening tunnel sectors и sun rays находятся вне
этой group. Порядок остальных SVG layers не меняется. Pool opacity остаётся
частью текущего color/gradient resolver; blend не удваивает base opacity.
`glow_base`, resolved room/data fill, opening tunnel sectors и sun rays
остаются sibling layers вне `glow-pools-frame`. Их порядок относительно друг
друга сохраняется по #55.
## Feature detection и fallback
Каждый circle сохраняет собственный `clip-path` после re-parenting. Clip ids
остаются уникальными и стабильными в пределах SVG; объединять per-source clips
в один общий clip запрещено, иначе свет начнёт проходить сквозь чужие стены.
- Capability определяется один раз per document через
`CSS.supports('mix-blend-mode','screen')` и кешируется.
- Unsupported engine рендерит текущий normal layering.
- User-agent sniffing и polyfill запрещены.
- Print/screenshot path использует тот же feature decision; reduced motion не
влияет на статичное blending.
## Нормативная alpha/blend семантика
Альфа отдельного pool задаётся только `stop-opacity` его radial gradient:
```text
Aplateau = clamp(fill_colors.glow_light.a * sourceBrightness, 0, 1)
A(r <= 70%) = Aplateau
A(r = 100%) = 0
screen(S, D) = 1 - (1 - S) * (1 - D) // отдельно для R, G, B в 0..1
```
На `.glow-pool` запрещены `opacity`, `fill-opacity`, filter и дополнительная
alpha: полупрозрачность должна участвовать в blend через gradient stops, а не
создавать отдельную element-композицию. После внутреннего screen-blending весь
плоский результат получает существующую общую opacity `0.7` через внешний
`glow-pools-frame`. Это значение применяется ровно один раз.
Glow base использует собственный alpha-контракт #55 и не входит в эту формулу.
Blend не меняет base/tunnel opacity и не осветляет paper/backdrop.
## Runtime capability probe
`CSS.supports('mix-blend-mode','screen')` разрешён только как быстрый отрицательный
pre-check. Положительный ответ не считается доказательством поддержки SVG.
Фактическая capability определяется одноразовым render probe:
1. Создать детерминированный мини-SVG с той же парой
`isolation:isolate` + `mix-blend-mode:screen`, двумя частично
перекрывающимися фигурами известных непрозрачных RGB-цветов и контрольным
фоном.
2. Без внешних ресурсов сериализовать SVG, растеризовать его в canvas с
фиксированными физическими размерами и прочитать source/overlap/background
pixels через `getImageData`.
3. Для overlap вычислить ожидаемый RGB по формуле `screen`; каждый канал должен
совпасть с допуском не более 2/255. Source pixels должны совпасть со своими
цветами, background — остаться неизменным. Проверка только overlap
недостаточна: артефакт isolation не должен дать false positive.
4. Любая ошибка, timeout, transparent/zero sample, security exception или
несовпадение переводит capability в `false`.
Результат кешируется как `Promise<boolean>` в `WeakMap<Document, …>` и
исполняется не более одного раза per document. Он не сохраняется в config,
localStorage или по UA: обновившийся WebView должен пройти пробу заново.
Пока Promise pending, карточка рисует normal fallback, не пустой слой. После
успешного результата все смонтированные карточки получают один update и
переходят на screen; при `false` повторного update нет. Print/screenshot path и
`houseplan-space-card`, если она в будущем начнёт показывать live pools,
используют то же capability-решение. `prefers-reduced-motion` не влияет на
статичное смешивание.
Для тестов capability dependency можно инъецировать как `true|false`; это
неэкспортируемый test hook, не пользовательская настройка.
## Fallback contract
Fallback сохраняет текущую визуальную семантику:
- pools идут в normal DOM layering;
- gradient alpha, общая opacity `0.7`, radius и per-source clips не меняются;
- порядок остальных SVG layers идентичен screen-path;
- отсутствие поддержки не создаёт warning/toast: это штатная деградация.
Baseline fallback проверяется отдельно, а не выводится из golden screen-path.
## Детерминированная fixture
Добавляется общая fixture `test/fixtures/glow/additive-pools.json` со сценариями
1, 10, 30 и 60 перекрывающихся pools. Она содержит schema-valid Houseplan
config и отдельный детерминированный HA state snapshot:
- фиксированные geometry, colors, brightness, radii и marker order;
- минимум два помещения с физической стеной и разными per-source clip paths;
- warm/cool и identical-dim контрольные пары;
- отключены sun/daynight/transition и другие недетерминированные эффекты.
Один и тот же JSON загружается Node-тестом и Python backend schema test. Ни
frontend, ни performance runner не держат собственную расходящуюся копию.
## Performance gate до включения
Добавить deterministic large-light fixture: 1, 10, 30 и 60 overlapping pools.
Сравнить baseline/branch в одном runner по `stateUpdate` p50/p95, render count,
long tasks и screenshot time. Ship gate: p95 delta остаётся внутри действующего
HP-PERF budget и нет устойчивого >10% regression на old kiosk reference
WebView. При провале issue возвращается в research без hidden toggle.
Создаётся отдельный профиль `large-light-blend-v1`; существующий
`large-house-v1` не меняет смысл молча. Baseline и candidate строятся из точных
SHA и запускаются последовательно одним runner/process family на pinned
Chromium при:
## Визуальная проверка
- DPR = 1;
- CPU throttling ×4 как воспроизводимом proxy старого kiosk-железа;
- минимум одном warm-up и семи measured samples на вариантах 1/10/30/60 pools.
- warm+cool overlap имеет цвет, отличимый от каждого входа;
- две одинаковые dim лампы дают более светлый overlap без clipping;
- isolated group не осветляет paper/backdrop/room base;
- tunnel sector не screen-blendится с pool;
- результат не зависит от порядка marker в config.
Отчёт содержит `stateUpdate` p50/p95, render count, Long Tasks, heap/cache
growth и screenshot capture time. Ship gate применяет одновременно
relative-to-base и отдельные absolute budgets этого профиля; более строгий
предел побеждает. Budget нельзя ослаблять только ради прохождения #19 без
письменного обоснования по артефактам нескольких запусков.
Неизмеримый критерий «не более 10% на старом reference WebView» удалён: такого
устройства нет в CI. Correctness сомнительных SVG-движков закрывает runtime
probe, а производительность старого железа — throttled профиль. Полевая beta на
реальных kiosk WebView желательна, но не заменяет автоматический gate.
При провале correctness или performance feature не ship-ится и возвращается в
research без скрытого fallback toggle.
## Разделение визуальных проверок
### Pure unit
- формула `screen` на известных RGB, clamp и округление;
- gradient alpha использует только `stop-opacity`;
- capability cache вызывается один раз per document;
- pending/false/true state machine и единое решение для всех pools.
### Browser pixel smoke
`demo/smoke_glow_blending.mjs` работает при фиксированном DPR=1 и сэмплирует
реальные pixels детерминированной SVG-сцены, а не сравнивает только DOM:
- warm+cool overlap соответствует screen-формуле с установленным допуском;
- две одинаковые dim-лампы дают overlap светлее одного pool без channel clip;
- background/base и tunnel sector сохраняют baseline pixels;
- перестановка markers не меняет overlap;
- принудительный test-only fallback совпадает с current normal baseline;
- per-room clips переживают re-parenting: pool не появляется за физической
стеной и остаётся видимым в разрешённой части своего clip.
Smoke использует canvas/PNG pixel sampling. Он не заменяется golden: обычный
screenshot diff может заметить изменение, но не доказывает математическую
формулу смеси.
### Golden
Golden-сцена фиксирует целостную композицию в light/dark theme, 1/2/много
источников, tunnel рядом с overlap и интеграцию с независимым overlay #55.
Candidate утверждается владельцем глазами и после принятия становится
визуальным контрактом; capture никогда не перезаписывает baseline сам.
## Edge cases
- 0 и 1 pool не создают лишнего визуального изменения;
- несколько карточек Houseplan в одном document используют один probe;
- разные комнаты, nested holes, doors/gates и physical/virtual walls;
- hidden/removed/disabled/unavailable источники не попадают в pools по
существующему resolver;
- dark/light theme, print/screenshot, kiosk и browser zoom;
- порядок markers, одинаковые цвета и полностью совпавшие центры;
- probe pending/fail/timeout и восстановление после reload документа.
## Документация и release artifacts
В том же feature commit обязательны:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- пользовательская документация Glow с объяснением смешивания и штатного
fallback старых движков;
- обновление ARCHITECTURE/render-order, если структура слоя меняется;
- owner-reviewed golden и performance comparison artifacts.
Release body описывает видимую пользователю смесь света; probe, классы и кеши
относятся к small fixes/improvements, если не требуют отдельного предупреждения.
## Приёмка
Golden + sampled pixel assertions доказывают смесь и isolation; unsupported
fallback совпадает с текущим baseline; performance gate зелёный; Glow #55
повторно использует тот же group, не создавая второе смешивание.
1. Положительный screen-path включается только после успешного фактического
SVG render probe; `CSS.supports` сам по себе недостаточен.
2. Все pools документа используют одно screen/normal решение; смешанного режима
нет.
3. Альфа живёт в gradient stops, per-pool element opacity отсутствует, общая
opacity `0.7` применяется один раз после внутреннего blending.
4. Glow base, data fill, paper/backdrop, tunnel sectors и sun остаются вне
isolated group и сохраняют baseline pixels.
5. Каждый pool сохраняет собственный clip-path после re-parenting.
6. Shared fixture 1/10/30/60 проходит frontend и backend schema tests.
7. Pixel smoke доказывает screen-формулу, isolation, order independence,
clipping и normal fallback; golden фиксирует целостную картинку отдельно.
8. `large-light-blend-v1` проходит relative и absolute budgets при CPU ×4.
9. #55 повторно использует ту же pool group и не создаёт второе смешивание.
10. Документация и ru/en changelog обновлены.
ТЗ готово к реализации только после повторного ревью capability probe,
alpha-семантики и измеримого performance gate.
+254 -39
View File
@@ -2,65 +2,280 @@
- Issue: https://github.com/Matysh/houseplan-card/issues/55
- Приоритет: P2
- Статус ТЗ: ready for review
- Связано: room override #36; additive pools #19
- Статус ТЗ: реализовано в кандидате v1.61.0-beta.2; exact-SHA verification/golden approval — release gate
- Связано: room override #36; additive pools #19; compatibility registry #33;
custom fill #56
## Цель
Разделить две независимые функции: data/static room fill и световой Glow.
Пользователь может видеть temperature/LQI/light/custom color и Glow одновременно.
Разделить две независимые функции:
## Модель
1. заливку комнаты данными или статичным цветом;
2. световой Glow как отдельный слой поверх этой заливки.
- `space.settings.fill_mode`: `none|lqi|light|temp|custom`;
- `space.settings.glow_enabled?: boolean`;
- `room.settings.fill_mode`: прежний inherit/override + `custom` после #56;
- `room.settings.glow?: boolean|null` по #36.
Пользователь может одновременно видеть LQI/освещённость/температуру/свой цвет
и Glow. Модель геометрии комнат, распространения света и проёмов не меняется.
Effective projection возвращает `{fill, glow}` двумя полями. Ни renderer, ни
opening tunnel не выводят Glow из fill mode после migration.
## Границы этапа и зависимости
## Read compatibility и migration
- #55 первым вводит независимую модель и compatibility-проекцию;
- #36 после #55 добавляет room-level tri-state Glow;
- #56 после #55 добавляет `custom` fill и его цвета;
- #19 может заменить обычную группу световых пятен на isolated additive group,
но не блокирует модель и миграцию #55.
Old `space.settings.fill_mode:'glow'` читается как
`fill_mode:'none', glow_enabled:true` без записи. Old room
`settings.fill_mode:'glow'`, если встречается future/legacy config, читается как
`fill inherit, room.glow:true`.
Порядок поставки: **#55 → (#36, #56)**; #36 и #56 могут выйти вместе с #55,
если их schema/UI/tests входят в тот же beta gate. Пока #56 не реализована,
frontend и backend #55 не принимают `fill_mode:'custom'`: будущий enum нельзя
открывать частично.
Новый UI при первом Save не обязан мигрировать untouched fields. Явное
«Оптимизировать планы» показывает conversion и atomic undo. Backend окно чтения
регистрируется в #33; новые writes не используют `fill_mode:'glow'` после
начала migration phase.
## Persisted model и effective projection
## Render order
### Новые поля
- `space.settings.fill_mode`: `none|lqi|light|temp`, плюс `custom` только после
#56;
- `space.settings.glow_enabled?: boolean`, отсутствие в новом config = `false`;
- `room.settings.fill_mode`: прежний inherit/override без новых записей `glow`,
плюс `custom` только после #56;
- `room.settings.glow?: boolean|null` по #36: `null/absent` = inherit.
Persisted read-type отдельно сохраняет legacy-токен `fill_mode:'glow'`.
Effective projection всегда возвращает два независимых значения:
```text
{ fill: none|lqi|light|temp|custom, glow: boolean }
```
Ни renderer, ни opening tunnel, ни `houseplan-space-card` не выводят Glow из
effective fill. Единственное место, где legacy `fill_mode:'glow'` влияет на
Glow, — compatibility projection ниже.
### Нормативный приоритет
Явное новое поле всегда сильнее legacy-токена:
```text
spaceGlow = space.glow_enabled
?? (space.fill_mode == 'glow' ? true : false)
roomGlow = room.glow
?? (room.fill_mode == 'glow' ? true : spaceGlow)
```
Legacy space `fill_mode:'glow'` проецируется в data fill `none`. Legacy room
`fill_mode:'glow'` проецируется в data fill `inherit`; затем обычный room/space
resolver определяет data fill. Поля разных уровней не смешиваются.
### Compatibility truth table
| Persisted space | Persisted room | Effective data fill | Effective Glow |
|---|---|---|---:|
| `fill=glow`, `glow_enabled` absent | inherit, `glow` absent | none | on |
| `fill=glow`, `glow_enabled=false` | inherit, `glow` absent | none | off |
| `fill=glow`, `glow_enabled=true` | inherit, `glow` absent | none | on |
| `fill=temp`, `glow_enabled` absent | inherit, `glow` absent | temp | off |
| `fill=temp`, `glow_enabled=true` | inherit, `glow` absent | temp | on |
| `fill=temp`, `glow_enabled=false` | inherit, `glow` absent | temp | off |
| `fill=temp`, Glow off | `fill=glow`, `glow` absent | temp | on |
| `fill=temp`, Glow on | `fill=glow`, `glow=false` | temp | off |
| `fill=light`, Glow off | `fill=glow`, `glow=true` | light | on |
| `fill=lqi`, Glow on | `fill=temp`, `glow` absent | temp | on |
Эта таблица обязательна для full plan и общей projection-функции. Статическая
карточка использует ту же data/base projection, но намеренно не рисует live
radial pools, см. ниже.
## Read compatibility, writes и смешанные версии клиентов
### Чтение
- открытие старого config ничего не записывает;
- backend принимает legacy `fill_mode:'glow'` всегда;
- в registry #33 оба legacy-path получают `deprecated-read`,
`read-compat-until: never`, current-UI write = false и migration через явный
Optimize;
- фраза «новые writes не используют `glow`» относится только к новому
frontend. Старые HACS-клиенты и уже открытые dashboard bundle остаются
допустимыми writers;
- unknown future fields сохраняются losslessly по #33.
### Обычный Save нового UI
Новый UI никогда не создаёт `fill_mode:'glow'`. При этом обычный Save не обязан
переписывать untouched legacy fields.
Если Save заменяет persisted `fill_mode:'glow'` новым data fill, он **обязан в
той же атомарной записи материализовать resolved Glow**:
- space legacy `glow` без явного поля → `fill_mode:<new>`,
`glow_enabled:true`;
- room legacy `glow` без явного override → новый/удалённый data fill и
`room.glow:true`;
- существующие explicit `false|true` сохраняются и не заменяются legacy
значением;
- явное переключение Glow записывает boolean, включая `false`; оно не может
исчезнуть как «значение по умолчанию», пока рядом остаётся legacy-токен.
Так смена заливки с legacy Glow на temperature/LQI не выключает свечение.
Save/Cancel/reload и future-field preservation входят в обязательную матрицу.
### Конфликт со старым writer
Если старый bundle снова записал `fill_mode:'glow'`, а explicit
`glow_enabled:false` сохранился, effective Glow остаётся off: новое поле сильнее.
Если старый writer физически удалил неизвестное поле, намерение восстановить
невозможно, и config снова считается чистым legacy (`glow` → on). Backend и
новый frontend не должны сами удалять неизвестные поля при round-trip.
## Явная migration через «Оптимизировать планы»
Optimize показывает preview до записи и выполняет один именованный command с
atomic undo:
- space `fill_mode:'glow'` → `fill_mode:'none'` + materialized effective
`glow_enabled`; explicit boolean сохраняется;
- room `fill_mode:'glow'` → data fill inherit + materialized effective
`room.glow`; explicit room boolean сохраняется;
- unrelated и unknown future fields не меняются;
- повторный Optimize идемпотентен и не создаёт новый diff.
Preview показывает число затронутых пространств/комнат и точные семантические
преобразования. Undo восстанавливает исходные persisted значения, а не только
визуально эквивалентную форму.
## Render contract
### Порядок слоёв
1. paper/backdrop;
2. resolved data/static room fill и matching opening tunnel floor fill;
3. Glow base darkness в clips effective-Glow rooms;
2. resolved data/static room fill и matching data fill внутреннего тоннеля;
3. Glow base для effective-Glow rooms и соответствующих частей тоннеля;
4. tunnel light sectors и radial pools;
5. sun/interactive layers согласно текущему contract.
5. sun и interactive layers по текущему контракту.
Glow base не заменяет data fill: compositing/opacity должен сохранять читаемый
underlay. Radial pools используют isolated additive group #19, если feature
доступна. Все Glow shapes pointer-transparent.
Glow base и pools pointer-transparent. Room hover, tooltip и editor hit targets
принадлежат геометрии под overlay.
### Нормативное смешивание Glow base
Glow base — отдельная SVG-геометрия с обычным `source-over`/`normal`
композитингом. `multiply`, `screen`, CSS filter и дополнительная групповая
opacity не применяются.
```text
a = clamp(fill_colors.glow_base.a, 0, 1)
Cout = Cglow_base * a + Cunderlay * (1 - a)
```
`Cunderlay` — уже скомпозированный результат paper + data fill. Используется
существующий пользовательский token `glow_base`; data fill color и alpha не
мутируются. Для legacy `fill_mode:'glow'` под Glow base лежит paper, а геометрия
base повторяет прежние room/tunnel shapes, поэтому при штатной палитре результат
должен сохранять pixel parity старого режима. Для новых сочетаний temp+Glow и
custom+Glow обязательны owner-reviewed golden baselines в light/dark theme;
после принятия они становятся визуальным контрактом и не обновляются
автоматически.
Room с effective Glow on получает base даже при отсутствии источников света;
radial pools тогда отсутствуют. Room с Glow off не получает base и исключается
из визуальных clip pools, но это не меняет физический transport через неё по
#36. Если Glow off у всех комнат, overlay не создаёт пустые SVG layers.
Тоннель сначала повторяет resolved data fill своей стороны, затем получает тот
же Glow-base overlay только со стороны effective-Glow room. Геометрия световых
секторов, отсечение откосами и проникновение через двери/ворота не меняются.
### Radial pools
До #19 пятна сохраняют текущую семантику цвета, brightness и общей opacity
`0.7`. После #19 они переходят в его isolated additive group. #55 не меняет
радиус, список `resolvedLightSources(room)`, статусы устройств или transport.
### `houseplan-space-card`
Статическая карточка использует тот же effective resolver и показывает:
- data/static fill;
- Glow base поверх него для effective-Glow rooms;
- matching базовую заливку поддерживаемых тоннелей.
Live radial pools и tunnel light sectors в статической карточке не рисуются —
это её существующее намеренное упрощение, а не drift. Parity-тест сравнивает
effective data/base styles между карточками и отдельно фиксирует отсутствие
pools.
## UX
Space dialog: отдельные controls «Заливка комнаты» и «Свечение источников».
Global palette разделяет data colors и Glow colors. Room dialog показывает
independent fill override и tri-state Glow #36. Preview обновляет оба без Save.
Space dialog разделяет controls:
- «Заливка комнаты»: Нет / LQI / Свет / Температура / Свой цвет после #56;
- «Свечение источников»: отдельный switch.
Room dialog после #36 показывает независимый fill override и tri-state Glow.
Global palette визуально разделяет data colors и Glow colors. Preview в
диалогах использует тот же effective projection до Save. Переключение одного
control не сбрасывает второй, цвета, радиус или room geometry.
## Edge cases
Room Glow off, nested holes, doors/gates, virtual/physical walls,
partitions/columns, no sources, source-glow device status, hidden/removed light,
show_borders false. Отключение overlay не меняет light aggregates/controls.
- room Glow inherit/on/off и legacy room token;
- nested rooms/holes и clean-floor clips;
- двери, ворота, окна, виртуальные/физические стены;
- partitions/columns и `show_borders:false`;
- room без sources, hidden/removed/disabled light и source-glow device status;
- overlapping pools и Glow через открытый тоннель;
- старый/new writer conflict и future fields;
- full plan, kiosk, editor preview и `houseplan-space-card`.
Отключение overlay не меняет light aggregates, room card или controls.
## Performance contract
Независимая модель может одновременно считать temperature/LQI и Glow на каждом
HA tick. Performance gate получает отдельный детерминированный профиль
`large-house-glow-overlay-v1` на 60 комнатах с `fill_mode:'temp'` и
`glow_enabled:true`; существующий `large-house-v1` молча не переопределяется.
`stateUpdate p95`, Long Tasks, heap и cache growth обязаны уложиться в
relative-to-base и absolute budgets общей performance-инфраструктуры. Изменять
budget только ради прохождения #55 запрещено без отдельного обоснования и
review артефактов.
## Документация и release artifacts
В одном feature commit обязательны:
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- пользовательская документация по заливкам/Glow на русском и английском, если
соответствующая страница существует;
- compatibility registry/audit #33;
- скриншот новых независимых controls;
- owner-reviewed golden matrix temp+Glow/custom+Glow и static-card state.
Release body выделяет независимый Glow как пользовательскую функцию; чисто
технические детали группируются по общему release-правилу.
## Проверки и приёмка
- compatibility truth table old/new space+room values;
- все data fills одновременно с Glow, tunnel colors и hover;
- Optimize preview/apply/undo и future field preservation;
- golden dark/light, temp+Glow, custom+Glow, mixed room overrides;
- старый config до migration выглядит без pixel regression;
- модель больше не требует выбрать Glow вместо полезной заливки.
1. Pure resolver покрывает полную space+room truth table, включая
`legacy glow + explicit false`.
2. Backend продолжает принимать legacy token; новый frontend не пишет его;
frontend/backend enum `custom` появляется только вместе с #56.
3. Обычный Save, заменяющий legacy token, атомарно материализует effective Glow;
untouched Save, Cancel и reload не создают silent migration.
4. Optimize preview/apply/undo идемпотентен и сохраняет future fields.
5. Старый config до migration имеет pixel parity; normal source-over formula и
layer order проверены DOM/style unit-тестами.
6. Golden: dark/light, temp+Glow, custom+Glow, mixed room overrides, no-source,
tunnel и hover; baseline принят владельцем до merge.
7. `houseplan-space-card` совпадает по data/base projection и не рисует pools.
8. Doors/gates, virtual/physical walls, nested holes, partitions/columns и
`show_borders:false` не меняют transport/geometry.
9. Профиль `large-house-glow-overlay-v1` проходит performance budgets.
10. Документация, compatibility registry, ru/en changelog и screenshot
обновлены.
Функция принята, когда модель больше не требует выбирать Glow вместо полезной
заливки, legacy-конфиги не меняют вид без явного действия, а смешанные версии
клиентов не могут молча отменить сохранённый explicit Glow state.
+15 -2
View File
@@ -11,6 +11,19 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
- **в реализации** — работа уже ведётся в рамках текущего этапа;
- **реализовано** — ТЗ сохранено как проверяемый acceptance contract.
## Обязательные release-артефакты номерного ТЗ
Если задача меняет пользовательское поведение, её ТЗ обязано явно перечислить:
- записи в `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`;
- затронутую пользовательскую документацию;
- требуемые screenshots/golden и способ их review, если меняется визуал;
- release/performance/security artifacts, если они входят в acceptance gate.
Отсутствие этого раздела не означает, что документация необязательна. Для
чистого refactoring ТЗ должно прямо зафиксировать отсутствие пользовательских
изменений и перечислить технические доказательства безопасного поведения.
## P1
| Issue | ТЗ | Статус ТЗ |
@@ -38,7 +51,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
| [#11](https://github.com/Matysh/houseplan-card/issues/11) Vacuum source health | [011-vacuum-source-health.md](011-vacuum-source-health.md) | в реализации |
| [#12](https://github.com/Matysh/houseplan-card/issues/12) Room cleaning highlight | [012-vacuum-room-cleaning-highlight.md](012-vacuum-room-cleaning-highlight.md) | готово к ревью |
| [#13](https://github.com/Matysh/houseplan-card/issues/13) Golden open context tray | [013-golden-open-context-tray.md](013-golden-open-context-tray.md) | реализовано |
| [#19](https://github.com/Matysh/houseplan-card/issues/19) Additive Glow blending | [019-glow-additive-blending.md](019-glow-additive-blending.md) | черновик: performance gate |
| [#19](https://github.com/Matysh/houseplan-card/issues/19) Additive Glow blending | [019-glow-additive-blending.md](019-glow-additive-blending.md) | v1.61.0-beta.2 candidate; exact-SHA gate |
| [#20](https://github.com/Matysh/houseplan-card/issues/20) Glow through open doors | [020-glow-open-door-spill.md](020-glow-open-door-spill.md) | готово к реализации |
| [#21](https://github.com/Matysh/houseplan-card/issues/21) Safe color CSS variables | [021-color-css-injection.md](021-color-css-injection.md) | готово к реализации |
| [#36](https://github.com/Matysh/houseplan-card/issues/36) Room Glow override | [036-room-glow-override.md](036-room-glow-override.md) | черновик продуктового решения |
@@ -53,7 +66,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
| [#51](https://github.com/Matysh/houseplan-card/issues/51) Custom decor images | [051-custom-decor-images.md](051-custom-decor-images.md) | черновик: security dependencies |
| [#52](https://github.com/Matysh/houseplan-card/issues/52) Dimensions in View | [052-view-dimensions.md](052-view-dimensions.md) | готово к ревью |
| [#54](https://github.com/Matysh/houseplan-card/issues/54) Zigbee topology overlay | [054-zigbee-topology-overlay.md](054-zigbee-topology-overlay.md) | research + adapter contract |
| [#55](https://github.com/Matysh/houseplan-card/issues/55) Independent Glow overlay | [055-independent-glow-overlay.md](055-independent-glow-overlay.md) | готово к ревью |
| [#55](https://github.com/Matysh/houseplan-card/issues/55) Independent Glow overlay | [055-independent-glow-overlay.md](055-independent-glow-overlay.md) | v1.61.0-beta.2 candidate; exact-SHA gate |
| [#56](https://github.com/Matysh/houseplan-card/issues/56) Static room color | [056-static-room-color.md](056-static-room-color.md) | готово после security gate #21 |
## Правило актуализации