mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
193 lines
17 KiB
Markdown
193 lines
17 KiB
Markdown
# 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`).
|