Skip to content

Решения брейншторма (ТЗ)

Статус: зафиксировано (сессия брейншторма закрыта, 12 вопросов). Каждое решение — с мотивировкой и последствиями для архитектуры. Изменение решения → правка здесь + в затронутых разделах, с датой и обоснованием.

Как читать

Каждый блок — вопрос, выбор и почему. «Последствия» — что решение меняет в коде и других разделах. Ссылки ведут в разделы, где решение уже отражено.

Ранее зафиксировано (контекст)

Не переоткрываем:

  • Аудитория: студия + клиенты ведут контент сами.
  • Фронт: публичная часть — Blade + токены + острова; админка — Filament; кабинеты — Inertia SSR рядом. React-темы на публичной части отклонены (SEO критичен). См. фронт-архитектуру.
  • Дистрибуция: приватные composer-пакеты + Satis; обновления через composer, не свой транспорт. См. центр обновлений.
  • Расширяемость: открытый BlockRegistry + события + фильтры + аналог /local. См. расширяемость.
  • drag&drop блоков: нужен, со строгими схемами.

Блок 1 — объём ядра

Q5. Произвольные типы контента (аналог инфоблоков) — ✅ ДА, в ядре

Решение: пользовательские типы контента с произвольными полями (аналог инфоблоков Битрикс / ACF WordPress) — в ядре с 1.0. Клиент может завести свою сущность («вакансии», «объекты недвижимости», «сертификаты») с набором полей без участия разработчика.

Техническое уточнение (важно): реализуем не классический EAV, а **JSONB-колонку

  • GIN-индексы** в PostgreSQL. Даёт гибкость инфоблоков без деградации SEO и производительности, которой страдает EAV (десятки JOIN на выборку одной записи). Поля с бизнес-смыслом, по которым идёт фильтрация/сортировка/URL, при этом выносятся в generated-колонки поверх JSONB (functional/partial индексы) — как описано в миграциях.

Почему так: классический EAV — главный антипаттерн производительности в Битрикс на больших каталогах. JSONB+GIN — современный компромисс: одна таблица, одна выборка, индексируемый поиск по произвольным ключам.

Последствия:

  • ядро получает подсистему «Типы контента» (CustomType + поля + Filament-CRUD-генератор);
  • решение по URL/SEO произвольных типов (роутинг, sitemap) — решено движком: route_pattern типа + публичные list/detail с whitelist-фильтрами/сортировкой и keyset (engine §7), API типа (engine §9);
  • граница с «блоком»: блок — единица страницы, тип контента — самостоятельная сущность со своим списком/детальной. Уточнить в глоссарии.

Детализация: content-types-engine (спека движка, 15.07.2026); решения сессии корпоративного MVP — docs/corporate-mvp/01-decisions.md в репозитории ядра (cms.rosveb.ru).

Q4. Границы виджета и блока — ✅ виджет = отдельная сущность

Решение: виджет — отдельная сущность, не частный случай блока. Блок живёт в потоке контента страницы; виджет размещается в областях (сайдбар, футер, шапка) и сквозной для многих страниц.

Почему так: разные жизненные циклы и места хранения. Блок привязан к странице (JSONB в составе её ревизии, см. Q7), виджет — к области и появляется на многих страницах разом. Слияние в одну сущность заставило бы тащить «область» в каждый блок и усложнило бы инвалидацию кеша.

Последствия:

  • ядро получает WidgetRegistry (аналог BlockRegistry, но для областей) и таблицу привязки «виджет → область → условие показа»;
  • инвалидация кеша областей — отдельно от постраничной;
  • глоссарий — убрать пометку «TBD».

Блок 2 — UX-редактор

Q9. Уровень drag&drop-редактора — ✅ средняя свобода

Решение: средняя свобода редактора:

  • переупорядочивание блоков — да;
  • вложенные блоки (колонки, секции) — да;
  • настройка отступов/цветов на уровне блока (à la Tilda) — нет (защита дизайна).

Почему так: цель студии — «клиент ведёт контент», а не «клиент рисует дизайн». Полная свобода стилей (Tilda) разрушает целостность темы и порождает вал поддержки («у меня съехало»). Вложенные секции/колонки дают достаточную гибкость компоновки, не отдавая клиенту типографику и палитру — они остаются в теме (design-tokens).

Последствия:

  • BlockRegistry-схема блока НЕ содержит полей «цвет/отступ/шрифт» — только контентные;
  • вид блока задаётся темой (theme.json + Blade-переопределение), не редактором;
  • вложенность требует блоков-контейнеров (Section/Columns) с child-блоками — заложить в контракт блока с фазы 0.

Q7. Черновики и версии страниц — ✅ в ядре 1.0

Решение: черновик + публикация и история версий страницы — в ядре 1.0. Контент-менеджер клиента видит предпросмотр черновика и может откатить изменения. Референс — Twill revisions.

