mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 11:49:16 +00:00
@@ -0,0 +1,236 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user