Files
houseplan-card/docs/reviews/CODE-REVIEW-233-r2.md
T
claude[bot] 66ffd6fdda
Validate / docs (push) Failing after 23s
Validate / provenance (push) Successful in 1m15s
Validate / process-gate (push) Failing after 1m14s
Validate / changes (push) Successful in 1m0s
Validate / hacs (push) Skipped
Validate / hassfest (push) Skipped
Validate / reuse (push) Successful in 47s
Validate / backend (push) Skipped
Validate / frontend (push) Successful in 8m16s
Validate / golden (push) Failing after 13m4s
Validate / performance_smoke (push) Failing after 13m7s
Validate / smoke (push) Failing after 34m13s
docs: review document for #233
Issue: #233
User-Visible: no
2026-08-22 07:22:40 +00:00

15 KiB
Raw Blame History

CODE-REVIEW — issue #233, заход r2

  • Issue: #233 — «Резайз показывает внутренние размеры, а не осевые»
  • ТЗ: docs/specs/233-resize-inner-dimensions.md, принят зелёным на r2 (docs/reviews/SPEC-REVIEW-233-r2.md)
  • Предыдущий раунд кода: r1, вердикт жёлтый, документ docs/reviews/CODE-REVIEW-233-r1.md (коммит bff47f5), проверенный коммит abfaae3 — feat: measure resize labels between wall faces
  • Заход: r2 (код), блокирующих циклов израсходовано 1 из 4 (потрачено на r1; зелёный этого раунда цикл не тратит)

Скоуп ревью

Это возврат по находкам M1/M2 предыдущего раунда, не новый заход задачи. Дельта локальна: git diff abfaae3..HEAD (= коммит 81f3868) трогает ровно три файла — test/wall-thickness.test.mjs, scripts/mutation-gate.mjs и сам документ r1-ревью. Продуктовый код (src/wall-thickness.ts, src/houseplan-card.ts) не тронут — это подтверждено не только диффом, но и байт-в-байт совпадением бандла (см. «Как проверялось», п.2). Никаких признаков «ребейз на ушедший вперёд dev», смены контракта поведения или новой подсистемы нет — origin/dev...HEAD идентичен тому, что видел r1, плюс этот один коммит. Поэтому разбор сокращён до дельты и наследования, а не полного прогона AC1–AC9.

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

Находка r1 Чем закрыта Где это видно
M1 — тест AC6b (test/wall-thickness.test.mjs:1533-1552 на abfaae3) не создавал настоящий сплит-разрыв: setWallThickness писал толщину на весь осевой отрезок ребра, поэтому thicknessCmAt по целому ребру совпадал с профилем, и тест не отличал бы исправленный код от регресса к H2 Тест переписан на реальный сплит: setWallThickness([], [0,0], [0.6,0], 20, pitch) при ребре комнаты [0,0]–[1,0] test/wall-thickness.test.mjs:1533-1571 (коммит 81f3868). Явные assert'ы: thicknessCmAt(split, [0,0], [1,0], pitch) === 0 (это и есть H2), thicknessCmAt(split, [0,0], [0.6,0], pitch) === 20, ownEdgeOffsets(...)[0] === halfDepth (не 0), плюс assert.notEqual(viaProfile[0], thicknessCmAt(...)) — тест теперь явно фиксирует расхождение источников, а не совпадение
M2 — scripts/mutation-gate.mjs (диф abfaae3) содержал три мутанта из четырёх, названных в принятом §11 ТЗ; inner-span-ignores-neighbour-thickness (страж AC2) отсутствовал Добавлена запись мутанта, патчащая cutAt в innerEdgeSpan (const o = Math.max(0, Number(offsets[edge]) || 0); → const o = own;), привязанная к тесту innerEdgeSpan measures between wall faces scripts/mutation-gate.mjs:111-125 (коммит 81f3868). Независимо прогнан: node scripts/mutation-gate.mjs --id=inner-span-ignores-neighbour-thickness → «поймано 1 из 1»

