mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
Проверка (CI) / Классификация изменённых файлов (push) Successful in 23s
Проверка (CI) / Предполётные проверки: документация, провенанс, процесс (push) Failing after 1m5s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Successful in 48s
Проверка (CI) / HACS: валидация репозитория (push) Failing after 19s
Проверка (CI) / Hassfest: манифест интеграции (push) Failing after 24s
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Failing after 7m4s
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Failing after 10m10s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Issue: #456 User-Visible: no
150 lines
23 KiB
Markdown
150 lines
23 KiB
Markdown
# CODE-REVIEW-456-r2
|
||
|
||
- **Issue:** #456 — копирование пространства без комнат и устройств
|
||
- **Ветка:** `issue/456-copy-space`
|
||
- **SHA материала:** `7dd5b4798a056b7f31dbb48ff00e833d90df5b9c` (сверено `git rev-parse HEAD` непосредственно перед выводом вердикта)
|
||
- **Заход:** r2 · блокирующих циклов израсходовано до этого разбора: 1/4 (зелёный вердикт этого захода бюджет не тратит, #227)
|
||
- **ТЗ:** `docs/specs/456-copy-space.md`, SPEC-REVIEW зелёный (`docs/reviews/SPEC-REVIEW-456-r1.md`)
|
||
- **Предыдущий раунд:** `docs/reviews/CODE-REVIEW-456-r1.md`, вердикт жёлтый, High: 0, Medium: 3 (в скоупе), SHA материала `2c355ee9ec97ce65fd6b9525f3f82cca3668c8e8`
|
||
|
||
## 0. Материал r1 и почему разбор — по дельте, а не заново
|
||
|
||
**SHA `2c355ee9` мёртв** (`git cat-file -t` — «could not get object info»): это ожидаемо, автор в хендоффе прямо пишет «Ветка приведена к актуальному `dev`» — между r1 и r2 сделан ребейз. Дерево материала r1 из блока «Материал раунда» (`3da0804bd0118dde2f3ccfcc41e253a73ce8ab66`) тоже не резолвится ни в одном объекте репозитория — ожидаемо, ребейз меняет полное дерево коммита, даже если патч коммита не менялся.
|
||
|
||
Проверил, не «другой ли это код» (§7.2/§2.10 требуют полного разбора при таком ребейзе), контентным сравнением, а не на слово автора:
|
||
|
||
- `origin/dev` на момент r1 был `6ef0804b` (указано в CODE-REVIEW-456-r1.md), сейчас `origin/dev` = `de30066b`.
|
||
- `git log --oneline 6ef0804b..de30066b` — **ровно один** новый коммит: `de30066b test(gates): мутанты на восемь модулей горячего пути из #451`, класс B (`scripts/mutation-gate.mjs`, `test/mutation-gate.test.mjs`, только добавления — 145 строк), не задевает ни один файл из `src/space-copy*`/`space-dialog`/`houseplan-*-runtime`.
|
||
- Коммит `feat: copy spaces without room bindings` (`b286d7ba`, текущий эквивалент бывшего `2c355ee9`) при диффе против нового `de30066b` показывает `scripts/mutation-gate.mjs` как **+115 строк** (11 новых мутантов #456, дописанных в конец существующего массива) — то есть содержимое патча идентично тому, что описано и построчно проверено в CODE-REVIEW-456-r1.md §4; ребейз сдвинул только базу, не тело коммита. Конфликтов не было (иначе диапазон дат/структура коммитов были бы другими).
|
||
|
||
Вывод: ребейз реальный, но **нелокальным (§2.10 «другая подсистема / смена контракта») не является** — задета только инфраструктура мутационного гейта другой задачи (#451), без пересечения с кодом #456. Разбираю по дельте: r1-материал восстановлен по коммиту `b286d7ba` (патч-эквивалент бывшего `2c355ee9`, тот же список файлов, тот же diffstat, что зафиксирован в CODE-REVIEW-456-r1.md §1/§4), дельта этого раунда — три коммита поверх него плюс коммит документа ревью:
|
||
|
||
```
|
||
b286d7ba feat: copy spaces without room bindings (= материал r1, патч не менялся)
|
||
6dd9fb03 docs: refresh screenshot source fingerprint (M1)
|
||
2c787913 test: account for space copy action (M3, часть — footer width)
|
||
51082024 docs: review document for #456 (публикация CODE-REVIEW-456-r1.md)
|
||
7dd5b479 test: cover the space copy workflow (M2 golden accept + M3 новый смок)
|
||
```
|
||
|
||
Ни один файл `src/**`/`custom_components/**/*.py` в дельте не тронут — вся дельта класса B/C/D (демо-смоки, golden-эталон, документация). Значит AC1–AC13 (функциональность фичи) дельта не задевает: доказательства r1 (11 реальных прогонов мутантов + живой браузерный E2E) наследуются без повторного прогона. Повторной проверке подлежат ровно три находки r1.
|
||
|
||
## 1. Скоуп (не изменился с r1)
|
||
|
||
Кнопка «Копировать» в footer настроек пространства (edit-only, вне `dialog-action-danger`), диалог имени, предварительный whole-plan Optimize с warning-подтверждением, модуль `src/space-copy.ts` конвертирует `wall_segments[]`+`partitions[]` источника в самостоятельные перегородки копии с перевязкой `openings[]` и очисткой `contact`/`lock`. Комнаты/устройства/markers/layout не переносятся.
|
||
|
||
## 2. Как проверялось
|
||
|
||
### 2.1 Дешёвые гейты — прогнаны заново (код изменился, стоят минуты)
|
||
|
||
| Гейт | Команда | Результат |
|
||
|---|---|---|
|
||
| Typecheck | `npx tsc --noEmit` | зелёный, без вывода |
|
||
| Unit/integration | `npm test` | **1975 tests, 1974 passed, 0 failed, 1 skipped** — совпадает с хендоффом автора |
|
||
| Build + bundle parity | `npm run build && npm run bundle:sync && cmp dist/houseplan-card.js custom_components/.../houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js` | зелёный, все три копии синхронны |
|
||
| `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | — | зелёный: «Новых any нет» (600 добавленных строк в 6 файлах) |
|
||
| `node scripts/process-gate.mjs` | — | «гейт пройден, предупреждений 0» (диапазон `origin/dev..HEAD`, 7 коммитов) |
|
||
|
||
### 2.2 Гейты, закрывающие находки r1 — прогнаны целиком (не только для проверки на слово автора)
|
||
|
||
| Гейт | Команда | Результат |
|
||
|---|---|---|
|
||
| `node scripts/check-docs.mjs` (M1) | — | **зелёный**: «Documentation checks passed (7 files, 12 external links)» |
|
||
| `npm run golden:verify` (M2, полный матрикс — `--scenario=` на verify запрещён самим скриптом, «must run the complete matrix») | — | **153/153 passed**, включая `space-room-color-popover-desktop-ru` |
|
||
| `node demo/smoke_space_copy.mjs` (M3) | — | **зелёный**, все 7 проверок `true` (см. §3 разбор содержимого) |
|
||
| `node demo/smoke_dialog_footer_width.mjs` (правки M3 в этом файле) | — | зелёный, `space`/`space_de` теперь `buttons: 4` (было 3) на desktop и narrow |
|
||
| `node scripts/mutation-gate.mjs --check` | — | зелёный, реестр валиден, все 11 мутантов #456 на месте (`space-copy-title-always-reuses-two` … `space-copy-does-not-select-result`) |
|
||
|
||
### 2.3 Не прогонялось повторно — обосновано в §5 «Унаследовано»
|
||
|
||
`python -m pytest tests_backend`, полная матрица `demo/smoke_*.mjs` (223 файла), реальный мутационный прогон (`--id=<mutant>`, не только `--check`) — делта их не задевает, доказательства r1 остаются в силе (см. §5).
|
||
|
||
## 3. Проверка содержимого `demo/smoke_space_copy.mjs` (M3) — не только «файл существует»
|
||
|
||
Прочитал скрипт целиком, не только его зелёный результат:
|
||
|
||
- Использует `launch()`/`page.evaluate` против **production-бандла** и **реального demo-backend** (`card.hass.callWS` обёрнут для перехвата порядка вызовов, но делегирует в `baseCall` — не мок с фиксированным ответом).
|
||
- Проверяет **порядок** реальных WS-вызовов при Accept: `acceptedCalls[0].type === 'houseplan/plan/optimize'`, `acceptedCalls[1].type === 'houseplan/config/set'` — не просто «вызов был», а именно последовательность (AC4/AC12).
|
||
- Проверяет **ноль записей** при Cancel (`cancelWrites.length === 0`) — защитный AC5-класса случай подтверждён утверждением о нуле сайд-эффектов, а не отсутствием ошибки.
|
||
- Проверяет **форму итоговой копии** предметно: `rooms?.length === 0`, `wall_segments?.length === 0`, `partitions?.length > 0` — то есть именно AC6/AC9 (не room_drafts/wall_segments в копии), не только факт создания записи.
|
||
- Проверяет **позицию вставки**: `card._serverCfg.spaces[1]?.id === cleanCopy.id` (сразу после источника, который является `spaces[0]`) — AC1/§7.1.
|
||
- Проверяет предложенное имя `proposedName.endsWith(' (2)')` — тот же контракт, что и мутант `space-copy-title-always-reuses-two`, но через реальный DOM-рендер инпута, а не только через чистую функцию.
|
||
|
||
Прогон подтверждает результат:
|
||
```json
|
||
{
|
||
"copyButtonIsNeutralAndDefaultNameIsNumbered": true,
|
||
"optimizeWarningNamesTheWholePlan": true,
|
||
"optimizeCancelMakesZeroWritesAndKeepsName": true,
|
||
"optimizeAcceptWritesInOrder": true,
|
||
"acceptCreatesAndEntersRoomlessCopy": true,
|
||
"cleanCopyNeedsNoExtraConfirmation": true,
|
||
"cleanCopyIsInsertedAfterSourceAndSelected": true
|
||
}
|
||
OK
|
||
```
|
||
Это не тривиально-истинные ассерты: каждый сравнивает с конкретным значением/длиной/порядком, а не проверяет отсутствие исключения. Смок закрывает M3 как постоянный регрессионный барьер, а не как разовое подтверждение.
|
||
|
||
## 4. Закрытие раунда r1
|
||
|
||
| Находка r1 | Чем закрыта | Где это видно |
|
||
|---|---|---|
|
||
| **M1** — `check-docs.mjs` красный, отпечаток скриншотов устарел | Коммит `6dd9fb03 docs: refresh screenshot source fingerprint`: пересчитан `sourceFingerprint` в `docs/images/screenshots.json` (`eb0b787c…` → `945121a2…`). Проверено, что все 10 `imageSha256` **не изменились** (`git diff b286d7ba..6dd9fb03` — во всех сценах меняется только `sourceSha256`/шапка, `imageSha256` идентичен) — правка чисто механическая, PNG не пересниманы заново без причины | `node scripts/check-docs.mjs` → «Documentation checks passed (7 files, 12 external links)», прогнано мной заново на HEAD |
|
||
| **M2** — `golden:verify` красный, `space-room-color-popover-desktop-ru` = different | Коммит `7dd5b479` обновляет `demo/golden/baselines/baselines-index.json` (только хэш этой одной сцены: `9bbbcf9c…` → `04bf325d…`, проверено скриптом — из 153 записей `scenarios` изменилась ровно одна, ноль добавленных/удалённых сцен) и сам PNG-эталон. Коммит несёт обязательные `Release: v1.72.0-beta.4` + `Baseline-Reviewed: …/actions/runs/33948778925` (правило: коммит, трогающий `demo/golden/baselines/**`, обязан их нести — трейлер на месте, `git show -s --format=full HEAD` подтверждает порядок) | `npm run golden:verify` → **153 passed, 0 different** на полном матриксе (прогнано мной целиком, не только эта сцена — `--scenario=` на verify запрещён скриптом-политикой) |
|
||
| **M3** — заявленный в ТЗ §17.6 browser smoke для Copy отсутствовал | Коммит `7dd5b479` добавляет `demo/smoke_space_copy.mjs` (141 строка, разобран построчно в §3): clean-copy, Optimize→Cancel (0 записей), Optimize→Accept→переход — все три ветки из ТЗ §17.6 покрыты. Дополнительно `2c787913` актуализирует ожидания `smoke_dialog_footer_width.mjs` (3→4 кнопки в footer пространства) под новую кнопку «Копировать» | `node demo/smoke_space_copy.mjs` → зелёный, все 7 полей `true` (см. §3); `node demo/smoke_dialog_footer_width.mjs` → зелёный, `buttons: 4` для `space`/`space_de` desktop и narrow |
|
||
|
||
Все три находки закрыты предъявленной строкой кода/данных и перепрогнанным гейтом, а не заявлением автора.
|
||
|
||
## 5. Унаследовано из r1 (без повторной проверки)
|
||
|
||
Документ: `docs/reviews/CODE-REVIEW-456-r1.md`, материал восстановлен по патч-эквивалентному коммиту `b286d7ba` (см. §0 — тот же diffstat, что `2c355ee9` в r1: 52 файла, `src/space-copy.ts`, `src/space-copy-runtime.ts`, `src/plan-optimize-write.ts`, `src/space-dialog.ts`, правки в `houseplan-editor-runtime.ts`/`houseplan-card.ts`, i18n×4).
|
||
|
||
- **Все 13 AC (AC1–AC13)** — функциональная реализация не менялась дельтой; наследую вывод r1: 11 защитных AC подтверждены реальным прогоном мутантов (`--id=<id>`, не только `--check`), AC1/AC13 — прямым сравнением. Таблица «чем краснеет» из r1 §4 остаётся в силе, я лишь перепроверил `--check` (реестр валиден, все 11 id на месте) — не гонял мутанты по новой, т.к. `src/space-copy*.ts` не менялся.
|
||
- **Живой браузерный E2E прогон** автора-предыдущего-раунда (кнопка → имя → Optimize warning → Accept → чистая копия) — принимаю как выполненный; теперь дополнительно закреплён постоянным `demo/smoke_space_copy.mjs` (это и есть закрытие M3, см. §4).
|
||
- **Backend**: `tests_backend/test_validation.py::test_issue_456_roomless_copy_with_rehosted_openings_is_valid` — `378 passed, 4 skipped` в r1, файл не тронут дельтой, не перепрогонял (песочница этого раунда тоже не несёт `pytest`/HA-стек — `python3 -m pytest` даёт `No module named pytest`, то же ограничение, что у r1).
|
||
- **Полная матрица `demo/smoke_*.mjs` (223 файла)** и `npm run invariants -- --config <export>` — r1 обоснованно ограничился тремя показательными смоками общих примитивов (`smoke_space_settings`, `smoke_danger_confirmation`, `smoke_optimize_geometry_preflight`) плюс fixture-набором `large-house`; делта не трогает `src/**`, значит вывод `smoke-select` не мог измениться — перепроверил сам `node scripts/smoke-select.mjs --base origin/dev --head HEAD`: те же ~90 «прямых совпадений» из 223 по тем же общим полям рантайма (`_frame`, `_modelCache`, `_path` и т.п.), новых узко-специфичных совпадений на делту нет, т.к. делта не содержит символов рантайма.
|
||
- **Core-file budgets** (`houseplan-card.ts`/`houseplan-editor-runtime.ts` на потолке) — не менялись дельтой, `test/core-file-budget.test.mjs` не тронут, `npm test` зелёный подтверждает, что потолок не пробит.
|
||
- **Единственная Low-находка r1** (фокус поля имени после Cancel не проверен) — снята решением ревьюера r1 без правки; дельта её не касается (`demo/smoke_space_copy.mjs` тоже не проверяет фокус, только значение и `busy===false`), риск тот же — оставляю снятой.
|
||
- **Быстрая проверка «одно число — один источник» для `nextSpaceCopyTitle`** (не отдельная находка r1, но обязательный вопрос этого правила): функция вызывается **один раз** (`space-copy-runtime.ts:66`), результат кладётся в редактируемое поле диалога и это же значение уходит в запись при сабмите — нет второго независимого вычисления «предпросмотр vs запись». Файл дельтой не тронут, вывод переносится без изменений.
|
||
|
||
## 6. Находки этого раунда
|
||
|
||
Не найдено. Три Medium из r1 закрыты предъявленными строками и зелёными гейтами (§4); дельта не вносит нового кода в `src/**`/`custom_components/**/*.py`; трейлеры коммитов корректны (`Issue: #456` на каждом, `User-Visible: no` везде — дельта не меняет пользовательское поведение, только доказательства/эталоны/документацию, changelog не тронут, что и требуется); коммит с golden-эталоном несёт `Release:`+`Baseline-Reviewed:`.
|
||
|
||
## 7. Что проверено и корректно (сверх наследования)
|
||
|
||
- Ребейз на `de30066b` не создал скрытого конфликта: дифф `de30066b..b286d7ba` для `scripts/mutation-gate.mjs` показывает чистую дозапись 11 объектов в конец массива, без изменения инфраструктуры #451.
|
||
- `process-gate.mjs` проходит на полном диапазоне `origin/dev..HEAD` (7 коммитов) без предупреждений.
|
||
- Порядок трейлеров в `git show -s --format=full HEAD` — `Issue`/`User-Visible`/`Release`/`Baseline-Reviewed`, ничего не искажено ребейзом.
|
||
|
||
## 8. Чего не проверял и почему
|
||
|
||
- **`test_ha_*.py` (полный HA-harness)** — как и в r1, песочница не несёт pinned Python 3.13/HA-стек; файл не тронут дельтой, риск не новый.
|
||
- **Реальный мутационный прогон `--id=<id>` для всех 11 мутантов заново** — код `src/space-copy*.ts` не менялся дельтой (подтверждено diffstat b286d7ba неизменным), прогон дал бы тот же результат, что в r1 (уже реально исполнен и задокументирован там построчно); прогнал только дешёвый `--check` (реестр/уникальность якорей), это соразмерно объёму дельты (§2.10 «заново проверять только то, что задевает дельта»).
|
||
- **`npm run invariants -- --config <export>`** — как и в r1, нет установки HA под рукой для реального `config/get`; геометрический код дельтой не тронут.
|
||
- **Полный запуск всех 223 смоков** — предрелизный гейт (§8), делта не расширяет площадь src/**, вывод `smoke-select` не изменился (см. §5).
|
||
|
||
## 9. Вердикт
|
||
|
||
Все три Medium-находки r1 закрыты и перепроверены гейтами, а не заявлением автора: `check-docs` зелёный, `golden:verify` 153/153 (включая ранее «different» сцену), новый `demo/smoke_space_copy.mjs` — содержательный, прочитан построчно и подтверждён живым прогоном против реального demo-backend. Ребейз на ушедший на один коммит вперёд `dev` проверен контентно и не задевает код #456 (только независимая инфраструктура мутационного гейта #451) — полный разбор с нуля не требуется, дельта локальна. Новых находок нет. Функциональность (AC1–AC13) не менялась дельтой и наследуется из зелёного заключения r1 без повторного прогона мутантов.
|
||
|
||
**Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0**
|
||
|
||
Документ: `docs/reviews/CODE-REVIEW-456-r2.md` (публикуется шагом конвейера).
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/456-copy-space`, коммит `7dd5b4798a05` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `2221f7cc75b7c2d4a2077ed6a70f4c9e2042d3b0`
|
||
```
|
||
git log --all --format='%H %T' | grep 2221f7cc75b7
|
||
```
|
||
- ТЗ `docs/specs/456-copy-space.md`, блоб `a1def3eeefa15cac8b1bfe0542eb8d1ad0515fcf`
|
||
```
|
||
git log --all --find-object=a1def3eeefa15cac8b1bfe0542eb8d1ad0515fcf -- docs/specs/456-copy-space.md
|
||
```
|