Files
houseplan-card/docs/reviews/CODE-REVIEW-224-r2.md
T
2026-08-22 12:48:41 +00:00

17 KiB
Raw Blame History

CODE-REVIEW-224-r2

  • Issue: #224 — канонические координаты на каждой записи
  • ТЗ: docs/specs/224-config-coordinate-canonicalization.md, ревью ТЗ зелёное (docs/reviews/SPEC-REVIEW-224-r1.md)
  • r1: docs/reviews/CODE-REVIEW-224-r1.md, вердикт красный на SHA 4a798e3e13275c0820aec2db1e63d1fae31a41f3
  • Дельта этого раунда: git diff 4a798e3..HEAD (HEAD = 4dbdb446f87717ce65471c7d8123a59353856821, коммит «fix: address coordinate review findings»)
  • Заход: r2 · блокирующих циклов израсходовано 1 из 4 (r1 был красным вердиктом, потратил цикл; настоящий заход при зелёном исходе цикла не потратит — #227)
  • Вердикт: зелёный · High: 0 · Medium: 0

Скоуп

Дельта — точечный фикс трёх находок r1, а не новая работа: 11 файлов, +340/-32 (без учёта трёх бинарных копий бандла, которые меняются только по контрольной сумме, и docs/reviews/CODE-REVIEW-224-r1.md, который в этот коммит просто добавлен пайплайном, а не автором):

  • src/houseplan-card.ts — _writeConfig() перестаёт безусловно переприсваивать _serverCfg новым клоном; ручной _cfgEpoch++ убран;
  • src/serialized-write-queue.ts (новый) — сериализация записи вынесена в чистую функцию enqueueSerializedWrite;
  • test/serialized-write-queue.test.mjs (новый) — controlled-promise unit на саму очередь (AC6);
  • test/coordinate-canonicalization.test.mjs — усилен regex-guard на форму _writeConfig();
  • scripts/mutation-gate.mjs, tsconfig.test.json — механические правки под переименованный отступ/новый файл;
  • docs/images/screenshots.json, три копии бандла — пересборка/пересъёмка.

Правка не меняет контракт поведения, не трогает Python/custom_components/** и не задевает новую подсистему — делает её локальной по критерию §2.10, разбор по дельте оправдан.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
H1 (High) — 20 golden-сцен different, похоже на пропавший визуальный элемент, вопреки AC12/§2/§15 ТЗ Причина найдена и устранена: _serverCfg теперь переприсваивается только когда canonicalizeConfigGeometry реально меняет контент; раньше безусловная переприсвойка новым клоном (тот же контент, новая ссылка) реактивно триггерила willUpdate() → _cfgEpoch++ → инвалидацию геометрического кэша, и в это же самое обновление подмешивалась несвязанная нормализация _dropLegacySegments() (стены после known_devices-записи при старте) — она и оказывалась видимой как «пропавший» элемент src/houseplan-card.ts:6949-6969 (диф 4a798e3..HEAD); перепроверено исполнением, не по заявлению автора: npm run golden:verify на этой ветке — 3 different из 82, все три — opening-placement-*-thick-wall-*; тот же прогон на чистом origin/dev (изолированный worktree, тот же Chromium/бандл) даёт ровно те же три different с теми же diffRatio/maxObservedDelta — регрессии #224 больше нет, остаток предсуществующий и к задаче не относится (см. «Как проверялось»)
M2 (Medium, в скоупе) — AC6 обещает «Queue/debounce unit с controlled promises», а в диффе был только regex по тексту исходника Сериализация записи вынесена в отдельную функцию enqueueSerializedWrite и покрыта двумя controlled-promise тестами: правка, сделанная во время in-flight записи, доходит до следующего звена; упавшая запись не блокирует следующее src/serialized-write-queue.ts (новый файл), test/serialized-write-queue.test.mjs:1-53; прогнано (npm test) — ok 788, ok 789; тест умеет падать — прослежено по коду: без ожидания previous второй write() выполнился бы синхронно при вызове enqueue(), observed получил бы 'second' раньше assert.deepEqual(observed, ['first'], …) и тест бы покраснел (правку enqueueSerializedWrite для живого прогона делать не могу — рецензенту запрещено менять продуктовый код, а инструмент редактирования это и подтвердил отказом в разрешении)
M1 (Medium, в скоупе) — check-docs.mjs красный, отпечаток скриншотов не обновлён npm run build && node demo/docs/capture.mjs прогнаны, docs/images/screenshots.json обновил sourceFingerprint/sourceSha256; все десять imageSha256 не изменились — визуал скриншотов документации не затронут docs/images/screenshots.json (диф 4a798e3..HEAD, только строки sourceFingerprint/sourceSha256); перепроверено: node scripts/check-docs.mjs → «Documentation checks passed (7 files, 10 external links)»
L1 (Low, было можно снять без правки) — двойной инкремент _cfgEpoch Правка вошла в тот же коммит заодно с H1: ручной if (…) this._cfgEpoch++ удалён, willUpdate() остался единственным местом инкремента src/houseplan-card.ts:6961-6963 — сравните с r1-версией, где после этой строки стоял ручной this._cfgEpoch++

Как проверялось

Гейт Результат
npx tsc --noEmit зелёный (без вывода)
npm test 1063 тестов, 0 упало, 0 skipped (у автора локально было 1062+1 skip — расхождение из-за environment-зависимых t.skip('git недоступен'/'нужен исполняемый stub gh') в test/process-gate.test.mjs, не связано с #224)
npm run build + cmp трёх копий (dist/custom_components/.../frontend/demo/srv/assets) все три побайтово идентичны
node scripts/check-docs.mjs (обязателен: diff трогает src/**) зелёный: «Documentation checks passed (7 files, 10 external links)»
npm run golden:verify (нужен: r1 был красным именно здесь — AC12) зелёный относительно #224: 3 different из 82, все — opening-placement-*-thick-wall-*. Независимо перепроверено на origin/dev в изолированном git worktree (тот же бандл-пайплайн, тот же Chromium): та же тройка с теми же diffRatio/maxObservedDelta (0.0053/0.00528, 210) уже отличается на dev — не регрессия этой задачи, ни этого раунда, ни r1. Прогнано дважды подряд на ветке — стабильно, не флейк
git diff --check 4a798e3..HEAD чисто, без ошибок пробелов
node scripts/process-gate.mjs «гейт пройден, предупреждений 0» (офлайн-часть; --issues не запускал — статус issue тут не требуется)
Мутанты, которые дельта могла задеть по форме патча (frontend-writes-raw-coords, quantization-hits-allowlist) оба «покраснел, как обязан» → «поймано 1 из 1»
Мутанты schema-quantization-removed, import-path-bypasses-schema (backend) не прогонял — в этой ревью-среде нет pytest/.venv-backend (Python backend не установлен). Не требуется этим раундом: дельта 4a798e3..HEAD не трогает ни одного файла custom_components/**/*.py, оба мутанта — «поймано 1 из 1» в r1 на прежней проверке, наследуются без переисполнения (см. «Унаследовано из r1»)
python -m pytest tests_backend -q не прогонял — дельта не трогает custom_components/**/*.py, не требуется §8; результат r1 (346 passed + 1 посторонний teardown-error, не связанный с диффом) наследуется
Смоки по grep изменённых имён (enqueueSerializedWrite, serialized-write-queue, _writeConfig() точных совпадений на новое имя enqueueSerializedWrite/serialized-write-queue в demo/smoke_*.mjs нет (ни один смок не адресует внутреннюю очередь напрямую); прогнаны два смока, которые r1 уже определил как релевантные поверхности записи конфига — node demo/smoke_config_writer.mjs → OK; node demo/smoke_optimize_coordinate_canonicalization.mjs → OK (все 13 полей отчёта true)
Полный набор demo/smoke_*.mjs (163 шт.) не прогонял — дельта не расширяет затронутые поверхности сверх того, что уже проверено выше; полный набор — предрелизный гейт (§8)
Performance-профили не прогонял — не названы в AC, дельта не трогает чувствительные к перфу пути (только сериализация записи и условие переприсвоения ссылки)

Находки

Нет находок этого раунда — все High/Medium/Low из r1 закрыты и перепроверены (см. «Закрытие раунда r1»); новых, привнесённых дельтой, не обнаружено.

Что проверено и корректно

  • AC12 (golden не меняется) — перепроверено исполнением дважды на ветке плюс контрольным прогоном на изолированной копии origin/dev: разница ограничена тремя предсуществующими сценами, привязки к #224 нет.
  • AC6 (правка во время in-flight записи не теряется) — теперь доказано заявленным в ТЗ способом (controlled-promises unit), а не текстовым regex; логика очереди (enqueueSerializedWrite) идентична тому, что раньше было инлайном в _writeConfig() — рефакторинг чистый, семантика записи не менялась, только извлечена и покрыта тестом.
  • Три копии продакшн-бандла синхронны; check-docs.mjs зелёный, скриншоты документации не изменились по содержимому (только исходный fingerprint).
  • Коммит 4dbdb44: трейлеры Issue: #224, User-Visible: no — корректно: правка чинит нерелизованную регрессию внутри той же незавершённой задачи и не меняет ничего, что пользователь уже мог увидеть; отдельный changelog не требуется, User-Visible: yes уже был закрыт основным коммитом 4a798e3 в r1.
  • git diff --check и офлайн process-gate.mjs чистые.

Унаследовано из r1

Всё, что дельта не трогает, принято без повторной проверки по документу docs/reviews/CODE-REVIEW-224-r1.md на SHA 4a798e3e13275c0820aec2db1e63d1fae31a41f3:

  • AC1–AC5, AC7–AC11 — контракт квантования Python/TypeScript, allowlist/denylist, schema barrier, no-op контракт config/set/layout/set/layout/update, импорт, Undo/Optimize, регресс #218, диагональные координаты — ни один файл custom_components/houseplan/**/*.py, src/coordinate-canonicalization.ts или их тестов не менялся в дельте 4a798e3..HEAD.
  • python -m pytest tests_backend -q (346 passed + 1 посторонний teardown-error, не связан с диффом) и backend-мутанты schema-quantization-removed/import-path-bypasses-schema (1/1) — результат r1, backend-код не тронут.
  • Документация продукта (docs/CANVAS.md, docs/CONFIG-COMPATIBILITY.md, docs/USER-GUIDE.ru.md, оба CHANGELOG*, docs/TESTING.md, docs/STATUS.md) — не менялась в дельте, содержательно не переоценивалась повторно.
  • Смоки smoke_config_writer.mjs/smoke_optimize_coordinate_canonicalization.mjs запущены заново (см. «Как проверялось»), а не только унаследованы — дельта достаточно близко касается _writeConfig(), чтобы перепрогнать их, а не поверить прежнему результату.

Чего не проверял

  • pytest tests_backend, backend-мутанты — дельта не трогает Python; кроме того, в этой ревью-среде нет установленного .venv-backend/pytest, так что даже сверх необходимого прогнать их здесь было бы нельзя.
  • Полный demo/smoke_*.mjs (163 шт.) и mutation-gate.mjs целиком — дельта локальна, названные в r1 и перепрогнанные в этом раунде смоки/мутанты покрывают затронутую поверхность.
  • Performance-профили — не названы в AC, дельта не трогает чувствительные пути.
  • _persistLayout()'s localStorage-ветка (src/houseplan-card.ts, добавлена в 4a798e3) переприсваивает this._layout тем же безусловным паттерном, что был источником H1 в _serverCfg — но эта строка не входит в дельту r1→r2, и golden:verify не показал никакой связанной регрессии. Оставляю как наблюдение, не как находку: нет ни изменения в этом раунде, ни воспроизведённого дефекта, который оправдывал бы возврат в скоуп, закрытый r1 по AC12.

Итог

H1, M1, M2 закрыты и перепроверены исполнением (не по заявлению автора): golden чист относительно #224 (независимо сверено с origin/dev), check-docs зелёный без изменения содержимого скриншотов, AC6 теперь доказан controlled-promise тестом, который умеет падать. L1 закрыт заодно, хотя мог быть просто снят. Новых находок дельта не принесла. Зелёный вердикт.