Files
houseplan-card/docs/reviews/SPEC-REVIEW-462-r3.md
T
2026-09-05 16:00:54 +03:00

21 KiB
Raw Blame History

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

Материал раунда

  • Ветка: 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