mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности (`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` → `legacy/specs/`), а относительные ссылки внутри перенесённых документов и в соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно только до переноса документов ревью). Гейты архив не смотрят. `reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не резолвится от нового места, а цель находится от нового или старого места через карту переносов; битая и до переноса ссылка не трогается. `--apply` делает это само, `--repair-links=<rev>` — для всех переименований `<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом `--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты: перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет; мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и DEVELOPMENT › Release называют переписывание и `--check-links`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
301 lines
25 KiB
Markdown
301 lines
25 KiB
Markdown
# Code review — issue #146, cycle r1
|
||
|
||
Вердикт: **красный** · цикл r1/4 · High: 1 · Medium: 0
|
||
|
||
Ветка: `issue/146-four-phase-sun-background` · implementation-коммит
|
||
[`debb13b`](https://github.com/Matysh/houseplan-card/commit/debb13baa280042c79a004c97152e3b1be5ab11b)
|
||
· ТЗ: [`docs/specs/146-four-phase-sun-background.md`](../../../docs/specs/146-four-phase-sun-background.md)
|
||
(зелёный [`SPEC-REVIEW-146-r1`](SPEC-REVIEW-146-r1.md), High:0, Medium:1 → #147,
|
||
не блокирует).
|
||
|
||
## Скоуп проверки
|
||
|
||
`git diff origin/dev...HEAD` — 35 файлов, 2575(+)/782(-). Ядро изменений:
|
||
`src/sun.ts` (новый `resolveDayCycle`/палитра/резолвер), `src/day-cycle-render.ts`
|
||
(новый файл — общие layer-шаблоны), `src/houseplan-card.ts` (lifecycle,
|
||
defaults, рендер стейджа), `src/space-card.ts`/`src/space-render.ts`
|
||
(статическая карточка), `src/styles.ts` (весь визуальный контракт),
|
||
`custom_components/houseplan/{store,const,import_export}.py` (миграция и
|
||
export/import), `src/i18n/{en,ru}.json`, тесты (`test/sun.test.mjs`,
|
||
`tests_backend/test_ha_import_export.py`, smoke), документация (`SUN.md`,
|
||
`ARCHITECTURE.md`, `CONFIG-COMPATIBILITY.md`, `USER-GUIDE.ru.md`,
|
||
`TESTING.md`, `STYLING-HOOKS.md`) и оба changelog.
|
||
|
||
Единственный implementation-коммит `debb13b` несёт `Issue: #146` ·
|
||
`User-Visible: yes`; оба changelog обновлены в этом же коммите — требование
|
||
выполнено. Три копии bandle идентичны (см. ниже).
|
||
|
||
## Как проверялось
|
||
|
||
Дешёвые гейты (всегда):
|
||
|
||
- `npx tsc --noEmit` → **зелёный**, без вывода.
|
||
- `npm test` → **802/802 green**.
|
||
- `npm run build` → зелёный; свежая пересборка дала SHA-256
|
||
`67293527e2154b2bc879d45a173dfba64c5a70cd090e770059e2b08c7c6ea0fe` для всех
|
||
трёх файлов (`dist/houseplan-card.js`,
|
||
`custom_components/houseplan/frontend/houseplan-card.js`,
|
||
`demo/srv/assets/houseplan-card.js`) — совпадает с хендофф-комментарием
|
||
автора; `git status` после билда пуст (закоммиченные копии побайтно
|
||
совпадают со свежей сборкой).
|
||
|
||
Гейты по необходимости (diff меняет рендер/CSS-layering/lifecycle на всех
|
||
поверхностях фона — обоснование запуска целевых smoke и golden):
|
||
|
||
- `node demo/smoke_bg_color.mjs` → все проверки `true`, `OK` (paper opacity,
|
||
night-does-not-dim-plan, static/kiosk/space-card parity).
|
||
- `node demo/smoke_sun.mjs` → все проверки `true`, `OK` (существующие оконные
|
||
лучи/compass не задеты).
|
||
- `node demo/smoke_sun_live_bg.mjs` → все проверки `true`, `OK`.
|
||
- `node demo/smoke_general_settings.mjs` → все проверки `true`, `OK`, включая
|
||
`newSpaceUsesDaynight`/`floorImportUsesDaynight` (AC10).
|
||
- `node demo/smoke_render_perf.mjs` → все проверки `true`, `OK`, включая
|
||
`clockTickModelBuilds: 0`/`modelBuildsPer10Renders: 0` — прямое
|
||
доказательство AC14 (нет geometry/device rebuild на clock tick).
|
||
- `npm run golden:verify` (полный набор, инструмент запрещает частичный прогон
|
||
в режиме verify) → **нашёл найденный ниже High**. Дополнительно: 4 новые
|
||
`day-cycle-*` сцены и другие тематические сцены (`plan-snap-*`,
|
||
`wall-junctions-*`, `isometric-wall-junctions-dark`) — `missing-baseline`;
|
||
для контроля я прогнал тот же `golden:verify` на чистом `origin/dev` (тот же
|
||
Linux/Chromium в этой среде, `demo/golden/baselines/**` не менялся этим
|
||
diff'ом) — там `plan-snap-*`/`wall-junctions-*` тоже отсутствуют, а
|
||
`geometry-plan-editor-dark`/`tray-wide-selection-en`/`tray-wide-tool-ru`/
|
||
`tray-medium-group-en` тоже `different`. Это подтверждает: это все —
|
||
пред-существующая среда-специфичная нестабильность/техдолг, не относящийся к
|
||
#146. Но `large-house-zoom-250-dark` на `origin/dev` **passed**, а на этой
|
||
ветке (дважды, воспроизводимо) — `different`. Разбор ниже, в «Находках».
|
||
- `python -m pytest tests_backend` — не прогонялся: в этой среде отсутствует
|
||
пакет `homeassistant` (`ModuleNotFoundError`), значит `test_ha_*.py`
|
||
(включая новый `test_ha_import_export.py`) молча пропускаются, а зелёный
|
||
результат ничего не доказывает (`AGENTS.md`, «Backend»). Backend-логика
|
||
(`store.py::migrate_config_background_mode`, `import_export.py`) и сам новый
|
||
тест проверены **чтением, не исполнением** — см. «Что проверено».
|
||
- Performance-профили/Full Performance — не прогонялись: предрелизный гейт
|
||
(AC16), а таргетированный `smoke_render_perf.mjs` уже дал прямое числовое
|
||
доказательство отсутствия rebuild на clock tick (см. выше).
|
||
|
||
## Находки
|
||
|
||
### [High] `.zoomwrap`'s новый `z-index: 1` прячет `.zoombadge` за планом на любом zoom>100% — независимо от `bg_mode`
|
||
|
||
**Файл:** `src/styles.ts:181-185` (`.zoomwrap { position: absolute; inset: 0;
|
||
z-index: 1; }` — строка `z-index: 1` добавлена этим diff'ом) против
|
||
`src/styles.ts:472-485` (`.zoombadge` — `position: absolute`, без `z-index`)
|
||
и `src/houseplan-card.ts:14758-14760` (`${this._zoom > 1 ? html`<div
|
||
class="zoombadge">...` : nothing}`, рендерится как **сиблинг** `.zoomwrap`,
|
||
после него в DOM).
|
||
|
||
**Причина.** До этого diff'а ни `.zoomwrap`, ни `.zoombadge` не имели
|
||
`z-index` — оба в слое `auto`, порядок рисования определялся порядком в DOM, и
|
||
`.zoombadge` (декларирован ПОСЛЕ `.zoomwrap`) закономерно рисовался сверху.
|
||
Этот diff добавил `.zoomwrap { z-index: 1 }`, чтобы держать план выше нового
|
||
`.hp-day-cycle-env { z-index: 0 }` (`styles.ts:201-208`). Но
|
||
`.hp-day-cycle-env` и без этого правила уже стоял бы ниже `.zoomwrap` по чистому
|
||
порядку DOM (он декларирован раньше) — явный `z-index: 1` был не нужен для
|
||
заявленной цели и стал побочным регрессом: положительный `z-index` выводит
|
||
ВСЁ содержимое `.zoomwrap` (план, комнаты, устройства, measure-layer) в более
|
||
высокий слой стекинга, чем любой сиблинг с `z-index: auto`, **независимо от
|
||
порядка в DOM**. `.farhint`/`.homearrow`/`.recoveryoverlay`/`.bootveil` не
|
||
задеты — у них уже был явный `z-index` (12/12/75/40). У `.zoombadge` его не
|
||
было и не появилось.
|
||
|
||
**Сценарий отказа:** любой пользователь View/kiosk, увеличивающий план сверх
|
||
100% (масштабирование колёсиком/пинчем — обычное действие, никак не связанное
|
||
с `bg_mode`), теряет видимый индикатор процента зума в левом нижнем углу —
|
||
элемент существует в DOM (`pointer-events: none`, не блокирует
|
||
взаимодействие), но визуально полностью скрыт под планом.
|
||
|
||
**Воспроизведено, не только прочитано:**
|
||
|
||
1. `npm run golden:verify` на этой ветке — сцена `large-house-zoom-250-dark`
|
||
(View, `zoom: 2.5`, фикстура `bg_mode` не задан → эффективный режим
|
||
`static`, то есть день/night-фон здесь вообще не активен — регресс не
|
||
специфичен для `daynight`) даёт `different`; повторный прогон —
|
||
тот же результат (детерминированно, не флейк).
|
||
2. Тот же `golden:verify` на чистом `origin/dev` (тот же чекаут, тот же
|
||
Chromium) — та же сцена **passed**.
|
||
3. Визуальное сравнение `demo/golden/baselines/large-house-zoom-250-dark.png`
|
||
(бейзлайн) и `artifacts/golden/actual/large-house-zoom-250-dark.png`
|
||
(текущий рендер): единственное отличие — плашка `250%` в левом нижнем углу
|
||
присутствует на бейзлайне и полностью отсутствует на актуальном рендере;
|
||
`artifacts/golden/diff/large-house-zoom-250-dark.png` подсвечивает magenta
|
||
ровно область этой плашки, весь остальной кадр — «без изменений» (серый с
|
||
низкой альфой).
|
||
|
||
**Не покрыто ни одним AC/smoke этой задачи** — ни один из существующих
|
||
`demo/smoke_*.mjs` не проверяет видимость `.zoombadge` при zoom>1, поэтому
|
||
`npm test`/targeted smoke зелёные, а регресс виден только через golden-пиксели
|
||
существующей (не новой) сцены `large-house-zoom-250-dark`.
|
||
|
||
**Требуется:** дать `.zoombadge` собственный `z-index` выше `1` (например,
|
||
как у `.farhint`/`.homearrow` — `12`), либо убрать не обязательный для цели
|
||
`z-index: 1` с `.zoomwrap` (естественный порядок DOM уже держит его выше
|
||
`.hp-day-cycle-env` без этого правила) — на выбор автора; и повторный прогон
|
||
`npm run golden:verify` (весь набор — гейт verify не даёт частичного) до
|
||
`passed` на `large-house-zoom-250-dark`, а также визуальный просмотр 4 новых
|
||
`day-cycle-*` сцен перед их принятием как baseline.
|
||
|
||
## Что проверено и корректно
|
||
|
||
- **AC1 (real-sun резолвер границ)** — `test/sun.test.mjs` («day cycle: real
|
||
sun selects exact four-phase boundaries») бьёт по обеим сторонам ±6° и
|
||
точным границам при `rising`/`falling`; тест умеет падать (замена `<=`/`>=`
|
||
на `<`/`>` в `dayCyclePhaseFromSun` немедленно ломает утверждения на
|
||
границах).
|
||
- **AC2 (atomic clock-fallback на garbage)** — `test/sun.test.mjs` («strict sun
|
||
snapshot keeps rays compatibility separate») перебирает
|
||
missing/NaN/Infinity/non-boolean для каждого поля и подтверждает `null`
|
||
(→ fallback), а `sunStateOf` (оконные лучи) не получил новое требование
|
||
`rising` — соответствует ТЗ §7.1. `smoke_sun_live_bg.mjs`:
|
||
`invalidSampleUsesClock: true`. Известное расширение до «любого из
|
||
elevation/azimuth/rising» (а не только elevation/rising) реализовано именно
|
||
так, как задокументировано в Medium-1 зелёного SPEC-REVIEW (→ #147,
|
||
сознательно не блокирует, не новая находка).
|
||
- **AC3 (позиция света следует реальным данным, fallback — дуга прототипа,
|
||
geometry не пересчитывается)** — `test/sun.test.mjs` («real decorative
|
||
position…», «fallback arc and night visibility…») численно совпадает с §8.1/
|
||
§8.2 ТЗ (east/south/west/north, дуга 05:00→13:00→21:00); `night.sunOpacity
|
||
=== 0` подтверждён и в unit, и в `smoke_sun_live_bg.mjs`
|
||
(`nightLightHidden: true`). Отсутствие geometry rebuild — прямое числовое
|
||
доказательство `smoke_render_perf.mjs` (см. «Как проверялось»).
|
||
- **AC5 (пиксели плана не меняются между фазами)** — прочитан
|
||
`src/styles.ts:198-248`: единственный phase-зависимый эффект на SVG —
|
||
`filter: drop-shadow(...)` на групповом `.hp-paperg` (три zero-offset
|
||
drop-shadow, как требует §9) — drop-shadow не подмешивает цвет в пиксели
|
||
самой фигуры, только рисует внешний halo за её alpha-контуром, поэтому
|
||
внутренность footprint остаётся байт-в-байт прежней. Подтверждено визуально:
|
||
`artifacts/golden/actual/day-cycle-dawn-dark.png` и `...night-dark.png` —
|
||
комнаты/стены/labels идентичны по цвету между сценами, виден только
|
||
внешний halo и фон вокруг плана. `smoke_bg_color.mjs`:
|
||
`nightDoesNotDimPlan/nightPaperStaysOpaque/paperOpaque: true`. Старый
|
||
потребитель `dayPhase().planDim` удалён из `_targetBrightness`
|
||
(`houseplan-card.ts:908-911`, теперь безусловно `return 1`) и из
|
||
`_stageBg`/`transitionBrightness` — риск №1 таблицы §19 ТЗ закрыт.
|
||
- **AC6 (переход 1100ms, plan не мигает, reduced-motion мгновенно)** —
|
||
`smoke_sun_live_bg.mjs`: `phaseTransitionIs1100ms: true`; в `styles.ts`
|
||
четыре константных `.hp-day-cycle-bg` слоя честно кросс-фейдят через `opacity
|
||
1100ms` (не декларация `transition` на самом градиенте) — соответствует
|
||
требованию §11.1 «действительно интерполируемые CSS primitives»; `@media
|
||
(prefers-reduced-motion: reduce)` отключает все четыре анимируемых правила
|
||
(`transition: none`).
|
||
- **AC7 (одна фаза на full View/kiosk/static card, editors не задеты)** —
|
||
`smoke_bg_color.mjs`: `kioskApplies/kioskSharesDay/staticCardApplies/
|
||
staticCardSharesDawn: true`; `_dayCycleState()` возвращает `null` при
|
||
`viewWeight <= 0` (editors) — прочитано в `houseplan-card.ts:12654-12659`.
|
||
- **AC8 (фон работает без north/sun.sun, оконные лучи — нет)** —
|
||
`smoke_sun_live_bg.mjs`: `backgroundIndependentOfNorth/
|
||
raysStillNeedNorth: true`. i18n-хинты (`gs.sun_missing`, `gs.north_hint`)
|
||
корректно переписаны на «фон работает, лучи — нет» в обеих локалях.
|
||
- **AC9 (однократная идемпотентная миграция)** — прочитан
|
||
`custom_components/houseplan/store.py:29-71`: `migrate_config_background_mode`
|
||
— чистая функция, no-op при уже валидном `bg_mode`, no-op на документах без
|
||
`config` (layout/virtual-light stores), сохраняет неизвестные поля
|
||
(`copy.deepcopy` + точечная модификация); вызывается только при
|
||
`old_minor_version < 2` (после миграции `STORAGE_MINOR_VERSION=2` не
|
||
перезапускает её). Новый тест
|
||
`tests_backend/test_ha_import_export.py::test_background_defaults_and_store_migration_preserve_legacy_view`
|
||
бьёт ровно эти случаи (idempotence через `is migrated`, сохранение
|
||
`future_*`/`rev`, no-op для layout-подобного документа) — **проверено
|
||
чтением, не исполнением** (см. «Как проверялось», нет `homeassistant`).
|
||
- **AC10 (новые defaults материализуют `daynight`, Edit не трогает
|
||
существующий mode)** — `smoke_general_settings.mjs`:
|
||
`newSpaceUsesDaynight/floorImportUsesDaynight: true`.
|
||
`custom_components/houseplan/const.py`: `DEFAULT_CONFIG["settings"]["bg_mode"]
|
||
== "daynight"`. Прочитан `houseplan-card.ts:12168` (space-dialog init
|
||
сохраняет `null`, если явного override нет) и `:12961`
|
||
(`bgModeOf(this._settings, {})` — глобальный dialog открывается с текущим
|
||
ЭФФЕКТИВНЫМ режимом, не константой) — открытие+сохранение существующего
|
||
space/global dialog без изменения выбора не меняет сохранённый режим.
|
||
- **AC11 (export/import контракт §12.4)** — прочитан
|
||
`custom_components/houseplan/import_export.py`: `_materialize_global_
|
||
background`/`_materialize_space_background` — раздельные fallback
|
||
(`create_export` для space передаёт **эффективный global** экспортируемого
|
||
инстанса, а `parse_document`/`build_space_merge` для одиночного
|
||
space-импорта — жёсткий `"static"`, не режим целевой установки) — в точности
|
||
различие, которое требует §12.4 («legacy per-space import не наследует
|
||
target»). Новый тест `test_background_mode_is_materialized_across_export_
|
||
and_legacy_import` бьёт full/space export, legacy full/space import (через
|
||
`parse_document`) и `build_space_merge` — **проверено чтением, не
|
||
исполнением**.
|
||
- **AC12 (i18n без нового режима, pointer-inert, не двигает focus/hit
|
||
targets)** — `src/i18n/{en,ru}.json` обновлены симметрично, третий
|
||
публичный токен не добавлен (селектор остаётся `static`/`daynight`,
|
||
подтверждено чтением `houseplan-card.ts:14025-14027`). `.hp-day-cycle-env`
|
||
— `pointer-events: none` (styles.ts:206) и `aria-hidden="true"`
|
||
(`day-cycle-render.ts`). `npm test` включает общий тест полноты i18n-ключей
|
||
(802/802 green) — контентная проверка конкретных строк выполнена чтением.
|
||
- **AC13 (lifecycle: 30s только в fallback+visible, catch-up, cleanup)** —
|
||
`smoke_sun_live_bg.mjs`: `clockTimerArmed/hiddenStopsClock/
|
||
visibleRestartsClock/visibilityKeepsHover: true`. Прочитаны
|
||
`_dayCycleTick`/`_syncDayCycleClock`/`_dayCycleVisibility`
|
||
(`houseplan-card.ts:12660-12706`) и их зеркало в `space-card.ts:143-190`:
|
||
таймер существует только при `source === 'clock'` и видимой вкладке,
|
||
`disconnectedCallback` чистит и таймер, и `_dayCycleClockKey` в обоих
|
||
файлах.
|
||
- **AC14 (bounded update, без geometry/device rebuild, RAF/canvas/сети/
|
||
storage)** — прямое числовое доказательство `smoke_render_perf.mjs`:
|
||
`modelBuildsPer10Renders: 0`, `clockTickModelBuilds: 0`,
|
||
`clockTickKeepsModel/Devices/WallUnion/SunRays: true`. Diff не добавляет ни
|
||
`requestAnimationFrame`, ни canvas/WebGL, ни новых сетевых/HA/storage вызовов
|
||
— подтверждено чтением всего diff (`git show debb13b`).
|
||
- **AC15 (гейты, документация, три bundle, один коммит)** — единственный
|
||
коммит `debb13b` несёт оба трейлера, оба changelog,
|
||
`SUN.md`/`ARCHITECTURE.md`/`CONFIG-COMPATIBILITY.md`/`USER-GUIDE.ru.md`/
|
||
`TESTING.md`/`STYLING-HOOKS.md` и все три копии bundle; локальная пересборка
|
||
побайтно совпала (см. «Как проверялось»).
|
||
- **Инвариант неизменного плана (§10 ТЗ)** — ни один цветовой filter
|
||
(`brightness`/`contrast`/`saturate`/`sepia`/`hue-rotate`), overlay или смена
|
||
fill не применяется к дереву плана; единственный phase-зависимый filter —
|
||
внешний drop-shadow на группе paper (см. AC5 выше). Старые
|
||
`_skyPlan`/`_skyElev`/`_skySnap`/`.stage.daynight`/`.skysnap` полностью
|
||
удалены, а не оставлены мёртвым кодом рядом с новым путём.
|
||
- **Security/network verdict (AC16, §18 ТЗ)** — прочитан весь diff: ни новых
|
||
HTML/URL-приёмников, ни новых сетевых вызовов, ни новых HA service calls,
|
||
ни новых storage-полей сверх заявленной `bg_mode`-семантики. Негативный
|
||
вердикт подтверждается.
|
||
|
||
## Чего не проверял
|
||
|
||
- **`npm run golden:accept -- --reviewed`** — не выполнялся и не должен
|
||
выполняться на этом этапе: 4 новых `day-cycle-*` сцены (и ряд не связанных с
|
||
#146 сцен) остаются `missing-baseline`; принятие baseline — предрелизный шаг
|
||
по полному Linux CI артефакту (`AGENTS.md`, `PROCESS.md` §8/13), не гейт
|
||
код-ревью. Сами 4 сцены просмотрены визуально (`artifacts/golden/actual/
|
||
day-cycle-{dawn,day,dusk,night}-dark.png`) — план не тонирован, ночью не
|
||
виден световой круг, halo не создаёт по-комнатных швов; годны как кандидат
|
||
на baseline после фикса High и повторного прогона.
|
||
- **`python -m pytest tests_backend`** — не выполнялся (нет `homeassistant` в
|
||
этой среде); backend-логика и новый тест разобраны чтением (см. «Как
|
||
проверялось» и AC9/AC11 выше).
|
||
- **Полный browser smoke-suite (127 файлов)** — не прогонялся; прогнаны 5
|
||
целевых smoke, относящихся к тронутым поверхностям (`bg_color`, `sun`,
|
||
`sun_live_bg`, `general_settings`, `render_perf`), плюс golden как более
|
||
широкая пиксельная сеть — она и нашла регресс, не покрытый ни одним из этих
|
||
smoke по имени.
|
||
- **Performance smoke / Full Performance (эталонные бюджеты)** — не
|
||
прогонялись, предрелизный гейт; косвенное доказательство отсутствия rebuild
|
||
уже получено через `smoke_render_perf.mjs`.
|
||
- **`prefers-reduced-motion`/`forced-colors` в реальном браузере с
|
||
ОС-настройкой** — не проверялось интерактивно; поведение разобрано чтением
|
||
CSS (`@media (prefers-reduced-motion: reduce)` блок в `styles.ts`) и
|
||
соответствует контракту §13 ТЗ.
|
||
- **Ручной интерактивный проход в браузере (не headless)** — не выполнялся;
|
||
весь визуальный вывод основан на golden/smoke харнессе и прямом просмотре
|
||
сохранённых PNG.
|
||
|
||
## Итог
|
||
|
||
High: 1 (описан выше, блокирует — регресс видимости `.zoombadge` на
|
||
zoom>100%, воспроизведён детерминированно и локализован до одной CSS-строки).
|
||
Medium: 0 (единственная содержательная находка этого масштаба — расширение
|
||
azimuth-гейта на фазу — уже заведена на этапе ревью ТЗ как
|
||
[#147](https://github.com/Matysh/houseplan-card/issues/147) и не повторяется
|
||
здесь). Low: 0 новых; ранее отмеченные в SPEC-REVIEW Low-2/Low-3 не относятся к
|
||
коду.
|
||
|
||
Вердикт красный: цикл возвращается автору на исправление стекинга
|
||
`.zoombadge`/`.zoomwrap` (см. «Требуется» в находке), с последующим зелёным
|
||
`npm run golden:verify` на `large-house-zoom-250-dark` и визуальным
|
||
подтверждением 4 новых `day-cycle-*` сцен перед их будущим принятием как
|
||
baseline.
|