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

13 KiB
Raw Permalink Blame History

ТЗ #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 Инварианты

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