17 KiB
SPEC-REVIEW-74-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/74
- Этап: ревью ТЗ (PROCESS.md §2.4)
- Материал:
docs/specs/074-device-position-undo.mdна SHAa3c305e6052c001d3ea39a190ed6b528f56e2df5(веткаissue/74-device-position-undo, комментарий владельца от 2026-08-30 фиксирует этот SHA как перебазированную и расширенную редакцию ТЗ) - Заход: r1 (первое ревью документа; прежних вердиктов по этому ТЗ в issue нет — комментарии от 2026-08-14/08-15/08-30 это аналитика и акт написания ТЗ, не ревью)
- Вердикт: зелёный
- High: 0 · Medium: 0 · Low: 2 (обе сняты ревьюером с записью ниже)
Скоуп разбора
Первый заход — полный разбор: сам документ ТЗ, тело issue #74 и все три комментария
(аналитика 2026-08-14, публикация ТЗ 2026-08-15, актуализация на dev 2026-08-30),
docs/SCOPE.md, AGENTS.md, PROCESS.md §2.4/§7.1, docs/UX-MODES.md,
docs/TOUCH-SUPPORT.md, и заявленное в ТЗ текущее поведение dev (раздел 2
«Актуальность на 2026-08-30») сверено построчно с кодом.
Как проверялось
Ревью ТЗ не запускает гейты (это не код-ревью), но обязано поймать догадку,
выданную за факт. Документ №074 необычно детален и содержит развёрнутый раздел
«Актуальность» с явными утверждениями о текущем поведении dev — каждое из них
можно фальсифицировать чтением кода, поэтому все они прочитаны и сверены:
CommandStackсуществует и используется только для geometry/decor, отдельной истории позиций устройств нет — подтверждено:src/command-stack.ts:15-55,src/houseplan-card.ts:1704(_geometryHistory = new CommandStack<...>(50)), отдельной по-девайсной истории в коде нет.- Device toolbar не содержит Undo/Redo — подтверждено:
_renderDevicesBar()(src/houseplan-editor-runtime.ts:11743-11768) не рендерит такие кнопки, в отличие от_renderMarkupBar()(:11722-11729). - Keyboard handler выходит до истории в
devices— подтверждено:src/houseplan-card.ts:2816if (!this._markup) return;до диспетчеризации undo/redo (:2824-2856), а_markup— этоmode === 'plan'(:1699-1701);devicesне попадает ни в decor-ветку, ни в_markup. _pointerMove()вызывает_savePos()и планирует persist на каждом кадре — подтверждено:src/houseplan-card.ts:6381-6405вызывает_savePosбезусловно;_savePos(:5001-5021) кладёт id в_dirtyPosи вызывает_persistLayout()(debounce,:4769) при каждом вызове.pointercancelнаправлен в тот же_pointerUp, что и обычныйpointerup— подтверждено:src/houseplan-card.ts:11806-11807, оба события биндятся наthis._pointerUp(e, d); отдельного abort-пути для устройств сегодня нет.- Backend уже предоставляет точечные
houseplan/layout/updateиhouseplan/layout/deleteбез изменения схемы — подтверждено:custom_components/houseplan/websocket_api.py:147-148,622,1077, уже вызываются из фронтенда (houseplan-card.ts:4782,houseplan-editor-runtime.ts:8308,8369). _sentPos(pending-authority карта, §9) существует — подтверждено:src/houseplan-card.ts:4767,4739,4780,4785.- Механизм различения own echo / внешнего изменения по revision/content, на
переиспользование которого рассчитывает §10, существует и именно
content-based, не timeout — подтверждено:
_adoptStructuralResponses(houseplan-card.ts:4033-4053) сравниваетcontentFingerprint(...)и чистит_geometryHistoryтолько при реальном расхождении, с явным комментарием в коде «A reconnect echo with identical content deliberately does not [clear]». - Кросс-пространственная история (§9 «команда другого пространства сначала
переключает карточку на
spaceId») — не новый паттерн:_applyGeometryState(houseplan-editor-runtime.ts:2344-2348) уже переключаетhost._spaceперед применением geometry-команды, а_commitSpace/_slideTo(houseplan-card.ts:1443-1463) никогда не чистят_geometryHistory— существующий стек уже глобальный, не по-пространственный. ТЗ описывает продолжение существующего контракта, а не изобретает новый. rl_*(room-label) действительно имеют отдельный от устройств путь сохранения — подтверждено (houseplan-card.ts:11913,11941,12123,houseplan-editor-runtime.ts:10706-10770), корректно исключены из §7/§10.- Guards перед
pointerdown, на которые ссылается §8.1 — подтверждено:houseplan-card.ts:6356(mode guard),:6373(ha_disabledguard) до фиксации_drag(:6374-6376). docs/TOUCH-SUPPORT.mdиdocs/UX-MODES.mdсверены на противоречия с §1 и §9/§12: противоречий не найдено, «не сохранять случайную позицию при системной отмене жеста» — прямое следствие уже существующего требованияTOUCH-SUPPORT.md:78-79(«not... saving unintended geometry merely because a pinch, pointer cancellation or second touch was misread as a click»).- Формат интерполяции
history.device_moveс именем устройства технически совместим с существующим паттерном{name}вhistory.undo_named/redo_named(src/i18n/en.json:105-110), хотя это первый ключ, где сам{name}— динамически подставленное (не статическое) значение; отмечаю как новый, но не проблемный шаг того же механизма. - Числа языков/ключей: en/ru/de/fr параллельны (по 37
history.*ключей в каждом), подтверждает выполнимость плана и18n на 4 языка из §16.
Отдельно проверено требование §7.1 «сценарий + что человек увидит» (раздел 1
документа: персона — администратор HA, поверхность — Device editor,
desktop-first; до/после — одной фразой без терминов реализации, за вычетом
одного слова «layout», которое уже вошло в пользовательскую терминологию
проекта через docs/USER-GUIDE.ru.md) и соответствие Core user job (J6 «Keep
the plan true as the home evolves», docs/SCOPE.md:48) — подтверждено как
владельцем в аналитике (2026-08-14), так и по существу.
Находки
Low-1 — неверные пути трёх скриптов в §14 «Регрессия и gates»
docs/specs/074-device-position-undo.md:285-287 называет
python scripts/check_docs.py, node scripts/no_new_any.mjs,
node scripts/smoke_select.mjs. Фактические имена в репозитории:
scripts/check-docs.mjs (Node, не Python), scripts/no-new-any.mjs,
scripts/smoke-select.mjs (дефис, не подчёркивание) — проверено ls scripts/.
Как написано, ни одна из трёх команд не выполнится.
Воспроизведение: ls scripts/check_docs.py → No such file or directory;
реальный файл — scripts/check-docs.mjs. Аналогично для двух других.
Решение ревьюера: снимаю как Low без возврата в цикл — не влияет на
выполнимость самого AC-контракта (доказательства AC1-AC14 не зависят от этих
трёх строк), тривиально исправляется при выходе в реализацию простой заменой
имён на актуальные. Автору стоит поправить эти три строки перед S5-ready, но
отдельного цикла ревью это не требует.
Low-2 — нет явного блока «принято предположительно, поменять свободно» и явной строки про bundle-бюджет
PROCESS.md §7.1 требует технические решения автора, не наблюдаемые
пользователем, фиксировать явным блоком в конце ТЗ. Документ принимает несколько
таких решений по ходу текста (имя нового модуля src/device-position-history.ts
помечено «рекомендуемо», отдельный CommandStack<DevicePositionCommand>(50) вместо
переиспользования geometry-стека, форма интерфейса DevicePlacement), но не
сводит их в отдельный итоговый блок. Также §12 называет производительность (O(1),
снижение нагрузки на pointermove), но не отдельной строкой — влияние на
bundle:budget (256000 B gzip, AGENTS.md:334) от нового модуля + 2 кнопок + 2
ключей i18n×4 языка.
Решение ревьюера: снимаю как Low. Технические решения по существу разумны, обоснованы рядом (риски §17 прямо привязаны к каждому), и реальный прирост бюджета от pure-модуля на пару функций и двух иконок пренебрежимо мал по сравнению с порогом 256000 B — нет оснований ожидать, что бюджет пострадает. Формальное отсутствие сводного блока не создаёт продуктовой неоднозначности и не блокирует DoR по существу.
Других находок нет. High и Medium не обнаружены — ни в скоупе, ни вне его.
Что проверено и признано корректным
- Все технические утверждения §2 «Актуальность на 2026-08-30» о поведении
текущего
dev— фактически точны (детали выше, пп. 1-11). Раздел не содержит выданной за факт догадки: каждое утверждение фальсифицируемо и выдержало проверку. - Продуктовая рамка (§1): персона, поверхность, момент и видимый
результат названы, соответствуют J6 из
docs/SCOPE.mdи позиции «редакторы admin-only, desktop-first» — противоречий сUX-MODES.md/TOUCH-SUPPORT.mdне найдено. - Не-скоуп (§4) и строгая граница истории (§11) описывают ровно то, что оставлено вне задачи, без незаявленных побочных эффектов на geometry/decor историю: reuse-механизмы (own-echo/content fingerprint, кросс-space переключение) — это переиспользование уже существующего в коде контракта, а не новое допущение, которое стоило бы адресовать владельцу.
- AC1-AC14 (§13) — каждый снабжён способом доказательства (
unit/browser smoke/golden/source guard), формулировки однозначны, ни один не описывает выдуманное поведение. - Откат (§17, последний абзац) и миграция (везде явно «не требуется», формат layout не меняется) — присутствуют и не голословны, поскольку backend API уже существует без изменений (см. п. 6 выше).
- Открытых продуктовых вопросов владельцу нет; предыдущий комментарий владельца (2026-08-14) подтверждает это независимо («Вопросы: нет»), и разбор ТЗ не нашёл скрытого продуктового вопроса, который следовало бы поднять вместо этого.
- i18n: новые ключи (
history.device_move,history.device_stale) не коллизируют с существующими (проверено grep по всем четырёмsrc/i18n/*.json), план покрывает en/ru/de/fr, что соответствует фактическому состоянию проекта (по 37history.*ключей на язык уже сегодня).
Чего не проверял
- Не запускал
npm run typecheck/npm test/npm run build— на этапе ревью ТЗ кода ещё нет (ветка на SHAa3c305e6не содержит изменений вsrc/**илиtest/**для этой задачи, только сам файл спецификации), гейты реализации нечего гонять. - Не проверял golden-инфраструктуру построчно за пределами подтверждения, что
существующий baseline
device-editortoolbar-сценария (geometry-devices-editor-*.png) уже существует как объект для будущего diff — сам скриншот не перегенерировал. Это не требуется на этапе ТЗ. - Не оценивал производительность драга количественно (нет кода для профилирования);
раздел §12 принят по правдоподобию инженерного рассуждения (O(1) snapshot/apply,
устранение persist на каждый
pointermoveобъективно снижает, а не повышает нагрузку), не по измерению. - Не сверял с точностью до символа все 14 AC на предмет полноты матрицы edge-cases
сверх выборочной проверки логической согласованности §8-§11 — это стандартный
предмет код-ревью (
тест умеет падать), а не ревью ТЗ.
Итог
Документ ТЗ #074 методологически образцовый для полного трека: раздел
«Актуальность» не содержит ни одной догадки, выданной за факт — все 11+
проверенных технических утверждений о dev подтвердились построчным чтением
кода; продуктовая рамка и Core user job названы и совпадают с
docs/SCOPE.md; AC полны, пронумерованы и каждый снабжён способом
доказательства; риски и откат заполнены содержательно, а не для галочки.
Единственные две находки — Low, обе не блокируют DoR и сняты этим ревью с
запиской для автора поправить имена скриптов перед стартом реализации.
Вердикт: зелёный. Переход в «Готово к разработке» (S5-ready).