mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
81 lines
16 KiB
Markdown
81 lines
16 KiB
Markdown
# CODE-REVIEW-384-r1
|
|
|
|
Issue: #384 · «hidden architecture no longer widens the `fit: house` frame»
|
|
Заход: r1 · трек `small` (лимит циклов код-ревью — 2) · блокирующих циклов израсходовано 0/2
|
|
Материал: `git diff origin/dev...HEAD`, диапазон `origin/dev..HEAD` (2 коммита)
|
|
SHA на момент вердикта: `4f28bf4f7f1699a7fb094ab05c72348c9a66acb2` (проверено `git rev-parse HEAD` непосредственно перед выводом)
|
|
Ветка: `issue/384-fit-house-hidden-walls`
|
|
|
|
Первый раунд код-ревью — раздел «объём по дельте» (§2.10) не применяется, разбор полный.
|
|
|
|
## Скоуп
|
|
|
|
Коммиты:
|
|
- `16133394` `fix: hidden architecture no longer widens the fit: house frame (#384)` — `User-Visible: yes`, `Issue: #384`. Меняет `src/space-render.ts`, расширяет `demo/smoke_space_card.mjs`, добавляет мутант в `scripts/mutation-gate.mjs`, правит `docs/CANVAS.md`, `docs/USER-GUIDE.md`, `docs/USER-GUIDE.ru.md`, `docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`.
|
|
- `4f28bf4f` `build: refresh bundle trees for #384` — `User-Visible: no`, класс D (пересборка бандла + скриншоты документации).
|
|
|
|
ТЗ — лёгкий трек, тело issue (редакция 2 после SPEC-REVIEW-384-r1/r2, оба ревью ТЗ уже пройдены: r1 жёлтый M1 закрыт, r2 зелёный). AC1–AC4 из тела issue проверялись против этого диффа.
|
|
|
|
## Как проверялось
|
|
|
|
Прогнано лично на SHA `4f28bf4f` (зелёного Validate на этом SHA не найдено, поэтому дешёвые и относящиеся к делу гейты прогнаны самостоятельно):
|
|
|
|
| Гейт | Команда | Результат |
|
|
|---|---|---|
|
|
| Typecheck | `npx tsc --noEmit` | чисто, без вывода |
|
|
| Юнит | `npm test` | `# tests 1585 / pass 1584 / fail 0 / skipped 1` — совпадает с заявленным в хендоффе |
|
|
| Build + сверка бандла | `npm run build && cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js` | `MATCH`; дополнительно `npm run bundle:sync` — `git status`/`git diff --stat` после пересборки пуст, дерево уже синхронно |
|
|
| Бюджет | `npm run bundle:budget` | initial View 277 978 Б / 300 000 (запас 22 022 Б) — совпадает с заявленным «+14 Б» (277 951 в хендоффе), расхождение в пределах шума хешей чанков |
|
|
| `any` на новых строках | `node scripts/no-new-any.mjs --base origin/dev --head HEAD` | «Новых any нет» (8 добавленных строк в 1 файле) |
|
|
| Доки-отпечаток | `node scripts/check-docs.mjs` (diff трогает `src/**` → обязателен) | «Documentation checks passed (7 files, 10 external links)» |
|
|
| Выбор смоков | `node scripts/smoke-select.mjs --base origin/dev --head HEAD` | «НЕОПРЕДЕЛЁННОСТЬ» — символ на изменённых строках не совпал ни с одним смоком автоматически (в диффе нет нового именованного символа проекта — правка сидит внутри уже существующей функции `renderSpaceStatic`). Решение по строкам ниже принято вручную, не автоматом |
|
|
| Целевой смок (AC1/AC2/AC3) | `node demo/smoke_space_card.mjs` | `OK space-card: ...` — прогнан, зелёный; `hiddenTwinFrames.hidden.w = 752` против `hiddenTwinFrames.shown.w = 300938` (см. ниже) |
|
|
| Смежный смок (Glow/viewBox) | `node demo/smoke_glow_blending.mjs` | `{"ok":true,"blend":"screen","pools":60,"staticParity":true,"staticPools":60}` — совпадает с заявленным «parity 60/60»; прогнан, т.к. риск-раздел ТЗ утверждает независимость Glow от структуры кадра, а Glow рисуется в том же viewBox, который эта задача меняет |
|
|
| Мутационный гейт (целевой мутант) | `node scripts/mutation-gate.mjs --id=fit-house-hidden-walls-vote` | `ok чистый прогон: node demo/smoke_space_card.mjs` → `ok fit-house-hidden-walls-vote: тест покраснел, как обязан` → `поймано 1 из 1` |
|
|
| Процесс-гейт | `node scripts/process-gate.mjs` | «гейт пройден, предупреждений 1» (п.3: ТЗ для класса A не найдено файлом — ожидаемо для метки `small`, ТЗ в теле issue) |
|
|
|
|
Не прогонялись и почему:
|
|
- **`golden:verify`** — диф меняет видимый кадр (`viewBox`), но проверено `grep -n "fit:" demo/golden/matrix.mjs`: ни один golden-сценарий не задаёт `fit: 'house'` вовсе (только `showBorders:false` в изометрических сценариях без `fit`, которые используют другой, независимый путь кадрирования — см. «Один голос — один источник» ниже). Полный прогон golden не покрыл бы изменённую ветку и не доказал бы ничего сверх уже прогнанных смоков; прогон предрелизный (PROCESS §8) в любом случае.
|
|
- **`python -m pytest tests_backend`** — диф не трогает `custom_components/**/*.py`.
|
|
- **`npm run invariants -- --config ...`** — диф не меняет модель геометрии (рёбра комнат, записи толщины, `layout`, `marker.space`, `open_spans`); меняется только то, что уже посчитанная геометрия отдаёт в подсчёт кадра. `npm test` (зелёный) уже гоняет инварианты на всех моделях проекта.
|
|
- **Полный набор `demo/smoke_*.mjs` (205 файлов)** — задача — один модуль, один цикл кадрирования; `smoke-select.mjs` не нашёл автоматической связи («НЕОПРЕДЕЛЁННОСТЬ»), а `smoke_wall_junctions` (похожее по названию, но нерелевантное — прецедент #234) сюда не подходит по смыслу: там про геометрию стыков, здесь — про то, участвует ли уже готовая геометрия в подсчёте bbox. Прогнаны только два смока, чью связь с диффом я установил по чтению кода (прямое совпадение — `smoke_space_card` сам расширен этим коммитом; зарегистрированная связь — `smoke_glow_blending` из-за общего viewBox).
|
|
- **Performance-профили** — не названы в AC, диф не задевает горячий путь (условие плюс пропуск цикла, не новый проход по геометрии).
|
|
|
|
## Находки
|
|
|
|
Нет находок уровня High или Medium — ни в скоупе, ни вне его.
|
|
|
|
### Дополнительная проверка (сделана ревьюером сверх AC, результат — отрицательный)
|
|
|
|
При чтении обнаружил, что параллельная (не структурная) «content»-рамка `fit: content`/по умолчанию тоже безусловно включает `extras`/`zeroWalls.lines` в список `placed` (`src/space-render.ts:355-365`) — то есть на первый взгляд тот же класс дефекта («невидимая архитектура голосует в кадре») мог существовать и для дефолтного режима, вне скоупа #384. Эмпирически проверил отдельным сценарием (твин-карточки без `fit`, с тем же удалённым столбом, `show_borders` true/false): рамки совпали побайтово (`{x:-50,y:50,w:1100,h:900}` в обоих случаях). Причина — `contentFrame` использует `core` (статистическое отбрасывание выбросов, §4.1 CANVAS.md), а не «сохранить каждый структурный элемент», как `fit: house`; удалённый столбец в обоих случаях отбрасывается как выброс независимо от видимости. Это не дефект — отдельного issue не заводил.
|
|
|
|
## Что проверено и корректно (AC1–AC4)
|
|
|
|
- **AC1** (далеко выступающая стена/колонна-extra + `show_borders:false` + `fit:house` → кадр по комнатам): доказано автотестом — новый блок `hiddenTwinFrames` в `demo/smoke_space_card.mjs` клонирует снапшот конфига, добавляет `wall_columns` с дальним столбцом и разнится только `show_borders`; ассерт `hidden.x+hidden.w < shown.x+shown.w - 100` прошёл (`752` против `300938` по ширине на живом прогоне). **Тест умеет падать** — подтверждено мутационным гейтом (`--id=fit-house-hidden-walls-vote`, патч снимает `if (disp.showBorders)` перед циклом `extras` → смок красный, поймано 1/1).
|
|
- **AC2** (`show_borders:true` → кадр байт-в-байт как раньше): доказано и чтением, и исполнением. По коду: все три новых гварда — `if (disp.showBorders) for (...)` — при `showBorders === true` строго эквивалентны прежнему безусловному циклу (условие не меняет тело, только пропускает его при `false`); `needsCanonicalWallGeometry` новой формы `walls.length || (extras.length && disp.showBorders)` при `showBorders === true` даёт то же логическое значение, что старая `... || (extras.length && (disp.showBorders || fit === 'house'))` — обе истинны на этой ветке ИЛИ. Исполнением: существующие ассерты `tightFrame`, `tightNoTitleFrame`, `containment ±0.51px`, `tightPaintedEnvelope` в том же прогоне `smoke_space_card.mjs` не изменились и остались зелёными (значения `tightFrame` и `tightNoTitleFrame` идентичны в выводе прогона).
|
|
- **AC3** (`show_borders:false` + видимые проёмы → проёмы всё ещё голосуют): **проверено чтением, не исполнением**. Гвард проёмов (`if (!disp.hideOpenings) for (const resolved of resolvedHosted)`, :453) диффом не тронут; в обоих твинах `hideOpenings` не установлен (по умолчанию `false`), то есть проёмы формально видимы и участвуют в обоих сценариях смока, но тест не изолирует это отдельным ассертом (например, не сравнивает `hidden`-кадр с эталонным «только комнаты+проёмы»). Symmetричность с рендер-гвардом проёмов подтверждена и в docs (CANVAS.md, USER-GUIDE) — формулировки согласованы с кодом.
|
|
- **AC4** (полный гейт зелёный, бюджет ≈0): подтверждено таблицей выше; бюджет 277 978/300 000 Б (запас 22 022 Б, дельта от заявленных автором 277 951 в пределах хеш-шума).
|
|
|
|
### Асимметрия проверки трёх симметричных гвардов (Low, не блокирует, не правится)
|
|
|
|
Диф добавляет один и тот же гвард `if (disp.showBorders)` в трёх местах: тела стен из `canonicalWallGeometry.components`, `extras`, `zeroWalls.lines`. Мутационный тест и новый твин-смок доказывают исполнением только гвард `extras` (демо-фикстура `smoke_space_card.mjs` никогда не задаёт `spCfg.walls` — проверено отдельным прогоном `houseplan/config/get` на пространстве `f1`: `wallsLen: 0` до патчей смока), поэтому гварды `canonicalWallGeometry.components` и `zeroWalls.lines` доказаны **только чтением**: та же механическая конструкция `if (disp.showBorders) for (...)`, тело циклов не изменено, риск опечатки симметричен и низок. AC1 это покрывает буквой («стена ИЛИ колонна-extra»), так что это не невыполненный AC, а точечный пробел мутационного покрытия. Снимаю без правки: расширение демо-фикстуры настоящими `walls`/нулевыми стенами ради этого — по объёму сравнимо с отдельной задачей, не стоит того здесь.
|
|
|
|
## Один голос — один источник
|
|
|
|
Диф не добавляет и не меняет ни одной пользовательской величины (нет новых чисел/подписей/превью) — правило неприменимо. Затронутая величина — `viewBox` (геометрический кадр), а не отображаемое число; `test/single-source-numbers.test.mjs` входит в зелёный `npm test` и не задет по существу.
|
|
|
|
## Трейлеры и трассируемость
|
|
|
|
- `16133394`: `Issue: #384`, `User-Visible: yes` — оба changelog (`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`) правлены в этом же коммите, запись под `## Unreleased`, ссылка на #384 есть в обоих.
|
|
- `4f28bf4f`: `Issue: #384`, `User-Visible: no` — только класс D (бандл + скриншоты документации), корректно.
|
|
- Ветка `issue/384-fit-house-hidden-walls` соответствует `issue/<NN>-slug`.
|
|
- `process-gate.mjs` — зелёный (1 некритичное предупреждение, ожидаемое для лёгкого трека).
|
|
|
|
## Документация
|
|
|
|
`docs/CANVAS.md:232-236`, `docs/USER-GUIDE.md:798-803`, `docs/USER-GUIDE.ru.md:1546-1552` — все три обновлены оговоркой «участвуют только пока видимы (`show_borders`)», согласовано с кодом и друг с другом; это ровно то, что требовало M1 из SPEC-REVIEW-384-r1. `check-docs.mjs` подтверждает отпечаток скриншотов (7 файлов) в порядке.
|
|
|
|
## Вердикт
|
|
|
|
Зелёный. High: 0, Medium: 0. Единственная заметка — Low по асимметрии мутационного покрытия трёх симметричных гвардов — снята решением ревьюера без правки (см. выше), в документ занесена с обоснованием.
|