66 KiB
#437 — Конфигурируемая сводная панель поверх плана
Issue: #437.
Ветка: issue/437-summary-panel; база аналитики: dev@ed9ee026.
Редакция: 2026-09-08. Полный трек, P2/feature.
Канонический статус — метка issue, не заголовок этого документа.
Команда владельца: пройти ревью ТЗ до S5-ready и остановиться. Реализацию не начинать.
1. Сценарий и результат для человека
Администратор дома настраивает общую сводку один раз. Домочадцы читают её поверх плана в обычной карточке, полноэкранной панели, на телефоне или настенном планшете; могут скрыть её на своём экране, не меняя чужие экраны.
До: показатели собирают псевдотаблицами из декора на плане. После: нужные показатели находятся в компактной панели справа или снизу, настраиваются одним окном и не меняют размер самого плана.
Это ограниченное исключение из запрета general dashboard framework в SCOPE, одобренное владельцем 2026-09-03: контекстная read-only сводка при пространственном View, не новый конструктор dashboard. Поглощённые сценарии: #83 и #149.
Оценка: пользовательская ценность 8/10, разработческая 6/10,
сложность 7/10, риск 7/10. small не подходит: новый UX-контракт,
несколько поверхностей, persistent shared/local state, touch и performance.
2. Источники решений и границы
Приоритет: позднейшее решение владельца → этот согласованный контракт → Dashboard 5. Старые предложения о выделенной колонке и сворачивании отменены.
- Dashboard 5, сообщение дизайнера.
- Принятые Q1–Q5 и компоновка.
- Скрытие при недостатке места.
- Начало номерного ТЗ и ограничение S5.
Архив 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; 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 локализованы
при первом явном сохранении, пользовательский текст затем не переводится):
{
"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,
entity registry API.
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. Текущий _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. План реализации и автотестов (после отдельной команды)
- Pure model/defaults/diff validation/local identity/layout; backend additive namespace и capability/compatibility. Тесты before UI integration.
- Extract shared represented-device/clean-floor totals, states resolver/caches.
- Overlay + header/kiosk controls + lazy form + narrow bridge + local sizes.
- 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. Принято предположительно — техническое, менять свободно на ревью
- Namespace/version 1, snake_case, UUID/id limits, capability и diff-validation — способ реализации утверждённого shared/local поведения, не новый продукт.
- Per-card key по logical host path с сохранением на reload; dashboard restructuring не получает миграцию identity. Fallback не использует общий preference. Legacy sizes seed новой записи без удаления старой.
- 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.
- Native narrow bridge для старых HA/unknown, caches/timer, lazy modules и perf fixtures — инженерная адаптация, не новые настройки пользователю.
- Отсутствующий namespace читается как derived defaults без server write; deliberate empty остаётся empty. Это сохраняет первый запуск read-only.
- Кодировки ошибок/id naming, расположение pure модулей и названия тестов допускают уточнение. Не допускают произвольного изменения Q1–Q5, прав/киоска, метрик, разделения shared/local или команды остановиться на S5.