Issue: #197 User-Visible: no
20 KiB
Код-ревью 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:
stableJunctionPatch(patch, coordScale)— квантует координаты вычисляемого junction-патча с относительным шагомmax(1, coordScale) × 10⁻¹², отбраковывает нефинитные точки и патчи с площадью не большеquantum².unionJunctionPatches(body, patches, coordScale, unionFn)— транзакционно добавляет каждый стабилизированный патч: локальныйtry/catchвокруг одногоunion, отказ сохраняет последнее валидноеbodyи не прерывает обработку следующих патчей.- Цикл
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), не тронут и по-прежнему оборачивает точку вызова. 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, при throwcurrentне меняется; проверено и 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), а не скопировано из чужого документа.