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

163 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CODE-REVIEW — issue #233, заход r2
- Issue: [#233](https://github.com/Matysh/houseplan-card/issues/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**