mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 13:48:57 +00:00
Release v1.61.0-beta.2 candidate
This commit is contained in:
+26
-18
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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%.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
## Правило актуализации
|
||||
|
||||
Reference in New Issue
Block a user