mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
256 lines
22 KiB
Markdown
256 lines
22 KiB
Markdown
# 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»).
|