Files
houseplan-card/docs/specs/437-summary-panel.md
T
2026-09-08 17:11:44 +03:00

726 lines
66 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# #437 — Конфигурируемая сводная панель поверх плана
Issue: [#437](https://github.com/Matysh/houseplan-card/issues/437).
Ветка: `issue/437-summary-panel`; база аналитики: `dev@ed9ee026`.
Редакция: 2026-09-08. Полный трек, P2/feature.
Канонический статус — метка issue, не заголовок этого документа.
**Команда владельца: пройти ревью ТЗ до S5-ready и остановиться. Реализацию не начинать.**
## 1. Сценарий и результат для человека
Администратор дома настраивает общую сводку один раз. Домочадцы читают её
поверх плана в обычной карточке, полноэкранной панели, на телефоне или настенном
планшете; могут скрыть её на своём экране, не меняя чужие экраны.
**До:** показатели собирают псевдотаблицами из декора на плане.
**После:** нужные показатели находятся в компактной панели справа или снизу,
настраиваются одним окном и не меняют размер самого плана.
Это ограниченное исключение из запрета general dashboard framework в
[SCOPE](../SCOPE.md), одобренное владельцем 2026-09-03: контекстная read-only
сводка при пространственном View, не новый конструктор dashboard.
Поглощённые сценарии: [#83](https://github.com/Matysh/houseplan-card/issues/83)
и [#149](https://github.com/Matysh/houseplan-card/issues/149).
Оценка: пользовательская ценность 8/10, разработческая 6/10,
сложность 7/10, риск 7/10. `small` не подходит: новый UX-контракт,
несколько поверхностей, persistent shared/local state, touch и performance.
## 2. Источники решений и границы
Приоритет: позднейшее решение владельца → этот согласованный контракт →
Dashboard 5. Старые предложения о выделенной колонке и сворачивании отменены.
- [Dashboard 5, сообщение дизайнера](https://github.com/Matysh/houseplan-card/issues/437#issuecomment-5585797767).
- [Принятые Q1–Q5 и компоновка](https://github.com/Matysh/houseplan-card/issues/437#issuecomment-5586002352).
- [Скрытие при недостатке места](https://github.com/Matysh/houseplan-card/issues/437#issuecomment-5586088350).
- [Начало номерного ТЗ и ограничение S5](https://github.com/Matysh/houseplan-card/issues/437#issuecomment-5586184163).
Архив `Dashboard-5-Issue-437-handoff-2026-09-08.zip`, SHA256
`fca6d0c7aa45cf16f4350c4f8790ed8fa475bd88d3809d9536ad8f8f9a016597`:
`Dashboard 5/index.html`, CSS/JS, SVG, README/DEVELOPER-HANDOFF/
IMPLEMENTATION-NOTES/ISSUE-HANDOFF и документ финальных изменений Dashboard 4.
На этапе аналитики прочитаны документы и исходники; работающий продукт и
браузерные скриншоты **этим не проверены**. Прототип не импортировать как код.
В scope: overlay, составной контрол, одна форма настроек, блоки/строки,
источники HA и три встроенных показателя, shared/local persistence,
мобильность, киоск, явный доступ к локальным размерам и их применение в View.
Не в scope: графики/история, формулы и атрибуты как самостоятельные источники,
Lovelace-карточки внутри панели, HTML/CSS/JS пользователя, сервисные действия
по строке, автоматические агрегаты тревог/климата, вложенные блоки,
сворачивание, ручное перемещение панели, профили состава по карточкам,
изменение геометрии/редакторов, интерактивность `houseplan-space-card`.
Статичная карточка пространства не получает эту панель.
## 3. Текущее устройство кода и точки изменения
Ориентиры на базе выше; это план изменений, не уже выполненная реализация:
- `src/houseplan-card.ts`: `.stage`, `_stageEl`, ResizeObserver,
`_canManageConfiguration`, `_sendConfigCandidate`, `_roomArea`, `_cleanFloor`,
`_renderKioskDialog`; `src/styles/chrome.styles.ts` — шапка/киоск.
- `src/houseplan-panel.ts`: full-card wrapper уже получает `narrow`, но не
передаёт его карточке. `src/types.ts`: `ServerConfig`, `CardConfig`.
- `src/devices.ts`: `buildDevices` — **не готовый общий счётчик**:
фильтрует marker visibility, registry и активное пространство.
`src/ha-binding-status.ts` — полнота registry и разрешение привязок.
- `src/wall-thickness.ts`: `innerContourForRoom`;
`src/physical-geometry.ts`: `floorMinusBodies`, `geometryArea`.
- Backend: `websocket_api.py` (`config/get`, `config/set`), `validation.py`,
`store.py`, `auth.py`, `import_export.py`, `support_package.py` в
`custom_components/houseplan/`.
Выделить небольшие pure модули model/validation, source resolution,
visibility/layout и local preferences; отдельное представление панели и
лениво загружаемую форму. Не добавлять второй монолит в `houseplan-card.ts`,
не копировать существующую геометрическую или placement-семантику.
Shared конфигурация идёт через существующий optimistic-locking путь.
## 4. Пользовательский контракт
### 4.1 Показ и управление
В шапке House Plan — составной контрол без видимого текста:
слева шестерёнка настроек, справа значок панели показать/скрыть.
Раздельные hit targets, Tab-фокус, доступные имена и подсказки.
Правая часть имеет `aria-pressed`, отражающий **сохранённое локальное желание
показа**, а не временное скрытие mobile/fits. В таком случае подсказка поясняет
причину скрытия. Внутри сводки только её пользовательский заголовок; без
дублирующей шестерёнки и крестика.
Панель имеет только «показана / скрыта», не collapsed/expanded. При скрытии
конфигурация сохраняется. Переключение правой кнопкой применяется немедленно
локально, без серверной записи. Состояние переживает reload.
При отсутствии локального предпочтения панель выключена; обновление установки
не навязывает новый overlay. Первое явное включение показывает defaults §6.
В View блоки всегда развёрнуты, показываются только включённые и подходящие
текущему пространству. Порядок как в настройках. Если подходящих блоков нет,
сохраняются заголовок и нейтральная строка «Нет показателей для этого пространства»;
новые блоки автоматически не добавляются. Строки не открывают more-info и
не вызывают сервисов HA.
В редакторах плана, устройств и подложки overlay и его View-контролы скрыты;
при возврате в View восстанавливаются по тем же условиям. Нет структурного
редактирования в самом overlay и нет перетаскивания строк в View.
### 4.2 Одна форма настроек
Поля в порядке Dashboard 5: «Название панели», «Показывать панель» (локальное),
«Отображать на мобильных устройствах» (общее), блоки, «Добавить блок»,
«Отмена» и «Сохранить». Весь диалог — один draft; panel preview вне диалога
продолжает показывать сохранённые данные до успешного Save.
Открытие доступно и при скрытой панели в пределах прав §8.
На узком экране форма использует доступную ширину с отступами; скролл внутри
тела, footer с действиями доступен при экранной клавиатуре. Dropdown не выходит
за границы диалога/visual viewport, переносится вверх при нехватке места снизу.
«Сохранить» активно только после реального валидного изменения; trim для
проверки пустоты/лимитов, пользовательские тексты хранятся без наружных пробелов.
Отмена/X/Escape отбрасывают весь draft, включая локальный показ.
Не добавлять подтверждение несохранённых правок.
Ошибки у конкретных полей, фокус на первой при попытке Save.
### 4.3 Блоки и строки
Блок: заголовок, видимость, область «Все пространства» либо
«Определённое пространство» с выбором одного stable id, упорядоченные строки.
Новый блок: видимый, все пространства, пустой список с «Добавить значение»;
заголовок обязателен. Пустой список разрешён, пустая созданная строка — нет.
Перестановка блоков/строк: drag handle и кнопки «Выше»/«Ниже» с disabled
на границах. Удаление непустого блока подтверждается существующим confirmation
controller; удаление остаётся draft и отменяется общим Cancel.
Удаление строки/пустого блока не требует дополнительного подтверждения.
Строка: обязательное название + один источник. Picker содержит группы
«Системные показатели» и «Сущности Home Assistant». Полный доступный пользователю
`hass.states`, не плановый `_renderPlanHass`, без фильтра по размещению/домену.
Поиск по friendly name и entity_id, идентификатор виден при одинаковых именах.
Новые Template-сущности HA появляются после обновления states; формулы
создаются только в HA. Структура и значения выводятся как текст, не HTML.
Ограничения включают выключенные блоки: 10 блоков; 20 строк в блоке;
48 Unicode code points для заголовка панели/блока, 64 для названия строки.
На лимите Add disabled с объяснением. Все заголовки/названия непустые.
### 4.4 Удалённые ссылки — принятый Q5
Сравнение с последней подтверждённой сервером конфигурацией **по stable id**,
а не по позиции, тексту или равенству всего объекта:
| Ситуация | Что сохраняется и показывается |
|---|---|
| Старая entity пропала/стала недоступна/не видна пользователю | Точный entity_id сохраняется, warning в форме, нейтральное отсутствие данных в View; другие поля редактируются. |
| Старое пространство удалено | Его id сохраняется с warning, блок не показывается на другом пространстве; не подменять `all` или первым этажом. |
| Переименование/перестановка строки или блока со старой сломанной ссылкой | Разрешено; ссылка считается прежней, даже если текст/индекс изменён. |
| Новая строка без источника или новый scope без id | Ошибка, Save запрещён. |
| Новый/заменённый entity_id или space_id | Проверить корректность и существование/доступность выбора на момент Save; исчезнувший новый выбор — ошибка, не старый warning. |
| Существующая readable entity имеет `unavailable`/`unknown` | Допустимый конкретный выбор; состояние отображается честно, не нулём. |
Старый невалидный объект целиком не служит разрешением на произвольную новую
ссылку. Перенос строки между блоками (если реализован как delete/add) не обещан;
reorder внутри списка сохраняет id. Возможная потеря доступа не раскрывает
значение через backend и не заставляет автоматически удалять ссылку.
## 5. Layout, narrow, малые карточки и ввод
### 5.1 Геометрия overlay
Измерять **саму `.stage` House Plan** в CSS px, не window/экран и не HA narrow.
Внешние header/sidebar HA уже вне контейнера и второй раз не вычитаются.
При W ≥ H панель справа, при H > W снизу. Равенство — справа.
На resize положение меняется без собственного fit/refit; штатное поведение
камеры на реальное изменение stage не менять.
Overlay — DOM sibling трансформируемого слоя, не SVG/zoomwrap и не участник
layout flow. В 2D/изометрии одинаковый экранный UI. Он не входит в content bbox,
fit, размеры stage, экспорт геометрии, occlusion, room hits и sun/shadow model.
Габариты по Dashboard 5: минимум читаемой ширины **280 CSS px**;
справа ширина по содержимому в диапазоне 280…420 px, ограниченная доступным
местом; снизу компактно по содержимому с min-width `min(360, availableWidth)`,
max-width `availableWidth` и центрированием. 360 — **не жёсткий минимум**:
на телефоне шириной 360 px остаётся 336 px после отступов и панель обязана
помещаться, если проходит общий читаемый минимум 280 и ограничение высоты.
На более широкой области длинное содержимое может сделать bottom-панель шире
360 px, но не растягивать пустую/короткую панель на всю ширину.
Длинные подписи переносятся; строка значения не вытесняет название за границы,
длинный неразрывный текст переносится; единицы не скрываются молча.
Прокручивается область данных, заголовок остаётся доступен.
### 5.2 Контракт достаточности места
Базовые внешние отступы 12 px + реально применимые safe-area inset внутри HP.
Сверху/снизу резервируются пересекающие полосу панели контролы по измеренным
border boxes + 12 px; в киоске это плавающая панель управления, не жёсткая
поправка «высота телефона». Вкладки/инструменты вне stage повторно не вычитать.
После вычитания insets и control reserve получаем `availableWidth`,
`availableHeight`. Ограничение высоты: справа `availableHeight`,
снизу `min(0.60 * H, availableHeight)` — по макету.
`fits = availableWidth >= 280 && heightCap >= minimumReadableHeight`.
Нулевые/неизвестные начальные размеры считаются ещё не готовыми, не persist.
`minimumReadableHeight` измеряется на невидимом, inert/aria-hidden образце
минимального содержимого: заголовок панели, заголовок блока, одна строка
состояния с отступами/границами при выбранной ширине и текущем шрифте.
Текстовые входы образца: сохранённый заголовок панели, локализованный
образцовый заголовок блока и одна фиксированная локализованная строка статуса
«Источник недоступен». Не подставлять текущее HA-значение, все labels или число
реальных строк. Длинное live-значение меняет высоту прокручиваемой строки,
но не gate. Образец не подписывается на HA, не фокусируется и не участвует
в layout stage.
Берётся natural border-box без max-height/scroll. Увеличенный шрифт/zoom,
перенос заголовков и тема переизмеряют минимум. Не брать scrollHeight всех
реальных 200 строк: их переполнение должно включать scroll, а не скрывать панель.
Пустой состав использует такой же резерв одной читаемой строки.
Базовый минимум по CSS архива — 162 px (2 border + 48 header + 20 padding +
2 block border + 43 block header + 11 values padding + 36 row).
Это baseline проверки, не hardcode вместо измерения. При неизменной стороне и
типографике увеличение доступного размера монотонно разрешает показ.
Смена стороны заново проверяет свой height cap (right→bottom может скрыть
панель из-за ограничения 60%). Измерение не зависит от предыдущего hidden
и не чередует hidden/visible при постоянных размерах.
Примеры без дополнительных safe area/контролов и с baseline 162:
360×640 → снизу, ширина 336, fits; 303×640 → не fits, 304×640 → fits;
800×185 → высота 161, не fits; 800×186 → fits.
Для киоска с измеренным верхним пределом 72: 800×245 → не fits,
800×246 → fits. Проверки также используют реальную, а не заданную заранее
высоту контролов. 1 и 200 строк имеют одинаковый gate, но разный scroll.
### 5.3 Независимая мобильность
Общая настройка `show_on_mobile` проверяет **native HA narrow**.
Она не выбирает сторону и не является CSS breakpoint 768/600/etc.
`houseplan-panel` передаёт reactive `narrow` во full card при создании и
обновлении. В Lovelace изолированный adapter получает native narrow context
на HA, где он доступен; для поддерживаемых старых HA — boolean narrow
ближайшего штатного Lovelace host. Не monkeypatch приватные setter-ы,
не выводить narrow из W/H stage и не превращать Lovelace в custom panel.
HA minimum 2024.6 остаётся поддержанным.
Неопределённость имеет отдельное runtime `unknown`: при mobile=true не
мешает показу; при mobile=false скрывает до получения достоверного narrow.
Ни user-agent, ни новый пользовательский breakpoint не добавляются.
`viewAllowed` — full card находится в режиме View (обычном или kiosk),
не в редакторе плана, устройств или подложки (§4.1).
`effectiveVisible = viewAllowed && localShow && mobileAllowed && fits`;
mobileAllowed = sharedMobile || nativeNarrow === false.
Отсутствующая конфигурация использует defaults; неподдерживаемая версия
не исполняется (§9). Никакого дополнительного общего `enabled` флага.
Auto-hide не меняет localShow и не запускает WS save. Resize возвращает
панель, только если остальные guards разрешают.
### 5.4 Ввод и доступность
**Touch View: supported. Touch editor: supported только для новой простой
формы сводки.** Гарантии остальных редакторов остаются по
[TOUCH-SUPPORT](../TOUCH-SUPPORT.md); task не расширяет их.
Размер tappable зоны каждого контрола ≥44×44 CSS px; иконка может быть меньше
по макету. Кнопки не накладываются зонами. Составной контрол остаётся
обнаруживаемым, когда панель скрыта mobile/fits. На слишком узком экране
контролы не вынуждают stage становиться шире; использовать существующий
overflow шапки, а в киоске перенос компактных контролов.
Pointer down/click/double click/wheel/drag на overlay/его контролах не
попадают в pan/zoom/fit/действия устройств снизу. Скролл панели не масштабирует
план; `overscroll-behavior: contain`, без глобального запрета touch.
Начатый на плане pan/pinch остаётся плановым до завершения, не превращается
в click на панели; pointercancel снимает transient state, не сохраняет draft.
Keyboard focus виден, modal focus trap/возврат в opener через общий `hp-dialog`.
Клавиатурная альтернатива reorder обязательна; live values не озвучиваются
все одновременно через постоянно активный aria-live.
Появление короткое (190 ms по Dashboard 5), без изменения размеров плана;
`prefers-reduced-motion` отключает перемещение.
## 6. Модель конфигурации и defaults
Общая конфигурация: `ServerConfig.settings.summary_panel`, версия 1.
Wire naming — snake_case как остальная серверная конфигурация; camelCase
прототипа не копируется вслепую. Минимальный пример (значения label локализованы
при первом явном сохранении, пользовательский текст затем не переводится):
```json
{
"version": 1,
"title": "Сводная информация",
"show_on_mobile": true,
"blocks": [{
"id": "b-default",
"title": "Общее",
"visible": true,
"scope": {"type": "all"},
"values": [
{"id": "v-devices", "label": "Количество устройств", "source": {"type": "system", "key": "device_count"}},
{"id": "v-area", "label": "Общая площадь комнат", "source": {"type": "system", "key": "total_area"}},
{"id": "v-time", "label": "Текущие дата и время", "source": {"type": "system", "key": "datetime"}}
]
}]
}
```
Другой scope: `{"type":"space","space_id":"stable-id"}`.
Другой source: `{"type":"entity","entity_id":"sensor.example"}`.
Для новых объектов id генерируются один раз (UUID), не из текста/индекса;
непустые ≤64 chars, уникальны среди блоков и среди всех строк панели.
Defaults ids детерминированы до первого сохранения; произвольные новые id
при каждом render запрещены. `collapsed` и независимых вложенных таблиц нет.
Отсутствие namespace даёт read-only derived default model: первое включение
даже у read-only не пишет сервер. При первом явном Save авторизованным
пользователем сохраняется версия 1. Это один начальный набор, не автоматический
создатель после каждого reload: присутствующее `blocks: []` и пустые values
никогда не reseed. Настоящий ноль площади/устройств допустим.
Локальная запись отдельна: `{version:1, show:false, icon_scale:1, font_scale:1}`.
Она не уходит в server config/export и не входит в ревизию общего конфига.
Нет сохранённых текущих HA states, вычисленных totals, времени и координат
панели. Системные значения вычисляются на чтение.
## 7. Источники и вычисления
### 7.1 HA states
Использовать текущий пользовательский `hass`, штатное formatEntityState/
эквивалент существующего HA formatter: локаль, единицы, локализованные enum,
precision. При недоступном helper — существующий безопасный текстовый fallback,
не кастомный список переводов state. Не извлекать произвольные attributes.
`unknown`, `unavailable`, отсутствующий id, отсутствие доступа/связи дают
различимое нейтральное состояние (где источник позволяет отличить причину),
не выдуманный 0/green; не показывать прежнее значение как свежее после потери
соединения. Значение появляется вновь при восстановлении.
Не обещать различить удаление и запрет доступа, если HA их не различает.
### 7.2 Уникальные устройства — Q2
Общий set реальных HA device_id, представленных на ≥1 действительном плане
по полному актуальному config/layout и registry, **до visual фильтров**.
Учитывать автоматическое размещение через HA area, привязанную к комнате,
и явные device/entity placement. Entity сопоставляется parent device_id.
Дубликаты сущностей/этажей дают один id; скрытый marker, HA disabled entity
или временно unavailable device не уменьшают число. Виртуальный marker,
самостоятельная entity без parent, удалённое из HA устройство, снятая с планов
привязка и orphan к удалённому пространству не добавляют id.
`removedPlanBindings` и существующее исключение явного возвращения child entity
обрабатываются тем же placement resolver, что продукт; не вводить счётчик
просто `Object.keys(registry).length`, visible roster или layout entries only.
Использовать общий для HA connection полный snapshot из `ha-binding-status.ts`:
успешные `config/device_registry/list` и `config/entity_registry/list`,
не `list_for_display`/`hass.entities`/сохранённый binding-status cache.
Эти штатные list API доступны также read-only пользователям; сам read-only
не причина отказа. При загрузке/ошибке одной команды/reconnect —
loading/unavailable, а не уверенный частичный total, прежний snapshot или 0.
Новый endpoint счётчика не нужен. Не обходить HA permissions и не переносить
snapshot между connections/пользователями. Backend `import_registry_snapshot`
для editor import не публиковать в View: это другой, неполный для этой цели
контракт. Источники: официальные
[device registry API](https://github.com/home-assistant/core/blob/2026.9.0/homeassistant/components/config/device_registry.py),
[entity registry API](https://github.com/home-assistant/core/blob/2026.9.0/homeassistant/components/config/entity_registry.py).
### 7.3 Чистая площадь — Q3
Для каждого существующего пространства получить канонический clean floor
каждой валидной комнаты: innerContourForRoom и floorMinusBodies с теми же
стенами/перегородками/колоннами, что `_cleanFloor` и значение площади комнаты.
Объединить **полные MultiPolygon с holes** внутри пространства и только затем
вычислить geometryArea; нельзя суммировать пересекающиеся комнаты или
превращать holes в наружные кольца. Для render-coordinate geometry использовать
`cmPerUnit = cell_cm / gridPitch` этого пространства, площадь в m² =
`geometryArea * cmPerUnit² / 10000`; не смешивать cm и render units.
Суммировать физическую площадь всех пространств. Копии этажей
независимы. Этаж без комнат даёт 0. Сломанная геометрия/сбой union не подменяются
нулём, суммой с double count или исчезновением остальных элементов плана:
показатель unavailable, диагностический код без пользовательской геометрии.
Округлять только итог через принятую систему единиц HA (m²/ft²) и существующий
room-area формат, не каждый полигон. Активное пространство и scope блока
влияют только на видимость строки, не на total. Вынести shared pure clean-floor
calculation, если существующая `_cleanFloor` привязана к активному пространству.
### 7.4 Время и обновления
Дата и время по locale/timezone HA, без секунд, обновление на границе минуты.
Немедленное обновление после resume/смены timezone. Один minute timer на
смонтированную видимую панель, только если datetime есть в видимых блоках;
при hidden/disconnect/page hidden остановлен. Не создавать timer на строку.
Area cache зависит от физической геометрии и cell_cm, device cache — от
placement/config/layout и registry revision. Обычный HA state update не
пересчитывает union или device roster. Entity rows обновляются адресно;
dropdown search index пересобирается по составу/именам, не по каждому значению.
Все observers/listeners/timers снимаются на disconnect.
## 8. Права, локальная идентичность и размеры
### 8.1 Режимы
| Пользователь/режим | Шестерёнка | Правая кнопка и размеры |
|---|---|---|
| Обычный View, `_canManageConfiguration` | Полная форма §4.2, явный вход «Размеры на этом экране» | Локально доступны |
| Обычный View, read-only | Только локальная часть: показать панель и размеры, без shared-полей | Локально доступны |
| Kiosk, в том числе HA admin | Та же локальная часть, без редакторов | Доступны из компактных контролов вне скрытой шапки |
| Редакторы плана/устройств/подложки | Новый View-контрол отсутствует | Существующие редакторские инструменты без изменений |
Локальная часть — адаптация общего shell настроек к правам, не второй
редактор структуры и не обход скрытой административной кнопки.
Shared-форма и runtime Save guard: `!kiosk && _canManageConfiguration`.
Сервер по-прежнему проверяет `may_write`/`admin_only`; не заменять контракт
безусловным `hass.user.is_admin`. До получения прав fail closed, локальные
настройки не открывают server writes. Kiosk не даёт редактор даже admin,
согласно [UX-MODES](../UX-MODES.md). Текущий `_canEdit` сам по себе этого
не гарантирует — нужен явный kiosk guard в новом handler.
### 8.2 Как различать карточки
Local preference key: версия namespace + HA user id + канонический route
dashboard/view + host kind + стабильный **логический путь экземпляра**.
Не один global key и не только hash card config: две одинаковые карточки
должны оставаться независимыми. Не runtime UUID, иначе reload теряет выбор.
Изолированный resolver связывает enclosing `hui-card` с логическим индексом
в native view: Sections — `[viewIndex, sectionIndex, cardIndex]`, Masonry —
индекс в исходном упорядоченном `cards`, не номер визуальной колонки.
Для stack/conditional дописывается путь вложенного card slot. Для собственного
`houseplan-panel` фиксированный primary slot. Для неизвестного custom wrapper
fallback — детерминированный composed slot path + ordinal совпадающей
конфигурации; не писать local preference до разрешения user/host identity.
Если идентичность нельзя достоверно получить, использовать session-only
настройку с честным пояснением, не чужой global preference.
Гарантия: reload/технический remount/обычный resize без изменения структуры
dashboard сохраняют выбор; два одинаковых instance не сливаются.
Редактирование/перестройка dashboard может изменить logical identity —
перенос по новому расположению не входит в эту версию. Preview/editor HA
не пишет preference рабочей карточки. Никакой автоматической записи YAML
dashboard или нового обязательного `card_id` пользователю.
### 8.3 Размеры на этом экране (#149)
Явный click/tap вход, не только long hold: «Размер значков устройств» и
«Размер текста карточек комнат». Сохранить диапазон 50…300%, шаг 5%, Reset 100%.
Локальное немедленное применение как в существующем size dialog;
закрытие завершает настройку, отдельный Cancel для размеров не вводится.
Это отличается от draft локального show в полной форме; названия действий
не должны обещать откат уже применённых размеров.
Сейчас `_kioskScale` применяется только при `_kiosk`. Расширить на обычный
**View** в 2D и изометрии: значки/room-card text следуют этим двум значениям;
редакторские узлы, физическая геометрия, room labels на плане, шрифт самой
сводной панели и остальные колор-пикеры/настройки не затрагиваются.
Масштабы не умножаются дважды в дочернем слое и iso. Размеры доступны при
любом hidden-поводе панели и без прав редактирования установки.
При отсутствии новой local записи один раз прочитать валидные legacy
`houseplan_card_kiosk_v1` icon/font как начальные значения, иначе 100%.
Старый ключ не удалять и не переписывать. Дальнейшие записи per-instance;
повреждённый/вне диапазона input нормализовать без NaN/exception.
localStorage disabled/quota не ломают UI; значение действует в текущей
сессии, показать нейтральное сообщение о невозможности сохранить на экране.
## 9. Сохранение, совместимость, импорт и безопасность
### 9.1 Общая запись и concurrency
Использовать `config/set` с текущим `expected_rev` и существующим write lock.
Передавать полный базовый config с заменой только `settings.summary_panel`,
без побочного изменения settings/layout/space geometry. Серверная форма
валидируется structural + change-aware semantic проверкой §4.4.
Не опираться только на frontend: raw WS с duplicate id/лишними строками/
пустыми названиями/невалидным discriminator должен отклоняться.
Draft хранит base revision, base panel и localShow snapshot. Shared Save:
запрос → подтверждённая сервером ревизия → применение localShow → закрытие.
Отказ/конфликт не применяет localShow и не уничтожает draft. Если изменён
только localShow, серверная запись не нужна. Header show, пока открыта modal,
недоступен из-за modal; не создавать два конкурентных локальных редактора.
При внешней конфигурации во время dirty draft показывать conflict и явную
возможность загрузить актуальную конфигурацию/начать заново, без silent merge
и overwrite чужих правок. `expected_rev` защищает и неизменённые разделы
полного config. Network timeout с неизвестным исходом: прочитать config/rev,
подтвердить применённое содержимое, не слать повторную blind запись.
При отказе localStorage уже успешно записанный shared config не откатывать
чужой серверной записью: apply local в памяти, сообщить об ограничении
переживания reload. «Атомарно» здесь значит отсутствие частичного draft при
отказе общего Save, не распределённая транзакция browser-storage/HA.
### 9.2 Версии и старые ссылки
`config/get` сообщает runtime capability `summary_panel_api: 1` (не поле
пользовательского config). Новая frontend + старый backend: View/local
настройки работают с читаемой схемой, shared-редактор disabled с подсказкой
об обновлении интеграции; не записывать config в обход неизвестной проверки.
Это дополнение существующего config/get, не новый metrics/state endpoint.
Сервер не требует существования каждой старой entity/space при каждом
постороннем config save. Structural schema ограничивает namespace/типы/размер;
references проверяются как diff к текущему серверному объекту по id.
Новый entity выбор проверять на наличие и разрешённое чтение текущим
пользователем, а не произвольным service/admin запросом. Старую ссылку можно
сохранить неизменённой при редактировании label/title/порядка.
| Вход | Политика |
|---|---|
| Старый config без namespace | Derived defaults; никаких обязательных записей при открытии. |
| Настроенная version 1 | Round-trip всех известных и неизвестных расширяющих полей; mutate только выбранные поля, не rebuild объекта с потерями. |
| Старый клиент не прислал существующий namespace в обычном config/set | Сервер сохраняет прежний namespace. Удаление всех блоков выражается `blocks: []`, не omission. |
| Незнакомая будущая version | Сохранить lossless при посторонних операциях, не исполнять и не редактировать как version 1; нейтральная недоступность панели, не сброс defaults. |
| Установка новой версии/rollback frontend | Не выполнять destructive миграцию store; старое поле остаётся расширением settings. |
Не менять глобальную версию геометрии/хранилища ради UI namespace.
Обновить registry полей/документы совместимости; allowlist нового namespace
не должна автоматически разрешить пользовательские исполняемые данные.
Сохранённые строки всегда проходят text rendering, id/source enumerations
не могут исполнять URL/HTML/JS. Unknown fields сохраняются, но не исполняются.
### 9.3 Export/import и privacy
Полный export/restore установки включает общую конфигурацию панели;
local preference/текущие значения/кеш registry не включает.
Полный restore — явная замена конфигурации (в том числе отсутствие панели
в старом полном архиве); это исключение из preserve-on-omission обычного Save.
Single-space export/import, plan-only, copy/new space не меняют глобальную
панель назначения. Если существующий full-import явно remap-ит space ids,
переписать scope по **той же** mapping; не угадывать по имени/первому этажу.
На перенос в другой HA корректные сохранённые refs, отсутствующие там,
разрешены как broken refs с warning. Это не пользовательский новый выбор;
структурно некорректный импорт отклоняется атомарно.
User-facing заголовки/entity_id и тем более текущие state values не добавлять
в support package, telemetry/logs. Существующая allowlist support exporter
остаётся fail closed; при необходимости допустимы только validated counts
блоков/строк/version. Данные HA state не запрашиваются через новый backend
proxy, не кэшируются между пользователями и не выводятся через небезопасный
HTML. House Plan read/write policy и secure-device invariant не ослабляются.
## 10. i18n и визуальное соответствие
Все новые runtime строки в **RU, EN, DE и FR** — всех четырёх существующих
поддерживаемых локалях. Ошибки frontend/backend — через текущий
локализационный путь. Паритет ключей, непустые значения и неизменные
placeholders проверяются штатными parity-тестами `npm test` по всем локалям
из `src/i18n/registry.ts`, согласно `CONTRIBUTING.md`.
Ключи и формулировки общих Save/Cancel/Close/Up/Down брать из существующего UI,
не дублировать. Пользовательские label/title не переводить после сохранения.
| RU | EN | DE | FR |
|---|---|---|---|
| Сводная информация | Summary | Übersicht | Synthèse |
| Настройки панели | Panel settings | Panel-Einstellungen | Paramètres du panneau |
| Показывать панель | Show panel | Panel anzeigen | Afficher le panneau |
| Название панели | Panel title | Panel-Titel | Titre du panneau |
| Отображать на мобильных устройствах | Show on mobile devices | Auf Mobilgeräten anzeigen | Afficher sur les appareils mobiles |
| Общее | General | Allgemein | Général |
| Все пространства | All spaces | Alle Bereiche | Tous les espaces |
| Определённое пространство | Specific space | Bestimmter Bereich | Espace spécifique |
| Добавить блок / Добавить значение | Add block / Add value | Block hinzufügen / Wert hinzufügen | Ajouter un bloc / Ajouter une valeur |
| Количество устройств | Device count | Anzahl der Geräte | Nombre d’appareils |
| Общая площадь комнат | Total room area | Gesamte Raumfläche | Surface totale des pièces |
| Текущие дата и время | Current date and time | Aktuelles Datum und Uhrzeit | Date et heure actuelles |
| Размеры на этом экране | Sizes on this screen | Größen auf diesem Bildschirm | Tailles pour cet écran |
| Нет показателей для этого пространства | No values for this space | Keine Werte für diesen Bereich | Aucune valeur pour cet espace |
| Недостаточно места для панели | Not enough space for the panel | Nicht genügend Platz für das Panel | Espace insuffisant pour le panneau |
| Источник недоступен | Source unavailable | Quelle nicht verfügbar | Source indisponible |
Также локализовать загрузку, no search results, удалённое пространство,
пустое обязательное поле, каждый лимит, conflict/reload, mobile-hidden,
storage-unavailable, unsupported-backend/schema, delete confirmation.
HA state names/units/date/time — HA locale, не переводы этой таблицы.
Матрица макетов: light/dark × right/bottom; ordinary/kiosk; панель hidden;
форма normal/narrow; пустой состав/старые broken refs/максимум строк;
длинные RU/DE/FR подписи, safe-area, крупный шрифт и 200% browser zoom.
Минимум ширины/высоты адаптируется без уменьшения шрифта.
Dashboard 5 определяет отступы, заголовки, порядок controls, палитру и иконки;
его `orientation: portrait`, `100vw/100vh`, demo states 50/156 m² и whole-config
localStorage заменяются соответствующими контрактами выше. Hit areas 27 px
прототипа не копируются: min 44 является продуктовым touch-контрактом.
## 11. Критерии приёмки и доказательства
AC1…AC20 соответствуют общей постановке issue; следующие уточняют инженерные
граничные случаи. **Все доказательства ниже планируются для реализации;
на стадии ТЗ ни unit, ни browser-pass по новому поведению не заявлены.**
| AC | Проверяемый контракт | Доказательство и отрицательный свидетель |
|---|---|---|
| AC1 | Раздельные settings/show, только shown/hidden, local per-card после reload. | Two-card identical-config smoke + reload; общий ключ или runtime UUID ломает независимость/сохранение. |
| AC2 | Overlay, W≥H справа/H>W снизу, bottom centered, без collapse/CRUD в View. | Unit W/H/equality и smoke высокой карточки в широком окне; заменить stage на window → тест красный. |
| AC3 | Одна адаптивная форма, доступ при hidden, все поля и Save/Cancel, dropdown не обрезан. | Mouse/keyboard/touch smoke narrow/keyboard viewport; bounds assert для list/footer. |
| AC4 | Draft Save/Cancel/X/Escape включает localShow, failure не применяет часть. | Unit save state machine + mocked WS failure/conflict/timeout; premature local write ломает assert. |
| AC5 | Block CRUD/reorder/visibility, confirm непустого delete, Cancel восстанавливает. | Unit и browser через реальные controls, не вызовом private handler. |
| AC6 | All/one exact space и visible фильтруют блоки, defaults all. | Табличный unit + смена этажей, same-title distinct ids. |
| AC7 | Новая пустая scope ошибка; старая deleted scope warning, сохраняется при rename/reorder. | Backend + frontend tests по stable id; сравнение всего объекта или fallback-first ломают тест. |
| AC8 | Row CRUD/reorder только в форме, pointer и кнопки дают один порядок. | Unit + мышь/клавиатура/touch; drag в View не меняет config. |
| AC9 | Пустой label/source запрещён, system source полноценен. | Negative tests raw WS и UI, trim/Unicode boundaries. |
| AC10 | Полный readable hass.states picker, friendly name/entity_id, новая Template entity. | Fixture entity вне плана, дубли friendly name и hot-add; plan-filter mutation → красный. |
| AC11 | Broken entity сохраняется при других правках; unknown/unavailable не 0; HA форматирование. | UI+server change-aware cases, потеря/восстановление связи, RU/EN/DE/FR/units. |
| AC12 | Первое включение даёт три defaults, реальные 0, intentional-empty не reseed. | Unit absent vs empty; read-only first show без WS, second reload/toggle. |
| AC13 | Лимиты 10/20/48/64 с hidden blocks, disabled Add и ошибки. | Both sides N−1/N/N+1, Unicode code points; прямой WS не обходит. |
| AC14 | Native narrow, localShow и fits независимы; W/H выбирает сторону. | Полная boolean/unknown unit matrix + custom panel/Lovelace native narrow smoke; fake 768 px mutation красный. |
| AC15 | View/kiosk controls и local sizes доступны, нет editor/service/pan leakage. | Touch pointercancel/pan/pinch/scroll/doubletap и admin-kiosk/read-only route guards; service spy 0. |
| AC16 | Общая persistence/export не меняет geometry/layout. | Backend round-trip/full/space/plan-only/copy + hashes geometry до/после. |
| AC17 | Макеты/темы/overflow/safe area без скачков камеры или перекрытия controls. | Fresh named screenshots + bounding boxes, zoom/pan matrix до/после show/hide; layout-column mutation красный. |
| AC18 | Q2 unique HA ids и Q3 union clean floor всех пространств. | Exact numeric fixtures §12, scope/activefloor не меняют total; visible-roster/sum-room-areas mutations красные. |
| AC19 | Общие правки приходят другому клиенту, local нет; права/конфликты сохранены. | Two-client revision test, read-only direct write refusal, admin_only=false writer и kiosk guard. |
| AC20 | Малый контейнер auto-hide, enlargement условно restore; длинный список scroll. | Границы 303/304 и 185/186, control-reserve/safe-area/largefonts, wide dashboard tiny card; remove-gate/reset-local/unconditional-show mutations красные. |
| AC21 | Identity стабильна без смешения identical cards/resize/HA users. | Native Sections/Masonry/nested fixtures, reload/remount/reflow, preview isolation, unknown-wrapper safe fallback. |
| AC22 | Новая/старая frontend/backend, unknown-version, omission сохраняют данные согласно §9. | Backend compatibility matrix, full restore exception; unrelated-save не уничтожает namespace/extension fields. |
| AC23 | В View 2D/iso local scales 50/100/300%, редакторы неизменны, old key не удалён. | Scale geometry/hit bounds и UI smoke; двойное умножение/только-kiosk mutation красные. |
| AC24 | Partial registry не total 0; read-only с полным snapshot получает правильный total. | Одна list-команда failed/reconnect, полный disabled-inclusive snapshot; no privileged API assertion. |
| AC25 | Без постоянного RAF/дублирующих timers/area recalculation от states. | Counters/timers/connection cleanup и performance fixture §12; unrelated state → 0 unions. |
| AC26 | User strings inert, выбранные states только из user hass, support без raw data. | XSS payload text-only, ACL/reconnect/user-switch, support allowlist test; never service calls. |
## 12. План реализации и автотестов (после отдельной команды)
1. Pure model/defaults/diff validation/local identity/layout; backend additive
namespace и capability/compatibility. Тесты before UI integration.
2. Extract shared represented-device/clean-floor totals, states resolver/caches.
3. Overlay + header/kiosk controls + lazy form + narrow bridge + local sizes.
4. i18n/docs, интеграционные assertions, визуальные артефакты, performance.
Frontend unit: `test/summary-panel-model.test.ts`, `summary-panel-layout.test.ts`,
`summary-panel-sources.test.ts`, `summary-panel-preferences.test.ts` (названия
технически заменяемы). Реальные reducers/geometry helpers, не копия алгоритма
в тесте. Backend: `tests_backend/test_summary_panel.py` для config/get/set,
write guards, conflicts, change-aware semantics и import/export/support.
Полный HA harness — Linux CI, не фиктивный Windows-pass (`fcntl`).
Численные fixtures: один HA device с 2 children на 2 этажах → 1; другой hidden
и disabled → ещё1; virtual и no-parent → 0 дополнительных; removed binding
исключён, explicit restored child возвращает ровно 1. Ошибка registry отлична
от valid empty registry. Площадь: два перекрывающихся clean floor 4 m² и 4 m²
с overlap 1 m² → 7 m²; копия пространства → 14 m². Hole 1 m² сохраняется вычитанием,
не заполняется union. Разные cell_cm с одинаковой физической геометрией,
ft², колонны/partition bodies и failure union обязательны. Использовать
реальную canonical pipeline с отдельно известной ожидаемой площадью.
Browser smoke `demo/smoke_summary_panel.mjs`: реальные pointer/keyboard
действия и async WS/hass updates, fixture нескольких карточек/клиентов,
View/kiosk/2D/iso, narrow/native hosts, live mutable containers. Проверять
config/layout до/после, service calls, stage bounding rect, camera transform,
pointer hit boxes. Не ограничиваться DOM class/screenshot-only assertion.
Visual evidence: полный scene и close-up right/bottom в light/dark,
360×640 и 640×360, маленькая карточка в широком dashboard, fits boundaries,
kiosk safe area, длинный текст/empty/unavailable, форма и 200 строк. Сопоставить
Dashboard 5 и фактические скриншоты с отмеченными согласованными отличиями;
не объявлять визуальное соответствие по одному успешному screenshot capture.
Performance witness: до/после на одном baseline hardware, 200 rows/10 blocks,
10k HA states, 3 cards. После прогрева один unrelated state update → 0 geometry
unions,0 roster rebuild,0 picker index rebuild; matching state изменяет
нужные строки. Idle panel не запускает RAF, minute timers≤visible panels
с datetime; hidden 0. Cached metric overhead p95≤2 ms/update в demo runner;
threshold failure расследуется, не удаляется. Новая закрытая форма не
подгружает весь editor graph; initial View bundle≤256000 B gzip по repo budget.
Снять median/p95/render counters до/после через действующий perf harness;
state burst не добавляет long task>50 ms из-за summary.
Негативные свидетели из AC фиксировать в будущем code handoff конкретно:
какой assert падает при временном отключении guard/подмене resolver; не
коммитить мутации. Не менять fixtures/бюджеты ради зелёного результата.
## 13. Риски, откат и release-артефакты
Основные риски: зависимость host identity/narrow от HA frontend, false total
из visual roster, дырки lost при union, data loss из старого config writer,
UI leakage в kiosk, scrolling через overlay и импорт из другого HA.
Для каждого есть отдельный boundary/negative witness выше.
HA compatibility прогнать на minimum 2024.6 и актуальном поддерживаемом HA;
адаптеры изолированы, не делают публичные assumptions из частных методов.
Rollback: пользователю доступно local hide без удаления состава. Кодовый
откат не удаляет namespace/локальные ключи, не мигрирует геометрию назад,
не переписывает общий конфиг при открытии старого frontend. Любой destructive
repair — отдельное решение. Публикация только по команде владельца.
Обязательные артефакты будущей реализации:
- `docs/CHANGELOG.md` + `docs/CHANGELOG.ru.md`: включение сводки, local show,
native mobile/малый контейнер, доступные локальные размеры; ссылка#437.
- `docs/USER-GUIDE.ru.md` и `docs/USER-GUIDE.md`: настройка,
defaults, общие/локальные изменения, units/totals, причины hidden, права.
- `docs/UX-MODES.md`, `docs/TOUCH-SUPPORT.md`, `docs/ARCHITECTURE.md`,
`docs/CONFIG-COMPATIBILITY.md`, `scripts/config-field-registry.mjs` и
`scripts/config-schema.json`:
narrow/overlay/per-card storage и compatibility-матрица.
- Fresh screenshots для guides и перечисленных выше golden сцен; принимать
baseline только по полному Linux CI artefact через штатный reviewed путь.
- Implementation gates по текущему PROCESS: `npm run typecheck`, `npm test`,
`npm run build`/`npm run bundle:sync`, budget, необходимые diff-selected
smoke и backend tests. Перед бетой golden/smoke/performance/Linux HA,
strict documentation screenshots. Команды/результаты на точном SHA, не
ручные счётчики тестов (`npm run inventory` — источник числа тестов).
Сейчас этот commit — только ТЗ/SCOPE/index, `User-Visible: no`;
новые changelog/tests/product code до команды на реализацию не добавляются.
Ревью ТЗ выполняет независимый Claude по PROCESS, не автор/исследовательские
подагенты. После green и S5 остановка; issue не закрывается.
## 14. Принято предположительно — техническое, менять свободно на ревью
1. Namespace/version 1, snake_case, UUID/id limits, capability и diff-validation
— способ реализации утверждённого shared/local поведения, не новый продукт.
2. Per-card key по logical host path с сохранением на reload; dashboard
restructuring не получает миграцию identity. Fallback не использует общий
preference. Legacy sizes seed новой записи без удаления старой.
3. Exact fit baseline 280×162, 12 px insets, side max 420/bottom preferred 360,
height cap 60%, probe/ResizeObserver — вывод из Dashboard 5. Можно изменить
механику, сохранив читаемость, small-card hide и все граничные AC.
4. Native narrow bridge для старых HA/unknown, caches/timer, lazy modules и
perf fixtures — инженерная адаптация, не новые настройки пользователю.
5. Отсутствующий namespace читается как derived defaults без server write;
deliberate empty остаётся empty. Это сохраняет первый запуск read-only.
6. Кодировки ошибок/id naming, расположение pure модулей и названия тестов
допускают уточнение. Не допускают произвольного изменения Q1–Q5,
прав/киоска, метрик, разделения shared/local или команды остановиться на S5.