Files
houseplan-card/docs/reviews/SPEC-REVIEW-378-r1.md
T
2026-08-29 22:17:11 +03:00

13 KiB
Raw Blame History

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. Продуктовый код не менялся и не оценивался как реализация — только как источник фактов для проверки утверждений ТЗ (существуют ли названные функции/поля, действительно ли устроено так, как описано).

Как проверялось

Читал в порядке, предписанном процессом:

  1. docs/SCOPE.md — соответствие Core user jobs. Задача расширяет уже закрытый J1/J5-механизм («лицо» устройства на плане), не создаёт новую персону и не входит в out-of-scope список. Класс изменения (A, полный трек) назван и обоснован верно: конфиг marker получает новое публичное поле, задета миграция/reference seam, несколько renderer, touch-контракт — именно тот критерий small, который называет сам автор как нарушенный.
  2. PROCESS.md §2.3–2.5, §7.1, §5 — проверка обязательных разделов ТЗ и выбора трека.
  3. Тело issue #378 и оба комментария (аналитика, затем «ТЗ готово»).
  4. docs/USER-GUIDE.ru.md (раздел «Четыре варианта „Отображение“» и «Бейдж со значением рядом с устройством», строки ~1047–1113) — сверка терминологии и уже задокументированного поведения.
  5. docs/DEVICE-PRESENTATION.md — таблица решений F09–F12, S01–S15, владение модулями (device-presentation.ts, device-presentation-policy.ts, device-face.ts).
  6. Код на 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, backend import_export.py rebind/space-transfer) для value_badge.source.ref уже реализован тем самым паттерном, который ТЗ предлагает распространить на value_source.ref — это не новый механизм, а его вторая точка подключения;
    • resolveDevicePresentation() действительно используется и основным планом, и space-card.ts/space-render.ts — утверждение AC2 «полный план и static space card показывают одно и то же» технически обосновано общим вызовом;
    • d.virtual gate в 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 → в задаче