21 KiB
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 недоступны») в этом раунде закрыт
не доверием к цитате автора, а независимой проверкой того же источника другим
инструментом.
- Проверка 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_modeto replacemodeso it's clear that it's only for resources.» — то есть первоисточник подтверждает именно то утверждение, которое ТЗ приписывает ему, а не общую формулировку не по делу. - Проверка исходника на теге, где утверждается контракт.
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 игнорируется целиком») остаётся верной, и ТЗ её не отменяет — оно вводит доп. путь только для новых версий, что и обещает.
- Проверка риска "чужой ключ ломает валидацию", которым 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 блокировал находку, ТЗ структурно исключает версионным разделением, а не только цитатой. - Проверка дельты по native more-info guard (§11.3, AC8). Прочитан
src/houseplan-card.ts:_openMoreInfo(строка 5565) — единственная точка вызоваfireEvent(this, 'hass-more-info', …)в файле; все places, открывающие more-info (right-clickview-стейджа: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 остаются за реализацией»). - Согласованность правки по всем пяти точкам делты (§3, §11.3, §12, AC8, §16 «Browser smoke/golden») — не осталось места, где старая формулировка («открытие уже ставит общую interaction pause») продолжала бы жить рядом с новой; численно проверено, что заново введённая фраза одна и та же по смыслу во всех пяти местах.
- Не прогонялись
typecheck/test/build— этап spec, кода нет; они применимы к код-ревью (§2.7PROCESS.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
Материал раунда
- Ветка:
issue/462-card-resource-registration, коммитb45c3464ca18— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
d8d3c0f0dd8cfabad9e9cb583142bda2a8f50ddbgit log --all --format='%H %T' | grep d8d3c0f0dd8c - ТЗ
docs/specs/462-card-resource-registration.md, блобd3620eab48950255aa91240cd70ec3ed1edfd756git log --all --find-object=d3620eab48950255aa91240cd70ec3ed1edfd756 -- docs/specs/462-card-resource-registration.md