Files
houseplan-card/docs/reviews/SPEC-REVIEW-486-r1.md
T
2026-09-07 20:32:51 +00:00

229 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-486-r1
- **Issue:** #486 — «Панель House Plan в боковом меню Home Assistant: вход как у
приложения и редактор на всю страницу»
- **Этап:** ТЗ на ревью (PROCESS.md §2.4)
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4 (первый заход, цикл
ещё не открыт)
- **Материал:** `docs/specs/486-house-plan-panel.md` на ветке
`issue/486-house-plan-panel`, коммит `2821c359` (совпадает с вершиной ветки
на момент ревью и с SHA зелёного Validate,
https://github.com/Matysh/houseplan-card/actions/runs/34159261423)
- **Трек:** полный (`S4-spec-review`, без меток `small`/`trivial`)
- **Ревьюер:** Claude (отдельная от автора модель, роль по PROCESS.md §6)
## Вердикт
**Зелёный.** High: 0, Medium: 0. Три Low-замечания — сняты решением ревьюера с
записью ниже (правки не требуются для перехода в «Готово к разработке»).
## Скоуп ревью
Проверялось только ТЗ (`docs/specs/486-house-plan-panel.md`, 709 строк) и его
согласованность с:
1. телом issue #486 и обоими комментариями (`S2 — аналитика и оценка`,
`S3 — ТЗ готово`);
2. `docs/SCOPE.md` (границы продукта, персоны, lock invariant);
3. `PROCESS.md` §7.1 (обязательные разделы ТЗ) и §2.5 (чек-лист DoR);
4. канон подсистем, которые задача трогает: `docs/UX-MODES.md` (View как
default, last-space, route-departure, admin_only), `docs/TOUCH-SUPPORT.md`
(hit target, desktop-first редакторы), `docs/CONFIG-COMPATIBILITY.md`
(миграции — задача заявляет «нет», проверено, что в файле нет
противоречащих обязательств);
5. фактическим состоянием кода на исходной вершине `origin/dev` — ТЗ раздел
«3. Подтверждённое текущее состояние» описывает семь конкретных утверждений
о текущем коде, и именно они несут наибольший риск «догадка, выданная за
факт» (см. ниже).
Код реализации не существует (`S4-spec-review`, до `S5-ready`), поэтому
код-ревью, автотесты, mutation-таблицы и гейты §8 к этому этапу не относятся и
не прогонялись — они станут предметом код-ревью после `S6-in-progress`.
## Как проверялось
### 1. Сверка утверждений о текущем коде с фактическим `origin/dev`
Раздел 3 ТЗ и обосновывающие его решения раздела 4 опираются на семь фактических
утверждений о поведении кода, которого ТЗ не меняет. Каждое проверено чтением
`origin/dev` (агентом Explore и напрямую):
| # | Утверждение ТЗ | Результат проверки |
|---|---|---|
| 1 | `__init__.py` не регистрирует HA panel | Подтверждено: `custom_components/houseplan/__init__.py` — ни `panel_custom`, ни `async_register_panel`, ни `async_register_built_in_panel` не встречаются нигде в `custom_components/houseplan/**/*.py` |
| 2 | `frontend_registration.py` — Lovelace resource, fallback, versioned URL, одноразовое уведомление, System Health | Подтверждено построчно: `async_register_lovelace_resource()`, `_add_fallback()`, `module_url = f"{FRONTEND_URL}?v={VERSION}"`, `_async_create_reload_notice()` с one-shot флагом в `entry.data`, отдельный `system_health.py` |
| 3 | Empty-state ветка не проверяет `_canEdit`: read-only пользователь тоже может получить авто-открытие onboarding | Подтверждено: `src/houseplan-card.ts:4162-4178` — условие авто-открытия `_openSpaceDialog('create')`/import-диалога проверяет `_serverStorage`, `_loadOk`, `_model.length === 0`, `!_onboardingShown`, но не `_canEdit` (геттер `_canEdit` определён на `houseplan-card.ts:1086` и используется в 10+ других местах — то есть пропуск в этой ветке точечный, не системный) |
| 4 | Есть `getCardSize()`, нет `getGridOptions()` | Подтверждено: `getCardSize()` на `houseplan-card.ts:3833`; `getGridOptions` — 0 совпадений в `src/**` (проверено Grep по всему дереву) |
| 5 | `rollup.config.mjs` — один entry; `bundle-manifest.mjs` берёт первый `isEntry` | Подтверждено дословно: `rollup.config.mjs:18` `input: 'src/houseplan-card.ts'` (одна строка); `scripts/bundle-manifest.mjs:63` `files.find((file) => file.isEntry)?.path` |
| 6 | HA 2024.6.0 — минимальная поддерживаемая версия | Подтверждено: `docs/USER-GUIDE.md:77`/`docs/USER-GUIDE.ru.md:78` `Home Assistant 2024.6.0 или новее` |
| 7 | Существующий route-departure/last-space контракт (сброс editor при уходе с route, память только о пространстве) | Подтверждено в `docs/UX-MODES.md:20-24` и в коде: `LS_NAV` (`houseplan-card.ts:500`), запись/чтение (`:7447,7458`), `_leaveCardRoute()` со сбросом `_mode` в `'view'` (`:7470-7477`) |
Ни одного случая «догадка, выданная за факт» не найдено: каждое фактическое
утверждение о текущем состоянии либо подтверждено кодом, либо (для API HA
2024.6, недоступного в этом репозитории — `panel_custom.async_register_panel`,
приватный `hass.data[frontend.DATA_PANELS]`) явно помечено как риск с named
защитой — compatibility-тестами на minimum/current HA (§9.3, риск-таблица §22)
— а не подано как решённый факт.
Отдельно проверено, что раздел «5. Принятые предположения» содержит только
технические решения (имя route/element/icon, используемый публичный event,
точное число px hit target, обработка native user overrides) — ни одно не
подменяет продуктовый вопрос. Проверены все пять «Открытых вопросов к S2» из
тела issue: все пять закрыты явными решениями либо в S2-комментарии (доступ
`OWNER`, см. ниже), либо в самом ТЗ (§4 пп. 1, 4, 7, 8; §8 «Не входит»).
### 2. Проверка полномочий продуктовых решений
`gh issue view 486 --json comments` подтверждает, что оба решающих комментария
(S2-аналитика с принятыми решениями для ТЗ и S3 «ТЗ готово») имеют
`authorAssociation: OWNER` — то есть продуктовые вопросы (что видит
read-only/writer пользователь без плана, видимость панели всем аутентифицированным
пользователям, объём `getGridOptions` в этом же issue) закрыты стороной, которая
имеет на это право по PROCESS.md §7.1, а не додуманы автором ТЗ.
### 3. Проверка обязательных разделов ТЗ (PROCESS.md §7.1)
| Раздел §7.1 | Есть в ТЗ | Где |
|---|---|---|
| Сценарий | ✅ | §1 |
| Что человек увидит до/после | ✅ | §2 |
| Проблема | ✅ (в §1 и явной ссылке на тело issue с цитатой Reddit-отзыва) | §1 |
| Скоуп и не-скоуп | ✅ | §7, §8 |
| Контракт поведения | ✅ | §9–§16 |
| UX | ✅ | §12, §17 |
| Модель данных и миграция | ✅ («не меняется», явно) | §16 |
| i18n | ✅ (точные ключи EN/RU/DE/FR) | §15.2 |
| AC1…ACn с доказательством | ✅ (15 AC, у каждого назван метод: backend/unit/smoke/golden/mutation/code review) | §19 |
| План автотестов | ✅ | §20 |
| Риски | ✅ (таблица вероятность/ущерб/защита) | §22 |
| Откат | ✅ (независимый по частям: panel registration, panel entry, `getGridOptions`, notification text) | §23 |
| Release-артефакты | ✅ | §24 |
Все обязательные разделы присутствуют и не формальны — каждый содержит
конкретные, проверяемые утверждения, а не пересказ задачи.
### 4. Проверка непротиворечивости с продуктовым скоупом
- Lock invariant (`docs/SCOPE.md` «Standing rule» и раздел про lock) не
затронут: панель не вводит новую поверхность actuation, `can_write`/`admin_only`
остаются единственным источником прав (§10), sidebar visibility явно назван
не-авторизацией.
- `docs/TOUCH-SUPPORT.md`: View остаётся полностью touch-поверхностью,
редакторы остаются desktop-first best-effort — ТЗ прямо говорит, что #486 не
подменяет отдельный touch-аудит #31 (§17). Соответствует политике «улучшение
трогаем, только если оно дешёвое и не усложняет desktop-модель».
«Household members»/«Guests» из `docs/SCOPE.md` не переопределяются: панель
не меняет, кто есть кто, только точку входа.
- `docs/CONFIG-COMPATIBILITY.md`: миграций нет, `CardConfig`/schema не меняются
— заявлено в ТЗ (§16) и не противоречит канону совместимости.
## Находки
Ничего блокирующего. Ниже — три Low, каждое снято решением ревьюера с записью
(правка не требуется для перехода в `S5-ready`):
1. **Low — неоднородная терминология метода доказательства.** AC4/AC5
называют способ доказательства «bundle gate», AC13 — «i18n gate»; в
каноническом словаре PROCESS.md §2.5 таких категорий нет (только
`unit`/`backend`/`smoke`/`golden`/«ревью кода»). По факту оба относятся к
`unit`: `test/i18n.test.mjs`/`test/i18n-dead-keys.test.mjs` и
`test/bundle-assets.test.mjs` уже существуют и являются частью обычного
`npm test`. Снимаю: способ доказательства однозначен и проверяем, несмотря
на неканоническую метку; переименование ничего не меняет по существу и не
стоит повторного цикла.
2. **Low — двусмысленная формулировка одного пункта AC7.** «narrow
read-only/empty `320×720`» можно прочитать как один golden (комбинированное
состояние «пустой план, read-only» из таблицы §13) либо как два отдельных
golden (narrow read-only И отдельно narrow empty). Более естественное чтение
— первое (в §13 «Пустой план, read-only» — это ровно одна строка состояния),
и тестовый план §20 п. 2 отдельно называет «Empty writer onboarding and
empty read-only state» как отдельную пару сценариев от AC7. Снимаю: контекст
§13/§20 разрешает неоднозначность без необходимости переписывать AC.
3. **Low — бюджет `initialPanelOnlyGzipBytes ≤ 8 KiB` (§11.4, AC5) не имеет
измеренного основания.** В отличие от `INITIAL_VIEW_GZIP_BUDGET` (текущее
значение 301 066 Б, откалиброванное по факту с историей в
`scripts/bundle-budget.mjs`), число «8 KiB» для нового panel-only довеска
нигде в репозитории не встречалось раньше и не подтверждено измерением
реального шелла (панель ещё не реализована). Снимаю: это чисто техническая,
легко пересматриваемая величина (откат §23 п.3 прямо разрешает независимую
правку), а не продуктовый контракт; если фактический размер шелла на
код-ревью выйдет за 8 KiB — это стандартная процедура повышения потолка «в
том же коммите с объяснением» (правило уже описано в
`scripts/bundle-budget.mjs`), а не дефект ТЗ.
## Что проверено и корректно
- Все продуктовые вопросы, поднятые в теле issue («Открытые вопросы к S2»),
закрыты явными решениями, авторизованными стороной с правом (`OWNER`).
- Ни одно утверждение о текущем состоянии кода (раздел 3 ТЗ) не оказалось
домыслом — семь фактических тезисов проверены построчно на `origin/dev` и
подтвердились, включая не самый очевидный (пропуск `_canEdit` в конкретной
empty-state ветке `houseplan-card.ts:4162-4178`, а не во всём empty-state
рендере — кнопка «Add space» в табах уже корректно гейтуется `_canEdit` на
строке 11439, и ТЗ не путает эти два места).
- API-риски, которые нельзя проверить чтением этого репозитория (совместимость
`panel_custom.async_register_panel` и приватного
`hass.data[frontend.DATA_PANELS]` с HA 2024.6), явно не выданы за факт: они
вынесены в риск-таблицу §22 и закрыты требованием compatibility-тестов на
minimum/current HA в AC1/AC2/AC14 — это корректный способ обращения с
неопределённостью на этапе ТЗ (не гадать, а требовать доказательство от
реализации).
- Разделение product/technical в §5 «Принятые предположения» выполнено
корректно: каждый пункт — техническое решение или несущественная деталь
presentation, ни один не подменяет вопрос «что видит пользователь».
- Скоуп и не-скоуп (§7–§8) точно соответствуют тому, что зафиксировано в S2 и
предложении issue: явно исключены server config панели, перенос CardConfig,
новый onboarding, kiosk панели, редизайн редакторов, #437 и отдельный
standalone asset.
- «Одно число — один источник» (гейт §8/PROCESS.md): в этом диффе нет
пользовательски видимой величины, показанной дважды (панель не вводит новых
измерений/площадей/подписей) — типовой риск #234/#233 к этой задаче не
относится, признаю применимость проверил и отрицаю её.
- i18n-скоуп (EN/RU/DE/FR) совпадает с реально существующими локалями и в
`custom_components/houseplan/translations/` и в `src/i18n/` — ТЗ не
придумывает несуществующий язык и не забывает существующий.
- Rollback (§23) не требует миграции данных и позволяет откатывать panel
registration, panel entry и `getGridOptions` независимо друг от друга —
соответствует стандарту отката DoR (§2.5).
## Чего не проверял и почему
- **Код реализации** — не существует на этом этапе; будет предметом код-ревью
после `S6-in-progress`. Мутационные таблицы, конкретные unit/smoke/golden
тесты, бэкенд compatibility-стабы и фактический бюджет собранного
`houseplan-panel.js` не могут быть проверены до реализации.
- **Точное поведение `panel_custom.async_register_panel` и приватного
`hass.data[frontend.DATA_PANELS]` в реальном Home Assistant 2024.6** — вне
репозитория (HA — внешняя зависимость, не vendored). ТЗ признаёт это риском и
требует compatibility-тестов; я не запускал реальный HA core, чтобы
independently подтвердить сигнатуры — это и не задача этапа ТЗ.
- **Дешёвые гейты (`typecheck`/`test`/`build`) и `check-docs`** не
перепрогонялись: код не менялся, Validate зелёный на этом же SHA `2821c359`
(https://github.com/Matysh/houseplan-card/actions/runs/34159261423), а этап
ТЗ не предполагает изменений в `src/**`/`custom_components/**` — сам ТЗ-файл
лежит в `docs/**` (класс C), для которого дешёвые гейты кода не применимы.
- **Golden/smoke/performance-профили, инварианты модели** — неприменимо: ТЗ не
меняет рендер, геометрию или ссылки на неё; изменение полностью в
`docs/specs/**`.
## Материал раунда
Заполняется конвейером публикации автоматически (PROCESS.md §2.10, п.1) —
руками не редактировался.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/486-house-plan-panel`, коммит `2821c359634a` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `736e05616d432cc61befaed33fbb1d114e6c79b9`
```
git log --all --format='%H %T' | grep 736e05616d43
```
- ТЗ `docs/specs/486-house-plan-panel.md`, блоб `dd37e779f5cd5e4dae09b6b70453634448ece9cd`
```
git log --all --find-object=dd37e779f5cd5e4dae09b6b70453634448ece9cd -- docs/specs/486-house-plan-panel.md
```