mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 21:28:59 +00:00
222 lines
17 KiB
Markdown
222 lines
17 KiB
Markdown
# 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` нет. Зелёный.
|