mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -0,0 +1,191 @@
|
||||
# SPEC-REVIEW-29-r1
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/29
|
||||
- Этап: spec (PROCESS.md §2.4)
|
||||
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
|
||||
- ТЗ: `docs/specs/029-device-inbox-lifecycle.md`
|
||||
- SHA материала ревью: `a6ce1ae7e534fa4b41fcc0ab93e5ed95a693c4c1` (ветка `issue/29-device-inbox-lifecycle`)
|
||||
- Трек: обычный (не `small`), лимит циклов ревью ТЗ — 4.
|
||||
|
||||
Это первый заход ревью по этой задаче — предыдущего вердикта в issue нет,
|
||||
раздел «Унаследовано» не применяется, разбор полный.
|
||||
|
||||
## Скоуп разбора
|
||||
|
||||
Проверено:
|
||||
|
||||
1. `docs/SCOPE.md` — соответствие job'ам J4/J6, отсутствие выхода за
|
||||
продуктовую рамку (out-of-scope список, standing rules).
|
||||
2. `AGENTS.md` и `PROCESS.md` §2.4, §7.1 — формальные требования к ТЗ и
|
||||
формат вердикта.
|
||||
3. Тело issue #29 и все комментарии, включая повторную актуализацию анализа
|
||||
от 2026-08-28 и решение владельца по Q1.
|
||||
4. `docs/USER-GUIDE.ru.md` (терминология «Скрытые и деактивированные»,
|
||||
«Редактор устройств», раздел про скрытие/удаление) и `docs/FILTERING.md`
|
||||
(канонический контракт hidden/removed/tombstone/entity-ownership).
|
||||
5. Исходный код: `src/houseplan-card.ts` (`_bindingCandidates`,
|
||||
`new_device_ids`, `_maybeRebuildDevices`, `.slice(0, 200)` дважды),
|
||||
`src/ha-binding-status.ts` (`HaBindingStatus.kind`), `src/types.ts`
|
||||
(`marker.hidden`, `marker.removed`), `src/i18n/ru.json` и `en.json`.
|
||||
6. Существующие тесты, упомянутые в AC11/§16: `test/devices.test.mjs`,
|
||||
`test/ha-binding-status.test.mjs`, `test/device-presentation*.test.mjs`,
|
||||
`demo/smoke_hidden_flag.mjs`, `demo/smoke_binding_picker.mjs` —
|
||||
существуют, содержимое `smoke_hidden_flag.mjs` прочитано целиком.
|
||||
7. `docs/CONFIG-COMPATIBILITY.md` — беглая проверка на отсутствие
|
||||
противоречий заявлению «нет новых persisted-полей».
|
||||
|
||||
Ревью — на этапе ТЗ: продуктового кода нет, автотесты не запускались (их
|
||||
ещё не существует), гейты §8 к этому этапу не относятся.
|
||||
|
||||
## Метод проверки
|
||||
|
||||
Каждое фактическое утверждение ТЗ о текущем поведении продукта
|
||||
(«До», §3, §7, §8, §9, §10.1) сверено с кодом или каноническим документом, а
|
||||
не принято на слово автора. Отдельно проверено, есть ли в тексте
|
||||
утверждение о поведении, которого нет ни в одном документе и которое не
|
||||
помечено как предположение (раздел 21 ТЗ).
|
||||
|
||||
## Находки
|
||||
|
||||
### M1 (Medium, в скоупе) — исчезновение призрачного показа скрытых/HA-disabled маркеров на плане не названо как продуктовое решение
|
||||
|
||||
**Файл:** `docs/specs/029-device-inbox-lifecycle.md`, §10.1 (строки 216–222).
|
||||
|
||||
Сегодня редактор устройств может показать скрытые и HA-disabled маркеры
|
||||
прямо на плане, в их реальной позиции, призраками — режим переключается
|
||||
кнопкой «Скрытые и деактивированные» (`docs/FILTERING.md` строки 84–91,
|
||||
`docs/USER-GUIDE.ru.md:178` — «доступны скрытые маркеры», перетаскиваются;
|
||||
подтверждено в `demo/smoke_hidden_flag.mjs`, где ghost рендерится в
|
||||
`_setMode('devices')` с `_showHidden = true` и клик по нему открывает
|
||||
диалог). Это единственный способ увидеть, ГДЕ на плане сидит скрытое или
|
||||
деактивированное устройство, не отменяя его скрытость.
|
||||
|
||||
ТЗ прямо убирает этот режим: «Скрытые markers больше не рисуются поверх
|
||||
плана постоянным локальным режимом: доступ к ним даёт каталог» (§10.1).
|
||||
Но каталог (§10.2–10.4) не даёт эквивалента: `Find` явно доступен «только
|
||||
если marker реально отрисовывается» (§10.4), а для строк категории
|
||||
«Скрытые» и для «На плане, временно не отображается из-за HA status»
|
||||
primary/secondary действия — это «Показать»/«Настроить», не позиция на
|
||||
плане. Значит, чтобы увидеть, где стоит скрытый маркер, администратору
|
||||
придётся сначала его показать (что меняет конфиг), посмотреть, и при
|
||||
необходимости скрыть обратно — вместо непосредственного просмотра.
|
||||
|
||||
Это видимое пользователю изменение объёма функциональности («какая
|
||||
персона что видит и делает» — ровно тот класс вопросов, который согласно
|
||||
PROCESS.md §7.1 задаётся владельцу или явно фиксируется как принятое
|
||||
предположение в §21). В тексте ТЗ оно подано как самоочевидный
|
||||
технический побочный эффект объединения кнопок, а не как решение,
|
||||
которое можно оспорить: обоснование «устраняет состояние панели,
|
||||
неочевидное после возврата в редактор» — это плюс нового дизайна, но оно
|
||||
не адресует потерю прямого просмотра позиции.
|
||||
|
||||
**Не High**, потому что: обходной путь существует (Показать → посмотреть/
|
||||
перетащить → Скрыть), это не потеря данных и не поломка AC — просто
|
||||
непроверенное продуктовое допущение, которое дёшево закрыть на этом этапе.
|
||||
|
||||
**Как закрыть в этом же цикле (на выбор автора):** либо явно вынести это в
|
||||
блок §21 как предположение, которое ревьюер/владелец может оспорить, с
|
||||
описанием обходного пути; либо задать это владельцу одним пакетным
|
||||
вопросом с предложенным дефолтом (например: «Find для скрытой/disabled
|
||||
строки временно подсвечивает позицию на плане не снимая hidden» как
|
||||
альтернативный дизайн); либо сознательно сохранить упрощённый вариант, но
|
||||
явно назвать компромисс и обходной путь в §2 «До/После» и в §18 «Риски».
|
||||
|
||||
### M2 (Medium, в скоупе) — устаревающая строка i18n не включена в план обновления
|
||||
|
||||
**Файлы:** `src/i18n/ru.json:673`, `src/i18n/en.json:673` (`marker.hide_tip`).
|
||||
|
||||
Текущий текст подсказки при скрытии маркера дословно ссылается на кнопку,
|
||||
которую это ТЗ удаляет: RU — «Вернуть его можно через кнопку «Скрытые и
|
||||
деактивированные» в редакторе устройств»; EN — `Restore it through "Hidden
|
||||
and disabled" in the device editor`. §10.1 заменяет обе кнопки («Добавить»
|
||||
и «Скрытые и деактивированные») одной кнопкой «Устройства» (`devbar.add`,
|
||||
`devbar.show_all` перестают существовать в UI в текущем виде).
|
||||
|
||||
Раздел 14 (i18n) перечисляет только **новые** ключи и не содержит пункта
|
||||
«обновить существующие ключи, ссылающиеся на удаляемые элементы
|
||||
интерфейса». Если реализовать ТЗ как написано, `marker.hide_tip` останется
|
||||
нетронутым и после релиза будет указывать пользователю нажать
|
||||
несуществующую кнопку — конкретный, проверяемый дефект, а не гипотетический.
|
||||
|
||||
**Как закрыть:** добавить в §14 явный пункт «`marker.hide_tip` (en+ru)
|
||||
обновляется, чтобы указывать на новую точку входа «Устройства»» (и
|
||||
проверить, нет ли других строк с той же ссылкой — быстрый `grep` по
|
||||
`show_all`/«Скрытые и деактивированные» в `src/i18n/*.json` показывает,
|
||||
что это единственная пара строк такого рода, кроме самих `devbar.*`
|
||||
/`title.show_all`, которые и так меняются по §10.1).
|
||||
|
||||
Обе находки Medium, в скоупе задачи (правки в самом файле ТЗ) — по
|
||||
PROCESS.md §2.4/§2.7 они не создают отдельный issue (решение владельца
|
||||
2026-08-19, #202) и возвращают ТЗ автору с жёлтым вердиктом.
|
||||
|
||||
## Что проверено и признано корректным
|
||||
|
||||
- **Формальные разделы §7.1** — сценарий, «что человек увидит до/после»,
|
||||
проблема, скоуп/не-скоуп, контракт поведения, UX, модель данных и
|
||||
миграция, i18n, AC1…AC11 с указанием способа доказательства, план
|
||||
автотестов, риски, откат, release-артефакты — все присутствуют.
|
||||
- **AC1–AC11** однозначны и у каждого назван способ доказательства
|
||||
(unit/smoke/golden/review); ни один не описывает недоказуемое поведение.
|
||||
- **Q1 владельца** (классификация автообнаруженного видимого устройства
|
||||
без marker) корректно отражена в §7.2 (категория `on_plan`) и в AC3 —
|
||||
соответствует решению владельца в комментарии issue.
|
||||
- **Заявления о текущем поведении, использованные как база для «До»,
|
||||
подтверждены кодом и канонoм**, а не додуманы:
|
||||
- `_bindingCandidates` в `src/houseplan-card.ts:14032` — существующий
|
||||
eligibility-код, который ТЗ предлагает извлечь в общий helper (§12);
|
||||
не выдумка.
|
||||
- `HaBindingStatus.kind` (`active`/`ha_disabled`/`orphaned`/`unverified`)
|
||||
в `src/ha-binding-status.ts:14-17` — точное совпадение с таблицей §7.3.
|
||||
- `marker.hidden`/`marker.removed` в `src/types.ts:118,121` — существуют,
|
||||
семантика совпадает с `docs/FILTERING.md`.
|
||||
- Жёсткий кап на 200 элементов в существующих списках кандидатов
|
||||
(`src/houseplan-card.ts:14106`, `19114` — `.slice(0, 200)`) —
|
||||
подтверждает описанную в §8.1/AC9 проблему, которую каталог обязан не
|
||||
унаследовать.
|
||||
- `duplicate_name_area` действительно устарела: `docs/FILTERING.md:145`
|
||||
прямо говорит «Duplicate names are still numbered», не скрываются.
|
||||
- `settings.new_device_ids` фильтруется от уже скрытых id при seed
|
||||
(`src/houseplan-card.ts:3679–3684`) — подтверждает §8.2 «Первично
|
||||
отфильтрованный скрытый кандидат не получает badge, как и сегодня».
|
||||
- Терминология «Добавить», «Скрытые и деактивированные», «Правила
|
||||
иконок» — точное совпадение с `src/i18n/ru.json:455-457` и
|
||||
`docs/USER-GUIDE.ru.md`.
|
||||
- **Технические предположения (§21)** промаркированы явно и корректно
|
||||
отделены от продуктовых решений — кроме пробела, описанного в M1.
|
||||
- **Не входит (§6)** корректно исключает #44 (discovery-настройки), #126
|
||||
(смена area), #109 (multi-channel), bulk-операции, историю/графики,
|
||||
touch-паритет — всё по `docs/SCOPE.md` и `docs/TOUCH-SUPPORT.md`, без
|
||||
расширения скоупа.
|
||||
- **Откат (§19)** реалистичен: нет новых persisted-полей и версии модели,
|
||||
откат — вернуть старые кнопки; совместимость с созданными через «Скрыть
|
||||
из списка» обычными `hidden:true` маркерами сохраняется.
|
||||
- Названные тестовые артефакты существуют: `test/devices.test.mjs`,
|
||||
`test/ha-binding-status.test.mjs`, `test/device-presentation.test.mjs`,
|
||||
`test/device-presentation-policy.test.mjs`, `demo/smoke_hidden_flag.mjs`,
|
||||
`demo/smoke_binding_picker.mjs`, `scripts/smoke-select.mjs` — ни один не
|
||||
является выдумкой.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Не запускал никакие гейты (`typecheck`/`test`/`build`/смоки) — на этапе
|
||||
ТЗ продуктового кода нет, это не применимо (§8 относится к
|
||||
код-ревью).
|
||||
- Не проверял `docs/ARCHITECTURE.md` и `docs/CANVAS.md` целиком постранично
|
||||
— только точечно то, что касается заявленных в ТЗ архитектурных решений
|
||||
(pure resolver, eligibility helper); полный аудит этих документов не
|
||||
требуется для ревью ТЗ.
|
||||
- Не оценивал производительность реализации (её ещё нет); проверил только,
|
||||
что заявленная асимптотика (§12, §17) не противоречит существующему коду
|
||||
(`.slice(0, 200)` факт капа подтверждён).
|
||||
- Не проверял golden/скриншот-инфраструктуру предметно — она релевантна
|
||||
коду, а не ТЗ.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Полностью выполненные формальные требования ТЗ (§7.1) не отменяют жёлтый
|
||||
вердикт: две находки Medium в скоупе (M1, M2) — реальные пробелы, оставить
|
||||
их «в тексте ревью» без правки запрещено §12 PROCESS.md. High-находок нет.
|
||||
|
||||
**Вердикт: жёлтый.** ТЗ возвращается автору на правку M1 и M2; после
|
||||
правки — новый заход ревью по дельте (PROCESS.md §2.10).
|
||||
Reference in New Issue
Block a user