23 KiB
Ревью ТЗ — 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), поэтому код не проверялся.
Как проверялось
- Прочитаны
docs/SCOPE.md,AGENTS.md,PROCESS.mdцеликом. - Прочитано тело issue #179 и все три комментария (
gh issue view 179 --json ...). - Прочитан файл ТЗ целиком (534 строки).
- Архив дизайнера скачан и распакован (
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 не дают, только качественное описание).
- Сверено с текущей реализацией:
src/device-pulse.ts,src/device-presentation.ts,src/styles.ts,src/types.ts,src/houseplan-card.ts— чтобы отличить утверждения ТЗ о «текущем поведении» (раздел «До реализации») от факта, и проверить, действительно ли новая раскладка цветов континуальной пульсации — новшество. - Прочитаны релевантные фрагменты
docs/USER-GUIDE.ru.md(терминология состояний, приоритет статусов, пульсация, «Всегда статичный значок», LQI комнаты) иdocs/CONFIG-COMPATIBILITY.md,docs/TOUCH-SUPPORT.mdцеликом. - Код не запускался, гейты не прогонялись — на этапе ревью ТЗ кода нет, раздел «Гейты» 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, lockblack— все сверены построчным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.