docs: review document for #485

Issue: #485
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-09 03:50:50 +03:00
committed by Matysh
parent 51d5858b3d
commit 1d3d379d6c
+275
View File
@@ -0,0 +1,275 @@
# SPEC-REVIEW-485-r3
Issue: [#485](https://github.com/Matysh/houseplan-card/issues/485).
Материал: ветка `issue/485-radar-presence`, SHA `21950437b4343deead48c955d06191ea15bb567a`
(baseline r2 материал `f4bab4a4bd5f4ac9b91cc16363985323a20ae907`, докс-only diff,
проверено `git diff --stat` — только `docs/specs/485-radar-presence.md` и
`docs/specs/485-radar-presence-stage1.md`; коммит `docs/reviews/SPEC-REVIEW-485-r2.md`
самого документа r2 в дельту не входит, по прецеденту r2).
Этап: **spec** (S4-spec-review). Заход: r3. Блокирующих циклов израсходовано 1 из 4
(зелёный вердикт r2 цикл не образовал и бюджет не потратил, #227). Трек: полный
(метка `small` отсутствует). Ревьюер ≠ автор.
## Скоуп разбора (по дельте, PROCESS §2.9/§2.10, issue #214)
Round r2 закрылся зелёным (High 0 / Medium 0) на материале `f4bab4a4`; findings
предыдущего раунда закрывать нечего — этот раунд разбирает не исправление r2, а
**новое уточнение владельца**, полученное уже после зелёного r2 (комментарий issue
2026-09-08T11:30:04Z): раздел «Присутствие на плане» в редакторе устройства
показывается только подходящим устройствам, ранее настроенным радарам сохраняется
путь исправления; общий переключатель не меняется.
`git diff f4bab4a4..21950437 --stat` (без коммита самого документа r2):
```
docs/specs/485-radar-presence-stage1.md | 66 ++++++++++++++++++++++++++++++---
docs/specs/485-radar-presence.md | 15 ++++++--
```
Формально дельта локальна по объёму (два файла, точечные абзацы в §2/§3 общего
контракта и §2/§3/AC-таблицы этапа 1). Но по содержанию это не техническая правка
и не декларативная фиксация ранее решённого — это **новый поведенческий контракт**
(кто вообще видит точку входа в настройку радара), которого не было ни в r1, ни в
r2. Согласно правилу §2.10 «разбор остаётся полным, если... смена контракта
поведения» — я не ограничился построчной сверкой «что просил владелец / что
дописано», а перечитал §3 обоих документов (модель источников/профилей) и точки
входа в этапах 2–3, чтобы проверить, не рвёт ли новый гейт видимости то, что r1/r2
уже приняли как рабочее. Остальное содержимое пакета (модель идентичности,
совместимость, i18n, перф, этапы 2–3 целиком) дельта не задевает — там разбор по
дельте, без повторного прохода (см. «Унаследовано из r2»).
## Как проверялось
- `gh issue view 485 --json labels,state` — метки `P2, feature, S4-spec-review`,
`small` отсутствует: полный трек подтверждён, счётчик заходов/циклов совпадает
с описанием задачи.
- Построчный `git diff f4bab4a4..21950437` по обоим изменённым файлам (полный
текст цитирую в разделе «Находки»).
- Перечитано начало и конец тела issue #485 (`gh issue view 485 --json body`) —
включая раздел «Что есть у easy-floorplan и что из этого брать» и его
«Берём»/«Не берём» — источник профиля `range_v1`/«любой числовой сенсор».
- Сверены точки входа в `docs/specs/485-radar-presence-stage2.md:75` и
`docs/specs/485-radar-presence-stage3.md:86` («Device editor → Presence on plan
→ …») — обе описывают вход из уже определённого/настроенного радара, новому
гейту видимости не противоречат.
- Проверено техническое утверждение stage1 §2 про «existing conditional vacuum
section» как прецедент: `src/houseplan-editor-runtime.ts` действительно содержит
условный рендер `renderVacuumMapsSection` (строка 10701, внутри `... ? html\`...
${renderVacuumMapsSection(...)}\` : nothing`) — утверждение не выдумано.
- `docs/UX-MODES.md` просмотрен на предмет канонического правила про
показ/скрытие секций в редакторе устройства (в противовес «показывать, но
дизейблить») — прямого правила, которое запрещало бы условный показ по типу
устройства, не найдено; секция «пылесоса» — рабочий прецедент того же рода.
- `docs/SCOPE.md` — дельта его не трогает; новый гейт видимости не расширяет и не
сужает ранее записанное исключение (это вопрос UI-точки-входа в существующий
редактор устройства, не новый класс данных/хранения).
- Трейлеры коммита `21950437`: `Issue: #485`, `User-Visible: no` — верно для
докс-only правки без видимого поведения.
- Таблица i18n (§8 stage1) дельтой не тронута: новый гейт видимости не вводит
собственного текста (ineligible-устройства просто не получают раздел), поэтому
отсутствие нового ключа — не пропуск.
### Гейты
Диапазон затрагивает только `docs/specs/*.md` (class C), продуктовый код и тесты
не тронуты (`git diff --stat origin/dev..HEAD -- src custom_components` — пусто).
Прогнаны сам, дёшево и напрямую по этому SHA:
| Гейт | Команда | Результат |
|---|---|---|
| Markdown/diff-гигиена | `git diff --check f4bab4a4..HEAD -- docs/specs/485-radar-presence.md docs/specs/485-radar-presence-stage1.md` | чисто (exit 0) |
| Документационные проверки | `node scripts/check-docs.mjs` | `Documentation checks passed (7 files, 12 external links)` |
`tsc`/`test`/`build`/`invariants`/смоки/`golden`/`pytest` — не запускал: diff не
касается `src/**`/`custom_components/**/*.py`, ни один AC этой стадии их не
требует (эта же логика применялась в r1/r2 и подтверждена авторским
`git diff --stat`). Отдельный зелёный Validate на этом SHA для данного раунда не
искал — предыдущий раунд (r2, `f4bab4a4`) уже подтвердил репозиторий в целом, а
эта дельта докс-only и не расширяет исполняемую поверхность.
## Закрытие раунда r2
r2 закрылся с High 0 / Medium 0 — закрывать нечего, таблица «находка → чем
закрыта» неприменима. Материал этого раунда — не фикс находок r2, а новое
уточнение владельца, полученное после зелёного вердикта (см. «Скоуп разбора»).
## Унаследовано из r2
Принято без повторной проверки в этом раунде, так как дельта их не задевает;
основание — [SPEC-REVIEW-485-r2](https://github.com/Matysh/houseplan-card/blob/f4bab4a4bd5f4ac9b91cc16363985323a20ae907/docs/reviews/SPEC-REVIEW-485-r2.md)
на SHA `f4bab4a4bd5f4ac9b91cc16363985323a20ae907`:
- Продуктовая рамка, границы этапов 1→2→3 и их отнесение к J1/`docs/SCOPE.md`.
- `docs/SCOPE.md`-исключения для производных HA-сущностей и записи/тепловой карты
(закрытые в r1→r2) — эта дельта их не расширяет и не сужает.
- Touch-декларация мастера калибровки этапа 1 (`best effort / intentionally
degraded`) и этапов 2/3 — не тронута.
- Единая модель идентичности (`source_generation`, `calibration_revision`,
`server_session_id`/`seq`), лимиты, WS-контракт §7 stage1.
- Полнота AC-таблиц C-1…C-8, S2-1…S2-22, S3-1…S3-18 и S1-1…S1-18 (кроме новой
S1-19, разобранной в этом раунде отдельно).
- Матрицы совместимости/жизненного цикла, i18n-таблица §8 stage1, перф-бюджеты
§10 stage1.
- Ответы Q1–Q4 и их количественное соответствие тексту.
## Находки
### M-1 (Medium, в скоупе) — новый гейт видимости, по тексту, исключает ранее принятый адаптер «любой числовой сенсор» (класс `range`), не оставляя описанного пути его настроить
`docs/specs/485-radar-presence-stage1.md:55-59` (после дельты):
> A positive presence-radar device/profile match or a verified radar-specific
> source-role descriptor on the exact owning device makes it eligible. Match
> evidence is structural adapter/registry metadata, not a friendly-name search.
и `stage1.md:69-73`:
> A normal light, switch, temperature/humidity sensor, ordinary PIR, virtual
> marker, or a device with merely arbitrary numeric/binary entities is not a
> radar candidate. Two numeric values or a generic occupancy/motion class alone
> do not prove a radar profile.
Оба условия — «структурные метаданные адаптера/реестра» и явное исключение
«merely arbitrary numeric… entities» — требуют, чтобы устройство было
**автоматически опознано** ещё до того, как раздел вообще появится. Ни здесь, ни
где-либо ещё в дельте нет описанного пути «пользователь вручную объявляет
устройство радаром», отличного от двух метаданных-путей.
Это прямо противоречит собственному решению задачи, зафиксированному в теле issue
#485 (раздел «Что есть у easy-floorplan и что из этого брать», подтверждено
владельцем 07.09 вместе с остальным «Решение владельца»):
> **Берём:** presence gate с fail-safe семантикой (сомнение → не показывать);
> **«любой числовой сенсор» как низший общий знаменатель (класс «дальность»)**;
> линия/дуга для одной оси; контур зоны только в редакторе; сглаживание между
> отсчётами 1–4 Гц.
и в самом контракте (`stage1.md:144-151`, таблица профилей, не тронута дельтой):
> `range_v1` | One or two named distance channels, explicit length unit, optional
> occupancy gate | Arc(s), not two people…
`range_v1` — это буквально «любой числовой сенсор» из принятого решения: датчики
класса Tuya ZY-M100/TS0601, Aqara FP1E, Linptech ES1, Seeed MR24 и любой
самодельный узел, перечисленные в самой постановке issue (раздел «Что радары умеют
отдавать»), никак не опознаются «структурными метаданными адаптера» — у них нет
признанного дескриптора вроде LD2450. До этой дельты пользователь мог зайти в
раздел устройства и вручную назначить источники (`range_v1`/`cartesian_v1`/
`polar_v1` — «generic explicit Cartesian/polar/range/occupancy inputs», common
contract `485-radar-presence.md:86-88`, тоже не тронуто дельтой). После дельты
раздел для такого устройства попадает под «merely arbitrary numeric… entities is
not a radar candidate» и **не показывается вовсе** — то есть ручной путь входа,
на котором держится вся «дальность»-ветка и вся идея «класса устройств, а не
конкретной модели» (Суть issue: «Инструмент работы с любыми радарами присутствия…
не конкретная модель, а класс устройств»), не имеет точки входа в UI.
AC S1-19 (новая, `stage1.md:558`) требует протестировать в том числе «range»-радар
как один из допущенных типов — то есть сам автор рассчитывает, что такой путь
существует, но текст §2 его не описывает.
**Почему это не мелочь, а не редактирование формулировки.** Уточнение владельца
было одной строкой: «показывать только подходящим устройствам» — это про то, чтобы
не засорять редактор лампочек и выключателей пустыми разделами. Реализация этой
строки как «только структурно опознанные устройства» — решение автора, а не
владельца, и оно тише удаляет из скоупа именно ту часть задачи (произвольный
числовой датчик, взятый из easy-floorplan как «низший общий знаменатель»), которую
до сих пор явно поддерживали r1 и r2. Это ровно тот случай из PROCESS §7.1:
«поведение в пограничном случае» — граница между «опознанный радар» и «обычный
датчик» — продуктовый вопрос, а не инженерный, и здесь он решён предположением,
выданным за факт, без пометки «assumed, change freely» и без вопроса владельцу.
**Как чинится (любой из вариантов, в скоупе задачи):**
1. Добавить в §2 явный ручной путь — например, действие «Это радар присутствия»
на неопознанном устройстве, которое ведёт в тот же мастер с профилями
`range_v1`/`cartesian_v1`/`polar_v1`, отдельное от двух автоматических путей и
не открывающее раздел на самом деле неподходящих устройствах (лампах,
выключателях) без явного действия пользователя; согласовать с «no friendly-name
search» так, чтобы ручной путь не читался как автоматическое распознавание.
2. Либо явно сузить AC S1-19 и профильную таблицу так, чтобы `range_v1`/`cartesian_v1`/
`polar_v1` были помечены как доступные только через путь ремонта уже
сохранённой конфигурации (т.е. фактически недостижимы для нового радара без
признанного дескриптора), и одним пакетным вопросом с вариантом по умолчанию
спросить владельца, допустимо ли это сужение — вопрос ровно того типа, который
PROCESS §7.1 адресует владельцу («relates to what a person sees or does», здесь
— сможет ли человек вообще настроить нераспознанный радар).
Находка в скоупе задачи (правится в этом же ТЗ, отдельный issue не заводится,
#202).
## Что проверено и корректно
- Правило скрытия для обычных устройств (лампы/выключатели/температура/обычный
PIR/виртуальный маркер) и сохранение пути ремонта для уже настроенного радара
(в том числе при `unavailable`/неподдерживаемой версии) — сформулировано
недвусмысленно, с исчерпывающим списком случаев и негативными свидетелями в AC
S1-19 (`ordinary-device header assertion`, `offline/range/manual repair
assertion`, `empty-installation settings assertion`).
- Общий переключатель «Показывать присутствие на плане» остаётся видимым без
радаров и не зависит от gate — текст (`stage1.md:108-110`,
`485-radar-presence.md:69-71`) дословно совпадает с формулировкой уточнения
владельца в issue.
- Точки входа этапов 2 и 3 (`stage2.md:75`, `stage3.md:86`) остаются валидны при
новом гейте: обе описывают вход из уже определённого/настроенного радара, не из
произвольного устройства.
- Новых i18n-ключей дельта не требует и не добавляет — раздел на неподходящих
устройствах просто отсутствует, не превращается в текст с пустым состоянием.
- Техническое утверждение про существующий условный раздел пылесоса как прецедент
подтверждено чтением `src/houseplan-editor-runtime.ts:10701` — не выдумано.
- `docs/SCOPE.md` не требует правки для этой дельты: вопрос — точка входа в
существующий редактор устройства, не новый класс данных/хранения.
- Трейлеры коммита корректны; дельта не касается `src/**`/`custom_components/**/*.py`.
## Чего не проверял
- Не перепроверял AC-таблицы C-1…C-8, S2-1…S2-22, S3-1…S3-18 и S1-1…S1-18 (кроме
новой S1-19), модель идентичности, совместимость, перф-бюджеты, WS-контракт —
дельта их не задевает; раздел «Унаследовано из r2» перечисляет, что принято без
повторной проверки.
- Не запускал `npx tsc --noEmit`, `npm test`, `npm run build`, `npm run
invariants`, браузерные смоки, `golden:verify`, `python -m pytest
tests_backend` — diff докс-only, не касается `src/**`/`custom_components/**/*.py`,
ни один AC пакета не требует их прогона на стадии spec-review.
- Не проверял `docs/USER-GUIDE.ru.md`/`docs/USER-GUIDE.md` построчно на предмет
терминологии — фича не выпущена, руководство ещё не содержит раздела о радарах
(`grep` по обоим файлам — без совпадений), сверять не с чем.
- Не оценивал остальные два варианта устранения M-1 на предмет объёма следующего
раунда — это решение автора и, при необходимости, владельца.
## Вердикт
**Жёлтый.** High — 0, Medium — 1 (M-1, в скоупе задачи, чинится в этом же issue,
#202). Находка блокирует переход S4 → S5: новый гейт видимости, введённый этой
дельтой, тише сужает ранее принятое решение «любой числовой сенсор — класс
`range`» без пометки предположения и без вопроса владельцу. Остальная часть
пакета (унаследованная из r2) находок не имеет.
Этот жёлтый вердикт образует цикл и расходует бюджет §4: было израсходовано 1 из
4, после этого раунда — 2 из 4.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/485-radar-presence`, коммит `21950437b434` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `717b51fb35f3c98731eb72e53d0cc2ab87db25d4`
```
git log --all --format='%H %T' | grep 717b51fb35f3
```
- ТЗ `docs/specs/485-radar-presence-stage1.md`, блоб `c5d9960cfaa0fa5573e58d384e8e2b7eeddc6fcc`
```
git log --all --find-object=c5d9960cfaa0fa5573e58d384e8e2b7eeddc6fcc -- docs/specs/485-radar-presence-stage1.md
```
- ТЗ `docs/specs/485-radar-presence-stage2.md`, блоб `b403e73fc26ba0ffb97be4ffa73be067d179880f`
```
git log --all --find-object=b403e73fc26ba0ffb97be4ffa73be067d179880f -- docs/specs/485-radar-presence-stage2.md
```
- ТЗ `docs/specs/485-radar-presence-stage3.md`, блоб `c65e28389d762216c47f00249bdb751f3c9d5082`
```
git log --all --find-object=c65e28389d762216c47f00249bdb751f3c9d5082 -- docs/specs/485-radar-presence-stage3.md
```
- ТЗ `docs/specs/485-radar-presence.md`, блоб `dfc1edc3aa10d19c16eb37e1ddacf7c81c439216`
```
git log --all --find-object=dfc1edc3aa10d19c16eb37e1ddacf7c81c439216 -- docs/specs/485-radar-presence.md
```