13 KiB
SPEC-REVIEW-378-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/378
- Этап: ТЗ на ревью (
S4-spec-review), заход r1, блокирующих циклов израсходовано 0/4 - Артефакт ТЗ:
docs/specs/378-value-face-source.md - SHA материала:
7d5d006cccb5eeb36f2ecc92dabd124b7d137e32(веткаissue/378-value-face-source, HEAD на момент ревью) - Ревьюер: Claude (роль «ревьюер ТЗ»), сессия без контекста автора
- Первый раунд — раздел «Унаследовано из r» неприменим, разбор полный.
Скоуп ревью
Оценивался только этап ТЗ: тело issue #378, аналитический комментарий владельца
(2026-08-29T18:37:48Z) и файл docs/specs/378-value-face-source.md на коммите
7d5d006c. Продуктовый код не менялся и не оценивался как реализация — только
как источник фактов для проверки утверждений ТЗ (существуют ли названные
функции/поля, действительно ли устроено так, как описано).
Как проверялось
Читал в порядке, предписанном процессом:
docs/SCOPE.md— соответствие Core user jobs. Задача расширяет уже закрытый J1/J5-механизм («лицо» устройства на плане), не создаёт новую персону и не входит в out-of-scope список. Класс изменения (A, полный трек) назван и обоснован верно: конфиг marker получает новое публичное поле, задета миграция/reference seam, несколько renderer, touch-контракт — именно тот критерийsmall, который называет сам автор как нарушенный.PROCESS.md§2.3–2.5, §7.1, §5 — проверка обязательных разделов ТЗ и выбора трека.- Тело issue #378 и оба комментария (аналитика, затем «ТЗ готово»).
docs/USER-GUIDE.ru.md(раздел «Четыре варианта „Отображение“» и «Бейдж со значением рядом с устройством», строки ~1047–1113) — сверка терминологии и уже задокументированного поведения.docs/DEVICE-PRESENTATION.md— таблица решений F09–F12, S01–S15, владение модулями (device-presentation.ts,device-presentation-policy.ts,device-face.ts).- Код на SHA
7d5d006c— не как реализация (её нет), а как проверка того, что ТЗ не выдаёт догадку за факт:src/device-value-badge.tsцеликом —VALUE_BADGE_ATTRIBUTES,valueBadgeCandidates(),valueBadgeSourceKey/FromKey(),resolveSource(), unavailable-контракт, кэш кандидатов;src/device-presentation.ts:449-547—validStateValue(),resolveValue(), climate/power-gate/ambiguity эвристика,signatureOf(),presentationSourceSignature();src/houseplan-editor-runtime.ts(грепом) — существующий selector value badge в диалоге (строки ~12586-12633),innerValueSourceKey(12122, сравнение сbadgeSourceKeyдля duplicate-hint, 12611),_valueBadgeForBinding(11904-11922, сброс/рекомендация источника при смене binding);custom_components/houseplan/validation.py:805-865—validate_marker_value_badges, доступные коды ошибок;custom_components/houseplan/import_export.py:1090-1111, 1344-1391— rebind и space-transfer дляvalue_badge.source.ref;src/devices.ts:868-889—rewriteMarkerControlReferences(), уже переписывающийvalue_badge.source.refпри смене marker id;docs/specs/README.md:116— ссылка issue ↔ ТЗ на месте;- четыре файла
src/i18n/*.json— паритет локалей, на которые претендует ТЗ.
Гейты (typecheck/test/build/golden и т.д.) на этом этапе не запускались: кода нет, реализации нет, оценивать нечего. Это ожидаемо для этапа spec-review и не является пропуском гейта.
Находки
Блокирующих (High) и Medium-находок нет.
Low — не блокирует, снимаю с записью. Контракт п.1.6 («При явной смене
HA binding в диалоге старый value_source сбрасывается в auto») асимметричен
уже существующему поведению соседнего селектора: _valueBadgeForBinding()
(src/houseplan-editor-runtime.ts:11904-11922) при смене binding не просто
сбрасывает value_badge.source в null, а вызывает recommendedValueBadgeSource()
и подставляет новую рекомендацию для только что привязанного устройства. ТЗ же
предписывает для value_source только сброс в auto, без аналогичной
рекомендации. Это может привести к тому, что при рebind марказы на другой cover
бейдж рядом получит разумную рекомендацию (current_position), а лицо значения
откатится к слову состояния — ровно к той проблеме, которую issue закрывает.
Решение не ошибочно (объяснение «перенос источника прежнего устройства был бы
ложной настройкой» состоятельно и последовательно с общим принципом «явный
источник не заменяется автоматически»), и это техническое, не продуктовое
решение (PROCESS.md §7.1: где строится дефолт после rebind — не то, что
спрашивают владельца). Оставляю как решение автора, но фиксирую для внимания
на код-ревью AC8: если реализация всё же скопирует recommendedValueBadgeSource-
паттерн для симметрии, это не должно расцениваться как отход от ТЗ.
Что проверено и корректно
- Полнота обязательных разделов §7.1: сценарий, что человек увидит до/после, проблема, скоуп/не-скоуп, контракт поведения, UX, модель данных и миграция, i18n, AC1–AC10 с доказательством, план автотестов, риски, откат, release-артефакты — присутствуют все, в этом порядке.
- Продуктовые разделы отвечают на два обязательных вопроса: персона
(администратор дома, desktop, диалог устройства), поверхность и момент
встречи, а также «что человек увидит до/после» одной фразой без терминов
реализации (
Open→42 %) — до раздела с реализацией, как требует канон. - Выбор полного трека обоснован явно названным нарушенным критерием (публичное поле конфига + reference seam + несколько renderer + touch), а не общим «сложно».
- Ни одного факта не оказалось выдумкой. Каждое техническое утверждение
ТЗ проверено против реального кода на SHA
7d5d006cи подтвердилось:VALUE_BADGE_ATTRIBUTES,valueBadgeCandidates(), unavailable-контракт — существуют буквально как описаны;- общий resolver/formatter для лица и бейджа — реалистичная цель: уже есть
частичное сближение (
innerValueSourceKeyв редакторе уже сравнивается с ключом бейджа для duplicate-hint, т.е. инфраструктура для п.2.8 контракта существует, а не изобретается с нуля); - reference seam для
derived_marker_state(rewriteMarkerControlReferences, backendimport_export.pyrebind/space-transfer) дляvalue_badge.source.refуже реализован тем самым паттерном, который ТЗ предлагает распространить наvalue_source.ref— это не новый механизм, а его вторая точка подключения; resolveDevicePresentation()действительно используется и основным планом, иspace-card.ts/space-render.ts— утверждение AC2 «полный план и static space card показывают одно и то же» технически обосновано общим вызовом;d.virtualgate вresolveValue()срабатывает раньше любого другого условия — контракт п.2.6 («virtual имеет приоритет над сохранённым источником») соответствует реальному порядку проверок, изменений в этой ветке не требуется;- F09–F12 и связанные ID из
docs/DEVICE-PRESENTATION.mdиспользованы точно, без придумывания несуществующих строк таблицы; - i18n-паритет заявлен для ровно тех четырёх локалей, что есть в дереве
(
en/ru/de/fr), лишних/недостающих не названо.
- Не-скоуп совпадает 1:1 с разделом «Не входит» тела issue: новый display mode, изменение самого списка атрибутов, изменение бейджа, перенацеливание tap-действия — везде явно исключено и не просачивается в контракт.
- Каждый AC называет способ доказательства (unit/backend/smoke/golden/ docs gate/commands), ни одного голого «проверено».
- Откат безопасен и объяснён на уровне данных: старый frontend/backend с
ALLOW_EXTRAпропустит поле, данные не теряются — соответствует моделиdocs/CONFIG-COMPATIBILITY.md(lossless / untouched future literal). - Открытых продуктовых вопросов нет, и это обоснованно: сценарий, объём
видимого изменения и границы деградации (unavailable →
—, virtual → икона) зафиксированы однозначно самим ТЗ, а не додуманы — раздел «Принято предположительно» содержит только техническое (имя поля, извлечение модуля, структура smoke-теста), что и требуется §7.1.
Чего не проверял
- Не запускал
npx tsc --noEmit/npm test/npm run build— на этапе spec-review код ещё не написан, гейты неприменимы. - Не проверял
MARKER_SCHEMA(validation.py:1730) построчно на предмет того, как именно будет описанvalue_sourceв схеме — это код-ревью реализации, не предмет ТЗ. - Не оценивал производительность нового обхода
valueBadgeCandidates()в реальном большом доме — раздел «Производительность» в ТЗ ссылается на существующий кэш и budget-гейт, этого достаточно для этапа spec. - Не проверял golden/скриншоты — они не существуют до реализации.
Вердикт
Вердикт: зелёный · заход r1 · блокирующих циклов 0/4 · High: 0 · Medium: 0 → в задаче