docs: review document for #262

Issue: #262
User-Visible: no
This commit is contained in:
claude[bot]
2026-08-23 13:25:44 +00:00
parent 7b3ea3769a
commit a0b39b626f
+174
View File
@@ -0,0 +1,174 @@
# SPEC-REVIEW-262-r1
- Issue: [#262](https://github.com/Matysh/houseplan-card/issues/262) — Deleting entities prevents them from being added again later
- ТЗ: [`docs/specs/262-readd-child-entity-after-device-delete.md`](https://github.com/Matysh/houseplan-card/blob/issue/262-readd-child-entity/docs/specs/262-readd-child-entity-after-device-delete.md)
- Ветка: `issue/262-readd-child-entity`, коммит на ревью: `7b3ea37`
- Этап: `S4-spec-review` (ревью ТЗ, PROCESS.md §2.4)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4 (зелёный вердикт бюджет не тратит, #227)
- Диф ревью: `git diff origin/dev...origin/issue/262-readd-child-entity` — только
`docs/specs/262-readd-child-entity-after-device-delete.md` (новый файл) и одна
строка в `docs/specs/README.md`. Продуктового кода нет — соответствует стадии
`S3→S4` (Rule #1: код трогают только с `S5-ready`).
## Скоуп
Bug-репорт: после удаления HA-**устройства** с плана его отдельные дочерние
**сущности** невозможно добавить обратно даже при включённом флаге «Показывать
сущности» — можно вернуть только всё устройство целиком. Аналитика владельца
(комментарий в issue) разложила репорт на три сценария и подтвердила, что
именно третий («удалить device, добавить одну entity того же device») — баг,
остальные два уже работают штатно. ТЗ фиксирует контракт на этот третий
сценарий: exact live `entity:X` должна перевешивать exact/parent tombstone
только для себя самой, не воскрешая ни родительское устройство, ни соседние
сущности.
Соответствие `docs/SCOPE.md`: закрывает J6 («Keep the plan true as the home
evolves») — часть про «поддерживать план в актуальном состоянии» и явный выбор
нужной HA-привязки после удаления. Продуктовой рамке не противоречит, новых
поверхностей не открывает.
## Как проверялось
Ревью технической части ТЗ построено не на доверии тексту, а на сверке каждого
фактического утверждения (§3, §4, §6, §8) с кодом на `origin/dev` — здесь
именно тот случай, где «утверждение о поведении не в документах и не
помеченное как предположение» было бы находкой, если бы не подтвердилось.
1. **§4.1 (picker разрешает только точный tombstone binding).** Прочитан
`_bindingCandidates()` в `src/houseplan-card.ts` (ветка «Individual entities
of devices — behind the show entities checkbox»): `removedBindings` — Set
точных строк binding с `m.removed`; исключение `!removedBindings.has(v)`
сравнивается с `v = 'entity:'+eid`, а не с `'device:'+parentId`. При
`device:D` tombstone `isRemovedPlanEntity(h, eid, removed)` возвращает
`true` (по `removed.devices.has(deviceId)`), `removedBindings.has('entity:'+eid)`
— `false` ⇒ строка `continue` отбрасывает X из списка даже при включённом
чекбоксе. Ровно то, что описывает ТЗ. Подтверждено чтением.
2. **§4.2 (фикса списка недостаточно).** Прочитан `_saveMarker()`:
`replacedRemovedIds = markers.filter(m => m.removed && m.binding === dlg.binding)`
— снимает tombstone только с тем же exact binding (`entity:X`), родительский
`device:D` tombstone переживает сохранение. Прочитан `buildDevices()`, ветка
`kind === 'entity'`: `if (isRemovedPlanEntity(fullHass, ref, removed)) continue;`
— уже сохранённый **живой** explicit marker `entity:X` всё равно отбрасывается,
потому что `removed` строится из того же `markers[]`, где всё ещё лежит
`device:D` tombstone. Оба утверждения ТЗ подтверждены чтением, не
домыслены.
3. **§8 (schema-valid combination).** Прочитан `validation.py`: схема маркера
не запрещает сосуществование двух записей с разными `binding` и независимым
`removed`; для `device:D removed:true` + живой `entity:X` нет constraint,
который бы это отклонил. Утверждение «уже schema-valid» — не гипотеза.
4. **§3 (воспроизведение).** Прочитан `demo/smoke_binding_picker.mjs` (добавлен
в #263): блок 3 буквально помечает
`o.knownDefect262ChildEntityBlocked = !(await offered(true)).includes('entity:'+childEntity)`
с комментарием «почитают #262, смок покраснеет, потребовав перевернуть
проверку» — соответствует плану автотестов ТЗ (AC1: «проверка known defect
перевёрнута в положительную»).
5. **Согласованность с #226.** Прочитан `docs/specs/226-entity-parent-dedup.md`
(решения 4–5: «явная конфигурация сильнее автоматической», «tombstone не
владеет сущностью») — контракт §6.4 ТЗ #262 («явная D не подавляет явную X»)
этому не противоречит, использует уже принятую модель, не переопределяет её.
6. **Согласованность с #104.** `docs/specs/104-opening-ha-reference-after-marker-delete.md`
сам ссылается на `isRemovedPlanEntity`/`isRemovedPlanSource` — подтверждает
заявление ТЗ, что точные ссылки opening contact/lock уже независимы от
marker tombstones и не задеты этим исправлением.
7. **Регистрация smoke-связи.** `scripts/smoke-links.mjs` уже связывает символы
`removedPlanBindings`, `isRemovedPlanEntity`, `deletePlanMarkerRecords` со
`smoke_binding_picker.mjs` (внесено в #263) — заявление §11 ТЗ («имя
`_bindingCandidates` остаётся прямым совпадением, дублировать не требуется»)
корректно, связь уже есть, ничего добавлять не нужно.
8. **Терминология UI.** Флаг называется в ТЗ «Показывать сущности» —
совпадает с формулировкой из `docs/USER-GUIDE.ru.md` (строки 657, 1587), а
не изобретён. (Сам i18n-ключ `marker.show_entities` в коде переведён как
«Отображать сущности» — расхождение между `USER-GUIDE.ru.md` и `ru.json`
существует независимо от #262, автор ТЗ корректно взял термин из
пользовательского гайда, как требует AGENTS.md; фиксирую это отдельно ниже
как находку вне скоупа, не блокирующую этот заход.)
9. **Обязательные разделы ТЗ (PROCESS.md §7.1).** Сверены построчно: сценарий
и персона (§1) — есть, включая поверхность и момент; что человек видит
до/после одной фразой без терминов реализации (§2) — есть, без упоминаний
функций/tombstone; проблема (§3–4); скоуп и не-скоуп (§5); контракт
поведения (§6); UX/touch/a11y (§7); данные/compatibility/миграция (§8);
i18n/security/performance (§9); AC1…AC6 с доказательством (§10); план
автотестов (§11); файлы (§12); release-артефакты (§13); риски (§14);
откат (§15); блок принятых предположений (§16). Ничего не пропущено.
10. **Проверка «не выдана ли догадка за факт».** Все технические утверждения о
текущем поведении (§3, §4, §8) проверены чтением реального кода (пункты
1–3 выше), а не приняты на слово. Пять пунктов §16 корректно помечены как
предположения и являются техническими (форма реализации, позиция нового
маркера, отсутствие whitelist для registry-hidden), а не спрятанными
продуктовыми решениями — ни один не требовал вопроса владельцу.
11. **AC на проверяемость и доказательства.** AC1–AC6 однозначны, у каждого
указан способ доказательства (`smoke`/`unit`/«существующие
unit/smoke #104/#161/#226»/gates), совпадающий с §11 и с уже
зарегистрированными файлами/тестами.
## Находки
Блокирующих (High) и находок Medium в скоупе — нет.
### Medium — вне скоупа (не блокирует, заводится отдельным issue по §12 PROCESS.md)
Расхождение термина чекбокса между `docs/USER-GUIDE.ru.md` («Показывать
сущности», строки 657 и 1587) и фактическим i18n-ключом `marker.show_entities`
в `src/i18n/ru.json` («Отображать сущности»). Автор ТЗ #262 действовал верно —
взял формулировку из канонического пользовательского гайда, как требует
AGENTS.md, — но само расхождение живёт в репозитории независимо от #262 и не
имеет отношения к его сценарию. Правка в этой ветке была бы посторонним
скоупом (диалог маркера правит другой файл — `houseplan-card.ts`/`ru.json`, не
затронутый ТЗ #262). Заведён отдельным issue — см. решение ниже.
Низких (Low) находок нет.
## Что проверено и корректно
- Диагноз двухчастного дефекта (picker exact-binding whitelist + runtime
`isRemovedPlanEntity` в `buildDevices`) — точное соответствие коду `dev`, не
гипотеза.
- Контракт §6 (матрица Add picker, транзакция сохранения, runtime-приоритет,
что остаётся удалённым) — самосогласован: список Add специально шире, чем
runtime-построение (все дети D становятся видимыми в Add, но
строится/рендерится только явно сохранённый marker) — это осмысленное
разделение, а не противоречие.
- Совместимость с уже принятыми контрактами #226, #161, #104 подтверждена по
их собственным спекам и коду, не только заявлена.
- Данные/миграция: комбинация `device:D removed:true` + живой `entity:X` уже
schema-valid, новых полей и model bump нет — подтверждено чтением
`validation.py`.
- Воспроизведение (§3) подтверждено существующим browser smoke
`demo/smoke_binding_picker.mjs` (#263), который уже фиксирует
`knownDefect262ChildEntityBlocked: true` и явно рассчитан на переворот
проверки этим исправлением.
- Все обязательные разделы ТЗ (PROCESS.md §7.1) присутствуют и по существу, не
формально.
- Продуктовых вопросов владельцу нет, и это оправдано: ни одна неопределённость
в ТЗ не задевает то, что видит или делает пользователь сверх уже описанного
в §1–§2; сценарий закрыт исходным репортом и аналитическим комментарием
владельца.
- i18n-раздел корректен: новых ключей нет, использованная терминология взята
из `docs/USER-GUIDE.ru.md`, а не изобретена.
## Чего не проверял
- Гейты `typecheck`/`test`/`build`/`check-docs` не гонялись: этап — ревью ТЗ
(`S3→S4`), продуктового и тестового кода в дифе нет (только новый
`docs/specs/*.md` и строка в `docs/specs/README.md`), эти гейты относятся к
этапу код-ревью (PROCESS.md §2.7, §8) и будут прогнаны там.
- Не проверялась работоспособность будущей реализации — её ещё нет; предмет
этого ревью — выполнимость и однозначность ТЗ, а не код.
- Не запускал browser smoke `demo/smoke_binding_picker.mjs` — код на этой
ветке не менялся относительно `dev`, поведение смока не могло измениться;
чтения исходника было достаточно, чтобы подтвердить, что смок уже
воспроизводит описанный в ТЗ дефект.
- Не проверял golden/performance/mutation-gate — на этой стадии нет ни
визуальных, ни мутационных изменений: диф ограничен документацией.
## Решение
Найдена ровно одна находка — Medium, вне скоупа задачи (терминологическое
расхождение чекбокса, не создано и не усугублено этим ТЗ). Она не блокирует
переход в `S5-ready` и заводится отдельным issue со ссылкой на #262, а не
чинится в этой ветке.
**Вердикт: зелёный.** ТЗ выполнимо, однозначно, каждый AC проверяем и снабжён
способом доказательства, технические утверждения о текущем поведении
подтверждены чтением кода, а не приняты на веру, продуктовых вопросов
владельцу нет.