diff --git a/docs/reviews/SPEC-REVIEW-146-r1.md b/docs/reviews/SPEC-REVIEW-146-r1.md new file mode 100644 index 00000000..77227544 --- /dev/null +++ b/docs/reviews/SPEC-REVIEW-146-r1.md @@ -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 полностью, визуальные токены прослежены к +источнику, а не изобретены, продуктовые вопросы закрыты владельцем по +процессу без утечки технических вопросов.