mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 05:41:34 +00:00
172 lines
18 KiB
Markdown
172 lines
18 KiB
Markdown
# SPEC-REVIEW-267-r1
|
||
|
||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/267
|
||
- **Этап:** ТЗ на ревью (PROCESS.md §2.4)
|
||
- **Заход:** r1 · блокирующих циклов израсходовано 0 из 4 (первый заход)
|
||
- **Материал:** `docs/specs/267-device-presentation-decision-table.md` на SHA
|
||
`c5e14526` (ветка `issue/267-device-presentation-table`), плюс тело issue и
|
||
два комментария («Аналитика», «ТЗ готово к независимому ревью»)
|
||
- **Вердикт: зелёный**
|
||
|
||
## Скоуп ревью
|
||
|
||
Первый заход — разбор полный. Проверялось:
|
||
|
||
1. соответствие `docs/SCOPE.md` (какой Core user job закрывает задача);
|
||
2. обязательные разделы ТЗ по PROCESS.md §7.1 и однозначность/доказуемость
|
||
каждого AC;
|
||
3. отсутствие догадок, выданных за факт — каждое «зафиксированное продуктовое
|
||
решение» (§3 ТЗ) сверено с первоисточником (`docs/USER-GUIDE.ru.md` §12,
|
||
issue #251/#274/#98) построчно;
|
||
4. измеренная база (строки/ветвления/имена функций) сверена с текущим
|
||
`src/device-presentation.ts` на `HEAD`;
|
||
5. технические допущения (§20 ТЗ) — что помечено как «можно менять на
|
||
ревью», не выдано за продуктовое решение;
|
||
6. инструменты, на которые ссылается план тестов (`scripts/mutation-gate.mjs
|
||
--check`/`--id=`, `scripts/smoke-select.mjs`, существующие smoke-файлы),
|
||
существуют и поддерживают заявленный интерфейс.
|
||
|
||
## Как проверялось
|
||
|
||
| Что | Команда/действие | Результат |
|
||
|---|---|---|
|
||
| Класс изменения коммита | `git show --stat HEAD` | только `docs/specs/267-*.md` + `docs/specs/README.md` — класс C, соответствует заявленному в шапке ТЗ |
|
||
| Измеренная база §2 ТЗ | `wc -l src/device-presentation.ts`, `grep -n` на `resolvePresentationSources`/`resolveDevicePresentation` | 781 строка, функции на строках 252→404 (152) и 600→781 (181) — совпадает с заявленными 781/152/182 |
|
||
| Существование смежных модулей §7.2 | `ls src/device-*.ts` | `device-visual.ts`, `device-pulse.ts`, `device-value-badge.ts`, `device-face.ts`, `device-toggle.ts` — все существуют, как заявлено |
|
||
| Соответствие §3.2 (#251) | чтение `docs/USER-GUIDE.ru.md:914-921` | текст руководства слово в слово подтверждает решение (доступность контроллера только по своим сущностям, виртуальный контроллер доступен) |
|
||
| Соответствие §3.3 (#274) | чтение `docs/USER-GUIDE.ru.md:922-924` | подтверждено: удалённая цель не делает контроллер недоступным и не расходит план/preview |
|
||
| Соответствие §3.4 (#98, пульсации) | чтение `docs/USER-GUIDE.ru.md:945-963` | подтверждено: обычная пульсация только в «Значок + состояние и активность», статичных колец нет |
|
||
| Соответствие §3.5 (`static_icon`) | чтение `docs/USER-GUIDE.ru.md:1007-1014` | подтверждено дословно, включая исключение hover/controls/Glow/заливки |
|
||
| L05 (`hidden_design_preview`) не выдумка | `grep -rn hidden_design_preview src/ test/` | строка уже существует в `device-presentation.ts:87,728`, `i18n/ru.json:236`, покрыта `test/device-presentation.test.mjs:751` — существующее поведение, не новая догадка |
|
||
| F09–F12 (value fallback reasons) | `grep -rn value_ambiguous_sources\|value_virtual\|activity_display_disabled` | все три reason уже в коде и тестах |
|
||
| F16 (LQI-полосы 0/40, 41/179, 180+) | чтение `markerLqiBand()` (`device-presentation.ts:46-49`) | точное совпадение границ |
|
||
| Инструмент `mutation-gate.mjs --id=`/`--check` | `grep -n "'--check'" `, `grep -n "startsWith('--id="` | оба флага существуют и поддерживаются |
|
||
| Смоки, названные в ТЗ | `ls demo/smoke_*.mjs \| grep -E "wireless\|device_icon"` | `smoke_wireless_controller_parity.mjs`, `smoke_device_icon_design.mjs` существуют |
|
||
| Легаси-совместимость `ripple`→`icon_ripple` (§13) | чтение `normalizeDeviceDisplay()` (`src/logic.ts:852-856`) | подтверждено дословно |
|
||
| Связанные issues реальны и закрыты | `gh issue view 98/251/274/34` | #98, #251, #274 закрыты и по теме совпадают с описанием в ТЗ; #34 открыт как архитектурный umbrella — корректная ссылка, не блокирующая зависимость |
|
||
| Уникальность decision row ID | `grep` по `^\| L0\|^\| S0…` + `uniq -d` | дубликатов ID нет (30 строк: 6 L, 13 S, 17 F, 8 A) |
|
||
| Порядок приоритета §7.3 vs реальный код | чтение `resolveDevicePresentation()` целиком (`device-presentation.ts:600-781`) | заявленный порядок (lifecycle → static → alarm переживает live-gate → live-gate → controller availability → …) соответствует фактической последовательности веток в текущей реализации — не выдумана «идеальная» модель без связи с кодом |
|
||
| Индекс `docs/specs/README.md` | `git diff HEAD~1 HEAD -- docs/specs/README.md` | строка `#267` добавлена в том же коммите, ссылка верна |
|
||
| `node scripts/check-docs.mjs` | прогнан | `Documentation checks passed (7 files, 10 external links)` — зелёный |
|
||
| `git diff --check` на коммит спеки | `git diff --check HEAD~1 HEAD -- docs/specs/267-*.md` | **не зелёный**: `548: new blank line at EOF` (см. находки, Low) |
|
||
|
||
Гейты `typecheck`/`test`/`build` не гонялись: коммит `c5e14526` — чистый класс C
|
||
(только `docs/specs/**`), продуктовый код не менялся, `src/**` не тронут, и
|
||
`check-docs.mjs` (который зависит от `src/**`) уже прогнан и зелёный.
|
||
Браузерные смоки, golden, инварианты геометрии, backend-тесты — неприменимы:
|
||
diff не касается `src/**`/`custom_components/**` в этом заходе, это ещё
|
||
не реализация.
|
||
|
||
## Находки
|
||
|
||
### Low-1 — заявление «`git diff --check` — green» в хендоффе не совпадает с фактом
|
||
|
||
**Файл:** issue #267, комментарий «ТЗ готово к независимому ревью»
|
||
(2026-08-27T18:17:33Z).
|
||
|
||
Автор указал `git diff --check` как один из двух прогнанных чеков и заявил
|
||
результат «green». Повторный прогон `git diff --check HEAD~1 HEAD --
|
||
docs/specs/267-device-presentation-decision-table.md` даёт `548: new blank
|
||
line at EOF` (файл заканчивается двумя переводами строки вместо одного),
|
||
exit code 2. Само по себе это не искажает содержание ТЗ и не влияет ни на один
|
||
AC — `check-docs.mjs`, единственный настоящий гейт для доков в этом диффе,
|
||
зелёный. Но правило «"Verified" без названного результата не является
|
||
доказательством» работает в обе стороны: названный результат должен быть
|
||
точным. Ложноположительный «green» в хендоффе — ровно тот тип записи, который
|
||
процесс просит не допускать.
|
||
|
||
**Решение ревьюера:** снимается без правки. Косметическая лишняя пустая
|
||
строка в конце файла, не блокирует переход в `S5-ready`; будет естественно
|
||
убрана следующим коммитом по ветке (код или доп. правка спеки), отдельного
|
||
возврата на цикл не оправдывает.
|
||
|
||
### Что не является находкой (проверено и отклонено как ложное подозрение)
|
||
|
||
- **Отсутствие отдельных заголовков «UX» и «Модель данных и миграция».**
|
||
PROCESS.md §7.1 требует эти темы по содержанию, а не буквальным заголовком.
|
||
Содержание есть: §1 «Что человек увидит» прямо формулирует отсутствие
|
||
видимых изменений, §12 не-скоуп явно исключает новые статусы/цвета/иконки/
|
||
display-режимы, §13 явно закрывает данные/миграцию («отсутствуют»,
|
||
`normalizeDeviceDisplay()` остаётся read-gate). Прецедент того же жанра задач
|
||
— `docs/specs/264-resize-controller.md` — построен по не идентичному, но
|
||
сопоставимому по духу набору разделов. Не находка.
|
||
- **«Открытых продуктовых вопросов нет» при сложности 7/10.** Проверено
|
||
предметно, а не на слово: все три продуктовых вопроса из тела issue («что
|
||
видит человек при доступном контроллере/недоступной цели», «приоритет осей
|
||
при конфликте», «сколько состояний имеет право быть различимыми») закрыты
|
||
ссылкой на уже принятые решения (#251, #274, USER-GUIDE §12) и на явный
|
||
не-скоуп «новых статусов/цветов/иконок нет» — это не отказ отвечать, а
|
||
предметный ответ «поведение не меняется, только структура кода». Для
|
||
refactor-only задачи с зафиксированным контрактом это корректно, а не
|
||
недосмотр.
|
||
- **Достижимость AC2** («renderer и surfaces не принимают альтернативных
|
||
решений по raw HA state»). В `houseplan-card.ts` действительно остаются
|
||
прямые обращения к `hass.states[...]` (vacuum-телеметрия, friendly_name для
|
||
подписей/тултипов, состояние contact/lock для соседних, не face-related
|
||
элементов). AC2 ограничен явно: «Lifecycle/display/status/content/
|
||
diagnostics gates **из `resolveDevicePresentation()`**» — то есть про
|
||
вынос уже существующих внутри функции веток, а не про полный запрет чтения
|
||
`hass.states` во всём файле. Формулировка не создаёт невыполнимое
|
||
требование.
|
||
|
||
## Проверка обязательных разделов (PROCESS.md §7.1)
|
||
|
||
Сценарий и персона (§1) — есть, персона из `docs/SCOPE.md` (домашний
|
||
администратор + разработчик как второй адресат документа). Что человек
|
||
увидит до/после (§1) — есть, явно «ничего нового». Проблема (§2) — есть,
|
||
с измеренной и подтверждённой базой. Скоуп/не-скоуп (§11/§12) — есть,
|
||
не-скоуп явно закрывает риск декартова произведения и попутных продуктовых
|
||
изменений. Контракт поведения — распределён по §3 (зафиксированные решения) и
|
||
§6 (таблицы рядов), содержательно присутствует. UX/данные и миграция —
|
||
покрыты содержательно (см. «что не является находкой» выше). i18n — явно «нет
|
||
новых строк» (§13), проверено: задача не должна трогать `src/i18n/*.json`,
|
||
её и не трогает вне ТЗ. AC1…AC10 — пронумерованы, у каждого указан способ
|
||
доказательства (§15). План автотестов — есть (§16), с точным списком команд
|
||
перед S7. Риски — есть (§18), адресуют именно те риски, которые релевантны
|
||
этому рефакторингу (декартово произведение, второй resolver, plan/preview
|
||
расхождение, регрессия pixel-parity). Откат — есть (§19), корректен для
|
||
чисто структурного рефакторинга без миграции. Release-артефакты — есть
|
||
(§17), корректно `User-Visible: no`.
|
||
|
||
## Проверка AC на однозначность и доказуемость
|
||
|
||
Каждый из AC1–AC10 формулирует наблюдаемое или механически проверяемое
|
||
условие и называет способ доказательства (unit/architecture test/mutation
|
||
registry/smoke/golden/code review), без AC вида «работает корректно» без
|
||
критерия. AC4 и AC5 образуют закрытую петлю «документ ↔ fixture ↔ mutant»,
|
||
которая явно защищает от того самого дрейфа, ради которого затевается задача
|
||
(документ описывает желаемое, а не то, что тест умеет проверить). AC8 —
|
||
единственный жёсткий pixel/config-контракт рефакторинга — корректно замкнут
|
||
на существующий `golden:verify` и docs screenshot gate, а не на новое
|
||
самодельное сравнение.
|
||
|
||
Отдельно проверено на воспроизводимость до кода: порядок приоритетов §7.3
|
||
не является постулированной автором целевой моделью, оторванной от
|
||
реальности — он совпадает с фактической последовательностью условий в
|
||
`resolveDevicePresentation()` на `HEAD` (эффективная скрытость → static →
|
||
alarm переживает live-gate → live-gate → controller availability →
|
||
value/icon/pulse). Это значит рефакторинг, скорее всего, реализуем без
|
||
скрытого изменения поведения, а не переписывает контракт под видом
|
||
документирования.
|
||
|
||
## Унаследовано / предыдущие раунды
|
||
|
||
Неприменимо — это первый заход (r1) по этому этапу.
|
||
|
||
## Чего не проверял
|
||
|
||
- Реализацию (класс A/B) — её ещё нет, диапазон `S3-spec`/`S4-spec-review`
|
||
правок кода не касается.
|
||
- `typecheck`/`npm test`/`npm run build`/браузерные смоки/golden/инварианты
|
||
геометрии/backend — неприменимы к чисто документационному коммиту этого
|
||
захода; будут обязательны на код-ревью.
|
||
- Реальную достижимость AC9 (O(1) policy) и AC7 (surface parity) на уровне
|
||
производительности/поведения — это утверждения о будущей реализации,
|
||
доказываются на код-ревью, а не на этапе ТЗ.
|
||
- Полноту декомпозиции §6 (все ли нужные строки перечислены, не пропущена ли
|
||
комбинация) — не пересчитывал декартово произведение осей вручную; проверил
|
||
выборочно, что перечисленные строки соответствуют реальным веткам кода и
|
||
реальным issue-регрессиям (#251, #274), и что ID уникальны. Полнота набора
|
||
рядов — предметная (product) оценка автора и владельца, а не то, что
|
||
ревьюер ТЗ обязан пересчитать с нуля при отсутствии признаков пропуска.
|