Files
houseplan-card/docs/reviews/CODE-REVIEW-197-r1.md
T
claude[bot] 56f31dc199
Validate / docs (push) Failing after 38s
Validate / provenance (push) Successful in 1m11s
Validate / process-gate (push) Failing after 1m27s
Validate / changes (push) Successful in 1m20s
Validate / hacs (push) Skipped
Validate / hassfest (push) Skipped
Validate / backend (push) Skipped
Validate / frontend (push) Successful in 7m58s
Validate / golden (push) Failing after 9m32s
Validate / performance_smoke (push) Failing after 14m22s
Validate / smoke (push) Failing after 29m53s
docs: code review r1 for #197
Issue: #197
User-Visible: no
2026-08-19 10:12:32 +00:00

20 KiB
Raw Blame History

Код-ревью issue #197 — цикл r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/197
  • ТЗ: docs/specs/197-junction-patch-fail-dark.md (r2, зелёное docs/reviews/SPEC-REVIEW-197-r3.md)
  • Диапазон: origin/dev...HEAD (db5718095624e55f6407e37f867e8e520f9c4882, один продуктовый коммит fix: isolate degenerate junction patches)
  • Ревьюер: Claude, свежая сессия без контекста реализации
  • Вердикт: зелёный · цикл r1/4 · High: 0 · Medium: 0

Скоуп изменения

Единственный класс-A файл в диффе — src/wall-thickness.ts (+60/-6 строк). Остальное — тесты (test/wall-thickness.test.mjs, test/fixtures/197-junction-patch.json, test/golden-matrix.test.mjs), targeted browser smoke (demo/smoke_junction_patch_resilience.mjs), golden candidate-сцены (demo/golden/matrix.mjs, demo/golden/harness.mjs), документация (WALL-THICKNESS.md, ARCHITECTURE.md, TESTING.md, USER-GUIDE.ru.md, STATUS.md, оба CHANGELOG*) и три синхронные копии бандла. Ни backend, ни i18n, ни манифесты не затронуты — соответствует §6 ТЗ («не входит в задачу»).

Продуктовое изменение в src/wall-thickness.ts:

  1. stableJunctionPatch(patch, coordScale) — квантует координаты вычисляемого junction-патча с относительным шагом max(1, coordScale) × 10⁻¹², отбраковывает нефинитные точки и патчи с площадью не больше quantum².
  2. unionJunctionPatches(body, patches, coordScale, unionFn) — транзакционно добавляет каждый стабилизированный патч: локальный try/catch вокруг одного union, отказ сохраняет последнее валидное body и не прерывает обработку следующих патчей.
  3. Цикл for (const patch of junctions) body = body ? union(...) : ... в wallBodiesGeometry() (src/wall-thickness.ts:1760-1761 на dev) заменён вызовом unionJunctionPatches(body, junctions, coordScale). Общий структурный try { … } catch { return null; }, оборачивающий весь остальной pass (exterior envelope, room rings, edge bodies, opening cuts, extraBodies), не тронут и по-прежнему оборачивает точку вызова.
  4. virtualJunctionPatches сделана export (test seam, без изменения тела) — тот же паттерн, что и у остальных pure-geometry helpers этого модуля.

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

