mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 21:58:56 +00:00
166 lines
14 KiB
Markdown
166 lines
14 KiB
Markdown
# 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).
|