Files
houseplan-card/legacy/reviews/v1.64.0/SPEC-REVIEW-146-r1.md
T
Claudeandclaude[bot] cca9bc856b docs(reviews): документы ревью линий до v1.77.0 — в legacy/reviews (#682)
Волна 5 эпика #674, перенос документов ревью (класс C), сделан
инструментом: `node scripts/reviews-archive.mjs --through=v1.77.0 --apply`.
965 документов 332 задач разложены по `legacy/reviews/<тег>/` (v1.63.0 …
v1.77.0) — 330 задач доказаны трейлерами своей линии, два документа без
трейлера (#68, #94, работа до правила трейлеров) — по линии, где их
добавили. В `docs/reviews/` остались 154 документа задач с трейлером после
v1.77.0 — текущая линия v1.78.0 — и INDEX.md, пересобранный тем же
построителем. История не переписана: SHA-якоря «Материала раунда» живы.
Счёт раундов (`reviewRounds` по живому каталогу и архиву) совпадает до и
после переноса: 694 пары «этап:задача». Строка в `legacy/README.md`.

Issue: #682
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 22:10:47 +00:00

339 lines
30 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.
# SPEC-REVIEW-146-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/146
- **ТЗ под ревью:** `docs/specs/146-four-phase-sun-background.md` (коммит
`558dae9`, ветка `issue/146-four-phase-sun-background`)
- **Роль:** ревьюер ТЗ (не автор), этап `S4-spec-review`
- **Трек:** обычный (не `small`/`trivial`) — сложность/риск 8/10, множество
поверхностей (full View, kiosk, статическая карточка, defaults, storage
migration, import/export, i18n, golden/performance), файл ТЗ в
`docs/specs/` создан корректно.
- **Цикл:** r1/4
## Скоуп ревью
Проверялось соответствие ТЗ:
- `docs/SCOPE.md` — попадание в Core user jobs (J1), отсутствие расширения
скоупа за пределы декоративного фона;
- `PROCESS.md` §2.4, §2.5 (DoR), §7.1 (обязательные разделы), §3/§12
(запреты, в т.ч. «догадка вместо решения»);
- `AGENTS.md` — классы файлов, ветка, трейлеры коммита ТЗ;
- каноническому документу подсистемы `docs/SUN.md` (действующий непрерывный
`dayPhase()`, компас-гейт, `planDim`, wedge-контракт, которые ТЗ обязано
либо заменить, либо явно сохранить);
- `docs/USER-GUIDE.ru.md` — терминология «Следует за солнцем», раздел
«15. Солнце и фон день/ночь»;
- `docs/TOUCH-SUPPORT.md` — контракт View/kiosk/editors;
- `docs/CONFIG-COMPATIBILITY.md` — паттерн «materialize», идемпотентная
миграция, preview vs apply, same-instance/foreign import;
- `docs/ARCHITECTURE.md` — существование `#101` (`src/mode-transition.ts`) и
`#131` (cold-start), на которые ссылается ТЗ;
- полному тексту issue #146 (тело + 5 комментариев: два уточнения владельца,
аналитика с Q1–Q3, решение владельца по Q1–Q3, хендофф автора);
- приложенным к issue файлам (`SPECIFICATION.md`, `interactive-prototype.html`,
`README.md`) — на предмет того, что численные визуальные токены ТЗ (палитра,
контур, положение декоративного света) не выдуманы, а прослеживаются к
реальному источнику, при этом текст issue сохраняет приоритет там, где он
расходится с вложениями (сам issue это оговаривает явно).
## Как проверялось
1. Прочитан весь тред issue #146 через `gh issue view --json body,comments`:
исходное описание, два уточнения владельца («текст issue приоритетен над
вложениями»; «если высота недоступна — ориентируемся по времени суток»),
аналитика с тремя батчированными продуктовыми вопросами Q1–Q3 (каждый с
явным default), решение владельца «принимаю все предложенные defaults»,
финальный хендофф автора со ссылкой на коммит `558dae9`. Вопросы заданы
корректно — только продуктовые («как различать рассвет/сумерки», «чем
управлять положением света», «что делать с существующими планами»), ни
одного технического вопроса владельцу не эскалировано.
2. Сверены обязательные разделы ТЗ (§7.1 PROCESS.md) построчно — таблица
ниже.
3. Прочитан `docs/SUN.md` целиком: подтверждено, что действующий
`dayPhase()` — непрерывная интерполяция по `BG_STOPS`, зависящая от
`north_deg`, с `planDim` (`brightness(.9)` на весь `.zoomwrap` ночью) и
45-секундным CSS transition. ТЗ §3 корректно описывает это как исходную
техническую базу для замены (не голословно) и явно требует удаления
`planDim`-потребителей (§10, §14.1) и снятия compass-гейта именно с фона
(§4.5, §11.4), сохраняя compass-гейт для оконных лучей (§7.1 отдельно,
§11.4).
4. Скачаны и прочитаны все три вложения issue (`SPECIFICATION.md`,
`interactive-prototype.html`, `README.md`) для проверки, что численные
визуальные токены ТЗ не изобретены заново:
- все 14 hex-цветов §9 ТЗ (`#aabdd1`, `#e8c8b7`, `#dce9ef`, `#cbdce3`,
`#48536c`, `#9a7380`, `#111a27`, `#1f2f3e` и др.) присутствуют дословно
в CSS `interactive-prototype.html`;
- все rgba-токены (`horizon`, `sun`, `vignette`, три уровня outline)
таблиц §9 сверены построчно с `--hp-horizon`, `--hp-sun-color`,
`--hp-vignette`, `--hp-shadow-core/soft` и `--hp-outline-glow`
прототипа для каждой из четырёх фаз (`day` — базовый блок,
`[data-time="dawn"]`, `[data-time="dusk"]`, `[data-time="night"]`) —
совпадение полное и в цвете, и в привязке near/mid/far к
core/soft/glow;
- размер декоративного света `250 CSS px` (§8.3) найден буквально
(`width: 250px; height: 250px`) в CSS прототипа;
- константы клок-дуги §8.2 (`8 + progress·84`, `78 − sin(progress·π)·64`,
диапазон `05:00–21:00`) совпадают дословно с `SPECIFICATION.md` §7.1
(`sunrise=300`, `sunset=1260`, те же формулы) — не изобретены;
- переходная длительность `1100 ms` и `cubic-bezier(.22, .61, .36, 1)`
(§11.1) совпадают дословно с `SPECIFICATION.md` §7;
- обнаружено, что в CSS прототипа определены (но нигде не применяются)
переменные `--hp-plan-filter`/`--hp-plan-opacity` с `brightness/
contrast/saturate/hue-rotate` по фазам — то есть в самом прототипе
есть мёртвый код, который менял бы план. ТЗ (§10, инвариант
неизменного плана) корректно **не** переносит это в контракт и прямо
запрещает ровно такой набор фильтров на дереве плана — соответствует
тексту issue («сам план не тонируется») и `SPECIFICATION.md` §5/§12,
а не тому единственному месту прототипа, которое им противоречит.
5. Прочитан `docs/CONFIG-COMPATIBILITY.md` и `docs/specs/050-config-export-
import.md`: паттерн «materialize», «same-instance/foreign import»,
«preview не пишет storage» — не изобретены заново, это термины уже
существующей подсистемы, ТЗ §12 использует их правильно.
6. Прочитан `docs/ARCHITECTURE.md:968` (`## View/editor transition
ownership (#101)`) и `:720` (`#131`, «initial snapshot does not depend on
live-sync subscriptions») — оба issue, на которые ссылается §11.3/§14.9
ТЗ, реальны и описывают именно то поведение, которое ТЗ обязывает
сохранить.
7. Прочитаны действующие i18n-ключи (`src/i18n/ru.json`, `en.json`):
`gs.bg_daynight`, `gs.bg_daynight_hint`, `gs.sun_missing`, `gs.north_hint`
существуют буквально с теми именами, которые ТЗ §13 требует обновить —
ключи не выдуманы, а текущий текст хинта («Needs the compass below»)
действительно противоречит новому «работает без компаса» и корректно
включён в release-артефакты (§18) как подлежащий правке в том же
коммите.
8. Прочитан `docs/USER-GUIDE.ru.md` §15 («Солнце и фон день/ночь», строка
899: «Для солнечных функций нужны `sun.sun` и направление севера»).
Подтверждено намеренное расхождение с новым контрактом: ТЗ прямо
требует обновить этот раздел в release-артефактах (§18) — не пропущено.
9. Проверено наличие тестовой инфраструктуры для заявленных способов
доказательства: `test/i18n.test.mjs` существует (AC12 реалистичен),
`demo/smoke_sun*.mjs` (5 файлов) существуют как база для «targeted
production-bundle smoke» (§16.3), `npm run golden:verify`/`golden:accept
-- --reviewed` — существующие команды (AGENTS.md, PROCESS.md §8) —
доказательства AC не ссылаются на несуществующие механизмы.
10. Пересчитаны границы контракта фазы (§7.2) и fallback (§7.3) на
непротиворечивость: `(-∞,-6]` night, `(-6,6)` dawn/dusk по `rising`,
`[6,+∞)` day — полное покрытие без пересечения; `[300,480)` /
`[480,1080)` / `[1080,1260)` / остальное — то же самое для минут суток.
Обе шкалы математически корректны и совпадают с §4 (решение владельца).
11. Найдено единственное реальное расхождение внутри самого ТЗ — см.
Medium-1 ниже; проверено, что оно не отражено в §21 («принятые
технические предположения»), то есть не помечено как оспоримое
предположение.
12. Проверены трейлеры и class-принадлежность: `git diff --stat
origin/dev...HEAD` показывает только `docs/specs/146-four-phase-sun-
background.md` и `docs/specs/README.md` (класс C, ни одного файла
класса A — продуктовый код не тронут до `S5-ready`, правило №1
AGENTS.md соблюдено); коммит `558dae9` несёт `Issue: #146` и
`User-Visible: no` — корректно для документа ТЗ.
## Обязательные разделы (§7.1 PROCESS.md)
| Раздел | Есть | Комментарий |
|---|---|---|
| Сценарий (персона/поверхность/момент) | ✅ | §1 — домашний администратор/kiosk, View и kiosk, ежедневный взгляд на дом |
| Что человек увидит до/после | ✅ | §2, «До:»/«После:» — см. Low-2 (длина и термины реализации) |
| Проблема (с подтверждённой причиной) | ✅ | §3, семь пунктов, все сверены с `docs/SUN.md` (см. «Как проверялось» п.3) |
| Скоуп / не-скоуп | ✅ | §5 / §6, явные границы (без нового mode-токена, без geo/weather API, без editor-контракта) |
| Контракт поведения | ✅ | §7 (источник/фаза) + §8 (декоративный свет) + §9 (визуал) + §10 (инвариант плана) + §11 (переходы/surfaces) |
| Модель данных и миграция | ✅ | §12 — schema не расширяется, идемпотентная миграция, import/export матрица |
| UX, i18n, accessibility, touch | ✅ | §13 — существующие ключи названы поимённо, touch/forced-colors учтены — см. Low-3 |
| AC1…ACn с доказательством | ✅ | §15, 16 штук, у каждого назван способ доказательства и исполнитель |
| План автотестов | ✅ | §16, разбит на unit/backend/smoke/golden с конкретными сценариями |
| Риски | ✅ | §19, таблица вероятность/влияние/мера, 11 строк |
| Откат | ✅ | §20 — пользовательский (выбрать static) и технический (revert) отдельно |
| Release-артефакты | ✅ | §18, конкретный список документов и оба changelog в одном `User-Visible: yes` коммите |
Все обязательные разделы присутствуют и содержательны. Дополнительно есть
раздел «Решения владельца» (§4, дословно фиксирует принятые Q1–Q3) и явный
блок «Принятые технические предположения — можно менять без продуктового
ревью» (§21, 12 пунктов) — именно то разделение продуктового и технического,
которого требует PROCESS.md §7.1. Формулировка §21.12 «Открытых продуктовых
вопросов нет» подтверждается содержанием: все три вопроса из аналитики
(различение dawn/dusk, источник позиции декоративного света, судьба
существующих конфигов) закрыты явными owner-решениями до написания этого
файла.
## Находки
### Medium-1 — `azimuth` тайно расширяет owner-решение о fallback-триггерах на саму фазу
**Файл:** `docs/specs/146-four-phase-sun-background.md:123-134` (§7.1),
`:422-425` (AC2) против `:64-69` (§4.1-4.2) и `:603-608` (§21.1-21.2)
Решение владельца (issue #146, комментарий 2026-08-14T13:28:11Z) и его
дословный пересказ в §4.1-4.2 ТЗ называют ровно два условия полного
clock-fallback: «Если elevation или rising отсутствуют/некорректны». Формула
самой фазы (§7.2) действительно использует только `elevation` и `rising` —
`azimuth` в ней не участвует вовсе, он нужен исключительно для позиции
декоративного света (§8.1).
Однако §7.1 («Валидный real-sun sample») вводит **третье** условие,
отсутствующее в owner-тексте: невалидный/нечисловой `azimuth` тоже
«атомарно» отправляет в clock-fallback **всю** day-cycle — то есть и фазу,
которую он математически не определяет. AC2 закрепляет это как обязательный
unit-тест («отсутствие/garbage любого из elevation/azimuth/rising атомарно
включает clock-fallback»), делая расширение постоянной частью контракта.
**Воспроизведение:** `sun.sun` с валидными `elevation=20` и `rising=true`
(должно быть `day`, если бы `azimuth` не гейтил фазу отдельно), но
`azimuth=NaN` (например, временный сбой конкретной интеграции, которая
считает elevation/rising отдельно от azimuth) — по букве §7.1/AC2 card
покажет фазу **по местным часам браузера**, а не `day`, хотя есть
корректные реальные данные для однозначного определения фазы. Владелец
такой случай не разбирал: его ответ на Q1 говорит только о недоступности
высоты или направления движения.
Это ровно тот класс дефекта, о котором предупреждает PROCESS.md §7.1:
«Догадка, записанная как факт, — худший вид дефекта: она проходит ревью,
потому что выглядит решением». Расширение не отмечено в §21 как «принятое
техническое предположение, можно менять свободно» — то есть выдано за
решённый факт, а не за предположение, которое ревьюер вправе оспорить.
На практике реальный `sun.sun` от ядра HA всегда обновляет `azimuth` и
`elevation` одним циклом (оба поля или есть, или оба недоступны при
`unavailable`), поэтому вероятность живого расхождения низкая — это и
удерживает находку на уровне Medium, а не High: она не ломает заявленный
сценарий и не требует возврата ТЗ на цикл.
**Решение ревьюера:** Medium, заведён отдельный issue
[#147](https://github.com/Matysh/houseplan-card/issues/147) со ссылкой на
#146, метки `bug`/`P3`/`S1-new`. Не блокирует `S5-ready`; решается либо
уточнением владельца, либо явным переносом пункта в §21 при реализации.
### Low-2 — «что человек увидит» длиннее одной фразы и использует термины реализации
**Файл:** `docs/specs/146-four-phase-sun-background.md:30-42` (§2)
PROCESS.md §7.1 требует «одной фразой, без терминов реализации». Раздел
написан четырьмя предложениями и содержит конкретные implementation-термины
(«1100 ms», «alpha-aware контур», «локальные часы браузера»). По существу
требование выполнено — читатель понимает, что видно до/после, без
двусмысленности — но это дальше от буквы правила, чем прецедент в
`SPEC-REVIEW-141-r1` (тот был просто «два предложения вместо одного», без
числовых деталей реализации).
**Решение ревьюера:** Low, не блокирует. Косметическая правка на усмотрение
автора при следующей редакции.
### Low-3 — нет буквальной touch-editor метки по `docs/TOUCH-SUPPORT.md`
**Файл:** `docs/specs/146-four-phase-sun-background.md:369-389` (§13)
`docs/TOUCH-SUPPORT.md` требует явную метку `Touch editor: supported` /
`best effort` / `not exposed` от «новых спецификаций editor-фич». Задача
#146 editor-фичей не является (editors явно в не-скоупе, §6, и §13 прямо
говорит «Editor остаётся desktop-first и не получает новых действий») —
формально требование правила не адресовано этому ТЗ буквально. Тем не менее
одна строка `Touch editor: not exposed` сняла бы даже формальное сомнение.
**Решение ревьюера:** Low, не блокирует. Необязательная косметическая
правка.
## Что проверено и корректно
- **Соответствие `docs/SCOPE.md`:** задача закрывает **J1** («показать дом
одним взглядом» — фон как декоративный временной контекст без искажения
цветов пола/стен/устройств) и не расширяется на смежные Core user jobs;
явно не трогает J2/J3/J5/J7 и не создаёт нового публичного mode-токена
сверх утверждённого `static | daynight` (§6).
- **Легитимность полного трека:** сложность/риск 8/10, множество
поверхностей, влияние на миграцию/i18n/perf/touch — критерии `small`/
`trivial` (§5/§5.1 PROCESS.md) не выполняются ни по одному пункту, полный
трек и файл в `docs/specs/` выбраны верно.
- **Продуктовые вопросы закрыты по процессу, без утечки технических
вопросов владельцу:** Q1 (dawn/dusk внутри одного диапазона высоты), Q2
(источник позиции декоративного света), Q3 (судьба существующих
конфигов) — каждый в форме «что неясно · что изменится · default»,
batched одним комментарием, с явным `blocked`+`S3-spec` до ответа.
Технических вопросов владельцу не задано ни одного.
- **Технический диагноз §3 не голословен** — все семь пунктов сверены с
`docs/SUN.md` и совпадают (см. «Как проверялось» п.3).
- **Визуальные токены §9 не выдуманы** — построчно прослежены к
`interactive-prototype.html` (см. «Как проверялось» п.4); опасный
побочный путь прототипа (`--hp-plan-filter` на плане) правильно
проигнорирован, а не перенесён в контракт.
- **Инвариант неизменного плана (§10)** прямо запрещает весь набор
color-фильтров и overlay на дереве плана и требует удаления
`dayPhase().planDim` — устраняет главный риск регресса, названный в
таблице рисков (§19, строка 1).
- **Совместимость (§12)** корректно использует существующий паттерн
«materialize» вместо runtime-fallback (`docs/CONFIG-COMPATIBILITY.md`);
явно разведены три сценария (существующий global без поля → `static`;
явный `daynight` → новая семантика того же токена; новое пространство в
старой установке → `daynight`) — ровно то, что запросил владелец в Q3, без
скрытой пятой ветки.
- **Import/export (§12.4)** использует существующую терминологию подсистемы
(`docs/specs/050-config-export-import.md`: preview/apply, same-instance/
foreign) корректно, не изобретая параллельный механизм.
- **Архитектурные ссылки на #101/#131 точны** — оба существуют в
`docs/ARCHITECTURE.md` и описывают именно то поведение (owner transition
timeline; cold-start без второго рендера), которое ТЗ обязывает сохранить.
- **i18n (§13, §18)** называет существующие ключи (`gs.bg_daynight`,
`gs.bg_daynight_hint`, `gs.sun_missing`, `gs.north_hint`) поимённо и
корректно определяет, что их текущий текст («нужен компас») противоречит
новому контракту и должен быть обновлён — без добавления третьего
публичного режима.
- **Non-scope (§6)** корректно отсекает соседние соблазны: геолокация/
weather API, ручной phase selector, изменение геометрии/порогов оконных
лучей, day/night в редакторах, персистентность вычисленной фазы,
production `?time=` — каждый с обоснованием, почему не эта задача.
- **AC1–AC16 однозначны и снабжены способом доказательства** из
допустимого по §2.5 PROCESS.md набора (`unit`/`backend`/`smoke`/`golden`/
«ревью кода», плюс «backend migration tests» и «pixel regression» как
уточнённые подвиды `backend`/`golden`); ни один AC не оставляет открытым,
чем именно он доказывается.
- **Release-артефакты (§18)** перечисляют конкретные существующие
документы (`SUN.md`, `USER-GUIDE.ru.md`, `ARCHITECTURE.md`,
`CONFIG-COMPATIBILITY.md`, `TESTING.md`) и оба changelog в одном
`User-Visible: yes` коммите — соответствует правилу 11 PROCESS.md; golden
корректно ограничен `golden:accept -- --reviewed` по полному Linux-
артефакту (§16.4), perf/golden/smoke верно отнесены к пре-релизному, а не
implementation-гейту (AC16, §8/§11.4 PROCESS.md).
- **Откат (§20)** корректно опирается на отсутствие необратимой миграции:
явный `static` — немедленный пользовательский откат, revert
implementation-коммита — технический, без потери данных.
- **Трассируемость:** `docs/specs/README.md:50` обновлён тем же коммитом;
ветка `issue/146-four-phase-sun-background` и трейлеры (`Issue: #146`,
`User-Visible: no`) корректны для документа класса C. `git diff --stat
origin/dev...HEAD` не содержит ни одного файла класса A — продуктовый код
не тронут до `S5-ready` (правило №1 AGENTS.md).
## Чего не проверял
- Не проверял реализуемость «одного pure resolver» (§14.1) как конкретной
структуры данных/API, конкретных имён helper-функций, CSS custom
properties или способа crossfade (registered custom properties vs два
bounded layer, §21.5) — по тексту ТЗ это явно свободное техническое
решение автора кода, не предмет ревью ТЗ.
- Не запускал автотесты, `golden`, `performance` или browser-смоки — на
этапе `spec` это не требуется; существование тестовой инфраструктуры
(`test/i18n.test.mjs`, `demo/smoke_sun*.mjs`, команды `golden:verify`/
`golden:accept`) проверено чтением файловой системы и `package.json`, а не
исполнением.
- Не проверял осуществимость «одного shared fallback ticker» на несколько
карточек (§21.9) по реальному коду `space-card.ts`/`houseplan-card.ts` —
это явно помечено в ТЗ как свободное техническое предположение,
подлежащее доказательству тестами на этапе реализации/код-ревью.
- Не проверял точность числовых оценок аналитики (7/10 · 4/10 · 8/10 · P1)
по существу — это поле владельца (PROCESS.md §2.2), уже принято явным
решением владельца до написания ТЗ.
- Не проверял детали алгоритма определения «внешней стены» и геометрии
оконных лучей (`RAY_MIN_COS`, `RAY_FADE_END` и т.д.) — задача explicitly
не меняет эту геометрию (§6), затронута только независимость фона от
`north_deg`, что и было предметом проверки.
- Не проверял корректность реализации `--hp-plan-filter`/`--hp-plan-opacity`
в самом прототипе как продуктового кода — это чужой одноразовый макет
(вложение issue), а не часть репозитория; проверено только то, что ТЗ
не унаследовало этот фрагмент в свой контракт.
## Вердикт
Зелёный. High: 0, Medium: 1 (заведён отдельным issue
[#147](https://github.com/Matysh/houseplan-card/issues/147), не блокирует),
Low: 2 (косметика, не блокируют, оставлены с записью в этом документе). ТЗ
решает заявленный сценарий J1 полностью, визуальные токены прослежены к
источнику, а не изобретены, продуктовые вопросы закрыты владельцем по
процессу без утечки технических вопросов.