Files
houseplan-card/docs/reviews/SPEC-REVIEW-74-r1.md
T
2026-08-30 13:16:27 +00:00

193 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-74-r1
- **Issue:** https://github.com/Matysh/houseplan-card/issues/74
- **Этап:** ревью ТЗ (PROCESS.md §2.4)
- **Материал:** `docs/specs/074-device-position-undo.md` на SHA `a3c305e6052c001d3ea39a190ed6b528f56e2df5`
(ветка `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` — каждое из них
можно фальсифицировать чтением кода, поэтому все они прочитаны и сверены:
1. `CommandStack` существует и используется только для geometry/decor, отдельной
истории позиций устройств нет — подтверждено: `src/command-stack.ts:15-55`,
`src/houseplan-card.ts:1704` (`_geometryHistory = new CommandStack<...>(50)`),
отдельной по-девайсной истории в коде нет.
2. Device toolbar не содержит Undo/Redo — подтверждено:
`_renderDevicesBar()` (`src/houseplan-editor-runtime.ts:11743-11768`) не рендерит
такие кнопки, в отличие от `_renderMarkupBar()` (`:11722-11729`).
3. Keyboard handler выходит до истории в `devices` — подтверждено:
`src/houseplan-card.ts:2816` `if (!this._markup) return;` до диспетчеризации
undo/redo (`:2824-2856`), а `_markup` — это `mode === 'plan'`
(`:1699-1701`); `devices` не попадает ни в decor-ветку, ни в `_markup`.
4. `_pointerMove()` вызывает `_savePos()` и планирует persist на каждом кадре —
подтверждено: `src/houseplan-card.ts:6381-6405` вызывает `_savePos` безусловно;
`_savePos` (`:5001-5021`) кладёт id в `_dirtyPos` и вызывает `_persistLayout()`
(debounce, `:4769`) при каждом вызове.
5. `pointercancel` направлен в тот же `_pointerUp`, что и обычный `pointerup` —
подтверждено: `src/houseplan-card.ts:11806-11807`, оба события биндятся на
`this._pointerUp(e, d)`; отдельного abort-пути для устройств сегодня нет.
6. 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`).
7. `_sentPos` (pending-authority карта, §9) существует — подтверждено:
`src/houseplan-card.ts:4767,4739,4780,4785`.
8. Механизм различения 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. Кросс-пространственная история (§9 «команда другого пространства сначала
переключает карточку на `spaceId`») — не новый паттерн: `_applyGeometryState`
(`houseplan-editor-runtime.ts:2344-2348`) уже переключает `host._space` перед
применением geometry-команды, а `_commitSpace`/`_slideTo`
(`houseplan-card.ts:1443-1463`) никогда не чистят `_geometryHistory` —
существующий стек уже глобальный, не по-пространственный. ТЗ описывает
продолжение существующего контракта, а не изобретает новый.
10. `rl_*` (room-label) действительно имеют отдельный от устройств путь
сохранения — подтверждено (`houseplan-card.ts:11913,11941,12123`,
`houseplan-editor-runtime.ts:10706-10770`), корректно исключены из §7/§10.
11. Guards перед `pointerdown`, на которые ссылается §8.1 — подтверждено:
`houseplan-card.ts:6356` (mode guard), `:6373` (`ha_disabled` guard) до
фиксации `_drag` (`:6374-6376`).
12. `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»).
13. Формат интерполяции `history.device_move` с именем устройства технически
совместим с существующим паттерном `{name}` в `history.undo_named` /
`redo_named` (`src/i18n/en.json:105-110`), хотя это первый ключ, где сам
`{name}` — динамически подставленное (не статическое) значение; отмечаю как
новый, но не проблемный шаг того же механизма.
14. Числа языков/ключей: 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, что соответствует фактическому состоянию проекта
(по 37 `history.*` ключей на язык уже сегодня).
## Чего не проверял
- Не запускал `npm run typecheck`/`npm test`/`npm run build` — на этапе ревью
ТЗ кода ещё нет (ветка на SHA `a3c305e6` не содержит изменений в `src/**`
или `test/**` для этой задачи, только сам файл спецификации), гейты
реализации нечего гонять.
- Не проверял golden-инфраструктуру построчно за пределами подтверждения, что
существующий baseline `device-editor` toolbar-сценария (`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`).