Skip to content

Фаза 0 — Контракт темы

Статус: спека фазы 0. Что такое тема, как она разрешает шаблоны, откуда берёт токены и как принимает дизайн из claude.ai/design без потери SEO. Стек темы (Blade + Tailwind 4 + Vite + острова) — канонический стек, зона 1. Опирается на расширяемость → каскад тем и глоссарий → тема.

Что такое тема

Тема — приватный composer-пакет визуального слоя: Blade-переопределения блоков/виджетов

  • theme.json (дизайн-токены) + Vite-сборка (+ React-острова). Определяет, как выглядят блоки, не меняя их данные. Темы переключаются — контент остаётся. Смена темы = смена пакета, а не правка контента.

Тема не содержит контентной логики, не создаёт таблиц, не регистрирует роутов бизнес- уровня. Её единственная ответственность — представление. Всё поведение — в ядре/модулях.

Структура пакета темы

cms-theme-corporate/
├── composer.json              # extra.cms.theme (манифест темы)
├── theme.json                 # дизайн-токены (единственный источник вида)
├── resources/
│   ├── views/
│   │   ├── blocks/            # переопределения блоков (hero.blade.php, …)
│   │   ├── widgets/           # переопределения виджетов
│   │   ├── layouts/          # app.blade.php (header/footer/meta)
│   │   └── components/        # компоненты темы (кнопка, карточка)
│   ├── css/                   # входной Tailwind (@import токенов из theme.json)
│   ├── js/                    # entry Vite + корни React-островов
│   └── islands/               # React-компоненты островов (если есть)
├── content/                   # переносимый демо-контент темы (страницы-примеры)
├── public/                    # скомпилированные ассеты (Vite build)
└── vite.config.js             # сборка ассетов темы

Манифест темы

jsonc
// composer.json → extra.cms.theme
{
  "extra": {
    "cms": {
      "theme": {
        "name": "corporate",
        "parent": null,                    // родительская тема (наследование каскадом)
        "minimum_core_version": "2.0.0",
        "supports_blocks": ["*"],          // какие типы блоков тема умеет рисовать
        "areas": ["header", "sidebar", "footer"],  // области виджетов, которые тема рендерит
        "tailwind": 4,
        "has_islands": true,               // есть ли React-острова
        "demo_content": "content/",        // папка переносимого демо-контента
        "csp_sources": {                   // внешние источники темы для CSP ядра
          "font-src": ["https://fonts.gstatic.com"]   // (ревизия ядра №2: фильтр security.csp)
        }
      }
    }
  }
}

supports_blocks — честная декларация: если блок нового модуля не покрыт темой, ядро рисует его нейтральным fallback ядра (каскад ниже), а не ломает страницу.

areas — декларация областей виджетов (WidgetRegistry): ядро валидирует, что виджет размещают только в области, которую тема реально рендерит (<x-cms-area>). Смена темы на тему без области — виджеты области не рендерятся (деградация, не падение), админка предупреждает.

Каскад разрешения шаблона

Blade-шаблон блока/виджета/страницы резолвится по цепочке приоритетов через множественные view-namespace:

проект (app/Local)  →  активная тема  →  родительская тема  →  нейтральный fallback ядра
  • проект (app/Local/views/) — точечная кастомизация конкретного сайта, переживает обновление темы;
  • активная тема — основной вид;
  • родительская тема — child-theme наследует родителя, переопределяя только нужное (WordPress child themes / October);
  • fallback ядра — нейтральный шаблон блока: гарантия, что новый блок отрисуется хоть как-то, даже если тема про него не знает.

Гарантия: рендер никогда не падает из-за отсутствия шаблона в теме — цепочка всегда заканчивается fallback'ом ядра. Согласуется с barrier-рендером блоков.

Template suggestions (специфичность)

В духе Drupal — от специфичного к общему, ядро пробует по очереди:

blocks/hero--variant-accent.blade.php   →  вариант оформления (color-variant поля)
blocks/hero--page-home.blade.php        →  контекст страницы
blocks/hero.blade.php                   →  базовый шаблон блока
(fallback ядра)

Тема реализует ровно те suggestions, что нужны; остальное падает на базовый шаблон. Даёт тонкую настройку вида без разрастания условий в одном шаблоне.

Дизайн-токены: theme.json → Tailwind + CSS-переменные

theme.jsonединственный источник вида (цвета, шрифты, радиусы, отступы-шкала, тени). Из него генерируется:

theme.json
  ├── CSS custom properties (:root { --color-accent: … })   → рантайм-переключение
  ├── Tailwind-пресет (theme.extend)                        → классы садятся из claude.ai/design
  └── общий конфиг для трёх зон                              → тот же токен в Blade/Filament-акценте/React-кабинете
jsonc
// theme.json (фрагмент)
{
  "colors":  { "accent": "#0a5", "ink": "#111", "surface": "#fff" },
  "fonts":   { "sans": "Inter, sans-serif", "head": "Manrope, sans-serif" },
  "radius":  { "sm": "4px", "md": "8px", "lg": "16px" },
  "spacing": { "section": "clamp(3rem, 6vw, 6rem)" }
}

Стили блока — только здесь, не в схеме блока (Q9): контент-менеджер не задаёт цвет/шрифт/отступ, их задаёт тема через токены. Блок может выбрать вариант оформления (color-variant поле → light/dark/accent), но сами варианты рисует дизайнер в теме. Это и есть техническая реализация «средней свободы».

Приём дизайна из claude.ai/design

