Files
houseplan-card/docs/reviews/CODE-REVIEW-400-r3.md
T
2026-08-31 05:31:46 +00:00

222 lines
17 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-400-r3
Issue: #400 · заход r3 · блокирующих циклов израсходовано 2 из 4
HEAD ревью: `6a0bb985e78b622833e6b5c01b0f0b5453aa9314`
Верхний коммит с продуктовым кодом: `d153b20a` («fix: make the handle paint order a named decision and mutate it»), пересборка `bd6722cc`.
## Скоуп раунда
Единственный открытый вопрос, унаследованный из r2: Medium **M1** («мутационный
гейт `furniture-edge-handles-steal-the-corner` правит текст JS-комментария, а не
порядок отрисовки, и потому не воспроизводит регрессию AC1»).
r2 закрыл диапазон `73ecfbd1..HEAD`, но `HEAD` на момент того прогона фактически
останавливался на `f07e3c54` («docs: review document for #400», коммит r1) —
коммит с самой правкой, `d153b20a`, лёгший на ветку позже по UTC-времени
(`d153b20a` закоммичен `2026-08-31T05:14:55Z`, `f07e3c54` — `2026-08-31T05:06:43Z`,
а следующий за ним доковый коммит r2, `6a0bb985`, — `2026-08-31T05:22:03Z`).
Иными словами, дерево, на котором физически считался диапазон r2, не содержало
правки, хотя она уже существовала на ветке к моменту публикации вердикта r2.
Автор указал на это в issue и попросил перезапуск на текущей вершине — это и
есть предмет r3.
**Дельта r3 = `git diff f07e3c54..HEAD`** (не `73ecfbd1..HEAD`, как в r2 —
`73ecfbd1` уже входит в f07e3c54 без изменений кода между ними):
```
demo/smoke_furniture.mjs | 13 +++++++++----
scripts/mutation-gate.mjs | 7 +++++--
src/houseplan-card.ts | 45 +++++++++++++++++++--------------
+ докатка dist/, custom_components/houseplan/frontend/, demo/srv/assets (class D)
+ docs/images/*.png, docs/images/screenshots.json (пересъёмка скриншотов, class D)
+ docs/reviews/CODE-REVIEW-400-r2.md (публикация прошлого документа, class C)
```
Только три файла несут смысл; остальное — сгенерированное (D) или
опубликованный артефакт прошлого раунда (C). Продуктовый код в этой дельте
касается исключительно порядка отрисовки ручек ресайза мебели и тестовой
инфраструктуры вокруг него — той же точки, что и M1. AC1/AC2 (приоритет угла),
AC3/AC6 (решение по help-строкам) и AC4/AC5 (источник в `alignGuides`) дельта не
трогает содержательно: help-строки и `alignGuides` в диапазоне не изменились
вовсе.
## Как проверялось
Дешёвые гейты (зелёного Validate на `6a0bb985` нет — прогнал сам):
| Гейт | Команда | Результат |
|---|---|---|
| Типы | `npx tsc --noEmit` | чисто, без вывода |
| Юниты | `npm test` | **1667/0** (1 skipped), совпадает с числом, заявленным автором |
| Сборка | `npm run build` | ok, 15.6 с |
| Сверка трёх копий бандла | `npm run bundle:sync` | без диффа в рабочем дереве после — три копии совпадают с закоммиченными байт-в-байт |
| Бюджет | `npm run bundle:budget` | initial View **285 471 / 300 000** B gzip, headroom 14 529 B |
| Докскрин-отпечаток | `node scripts/check-docs.mjs --external` | «Documentation checks passed (7 files, 10 external links)» |
`src/**` в дельте тронут (`src/houseplan-card.ts`), поэтому `check-docs` обязателен —
прогнан, зелёный.
Геометрия комнат/толщины/`layout`/`marker.space`/`open_spans` дельтой не
затронута (ручки ресайза мебели — не структура помещений), `npm run invariants`
не требуется и не запускался.
Мутационные гейты, привязанные к предмету M1 — прогнаны штатным раннером, не
руками:
```
$ node scripts/mutation-gate.mjs --id=furniture-edge-handles-steal-the-corner
ok чистый прогон: node demo/smoke_furniture_polish.mjs
ok furniture-edge-handles-steal-the-corner: тест покраснел, как обязан
поймано 1 из 1
$ node scripts/mutation-gate.mjs --id=align-guides-exclude-dead-source
ok чистый прогон: node demo/smoke_align_guides.mjs
ok align-guides-exclude-dead-source: тест покраснел, как обязан
поймано 1 из 1
```
Браузерные смоки — выбор через инструмент плюс собственное суждение:
```
$ node scripts/smoke-select.mjs --base f07e3c54 --head HEAD
Изменено файлов src/**: 1 · символов проекта на изменённых строках: 2
НЕОПРЕДЕЛЁННОСТЬ: дифф исполняемый, но ни один смок не связан доказуемо.
Символы, которых нет ни в одном смоке: HANDLE_PAINT_ORDER, _dtStart
```
Инструмент даёт «неопределённость» (новый идентификатор `HANDLE_PAINT_ORDER`
инструменту не известен), но связь доказана иначе — тем же мутационным гейтом,
который бьёт ровно по этой константе, и смоками, которые он вызывает как
предусловие. Прогнал их напрямую:
```
$ node demo/smoke_furniture.mjs → OK (includes reworked SE-handle selector)
$ node demo/smoke_furniture_polish.mjs → OK, smallFurnitureCornerWinsTheHit: true,
largeFurnitureKeepsBothHandles: true
$ node demo/smoke_align_guides.mjs → OK, devGuideComesFromAnotherMarker: true
```
`golden:verify` не запускал: правка `d153b20a` не меняет итоговый визуальный
порядок отрисовки — он был установлен ещё в `fe3b85c0` (`corners` после
`sides`) и остаётся тем же после переноса в `HANDLE_PAINT_ORDER = ['edges',
'corners']`; меняется только то, как порядок выражен в коде и как его ловит
мутатор. Пиксельного риска в дельте нет.
`pytest tests_backend` не запускал: `custom_components/**/*.py` дельта не
трогает.
Performance-профили не запускал: не названы в AC, дельта не касается путей,
чувствительных к перфу (мебель рендерится тем же числом фигур, порядок в
разметке не влияет на стоимость кадра).
## Разбор находки M1 по коду
`src/houseplan-card.ts:825`:
```ts
const HANDLE_PAINT_ORDER = ['edges', 'corners'] as const;
```
и рендер (`:8480-8492`) вместо инлайновых `sides.map(...)` + `corners.map(...)`
в фиксированном порядке теперь итерирует `HANDLE_PAINT_ORDER`, диспетчеризуя на
`role === 'edges'`. Порядок для реального рендера не изменился (`edges` перед
`corners` — corners по-прежнему красятся последними и выигрывают хит), но
теперь это значение переменной, а не расположение блоков в шаблоне — то, что и
требовал вердикт r1.
`scripts/mutation-gate.mjs` (мутант `furniture-edge-handles-steal-the-corner`)
теперь патчит саму константу:
```diff
- find: ' ${/* #400: corners LAST.',
- replace: ' ${/* mutant: corners no longer last.',
+ find: "const HANDLE_PAINT_ORDER = ['edges', 'corners'] as const;",
+ replace: "const HANDLE_PAINT_ORDER = ['corners', 'edges'] as const;",
```
Прежний патч менял текст внутри JS-комментария — компилируемый код не менялся,
поэтому регрессия не воспроизводилась (`поймано 0 из 1`, найдено в r1,
подтверждено ещё раз в r2 независимо). Новый патч меняет порядок ролей на
`['corners', 'edges']`, отчего `edges` красятся последними и перехватывают хит
на мелкой мебели — именно то поведение, которое AC1 запрещает. Живой прогон
подтверждает: `поймано 1 из 1`, а смок-предусловие (`smoke_furniture_polish`)
на немутированном коде зелёное.
`demo/smoke_furniture.mjs` больше не берёт юго-восточную ручку по индексу
`handles[3]` (зависел от порядка отрисовки, что и уронило шард 3 CI на
`d153b20a`), а фильтрует по роли (`!dtrot && !dtedge`) и берёт третий элемент
массива `corners`. В исходнике `corners = [[-1,-1,'nwse'], [1,-1,'nesw'],
[1,1,'nwse'], [-1,1,'nesw']]` (:8466-8468) — индекс 2 действительно
юго-восточный угол (`sx>0, sy>0`). Комментарий смока («SE — второй `nwse`») и
код согласуются.
**M1 закрыта фактически, не только по утверждению автора.**
## Закрытие раунда r2
| Находка r2 | Чем закрыта | Где это видно |
|---|---|---|
| M1: мутант правит комментарий, регрессия AC1 не воспроизводится (`поймано 0 из 1`) | Порядок вынесен в `HANDLE_PAINT_ORDER`, мутант теперь патчит саму константу | `src/houseplan-card.ts:825,8480-8492`; `scripts/mutation-gate.mjs:757-762`; живой прогон → «поймано 1 из 1» (выше) |
| Побочный эффект: `smoke_furniture.mjs` ловил SE-ручку по индексу `handles[3]`, что зависело от порядка отрисовки | Выбор ручки переведён на роль + позицию в массиве `corners` | `demo/smoke_furniture.mjs:357-363,405-413`; `node demo/smoke_furniture.mjs` → OK |
| Мета-находка r2: заявленные автором детали правки не находились ни в одном коммите ни одной ветки на момент прогона r2 | Правка физически существует на ветке начиная с `d153b20a` (создан раньше публикации вердикта r2, но диапазон r2 его не захватил из-за момента снятия дерева) | `git log`, время коммитов см. раздел «Скоуп раунда»; `git grep -c HANDLE_PAINT_ORDER` / `git grep -c cornerHandles` на `origin/issue/400-beta-polish` (оба autor приводил в issue, подтверждено независимо чтением файлов выше) |
## Унаследовано из r1/r2 (без повторной проверки в этом раунде)
Дельта `f07e3c54..HEAD` не касается кода этих пунктов, поэтому доказательства
r1 остаются в силе без повторного исполнения:
- **AC1/AC2** (приоритет угловой ручки на мелкой мебели) — функционально не
менялись в этой дельте (визуальный порядок тот же), но я всё же
перепрогнал `smoke_furniture_polish.mjs` как побочный эффект проверки
мутанта: `smallFurnitureCornerWinsTheHit: true`, `largeFurnitureKeepsBothHandles: true`.
Документ: CODE-REVIEW-400-r1, SHA `73ecfbd1`.
- **AC4/AC5** (`_alignCandidates` использует актуальный источник перетаскивания
вместо мёртвого `_drag?.id`) — код `src/logic.ts` в дельте не тронут; повторно
прогнал `smoke_align_guides.mjs` только как предусловие второго мутанта:
`devGuideComesFromAnotherMarker: true`. Документ: CODE-REVIEW-400-r1, SHA `73ecfbd1`.
- **AC3/AC6** (help-строки #86 остаются в initial-чанке — зафиксированное
решение в `docs/ARCHITECTURE.md`, бюджет в пределах потолка) — файлы решения
дельтой не тронуты. Число бюджета пересчитано в этом раунде (см. ниже) и
остаётся в пределах потолка. Документ: CODE-REVIEW-400-r1, SHA `73ecfbd1`.
- Две Low-находки из r1 (AC4 доказан e2e-смоком вместо юнита; вес 38
help-строк не перемерян отдельно) — сняты ревьюером r1 с записью, дельта их
не затрагивает. Документ: CODE-REVIEW-400-r1, SHA `73ecfbd1`.
## Проверено и корректно (новое в этой дельте)
- `HANDLE_PAINT_ORDER` — именованная константа, используется в единственном
месте рендера, тип `readonly ['edges','corners']` через `as const`, `tsc`
чист.
- Мутационный гейт `furniture-edge-handles-steal-the-corner` теперь бьёт по
контракту, а не по комментарию — подтверждено живым запуском, «умеет
падать» и падает.
- Мутационный гейт `align-guides-exclude-dead-source` не задет дельтой,
перепрогнан для контроля регрессии — без изменений, 1 из 1.
- `smoke_furniture.mjs`: выбор SE-ручки по роли согласован с индексами массива
`corners` в источнике.
- Три копии бандла синхронны после пересборки, бюджет не превышен.
## Чего не проверял и почему
- **Полная матрица смоков (209 файлов)** — задача не задевает ничего за
пределами мебельных ручек и направляющих; `smoke-select.mjs` подтверждает
узкий диапазон (`src/** изменено файлов: 1`). Полный прогон — предрелизная
обязанность.
- **`golden:verify`** — итоговый визуальный порядок отрисовки не изменился
этой дельтой (см. обоснование выше); риск пикселей не вижу.
- **`npm run invariants`** — дельта не касается геометрии комнат/толщины/
`layout`/`marker.space`/`open_spans`.
- **`pytest tests_backend`** — `custom_components/**/*.py` не тронут.
- **Performance-профили** — не названы в AC, путь не чувствителен к перфу.
- **Разница бюджета (+46 Б: 285 425 → 285 471)** — не проверял отдельно
происхождение (лишний байт от структуры `HANDLE_PAINT_ORDER.map` против
инлайновых `.map`), потому что рост укладывается в потолок с запасом и не
меняет вывод AC3/AC6; предупреждение скрипта про запас <15000 Б — известная,
не новая для этого раунда ситуация (#367), не следствие правок r3.
## Вердикт
M1 — единственная блокирующая находка, унаследованная из r1/r2 — закрыта по
существу и подтверждена исполнением, а не только заявлением автора. Новых
находок в дельте `f07e3c54..HEAD` нет. Зелёный.