Issue: #598 User-Visible: no
19 KiB
SPEC-REVIEW-598-r1
Issue: https://github.com/Matysh/houseplan-card/issues/598
Заголовок: Три оставшихся диалога настроек на общий набор контролов: Пространство, Общие,
Устройство (шаг 3 эпика #591)
Этап: ревью ТЗ (PROCESS.md §2.4)
Заход: r1 · блокирующих циклов израсходовано (до этого раунда) 0 из 4
Материал: тело issue #598, раздел ## ТЗ, на момент чтения 2026-09-19. Комментариев к
issue на момент ревью — 0 (gh api repos/Matysh/houseplan-card/issues/598/comments → length: 0),
поэтому раздела «Унаследовано из r0» не требуется — это первый заход.
Скоуп ревью
Ревью ТЗ, не код: код по этому issue не писался (веток issue/598-* в репозитории нет,
рабочая копия на dev, git status — чисто). Проверялась (а) полнота обязательных разделов
ТЗ по PROCESS.md §7.1, (б) однозначность и проверяемость AC1…AC10, (в) отсутствие догадок,
выданных за факт — каждое числовое или структурное утверждение ТЗ сверено с реальным деревом
src/, demo/, docs/, а не принято на слово.
Как проверялось
Технической реализации нет, поэтому «прогон гейтов» неприменим — сверка шла чтением дерева на
dev (SHA 958ab366, рабочая копия чистая) против конкретных утверждений ТЗ:
- Счётчики файлов и разметки —
wc -lтрёх диалогов иform-kit.ts(177/318/760/140 строк) — совпадают с таблицей «Что предстоит переработать» построчно. - Классы-опоры К5 —
grepпоdemo/*.mjs:.srcrowвстречается в 9 файлах (smoke_discovery_filters,smoke_ha_controls,smoke_hide_layers,smoke_room_settings,smoke_room_tooltip_toggle,smoke_space_create_display_defaults,smoke_sun,smoke_tap_run,smoke_ux_fixes),.dispsection— в 2 (smoke_backup_transfer,smoke_general_settings). Совпадает с текстом ТЗ буквально. - Существующие примитивы набора (
form-kit.ts):formCard,formRow,segmented,colorRow, лёгкий CSS-лист черезensureFormKitStyles— реально существуют и уже лежат в ленивом редакторском графе (комментарий в файле прямо называет измеренный бюджет из #594), что подтверждает К7 и AC9 не как обещание, а как состояние кода. hp-color-opacityиспользуется во всех трёх целевых файлах плюс в уже переведённомroom-settings-dialog.ts— К3 не изобретает нового компонента.hpf-switchв коде не существует (grep -n "hpf-switch" src/editors/*.ts— 0 совпадений,withSwitch: falseзашито в текущей сборке листа) — ТЗ сама называет это «принято предположительно, поменять свободно» и даёт откат («дешевле оставить.srcrowкак есть»). Корректно помечено, не выдано за решённый факт.- Мутанты
dialog-control-writes-to-a-neighbour-keyиdialog-segment-drops-radio-semanticsвscripts/mutation-registry.mjsпока не существуют, но по нему уже есть точный прецедент того же автора того же приёма —form-kit-writes-to-a-neighbour-key(патч вroom-settings-dialog.ts, стражsmoke_room_settings.mjs) иform-kit-segment-drops-radio-semantics(патч вform-kit.ts, стражtest/form-kit.test.mjs) из #594. Именование и форма ссылки на ещё не написанный мутант соответствует уже принятой в реестре практике — это не догадка, а перенос доказанного шаблона. - Все 11 поимённых golden-сцен AC7 существуют в
demo/golden/baselines/baselines-index.json(scenariosсодержит все 11 id, всего сцен в индексе — 173) — сцены не изобретены, это реальные существующие кадры, часть которых пересматривается намеренно. space.display_section/space.roomcard_sectionреально существуют и переиспользуются как заявлено; арифметика счётчика новых заголовков (5 «Общих» + 2 «Пространства» + 5 «Устройства» = 12 × 4 языка = 48 строк) подтверждается построчным разбором UX-таблиц — см. находку M1 про второй ряд той же таблицы.- Терминология «карточки-группы» уже используется
docs/USER-GUIDE.ru.md:572для диалога комнаты (#594) — ТЗ #598 не изобретает новый термин интерфейса, а продолжает принятый. - 44 px в К6/AC5 — не число из воздуха, это документированный минимум
docs/TOUCH-SUPPORT.md(строки 14, 72, 97). smoke_backup_transfer.mjsсобирает.dispsectionчерез.some(node => textContent === …), а не по индексу/соседству — перекладка в карточки такую проверку не ломает, заявление AC2 «без единой правки» для этого смока правдоподобно.smoke_general_settings.mjsсегодня собираетout.groupsкак список ВСЕХ.dispsection— девять штук. Из УX-таблицы следует, чтоgs.glow_groupиgs.sun_groupв новой раскладке становятся заголовками карточек (<h3>вformCard(), без класса.dispsection), а не остаются подзаголовками — то есть реальный состав.dispsectionпосле переделки — семь, не девять. Это ровно то расширение, которое ТЗ называет единственным допустимым в AC3: вывод ревью — AC3 корректно предвидит настоящую причину изменения смока, а не создаёт её.
Находки
M1 (Medium, в скоупе) — судьба marker.run_target_gone не решена, счётчик i18n не сходится
Где: раздел «i18n», строка .help + .help.aria для семи существующих пояснений
(14 ключей × 4 языка = 56 строк); раздел «УX» → «Устройство на плане»; К4.
Воспроизведение:
- Вводная таблица ТЗ («Что предстоит переработать») называет ровно одно
пояснение-абзац диалога устройства —
marker.run_target_gone(строкаsrc/editors/marker-dialog.ts:334,<div class="rhint">). - УX-раздел расписывает, какую карточку и чей текст получает «?», для каждого
переносимого пояснения кроме этого: «Общие» — 4 явные пары «карточка → текст»
(
gs.hint→«Цвета заливки комнат»,gs.radar_show_live_hint→«Отображение»,gs.backup_hint/gs.grid_hint→«Данные»); «Пространство» — явноspace.hide_decor_tip/space.hide_openings_tip→«Внешний вид». Итого явно названо 6 пояснений. Для «Устройства» ни один из пяти рядов таблицы карточек не называет пояснение и не связываетmarker.run_target_goneни с одной из пяти карточек. - Таблица i18n утверждает «семь существующих пояснений». Явно поимённо в тексте
ТЗ названо шесть. Седьмым может быть только
marker.run_target_gone— но тогда ТЗ обязано сказать, в какую из пяти карточек устройства едет его «?», как это сделано для всех шести остальных. - Альтернатива —
marker.run_target_goneостаётся инлайновым callout'ом, какgs.sun_missing(который К4/УX прямо называют исключением: «gs.sun_missing остаётся callout'ом»). Но дляrun_target_goneтакого исключения текст не формулирует, хотя по факту (строка кода) это ровно тот же паттерн: условный инлайновый предупреждающий текст с переменной ({id}), а не статичное объяснение настройки. - Разница ощутима для AC4: «абзацев-пояснений в трёх формах не осталось» —
формула безусловная. Если
run_target_goneостаётся как есть, AC4 в этой редакции не выполняется буквально (абзац остался); если он переезжает под «?», разработчику неизвестно, в какую карточку, и ревьюеру кода нечем судить результат, кроме собственной догадки. - Сама арифметика тоже расходится: если верно, что пояснений было 6 (гипотеза «run_target_gone — callout, как sun_missing»), то строка i18n должна быть «6 × 2 × 4 = 48», а не «7 × 2 × 4 = 56»; итоговая сумма ключей/строк ТЗ (26 ключей / 104 строки) в этом случае тоже должна быть на 1 ключ и 4 строки меньше.
Почему это Medium, а не Low: это не опечатка в тексте, а нерешённый вопрос, прямо влияющий на проверяемость AC4 в самом рискованном из трёх файлов (устройство — 760 строк, единственный без единой существующей группы, по риску №2 самого ТЗ). Без решения код-ревьюер не может отличить правильную реализацию от нарушения AC4 чтением диффа — придётся гадать так же, как автор кода.
Почему в скоупе: диалог устройства прямо входит в скоуп этого issue
(src/editors/marker-dialog.ts — первая строка таблицы скоупа), решение
технического характера (куда девать один инлайн-текст), не продуктовое — автору
ТЗ решать самому, без вопроса владельцу.
Чем закрывается: один из двух вариантов, явно вписанный в текст ТЗ —
(а) добавить marker.run_target_gone в перечень пояснений с картой «карточка →
текст» вроде остальных шести (тогда i18n-таблица подтверждает «7», разночтения не
возникает), либо (б) явно объявить его исключением, аналогичным gs.sun_missing,
и убрать из счётчика (тогда i18n-таблица и итоговая сумма ключей пересчитываются
на «6»/48/96 вместо 7/56/104). Любой из двух вариантов делает AC4 проверяемым без
догадки.
Что проверено и признано корректным
- Обязательные разделы §7.1 присутствуют все: сценарий, что человек увидит до/после, проблема, скоуп/не-скоуп, контракт поведения (К1–К7), UX, модель данных и миграция, i18n, AC1–AC10 с доказательством и способом покраснения, план автотестов, риски, откат, release-артефакты. Ничего не пропущено.
- Первые два раздела продуктовые и отвечают на «какая персона/поверхность/момент» и «что видно одной фразой без терминов реализации» — соответствует §7.1 буквально, и персона (администратор дома) верно привязана к SCOPE.md («Editors are admin-only tools»).
- Скоуп/не-скоуп — образцовый: не-скоуп перечисляет ровно то, что архивный
SPEC.mdэпика #591 предлагал сверху (активность «Сохранить», инлайн-цвет, плитки, компас, футер, ширина) и что уже было осознанно оставлено вне #594 — граница воспроизведена, а не придумана заново. - К1–К7 проверяемы и опираются на существующий код, а не на воображаемое поведение (см. «Как проверялось» пп. 3–6, 10).
- Классы риска §2.6 разобраны все шесть, неприменимые обоснованно помечены («геометрия — не применимо», «async — не применимо: перекладка синхронной разметки»), а не пропущены молча.
- AC7 (golden) называет ровно те 11 сцен, которые реально существуют в индексе, и корректно суммируется с построчным разбором «7 устройство + 3 общие + 1 пространство» из раздела «Риски» №2.
- AC2/AC3 логически согласованы: AC3 верно предсказывает, что
smoke_general_settings— единственный смок, требующий правки, и по правильной причине (переходgs.glow_group/gs.sun_groupиз подзаголовков в заголовки карточек меняет реальный состав.dispsection, а не просто «на всякий случай»). - «Принято предположительно» блок в конце корректно отделяет технические решения
(имена ключей, включение
hpf-switch, порядок карточек,label.dispsectionдля подзаголовков) от продуктовых — ни одно продуктовое решение туда не спрятано. - Открытых вопросов владельцу в тексте нет, и по итогам проверки такого вопроса не появилось: единственная находка (M1) — техническая, автор ТЗ решает её сам.
Чего не проверял
- Не запускал
npm test/npm run typecheck/npm run build— на этапе ревью ТЗ кода ещё нет (веткиissue/598-*не существует), гейты неприменимы; они относятся к код-ревью этого issue. - Не проверял, действительно ли CI сможет прогнать смоки/golden с текущими путями — это код-ревью проверит по факту диффа; на этапе ТЗ проверялась только правдоподобность заявленных путей и существующих файлов, а не их прогон.
- Не оценивал соответствие присланного архивного
SPEC.md(файлов из архива эпика #591) построчно — ТЗ прямо и осознанно берёт из него только раскладку и явно называет, что остальное (поведение) отклонено; сверять отклонённые части не было предмета ревью. - Не проверял французскую (
fr) локаль на предмет реальных значений строк — только то, что счётчик «4 языка» соответствует существующимen/ru/de/frсловарям.
Вердикт
Единственная находка — Medium, в скоупе задачи, без High. Согласно PROCESS.md §2.4/§7.2,
это жёлтый вердикт: автор правит ТЗ (решает судьбу marker.run_target_gone явно и
приводит счётчик i18n в соответствие), фикс проходит повторный цикл ревью ТЗ.
Вердикт: жёлтый · заход r1 · блокирующих циклов 1/4 · High: 0 · Medium: 1 → в задаче
Материал раунда
- Ветка:
dev, коммит958ab36652eb— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
232dd22abb22fc04ea01c124ac4332a6060a4fc8git log --all --format='%H %T' | grep 232dd22abb22 - Тело issue:
aee54691f0fa3651f53c32dcb2934b5d0ebb983714e8c297783696b476976331 - Вердикт конвейера:
yellow· High 0