14 KiB
SPEC-REVIEW-244-r1
- Issue: #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 (не с пересказом автора).
Как проверялось
- Прочитаны
docs/SCOPE.md,PROCESS.md, тело issue #244 и оба аналитических комментария владельца (подтверждение Q1, затем Q2+Q3), комментарий «Взял: автор ТЗ» и комментарий «ТЗ готово». - Прочитан
docs/USER-GUIDE.ru.md(разделы про Optimize,default_floor, несколько карточек) — сверена терминология: «Оптимизировать планы», «предпросмотр», «Стартовое/резервное пространство» — ТЗ использует те же слова, не изобретает новые. - Построчно перепроверены все шесть пунктов «Подтверждённого диагноза» (§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.
- Проверено техническое основание контракта §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-команды свободно — корректно помечено как техническое решение, не спрятанная догадка. - Проверено техническое основание §8.3 про восстановление
marker.room_idпо «единственной комнате effective Area»:resolveExplicitMarkerPlacementотдаёт толькоarea/space, но клиент уже строит более богатую картуArea -> {space, room}(src/houseplan-card.ts:3551-3555,_areaToSpace), которая площадь для этого способна дать. Контракт реализуем без второго расходящегося resolver, как и требует §7. - Проверена ссылочная семантика импорта одного пространства
(
import_export.py:885-990, ремапmarker.room_id,vacuum.segment_map,rl_<room_id>,layout[*].s) — контракт §9 («применить тот жеid_mapк оставшимся target-ссылкам, если старого id больше нет») ложится на существующий код без противоречий. node scripts/check-docs.mjs— green (диапазон не трогаетsrc/**, гейт не обязателен, прогнан для очистки совести).- Класс изменения — 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).