From a4fe939f63a90e2eaaa42a2e150f39cc1bd26e06 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sat, 5 Sep 2026 11:36:37 +0000 Subject: [PATCH] docs: review document for #462 Issue: #462 User-Visible: no --- docs/reviews/SPEC-REVIEW-462-r3.md | 236 +++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 docs/reviews/SPEC-REVIEW-462-r3.md diff --git a/docs/reviews/SPEC-REVIEW-462-r3.md b/docs/reviews/SPEC-REVIEW-462-r3.md new file mode 100644 index 00000000..cde1e6d5 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-462-r3.md @@ -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` + +--- + + + +## Материал раунда + +- Ветка: `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 + ```