18 KiB
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на SHAc5e14526(веткаissue/267-device-presentation-table), плюс тело issue и два комментария («Аналитика», «ТЗ готово к независимому ревью») - Вердикт: зелёный
Скоуп ревью
Первый заход — разбор полный. Проверялось:
- соответствие
docs/SCOPE.md(какой Core user job закрывает задача); - обязательные разделы ТЗ по PROCESS.md §7.1 и однозначность/доказуемость каждого AC;
- отсутствие догадок, выданных за факт — каждое «зафиксированное продуктовое
решение» (§3 ТЗ) сверено с первоисточником (
docs/USER-GUIDE.ru.md§12, issue #251/#274/#98) построчно; - измеренная база (строки/ветвления/имена функций) сверена с текущим
src/device-presentation.tsнаHEAD; - технические допущения (§20 ТЗ) — что помечено как «можно менять на ревью», не выдано за продуктовое решение;
- инструменты, на которые ссылается план тестов (
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) оценка автора и владельца, а не то, что ревьюер ТЗ обязан пересчитать с нуля при отсутствии признаков пропуска.