Files
houseplan-card/docs/specs/489-data-hp-contract.md
T
2026-09-08 21:04:21 +03:00

28 KiB
Raw Blame History

#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, devices, decor Публичное имя текущего режима; словарь совпадает с уже опубликованным mode-devices и существующим data-editor-navigation="devices"

Empty card после завершённой загрузки является ready, даже если в ней нет ни одного пространства. Ошибка fixed-floor не возвращает карточку в booting.

6.3 Публичные data-hp для шапки и состояния

data-hp Существующий элемент Условие существования
settings кнопка общих настроек как сейчас: writable normal mode
pdf кнопка PDF как сейчас
support кнопка помощи/обратной связи как сейчас
zoom-in кнопка + zoom как сейчас: в ordinary plan render; в kiosk остаётся в DOM внутри скрытого CSS header
zoom-out кнопка − zoom как сейчас: в ordinary plan render; в kiosk остаётся в DOM внутри скрытого CSS header
zoom-fit кнопка «вписать всё» как сейчас: в ordinary plan render; в kiosk остаётся в DOM внутри скрытого CSS 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
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 имеет версионированную схему:

{
  "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 + обязательный node scripts/check-docs.mjs после принятия канонического screenshot-артефакта и обычный build.

AC8 — обязательные гейты

Проходят npm run typecheck, npm test, npm run build, npm run bundle:sync, npm run bundle:budget и локальный node demo/smoke_styling_hooks.mjs. Поскольку меняется src/**, дополнительно обязателен node scripts/check-docs.mjs: до пересъёмки он должен честно сообщить об устаревшем source fingerprint, после принятия нового комплекта — пройти зелёным.

Доказательство: точные команды и результаты в комментарии #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;
  • канонический комплект docs screenshots из workflow Docs screenshots на точном implementation SHA, принятый командой npm run docs:accept -- --reviewed --from=<распакованный-артефакт>; локальная самостоятельная пересъёмка PNG не принимается;
  • без golden baseline, если visual diff действительно нулевой.

15. Принятые предположения

Эти технические решения приняты предположительно и могут быть свободно изменены ревьюером без блокировки владельца:

  • JSON использует object map hooks[value], чтобы внешний E2E мог проверять селектор без линейного поиска;
  • audience различает styling и test, но оба набора получают одинаковое обещание стабильности;
  • toast в этой задаче имеет один честный kind message; severity нельзя достоверно восстановить из переведённого текста и она не нужна заявленному E2E-сценарию;
  • dialog kinds намеренно broad и не кодируют каждый внутренний подшаг;
  • devices сохраняет уже опубликованное имя .stage.mode-devices и значение data-editor-navigation="devices"; отдельное третье имя device для того же режима не вводится;
  • следующая запись since предварительно равна 1.73.0-beta.7 и уточняется release commit, если версия линии изменится;
  • внутренние исключения могут быть exact values сверх перечисленных в issue, если они уже существуют на исходной вершине dev; это не делает их API;
  • открытых продуктовых вопросов нет.