diff --git a/docs/specs/266-split-styles.md b/docs/specs/266-split-styles.md new file mode 100644 index 00000000..c402f287 --- /dev/null +++ b/docs/specs/266-split-styles.md @@ -0,0 +1,90 @@ +# ТЗ #266 — Расщепить styles.ts на файлы поверхностей + +Issue: https://github.com/Matysh/houseplan-card/issues/266 +Статус: ревизия 1. +База: #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` остаётся единственной точкой входа: + +```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 Инварианты + +1. **Пиксели не меняются.** `npm run golden:verify` — все 129 сцен зелёные **без переприёмки эталонов**, после каждого слайса и в финале. Это главный и объективный критерий. +2. **Без дубликатов.** Юнит: множества селекторов пяти файлов попарно не пересекаются (парсинг заголовков правил; допущенные исключения перечисляются в тесте явно с причиной, ожидаемо — пустой список). +3. **Порядок склейки фиксирован.** Юнит: `cardStyles` — массив ровно из пяти элементов в порядке §1.2 (сравнение ссылок на импортированные константы). +4. **Бандл не растёт больше шума.** Сравнение размера `dist/houseplan-card.js` до/после: дельта ≤ 1 КБ (склейка тех же строк). +5. **Refactor-only.** Ни один селектор и ни одно объявление не добавляется, не удаляется и не редактируется — только перенос. Проверка ревьюером: нормализованное множество правил (селектор → текст объявлений) до и после совпадает; авторский инструмент сверки прикладывается к ветке (`scripts/dev/styles-diff.mjs`, класс C, удаляется или остаётся по решению ревью). + +### 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. Риски + +1. **Скрытая зависимость каскада** — два правила разных зон с одинаковой специфичностью на один элемент: переупорядочивание меняет победителя. Ловится golden-набором (129 сцен, все поверхности) и смоками; при обнаружении правило-нарушитель уезжает в `base` на прежнюю относительную позицию с комментарием. +2. **Тихая правка при переносе** (опечатка, потерянная строка) — ловится инструментом сверки §1.3.5 и golden. +3. **Рост бандла** — инвариант 4. + +## 5. Release-артефакты + +CHANGELOG не трогается (User-Visible: no, пиксели не меняются). docs/ARCHITECTURE.md — структура стилей. Fingerprint скриншотов обновится (src/** меняется), кадры прежние. + +## 6. AC + +1. `src/styles.ts` ≤ 40 строк: только импорты, комментарий о порядке каскада и `export const cardStyles`. Доказательство: ревью кода + юнит §1.3.3. +2. Пять файлов `src/styles/*.styles.ts`, каждый ≤ 1 200 строк. Доказательство: ревью кода. +3. Golden: 129/129 зелёные, эталоны байтово не менялись (`git status` чист по `demo/golden/baselines/`). Доказательство: `golden`-прогон CI + ревью диффа. +4. Юнит непересечения селекторов пяти файлов — зелёный, список исключений пуст (или обоснован построчно). Доказательство: `unit`. +5. Юнит порядка склейки §1.3.3 — зелёный. Доказательство: `unit`. +6. Нормализованное множество правил до/после идентично (инструмент §1.3.5, отчёт в ветке/комментарии). Доказательство: ревью кода по отчёту. +7. Дельта размера бандла ≤ 1 КБ. Доказательство: числа в хендоффе, перепроверяются ревьюером. +8. `npm test`, `build` + сверка трёх бандлов, `check-docs` — зелёные. Доказательство: CI. + +## 7. План тестов + +Юниты: непересечение селекторов, порядок и состав сборщика. Инструмент сверки правил (норм. множество) — прогон до/после на каждом слайсе. Golden: полный verify без переприёмки. Мутационный гейт: новых мутантов нет — refactor-only без новой логики; существующие мутанты остаются зелёными (их якоря вне styles.ts). + +## 8. Откат + +Revert серии коммитов слайсов; внешний контракт `cardStyles` не менялся ни в один момент.