From 4a1aac16fefab0868557f95323be17053beee3e9 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Tue, 8 Sep 2026 16:59:58 +0300 Subject: [PATCH] docs: specify configurable summary overlay (#437) Issue: #437 User-Visible: no --- docs/SCOPE.md | 8 +- docs/specs/437-summary-panel.md | 720 ++++++++++++++++++++++++++++++++ docs/specs/README.md | 1 + 3 files changed, 728 insertions(+), 1 deletion(-) create mode 100644 docs/specs/437-summary-panel.md diff --git a/docs/SCOPE.md b/docs/SCOPE.md index 434c0130..3bad980e 100755 --- a/docs/SCOPE.md +++ b/docs/SCOPE.md @@ -113,7 +113,13 @@ the file — and if a future version wants to reclaim that space, it asks. the already maintained House Plan geometry to an installer, insurer or renovation contractor. - A general dashboard framework (menus, popups, theming engine) → Bubble Card, - Dwains and friends. + Dwains and friends. A narrow exception approved on 2026-09-03 for + [#437](https://github.com/Matysh/houseplan-card/issues/437) is a configurable + read-only summary overlay accompanying the spatial plan: named groups of + current HA states and three built-in values (represented device count, + clean room-floor area, date/time). It adds no arbitrary cards, formulas, + history, device actions or HA administration; shared GUI configuration and + per-screen visibility do not turn it into a general dashboard builder. ## Excess-functionality audit (2026-07-22) diff --git a/docs/specs/437-summary-panel.md b/docs/specs/437-summary-panel.md new file mode 100644 index 00000000..fc6650a5 --- /dev/null +++ b/docs/specs/437-summary-panel.md @@ -0,0 +1,720 @@ +# #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 не добавляются. + +`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%, Reset100%. +Локальное немедленное применение как в существующем 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; никаких обязательных записей при открытии. | +| Настроенная version1 | Round-trip всех известных и неизвестных расширяющих полей; mutate только выбранные поля, не rebuild объекта с потерями. | +| Старый клиент не прислал существующий namespace в обычном config/set | Сервер сохраняет прежний namespace. Удаление всех блоков выражается `blocks: []`, не omission. | +| Незнакомая будущая version | Сохранить lossless при посторонних операциях, не исполнять и не редактировать как version1; нейтральная недоступность панели, не сброс 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** (существующая немецкая локализация +сохраняется), ошибки frontend/backend — через текущий локализационный путь. +Ключи и формулировки общих Save/Cancel/Close/Up/Down брать из существующего UI, +не дублировать. Пользовательские label/title не переводить после сохранения. + +| RU | EN | DE | +|---|---|---| +| Сводная информация | Summary | Übersicht | +| Настройки панели | Panel settings | Panel-Einstellungen | +| Показывать панель | Show panel | Panel anzeigen | +| Название панели | Panel title | Panel-Titel | +| Отображать на мобильных устройствах | Show on mobile devices | Auf Mobilgeräten anzeigen | +| Общее | General | Allgemein | +| Все пространства | All spaces | Alle Bereiche | +| Определённое пространство | Specific space | Bestimmter Bereich | +| Добавить блок / Добавить значение | Add block / Add value | Block hinzufügen / Wert hinzufügen | +| Количество устройств | Device count | Anzahl der Geräte | +| Общая площадь комнат | Total room area | Gesamte Raumfläche | +| Текущие дата и время | Current date and time | Aktuelles Datum und Uhrzeit | +| Размеры на этом экране | Sizes on this screen | Größen auf diesem Bildschirm | +| Нет показателей для этого пространства | No values for this space | Keine Werte für diesen Bereich | +| Недостаточно места для панели | Not enough space for the panel | Nicht genügend Platz für das Panel | +| Источник недоступен | Source unavailable | Quelle nicht verfügbar | + +Также локализовать загрузку, 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 подписи, safe-area, крупный шрифт и 200% browser zoom. +Минимум ширины/высоты адаптируется без уменьшения шрифта. +Dashboard 5 определяет отступы, заголовки, порядок controls, палитру и иконки; +его `orientation: portrait`, `100vw/100vh`, demo states50/156m² и whole-config +localStorage заменяются соответствующими контрактами выше. Hit areas27px +прототипа не копируются: min44 является продуктовым 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/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; fake768px mutation красный. | +| AC15 | View/kiosk controls и local sizes доступны, нет editor/service/pan leakage. | Touch pointercancel/pan/pinch/scroll/doubletap и admin-kiosk/read-only route guards; service spy0. | +| 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 scales50/100/300%, редакторы неизменны, old key не удалён. | Scale geometry/hit bounds и UI smoke; двойное умножение/только-kiosk mutation красные. | +| AC24 | Partial registry не total0; 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 с2children на2этажах →1; другой hidden +и disabled → ещё1; virtual и no-parent →0 дополнительных; removed binding +исключён, explicit restored child возвращает ровно1. Ошибка registry отлична +от valid empty registry. Площадь: два перекрывающихся clean floor 4m² и4m² +с overlap1m² →7m²; копия пространства →14m². Hole1m² сохраняется вычитанием, +не заполняется 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,200rows/10blocks, +10k HA states,3cards. После прогрева один unrelated state update →0 geometry +unions,0 roster rebuild,0 picker index rebuild; matching state изменяет +нужные строки. Idle panel не запускает RAF, minute timers≤visible panels +с datetime; hidden0. Cached metric overhead p95≤2ms/update в demo runner; +threshold failure расследуется, не удаляется. Новая закрытая форма не +подгружает весь editor graph; initial View bundle≤256000B gzip по repo budget. +Снять median/p95/render counters до/после через действующий perf harness; +state burst не добавляет long task>50ms из-за 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 прогнать на minimum2024.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/version1, 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 baseline280×162,12px insets, side max420/bottom preferred360, + height cap60%, probe/ResizeObserver — вывод из Dashboard5. Можно изменить + механику, сохранив читаемость, 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. diff --git a/docs/specs/README.md b/docs/specs/README.md index db2cc16d..1a59f4be 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -96,6 +96,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | Issue | ТЗ | |---|---| +| [#437](https://github.com/Matysh/houseplan-card/issues/437) Конфигурируемая сводная панель поверх плана | [437-summary-panel.md](437-summary-panel.md) | | [#487](https://github.com/Matysh/houseplan-card/issues/487) Пороги комфортной температуры для комнаты | [487-room-temperature-thresholds.md](487-room-temperature-thresholds.md) | | [#478](https://github.com/Matysh/houseplan-card/issues/478) Отказ от persisted-сущности `room_drafts` | [478-remove-room-drafts.md](478-remove-room-drafts.md) | | [#477](https://github.com/Matysh/houseplan-card/issues/477) Fixed point оптимизатора после штатного редактирования | [477-editor-writer-fixed-point.md](477-editor-writer-fixed-point.md) |