mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 21:28:59 +00:00
237 lines
21 KiB
Markdown
237 lines
21 KiB
Markdown
# SPEC-REVIEW-462-r3
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/462
|
||
- **Спецификация:** `docs/specs/462-card-resource-registration.md`, коммит `b45c3464`
|
||
(ветка `issue/462-card-resource-registration`, HEAD на момент ревью — тот же SHA)
|
||
- **Трек:** полный
|
||
- **Заход:** r3 · блокирующих циклов израсходовано 2 из 4 (лимит на полном треке — 4;
|
||
зелёный вердикт этого раунда бюджет не тратит, #227)
|
||
|
||
## Материал раунда r2 (для дельты)
|
||
|
||
- Предыдущий вердикт: красный · заход r2 · High: 1 · Medium: 0 → в задаче
|
||
(комментарий issue, 2026-09-05T11:22:44Z), документ
|
||
`docs/reviews/SPEC-REVIEW-462-r2.md`.
|
||
- SHA материала r2 назван в документе дважды (шапка и «Материал раунда»):
|
||
`e9e3d6bd7d8e61a0e7d561da76b19cedb157f32b`. Резолвится и сейчас как предок
|
||
`HEAD` (`git merge-base --is-ancestor e9e3d6bd HEAD` → успех) — ребейза не
|
||
было, восстановление по дереву/блобу не требовалось.
|
||
- Дельта этого раунда: `git diff e9e3d6bd..HEAD -- docs/specs/462-card-resource-registration.md`,
|
||
два коммита: `c56afbd3` («docs: cite Home Assistant resource mode contract» —
|
||
ответ на High r2) и `b45c3464` («docs: cover native more-info reload guard» —
|
||
самостоятельно найденный автором до кода дополнительный unsafe-path в §11.3).
|
||
40 изменённых строк, пять точек правки: §3 (новое обоснование версионной
|
||
границы), §11.3 (расширение guard-условия про native more-info), §12 (те же
|
||
ссылки в тексте документации), AC8 (§15) и план тестов (§16).
|
||
|
||
Дельта не вводит новый терминологический контракт и не задевает подсистему,
|
||
которую не разбирал r2: она (а) подкрепляет источниками уже существующее в r2
|
||
разделение storage/YAML-resource-mode и (б) уточняет один из guard-предикатов
|
||
kiosk-перезагрузки, не меняя ни матрицу §11.1, ни список § заявленных outcome,
|
||
ни какой-либо другой AC. Оба места делта касается напрямую (AC1/§12 через
|
||
источники; AC8/§11.3/§16 через native more-info) — только эти два узла и
|
||
проверялись заново по существу; остальное наследуется из r2.
|
||
|
||
## Скоуп ревью
|
||
|
||
Прочитано перед разбором: `docs/SCOPE.md` (Core user jobs, J4), `AGENTS.md`,
|
||
`PROCESS.md` §1–10 и §2.10 (объём по дельте), тело issue #462 и все 13
|
||
комментариев (включая комментарии автора о закрытии High через PR HA и о новом
|
||
guard native more-info), документы и находки `SPEC-REVIEW-462-r1.md` и
|
||
`SPEC-REVIEW-462-r2.md`, `docs/UX-MODES.md` (Kiosk mode), `docs/TOUCH-SUPPORT.md`.
|
||
Из самого ТЗ перечитаны §3, §11.3, §12, AC8, §16 целиком; остальные разделы —
|
||
по диффу и по унаследованному выводу r1/r2 (см. «Унаследовано из r2»).
|
||
|
||
## Как проверялось
|
||
|
||
В этой сессии, в отличие от r1 и r2, `WebFetch` (по прямым URL) отказал тем же
|
||
`permission denied`, но `gh api` (через `Bash`) сработал без ограничений и дал
|
||
прямой доступ к GitHub REST API `home-assistant/core` — то есть найденный в r1/r2
|
||
пробел («не подтверждено — WebSearch/WebFetch недоступны») в этом раунде закрыт
|
||
не доверием к цитате автора, а независимой проверкой того же источника другим
|
||
инструментом.
|
||
|
||
1. **Проверка PR, закрывшего High r2.**
|
||
`gh api repos/home-assistant/core/pulls/161816` — существует, `merged: true`,
|
||
`merge_commit_sha: 190fe10eed42d6ed5122bf873e61986851bc0b15` (совпадает с
|
||
сокращённым `190fe10` из ТЗ), `milestone.title: "2026.2.0"` (совпадает с
|
||
заявленным в ТЗ), заголовок «Allow lovelace path for dashboard in yaml and fix
|
||
yaml dashboard migration», описание автора PR дословно: «Introduce a new key :
|
||
`resource_mode` to replace `mode` so it's clear that it's only for resources.» —
|
||
то есть первоисточник подтверждает именно то утверждение, которое ТЗ
|
||
приписывает ему, а не общую формулировку не по делу.
|
||
2. **Проверка исходника на теге, где утверждается контракт.**
|
||
`gh api repos/home-assistant/core/contents/homeassistant/components/lovelace/__init__.py?ref=2026.2.0`
|
||
(сохранён локально, прочитан полностью). Подтверждено дословно:
|
||
- `CONFIG_SCHEMA` домена `lovelace` содержит `vol.Optional(CONF_MODE, default=MODE_STORAGE)`
|
||
**и отдельно** `vol.Optional(CONF_RESOURCE_MODE)` — два независимых
|
||
необязательных ключа, не один "mode" как было в 2025.1.4 (r2 проверял ровно
|
||
эту версию и не нашёл там `resource_mode` — версии до и после границы
|
||
действительно различаются, как и утверждает ТЗ);
|
||
- `resource_mode = config[DOMAIN].get(CONF_RESOURCE_MODE, mode)` — при
|
||
отсутствии `resource_mode` поведение полностью откатывается к старому
|
||
единому `mode` (обратная совместимость сохранена, ТЗ этого не утверждает
|
||
явно, но и не противоречит);
|
||
- `if resource_mode == MODE_YAML: resource_collection = await create_yaml_resource_col(...)`
|
||
— YAML-ресурсы загружаются по значению `resource_mode`, **независимо** от
|
||
того, что находится в `mode` (то есть независимо от режима dashboard) — это
|
||
дословно тот функциональный контракт, который ТЗ описывает в §3/§5/§12/AC1;
|
||
- ветка `else` (когда `resource_mode != yaml`, т.е. storage) сохраняет старый
|
||
`_LOGGER.warning("Lovelace is running in storage mode. Define resources via
|
||
user interface")` при непустых `yaml_resources` — то есть для HA без
|
||
`resource_mode: yaml` (в том числе для версий до 2026.2, где ключа вообще
|
||
нет) исходная посылка issue («в storage-режиме `lovelace.resources` из YAML
|
||
игнорируется целиком») остаётся верной, и ТЗ её не отменяет — оно вводит
|
||
доп. путь **только** для новых версий, что и обещает.
|
||
3. **Проверка риска "чужой ключ ломает валидацию"**, которым r2 обосновывал
|
||
серьёзность High. Внутренний `vol.Schema({...})` для домена `lovelace` не
|
||
объявляет `extra=vol.ALLOW_EXTRA` (только внешний `CONFIG_SCHEMA` — это
|
||
касается ключей вне `lovelace:`, не внутри него) — значит на версиях **до**
|
||
2026.2, где `resource_mode` не входит в схему, этот ключ до сих пор обвалил бы
|
||
валидацию `lovelace:`. Проверил формулировку ТЗ (§12, «Legacy HA
|
||
2024.6–2026.1»): современный snippet с `resource_mode` предлагается **только**
|
||
для «HA 2026.2+», отдельная explicitly помеченная legacy-секция без этого
|
||
ключа — для старых версий. Риск, которым r2 блокировал находку, ТЗ
|
||
структурно исключает версионным разделением, а не только цитатой.
|
||
4. **Проверка дельты по native more-info guard (§11.3, AC8).** Прочитан
|
||
`src/houseplan-card.ts`: `_openMoreInfo` (строка 5565) — единственная точка
|
||
вызова `fireEvent(this, 'hass-more-info', …)` в файле; все places, открывающие
|
||
more-info (right-click `view`-стейджа `:5618`, action handler `:5746-5747`,
|
||
кнопки info-card `:12402`, `:13334`, `:13376`), проходят через неё. `grep -n
|
||
_cyclePausedUntil` показывает единственную точку записи паузы —
|
||
`_stagePointerDown` (`:6691`), и только при `this._kiosk`, только на
|
||
pointerdown по сцене. Кнопки info-card на `:12402` делают
|
||
`e.stopPropagation()` перед вызовом `_openMoreInfo`, то есть их клик не
|
||
обязан долетать до обработчика сцены; клавиатурный и «внутренний
|
||
программный» пути тем более не проходят через `pointerdown`. Значит
|
||
формулировка ТЗ «одной паузы из stage pointerdown недостаточно» и «единая
|
||
точка открытия more-info обязана продлить `_cyclePausedUntil`» — не догадка,
|
||
а точное описание существующей архитектуры и корректно найденная дыра:
|
||
единственная уже существующая точка сведения (`_openMoreInfo`) —
|
||
реалистичное место для реализации, что соответствует §17 («точные helper
|
||
names остаются за реализацией»).
|
||
5. **Согласованность правки по всем пяти точкам делты** (§3, §11.3, §12, AC8,
|
||
§16 «Browser smoke/golden») — не осталось места, где старая формулировка
|
||
(«открытие уже ставит общую interaction pause») продолжала бы жить рядом с
|
||
новой; численно проверено, что заново введённая фраза одна и та же по
|
||
смыслу во всех пяти местах.
|
||
6. Не прогонялись `typecheck`/`test`/`build` — этап spec, кода нет; они
|
||
применимы к код-ревью (§2.7 `PROCESS.md`), не к этому этапу.
|
||
|
||
## Находки
|
||
|
||
Ни одной High- или Medium-находки в делте `e9e3d6bd..HEAD` не обнаружено.
|
||
|
||
## Закрытие раунда r2
|
||
|
||
| Находка r2 | Чем закрыта | Где это видно |
|
||
|---|---|---|
|
||
| High: контракт `lovelace.resource_mode: yaml` для «HA 2026.2+» не подтверждён, противоречит доступной версии HA (2025.1.4) и собственной посылке issue; риск сломать `lovelace:` валидацией на старых HA | Первичные источники добавлены в ТЗ (PR, merge-коммит, тег, офиц. документация) и **независимо перепроверены в этом раунде напрямую через `gh api`** против реального исходника `homeassistant/components/lovelace/__init__.py` на теге `2026.2.0`: `CONF_RESOURCE_MODE` — реальный, отдельный от `CONF_MODE` optional-ключ схемы; `resource_mode == MODE_YAML` грузит YAML-ресурсы независимо от `mode` дашборда; версии до 2026.2 (без ключа) по-прежнему получают только legacy-snippet — риск валидации закрыт версионным разделением, а не игнорированием | `docs/specs/462-card-resource-registration.md` §3 (строки 75–93), §12 (398–436), AC1 (468–475); коммит `c56afbd3`; см. «Как проверялось» пп.1–3 этого документа |
|
||
|
||
Единственная находка r2 закрыта по существу и подтверждена независимо, не на
|
||
слово автора. Новых находок делта не внесла.
|
||
|
||
## Унаследовано из r2 (без повторной проверки в этом раунде)
|
||
|
||
Следующее не тронуто дельтой `e9e3d6bd..HEAD` за пределами описанного выше и
|
||
принимается по документам `docs/reviews/SPEC-REVIEW-462-r1.md` (SHA материала
|
||
`12ddd107a9cdd9b12ea8411360760f684f5293c1`) и
|
||
`docs/reviews/SPEC-REVIEW-462-r2.md` (SHA материала `e9e3d6bd7d8e61a0e7d561da76b19cedb157f32b`):
|
||
|
||
- Наличие и содержание обязательных разделов §7.1 `PROCESS.md` (сценарий, что
|
||
человек увидит, причина, скоуп/не-скоуп, контракт §8–11, данные/миграция,
|
||
i18n, AC1…AC13, план тестов, риски, откат, release-артефакты, §22).
|
||
- Закрытие обеих находок r1 (баннер vs terminal-тост #353; touch-target 44×44
|
||
как локальный, не канонный порог) — таблица «Закрытие раунда r1» в
|
||
`SPEC-REVIEW-462-r2.md`; дельта этого раунда их не касалась.
|
||
- Существование и смысл символов кода, проверенных в r1/r2:
|
||
`_register_lovelace_resource`/`_lovelace_resources`
|
||
(`custom_components/houseplan/__init__.py:336-378`), `manifest.json`
|
||
(dependencies без `lovelace`), `system_health_info`, `CARD_VERSION`
|
||
(`src/houseplan-card.ts:402`), `integration_version` в `config/get`,
|
||
`_preflightVersionsDiffer`, `persistent_notification.async_create` как
|
||
синхронный `@callback`, card-level overlay в трёх ранних render-ветках
|
||
(`src/houseplan-card.ts:11282-11320`).
|
||
- Подтверждённая по реальным wheel `homeassistant==2024.6.0` и `==2025.1.4`
|
||
цепочка зависимостей `houseplan → frontend → lovelace` и отсутствие
|
||
`remove_extra_js_url` в 2024.6 (§8.2, п.9 и абзац после).
|
||
- Пригодность `homeassistant.helpers.start.async_at_started` в связке с
|
||
`entry.async_on_unload` (§22 п.3).
|
||
- Полный список из 4 файлов документации с исходным невалидным плоским
|
||
`resources:` (README.md, README.ru.md, docs/USER-GUIDE.md,
|
||
docs/USER-GUIDE.ru.md) и обоснование выбора `persistent_notification` вместо
|
||
Repairs (§22 п.1).
|
||
- Соответствие `docs/SCOPE.md`: задача закрывает J4, out-of-scope не задет.
|
||
- AC2–AC7, AC9–AC13 — формулировки не менялись делтой `e9e3d6bd..HEAD` (сверено
|
||
построчно диффом); принимаю их однозначность и доказуемость как установленную
|
||
в r1/r2. AC1 и AC8 менялись — перепроверены заново в этом раунде (см. «Как
|
||
проверялось» пп.1–4 и «Закрытие раунда r2»).
|
||
|
||
## Что проверено и корректно (в этом раунде)
|
||
|
||
- Источники для `lovelace.resource_mode` — реальны, дают именно то
|
||
утверждение, которое им приписано, и независимо подтверждены против
|
||
исходного кода HA на названном теге, а не только против цитаты автора.
|
||
- Версионное разделение snippet'ов (2026.2+ / legacy 2024.6–2026.1) структурно
|
||
исключает риск, из-за которого High r2 был серьёзным (обвал валидации
|
||
`lovelace:` на старых HA неизвестным ключом): новый ключ предлагается только
|
||
там, где он существует в схеме.
|
||
- Новое guard-условие native more-info (§11.3/AC8) описывает существующую,
|
||
проверенную по коду архитектуру (`_openMoreInfo` как единая точка сведения,
|
||
`_cyclePausedUntil` как единственная точка паузы только из
|
||
`_stagePointerDown`), а не гипотетический символ; реализуемо без новой
|
||
неоднозначности в AC8 и плане тестов.
|
||
- Правка внесена согласованно во всех пяти местах, которые она логически
|
||
затрагивает (§3, §11.3, §12, AC8, §16) — старая и новая формулировка нигде не
|
||
остались одновременно.
|
||
- AC1 и AC8 после делты остаются однозначными и называют способ доказательства
|
||
(`unit`/docs contract для AC1; `unit + browser + mutation` для AC8).
|
||
|
||
## Чего не проверял
|
||
|
||
- Не проверял версии HA новее `2026.2.0` — не требуется: делта именно
|
||
зафиксировала версионную границу на этом теге, дальнейшие версии вне спора.
|
||
- Не проверял официальную документацию `home-assistant.io/dashboards/dashboards/`
|
||
напрямую (WebFetch по прямому URL отказал тем же `permission denied`, что и в
|
||
r1/r2) — не потребовалось: первичный источник (PR + исходник тега) сильнее
|
||
вторичного (документация) и уже даёт исчерпывающее прямое подтверждение.
|
||
- Не прогонял `typecheck`/`test`/`build` — этап spec, кода ещё нет; гейты
|
||
применимы к код-ревью, не к этому этапу.
|
||
- Не переоткрывал AC2–AC7, AC9–AC13, §8–10, §13–15, §17–22 по существу — делта
|
||
их не касалась (см. «Унаследовано из r2»).
|
||
- Не оценивал landing page/дистрибуцию — вне репозитория, зона Claude по
|
||
`AGENTS.md`, не часть ТЗ.
|
||
|
||
## Вывод
|
||
|
||
Единственная блокирующая находка r2 закрыта по существу и подтверждена
|
||
независимой проверкой первичного источника, а не принята на слово. Делта
|
||
сверх этого (native more-info reload guard) — корректное, реализуемое и
|
||
согласованное уточнение существующего требования, без новых High/Medium.
|
||
Задача готова к разработке.
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/462-card-resource-registration`
|
||
- SHA материала: `b45c3464ca1847b5f9041bdc277a0c01de49eb19`
|
||
- Дерево материала: `d8d3c0f0dd8cfabad9e9cb583142bda2a8f50ddb`
|
||
- Файл ТЗ: `docs/specs/462-card-resource-registration.md`, блоб
|
||
`d3620eab48950255aa91240cd70ec3ed1edfd756`
|
||
- SHA r2 (для дельты): `e9e3d6bd7d8e61a0e7d561da76b19cedb157f32b`
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/462-card-resource-registration`, коммит `b45c3464ca18` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `d8d3c0f0dd8cfabad9e9cb583142bda2a8f50ddb`
|
||
```
|
||
git log --all --format='%H %T' | grep d8d3c0f0dd8c
|
||
```
|
||
- ТЗ `docs/specs/462-card-resource-registration.md`, блоб `d3620eab48950255aa91240cd70ec3ed1edfd756`
|
||
```
|
||
git log --all --find-object=d3620eab48950255aa91240cd70ec3ed1edfd756 -- docs/specs/462-card-resource-registration.md
|
||
```
|