Files
houseplan-card/docs/reviews/CODE-REVIEW-567-r1.md
T
2026-09-13 20:34:12 +00:00

167 lines
13 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.
# CODE-REVIEW · issue #567 · заход r1
Материал: `03983946c561a7e8e3e27ef0c3ba40dbebca7790` (рабочая копия — на нём же,
HEAD detached). Трек — `trivial` (light), ТЗ и AC в теле issue, бюджет циклов —
2, израсходовано 0.
## Скоуп изменения
`keepTwoPoint` в `radarConfigFromDraft` (`src/radar-editor.ts`) сравнивал
`sources` через `JSON.stringify(...) === JSON.stringify(...)`, то есть текстом.
Билдер сам меняет порядок ключей (`slots`/`ranges`/`zones`/`occupancy_entity`/
`count_entity` удаляются и дописываются заново, `availability_entity` остаётся
на месте) — конфиг, только что записанный этим же билдером, при следующем
сравнении оказывался «другим», и радар с `availability_entity` молча терял
`refs`/`rms_cm` двухточечной калибровки при первом обычном сохранении.
Правка вводит `canonicalSources()` — каноничное по порядку ключей объектов
(рекурсивно, включая объекты внутри массивов) и чувствительное к порядку
*элементов* массивов сравнение через `JSON.stringify` с сортирующим
replacer'ом, сортировка по кодовым точкам. Диапазон правки соответствует
заявленному откату: одна функция плюс замена одной строки сравнения.
Изменённые файлы вне генерируемых: `src/radar-editor.ts` (+23/−1),
`test/radar-editor.test.mjs` (+99, новые тесты AC1–AC3), `scripts/mutation-
registry.mjs` (+12, один мутант), `docs/images/screenshots.json` (пересчёт
`sourceFingerprint` после `docs:accept --identical`). `dist/**` и
`custom_components/houseplan/frontend/**` — синхронный билд.
## Как проверялось
Дешёвые гейты (`tsc --noEmit`, `npm test`, `npm run build` + сверка трёх копий
бандла) на этом SHA уже зелёные в Validate
(https://github.com/Matysh/houseplan-card/actions/runs/34780715957) — не
перегонял.
Прогнал сам, точечно, под диффом:
- `npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs &&
node --test --test-name-pattern="#567" test/radar-editor.test.mjs` —
**3/3 зелёные** (AC1, AC2, AC3).
- **Дисциплина «тест умеет падать»**: временно откатил `canonicalSources(...)
=== canonicalSources(...)` на `JSON.stringify(...) === JSON.stringify(...)`
в рабочей копии и перепрогнал те же три теста — **AC1 и AC3 красные**
(`method`: ожидали `two_point`, получили `manual`), **AC2 остался зелёным**.
Это ровно то, что заявил автор в хендоффе (регресс ловится не ценой ложного
прохода AC2). Файл восстановлен из бэкапа, `git status --short` после
восстановления — пусто, дерево чистое.
- `node scripts/mutation-registry.mjs --check` — exit 0, без нарушений.
- `node scripts/smoke-select.mjs --base origin/dev --head HEAD` — вывод
**НЕОПРЕДЕЛЁННОСТЬ**, единственный незарегистрированный символ на изменённых
строках — `canonicalSources`. Проверил `demo/smoke_*.mjs` на упоминания
`two_point`/калибровки — ни один смок не трогает сохранение двухточечной
калибровки при повторном открытии редактора, значит запись в
`smoke-links.mjs` была бы ложной связью. Решение автора «браузерный смок не
нужен» — согласен: `canonicalSources` — приватный хелпер чистой функции,
единственный путь к нему из браузера — `radarConfigFromDraft`, целиком
закрытый тремя unit-AC и мутантом. Смок не прогонял (по диффу и AC не
требуется, а факт «широкой» связи не подтверждён).
- `node scripts/check-docs.mjs` — «Documentation checks passed (7 files, 12
external links)», зелёный на пересчитанном `sourceFingerprint`.
- `diff -rq dist custom_components/houseplan/frontend` — пусто, деревья
идентичны (третья копия, `demo/srv/assets`, больше не коммитится, #255).
- `node --test test/single-source-numbers.test.mjs` — 3/3, для очистки
совести: правка не касается ничего отображаемого, но механический гейт
«одно число — один источник» остаётся зелёным.
- Грепнул `src/**` на `JSON.stringify(...) === JSON.stringify(...)` — есть ещё
четыре места (`houseplan-editor-runtime.ts:1941,2237,10019,10513`,
`summary-panel.ts:309`), но все они сравнивают массивы/объекты, чей порядок
ключей контролирует не билдер того же модуля симметрично тому, как здесь —
не предмет этой задачи (откат ТЗ прямо ограничивает её одной функцией), и в
issue не заявлены. Не заведение, а просто фиксирую, что смотрел: если один
из них когда-то проявит тот же паттерн (класс #319, как и написано в issue),
это отдельная задача.
Не прогонял: `golden:verify` (диф не меняет рендер — чистая функция
преобразования конфига), `pytest tests_backend` (Python не тронут),
performance-профили (не заявлены в AC, путь не хайпасный). Полный набор
браузерных смоков — избыточен для одной внутренней функции без UI-поверхности.
## AC — разобраны по коду и исполнением
| AC | Доказательство | Вердикт |
|---|---|---|
| AC1 | `test/radar-editor.test.mjs` «#567 AC1», выполнил лично: `method: two_point`, `refs`, `rms_cm`, `mount`/`cell_cm`/`mirror` неизменны, `sources` содержит все три ключа по смыслу | подтверждено |
| AC2 | «#567 AC2», выполнил лично: смена `x_entity` → `method: manual`, `refs`/`rms_cm` удалены | подтверждено |
| AC3 | «#567 AC3», выполнил лично: перестановка полей внутри слота не мешает; добавление/удаление слота и перестановка сущностей между слотами — мешают | подтверждено |
Регресс-проверка (тест умеет падать) выполнена лично, не только со слов автора
— см. раздел «Как проверялось».
## Что проверено и корректно
- `canonicalSources` рекурсивна: `JSON.stringify`-replacer вызывается и для
элементов массивов, поэтому порядок полей *внутри* слота (второй слой,
упомянутый в issue отдельно) канонизируется тем же кодом, без специального
случая — это закрывает наблюдение issue про `slots` заодно с
`availability_entity`.
- Порядок элементов массива не трогается — позиция слота осталась смыслом
(`target_N`), что и требует контракт ТЗ; проверено тестом AC3 (перестановка
сущностей между слотами инвалидирует калибровку).
- Единственный мутант, `radar-sources-compared-as-text`, пойман (`--check`
зелёный). Решение не заводить второго мутанта на сортировку массивов
задокументировано и математически верно: `!Array.isArray` регулирует только
то, применяется ли сортировка ключей к узлу; для массива это оставило бы его
элементы под числовыми индексами в исходном порядке (для длин, о которых
идёт речь — единицы слотов, лексикографический порядок строковых индексов
совпадает с числовым) — реального различия в поведении нет, ловить нечего.
Отдельная запись в AC3-тесте фиксирует смысловое свойство вместо этого.
- Геометрия (`project_local`, `mount`/`cell_cm`/`mirror`) не затронута —
проверено и код-ревью, и выполнением (AC1 отдельно утверждает неизменность
этих величин).
- `User-Visible: no` обоснован: `method` и `rms_cm` нигде не рендерятся
(грепом по `src/*.ts` подтверждено отсутствие обращений вне
`radar-editor.ts`/`radar-setup.ts`/`types.ts`), геометрия не двигается —
changelog не нужен.
- Трейлеры коммита: `Issue: #567`, `User-Visible: no` — оба на месте, коммит
один.
- Классы изменений соблюдены: `src/**` и `test/**`/`scripts/**` — по одному
issue; `dist/**` и `custom_components/houseplan/frontend/**` синхронны
(класс D, не редактировались вручную); `docs/images/screenshots.json` —
часть DoD правки `src/**` (обновление отпечатка через `docs:accept
--identical`, все 11 кадров попиксельно совпали).
## Находки
Нет. High: 0, Medium: 0, Low: 0.
## Продуктовое рассуждение (SCOPE.md)
Правка не расширяет и не меняет ни один Core user job — она чинит J7-смежный
внутренний контракт (сохранность результата двухточечной калибровки радара
между сохранениями), не вводя нового поведения и не трогая ни один экран.
Она укладывается в «Keep the plan true as the home evolves» (J6) косвенно:
предотвращает молчаливую деградацию уже настроенной инфраструктуры без
действия пользователя. Возражений по скоупу нет.
## Чего не проверял и почему
- `npx tsc --noEmit` / полный `npm test` / `npm run build` — не перегонял,
зелёный Validate на этом же SHA уже подтверждён и назван по ссылке.
- Браузерные смоки — не прогонял ни одного: `smoke-select` дал
НЕОПРЕДЕЛЁННОСТЬ по единственному непокрытому символу, ручная проверка
показала, что ни один смок не относится к сценарию двухточечной калибровки,
прогон был бы шумом без сигнала.
- `golden:verify`, `pytest tests_backend`, performance-профили — не по AC, не
по diff (нет Python, нет изменений рендера).
## Вердикт
Зелёный. Обе стороны регресса (ложное «изменилось» и ложное «не изменилось»)
проверены исполнением, не только чтением; геометрия и достаточность отката
подтверждены; гейты, требуемые диффом, зелёные.
---
<!-- material-anchors: сгенерировано конвейером (#414) -->
## Материал раунда
- Ветка: `issue/567-radar-canonical-sources`, коммит `03983946c561` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
- Дерево материала: `f3a9eb9b27aa5d0ecd52c09a27483f9e8e50bc06`
```
git log --all --format='%H %T' | grep f3a9eb9b27aa
```
- Тело issue: `aa2955659fda7d26ffd448236319e735d0a5c1d9ebec765ad9c00e559e82f000`
- Вердикт конвейера: `green` · High 0