mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-04 05:41:34 +00:00
217 lines
20 KiB
Markdown
217 lines
20 KiB
Markdown
# 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
|
||
```
|