mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-03 13:18:58 +00:00
275 lines
27 KiB
Markdown
275 lines
27 KiB
Markdown
# CODE-REVIEW-521-r2
|
||
|
||
Issue: #521 «fix: alignment guides follow the live gesture again»
|
||
Материал ревью: `9a3af808d95684b32df8f102da4211f3f800703c` (ветка
|
||
`issue/521-align-guides-live`). Диапазон делты этого раунда —
|
||
`0715321fdc69a2955d7850f3f384c1a0fdc25311..9a3af808` (материал r1 → материал
|
||
r2): один коммит `deebcfb0` (публикация `CODE-REVIEW-521-r1.md` конвейером,
|
||
0 изменений кода) и один продуктовый/тестовый коммит `9a3af808` «fix: hide the
|
||
settled guides copy for the whole gesture, and prove it».
|
||
Заход: r2 · блокирующих циклов израсходовано 1 из 4 (r1 — жёлтый).
|
||
|
||
## Скоуп
|
||
|
||
Единственная находка r1 (Medium, в скоупе) — AC5 заявлял доказательство
|
||
(«снятие подавления осевой копии слоя даёт 2 группы `.alignguides` — смок
|
||
красный»), которого прежний свидетель не давал: осевая копия
|
||
`.hp-editor-only-layer` в сценарии смока всегда была пуста, потому что смок ни
|
||
разу не форсировал осевую отрисовку посреди самого жеста. Дельта r1→r2 —
|
||
исправление ровно этого: переписанный сценарий `devices` в
|
||
`demo/smoke_align_guides.mjs` форсирует настоящую осевую отрисовку
|
||
(`c._hdrH += 0.01`) в середине перетаскивания, проверяет, что она
|
||
действительно произошла, что владение слоем перешло к осевшей сцене с
|
||
правильной (живой) точкой, что видимая группа ровно одна на каждом шаге
|
||
передачи, и что следующее движение возвращает владение живому художнику; плюс
|
||
новый мутант `live-editor-keeps-the-settled-guides-visible` в
|
||
`scripts/mutation-gate.mjs`, откатывающий подавление обратно в ветку `plan`
|
||
(как было до задачи).
|
||
|
||
Продуктовый код (`src/live-editor.ts`) в этой дельте не изменился по существу:
|
||
единственная правка — комментарий, объясняющий измеренный механизм передачи
|
||
слоя (было общее «плановый режим уже это делал», стало «владение чередуется
|
||
само, `_commitLiveEditor()` … измерено: две `.alignline` …»). Логика
|
||
`makeTransparent(...)` вне ветки `plan` осталась той же, что была принята и
|
||
проверена в r1 — сама защита не менялась, менялось только доказательство её
|
||
работы.
|
||
|
||
Дельта локальна по критериям §2.10: не ребейз (единственный промежуточный
|
||
коммит — публикация документа предыдущего раунда), не смена контракта
|
||
поведения (контракт п.3/AC5 переформулированы под тот же механизм, а не
|
||
заменены другим), не новая подсистема, объём (17 строк комментариев в
|
||
`live-editor.ts`, 16 строк нового мутанта, ~90 строк в свидетеле) далеко не
|
||
сопоставим с исходной задачей. Полный повторный разбор всей задачи не
|
||
требуется — по AC1–AC4, AC6–AC9 переверяю только то, что могла задеть дельта
|
||
(сам свидетель), остальное наследую из r1.
|
||
|
||
### Отдельная находка: тело issue правилось после зелёного ревью ТЗ (#517)
|
||
|
||
Хеш нормализованного тела issue на момент зелёного `SPEC-REVIEW-521-r3.md`
|
||
(«Материал раунда», конец документа) — `1e7f8d192f340e80e878aec0d6de383c982263254b408f8fc928f383130f5684`.
|
||
Текущий хеш (пересчитан дважды независимо: `gh issue view --json body` и
|
||
`gh api repos/.../issues/521 -q .body`, оба дают одинаковый нормализованный
|
||
текст) — `d117679d25abeab7ebfad3ed7902a8222625fc2ad43d0396b56d6fdacc12b5d3`.
|
||
Хеши расходятся — тело менялось после зелёного ревью ТЗ. Дельту GitHub не
|
||
хранит, поэтому проверяю не разницу, а текущий текст целиком (см. «Что
|
||
проверено» ниже).
|
||
|
||
Что именно изменилось, восстанавливается по признанию автора в комментарии
|
||
«Исправление по код-ревью r1 → заход r2»: «ТЗ: пункт контракта 3 и AC5
|
||
переписаны под измеренный механизм». Прочитал текущий пункт 3 «Контракта» и
|
||
строку AC5 в теле issue — они дословно описывают именно тот механизм
|
||
(чередование владения через `_commitLiveEditor()`, измеренные «две
|
||
`.alignline`, осевая отстаёт на шаг»), который выяснился при разборе r1 и
|
||
теперь доказан переписанным свидетелем. Текст не расходится с кодом и тестами
|
||
этого раунда — я сверил это построчно (см. «Что проверено»), а не принял на
|
||
слово автора.
|
||
|
||
**Оценка:** это находка процесса — правка раздела `## ТЗ`, уже прошедшего
|
||
зелёное ревью, должна по идее возвращаться на новый цикл `S4-spec-review`, а
|
||
не вноситься по ходу код-ревью. Но по существу правка не меняет ни персону, ни
|
||
сценарий, ни объём видимого изменения, ни один AC-номер — она уточняет
|
||
формулировку контракта и доказательства AC5 под механизм, который сам же
|
||
код-ревью r1 и вскрыл, и делает это прозрачно (объяснено в комментарии, а не
|
||
тихой правкой). Отдельного цикла ревью ТЗ эта поправка не заслуживает: именно
|
||
для такого случая существует делегирование «технический спор решает ревью
|
||
кода» (`PROCESS.md` §7.1) — здесь спора и нет, есть согласие с находкой r1,
|
||
выраженное в тексте. Классифицирую как **Low**, снимаю решением ревьюера:
|
||
текст ТЗ проверен целиком и корректен, действие не требуется.
|
||
|
||
## Как проверялось
|
||
|
||
Валидация на материале раунда (`9a3af808`) зелёная
|
||
(https://github.com/Matysh/houseplan-card/actions/runs/34526718586), включая
|
||
шесть параллельных джобов «Мутанты по диффу» — я прочитал логи всех шести
|
||
шардов напрямую (`gh run view --job <id> --log`), а не поверил статусу
|
||
«success» на слово:
|
||
|
||
| Гейт | Прогнал | Результат |
|
||
|---|---|---|
|
||
| Validate на `9a3af808` (`tsc`, `test`, `build`+bundle-sync, `no-new-any`) | нет, зачтено по зелёному прогону CI (см. ссылку) | success |
|
||
| Логи 6 шардов «Мутанты по диффу» в этом прогоне | да, прочитаны построчно | все 5 мутантов задачи пойманы: `live-editor-devices-drops-align-guides` (шард 5/6), `live-editor-decor-drops-align-guides` (4/6), `live-editor-plan-drops-align-guides` (1/6), `align-point-reads-frozen-snapshot` (2/6), **`live-editor-keeps-the-settled-guides-visible` (6/6)** — новый мутант этого раунда, «тест покраснел, как обязан» |
|
||
| `npm run bundle:sync` (пересборка + сверка `dist`/`custom_components/.../frontend`/`demo/srv/assets`) | да, локально (нужно было поднять `demo/srv/assets`, он не коммитится) | чисто, `git status` не показал diff после сборки — включает `tsc --noEmit` |
|
||
| `node demo/smoke_align_guides.mjs` (полный сценарий, все 34 поля) | да | все `true`, `OK` |
|
||
| `node scripts/mutation-gate.mjs --id=live-editor-keeps-the-settled-guides-visible` | да, независимо от CI, в рабочем дереве материала | «поймано 1 из 1» |
|
||
| Ещё 4 мутанта задачи по одному (`--id=…`) | да, все 4 | все «поймано 1 из 1» |
|
||
| `node --test test/smoke-harness-contract.test.mjs` (AC8) | да | 7/7 |
|
||
| `node scripts/check-docs.mjs` | да | зелёный, 7 файлов, 12 внешних ссылок |
|
||
| `node scripts/smoke-select.mjs --base 0715321f --head HEAD` (выборка по дельте раунда, не по всей задаче) | да | 1 прямое совпадение — `demo/smoke_align_guides.mjs` (символ `_alignPoint`); других смоков дельта не касается |
|
||
| `node demo/smoke_isometric_live_touch.mjs`, `node demo/smoke_touch_tips.mjs` | да (продуктовый код не менялся между r1/r2, но риск ТЗ по touch не был явно закрыт в r1 — закрываю здесь) | оба зелёные, все поля `true` |
|
||
| `cmp dist/houseplan-card.js custom_components/.../houseplan-card.js`, то же для `houseplan-panel.js` и `houseplan-assets.json` | да | побайтово идентичны |
|
||
| `git show -s --format=full 9a3af808` (трейлеры) | да | `Issue: #521`, `User-Visible: no` — верно: дельта не меняет видимого поведения (сама правка уже была `User-Visible: yes` в `53585b45`, эта дельта — доказательство и комментарий) |
|
||
| Хеш тела issue (`issueBodyDigest` из `scripts/review-doc-guard.mjs`, два независимых способа получения текста) | да | не совпадает с зафиксированным в зелёном `SPEC-REVIEW-521-r3.md` — находка выше |
|
||
|
||
Не прогонял: `golden:verify` (дельта раунда не меняет визуал — только
|
||
комментарий, тестовый файл и конфиг мутанта; видимый рендер не тронут),
|
||
`pytest tests_backend` (Python не тронут), полный набор из 239 смоков (не
|
||
обосновано ни диффом, ни `smoke-select`), performance-профили (см. ниже),
|
||
WSL/HA-харнесс.
|
||
|
||
**Перф (AC9).** Не перезамерял. Диф `src/live-editor.ts` между материалом r1 и
|
||
материалом r2 — только комментарии (проверил построчно: `if`/`makeTransparent`
|
||
вызовы в `paintHouseplanEditor` и `editorTemplate` идентичны байт в байт по
|
||
существу, разница — только текст комментариев). Комментарии не выполняются,
|
||
поэтому число операций в кадре жеста не изменилось ни на одну со времён r1, где
|
||
я независимо (не на слово автора) прогнал `benchmark:large-house-interaction`
|
||
+ `benchmark:compare` с собственной базой на `fa7ac02c` и получил зелёный отчёт
|
||
без регрессии (см. `docs/reviews/CODE-REVIEW-521-r1.md`, таблица гейтов). Этот
|
||
вывод наследуется без повторного прогона.
|
||
|
||
## Закрытие раунда r1
|
||
|
||
| Находка r1 | Чем закрыта | Где это видно |
|
||
|---|---|---|
|
||
| Medium: AC5 заявлял «снятие подавления даёт 2 группы `.alignguides` — смок красный», но смок никогда не форсировал осевую отрисовку посреди жеста, поэтому осевая копия всегда была пуста и мутация не проверялась | Сценарий `devices` смока форсирует настоящую осевую отрисовку через `c._hdrH += 0.01` (свойство вне `liveProperties`/`hoverProperties`/`gestureProperties` — проверил по `src/live-editor.ts:80-86`, оно не маршрутизируется в живой путь ни при каком состоянии жеста), проверяет, что рендер действительно случился (`handoverHappened`, счётчик `willUpdate`), что слой перешёл сцене с правильной живой точкой (`handoverGivesTheLayerBackToTheSettledScene`, `handoverGuideStaysOnTheLivePoint`), что видимая группа ровно одна (`handoverKeepsExactlyOneGuide`), и что следующее движение возвращает владение живому художнику (`nextMoveTakesTheLayerBack`). Новый мутант `live-editor-keeps-the-settled-guides-visible` откатывает `makeTransparent(...)` обратно в ветку `plan` — смок красный именно на `nextMoveTakesTheLayerBack` | `demo/smoke_align_guides.mjs:162-197`; собственный прогон смока — все поля `true`; собственный прогон `mutation-gate.mjs --id=live-editor-keeps-the-settled-guides-visible` — «поймано 1 из 1»; то же самое независимо подтверждено логом шарда 6/6 в Validate на `9a3af808` |
|
||
| (побочно устранённая слабость свидетеля, не отдельная находка r1) счёт направляющих по числу узлов в DOM вместо видимых — гашение делает копию прозрачной, а не удаляет | `visible(selector)` фильтрует по `layer.style.opacity !== '0'` вместо сырого `querySelectorAll`; проверил, что `makeTransparent` (`src/live-editor.ts:157-163`) действительно ставит `element.style.opacity = '0'` строкой, а `restore()` снимает через `removeProperty('opacity')` | `demo/smoke_align_guides.mjs:27-36`, `src/live-editor.ts:157-163`, `:213-220` |
|
||
|
||
Находка закрыта по существу: не «сделали AC5 неопровержимым переписыванием
|
||
слов», а заставили смок действительно воспроизвести сценарий (внешний осевой
|
||
рендер посреди активного жеста), который единственный делает защиту
|
||
наблюдаемой, и подтвердили мутацией, что без защиты сценарий ловится.
|
||
|
||
## Унаследовано из r1
|
||
|
||
Без повторной проверки в этом раунде приняты выводы
|
||
`docs/reviews/CODE-REVIEW-521-r1.md` (материал:
|
||
`0715321fdc69a2955d7850f3f384c1a0fdc25311`), поскольку дельта r1→r2 их не
|
||
задевает:
|
||
|
||
- диагноз и причинность регресса (`c0d61ca3`/#451, оба слома — слой и
|
||
замороженная точка) — не менялись, дельта их не касается;
|
||
- AC1 (направляющая в трёх живых жестах), AC2 (живая точка), AC3, AC4 —
|
||
свидетель для `decor`/`plan`-сценариев не менялся вовсе в этой дельте
|
||
(правки смока — только в сценарии `devices`, между строками 162 и 197);
|
||
мутанты `live-editor-devices-drops-align-guides`,
|
||
`live-editor-decor-drops-align-guides`, `live-editor-plan-drops-align-guides`,
|
||
`align-point-reads-frozen-snapshot` перепрогнаны мной заново тем не менее
|
||
(дёшево, один и тот же смок теперь длиннее) — все 4 по-прежнему «поймано 1 из
|
||
1»;
|
||
- AC6 (один расчёт кандидатов на кадр) — код и проверка не менялись в этой
|
||
дельте, наследуется техническое заключение r1 («проверено чтением», без
|
||
отдельного мутанта — осталось так же, это не регрессия дельты);
|
||
- AC7 (старые гарантии, #400) — не менялся код и не менялся тот участок
|
||
смока;
|
||
- AC8 (свидетель не фабрикует состояние) — тест-линт `test/smoke-harness-contract.test.mjs`
|
||
не менялся; перепрогнал (7/7) на новом более длинном смоке — линт по-прежнему
|
||
проверяет отсутствие `_deviceDrag =`/`_decorDraft =`, новые строки (`c._hdrH
|
||
= …`, `c._layout = …` уже был в старой версии) под запрет не подпадают;
|
||
- AC9 (перф) — см. «Как проверялось» выше, база `fa7ac02c` и зелёный отчёт r1
|
||
наследуются без повторного прогона, так как дельта не трогает исполняемый
|
||
код;
|
||
- бюджет `core-file-budget` и мягкость `_renderAlignGuides` на карточке
|
||
(`this._editorRuntime?._renderAlignGuides() ?? nothing`) — не менялись,
|
||
проверено в r1 чтением;
|
||
- трейлеры и changelog коммита `53585b45` — не менялись, `User-Visible: yes`
|
||
подтверждён в r1.
|
||
|
||
Технический раздел «Чего не проверял» из r1 (golden, pytest, полная матрица
|
||
смоков, WSL/HA-харнесс) остаётся в силе тем же образом — дельта раунда их не
|
||
задевает и не расширяет их необходимость.
|
||
|
||
## Находки
|
||
|
||
### Low-1 (снимается, см. выше) — тело issue правилось после зелёного ревью ТЗ без нового цикла `S4-spec-review`
|
||
|
||
См. раздел «Отдельная находка» в «Скоупе». Текст пункта 3 «Контракта» и AC5
|
||
переписан автором вместе с исправлением по код-ревью r1, без возврата в
|
||
`S3-spec`/`S4-spec-review`. Проверено: новая формулировка точно описывает
|
||
реализованный и теперь доказанный механизм, персона/сценарий/объём/номера AC
|
||
не изменились, изменение раскрыто в комментарии автора. Действия не требую;
|
||
записываю, чтобы хеш в следующем зелёном документе (материал раунда ниже)
|
||
зафиксировал новое значение и цепочка не молчала.
|
||
|
||
Других находок нет.
|
||
|
||
## Что проверено и корректно
|
||
|
||
- **Механизм передачи владения слоем воспроизведён чтением независимо от
|
||
комментария автора.** `updated()` (`houseplan-card.ts:4157`) на каждом
|
||
осевом рендере зовёт `_commitLiveEditor()` →
|
||
`commitHouseplanEditor` (`live-editor.ts:439-454`): отменяет
|
||
незавершённый `raf`, вызывает `restore(state)` (снимает `opacity`/`visibility`,
|
||
выставленные `makeTransparent`/`dim`/`hide`) и очищает живой корень
|
||
(`render(nothing, target)`). Значит после любого settled-рендера слой физически
|
||
переходит к осевшей сцене чистым, без «памяти» о прежнем подавлении.
|
||
- **`_hdrH` гарантированно не маршрутизируется в живой путь.** Прочитал
|
||
`liveProperties`/`hoverProperties`/`gestureProperties`
|
||
(`live-editor.ts:80-86`) и `routeHouseplanEditorUpdate`
|
||
(`live-editor.ts:120-145`) — `_hdrH` не входит ни в один набор, значит
|
||
`namedLive` ложно и осевой рендер происходит независимо от активного жеста.
|
||
Форсирование смоком (`c._hdrH += 0.01`) действительно моделирует внешний
|
||
settled-рендер (приход `hass`, ресайз), а не подглядывает во внутреннее
|
||
состояние жеста.
|
||
- **`_alignPoint` — геттер, а не хранимое состояние**
|
||
(`houseplan-card.ts:12879`), поэтому и живой, и осевой путь при вызове
|
||
`_renderAlignGuides()` во время самого settled-рендера читают одну и ту же
|
||
живую точку — вот почему «отставание на шаг» проявляется только НА
|
||
СЛЕДУЮЩЕМ живом кадре после хендовера, а не в момент самого хендовера; смок
|
||
проверяет обе фазы раздельно (`handoverGuideStaysOnTheLivePoint`, затем
|
||
`nextMoveTakesTheLayerBack`), и это совпадает с объяснением автора.
|
||
- **Мутант `live-editor-keeps-the-settled-guides-visible` патчит именно тот
|
||
код, который сейчас в файле** — сверил `find`-строку патча
|
||
(`scripts/mutation-gate.mjs`) с текущим `paintHouseplanEditor`
|
||
(`live-editor.ts:357-360`) посимвольно, совпадает; применимость и результат
|
||
подтверждены собственным прогоном и логом CI.
|
||
- **AC8 не сломан новыми строками смока.** `c._hdrH = …`, `c._layout = …` (уже
|
||
было раньше), `c.requestUpdate()` не запрещены тест-линтом — запрещены
|
||
только присваивания `_deviceDrag =`/`_decorDraft =`; перепрогнал линт, 7/7.
|
||
- **Touch-контракт не задет** (риск ТЗ явно требовал: «существующие
|
||
touch-смоки обязаны остаться зелёными») — `smoke_isometric_live_touch.mjs` и
|
||
`smoke_touch_tips.mjs` зелёные на материале раунда; в r1 эти два смока не
|
||
были явно названы прогнанными, закрываю пробел здесь, а не переношу его
|
||
дальше.
|
||
- **«Одно число — один источник» не применимо.** Направляющая — линия и точка,
|
||
не число; пользователю в этом диффе не показывается ни одна величина
|
||
дважды.
|
||
- **Бандл и трейлеры.** `bundle:sync` пересобрал дерево с нуля, `dist`/
|
||
`custom_components/.../frontend`/`demo/srv/assets` побайтово совпали с
|
||
закоммиченными файлами; `git show -s --format=full 9a3af808` — трейлеры
|
||
`Issue: #521`/`User-Visible: no` корректны, changelog не тронут и не должен
|
||
быть (дельта раунда не меняет видимое поведение сверх уже опубликованного в
|
||
`53585b45`).
|
||
|
||
## Чего не проверял
|
||
|
||
- `npm run golden:verify` — дельта раунда не меняет визуал (комментарий +
|
||
тестовый файл + конфиг мутанта), в r1 видимый рендер тоже не менялся кроме
|
||
уже принятого identical-скриншота.
|
||
- `python -m pytest tests_backend` — Python не тронут ни в одном коммите
|
||
задачи.
|
||
- Полная матрица из 239 смоков — не обоснована ни диффом раунда, ни
|
||
`smoke-select.mjs` (одно прямое совпадение); полный набор — предрелизный
|
||
гейт.
|
||
- Не перезамерял `benchmark:large-house-interaction` — обосновано выше
|
||
(дельта раунда не исполняемый код).
|
||
- Не запрашивал у владельца дополнительного цикла `S4-spec-review` по поводу
|
||
правки ТЗ после зелёного вердикта r3 — see Low-1: решил вопрос по существу
|
||
сам, поскольку это чисто техническая правка формулировки под уже найденный в
|
||
код-ревью механизм, а не продуктовый вопрос из числа тех, что задаются
|
||
владельцу (§7.1).
|
||
|
||
## Вердикт
|
||
|
||
Единственная находка предыдущего раунда закрыта по существу и подтверждена
|
||
воспроизведением (собственный прогон смока и мутанта плюс независимое чтение
|
||
логов CI). Новых блокирующих находок нет; единственная новая находка —
|
||
процедурная (Low), снята решением ревьюера с записью выше. High: 0. Задача
|
||
готова к очереди на пре-релиз.
|
||
|
||
Вердикт: зелёный · заход r2 · блокирующих циклов 1/4 · High: 0 · Medium: 0 → в задаче
|
||
|
||
Документ: docs/reviews/CODE-REVIEW-521-r2.md
|
||
|
||
---
|
||
|
||
<!-- material-anchors: сгенерировано конвейером (#414) -->
|
||
|
||
## Материал раунда
|
||
|
||
- Ветка: `issue/521-align-guides-live`, коммит `9a3af808d956` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет.
|
||
- Дерево материала: `20fbfc5c7f020a361c04f252900511c2e383ceba`
|
||
```
|
||
git log --all --format='%H %T' | grep 20fbfc5c7f02
|
||
```
|
||
- Тело issue: `d117679d25abeab7ebfad3ed7902a8222625fc2ad43d0396b56d6fdacc12b5d3`
|
||
- Вердикт конвейера: `green` · High 0
|