22 KiB
CODE-REVIEW-123-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/123
- ТЗ:
docs/specs/123-corner-split-wall.md(зелёное ревьюdocs/reviews/SPEC-REVIEW-123-r1.md) - Диапазон:
git log --oneline origin/dev..HEAD— 4 коммита, релевантный для продукта47c6f10 Fix corner split exterior walls(Issue: #123,User-Visible: yes);git diff origin/dev...HEAD— 20 файлов (src/wall-thickness.ts,src/houseplan-card.ts,src/space-render.ts,test/wall-thickness.test.mjs,test/golden-matrix.test.mjs,demo/golden/matrix.mjs,demo/golden/harness.mjs,demo/smoke_split_corner_wall.mjs, три bundle snapshot, документация) - Роль: ревьюер кода (не исполнитель), этап
S7-code-review - Цикл: r1/4
Скоуп ревью
По каждому AC1–AC13 (docs/specs/123-corner-split-wall.md §10) — доказан ли он
автотестом, который умеет падать, либо чтением кода. Дополнительно: гейты §8
PROCESS.md, трейлеры и changelog §10.1/§2.6, соответствие docs/SCOPE.md (J4/J6,
регрессия внутри уже принятой функциональности — расширения скоупа нет),
терминология docs/USER-GUIDE.ru.md, каноника docs/WALL-THICKNESS.md /
docs/ARCHITECTURE.md.
Как проверялось
Окружение подготовлено npm ci (зависимости отсутствовали). Для нескольких
пунктов ниже дополнительно установлен npx playwright install chromium —
браузерные бинарники тоже отсутствовали, а без них нельзя было исполнить
браузерный смок и самостоятельно проверить AC7/AC8/AC9.
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
зелёный, без вывода |
| Unit | npm test |
752/752 (см. npm run inventory), 0 fail |
| Build | npm run build |
зелёный, dist/houseplan-card.js собран |
| Bundle sync | cmp dist/… custom_components/houseplan/frontend/… и cmp dist/… demo/srv/assets/… |
оба совпадают побайтно; sha256 всех трёх = 182fb55a…483ff, совпадает со значением из хендоффа |
| Process gate | node scripts/process-gate.mjs и node scripts/process-gate.mjs --issues |
гейт пройден, предупреждений 0 в обоих запусках (второй проверил и метку S7-code-review на #123 через gh) |
| Regression-can-fail | новый test/wall-thickness.test.mjs (тесты corner Split …, см. ниже) скопирован в чистый git worktree на origin/dev (SHA 8a3f6ef, т.е. без фикса #123) и прогнан отдельно |
57/63 pass, 6 fail — новые тесты keeps the original exterior wall body and paper, preserves the facade for thin and thick outer/divider matrices, keeps unequal exterior arms…, Split with both endpoints at exterior vertices…, а также существующий paper with walls covers shared centreline… реально падают на добаговом коде. Тест краснеет не тавтологически (см. AC2 в §10 ТЗ) |
| Browser smoke (новый) | node demo/smoke_split_corner_wall.mjs (после сборки и синхронизации demo/srv/assets/houseplan-card.js) |
crash, см. находку High-1 |
| Browser smoke (независимая проверка того же сценария с исправленным вызовом) | автономный скрипт вне репозитория, тот же фикстур/сценарий из demo/smoke_split_corner_wall.mjs, но с исправленным вызовом _lightBarriers(model, polys, physical) вместо _lightBarriers(model) |
все 14 проверок true, включая wall0/15/100KeepsFacade, paper0/15/100KeepsFacade, lightUsesFacade, planViewParity, kioskParity, isoUsesCanonicalBody, staticParity, renderDoesNotRewriteConfig — см. находку High-1 для интерпретации |
| Golden/performance/backend | не запускались | по PROCESS.md §8/§11.4 это пре-релизные гейты, не гейт код-ревью; custom_components/houseplan/**/*.py в диапазоне не менялся, backend вне скоупа |
Находки
High-1 — новый браузерный смок падает на первой же строке и не подтверждает ни один AC
Файл: demo/smoke_split_corner_wall.mjs:87, метод src/houseplan-card.ts:13154-13155
// demo/smoke_split_corner_wall.mjs:87
const lightGeom = c._lightBarriers(c._spaceModel()).masonryGeometry;
// src/houseplan-card.ts:13154-13155
private _lightBarriers(
space: SpaceModel, polys: { r: RoomCfg; poly: number[][] }[], physical: number[][][],
): { … }
_lightBarriers принимает три обязательных параметра; смок передаёт один. Все
корректные вызовы в самом продукте (_renderGlowLayer,
src/houseplan-card.ts:13258-13261) собирают polys/physical перед вызовом.
Воспроизведение: npm run build, синхронизировать
demo/srv/assets/houseplan-card.js, затем node demo/smoke_split_corner_wall.mjs.
Результат — необработанное исключение внутри page.evaluate:
browserType.launch: … (после `npx playwright install chromium`, если бинарник отсутствует)
page.evaluate: TypeError: e is not iterable
at wu._lightBarriers (…/houseplan-card.js:4710:3112)
at eval (…, <anonymous>:82:23)
Поскольку исключение бросается внутри await page.evaluate(...), весь вызов
рушится целиком — return out; в конце скрипта никогда не выполняется. Ни одно
из 14 полей (wall0/15/100KeepsFacade, paper0/15/100KeepsFacade,
dividerChangesInterior, lightUsesFacade, planViewParity, kioskParity,
isoUsesCanonicalBody, staticParity, renderDoesNotRewriteConfig) не
попадает в checkAll/finish — процесс падает необработанным исключением
Node (triggerUncaughtException), минуя даже отчёт FAILED (n).
Это единственный новый браузерный тест, который ТЗ называет доказательством
для AC7 (unit + smoke, Glow/солнце видят тот же фасад) и AC8
(smoke + golden, паритет Plan/View/kiosk/houseplan-space-card/изометрии), а
docs/WALL-THICKNESS.md и оба changelog ссылаются на этот файл как на
подтверждение кросс-поверхностного паритета. В текущем виде файл не
подтверждает вообще ничего — не является «тестом, который умеет падать
содержательно», а падает по причине, не связанной с проверяемым инвариантом.
Важно — это не признак ошибки в продуктовом коде. Я independently
воспроизвёл тот же сценарий (тот же фикстур, тот же путь Split из вершины,
0/15/100 см) отдельным скриптом с исправленным вызовом
_lightBarriers(model, polys, physical), зеркалящим построение polys/
physical из _renderGlowLayer. С исправленным вызовом все 14 проверок,
которые должен был выполнить смок, включая AC7 (свет использует тот же
фасад) и AC8 (Plan/View/kiosk/статичная карточка/изометрия рисуют идентичный
d-путь), возвращают true. Это подтверждает: сам фикс
(src/wall-thickness.ts, src/houseplan-card.ts, src/space-render.ts)
работает корректно; дефект локализован в одной строке доставленного смока.
Почему High, а не Low/Medium. Дефект не является хрупкостью окружения
(в отличие от известного smoke_opening_measure.mjs из AGENTS.md) — это
неверная сигнатура вызова, которая на 100% воспроизводима и рушит весь файл
без остатка, включая проверки, не связанные со светом. Не пропустить в этот
раз дешевле, чем чинить по исключению §11.4 после пре-релизного гейта: доводы
исключения («часть проблем физически не может быть найдена раньше») здесь не
применимы — проблема обнаруживается прямо сейчас, при код-ревью, инструментами,
которые у ревьюера есть.
Что нужно для зелёного вердикта: починить вызов на строке 87 (по образцу
_renderGlowLayer), пересобрать бандл и приложить содержательный (не просто
безошибочный) вывод node demo/smoke_split_corner_wall.mjs — OK со всеми 14
полями true, не только отсутствие исключения.
Что проверено и корректно
- AC1–AC6, AC9, AC10 (
unit): новые тесты вtest/wall-thickness.test.mjs(corner Split keeps the original exterior wall body and paper,…clips every divider thickness when exterior walls are absent,…preserves the facade for thin and thick outer/divider matrices,…keeps unequal exterior arms and is order/id/winding independent,Split from a concave vertex does not turn the child mitre into facade,Split with both endpoints at exterior vertices preserves both corners,…clean floors are exactly the room union minus canonical walls,…rendering does not materialize or mutate saved geometry) буквально покрывают матрицу AC3/AC4 (толщины разделителя 0/1/15/100, наружные 1/15/100, выпуклая/вогнутая вершина, оба endpoint, reverse winding/permutation) и сравнивают не bbox, а boolean-разность геометрии до/после — именно то, что требует §11.1 ТЗ. Существующий регрессионный набор (partial shared wall, virtual-T, nested room, split materialisation и т.д., AC10) остался зелёным:752/752. - Тест умеет падать содержательно, не тавтологически (§18 PROCESS.md):
прогон тех же новых тестов на добаговом
origin/dev(8a3f6ef) даёт6 fail / 57 passс содержательными сообщениями (expected 0, got 7240.3…,wall geometry missing for outer=15, divider=100), а не падение из-за отсутствующего экспорта — тесты действительно проверяют устранённый дефект, а не самосогласованность нового кода. - AC7/AC8/AC9 по существу (не по доставленному смоку, см. High-1):
подтверждено самостоятельным прогоном исправленной копии сценария — общая
каноническая геометрия (
wallBodiesGeometry/wallBodiesUnionPath) действительно используется светом (_lightBarriers), Plan, View, kiosk,houseplan-space-card(статичный рендер черезrenderSpaceStaticвsrc/space-render.ts) и скрытой изометрией (_isoSourceвsrc/houseplan-card.ts:4421-4451строит геометрию тем жеwallBodiesGeometry(...).geom,src/iso-walls.tsне менялся и не завёл вторую модель) — все дают идентичный путь/bbox для фикстуры из issue. - AC11 (ревью кода): кэширование на месте и не задевает hot path. Полный
рендер кэширует пару
{d, paperD}вthis._wallUnionCache, ключ —${space}|${cfgEpoch}|rooms.length}(src/houseplan-card.ts:9660-9679), т.е. HA state tick без структурных изменений не пересчитывает topology. Статичная карточка (src/space-render.ts:38-56) добавляетWeakMap-кэш по тому жеcfg-объекту с ключомcontentFingerprint({rooms, walls, extras, cellCm})— сервер отдаёт тот же неизменяемый объект конфигурации на каждый tick, так что фингерпринт не пересчитывает boolean-топологию заново. Проверено чтением, не исполнением —performance_smoke/large-house benchmark — пре-релизный гейт (см. таблицу выше), а не гейт код-ревью. - AC12: typecheck/test/build зелёные, три bundle snapshot побайтно идентичны (см. таблицу гейтов), sha256 совпадает со значением из хендоффа.
- AC13 и документация:
docs/CHANGELOG.md/docs/CHANGELOG.ru.mdописывают исправление как сохранение фасада при Split, без переименования инструмента или нового 3D-контракта; правки в обоих changelog находятся в том же коммите47c6f10, что и поведение (git show --stat 47c6f10). Терминологияdocs/USER-GUIDE.ru.md(«Split», «общая стена», «фасад») совпадает с таблицей инструментов в этом же документе.docs/WALL-THICKNESS.mdиdocs/ARCHITECTURE.mdописывают именно тот механизм (exteriorEnvelopeGeometry- surviving
outerintervals), который реализован вsrc/wall-thickness.ts:1209-1318— не изобретённая задним числом формулировка.
- surviving
- Трейлеры и процесс:
node scripts/process-gate.mjsиnode scripts/process-gate.mjs --issuesоба зелёные без предупреждений — веткаissue/123-corner-split-wall, трейлерыIssue: #123на всех 4 коммитах,User-Visible: yesтолько на коммите, меняющем поведение, и ровно в нём правки обоих changelog. МеткаS7-code-review— единственная статусная на issue. - Реакция на Low-1 ревью ТЗ: формулировка доказательства AC11 в
docs/specs/123-corner-split-wall.mdизменена с(performance + ревью кода)на(ревью кода)с явной ссылкой на существующийperformance_smoke/benchmark — ровно та правка, которую предложилSPEC-REVIEW-123-r1.md, без нового цикла ревью ТЗ. - Golden: три новых сценария (
split-corner-wall-before-dark,…-thin-dark,…-thick-dark,demo/golden/matrix.mjs) построены на отдельномspace: 'golden-corner-split', не пересекающемся ни с одним существующим id вdemo/fixtures/visual-matrix.mjs;GOLDEN_MATRIX_VERSIONкорректно увеличен (17 → 18);test/golden-matrix.test.mjsпроверяет форму фикстуры (show_borders, число комнат, толщину разделителя по стадии) без исполнения браузера. Эталоны не принимались — консистентно с §11.3/§13 ТЗ, golden — пре-релизный гейт. - Данные и compatibility: формат
RoomCfg/WallEntryне изменён; поиск по диапазону не нашёл новыхconfig key, миграций или изменения schema version — соответствует §8 ТЗ иdocs/CONFIG-COMPATIBILITY.md. - Скоуп: правки укладываются в заявленную поверхность
(
src/wall-thickness.ts,src/houseplan-card.ts,src/space-render.ts, тесты, демо, документация); инструмент «Перегородка», модельrooms/walls/partitions, снаппинг и диалог новой комнаты не тронуты — совпадает с §5 ТЗ (не-скоуп). - Дохлый код, замеченный по пути, но не блокирующий (Low-2, на решение
автора без нового цикла):
paperRoomShapesWithWalls()(src/wall-thickness.ts:1793) остаётся экспортируемой и покрытаtest/wall-thickness.test.mjs, но после этого изменения её больше не вызывает ни_paperShapes(src/houseplan-card.ts), ниrenderSpaceStatic(src/space-render.ts) — оба перешли наwallBodiesUnionPath(...).paperD. Она не создаёт вторую копию геометрии (внутри вызывает тот жеexteriorEnvelopeGeometry()), поэтому архитектурный контракт §7.4 ТЗ не нарушен — это просто более не используемый продуктом публичный экспорт. - Low-3 (на решение автора без нового цикла): отказ от старого
nonzero-fill fallback в
wallBodiesUnionPath(при отказе boolean-операции функция теперь возвращаетnull, то есть стены/бумага не рисуются вовсе, вместо прежнего искажённого, но видимого рендера) — намеренное решение, прямо предписанное §7.7 ТЗ и названное риском в хендоффе автора. Не нашёл отдельного unit-теста, который бы гонял именно этот путь (malformed input → null) — вся текущая матрица тестов проходит через успешные boolean-операции. Не блокирует: поведение явно специфицировано и осознанно, но следующей правкой стоит закрыть тестом.
Чего не проверял
- Golden capture/verify (
npm run golden:verify) иperformance_smoke— осознанно, это пре-релизные гейты поPROCESS.md§8/§11.4, не гейт код-ревью; их будущий провал (если случится) чинится по §11.4 без нового код-ревью. - Backend (
tests_backend) —custom_components/houseplan/**/*.pyне входит в диапазон, backend вне скоупа ТЗ. - Точность самого boolean-алгоритма (
polyclip-ts) на произвольных реально-сложных этажах за пределами матрицы фикстур ТЗ — проверены ровно те конфигурации, что перечислены в §11.1 ТЗ и AC3/AC4; более широкий fuzz по случайным полигонам не проводился и не был частью ТЗ. - Полный автоматический паритет Plan/View/kiosk/static/iso для реального
large-house fixture (
demo/fixtures/large-house.mjs) — проверено только на фикстуре issue (прямоугольник 900×800 с наружными стенами 15 см); эта же фикстура используется во всех новых unit- и golden-сценариях, так что это совпадает с заявленным скоупом ТЗ, а не с недосмотром ревью.
Вердикт
Красный. High: 1, Medium: 0. Единственная блокирующая находка — сломанный
demo/smoke_split_corner_wall.mjs (неверная сигнатура вызова
_lightBarriers), который не подтверждает ни один из AC7/AC8/AC9, хотя ТЗ и
документация ссылаются на него как на доказательство. Сам продуктовый фикс
(src/wall-thickness.ts, src/houseplan-card.ts, src/space-render.ts)
подтверждён: 752/752 unit, новые регрессионные тесты содержательно падают на
добаговом коде, а независимый прогон исправленной копии смок-сценария
подтверждает AC7–AC9 напрямую. Возврат в S6-in-progress нужен только для
починки одной строки смока и приложения его содержательного (не просто
безошибочного) вывода — повторный код-ревью после этого обязателен
(PROCESS.md §2.6: ребейз/правка гейта не освобождает от повторного ревью,
если правка не является чисто «упавший пре-релизный гейт» по §11.4, а этот
дефект найден на этапе код-ревью, не пре-релиза).