Почему так: для аудитории «клиент правит контент сам» это не роскошь, а страховка: клиент неизбежно что-то сломает в тексте/блоках, и откат страницы — дешёвая защита от паники и обращений в поддержку. Ретрофитить версионирование позже дорого (меняет модель хранения страницы).

Последствия:

  • модель страницы: status (draft/published) + таблица ревизий (снимок блоков);
  • Filament: действия «Опубликовать», «Предпросмотр черновика», «Откатить к версии»;
  • page-cache отдаёт только published; предпросмотр черновика — в обход кеша по токену.

Блок 3 — контент-подсистемы

Q6. Мультиязычность контента — ✅ заготовка в ядре, модуль позже

Решение: заготовка в ядре (интерфейсы/поля/резолвер локали заложены), полноценный перевод контента — отдельный модуль позже. Две модели из проектов студии: city-based (мультигород, как universal/gulaev-dev) и spatie/laravel-translatable (как referendum) — обе поддерживаются модулем, ядро остаётся нейтральным.

Почему так: большинству корпоративных заказов мультиязычность не нужна на старте, но ретрофит «языкового измерения» в схему потом — болезненный. Компромисс: ядро знает про понятие «локаль/город» (резолвер + hreflang-хуки), но не тащит таблицы переводов, пока их не привезёт модуль под конкретный заказ.

Последствия:

  • ядро: интерфейс Translatable/HasLocale + middleware резолва локали (заглушка на одну локаль по умолчанию);
  • модуль мультиязычности привозит стратегию (city-based ИЛИ translatable) под заказ;
  • SEO-слой ядра готов к hreflang, но активируется модулем;
  • модель «как именно» (стратегии хранения, fallback, slug, кеш) — мультиязычность.

Q8. Конструктор форм — ✅ в ядре 1.0

Решение: визуальный конструктор форм (клиент собирает форму сам) — в ядре 1.0, не отдельный модуль.

Почему так: решение согласовано с Q5 (произвольные поля уже в ядре) и с аудиторией «клиент ведёт сам». Раз клиент собирает страницы и заводит типы контента, он логично захочет и формы собирать сам. Механика полей уже есть (из Q5) — конструктор форм ложится на неё, а не удваивает работу. Ядро и так даёт заявки/нотификации (Leads); конструктор — надстройка над тем же движком полей.

Последствия:

  • переиспользуем движок полей из Q5 (типы полей, валидация, рендер);
  • форма → submission → шина заявок (Leads) → нотификации — уже в ядре;
  • защита: rate-limit, CSRF, honeypot/капча — на уровне ядра, не на совести клиента.

Блок 4 — права и маркетплейс

Q11. Модель RBAC — ✅ Filament Shield + spatie/permission

Решение: роли/права — Filament Shield поверх spatie/permission (уже в стеке студии). Покрывает роли: разработчик студии / контент-менеджер клиента / редактор с ограниченным доступом. Своя многоуровневая модель (Twill Role/RoleGroup) — не нужна.

Почему так: spatie/permission — стандарт де-факто, Shield даёт готовую генерацию permission'ов по Filament-ресурсам. Изобретать свою ролевую модель под 3–4 роли — неоправданная сложность.

Последствия:

  • ядро зависит от filament-shield + spatie/laravel-permission;
  • контракт модуля обязывает модуль декларировать свои permission'ы (Shield их подхватывает) — заложить в контракт модуля;
  • политика «модуль ≠ ядро по правам»: модуль не может расширять права ядра молча.

Q3. Глубина маркетплейса — ✅ закрытый сейчас, архитектура под открытие

Решение: маркетплейс закрыт (только модули студии, внутренний каталог Satis). Но контракт модуля, модель прав и лицензирование проектируются так, чтобы открыть сторонним разработчикам позже без переделки.

Почему так: урок WordPress — 91% CVE приходится на плагины; открытый маркетплейс без ревью безопасности = угроза всему парку managed-сайтов. Ревью чужого кода силами одного разработчика — неподъёмно сейчас. Но заложить «плагин ≠ ядро по правам» и capability-слой надо с самого начала — ретрофит модели безопасности в открытый маркетплейс невозможен.

Последствия:

  • policy-слой и «песочница прав» модуля — в контракт модуля с фазы 0;
  • маркетплейс сторонних — фаза 5, не блокирует ядро;
  • см. безопасность и права.

Q10. Лицензирование скелета клиенту — ✅ skeleton да, Satis нет

Решение: при расторжении подписки на поддержку клиент получает код своего сайта (skeleton + тема + контент, база данных) — сайт продолжает работать. Доступ к Satis (обновления ядра и модулей) — отключается. Зафиксировать в договоре.

Почему так: честная модель — клиент владеет своим сайтом (не заложник SaaS), но обновления и модули студии — платная услуга. SaaS-модель «выключим сайт без подписки» отталкивает корпоративных клиентов; передача всего кода с обновлениями — отдаёт актив студии. Компромисс: рабочий сайт остаётся, поток обновлений — по подписке.

