Files
houseplan-card/docs/reviews/SPEC-REVIEW-220-r2.md
T
2026-08-20 20:27:01 +00:00

18 KiB
Raw Blame History

SPEC-REVIEW-220-r2

  • Issue: https://github.com/Matysh/houseplan-card/issues/220
  • ТЗ: docs/specs/220-space-tab-reorder.md (текущий коммит e57e1d9, «docs: close M1 and M2 from the spec review of #220»)
  • Ревьюер: Claude (роль «ревьюер ТЗ», PROCESS.md §2.4)
  • Заход: r2 · блокирующих циклов 1/4
  • Вердикт: жёлтый · High: 0 · Medium: 1 (в скоупе задачи)

Скоуп ревью

Разбор по дельте (PROCESS.md §2.9, #214), а не заново — с одним расширением. Раунд r1 закрыл два Medium (M1, M2) точечной правкой текста ТЗ, но правка по M2 не косметическая: §8.3 полностью переписан — вместо «хранить якорь в settings» теперь «материализовать привязку в той же записи». Это смена контракта поведения (новая нормативная процедура записи конфигурации), а не переформулировка факта, поэтому именно этот кусок разобран полно — включая сверку с кодом devices.ts/houseplan-card.ts заново, а не по памяти r1. Остальное (§1–§7, §10–§16, AC1/2/4/5/6/7/8, мутационный гейт вне AC3, план автотестов) дельта не касается — унаследовано из r1 без повторной проверки (раздел ниже).

Как проверялось

  1. Найден вердикт r1 в комментариях issue #220 (https://github.com/Matysh/houseplan-card/issues/220#issuecomment-5361187761, 2026-08-20T20:17:49Z) и документ docs/reviews/SPEC-REVIEW-220-r1.md, зафиксировавший ТЗ на коммите 0fd2331.
  2. Объявлена дельта: git diff 0fd2331..HEAD -- docs/specs/220-space-tab-reorder.md (HEAD = e57e1d9). Дельта — 58 вставок / 19 удалений, только §8.3, §9, AC3, мутационная таблица, §17.3; остальные разделы файла побитово не менялись.
  3. Комментарий владельца о закрытии r1 называет коммит d0a5bfa — такого объекта в репозитории нет (git cat-file -t d0a5bfa → fatal: Not a valid object name), вероятно переписан ребейзом веток issue. Не доверяю названному SHA, проверка сделана по факту — прямым диффом файла между 0fd2331 (зафиксирован в документе r1) и текущим HEAD (e57e1d9, единственный коммит после c3278dd/review-doc r1, который трогает файл ТЗ) — содержимое дословно совпадает с тем, что владелец описал в комментарии, расхождение только в имени SHA в тексте комментария.
  4. Каждая находка r1 (M1, M2) сверена не по заявлению автора, а по строке текста ТЗ — см. таблицу «Закрытие раунда r1».
  5. docs/TOUCH-SUPPORT.md (§153–165, «Documentation rule») прочитан повторно, построчно сверена ровно та формулировка ярлыка, которую требует правило.
  6. Новая нормативная процедура §8.3 (материализация в той же записи) сверена с фактическим кодом разрешения firstSpaceId: src/devices.ts:1045-1065 (resolveExplicitMarkerPlacement, включая ветку manualRoomWithoutArea, ранее не разбиравшуюся отдельно ни в issue, ни в r1) и src/devices.ts:1249 (виртуальный маркер). Оба случая подтверждают общий критерий ТЗ «нет ни area, ведущей в пространство, ни собственного space» — он корректно обобщает все три ветки кода, включая ветку marker-без-HA-area из #3, которую ТЗ не называет по номеру, но покрывает по содержанию. Также проверено третье использование firstSpaceId (src/houseplan-card.ts:18818, черновик маркера в диалоге) — это непостоянный preview, не пишется в конфиг, к обязательству §8.3 («ни один маркер не меняет space») не относится: диалог всегда пересчитывает его заново по актуальной модели.
  7. AC3 (единственный AC, задетый дельтой) перепроверен на однозначность и доказуемость с учётом новой формулировки. AC1, AC2, AC4–AC8 дельтой не задеты — унаследованы из r1 без повторной проверки.

Закрытие раунда r1

Находка r1 Чем закрыта Где это видно
M1 — нет обязательной touch-классификации по docs/TOUCH-SUPPORT.md §153 В §9 добавлена строка Touch editor: not exposed. с обоснованием (вкладки живут во View, где переключение — fully supported/release-blocking; жест на вкладке рискует съесть тап) и отдельно — что safety floor §69 соблюдён по построению docs/specs/220-space-tab-reorder.md:156-165; формулировка ярлыка совпадает с требуемой буква в букву (сверено с docs/TOUCH-SUPPORT.md:160-162)
M2 — §9 отрицал новое поле конфигурации, §8.3/§17.3 предполагали якорь в settings §8.3 переписан: вместо якоря — материализация существующего поля marker.space в той же записи, что и порядок; §9 теперь говорит «новых полей конфигурации не появляется... см. §8.3», и это уже не противоречит остальному документу, а согласуется с ним; §17.3 переписан в терминах материализации, старое предположение про якорь явно помечено отвергнутым docs/specs/220-space-tab-reorder.md:107-139 (§8.3), :167-170 (§9), :268-271 (§17.3); AC3 (:202-206) и мутационная таблица (:240-241) синхронно обновлены под новую механику

Обе находки закрыты не декларативно: правка убирает саму причину противоречия (M2) и добавляет предметное содержание, а не просто ярлык (M1). Новых полей конфигурации в изменённом варианте действительно не появляется — проверено по коду: материализация пишет уже существующее поле маркера space, CONFIG_SCHEMA и scripts/config-field-registry.mjs в этой задаче не упоминаются как изменяемые, и это утверждение больше не противоречит §9.

Унаследовано из r1

Принято без повторной проверки в этом раунде — проверено и подтверждено в docs/reviews/SPEC-REVIEW-220-r1.md на коммите 0fd2331, дельта эти разделы не касается:

  • Персона, сценарий и job docs/SCOPE.md J6 (§1–§2 ТЗ).
  • Продуктовые решения владельца §4 (тач/права/клавиатура) и их соответствие owner-decision в комментариях issue.
  • §8.1, §8.2, §8.4, §8.5 контракта (порог 4px, .tabadd, запись через _writeConfig/expected_rev, swipeTarget, тост о числовом floor).
  • AC1, AC2, AC4, AC5, AC6, AC7, AC8 — однозначность и способ доказательства.
  • Мутанты tab-reorder-not-persisted, tab-reorder-eats-click, tab-reorder-ignores-pointer-type (не переписаны в дельте).
  • §11 (риски), §15 (release-артефакты), §16 (откат) кроме уже отражённых в дельте формулировок.
  • Low-находка r1 про .tabedit/pointerdown — оставлена без правки экспертным решением r1 («реализационная деталь, накрываемая кодревью»), дельта её не касается, пересматривать нет причины.

Находки

M3 — Medium, в скоупе. §17.3 маркирует нормативное требование как «свободно меняемое»

Заголовок §17 — «Принятые предположения (техническое, менять свободно)». Пункт 3 внутри него (docs/specs/220-space-tab-reorder.md:268-271) гласит: «Материализация привязки (§8.3) выполняется в том же config/set, что и порядок, а не отдельной записью: две записи дали бы окно, в котором порядок уже новый, а привязка ещё старая».

Но именно это же самое требование в §8.3 названо не предположением, а «Норматив» (:113, буквально это слово стоит заголовком абзаца) — и оно уже вошло в тестируемый контракт: AC3 требует «маркер получает явное space в той же записи» (:203-204), а мутант reorder-skips-materialization (:240) специально ловит его нарушение.

Почему это не формальность. §17.3 своим же текстом объясняет, почему атомарность нельзя менять свободно: если порядок и материализация уйдут двумя разными записями config/set, то в промежутке между ними firstSpaceId = model[0]?.id уже пересчитан по новому порядку — маркер, ещё не материализованный, в это окно резолвится в не то пространство. Если исполнитель прочитает заголовок §17 буквально («менять свободно») и раздельными записями, а не заголовок §8.3 («Норматив»), он получит ровно тот риск, ради которого писан весь раздел 8.3 и ради которого задача оценена в сложность 4/10 (см. §11, риск №2 «Маркеры уезжают» — главный риск задачи). Опасность усугубляется тем, что при таком (неверном) выборе реализации собственный юнит-тест AC3, написанный тем же исполнителем под ту же (неверную) модель, скорее всего будет проверять «согласованность после двух записей», а не «атомарность одной записи» — то есть мутационный гейт reorder-skips-materialization перестанет быть надёжным барьером именно в том сценарии, для которого его писали.

Чем закрывается: убрать пункт 3 из §17 (он не является свободным предположением — норма уже сформулирована в §8.3 как обязательная и проверяется AC3), либо явно пометить его как исключение из «менять свободно» с отсылкой на §8.3/AC3. Правка текстовая, AC и скоуп не меняются.

AC — что перепроверено дельтой

AC Задет дельтой Однозначен Доказательство Комментарий
AC1, AC2, AC4, AC5, AC6, AC7, AC8 нет — — унаследованы из r1 без повторной проверки
AC3 да да unit (buildDevices + запись) формулировка усилена («маркеры с area/space — побитово прежними»), однозначна и доказуема; ровно вокруг него — находка M3 (не сам AC, а соседний раздел §17.3, который создаёт риск неверной трактовки реализации, проверяемой этим же AC)

Что проверено и корректно

  • M1 и M2 закрыты предметно, а не декларативно — см. таблицу выше; §9 больше не противоречит §8.3.
  • Новая механика §8.3 (материализация) корректно обобщает все три реальных пути резолюции firstSpaceId в devices.ts, включая ветку manualRoomWithoutArea (маркер ручной комнаты без HA area, контракт #3), которую ни issue, ни r1 не разбирали пофамильно — критерий «нет area, ведущей в пространство, и нет собственного space» покрывает её без исключений.
  • Материализация не задевает preview-путь черновика маркера (houseplan-card.ts:18818) — он не персистентный, обязательство §8.3 на него не распространяется, и в ТЗ об этом ничего лишнего не заявлено.
  • Новая пара мутантов (reorder-skips-materialization, materialization-touches-bound-markers) корректно закрывает ровно два направления ошибки материализации (пропуск и избыточность), заменив собой устаревший first-space-follows-order.
  • Владелец сам в комментарии честно отделил решение от подгонки: назвал две альтернативы снятия противоречия M2 («признать поле» / «обойтись без него») и выбрал вторую с объяснением — это ровно то, что PROCESS требует от продуктовых/технических решений, выносимых в ревью.

Чего не проверял

  • Не проверял реализацию — кода ещё нет, это ревью ТЗ.
  • Не запускал tsc/npm test/npm run build — класс изменения C (документация), гейты неприменимы, как и в r1.
  • Не пересматривал разделы, не тронутые дельтой (§1–§7, §10–§16, AC1/2/4-8, три из пяти мутантов) — они унаследованы из r1 на коммите 0fd2331, дельта их не меняла ни байтом.
  • Не проверял точность SHA d0a5bfa, названного владельцем в комментарии о закрытии r1 — объект не существует в репозитории; вместо этого верификация сделана по прямому диффу файла до HEAD (e57e1d9), см. «Как проверялось», п.3. Расхождение SHA не влияет на вывод ревью, но зафиксировано как наблюдение.

Итог

Обе находки r1 закрыты предметно. Правка §8.3 (материализация вместо якоря) — смена контракта поведения, а не косметика, и именно в ней найдена новая находка M3: §17 маркирует как «свободно меняемое» требование, которое сам же документ в §8.3 называет обязательным норматив и проверяет тестируемым AC3. Находка в скоупе, чинится точечной правкой текста (перенос/каветирование пункта 17.3), без изменения AC, скоупа или контракта поведения. High-находок нет. Вердикт — жёлтый: правка проходит третий цикл ревью ТЗ (заход r3, лимит циклов ревью ТЗ — 4).