mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 05:08:53 +00:00
docs: specify public data-hp test contract (#489)
Issue: #489 User-Visible: no
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
# #489 — Объявленный `data-hp`-контракт для UI и E2E
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/489
|
||||
- **Тип / приоритет:** feature + tests + docs / P2
|
||||
- **Трек:** полный; задача охватывает несколько UI-поверхностей и вводит новый
|
||||
публичный DOM-контракт со стабильностью между релизами
|
||||
- **Оценка:** пользовательская ценность 7/10; ценность для разработки 9/10;
|
||||
сложность 6/10; риск 6/10
|
||||
- **Связано:** #486; `docs/STYLING-HOOKS.md`,
|
||||
`demo/smoke_styling_hooks.mjs`
|
||||
|
||||
## 1. Сценарий
|
||||
|
||||
Персона — Home admin или разработчик интеграционного теста, который проверяет
|
||||
House Plan внутри настоящего Home Assistant. Поверхности — обычная dashboard
|
||||
card, отдельная sidebar panel #486, три редактора и общие диалоги. Момент —
|
||||
автоматическая проверка загрузки, навигации и основных действий перед выпуском,
|
||||
либо точечная настройка card-mod опытным пользователем.
|
||||
|
||||
Внешний тестовый репозиторий `houseplan-e2e` не имеет права читать приватные
|
||||
поля экземпляра карточки, случайные CSS-классы или текущую вложенность DOM. Ему
|
||||
нужны небольшие, объявленные и версионируемые точки привязки.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** интерфейс выглядит и работает как обычно, но внешний тест вынужден
|
||||
искать служебные элементы по нестабильным классам и не может честно определить,
|
||||
готова ли карточка.
|
||||
|
||||
**После:** интерфейс выглядит и работает ровно так же, а тесты и card-mod могут
|
||||
находить объявленные элементы и состояние через стабильные атрибуты.
|
||||
|
||||
## 3. Проблема
|
||||
|
||||
`docs/STYLING-HOOKS.md` уже обещает стабильность объектам самого плана:
|
||||
устройствам, комнатам, проёмам, стенам, декору и вкладкам пространств. Но
|
||||
служебные поверхности не покрыты единообразно:
|
||||
|
||||
- готовность читается из приватного `_booting`, а режим — из `_mode`;
|
||||
- шапочные действия ищутся по `.settings-button`, `.pdf-button`, `.zb` и
|
||||
другим классам;
|
||||
- `hp-dialog` не сообщает назначение, а его основные действия не имеют общей
|
||||
семантической точки привязки;
|
||||
- тулбары редакторов, их инструменты, закрытие и вторичный трей не образуют
|
||||
объявленного контракта;
|
||||
- empty state, toast и app bar sidebar panel не имеют стабильных селекторов;
|
||||
- нет одного машиночитаемого источника, по которому внешний репозиторий может
|
||||
проверить допустимость своих селекторов;
|
||||
- текущий smoke доказывает объектные styling hooks, но не новый test contract.
|
||||
|
||||
Одновременно в исходниках есть диагностические `data-hp` (`iso-*`,
|
||||
`zigbee-topology-*`, `resize-*` и другие). Сам факт существования такого
|
||||
атрибута не должен случайно превращать его в обещание совместимости.
|
||||
|
||||
## 4. Скоуп
|
||||
|
||||
1. Расширить `docs/STYLING-HOOKS.md` отдельным разделом **Test hooks** на
|
||||
английском: назначение, селекторы, словари значений и политика стабильности.
|
||||
2. Добавить `data-hp` на уже существующие элементы шапки, редакторов,
|
||||
диалогов, empty state, toast и sidebar panel без добавления обёрток и без
|
||||
изменения CSS.
|
||||
3. Публиковать на каждом корневом `ha-card` готовность и текущий режим.
|
||||
4. Ввести машинный инвентарь `docs/data-hp-contract.json` и unit-проверку его
|
||||
согласованности с исходниками.
|
||||
5. Расширить `demo/smoke_styling_hooks.mjs` доказательствами DOM-состояний,
|
||||
которые нельзя подтвердить одним статическим поиском.
|
||||
6. Там, где существующий smoke уже обращается к охваченному элементу по
|
||||
внутреннему классу, перевести его на объявленный hook; полный перенос всех
|
||||
остальных smoke не требуется.
|
||||
7. Добавить пользовательскую запись в оба changelog со ссылкой на #489.
|
||||
|
||||
## 5. Не входит
|
||||
|
||||
- изменение разметки, вложенности, визуальных стилей, размеров или расположения;
|
||||
- новые кнопки, диалоги, тосты, действия или пользовательские настройки;
|
||||
- изменение логики готовности, загрузки, редакторов или разрешений;
|
||||
- backend seed/reset API и код внешнего `houseplan-e2e`;
|
||||
- обещание стабильности для CSS-классов, shadow DOM-вложенности и внутренних
|
||||
диагностических `data-hp`;
|
||||
- обязательный перевод всей существующей smoke-suite на новые селекторы;
|
||||
- добавление русскоязычного раздела в User Guide: это технический контракт, а
|
||||
не новый пользовательский сценарий.
|
||||
|
||||
## 6. Контракт поведения
|
||||
|
||||
### 6.1 Общие правила
|
||||
|
||||
1. `data-hp` остаётся единым атрибутом «что это за элемент».
|
||||
2. Все значения из публичного инвентаря стабильны. Переименование или удаление
|
||||
требует записи в оба changelog и сохранения старого значения либо
|
||||
эквивалентного совместимого селектора на протяжении одной следующей
|
||||
**стабильной** версии. Добавление нового значения совместимо.
|
||||
3. Атрибут не создаёт поведения и не участвует в стилях продукта. Удаление
|
||||
атрибута через card-mod не должно ломать работу интерфейса.
|
||||
4. Когда элемент не существует по действующим permission/mode/state-правилам,
|
||||
соответствующий hook также отсутствует. Контракт не требует скрытой копии
|
||||
элемента.
|
||||
5. Тесты выбирают элементы внутри shadow root конкретного экземпляра House
|
||||
Plan. Уникальность между несколькими карточками на странице не обещается.
|
||||
|
||||
### 6.2 Корневое состояние
|
||||
|
||||
Каждый отрисованный корневой `ha-card`, включая boot/fixed-floor/empty/error
|
||||
ветки, получает:
|
||||
|
||||
| Атрибут | Значения | Правило |
|
||||
|---|---|---|
|
||||
| `data-hp-state` | `booting`, `ready` | `booting`, пока действует существующий `_booting`; после его завершения — `ready` |
|
||||
| `data-hp-mode` | `view`, `plan`, `device`, `decor` | Публичное имя текущего режима; внутреннее `devices` отображается как `device` |
|
||||
|
||||
Empty card после завершённой загрузки является `ready`, даже если в ней нет ни
|
||||
одного пространства. Ошибка fixed-floor не возвращает карточку в `booting`.
|
||||
|
||||
### 6.3 Публичные `data-hp` для шапки и состояния
|
||||
|
||||
| `data-hp` | Существующий элемент | Условие существования |
|
||||
|---|---|---|
|
||||
| `settings` | кнопка общих настроек | как сейчас: writable normal mode |
|
||||
| `pdf` | кнопка PDF | как сейчас |
|
||||
| `support` | кнопка помощи/обратной связи | как сейчас |
|
||||
| `zoom-in` | кнопка `+` zoom | когда отображается header |
|
||||
| `zoom-out` | кнопка `−` zoom | когда отображается header |
|
||||
| `zoom-fit` | кнопка «вписать всё» | когда отображается header |
|
||||
| `space-add` | кнопка `+` рядом со вкладками | по существующим permission/fixed-floor/kiosk правилам |
|
||||
| `space-settings` | шестерёнка внутри вкладки | `data-id` равен id пространства |
|
||||
| `empty` | существующий контейнер пустого состояния | нет пространств либо отображается fixed-floor pending/error |
|
||||
| `create-space` | существующая CTA первого пространства | только если CTA уже разрешена |
|
||||
| `toast` | существующий toast | дополнительно `data-kind="message"`; семантика severity в #489 не вводится |
|
||||
|
||||
`space-settings` несёт `data-id`, потому что на экране может быть несколько
|
||||
шестерёнок. Остальные шапочные actions в пределах одной карточки единичны.
|
||||
|
||||
### 6.4 Редакторы
|
||||
|
||||
| `data-hp` | Дополнительный атрибут | Контракт |
|
||||
|---|---|---|
|
||||
| `toolbar` | `data-kind="plan|device|decor"` | корень видимого primary toolbar |
|
||||
| `tool` | `data-tool="<stable-id>"` | существующая кнопка выбора инструмента/команды редактора |
|
||||
| `editor-close` | — | существующая кнопка X в каждом toolbar и X активной mode-tab |
|
||||
| `tray` | `data-kind="<group-id>"` при наличии группы | корень видимой secondary/context surface редактора |
|
||||
|
||||
Минимальный словарь `data-tool`:
|
||||
|
||||
- Plan: `select`, `draw`, `column`, `merge`, `split`, `resize`,
|
||||
`wall-thickness`, `delete-room` и id существующих групповых launcher-кнопок;
|
||||
- Device: `add-device`, `device-inbox`, `icon-rules` и id существующих
|
||||
групповых launcher-кнопок;
|
||||
- Decor: `select`, `backdrop`, `line`, `rect`, `ellipse`, `text`, `furniture`,
|
||||
`image`, `erase` и id существующих групповых launcher-кнопок.
|
||||
|
||||
Undo/Redo, picker и отдельные save/cancel внутри трея не объявляются `tool`:
|
||||
они не выбирают инструмент. Существующий `data-editor-navigation` сохраняется;
|
||||
новый `editor-close` дополняет его, а не заменяет в переходный период.
|
||||
|
||||
### 6.5 Диалоги
|
||||
|
||||
Сам host каждого `hp-dialog` всегда получает `data-hp="dialog"`. Каждый call
|
||||
site задаёт один стабильный broad kind из закрытого стартового словаря:
|
||||
|
||||
`space`, `room`, `marker`, `opening`, `physical`, `decor`, `settings`, `pdf`,
|
||||
`support`, `confirm`, `onboarding`, `import`, `backup`, `kiosk`, `rules`,
|
||||
`device-inbox`, `vacuum`, `summary`, `info`.
|
||||
|
||||
Новые kind добавляются совместимо. Broad kind описывает пользовательскую
|
||||
задачу, а не имя приватного метода; несколько вариантов одного workflow могут
|
||||
иметь одинаковый kind.
|
||||
|
||||
На существующих главных действиях диалога:
|
||||
|
||||
- `data-hp="dialog-confirm"` — Save/Create/Apply/Send/Import/Run и другое
|
||||
действие, которое подтверждает результат данного диалога;
|
||||
- `data-hp="dialog-cancel"` — Cancel/Close/Back, которое закрывает диалог без
|
||||
принятия результата;
|
||||
- destructive secondary actions вроде Delete не получают `dialog-confirm`,
|
||||
если открывают отдельный confirm workflow;
|
||||
- встроенная fallback-кнопка X `hp-dialog` получает `dialog-cancel`; внутреннюю
|
||||
close-кнопку HA shadow tree контракт не охватывает.
|
||||
|
||||
У многошагового диалога hooks относятся к текущему шагу. Если у шага нет
|
||||
явного confirm/cancel button, синтетическая кнопка не добавляется.
|
||||
|
||||
### 6.6 Sidebar panel
|
||||
|
||||
В shadow root `<houseplan-panel>` существующие элементы получают:
|
||||
|
||||
- `data-hp="panel-menu"` на кнопке, отправляющей `hass-toggle-menu`;
|
||||
- `data-hp="panel-title"` на видимом заголовке House Plan.
|
||||
|
||||
Это не распространяет styling contract карточки через два shadow root и не
|
||||
обещает стабильность остальной panel-shell разметки.
|
||||
|
||||
## 7. UX
|
||||
|
||||
Визуальная и интерактивная дельта равна нулю:
|
||||
|
||||
- подписи, доступность, tab order и focus management не меняются;
|
||||
- кнопки остаются видимыми и активными ровно в прежних условиях;
|
||||
- режимы и readiness лишь проецируются в DOM, а не вычисляются заново;
|
||||
- никаких тестовых индикаторов, debug-панелей или пользовательских настроек не
|
||||
появляется.
|
||||
|
||||
Для card-mod действует прежнее предупреждение: поддерживаются только явно
|
||||
объявленные hooks, но не произвольный CSS и не внутренняя структура.
|
||||
|
||||
## 8. Модель данных и миграция
|
||||
|
||||
Пользовательская конфигурация и backend storage не меняются; миграции нет.
|
||||
|
||||
`docs/data-hp-contract.json` имеет версионированную схему:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"attribute": "data-hp",
|
||||
"hooks": {
|
||||
"device": { "elements": ["div"], "since": "1.59.0-beta.3", "audience": ["styling", "test"] },
|
||||
"settings": { "elements": ["button"], "since": "1.73.0-beta.7", "audience": ["test"] }
|
||||
},
|
||||
"rootAttributes": {
|
||||
"data-hp-state": { "element": "ha-card", "values": ["booting", "ready"], "since": "1.73.0-beta.7" }
|
||||
},
|
||||
"internalPrefixes": ["iso-", "zigbee-topology-", "resize-", "plan-snap-", "hidden-wall-"]
|
||||
}
|
||||
```
|
||||
|
||||
Полный файл содержит все уже объявленные значения §3
|
||||
`STYLING-HOOKS.md`, новые значения #489, `data-hp-mode`, связанные словари
|
||||
`data-kind`/`data-tool` там, где они являются частью нового селектора, и
|
||||
исчерпывающие исключения для существующих внутренних exact values/prefixes.
|
||||
`since` существующих hooks восстанавливается по документации/истории, новых —
|
||||
первая планируемая версия выпуска текущей линии (`1.73.0-beta.7`). Если до
|
||||
релиза линия изменится, release commit обновляет только metadata `since`.
|
||||
|
||||
JSON не импортируется runtime-кодом и не попадает в initial bundle. Это
|
||||
документ и вход для тестов/внешнего E2E.
|
||||
|
||||
## 9. i18n
|
||||
|
||||
Новых пользовательских строк нет. EN/RU/DE/FR bundles не меняются. Раздел Test
|
||||
hooks пишется на английском как разработческий контракт; существующая русская
|
||||
User Guide лишь сохраняет ссылку на styling hooks, если такая ссылка уже есть.
|
||||
|
||||
## 10. Критерии приёмки
|
||||
|
||||
### AC1 — корневые состояние и режим
|
||||
|
||||
Каждый render branch `ha-card` несёт допустимые `data-hp-state` и
|
||||
`data-hp-mode`; переход View → каждый editor → View обновляет mode без чтения
|
||||
приватных полей.
|
||||
|
||||
**Доказательство:** unit/source-contract test + расширенный
|
||||
`node demo/smoke_styling_hooks.mjs`.
|
||||
|
||||
### AC2 — шапка, empty и toast
|
||||
|
||||
Все доступные в текущем состоянии header actions находятся по селекторам из
|
||||
§6.3; writable empty state имеет `empty` и `create-space`, read-only empty не
|
||||
получает ложной CTA; toast находится как `toast[data-kind="message"]`.
|
||||
|
||||
**Доказательство:** `demo/smoke_styling_hooks.mjs` на обычной, empty writable
|
||||
и empty read-only fixtures.
|
||||
|
||||
### AC3 — три редактора
|
||||
|
||||
В каждом editor существует один видимый `toolbar` с правильным `data-kind`,
|
||||
каждая заявленная tool button имеет стабильный `data-tool`, закрытие находится
|
||||
как `editor-close`, а открытая secondary surface — как `tray`.
|
||||
|
||||
**Доказательство:** browser smoke проходит Plan/Device/Decor и открывает по
|
||||
одному group/tray; unit test сверяет словари inventory с source call sites.
|
||||
|
||||
### AC4 — диалоги
|
||||
|
||||
Каждый `hp-dialog` host имеет `dialog` и допустимый `data-kind`; все
|
||||
существующие семантические confirm/cancel buttons размечены по §6.5, не меняя
|
||||
действия и focus flow.
|
||||
|
||||
**Доказательство:** source-contract test запрещает call site без kind;
|
||||
unit-тест `hp-dialog` проверяет host/fallback X; smoke открывает settings,
|
||||
space/onboarding и confirm workflows и выполняет действия через hooks.
|
||||
|
||||
### AC5 — sidebar panel
|
||||
|
||||
Menu и title существующей #486 panel находятся как `panel-menu` и
|
||||
`panel-title`, при этом menu по-прежнему отправляет ровно одно
|
||||
`hass-toggle-menu`.
|
||||
|
||||
**Доказательство:** unit test `houseplan-panel`.
|
||||
|
||||
### AC6 — машинный инвентарь закрыт и сам себя защищает
|
||||
|
||||
`docs/data-hp-contract.json` валиден и содержит каждый публичный hook. Новый
|
||||
`test/data-hp-contract.test.mjs` падает, если:
|
||||
|
||||
- объявленное значение больше не встречается в исходниках;
|
||||
- в исходниках появилось статическое `data-hp`, которого нет в публичном
|
||||
списке и которое не разрешено внутренним prefix/exact allowlist;
|
||||
- `hp-dialog` call site не имеет kind;
|
||||
- обязательное поле `elements`, `since` или `audience` отсутствует;
|
||||
- переименованный fixture-hook не совпадает с inventory.
|
||||
|
||||
**Доказательство:** `npm test`, включая локальный mutant/fixture case внутри
|
||||
самого unit-теста.
|
||||
|
||||
### AC7 — документация и совместимость
|
||||
|
||||
`STYLING-HOOKS.md` отличает styling и test audiences, перечисляет селекторы и
|
||||
политику переходного периода. Оба changelog содержат понятную запись со ссылкой
|
||||
на #489. Пользовательская config и видимый DOM/layout не меняются кроме новых
|
||||
атрибутов.
|
||||
|
||||
**Доказательство:** review diff + `npm run check:docs` (если такой script
|
||||
доступен в текущем package) и обычный build.
|
||||
|
||||
### AC8 — обязательные гейты
|
||||
|
||||
Проходят `npm run typecheck`, `npm test`, `npm run build`,
|
||||
`npm run bundle:sync`, `npm run bundle:budget` и локальный
|
||||
`node demo/smoke_styling_hooks.mjs`.
|
||||
|
||||
**Доказательство:** точные команды и результаты в комментарии #489 перед
|
||||
`S7-code-review`.
|
||||
|
||||
## 11. План автотестов
|
||||
|
||||
1. Новый Node test читает JSON, валидирует схему и сканирует `src/**/*.ts`,
|
||||
включая статические и ограниченный набор динамических Lit assignments.
|
||||
2. Тест содержит контролируемую строку-мутант: замена одного публичного
|
||||
значения обязана дать ошибку `undeclared/missing`, чтобы доказать, что gate
|
||||
не является формальным чтением JSON.
|
||||
3. Unit `hp-dialog` проверяет автоматический host hook и fallback close hook;
|
||||
существующие focus/Escape тесты остаются зелёными.
|
||||
4. Unit panel-shell проверяет два hooks и отсутствие дублированного menu event.
|
||||
5. Styling smoke последовательно проверяет ready/view, header, toast, empty,
|
||||
три editor toolbars, tray и репрезентативные dialog kinds/actions.
|
||||
6. Полный smoke/golden/performance остаются предрелизными гейтами; golden
|
||||
baseline меняться не должен, поскольку пиксельной дельты нет.
|
||||
|
||||
## 12. Риски и меры
|
||||
|
||||
- **Пропущенный conditional branch.** Root attributes выносятся в один
|
||||
маленький resolver/helper и используются во всех ранних `ha-card` returns;
|
||||
smoke отдельно проходит empty и обычный path.
|
||||
- **Неполная классификация диалогов.** Unit сканирует все `<hp-dialog ...>` и
|
||||
запрещает неразмеченный call site; broad kinds не зависят от имени метода.
|
||||
- **Ложное превращение diagnostics в API.** Публичные hooks и внутренний
|
||||
allowlist лежат раздельно; docs прямо отрицает стабильность allowlist.
|
||||
- **Regex gate пропускает динамику.** Динамические assignments допускаются
|
||||
только через явно проверенные expressions/fixtures; runtime smoke доказывает
|
||||
результирующий DOM.
|
||||
- **Сломанный внешний E2E при расширении.** `schemaVersion` меняется только при
|
||||
несовместимом изменении структуры JSON; добавление hook не меняет версию
|
||||
схемы.
|
||||
- **Рост initial bundle.** JSON не импортируется runtime-кодом; атрибуты дают
|
||||
только небольшую строковую дельту, проверяемую bundle budget.
|
||||
|
||||
## 13. Откат
|
||||
|
||||
До выпуска ветка откатывается обычным revert целиком. После выпуска удалять
|
||||
или переименовывать опубликованные hooks немедленно нельзя: сначала changelog и
|
||||
одна стабильная версия совместимого перехода. Runtime-атрибуты можно перестать
|
||||
использовать внутри продукта в любой момент, пока публичные селекторы остаются
|
||||
доступны. JSON schema v1 сохраняется, либо при несовместимой структуре получает
|
||||
новую `schemaVersion`.
|
||||
|
||||
Пользовательские данные откатывать не требуется.
|
||||
|
||||
## 14. Release-артефакты
|
||||
|
||||
- source изменения в `src/**` и тесты в `test/**`, `demo/**`;
|
||||
- `docs/data-hp-contract.json` и обновлённый `docs/STYLING-HOOKS.md`;
|
||||
- запись `docs/specs/README.md` и двусторонняя ссылка issue ↔ ТЗ;
|
||||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` в том же user-visible commit;
|
||||
- синхронные bundle trees после `npm run bundle:sync`;
|
||||
- без golden baseline, если visual diff действительно нулевой.
|
||||
|
||||
## 15. Принятые предположения
|
||||
|
||||
Эти технические решения приняты предположительно и могут быть свободно
|
||||
изменены ревьюером без блокировки владельца:
|
||||
|
||||
- JSON использует object map `hooks[value]`, чтобы внешний E2E мог проверять
|
||||
селектор без линейного поиска;
|
||||
- `audience` различает `styling` и `test`, но оба набора получают одинаковое
|
||||
обещание стабильности;
|
||||
- toast в этой задаче имеет один честный kind `message`; severity нельзя
|
||||
достоверно восстановить из переведённого текста и она не нужна заявленному
|
||||
E2E-сценарию;
|
||||
- dialog kinds намеренно broad и не кодируют каждый внутренний подшаг;
|
||||
- `device` — публичное имя режима при внутреннем `_mode === "devices"`;
|
||||
- следующая запись `since` предварительно равна `1.73.0-beta.7` и уточняется
|
||||
release commit, если версия линии изменится;
|
||||
- внутренние исключения могут быть exact values сверх перечисленных в issue,
|
||||
если они уже существуют на исходной вершине `dev`; это не делает их API;
|
||||
- открытых продуктовых вопросов нет.
|
||||
@@ -29,6 +29,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| Issue | ТЗ |
|
||||
|---|---|
|
||||
| [#486](https://github.com/Matysh/houseplan-card/issues/486) Панель House Plan в боковом меню HA | [486-house-plan-panel.md](486-house-plan-panel.md) |
|
||||
| [#489](https://github.com/Matysh/houseplan-card/issues/489) Объявленный `data-hp`-контракт для UI и E2E | [489-data-hp-contract.md](489-data-hp-contract.md) |
|
||||
| [#484](https://github.com/Matysh/houseplan-card/issues/484) Внешняя размерная цепь ступенчатого фасада в PDF | [484-pdf-exterior-dimension-chain.md](484-pdf-exterior-dimension-chain.md) |
|
||||
| [#482](https://github.com/Matysh/houseplan-card/issues/482) Доводка экспорта пространства в PDF | [482-pdf-export-polish.md](482-pdf-export-polish.md) |
|
||||
| [#471](https://github.com/Matysh/houseplan-card/issues/471) Убрать белые raised plates вокруг маркеров и названий комнат | [471-isometric-overlay-white-plates.md](471-isometric-overlay-white-plates.md) |
|
||||
|
||||
Reference in New Issue
Block a user