mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 21:01:21 +00:00
131 lines
17 KiB
Markdown
131 lines
17 KiB
Markdown
# CODE-REVIEW-224-r2
|
||
|
||
- Issue: [#224](https://github.com/Matysh/houseplan-card/issues/224) — канонические координаты на каждой записи
|
||
- ТЗ: `docs/specs/224-config-coordinate-canonicalization.md`, ревью ТЗ зелёное (`docs/reviews/SPEC-REVIEW-224-r1.md`)
|
||
- r1: `docs/reviews/CODE-REVIEW-224-r1.md`, вердикт красный на SHA `4a798e3e13275c0820aec2db1e63d1fae31a41f3`
|
||
- Дельта этого раунда: `git diff 4a798e3..HEAD` (HEAD = `4dbdb446f87717ce65471c7d8123a59353856821`, коммит «fix: address coordinate review findings»)
|
||
- Заход: r2 · блокирующих циклов израсходовано 1 из 4 (r1 был красным вердиктом, потратил цикл; настоящий заход при зелёном исходе цикла не потратит — #227)
|
||
- Вердикт: **зелёный** · High: 0 · Medium: 0
|
||
|
||
## Скоуп
|
||
|
||
Дельта — точечный фикс трёх находок r1, а не новая работа: 11 файлов,
|
||
+340/-32 (без учёта трёх бинарных копий бандла, которые меняются только по
|
||
контрольной сумме, и `docs/reviews/CODE-REVIEW-224-r1.md`, который в этот
|
||
коммит просто добавлен пайплайном, а не автором):
|
||
|
||
- `src/houseplan-card.ts` — `_writeConfig()` перестаёт безусловно
|
||
переприсваивать `_serverCfg` новым клоном; ручной `_cfgEpoch++` убран;
|
||
- `src/serialized-write-queue.ts` (новый) — сериализация записи вынесена в
|
||
чистую функцию `enqueueSerializedWrite`;
|
||
- `test/serialized-write-queue.test.mjs` (новый) — controlled-promise unit на
|
||
саму очередь (AC6);
|
||
- `test/coordinate-canonicalization.test.mjs` — усилен regex-guard на форму
|
||
`_writeConfig()`;
|
||
- `scripts/mutation-gate.mjs`, `tsconfig.test.json` — механические правки под
|
||
переименованный отступ/новый файл;
|
||
- `docs/images/screenshots.json`, три копии бандла — пересборка/пересъёмка.
|
||
|
||
Правка не меняет контракт поведения, не трогает Python/`custom_components/**`
|
||
и не задевает новую подсистему — делает её локальной по критерию §2.10, разбор
|
||
по дельте оправдан.
|
||
|
||
## Закрытие раунда r1
|
||
|
||
| Находка r1 | Чем закрыта | Где это видно |
|
||
|---|---|---|
|
||
| **H1** (High) — 20 golden-сцен `different`, похоже на пропавший визуальный элемент, вопреки AC12/§2/§15 ТЗ | Причина найдена и устранена: `_serverCfg` теперь переприсваивается только когда `canonicalizeConfigGeometry` реально меняет контент; раньше безусловная переприсвойка новым клоном (тот же контент, новая ссылка) реактивно триггерила `willUpdate()` → `_cfgEpoch++` → инвалидацию геометрического кэша, и в это же самое обновление подмешивалась несвязанная нормализация `_dropLegacySegments()` (стены после `known_devices`-записи при старте) — она и оказывалась видимой как «пропавший» элемент | `src/houseplan-card.ts:6949-6969` (диф `4a798e3..HEAD`); **перепроверено исполнением, не по заявлению автора**: `npm run golden:verify` на этой ветке — 3 `different` из 82, все три — `opening-placement-*-thick-wall-*`; тот же прогон на чистом `origin/dev` (изолированный worktree, тот же Chromium/бандл) даёт **ровно те же три** `different` с теми же `diffRatio`/`maxObservedDelta` — регрессии #224 больше нет, остаток предсуществующий и к задаче не относится (см. «Как проверялось») |
|
||
| **M2** (Medium, в скоупе) — AC6 обещает «Queue/debounce unit с controlled promises», а в диффе был только regex по тексту исходника | Сериализация записи вынесена в отдельную функцию `enqueueSerializedWrite` и покрыта двумя controlled-promise тестами: правка, сделанная во время in-flight записи, доходит до следующего звена; упавшая запись не блокирует следующее | `src/serialized-write-queue.ts` (новый файл), `test/serialized-write-queue.test.mjs:1-53`; прогнано (`npm test`) — `ok 788`, `ok 789`; тест умеет падать — прослежено по коду: без ожидания `previous` второй `write()` выполнился бы синхронно при вызове `enqueue()`, `observed` получил бы `'second'` раньше `assert.deepEqual(observed, ['first'], …)` и тест бы покраснел (правку `enqueueSerializedWrite` для живого прогона делать не могу — рецензенту запрещено менять продуктовый код, а инструмент редактирования это и подтвердил отказом в разрешении) |
|
||
| **M1** (Medium, в скоупе) — `check-docs.mjs` красный, отпечаток скриншотов не обновлён | `npm run build && node demo/docs/capture.mjs` прогнаны, `docs/images/screenshots.json` обновил `sourceFingerprint`/`sourceSha256`; все десять `imageSha256` **не изменились** — визуал скриншотов документации не затронут | `docs/images/screenshots.json` (диф `4a798e3..HEAD`, только строки `sourceFingerprint`/`sourceSha256`); перепроверено: `node scripts/check-docs.mjs` → «Documentation checks passed (7 files, 10 external links)» |
|
||
| **L1** (Low, было можно снять без правки) — двойной инкремент `_cfgEpoch` | Правка вошла в тот же коммит заодно с H1: ручной `if (…) this._cfgEpoch++` удалён, `willUpdate()` остался единственным местом инкремента | `src/houseplan-card.ts:6961-6963` — сравните с r1-версией, где после этой строки стоял ручной `this._cfgEpoch++` |
|
||
|
||
## Как проверялось
|
||
|
||
| Гейт | Результат |
|
||
|---|---|
|
||
| `npx tsc --noEmit` | зелёный (без вывода) |
|
||
| `npm test` | 1063 тестов, 0 упало, 0 skipped (у автора локально было 1062+1 skip — расхождение из-за environment-зависимых `t.skip('git недоступен'/'нужен исполняемый stub gh')` в `test/process-gate.test.mjs`, не связано с #224) |
|
||
| `npm run build` + `cmp` трёх копий (`dist`/`custom_components/.../frontend`/`demo/srv/assets`) | все три побайтово идентичны |
|
||
| `node scripts/check-docs.mjs` (обязателен: diff трогает `src/**`) | зелёный: «Documentation checks passed (7 files, 10 external links)» |
|
||
| `npm run golden:verify` (нужен: r1 был красным именно здесь — AC12) | **зелёный относительно #224**: 3 `different` из 82, все — `opening-placement-*-thick-wall-*`. Независимо перепроверено на `origin/dev` в изолированном `git worktree` (тот же бандл-пайплайн, тот же Chromium): **та же тройка с теми же `diffRatio`/`maxObservedDelta` (0.0053/0.00528, 210) уже отличается на dev** — не регрессия этой задачи, ни этого раунда, ни r1. Прогнано дважды подряд на ветке — стабильно, не флейк |
|
||
| `git diff --check 4a798e3..HEAD` | чисто, без ошибок пробелов |
|
||
| `node scripts/process-gate.mjs` | «гейт пройден, предупреждений 0» (офлайн-часть; `--issues` не запускал — статус issue тут не требуется) |
|
||
| Мутанты, которые дельта могла задеть по форме патча (`frontend-writes-raw-coords`, `quantization-hits-allowlist`) | оба «покраснел, как обязан» → «поймано 1 из 1» |
|
||
| Мутанты `schema-quantization-removed`, `import-path-bypasses-schema` (backend) | **не прогонял** — в этой ревью-среде нет `pytest`/`.venv-backend` (Python backend не установлен). Не требуется этим раундом: дельта `4a798e3..HEAD` не трогает ни одного файла `custom_components/**/*.py`, оба мутанта — «поймано 1 из 1» в r1 на прежней проверке, наследуются без переисполнения (см. «Унаследовано из r1») |
|
||
| `python -m pytest tests_backend -q` | не прогонял — дельта не трогает `custom_components/**/*.py`, не требуется §8; результат r1 (346 passed + 1 посторонний teardown-error, не связанный с диффом) наследуется |
|
||
| Смоки по grep изменённых имён (`enqueueSerializedWrite`, `serialized-write-queue`, `_writeConfig(`) | точных совпадений на новое имя `enqueueSerializedWrite`/`serialized-write-queue` в `demo/smoke_*.mjs` нет (ни один смок не адресует внутреннюю очередь напрямую); прогнаны два смока, которые r1 уже определил как релевантные поверхности записи конфига — `node demo/smoke_config_writer.mjs` → OK; `node demo/smoke_optimize_coordinate_canonicalization.mjs` → OK (все 13 полей отчёта `true`) |
|
||
| Полный набор `demo/smoke_*.mjs` (163 шт.) | не прогонял — дельта не расширяет затронутые поверхности сверх того, что уже проверено выше; полный набор — предрелизный гейт (§8) |
|
||
| Performance-профили | не прогонял — не названы в AC, дельта не трогает чувствительные к перфу пути (только сериализация записи и условие переприсвоения ссылки) |
|
||
|
||
## Находки
|
||
|
||
Нет находок этого раунда — все High/Medium/Low из r1 закрыты и перепроверены
|
||
(см. «Закрытие раунда r1»); новых, привнесённых дельтой, не обнаружено.
|
||
|
||
## Что проверено и корректно
|
||
|
||
- **AC12 (golden не меняется)** — перепроверено исполнением дважды на ветке
|
||
плюс контрольным прогоном на изолированной копии `origin/dev`: разница
|
||
ограничена тремя предсуществующими сценами, привязки к #224 нет.
|
||
- **AC6 (правка во время in-flight записи не теряется)** — теперь доказано
|
||
заявленным в ТЗ способом (controlled-promises unit), а не текстовым regex;
|
||
логика очереди (`enqueueSerializedWrite`) идентична тому, что раньше было
|
||
инлайном в `_writeConfig()` — рефакторинг чистый, семантика записи не
|
||
менялась, только извлечена и покрыта тестом.
|
||
- Три копии продакшн-бандла синхронны; `check-docs.mjs` зелёный, скриншоты
|
||
документации не изменились по содержимому (только исходный fingerprint).
|
||
- Коммит `4dbdb44`: трейлеры `Issue: #224`, `User-Visible: no` — корректно:
|
||
правка чинит нерелизованную регрессию внутри той же незавершённой задачи и
|
||
не меняет ничего, что пользователь уже мог увидеть; отдельный changelog не
|
||
требуется, `User-Visible: yes` уже был закрыт основным коммитом `4a798e3`
|
||
в r1.
|
||
- `git diff --check` и офлайн `process-gate.mjs` чистые.
|
||
|
||
## Унаследовано из r1
|
||
|
||
Всё, что дельта не трогает, принято без повторной проверки по документу
|
||
`docs/reviews/CODE-REVIEW-224-r1.md` на SHA `4a798e3e13275c0820aec2db1e63d1fae31a41f3`:
|
||
|
||
- AC1–AC5, AC7–AC11 — контракт квантования Python/TypeScript, allowlist/denylist,
|
||
schema barrier, no-op контракт `config/set`/`layout/set`/`layout/update`,
|
||
импорт, Undo/Optimize, регресс #218, диагональные координаты — ни один
|
||
файл `custom_components/houseplan/**/*.py`, `src/coordinate-canonicalization.ts`
|
||
или их тестов не менялся в дельте `4a798e3..HEAD`.
|
||
- `python -m pytest tests_backend -q` (346 passed + 1 посторонний
|
||
teardown-error, не связан с диффом) и backend-мутанты
|
||
`schema-quantization-removed`/`import-path-bypasses-schema` (1/1) —
|
||
результат r1, backend-код не тронут.
|
||
- Документация продукта (`docs/CANVAS.md`, `docs/CONFIG-COMPATIBILITY.md`,
|
||
`docs/USER-GUIDE.ru.md`, оба `CHANGELOG*`, `docs/TESTING.md`, `docs/STATUS.md`) —
|
||
не менялась в дельте, содержательно не переоценивалась повторно.
|
||
- Смоки `smoke_config_writer.mjs`/`smoke_optimize_coordinate_canonicalization.mjs`
|
||
запущены заново (см. «Как проверялось»), а не только унаследованы —
|
||
дельта достаточно близко касается `_writeConfig()`, чтобы перепрогнать их,
|
||
а не поверить прежнему результату.
|
||
|
||
## Чего не проверял
|
||
|
||
- `pytest tests_backend`, backend-мутанты — дельта не трогает Python; кроме
|
||
того, в этой ревью-среде нет установленного `.venv-backend`/`pytest`, так
|
||
что даже сверх необходимого прогнать их здесь было бы нельзя.
|
||
- Полный `demo/smoke_*.mjs` (163 шт.) и `mutation-gate.mjs` целиком — дельта
|
||
локальна, названные в r1 и перепрогнанные в этом раунде смоки/мутанты
|
||
покрывают затронутую поверхность.
|
||
- Performance-профили — не названы в AC, дельта не трогает чувствительные
|
||
пути.
|
||
- `_persistLayout()`'s localStorage-ветка (`src/houseplan-card.ts`, добавлена
|
||
в 4a798e3) переприсваивает `this._layout` тем же безусловным паттерном, что
|
||
был источником H1 в `_serverCfg` — но эта строка не входит в дельту r1→r2,
|
||
и `golden:verify` не показал никакой связанной регрессии. Оставляю как
|
||
наблюдение, не как находку: нет ни изменения в этом раунде, ни
|
||
воспроизведённого дефекта, который оправдывал бы возврат в скоуп,
|
||
закрытый r1 по AC12.
|
||
|
||
## Итог
|
||
|
||
H1, M1, M2 закрыты и перепроверены исполнением (не по заявлению автора): golden
|
||
чист относительно #224 (независимо сверено с `origin/dev`), `check-docs`
|
||
зелёный без изменения содержимого скриншотов, AC6 теперь доказан
|
||
controlled-promise тестом, который умеет падать. L1 закрыт заодно, хотя мог
|
||
быть просто снят. Новых находок дельта не принесла. Зелёный вердикт.
|