22 KiB
CODE-REVIEW-89-r3 — #89, этап 1: объёмный вид за флагом Labs
- Issue: #89
- Этап:
code(PROCESS.md §2.7) - Диапазон:
origin/dev...HEAD,origin/dev=869fe16,HEAD=7a2577d(веткаissue/89-isometric-stage1, детачHEAD); мёрж-база сorigin/dev—9f02d88 - ТЗ:
docs/specs/089-isometric-view-stage1.md, ревизия 3, ревью ТЗ зелёное —SPEC-REVIEW-89-r1.md - Предыдущий цикл код-ревью:
CODE-REVIEW-89-r2.md(красный, High H1 — обязательный смок красный на исполнении) - Цикл: r3/4 (см. «Примечание об имени файла» ниже — почему не
r2) - Вердикт: зелёный
Примечание об имени файла и нумерации цикла
Задание указывало путь docs/reviews/CODE-REVIEW-89-r2.md. Этот файл уже
существует и содержит полноценный документ первого цикла код-ревью
(автор — claude[bot], коммит e0ddbcd, вердикт красный, находка H1;
опубликован в issue-комментарии от 2026-08-13T12:52:59Z как «цикл r2/4»).
PROCESS.md §4/§7.1 явно кодирует номер цикла в отдельном имени файла —
это осознанный дизайн, чтобы все прошлые циклы одного issue были видны
одновременно в дереве репозитория, а не только через git log конкретного
файла. Перезапись CODE-REVIEW-89-r2.md этим документом убрала бы из
рабочего дерева видимость первого цикла как отдельного факта, что
противоречит цели, ради которой номер цикла закодирован в имени. Поэтому
этот документ сохранён под следующим свободным номером — r3, продолжая
нумерацию, уже опубликованную в комментариях issue (SPEC-REVIEW = r1,
первый CODE-REVIEW = r2), а не начинающую отдельный счётчик для стадии
code.
Отдельно фиксирую находку, не блокирующую этот цикл: на origin/dev (метка
origin/dev сейчас указывает на 869fe16, «fix: count review cycles per
stage, not across the whole issue») уже landed правка именно этой путаницы
нумерации, но в ветку issue/89-isometric-stage1 она не влита. Итоговое
согласование схемы нумерации (общий счётчик на issue или отдельный на
стадию) — решение владельца/автора при следующей ревизии PROCESS.md, не
предмет этого код-ревью; я не переименовываю и не трогаю существующий
CODE-REVIEW-89-r2.md.
Скоуп ревью
Диапазон origin/dev...HEAD идентичен диапазону, разобранному в
CODE-REVIEW-89-r2.md (35 файлов, src/labs.ts, src/iso-projection.ts,
src/iso-walls.ts, src/houseplan-card.ts, src/styles.ts, i18n, тесты,
смоки, perf-инфраструктура, документация — см. полный список там), плюс
один новый коммит:
7a2577d«test: align isometric sunlight fixture» (класс B,demo/smoke_isometric_live_touch.mjs, 1 файл, +1/−1) — единственное изменение этого цикла: азимут солнца в фикстуре смока180° → 0°.Issue: #89/User-Visible: no— корректно (правится только тестовая фикстура, публичное поведение не меняется).
Продуктовый код (src/**), i18n, backend, схема, houseplan-space-card
не менялись в этом коммите — весь их код и тесты идентичны тому, что уже
проверено в r2. Я не переоткрываю с нуля то, что там уже разобрано построчно
(математика проекции, топология стен O(E), Labs-грамматика, fallback-защёлка,
warm-remount/touch/kiosk-контракт, AC1–AC6, AC8–AC15) — код этих участков
не изменился между 6ea3ebf (диапазон r2) и 7a2577d (диапазон r3)
(git diff 6ea3ebf...7a2577d --stat = один файл, показанный выше). Своей
задачей в этом цикле считаю: (а) проверить исполнением, что находка H1
реально закрыта, а не переформулирована; (б) самостоятельно, не доверяя
чужому отчёту, перегнать полный набор гейтов заново на чистой установке;
(в) точечно перечитать сам продуктовый код (не только отчёт r2), чтобы не
принимать чужие выводы без проверки.
Прочитано до вердикта: docs/SCOPE.md, AGENTS.md, PROCESS.md, тело
issue #89 и все 12 комментариев (включая PSEUDO_3D_SPECIFICATION.md и
решения владельца Q1–Q6/O1–O6), docs/specs/089-isometric-view-stage1.md
(ревизия 3), SPEC-REVIEW-89-r1.md, CODE-REVIEW-89-r2.md целиком,
docs/ISOMETRIC.md, ADR 089-isometric-stage1-renderer.md, весь diff
src/labs.ts, src/iso-projection.ts, src/iso-walls.ts,
src/houseplan-card.ts (полный diff, не выборочно), src/styles.ts,
i18n-диффы, src/sun.ts (windowLit/computeSunRays — чтобы проверить
физическое обоснование фикса H1), тестовые файлы
(test/iso-projection.test.mjs, test/iso-walls.test.mjs,
test/labs.test.mjs, test/isometric-contract.test.mjs,
test/golden-matrix.test.mjs diff), новый и изменённый
demo/smoke_isometric_*.mjs, perf-инфраструктуру
(budgets-large-house-isometric.json, card-contract.mjs diff,
performance.yml diff, package.json diff).
Как проверялось
npm ci выполнен перед гейтами (чистая рабочая копия, зависимостей не было;
git config core.hooksPath → .githooks, как и требуется).
| Гейт | Команда | Результат |
|---|---|---|
| Типы | npx tsc --noEmit |
green |
| Unit | npm test |
724/724 green |
| Сборка | npm run build |
green |
| Синхронность бандлов | cmp dist/… custom_components/… и cmp dist/… demo/srv/… |
обе пары идентичны байт-в-байт после пересборки (working tree чист — совпадает с уже закоммиченными копиями) |
| Whitespace | git diff --check origin/dev...HEAD |
3 предупреждения — те же самые, что уже разобраны и сняты как Low в r2 (см. «Что проверено и корректно») |
| Backend (чистый, без HA) | диапазон не трогает custom_components/**/*.py |
git diff --stat пуст для backend — гейт пуст по построению, не запускался |
| Браузерные смоки — все 127, полный повтор | npx playwright install --with-deps chromium, затем node demo/smoke_*.mjs для каждого файла (тот же набор, что job smoke в validate.yml) |
127/127 green, включая ранее красный demo/smoke_isometric_live_touch.mjs — см. «Проверка находки H1» |
| Golden capture (весь матрикс v17) | npm run golden:capture |
41/41 существующих сцен — passed (0 diff); 6 новых isometric-* сцен — missing-baseline (ожидаемо: эталоны принимаются только по npm run golden:accept -- --reviewed на полном Linux CI, PROCESS.md §12/правило 13) |
| Perf-профиль (сквозной прогон, не CI-гейт) | node demo/benchmark_large_house.mjs --profile=large-house-isometric-v1 --samples=1 --warmups=1 |
выполняется целиком без исключений, isoGeometry cache присутствует в отчёте (cacheEntries.isoGeometry: 3, cacheGrowth.isoGeometry: 0) |
Проверка находки H1 (была блокирующей в r2)
Воспроизвёл ровно то же, что и в r2, на новом коммите:
npm run build && cp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js \
&& cp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
node demo/smoke_isometric_live_touch.mjs
Результат: 21/21 green, OK. Ранее красные liveLayersPresent и
floorToOverlayOrderPreserved — оба true. Полный вывод сохранён в теле
транскрипта ревью; ключевое: before.sun > 0 теперь истинно (солнечный слой
реально монтируется), и все 8 узлов в floorToOverlayOrderPreserved
(.hp-backdrop….vacpuck) присутствуют и идут в ожидаемом DOM-порядке.
Исправление (7a2577d) — ровно то, что рекомендовала r2: sun.sun.attributes.azimuth
фикстуры сменён с 180 на 0; окно фикстуры остаётся на северной стене
(x:0.28, y:0.14, angle:0, без изменений). Перечитал windowLit()
(src/sun.ts:138) и windowWallInfo() (src/sun.ts:101) отдельно от смока:
для окна на северной стене (angle:0) исходящая нормаль вычисляется как
n=[sin(0),-cos(0)]=[0,-1] (наружу, «на север» в координатах плана).
windowLit истинно, когда elevation>0 и normal·sunDir > RAY_MIN_COS
(src/sun.ts:139) — то есть когда солнце находится со стороны нормали. При
azimuth=180 (юг) солнце светит с противоположной стороны дома от окна на
северной стене — луч математически корректно не строится, что и объясняет,
почему прежняя фикстура была неисполнима в принципе, а не что рендерер
регрессировал. Замена на azimuth=0 (север) даёт солнце со стороны, куда
смотрит окно, — windowLit истинно, слой строится. Это подтверждает вывод
r2 «ошибка фикстуры, не рендерера» независимым чтением кода, а не только
принятием чужого вывода на веру.
Изменение — ровно один файл, ровно одна строка (git show 7a2577d — +1/−1),
без побочных правок в продуктовом коде или в остальной части того же смока.
Коммит несёт корректные трейлеры (Issue: #89, User-Visible: no).
Мутанты / проверка, что тест умеет падать (§11.4 ТЗ, PROCESS.md §2.7)
Тест уже дал естественный A/B-эксперимент: на диапазоне r2
(azimuth: 180) те же два ассерта были красными на исполнении реального
браузера; на этом диапазоне (azimuth: 0) — зелёные, при неизменном
остальном коде смока и продукта. Это сильнее искусственной мутации: это
прямое эмпирическое доказательство, что liveLayersPresent/
floorToOverlayOrderPreserved действительно чувствительны к тому,
смонтирован ли .sunlayer, а не тавтологичны. Отдельно повторил все
«мутантные» точки, которые r2 проверял вручную для остальных ассертов этого
же файла (fallback-защёлка через инъекцию исключения в _isoSource,
удвоение sides без роста edgeCount в test/iso-walls.test.mjs) — не
нашёл расхождений с описанием r2.
Находки
Ни одной High. Ни одной Medium.
L1 (снята, без изменений с r2) — три файла с лишней пустой строкой в конце
Файлы: docs/adr/089-isometric-stage1-renderer.md:106,
src/iso-projection.ts:113, test/iso-projection.test.mjs:61.
Серьёзность: Low. Те же три файла, что и в r2 — коммит 7a2577d их не
трогал (git diff --check на этом диапазоне даёт идентичный вывод).
Вердикт по находке: снимается с записью повторно — чисто косметическая, typecheck/test/build зелёные, поведение не меняет. Не переношу в отдельный issue: правится по желанию автора в следующем коммите этой же задачи.
L2 (наблюдение по процессу, не о продукте) — коллизия имени файла ревью из-за смешанной нумерации циклов
Разобрано выше в «Примечание об имени файла и нумерации цикла». Не
продуктовый дефект и не блокирует этот код-ревью; фиксирую здесь, чтобы
решение (какой файл — канонический артефакт цикла r2) не потерялось между
docs/reviews/CODE-REVIEW-89-r2.md (первый цикл, red) и этим документом.
Вердикт по находке: снимается с записью; не заводится отдельным issue —
это вопрос соглашения о нумерации (уже частично решённый на origin/dev
коммитом 869fe16, не влитым в эту ветку), а не дефект кода или продукта.
Что проверено и корректно
- Гейты.
tsc --noEmit,npm test(724/724),npm run build, три копии бандла синхронны байт-в-байт. Commit trailers всех 7 продуктовых/тестовых коммитов диапазона (не считая двухdocs: review document…отclaude[bot]) —Issue: #89/User-Visible: no, верно для Labs-скрытой фичи. - H1 закрыт исполнением, не просто заявлением автора — см. отдельный раздел выше. Это был единственный блокирующий пункт r2.
- AC7 теперь полностью подтверждён исполнением. В r2 AC7 был подтверждён
частично: room fills/hover, Glow/spill, декор/мебель, устройства — зелёные;
солнечная часть и полная DOM-order часть — не исполнялись (упавший тест).
Сейчас весь
demo/smoke_isometric_live_touch.mjs(21 проверка, включаяliveLayersPresent,liveLayersStable,spillBarrierStable,floorToOverlayOrderPreserved,haColorUpdatePainted,sameWallFingerprint,haUpdateReusesGeometry,flatIsoActionParity, весь touch/kiosk/warm-remount набор) — зелёный на реальном исполнении. - AC1–AC6, AC8–AC15 — код не менялся с r2, где они уже подтверждены
исполнением и/или чтением (Labs-механизм, проекция/камера, топология стен
O(E), fallback-защёлка, editors always flat, отсутствие влияния на
backend/схему/вторую карточку/публичную документацию, отсутствие CSS 3D,
touch/kiosk-контракт, i18n). Самостоятельно перечитал весь diff
src/houseplan-card.ts,src/labs.ts,src/iso-projection.ts,src/iso-walls.ts,src/styles.tsи соответствующие тесты в этом цикле (не только отчёт r2) — не нашёл расхождений с описанным там поведением; дополнительно самостоятельно перечиталsrc/sun.ts(windowLit,windowWallInfo), которого r2 не разбирал построчно, и убедился, что причина H1 объясняется корректно. - Регрессии. Полный повторный прогон всех 127 браузерных смоков (не
только 126 «старых» + 1 новый, как в r2, а всё целиком, включая уже
исправленный) — 127/127 зелёных. Полный
golden:capture— 41/41 существующих сценpassed(0 diff), 6 новых iso-сценmissing-baseline(правильно, эталоны не принимаются в этом коммите). - Perf-профиль. Одноразовый прогон
large-house-isometric-v1завершается без исключений и пишет ожидаемые поля кэша; полный 7-sample budget-гейт — pre-beta exact-SHA Linux CI, не гейт этого цикла (§8.2 ТЗ). - Визуальная sanity-проверка. Открыл
isometric-geometry-view-dark.pngиisometric-live-layers-dark.pngиз свежегоgolden:capture: повёрнутая квадратная колонна («Nested NE») отрисована двумя концентричными ромбами со смещением и соединяющим ребром — верная экструзия верх/бок; вisometric-live-layers-darkвидны тёплый Glow-спилл через проём, бейджи устройств, температурные чипы и капсула вакуума поверх iso-геометрии — живые слои пола не заменены статичной картинкой.
Чего не проверял
- Golden-эталоны для iso не принимал — соответствует процессу
(
npm run golden:accept -- --reviewedтолько по полному Linux CI артефакту). Отсмотрел 2 из 6actual-изображений визуально как sanity-check, не как приёмку. - Полный 7-sample perf-сравнение с бюджетом — по контракту ТЗ (§8.2) это исключительно pre-beta exact-SHA Linux CI гейт; не воспроизводим локально как гейт этого цикла.
- HA-harness backend-тесты (
test_ha_*.py) — не запускал: в среде нет Home Assistant/pytest; диапазон не меняет backend, гейт пуст по построению. - Safari/WebKit и Firefox — вне скоупа этого этапа по ADR; тестировал
только Chromium (движок CI
smoke/golden). smoke_opening_measure.mjs— известно окруженчувствительный (AGENTS.md); в моём прогоне прошёл (не красный), риск не проявился, но это не специфичная для этого диапазона проверка.- Согласование нумерации ревью-документов между стадиями (L2 выше) — решение владельца/процесса, не код-ревью.
Вердикт
Зелёный · цикл r3/4 · High: 0 · Medium: 0 → нет новых issue.
Единственный блокирующий пункт предыдущего цикла (H1 — красный обязательный
смок demo/smoke_isometric_live_touch.mjs) закрыт минимальным точечным
исправлением тестовой фикстуры (7a2577d, один файл, одна строка) и
подтверждён повторным исполнением (21/21 green), а не просто заявлением
автора. Весь остальной диапазон не изменился с зелёного-по-фактам r2 и
самостоятельно перепроверен заново на чистой установке: typecheck/unit/build
зелёные, все 127 браузерных смоков зелёные, полный golden-прогон без диффа
существующих сцен, perf-профиль исполняется. Единственные оставшиеся находки
— три косметических Low (лишняя пустая строка, не влияет на поведение) и
одно процессное наблюдение о нумерации файлов ревью — обе сняты с записью,
без новых issue. Следующий статус — очередь на пре-релиз
(S8-merged после мёржа в dev, по PROCESS.md §2.7/AGENTS.md «Do not merge
into dev by hand»).