31 KiB
#374 — Полный Glow в houseplan-space-card по явному включению
Issue: #374
Сценарий
Персона домочадец или гость/киоск смотрит на отдельную
custom:houseplan-space-card, размещённую как основной визуальный элемент
Home Assistant dashboard. Администратор дома заранее включил для этого
экземпляра полный Glow.
Когда лампа включается, человек должен сразу увидеть не только жёлтый маркер или ровную заливку комнаты, но и пространственный свет: радиальный pool начинается в реальном положении источника, проходит через открытые внутренние проёмы и оставляет тени за стенами, перегородками и колоннами.
Что человек увидит до и после
Сейчас отдельная карточка показывает живые маркеры и ровную Glow-подложку комнат, но никогда не рисует радиальный свет и тени. После изменения новый переключатель Light pools and wall shadows / Световые пулы и тени от стен позволит получить в ней тот же полноценный Glow, что на полном плане; без включения переключателя карточка останется визуально и вычислительно прежней.
Проблема
Закрытый документационный issue
#370 подтвердил, что
отсутствие теней в static card не было поломкой: renderSpaceStatic() намеренно
не выполняет самый тяжёлый этап светового рендера. Но для dashboard, где
отдельная карточка является основным представлением дома, это удаляет одну из
ключевых пространственных возможностей House Plan.
Канонический путь полной карточки сейчас делает гораздо больше, чем рисует градиент:
- строит реальные тела стен, перегородок и колонн;
- вырезает только проходимую часть открытых внутренних дверей/ворот и учитывает сохранённые passages и zero-thickness walls;
- блокирует свет окнами, наружными проёмами и телом, внутрь которого ошибочно попал источник;
- вычисляет visibility polygon и пересекает его с полом всех комнат;
- применяет один радиальный field на источник, additive/screen composition, экранное feathering и bounded fade переходы;
- кеширует тяжёлую геометрию по конфигурации и квантованным состояниям проёмов.
houseplan-space-card уже имеет тот же House Plan snapshot, HA states,
виртуальные источники, позиции маркеров, room Glow/base fills и каноническую
геометрию стен. Отсутствует именно общая световая сцена. Копирование приватной
_renderGlowLayer() в static renderer недопустимо: две поверхности начнут
по-разному трактовать проёмы, толщины и последующие исправления света.
Скоуп
- новый boolean
light_poolsтолько уcustom:houseplan-space-card; - настройка в YAML и visual editor, default
false; - при
true— визуальная и семантическая parity с Glow полнойcustom:houseplan-cardдля выбранного пространства; - общий framework-light расчёт transport/clip/source appearance и общий SVG contract для обеих поверхностей вместо новой копии алгоритма;
- реальные и manual/virtual источники, marker-owned radius/color/brightness;
- стены, независимые перегородки, колонны, room drafts, zero-thickness walls,
внутренние двери/ворота/passages и наружные проёмы по канону
docs/LIGHT.md; - текущие room-level/global Glow gates, data fills и Glow base/tunnels;
- включение/выключение источника, смена цвета/яркости, перемещение маркера, изменение конфигурации и состояния проёма без перезагрузки карточки;
- те же bounded fade, reduced-motion и source-failure semantics;
- bounded caches и отдельный performance-профиль opt-in static card;
- regression-тесты default-off, parity, окклюзии, lifecycle и inert surface;
- UI/i18n, RU/EN guide,
docs/LIGHT.md, testing contract и оба changelog.
Не-скоуп
- включение
light_poolsпо умолчанию или миграция существующих Lovelace cards; - изменение визуального результата Glow полной карточки;
- новая упрощённая/low-quality модель света, лимит количества источников, адаптивное отключение теней или скрытая деградация на слабых устройствах;
- sunlight/window wedges: это независимая система
docs/SUN.md; - hover/click/more-info/toggle на самой схеме static card;
- изменение footer, deep link, title/crop, room/device presentation или
show_button; - новый глобальный House Plan setting, per-space setting или сохранение флага в backend config: флаг принадлежит конкретному Lovelace card instance;
- изменение редакторов геометрии и скрытой изометрии;
- изменение алгоритмов стен, проёмов, clean floor или Optimize Plans;
- повышение существующих performance/bundle budgets без отдельного решения по измеренному Linux artifact.
Контракт поведения
1. Конфигурация
type: custom:houseplan-space-card
space: ground_floor
light_pools: true
- Поле отсутствует или равно
false: static card не строит barrier/visibility scene, не создаёт SVG pool layer и сохраняет текущий результат. - Поле равно
true: карточка строит полноценный Glow выбранного пространства. - Visual editor показывает один boolean с локализованным названием. Изменение применяется стандартным Lovelace config update без backend write.
- Неизвестные значения не получают новой truthy-семантики: включением является
только нормализованное boolean
true, как у остальных boolean полей editor.
2. Источники и видимость
- Источники разрешаются через тот же
resolvedLightSources()иselectSpatialGlowSource(), что в полной карточке. Один marker владеет не более чем одним spatial pool даже при смене resolved HA entity. - Реальный, manual virtual и связанный virtual light дают тот же pool при тех же state, marker settings и позиции. Hidden/removed/non-spatial devices не начинают светить из-за новой поверхности.
resolveGlowAppearance()иglowAlpha()остаются единственным контрактом цвета, яркости и центральной alpha. Per-markerglow_colorиglow_radius_cmимеют тот же приоритет над palette/global radius.- Off/unknown/unavailable источник pool не рисует; on источник рисует. Переход on↔off и смена resolved source сохраняют marker-stable identity и тот же bounded fade, что полный план.
live_states:falseпродолжает подавлять обычное state/activity оформление маркеров, но не выключает Glow. Это существующая независимость Glow от device display полной карточки.
3. Геометрия и тени
- Для одинакового snapshot, HA states, пространства, позиции и радиуса обе карточки получают одинаковые barrier fingerprint, floor и lit clip paths с точностью существующего geometry tolerance.
- Стена блокирует свет своим реальным телом и толщиной, а не центральной осью. Перегородка, колонна, draft и solid zero wall непрозрачны; dashed zero wall прозрачен.
- Внутренняя door/gate пропускает только квантованную открытую часть; passage прозрачен полностью. Window и opening без пола с обеих сторон остаются непрозрачными.
- Источник внутри opaque plan body fail-dark: ни половинчатого pool, ни засвеченного tunnel не появляется.
- Visibility region пересекается со всем floor пространства до visual clip по Glow-enabled rooms. Поэтому свет может пройти через отключённую для покраски комнату, но пиксели pool/base появляются только в комнатах, где effective room Glow включён.
- Вырожденная или повреждённая геометрия сохраняет существующий bounded fail-dark/fallback и один диагностический warning на revision/room, а не падает и не освещает сквозь препятствие.
4. Слои и визуальная parity
- Порядок static scene становится: paper/backdrop → data room/tunnel fills → Glow base/tunnels → radial pools → стены/openings → labels/devices. Pool не перекрывает стену, маркер или подпись.
- Один источник создаёт один painted radial field и один clip; второго spill, sector, tunnel-light или shadow-mask слоя нет.
GLOW_FALLOFF, gradient units, blend mode, isolation, edge feather и fallback composition совпадают с полной карточкой и читаются из общего контракта, а не из двух наборов констант.- Data fill с валидным цветом/alpha остаётся видимым по действующим правилам; Glow base добавляется только там, где его добавляет полный renderer.
- Light/dark theme не меняет физическую область света; цвет и alpha приходят из существующей palette/marker state. Day-cycle background продолжает жить под тем же pool.
5. Lifecycle и кеши
- Каждый
houseplan-space-cardвладеет своим bounded Glow runtime: transition state, timers, last appearance и per-source clips не протекают между card instances и освобождаются при disconnect. - Barrier cache зависит от полного geometry fingerprint и отсортированной сигнатуры квантованных interior opening amounts. Обычный HA tick не перестраивает geometry; смена состояния проёма перестраивает ровно нужную сцену.
- Clip cache дополнительно зависит от space, barrier fingerprint, source position и radius. Перемещение marker/config revision не показывает stale shadow.
- Continuity snapshot/recovery не очищает уже видимый корректный кадр раньше действующего контракта. После нового authoritative snapshot pool и marker соответствуют одной device/config revision.
- При
light_pools:falseruntime не ставит transition timers и не выполняет visibility/boolean work. Выключение флага очищает оставшиеся timers/caches и удаляет layer на следующем render.
UX и доступность
- Visual editor: boolean рядом с live-state visual settings; название явно предупреждает, что это одновременно pools и wall shadows.
- Помогающий текст в guide сообщает, что режим тяжелее default static path; отдельный warning dialog/toast не добавляется.
.hp-static-stageостаётсяpointer-events:none; Glow layer имеетaria-hidden="true"и не участвует в tab order/accessible name.- Footer остаётся единственной интерактивной частью card.
- На desktop, wall tablet и phone визуальная модель одинакова; touch не включает
новое действие.
prefers-reduced-motionиспользует существующий reduced transition contract без потери финального светового состояния.
Модель данных, миграция и совместимость
Публичный Lovelace config расширяется одним полем:
interface SpaceCardConfig {
light_pools?: boolean;
}
- default в
setConfig()и stub config —false/отсутствие; - schema House Plan, backend store, model version и config migration не меняются;
- существующие YAML и GUI cards без поля визуально и по runtime cost не меняются;
- visual editor round-trip сохраняет явное значение стандартным механизмом HA;
- откат безопасен простым удалением поля или
light_pools: false; - deprecated alias
show_light_shadowsне принимается и не записывается: у функции один канонический ключ.
i18n
Добавить один editor key во все поставляемые словари
src/i18n/{en,ru,de,fr}.json:
- EN:
Light pools and wall shadows; - RU:
Световые пулы и тени от стен; - DE/FR — эквивалентный перевод без английского fallback.
Новых runtime errors, toast, dialogs или ARIA-фраз нет.
Архитектурный контракт
- Канонические transport и pool primitives выносятся из приватного монолита в
framework-light shared module(s). Допустимое разбиение:
- pure barrier/clip scene builder с явными geometry inputs;
- shared source-to-spot resolver;
- shared SVG pool template/константы;
- caller-owned bounded runtime/cache adapter.
houseplan-cardиspace-renderмогут по-разному собирать входы, но не содержат две реализации aperture classification, visibility intersection, source guard, falloff или SVG field.- Shared module не читает DOM, глобальный
hass, приватные поля карточки и не владеет неограниченным module-level cache. Side effects — только переданные callbacks диагностики/transition scheduling. - Существующая full-card output должна быть regression-equivalent. Extraction не является поводом менять её geometry, identifiers, blend/fade или budgets.
- Static renderer получает marker positions в plan coordinates до процентного преобразования devlayer и использует тот же frame/viewBox, что его SVG.
Точные имена shared files/functions — техническая деталь реализации, но ревьюер должен видеть один алгоритмический источник истины по пяти пунктам выше.
Производительность и bundle
- Default-off static card не вызывает
visibilityPolygon, boolean intersection и barrier build. Измеренный cold/warm default сценарий не должен регрессировать сверх действующего шума/бюджета. - Opt-in использует fingerprinted barrier cache один раз на geometry/opening revision и bounded per-source clip cache; обычные state ticks переиспользуют геометрию.
- Добавить кандидатный профиль
large-space-card-glow-overlay-v1либо эквивалентное расширение канонического Glow profile: cold first render, warm HA tick, opening-state invalidation и teardown для одной large static card. - Порог утверждается только из полного Linux performance artifact по
demo/performance/README.md; локальное число не становится budget. - Скрытый source cap, sampling и повышение существующего
large-house-glow-overlay-v1запрещены. - Shared extraction не добавляет runtime dependency. Bundle budget может измениться только в пределах действующего gate; повышение требует отдельного решения и не входит в #374.
Touch, темы и размеры
- static stage остаётся inert на touch и mouse;
- pool использует plan coordinates и сохраняет геометрию при любом CSS width,
DPR и
title: ""compact frame; - screen-space edge feather пересчитывается из фактической ширины stage, чтобы не становиться толще/тоньше на phone и desktop;
- light/dark, reduced motion и continuity overlay входят в blocking browser matrix;
- никакого horizontal overflow, нового scroll/gesture listener или захвата pointer нет.
Затронутые файлы и модули
Ожидаемый минимум (точное разбиение может уточнить реализация):
src/houseplan-card.ts— перейти на shared Glow scene/render contract без изменения полного плана;- новый
src/light-scene.tsи/илиsrc/glow-render.ts— общий transport, spots, SVG и bounded runtime primitives; src/space-render.ts— opt-in layer и передача static geometry/positions;src/space-card.ts—light_pools, per-instance runtime/lifecycle и stage measurement;src/space-editor.ts— visual boolean;src/types.tsпри необходимости общего config/runtime type;src/i18n/{en,ru,de,fr}.json;- unit-тесты light scene/cache/source parity;
demo/smoke_space_card.mjsи/или отдельный targeted static Glow smoke;demo/performance/**— opt-in static profile;docs/LIGHT.md,docs/ARCHITECTURE.md,docs/TESTING.md;docs/USER-GUIDE.md,docs/USER-GUIDE.ru.md;docs/CHANGELOG.md,docs/CHANGELOG.ru.md;- canonical Docs screenshots manifest/artifact по правилу репозитория.
Критерии приёмки
- AC1 — opt-in и обратная совместимость: omitted/
falseне создаёт pool layer и не выполняет тяжёлую light geometry;trueсоздаёт layer, visual editor и YAML дают один результат, неизвестныйspaceсохраняет текущую error card. Доказательство: unit config contract + browser smoke с тремя static cards (omitted,false,true). - AC2 — визуальная parity источника: одинаковая full/static fixture с одним real light и одним manual/linked virtual light даёт одинаковые center, radius, color, alpha, falloff stops, clip geometry и один painted field на marker. Доказательство: shared-module unit + browser DOM/SVG assertions + semantic pixel witness light/dark.
- AC3 — окклюзия parity: pool проходит через открытые interior door/gate, passage и dashed zero wall; закрывается закрытым проёмом, window, exterior door, solid zero wall, partition и column; source inside masonry fail-dark. Full и static расходятся не более существующего raster tolerance. Доказательство: shared geometry unit + targeted browser smoke на канонической lighting fixture.
- AC4 — rooms/fills/layering: global/per-room Glow gate, data fill, Glow base и opening tunnels совпадают с full card; transport проходит через disabled room, но там не красит; стены, symbols, labels и devices остаются поверх pool. Доказательство: browser smoke + reviewed light/dark visual artifact.
- AC5 — live lifecycle: on/off, brightness/RGB, resolved source swap,
marker move, config revision и door amount обновляют ожидаемую часть сцены;
fade bounded, reduced motion корректен, stale clip не остаётся и timers
исчезают после disconnect/flag-off. Доказательство: fake-timer/cache unit
- browser smoke.
- AC6 — static surface остаётся static: при включённом pool hover/click/tap
по pool, стене, комнате и marker не вызывают action/more-info; footer deep
link работает; phone/desktop и
title: ""не меняют plan-coordinate геометрию. Доказательство: browser pointer/touch smoke на двух widths. - AC7 — производительность и bounded memory: default-off не регрессирует; opt-in cold/warm/opening invalidation соответствует утверждённому Linux профилю; barrier не перестраивается на обычном HA tick, caches bounded и teardown очищает instance state. Доказательство: instrumentation unit + полный performance artifact без повышения действующих budgets.
- AC8 — full card без регрессии: существующие light visibility, opening,
zero-wall, Glow smoke/golden и
large-house-glow-overlay-v1проходят после shared extraction без принятых визуальных дельт полной карточки. Доказательство: unit + полный Linux smoke/golden/performance artifact. - AC9 — документация, i18n и релиз: visual editor локализован во всех
поставляемых языках; RU/EN guide описывает default, YAML и performance cost;
docs/LIGHT.mdбольше не утверждает, что static card всегда без pools; оба changelog содержат #374. Доказательство: i18n/config tests,check-docs, docs screenshot artifact и ревью кода. - AC10 — сборка и budgets: typecheck, unit, build, bundle sync/budget, process/docs gates и Validate на точном SHA зелёные; новых зависимостей и повышения budget нет. Доказательство: CI checks ветки.
План автотестов
- Pure unit для shared barrier builder: wall body, interior open/closed aperture, passage, window/exterior opening, partition, column, dashed/solid zero wall, malformed fallback и deterministic fingerprint.
- Pure unit source-to-spot: real/manual/linked virtual, hidden, off, unavailable, RGB/brightness, marker radius/color, fail-dark и stable key при source swap.
- Instrumented cache/runtime unit: geometry build count на first render,
ordinary HA tick, door quantization change, marker move, config revision,
light_pools:falseи disconnect; fake timers не остаются. - Расширить static browser harness двумя одинаковыми экземплярами пространства: default-off control и opt-in. Проверить отсутствие/наличие layer и inert DOM.
- На той же canonical lighting fixture рядом снять full card и static card; сравнить source centers/radii/gradient stops/clip topology и pixels в точках «у источника», «через проём», «за колонной», «за окном/наружной дверью».
- Повторить для light/dark, 390/900 px, DPR 1/2,
titleomitted/empty и reduced motion; CSS scaling не меняет plan-coordinate clip. - Mutation witnesses: вернуть unconditional no-pool static path; пропустить wall occluder; разрешить exterior door; не очистить runtime при flag-off — соответствующие targeted checks должны падать.
- В цикле реализации:
npm run typecheck,npm test,npm run build; перед S7 — targeted browser smoke. Полные golden/smoke/performance и принятие Docs screenshots выполняются только по каноническому Linux artifact процесса.
Риски
- Расхождение двух моделей света. Снижается shared pure scene/render
contract и cross-surface AC2/AC3; копия
_renderGlowLayer()запрещена. - Регрессия полной карточки при extraction. Снижается output-equivalence, полными существующими light goldens/smokes и запретом попутной смены констант.
- Default static card становится тяжёлой. Снижается точным boolean gate до barrier/visibility work, instrumentation и default-off performance case.
- Неправильный слой закрывает стены/маркеры. Снижается явным render order и semantic pixel witness.
- Stale тень после проёма/позиции. Снижается полным fingerprint/cache key и lifecycle test.
- Несколько static cards делят timers/caches. Снижается per-instance owner, bounded LRU и teardown test.
- Feather зависит от CSS size. Снижается stage measurement и DPR/width matrix.
- Docs screenshots fingerprint устаревает после
src/**. Снижается только полным canonical workflow и запретом частичного принятия artifact.
Откат
Пользовательский откат — удалить light_pools или установить false; данные и
House Plan config не меняются. Кодовый откат удаляет static adapter/editor key,
но сохраняет shared extraction, если она regression-equivalent и продолжает
обслуживать full card. Если shared extraction сама является причиной регрессии,
она откатывается целиком вместе с static layer. Обратной миграции нет.
Release-артефакты
docs/CHANGELOG.mdиdocs/CHANGELOG.ru.md: opt-in полный Glow static card со ссылкой на #374;docs/USER-GUIDE.mdи.ru.md: YAML, defaultfalse, visual setting,live_statesindependence и performance warning;docs/LIGHT.md/ARCHITECTURE.md: две поверхности одного канонического transport/runtime contract;docs/TESTING.md: static-card Glow parity matrix;- reviewed browser artifact: full/static рядом, light/dark и narrow/wide с открытым проёмом и тенью за колонной;
- полный Linux golden/smoke artifact; визуальная дельта full card не принимается;
- полный Linux performance artifact с default-off и opt-in static profiles, без повышения существующих budgets;
- canonical Docs screenshots workflow после
src/**; принять только полный reviewed artifact черезnpm run docs:accept -- --reviewed --from=<artifact>и закоммитить актуальныйdocs/images/screenshots.json; - backend, migration и security artifacts не требуются; bundle/process gates обязательны.
Принято предположительно, поменять свободно
- точные имена и границы shared
light-scene/glow-renderмодулей; - форма caller-owned runtime API и конкретные размеры LRU, если они не меньше действующих возможностей и остаются bounded;
- способ передать static wall-union/opening inputs без повторного boolean pass;
- имя targeted smoke и конкретные synthetic fixtures;
- точные raster tolerances берутся из действующих Glow tests, а не вводятся шире ради прохождения новой проверки;
- настройка стоит рядом с
live_statesв visual editor; отдельная секция формы не создаётся; - documentation warning остаётся текстовым: dialog/toast при включении не нужен.