Files
houseplan-card/legacy/reviews/v1.68.0/SPEC-REVIEW-262-r1.md
T
Claudeandclaude[bot] cca9bc856b docs(reviews): документы ревью линий до v1.77.0 — в legacy/reviews (#682)
Волна 5 эпика #674, перенос документов ревью (класс C), сделан
инструментом: `node scripts/reviews-archive.mjs --through=v1.77.0 --apply`.
965 документов 332 задач разложены по `legacy/reviews/<тег>/` (v1.63.0 …
v1.77.0) — 330 задач доказаны трейлерами своей линии, два документа без
трейлера (#68, #94, работа до правила трейлеров) — по линии, где их
добавили. В `docs/reviews/` остались 154 документа задач с трейлером после
v1.77.0 — текущая линия v1.78.0 — и INDEX.md, пересобранный тем же
построителем. История не переписана: SHA-якоря «Материала раунда» живы.
Счёт раундов (`reviewRounds` по живому каталогу и архиву) совпадает до и
после переноса: 694 пары «этап:задача». Строка в `legacy/README.md`.

Issue: #682
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 22:10:47 +00:00

175 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 проверяем и снабжён
способом доказательства, технические утверждения о текущем поведении
подтверждены чтением кода, а не приняты на веру, продуктовых вопросов
владельцу нет.