Обе находки были про доказательную базу, не про поведение продукта (r1 это установил независимым исполнением сценария сплит-ребра вне репозитория) — и продуктовый код в этом раунде действительно не менялся, значит поведение, подтверждённое в r1, не могло сломаться этой правкой.

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

  1. Построчно сверен диф 81f3868 с рецептами правок из документа r1 (см. таблицу выше) — обе правки текстуально соответствуют тому, что r1 просил исправить, а не что-то похожее.
  2. Проверено, что продуктовый код не менялся: git diff abfaae3..HEAD -- src/ пуст; после npm run build все три копии бандла дают тот же md5, что заявляли и автор, и r1-ревью — 5ffd65073d92c4845aecb1a4145f0948 (dist/houseplan-card.js, demo/srv/assets/houseplan-card.js, custom_components/houseplan/frontend/houseplan-card.js). Это делает независимую повторную проверку поведения продукта (AC1–AC9) избыточной — она физически не могла измениться.
  3. Гейты прогнаны заново на дереве 81f3868 (не унаследованы из r1, так как код тестов/гейта изменился):
    • npx tsc --noEmit — чисто;
    • npx tsc -p tsconfig.test.json — чисто (это конфиг, которым фактически компилируются тесты и мутационный гейд);
    • npm test — 1026/1026, включая переписанный тест M1;
    • node scripts/mutation-gate.mjs --check — 88/88 ok, exit 0 (было 87 на r1, теперь на один больше — учтён новый мутант M2);
    • адресный прогон всех четырёх мутантов задачи по одному, чтобы не доверять агрегату:
      • --id=inner-span-ignores-neighbour-thickness → поймано 1 из 1 (новый, M2);
      • --id=inner-span-reads-whole-edge-thickness → поймано 1 из 1 (переписанный тест M1 всё ещё ловит его, но теперь по правильной причине — падает конкретно на сплит-ребре, а не потому что путь через профиль не исполнился);
      • --id=inner-span-shortens-a-passage → поймано 1 из 1;
      • --id=resize-labels-show-centreline → поймано 1 из 1.
  4. Прочитан патч M2 в контексте реального src/wall-thickness.ts:898 (cutAt, внутри innerEdgeSpan) — строка find мутанта текстуально совпадает с текущим кодом, мутация подставляет собственную толщину own вместо толщины соседнего ребра offsets[edge], что и есть штраф AC2 («сокращение считается только по своей толщине, соседи игнорируются»). Патч не «на что-то похожее», а именно на тот код, который правка защищает.
  5. Проверено, что дифф 81f3868 не трогает docs/CHANGELOG*.md и docs/USER-GUIDE*.md — ожидаемо, коммит несёт Issue: #233, User-Visible: no (тестовый коммит класса B), changelog не требуется.

Находки

Нет новых находок. Обе находки r1 закрыты доказательно (см. таблицу выше и «Как проверялось», пп. 1, 3, 4), High: 0, Medium: 0.

