Files
houseplan-card/docs/reviews/SPEC-REVIEW-212-r1.md
T
2026-08-20 05:21:34 +00:00

183 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-212-r1
- **Issue:** [#212](https://github.com/Matysh/houseplan-card/issues/212) — «Правки по новым иконкам»
- **ТЗ:** `docs/specs/212-device-icons-polish.md` (обычный трек, не `small`)
- **Этап:** ревью ТЗ (PROCESS.md §2.4)
- **Вердикт:** зелёный
- **Цикл:** r1/4
- **High:** 0 · **Medium (в скоупе):** 0 · **Medium (вне скоупа):** 0 · **Low:** 1 (снят с записью)
## Скоуп ревью
Issue #212 — консолидация трёх открытых задач (#22 отклик на нажатие, #154
sticky touch-hover, #181 неверный адрес документации) плюс собственные пункты
владельца: уменьшение маркера устройства на 10% и исправление формы капсулы
`display: value`. ТЗ живёт файлом `docs/specs/212-device-icons-polish.md`
(трек «обычный», не `small`) — формат файла корректен для этого трека.
Проверялось: тело и комментарии #212, тела #22/#154/#181 (полностью, без
усечения), #179 и #211 (нормативный дизайн-пакет и его принятое исправление),
`docs/SCOPE.md`, `docs/TOUCH-SUPPORT.md`, `docs/UX-MODES.md`,
`docs/CONFIG-COMPATIBILITY.md`, сам текст ТЗ целиком (§1–§22) и — построчно —
код на `dev`, который ТЗ цитирует как «подтверждённую причину» в §3 и как
основание архитектурных решений в §16.
## Как проверялось
Ревью на этапе `spec` не запускает гейты сборки/тестов — предмет проверки
это ambiguity, доказуемость AC и отсутствие выданных за факт догадок. Вместо
прогона тестов я построчно сверил каждое фактическое утверждение ТЗ о текущем
коде с самим кодом на `dev` (`ec6f77b`), поскольку §3 ТЗ прямо заявляет
«аудит текущего dev подтвердил четыре независимых дефекта» — такое заявление
обязано быть проверяемым, а не риторическим.
Сверено чтением (`grep`/`sed` по `src/styles.ts`, `src/houseplan-card.ts`,
`src/space-card.ts`, `src/hp-dialog.ts`, `src/hp-help.ts`,
`src/hp-color-opacity.ts`, `src/vacuum.ts`):
| Утверждение ТЗ | Где | Результат сверки |
|---|---|---|
| §3.1: shell = `1.26875 × core`, нет отдельного коэффициента 0.9 | `styles.ts:1877` | подтверждено; `grep` на `* 0.9` / `--global-icon-factor` — ноль совпадений |
| §3.2: `.device-core` держит `border-radius: 50%` глобально, `valonly` растит только ширину | `styles.ts:1812–1816, 1991–2001` | подтверждено — эллипс на широком value неизбежен |
| §3.2: shell использует постоянный радиус `device-shell-size/2` | `styles.ts:1939, 1955` | подтверждено |
| §3.3: `_clickDevice()` не создаёт transient feedback | `houseplan-card.ts` (нет отдельного feedback-owner рядом с dispatch) | подтверждено отсутствием |
| §3.4: `_touchSeen` — статический (глобальный) флаг, `_notePointer()` закрывает только `_tip` | `houseplan-card.ts:5765,5776–5779` | подтверждено |
| §3.4: `_hoverRoom` пишется из `mouseenter`/`mouseleave` | `houseplan-card.ts:15837–15864` | подтверждено |
| §3.5/#181: hover-контракт View описан в `UX-MODES.md`, не в `CANVAS.md` | `UX-MODES.md` «View — display and device interaction only» | подтверждено дословно |
| §7.3: Double-value pill уже имеет константу `0.39375 × core` | `styles.ts:2248` | подтверждено |
| §7.2: vacuum puck зависит от `--dev-size` | `styles.ts:2556` (`--puck-size: calc(var(--dev-size)…)`) | подтверждено |
| §16: перечисленные файлы (`device-face.ts`, `space-render.ts`, `hp-device-preview.ts`, `hp-dialog.ts`, `hp-help.ts`, `hp-color-opacity.ts`, `space-card.ts`) существуют; `pointer-modality.ts` — новый | `ls src/` | подтверждено |
| §13: единственный `:hover` в `space-card.ts` — `.hp-static-btn:hover` | `space-card.ts:903` | подтверждено |
| §13: по одному `:hover` в `hp-dialog.ts`/`hp-help.ts`/`hp-color-opacity.ts` (`.close`, `.trigger`) | построчный `grep` | подтверждено |
| §19.1: названные smoke-файлы существуют, «device action feedback smoke» — новый, не переиспользование `smoke_feedback_v2.mjs` | `ls demo/`, чтение `smoke_feedback_v2.mjs` | подтверждено; `smoke_feedback_v2.mjs` вообще не про device-маркеры (комнатная gear-кнопка) — ТЗ верно не сослалось на него |
| §4: версия пакета «1.1.1» | `docs/specs/179-…md`, `docs/specs/211-…md`, `docs/TESTING.md:342` | подтверждено предыдущими принятыми ТЗ, не изобретено |
| 44×44 CSS px как уже существующий пол, а не новое требование | `styles.ts:1939` (`max(44px, var(--device-shell-size))`) | подтверждено |
Все проверенные фактические утверждения ТЗ о текущем состоянии кода и
документации оказались точными. Раздел §3 «Проблема и подтверждённая причина»
не содержит ни одной догадки, выданной за факт, — каждый пункт воспроизводится
в реальном коде по указанным строкам.
Дополнительно сверены структурные требования PROCESS.md §7.1: сценарий,
«что человек увидит», проблема, скоуп/не-скоуп, поведенческий контракт, UX,
данные/миграция/i18n, AC1…AC16 с указанием способа доказательства, план
тестов, риски, откат, release-артефакты — все разделы присутствуют, ни один
не сведён к формальной заглушке. Блок §22 «принятые технические
предположения» оформлен явно и содержит только технические (не продуктовые)
решения — ни один из семи пунктов не подменяет продуктовое решение техническим
языком.
## Находки
### Low-1 — межевой случай «plan-wide setting» из #22 закрыт молча, без явной пометки
- **Файл:** `docs/specs/212-device-icons-polish.md`, §6 «Не входит в задачу»
- **Что происходит:** оригинальный текст #22 (полностью поглощаемого этим ТЗ)
перечисляет ожидаемое поведение как «a subtle scale dip or flash,
**plan-wide setting**, only on devices that actually act, reduced-motion
fallback». Формулировка «plan-wide setting» синтаксически допускает два
прочтения: (a) эффект применяется единообразно по всему плану, без
per-marker переопределения — то, что фактически описывает ТЗ в §10; или
(b) запрос на новый пользовательский переключатель уровня плана
(вкл/выкл или интенсивность). ТЗ выбирает (a) молча и одновременно явно
исключает (b) в §6 («новый пользовательский toggle интенсивности/
длительности feedback»), не фиксируя, что здесь вообще была развилка.
- **Почему не Medium.** Это ровно тот тип вопроса, который по PROCESS.md
§7.1 адресуется владельцу («какой объём видимых изменений входит в этот
issue»), но: (1) §4 ТЗ прямо и обоснованно ставит текст #212 выше текста
#22 по приоритету, а сам #212 (написанный позже, тем же владельцем)
перечисляет пять конкретных пунктов и ни словом не упоминает настройку;
(2) §15 ТЗ уже держит открытым предохранитель — если реализация всё же
потребует новую строку/переключатель, это автоматически трактуется как
расширение скоупа с отдельным решением владельца, то есть риск «тихо
потерять требование навсегда» отсутствует.
- **Решение ревьюера:** снимается с записью. Автору стоит одной строкой в
§22 зафиксировать, что «plan-wide setting» прочитано как «единая политика
на весь план, не per-marker», а не как новый UI-переключатель — это
ничего не меняет по существу, но убирает тихое разночтение источника,
которое иначе выглядит как то самое «оставили в тексте, а не решили».
Других находок — ни High, ни Medium, ни дополнительных Low — не выявлено.
## Что проверено и корректно
- **Формат трека.** Issue не помечен `small`/`trivial` (сложность и риск 8/10
по аналитике владельца) — файл в `docs/specs/` обязателен и создан
корректно, `small`-путь неприменим.
- **Продуктовые разделы.** §1 «Сценарий» называет персону из `docs/SCOPE.md`
(домочадец на телефоне/панели/компьютере, админ в редакторе устройств) и
момент («нажимает на устройство»); §2 «Что человек увидит» — одной парой
фраз, без терминов реализации (не упоминает `--dev-size`, resolver,
pointermodality и т.п.).
- **Обоснование скоупа по SCOPE.md.** Задача закрывает J1/J2/J7 (спойлер
плашки для длинного значения читается хуже — J1; отклик на нажатие — J3;
честный touch-контракт — обязательное требование `TOUCH-SUPPORT.md`, а не
отдельная строка SCOPE, но сам документ называет touch-only дефект View
продуктовым багом). Ничего из «Out of scope» SCOPE.md не задето: цвета,
lock-инвариант, glyph viewport, LQI threshold — везде явно исключены в §6.
- **Lock-инвариант не тронут.** §6 явно исключает изменение lock security;
§9 явно исключает feedback для «secure/no-target/unavailable/missing/
unsupported no-op»; confirmation-путь (§9) требует повторной валидации
target при подтверждении — согласуется с параграфом «The lock invariant»
`docs/SCOPE.md` (единственная санкционированная поверхность — кнопка
Unlock/Lock внутри открытой карточки, а не тап по плану).
- **Правильный канонический адрес документации.** §19.4 обновляет
`docs/UX-MODES.md` (не `docs/CANVAS.md`) для hover/pressed-владения —
именно то, что требует #181, с явной фразой «`docs/CANVAS.md` не
обновляется ради hover ownership».
- **AC1…AC16.** Каждый критерий сформулирован как проверяемое числовое или
структурное утверждение (допуски `±0.005`, `±20 ms`, `≤0.5 CSS px`,
`44×44 CSS px`) и несёт способ доказательства (`unit`/`smoke`/`golden`/
`code review`/`performance profile`), как того требует DoR (PROCESS.md
§2.5). Ни один AC не сформулирован через реализацию так, что критерий
нельзя было бы фальсифицировать независимо от кода.
- **Данные/миграция/i18n.** §15 корректно фиксирует отсутствие новых
config/localStorage/backend-полей и отсутствие миграции; совместимость с
`docs/CONFIG-COMPATIBILITY.md` не требует новой записи, так как round-trip
не меняется — проверено чтением раздела «Status meanings» в этом документе.
- **Touch-контракт.** Трейлер `Touch editor: best effort / intentionally
degraded` относится к редакторам (Device/Plan/Background остаются
desktop-first, §14), тогда как View/kiosk-touch — обязательная,
release-blocking часть скоупа (AC8–AC12) в полном соответствии с
`docs/TOUCH-SUPPORT.md` («A touch-only failure in View is a product
defect»). Формат трейлера соответствует одному из трёх канонических
значений документа.
- **Откат.** §20 указывает на одиночный revert реализации без миграции
данных и без storage rollback — согласуется с «Migration не нужна» (§15).
- **Никаких искусственно вычисленных «Fresh»-цифр.** Числа 0.90/0.95/200 ms
трассируются либо к телу #212 (5%, 0.2s), либо к уже принятому ТЗ #22
(перенос контракта eligibility/confirmation/reduced-motion, §4), а не
придуманы автором ТЗ.
- **Единственный открытый вопрос не требуется.** Явных продуктовых развилок,
которые нельзя было бы разрешить чтением #212/#22/#154/#181 и уже принятых
#179/#211, не найдено — комментарий аналитики владельца прямо закрывает
этот путь («Продуктовых вопросов не осталось»), а единственная найденная
двусмысленность (Low-1) не поднимается до уровня, требующего его участия.
## Чего не проверял
- Не запускал `npx tsc --noEmit`, `npm test`, `npm run build` — на этапе
`spec` кода ещё нет, эти гейты относятся к код-ревью (PROCESS.md §2.7) и
реализации (§2.6).
- Не запускал browser-смоки/golden — они тоже принадлежат циклу реализации
и код-ревью, а не ревью ТЗ.
- Не проверял `custom_components/**/*.py` — ТЗ прямо заявляет отсутствие
изменений бэкенда и новых config-полей (§15), backend-путей в
«Ожидаемые файлы» (§16) нет.
- Не оценивал реализуемость 200 ms/0.95 анимации в конкретном браузерном
движке (WAAPI vs CSS transition) — это явно помечено в §22 как «принято
предположительно, ревьюер вправе изменить свободно» и будет предметом
код-ревью, а не ревью ТЗ.
## Итог
`docs/specs/212-device-icons-polish.md` — самодостаточное, добросовестно
проверяемое ТЗ: каждое фактическое утверждение о текущем коде подтверждено
построчной сверкой, каждый AC проверяем и несёт способ доказательства, скоуп и
не-скоуп аккуратно очерчены и не задевают lock-инвариант или другие
ограничители `docs/SCOPE.md`, канонический адрес документации (#181)
исправлен верно. Единственная находка — Low, снимается с запиской автору, не
блокирует переход в «Готово к разработке».
**Вердикт: зелёный · цикл r1/4 · High: 0 · Medium: 0 в задаче**