Files
houseplan-card/docs/reviews/CODE-REVIEW-89-r3.md
T
2026-08-13 13:34:19 +00:00

256 lines
22 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-89-r3 — #89, этап 1: объёмный вид за флагом Labs
- Issue: [#89](https://github.com/Matysh/houseplan-card/issues/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`](../specs/089-isometric-view-stage1.md),
ревизия 3, ревью ТЗ зелёное — [`SPEC-REVIEW-89-r1.md`](SPEC-REVIEW-89-r1.md)
- Предыдущий цикл код-ревью: [`CODE-REVIEW-89-r2.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 из 6 `actual`-изображений визуально как
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»).