Отдельно отмечаю: в предпоследнем комментарии автор сообщил про красный job docs в validate.yml (несвежий sourceFingerprint в docs/images/screenshots.json из-за слияния #234 без пересъёмки) и завёл под это отдельный issue. Это не находка этого ревью — дефект не в ветке #233, не в скоупе задачи, и уже обслуживается отдельно самим автором, значит нового issue от ревьюера не требуется (правило дублирования наблюдений, §12: не заводить второй issue на то же наблюдение).

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

  • M1, M2 — закрыты, доказано исполнением, не только чтением коммит-сообщения (см. «Как проверялось»).
  • Продуктовое поведение (AC1–AC9) — не могло измениться этой правкой: бандл байт-в-байт идентичен версии, которую r1 уже проверил чтением, исполнением гейтов и независимым ручным сценарием сплит-толщины (H2).
  • Трейлеры 81f3868: Issue: #233, User-Visible: no — корректны для тест-only правки; changelog не трогается, как и должно быть при User-Visible: no.
  • Формальный контракт §11 ТЗ (четыре мутационных стража) — теперь выполнен полностью: все четыре присутствуют в scripts/mutation-gate.mjs и каждый independently подтверждён «1 из 1».

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

Без повторной проверки в этом раунде принято из docs/reviews/CODE-REVIEW-233-r1.md (документ на коммите bff47f5, проверенный коммит продукта abfaae3):

  • AC1–AC5 — численно сверенные длины (285/385 см на 300×400 при стенах 15 см), диагональные рёбра, случай «стены толще комнаты» → 0.
  • AC6a (H1, правило нуля для проёмов) — if (!(own > 0)) return centre; идёт первым в innerEdgeSpan, перепроверено математически на кейсе H1.
  • AC6b (H2, источник толщин) — механизм ownEdgeOffsets → roomWallProfile → atomicPolyForRoom подтверждён независимым исполнением сценария сплит-ребра вне репозитория (не тем тестом, который чинит M1, — отдельным ручным прогоном r1). В этом раунде довеском стало то, что теперь и сам юнит-тест воспроизводит этот же сценарий (закрытие M1 выше).
  • AC7 — три подписи _rszEdgeLabels и габарит _rszScaleLabels внутренние; подтверждено прогоном demo/smoke_resize_inner_dimensions.mjs (OK) на дереве abfaae3. Не перепрогонялось в r2, так как бандл (см. п.2 выше) идентичен — прогон на неизменившемся артефакте не может дать другой результат.
  • AC8 — площадь не менялась (innerContourForRoom + floorMinusBodies), подтверждено тем же смоком (dragAreaStillInner, frameAreaAgreesWithSize).
  • AC9 — трейлеры и changelog для продуктового коммита abfaae3 (Issue: #233, User-Visible: yes, docs/CHANGELOG.md/.ru.md и docs/USER-GUIDE*.md правлены в нём же).
  • i18n — диф abfaae3 не трогает src/i18n/*.json.
  • Golden — вне риска по построению (_rszLive живёт только на время жеста ресайза, обнуляется на pointerup); проверено r1 чтением, не исполнением golden:verify.
  • Регрессия толщины стен — smoke_draw_wall_thickness.mjs, smoke_wall_thickness_transition.mjs зелёные на дереве abfaae3.
  • Touch — диф не трогает pointer-пути, docs/TOUCH-SUPPORT.md не затронут.

Все эти пункты наследуются с уверенностью, потому что предмет наследования — поведение бандла, а бандл в r2 не пересобран из другого исходника: он тот же файл, что и на момент r1-вывода (совпадение md5 подтверждено заново в этом раунде, не взято на веру).

Чего не проверял и почему

  • demo/smoke_resize_inner_dimensions.mjs и регрессионные смоки повторно — не перепрогонял: продуктовый код не менялся (бандл идентичен), результат прогона на неизменившемся артефакте не может отличаться от того, что уже зафиксировал r1. Перепрогон был бы тратой минут без шанса найти что-то новое.
  • npm run golden:verify — не гонял, по той же причине плюс аргумент r1 (оверлей _rszLive вне активного жеста не рендерится, статический golden его не видит); дифф этого раунда вообще не в src/**.
  • python -m pytest tests_backend — не гонял, диф не трогает custom_components/**/*.py.
  • performance-профили — не гонял, не названы в AC, диф не в горячих путях рендера и вообще не в продуктовом коде.
  • demo/smoke_*.mjs вне названных в AC/§12 — не прогонял весь набор из 127: дифф ограничен тестовым файлом и мутационным гейтом, у которых нет браузерной поверхности вовсе.

Вердикт

Обе находки r1 (M1, M2) закрыты именно так, как требовал документ r1: тест теперь строит настоящий сплит-разрыв и различает исправленный код от регресса к H2, мутационный гейт получил четвёртый страж контракта §11 и независимо подтверждён «1 из 1» по каждому из четырёх мутантов задачи. Продуктовый код не менялся — бандл байт-в-байт идентичен версии, которую r1 уже проверил чтением, гейтами и ручным сценарием, так что AC1–AC9 наследуются без риска регрессии из этой правки. Новых находок нет.

Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0