Files
houseplan-card/docs/reviews/CODE-REVIEW-361-r2.md
T
2026-08-29 07:55:23 +00:00

21 KiB
Raw Blame History

CODE-REVIEW-361-r2

Issue: #361 — «Мебель: физическая толщина линий не масштабируется при zoom» Этап: code · заход r2 · блокирующих циклов израсходовано 1/4 (зелёные вердикты бюджет не тратят, #227) ТЗ: docs/specs/361-furniture-stroke-zoom.md (зелёный в SPEC-REVIEW-361-r2, SHA 31c6e77c)

Раунд r1 код-ревью: жёлтый, SHA 4b273e94 (документ docs/reviews/CODE-REVIEW-361-r1.md, единственная Medium-находка: golden-бейзлайны с реальной мебелью не приняты под новый физический stroke).

Ветка приведена к dev конвейером до этого раунда: поверх легло 3 dev-коммита (8896d610 -> 737d2f66, добавлены c50d9e42, 631bafea, 00197a3a, 23d6681d, cbf9318e, f2e36572, 7c45372c — параллельные задачи #369/#366/#39, не #361). По §7.2 это другой код, и разбор ниже — полный, а не по дельте, несмотря на то что это формально второй код-ревью-цикл.

Скоуп проверки

Диапазон origin/dev...HEAD, 46 файлов. Продуктовый код — тот же, что уже был полностью разобран в r1: src/furniture.ts (+55/-4), src/houseplan-card.ts (+30/-6), коммит 6c608485 (fix: scale furniture strokes with plan zoom, trailers Issue: #361 / User-Visible: yes). Я построчно сверил оба файла с диффом, процитированным в CODE-REVIEW-361-r1.md, — содержимое побайтово идентично тому, что уже прошло полный разбор на 4b273e94; сменился только SHA из-за двух последовательных ребейзов (сначала ручной автором на dev@23d6681d после конфликта в generated bundle, затем конвейерный перед этим ревью). Остальное — тесты, три копии бандла, документация, changelog, docs-скриншоты и golden-бейзлайны.

Продуктовое рассуждение (сверено заново, не по памяти r1): задача закрывает строку J6 docs/SCOPE.md («Keep the plan true as the home evolves» — редактирование/drag/resize существующих элементов плана должно оставаться физически достоверным) и косвенно J4 (создание плана «как физического объекта»). Диагноз (line/rect/ellipse получают физический stroke-width без vector-effect, мебель — тот же stroke-width, но с принудительным non-scaling-stroke, который одновременно гасит и локальный анизотропный transform, и внешний camera zoom) подтверждён чтением кода заново, не переносится как готовый вывод.

Зелёного Validate на 737d2f66 нет — все гейты ниже прогнаны мной локально.

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

  1. Прочитаны заново: весь diff src/furniture.ts/src/houseplan-card.ts, ТЗ, issue и все комментарии обоих SPEC-REVIEW и обоих CODE-REVIEW раундов, docs/SCOPE.md (J4/J6), docs/FURNITURE.md, docs/USER-GUIDE.ru.md.
  2. Построчное сравнение текущего диффа src/furniture.ts/src/houseplan-card.ts против origin/dev с кодом, процитированным в CODE-REVIEW-361-r1.md: furniturePlanScreenScale, furnitureStrokePx, точки вызова в _renderFurniturePlacementPreview и _renderDecorLayer — идентичны знак в знак.
  3. Дешёвые гейты (прогнаны сам, Validate на этом SHA не найден):
    • npx tsc --noEmit — PASS;
    • npm test — 1533 pass, 1 skip, 0 fail (~26.5s, полный набор);
    • npm run build — чисто, git status --short пуст после сборки;
    • npm run bundle:sync — три копии бандла (dist/, custom_components/houseplan/frontend, demo/srv/assets) пересобраны, git status --short после — пуст, дифф с рабочим деревом нулевой;
    • npm run bundle:budget — PASS, initial View 273551 B gzip / budget 282000 B, запас 8449 B (запас чуть меньше, чем в r1 (9152 B), — за счёт трёх посторонних dev- коммитов, легших поверх при ребейзе, не этой задачи);
    • node scripts/check-docs.mjs (diff трогает src/**) — PASS, 7 файлов, 10 внешних ссылок;
    • node scripts/no-new-any.mjs --base origin/dev --head HEAD — PASS, 75 новых строк в 2 файлах, новых any нет.
  4. node scripts/smoke-select.mjs --base origin/dev --head HEAD — та же картина, что в r1: 29 «прямых совпадений» по _baseVb/_viewOr/_stageEl (методы камеры/viewport, не изменены диффом, только читаются в новой ветке рендера мебели) и 1 «слабая связь» (smoke_canvas_frame.mjs ← _baseVb). Решение по каждой строке — как в r1: методы не изменены, ни pan/zoom/backdrop/kiosk/жесты/виртуальный свет к furniture-stroke отношения не имеют — не прогонялись; smoke_canvas_frame.mjs (слабая связь) просмотрен — про рамку вида/aspect, мебели не касается, не прогонялся.
  5. Целевой смок из AC1/AC3/AC4/AC6: node demo/smoke_furniture.mjs → OK, все 90 полей true, включая растровые (furnitureFollowsPhysicalCameraZoom, designerAndPrimitiveMatchOrdinaryDecor, anisotropicResizeKeepsBothAxesEqual, rotatedArtworkKeepsTheSameThickness, viewportResizeRecalculatesTheSharedPhysicalStroke, previewAndCommitShareThePhysicalStroke).
  6. Дисциплина «тест должен уметь падать» — унаследована из r1, не переисполнена: revert- проверка (откат src/furniture.ts/src/houseplan-card.ts к origin/dev, повторный прогон furniture-stroke-contract.test.mjs и demo/smoke_furniture.mjs, восстановление дерева) уже документирована в CODE-REVIEW-361-r1.md на побайтово идентичном коде. Гонять заново дорогой Chromium-revert-цикл ради кода, который я построчно сверил как идентичный, — трата времени без нового знания; см. раздел «Унаследовано из r1».
  7. Golden baseline — находка r1. Проверил закрытие на факт-коде:
    • git diff origin/dev...HEAD -- demo/golden/baselines/baselines-index.json показывает, что изменены ровно 4 записи из ТЗ (furniture-placement-preview-light, furniture-plan-art-dark, decor-over-opaque-hover-light, decor-over-glow-base-dark) плюс sourceFingerprint/acceptedAt/witnesses.count (133→131, floor 10 — запас есть). Остальные 138 хешей не тронуты;
    • после npm run bundle:sync (чтобы demo/srv/assets был собран из факт-кода) прогнал node demo/golden/run.mjs --mode=capture --scenario=<id> для всех 4 сценариев → passed на всех четырёх (актуальный рендер совпадает с новым принятым бейзлайном);
    • контрольный прогон furniture-categories-light (мебель есть только в палитре, decor-слоя с реальной мебелью нет) дал different (diffRatio≈0.00175 при пороге 0.0008); чтобы отличить реальную регрессию от дрейфа среды, откатил src/furniture.ts/src/houseplan-card.ts к origin/dev, пересобрал (bundle:sync) и повторил тот же сценарий — тот же actualSha256, тот же diffRatio, то есть расхождение существует независимо от этого фикса (предсуществующий дрейф рендеринга, не тронутый диффом) — не находка. После проверки восстановил рабочее дерево (cp оригиналов + git reset HEAD, git diff HEAD пуст) и пересобрал бандл;
    • npm run golden:verify целиком (182 сценария) не запускался: инструмент сам запрещает --scenario в режиме verify («golden verify must run the complete matrix»), а diff не трогает ничего вне decor/furniture render — остальные сценарии не имеют мебели в decor-слое и не могут пострадать от этого diff (то же обоснование, что в r1).
  8. npm run invariants не прогонялся: git diff origin/dev...HEAD --stat -- src/ показывает только src/furniture.ts и src/houseplan-card.ts, ни один не пишет и не читает WallSegmentModel/рёбра комнат/layout/marker.space/open_spans — только decor/furniture render (перепроверено чтением обеих новых функций и их точек вызова).
  9. python -m pytest tests_backend не прогонялся: diff не трогает custom_components/**/*.py (только сгенерированные frontend-бандлы внутри custom_components/houseplan/frontend/).
  10. Performance-профили не прогонялись: AC8 не называет конкретный профиль; source-contract тест (test/furniture-stroke-contract.test.mjs) мутационно гарантирует, что furnitureScreenScale резолвится один раз на весь decor-слой, не на предмет.
  11. «Одно число — один источник»: физическая толщина мебели вычисляется один раз (furnitureScreenScale в _renderDecorLayer) и передаётся параметром в _renderFurniturePlacementPreview — единственная точка получения furnitureStrokePx(...) для preview и единственная для saved-пути; второго независимого вычисления той же видимой величины нет (проверено чтением: houseplan-card.ts:8095-8098, 8171, и мутационно — furniture-stroke-contract.test.mjs требует ровно одно вхождение const furnitureScreenScale = furniturePlanScreenScale(). test/single-source-numbers.test.mjs эту величину не называет явно, но входит в зелёный npm test и не задет диффом.

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

Находка r1 (Medium, в скоупе) Чем закрыта Где это видно
Golden-бейзлайны 4 сценариев с реальной мебелью (furniture-plan-art-dark, furniture-placement-preview-light, decor-over-opaque-hover-light, decor-over-glow-base-dark) не обновлены под новый физический stroke; npm run golden:verify упал бы на SHA 4b273e94. Коммит 737d2f66 (test: accept furniture stroke golden baselines, trailers Issue: #361 / User-Visible: no / Baseline-Reviewed: <CI run 33240556186>) принимает ровно эти 4 хеша через npm run golden:accept -- --reviewed из полного Linux CI-артефакта; остальные 138 бейзлайнов не тронуты. git diff origin/dev...HEAD -- demo/golden/baselines/baselines-index.json — 4 изменённых записи; node demo/golden/run.mjs --mode=capture --scenario=<id> → passed на всех четырёх на факт-коде (раздел «Как проверялось», п.7).

Других находок r1 не было (High: 0, Medium: 1 — закрыта выше).

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

Со ссылкой на docs/reviews/CODE-REVIEW-361-r1.md, SHA 4b273e94:

  • Falsifiability-проверка новых тестов и смока (revert src/furniture.ts/ src/houseplan-card.ts к origin/dev, повторный прогон test/furniture-stroke-contract.test.mjs и demo/smoke_furniture.mjs, наблюдение падения ровно на ожидаемых утверждениях, восстановление дерева) — не переисполнена в этом раунде. Основание: я построчно сверил текущий src/furniture.ts/src/houseplan-card.ts с кодом, процитированным в r1, — содержимое идентично; повторный дорогой Chromium-revert-цикл на неизменившемся коде не добавляет знания.
  • Разбор нетипичного iso-случая (_baseVb() → !showBorders/no-borders ветка, где furnitureScreenScale считается от planView, а фактический viewBox decor-слоя иногда берётся из floorView) — переперечитан мной в этом раунде (те же строки houseplan-card.ts:11087-11090, тот же вывод: оба входа конечны и положительны, NaN/пропажа мебели невозможны, численная калибровка iso вне контракта #361) — формально не «унаследовано без проверки», а подтверждено заново, привожу здесь для полноты трассировки.
  • Продуктовый диагноз и AC1…AC9 как таковые (что чинить, что не входит в скоуп) — установлены на этапе SPEC-REVIEW-361-r2 (SHA 31c6e77c, зелёный); в этом раунде я не оспаривал сам контракт, а проверял, что реализация ему соответствует.

Проверено и корректно (по AC)

  • AC1/AC3 (физический zoom, rotate+zoom): furniturePlanScreenScale — точная формула ТЗ min(viewportW/viewBoxW, viewportH/viewBoxH), учитывает letterboxing. Растровый смок подтверждает кратное изменение толщины при переходе zoom1→zoom2 для designer- и primitive- артворка, включая повёрнутый на 30°.
  • AC2 (анизотропный resize): furnitureStrokePx не принимает w/h/viewW/viewH предмета вообще — анизотропии неоткуда взяться; смок подтверждает равенство толщины по осям в пределах допуска.
  • AC4 (preview/commit parity): один расчёт furnitureScreenScale на весь decor-слой, передан параметром в preview — единый источник для обоих render-путей; source-contract тест запрещает preview обходить furnitureStrokePx; смок previewAndCommitShareThePhysicalStroke: true.
  • AC5 (совместимость): полный npm test зелёный (1533/1533, все 56 symbols, erase-hit, data-hp/data-kind/data-symbol не задеты); смок целиком (палитра, drag, resize, rotate, wall magnet, erase) зелёный.
  • AC6 (layout safety): юнит-тесты на нулевые/NaN/Infinity входы всегда возвращают конечное число; смок viewportResizeRecalculatesTheSharedPhysicalStroke подтверждает пересчёт после ресайза viewport.
  • AC7 (поддерживаемые поверхности), кроме golden: рендер общий для View/kiosk/ Background editor, light/dark не входят в расчёт (проверено чтением); golden — закрыт выше отдельным разделом. Один нетиповой iso-случай разобран как явно принятый остаточный риск (раздел «Унаследовано из r1»), не находка.
  • AC8 (perf/bundle): typecheck/test/build/bundle:sync/bundle:budget зелёные; furnitureScreenScale — один вызов на слой, гарантировано мутационным тестом.
  • AC9 (документация): docs/FURNITURE.md больше не приписывает non-scaling-stroke физическую толщину; оба changelog обновлены в том же коммите 6c608485 (User-Visible: yes); docs/USER-GUIDE.ru.md — одно согласованное с разделом предложение.

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

  • Полный npm run golden:verify (182 сценария) — режим verify не допускает фильтр по сценарию, а прогнать все 182 ради diff, трогающего только decor/furniture render, — несоразмерная трата времени; прогнаны 4 названных в находке r1 сценария плюс 2 контрольных (furniture-categories-light, tray-narrow-tool-ru), с явным разбором «различие или дрейф среды» для контроля.
  • Falsifiability revert-check новых тестов/смока — не переисполнена, см. «Унаследовано из r1» с обоснованием.
  • Полная browser-smoke матрица (201 файл) — прогнан целевой smoke_furniture.mjs; связи, названные scripts/smoke-select.mjs, разобраны построчно (раздел «Как проверялось», п.4).
  • npm run invariants — diff не геометрический (обоснование в п.8).
  • python -m pytest tests_backend — diff не трогает custom_components/**/*.py (п.9).
  • Performance-профили/бенчмарки — не названы в AC, риск закрыт чтением и мутационным тестом.
  • Ручное тестирование в браузере человеком — не проводилось; весь цикл проверен автоматикой и чтением кода, как предписано процессом для код-ревью.

Вывод

Код и тесты в этом раунде — тот же, уже полностью разобранный в r1, продукт содержательно не изменился, изменился только SHA из-за ребейза на ушедший вперёд dev. Единственная Medium-находка r1 (непринятые golden-бейзлайны) закрыта точно предписанным в ТЗ способом: 4 названных сценария приняты из полного Linux CI-артефакта, passed подтверждён локальным --mode=capture прогоном на факт-коде, контрольные сценарии показывают только предсуществующий дрейф среды, не связанный с этим диффом. Новых находок нет.

High: 0 · Medium: 0 → в задаче