docs: review document for #577

Issue: #577
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-14 22:54:36 +00:00
parent 59c5f6f6f2
commit 3e191a1c80
+211
View File
@@ -0,0 +1,211 @@
# CODE-REVIEW-577-r2
Issue: #577 «Выбор граней окон для солнечных лучей» · заход **r2** · блокирующих циклов
использовано **1/4** (лимит 4, полный трек; зелёный вердикт бюджет не тратит, §4).
Материал: ветка `issue/577-sun-ray-window-corners`. Раунд r1 код-ревью прошёл на SHA
`6f6a85a11687c4f5cd20e79a2088d05ab6be12df` (жёлтый, Medium в скоупе). Материал
ревью **этого** раунда — ровно `59c5f6f6f259bed13ae39df88a7a163be8c4ec83`, рабочая
копия на этом SHA.
## Дельта: что рассматривает этот раунд
Разбор объёма ограничен дельтой `git diff 6f6a85a1..HEAD`, потому что она
локальна по критерию §2.9: не задевает `src/**`, не меняет ни один продуктовый
файл (`custom_components/**/*.py`, i18n, `types.ts`) и не меняет контракт
поведения — только гейты и артефакты доказательства (класс B/C/D):
```
git diff --stat 6f6a85a1..HEAD -- . ':!docs/reviews'
```
- `demo/golden/matrix.mjs`, `demo/golden/harness.mjs` — новый сценарий
`lighting-sun-window-outer-thick-dark` (толстая наружная стена 15 см,
`sun_ray_origin: outer` через **глобальный** `settings`, не per-space);
- `demo/golden/baselines/**` + `baselines-index.json` — новый эталон плюс
три пересобранных (`safe-resize-handles-clamp-light`,
`settings-help-zoom-200-{en-light,ru-dark}` — оба сдвинулись из-за нового
select в общих настройках);
- `demo/smoke_sun.mjs` — новые проверки на реальной сцене: `sourceX`,
количество полигонов, наличие тоннеля/комнатной части, конечность DOM SVG
координат;
- `demo/smoke_general_settings.mjs` — счётчик строк 15 → 16;
- `scripts/mutation-registry.mjs` — уточнён якорь мутанта
`sun-golden-north-neutralized` (старый anchor стал неоднозначным: новая
сцена тоже содержит `northDeg: 90,`);
- `test/golden-matrix.test.mjs` — структурные проверки новой сцены
(`GOLDEN_MATRIX_VERSION` 61→62, глобальность поля, `cm: 15`, `capture`);
- `docs/TESTING.md`, `docs/images/screenshots.json` — обновление ссылки на
доказательство и отпечатка исходников (не картинок — см. ниже).
- Класс D (`custom_components/houseplan/frontend/**`, `dist/**`) — пересборка
вслед за `bundle:sync`, без изменений источника.
Продуктовый код (`src/**`, `custom_components/houseplan/**/*.py`, i18n) в
дельте **не участвует ни строкой** — подтверждено:
```
git diff --stat 6f6a85a1..HEAD -- src/ custom_components/ 'src/i18n/*.json'
```
→ пусто помимо сгенерированного `custom_components/houseplan/frontend/**`.
Следовательно AC1–AC3, AC5, AC6, AC8, AC9, i18n, config-registry, откат —
доказательства этого раунда дельта не задевает: наследуются из r1 без
повторного прогона (раздел «Унаследовано из r1» ниже). Разбор в этом раунде
сфокусирован на AC4 и AC7 — единственном, что дельта меняет.
## Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| **Medium (в скоупе):** AC4/AC7 объявляют доказательство `golden`, но `demo/golden/matrix.mjs`/`baselines/**` не тронуты вовсе; `smoke_sun.mjs` проверял только диалог, не реальную геометрию толстой наружной стены в `outer`. | 1) Добавлен golden-сценарий `lighting-sun-window-outer-thick-dark` (толстая наружная стена 15 см, тема dark, `sun_ray_origin: outer` через фикстуру `harness.mjs`, а не per-space override — снимает риск скрытой утечки в space-скоуп). 2) `smoke_sun.mjs` получил 6 новых проверок на реальной сцене (`sourceX > 960`, `polyCount ≥ 2`, тоннель нарисован, комнатная часть нарисована, DOM-полигонов ≥ 2, все SVG-координаты конечны) — именно тот executable oracle, которого не хватало. 3) Эталон принят из Linux CI со свидетельством ревью. | `demo/golden/matrix.mjs:856-863` (сценарий), `demo/smoke_sun.mjs:258-331` (assertions), `demo/golden/harness.mjs:687-694` (глобальное применение `sunRayOrigin` в фикстуре), коммит `cb4a5451` (`Baseline-Reviewed: .../runs/34901679608`, `Release: v1.76.0-beta.3`) |
Дельта нового расхождения не внесла: `git diff 6f6a85a1..HEAD -- docs/SUN.md
docs/WALL-THICKNESS.md docs/CONFIG-COMPATIBILITY.md docs/USER-GUIDE.ru.md
docs/CHANGELOG.md docs/CHANGELOG.ru.md` — пусто, тексты r1 не менялись.
## Как проверялось
Дешёвые гейты подтверждены зелёным Validate на этом SHA
(https://github.com/Matysh/houseplan-card/actions/runs/34903422427,
`conclusion: success`, проверено `gh run view --json` — job'ы `typecheck`/
`unit`/`build`/`bundle-sync`, `Мутанты по диффу` 6/6, `Смоки в браузере` 3/3
green; тяжёлые job'ы `golden`, `backend`, `performance_smoke`, `hassfest`,
`hacs`, `geometry_parity` в этом прогоне **skipped** — это push, не heavy-гейт,
см. PROCESS.md, не признак проблемы). Поэтому `npx tsc --noEmit`, `npm test`
(в части, не относящейся к дельте) и `npm run build` повторно не гонялись.
То, что Validate не покрывает и что относится к дельте, прогнано здесь:
| Гейт | Команда | Результат |
|---|---|---|
| Unit — sun (дельта его не трогала, контроль регрессии) | `node --test test/sun.test.mjs` | 40/40 green |
| Unit — golden-matrix (дельта его меняет) | `node --test test/golden-matrix.test.mjs` | 50/50 green, включая новый тест на `lighting-sun-window-outer-thick-dark` |
| Мутант на изменённый якорь | `node scripts/mutation-gate.mjs --check --id=sun-golden-north-neutralized` | `ok` — якорь встречается ровно 1 раз (было бы 2 без правки, конфликт со старым анкором доказан отдельно ниже) |
| Тот же мутант, воспроизведение красного прогона | `node scripts/mutation-gate.mjs --id=sun-golden-north-neutralized` | «заявленный тест покраснел на мутанте» — поймано 1 из 1 |
| check-docs (дельта трогает `demo/golden/*.mjs`, входящие в корпус отпечатка) | `node scripts/check-docs.mjs --screenshots=strict` | green: «Documentation checks passed (7 files, 12 external links)» — отпечаток в `docs/images/screenshots.json` пересчитан правильно, картинки не изменились (только `sourceFingerprint`/`sourceSha256`, `imageSha256` те же) |
| smoke — `smoke_sun` (дельта его меняет, единственный носитель новых assertions) | `npm run build && npm run bundle:sync && node demo/smoke_sun.mjs` | green (список чеков включает новые `originAvailableWhenRaysOff`, `originSavedOuter`, `originRoundTripsToDialog`; новые безымянные `check()` на реальной стене не попали в печатаемый summary, но `finish()` печатает `OK` только при пустом списке провалов — подтверждено чтением `demo/serve.mjs:68-78`) |
| smoke — `smoke_general_settings` (дельта его меняет — счётчик строк) | `node demo/smoke_general_settings.mjs` | green, `rows: 16` подтверждён |
| smoke-select (страховка — не пропущен ли смок, задетый дельтой но не выбранный вручную) | `node scripts/smoke-select.mjs --base 6f6a85a1 --head HEAD` | «Исполняемого frontend-диффа нет (src/**/*.ts не тронут)» — корректно: дельта не в `src/**`, смоки на неё не завязаны через прямое совпадение символов |
| no-new-any (контроль, что дельта действительно пуста в src) | `node scripts/no-new-any.mjs --base 6f6a85a1 --head HEAD` | «Проверено добавленных строк в src/\*\*/\*.ts: 0 в 0 файл(ах)» |
| **golden:verify — полный прогон, все 170 сцен** (AC4/AC7 требуют golden явно, а heavy-гейт в push-Validate пропущен) | `npm run golden:verify` | **170/170 passed, 0 failed/missing**, в т.ч. `lighting-sun-window-outer-thick-dark`, `lighting-sun-window-state-only-dark`, три пересобранных эталона (`safe-resize-handles-clamp-light`, `settings-help-zoom-200-en-light`, `settings-help-zoom-200-ru-dark`) |
| single-source-numbers (страховка; задача не добавляет новых видимых чисел — только enum) | `node --test test/single-source-numbers.test.mjs` | 3/3 green, не нашёл второго источника — ожидаемо, задача не вводит числовых значений |
Разбирался также CI-артефакт кандидата на баланс: run `34901679608`
(на SHA `0d1df78f`, процитирован в трейлере `Baseline-Reviewed` коммита
`cb4a5451`) на первый взгляд читается как красный (`conclusion: cancelled`,
`Golden-кадры против принятых эталонов`: failure, `Смоки… шард 1 из 3`:
failure) — проверено логом:
`gh run view 34901679608 --log-failed` → golden упал единственной строкой
`missing-baseline lighting-sun-window-outer-thick-dark` (ожидаемо: это и есть
прогон-кандидат ДО принятия эталона, отсюда взяты кадры для визуальной
приёмки), а `smoke_general_settings` упал на старом `rows: 15` — это тот же
дефект, который следующий коммит `59c5f6f6` устранил. Расхождение не
находка, а подтверждение, что коммит-цепочка честная: кандидат сначала
показал недостающий эталон и рассинхронизированный счётчик, затем оба были
исправлены отдельными коммитами.
## Находки
Нет. High: 0. Medium: 0. Low: 0.
## Что проверено и корректно (эта дельта)
- Golden-сценарий `lighting-sun-window-outer-thick-dark` задаёт
`sun_ray_origin: 'outer'` через `fixture.config.settings` в
`harness.mjs:687-694`, а не через `space.settings` — комментарий в коде
прямо объясняет, почему это важно (совпадение с реальным путём чтения
общих настроек, а не молчаливый fallback на `inner`); `test/golden-matrix.test.mjs`
дополнительно утверждает `outerSpace.settings.sun_ray_origin === undefined`
— структурная гарантия, что селектор не просочился в per-space скоуп.
- `smoke_sun.mjs` проверяет именно то, что не хватало по итогам r1: реальная
толстая наружная стена, оба полигона (тоннель + комнатная часть)
фактически нарисованы в DOM (`domPolyCount ≥ 2`), координаты не `NaN`/`Infinity`.
- Мутант `sun-golden-north-neutralized` был бы сломан новой сценой (два
идентичных фрагмента `northDeg: 90,` в файле после добавления новой сцены);
автор это заметил и уточнил якорь длинным уникальным префиксом — проверено
прогоном `--check` (ровно 1 совпадение) и живым воспроизведением мутации
(тест краснеет).
- Изменение отпечатка `docs/images/screenshots.json` — не регресс
документационных скриншотов: `sourceFingerprint`/`sourceSha256` пересчитаны
из-за правки `demo/golden/*.mjs` (файлы входят в корпус отпечатка по
`scripts/source-fingerprint.mjs`), но ни один `imageSha256` не изменился —
`check-docs --screenshots=strict` подтверждает это green.
- Трейлеры: все 5 коммитов дельты несут `Issue: #577`; `User-Visible: no`
корректен — пользовательское поведение не менялось (оно было доставлено и
проверено ещё в r1 коммитах `db4b397e`/`6f6a85a1`, там же оба changelog).
`cb4a5451` (единственный, трогающий `demo/golden/baselines/**`) несёт
обязательные `Release: v1.76.0-beta.3` и `Baseline-Reviewed:` — условие
AGENTS.md выполнено.
- `baselines-index.json`: `matrixVersion` 61→62, `platform` `win32`→`linux`
(баланс принят с канонической ОС, не с рабочей машины автора),
`witnesses.count` 101→109 — согласовано с текстом коммита.
## Унаследовано из r1
Без повторной проверки в этом раунде (документ `CODE-REVIEW-577-r1.md`
[закоммичен в `7cc9b4b5`], SHA материала `6f6a85a11687c4f5cd20e79a2088d05ab6be12df`).
Основание: дельта r1→r2 не содержит ни строки в `src/**`,
`custom_components/houseplan/**/*.py` или `src/i18n/*.json` (подтверждено
`git diff --stat` выше в этом же документе), поэтому ничто из перечисленного
ниже не могло измениться между раундами:
- **AC1 (UI/сохранение):** select `#gs-sun-ray-origin` рендерится один раз,
всегда доступен, в space-диалоге отсутствует.
- **AC2 (совместимость):** `sunRayOriginOf()` fail-closed на `inner` для всех
испорченных значений; backend `vol.In(["inner","outer"])`; import/export и
support package round-trip.
- **AC3 (внутренняя грань):** формула `inner` байт-в-байт совпадает с
`origin/dev`; существующие golden для `inner` не менялись.
- **AC5 (инварианты луча):** `direction`/`len`/`fade`/`rim`/препятствия не
меняются между `inner`/`outer`; нулевая толщина даёт идентичный результат.
- **AC6 (кеш):** `origin` — часть ключа `_sunRaysCache`.
- **AC8 (соседние контракты):** `windowWallInfo`, `isExteriorWall`, per-space
`sun_rays`/`north_deg` не тронуты диффом ни в r1, ни в дельте r1→r2.
- **AC9 (производительность):** одна ветка `side = ... ? -1 : 1` в уже
существующем цикле, без новых таймеров/сетевых вызовов — проверено чтением.
- **i18n:** en/ru/de/fr, тексты совпадают с телом issue.
- **Config registry / schema parity, откат, бюджет бандла, типизация
`types.ts`** — как в r1.
- **Документация (класс C):** `docs/SUN.md`, `docs/WALL-THICKNESS.md` §5,
`docs/CONFIG-COMPATIBILITY.md`, `docs/USER-GUIDE.ru.md`, оба
`docs/CHANGELOG*.md` — числятся в скоупе диффа r1 без замечаний; содержимое
повторно построчно не сверялось в этом раунде (дельта их не касается).
## Чего не проверял и почему
- **`python -m pytest tests_backend`** — не прогонялся; backend-файлы не
входят в дельту r1→r2, а на r1 SHA `backend` job Validate был зелёным
(унаследовано).
- **`performance_smoke`** — heavy-гейт, AC9 называет доказательством «ревью
кода»; выполнено чтением в r1, дельта эту ветку не касается.
- **Полный `smoke-select` матч (18 прямых совпадений из r1)** — не
перегонялся: дельта не в `src/**`, поэтому набор прямых совпадений не
изменился, а `smoke_sun`/`smoke_general_settings` (единственные, которые
дельта реально меняет) прогнаны явно выше.
- **`npm run invariants`** — дельта не в модели геометрии (рёбра комнат,
`layout`, `marker.space`, записи толщины) и не меняет `src/**` вовсе; гейт
не относится к этому диффу.
- **HACS/Hassfest/geometry_parity** — не относятся к диффу (нет изменений в
манифесте, интеграции или geometry parity harness); в Validate — skipped
по тем же причинам (push, не heavy).
## Материал раунда
- Ветка: `issue/577-sun-ray-window-corners`
- SHA материала: `59c5f6f6f259bed13ae39df88a7a163be8c4ec83`
- Предыдущий раунд: `CODE-REVIEW-577-r1` (жёлтый, Medium в скоупе), SHA
`6f6a85a11687c4f5cd20e79a2088d05ab6be12df`
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/577-sun-ray-window-corners`, коммит `59c5f6f6f259` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `b65cd3cf6d62954b65e9be4f605a767c55b298d0`
```
git log --all --format='%H %T' | grep b65cd3cf6d62
```
- Тело issue: `ebb1c05540c7191db0b4a76841f19b1371239caa0d41798ba82ec319116b32fc`
- Вердикт конвейера: `green` · High 0