Files
houseplan-card/docs/reviews/SPEC-REVIEW-146-r1.md
T
2026-08-14 13:47:32 +00:00

30 KiB
Raw Blame History

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 со ссылкой на #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, не блокирует), Low: 2 (косметика, не блокируют, оставлены с записью в этом документе). ТЗ решает заявленный сценарий J1 полностью, визуальные токены прослежены к источнику, а не изобретены, продуктовые вопросы закрыты владельцем по процессу без утечки технических вопросов.