docs: review document for #329

Issue: #329
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-27 21:37:28 +00:00
parent 26c6e87079
commit f2c2084dd4
+187
View File
@@ -0,0 +1,187 @@
# CODE-REVIEW-329-r2
Issue: #329 — экстремально острая вершина комнаты рендерится «трезубцем»; ограничения на стыки стен.
Заход: r2. Предыдущий цикл (r1) — вердикт **красный**, `High: 1, Medium: 4`, документ
`docs/reviews/CODE-REVIEW-329-r1.md`, получен на SHA `e1df015d` (коммит «docs: review document for #329»,
после которого автор добавил четыре коммита закрытия находок).
## Скоуп разбора (§2.9)
Дельта r1→r2 — 4 коммита, 12 файлов, +301/-191:
```
0824e510 test: prove AC10 — Optimize adds no junction violation (#329 M4)
574fea98 docs: one limits section, and Resize does raise a toast (#329 M2, M3)
8f0b6b97 refactor: drop the chamfer helpers the honest apex made dead (#329 M1)
0bb42cae fix: the backend judges both sides after the same migration (#329 H1)
```
Файлы: `custom_components/houseplan/junction_limits.py`, `src/wall-thickness.ts`,
`test/junction-limits.test.mjs`, `tests_backend/test_junction_limits.py`,
`scripts/mutation-gate.mjs`, `docs/CONFIG-COMPATIBILITY.md`, `docs/USER-GUIDE.md`,
`docs/USER-GUIDE.ru.md`, `docs/specs/329-junction-limits.md`,
`docs/images/screenshots.json`, `dist/houseplan-card.js`,
`custom_components/houseplan/frontend/houseplan-card.js`.
Каждый коммит — прямое закрытие одной находки r1 (H1, M1, M2+M3, M4), rebase на
ушедший вперёд `dev` не производился (дельта считается от r1-документа, ветка не
двигалась), новая подсистема не задета, поведенческий контракт не меняется — он
чинится до заявленного. Разбор ограничен дельтой плюс всем, до чего она
дотягивается (backend-паритет AC9, AC10, §4/AC6 честная вершина — см. ниже).
П1–П5 как таковые (AC1–AC5), AC7a/AC7b, AC8 дельтой не задеты — унаследованы.
## Как проверялось
Прогнано (SHA `0824e510`):
- `npx tsc --noEmit` — чисто.
- `npm test` — **1389 passed, 1 failed, 1 skipped** (см. находку r2-H1 ниже).
Функциональные тесты этой дельты, включая новые AC10-юниты (`ok 493`, `ok 494`)
и переписанные бэкенд-паритетные фикстуры, — все зелёные; падает отдельный
метатест реестра мутантов.
- `npm run build` + сверка бандлов: `dist/houseplan-card.js` ==
`custom_components/houseplan/frontend/houseplan-card.js` (байт-в-байт), пересборка
с нуля идентична закоммиченному `dist` (diff пуст).
- `node scripts/check-docs.mjs` — «Documentation checks passed (7 files, 10
external links)» — отпечаток `docs/images/screenshots.json` (единственный файл
дельты в `docs/`, пересчитанный после правки `src/wall-thickness.ts`) валиден.
- `python3 -m pytest tests_backend/test_junction_limits.py -q` — **8 passed**
(окружение не несло `pytest` изначально; установлен локально `pip3 install
pytest pytest-asyncio`, что и позволило прогнать гейт, а не просто прочитать код).
Дисциплина «тест умеет падать»: вручную откатил патч мутанта
`junction-limit-backend-raw-baseline` (`migrated, _ = commit_wall_segment_model(config)`
→ `migrated = config`) — **2 из 8 тестов падают**
(`test_legacy_baseline_is_judged_after_the_same_migration`,
`test_a_write_that_adds_a_violation_is_refused_with_a_stable_code`), затем
восстановил файл — тест 8/8 подтверждён на исходном коде.
- `npm run bundle:sync && npm run golden:verify` — вся матрица (~80 сцен, включая
`sharp-apex-legacy-dark` и `junction-owner-repro-dark`) — **зелёная**, 0 отказов.
Нужен был, поскольку M1 правит геометрический файл `wall-thickness.ts`; удаление
мёртвого кода подтверждено эмпирически — ни один кадр не сдвинулся.
- `node scripts/mutation-gate.mjs --check` — реестр анкеров цел (совпадает с
прошедшим юнитом «every mutant patch anchors exactly once», `ok 688`).
- `node scripts/smoke-select.mjs --base e1df015d --head HEAD` — «дифф исполняемый,
но ни один смок не связан доказуемо»; единственный символ дельты в `src/**`,
которого нет в смоках, — `DEGENERATE_APEX_MAX_DEGREES`, но дельта его не меняет
(значение и семантика те же, переставлен только JSDoc-комментарий при удалении
соседних мёртвых функций). Решение: смоки не гоняю — это чистое перемещение
комментария, а не новый контракт; поведенческую часть уже покрывает
`golden:verify` (зелёный целиком).
Не прогонялось: полный HA-harness (`pytest tests_backend` целиком падает при
сборе — `ModuleNotFoundError: homeassistant`, `voluptuous`; тот же пробел был и в
r1, дельта его не устраняет и не обязана). Полная матрица браузерных смоков и
мутантов — дельта не даёт для этого повода (см. вывод `smoke-select.mjs` выше);
она предрелизный гейт, не гейт ревью.
## Находка r2 (High, в скоупе — блокирует)
### r2-H1. Новый мутант ломает `npm test`, то есть красный CI-гейт `validate.yml`
`scripts/mutation-gate.mjs` (коммит `0bb42cae`, закрытие H1) добавляет запись
`junction-limit-backend-raw-baseline` с
```js
guard: 'python3 -m pytest tests_backend/test_junction_limits.py -q',
```
`test/mutation-gate.test.mjs:33` («every guard command points at a file that
exists») требует, чтобы `guard` называл `.mjs`-файл — конвенция, которой следуют
ВСЕ остальные бэкенд-мутанты в этом же реестре (`node
scripts/backend-test-guard.mjs <pattern> [<file>]`, см. строки 1061, 1073, 1099,
1156, 1223, 1541, 1766, 1777, 1788, 1799, 1810, 1856, 3025, 3037, 3049). Новая
запись — сырой `python3 -m pytest ...` без `.mjs`, и `.split(' ').find(part =>
part.endsWith('.mjs'))` возвращает `undefined`.
Воспроизведено исполнением: `npm test` на HEAD (`0824e510`) —
`# fail 1`, тест 689 падает с
`junction-limit-backend-raw-baseline: guard не называет исполняемый файл`.
Проверено дважды (`npm test` целиком и точечно `node --test
test/mutation-gate.test.mjs`) — не флейк. `.github/workflows/validate.yml:282`
гоняет именно `npm test`, значит CI на этой ветке красный по этой же причине.
Функционально мутант работает и семантически осмыслен (я вручную применил его
патч и подтвердил, что он ловит регресс H1 — 2/8 тестов падают, откат правки
восстанавливает 8/8); дефект чисто в форме `guard`, не в сути защиты. Это не
довод в пользу «оставить как есть»: `npm test` — обязательный дешёвый гейт этого
самого ревью, и он красный на проверяемом SHA.
**Нужно:** привести `guard` к конвенции реестра, например
`node scripts/backend-test-guard.mjs test_legacy_baseline_is_judged_after_the_same_migration tests_backend/test_junction_limits.py`
(или обеими новыми тестами через `-k "test_legacy_baseline_is_judged_after_the_same_migration or test_a_write_that_adds_a_violation_is_refused_with_a_stable_code"`,
если нужно ловить оба). Однострочная правка, но `npm test` должен быть зелёным на
SHA, уходящем в `dev`.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где видно |
|---|---|---|
| **H1** (High) — бэкенд судит легаси-базу сырой, отклоняет несвязанную правку | `0bb42cae`: `validate_junction_limits` гоняет и `previous`, и `candidate` через новую `_migrated_spaces()` → `commit_wall_segment_model` до подсчёта | `junction_limits.py` (диф выше); тест `test_legacy_baseline_is_judged_after_the_same_migration` — 8/8 pytest; мутант `junction-limit-backend-raw-baseline` вручную подтверждён (2/8 падают без фикса) |
| **M1** — мёртвый код `apexCaps`/`clipPolygonOutsideCap`/`degenerateApexCaps` | `8f0b6b97`: поле интерфейса и обе функции удалены целиком, JSDoc над `wallBodiesGeometry` возвращён на место | `grep -rn` по всему репо (кроме текста самого r1-документа) — не находит; `golden:verify` вся матрица зелёная — поведение не изменилось |
| **M2** — раздел «Ограничения стыков стен» задвоен в RU-гайде | `574fea98` | `docs/USER-GUIDE.ru.md` — ровно один заголовок `### Ограничения стыков стен` (строка 420) |
| **M3** — гайд (RU+EN) описывает Resize как «просто останавливается», хотя тост есть | `574fea98`: текст переписан — «…и один раз за жест поясняет тостом, какое правило дальше нарушится» | `docs/USER-GUIDE.md`/`.ru.md` (диф выше); код `_rszMove` (`src/houseplan-card.ts:9183-9191`, `resize.limit_stopped` + `_junctionLimitLabel`) не менялся — он и раньше показывал тост, теперь описан верно |
| **M4** — AC10 (Optimize не добавляет нарушений) не доказан | `0824e510`: два юнит-теста в `test/junction-limits.test.mjs`, считающие нарушения тем же способом, что барьер записи (обе стороны через `commitWallSegmentModel`/`commit_wall_segment_model`) | `npm test` — `ok 493`, `ok 494`; первый тест на фикстуре владельца, второй — граничный случай П4 (узлы ровно в 5 см), явно проверяет, что база несёт нарушение (не доказывает пустоту) |
Все пять находок r1 закрыты по существу, с наблюдаемым следом в коде/тестах, а не
только заявлением автора. Ни одна не переоткрыта.
## Унаследовано из r1
Не тронуто дельтой r1→r2, повторно не проверялось — принято по документу
`docs/reviews/CODE-REVIEW-329-r1.md`, SHA `e1df015d`:
- **AC1–AC5** (П1–П5 как юнит-предикаты с границами) — `test/junction-limits.test.mjs`
до строки 216 дельтой не изменён (только дополнен снизу тестами AC10).
- **§4/AC6** (честная вершина легаси-апекса + golden-сцена `sharp-apex-legacy-dark`) —
логика самого предиката (`isDegenerateApexCorner`) не менялась, только удалён
неиспользуемый код вокруг неё; повторно подтверждено попутно этим раундом через
`golden:verify` (зелёный), но отдельного разбора алгоритма не требовалось.
- **AC7a/AC7b** (Resize/«Толщина»: смок через настоящий pointer-жест, байт-
неизменность плана, текст объяснения/тоста) — код и смоки не в дельте; M3 правил
только прозу гайда, не контракт и не тест.
- **AC8** (скоуп применения — какие поверхности проверяются) — не в дельте.
- Риски, откат, release-артефакты, паритет чисел П1–П4 (кроме самого способа
получения базовой линии, который и есть H1) — не в дельте.
## Что проверено и корректно (сверх таблицы выше)
- `docs/CONFIG-COMPATIBILITY.md` — новый абзац про обязательность миграции базовой
линии и явное указание обеих реализаций (`commitWallSegmentModel` /
`commit_wall_segment_model`) фактически точны: обе функции существуют и вызываются
ровно там, где написано.
- `docs/specs/329-junction-limits.md` — ревизия 7 честно фиксирует все пять
закрытий и не вводит новых продуктовых решений задним числом.
- Обработка `except Exception` в `_migrated_spaces()` (бэкенд): падение миграции не
становится вердиктом этого валидатора — по коду до этой точки уже должен был
отработать барьер модели стен, так что это защитный, а не рабочий путь; не
создаёт регресса H1, так как обе стороны получают одинаковую (либо мигрированную,
либо сырую при одинаковом сбое) трактовку независимо друг от друга только в
теории — на практике оба документа уже прошли барьер записи раньше в конвейере.
- Трейлеры всех 4 коммитов — `Issue: #329`, `User-Visible: no`; корректно: правки
чинят баг ещё не выпущенной (Unreleased) фичи этого же issue — запись в
`docs/CHANGELOG.md`/`.ru.md` уже существует с `f514fb27` и описывает финальное,
уже исправленное поведение, отдельной строки для самого фикса не требуется.
- «Одно число — один источник»: дельта не вводит и не меняет ни одной новой
пользователем видимой величины (правки бэкенда, тестов и прозы документации).
## Чего не проверял и почему
- Полный `pytest tests_backend` (не только `test_junction_limits.py`) — падает уже
на сборе (`ModuleNotFoundError: homeassistant`, `voluptuous`), эти пакеты в
окружении не установлены; тот же пробел был в r1, дельта его не касается и не
обязана устранять.
- Полная матрица `demo/smoke_*.mjs` и полный прогон реестра мутантов — не
запускал: `smoke-select.mjs` не нашёл ни одной связи для этой дельты (единственный
изменённый символ в `src/**` — переставленный комментарий), а полный прогон
реестра — предрелизный гейт (PROCESS.md §8), не гейт ревью такого размера.
- `npm run invariants -- --config ...` — дельта не меняет геометрический контракт
(только удаляет мёртвый код и правит бэкенд/тесты/прозу); поведенческая часть уже
закрыта зелёным `golden:verify` по всей матрице.
## Вердикт
**Красный.** Единственная находка — High, блокирует. Она не переоткрывает ни одну
из пяти закрытых находок r1 (все пять закрыты корректно и по существу) и
исправляется одной строкой в `scripts/mutation-gate.mjs`, но `npm test` сейчас
красный на SHA, уходящем в `dev`, а это один из обязательных дешёвых гейтов этого
же процесса.