Files
houseplan-card/docs/reviews/SPEC-REVIEW-179-r1.md
T
2026-08-19 19:05:01 +00:00

23 KiB
Raw Blame History

Ревью ТЗ — issue #179, цикл r1

  • Issue: https://github.com/Matysh/houseplan-card/issues/179
  • ТЗ: docs/specs/179-device-icons-redesign.md (коммит 517a710, ветка issue/179-device-icons-redesign)
  • Трек: обычный (аналитика явно исключила small/trivial)
  • Вердикт: жёлтый · цикл r1/4 · High: 0 · Medium: 5 (все в скоупе задачи) · Low: 2

Скоуп ревью

Проверялось только ТЗ как артефакт этапа S4-spec-review: обязательные разделы §7.1 PROCESS.md, однозначность и доказуемость AC1…AC14, соответствие docs/SCOPE.md (J1/J2/J7), docs/USER-GUIDE.ru.md, docs/CONFIG-COMPATIBILITY.md, docs/TOUCH-SUPPORT.md, а также фактическое соответствие ТЗ нормативному источнику — архиву дизайнера, приложенному к issue #179. Продуктовый код не существует (issue не дошёл до S5-ready), поэтому код не проверялся.

Как проверялось

  1. Прочитаны docs/SCOPE.md, AGENTS.md, PROCESS.md целиком.
  2. Прочитано тело issue #179 и все три комментария (gh issue view 179 --json ...).
  3. Прочитан файл ТЗ целиком (534 строки).
  4. Архив дизайнера скачан и распакован (curl по ссылке из тела issue, sha256sum совпал с указанным в ТЗ 63670C73...), чтобы проверить не только внутреннюю согласованность ТЗ, но и его фактическое соответствие нормативному источнику — это единственный способ отличить решение владельца от догадки, выданной за факт:
    • SPECIFICATION.md, ACTIVE_ANIMATION_SPEC.md, DEVELOPER_HANDOFF.md, README.md, PACKAGE_ANNOTATION.txt, manifest.json — построчно;
    • геометрия и цвета — вычислены из реальных SVG (viewBox, <rect>, <path> координаты circle-shell, fill/stroke, feColorMatrix);
    • тайминги анимаций — вычислены из @keyframes/animation в Animated/Light/*.svg (текстовые документы архива тайминги короткого события и alert не дают, только качественное описание).
  5. Сверено с текущей реализацией: src/device-pulse.ts, src/device-presentation.ts, src/styles.ts, src/types.ts, src/houseplan-card.ts — чтобы отличить утверждения ТЗ о «текущем поведении» (раздел «До реализации») от факта, и проверить, действительно ли новая раскладка цветов континуальной пульсации — новшество.
  6. Прочитаны релевантные фрагменты docs/USER-GUIDE.ru.md (терминология состояний, приоритет статусов, пульсация, «Всегда статичный значок», LQI комнаты) и docs/CONFIG-COMPATIBILITY.md, docs/TOUCH-SUPPORT.md целиком.
  7. Код не запускался, гейты не прогонялись — на этапе ревью ТЗ кода нет, раздел «Гейты» PROCESS.md §8 к этому циклу неприменим.

Находки

M1 — цвет внешней тени искажён относительно нормативного источника

Файл: docs/specs/179-device-icons-redesign.md:122-123

ТЗ утверждает для light-shell на референсе 56 px: 0 1px 2px rgb(0 0 0 / 12%) и 0 4px 8px -1.07px rgb(0 0 0 / 18%) — чистый чёрный.

Нормативный источник (SPECIFICATION.md, таблица §8) даёт другой цвет: 0 1px 2px 0 rgb(37 40 45 / 12%) и 0 4px 8px -1.07px rgb(37 40 45 / 18%). Это подтверждается самим SVG: feColorMatrix в Light/Icon Default.svg задаёт 0.145098 / 0.156863 / 0.176471 — это ровно 37/255, 40/255, 45/255, то есть тёплый тёмно-серый #25282D, а не #000000.

По порядку приоритета самого ТЗ (§3: решения владельца → текстовые документы архива → SVG-примеры → текущая реализация) текстовый документ и SVG совпадают между собой и расходятся с ТЗ; решения владельца по этому пункту нет. Это не разночтение источников, а фактическая ошибка транскрибирования в самом ТЗ.

Почему это важно: AC1 обещает golden-эталон 32/56/96 px по геометрии и токенам пакета. Реализация по тексту ТЗ буквально даст на глаз более резкую, холодную тень, чем в дизайне, и это will пройти собственный (неверный) golden, проверяющий не то, что задумано.

Требуется: заменить rgb(0 0 0 / …) на rgb(37 40 45 / …) в §7.1 (обе тени, 56 px референс; проверить также, что при масштабировании на 32/96 px в реализации используется тот же цвет).

M2 — цвет continuous-пульсации для reason presence не определён

Файл: docs/specs/179-device-icons-redesign.md:222-230 (§10.1), :129-144 (§7.2)

Пакет содержит три статических и три анимированных варианта Continuous Working: Default (обводка #0C82F0), Yellow (#F0A00C), Green (#1DC21D). SPECIFICATION.md §4 перечисляет источники непрерывной работы: «свет, вентилятор, климат, уборка, присутствие» — но ни один текстовый документ архива (SPECIFICATION.md, ACTIVE_ANIMATION_SPEC.md, DEVELOPER_HANDOFF.md) не говорит, какому из трёх цветов соответствует какая причина. Green нигде не привязан к семантике — ни в архиве, ни в ТЗ.

Это не абстрактная тонкость: в текущей реализации resolveDevicePulse (src/device-pulse.ts:93-103) уже различает три причины continuous-пульсации — presence, transition, running — но все три сегодня рисуются одним переданным цветом, без различия по причине. Пакет впервые вводит три разных цветовых варианта того же мотива, а ТЗ §7.2/§10.1 говорит только «цвет соответствует resolved ordinary activity», не решая: (а) вводится ли новое различение по reason (presence → зелёный, как логично предположить по тексту пакета) или (b) Green-ассет в этой итерации не используется вовсе.

AC8 требует, чтобы «цвета соответствовали §10» — при текущем тексте §10 это непроверяемо для presence-пульсации, потому что нормативного значения нет ни в одном документе.

Требуется: явное решение (не владельца — это техническое/дизайн-решение, которое ТЗ вправе принять само, см. PROCESS.md §7.1 "всё, чего пользователь не наблюдает, агенты решают сами"; здесь пользователь наблюдает результат, но выбор между двумя понятными вариантами не требует владельца) — либо зафиксировать маппинг presence → Green, либо явно исключить Green из поставки с пометкой «принято предположительно» в §20.

M3 — easing для Short и Alert не зафиксирован

Файл: docs/specs/179-device-icons-redesign.md:232-246 (§10.2, §10.3)

§10.1 (Continuous) корректно и проверяемо квотирует cubic-bezier(.45,.05,.55,.95) — совпадает с ACTIVE_ANIMATION_SPEC.md и с working-ring/working-core-blue в Animated/Light/Continuous Working Default Animated.svg. §10.2 (Short) и §10.3 (Alert) дают duration, delay, scale, stroke, цикл — но не easing. В реальных SVG (Animated/Light/Short Activity Animated.svg, Animated/Light/Alert Animated.svg) обе анимации используют cubic-bezier(.22,.61,.36,1) — другую кривую, чем Continuous. ACTIVE_ANIMATION_SPEC.md этот параметр текстом тоже не называет, только качественно («цикл должен быть плавным»), то есть источник факта — только SVG-пример (третий приоритет по ТЗ), и раз он существует и однозначен, ТЗ должен был его перенести.

AC8 обещает «timings ... и colors соответствуют §10» — без easing реализация по умолчанию получит линейное или произвольное ускорение, что даст другое ощущение движения и не пройдёт визуальную приёмку по духу задачи ("читаемое глазами" состояние).

Требуется: добавить easing: cubic-bezier(.22,.61,.36,1) в §10.2 и §10.3.

M4 — стоимость и выбор backdrop-blur для Dark не решены

Файл: docs/specs/179-device-icons-redesign.md:125-127 (§7.1), §17 (риски), AC13

manifest.json прямо объявляет "backdropBlurPx": 20 для темы Dark, и Dark/Icon Default.svg реально содержит foreignObject с backdrop-filter:blur(20px) — а не просто SVG-тень. Пакет отдельно поставляет Dark/Icon Default No Blur.svg, который побайтно совпадает с Icon Default.svg за вычетом именно этого слоя (проверено diff'ом), и, в отличие от Light-алиаса, не помечен в manifest.json как deprecated/aliasOf — то есть это не забытый дубликат, а отдельный, специально сохранённый fallback.

ТЗ §7.1 ограничивается «Dark использует неизменённую dark-ревизию пакета… эталонные эффекты» — не решая, войдёт ли backdrop-filter: blur(20px) на каждый маркер в продакшен, или используется No-Blur вариант. Это не мелочь: backdrop-filter — дорогая по композитингу операция; docs/SCOPE.md называет 20–200 устройств на план и киоск-планшет как основную View-поверхность для двух из трёх персон (docs/TOUCH-SUPPORT.md: View/kiosk — блокирующие). В src/ сегодня backdrop-filter не применяется ни к одному маркеру (только к editor-tray, src/editor-secondary.styles.ts), так что это действительно новая для маркеров нагрузка, а не продолжение существующей. AC13 («нет per-frame JS, ResizeObserver…») не упоминает composite-cost blur-слоёв, и §17 «Риски» этот риск не называет вовсе.

Требуется: явное решение — либо применять blur (тогда назвать это в §17 и покрыть производительность в AC13/perf-профиле), либо взять готовый No-Blur Dark-ассет как основной рендер продакшена (быстрее, безопаснее для 200 устройств), с записью решения и причины.

M5 — не назван обязательный тег Touch editor: из TOUCH-SUPPORT.md

Файл: docs/specs/179-device-icons-redesign.md (документ целиком)

docs/TOUCH-SUPPORT.md, раздел «Documentation rule»: «New editor feature specifications and code reviews must state one of: Touch editor: supported; Touch editor: best effort / intentionally degraded; Touch editor: not exposed.» ТЗ меняет интерактивный маркер (shell, hit area, focus/keyboard) на поверхности Device editor (§11: «Интерактивные маркеры в View/kiosk и Device editor получают tabindex="0"…»), то есть подпадает под правило, но нигде в документе буквально не заявляет ни один из трёх канонических тегов. Канонический документ подсистемы даёт готовый ответ («Device editor: Best effort» — из таблицы контракта), ТЗ просто не переносит его явным образом.

Требуется: добавить одну строку вида Touch editor: best effort / intentionally degraded (или supported, если автор считает keyboard/hit-area изменения полностью бесплатными для touch — решение автора, не владельца) в раздел §11 или §17.

L1 — Light-only combo-эталоны (Selected + Hover/Working/Green/Alert,

Focus + Alert) не отражены в golden-матрице

manifest.json для Light содержит пять составных QA-эталонов (Selected + Hover.svg и т.д.), у Dark таких файлов нет вовсе (не только не хватает файлов — узлы Figma для них не заведены). §16.4 (golden matrix) перечисляет одиночные состояния, но не комбинации Selected/Focus с semantic state отдельно для Dark. Поскольку приоритет слоёв (§7.3) декларативный и реализуется одним рендерером, а не набором ассетов, это не блокирует разработку — но стоит явно зафиксировать в golden-матрице, что комбинации проверяются для обеих тем логически, а не по образцу (в Dark образца нет). Снимается автором с записью либо на одну строку в §16.4.

L2 — избыточная оговорка «unavailable hover запрещён» для static_icon

§7.3 говорит про static_icon: «на интерактивной поверхности он сохраняет разрешённые hover/focus/click; unavailable hover запрещён». Действующее поведение static_icon, зафиксированное в docs/USER-GUIDE.ru.md:811-816, таково, что этот режим вообще не входит в ветку unavailable («не показывает… недоступность»). Формулировка ТЗ не противоречит этому, но звучит как отдельное правило для случая, который по документированному контракту не может произойти. Не блокирует; можно оставить как защитную формулировку или убрать как мёртвый кейс — на решение автора.

Проверено и признано корректным

  • SHA-256 архива (63670C73E25D1E59DDAF1BE236F3D7F2FAC827B9B5D6DD4B77125EA9BC012025) совпадает с реально скачанным файлом по ссылке из issue.
  • Геометрия shell/core: круговой shell — путь-круг с центром (63.5, 55.5) и радиусом 50.75 (диаметр 101.5), core — <rect> 80×80 с тем же центром. 101.5/127 ≈ 0.799, 80/101.5 ≈ 0.788 — оба числа в ТЗ (§7.1) подтверждены вычислением по реальному SVG, не переписаны на глаз.
  • Цвета состояний (§7.2): hover/focus #0C82F0, active/working/unlocked #F0A00C, alert #F0410C, unavailable core #B5BAC1, high LQI #1DC21D, lock black — все сверены построчным grep по fill/stroke в соответствующих Light SVG и совпадают.
  • Continuous/Short/Alert тайминги (кроме easing Short/Alert — см. M3): 3.6 с / scale 1→1.5 / opacity .55→0 для Continuous и 1.1 с × 3 с задержками 0/1.1/2.2 с для Short — оба подтверждены и текстовым документом архива, и реальными @keyframes.
  • Обнаруженная и верно разрешённая ошибка пакета: Light/Zigbee LQI Low.svg действительно закрашен #F0A00C (тот же цвет, что Mid), а не красным. Утверждение ТЗ §9/§20.2 о том, что это ошибка экспорта и что production-эталон — red по текстовой спецификации и решению владельца, подтверждено — это пример правильно задокументированного разрешения конфликта источников, а не догадки.
  • LQI-границы 40/180 согласуются с уже существующей документированной границей комнатного LQI-градиента (docs/USER-GUIDE.ru.md:873: «градиент от красного (≤40) до зелёного (≥180)») — решение владельца не произвольно, оно продолжает существующий продуктовый факт.
  • Секьюрность: §11 явно исключает новый путь обхода подтверждения для замков/клапанов через клавиатуру — Enter/Space вызывают тот же существующий click handler. Инвариант docs/SCOPE.md («никогда не по тапу без подтверждения») не нарушается ни в одном пункте ТЗ.
  • Совместимость (§12): поведение "Open → Save не материализует ripple_size/ripple_color при смене UI-default 3→1.5" явно и правильно учитывает существующий паттерн записи (d.rippleSize !== 3 ? … : null в src/houseplan-card.ts:18448) и предотвращает тихий сброс уже явно сохранённых значений.
  • Обязательные разделы §7.1 PROCESS.md — все присутствуют: сценарий, что видит пользователь до/после, проблема, скоуп/не-скоуп, контракт поведения, UX/доступность, данные/миграция, i18n, AC1…AC14 с доказательством, план автотестов, риски, откат, release-артефакты.
  • Трассируемость: docs/specs/README.md обновлён в том же коммите с двусторонней ссылкой issue↔ТЗ; коммит 517a710 несёт корректные трейлеры Issue: #179 и User-Visible: no.
  • Открытых продуктовых вопросов владельцу нет — все решения, требующие продуктового выбора (unavailable hover, приоритет pulse-настроек, границы LQI, клавиатура), приняты владельцем в комментарии аналитики и перенесены в §4 ТЗ без искажений.

Чего не проверял

  • Figma-фреймы (99:1290, 104:1539) напрямую не открывались — ревью ограничено содержимым архива, доступного по ссылке из issue; это тот же источник, которым пользовался автор ТЗ.
  • Index.html/PNG-визуализации архива не открывались в браузере — ревью на этапе ТЗ смотрит на текст и код, не на артефакт для дизайнеров; визуальная проверка релевантна golden-этапу code review.
  • Полная построчная сверка размеров 32 px и 96 px (только 56 px эталон проверен вычислением; 32/96 в ТЗ не квотируются отдельно, они делегированы таблице SPECIFICATION.md §8 без искажения).
  • Производительность не измерялась (кода нет); M4 — это находка на уровне решения, а не измеренный факт деградации.
  • Гейты typecheck/test/build/smoke/golden не запускались — на этапе ревью ТЗ продуктового кода не существует, раздел неприменим.

Итог

Ни одна находка не блокирует по существу — все пять Medium устранимы точечными правками текста ТЗ (это ревью самого текста, а не архитектурного решения), но без них AC1, AC8 и AC13 не выполняют собственное обещание доказуемости («соответствует §10», «соответствует пакету», «нет неизвестных perf-рисков»). Вердикт — жёлтый, возврат автору в рамках текущего issue.