docs: spec review r1 for #146

Issue: #146
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-14 13:47:32 +00:00
parent 558dae95cd
commit ab2a014568
+338
View File
@@ -0,0 +1,338 @@
# 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 полностью, визуальные токены прослежены к
источнику, а не изобретены, продуктовые вопросы закрыты владельцем по
процессу без утечки технических вопросов.