docs: review document for #442

Issue: #442
User-Visible: no
This commit is contained in:
claude[bot]
2026-09-03 21:42:57 +00:00
parent 4bd30423f2
commit 215d59823b
+216
View File
@@ -0,0 +1,216 @@
# SPEC-REVIEW-442-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/442
- ТЗ: `docs/specs/442-marker-write-rollback.md`
- Этап: `S4-spec-review`, заход r1, блокирующих циклов израсходовано 0 из 4
- Ревьюер: Claude (роль «ревьюер ТЗ», отдельная сессия от автора)
- Материал ревью: ТЗ на коммите `a8426268db90666920801f9e09554aa81343ceba`
(ветка `issue/442-marker-write-rollback`), тело issue #442 и все его
комментарии на момент разбора (2026-09-03)
## Скоуп разбора
Первый заход — разбор полный: продуктовая рамка (`docs/SCOPE.md`), процесс
(`PROCESS.md` §2.4/§7.1, `AGENTS.md`), тело issue #442 и вся цепочка
комментариев (аналитика → вопросы владельцу → решение владельца → готовое
ТЗ), связанные issue #439 (guarded rollback общих настроек) и #441 (атомарный
route CRUD), канонические `docs/CONFIG-COMPATIBILITY.md` и `docs/VACUUM.md`,
терминология `docs/USER-GUIDE.ru.md`. Отдельно верифицированы факты о текущем
коде, на которых ТЗ строит контракт: `src/serialized-write-queue.ts`,
`src/houseplan-editor-runtime.ts`, `src/editors/vacuum-maps-section.ts`,
`custom_components/houseplan/validation.py`, наличие названных тестов/смоков.
## Как проверялось
1. Прочитаны `docs/SCOPE.md`, `PROCESS.md` (§1–§10), `AGENTS.md` полностью.
2. Прочитаны тело issue #442 и все 4 комментария (аналитика с оценками
8/10 польза · 9/10 разработка · 5/10 сложность · 5/10 риск · P2 · full
track; пачка вопросов Q1/Q2 с default-вариантами; решение владельца,
принявшее оба default; финальный комментарий со ссылкой на готовое ТЗ).
3. Прочитан весь текст `docs/specs/442-marker-write-rollback.md` (379 строк).
4. Прочитаны связанные issue #439 и #441 — оба уже приняты и описывают ровно
тот прецедент (guarded rollback, атомарный route writer), на который ТЗ
#442 ссылается как на эталон и явно не дублирует.
5. Прочитан `docs/CONFIG-COMPATIBILITY.md` целиком — не найдено противоречий
между заявленным «revision + content fingerprint» guard'ом и уже
задокументированным контрактом ревизий (#340), геометрии (#282/#306/#224)
и маршрутов пылесоса (#162).
6. Прочитан `docs/VACUUM.md` целиком — раздел «Calibration» действительно не
документирует порядок toast/подтверждение относительно ответа сервера, то
есть заявленный в ТЗ release-артефакт (обновление `VACUUM.md`) не
дублирует уже существующий текст, а закрывает реальный пробел.
7. Проверена терминология `docs/USER-GUIDE.ru.md`: «Редактор устройств»,
«Карты и этажи», «ручная подгонка», «Применить» — используемые в ТЗ
термины (кроме одного, см. Low ниже) не расходятся с интерфейсом.
8. Делегирована фактологическая проверка кода отдельному агенту (Explore) по
9 конкретным пунктам — см. «Верификация фактов» ниже. Каждый пункт
перепроверен по конкретным строкам файла, не на веру.
9. Самостоятельно прочитан `src/serialized-write-queue.ts` целиком (60 строк)
— независимо подтверждён guard: `host._cfgRev !== attempt.revision ||
(current !== attempt.attempted && fingerprint(current) !==
attempt.attemptedFingerprint)`, то есть ревизия **и** контент-фингерпринт
одновременно, включая ветку одного and того же object identity —
именно то, что ТЗ требует мутационно проверить в AC2.
10. Самостоятельно найден `_writeChain`/`expected_rev` (`src/houseplan-card.ts:7662-7730`,
`src/houseplan-editor-runtime.ts:1175` и далее) — сериализация запросов,
на которую ссылается контракт §2, существует и используется.
## Верификация фактов ТЗ против текущего кода (HEAD `a8426268`)
| # | Утверждение ТЗ/issue | Результат |
|---|---|---|
| 1 | `_saveMarker()` мутирует `cfg.markers` до `await _saveConfigNow()`, `catch` только снимает busy и показывает toast | **Подтверждено** — `src/houseplan-editor-runtime.ts:8270-8285` мутация, `:8330` await, `catch` `:8359-8366` без восстановления `_serverCfg` и без rebuild devices |
| 2 | *(из тела issue, не из ТЗ)* готовый guarded rollback применён «ровно к одному пути — общим настройкам (`_updateDecorStyle`)» | **Неточность в тексте issue.** Пара `optimisticAttempt`/`rollbackOptimistic` действительно встречается 3 раза (импорт + одна пара вызовов), но пара живёт в `_saveSettingsDialog` (`:10231-10275`, вызовы на `:10260`/`:10271`), а не в `_updateDecorStyle` (`:10204`) — та не содержит optimistic-логики вовсе и делегирует в `_persistDecorStyle` → обычный debounced `_saveConfig()`. Счётчик «18 вызовов `_saveConfig()`» корректен |
| 3 | `_vacSaveMatrix()` мутирует in-place, коллеры показывают success toast и закрывают UI до подтверждения записи сервером | **Подтверждено** — мутация `m.vacuum`, `_maybeRebuildDevices()`, невawait-нутый debounced `_saveConfig()`; `toast.autocal_done`/`toast.cal_done` показываются сразу после синхронного (boolean) возврата `_vacSaveMatrix`, без ожидания сервера |
| 4 | Route CRUD в `vacuum-maps-section.ts` уже использует `optimisticAttempt`/`rollbackOptimistic` (эталон #441) | **Подтверждено** — импорт `:21`, вызовы `:105`/`:116` |
| 5 | Guard сравнивает revision **и** content fingerprint; есть `_writeChain`/`expected_rev` | **Подтверждено** самостоятельно (см. «Как проверялось» п.9-10) |
| 6 | Четыре семантических валидатора маркера существуют под этими именами | **Подтверждено** ровно с этими именами (кроме generic «проверка controls», которую ТЗ и не называет точным именем) — `validate_marker_value_badges` (`:811`), `validate_marker_vacuum_routes` (`:886`), `validate_marker_light_entities` (`:930`), `validate_marker_controls` (`:972`, цикл — `:1049`) в `custom_components/houseplan/validation.py` |
| 7 | Названные в плане тестирования файлы существуют | **Подтверждено** — все 6: `test/serialized-write-queue.test.mjs`, `demo/smoke_vacuum_route_draft.mjs`, `demo/smoke_vacuum_firstuse.mjs`, `demo/smoke_vacuum_multifloor.mjs`, `demo/smoke_vacuum.mjs`, `demo/smoke_dialog_zombie.mjs` |
| 8 | `docs/VACUUM.md` ещё не документирует reject/success UX калибровки | **Подтверждено** — раздел «Calibration» молчит именно об этом |
| 9 | `scripts/mutation-gate.mjs` существует | **Подтверждено** |
Итог верификации: единственное расхождение с кодом (пункт 2) находится в
**теле issue**, не в тексте ТЗ — сам документ `docs/specs/442-...md` нигде не
называет `_updateDecorStyle` и не строит контракт на этом имени; он ссылается
на механизм обобщённо («общий writer», «уже готовый `optimisticAttempt`/
`rollbackOptimistic`»), что после проверки оказалось верным независимо от
того, в какой именно функции этот механизм физически живёт. Так что дефект
не проникает в исполнимость ТЗ — см. Low ниже, почему он всё равно стоит
записи.
## Соответствие §7.1
Все обязательные разделы присутствуют: Сценарий · Что человек увидит до и
после · Подтверждённая проблема · Скоуп/Не-скоуп · Контракт поведения (6
подпунктов) · Данные и совместимость · Touch/клавиатура/доступность ·
Ошибки и крайние случаи · AC1–AC9 с доказательством (unit/smoke/test double
у каждого) · План тестирования · Карта реализации · Риски и rollback ·
Release-артефакты · Принятые предположения.
Владелец отвечал только на продуктовые вопросы (Q1 — какие marker-пути
входят в атомизацию; Q2 — что делать с calibration UI при отказе), оба с
default-вариантом и явным принятием; технические решения (immutable
candidate, где именно живёт guard, разбиение на pure-модуль) автор
резервирует себе явным блоком «Принятые предположения» в конце — ровно так,
как требует процесс.
## Находки
### Low-1. Неточное имя функции в теле issue (не в ТЗ)
Issue #442 в разделе «Проблема» называет уже реализованный guarded-rollback
путь `_updateDecorStyle`; фактически это `_saveSettingsDialog`
(`src/houseplan-editor-runtime.ts:10231`). `_updateDecorStyle` — соседняя
функция без optimistic-логики. Ошибка не искажает контракт ТЗ (там имя
функции не упоминается вовсе, только «общий writer» и сам механизм), поэтому
не блокирует и не требует правки ТЗ. Фиксирую как заметку для реализации:
если разработчик будет ориентироваться на текст issue при поиске «уже
готового пути», неверное имя может стоить нескольких минут поиска не там —
не более того.
**Решение ревьюера:** снимается без правки, запись сделана для истории раунда.
### Low-2. Явного раздела «i18n» нет — контент есть, но не под заголовком
§7.1 перечисляет i18n отдельным обязательным разделом. В ТЗ #442 подтверждение
«новых строк нет» разнесено по двум местам («Новых targets, жестов и строк
i18n нет» в разделе Touch; «новых текстов ошибок... не добавляется» в
Не-скоуп) без отдельного заголовка. По содержанию требование выполнено
(эксплицитно и дважды непротиворечиво), поэтому не блокирует переход в
`S5-ready` — DoR требует «i18n: ключи en+ru перечислены», а перечислять
здесь нечего, что и сказано явно. Отмечаю как стилистическую придирку, не
находку, которая должна кого-то останавливать.
**Решение ревьюера:** снимается без правки.
## Что проверено и корректно
- **Продуктовая рамка.** Задача закрывает J6 (`docs/SCOPE.md`: «Keep the plan
true as the home evolves» — «View не должен показывать конфигурацию,
которую сервер отверг»), сценарий и «что человек увидит» — оба заполнены
без терминов реализации первым предложением, персона (Home admin) и
поверхность (Device editor + calibration overlay) названы.
- **Скоуп/не-скоуп** зеркалят принятые owner defaults Q1/Q2 дословно, без
расширения и без урезания против решения владельца.
- **Технические утверждения о текущем коде** — 8 из 9 проверенных фактов
подтверждены чтением реальных строк; единственное расхождение (Low-1) не
влияет на сам контракт ТЗ.
- **Контракт поведения** опирается на реально существующий примитив
(`optimisticAttempt`/`rollbackOptimistic` с двойным guard'ом ревизия+
фингерпринт, уже обкатанным в #441 и в `_saveSettingsDialog`), а не
изобретает новый механизм с нуля — минимизирует риск.
- **AC1–AC9** каждый указывает способ доказательства (unit / browser smoke /
test double) и достаточно конкретен для написания теста без дополнительных
решений: например, AC2 явно называет два обязательных мутанта
(unconditional rollback; пропуск fingerprint для same identity),
предвосхищая требование код-ревью §2.7 о «таблице чем краснеет».
Формулировки допускают ровно одну трактовку каждая — не нашёл AC, который
можно закрыть двумя разными реализациями с разным видимым поведением.
- **Регрессионный периметр** (AC8, таблица «Ошибки и крайние случаи», строка
«route CRUD #441 reject/retry») явно защищает уже принятый атомарный route
writer от повторной реализации или порчи — соответствует правилу «не
подменять существующий механизм».
- **Данные и совместимость**: явно «миграции нет», persisted schema не
меняется, что соответствует характеру задачи (только client-side
транзакционность) и не противоречит ни одному разделу
`CONFIG-COMPATIBILITY.md`.
- **Touch/доступность**: busy — настоящий disabled, а не только визуальный
индикатор; фокус остаётся в открытом диалоге — соответствует
`TOUCH-SUPPORT.md` (редакторы desktop-first, но контракт не деградирует
специально под touch).
- **Release-артефакты** называют оба changelog, `CONFIG-COMPATIBILITY.md`,
`VACUUM.md` (подтверждено выше, что там действительно есть пробел для
заполнения), условие для docs-скриншотов и явное «golden не нужен, если
подходящей канонической поверхности нет» — не оставляет открытых пунктов
DoR.
- **Догадки, выданные за решения**: не найдено. Каждое место, где решение
не следует напрямую из существующего документа, либо помечено как
default-ответ владельца (Q1/Q2), либо вынесено в явный блок «Принятые
предположения» в конце ТЗ, что ревьюер вправе (и не находит повода)
оспорить.
## Чего не проверял
- Не запускал `npx tsc --noEmit`/`npm test`/`npm run build` — на этапе
ревью ТЗ кода ещё нет (это этап `spec`, не `code`), гейты §8 к этому
документу неприменимы.
- Не проверял мутационные сценарии AC2 практически (мутант ещё не написан —
это работа код-ревью, не спек-ревью); отметил только, что защитный
примитив уже существует и уже используется в проде (#441).
- Не проверял браузерные смоки исполнением — только их наличие как файлов
(см. таблицу верификации, п.7). Их фактическое покрытие AC — задача
реализации и код-ревью.
- Не проверял `docs/specs/README.md` на предмет обязательной строки в
таблице issue↔ТЗ — это релиз-артефакт автора ТЗ, не блокирует ревью.
## Вердикт
**Зелёный.** High: 0, Medium: 0. Обе Low-находки сняты без правки текста ТЗ
(записаны выше для истории раунда, не блокируют). ТЗ полно по §7.1, каждый
AC однозначен и снабжён способом доказательства, продуктовые вопросы решены
владельцем до написания ТЗ, а технические утверждения о существующем коде
подтверждены независимым чтением, а не приняты на веру.
## Материал раунда
- Ветка: `issue/442-marker-write-rollback`
- SHA ТЗ: `a8426268db90666920801f9e09554aa81343ceba`
- Путь ТЗ: `docs/specs/442-marker-write-rollback.md`
- Найти дерево: `git log --all --format='%H %T' | grep <дерево HEAD>`
- Найти блоб ТЗ: `git log --all --find-object=<блоб> -- docs/specs/442-marker-write-rollback.md`
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/442-marker-write-rollback`, коммит `a8426268db90` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `5d3fbe71add69e722a9f6d87b1ce04dcea586729`
```
git log --all --format='%H %T' | grep 5d3fbe71add6
```
- ТЗ `docs/specs/442-marker-write-rollback.md`, блоб `a9dcca2ff269a1c309387902875a4937efbd4ebf`
```
git log --all --find-object=a9dcca2ff269a1c309387902875a4937efbd4ebf -- docs/specs/442-marker-write-rollback.md
```