Files
houseplan-card/docs/reviews/CODE-REVIEW-649-r2.md
T
claude[bot] 0e8e87303e
Проверка (CI) / Классификация изменённых файлов (push) Successful in 22s
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Failing after 48s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 23s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 24s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 46s
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Failing after 34s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 44s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 8m29s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 19s
docs: review document for #649
Issue: #649
User-Visible: no
2026-09-25 16:51:38 +00:00

248 lines
25 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-649-r2
Issue: [#649](https://github.com/Matysh/houseplan-card/issues/649) · 2.5D, этап 6:
объёмные иконки-плитки с тенью, мягкий свет по солнцу, слой декора без
изменений, режим из experimental в General settings.
Материал: `git log --oneline origin/dev..HEAD` / `git diff origin/dev...HEAD`,
ровно на SHA `106fdc096d5dd7de55d22a88c3a0a2b894b83b49` (рабочая копия уже на
нём; ветку не переключал, `git fetch`/`pull`/`checkout` на другой коммит не
выполнял).
```
106fdc09 fix(2.5D): prove the sun beam is cut by bodies; review r1 lows (#649)
e1f1c7d9 docs: review document for #649
2ed05031 test(mutants): stage3-w12 guard follows the renamed #649 contract test
e5ab4659 feat: 2.5D stage 6 — public setting, raised tiles, soft sun, theme-free walls (#649)
```
164 файла в фиче + 73 файла в делте раунда. Трейлеры на всех трёх рабочих
коммитах корректны: `Issue: #649` везде; `User-Visible: yes` на `e5ab4659`
(оба changelog в том же коммите — `docs/CHANGELOG.md`/`.ru.md`, +12/+12 строк);
`User-Visible: no` на `2ed05031` (гейт мутанта) и на `106fdc09` (делта этого
раунда — см. ниже, почему верно). Заход r2 код-ревью; ТЗ (r2 в теле issue) уже
прошло зелёное SPEC-REVIEW-649-r2.
**SHA материала r1 не резолвится.** `de4521823ba69c390fda4c0534e52e24a63cc66a`
(коммит) и дерево `571541905136a48c1729755f6d7877e5ac6aece4` из блока
«Материал раунда» документа r1 отсутствуют в `git log --all` этого клона (не
shallow — `git rev-parse --is-shallow-repository` → `false`, 3576 коммитов на
`origin/dev`). Это не находка (§2.10: «SHA не резолвится — обычное дело»): у
текущего `e5ab4659` тот же commit message, тот же автор/дата инструмента, тот
же родитель (`origin/dev` tip) и почти идентичная статистика диффа (164 файла,
+5569/−3766 против заявленных в r1 +5570/−3767 — расхождение в одну строку в
обе стороны, не содержательное). Это типичная механика конвейера: ветка
ребейзится на актуальный `origin/dev` перед каждым раундом, что меняет все SHA
ниже точки ребейза без изменения содержимого. Дельта раунда — ровно то, что
показывает `git diff e5ab4659..106fdc09` (коммит `2ed05031` между ними не
трогает продуктовый код, только чинит протухший guard мутанта — унаследован
как есть). Объём разбора: по дельте (§2.10), она полностью локальна — один
новый коммит `106fdc09`, который сам называет себя ответом на r1 («review r1
lows»).
## Скоуп
Без изменений с r1 (см. `docs/reviews/CODE-REVIEW-649-r1.md` → «Скоуп»).
Предмет ЭТОГО раунда — единственный новый коммит `106fdc09`: закрывает
Medium-находку r1 (M1, обрезка 2.5D-луча барьерами не была доказана тестом) и
пять из шести Low (L1, L3, L4, L5, L6; L2 была снята самим r1 без правки кода
как опечатка хендоффа — здесь закрывать нечего).
## Как проверялось (объём по дельте, §2.10)
1. Прочитан документ и вердикт предыдущего раунда:
`docs/reviews/CODE-REVIEW-649-r1.md` — жёлтый, High 0, Medium 1 (M1), Low 6
(L2 снята r1 сразу).
2. Объявлена дельта: `git diff e5ab4659..106fdc09` — 73 файла (в основном
пересобранный бандл), содержательно: `src/iso-sun.ts` не тронут (только
`test/`, `demo/`, `scripts/mutation-registry.mjs`, `src/iso-tiles.ts`,
`src/houseplan-card.ts` комментарий, `docs/DEVELOPMENT.md`,
`docs/ARCHITECTURE.md`, `demo/benchmark_large_house.mjs`). Полный текст
диффа коммита `106fdc09` прочитан построчно.
3. Для M1 — прочитан `src/iso-sun.ts:82-122` (`computeIsoSunBeams`),
подтверждено, что `directionalOccluders(input.occluders, away, travel)`
(строка 112) — ровно то место, которое M1 называл риском (перепутанный
знак направления/длина). Новый юнит
`test/iso-stage6.test.mjs` (тест «AC7 the 2.5D beam is cut…») ставит
колонну на пути луча и проверяет три точки (перед колонной светло, за
колонной темно, рядом с тенью снова светло) плюс площадь тени.
4. **Мутационное доказательство выполнено исполнением, не чтением.** Для всех
трёх новых мутантов (`iso-sun-occluders-toward-sun`,
`iso-sun-occluders-ignored`, `iso-sun-card-drops-occluders`) я вручную
применил патч из `scripts/mutation-registry.mjs`, прогнал именно тот guard,
что записан в реестре, убедился в красном результате, затем откатил файл и
для двух из них (те, что требуют бандл) пересобрал (`npm run bundle:sync`)
и подтвердил `git status` чист — см. таблицу ниже, результаты все реальные
прогоны этой сессии.
5. Для L3 (`src/iso-tiles.ts:109-117`, `isoTileStateCss`) вручную пересчитана
специфичность CSS: новые правила `.dev.theme-dark.${state}` (3 класса) и
`.dev.theme-dark.${state}.iso-floor-light` (4 класса) перебивают старые
`.dev.${state}` (2) / `.dev.${state}.iso-floor-light` (3) по специфичности
независимо от порядка — правки корректны структурно, не только текстуально.
Также перепроверено (как и в r1), что для всех 6 `ISO_STATE_BODIES`
`isoEdgeColor` сегодня возвращает одно и то же значение независимо от
`theme`, потому что ни один не тёмный (`luma·255<70`) и ни один не
«near-white» в 0..1-шкале (`luma>0.98`) — значит фикс сегодня не меняет
видимый результат ни для одного реального состояния, что и объясняет
`User-Visible: no`.
6. Для L1 прочитаны все 4 места, названные в r1 (`docs/DEVELOPMENT.md:231`,
`docs/ARCHITECTURE.md:31`, `src/houseplan-card.ts:627`,
`demo/benchmark_large_house.mjs:145`) — все четыре переписаны, проверено,
что переписанный текст не противоречит соседним абзацам того же файла
(`docs/DEVELOPMENT.md:249-251`, `docs/ARCHITECTURE.md:86-88`,
`src/houseplan-card.ts:~2305`), и что не осталось других вхождений
«experimental/alpha-only/opt-in alpha» применительно именно к 2.5D/Stage 4
(`grep -n alpha` по трём файлам — только легитимные несвязанные
употребления: общий `hp_alpha`/Labs-инфраструктура и цветовая альфа).
7. Для L4/L5/L6 прочитаны новые ассерты `demo/smoke_iso_tiles.mjs` и сверены с
CSS-правилами, на которые они опираются (специфичность
`.dev.theme-dark`/`.oplock`/`.virtual .device-shell-frame`,
`iso-overlays?.devices.get(d.id)` опциональная цепочка для L6) — все три
были задокументированы в r1 как «проверено чтением, корректно, но без
теста»; теперь тест есть и я лично прогнал его (все ассерты `true`).
8. Прогнаны реально (не приняты по Validate) — таблица ниже.
### Гейты — что прогнано и с каким результатом
| Гейт | Прогнан | Результат |
|---|---|---|
| `Validate` на `106fdc09` | нет, зачтён по ссылке (условие задачи) | success, https://github.com/Matysh/houseplan-card/actions/runs/36161723663 |
| `npx tsc -p tsconfig.test.json` + `node scripts/fix-test-build.mjs` | да | 0 ошибок |
| `npm run bundle:sync` (`tsc --noEmit && rollup -c` + bundle-tree sync) | да, дважды (baseline и после отката мутанта) | чисто оба раза, `git status --porcelain` пуст после отката — закоммиченные `dist/**`, `custom_components/.../frontend`, `demo/srv/assets` побайтово совпадают со свежей сборкой на этом SHA |
| `node --test test/iso-stage6.test.mjs` (весь файл, 10 тестов) | да | **OK**, 10/10 `pass` |
| `node --test --test-name-pattern="AC7 the 2.5D beam is cut" test/iso-stage6.test.mjs` на немутированном коде | да | **OK** |
| Мутант `iso-sun-occluders-toward-sun` (`away`→`toSun`) применён к `src/iso-sun.ts`, тот же guard | да | **красный** — `AssertionError: between the window and the body the floor stays lit`, ожидалось `true`, получено `false` |
| Мутант `iso-sun-occluders-ignored` (доп. условие `elevation > 90`) применён, тот же guard | да | **красный** — `AssertionError: behind the body, away from the sun, the floor is in shadow` |
| Мутант `iso-sun-card-drops-occluders` (`occluders: []` в `houseplan-card.ts`) применён, пересобран, `node demo/smoke_iso_sun.mjs` | да | **красный** — `bodyCutsTheBeam: expected true, got false`; после отката и пересборки `git status` снова чист |
| `node demo/smoke_iso_sun.mjs` на немутированном коде | да | **OK**, все 20 проверок `true`, включая новые `bodyCutsTheBeam`, `beamBackWithoutBody` |
| `node demo/smoke_iso_tiles.mjs` на немутированном коде | да | **OK**, все 39 проверок `true`, включая новые `alarmBeatsHover`, `alarmBeatsSelected`, `virtualNoDashedRing`, `noBordersKeepsTiles`, `noBordersKeepsShadows` |
| `node scripts/check-docs.mjs --external --screenshots=warn` (режим обычного пуша, `classify-changes.mjs`/`validate.yml`) | да | **OK**, 7 файлов, 12 внешних ссылок; один `WARN` про устаревший фингерпринт скриншотов — унаследован, предрелизный гейт (#479), делта его не касается |
| `node scripts/smoke-select.mjs --base 2ed05031 --head HEAD` (скоуп ровно делты раунда) | да | `НЕОПРЕДЕЛЁННОСТЬ` — дифф исполняемый, но инструмент не находит символьной связи (делта — 2 файла `src/**`, изменения в основном текстовые/CSS-generation внутри уже покрытых функций); решение — см. ниже |
| `node scripts/smoke-select.mjs --base origin/dev --head HEAD` (полный охват фичи, как в r1) | да | те же 78 прямых совпадений, что в r1 (делта раунда их не меняет) — решение r1 унаследовано без повтора |
| `python -m pytest tests_backend` | нет | делта раунда не трогает ни одного `.py`-файла (`git show 106fdc09 --stat` — 0 совпадений `custom_components/.*\.py`); гейт неприменим к этой делте |
| `npm run golden:verify` | нет | делта `User-Visible: no`, ни одна золотая сцена не задета |
| `npm run invariants` | нет | делта не меняет каноническую геометрию (только презентационные occluders/CSS-эмиссию) |
| Полный `npm test` | нет, принят по Validate на этом SHA | — |
**Решение по `smoke-select --base 2ed05031 --head HEAD` (НЕОПРЕДЕЛЁННОСТЬ).**
Инструмент не связал делту ни с одним смоком символьно (изменения в
`iso-sun.ts` не тронуты вовсе — только новый юнит-тест их покрывает; изменения
в `iso-tiles.ts` — две дополнительные CSS-строки внутри уже существующей
функции). Это не разрешение ничего не прогонять (по инструкции промпта): я сам
выбрал и прогнал `smoke_iso_sun.mjs` и `smoke_iso_tiles.mjs` — ровно те смоки,
что физически упражняют изменённые функции (`computeIsoSunBeams`,
`isoTileStateCss`) через реальный рендер в браузере, и лично воспроизвёл
падение всех трёх новых мутантов на них. Остальные ~275 смоков матрицы не
запускал: делта не трогает ничего вне `iso-sun.ts`/`iso-tiles.ts`/тестов/доков.
## Находки
Нет ни High, ни Medium, ни Low. Делта раунда полностью и по существу
закрывает M1 (доказано исполнением: тест и три мутанта, все три реально
покраснели) и пять Low из шести (L1, L3, L4, L5, L6); L2 была снята ещё в r1.
## Закрытие раунда r1
| Находка | Чем закрыта | Где это видно |
|---|---|---|
| **M1** (Medium) — обрезка 2.5D-луча барьерами (физические тела, Solid-перегородки) нигде не доказана тестом; названный риск — перепутанный знак направления или длина экструзии в `directionalOccluders(input.occluders, away, travel)` | Новый юнит `test/iso-stage6.test.mjs` («AC7 the 2.5D beam is cut…», колонна на пути луча: лит перед ней, тень за ней, лит рядом) + новые ассерты `demo/smoke_iso_sun.mjs` (`bodyCutsTheBeam`, `beamBackWithoutBody`, Solid-перегородка в реальном рендере) + три мутанта реестра (`iso-sun-occluders-toward-sun`, `iso-sun-occluders-ignored`, `iso-sun-card-drops-occluders`). Все три лично применены и подтверждены красными в этой сессии (см. таблицу гейтов) | `test/iso-stage6.test.mjs:151-179`; `demo/smoke_iso_sun.mjs:99-130`; `scripts/mutation-registry.mjs:3526-3546`; код неизменён — `src/iso-sun.ts:111-114` |
| **L1** — устаревшие «alpha/hidden»-формулировки рядом с исправленным текстом | Переписаны все 4 места, проверено на противоречие соседним абзацам | `docs/DEVELOPMENT.md:231-232`, `docs/ARCHITECTURE.md:31`, `src/houseplan-card.ts:627`, `demo/benchmark_large_house.mjs:145` |
| **L2** — опечатка числа в хендоффе (не код) | Была снята уже в r1 без правки; в этом раунде править нечего | — |
| **L3** — `isoTileStateCss()` не различал тему для торца состояний | Добавлены `.dev.theme-dark.${state}` / `...iso-floor-light` со своим вызовом `edge(body,'dark',...)`, специфичность выше старых правил (проверено пересчётом), покрыто новым ассертом юнита | `src/iso-tiles.ts:113-116`; `test/iso-stage6.test.mjs:68-70` |
| **L4** — «Alert > чистый Hover» и «Alert > чистый Selected» не проверялись отдельно | Два новых прогона: реальный `mouse.move` + `alarm` без селекта/фокуса; `sel`+`alarm` без hover/focus | `demo/smoke_iso_tiles.mjs:197-200,207-221` |
| **L5** — нет теста на `.dev.virtual` (Q1: без пунктира) | Новый ассерт `virtualNoDashedRing` на `.device-shell-frame` с классом `.virtual` | `demo/smoke_iso_tiles.mjs:201-203,222` |
| **L6** — плитки/тени без стен (`show_borders:false`) не покрыты тестом | Новые ассерты `noBordersKeepsTiles`/`noBordersKeepsShadows` через реальный `setServerConfig` | `demo/smoke_iso_tiles.mjs:224-238` |
## Унаследовано из r1
Без повторной проверки, документ `docs/reviews/CODE-REVIEW-649-r1.md`,
материал — фактическое содержимое `e5ab4659`/`2ed05031` (идентично по
существу тому, что рецензировалось в r1 под старыми SHA `e59c89df`/`de452182`,
см. обоснование выше — ребейз без изменения содержимого):
- AC1 (Flat неизменен, `test/styles-split.test.mjs`, единственный вызов
`furniturePlanScreenScale`), AC2 (геометрия/торец плитки), AC3 (коллизии
×1.12), AC4 (тень, таблица параметров), AC5 (рамки/приоритет — базовая
часть, за вычетом закрытых сейчас L4-пар), AC6 (forced-colors), AC7 (формула
луча, градиенты, стопы, ворота видимости — за вычетом закрытого сейчас M1),
AC8 (мебель, `stroke-width`), AC9 (тема не красит стены), AC10/AC11
(`settings.volumetric_view`, alpha-вход убран, backend-валидация), AC12
(макеты, side-by-side), Golden/матрица, мутационный реестр (16 старых
мутантов), бюджет бандла — все приняты по документу r1 без повторного
прогона в этом раунде, т.к. делта `106fdc09` их не касается.
- `python -m pytest tests_backend` не прогонялся ни в r1 (нет `pytest` в
песочнице), ни сейчас (делта не трогает Python) — тот же непокрытый гейт,
не расширившийся.
- ~70 «слабых» совпадений `smoke-select --base origin/dev` — решение r1 не
прогонять сохраняется без изменений (делта их не касается).
## Проверено и корректно
- M1 закрыта доказательством через исполнение, а не только чтением: юнит и
смок падают именно там, где ожидается (перед барьером — светло, за ним —
тень, рядом — снова светло; площадь луча уменьшается минимум на 20×50
плановых единиц), и все три мутанта реестра лично воспроизведены красными,
затем код и бандл возвращены в состояние `HEAD` (`git status` чист).
- L3 — фикс специфичностно корректен (новые селекторы объективно перебивают
старые) и сегодня не меняет видимый результат ни для одного из 6
`ISO_STATE_BODIES` (пересчитано вручную по порогам `luma<70`/`luma>0.98`),
что подтверждает трейлер `User-Visible: no`.
- L1 — все 4 места переписаны без противоречий с соседним текстом; других
вхождений «2.5D как alpha/hidden/opt-in» в кодовой базе не осталось
(`grep` по изменённым и смежным файлам).
- L4/L5/L6 — только новые тесты поверх неизменного, уже корректного (по
выводу r1) поведения; лично прогнаны, все зелёные.
- Три-way синхронизация бандла (`dist/**`, `custom_components/.../frontend`,
`demo/srv/assets`) чиста на `106fdc09` — подтверждено дважды: один раз как
есть, один раз после применения и отката мутанта `iso-sun-card-drops-occluders`.
- Монолит `src/houseplan-card.ts` — 12880 строк, без изменений (правка L1 в
нём — замена текста комментария в тех же строках, не добавление).
- Трейлеры всех трёх коммитов раунда корректны; `User-Visible: no` на
`106fdc09` обоснован построчно (доп. тесты + одна текстуально верная, но
сегодня недостижимая ветка CSS).
## Чего не проверял
- `python -m pytest tests_backend` — делта не трогает `custom_components/**/*.py`
(0 совпадений в статистике коммита); не гейт этой делты.
- `npm run golden:verify` / `npm run invariants` — делта `User-Visible: no`,
не меняет золотые сцены и не трогает каноническую геометрию модели.
- Полный `npm test` и полный `Validate` (мутанты) — приняты по зелёному
прогону на этом самом SHA (ссылка в задаче); перегонял только те тесты и
мутанты, что относятся к делте раунда.
- ~275 смоков вне `smoke_iso_sun.mjs`/`smoke_iso_tiles.mjs` — `smoke-select`
с базой ровно на делту (`2ed05031..HEAD`) не нашёл символьной связи
(НЕОПРЕДЕЛЁННОСТЬ) ни с одним из них, и содержательно делта не выходит за
пределы `iso-sun.ts`/`iso-tiles.ts`/тестов/документации; полный прогон
матрицы против всей фичи (78 прямых совпадений `origin/dev..HEAD`) унаследован
из r1 без повтора.
- Реальный экран/планшет, Safari — как и в r1, недоступны в песочнице.
## Вывод
High: 0. Medium: 0. Low: 0. Делта раунда (`106fdc09`) полностью закрывает
единственную блокирующую находку r1 (M1) доказательством через исполнение — я
лично применил все три новых мутанта реестра и подтвердил, что юнит и смок
падают именно так, как заявлено в `because`, а не просто прочитал код на
доверие. Пять Low из шести получили тесты или точечные правки без побочных
эффектов (L3 пересчитан на отсутствие видимого изменения сегодня — трейлер
`User-Visible: no` корректен); L2 была снята ещё в r1. Дельта локальна и
изолирована (`src/iso-sun.ts` не изменён вовсе, `src/iso-tiles.ts` — 2 строки,
остальное — тесты/доки/бандл), новых находок не внесла.
Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/649-25d-stage6`, коммит `106fdc096d5d` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `77286175d538a146863d0b2f80a63e9ffa20af96`
```
git log --all --format='%H %T' | grep 77286175d538
```
- Тело issue: `e7d94f185bc9a5d2d7e168933579903e54c88a9819347cb49f0f7c35db60a9e7`
- Вердикт конвейера: `green` · High 0