Medium жёлтого r2 (случайно перезапущенное ревью на смерженной задаче):
AC6a приведён к факту — 12 reduced-motion обёрток (10 исходных, две
смешанные разрезаны фиксом каскада 0b782ee по зонам), сохранность обёртки
каждого правила держит scope-ключ styles-diff; AC2 — принятые wc -l числа.
Issue: #266
User-Visible: no
13 KiB
ТЗ #266 — Расщепить styles.ts на файлы поверхностей
Issue: https://github.com/Matysh/houseplan-card/issues/266
Статус: ревизия 4 (пост-мерж, по Medium CODE-REVIEW-266-r2: числа AC2/AC6a приведены к принятому коду — фикс каскада 0b782ee разрезал две смешанные reduced-motion обёртки на по-зонные копии).
База: #34 (зонтик декомпозиции, этап 5), #241 (соразмерный выбор проверок), прецедент editor-secondary.styles.ts.
0. Сценарий
Разработчик правит стиль одной поверхности (диалог, тулбар, слой плана) — а дифф идёт через файл на 3 690 строк, через который идут все остальные правки. Конфликты механические, радиус поражения селектора неочевиден, ревью вынуждено гонять весь golden-набор.
До: один css\`на всё; 110 коммитов за 30 дней во второй по частоте файл. **После:** пять файлов поверхностей вsrc/styles/` + сборщик; правка диалога не трогает файл плана; golden-сцены можно выбирать по затронутой поверхности.
1. Контракт
1.1 Структура
Новый каталог src/styles/:
| файл | содержимое (селекторные кластеры) |
|---|---|
base.styles.ts |
:host, CSS-переменные, resets, общие утилиты, @media общего назначения |
plan.styles.ts |
сцена плана: .stage*, стены/оси/снап (.seg, .vertex, .pathline, .wallbody*, .plan-snap-*, .hidden-wall-*, .griddot, .physical-*), декор-слой, .compass, .measurelabel, .room*, .bdframe/.dtframe |
devices.styles.ts |
.dev*, .device-*, маркеры, .vac*, badges, device-pulse |
chrome.styles.ts |
тулбары и рамки редактора: .editbar, .tab, .modetab, .decorbar, .editorchrome, tray |
dialogs.styles.ts |
диалоги/формы/меню: .btn, .menu, .rrow, .colorrow, .oplock, .optimize-details, .recoveryoverlay, .savedplan, .habindingbanner, баннеры |
Точная принадлежность каждого правила решается при переносе по владельцу селектора; правило, обслуживающее две поверхности, уходит в base с комментарием — дубликат запрещён (§1.3).
1.2 Сборщик и внешний контракт
src/styles.ts остаётся единственной точкой входа:
export const cardStyles: CSSResultGroup = [
baseStyles, planStyles, devicesStyles, chromeStyles, dialogsStyles,
];
Порядок склейки — часть контракта каскада, фиксируется комментарием в сборщике и юнитом (§3). Потребители (houseplan-card, space-card, hp-device-preview) не меняются: Lit разворачивает вложенные CSSResultGroup. editor-secondary.styles.ts остаётся как есть (подключается после cardStyles).
1.3 Инварианты
- Пиксели не меняются.
npm run golden:verify— все 129 сцен зелёные без переприёмки эталонов, после каждого слайса и в финале. Это главный и объективный критерий. - Без дубликатов. Юнит: множества селекторов пяти файлов попарно не пересекаются (парсинг заголовков правил; допущенные исключения перечисляются в тесте явно с причиной, ожидаемо — пустой список).
- Порядок склейки фиксирован. Юнит:
cardStyles— массив ровно из пяти элементов в порядке §1.2 (сравнение ссылок на импортированные константы). - Бандл не растёт больше шума. Сравнение размера
dist/houseplan-card.jsдо/после: дельта ≤ 1 КБ (склейка тех же строк). - Refactor-only. Ни один селектор и ни одно объявление не добавляется, не удаляется и не редактируется — только перенос. Проверка ревьюером: нормализованное множество правил до и после совпадает, где ключ правила — полный путь вложенности (
@media/@supports/@keyframes-обёртки) + селектор, значение — отсортированные декларации. Потеря или смена медиа-обёртки при переносе меняет ключ и краснит сверку — ровно сценарий отказа из SPEC-REVIEW-266-r1 (golden не эмулируетforced-colorsи снимает всё подreducedMotion: 'reduce'). Инструмент:scripts/dev/styles-diff.mjs(класс B, остаётся в репозитории). - Медиа-обёртки под прямым прогоном. Слепые зоны golden закрываются смоками:
smoke_plan_snap_overlay.mjs(блокforced-colorsу.plan-snap-line/.hidden-wall-line) иsmoke_preloader.mjs(prefers-reduced-motionboot-pulse) обязательны на слайсахplanиbase. Блок.iso-*подforced-colorsне покрыт ни одним прогоном — его перенос дополнительно фиксируется юнитом: в итоговой склейке присутствуют оба@media (forced-colors: active)блока с их правилами (проверка по данным инструмента сверки).
1.4 Порядок работ — слайсы
По одной поверхности за коммит, от изолированной к связной: 1) chrome, 2) dialogs, 3) devices, 4) plan, 5) base + финальная уборка styles.ts до чистого сборщика. После каждого слайса локально: tsc, npm test, golden:verify без переприёмки.
2. Скоуп и не-скоуп
Скоуп: перенос правил из src/styles.ts в src/styles/*.styles.ts, сборщик, юниты инвариантов, сверочный инструмент, docs/ARCHITECTURE.md (абзац о структуре стилей).
Не-скоуп: изменение любых правил CSS; editor-secondary.styles.ts; scoped-стили других компонентов; тема/токены; порядок подключения в потребителях; #34-этапы 1–4.
3. UX, данные, i18n, touch
Не затрагиваются: рендер обязан быть пиксельно идентичен (инвариант 1), слушателей/разметки/строк нет.
4. Риски
- Скрытая зависимость каскада — два правила разных зон с одинаковой специфичностью на один элемент: переупорядочивание меняет победителя. Ловится golden-набором (129 сцен, все поверхности) и смоками; при обнаружении правило-нарушитель уезжает в
baseна прежнюю относительную позицию с комментарием. - Тихая правка при переносе (опечатка, потерянная строка) — ловится инструментом сверки §1.3.5 и golden.
- Рост бандла — инвариант 4.
5. Release-артефакты
CHANGELOG не трогается (User-Visible: no, пиксели не меняются). docs/ARCHITECTURE.md — структура стилей. Fingerprint скриншотов обновится (src/** меняется), кадры прежние.
6. AC
src/styles.ts≤ 40 строк: только импорты, комментарий о порядке каскада иexport const cardStyles. Доказательство: ревью кода + юнит §1.3.3.- Пять файлов
src/styles/*.styles.ts; замер r1-прототипа (4 431 суммарно) не проходил собственный порог — источником роста были пустые строки-разделители генератора; после уплотнения и фикса каскада принятые числа: base 165, chrome 391, devices 559, dialogs 1 201, plan 1 396, сборщик 19 (числа сдвинулись фиксом0b782ee— реклассификация :host-гейтнутых групп и разрезка смешанных обёрток). Порог держится: каждый файл ≤ 1 650, сумма 3 731 ≤ исходник + 5 % (3 874). Доказательство: ревью кода (wc -l). - Golden: 129/129 зелёные, эталоны байтово не менялись (
git statusчист поdemo/golden/baselines/). Доказательство:golden-прогон CI + ревью диффа. - Юнит непересечения селекторов пяти файлов — зелёный, список исключений пуст (или обоснован построчно). Доказательство:
unit. - Юнит порядка склейки §1.3.3 — зелёный. Доказательство:
unit. - Нормализованное множество правил до/после идентично — ключ включает полный путь вложенности (§1.3.5); отчёт (
diffдвух JSON — пустой) в хендоффе, перепроверяется ревьюером. Доказательство: ревью кода по отчёту. 6a. Юнит присутствия медиа-обёрток: оба блока@media (forced-colors: active)и 12 обёрток@media (prefers-reduced-motion: reduce)в итоговой склейке — 10 исходных (строки 72…3537 исходника; в r1/r2 фигурировали «8» — неточность обоих раундов), из которых две смешанные по зонам разрезаны фиксом каскада на по-зонные копии; сохранность обёртки КАЖДОГО правила доказывает сверка scope-ключом (styles-diff.mjs, пустой diff). Доказательство:unit(test/styles-split.test.mjs, якорь 12 с комментарием). - Дельта размера бандла ≤ 1 КБ. Доказательство: числа в хендоффе, перепроверяются ревьюером.
npm test,build+ сверка трёх бандлов,check-docs— зелёные. Доказательство: CI.
7. План тестов
Юниты: непересечение селекторов, порядок и состав сборщика, присутствие медиа-обёрток (§6a). Инструмент сверки правил (scope-ключ) — прогон до/после на каждом слайсе. Смоки: smoke_plan_snap_overlay (forced-colors) и smoke_preloader (reduced-motion) обязательны на слайсах plan/base. Golden: полный verify без переприёмки. Мутационный гейт: новых мутантов нет — refactor-only без новой логики; существующие мутанты остаются зелёными (пять мутантных патчей адресуют src/styles.ts — их file/якоря обновляются на новые файлы поверхностей в коммитах соответствующих слайсов, юнит «якорь ровно один раз» держит реестр в актуальном состоянии).
8. Откат
Revert серии коммитов слайсов; внешний контракт cardStyles не менялся ни в один момент.