Гейт Команда Результат
Typecheck npx tsc --noEmit зелёный, без вывода
Unit npm test 900/900, зелёный
Build + сверка бандлов npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js зелёный; обе копии побайтно идентичны dist/, SHA-256 b0dcd6651964c1bf9b964e750758b8dc5cad25bccf099a1b740af9b873fe68b5 — совпадает с хендофф-комментарием автора
Дисциплина «тест умеет падать» временный откат unionJunctionPatches(...) на исходный незащищённый цикл в рабочей копии, npm test ровно один новый тест (issue #197 keeps the full masonry when one virtual-junction patch has ULP noise) стал красным, остальные 899 остались зелёными; после возврата файла npm test снова 900/900. Мутация подтверждает, что регрессия реальна, а не тавтологична
Targeted smoke (AC7/AC8) node demo/smoke_junction_patch_resilience.mjs (после свежей сборки и копирования в demo/srv/assets) зелёный, все 15 проверок true: полный fixture (8/25/3), Plan/View/kiosk/static/hidden Iso держат один и тот же canonical d, paper/floor/Glow/sun barriers непустые, dark-theme toggle и HA state tick не меняют path и переиспользуют кеш (_wallUnionCache, fingerprint), рендер не пишет _serverCfg
Golden (доп. проверка, не входит в обязательный гейт) node demo/golden/run.mjs --mode=verify (полный локальный прогон, уже был начат случайно и доведён до конца) 0 failed; все существующие wall-junction сцены (wall-junctions-plan-preview-light, wall-junctions-plan-t-dark, wall-junctions-view-dark, isometric-wall-junctions-dark) — passed, т.е. численная стабилизация не сдвинула ни один уже принятый эталон; обе новые сцены junction-patch-resilience-* — ожидаемо missing-baseline (AC9 требует принятия только через golden:accept -- --reviewed на полном Linux CI артефакте, что вне этого цикла); одна несвязанная сцена decor-color-popover-mobile-ru — different, и 6 других missing-baseline — все они про #180/opening-placement, не задеты этим диффом (диффом не создаются и не меняются)

Не прогонялось и почему:

  • npm run golden:capture/принятие baseline — вне цикла ревью по процессу (§8 PROCESS.md, AC9 ТЗ): baseline принимается только golden:accept -- --reviewed на полном Linux CI артефакте, локальное принятие запрещено. golden:verify был прогнан целиком (см. выше) и дал полезный негативный результат — регрессий нет; этого достаточно.
  • Полный набор demo/smoke_*.mjs (127 сценариев) — diff касается одной чистой geometry-функции без renderer-specific веток; ограничился targeted smoke, явно названным в AC7/АС8, плюс уже пройденным golden verify как независимым подтверждением на уровне рендера.
  • python -m pytest tests_backend -q — backend не тронут (0 файлов custom_components/**/*.py в диффе).
  • Performance-профили — ТЗ §13 явно оставляет fixture #197 вне отдельного performance-бюджета («добавляется в performance только если измерение покажет отдельный значимый профиль»); изменение — один дополнительный try/catch и линейное квантование вершин на патч, асимптотика не меняется.

Разбор критериев приёмки

  • AC1 (unit). test/wall-thickness.test.mjs:794-856 строит полный fixture ровно по reproducer §3 ТЗ (только rooms/open_spans × NORM_W, walls[].a/b без предварительного масштабирования), проверяет [8, 25, 3], наличие 20-см узла и вызывает production-сигнатуру wallBodiesGeometry(...). На исходном dev эта же комбинация даёт null (см. мутационную проверку выше); после исправления — непустые geom/paperGeom с площадями 124991.31944444453 и 727303.8194444444, что совпадает с числами из хендофф-комментария автора. Доказано автотестом, тест умеет падать. AC1 выполнен.
  • AC2 (unit). Строки 818-843: virtualJunctionPatches() даёт ровно один патч с побайтно теми же четырьмя вершинами, что в §3 ТЗ; stableJunctionPatch на исходных вершинах и на предварительно нормированных (/NORM_W) даёт согласованные форкнутые координаты (stable[1][0] === stable[2][0], stable[2][1] === stable[3][1]) и bounds совпадают с исходным патчем с точностью 1e-9. AC2 выполнен.
  • AC3 (unit). Отдельный узкий тест junction patch union isolates one failure and continues with later patches (строки 881-902) инжектирует контролируемый throw через unionFn-параметр unionJunctionPatches: первый патч падает, второй получает именно последнее валидное body (calls[1].body === 'initial-body'), финальный результат — 'body-with-second-patch'; отдельно проверено, что нефинитные/нулевые по площади патчи не доходят до boolean engine (invalidCalls === 0). Основной структурный throw по-прежнему даёт null — не покрыто отдельным новым unit-тестом, но подтверждено чтением кода: unionJunctionPatches(...) вызывается внутри неизменного внешнего try { … } catch { return null; } (src/wall-thickness.ts:1771 и :1844-1846), который до этого изменения уже оборачивал весь structural pass и продолжает делать это после патча; существующие 900 тестов, включая множество null-return сценариев для #123/#141/#150/#172, продолжают проходить без регрессии этого поведения. Зафиксировано как «проверено чтением, не исполнением» в части общего fail-dark; изолирующая часть доказана исполняемой мутацией. AC3 выполнен.
  • AC4 (unit). Строки 806, 853-856: before/после сравнение JSON.stringify({ rooms, walls, cuts, openings, extraBodies }) до и после вызова — побайтно равны; исходные 25 wall records не редактируются и не сливаются перед вызовом. AC4 выполнен.
  • AC5 (unit). Строки 858-878: [...rooms].reverse()/[...walls].reverse() и вариант с разворотом каждого сегмента (a↔b) дают ту же geometry (geometryDifferenceArea в обе стороны < 1e-7); повторный вызов с теми же аргументами идемпотентен. AC5 выполнен.
  • AC6 (unit). npm test зелёный на всех 900 тестах, включая существующие матрицы corner Split (#123), junction/thickness (#141), transition (#150), zero-divider (#172), openings, open spans, independent extras — без единой регрессии. AC6 выполнен.
  • AC7/AC8 (smoke). node demo/smoke_junction_patch_resilience.mjs — зелёный, разобран выше по каждой проверке: Plan/View/kiosk/static/hidden Iso держат идентичный masonry path; paper, clean-floor hover, Glow/sun barriers непустые; dark-theme toggle и HA state tick не пересчитывают topology (кеш и fingerprint переиспользуются) и не пишут config. AC7/AC8 выполнены.
  • AC9 (golden). Кандидатные сцены junction-patch-resilience-plan-dark и junction-patch-resilience-view-dark добавлены в матрицу (dark theme, Plan и View), test/golden-matrix.test.mjs проверяет их состав ([8, 25, 3]) и версию матрицы (GOLDEN_MATRIX_VERSION = 30). Baseline не принят локально (missing-baseline в прогоне golden:verify — ожидаемо и корректно, ТЗ прямо требует принятия только через reviewed Linux CI артефакт). AC9 выполнен на уровне доступном код-ревью; фактическое принятие эталона — вне этого цикла.
  • AC10 (ревью кода). Diff ограничен src/wall-thickness.ts; ни renderer-specific обхода, ни grid-snapping физических offset-вершин (квантование идёт по coordScale × 10⁻¹², не по шагу сетки), ни schema/backend/i18n правок, ни правок #198/#199 не найдено. Локальный catch окружает исключительно один union(current, piece) внутри unionJunctionPatches, а не остальной structural pass — основной отказ по-прежнему долетает до внешнего catch. AC10 выполнен.
  • AC11 (гейты). Таблица выше: typecheck/unit/build зелёные, три копии бандла байтово идентичны, targeted smoke зелёный до передачи на ревью. AC11 выполнен.

Находки

Находок нет. High: 0, Medium: 0, Low: 0.

Рассмотренные и отклонённые как не-находки:

  • Экспорт stableJunctionPatch/unionJunctionPatches/virtualJunctionPatches как публичных функций модуля — это test seam в том же стиле, что и десятки других уже экспортированных pure-geometry helpers этого файла (см. список импорта в начале test/wall-thickness.test.mjs), а не новый runtime diagnostic API; ТЗ §17.6 явно допускает такой путь инъекции отказа.
  • Квантование применяется ко всем вычисляемым junction-патчам, а не только к падающему — в спецификации (§7.1.2-4) это заложено как общее правило, и golden:verify подтверждает исполнением, что это не сдвинуло ни один уже принятый junction-эталон (wall-junctions-*, isometric -wall-junctions-dark — все passed).
  • Продуктовый контракт (AC10, §4/§7 ТЗ со ссылкой на docs/specs/141-wall- junctions.md) соответствует итоговой реализации: изоляция ровно на границе optional patch, а не на границе всего structural pass.

Документация и трейлеры

Коммит db57180 несёт Issue: #197 и User-Visible: yes; changelog правки (docs/CHANGELOG.md, docs/CHANGELOG.ru.md) присутствуют в том же коммите, формулировки на двух языках согласованы и ссылаются на #197. Обновлены docs/WALL-THICKNESS.md (§3, §8 — per-piece failure и numeric stabilisation), docs/ARCHITECTURE.md (граница optional patch vs. structural failure), docs/USER-GUIDE.ru.md (новая строка диагностики «После оптимизации исчезла вся толстая кладка» в таблице troubleshooting — терминология согласована с остальной таблицей раздела), docs/TESTING.md (новый чеклист-пункт с явной привязкой к unit/smoke/golden) и docs/STATUS.md (текущий локальный цикл). Всё — в том же коммите, что и поведение, как того требует §2.6/§11 PROCESS.md.

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

  • Численная стабилизация патча собирает ровно ту же математику, что описана в ТЗ §7.1 и §17.1: относительный квантум, не завязанный на шаг сетки, отбраковка нефинитных/вырожденных патчей до попытки union.
  • Изоляция патча — честная транзакция: присваивание current = next происходит только после успешного unionFn, при throw current не меняется; проверено и unit-тестом с инжектированным отказом, и чтением.
  • Общий fail-dark контракт (структурный отказ вне патчей всё ещё даёт null) не нарушен — внешний try/catch не тронут.
  • Единый canonical wallBodiesGeometry() остаётся источником истины для всех перечисленных в ТЗ §7.3 потребителей — подтверждено смоуком, не только декларацией в ТЗ.
  • Persisted config не переписывается и не мутируется (immutability-проверка в unit и renderNeverWritesConfig в smoke).
  • Три бандл-копии синхронны, SHA совпадает с заявленным в хендоффе.
  • Golden-регрессий на существующих сценах нет (полный golden:verify, выполненный сверх обязательного минимума, подтвердил это исполнением).

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

  • Полный demo/smoke_*.mjs (127 сценариев за пределами smoke_junction_patch_resilience.mjs) — не запускал; diff ограничен одной чистой geometry-функцией без затронутых renderer-веток, targeted smoke плюс выполненный (сверх обязательного) полный golden:verify дают соразмерное покрытие.
  • Принятие golden baseline (golden:capture/golden:accept -- --reviewed) — вне этого цикла по процессу; baseline принимается только на полном Linux CI артефакте.
  • python -m pytest tests_backend — backend не тронут диффом, прогон не требуется.
  • Performance-профили (performance_smoke, Full Performance) — не названы в AC, ТЗ §13 явно не требует отдельного бюджета для этого fixture; не запускал.
  • Полный npm run inventory (актуальные счётчики тестов для документации) — не запускал; в этом обзоре количество тестов приведено по фактическому выводу npm test (900/900), а не скопировано из чужого документа.