diff --git a/docs/reviews/CODE-REVIEW-248-r1.md b/docs/reviews/CODE-REVIEW-248-r1.md index b738e335..ba5bfe86 100644 --- a/docs/reviews/CODE-REVIEW-248-r1.md +++ b/docs/reviews/CODE-REVIEW-248-r1.md @@ -1,222 +1,232 @@ -# CODE-REVIEW-248-r1 +# CODE-REVIEW-248-r2 - Issue: [#248](https://github.com/Matysh/houseplan-card/issues/248) — «Оптимизировать» не идемпотентна после записи и reload -- Ветка: `issue/248-optimize-idempotence`, коммит реализации `cbdd5a7e73b2b7afd1e05bee453154d3e75cd60b` +- Ветка: `issue/248-optimize-idempotence` - ТЗ: `docs/specs/248-optimize-idempotence.md` (SPEC-REVIEW-248-r1, зелёный) -- Этап: code (PROCESS.md §2.7), заход r1, трек обычный -- Вердикт: **красный** +- Этап: code (PROCESS.md §2.7), **заход r2**, трек обычный +- Вердикт: **зелёный** -Первый заход кода — разбор полный, разделов «Закрытие раунда» и «Унаследовано» -нет (PROCESS.md §2.9/§2.10 применяются со второго цикла; предыдущий цикл был -только на этапе spec и уже закрыт зелёным). +## Расхождение с переданным заголовком задачи + +Заголовок этого запуска называл «заход r1 · блокирующих циклов 0/4». Это не +соответствует действующему состоянию issue и было исправлено по факту, а не +принято на слово: + +- в комментариях #248 уже есть код-ревью с вердиктом **красный · заход r1 · + блокирующих циклов 1/4 · High: 1** (находка H1 — устаревший docs-отпечаток); +- документ того раунда уже закоммичен в дерево задачи — + `docs/reviews/CODE-REVIEW-248-r1.md`, коммит `e468414` (текущий HEAD); +- автор закрыл H1 и владелец явно написал «Возвращаю задачу в code review + для захода r2». + +Соответственно этот документ — **`-r2`**, а не `-r1`; публикация в +`docs/reviews/` не должна затирать уже существующий `CODE-REVIEW-248-r1.md`. +Бюджет циклов после этого зелёного вердикта остаётся **1/4** (зелёный вердикт +цикл не тратит, #227). + +Отдельно к самому раунду r1: его вердикт не называл SHA, на котором получен — +только производная находка H1 упоминает `HEAD (cbdd5a7)` внутри своего текста. +Формально это ровно то расхождение, которое делает раунд 2 менее +воспроизводимым, чем должен быть; в этот раз SHA раунда явно зафиксирован +(см. ниже), чтобы для потенциального r3 не потребовалось той же реконструкции. + +**SHA раунда r1 (материал предыдущего вердикта):** `cbdd5a7e73b2b7afd1e05bee453154d3e75cd60b` +(реконструирован из текста находки H1 — там же единственная явная привязка). +**SHA этого раунда (r2, текущий HEAD):** `e468414c241900f2175923cfb0901a73e86e9767`. ## Скоуп ревью -Материал: `git log --oneline origin/dev..HEAD` (3 коммита: spec, SPEC-REVIEW, -implementation) и `git diff origin/dev...HEAD` (21 файл, +1084/-170). -Продуктовый код меняется только в `src/plan-optimizer.ts`; backend production -code (`custom_components/houseplan/**/*.py`) не тронут — согласно §13.5 ТЗ это -осознанное решение, обоснованное тем, что `store.py`/`websocket_api.py` уже -канонизируют обе половины записи (проверено чтением, см. ниже). +Предмет раунда — дельта `git diff cbdd5a7..HEAD`, а не задача целиком (PROCESS.md +§2.10): три коммита сверх `cbdd5a7` — -Проверено по AC1–AC6 из ТЗ (§9), пункт за пунктом, чтением production-кода и -исполнением тестов, которые эти AC называют. +- `5ad4280` test: complete Optimize storage fixtures (`User-Visible: no`) +- `120b491` test: require schema-valid Optimize fixture (`User-Visible: no`) +- `e468414` docs: review document for #248 (`User-Visible: no`, только + `docs/reviews/CODE-REVIEW-248-r1.md`, класс C, не участвует в проверке + статуса issue по process-gate) + +Дельта касается ровно 4 файлов кроме самого review-документа: + +``` +docs/images/05-plan-context-tray.png | Bin 345560 -> 345559 bytes +docs/images/screenshots.json | 24 +-- +test/fixtures/optimize-storage-roundtrip.json | 4 + +tests_backend/test_coordinate_canonicalization.py | 2 + +``` + +`src/**` и `custom_components/houseplan/**/*.py` в дельте не тронуты — +продуктовый фикс идемпотентности (`src/plan-optimizer.ts`) целиком лежит в +`cbdd5a7` и уже был разобран в r1. Дельта — это (а) закрытие H1 (docs +fingerprint) и (b) самостоятельная находка автора по итогам первого CI: +общая fixture не проходила backend-схему без `view_box`. + +Разбор оставлен полным по чувствительным точкам, а не «только H1»: раунд не +локален формально (дельта пересекает docs-гейт и backend-fixture-контракт +AC4), поэтому ниже переисполнены typecheck/test/build/check-docs/process-gate +и целевой AC5-смок, а не только прочитан diff. ## Как проверялось -### Код - -- `src/plan-optimizer.ts` — полный diff `optimizePlans()`. Итоговая пара - `persistedConfig = canonicalizeConfigGeometry(config)` / - `persistedLayout = canonicalizeLayoutGeometry(aligned.layout)` теперь - вычисляется **до** сравнения с `original`/`originalLayout` и до решения по - `model_version`, а не после. Это именно тот сдвиг границы, который требует - §6.1 ТЗ: второй прогон сравнивает уже сохранённый на диске 9-знак с тем же - 9-знаком, а не с двоичным `snapN()`-результатом. -- `src/coordinate-canonicalization.ts` (`canonicalizeConfigGeometry`, - `canonicalizeLayoutGeometry`) — обе функции клонируют вход (`cloneJson`) и - не мутируют `config`/`aligned.layout` на месте, поэтому промежуточные - вычисления (`alignReport`, `beforeSpaces`-сравнения) остаются корректными - относительно немодифицированных объектов. -- Зануление счётчиков отчёта (`persistedAlignReport`, `persistedReferences`, - тернарники `migrated`/`canonicalized`/`wallsMerged`/…) срабатывает только - когда `changed === false`, то есть только когда итоговая канонизированная - пара побайтово равна входу. `total` в зануление не входит — соответствует - §6.2 ТЗ («total остаётся диагностическим и не обязан быть нулём»). -- Прочитан существующий тест «six-room ULP» (`test/plan-optimizer.test.mjs:100-129`) - — подтверждает, что AC2 (реальный noisy-вход по-прежнему даёт - `changed:true`, `coordsCanonicalized>0`, честный отчёт) не сломан: правка - зануляет отчёт только при отсутствии итоговой разницы, а не всегда. -- `custom_components/houseplan/store.py:150-229` и - `custom_components/houseplan/websocket_api.py:1554-1666` — подтверждено - чтением, что `ws_plan_optimize` канонизирует `optimize_pending` явно - (`canonicalize_config_geometry`/`canonicalize_layout_geometry`, строки - 1621-1633) и что `async_save_config_state`/`async_save_layout_state` - канонизируют ещё раз внутри себя независимо от вызывающего кода — двойная - канонизация идемпотентна, поэтому backend production-код действительно не - нуждался в правке (AC3 предположение §13.5 подтверждено, не принято на - слово). -- `test/fixtures/optimize-storage-roundtrip.json` — общая fixture с двумя - масштабами (`cell_cm: 3` и `5`), воспроизводит ровно тот дефект, который - описал владелец в аналитике (`0.004166666766666667` → `0.004166667`). -- `scripts/mutation-gate.mjs` — три новых мутанта: `optimize-storage-boundary-removed` - (frontend), `optimize-config-storage-half-raw`, `optimize-layout-storage-half-raw` - (backend, обе точки `store.py`). - -### Выполненные команды и результат - | Команда | Результат | |---|---| | `npx tsc --noEmit` | green, без вывода | -| `npm test` | 1116/1116 pass, 0 fail, 0 skip (см. расхождение в находках) | -| `npm run build` | green; `git status` после сборки чист — бандл в коммите уже актуален | -| `md5sum dist/houseplan-card.js demo/srv/assets/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` | все три идентичны (`c5510c455…`) | -| `node scripts/check-docs.mjs` | **FAIL** — см. находку H1 | -| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | НЕОПРЕДЕЛЁННОСТЬ: 1 файл src, 5 символов (`AlignReport`, `PLAN_MODEL_VERSION`, `SpaceReferenceReport`, `canonicalizeConfigGeometry`, `canonicalizeLayoutGeometry`), ни один смок не назвал их напрямую | -| `node demo/smoke_optimize_coordinate_canonicalization.mjs` | green, все 16 полей true — это целевой смок AC5, расширенный этим коммитом (apply/update-reload/cold-reload/undo) | -| `node demo/smoke_optimize_geometry_preflight.mjs` | green — смок с сильной тематической связью (тоже имя `optimize`), проверен по своей инициативе из-за НЕОПРЕДЕЛЁННОСТИ инструмента | -| `node demo/smoke_optimize_micro_interval.mjs` | green — та же причина | -| `node scripts/mutation-gate.mjs --check` | все мутанты (включая три новых) — статически валидны, якорь патча найден ровно один раз | -| `node scripts/mutation-gate.mjs --id=optimize-storage-boundary-removed` | mutant caught: чистый прогон зелёный, с мутацией — целевой тест `issue 248 Optimize stays…` краснеет, как обязан | -| `node scripts/process-gate.mjs --issues` | green, диапазон 3 коммита, 0 предупреждений | +| `npm test` | 1116/1116 pass, 0 fail, 0 skip | +| `npm run build` | green | +| `cmp dist/… custom_components/…/frontend/… && cmp dist/… demo/srv/assets/…` | все три бандла идентичны байт-в-байт | +| `git status --porcelain` после build | пусто — закоммиченный бандл уже актуален | +| `node scripts/check-docs.mjs` | green — «Documentation checks passed (7 files, 10 external links)» — H1 закрыт | +| `node scripts/smoke-select.mjs --base cbdd5a7 --head HEAD` | «Исполняемого frontend-диффа нет (src/**/*.ts не тронут)» — дельта раунда не выбирает ни один смок | +| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | воспроизведено идентично отчёту r1: 1 файл src, 5 символов (`AlignReport`, `PLAN_MODEL_VERSION`, `SpaceReferenceReport`, `canonicalizeConfigGeometry`, `canonicalizeLayoutGeometry`), НЕОПРЕДЕЛЁННОСТЬ | +| `node demo/smoke_optimize_coordinate_canonicalization.mjs` | green, все 16 полей `true` (включая `serverEventReloadIsExactNoOp`, `coldReloadIsExactNoOp`) — перепрогнан лично на текущем HEAD, не только унаследован | +| `node scripts/mutation-gate.mjs --check` | green, все мутанты валидны статически, включая три из #248 (`optimize-storage-boundary-removed`, `optimize-config-storage-half-raw`, `optimize-layout-storage-half-raw`) | +| `node scripts/process-gate.mjs` | green, диапазон `origin/dev..HEAD`, 6 коммитов, 0 предупреждений | +| `node scripts/process-gate.mjs --issues` | green (issue #248 в `S7-code-review`) | +| `python3 -c "import pytest"` / `import voluptuous` | оба `ModuleNotFoundError` — окружение этой ревью-сессии не содержит backend-харнесс, как и в r1 | +| `gh run view 32602363169 --json status,conclusion,headSha,jobs` | **исполнено, не унаследовано на слово**: `conclusion: success`, `headSha: 120b491…`, job `backend` — success (`pip install pytest … pytest-homeassistant-custom-component …` + `python -m pytest tests_backend/ -q` — success), job `docs` — success, `process-gate` — success | +| `git diff 120b491..HEAD --stat` | подтверждает, что HEAD (`e468414`) = проверенное CI-дерево `120b491` + только `docs/reviews/CODE-REVIEW-248-r1.md` — CI-результат применим к текущему HEAD без экстраполяции | +| `gh run view 32602050492 --json conclusion,workflowName,headBranch` | success, `Docs screenshots`, ветка `issue/248-optimize-idempotence` — легитимная пересъёмка, не самодельный PNG | -### Полный список смоков (167/169) — не прогонялся - -Диф изменяет только внутренности `optimizePlans()` (уже отражено в отчёте о -инструменте выбора как «5 символов на 1 файле»); полная матрица уместна, -когда задача задевает всё приложение, а не один расчёт отчёта Optimize. -Выбор ограничен тремя смоками с прямым или сильным тематическим совпадением -(`optimize_coordinate_canonicalization` — целевой AC5, плюс -`optimize_geometry_preflight` и `optimize_micro_interval` — та же подсистема). -Остальные 164 не запускались. - -### golden:verify — не прогонялся - -Diff не меняет геометрию, стили или слои рендера — только числовое -представление, которое уже совпадает с действующим 9-знаковым storage -contract на масштабах, которые видит пользователь (различие ~1e-9, за -пределами любого визуального пикселя). ТЗ §12 прямо говорит «Golden не должен -измениться»; AC6 относит golden к общим предрелизным гейтам, не к review-циклу. - -### python -m pytest tests_backend -q — не прогонялся (окружение) - -В этой ревью-сессии не установлен `pytest`/`pytest-homeassistant-custom-component` -(`ModuleNotFoundError: No module named 'pytest'`, `pip3 list` пусто по этому -пакету, `pyproject.toml`/`requirements*.txt` под backend-тесты в репозитории -нет). Production backend-код в диффе не тронут, поэтому это не блокирует -вывод по AC3/AC4, но сами новые backend-тесты -(`tests_backend/test_ha_websocket.py::test_plan_optimize_persists_exact_storage_roundtrip_target`, -`tests_backend/test_ha_import_export.py::test_setup_recovers_exact_optimize_storage_roundtrip_pair`, -`tests_backend/test_coordinate_canonicalization.py::test_optimize_roundtrip_fixture_has_one_backend_canonical_target`) -и два backend-мутанта (`optimize-config-storage-half-raw`, -`optimize-layout-storage-half-raw`) **проверены чтением, не исполнением**: -код handler/store совпадает с тем, что тесты утверждают (см. раздел «Как -проверялось» выше). Канонический прогон — Linux CI, как и указал автор в -своём комментарии про полный HA harness. +Дешёвые гейты прогнаны заново, как требует §2.10 (код изменился, они стоят +минуты). Тяжёлые — по дельте: golden/полный smoke-набор/performance не +прогонялись (обоснование в «Чего не проверял»), backend pytest не исполнялся +локально, но подтверждён предъявленным CI-прогоном на точном дереве текущего +HEAD, а не восстановлен из слов автора. ## Находки -### H1 (High, в скоупе) — гейт документации красный: скриншот-отпечаток не пересчитан +Нет находок High или Medium в этом раунде. -`node scripts/check-docs.mjs` падает на HEAD (`cbdd5a7`): +**Low (на усмотрение, не блокирует):** заголовок задачи, полученный этой +сессией, называл неверный заход/бюджет (`r1 · 0/4` вместо фактических +`r2 · 1/4`). Указано выше в отдельном разделе, не дублируется здесь как +отдельная находка кода — это дефект оркестрации запуска ревью, а не диффа +#248, и не входит в скоуп автора. Если он повторяется на других issue, стоит +завести `process`-issue отдельно от #248. -``` -ERROR screenshot source fingerprint is stale; run npm run build && node demo/docs/capture.mjs -``` +## Закрытие раунда r1 -Воспроизведение: на `origin/dev` (`6473d5e`) тот же скрипт зелёный -(«Documentation checks passed»); единственная переменная — diff этой ветки. -Причина находится точечно: `docs/images/screenshots.json` хранит -`sourceFingerprint: "2eaf9a2d67f8d6c73b9397b0718668e1a1ca1da7e9156f03aa5850e8bd4b538f"`, -а фактический отпечаток `src/**` на HEAD — -`3f3237694ccc9aabd77df6204c598797dc528c2f7a23520915083486666be740` -(проверено вызовом `visualFingerprint()` из `scripts/source-fingerprint.mjs` -напрямую). `src/plan-optimizer.ts` входит в набор, по которому считается -отпечаток, — правка в нём безусловно делает манифест снимков устаревшим, -независимо от того, что сам расчёт не меняет ни одного визуального пикселя. +| Находка r1 | Чем закрыта | Где это видно | +|---|---|---| +| **H1** (High) — `check-docs.mjs` красный на `cbdd5a7`: `docs/images/screenshots.json` хранит устаревший `sourceFingerprint`, `src/plan-optimizer.ts` из диффа делает его неактуальным | Пересъёмка через каноническую джобу `Docs screenshots` (workflow_dispatch) на Chromium 151.0.7922.34, принята `npm run docs:accept -- --reviewed`; `sourceFingerprint`/`sourceSha256` в `docs/images/screenshots.json` обновлены на `3f3237694ccc9aabd77df6204c598797dc528c2f7a23520915083486666be740` во всех 10 сценариях | Коммит `5ad4280`; лично перепрогнан `node scripts/check-docs.mjs` на HEAD `e468414` → green; `gh run view 32602050492` → success на ветке задачи; из 10 PNG изменился только `05-plan-context-tray.png` (345560→345559 байт, один `imageSha256` из десяти), остальные девять байт-в-байт прежние — подтверждено `git diff cbdd5a7..HEAD -- docs/images/screenshots.json \| grep imageSha256` | +| **L1** (Low) — хендофф говорил «1115 pass, 1 skip», факт — «1116 pass, 0 skip» | Объяснено, не правкой: разные среды (Windows platform-skip у автора, Linux 0 skip у ревьюера r1), оба числа корректны для своей среды | Комментарий владельца «Закрытие CODE-REVIEW-248-r1»; лично перепрогнан `npm test` на HEAD `e468414` → `1116 pass, 0 skip`, совпадает с числом, которое r1 сессия получила на Linux — outcome: no_change_needed | -Это ровно тот класс регрессии, который инструкция по ревью называет по -номеру: пропуск этого шага в #230 и #234 оставил `dev` с красным job `docs` -до следующей задачи (#237). Список проверок, который автор привёл в -хендофф-комментарии (`typecheck`, `unit`, `build`, targeted smoke, -mutation-gate, process-gate, py_compile), **не включает `check-docs.mjs`** — -шаг был пропущен, а не признан неприменимым. +## Унаследовано из r1 -Исправление механическое и в скоупе задачи: `npm run build && node -demo/docs/capture.mjs`, закоммитить обновлённый -`docs/images/screenshots.json` (и, при необходимости, сами PNG — картинки, -скорее всего, побайтово не изменятся, но `sourceSha256`/`sourceFingerprint` в -манифесте обязаны обновиться) в том же commit. Отдельный issue не заводится -(решение владельца 2026-08-19, #202) — это Medium-по-типу-фикса, но -поднимается до High, потому что оставляет `dev` в объективно красном -состоянии сразу после мержа, а не «может сломать» — эффект детерминирован и -уже дважды случался. +Принято без повторной проверки в этом раунде, поскольку дельта `cbdd5a7..HEAD` +не касается ни одного из перечисленных файлов (подтверждено `git diff +cbdd5a7..HEAD --stat`, см. «Скоуп ревью»). Источник — `docs/reviews/CODE-REVIEW-248-r1.md`, +коммит `e468414` (уже в дереве), проверено на SHA `cbdd5a7`: -### L1 (Low, на усмотрение автора) — неточность в отчёте о прогоне тестов +- **AC1** — `test/plan-optimizer.test.mjs`, тест «issue 248 Optimize stays a + no-op across the nine-decimal storage round-trip»; мутационный guard + `optimize-storage-boundary-removed` подтверждён исполнением в r1 (тест + краснеет без правки границы в `src/plan-optimizer.ts`). Не тронуто дельтой. +- **AC2** — параметризованный тест по `cell_cm` 1/3/5/1000 в + `test/plan-optimizer.test.mjs`; существующий ULP-тест «six-room» подтверждает, + что реальный шум по-прежнему даёт `changed:true`, счётчики не занулены + безусловно. Не тронуто дельтой. +- **AC3** — `custom_components/houseplan/store.py:150-229`, + `websocket_api.py:1554-1666`: обе половины записи (`optimize_pending` и live + store) канонизируются независимо; backend production-код не менялся (§13.5 + ТЗ). В r1 это было «проверено чтением, не исполнением» из-за отсутствия + `pytest` в сессии; **в этом раунде дополнительно подтверждено исполнением** + через `gh run view 32602363169` — job `backend` success на `120b491`, то + есть на дереве, идентичном текущему HEAD за вычетом самого review-документа. +- **AC4** (частично) — общая fixture `test/fixtures/optimize-storage-roundtrip.json` + и её потребители в Node/Python. Наследуется факт «одна fixture, оба + runtime» из r1; **не** наследуется схемная валидность фикстуры на момент + `cbdd5a7` — это ровно то, что дельта раунда исправляет (см. «Как + проверялось» и разбор `view_box` ниже), поэтому по AC4 сделана + дополнительная самостоятельная проверка, а не чистое наследование. +- **AC5** — `demo/smoke_optimize_coordinate_canonicalization.mjs`, + расширенный под `serverEventReloadIsExactNoOp`/`coldReloadIsExactNoOp`. + Файл не тронут дельтой; тем не менее смок лично перепрогнан на текущем HEAD + (см. таблицу гейтов) — совпадает с r1: все 16 полей `true`. +- **AC6** (документация) — `docs/CANVAS.md` §9.5, `docs/CONFIG-COMPATIBILITY.md`, + `docs/USER-GUIDE.ru.md`, `docs/TESTING.md`, оба changelog: содержание не + тронуто дельтой, разобрано и принято в r1. +- Трейлеры `cbdd5a7` (`Issue: #248`, `User-Visible: yes`, оба changelog в том + же коммите) — унаследовано, не перепроверялось повторно (коммит не входит в + дельту раунда). +- Смок-выборка полного диффа `origin/dev...HEAD` (5 символов, + НЕОПРЕДЕЛЁННОСТЬ, три целевых смока с прямой/тематической связью запущены + и зелёные в r1: `optimize_coordinate_canonicalization`, + `optimize_geometry_preflight`, `optimize_micro_interval`) — воспроизведено + идентично инструментом в этом раунде (см. таблицу), сами смоки `_preflight` + и `_micro_interval` не перезапускались повторно, т.к. их файлы не в дельте. +- `golden:verify` и полный набор 167/169 браузерных смоков — не прогонялись + ни в r1, ни здесь; обоснование (diff не меняет геометрию/рендер, golden — + предрелизный гейт по AC6 и §12 ТЗ) наследуется без изменений, т.к. дельта + этого раунда тем более не касается рендера (только docs-манифест, фикстура, + один assert). +- Performance — не названо в AC, чувствительный путь (`O(n)` проход) не + тронут дельтой; наследуется из r1. -Хендофф-комментарий утверждает «npm test — 1116 tests: 1115 pass, 1 skip, 0 -fail». Фактический прогон на HEAD: `# tests 1116 / # pass 1116 / # skipped 0`. -Числа не совпадают (skip=0, не 1); реальный результат строго лучше -заявленного, так что это не блокер и не свидетельствует о нестабильности, но -доказательный текст должен отражать фактический вывод команды, а не -приблизительную память о нём — иначе следующий ревьюер не может доверять -числам без перепроверки. +## Проверено и корректно (сверх наследования) -## Проверено и корректно (AC1–AC6) - -- **AC1** — `test/plan-optimizer.test.mjs`: тест «issue 248 Optimize stays a - no-op across the nine-decimal storage round-trip» проверяет in-memory - второй прогон и прогон после явного `canonicalizeConfigGeometry`/ - `canonicalizeLayoutGeometry` (эмуляция backend echo) — оба дают - `changed:false`, нулевые счётчики, deep-equal пару. Мутационный guard - `optimize-storage-boundary-removed` подтверждён исполнением: без - канонизации границы целевой тест краснеет. -- **AC2** — тест «issue 248 every persisted geometry surface converges at - every supported scale» параметризован по `cell_cm` 1/3/5/1000, проверяет - `canonicalizeConfigGeometry(first.config) deepEqual first.config` (то есть - результат первого прохода уже находится на границе допустимости) и второй - прогон — no-op на каждом масштабе. Существующий ULP-тест подтверждает, что - реальный шум по-прежнему даёт `changed:true` — счётчики не занулены - безусловно. -- **AC3** — backend-тесты (`test_plan_optimize_persists_exact_storage_roundtrip_target`, - `test_setup_recovers_exact_optimize_storage_roundtrip_pair`) сверяют - `pending`/final store/startup-recovery с одной и той же fixture-парой через - `json.dumps(..., sort_keys=True)` — проверено чтением кода теста и кода - handler/store (исполнение недоступно в этом окружении, см. выше). -- **AC4** — `test/fixtures/optimize-storage-roundtrip.json` читается и Node - (`test/plan-optimizer.test.mjs`), и Python - (`tests_backend/test_coordinate_canonicalization.py`, - `test_ha_websocket.py`, `test_ha_import_export.py`) — общая fixture, - отдельных expected-копий нет. -- **AC5** — `demo/smoke_optimize_coordinate_canonicalization.mjs` расширен - ровно под этот AC: добавлены `serverEventReloadIsExactNoOp` (через - `_reloadConfigOnly`/`_reloadLayoutOnly`) и `coldReloadIsExactNoOp` (через - сброс `_serverCfg`/`_layout`/fingerprints и `_loadFromServer()`), плюс - сохранены `applyGeometryEqualsBackendTarget`/`applyLayoutEqualsBackendTarget`. - Прогон зелёный на production-бандле. -- **AC6** — `typecheck`, `unit`, `build` зелёные; три копии бандла - побайтово идентичны; golden/smoke/performance корректно отложены на - предрелизный гейт (см. обоснование выше). check-docs.mjs, который тоже - входит в «дешёвые и всегда» гейты этого ревью, красный — H1. - -Трейлеры коммита `cbdd5a7`: `Issue: #248`, `User-Visible: yes` — оба -changelog (`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`) обновлены в этом же -коммите, как требуется. +- **Закрытие H1** — не принято на слово: `check-docs.mjs` лично перепрогнан + зелёным на HEAD `e468414`, `git status` после `npm run build` чист, а + сравнение `imageSha256` по всем 10 сценариям показывает ровно один + изменённый файл (`plan-context-tray`, 1 байт), как и утверждал автор — + никакой другой скриншот не «съехал» тайком. +- **Самостоятельная находка автора (`view_box`)** — до фикса + `test/fixtures/optimize-storage-roundtrip.json` не содержал обязательное + поле `vol.Required("view_box")` (`custom_components/houseplan/validation.py:984`); + без него `CONFIG_SCHEMA(source["config"])` в + `test_optimize_roundtrip_fixture_has_one_backend_canonical_target` не могла + бы пройти. Значение `[0, 0, 1, 1]` проверено против `_view_box()` + (`validation.py:566-570`): `x=0, y=0` в допуске `_GEOM`, `w=1, h=1` в + допуске `_EXTENT` (`0.001..CANVAS_LIMIT`) — валидно и не вырождено (не + повторяет баг HP-1502-01 про `[0,0,0,0]`). Значение идентично в `input` и + `expected` для обеих geometry (`fine`, `regular`) — канонизация не должна + его менять, что и подтверждает зелёный backend-прогон в CI. +- **AC3/AC4 backend-доказательство теперь подтверждено исполнением, а не + только чтением** — `gh run view 32602363169` называет точный SHA (`120b491`) + и job (`backend`) с командой `python -m pytest tests_backend/ -q`, success; + `git diff 120b491..HEAD --stat` показывает, что единственная разница между + проверенным CI-деревом и текущим HEAD — файл `docs/reviews/CODE-REVIEW-248-r1.md` + (класс C, не участвует в тестах). Это устраняет ограничение «проверено + чтением, не исполнением», отмеченное в r1 для AC3. +- **Docs screenshots run легитимен** — `gh run view 32602050492` подтверждает + workflow `Docs screenshots` на ветке задачи с успешным результатом, то есть + принятие через `docs:accept --reviewed` соответствует правилу §8 PROCESS.md + (не самодельный локальный кадр). +- **Трейлеры дельты** — `5ad4280`, `120b491`, `e468414` все несут + `Issue: #248`; `User-Visible: no` корректен для всех трёх (тесты/fixture/ + docs-манифест и сам review-документ не меняют пользовательское поведение + сверх того, что уже задокументировано в `cbdd5a7`). +- **process-gate** зелёный на полном диапазоне `origin/dev..HEAD` (6 + коммитов, 0 предупреждений) и с `--issues` (статус issue #248 соответствует + `S7-code-review`). ## Чего не проверял -- Полную матрицу 167/169 браузерных смоков — обоснование в разделе «Как - проверялось» (слабая/отсутствующая связь по инструменту выбора, диф не - затрагивает рендер). -- `npm run golden:verify` — diff не может изменить визуальный результат - (обоснование выше); гейт предрелизный по ТЗ и AC6. -- `python -m pytest tests_backend -q` и оба backend-мутанта - (`optimize-config-storage-half-raw`, `optimize-layout-storage-half-raw`) — - недоступны в этом окружении (нет `pytest`); backend production-код не - тронут, соответствующие AC закрыты чтением кода, не исполнением. -- Performance-профили — не названы в AC, чувствительные к перфу пути (кроме - одного дополнительного `O(n)` прохода, который сам ТЗ признаёт в §11 и не - вводит новый бюджет) не тронуты. -- Полный Linux HA harness — автор сам указал, что канонический прогон - выполняется в CI; в этой сессии окружение того же типа не позволило это - перепроверить (см. выше). +- Полную матрицу 167/169 браузерных смоков — не унаследовано слепо: лично + подтверждено, что `smoke-select` даёт тот же результат («НЕОПРЕДЕЛЁННОСТЬ», + 5 символов) на полном диффе `origin/dev...HEAD`, и что дельта этого раунда + (`cbdd5a7..HEAD`) вообще не выбирает смоков (`src/**/*.ts` не тронут). + Целевой AC5-смок перепрогнан лично; `_preflight` и `_micro_interval` — нет + (не в дельте, зелёные в r1). +- `npm run golden:verify` — diff (полный и дельта) не меняет геометрию, стили + или слои рендера; предрелизный гейт по AC6/§12 ТЗ. +- `python -m pytest tests_backend -q` — не исполнялся локально + (`pytest`/`voluptuous` не установлены в этой сессии), но подтверждён через + `gh run view` на CI-прогоне точного дерева HEAD минус review-документ — + это сильнее «проверено чтением», хотя и не то же самое, что личное + исполнение. +- Оба backend-мутанта (`optimize-config-storage-half-raw`, + `optimize-layout-storage-half-raw`) — `mutation-gate.mjs --check` + подтвердил только статическую валидность патча (якорь найден), не факт, + что тест краснеет с мутацией; их guard (`backend-test-guard.mjs`) тоже + требует `pytest`, недоступного в сессии. Backend CI job (`python -m pytest + tests_backend/ -q`) не запускает `mutation-gate.mjs`, поэтому исполнение + этих двух мутантов не подтверждено ни в r1, ни здесь — фактическое + исполнение остаётся долгом до следующего Linux-сеанса с установленным + `pytest`, но не блокирует зелёный вердикт: сам факт что тест существует и + статически бьёт по нужной строке проверен, а полное отсутствие исполнения + этих двух конкретных мутантов было тем же ограничением уже в r1 и не + регрессировало. +- Полный Linux HA harness (полная HA-обвязка, не только `tests_backend/` + pure-подмножество) — вне скоупа код-ревью, канонический прогон в CI/WSL. +- Performance-профили — не названы в AC, чувствительные пути не тронуты + дельтой.