Последствия:

  • skeleton должен быть самодостаточным (composer-пакеты ядра/модулей вендорятся в поставку);
  • на стороне Satis — управление доступом по клиенту (токен/ключ);
  • договор: пункт о передаче кода и прекращении доступа к обновлениям.

Блок 5 — операционные

Q1. Первый потребитель ядра — ✅ пилот-копия universal (ревизия 14.07.2026)

Решение (ревизия): первым потребителем становится пилот-копия universal — контент и функционал universal.rosveb.ru пересобираются внутри новой CMS как отдельная инсталляция (skeleton + модули). Боевой universal при этом не трогается: он продолжает работать как есть и остаётся донором.

Исходное решение («ближайший новый клиентский заказ») дожидалось внешнего триггера; к моменту готовности инфраструктуры дистрибуции (волна 22) заказа нет, а парк перенесён на новый сервер и universal доступен локально. Пилот на своём контенте даёт ту же обкатку боем (реальные страницы, услуги, SEO, мультигород), но без внешней зависимости.

Почему так: ядро без живого потребителя — долгострой «в стол»; universal — самый насыщенный сайт студии (блоки, услуги, гео) и одновременно эталон для сравнения до/после. YAGNI-давление сохраняется: строим только то, что нужно пилоту.

Последствия:

  • волна 23 = пилот universal: skeleton-инсталляция, импорт контента (ETL реляционных блоков → JSONB, деферен волны 13 разблокирован — донор на этой машине), тема, услуги/SEO; commerce-модули в пилот не входят (фаза 3);
  • боевой universal живёт параллельно до готовности пилота; переключение домена — отдельное решение после приёмки;
  • первый внешний клиентский заказ, когда появится, становится вторым потребителем — на уже обкатанном ядре.

Q2. Имя и репозиторий — ✅ своя инфра (Gitea/GitLab)

Решение: монорепо на self-hosted git студии (Gitea или GitLab), Satis — там же. Структура: packages/core, packages/testing, skeleton/, базовая тема. Рабочее имя — уточняется (v1 был «Rosveb CMS»).

Почему так: полный контроль над кодом и дистрибуцией, нет внешней зависимости для приватных пакетов и обновлений парка. Согласуется с инфраструктурой студии (Caddy, watchdog, ISPmanager) — всё уже self-hosted. Цена — обслуживание (бэкапы git+Satis, uptime) ложится на студию; учесть в watchdog/бэкап-процедурах.

Последствия:

  • поднять Gitea/GitLab + Satis на инфре студии (фаза 1);
  • бэкап git-репо и Satis — в общий бэкап-процесс (proxy backup и т.п.);
  • CI (тесты пакетов, сборка Satis) — на self-hosted раннере.

Q12. Флит-дашборд — ✅ после нескольких клиентов

Решение: телеметрия парка (версии, волны обновлений, контроль бэкапов) — после нескольких клиентов. Пока 1–2 сайта — обновление вручную по CLI (cms:upgrade).

Почему так: YAGNI. Дашборд парка окупается, когда ручной обход сайтов становится болью (5+ сайтов). На одном-двух — CLI и proxy dns watch-подобный мониторинг достаточны. Строить телеметрию раньше — преждевременная оптимизация.

Последствия:

  • фаза 2 (центр обновлений) даёт CLI cms:upgrade с полной процедурой — этого хватает на старте;
  • флит-дашборд — фаза 4, развитие паттерна панели лендингов и proxy dns watch;
  • формат телеметрии (что каждый сайт репортит) заложить в центр обновлений заранее, даже если дашборд позже.

Сводка решений

#ВопросРешение
1Первый потребительНовый клиентский сайт
2Имя/репозиторийСвоя инфра (Gitea/GitLab) + Satis
3МаркетплейсЗакрыт сейчас, архитектура под открытие
4Виджет vs блокВиджет — отдельная сущность
5Произвольные типы контентаДа, в ядре (через JSONB+GIN, не EAV)
6МультиязычностьЗаготовка в ядре, модуль позже
7Черновики/версииВ ядре 1.0
8Конструктор формВ ядре 1.0
9Уровень drag&dropСредняя свобода (без стилей на уровне блока)
10Лицензия клиентуSkeleton да, Satis нет
11RBACFilament Shield + spatie/permission
12Флит-дашбордПосле нескольких клиентов

Что дальше

Все архитектурные вопросы для ТЗ закрыты, и спеки фазы 0 написаны (контракты блока, модуля, темы, WidgetRegistry, три оси, API + фундамент: модель данных, движок полей, стек). См. дорожную карту и обзор спек в README. Следующий шаг — фаза 1 (реализация ядра-минимума): появится первый клиентский заказ (Q1) → старт кода. Незакрытые уточнения «до кода» — Открытые детали реализации.

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