19 KiB
CODE-REVIEW-376-r1
Issue: #376 — «Пачка Low из adversarial-аудита beta.4» (лёгкий трек, редакция 2)
Ветка: issue/376-audit-lows-beta4, база — актуальный origin/dev
Коммиты: db7f0587 (fix, User-Visible: yes) + dceaf2d8 (build: refresh bundle trees, User-Visible: no)
Заход: r1 · этап code · блокирующих циклов израсходовано 0 из 2 (лёгкий трек)
1. Скоуп
ТЗ (редакция 2, принята зелёным SPEC-REVIEW-376-r2) содержит пять точечных правок; пункт (в) вынесен в #377 на полный трек и в этот код-ревью не входит:
- (а)
houseplan-space-card:title: null(YAMLtitle:без значения) получает тот же компактный кадр, что иtitle: ''. - (б) USER-GUIDE(.ru), раздел «Редактор подложки»: зафиксировано, что маркеры устройств и подписи комнат не перехватывают указатель. Код не трогается — это осознанно существующее поведение.
- (г)
furnitureScreenScaleв labs-изометрии больше не применяет 2D-формулу компенсации камеры; в iso —1, как у обычного декора. - (д)
docs/TESTING.md:1704— оговорка проlight_poolsopt-in (#374). - (е)
space-card.ts: dispose-гейтlight_poolsвыровнен на!== true, зеркально строгому рендер-гейту=== true.
Диапазон разбора: git log --oneline origin/dev..HEAD (2 коммита),
git diff origin/dev...HEAD (40 файлов: 2 файла продукта класс A
(src/houseplan-card.ts, src/space-card.ts), тесты/скрипты класс B
(demo/smoke_space_card.mjs, test/*.mjs, scripts/mutation-gate.mjs),
документация класс C (CHANGELOG*, USER-GUIDE*, TESTING.md,
docs/images/**), сгенерированный бандл класс D (dist/**,
custom_components/houseplan/frontend/**)).
Первый заход код-ревью для этого issue — раздел «унаследовано» не нужен, все пять пунктов разобраны заново по коду и по AC ТЗ rev2.
2. Как проверялось
Построчная сверка кода против ТЗ и AC
- AC-а (
src/space-card.ts:826):compactTopFrame: this._config.title === '' || this._config.title === null— дословно как в ТЗ.titleдля рендера заголовка (:816)this._config.title !== undefined ? this._config.title : sp?.title || ''—null !== undefined, так чтоtitle = null, и${title ? … : nothing}(:875) уже скрывал header дляnullдо этой правки; правка меняет только кадр — согласуется с утверждением ТЗ «скрытие header уже работает».undefined-ветка (дефолтный заголовок) не тронута. - AC-б: код
plan.styles.tsне менялся (проверено — файла нет в диффе). Утверждение ТЗ и текста доков проверено по коду:.stage.mode-decor .devlayer, .stage.mode-decor .devlayer *, .stage.mode-decor .dev::before(plan.styles.ts:863-865) —devlayerдействительно является родителем разметки room-label (houseplan-card.ts:11069, CSS-переменные--rl-icon-size/--rl-fontзаданы на том же<div class="devlayer">, что оборачивает и устройства, и лейблы), так что универсальный потомок*накрывает и лейблы. Формулировка вUSER-GUIDE.md:694-696иUSER-GUIDE.ru.md:1242-1244(«device markers and room labels do not intercept the pointer» / «маркеры устройств и подписи комнат не перехватывают указатель») — не догадка, а точное описание существующего кода. - AC-г (
src/houseplan-card.ts:8089):const furnitureScreenScale = this._renderProjection === 'iso' ? 1 : furniturePlanScreenScale(...). Прослежен весь путь потребления значения: единственный вызовfurniturePlanScreenScale(во всём_renderDecorLayer(:8074-8209, тестfurniture-stroke-contract.test.mjsсчитает вхождения — единственный источник для декор-слоя), проброс вfurnitureStrokePx(strokeWidth, furnitureScreenScale)(:8157) и в_renderFurniturePlacementPreview( furnitureScreenScale)(:8209) — общий и превью, и сохранённые фигуры берут одно и то же число._renderDecorLayer()вызывается безусловно (единственный сайт —:10992, гейт толькоhideDecor, не проекция), то есть декор, включая мебель, рендерится и в iso — значит правка реально исполняется в iso-режиме, а не мертвый код._renderWallBodies/_renderIsoWalls(:8771, :8888) — гейты проекции на стены, к декору не относятся, не спутаны. - AC-д (
docs/TESTING.md:1705):unless \light_pools: true` opts them in (#374)` — добавлено ровно к пункту про static room cards, дословно как в AC-д (grep подтверждён). - AC-е (
src/space-card.ts:290-297, 847): dispose-гейтthis._config.light_pools !== true— точное зеркало рендер-гейтаlightPools: this._config.light_pools === true(:847).light_pools: 1теперь и не рисует пулы (уже было так,===trueне давал1), и не сохраняет рантайм живым (раньше!this._config.light_poolsбылоfalseдля1, рантайм не dispose'ился — асимметрия, которую чинит эта правка).
Гейты — прогнаны лично на этом SHA (зелёного Validate для dceaf2d8 не найдено)
| Гейт | Результат |
|---|---|
npx tsc --noEmit |
чисто, без вывода |
npm test |
1551 pass / 0 fail / 1 skip (1552 всего) — совпадает с заявленным автором числом |
npm run build |
сборка чистая, git status после — пусто (бандл в dist/ уже байт-в-байт совпадает с закоммиченным) |
npm run bundle:sync |
без диффа (три копии дерева уже синхронны) |
npm run bundle:budget |
initial View 275801 B / 300000 (headroom 24199) — совпадает с числом автора |
node scripts/check-docs.mjs |
passed (7 files, 10 external links) — обязателен, диф трогает src/** |
Мутанты (дишонести-дисциплина: тест обязан уметь падать) — прогнаны лично, не только по слову автора:
node scripts/mutation-gate.mjs --id=space-card-null-title-compact-narrowed
→ поймано 1 из 1 (test/space-card-audit-lows.test.mjs краснеет без правки)
node scripts/mutation-gate.mjs --id=furniture-stroke-iso-camera-mismatch
→ поймано 1 из 1 (test/furniture-stroke-contract.test.mjs краснеет без правки)
(Полный прогон mutation-gate.mjs без --id пробовал ошибочно как общий
чек — упал на несвязанной строке tests_backend/test_frontend_assets.py
из-за отсутствия pytest в окружении; это существующая инфраструктурная
дыра окружения, не относится к диффу #376 — custom_components/**/*.py и
tests_backend/** в диффе нет, так что backend-гейт для этой задачи не
обязателен и не разбирался дальше.)
Смоки — выбор обоснован, не «прогнал всё»
node scripts/smoke-select.mjs --base origin/dev --head HEAD: изменено 2
файла src/**, 3 символа на изменённых строках. Инструмент не нашёл ни
одного смока с прямым совпадением по символу; 19 файлов помечены как слабая
связь через одно распространённое имя _config (общее для всех карточек,
не специфично для этой правки) — решение ревьюера: не прогонять, связь
неинформативна. Неопределённость по _renderProjection,
furniturePlanScreenScale — новых смоков под них нет; решаю по AC и по
доказательству чтением (см. AC-г выше) плюс по одному прогнанному смоку,
который фактически упражняет соседний код того же слоя:
demo/smoke_space_card.mjs— обновлён самим диффом (добавлена веткаtitle: null), прямое доказательство AC-а исполнением:nullTitleFrameбайт-в-байт равенcompactFrame({x:-50,y:100,w:1100,h:850}),nullTitleHasTitle:false. Прогнан — OK.demo/smoke_glow_blending.mjs— регистрированная связь: явно создаётhouseplan-space-cardсlight_pools: trueи без. Прогнан —{"ok":true,"blend":"screen","pools":60,"staticParity":true, "staticPools":60}— не деградировал (та же цифра, что в отчёте автора). Не покрывает конкретно новую ветку «light_pools: 1» (её ловит только новый юнит-тест) — рантайм-регрессию поtrue/falseпарности проверяет.demo/smoke_furniture.mjs— регистрированная связь (общий декор/мебель слой). Прогнан — все 18 полейtrue/OK, включаяfurnitureFollowsPhysicalCameraZoom,designerAndPrimitiveMatchOrdinaryDecor— не деградировал. Не покрывает iso-ветку напрямую (в фикстуре нетprojection: 'iso'), только соседнее 2D-поведение.
Остальные 200 смоков не прогонялись — ни прямой, ни регистрированной связи с изменёнными тремя символами инструмент не нашёл, а полный прогон матрицы — предрелизная обязанность, не гейт этого ревью.
Golden — сознательно не прогонялся, обоснование
Diff меняет видимый рендер (кадр space-card, толщина штриха мебели в iso),
что по правилам требует рассмотреть golden:verify. Проверено по
demo/golden/matrix.mjs и demo/golden/harness.mjs, что ни один текущий
golden-сценарий не упражняет ни одну из двух изменённых веток:
- space-card нигде не участвует в golden-матрице (нет ни одного
сценария с
houseplan-space-card) —golden:verifyфизически не может ни поймать, ни подтвердить AC-а. - iso + мебель: все
projection: 'iso'сценарии (isometric-*, 7 штук) используютspace: 'golden-geometry' | 'golden-lighting' | 'golden-wall-junctions' | 'perf-floor-2' | 'golden-opening-symbols'безdecorOverride; дефолтныйspace.decor = [](harness.mjs:245,514), то есть в них нет ни одной декор-фигуры вообще. Сценарии с мебелью (golden-decor-sofaчерезdecorOverride: decorLayerFixture) — только вmode: 'decor'/mode: 'view'безprojection: 'iso'(furniture-*,decor-over-opaque-hover-light,decor-over-glow-base-dark) — тамfurnitureScreenScaleидёт по прежней 2D-формуле, не тронутой этой правкой.
Прогон golden:verify дал бы нулевую диагностическую ценность для этого
диффа — решение не прогонять, а не пропуск.
Invariants / backend
Diff не трогает геометрию комнат, толщину стен, layout, marker.space,
open_spans, custom_components/**/*.py — npm run invariants и
pytest tests_backend не требуются и не запускались.
Трейлеры и changelog
db7f0587: Issue: #376, User-Visible: yes, оба changelog
(docs/CHANGELOG.md, docs/CHANGELOG.ru.md) правлены в этом же коммите —
верно. dceaf2d8: Issue: #376, User-Visible: no — верно для
build-коммита (только бандл-деревья).
Одно число — один источник
Единственная новая пользовательская величина в этом диффе —
furnitureScreenScale для iso. Проверено: значение вычисляется один раз в
_renderDecorLayer и передаётся обоим потребителям (сохранённые фигуры и
placement-preview) одним и тем же параметром — дублирования источника нет,
и именно это утверждение теперь явно проверяет
furniture-stroke-contract.test.mjs («the viewport scale is resolved once
for the whole decor layer»). compactTopFrame/lightPools — булевы гейты
без дублирующего представления величины, вопрос неприменим.
3. Что проверено и корректно
- Все пять пунктов (а, б, г, д, е) реализованы ровно так, как описаны в принятом ТЗ rev2, без расширения скоупа и без пункта (в) — вынесен в #377.
title: nullдаёт компактный кадр байт-в-байт равныйtitle: '',undefined-ветка (дефолтный заголовок) не задета — подтверждено исполнением смока и юнит-тестом с мутантом.- iso-ветка мебели отключает 2D-компенсацию, не затрагивая ни один текущий golden/смок-сценарий (все они либо без мебели в iso, либо без iso для мебели) — подтверждено чтением плюс мутантом.
light_poolsrender/dispose гейты симметричны — подтверждено чтением, юнит-тестом (grep-контракт с явным запретом старого гейта) и смоком наtrue/falseпараx.- Документация (б, д) точно описывает существующий код, не выдаёт догадку за факт — сверено построчно с CSS/JS.
- Трейлеры, оба changelog, класс изменений (A:
src/*.ts; B: тесты/скрипты; C: доки; D: бандл) — все соблюдены. - Бюджет бандла практически не изменился (+3 Б), докстрока фингерпринта
скриншотов синхронна с текущим
src/**.
4. Чего не проверял (и почему)
- Полный смок-набор (203 файла) — не прогнан;
scripts/smoke-select.mjsне нашёл ни прямой, ни сильной регистрированной связи, кроме уже прогнанных трёх. Это предрелизная обязанность, не гейт код-ревью соразмерного диффа. npm run golden:verify— не прогнан, обоснование в §2 (ни один сценарий не упражняет изменённые ветки; см. выше).python -m pytest tests_backend— не прогнан, диф не трогаетcustom_components/**/*.py.npm run invariants— не прогнан, диф не трогает геометрию/толщину/layout.- Полный
mutation-gate.mjsбез--id— не завершён (упал на несвязанном backend-мутанте из-за отсутствияpytestв окружении ревьюера); не относится к диффу, не пересматривался повторно ради этого. - Ручного браузерного тестирования не было (по правилам цикла) — заменено прогоном названных смоков и построчным чтением кода.
5. Находки
Нет ни одной High, ни одной Medium (в скоупе или вне). Все три находки
предыдущего (spec) раунда были закрыты в редакции 2 и здесь не
пересматриваются повторно — это код-ревью первого захода для этапа code,
делить по дельте не от чего.
Вердикт
Все пять AC (а, б, г, д, е) выполнены и доказаны — частью исполнением (смоки, мутанты), частью чтением с явной пометкой. Гейты, соразмерные диффу, зелёные. Golden и backend осознанно не прогонялись с указанной причиной. Возражений по скоупу, процессу или качеству нет.
Зелёный.