mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Medium жёлтого r2 (случайно перезапущенное ревью на смерженной задаче):
AC6a приведён к факту — 12 reduced-motion обёрток (10 исходных, две
смешанные разрезаны фиксом каскада 0b782ee по зонам), сохранность обёртки
каждого правила держит scope-ключ styles-diff; AC2 — принятые wc -l числа.
Issue: #266
User-Visible: no
93 lines
13 KiB
Markdown
93 lines
13 KiB
Markdown
# ТЗ #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` не менялся ни в один момент.
|