Files
houseplan-card/docs/specs/266-split-styles.md
T
2026-08-26 00:27:21 +03:00

9.4 KiB

ТЗ #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 остаётся единственной точкой входа:

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 не менялся ни в один момент.