Тема
Решения брейншторма (ТЗ)
Статус: зафиксировано (сессия брейншторма закрыта, 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 нет |
| 11 | RBAC | Filament Shield + spatie/permission |
| 12 | Флит-дашборд | После нескольких клиентов |
Что дальше
Все архитектурные вопросы для ТЗ закрыты, и спеки фазы 0 написаны (контракты блока, модуля, темы, WidgetRegistry, три оси, API + фундамент: модель данных, движок полей, стек). См. дорожную карту и обзор спек в README. Следующий шаг — фаза 1 (реализация ядра-минимума): появится первый клиентский заказ (Q1) → старт кода. Незакрытые уточнения «до кода» — Открытые детали реализации.