14 KiB
CODE-REVIEW-266-r1
Issue: #266 — «Рефакторинг 3/5: расщепить styles.ts — 3 648 строк в одном шаблоне css``»
Этап: code (PROCESS.md §2.7). Заход: r1 · блокирующих циклов израсходовано 0 из 4.
Материал: git log --oneline origin/dev..HEAD, git diff origin/dev...HEAD на SHA 0d62a48b (HEAD detached, ветка origin/issue/266-split-styles).
ТЗ: docs/specs/266-split-styles.md, ревизия 3 (SPEC-REVIEW-266-r3 — зелёный, High 0 / Medium 0).
Скоуп
Перенос содержимого src/styles.ts (3 690 строк, один экспорт cardStyles = css\...`) в пять файлов поверхностей src/styles/{base,plan,devices,chrome,dialogs}.styles.ts+ сборщик; юниты инвариантов; сверочный инструментscripts/dev/styles-diff.mjs; генератор слайсов scripts/dev/styles-split.mjs; правки контрактных тестов, греппящих CSS-исходник, на общий хелпер; переадресация 5 мутантных якорей; docs/ARCHITECTURE.md`. Refactor-only — ни один селектор/объявление не должны были измениться (инвариант §1.3.5 ТЗ).
Диапазон коммитов: cbdc2489 (инструменты) → 5 слайсов (c684f178 chrome, 7d094fd2 dialogs, bf52524d devices, 55f7126a plan, 0d9320fe base+сборщик) → 3a49b142/0d62a48b (docs, не код).
Изменённые файлы src/**: src/styles.ts, 5 новых src/styles/*.styles.ts — ровно то, что заявлено скоупом ТЗ. Продуктовый код (houseplan-card.ts, space-card.ts, hp-device-preview и т.д.) не тронут — потребители cardStyles действительно не менялись, как и обещало ТЗ §1.2.
Как проверялось
Полный разбор (не по дельте): это первый заход код-ревью для #266, дельты предыдущего код-раунда не существует. Все гейты ниже прогнаны лично на дереве 0d62a48b, не приняты на слово автора.
Прогнано:
npx tsc --noEmit— зелёный, без ошибок.npm test— 1319 тестов, 1318 pass / 0 fail / 1 skip (существовавший skip, не новый).npm run build(tsc --noEmit && rollup -c) — зелёный;node scripts/bundle-sync.mjs— три копии бандла (dist/houseplan-card.js,custom_components/houseplan/frontend/houseplan-card.js,demo/srv/assets/houseplan-card.js) байтово идентичны (md57f4754ba582a8ce4c29614a4c8c119e7),git statusпо этим путям чист после пересборки — значит закоммиченные копии уже соответствовали билду, дрейфа нет.- Размер бандла: origin/dev
dist/houseplan-card.js= 1 291 440 байт, HEAD = 1 291 458 байт → +18 байт. AC7 (≤1 КБ) выполнен с большим запасом; цифра автора (+18 байт) подтверждена независимо. node scripts/check-docs.mjs— зелёный («7 files, 10 external links»); нужен, так как diff трогаетsrc/**. Отдельно сверилdocs/images/screenshots.json:sourceFingerprint/sourceSha256изменились (ожидаемо — src изменился), но каждыйimageSha256в диффе — тот же самый, что наorigin/dev. Это независимое подтверждение «пиксели не изменились» на уровне артефакта документации, отдельное от golden.npm run golden:verify(129 сцен) — прогнан дважды (первый прогон черезtailобрезал заголовок лога, перезапустил без обрезки): 129/129 passed, 0 failed.git status --short demo/golden/baselines/— пусто, эталоны не переприняты. Это главный критерий ТЗ (§1.3.1) и он подтверждён исполнением, не докладом автора.- Сверочный инструмент
scripts/dev/styles-diff.mjsдо/после — прогнан лично наgit show origin/dev:src/styles.tsи на пяти новых файлах:diff before.json after.json→ пустой. Это самое сильное доступное доказательство refactor-only (нормализованное множество правил с ключом «полный путь вложенности@media/@supports+ селектор» побайтово совпало), сильнее, чем «ревью диффа» из AC6. grepчисла AC2 и AC6a лично:wc -lфайлов поверхностей = 248/381/540/1180/1359, сборщик 19, сумма 3727 ≤ 3690×1.05=3874.5, каждый файл ≤1650 — сходится.@media (forced-colors: active)— 2 блока (оба вplan.styles.ts, строки 314 и 1189),@media (prefers-reduced-motion: reduce)— 10 блоков в сумме по пяти файлам. Совпадает и с ТЗ, и сorigin/dev:src/styles.ts(те же 2/10 в исходнике).- Обязательные по ТЗ §1.3.6/§7 смоки на слайсах
plan/base:demo/smoke_plan_snap_overlay.mjs— OK, ключевая проверкаforcedColorsStayReadable: trueприсутствует и зелёная;demo/smoke_preloader.mjs— OK,reducedMotionStaticHouse: 'none'подтверждает, чтоanimation-nameдействительно гасится подprefers-reduced-motion: reduceпосле переноса. node scripts/smoke-select.mjs --base origin/dev --head HEAD: 6 файловsrc/**/ 8 символов на изменённых строках → 4 «прямых совпадения» (smoke_decor,smoke_furniture,smoke_render_parity,smoke_sun_rim). Проверил, откуда взялись совпадения:FURNITUREиRAY_FADE_MS— это текст внутри комментариев (docs/FURNITURE.md §3/§4/§6, «RAY_FADE_MS в src/sun.ts должен совпадать» — самsrc/sun.tsне тронут), а не код, использующий эти символы, — инструмент честно совпал по тексту строки, а не по семантике.cardStyles— реальное прямое совпадение (это и есть переименованный контракт сборщика), поэтомуsmoke_render_parity.mjsпрогнан отдельно — OK.smoke_decor/smoke_furniture/smoke_sun_rimне прогонял: golden уже покрывает decor/glow/sun пиксельно в 129 сценах (decor-over-opaque-hover-light,decor-over-glow-base-dark,lighting-glow-sun-dark,lighting-custom-glow-*и др. — все зелёные), а сами смоки тестируют JS-поведение (расчёт цвета/geometry furniture, тайминг sun-rim), которое в этом diff не менялось. Записываю решение явно: не прогонял, основание — false-positive по тексту комментария плюс независимое golden-покрытие той же поверхности.git diffвспомогательных файлов вручную:scripts/mutation-gate.mjs(5 якорей переадресованы на верные новые файлы: 2 →base.styles.ts—:host([data-pointer-hover]) .dev:hover/.dev:not(.unavail):hover; 3 →devices.styles.ts—.valtext,--dev-size,border-radius),scripts/fix-test-build.mjs(точка в имени модуляstyles/base.stylesтеперь не путается с расширением),tsconfig.test.json(новые файлы добавлены вinclude),test/styles-source.mjs(новый хелпер), 5 контрактных тестов переведены на него. Все эти файлы участвуют вnpm test, который прошёл целиком.docs/ARCHITECTURE.md— новый раздел «Styles (#266)» корректно описывает сборщик, порядок каскада, владение поверхностями и инструментарий; не расходится с кодом.- Трейлеры: во всех 11 коммитах диапазона —
Issue: #266,User-Visible: no. CHANGELOG (docs/CHANGELOG.md,docs/CHANGELOG.ru.md) не тронут — согласовано сUser-Visible: no(пиксели идентичны, внешний контрактcardStylesне менялся).
Не прогонял (и почему):
- Полный набор
demo/smoke_*.mjs(190 файлов) — задача не задевает всё дерево смоков, только CSS-каскад; выбор поsmoke-select.mjs+ два обязательных по ТЗ смока достаточны и соразмерны. npm run invariants(геометрические инварианты #254) — diff не трогает рёбра комнат, толщину,layout,marker.space,open_spansни в каком файле; это CSS-only рефакторинг, гейт неприменим.python -m pytest tests_backend -q—custom_components/**/*.pyне тронут.- Performance-профили — не названы в AC, чувствительные к перфу пути (рендер-цикл, resize) не изменены (только статические CSS-правила переставлены между файлами).
Находки
Нет ни одной находки уровня High или Medium. Реализация соответствует ТЗ ревизии 3 буквально, и главные риски ТЗ (§4: скрытая зависимость каскада, тихая правка при переносе, рост бандла) закрыты объективными инструментами, которые я прогнал лично, а не приняты со слов автора.
Low-наблюдение, не блокирует: сообщение слайса bf52524d («devices») говорит «npm test 1315/0», слайс 55f7126a («plan») тоже «npm test 1315/0», финальный слайс 0d9320fe не называет итоговое число тестов явно (просто «npm test 1318/0» в сводном комментарии владельца в issue) — небольшая небрежность в промежуточных числах коммит-сообщений (1315 на двух разных слайсах подряд, хотя между ними добавлялись файлы), но итоговое число проверено мной напрямую (npm test на HEAD → 1318/0) и совпадает с финальной сводкой владельца. Не требует правки — не влияет на приёмку.
Что проверено и корректно
- AC1 (
styles.ts≤ 40 строк) — 19 строк, только импорты/комментарий контракта/экспорт. ✓ - AC2 (пороги размеров файлов) — арифметика сходится на фактическом коде, не на прототипе. ✓
- AC3 (golden 129/129, эталоны не переприняты) — исполнено лично, зелёное,
git statusчист. ✓ - AC4 (непересечение селекторов, юнит) — единственное исключение (
\.dev:focus-visiblebase∩devices) обосновано в тесте и соответствует §1.1 ТЗ (кросс-поверхностная группа:host(...) .dev:hover, .dev:focus-visible— двум разным правилам с одинаковым текстом селектора на разных scope). ✓ - AC5 (порядок склейки сборщика, юнит) —
[baseStyles, planStyles, devicesStyles, chromeStyles, dialogsStyles], зафиксировано и тестом, и комментарием-контрактом вstyles.ts. ✓ - AC6/AC6a (refactor-only + медиа-обёртки) — подтверждено сильнейшим доступным способом: пустой diff сверочного инструмента до/после, посчитанный мной лично, плюс независимая проверка счётчиков 2 forced-colors / 10 reduced-motion. ✓
- AC7 (бандл ≤ 1 КБ) — +18 байт, три копии синхронны. ✓
- AC8 (
npm test/build/check-docsзелёные) — все три подтверждены исполнением. ✓ - Дисциплина «тест умеет падать»:
test/styles-split.test.mjs— тест непересечения содержит реальный списокallowed-исключений (изменение любого клэша ломаетassert.deepEqual), тест медиа-обёрток сравнивает точные числа (2/10) — оба способны упасть на регрессии, не тавтологичны. - Golden покрывает decor/glow/sun/junction/dialog поверхности напрямую пиксельно — независимое от styles-diff доказательство отсутствия визуальных регрессий.
- Процессное замечание из SPEC-REVIEW-266-r3 (код в ветке раньше статуса S4) уже заведено отдельным issue (#311, вне скоупа #266) — не переоткрываю здесь.
Вердикт
Зелёный. High 0, Medium 0. Все AC доказаны исполнением гейтов лично ревьюером (golden, styles-diff, tsc, npm test, build+bundle-sync, check-docs, два обязательных смока, smoke_render_parity), а не заявлены автором. Задача может закрываться.