mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-01 04:09:17 +00:00
@@ -0,0 +1,151 @@
|
||||
# CODE-REVIEW-366-r1
|
||||
|
||||
Issue: #366 · «Glow #20: движущиеся ворота на cover-сущности вызывают до ~100
|
||||
пересчётов геометрии света за одно открытие»
|
||||
Этап: code · заход r1 · блокирующих циклов 0/2 (light track — бюджет 2 цикла)
|
||||
SHA ревью: `51657831e6c0a00336a099795943b9f002f1455f` (HEAD ветки
|
||||
`issue/366-cover-glow-quantum`, диапазон `origin/dev...HEAD`)
|
||||
ТЗ: тело issue #366, ревизия 2 (принята SPEC-REVIEW-366-r2, зелёный вердикт,
|
||||
0 Medium)
|
||||
|
||||
## Скоуп
|
||||
|
||||
Один коммит, класс A+B+C+D:
|
||||
|
||||
- `src/logic.ts` — новая константа `OPENING_LIGHT_AMOUNT_QUANTUM = 0.05` и
|
||||
чистая функция `quantizeOpeningLightAmount`.
|
||||
- `src/houseplan-card.ts` — импорт функции и её применение в **единственной**
|
||||
точке сборки `passageStates` (:10381), которая питает и сигнатуру барьеров
|
||||
(:10383-10387), и длину светового выреза (:10408, :10420-10423).
|
||||
- `test/logic.test.mjs` — новый юнит на границы/зажимы/узлы + свип 0..1 через
|
||||
`openingLightStateSignature`.
|
||||
- `scripts/mutation-gate.mjs` — новый мутант `opening-light-quantum-identity`
|
||||
(квант → identity), guard `--test-name-pattern="#366"`.
|
||||
- `docs/LIGHT.md` §Caching — упоминание сетки 0.05.
|
||||
- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md` — запись в `## Unreleased`
|
||||
в том же коммите, `User-Visible: yes` соблюдён.
|
||||
- `docs/images/*` + `screenshots.json` — пересчитанный `sourceFingerprint`
|
||||
из-за правки `src/**` (механический след check-docs, не отдельная работа).
|
||||
- `dist/**`, `custom_components/houseplan/frontend/**` — синхронный бандл.
|
||||
|
||||
Никакой геометрии комнат/стен, `layout`, `marker.space`, `open_spans` diff не
|
||||
трогает — инварианты модели (#254) на этот PR не применимы.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
Прогнано лично на этом SHA (зелёного Validate на SHA `51657831` нет):
|
||||
|
||||
| Гейт | Команда | Результат |
|
||||
|---|---|---|
|
||||
| Typecheck | `npm run typecheck` | зелёный, без вывода |
|
||||
| Юниты | `npm test` | `1534/0`, `skipped 1` — совпадает с заявленным в комментарии автора |
|
||||
| Целевой юнит | `node --test --test-name-pattern="#366" test/logic.test.mjs` | 1/1 зелёный |
|
||||
| Мутант (умеет падать) | `node scripts/mutation-gate.mjs --id=opening-light-quantum-identity` | `тест покраснел, как обязан` — AC5 подтверждён исполнением, не начитыванием |
|
||||
| Сборка | `npm run build` | зелёная |
|
||||
| Синхронность бандла | `npm run bundle:sync` затем `git status --porcelain` | пусто — `dist`, `custom_components/houseplan/frontend`, `demo/srv/assets` совпадают байт-в-байт с уже закоммиченным |
|
||||
| Документация | `node scripts/check-docs.mjs` | `Documentation checks passed (7 files, 10 external links)` — обязателен, т.к. diff трогает `src/**` |
|
||||
| Выбор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «Прямое совпадение (2)»: `smoke_opening_binding.mjs`, `smoke_registryless_opening.mjs` (символ `_openingAmt`) |
|
||||
| Смок (AC4, названный автором) | `node demo/smoke_glow.mjs` | зелёный, все проверки `true`/`OK` |
|
||||
| Смок (прямое совпадение) | `node demo/smoke_opening_binding.mjs` | зелёный |
|
||||
| Смок (прямое совпадение) | `node demo/smoke_registryless_opening.mjs` | зелёный |
|
||||
| Видимый рендер | `npm run golden:verify` (после свежего build+bundle:sync) | все сценарии `passed`, 0 упоминаний fail; включая `lighting-opaque-glow-two-doorways-dark`, где `cover.golden_door_half` стоит на `current_position: 50` — точный узел сетки 0.05, изображение не сдвинулось |
|
||||
| Single-source-numbers | `node --test test/single-source-numbers.test.mjs` | зелёный (не относится напрямую к правке, но затронутое число — amount, видимое дважды — проверено ниже отдельно) |
|
||||
|
||||
Не прогонял и почему:
|
||||
- **Полный набор `demo/smoke_*.mjs`** — smoke-select не назвал ничего сверх
|
||||
двух прямых совпадений плюс явно названный автором `smoke_glow.mjs`; diff —
|
||||
одна точка применения чистой функции, не новая подсистема. Все три
|
||||
релевантных смока зелёные.
|
||||
- **`npm run invariants`** — diff не трогает рёбра/толщину/layout/ссылки.
|
||||
- **`python -m pytest tests_backend`** — diff не трогает `custom_components/**/*.py`
|
||||
(единственный неродственный провал в моей среде — `backend-test-guard.mjs`
|
||||
внутри полного прогона mutation-gate, `No module named pytest`; это
|
||||
окружение без backend-venv, не относится к этой задаче, диагностировано и
|
||||
отброшено).
|
||||
- **`performance_smoke`** — не назван в AC, задача не меняет измеряемый в нём
|
||||
путь напрямую (только уменьшает частоту его срабатывания); предрелизный
|
||||
гейт прогонит его на полном наборе.
|
||||
- Полный `node scripts/mutation-gate.mjs` (без `--id=`) не гонял целиком —
|
||||
дорогой предрелизный гейт; целевой мутант прогнан отдельно и это то, что
|
||||
доказывает AC5.
|
||||
|
||||
## Находки
|
||||
|
||||
Ни одной. High: 0, Medium: 0.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
**К1 (один квант, один источник).** `quantizeOpeningLightAmount` — чистая
|
||||
функция: `NaN`→0 (через `Number.isFinite` guard), зажим в `[0,1]` до и после
|
||||
округления, округление к ближайшему узлу сетки 0.05. 0 и 1 — точные узлы
|
||||
арифметически (`0/0.05=0`, `1/0.05=20` — целые), не требуют отдельного кейса.
|
||||
Применена **ровно в одной** точке — `houseplan-card.ts:10381`, при сборке
|
||||
`passageStates`; это единственный производитель, который читает и сигнатура
|
||||
(:10383-10387), и вырез (:10408 `openingLightApertureLength`, :10420-10423
|
||||
`lightPartitionCuts`) — по построению не могут разойтись (single-source
|
||||
принцип соблюдён: amount виден дважды — в сигнатуре кэша и в геометрии, — но
|
||||
у обоих ровно один источник, сама переменная `amount` в замыкании
|
||||
`flatMap`). Другие потребители `_openingAmt` — `:8835` (изометрия),
|
||||
`:12494`, `:12615` (рендер створки/подписи) — не квантуются, что и требовал
|
||||
контракт («анимация створки остаётся плавной»); проверено чтением всех
|
||||
вызовов `_openingAmt` в файле (4 сайта, один изменён).
|
||||
|
||||
**К2 (≤21 сигнатура).** Математически: узлов сетки 0.05 на `[0,1]` ровно 21
|
||||
(0, 0.05, …, 1.00). Юнит-свип шагом 0.001 подтверждает исполнением, не
|
||||
только по построению.
|
||||
|
||||
**AC1** — юнит на границы/зажимы/узлы — присутствует и зелёный, воспроизвёл
|
||||
локально.
|
||||
|
||||
**AC2** — свип-юнит на ≤21 сигнатуру — присутствует, зелёный, воспроизвёл;
|
||||
дополнительно проверил, что мутант «квант→identity» красит именно этот
|
||||
ассерт (`тест покраснел, как обязан`).
|
||||
|
||||
**AC3** (binary-двери байт-в-байт) — юнит фиксирует точные строки
|
||||
`door:0.000` / `door:1.000` и то, что `quantizeOpeningLightAmount(1)` даёт ту
|
||||
же сигнатуру, что и неквантованная `1`. Формат сигнатуры (`toFixed(3)`,
|
||||
`logic.ts:371`) не менялся.
|
||||
|
||||
**AC4** (существующие смоки/golden #20 зелёные без правки ассертов) —
|
||||
`smoke_glow.mjs`, `golden:verify` (полный набор, включая сценарий с cover на
|
||||
50%) зелёные; ни один ассерт в этих файлах не тронут diff'ом (проверено —
|
||||
diff не касается `demo/**` кроме `mutation-gate.mjs`).
|
||||
|
||||
**AC5** (мутант красит юнит) — прогнан лично, поймано 1/1.
|
||||
|
||||
**Трейлеры и changelog.** `Issue: #366`, `User-Visible: yes` на коммите;
|
||||
`docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md` правлены в этом же коммите —
|
||||
требование §Commits соблюдено. Формулировки на пользовательском языке («не
|
||||
дёргают карточку», «ступает по сетке 5%») без утечки реализации.
|
||||
|
||||
**docs/LIGHT.md** — Low из спек-ревью (r1-L2/r2-L2′: сетка квантования
|
||||
должна попасть в LIGHT.md тем же коммитом) закрыт: §Caching описывает сетку
|
||||
0.05 и ссылку на #366.
|
||||
|
||||
**Продуктовая рамка.** Правка обслуживает J1 (живой обзор состояния дома) —
|
||||
устраняет визуальные подёргивания всей карточки на wall-планшете во время
|
||||
движения ворот, что прямо противоречит "at a glance" ценности продукта.
|
||||
Скоуп не расширен: чистая квантовая функция в одной точке конвейера, ничего
|
||||
геометрического или конфигурационного не тронуто. Решение (квант 0.05, а не
|
||||
debounce) — принятое предположение спек-ревью r1-M2, с ценой отката в одну
|
||||
константу; реализация не отступает от контракта ТЗ.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
- Ручного тестирования на реальном wall-планшете нет (не входит в цикл ревью);
|
||||
визуальная неразличимость шага 5% принята как предположение ещё на этапе
|
||||
спеки (r1-M2), не переоценивается здесь заново — это продуктовый вопрос,
|
||||
а не технический.
|
||||
- Не мерил фактическое число пересчётов на живом cover со стрим-обновлениями
|
||||
позиции (нет интеграционного стенда HA) — К2 доказан математически и
|
||||
юнит-свипом, что признано в ТЗ достаточным доказательством (юнит вместо
|
||||
browser-профиля).
|
||||
- Полный `demo/smoke_*.mjs` (201 файл) и `performance_smoke` — не прогонял,
|
||||
обоснование выше в разделе «Как проверялось».
|
||||
- `tests_backend` — не прогонял, diff не трогает Python.
|
||||
|
||||
## Вердикт
|
||||
|
||||
Зелёный. Технический контракт реализован ровно как специфицирован в ревизии
|
||||
2 ТЗ, все AC доказаны исполнением (не только чтением), затронутые смоки и
|
||||
golden зелёные, трейлеры и changelog на месте, бандл синхронен.
|
||||
Reference in New Issue
Block a user