Files
houseplan-card/docs/specs/266-split-styles.md
Codex 9726f2dfdb docs: spec #266 revision 4 — post-merge numbers per CODE-REVIEW-266-r2
Medium жёлтого r2 (случайно перезапущенное ревью на смерженной задаче):
AC6a приведён к факту — 12 reduced-motion обёрток (10 исходных, две
смешанные разрезаны фиксом каскада 0b782ee по зонам), сохранность обёртки
каждого правила держит scope-ключ styles-diff; AC2 — принятые wc -l числа.

Issue: #266
User-Visible: no
2026-08-26 08:52:42 +03:00

93 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ #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` остаётся единственной точкой входа:
```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.** Ни один селектор и ни одно объявление не добавляется, не удаляется и не редактируется — только перенос. Проверка ревьюером: нормализованное множество правил до и после совпадает, где ключ правила — **полный путь вложенности** (`@media`/`@supports`/`@keyframes`-обёртки) + селектор, значение — отсортированные декларации. Потеря или смена медиа-обёртки при переносе меняет ключ и краснит сверку — ровно сценарий отказа из SPEC-REVIEW-266-r1 (golden не эмулирует `forced-colors` и снимает всё под `reducedMotion: 'reduce'`). Инструмент: `scripts/dev/styles-diff.mjs` (класс B, остаётся в репозитории).
6. **Медиа-обёртки под прямым прогоном.** Слепые зоны golden закрываются смоками: `smoke_plan_snap_overlay.mjs` (блок `forced-colors` у `.plan-snap-line`/`.hidden-wall-line`) и `smoke_preloader.mjs` (`prefers-reduced-motion` boot-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. Риски
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`; замер 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`).
3. Golden: 129/129 зелёные, эталоны байтово не менялись (`git status` чист по `demo/golden/baselines/`). Доказательство: `golden`-прогон CI + ревью диффа.
4. Юнит непересечения селекторов пяти файлов — зелёный, список исключений пуст (или обоснован построчно). Доказательство: `unit`.
5. Юнит порядка склейки §1.3.3 — зелёный. Доказательство: `unit`.
6. Нормализованное множество правил до/после идентично — ключ включает полный путь вложенности (§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 с комментарием).
7. Дельта размера бандла ≤ 1 КБ. Доказательство: числа в хендоффе, перепроверяются ревьюером.
8. `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` не менялся ни в один момент.