From 2fae84af58122b7ea9615da6b052beddb64fea78 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 14:14:43 +0000 Subject: [PATCH] docs: review document for #402 Issue: #402 User-Visible: no --- docs/reviews/CODE-REVIEW-402-r1.md | 225 +++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 docs/reviews/CODE-REVIEW-402-r1.md diff --git a/docs/reviews/CODE-REVIEW-402-r1.md b/docs/reviews/CODE-REVIEW-402-r1.md new file mode 100644 index 00000000..abc2eeca --- /dev/null +++ b/docs/reviews/CODE-REVIEW-402-r1.md @@ -0,0 +1,225 @@ +# CODE-REVIEW-402-r1 + +- Issue: https://github.com/Matysh/houseplan-card/issues/402 +- ТЗ: `docs/specs/402-confirm-outside-main-branch.md` (ревизия 2, зелёное SPEC-REVIEW-402-r2) +- Диапазон: `origin/dev..HEAD`, SHA на момент вывода вердикта: `ccd870ddf77c9a03ec4618df3e9ded5f159d174b` +- Заход: код-ревью r1 · блокирующих циклов израсходовано 0/4 (первый заход этапа code) +- Класс изменения: A (продукт) + B (гейт-мутант) + C (документация/changelog) + D (бандл/скриншоты) + +## Скоуп + +Диагноз и контракт полностью изложены в ТЗ и обоих раундах ревью ТЗ (оба уже +пройдены, r1 — красный, r2 — зелёный на `11959e4c`). Реализация: + +- `src/houseplan-card.ts`: `render()` разбит на `_renderBody()` (старая цепочка + ранних `return`, без изменений по существу) + новый `render()`-обёртку, + которая достраивает `` рядом с телом, если тело не `noChange` и + не `nothing`; `_confirmDanger` отказывает немедленно (`Promise.resolve(false)`), + если `!this._config || !this.hass`. +- `scripts/mutation-gate.mjs`: новый мутант `danger-confirm-back-into-the-branch`, + возвращающий `hp-confirm` внутрь ветки, guard — новый смок. +- `demo/smoke_danger_confirm_branches.mjs` (новый, 140 строк, 9 проверок, + touch-эмуляция `hasTouch: true`). +- `test/optional-space-model-contract.test.mjs`: перенесена цель чтения на + `_renderBody`, добавлены статические проверки обёртки `render()`. +- `docs/CHANGELOG.md` / `docs/CHANGELOG.ru.md`: пункт про #402, в том же + коммите, что и код (`00b6f412`). +- `ccd870dd`: только классы D/C — пересборка бандла и обязательная пересъёмка + отпечатка документации (см. «Как проверялось»). + +## Как проверялось + +Зелёного Validate на этом SHA не найдено — все гейты ниже прогнаны лично. + +| Гейт | Команда | Результат | +|---|---|---| +| Типы | `npx tsc --noEmit` | чисто, 0 ошибок | +| Юниты | `npm test` | `# tests 1690`, `# pass 1689`, `# fail 0`, `# skipped 1` (совпадает с заявленным в хендоффе) | +| Сборка + сверка копий | `npm run build` затем `cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` | собрано за 15.6с, `cmp` без вывода — байт-в-байт совпадение; `git status` после сборки чист (комитнутый `dist` уже в актуальном состоянии) | +| Документация | `node scripts/check-docs.mjs` (обязателен — diff трогает `src/**`) | «Documentation checks passed (7 files, 10 external links)» | +| Бюджет (AC7) | `npm run bundle:budget` | `initial View: 287386 B gzip (budget 300000 B, headroom 12614 B)` — совпадает с числом из хендоффа день-в-день; предупреждение про запас бюджета — известный факт #367, не связано с #402 | +| Выбор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | 9 прямых совпадений (`_confirmDanger`/`_dangerConfirm`), 18 слабых (общее имя `_config`) | +| Прямые смоки (9/9) | `node demo/smoke_danger_confirm_branches.mjs` и ещё 8, см. ниже | все зелёные | +| Мутационная дисциплина | ручной откат правки + пересборка + прогон нового смока | смок подвисает (промис никогда не резолвится) — мутант красит смок, как заявлено в реестре | +| process-gate | `node scripts/process-gate.mjs --range origin/dev..HEAD` | «гейт пройден, предупреждений 0» | +| Инвентарь | `npm run inventory` | 1690 unit / 211 browser smokes — числа не копировались вручную | + +Прямые смоки и их результат: + +``` +demo/smoke_danger_confirm_branches.mjs 9/9 → OK (новый) +demo/smoke_danger_confirmation.mjs OK (AC6: без правок утверждений) +demo/smoke_orphan_space_references.mjs OK +demo/smoke_binding_picker.mjs OK +demo/smoke_free_walls.mjs OK +demo/smoke_hidden_flag.mjs OK +demo/smoke_lock_invariant.mjs OK — инвариант блокировок (SCOPE.md) не задет +demo/smoke_optional_space_model.mjs OK +demo/smoke_registryless_opening.mjs OK +``` + +**`smoke_danger_confirmation.mjs` печатает необработанный `TypeError` +(`_bindingHasHaPage`, чтение `.split` из `undefined` внутри рендера диалога +маркера) до итогового `OK`.** Проверил, не регрессия ли это: поднял отдельный +git worktree на чистом `origin/dev` (символическая ссылка на тот же +`node_modules`), собрал бандл, прогнал тот же смок — **то же самое исключение, +слово в слово**, воспроизводится и без диффа #402. Совпадает с тем, что описал +автор в хендоффе (неполные фикстуры диалога маркера, которые не относятся к +этой задаче). Не регрессия — не блокирует. + +Слабые совпадения (18, общее имя `_config`) не прогонял: diff #402 не трогает +ни один из путей, которые эти смоки проверяют (общие настройки, цветовые +пикеры, геометрия стен, раскладка вкладок) — общее имя случайное, не +зарегистрированная связь. + +**Не прогонял и почему:** +- `npm run golden:verify` — diff не меняет видимый статический результат: + разметка `` не изменилась ни на строку, изменилось только место + инстанцирования в дереве рендера; ни один голден-сценарий не открывает этот + диалог по умолчанию (ТЗ прямо фиксирует «в статике диалог не открыт»). +- `python -m pytest tests_backend -q` — ни один файл `custom_components/**/*.py` + не тронут. +- `node scripts/model-invariants.mjs` — diff не касается геометрии, `layout`, + `marker.space`, `open_spans`. +- Performance-профили — не названы в AC, путь не чувствителен к перфу + (структурная правка порядка рендера, не алгоритм). + +## Находки + +Ни одной High, ни одной Medium. Ниже — Low-наблюдения, все закрыты чтением +кода (не блокируют, сняты с записью, как разрешает §8/§2.7). + +### L1 — AC2 не покрыт смоком буквально (ветки `fixed_floor` pending/invalid, `!space`) + +ТЗ обещает доказательство «тот же смок, три ветки», но +`smoke_danger_confirm_branches.mjs` реально входит только в ветку +`!model.length` (онбординг) через `enterBranch([])`; веток `fixed_floor` +pending/invalid и `!space` смок не касается. + +**Закрыто чтением.** `render()` (`src/houseplan-card.ts:11193-11202`) — общая +обёртка над результатом `_renderBody()`: она не знает, какая именно ранняя +ветка вернула шаблон, и достраивает `_renderDangerConfirm()` к любому телу, +кроме `noChange`/`nothing`. Ветки `fixed.kind === 'pending'/'invalid'` +(`:11230-11254`) и `!space` (`:11286`) не трогают `_dangerConfirm` ни прямо, ни +косвенно — единственное место, где это поле меняется, это колбэк контроллера +(`:2151`, вызывается только из `HpConfirmController`). Значит поведение этих +трёх веток идентично уже доказанной ветке онбординга по построению, а не по +совпадению. Риск регрессии низкий: правка одна и общая для всех веток. + +### L2 — AC3 не проверен буквальным сценарием issue (клик по корзине → `_deleteServerPlan`) + +Смок вызывает `card._confirmDanger(...)` напрямую, а не через клик по +`