docs: review document for #588

Issue: #588
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-18 08:54:38 +00:00
parent eaaba7ab42
commit 03aaad3e2b
+92
View File
@@ -0,0 +1,92 @@
# SPEC-REVIEW-588-r3 — «Настройки устройства: отображение „Значение + статичный значок“»
Issue: [#588](https://github.com/Matysh/houseplan-card/issues/588)
Этап: spec (полный трек — S2-analysis назвал явно два нарушенных критерия §5: «одна поверхность» и «нет нового UX-контракта»)
Заход: r3 · блокирующих циклов израсходовано на входе 2/4 (зелёный вердикт этого раунда бюджет не тратит, #227)
## Вердикт
**Зелёный.** High: 0. Medium: 0. Low: 0.
## Скоуп разбора (по дельте, §2.10)
Предыдущий вердикт: жёлтый, заход r2, [docs/reviews/SPEC-REVIEW-588-r2.md](../../docs/reviews/SPEC-REVIEW-588-r2.md), материал — тело issue на момент разбора (SHA-256 тела `40a3c8b12af7fe3803ad3fece1a00b54c8fa12bd8fdc36b6bccd6516bf41756f`, дерево материала `002d320431837447efa84ee6119a95334bac4080`, рабочая копия кода на `git rev-parse HEAD` = `5b7add94d15a3961adc7f288f00750820cbbba07`).
Дельта между r2 и r3 объявлена автором в комментарии перехода (`Matysh`, 2026-09-18T08:48:00Z) и подтверждена построчной сверкой текущего тела issue с цитатами в документе r2:
1. **AC6** переписан: три факта (`.vacpuck` отсутствует, `.vactrail` отсутствует, `.vacwarn` отсутствует) разведены по **двум** конфигурациям — puck/след доказываются сопоставленной картой (образец `:86-180`), `.vacwarn` — только несопоставленной (`map_name: 'm9'`, рецепт `demo/smoke_vacuum_multifloor.mjs:70-78`, `unmappedWarns`); явно объяснено, почему совмещённая фикстура делала третий факт тавтологией (маршрут `ready` ⇒ `routeWarningKey` всегда `null`); добавлено требование доказывать обе стороны в каждой конфигурации (при `value_static_icon` факта нет, при `badge` на том же маркере он есть).
2. **Риск №1** дополнен фразой о причине недоказуемости третьей ветки на исходной фикстуре и о том, что новый блок попутно закрывает унаследованный пробел покрытия (`.vacwarn` не был доказан автотестом и для `static_icon`).
3. **План автотестов** обновлён: `demo/smoke_static_icon.mjs` описан как два блока конфигурации (сопоставленная + несопоставленная карта), каждый факт с обратным переключением в `badge`.
Дельта локальна: новая подсистема не затронута (пылесос уже был в скоупе r1/r2), контракт К1, К2, К3–К6, AC1–AC5, AC7–AC12, скоуп/не-скоуп, i18n, модель данных, откат, release-артефакты не менялись. Код в рабочей копии не изменился между r2 и r3 (фича не реализована, HEAD сейчас `eaaba7ab42914feeed3f6fda05f965c4515f03b7` — это только документы ревью r1/r2, коммитнутые пайплайном; продуктовый код тот же, что проверялся в r2). По §2.10 повторно проверялись только AC6, риск №1 и план автотестов — плюс код, на который они ссылаются. Остальное унаследовано без повторной проверки (раздел ниже).
## Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
| **M3** (в скоупе). Третий факт AC6 (отсутствие `.vacwarn`) доказывался образцом `:86-180`, где карта откалибрована и совпадает с `map_name` — маршрут `ready`, `routeWarningKey` для `ready` всегда `null`, поэтому бейдж не появляется ни в одном режиме и мутация ветки `_vacRouteBadge` прошла бы зелёной и на дефектном коде. | AC6 явно разведён на две конфигурации: puck/след — сопоставленная карта (тот же образец `:86-180`), `.vacwarn` — несопоставленная (`map_name: 'm9'`, рецепт `unmappedWarns`); причина разведения записана в самом критерии, чтобы не свернуть обратно к одной фикстуре; требование доказывать обе стороны (факт есть при `badge`, нет при `value_static_icon`) исключает повтор той же ошибки для новой конфигурации. Риск №1 фиксирует и то, что это попутно закрывает унаследованный пробел покрытия у `static_icon`. | Тело issue, таблица AC (строка AC6), раздел «Риски» (пункт 1, последнее предложение), «План автотестов» (строка `demo/smoke_static_icon.mjs`). Код-факты подтверждены повторным чтением: `src/vacuum-routes.ts:344-353` (`routeWarningKey`) возвращает не-`null` только для `unmapped`/`needs_calibration`/`ambiguous`/`missing_space`; `demo/smoke_vacuum_multifloor.mjs:70-85` действительно создаёт несопоставленную карту (`m9`) под движущимся роботом и получает `unmappedWarns` на маркере без `display` (т.е. на дефолтном `badge`); `demo/smoke_static_icon.mjs:86-180` (образец) задаёт `calibration: { m1: [...] }` при `map_name: 'm1'` — совпадение, маршрут `ready`, что и требовалось для puck/след, но не для `.vacwarn`. |
## Унаследовано из r2
Без повторной проверки в этом раунде — код и текст этих разделов не менялись дельтой r2→r3; см. [SPEC-REVIEW-588-r2.md](../../docs/reviews/SPEC-REVIEW-588-r2.md) (материал: тело issue SHA-256 `40a3c8b1...b7e6ce`, дерево `002d3204...`, рабочая копия `5b7add94d15a`) и через него [SPEC-REVIEW-588-r1.md](../../docs/reviews/SPEC-REVIEW-588-r1.md) (материал: тело issue SHA-256 `ee15ac6d...b7e6ce`, дерево `14bf5e35f9bc...`, `dev`@`a6185e295d2e`):
- Сценарий и «что человек увидит до/после» — форма и содержание по §7.1 (r1 §«Что проверено и корректно», п.1).
- Скоуп/не-скоуп — соответствует файлам, реально содержащим логику `static_icon`/`value`; не-скоуп корректно исключает `import_export.py`; фраза про «подсветку убираемой комнаты» убрана (M2, закрыт в r2), #589 существует, открыт, метки `bug, docs, vacuum, P3, S1-new` — проверено повторно в r2, не проверялось заново здесь.
- Контракт К1, К3–К6 и К2а (кроме переписанного AC6) — подтверждён построчным чтением кода в r1/r2 (быстрый путь `sourceDetails`, четыре причины отказа значения, подавление пульсации/бейджа у `static_icon`, порядок `DISPLAY_MODES`, видимость поля «Источник значения», три литеральных сравнения режима в `houseplan-card.ts:12198,12414,12593`).
- Модель данных, миграция, деградация старого клиента (`normalizeDeviceDisplay` → `badge`) — проверено в r1.
- i18n-ключи и их именование — не менялись.
- AC1–AC5, AC7–AC12 — содержательно не изменились со r2; номера и содержание сверены r1/r2 построчно.
- Дубликаты (#3, #26, #158, #219) — принято на слово в r1, дельта их не касается.
- «Обязательные разделы §7.1 — комплектность» — весь список присутствует (сценарий, «до/после», проблема, скоуп/не-скоуп, контракт поведения, UX, модель данных и миграция, i18n, AC1…AC12 с доказательством, план автотестов, риски, откат, release-артефакты); r1 подтвердил один раз, дельта не убрала ни одного раздела.
- Терминология «Отображение» (`docs/USER-GUIDE.ru.md:1280-1287`) — совпадает с ТЗ; не проверялась заново, так как раздел USER-GUIDE дельтой не затронут (сверено повторно только фактом наличия строк в файле, см. «Как проверялось»).
## Как проверялось (только по дельте)
1. Построчно сравнил текущее тело issue с цитатами AC6/риска №1/плана автотестов в документе r2 и с описанием автора в комментарии перехода (`08:48:00Z`) — дельта соответствует объявленной, скрытых расхождений не нашёл.
2. Перечитал `src/vacuum-routes.ts:320-353` (`planVacuumOverlay`, `routeWarningKey`) — подтвердил, что `routeWarningKey` возвращает не-`null` только для `unmapped`/`needs_calibration`/`ambiguous`/`missing_space`, для `ready` (и любого другого `kind`) — `null`. Точное текстовое основание claim'а AC6.
3. Перечитал `demo/smoke_static_icon.mjs:86-180` (образец для puck/след) — калибровка `m1` совпадает с `map_name: 'm1'` камеры ⇒ `ready`; корректный образец для двух первых фактов, бесполезен для третьего — ровно как теперь и написано в AC6.
4. Перечитал `demo/smoke_vacuum_multifloor.mjs:50-99` — подтвердил рецепт `unmappedWarns` (`map_name: 'm9'` под движущимся роботом, строки 76-82) и что маркер `e_vacuum_robo` не задаёт `display` явно (значит дефолт `badge` из `DISPLAY_MODES[0]`, `src/logic.ts:898`) — то есть сравнение «при `badge` факт есть» в AC6 опирается на реальный существующий тест, а не на предположение.
5. Перечитал три ветки пылесоса в `src/houseplan-card.ts` (`_vacTick:12194-12201`, рендер puck/trail:12405-12497, `_vacRouteBadge:12591-12599`) — код не изменился со r2 (фича не реализована), все три по-прежнему сравнивают `normalizeDeviceDisplay(...) === 'static_icon'` напрямую; буфер `_vacRt` наполняется в `_vacTick` независимо от результата калибровки маршрута (зависит только от `moving` и телеметрии камеры) — значит четвёртый факт AC6 («буфер не наполняется») проверяется той же веткой кода (`:12198`) и естественно доказывается в любой из двух конфигураций AC6, отдельной третьей конфигурации под него не требуется — неоднозначности здесь нет.
6. Проверил нумерацию AC1…AC12 в текущем теле issue — 12 строк, без дублей и пропусков, соответствует таблице; «План автотестов» и «Классы риска» ссылаются на те же номера без рассинхронизации.
7. Проверил issue #589 повторно (`gh issue view 589`) — открыт, метки `bug, P3, docs, vacuum, S1-new` не изменились, всё ещё корректны.
8. Гейты кода не запускал — на этапе ревью ТЗ неприменимо: фича не реализована, продуктовый код между r2 и r3 не менялся (диффу подвергалось только тело issue).
## Находки
Не найдено. M3 закрыт полностью и без остатка: новая формулировка AC6 технически точна (подтверждено построчным чтением `vacuum-routes.ts` и обоих демо-файлов), устраняет тавтологию третьего факта и попутно документирует унаследованный пробел покрытия у `static_icon`, не открывая новых пробелов (проверено, в т.ч. для четвёртого факта — буфера `_vacRt`, который в исходной формулировке AC6 не был явно привязан к одной из двух конфигураций, но по коду доказуем в любой из них).
## Что проверено и корректно
- AC6 в новой редакции доказывает все четыре заявленных факта (puck, след, `.vacwarn`, буфер) существующими в проекте фикстурами/рецептами без тавтологии; мутант `value-static-icon-keeps-live-vacuum` способен покраснеть на каждой из трёх строковых веток, включая `_vacRouteBadge`, при указанной несопоставленной конфигурации.
- Риск №1 корректно фиксирует причину недоказуемости прежней формулировки и явно называет её унаследованной (не только новой) проблемой покрытия.
- Обратное переключение в `badge` в обеих конфигурациях AC6 опирается на реально существующий признак различия (`badge` — дефолтный режим без подавления пульсации/бейджа/цвета), а не на предположение.
- Нумерация и перекрёстные ссылки AC/«План автотестов»/«Классы риска» непротиворечивы после правки.
- Всё унаследованное из r1/r2 (см. раздел выше) не тронуто дельтой — не проверялось повторно, обоснование дано.
## Чего не проверял
- Не проверял повторно AC1–AC5, AC7–AC12, контракт К1/К3–К6, скоуп/не-скоуп, модель данных, i18n, дубликаты (#3/#26/#158/#219) — дельта r2→r3 их не касается, r1/r2 уже проверили (см. «Унаследовано из r2»).
- Не читал `custom_components/houseplan/validation.py` и `scripts/config-schema.json` — как и в r1/r2, это предмет код-ревью.
- Не запускал `tsc`/`npm test`/`npm run build`/`npm run golden:verify` — на этапе ревью ТЗ неприменимо: фича не реализована, диффу подвергалось только тело issue, а не код.
- Не проверял, выполнено ли исправление #589 — не входит в скоуп #588 и не блокирует эту задачу.
- Не перепроверял содержимое `docs/USER-GUIDE.ru.md` целиком — только строки таблицы «Четыре варианта „Отображение“», релевантные терминологии ТЗ (не менялись дельтой).
## Материал раунда
- Issue: #588, тело на момент разбора r3 (SHA-256 нормализованного тела — конвейер впишет в блок якорей публикации ниже).
- Заход r3, циклов ревью ТЗ израсходовано на входе 2 из 4 (лимит для полного трека — 4). Этот вердикт зелёный и бюджет не тратит (§4, #227) — при принятии в разработку счётчик остаётся 2/4.
- Рабочая копия репозитория на момент проверки кодовых фактов: `git rev-parse HEAD` = `eaaba7ab42914feeed3f6fda05f965c4515f03b7`; продуктовый код (`src/**`, `custom_components/**`) идентичен коду, проверенному в r2 (`5b7add94d15a`) — фича не реализована, между раундами менялось только тело issue и коммитились документы ревью.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `dev`, коммит `eaaba7ab4291` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `2cd69c616ddd3f719677420519d22b6da3e9a38b`
```
git log --all --format='%H %T' | grep 2cd69c616ddd
```
- Тело issue: `503d9f130d95a9bb723107478993f63ddb64e51e20b212e60a0f9d8dac331e44`
- Вердикт конвейера: `green` · High 0