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