mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности (`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` → `legacy/specs/`), а относительные ссылки внутри перенесённых документов и в соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно только до переноса документов ревью). Гейты архив не смотрят. `reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не резолвится от нового места, а цель находится от нового или старого места через карту переносов; битая и до переноса ссылка не трогается. `--apply` делает это само, `--repair-links=<rev>` — для всех переименований `<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом `--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты: перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет; мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и DEVELOPMENT › Release называют переписывание и `--check-links`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
218 lines
18 KiB
Markdown
218 lines
18 KiB
Markdown
# Код-ревью issue #198 — очистка изолированного микро-интервала толщины через Optimize (цикл r3)
|
||
|
||
- Цикл: r3/4
|
||
- Вердикт: **зелёный** · High: 0 · Medium: 0
|
||
- Ветка: `issue/198-optimize-micro-interval`
|
||
- Диапазон коммитов: `git log --oneline origin/dev..HEAD` = `3a68efa` (ТЗ),
|
||
`cd17a0b` (ревью ТЗ r1), `9b05dd5` (реализация), `833e8e5` (ревью-документ
|
||
r1), `a797752` (follow-up тесты на замечания r1), `ef22b23` (ревью-документ
|
||
r2), `5ce3cee` (исправление находки r2 — изоляция AC5-фикстуры), `2d5fec0`
|
||
(обновление screenshot provenance после технического ребейза)
|
||
- Диапазон диффа: `git diff origin/dev...HEAD`
|
||
- ТЗ: [`docs/specs/198-optimize-micro-interval.md`](../../specs/198-optimize-micro-interval.md)
|
||
(ревью ТЗ зелёное, r1)
|
||
- Предыдущий цикл: [`docs/reviews/CODE-REVIEW-198-r2.md`](CODE-REVIEW-198-r2.md)
|
||
(жёлтый · Medium-1 — новый AC5-тест не хермитичен к сценарию входной
|
||
мутации, который он должен доказывать)
|
||
|
||
## Скоуп
|
||
|
||
Тот же, что в r1/r2: явное действие «Общие настройки → Оптимизировать
|
||
планы» получает строго ограниченный lossy-шаг — схлопывание одиночного
|
||
изолированного интервала толщины стены короче половины шага сетки между
|
||
двумя одинаковыми коллинеарными соседями, при отсутствии room-vertex/
|
||
opening-узла на его концах (контракт §6 ТЗ). Runtime и редактор остаются
|
||
lossless.
|
||
|
||
Этот цикл закрывает единственную оставшуюся находку r2 (Medium-1, AC5):
|
||
тест «micro-interval cleanup is endpoint, input-order and room-order
|
||
invariant without mutation» (`test/plan-optimizer.test.mjs`) строил
|
||
`baseline`-вызов на непроклонированном `fixture`, а затем читал
|
||
`fixture.walls[1]` для `reversedMiddle` уже после этого вызова — если бы
|
||
`collapseIsolatedWallThicknessIslands` начала мутировать входной массив на
|
||
месте, `baseline`-вызов сам испортил бы источник для второго сценария
|
||
раньше, чем снимался снимок «до», и тест прошёл бы тривиально.
|
||
|
||
Продуктовый код (`src/plan-optimizer.ts`, `src/wall-thickness.ts`) в этом
|
||
цикле не менялся — `git diff ef22b23..HEAD -- src/` пуст. Единственная
|
||
содержательная правка — `5ce3cee` (3 строки в `test/plan-optimizer.test.mjs`):
|
||
`baseline` теперь строится из независимого `baselineFixture =
|
||
microIntervalFixture()`, а `detached` для этого вызова клонируется через
|
||
`structuredClone`, так что `fixture.walls[1]`, используемый дальше для
|
||
`reversedMiddle`, никаким предыдущим вызовом не затрагивается.
|
||
|
||
Коммит `2d5fec0` — provenance-обновление `docs/images/screenshots.json`
|
||
после того, как ветка была технически перебазирована ещё раз (см. «Как
|
||
проверялось»): `git diff` содержимого #198 между старым и новым срезом
|
||
ребейза пуст, изменился только `sourceFingerprint`/скриншот вслед за уже
|
||
слитыми в `dev` посторонними issue. Класс C, `User-Visible: no`, вне
|
||
продуктового поведения #198.
|
||
|
||
## Как проверялось
|
||
|
||
**Инцидент с устаревшим срезом ветки.** Сессия ревью изначально была
|
||
развёрнута на локальном `HEAD=b411a58` (после `git fetch origin dev` в
|
||
начале сессии). Перед вынесением вердикта повторный `git fetch origin
|
||
issue/198-optimize-micro-interval` показал, что удалённая ветка уже
|
||
продвинулась до `2d5fec0` — тем же набором из восьми коммитов #198, но
|
||
перебазированным поверх более нового `origin/dev`, куда за время ревью
|
||
успел влиться не связанный с #198 issue #205 (vacuum trail resume grace).
|
||
Сверил `git diff b411a58 2d5fec0` — единственная разница это файлы #205
|
||
(уже находящиеся в `dev`, значит не относящиеся к диапазону #198) плюс
|
||
1-байтовый шум provenance-скриншота; для всех файлов, содержательных для
|
||
#198 (`src/plan-optimizer.ts`, оба тестовых файла, ТЗ, три предыдущих
|
||
ревью-документа, `docs/WALL-THICKNESS.md`, targeted smoke), diff между
|
||
двумя срезами ребейза пуст. Переключился на актуальный `2d5fec0`
|
||
(`git checkout 2d5fec0`) и весь гейт-прогон ниже выполнен именно на нём —
|
||
иначе вердикт судил бы уже не тот код, что лежит на удалённой ветке.
|
||
|
||
| Гейт | Результат | Команда |
|
||
|---|---|---|
|
||
| typecheck | green | `npx tsc --noEmit` |
|
||
| unit | green, 924/924 (было 917/917 в r2 на предыдущем ребейзе; рост числа тестов — от слияния #204/#205 в `dev`, не от этого issue) | `npm test` |
|
||
| build + 3 копии бандла | green, побайтово идентичны | `npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js && cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js` |
|
||
| targeted smoke (AC7) | green, все 10 подпроверок | `node demo/smoke_optimize_micro_interval.mjs` |
|
||
| mutation-gate, целевой мутант (AC9) | мутант пойман 1/1 | `node scripts/mutation-gate.mjs --id=optimizer-micro-interval-cleanup-disabled` |
|
||
| mutation-gate, полный self-check | green, 37/37 | `node scripts/mutation-gate.mjs --check` |
|
||
| docs-gate (затронут `2d5fec0`) | green | `node scripts/check-docs.mjs --external` |
|
||
| whitespace | чисто | `git diff --check origin/dev...HEAD` |
|
||
| process-gate (офлайн) | green, предупреждений 0 | `node scripts/process-gate.mjs` (диапазон `origin/dev..HEAD`, 8 коммитов) |
|
||
| трейлеры всех 8 коммитов диапазона | `Issue: #198` на каждом; ровно один `User-Visible: yes` — на `9b05dd5` (реализация), и именно в нём оба changelog правятся тем же коммитом | `git log`, `git show 9b05dd5 --stat` |
|
||
| **адверсариальная проверка исправленного AC5-теста** | тест ловит ровно ту мутацию, из-за которой в r2 был выставлен Medium-1 | см. «Проверка дисциплины „тест должен уметь падать“» ниже |
|
||
|
||
**Не прогонялось и почему (без изменений относительно r1/r2 — диапазон по
|
||
продуктовому коду и UI-поверхности не менялся с `9b05dd5`):**
|
||
|
||
- `npm run golden:verify` и смежные `smoke_grid_snap`/`smoke_align_guides` —
|
||
этот цикл не трогает `src/plan-optimizer.ts`, рендер, геометрию, стили
|
||
или UI-пайплайн Optimize; изменился только один тестовый файл на 3
|
||
строки плюс provenance двух docs-артефактов. Тот же код уже прогонялся
|
||
против той же UI-поверхности зелёным в r1.
|
||
- `python -m pytest tests_backend -q` — ни один файл
|
||
`custom_components/**/*.py` не входит в диапазон #198 ни в одном из
|
||
трёх циклов (подтверждено: `git diff origin/dev...HEAD --name-only | grep
|
||
'\.py$'` — пусто).
|
||
- Производительность/бенчмарки — не названы в AC; изменения этого цикла —
|
||
три строки теста и docs-provenance, никаких perf-путей не затронуто.
|
||
- Полный набор из 127+ browser-смоков — диапазон r3 не касается ни одной
|
||
новой UI-поверхности; единственный релевантный
|
||
(`smoke_optimize_micro_interval`) прогнан и зелёный.
|
||
|
||
### Проверка дисциплины «тест должен уметь падать» для исправленного AC5-теста
|
||
|
||
Не ограничился чтением диффа `5ce3cee` — воспроизвёл ровно ту
|
||
адверсариальную мутацию, которой в r2 было доказано отсутствие защиты, и
|
||
подтвердил, что теперь тест её ловит.
|
||
|
||
В `src/plan-optimizer.ts` временно заменил иммутабельный
|
||
```
|
||
out = out.map((wall) => {
|
||
if (!exactMatches(wall, candidate)) return wall;
|
||
exactMatch = true;
|
||
return wall.cm === target ? wall : { ...wall, cm: target };
|
||
});
|
||
```
|
||
на мутирующий цикл
|
||
```
|
||
for (const wall of out) {
|
||
if (!exactMatches(wall, candidate)) continue;
|
||
exactMatch = true;
|
||
wall.cm = target;
|
||
}
|
||
```
|
||
(тот же самый объект, на который ссылаются и `out`, и исходный `walls`,
|
||
поскольку `out = walls.slice()` — поверхностная копия массива). Пересобрал
|
||
`test-build` (`npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs`)
|
||
и прогнал изолированно только целевой тест:
|
||
`node --test --test-name-pattern="invariant without mutation"
|
||
test/plan-optimizer.test.mjs`.
|
||
|
||
**Результат: 1 fail** — `assert.deepEqual(walls, beforeWalls, 'helper must
|
||
not mutate the walls array or entries')` красный, `walls[2].cm` меняется с
|
||
`15` на `22`. Это ровно тот класс регрессии, для которого r2 показал
|
||
тривиальное прохождение теста. Откатил мутацию (`git checkout --
|
||
src/plan-optimizer.ts`), пересобрал `test-build` заново и подтвердил: тот
|
||
же тест снова зелёный (1/1), а `npm test` на чистом дереве — 924/924,
|
||
`git status --short` пуст.
|
||
|
||
Правка `5ce3cee` действительно устраняет корень находки r2: `baseline`
|
||
теперь строится из отдельного вызова `microIntervalFixture()`
|
||
(`baselineFixture`), а `detached`-комната для этого вызова клонируется, так
|
||
что второй сценарий (`reversedMiddle`, `walls`, `rooms`) строится из
|
||
`fixture`, который первый вызов никогда не видел — «до»-снимок снимается
|
||
до единственного вызова над этим конкретным входом, ровно как просил r2.
|
||
|
||
## Находки
|
||
|
||
Не выявлено. Обе Medium-находки r1 (AC5, AC8) и оставшаяся Medium-находка
|
||
r2 (нехерметичность AC5-теста) закрыты и подтверждены исполнением, а не
|
||
только чтением диффа.
|
||
|
||
## Что проверено и корректно
|
||
|
||
- **Находка r2 (Medium-1, AC5) закрыта полностью и доказана заявленным
|
||
способом.** См. адверсариальную проверку выше — тест теперь
|
||
дискриминативен именно для сценария мутации входа на месте, который он
|
||
обязан ловить.
|
||
- **Продуктовый код не менялся с r1/r2.** `git diff ef22b23..HEAD -- src/`
|
||
пуст: контракт §6 ТЗ (строгий порог `0.5 × pitch`, коллинеарность через
|
||
`roomWallProfile`, topology-guard по room vertex/open cut, non-cascading
|
||
snapshot через `targets.size === 1`, разрешение неоднозначности) не
|
||
требовал повторной проверки по существу в r2 и в этом цикле — он был
|
||
предметно проверен чтением и тестами в r1 (см.
|
||
`docs/reviews/CODE-REVIEW-198-r1.md`) и с тех пор не тронут ни одним
|
||
коммитом. Перепроверены только пересобранные гейты на актуальном коде —
|
||
все зелёные.
|
||
- **Ребейз безопасен.** Технический ребейз перед r3 (упомянутый владельцем
|
||
в issue-комментарии «Технический ребейз перед r3») не изменил ни одной
|
||
строки, относящейся к #198: единственная разница между срезом ребейза,
|
||
на котором сессия стартовала (`b411a58`), и актуальным (`2d5fec0`) — это
|
||
уже слитый в `dev` посторонний issue #205 и провенанс-обновление
|
||
скриншота. Конфликт при ребейзе (упомянутый в комментарии «объединением
|
||
changelog/STATUS») не оставил следов расхождения: `docs/CHANGELOG.md`,
|
||
`docs/CHANGELOG.ru.md`, `docs/STATUS.md` содержат обе записи (#198 и
|
||
соседние issue), пересечения или потери контента не найдено.
|
||
- **AC1–AC4, AC6, AC7, AC9, AC10** — доказаны так же, как в r1/r2
|
||
(перечисление unit/negative-matrix/smoke/mutation-gate), код не менялся,
|
||
гейты перепрогнаны и зелёные на актуальном коде.
|
||
- **AC8** — закрыт в r1-follow-up, подтверждён адверсариальной мутацией в
|
||
r2 (`degradeWalls()` мутация `walls[0].cm = 999` ловится новым unit),
|
||
код и тест с тех пор не менялись.
|
||
- **Трейлеры и changelog.** Все 8 коммитов диапазона несут `Issue: #198`;
|
||
ровно один `User-Visible: yes` (`9b05dd5`), и именно в нём правятся оба
|
||
changelog в том же коммите — подтверждено `git show 9b05dd5 --stat`.
|
||
Ветка называется `issue/198-optimize-micro-interval`, соответствует
|
||
трейлерам.
|
||
- **Три копии бандла синхронны.** `cmp` дал побайтовое совпадение
|
||
`dist/`, `custom_components/houseplan/frontend/` и `demo/srv/assets/`
|
||
после `npm run build` на актуальном `HEAD=2d5fec0`.
|
||
- **Docs-provenance корректен.** `node scripts/check-docs.mjs --external`
|
||
зелёный (7 файлов, 10 внешних ссылок) на актуальном срезе после второго
|
||
ребейза.
|
||
|
||
## Чего не проверял
|
||
|
||
- Полный HA backend harness (`tests_backend` под настоящим Home
|
||
Assistant) — диапазон #198 не содержит правок в
|
||
`custom_components/**/*.py` ни в одном цикле.
|
||
- `npm run golden:verify` и полный browser-smoke набор (127+ файлов) —
|
||
диапазон r3 не трогает рендер/геометрию/UI-пайплайн сверх уже
|
||
проверенного в r1; продуктовый код не менялся.
|
||
- Производительность/бенчмарки — не названы в AC, изменения r3 — три
|
||
строки теста и provenance.
|
||
- Повторное чтение контракта §6 в `src/plan-optimizer.ts` построчно —
|
||
выполнено предметно в r1 (см. `CODE-REVIEW-198-r1.md`) и не повторялось
|
||
заново построчно в r2/r3, так как `git diff` подтверждает отсутствие
|
||
изменений в этом файле с тех пор; вместо повторного чтения перепрогнаны
|
||
все относящиеся гейты и целевая мутация на актуальном коде.
|
||
|
||
## Вывод
|
||
|
||
High-находок нет, Medium-находок нет. Обе находки r1 (AC5, AC8) и
|
||
оставшаяся находка r2 (нехерметичность AC5-теста) закрыты и лично
|
||
перепроверены исполнением — включая независимое воспроизведение той же
|
||
адверсариальной мутации, которой r2 доказал дефект. Продуктовый код с r1
|
||
не менялся; все обязательные и относящиеся к диапазону гейты зелёные на
|
||
актуальном срезе удалённой ветки (`2d5fec0`), включая процессный
|
||
`process-gate.mjs`. Вердикт: **зелёный**, задача готова к слиянию в `dev`.
|