docs: review document for #267

Issue: #267
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-27 19:57:33 +00:00
parent 118062bb8c
commit 8161394178
+145
View File
@@ -0,0 +1,145 @@
# CODE-REVIEW-267-r2
- **Issue:** https://github.com/Matysh/houseplan-card/issues/267
- **Этап:** код-ревью (PROCESS.md §2.7)
- **Заход:** r2 · блокирующих циклов израсходовано 1 из 4
- **Материал:** ветка `issue/267-device-presentation-table`, `git rev-parse HEAD` =
`118062bb8cce6ffe7ab86233f10b9dfd00ac6f6d` (совпадает с SHA, заявленным в
хендоффе r2), база `origin/dev@2294d46f`
- **Предыдущий раунд:** r1, вердикт красный, `docs/reviews/CODE-REVIEW-267-r1.md`,
получен на `6efc315ba62e9b62ad5d95bbd0d2bdecb7d967ba`
- **Вердикт: зелёный**
## Скоуп ревью
Второй заход. Разбор — по дельте: `git diff 6efc315b..118062bb`
(исключая сам файл `docs/reviews/CODE-REVIEW-267-r1.md`, который автор
закоммитил вместе с хендоффом и который не является предметом правки).
Полный повторный прогон не требовался: дельта локальна (один коммит
`118062bb`, 8 нерендер-файлов вне `docs/reviews`), не задевает ребейз, не
меняет контракт видимого поведения и не вводит новую подсистему — это ровно
точечное закрытие трёх находок r1.
Файлы дельты (без сборочных артефактов и review-документа):
```
docs/DEVICE-PRESENTATION.md | 7 +--
docs/specs/267-device-presentation-decision-table.md | 10 ++--
scripts/mutation-gate.mjs | 60 +++++++++++++++++++++-
src/device-presentation-policy.ts | 7 +--
src/device-presentation.ts | 2 +-
test/device-presentation-policy.test.mjs | 14 +++--
test/fixtures/device-presentation-decisions.mjs | 7 +--
```
Плюс синхронные пересборки (`dist/`, `custom_components/.../houseplan-card.js`,
`docs/images/screenshots.json`) — сверены штатными гейтами, не разбирались
построчно.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| **High-1** — L04/L05/L06 в fixture ссылались на общий mutant `device-presentation-policy-lifecycle`, чей патч физически задевает только ветку `ha_disabled` и не может сломать `userHidden`/`orphaned` | Добавлены три новых, реально нацеленных мутанта: `device-presentation-policy-user-hidden` (патчит `else if (input.userHidden && !input.designPreview)`), `device-presentation-policy-user-hidden-preview` (патчит `else if (input.userHidden)` через `false &&`), `device-presentation-policy-orphaned` (патчит `else if (input.bindingLifecycle === 'orphaned')` тем же приёмом). Fixture переключена на них: L04→`…-user-hidden`, L05→`…-user-hidden-preview`, L06→`…-orphaned` | `scripts/mutation-gate.mjs` (новые записи в `MUTANTS`, ветка после мутанта `device-presentation-policy-lifecycle`); `test/fixtures/device-presentation-decisions.mjs:11-13`; `src/device-presentation-policy.ts:86-92` — текст `find` каждого патча посимвольно совпадает с соответствующей веткой production-кода. Проверено лично прогоном каждого `--id=` (см. таблицу гейтов) — все три «поймано 1 из 1», при снятом мутанте `git status` чист |
| **Medium-1 (в скоупе)** — `unverified` в `BindingPresentationLifecycle` был недостижим: `devices.ts` фильтрует его через `continue` до построения `DevItem`, но L06 «доказывал» его напрямую вызовом policy-функции, а не через `resolveDevicePresentation()` | `unverified` убран из типа `BindingPresentationLifecycle` (`src/device-presentation-policy.ts:27-28`), из ветки резолвера (`src/device-presentation.ts:587-588`, тернарник больше не содержит `unverified`), из теста (снят отдельный `assertDecision(unverified, …)` прогон) и из строки L06/§6.1 ТЗ (переименована в «orphaned binding», без второго исхода). Я перепроверил читанием, что `devices.ts` действительно фильтрует `unverified` **на всех** путях построения `DevItem`: явные device/entity-маркеры (`src/devices.ts:1193,1225`, `continue` до конструирования) и авто-обнаруженные устройства (`src/devices.ts:1108`, `if (bindingStatus.kind !== 'active') continue`) — других мест, где `bindingStatus:` присваивается `DevItem`, в файле нет (`grep -n "bindingStatus:" src/devices.ts` → 3 совпадения, все проверены). Значит удаление ветки — доказуемо чистое удаление мёртвого кода, а не изменение поведения | `src/device-presentation-policy.ts:27-28,86-92`; `src/device-presentation.ts:587-588`; `src/devices.ts:1108,1193,1225`; `docs/DEVICE-PRESENTATION.md` L06; `docs/specs/267-…table.md` §6.1 и интерфейс `DevicePresentationPolicyInput` |
| **Low-1** — `source.skipped_static_fast_path` производился кодом, но не документирован ни в одной строке | Добавлен ряд **S14** в `docs/DEVICE-PRESENTATION.md`, §6.2 ТЗ, `test/fixtures/…` и тест (`presentationRow` для static-fast-path), плюс новый targeted mutant `presentation-static-source-fast-path`, патчащий именно `EMPTY_SOURCES.decisionIds` | `docs/DEVICE-PRESENTATION.md` строка S14; `test/device-presentation-policy.test.mjs:205-210`; `scripts/mutation-gate.mjs` (мутант `presentation-static-source-fast-path`, патч `find: "sourceKind: 'none', decisionIds: ['source.skipped_static_fast_path'],"` — текстуально совпадает с `src/device-presentation.ts:178`). Прогнан лично — «поймано 1 из 1» |
По каждой строке проверка — не заявление автора: я сверил текст патча каждого
нового мутанта посимвольно с текущим кодом (`find` — точная подстрока) и
лично прогнал все четыре новых/изменённых `--id=` индивидуально, наблюдая
результат `поймано 1 из 1` и чистый `git status` после (мутация не осталась
в рабочем дереве).
## Унаследовано из r1
Без повторной проверки в этом раунде — дельта их не задевает:
- **AC1** (каноническая таблица покрывает все ряды) — `docs/reviews/CODE-REVIEW-267-r1.md`, проверено на `6efc315b`. Дельта добавляет один новый ряд (S14, см. выше, перепроверен), остальные 43 не менялись.
- **AC2** (единственный pure policy owner) — там же. Дельта не трогает архитектуру вызовов, только состав веток внутри уже выделенного `resolveDevicePresentationPolicy()`.
- **AC3** (source decisions названы) — там же, на `6efc315b`.
- **AC6** (controller/target parity) и **AC7** (surface parity) — там же; дельта не касается controller-ветки и preview-путей.
- **AC8** (refactor-only pixel/config contract), включая **полный** `npm run golden:verify` — 130/130 на Linux на `6efc315b`. В этом раунде golden не перезапускался целиком: единственное изменение в рендер-пути (`src/device-presentation.ts:587-588`) — удаление ветки `unverified`, которая, как показано в таблице выше, доказуемо никогда не могла быть достигнута с реальным `DevItem`; следовательно результат любого golden-сценария тождественен прогону на `6efc315b`. Три копии бандла пересобраны и идентичны по SHA-256 на `118062bb` (см. таблицу гейтов) — байт-в-байт то же, что даёт `git status` после локальной пересборки (чисто), то есть коммит уже содержит именно этот бандл.
- **AC9** (fast path без лишних вызовов) — там же, чтением кода; дельта не трогает эту ветку.
- **AC10** (штатные гейты) — перепрогнаны заново на `118062bb` в этом раунде (см. ниже), не наследуются.
- Трейлеры `Issue: #267`, `User-Visible: no` — верны на всех коммитах ветки, включая новый `118062bb` (проверено в этом раунде).
## Как проверялось (гейты этого раунда)
| Что | Команда | Результат |
|---|---|---|
| Типы | `npx tsc --noEmit` | зелёный |
| Юнит-тесты | `npm test` | 1379 тестов, 1378 passed, 0 failed, 1 skipped (совпадает с r1) |
| Сборка + сверка бандлов | `npm run build && npm run bundle:sync` + `sha256sum` трёх копий | все три идентичны: `01c5390f051b135f2c21e4e19bd5fa059abb9381d69fbda3eccffa677bf68a1a`; `git status` после пересборки чист — коммит уже содержит этот бандл |
| Документация | `node scripts/check-docs.mjs` | `Documentation checks passed (7 files, 10 external links)` |
| Провенанс/статус issue | `node scripts/process-gate.mjs --base origin/dev --head HEAD --issues` | `гейт пройден, предупреждений 0` |
| Реестр мутантов, структурная сверка | `node scripts/mutation-gate.mjs --check` | зелёный, включая 4 новых/изменённых записи |
| Targeted mutation — реально применены и пойманы (предмет находки High-1) | `node scripts/mutation-gate.mjs --id=device-presentation-policy-user-hidden`, `--id=device-presentation-policy-user-hidden-preview`, `--id=device-presentation-policy-orphaned`, `--id=presentation-static-source-fast-path` (каждый отдельно) | все четыре «поймано 1 из 1»; `git status` после каждого — чисто |
| Выборка смоков по дельте раунда | `node scripts/smoke-select.mjs --base 6efc315b --head HEAD` | НЕОПРЕДЕЛЁННОСТЬ: 0 символов проекта на изменённых строках дельты — решение см. ниже |
| Выборка смоков по полному диффу issue | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | прямое совпадение (2): `smoke_controls.mjs` (← `DevItem`), `smoke_wireless_controller_parity.mjs` (← `controllerAvailability`) — то же, что в r1 |
| Названные смоки | `node demo/smoke_controls.mjs`, `node demo/smoke_wireless_controller_parity.mjs`, `node demo/smoke_device_icon_design.mjs` | все три `OK` |
| Golden | не перезапускался в этом раунде | обоснование — см. «Унаследовано из r1», AC8 |
| Инварианты геометрии/модели | не прогонялись | дельта не трогает геометрию/`layout`/толщину стен/`marker.space`/`open_spans` — неприменимо |
| Backend | не прогонялся | дельта не трогает `custom_components/**/*.py` |
**Решение по смокам:** `smoke-select` на дельте раунда (`6efc315b..HEAD`) не
находит доказанной связи — сама дельта не меняет исполняемых веток
production-кода, кроме удаления мёртвой `unverified`-ветки (доказано выше) и
одного текстового литерала (`decisionIds` фолбэк S14, уже покрыт новым
production-тестом `S14`). Прогнал три смока, названные в r1 и в issue
(`smoke_controls`, `smoke_wireless_controller_parity`, `smoke_device_icon_design`),
для контроля регресса на итоговом `118062bb` — все зелёные. Полный набор
192 смоков — предрелизный гейт, не гейт ревью; дельта не даёт оснований его
расширять сверх того, что уже покрывал r1.
## Находки
Нет. Все три находки r1 (High-1, Medium-1, Low-1) закрыты и верифицированы
чтением production-кода и личным прогоном мутантов/тестов/смоков (таблица
выше), новых находок в дельте не возникло.
## Что проверено и корректно
- Закрытие High-1: три новых мутанта нацелены на уникальные, реально
различающиеся ветки `resolveDevicePresentationPolicy()`; патчи проверены
посимвольно и прогнаны лично.
- Закрытие Medium-1: `unverified` доказуемо недостижим на всех путях
построения `DevItem` (три места присвоения `bindingStatus:` в
`devices.ts`, все фильтруют `unverified` до конструирования) — удаление
ветки корректно, а не «спрятанная» правка поведения.
- Закрытие Low-1: S14 добавлен по всей цепочке document↔fixture↔production↔
mutation, включая рабочий production-тест (`presentationRow` с реальным
`resolveDevicePresentation()`, не изолированный policy-вызов).
- Контракт-тест `device presentation decision document, fixture and mutation
registry stay exact` (`test/device-presentation-policy.test.mjs:93-107`)
по-прежнему проходит на новом составе из 44 рядов (замена L06 на «orphaned
binding», добавление S14) — сверка документ↔fixture↔registry не нарушена.
- Трейлеры `Issue: #267` / `User-Visible: no` на `118062bb` корректны:
видимое поведение не менялось (удалена недостижимая ветка,
добавлена тестовая/документационная инфраструктура), `CHANGELOG.md` не
тронут — это верно для `User-Visible: no`.
- `git rev-parse HEAD` = `118062bb…`, совпадает с материалом,
заявленным в хендоффе r2.
## Чего не проверял
- Полный `npm run golden:verify` в этом раунде — не перезапускал; см.
обоснование в разделе «Унаследовано из r1» (AC8): единственное изменение
рендер-пути — доказуемо мёртвая ветка.
- Полный набор `demo/smoke_*.mjs` (192 файла) — не запускал, только три
названных плюс два, названных `smoke-select` по полному диффу issue;
остальные 189 не связаны дельтой ни прямо, ни по «слабой связи» — не
повод их гонять на этом раунде.
- Инварианты геометрии/модели и backend-тесты — дельта их не задевает.
- Ручное открытие демо-стенда в браузере — не делал; полагаюсь на
унаследованный из r1 полный golden (130/130) и текущие смоки как
эквивалентное подтверждение отсутствия визуального регресса.
## Итог
Все находки r1 закрыты и верифицированы независимо (не по слову автора):
патчи мутантов сверены построчно с production-кодом, все четыре
новых/изменённых targeted-мутанта лично прогнаны и пойманы,
недостижимость `unverified` доказана чтением всех точек конструирования
`DevItem`. Штатные гейты (typecheck/test/build+сверка бандлов/check-docs/
process-gate/mutation-gate --check) зелёные на `118062bb`. Новых находок
нет. High: 0, Medium: 0.