diff --git a/docs/reviews/CODE-REVIEW-746-r1.md b/docs/reviews/CODE-REVIEW-746-r1.md new file mode 100644 index 00000000..429774bc --- /dev/null +++ b/docs/reviews/CODE-REVIEW-746-r1.md @@ -0,0 +1,175 @@ +# CODE-REVIEW-746-r1 + +Issue: #746 · трек: show · заход: r1 · блокирующих циклов израсходовано 0 из 2 +Материал: `aa0b60e75498d052625886d1cdc23190566f9bde` (ветка `issue/746-room-fill-alpha`, +один коммит поверх `dev` `653d94ef`, ребейз-без-изменения-кода зафиксирован автором +в комментарии issue от 2026-10-01T14:27:02Z) +Validate на этом SHA: https://github.com/Matysh/houseplan-card/actions/runs/36876972490 — success + +## Скоуп + +Баг: `.room { transition: 0.12s }` анимирует цвет и `fill-opacity`/`stroke-opacity` +независимо, видимая непрозрачность — их произведение. Состояния без заливки +(`overlay`, `yard`, `outlined`, `picked`) держали альфу **в цвете** (`rgba(...)`, +`*-opacity` по умолчанию `1`), а `.styled` — наоборот, непрозрачный цвет плюс +`fill-opacity: var(--room-fill-op)`. При переходе между половинами одна растёт, +другая падает, и продукт в середине пути больше обоих концов: комнаты вспыхивали +серым при открытии «Настроек пространства» на этаже без заливки и темнели при +входе/выходе из редактора плана на закрашенном этаже. + +Правка приводит четыре состояния `.room` (`overlay`, `yard`, `outlined`, `picked`) +к той же записи, что уже была у `.styled`: непрозрачный цвет + явная +`fill-opacity`/`stroke-opacity`, `transparent` — только вместе с нулевой +`*-opacity`. Сам переход, `.styled` и переменные `--room-*` не тронуты. +Поверхность одна — блок правил `.room` в `src/styles/plan.styles.ts`; +`houseplan-space-card` берёт те же стили реэкспортом (`src/styles.ts`), отдельной +правки не требует — проверено чтением: `src/styles.ts:10,19` подключает +`planStyles` без переопределения `.room.*`. + +Диапазон `git diff origin/dev...HEAD`: `src/styles/plan.styles.ts` (+24/-4), +новый `demo/smoke_room_fill_transitions.mjs`, `docs/CHANGELOG.md`, +`docs/CHANGELOG.ru.md`, `docs/STYLING-HOOKS.md` — один коммит, трейлеры +`Issue: #746` и `User-Visible: yes` на месте, оба changelog в том же коммите. + +## Критерии §5 (трек show) — все пройдены + +- **complexity** — issue оценивает сложность 2/10; правка — точечная замена + записи прозрачности в 4 блоках CSS-правил, без новой логики. +- **surfaces** — одна поверхность: правила `.room` в одном файле стилей, + переиспользуемые всеми рендерами плана. +- **migration** — нет миграции конфига; риск для пользовательских правил + card-mod (правило, задающее только `fill` без `fill-opacity`, станет + невидимым) явно описан в issue, в обоих CHANGELOG и в STYLING-HOOKS.md — + не обойдён молчанием. +- **ux-contract** — видимое поведение в покое не меняется (доказано пофреймово, + см. ниже); исправляется переходный артефакт, а не контракт. +- **perf-touch** — чистый CSS, без touch-поверхности; perf-смок в Validate + зелёный (не требовался AC, но прогнался сам). +- **undocumented** — ожидаемое поведение зафиксировано самим issue (таблица + AC с точными числами) и теперь — в `docs/STYLING-HOOKS.md` §3.2 (новый + абзац, ссылка на §3.3 «Everything generated» корректна). + +Маршрут: `fix` (вердикт зелёный, `reclassify` неприменим). + +## Как проверялось + +| Гейт | Статус | Как | +|---|---|---| +| `tsc --noEmit`, `npm test`, `npm run build` + сверка бандла | не перегонял | зелёный Validate на точном SHA `aa0b60e7` (run 36876972490), "Фронтенд: типы, юниты, мутанты, синхрон бандла" — success | +| `golden:verify` | не перегонял сам, приняты результаты CI | тот же Validate, job "Golden-кадры против принятых эталонов" — success (`ci:golden` выставлена и отработала) | +| Смоки (полная матрица, 3 шарда) | не перегонял всю матрицу | тот же Validate, все три шарда "Смоки в браузере" — success; лог шарда 1 содержит `ok smoke_room_fill_transitions` — сам смок задачи выполнялся в CI и прошёл | +| `demo/smoke_room_fill_transitions.mjs` | **прогнал сам, дважды** | см. ниже — независимая проверка автотеста на red/green | +| `scripts/smoke-select.mjs --base origin/dev --head HEAD` | прогнал |「НЕОПРЕДЕЛЁННОСТЬ» — диффа в `src/styles/plan.styles.ts` символьная связь с смоками не выводится автоматически (CSS-правила, не функции); решение по AC1 issue и ручной проверке ниже | +| pytest / Hassfest / HACS / мутанты по диффу / геометрия TS-Python | не прогонял | нет правок Python/конфига/геометрии — неприменимо, в Validate эти джобы `skipped` | +| Мутанты (реестр) | не прогонял и не обязан | трек show, §10.4: мутанты на разработке не гоняются ни на одном треке | + +**Чего не проверял:** полную матрицу golden и смоков заново (доверяю +зелёному Validate на точном материале — свежесть подтверждена #343); ручное +тестирование в браузере сверх смока (нет штатного гейта, Chromium не +запускал для этого — только для перепрогона смока, который сам headless); +поведение `.radarsetup .room` — явно вне скоупа в issue («Не-скоуп»). + +### Независимая проверка AC1/AC2 (смок умеет падать) + +Защитный AC — здесь это обязательная таблица «чем доказан · чем краснеет»: + +| AC | Чем доказан | Чем краснеет | +|---|---|---| +| AC1: видимая непрозрачность заливки/обводки ни в одной точке перехода не превышает больший из концов более чем на 0.005 | `demo/smoke_room_fill_transitions.mjs`, прогнан мной на `aa0b60e7`: **OK**, все проверки (`fillOvershoot`/`strokeOvershoot` ≤ tolerance) зелёные для 4 путей + контроль | Я откатил патч `src/styles/plan.styles.ts` до состояния `origin/dev` (`git apply -R`), пересобрал бандл и перепрогнал тот же смок: **FAILED (6)**, с точными числами из issue — `openSettings` fill/stroke 0 → **0.2413** → 0, `cancelSettings` fill 0.18 → **0.3049** → 0 и stroke 0 → **0.2418** → 0, `enterPlanEditor` fill 0.18 → **0.3172** → 0.06, `leavePlanEditor` fill 0.06 → **0.314** → 0.18. Совпадает с цифрами issue (0.241/0.305/0.317/0.314) и отчётом автора. Тест действительно умеет падать именно на дефекте из issue, не на постороннем | +| AC2: кадр в покое не меняется (`overlay`, `styled`, `outlined`, `picked`, `yard`) | Тот же смок, секция `rest`: цвет и видимая непрозрачность по пяти состояниям совпадают с таблицей `REST`, зашитой в смок как ожидание дев-поведения; плюс Golden CI на точном SHA — success | На отклонение кадра покоя красит сам смок (`check` по каждому состоянию и каналу) — отдельно не провоцировал, т.к. правка кода не трогает значения `styled`/рест-часть `overlay` и т.д.; разбор по коду подтверждает: изменились только порядок/форма записи (hex→rgb + отдельная opacity), числовое произведение то же (проверено чтением, не исполнением, для каждой из 4 строк диффа — см. ниже) | + +После обеих проверок рабочая копия и `dist/` возвращены ровно к материалу +ревью (`git checkout -- dist/`, `git clean -fd dist/`, `git status` чист, +`git rev-parse HEAD` = `aa0b60e75498d052625886d1cdc23190566f9bde`). + +### Разбор по коду (проверено чтением, не исполнением) + +Для каждого изменённого блока: произведение «альфа цвета × *-opacity» в покое +не меняется, а при переходе оба сомножителя теперь монотонны от 0 (или от +другого конца) к цели, поэтому продукт двух неубывающих/невозрастающих +кривых ограничен большим из концов — перелёта быть не может: + +- `.room.overlay`: было `fill: transparent` (альфа 0, `fill-opacity` по + умолчанию 1) → стало `fill: transparent; fill-opacity: 0` — продукт 0 в + обоих случаях, при переходе к `.styled` оба множителя (альфа цвета и + opacity) растут от 0 монотонно → продукт ограничен произведением конечных + значений, перелёта нет. Симметрично для `stroke`. +- `.room.yard`: было `fill: rgba(75, 140, 90, 0.14)` (продукт 0.14) → стало + `fill: rgb(75, 140, 90); fill-opacity: 0.14` (продукт 0.14) — рест не + меняется, подтверждено прогоном (`rest.yard.fill = ["rgb(75, 140, 90)", + 0.14]`). +- `.room.outlined`: `rgba(62,166,255,0.06)`/`0.55` → `rgb(...); fill-opacity: + 0.06` / `stroke-opacity: 0.55` — рест не меняется (подтверждено прогоном). +- `.room.picked`: `rgba(255,193,77,0.25)` → `rgb(...); fill-opacity: 0.25`; + `stroke: #ffc14d` (уже непрозрачный) получил явный `stroke-opacity: 1` — + без визуального эффекта в покое, что и наблюдается (`rest.picked.stroke = + ["rgb(255, 193, 77)", 1]`). + +Побочный эффект, который автор сам назвал в отчёте и я подтвердил чтением: +у `.room.overlay:hover`/`.room.yard:hover` (наведение на незакрашенную +комнату) теперь обе половины (`stroke` и `stroke-opacity`) идут от 0 к 1 +вместо одной — начало затухания визуально чуть мягче, монотонность +сохраняется, перелёта нет. Не находка: раскрыто в отчёте автора, не меняет +контракт, не заявлено как AC. + +### Проверка «одно число — один источник» + +Числа 0.14 / 0.06 / 0.55 / 0.25 (видимые альфы `yard`/`outlined`/`picked`) +встречаются только в `src/styles/plan.styles.ts` — их единственный источник. +Поиском (`grep` по RGB-триплетам `75,140,90` / `62,166,255` / `255,193,77` +вне `plan.styles.ts`) второго места с тем же числом не нашлось; совпадение в +`src/styles/devices.styles.ts:379` (`rgba(62, 166, 255, 0.22)`) — другой +компонент (устройство), другое число (0.22), не дублирует величину задачи. +`demo/smoke_merge_highlight.mjs` сверяет только подстроку цвета +(`includes('255, 193, 77')`), а не альфу — совместимость с переходом на +`rgb(...)` + отдельный `fill-opacity` подтверждена прогоном CI (шард 1: +смок зелёный). + +## Что проверено и корректно + +- Диапазон диффа `origin/dev...HEAD` — ровно заявленное в ТЗ: `.room.overlay`, + `.yard`, `.outlined`, `.picked`; `.styled` и переменные `--room-*` не + тронуты (построчная сверка). +- `houseplan-space-card` подтверждённо берёт те же стили без дублирования + правил. +- Трейлеры `Issue: #746`, `User-Visible: yes` на коммите; оба CHANGELOG + правлены в том же коммите, текст совпадает с AC3 issue дословно (RU и EN). +- `docs/STYLING-HOOKS.md` получил абзац, фиксирующий новый инвариант как + контракт — ссылка на §3.3 «Everything generated» для самих величин + корректна (значения остаются негарантированными, инвариант записи — + гарантирован). +- Смок умеет падать именно на дефекте issue (см. раздел выше) — не ложно + положительный. +- CI на точном SHA материала: Validate, Golden, вся матрица смоков (включая + новый) — зелёные. +- Метки issue соответствуют треку и объёму: `track:show`, `ci:golden`, + `S7-code-review`. +- Риски (невидимость для правил card-mod без явной `*-opacity`, более мягкое + начало hover-замирания) названы автором и подтверждены разбором, задокументированы + в CHANGELOG/STYLING-HOOKS — не висящие Medium. + +## Находки + +Нет. High: 0, Medium: 0 (ни в скоупе, ни вне скоупа). + +## Вердикт + +Зелёный. AC1 и AC2 доказаны автотестом, тест проверен на способность +падать лично (red на отменённом патче с теми же числами, что в issue), +AC3 (гейт/CHANGELOG) выполнен. Побочных дефектов не найдено, риски раскрыты +и задокументированы. + +--- + + + +## Материал раунда + +- Ветка: `issue/746-room-fill-alpha`, коммит `aa0b60e75498` — ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. +- Дерево материала: `0d71ae29c68313fe4be018c5026ec20ccb600cc8` + ``` + git log --all --format='%H %T' | grep 0d71ae29c683 + ``` +- Тело issue: `e2d6161bb012959bb539ad49b1ed92bdc86fdc4cb703235caab7bca18258dc38` +- Вердикт конвейера: `green` · High 0 · маршрут `fix` +