Files
houseplan-card/docs/reviews/SPEC-REVIEW-244-r1.md
T
2026-08-23 00:34:32 +03:00

166 lines
14 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-244-r1
- Issue: [#244](https://github.com/Matysh/houseplan-card/issues/244) — «Маркеры остаются привязанными к удалённому id пространства и пропадают с плана; Optimize такую ссылку не лечит»
- Этап: ТЗ на ревью (PROCESS.md §2.4)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4
- ТЗ: `docs/specs/244-orphan-space-references.md` (446 строк), коммит `9c3c7e1b378b9bef9bcb02b9a8c4ef725c99a041`
- Ветка: `issue/244-orphan-space-references`, `HEAD` на момент ревью = `9c3c7e1`
- Метки issue на входе: `bug`, `P2`, `S4-spec-review` (лёгкий/короткий трек не применяются — файл ТЗ обязателен, он и создан)
## Скоуп ревью
Первый заход. Разбор полный: сценарий, продуктовая рамка, все 14 AC, термины
и инварианты §7, контракты Optimize/импорта/удаления/редактора карточки,
release-артефакты, откат, раздел принятых предположений §18. Диагноз ТЗ (§3)
сверен построчно с текущим кодом на `HEAD` (не с пересказом автора).
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `PROCESS.md`, тело issue #244 и оба
аналитических комментария владельца (подтверждение Q1, затем Q2+Q3),
комментарий «Взял: автор ТЗ» и комментарий «ТЗ готово».
2. Прочитан `docs/USER-GUIDE.ru.md` (разделы про Optimize, `default_floor`,
несколько карточек) — сверена терминология: «Оптимизировать планы»,
«предпросмотр», «Стартовое/резервное пространство» — ТЗ использует те же
слова, не изобретает новые.
3. Построчно перепроверены все шесть пунктов «Подтверждённого диагноза» (§3)
по факту, а не на слово автора:
- `resolveExplicitMarkerPlacement()` (`src/devices.ts:1045-1065`) —
подтверждено: `(area && areaToSpace[area]) || marker.space || firstSpaceId`,
а для `manualRoomWithoutArea` — `marker.space || firstSpaceId` без ветки
на Area;
- virtual-маркер (`src/devices.ts:1248-1249`) — подтверждено:
`m.space || (area && areaToSpace[area]) || firstSpaceId` — `marker.space`
действительно приоритетнее Area, ровно как заявлено;
- View/kiosk рендер (`src/houseplan-card.ts:16409`,
`src/space-render.ts:190`) — подтверждено: фильтр `d.space === space.id`
точный, мёртвый id не совпадёт ни с одним пространством — маркер
невидим на всех планах, а не «где-то не так»;
- `_deleteSpace()` (`src/houseplan-card.ts:14457-14473`) — подтверждено:
удаляется только элемент `config.spaces`, `layout`/`markers` не трогаются,
запись идёт через общий `houseplan/config/set`, который `layout` не
касается вообще (`custom_components/houseplan/websocket_api.py:1123` —
`houseplan/config/set` работает с одним стором);
- `build_space_merge()` (`custom_components/houseplan/import_export.py:825-995`)
— подтверждено: копия всегда получает свежий `space_<old>_<hex>` id,
старое пространство при этом способе не удаляется — заявление ТЗ «код не
подтверждает, что импорт сам портит корректную конфигурацию» точное, не
смягчение;
- `resolveInitialSpace()` (`src/initial-load.ts:70-90`) — подтверждено:
`hash -> saved -> default -> first`, невалидный `default_floor` тихо
проваливается в `first`.
4. Проверено техническое основание контракта §10 (атомарная запись
config+layout под двумя revision-guard'ами и код `conflict`/`space_in_use`)
— это не выдумка: тот же паттерн уже реализован для
`houseplan/plan/optimize` (`websocket_api.py:1355-1466`, два
`expected_*_rev`, единая транзакция под `rt.write_lock`) и для отказа по
занятости в `houseplan/plans/delete` (`websocket_api.py:888-931`, код
`in_use`). У ТЗ в §15 прямо сказано, что имя новой WS-команды свободно —
корректно помечено как техническое решение, не спрятанная догадка.
5. Проверено техническое основание §8.3 про восстановление `marker.room_id`
по «единственной комнате effective Area»: `resolveExplicitMarkerPlacement`
отдаёт только `area`/`space`, но клиент уже строит более богатую карту
`Area -> {space, room}` (`src/houseplan-card.ts:3551-3555`,
`_areaToSpace`), которая площадь для этого способна дать. Контракт
реализуем без второго расходящегося resolver, как и требует §7.
6. Проверена ссылочная семантика импорта одного пространства
(`import_export.py:885-990`, ремап `marker.room_id`,
`vacuum.segment_map`, `rl_<room_id>`, `layout[*].s`) — контракт §9
(«применить тот же `id_map` к оставшимся target-ссылкам, если старого id
больше нет») ложится на существующий код без противоречий.
7. `node scripts/check-docs.mjs` — green (диапазон не трогает `src/**`, гейт
не обязателен, прогнан для очистки совести).
8. Класс изменения — C (`docs/specs/**`), `typecheck`/`test`/`build` к
ревью ТЗ не относятся и не запускались.
## Находки
### Medium (в скоупе задачи — возвращается автору)
**M1. Не назван пункт DoR «влияние на touch».**
PROCESS.md §2.5 требует явно назвать влияние на touch по
`docs/TOUCH-SUPPORT.md` как блокирующий пункт готовности к разработке (View
и киоск — блокирующие). В ТЗ #244 слово «touch» и «киоск» не встречаются ни
разу (`grep -in "touch\|киоск" docs/specs/244-orphan-space-references.md` —
пусто), хотя изменение имеет прямое отношение к View: восстановленный маркер
после Optimize становится видимым на плане, который открывают в том числе на
планшете-киоске. Остальные редакторские поверхности (диалог Optimize,
удаление пространства, поле `default_floor`) подпадают под «desktop-first
editors, best effort on touch» и, по всей видимости, действительно не требуют
отдельного контракта — но это должно быть сказано явно, а не восстановлено
ревьюером по догадке.
*Как воспроизвести отсутствие:* открыть §12 «Данные, compatibility и
миграция» или §16 «Риски, производительность и security» — оба раздела,
где такая строка ожидалась бы рядом с «производительность… не нужен»,
про touch ничего не говорят.
*Почему это не Low:* пункт прямо назван блокирующим в §2.5, и его отсутствие
делает чек-лист DoR формально невыполненным — не стилистическая придирка.
*Что чинить:* одна-две фразы в §12 или §16: рендер маркера и все три
изменяемые UI-поверхности не вводят новых интерактивных путей и наследуют
существующий touch-контракт (View — уже поддерживаемый рендер/тап без
изменений; редакторы — desktop-first best effort как обычно), либо иной
явный вывод, если авторы видят особый случай.
## Что проверено и корректно
- Все шесть пунктов диагноза (§3) подтверждены чтением текущего кода, не
голословны.
- Все три продуктовых решения владельца (Q1/Q2/Q3) отражены в контракте
дословно и без искажений (§4, §10, §11).
- Приоритет правил §8.2 (signature → effective Area → detach) корректно
наследует пограничные случаи `resolveExplicitMarkerPlacement`, включая
`manualRoomWithoutArea` и особый порядок virtual-маркера — я прошёл через
оба ветвления вручную и не нашёл случая, где приоритет противоречил бы
рантайму.
- §7 инварианты (1–5) корректно закрывают ровно тот набор проблем, который
описан в диагнозе; ничего лишнего не требуется.
- Раздел §18 «Принятые предположения» — все шесть пунктов действительно
технические (не пользовательские) решения: длина stem, поведение
tombstone, сохранение unattached layout (обосновано ссылкой на «never
delete a user's file on an inference» из `docs/SCOPE.md`), полное удаление
позиции при Area/detach, отсутствие отдельного modal, лимит 10 id в UI.
Ни один пункт не подменяет продуктовое решение технической догадкой.
- AC1–AC14 однозначны, у каждого указан способ доказательства (unit /
backend / browser smoke / golden / ревью кода), и AC12/AC13 закрывают
неизменность входа и производительность отдельно от «счастливого пути».
- Риски (§16) покрывают именно те точки, где мог случиться ложный автопуш
маркера не туда: ограничение сигнатуры, отсутствие переноса координат,
атомарность двух Store.
- Release-артефакты (§17) перечисляют оба changelog, оба User-Guide, три
bundle-копии, docs screenshot после `src/**`, `CONFIG-COMPATIBILITY.md`.
- Открытых продуктовых вопросов к владельцу нет — они закрыты в
аналитике/S2 до входа в ТЗ, это не пропуск, а правильный порядок процесса.
- Продукт: задача закрывает J6 «Keep the plan true as the home evolves» из
`docs/SCOPE.md` (устройство не должно тихо пропадать при штатной
эксплуатации), никакого расширения скоупа за пределы диагноза не
обнаружено — «Не входит» (§6) явно отсекает соседние соблазны (лечение
произвольных broken controls, полная миграция full-restore, GC неизвестных
layout entries).
## Чего не проверял
- Не проверялась реализация — её ещё нет, стадия чисто ТЗ.
- Не прогонялись `typecheck`/`test`/`build`/смоки — класс изменения C
(docs-only), гейты код-ревью к этому этапу не относятся.
- Не проверялась точная реализуемость атомарной backend-транзакции глубже
чтения существующего кода `houseplan/plan/optimize` — это ревью
архитектурного основания контракта, а не написанного кода, которого нет.
- Не оценивалась производительность на реальных данных — в ТЗ заявлено
«MAX_SPACES/MAX_MARKERS/MAX_LAYOUT уже ограничивают объём», это принято на
слово как разумная оценка сложности линейного прохода, а не измерено.
## Вердикт
Диагноз точен и перепроверен по коду, все продуктовые решения владельца
отражены без искажений, AC полны и доказуемы, скоуп не расширен. Единственная
находка — отсутствие явного пункта про touch/kiosk из обязательного DoR
чек-листа §2.5; High нет, Medium в скоупе задачи и чинится добавлением одной
фразы в существующий документ.
**Жёлтый.** Возврат автору на правку M1 в текущем ТЗ (`S3-spec` → доработка →
повторное ревью, не более 2 циклов сверх этого, если трек останется
обычным — фактически лимит 4).