mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
docs: specify registryless opening entities
Issue: #117 User-Visible: no
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
# Issue #117 — registry-less YAML entity работает у проёма в View
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/117
|
||||
- **Статус документа:** готово к будущей реализации; issue остаётся на `S3-spec`
|
||||
- **Приоритет:** P2
|
||||
- **Тип:** bug, обычный трек
|
||||
- **Пользовательское изменение:** да
|
||||
|
||||
## 1. Проблема
|
||||
|
||||
Picker проёма принимает точную live YAML entity без `unique_id`: у неё есть
|
||||
`hass.states[entity_id]`, но нет Entity Registry row. Live availability уже
|
||||
считает такую ссылку active. Immutable render path дополнительно требует
|
||||
`_renderPlanHass.entities[entity_id]`, поэтому сохранённый contact/lock не влияет
|
||||
на отрисованный проём.
|
||||
|
||||
Это разрыв одного exact-binding contract между editor, action и painted frame.
|
||||
|
||||
## 2. Установленная причина
|
||||
|
||||
`activeRegistryHass()` намеренно сохраняет live states registry-less entities и
|
||||
удаляет states только при явном disabled/orphaned registry evidence. Однако
|
||||
`renderOpeningEntityAvailable()` требует одновременно:
|
||||
|
||||
```ts
|
||||
projectedHass.entities[entityId] && projectedHass.states[entityId]
|
||||
```
|
||||
|
||||
У YAML entity первая часть всегда отсутствует. При этом `_openingAmt()` и lock
|
||||
badges используют именно frame-local helper. Issue #104 сознательно оставил эту
|
||||
часть отдельной и отметил parity как #117.
|
||||
|
||||
## 3. Цели
|
||||
|
||||
1. Если exact entity предлагается picker и имеет active live state, её contact
|
||||
или lock работает в painted frame.
|
||||
2. Сохранить запрет disabled/orphaned/missing entities.
|
||||
3. Сохранить immutable render snapshot: ни один слой не читает raw live hass в
|
||||
середине кадра.
|
||||
4. Не связывать opening reference с marker lifecycle/tombstone.
|
||||
|
||||
## 4. Не входит в задачу
|
||||
|
||||
- добавление Entity Registry row или `unique_id`;
|
||||
- изменение candidate picker;
|
||||
- миграция `opening.contact`/`opening.lock`;
|
||||
- marker lifecycle и removed bindings;
|
||||
- изменение lock security/confirmation;
|
||||
- generic поддержка registry-less entities во всех marker bindings;
|
||||
- новые opening types или geometry.
|
||||
|
||||
## 5. Каноническая availability policy
|
||||
|
||||
### 5.1 Live path
|
||||
|
||||
`openingEntityAvailable(hass, entityId, snapshot)` остаётся authoritative для
|
||||
picker, info card и lock action. Exact opening reference active, когда
|
||||
`resolveHaBindingStatus(...).kind === 'active'`.
|
||||
|
||||
### 5.2 Render path
|
||||
|
||||
`renderOpeningEntityAvailable(projectedHass, entityId)` принимает только
|
||||
projection, построенную `activeRegistryHass()` и замороженную в общем render
|
||||
snapshot.
|
||||
|
||||
В такой projection наличие state является достаточным frame-local доказательством:
|
||||
|
||||
```ts
|
||||
return !!entityId && !!projectedHass.states?.[entityId];
|
||||
```
|
||||
|
||||
Отсутствующая registry row не является доказательством disabled. Явно disabled
|
||||
entity/device и authoritative orphan не проходят, потому что
|
||||
`activeRegistryHass()` удаляет их state до capture.
|
||||
|
||||
Запрещено вызывать render helper с raw `this.hass`. Тип/имя параметра,
|
||||
комментарий и source-contract test должны сохранять эту trust boundary.
|
||||
|
||||
## 6. Матрица поведения
|
||||
|
||||
| Entity | Live helper | Render helper | Результат |
|
||||
| --- | --- | --- | --- |
|
||||
| registry row active + state | active | active | работает |
|
||||
| YAML/no registry row + live state | active | active | работает |
|
||||
| registry entity `unavailable/unknown` | active | active | существующая unknown semantics |
|
||||
| explicit `disabled_by` entity | inactive | state удалён | не работает |
|
||||
| explicit disabled parent device | inactive | state удалён | не работает |
|
||||
| authoritative orphan parent | inactive | state удалён | не работает |
|
||||
| missing state | inactive/missing | inactive | не работает |
|
||||
| limited registry + exact live state | active | active | работает |
|
||||
| marker tombstone того же entity | не влияет | не влияет | opening работает |
|
||||
|
||||
`unavailable/unknown` здесь означает, что exact reference существует; текущая
|
||||
opening renderer semantics решает, рисовать closed/unknown и badge state. #117
|
||||
не подменяет эти states active/open.
|
||||
|
||||
## 7. Render snapshot
|
||||
|
||||
Capture уже добавляет `opening.contact` и `opening.lock` в `entityIds`. После
|
||||
исправления необходимо доказать:
|
||||
|
||||
- registry-less state входит в `_renderPlanHass.states`;
|
||||
- один кадр использует одну и ту же state projection для leaf amount, tone,
|
||||
lock badge и tooltip data;
|
||||
- HA tick заменяет snapshot атомарно;
|
||||
- config/registry update не смешивается с предыдущим state;
|
||||
- static `houseplan-space-card` не получает новую интерактивность.
|
||||
|
||||
Нельзя исправлять баг чтением `this.hass.states` непосредственно в
|
||||
`_openingAmt()` или `_renderOpeningLocks()`.
|
||||
|
||||
## 8. Contact contract
|
||||
|
||||
Для door/window/gate с registry-less contact:
|
||||
|
||||
- state проходит текущий `openingAmount(type, state, invert)`;
|
||||
- invert сохраняется;
|
||||
- open tone/leaf и opening info согласованы;
|
||||
- неизвестный state не становится самовольно open;
|
||||
- скрытие opening symbols не меняет физическую/light semantics, определённую
|
||||
существующим opening contract.
|
||||
|
||||
## 9. Lock contract и безопасность
|
||||
|
||||
Registry-less `lock.*`:
|
||||
|
||||
- показывает badge/state по той же матрице, что registry lock;
|
||||
- info card использует live exact availability;
|
||||
- lock/unlock action остаётся только в явно открытой info card;
|
||||
- unlock по-прежнему требует confirmation;
|
||||
- перед service call выполняется live availability check;
|
||||
- tap по plan badge/opening не получает новую actuation семантику.
|
||||
|
||||
#117 не ослабляет secure-device rules универсального toggle.
|
||||
|
||||
## 10. Совместимость
|
||||
|
||||
- config/storage/API не меняются;
|
||||
- существующие registry-backed openings pixel-identical;
|
||||
- disabled/missing cases не становятся видимыми;
|
||||
- Plan/View/kiosk touch gestures и hit targets не меняются;
|
||||
- no new i18n key;
|
||||
- backend не меняется.
|
||||
|
||||
## 11. Acceptance criteria
|
||||
|
||||
1. Registry-less live contact, выбранный picker, меняет opening presentation.
|
||||
2. Registry-less live lock показывает корректный badge/info state.
|
||||
3. Full-card frame использует immutable active projection, не raw hass.
|
||||
4. Active registry entity сохраняет прежнее поведение.
|
||||
5. Disabled entity/device, authoritative orphan и missing state остаются
|
||||
unavailable.
|
||||
6. Limited-registry live exact entity работает.
|
||||
7. Marker tombstone не блокирует independent opening reference.
|
||||
8. Lock action security и unlock confirmation не меняются.
|
||||
9. Contact/lock state update не создаёт geometry/config rebuild.
|
||||
10. Existing opening golden и interactions не регрессируют.
|
||||
|
||||
## 12. План тестирования
|
||||
|
||||
### Unit
|
||||
|
||||
- расширить `ha-binding-status.test.mjs` для render helper:
|
||||
registry-backed, registry-less, disabled entity, disabled parent, orphan,
|
||||
missing state, limited registry;
|
||||
- mutation: вернуть requirement `.entities[entityId]` — YAML case красный;
|
||||
- `openingAmount` registry-less state/invert parity.
|
||||
|
||||
### Browser smoke
|
||||
|
||||
- YAML-like `binary_sensor` без registry row: closed → open → closed;
|
||||
- YAML-like `lock`: locked/unlocked/unknown badge и info;
|
||||
- disabled row со stale live state не отображается;
|
||||
- same entity tombstoned as marker, opening всё ещё работает;
|
||||
- render snapshot atomicity на state tick;
|
||||
- no service call from direct opening/badge tap; explicit info action unchanged.
|
||||
|
||||
### Регрессия
|
||||
|
||||
- existing opening/contact/lock smokes;
|
||||
- `test/render-device-snapshot.test.mjs`;
|
||||
- typecheck, full unit и build.
|
||||
|
||||
Golden обновлять не нужно: registry-backed baseline не меняется. Новый YAML case
|
||||
проверяется targeted smoke/DOM state; screenshot допустим как новое доказательство,
|
||||
но не требует переакцептации старых сцен.
|
||||
|
||||
## 13. План реализации
|
||||
|
||||
1. Исправить frame-local helper и его trust-boundary comment.
|
||||
2. Добавить unit matrix и mutation guard.
|
||||
3. Добавить registry-less contact/lock smoke.
|
||||
4. Проверить snapshot/action parity и regression suite.
|
||||
|
||||
## 14. Документация и release-артефакты
|
||||
|
||||
- оба changelog получают user-visible bug-fix пункт;
|
||||
- `docs/USER-GUIDE.ru.md`/diagnostics уточняет, что exact YAML entity без
|
||||
`unique_id` поддерживается у contact/lock;
|
||||
- `docs/ARCHITECTURE.md` или HA binding doc фиксирует render projection rule;
|
||||
- новых RU/EN строк нет;
|
||||
- performance/golden/full backend harness не требуются;
|
||||
- targeted browser smoke обязателен перед S7 по AC.
|
||||
|
||||
## 15. Риски и откат
|
||||
|
||||
| Риск | Мера |
|
||||
| --- | --- |
|
||||
| Raw hass обходит immutable frame | helper принимает только projection + source test |
|
||||
| Disabled state случайно оживает | complete disabled/orphan matrix |
|
||||
| Lock security расширяется | отдельные render/live action assertions |
|
||||
| Marker tombstone снова влияет | independent-reference unit/smoke |
|
||||
|
||||
Откат возвращает прежний render helper. Данные не мигрируют; сохранённые exact
|
||||
references остаются в config.
|
||||
|
||||
## 16. Принятые технические предположения
|
||||
|
||||
- `activeRegistryHass()` остаётся единственным producer render projection;
|
||||
- наличие state в этой projection достаточно для exact opening reference;
|
||||
- unavailable/unknown считаются существующей entity, но не автоматически open;
|
||||
- `houseplan-space-card` остаётся статическим и не получает actions.
|
||||
@@ -86,6 +86,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#94](https://github.com/Matysh/houseplan-card/issues/94) Универсальное действие «Переключить состояние» | [094-universal-state-toggle.md](094-universal-state-toggle.md) |
|
||||
| [#101](https://github.com/Matysh/houseplan-card/issues/101) Плавный переход View ↔ редакторы | [101-view-editor-transition.md](101-view-editor-transition.md) |
|
||||
| [#107](https://github.com/Matysh/houseplan-card/issues/107) Переключение виртуального источника света «Всегда» | [107-virtual-light-toggle.md](107-virtual-light-toggle.md) |
|
||||
| [#117](https://github.com/Matysh/houseplan-card/issues/117) Registry-less entity у проёма | [117-registryless-opening-entity.md](117-registryless-opening-entity.md) |
|
||||
| [#122](https://github.com/Matysh/houseplan-card/issues/122) Изометрический режим Stage 2: скрытый режим и визуальная полировка | [122-isometric-stage2.md](122-isometric-stage2.md) |
|
||||
| [#123](https://github.com/Matysh/houseplan-card/issues/123) Split из вершины не меняет наружную геометрию стен | [123-corner-split-wall.md](123-corner-split-wall.md) |
|
||||
| [#132](https://github.com/Matysh/houseplan-card/issues/132) Проёмы в независимых стенах (+ bug [#185](https://github.com/Matysh/houseplan-card/issues/185)) | [132-partition-openings.md](132-partition-openings.md) |
|
||||
|
||||
Reference in New Issue
Block a user