Выход дизайна — React + Tailwind. На публичной части (зона 1) React в рантайме не нужен — точность достигается иначе (фронт-архитектура):

ЧтоКонвейер
Статический блокJSX → Blade-компонент: разметка и Tailwind-классы 1:1 (механически, задача для Claude Code)
Интерактивный блоккорень React-острова без переделки (React 19)
ТокеныJSX-Tailwind-конфиг → общий theme.json → пресет
Сверкагалерея блоков /_gallery: каждый блок на демо-данных, скриншот-сверка с макетом

Правила посадки:

  • конвертировать JSX→Blade сразу, не «временно оставим React для статики» (иначе статический блок тащит гидрацию зря);
  • просить дизайн поблочно (hero, карточка, форма), не страницей — чище ложится;
  • иконки — Blade-icons/SVG-компоненты, не инлайн-копипаста (blade spec);
  • вид определяется классами, а не тем, Blade это рендерит или React — потому Blade даёт ту же точность без рантайма.

React-острова темы

Тема поставляет корни островов в resources/islands/. Правила (канонический стек):

  • остров — только блоку, которому нужно состояние (калькулятор, живой фильтр, чат);
  • табы/аккордеоны/анимации — CSS/Alpine, не React;
  • 3+ острова на странице, знающих друг о друге → это уже приложение → зона 3 (Inertia);
  • остров получает server-rendered HTML + точечную гидрацию (блок с interactive(true) в BlockRegistry); без флага React не подключается.

Демо-контент темы

Тема привозит стартовый контент, чтобы клиент не начинал с белого листа (модель данных):

  • content/ — переносимый формат (YAML/JSON на сущность), демо-страницы/виджеты/настройки;
  • при активации темы — cms:content:import из content/, идемпотентно, по стабильным ключам (slug/key), не по id;
  • демо несёт _v блоков — импорт применяет data-миграцию до актуальной версии.

Переключение и совместимость

  • смена активной темы (cms:theme:use corporate) меняет только слой представления; контент и данные не трогаются;
  • тема декларирует minimum_core_version и supports_blockspreflight предупреждает, если тема не покрывает используемые блоки или требует более новое ядро;
  • обновление темы — через центр обновлений как обычный пакет; app/Local-переопределения проекта переживают обновление темы (верх каскада).

Управление темами из админки

Страница «Темы» в Filament (контент-менеджеру и студии, по правам):

  • галерея установленных тем: скриншот (из demo_content/манифеста), версия, статус совместимости (supports_blocks/areas против фактически используемых);
  • превью до переключения — подписанная токен-ссылка рендерит сайт в кандидат-теме только для держателя ссылки (тот же механизм, что preview черновика), прод не затронут;
  • «Активировать» — та же транзакция, что cms:theme:use: preflight (блоки/области/ версия ядра) → view:clear → пересборка манифеста резолва (ниже) → инвалидация page-cache → событие ThemeChanged (прогрев, CDN-purge слушают его);
  • установка новой темы — через центр обновлений (composer-пакет из Satis), не upload zip: тот же пайплайн preflight/бэкап/откат, что у модуля.

Кеш резолва шаблонов

Каскад проект → тема → parent → fallback ядра не сканирует файловую систему на каждый рендер: при активации темы/деплое компилируется манифест резолва — карта «view-имя (+suggestion) → конкретный путь». Рендер блока = один lookup по карте.

  • манифест пересобирается на cms:theme:use, деплое и обновлении темы;
  • в APP_DEBUG резолв живой (правки шаблонов видны сразу), в проде — только манифест;
  • cms:theme:resolve {view} — отладка: показывает всю цепочку кандидатов и кто выиграл (аналог cms:cache:inspect из производительности);
  • это же «безопасно»: тема обращается к блокам/виджетам только через каскад и props — прямых вызовов моделей/БД в шаблонах нет (контрактный тест темы).

Контрактные тесты темы (cms-testing)

  1. манифест валиден; parent (если есть) разрешим;
  2. для каждого блока из supports_blocks шаблон резолвится (или явно падает на fallback);
  3. theme.json валиден → генерирует корректный Tailwind-пресет + CSS-переменные;
  4. Vite-сборка проходит; острова (если has_islands) собираются;
  5. демо-контент content/ импортируется идемпотентно по стабильным ключам;
  6. вывод шаблонов — только ; {!! !!} вне whitelist SEO-полей — красный тест;
  7. каждая область из areas присутствует в layout (<x-cms-area>); демо-контент не ссылается на необъявленные области.

Чеклист фазы 0 (контракт темы)

  • [ ] Структура пакета темы + манифест extra.cms.theme (включая areas).
  • [ ] Каскад разрешения шаблона (проект→тема→parent→fallback ядра) + гарантия fallback.
  • [ ] Template suggestions (специфичность).
  • [ ] theme.json → Tailwind-пресет + CSS-переменные + общий конфиг трёх зон.
  • [ ] Стили только в теме, не в схеме блока (Q9); варианты через color-variant.
  • [ ] Конвейер JSX→Blade + галерея блоков для сверки.
  • [ ] React-острова темы с дисциплиной островов.
  • [ ] Демо-контент content/ — переносимый формат, идемпотентный импорт.
  • [ ] Страница «Темы» в админке: галерея, превью по токену, активация с preflight.
  • [ ] Манифест резолва шаблонов (компиляция каскада) + cms:theme:resolve для отладки.
  • [ ] Контрактные тесты темы.

Связи

Внутренняя база знаний студии