Issue: #290 User-Visible: no
16 KiB
CODE-REVIEW-290-r4
- Issue: #290 — «Уступ в один шаг на общей стене создаётся молча и невидим при обычном зуме»
- Этап: код-ревью (PROCESS.md §2.7)
- Заход: r4 · блокирующих циклов израсходовано на входе 2 из 4
- Ветка:
issue/290-near-axis-authoring-repair - HEAD на момент ревью:
01512081db0d41cb86c921a61961275c8c18aedb - Предыдущий раунд: r3, жёлтый,
docs/reviews/CODE-REVIEW-290-r3.md, SHA на котором получен вердикт —d33ae88da4049bdbdfd02c57f935199007157cf3(назван в самом документе r3, раздел «HEAD на момент ревью» — в этот раз находки в процессе нет).
Разбор по дельте (PROCESS.md §2.10) — почему сокращённый объём законен
Дельта: git diff d33ae88d..HEAD — 2 коммита, 2 файла, только классы C и B:
docs/reviews/CODE-REVIEW-290-r3.md | 218 ++++++++++++++++ (класс C, документ ревью r3)
test/near-axis.test.mjs | 15 ++ (класс B, только новый тест)
Ни одного файла класса A. src/** этим раундом не тронут вовсе — проверено
git diff --stat d33ae88d..HEAD -- src/: пусто. Ветка полностью содержит
origin/dev (git merge-base --is-ancestor origin/dev HEAD — true, dev не
уходил вперёд после r3), контракт поведения не менялся, новая подсистема не
задета, объём дельты (15 строк теста) многократно меньше исходной задачи.
Ни одно из исключений §2.10 («ребейз на ушедший вперёд dev», «смена контракта»,
«новая подсистема», «объём сопоставим с задачей») не применяется →
разбор ограничен закрытием единственной находки r3 (M1) и дешёвыми гейтами,
которые гоняются в любом раунде.
Скоуп раунда
Единственная находка r3 (Medium M1, в скоупе задачи, жёлтый вердикт) требовала
механического source guard на AC1: «Authoring, Optimize и renderer #279
импортируют один threshold source; source guard запрещает отдельные литералы
0.25 в этих classifiers» (docs/specs/290-near-axis-authoring-and-repair.md:173-174).
Автор выбрал вариант (а) из двух, предложенных r3: статический guard-тест,
читающий исходники трёх потребителей как текст и требующий literal import +
literal вызов/присваивание общего near-axis API, а не инлайн-константу.
Закрытие раунда r3
| Находка | Чем закрыта | Где это видно |
|---|---|---|
M1 (Medium, в скоупе): AC1 обещает source guard против инлайн-литерала 0.25 в renderer/authoring/Optimize; такого guard'а не было — числовое равенство в test/near-axis.test.mjs:23 не ловит дублирование значения, только его совпадение |
Новый тест #290 authoring, Optimize and renderer use one near-axis threshold source (test/near-axis.test.mjs:22-34, коммит 01512081) читает src/wall-thickness.ts, src/houseplan-card.ts, src/plan-optimizer.ts как текст и требует буквального import … from './near-axis' плюс использования соответствующего экспорта в каждом из трёх файлов |
Воспроизвёл ровно ту же мутацию, что и r3: заменил в src/wall-thickness.ts:85 импортированную ссылку на инлайн-литерал = 0.25 (удалив зависимость от NEAR_AXIS_MAX_DEGREES) — новый guard-тест краснеет (The input did not match the regular expression /import \{ NEAR_AXIS_MAX_DEGREES \} .../). Дополнительно проверил ту же схему для двух других потребителей: убрал import { snapNearAxisEndpoint } from './near-axis'; из src/houseplan-card.ts — тест краснеет; убрал import { repairNearAxisRoomWalls } from './near-axis'; из src/plan-optimizer.ts — тест краснеет. После каждой мутации файл восстановлен, git status --short пуст, npm test снова зелёный (см. таблицу гейтов) |
Других находок в r3 не было (High:0, остальные Medium:0) — закрывать больше нечего.
Как проверялось — гейты
| Гейт | Команда | Результат |
|---|---|---|
| Typecheck | npx tsc --noEmit |
чисто, без ошибок |
| Unit (полный, дешёвый гейт гоняется в любом раунде) | npm test |
1235 тестов: 1234 passed, 1 skipped, 0 failed (на 1 больше, чем в r3 — новый guard-тест); подтверждено npm run inventory (Node unit: 1235) |
| Build + сверка копий бандла | npm run build; diff dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js; git status --short |
пересобранный dist побайтово идентичен закоммиченному и копии в custom_components; git status после пересборки чист — рабочая копия не разошлась с закоммиченными артефактами |
| Дисциплина «тест умеет падать» — M1 (renderer) | откат src/wall-thickness.ts:85 к инлайн-литералу 0.25 без импорта, npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test --test-name-pattern="#290 authoring, Optimize and renderer..." test/near-axis.test.mjs |
тест краснеет (regex import не совпал); после отката файла — снова зелёный, git status чист |
| Дисциплина «тест умеет падать» — M1 (authoring) | тот же приём для import { snapNearAxisEndpoint } from './near-axis'; в src/houseplan-card.ts |
тест краснеет; откат — зелёный |
| Дисциплина «тест умеет падать» — M1 (Optimize) | тот же приём для import { repairNearAxisRoomWalls } from './near-axis'; в src/plan-optimizer.ts |
тест краснеет; откат — зелёный |
| Smoke-select по дельте раунда | node scripts/smoke-select.mjs --base d33ae88d --head HEAD |
«Исполняемого frontend-диффа нет (src/**/*.ts не тронут). Browser-smoke этим диффом не выбираются — выбирать нечего» — согласуется с тем, что дельта не трогает src/** |
| Docs fingerprint | не перезапускал | не требуется этим раундом: src/** дельтой не тронут (см. «Разбор по дельте»); последний прогон — r3, «Documentation checks passed (7 files, 10 external links)», наследуется |
| Инварианты геометрии | не перезапускал | не требуется: дельта не трогает геометрию/layout/marker.space/open_spans; последний прогон — r3, код 0 на обоих real-plan фикстурах, наследуется |
| Golden | не перезапускал | не требуется: дельта не меняет рендер/UI; последний прогон — r3, 161/161 passed, наследуется |
| Backend pytest | не запускал | git diff --stat d33ae88d..HEAD -- 'custom_components/**/*.py' пуст |
Находки
Нет. High: 0, Medium: 0.
Что проверено и корректно
- M1 закрыт по существу, не только по факту существования теста — три
прямые мутации (см. таблицу гейтов) доказывают, что guard действительно
ловит инлайн-литерал в каждом из трёх названных AC1 потребителей
(
wall-thickness.ts,houseplan-card.ts,plan-optimizer.ts), а не только в одном из них. - Guard читает исходник, а не собранный бандл —
sourceOf()(test/near-axis.test.mjs:20) делаетreadFileSyncизsrc/*.tsнапрямую, минуяtest-build/; расхождение между исходником и типизированной сборкой тест не маскирует. - Регекс на вызов, а не только на импорт —
assert.match(authoring, /snapNearAxisEndpoint\(/)иassert.match(optimizer, /repairNearAxisRoomWalls\(space\)/)отдельно требуют реального использования импортированного символа, а не только присутствия строки импорта (мёртвый импорт не прошёл бы эту часть проверки). - Трейлеры и класс изменения —
01512081(test: enforce shared near-axis threshold source) несётIssue: #290,User-Visible: no; правильно, это чисто тестовый файл (test/**, класс B), продуктовое поведение не менялось, changelog не требуется. - Дерево не расходится с гейтами — до, во время и после всех мутационных
проверок
git status --shortоставался чист; финальная пересборкаdistпобайтово совпадает с обеими закоммиченными копиями. - AC2–AC10, H1-регрессия r1, mutation-gate ×6, golden, инварианты, single
source of numbers, совместимость, performance — не пересматривались в этом
раунде: дельта их не задевает (см. «Разбор по дельте»), полностью
унаследованы из r3 (раздел ниже), который сам разобрал их читая весь
origin/dev...HEAD(а не по дельте) из-за промежуточного ребейза.
Чего не проверял
node scripts/check-docs.mjs,npm run invariants,npm run golden:verify, полныйmutation-gate, touch-смоки,pytest tests_backend— не перезапускал в этом раунде; все они не выбираются ни диффом (src/**не тронут), ни AC, задетым этой дельтой (только AC1/source guard). Их последние зелёные прогоны — в r3, см. таблицу гейтов там и «Унаследовано» ниже.- Внешний Linux CI Validate на точном HEAD
01512081— не проверял напрямую; последняя подтверждённая владельцем ссылка (Validate 32733382537) относится к SHAd33ae88d, до этого раунда. Пересборка/тесты локально воспроизводят зелёное состояние, но отдельного прогона CI на новом HEAD в комментариях issue не найдено на момент ревью. - Комментарий-хендофф автора после r3 (issue #290, id
5396316118, 2026-08-24T14:03:02Z) обрывается на полуслове: «Исправление Medium M1 из code review r3 запушено в \» — SHA коммита-фикса в нём не назван. Это не влияет на вердикт: коммит независимо идентифицирован черезgit log d33ae88d..HEAD(01512081, единственный коммит с продуктовым/тестовым содержимым в дельте) и проверен напрямую, а не со слов автора. Отмечаю как находку процесса, не как находку кода — вердикт не снижается, править нечего в этой ветке.
Унаследовано из r3
Документ: docs/reviews/CODE-REVIEW-290-r3.md, SHA d33ae88da4049bdbdfd02c57f935199007157cf3
(назван в самом документе r3). Принято без повторной проверки в этом раунде,
так как дельта d33ae88d..HEAD их не задевает:
- AC1 (кроме source guard), AC2–AC10 — весь предметный разбор по коду
(
src/near-axis.ts,src/houseplan-card.ts,src/resize.ts,src/plan-optimizer.ts,src/wall-thickness.ts,scripts/model-invariants.mjs) — r3 прочитал и перепроверил их полностью (не по дельте относительно r2, а заново, из-за промежуточного ребейза на ушедший вперёдdev), включая прямой откат H1-регрессии r1 и все 6 обязательных mutation-gate ID индивидуально. - Golden (161/161 сценариев), docs fingerprint (7 файлов, 10 внешних
ссылок), near-axis профиль на обоих real-plan фикстурах (
--near-axisCLI), bundle parity на момент r3 — гоняны и зафиксированы в r3, дельта этого раунда не меняет ни один из задействованных там файлов. - Продуктовые решения владельца (авто-выравнивание без modifier bypass,
подтверждаемый lossy-пас Optimize с отчётом/Undo, единый допуск
0.25°) — зафиксированы на этапе ТЗ (SPEC-REVIEW-290-r2, зелёный,dc9f9a6c) и не относятся к коду; ребейз и текущая дельта их не касаются. - Backend —
custom_components/**/*.pyне менялся ни в r3, ни в этой дельте;pytest tests_backendпо-прежнему не требуется.
Вывод
Единственная находка предыдущего раунда (M1) закрыта и подтверждена прямой
мутацией на всех трёх названных в AC1 потребителях, а не только заявлением
автора. Новых находок дельта не создаёт. Дешёвые гейты (tsc, npm test,
build + сверка бандла) зелёные на текущем HEAD. High: 0, Medium: 0 →
зелёный вердикт.