mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
docs: define summary panel hardening contract (#493)
Issue: #493 User-Visible: no
This commit is contained in:
@@ -0,0 +1,481 @@
|
||||
# #493 — Ограниченный picker и надёжный lifecycle сводной панели
|
||||
|
||||
Issue: [#493](https://github.com/Matysh/houseplan-card/issues/493).
|
||||
Связанные задачи: [#437](https://github.com/Matysh/houseplan-card/issues/437),
|
||||
[#490](https://github.com/Matysh/houseplan-card/issues/490).
|
||||
Ветка: `issue/493-summary-panel-hardening`; база спецификации:
|
||||
`dev@71aeb860`.
|
||||
Редакция: 2026-09-09. Полный трек, P2/bug.
|
||||
Канонический статус — метка issue, не заголовок этого документа.
|
||||
|
||||
## 1. Сценарий
|
||||
|
||||
Администратор дома открывает настройки сводной панели в карточке House Plan,
|
||||
где уже может быть до 200 показателей и тысячи доступных HA-сущностей. Он
|
||||
выбирает источник показателя, меняет локальный размер значков/текста, повторно
|
||||
открывает или пересоздаёт карточку, оптимизирует/копирует/импортирует план.
|
||||
Домочадец либо пользователь киоска меняет только локальный показ и размеры.
|
||||
На телефоне любой доступный ему вариант формы должен помещаться в экран.
|
||||
|
||||
Это продолжение утверждённого исключения #437 из `docs/SCOPE.md`: надёжный
|
||||
read-only overview (J1) и сохранение настройки дома при его изменении (J6), а
|
||||
не расширение House Plan до произвольного dashboard framework.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** большая форма может на секунды зависать, потому что полный список
|
||||
сущностей повторяется в каждой строке; повторный `setConfig` визуально сбрасывает
|
||||
сохранённые размеры; после ухода со страницы или смены пользователя может
|
||||
остаться старый черновик; на узком русском интерфейсе проценты и действия
|
||||
обрезаются; Optimize имеет отличающийся серверный контракт сохранения панели.
|
||||
|
||||
**После:** у каждой строки остаётся понятный выбор источника, но открыт и
|
||||
отрисован только один ограниченный поисковый список; локальные размеры не
|
||||
скачут; новая сессия/пользователь/набор прав не наследует чужой диалог; форма
|
||||
полностью доступна на узком экране; все штатные способы изменения плана
|
||||
сохраняют либо явно заменяют панель по одной документированной матрице.
|
||||
|
||||
## 3. Проблема и подтверждённые причины
|
||||
|
||||
Аудит и повторное чтение текущего `dev` подтверждают пять независимых пробелов:
|
||||
|
||||
1. `summary-panel-editor.ts` сортирует весь `hass.states` при каждом render и
|
||||
создаёт полный набор `<option>` внутри каждой value-строки. Диагностический
|
||||
случай 100 строк × 1012 states создал 101510 option-узлов; открытие заняло
|
||||
около 2982 ms, один ввод — около 829 ms. Эти числа не являются CI budget,
|
||||
но доказывают архитектурный рост `rows × states`.
|
||||
2. `houseplan-card.setConfig()` повторно записывает `_kioskScale` из старого
|
||||
общего `houseplan_card_kiosk_v1`, а `LoadedSummaryPanelRuntime.loadLocal()`
|
||||
пропускает уже известный preference key. Поэтому local 200%/150% фактически
|
||||
становятся 100%/100%, хотя правильная запись остаётся в localStorage.
|
||||
3. `_leaveCardRoute()` не завершает summary dialog, а `disconnect()` снимает
|
||||
только clock timer. Диалог, draft и результаты незавершённой lazy/async
|
||||
операции не привязаны к generation пользователя/соединения.
|
||||
4. `.summary-local-sizes` всегда требует три колонки
|
||||
`max-content + minmax(140px,1fr) + 48px`. При доступной ширине 346 px русская
|
||||
форма требует около 389 px, и значение процента выходит за экран.
|
||||
5. `config/set` вызывает `preserve_summary_panel_namespace()` и
|
||||
`validate_summary_panel_references()`, а `plan/optimize` — нет. Остальные
|
||||
полные/частичные writers используют разные механизмы, но единая проверяемая
|
||||
матрица для namespace и семантики отсутствует.
|
||||
|
||||
#490 закрывает только authoritative lost-ACK recovery и live dependency
|
||||
invalidations. Она намеренно не исправляет перечисленные пути и не дублирует
|
||||
эту задачу.
|
||||
|
||||
## 4. Область изменения
|
||||
|
||||
В scope:
|
||||
|
||||
1. один активный ограниченный picker источника с общим поисковым индексом;
|
||||
2. сохранение фактически применённых local scales при повторном same-key
|
||||
`setConfig`, remount и reload;
|
||||
3. завершение dialog/draft/active picker и инвалидирование закрытого runtime
|
||||
state при route leave, disconnect/reconnect, смене HA user или прав;
|
||||
4. адаптивная полная и local-only форма на 320/390 px, RU/EN и увеличенном
|
||||
системном тексте;
|
||||
5. единый backend helper/contract для обычной записи `config/set` и Optimize;
|
||||
6. явная writer matrix для config/set, Optimize, full/space/plan-only import,
|
||||
Optimize/Import Undo, создание/копирование/удаление пространства;
|
||||
7. автоматические unit/backend/browser/performance доказательства регрессий.
|
||||
|
||||
Не в scope:
|
||||
|
||||
- новые типы значений, формулы, действия, произвольные карточки или больше
|
||||
лимитов 10 блоков × 20 строк;
|
||||
- изменение содержимого или компоновки read-only overlay;
|
||||
- перенос local preferences между браузерами, пользователями или карточками;
|
||||
- новая настройка, ручная миграция либо изменение server config/store version;
|
||||
- изменение правил доступа HA и возможность редактировать shared panel в kiosk;
|
||||
- исправления lost-ACK/live invalidation, уже принятые в #490;
|
||||
- переработка всех entity-picker в House Plan;
|
||||
- смягчение schema, geometry, marker-link или revision guards других writers.
|
||||
|
||||
## 5. Термины и обязательные инварианты
|
||||
|
||||
- **index** — не-DOM массив доступных текущему `hass` entity id и friendly name,
|
||||
нормализованный для поиска и отсортированный детерминированно;
|
||||
- **active picker** — единственная открытая поверхность выбора источника во всей
|
||||
summary form;
|
||||
- **bounded results** — ограниченное число строк результата, независимо от
|
||||
общего числа HA states;
|
||||
- **local identity** — stable preference key плюс HA user/host/route/card slot;
|
||||
- **lifecycle generation** — токен текущего соединённого пользователя и набора
|
||||
прав; завершение старой async-операции не может менять новую generation;
|
||||
- **ordinary writer** — конфигурационная запись поверх текущего документа;
|
||||
- **authoritative restore** — явная полная замена config из full archive либо
|
||||
точное восстановление ранее записанного backup.
|
||||
|
||||
Инварианты:
|
||||
|
||||
1. число DOM-строк picker не зависит от `rows × states`;
|
||||
2. через поиск доступна каждая читаемая `hass.states` entity, в том числе не
|
||||
размещённая на плане; одинаковые friendly names различаются по entity id;
|
||||
3. выбранный старый broken source остаётся видимым и round-trip-ится, пока
|
||||
пользователь явно не заменит его;
|
||||
4. local scale после разрешения identity имеет одного владельца — summary local
|
||||
preference; старый kiosk key только одноразовый seed при отсутствии записи;
|
||||
5. dialog/draft принадлежат ровно одной lifecycle generation;
|
||||
6. omission namespace обычным writer не означает удаление; явный `blocks: []`
|
||||
остаётся единственным пустым version-1 составом;
|
||||
7. ни один writer не реконструирует весь `settings` из известного allowlist и
|
||||
не теряет независимые известные/будущие поля.
|
||||
|
||||
## 6. UX ограниченного выбора источника
|
||||
|
||||
### 6.1 Закрытая строка
|
||||
|
||||
В каждой value-строке вместо постоянно наполненного native select находится
|
||||
одно поле выбора, показывающее текущий источник: системное имя либо
|
||||
`friendly name — entity_id`. Пустой и старый broken source используют уже
|
||||
принятые тексты/предупреждения #437. Изменение остаётся частью общего draft и
|
||||
фиксируется только кнопкой «Сохранить» всей формы.
|
||||
|
||||
### 6.2 Открытие и поиск
|
||||
|
||||
Click/tap/Enter/Space открывает picker только этой строки. Другой открытый
|
||||
picker закрывается. Внутри находятся:
|
||||
|
||||
1. поле поиска по friendly name и entity id;
|
||||
2. три системных показателя;
|
||||
3. не более 100 первых совпадений entity в стабильном порядке;
|
||||
4. текущий broken source отдельной строкой, даже если его нет в index;
|
||||
5. подсказка уточнить поиск, если совпадений больше лимита, либо принятое
|
||||
сообщение «ничего не найдено».
|
||||
|
||||
Все 10 000 entity достижимы через уточнение поиска; ограничение относится к
|
||||
одновременно созданным DOM-узлам, а не к доступному набору. Выбор закрывает
|
||||
picker, переносит source в draft и возвращает фокус в поле строки. Escape и
|
||||
click/tap снаружи закрывают picker без изменения; Tab не запирается внутри
|
||||
вложенной поверхности и сохраняет нормальный modal focus order. На touch
|
||||
каждая интерактивная строка не меньше 44×44 px.
|
||||
|
||||
Active row определяется stable block/value ids, не индексом массива. Если во
|
||||
время draft-операции строка удалена, picker закрывается; reorder не направляет
|
||||
выбор в другую строку. Глобальный поиск над всеми native selects удаляется:
|
||||
поиск живёт рядом с единственным активным списком и не выглядит как фильтр
|
||||
скрытых строк.
|
||||
|
||||
### 6.3 Индекс и обновления
|
||||
|
||||
Index строится один раз на состав/пары `entity_id + friendly_name` текущего
|
||||
доступного `hass.states`, а не на каждое state value update и не для каждой
|
||||
строки. Новая/удалённая/переименованная entity инвалидирует index; обычное
|
||||
изменение state не сортирует и не перестраивает его. Кеш не переносится между
|
||||
пользователями или соединениями и очищается при lifecycle reset.
|
||||
|
||||
## 7. Local scales и repeated setConfig
|
||||
|
||||
После определения local identity значения `icon_scale` и `font_scale` из
|
||||
`houseplan_summary_local_v1:*` являются authority для View, kiosk и открытой
|
||||
формы. `setConfig` не имеет права перезаписать их старым kiosk key, если runtime
|
||||
уже загрузил тот же key.
|
||||
|
||||
Контракт переходов:
|
||||
|
||||
| Событие | Ожидаемое значение |
|
||||
|---|---|
|
||||
| Первый запуск, новой записи нет | Валидные legacy icon/font один раз как seed, иначе 100% |
|
||||
| Повторный `setConfig`, local identity та же | Текущее фактически применённое local значение без скачка |
|
||||
| Reload/remount, identity та же | Значение из localStorage этого key |
|
||||
| Смена user/route/card slot | Значение нового key; ничего из прежнего key не переносится |
|
||||
| Storage недоступен | Session-only значение действует, показывается принятое предупреждение |
|
||||
|
||||
Старый ключ не удаляется и не переписывается. Диапазон 50…300%, шаг 5%, Reset
|
||||
100% и отсутствие влияния на редакторскую геометрию остаются как в #437.
|
||||
|
||||
## 8. Lifecycle, права и async-операции
|
||||
|
||||
Runtime хранит snapshot identity/capabilities: user id (fallback name только по
|
||||
действующему контракту), route/host/slot, kiosk и право shared edit. На каждом
|
||||
`updated/connect` сравнивается snapshot, а не только storage key.
|
||||
|
||||
Следующие события закрывают active picker и summary dialog, отбрасывают draft,
|
||||
снимают timer/listener/observer и инвалидируют index/metrics/live-dependency
|
||||
snapshot: реальный route leave, disconnect, reconnect после disconnect, смена
|
||||
user, смена admin/write capability, переход в/из kiosk. Local preferences на
|
||||
диске при этом не удаляются.
|
||||
|
||||
Каждая lazy import/save/reload/confirmation операция получает generation token.
|
||||
Поздний результат старой generation может завершить уже отправленную серверную
|
||||
запись, но не может снова открыть dialog, применить старый localShow/local scale,
|
||||
показать сообщение в новой пользовательской сессии или вернуть старые entity
|
||||
dependencies. Новый контекст перечитывает authoritative config и собственные
|
||||
local preferences обычным путём.
|
||||
|
||||
Обычная смена размера без disconnect и без смены identity не закрывает draft.
|
||||
Page visibility hidden только останавливает clock по прежнему контракту и сама
|
||||
по себе не считается уходом со страницы.
|
||||
|
||||
## 9. Адаптивная форма
|
||||
|
||||
Полная admin-форма и local-only форма используют одну responsive основу.
|
||||
На ширине dialog body, где три колонки не помещаются, каждый размер становится
|
||||
двухстрочным блоком: подпись и процент в первой строке, полноширинный slider во
|
||||
второй; Reset переносится отдельной доступной строкой. На широкой форме
|
||||
допустима существующая компактная компоновка.
|
||||
|
||||
Обязательные свойства на 320 и 390 CSS px, light/dark, RU/EN, admin/household/
|
||||
kiosk и браузерном увеличении текста 200%:
|
||||
|
||||
- нет горизонтального overflow у body/footer;
|
||||
- подписи, проценты 50/100/300%, предупреждения и Save/Cancel/Reset полностью
|
||||
видимы либо достижимы вертикальным scroll;
|
||||
- footer не перекрывает последний control, safe-area учитывается;
|
||||
- hit target не меньше 44 px, focus outline не обрезан;
|
||||
- форма не уменьшает текст для вмещения и не меняет план/overlay layout.
|
||||
|
||||
## 10. Единый контракт обычной записи панели
|
||||
|
||||
В backend выделяется единая функция подготовки обычного config candidate. Она
|
||||
до `CONFIG_SCHEMA` сохраняет прежний `settings.summary_panel` при omission, а
|
||||
после schema выполняет change-aware reference validation относительно exact
|
||||
stored previous и множества entity, читаемых текущим пользователем.
|
||||
|
||||
Функция вызывается одинаково из `config/set` и `plan/optimize`. Optimize получает
|
||||
readable entity ids по той же permission-политике, что config/set; вычисление
|
||||
не переносится на event loop и остаётся внутри существующего executor/write
|
||||
lock порядка. Идущие следом wall/marker/opening/junction guards не ослабляются.
|
||||
|
||||
Reference validation сохраняет правила #437:
|
||||
|
||||
- неизменённая старая broken entity/space разрешена;
|
||||
- смена только label/title/order не превращает старую ссылку в новую;
|
||||
- новый/заменённый entity id обязан присутствовать в readable hass states;
|
||||
- новый/заменённый space scope обязан ссылаться на существующее пространство;
|
||||
- future version сохраняется lossless и inert, без применения v1 semantics.
|
||||
|
||||
## 11. Writer matrix
|
||||
|
||||
| Операция | Authority для `settings.summary_panel` | Ссылки и независимые namespaces |
|
||||
|---|---|---|
|
||||
| `config/set` | Candidate; omission сохраняет stored, `blocks: []` — явная пустота | Change-aware v1 refs; остальные settings/unknown fields lossless |
|
||||
| `plan/optimize` | Тот же ordinary contract, что `config/set` | Те же refs/permissions; Optimize не становится обходом validation |
|
||||
| Full import/restore | Архив authoritative, включая отсутствие namespace | Structural schema; перенос в другую HA может оставить refs broken; остальные imported namespaces заменяются архивом |
|
||||
| Space/plan-only import | Exact namespaces текущей установки | Source global settings игнорируются; remap касается только импортируемого space graph |
|
||||
| Optimize Undo | Exact config из optimize backup | Не переоценивает восстановленную старую ссылку как новый выбор |
|
||||
| Full Import Undo | Exact config до импорта | Возвращает все прежние namespaces вместе, без preserve-current merge |
|
||||
| Создание/копирование пространства | Candidate строится поверх exact current config | Панель и все независимые settings сохраняются; copy не переназначает её scope |
|
||||
| Удаление пространства | Ordinary config candidate | Неизменённый scope удалённого space становится старой broken-ссылкой/warning, а не скрытым сбросом панели |
|
||||
|
||||
Для каждой строки тест использует минимум три sentinel namespace: текущий
|
||||
`summary_panel`, другое известное setting и неизвестное расширяющее поле. Там,
|
||||
где операция authoritative, проверяется их полная замена/восстановление; там,
|
||||
где операция частичная, exact сохранение. Полный импорт остаётся единственным
|
||||
исключением preserve-on-omission обычной записи.
|
||||
|
||||
## 12. Модель данных и совместимость
|
||||
|
||||
Новых persisted-полей нет; `settings.summary_panel.version` остаётся 1. Store и
|
||||
plan model version не повышаются. Local key/shape и legacy seed не меняются.
|
||||
|
||||
Старый frontend продолжает игнорировать и round-trip-ить namespace через
|
||||
backend preservation. Новый frontend со старым backend сохраняет действующий
|
||||
local-only режим и не отправляет shared write. Future summary version остаётся
|
||||
lossless/inert. Full archive поведение, privacy projection и support package из
|
||||
#437 не меняются.
|
||||
|
||||
Нельзя чинить Optimize удалением неизвестных fields, принудительным добавлением
|
||||
derived defaults или превращением отсутствующего namespace в persisted default.
|
||||
|
||||
## 13. i18n
|
||||
|
||||
Новые пользовательские строки ограничены active picker: доступное имя поиска,
|
||||
подсказка уточнить запрос при >100 совпадениях и доступное действие закрытия,
|
||||
если оно не выражается существующим shell. Они добавляются с полным паритетом
|
||||
в lazy `summary-panel-i18n.ts` для RU/EN/DE/FR.
|
||||
|
||||
Существующие названия системных показателей, warning broken source,
|
||||
Save/Cancel/Reset, storage warning и проценты не переименовываются. Friendly
|
||||
name, entity id и state values не переводятся House Plan.
|
||||
|
||||
## 14. Производительность
|
||||
|
||||
Канонический witness: 3 карточки, 10 блоков × 20 строк (200 rows) в каждой и
|
||||
10 000 `hass.states`. Измерение выполняется на одном runner до/после, после
|
||||
preload lazy editor chunk, не по одиночному ручному Windows timing.
|
||||
|
||||
Обязательные границы:
|
||||
|
||||
- одновременно не более одного active picker на форму;
|
||||
- не более 100 entity result rows + 3 system rows + одна current-broken row в
|
||||
DOM одного picker; закрытые 200 строк не содержат entity option-list;
|
||||
- обычный state-value update даёт 0 index rebuild; hot add/remove/rename — ровно
|
||||
один rebuild на карточку/generation;
|
||||
- после трёх warmups и не менее 20 samples: открытие уже загруженной формы
|
||||
p95 ≤250 ms, ввод в уже открытом picker p95 ≤50 ms;
|
||||
- state burst не добавляет long task >50 ms из-за закрытой формы/picker;
|
||||
- existing initial View gzip budget и lazy boundary #437 не ослабляются.
|
||||
|
||||
Если абсолютный timing нестабилен на runner, структурные DOM/rebuild assertions
|
||||
всё равно блокирующие; budget не удаляется, а расследуется/калибруется отдельным
|
||||
решением владельца до S7.
|
||||
|
||||
## 15. Критерии приёмки
|
||||
|
||||
**AC1 — ограниченный picker сохраняет полный выбор.** На fixture 200 rows /
|
||||
10 000 states каждая читаемая entity находится по friendly name или entity id,
|
||||
системные и текущий broken source доступны, но DOM bounds из §14 соблюдены.
|
||||
|
||||
Доказательство: pure search/index unit + browser smoke реальными pointer,
|
||||
keyboard и touch actions; mutation возврата `entities.map()` в каждую строку
|
||||
делает DOM assertion красным.
|
||||
|
||||
**AC2 — draft и stable row ownership корректны.** Выбор меняет только active
|
||||
value по stable ids, reorder не перенаправляет его, delete закрывает picker;
|
||||
Escape/click-out не меняют draft, общий Cancel отбрасывает изменения.
|
||||
|
||||
Доказательство: frontend unit + browser smoke; index-based active-row mutation
|
||||
красная.
|
||||
|
||||
**AC3 — индекс не пересобирается от state value.** Обычное обновление значения
|
||||
не сортирует index, hot add/remove/friendly-name change отражаются один раз,
|
||||
user/reconnect не использует прежний index.
|
||||
|
||||
Доказательство: instrumented unit/smoke counters; unconditional rebuild mutation
|
||||
красная.
|
||||
|
||||
**AC4 — local scales переживают repeated same-key setConfig.** Значения
|
||||
200%/150% до и после нескольких `setConfig` фактически и в форме остаются теми
|
||||
же; reload/remount читает их из своего key, новая identity не наследует.
|
||||
|
||||
Доказательство: runtime unit + two-card/two-user browser smoke; legacy overwrite
|
||||
mutation красная.
|
||||
|
||||
**AC5 — lifecycle не переносит старый UI state.** Route leave, disconnect →
|
||||
reconnect, user/permission/kiosk change закрывают modal/active picker, удаляют
|
||||
draft/timers/listeners и старые dependencies. Late lazy/save/reload completion
|
||||
не меняет новую generation.
|
||||
|
||||
Доказательство: fake-timer/promise unit и reuse browser smoke; снятие generation
|
||||
guard краснит late-completion case.
|
||||
|
||||
**AC6 — mobile/local-only формы доступны.** RU/EN, 320/390 px, light/dark,
|
||||
200% text, admin/household/kiosk показывают доступные роли поля, проценты и
|
||||
действия без горизонтального overflow и с hit targets ≥44 px.
|
||||
|
||||
Доказательство: bounding-box smoke + reviewed fresh screenshots/golden;
|
||||
возврат фиксированной трёхколоночной grid красный.
|
||||
|
||||
**AC7 — config/set и Optimize имеют один ordinary contract.** Omission
|
||||
сохраняет v1/future panel exact; `blocks: []` сохраняется как пустая; новые
|
||||
unreadable entity/missing space отклоняются одинаково; старые broken refs и
|
||||
независимые settings round-trip-ятся.
|
||||
|
||||
Доказательство: pure backend matrix + HA websocket tests обоих endpoints;
|
||||
удаление helper call из Optimize красное через обязательный mutation.
|
||||
|
||||
**AC8 — import/copy/undo следуют writer matrix.** Full import authoritative,
|
||||
space/plan-only import и copy сохраняют target namespaces, оба Undo exact
|
||||
восстанавливают backup, delete space не стирает панель.
|
||||
|
||||
Доказательство: backend import/undo harness + frontend space-copy candidate
|
||||
unit; rebuild-settings mutation красная.
|
||||
|
||||
**AC9 — performance witness проходит.** DOM/rebuild bounds и p95/long-task
|
||||
границы §14 выполнены на точном SHA без ослабления других budgets.
|
||||
|
||||
Доказательство: новый профиль/артефакт performance harness и checked-in budget;
|
||||
code review фиксирует команду, runner и before/after.
|
||||
|
||||
**AC10 — совместимость, lazy boundary и docs сохранены.** Нет schema/model bump,
|
||||
старый/new frontend/backend и future version ведут себя по §12; initial View не
|
||||
загружает editor/picker; docs описывают фактический контракт.
|
||||
|
||||
Доказательство: compatibility/lazy manifest tests, `npm run bundle:budget`,
|
||||
`node scripts/check-docs.mjs`, ревью diff schemas и документации.
|
||||
|
||||
## 16. План реализации и автотестов
|
||||
|
||||
1. Вынести pure индекс/поиск/лимит active source picker; сначала добавить
|
||||
unit-тесты доступности всех entities и structural DOM bounds.
|
||||
2. Перевести editor на stable-id active picker и удалить global rows filter;
|
||||
добавить клавиатуру/touch/focus smoke.
|
||||
3. Сделать summary runtime владельцем local scales после identity load;
|
||||
связать async callbacks с lifecycle generation и вызвать reset из route
|
||||
lifecycle карточки.
|
||||
4. Перестроить responsive local-size grid; снять bounding boxes и fresh visual
|
||||
evidence по матрице AC6.
|
||||
5. Обобщить backend ordinary summary guard для config/set/Optimize, не меняя
|
||||
порядок остальных validators; добавить readable entity snapshot Optimize.
|
||||
6. Добавить writer-matrix tests для import/undo/copy/delete и sentinels
|
||||
независимых namespaces.
|
||||
7. Добавить performance fixture 200/10k/3, checked-in budget/result schema и
|
||||
diff-aware harness routing; перед beta прогнать полный performance/golden/
|
||||
smoke по runbook.
|
||||
|
||||
Ожидаемые файлы/модули:
|
||||
|
||||
- `src/summary-panel-editor.ts`, `src/summary-panel-runtime-loaded.ts`,
|
||||
`src/summary-panel-style.ts`, `src/summary-panel-host.ts`,
|
||||
`src/houseplan-card.ts`, новый pure picker helper при необходимости;
|
||||
- `src/summary-panel-i18n.ts` (RU/EN/DE/FR);
|
||||
- `custom_components/houseplan/validation.py`,
|
||||
`custom_components/houseplan/websocket_api.py`, import/export только если
|
||||
matrix test выявит фактическое расхождение;
|
||||
- `test/summary-panel.test.mjs`, browser smoke/performance fixture и budgets,
|
||||
`tests_backend/test_summary_panel.py`, websocket/import tests;
|
||||
- `docs/ARCHITECTURE.md`, `docs/CONFIG-COMPATIBILITY.md`, `docs/TESTING.md`,
|
||||
user guide RU/EN и оба changelog.
|
||||
|
||||
## 17. Риски и меры
|
||||
|
||||
- **Лимит списка делает entity недостижимой.** Лимит только на DOM, поиск идёт
|
||||
по полному index; exact id всегда находится.
|
||||
- **Stale picker пишет не в ту строку.** Active owner — stable ids; отсутствие
|
||||
owner закрывает поверхность без mutation.
|
||||
- **Смена user во время save.** Generation guard отделяет server outcome от
|
||||
local/UI adoption; новый контекст перечитывает authority.
|
||||
- **Optimize расходится с config/set.** Один helper и paired endpoint matrix,
|
||||
а не две похожие последовательности.
|
||||
- **Full restore ошибочно сохраняет текущую панель.** Authoritative операции
|
||||
проверяются противоположными sentinels к ordinary omission.
|
||||
- **Мобильный фикс ломает desktop.** Responsive breakpoint выбирается по
|
||||
доступной ширине dialog body; wide и narrow golden обязательны.
|
||||
- **Perf-тест ложно зелёный.** Структурный count блокирует независимо от timing,
|
||||
timing сравнивается на одном runner и mutation возвращает N×M.
|
||||
|
||||
## 18. Откат
|
||||
|
||||
Реализацию можно откатить одним issue-коммитом: persisted schema и local key не
|
||||
меняются, миграции данных нет. Откат возвращает медленный picker, scale/lifecycle
|
||||
и Optimize-пробелы, но не делает сохранённый namespace нечитаемым. Нельзя
|
||||
откатывать только backend guard либо только generation token, оставляя тесты и
|
||||
документацию от нового контракта.
|
||||
|
||||
## 19. Release-артефакты
|
||||
|
||||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`: быстрый выбор источника,
|
||||
сохранение размеров, mobile/lifecycle и безопасный Optimize;
|
||||
- `docs/USER-GUIDE.md` и `.ru.md`: active picker и local settings без новых
|
||||
пользовательских настроек;
|
||||
- `docs/ARCHITECTURE.md`: index/lifecycle generation и единый writer guard;
|
||||
- `docs/CONFIG-COMPATIBILITY.md`: полная writer matrix;
|
||||
- `docs/TESTING.md`: команды, fixtures, DOM/p95 budgets и negative witnesses;
|
||||
- свежие reviewed screenshots/golden wide/narrow light/dark и RU/EN enlarged;
|
||||
- performance artifact 200 rows/10k states/3 cards на точном SHA;
|
||||
- security report не требуется: права не расширяются, но permission matrix
|
||||
входит в backend/browser tests;
|
||||
- перед beta обязательны golden, smoke, performance и Linux CI HA harness по
|
||||
runbook; в цикле реализации — typecheck, unit и build.
|
||||
|
||||
## 20. Принятые технические предположения
|
||||
|
||||
Эти пункты не меняют видимый продуктовый контракт и могут быть скорректированы
|
||||
ревьюером при сохранении AC:
|
||||
|
||||
1. Bounded result limit — 100 entity rows; index может быть per-runtime либо
|
||||
безопасно разделён между карточками по immutable composition signature.
|
||||
2. Active picker реализуется внутри lazy summary editor без зависимости от
|
||||
нестабильных внутренних HA components; конкретный DOM primitive свободен.
|
||||
3. Friendly-name index инвалидируется детерминированной signature, а state
|
||||
values не входят в неё; допускается более дешёвый эквивалентный механизм.
|
||||
4. Lifecycle generation увеличивается на disconnect и identity/capability
|
||||
change; route leave вызывает явный summary reset до warm snapshot.
|
||||
5. Ordinary backend helper располагается в `validation.py` либо рядом с WS
|
||||
writer; важно единственное исполнение контракта, не имя функции.
|
||||
6. Full import missing refs остаются допустимыми transfer broken refs и
|
||||
обнаруживаются формой по правилам #437; новый import-warning UI не вводится.
|
||||
7. Точные имена новых tests/scripts/budget files можно выбрать при реализации;
|
||||
witness, отрицательные мутации и границы AC менять нельзя без нового решения.
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#493](https://github.com/Matysh/houseplan-card/issues/493) Ограниченный picker и надёжный lifecycle сводной панели | [493-summary-panel-hardening.md](493-summary-panel-hardening.md) |
|
||||
| [#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) |
|
||||
|
||||
Reference in New Issue
Block a user