project.json
— манифест проекта
Статус: проектная спецификация (draft). Проект только начат — схема намеренно минимальна. Версионирование схемы пока
неявное
(v1): поле
schemaVersion
в файл не пишется, его отсутствие означает v1. Оно появится, только когда возникнет несовместимое изменение.
project.json
— декларативное описание REEEL-проекта и набора его
собираемых конфигураций
. Это build-time
источник истины
: из него редактор строит список «вариантов сборки», а build-worker понимает, что и как собирать.
Идентичность проекта — это его директория
; отдельного поля
name
у проекта нет.
Необязательный
title
— это ПОДПИСЬ, а не идентичность: как проект называют люди. Им ничего не адресуется, он не обязан быть уникальным и его можно менять в любой момент. Нужен там, где сказанное иначе теряется: человек просит «Список задач», каталог получает
task_list
, и слово не сохраняется нигде.
Принципы
-
Декларативность.
Манифест описывает
что
собирать, а не
как
. Конкретные шаги сборки остаются за
config_builder/make build. -
Источник истины.
Поля сборки (платформа, точка входа, плагины и т.д.) живут здесь, а не в глобальных
configs/. Направление развития — перенестиconfigs/<profile>/config.jsonв конфигурации проекта (см. ниже, миграция не завершена). -
Плоская и явная модель контента.
Точка входа (
entry) — это явный путь к файлу относительно корня проекта. Каталог этого файла становится корнем контента; все вложенные ссылки (AppRoute /home,import components/theme) резолвятся относительно него. Слоиplatform/design/commonв построении пути не участвуют . -
Минимализм.
Никаких полей «на вырост». Версия схемы неявная (v1) и в файл не пишется;
nameу проекта нет (идентичность = директория).titleисключением не является: он появился не «на вырост», а потому что имя, названное человеком при создании, было некуда положить.
Схема верхнего уровня
{
"version": "1.0.0",
"entry": "reeel/main.reeel",
"configurations": [
{
"name": "reeel-vod_ios",
"target": "ios",
"settings": {
"bundleId": "su.flutter.reeel.vod",
"displayName": "Reeel VOD"
}
},
{
"name": "reeel-vod_web",
"target": "web",
"entry": "reeel/landing.reeel",
"settings": {
"displayName": "Reeel VOD"
}
}
]
}
| Поле | Тип | Описание |
|---|---|---|
title
|
string | Как проект называют люди — ПОДПИСЬ, не идентичность: ничем не адресуется, уникальности не требует, меняется свободно. Пустая строка равна отсутствию. Необязательное. |
version
|
string | Версия проекта (semver), общая для конфигураций. Необязательное. |
entry
|
string |
Точка входа по умолчанию для всех конфигураций — путь к
.reeel
-файлу относительно корня проекта (напр.
reeel/main.reeel
). По умолчанию
reeel/main.reeel
.
|
theme
|
object | string |
Файлы темы по умолчанию:
{ "light": "data/theme/light.json", "dark": "data/theme/dark.json" }
или одна строка (один файл на обе). Необязательное — без него файлы ищутся по конвенции
data/theme/{light,dark}.json
. Переопределяется в конфигурации.
|
locales
|
object | string |
Локализация по умолчанию:
{ "default": "ru" }
(фолбэк-локаль, когда системная недоступна) или одна строка-локаль. Список файлов локалей обнаруживается из
data/strings/*
. Переопределяется в конфигурации.
|
designWidth
|
number |
Логическая ширина, в которой свёрстан дизайн. Когда задана — весь UI раскладывается в этой ширине и равномерно масштабируется под экран (resolution-independent дизайн, напр. TV-вёрстка под 1920, обязанная выглядеть 1:1 независимо от разрешения). Без неё — адаптивная раскладка по логическому размеру устройства. Используется в превью редактора (перекрывает выбранное устройство) и при сборке зеркалится в
design_width
билд-конфига. Необязательное.
|
docs
|
array |
Пути (файлы или каталоги) с документацией проекта, которую читает движок знаний. По умолчанию
["docs"]
. Каталог даёт все
.md
внутри рекурсивно, файл — сам себя; шаблонов и масок нет. Несуществующий путь — не ошибка. Необязательное.
|
domains
|
array |
Собственные домены сайта — имена, на которых он отвечает помимо адреса по слагу (
reeel.dev
,
shop.acme.com
). См.
Собственные домены
. Необязательное.
|
git
|
object | Репозиторий проекта и правила работы с ним — см. Секция `git` . Появляется, когда редактор заводит в проекте git; секретов не содержит. Необязательное. |
configurations
|
array | Список собираемых конфигураций. Минимум одна. |
schemaVersionиnameнамеренно отсутствуют (см. «Принципы»). Если в файле встретитсяschemaVersion, инструмент его прочитает; при записи v1 не эмитится.
Секция
git
Редактор хранит версии проекта во встроенном git (ставить git на компьютер не нужно). Секция говорит, где репозиторий и по каким правилам с ним работать; её пишет редактор, но править руками можно.
"git": {
"remote": "https://github.com/acme/app.git",
"defaultBranch": "main",
"policy": {
"flow": "trunk",
"integration": "rebase",
"featurePrefix": "feature/",
"requireUpToDateBeforePush": true,
"autoCommitOnCloudPublish": false,
"protectMain": true,
"deleteBranchOnFinish": true,
"pushOnCommit": true
}
}
| Поле | Тип | Описание |
|---|---|---|
remote
|
string |
Адрес репозитория (
https://…
или
git@host:…
). Нужен, чтобы проект, скачанный из облака на другой компьютер, предложил подключиться к своему репозиторию.
Токены и ключи сюда не попадают никогда
: токен живёт в системном хранилище паролей, ключ — в
~/.reeel/ssh
. Необязательное.
|
defaultBranch
|
string |
Главная ветка. По умолчанию
main
.
|
policy.flow
|
string |
Схема веток. Сегодня единственная —
trunk
: одна главная ветка и короткие ветки-фичи от неё.
|
policy.integration
|
rebase
|
merge
|
squash
|
Как завершённая ветка попадает на главную:
rebase
— версии встают в линию поверх главной;
merge
— одним слиянием, ветка остаётся видна в истории;
squash
— одной версией (пока движок выполняет его как
merge
). По умолчанию
rebase
. Так же объединяется и то, что приносит «Обновить»:
rebase
или
merge
.
|
policy.featurePrefix
|
string |
С чего начинается имя ветки-фичи:
feature/login
. По умолчанию
feature/
. Ветка с таким префиксом получает кнопку «Завершить».
|
policy.requireUpToDateBeforePush
|
bool |
Отправка сначала сверяется с репозиторием и отказывает, пока в нём есть версии, которых нет здесь. По умолчанию
true
.
|
policy.autoCommitOnCloudPublish
|
bool |
Публикация в облако заодно сохраняет версию, если есть изменения. По умолчанию
false
.
|
policy.protectMain
|
bool |
Когда подключён репозиторий, версия прямо на главной ветке не сохраняется — редактор предлагает ветку. Без репозитория правило ничего не значит: одиночный проект не дёргают. По умолчанию
true
.
|
policy.deleteBranchOnFinish
|
bool |
Завершённая ветка удаляется — и здесь, и в репозитории, если в его копии нет версий, которых нет на этой машине. Выключите, если ветки в проекте живут дольше влития (их читают на ревью): тогда «Завершить» только переносит версии. По умолчанию
true
.
|
policy.pushOnCommit
|
bool |
«Сохранить версию» сразу отправляет её в репозиторий, если он подключён и в нём нет версий, которых нет здесь. Отказ (нет сети, истёк токен) версию не отменяет: окно говорит, что она сохранена, но осталась на этом компьютере, и «Отправить» появляется отдельной кнопкой. По умолчанию
true
.
|
Секция читается снисходительно: неизвестное значение — значение по умолчанию, а не ошибка. Пишется целиком, чтобы читающий файл видел правило, не зная умолчаний.
.git
в облачный снапшот не попадает (как и всё, что начинается с точки), и облако git не заменяет: облако доставляет интерфейс в приложение, git хранит историю и ветки.
Собственные домены (
domains
)
{
"domains": ["reeel.dev", "www.reeel.dev"],
"configurations": [
{ "name": "site_web", "target": "web", "settings": { "origin": "https://reeel.dev" } }
]
}
Это заявка, а не владение. Имя в списке само по себе не даёт ни маршрута, ни сертификата. Раздача каждый цикл заново выводит, законно ли имя, из DNS: в зоне домена должна стоять запись:
_reeel-verify.<имя> TXT reeel-verify=<id владельца проекта>
Нет записи, не та запись, запись убрали — имя просто не приезжает, и сайт живёт на своём адресе по слагу. Отдельной «отвязки» не существует: убрали запись или убрали строку из
domains[]
— на следующем цикле имени не станет. Поэтому один и тот же домен может стоять в
domains[]
у нескольких проектов: подтвердит его тот, у кого есть доступ к DNS.
Имя приводится к одному виду — нижний регистр, без завершающей точки, дубликаты отбрасываются: дальше значение сверяется с заголовком
Host
побайтово, и два написания одного имени не должны стать двумя сайтами.
Чего в списке быть не может:
| почему | |
|---|---|
*.example.com
|
заявка на целое поддерево требует другого доказательства и другого сертификата — перечислите имена |
https://example.com
|
здесь голое имя; URL — это
settings.origin
соседней конфигурации
|
example.com/page
,
example.com:8080
|
путь и порт частью имени хоста не бывают |
localhost
,
example
|
одна метка — это хост в чьей-то локальной сети, не сайт в интернете |
пример.рф
|
объявите форму
xn--…
: преобразование не угадывается, а ошибка публикует имя, ведущее в другое место
|
settings.origin
обязан быть среди объявленных имён.
Из него собираются
canonical
,
og:url
и
sitemap
; если он указывает на имя, которого сайту не давали, целый домен страниц канонизируется на чужой адрес. Проверка манифеста предупреждает об этом — но только когда домен объявлен: сайт на адресе по слагу тоже задаёт
origin
, и сравнивать там не с чем.
Он же — основной домен: остальные уходят на него редиректом.
Как только хост
origin
реально обслуживается (TXT подтверждён,
A
указывает на раздачу), запрос на любой другой подключённый домен получает
301
на тот же путь и ту же строку запроса основного. Доменов может быть сколько угодно — уходят все: только редирект передаёт основному имени вес ссылок, поставленных на
www.
или на старый адрес;
canonical
и
noindex
этого не делают.
| Куда пришёл запрос | Ответ |
|---|---|
хост
origin
|
200
|
| другой подключённый домен |
301
на
origin
+ путь + строка запроса,
Cache-Control: public, max-age=3600
|
адрес по слагу
<slug>.reeel.site
|
200
и
X-Robots-Tag: noindex
; с
"redirectSlug": true
— тоже
301
|
/.well-known/…
на любом имени
|
не редиректится никогда |
origin
не задан или его хост ещё не обслуживается
|
редиректов нет: все имена отвечают
200
|
settings.redirectSlug
включается отдельно, потому что адрес по слагу — вход на сайт до подключения домена и запасной после. Без
origin
настройка не делает ничего, и проверка манифеста об этом предупреждает.
Куда домен ведёт в DNS — запись
A
на адрес раздачи — говорит консоль проекта; сертификат выпускается сам при первом обращении по имени.
Схема одной конфигурации
| Поле | Тип / Значения | Описание |
|---|---|---|
name
|
string |
Уникальное имя варианта сборки (показывается в выпадающем списке редактора). Обязательное. Конвенция —
<имя_директории_проекта>_<target>
(напр.
reeel-vod_ios
); редактор/шаблоны проставляют его автоматически при создании.
|
target
|
ios
|
android
|
macos
|
windows
|
linux
|
web
|
tizen
| …
|
Целевая платформа сборки. Обязательное. Список расширяемый. |
entry
|
string |
Необязательный override точки входа для конкретной конфигурации. Если не задан — берётся top-level
entry
манифеста.
|
theme
|
object | string |
Необязательный override темы (формат как у top-level
theme
). Если не задан — берётся top-level
theme
, иначе обнаружение.
|
locales
|
object | string |
Необязательный override локализации (формат как у top-level
locales
). Если не задан — берётся top-level
locales
.
|
pages
|
object |
Только
kind: website
.
Адреса страниц:
{"o-klube": "about"}
— ключ — это URL, значение — имя
.reeel
-файла в
reeel/
. Без него адрес страницы — имя файла, каким его назвал автор. См.
Адреса страниц
.
|
settings
|
object | Платформо-специфичные обязательные/опциональные поля (см. таблицу-заготовку). |
Адреса страниц (
pages
)
Только для веб-конфигурации. Без этого поля URL страницы — это имя файла:
reeel/idx.reeel
живёт по
/idx
и никак иначе.
{ "name": "site_web", "target": "web", "kind": "website",
"pages": { "o-klube": "about", "nashi-partnery": "sponsors_2024" } }
Объявлено в конфигурации , а не на верхнем уровне: адрес — свойство того, как проект публикуется , а не самого проекта. Один и тот же файл бывает экраном приложения и страницей сайта, и URL есть только у второго.
Правила:
- Имя файла продолжает работать. Алиас добавляет адрес, а не отбирает — ничто, что уже ссылалось на страницу, не ломается.
-
Сайт рекламирует адрес, а не файл.
hreflang,canonicalи статический экспорт печатают алиас; при заходе по имени файла страница всё равно указывает канонический адрес. Иначе сайт публиковал бы URL, по которым сам не отвечает. - Ссылки в разметке можно писать на имя файла — в выдачу они пойдут по адресу, так что посетитель видит имя, выбранное сайтом.
- Два запрета, проверяемых при загрузке проекта: адрес не может совпадать с кодом локали (спрятал бы целый язык) и с именем другой страницы (спрятал бы её). Такая запись отбрасывается, а причина пишется в лог инстанса.
Точка входа (
entry
)
-
Явный путь к файлу
от корня проекта (напр.
reeel/main.reeel), а не имя файла + автопоиск по слоям. -
По умолчанию
reeel/main.reeelдля новых проектов. -
Задаётся на уровне манифеста (
entry) как дефолт для всех конфигураций и переопределяется в конкретной конфигурации (configuration.entry). -
Корень контента = каталог точки входа.
Для
entry: "reeel/main.reeel"корень контента —reeel/, стартовый view —main. Все маршруты и импорты внутри файлов резолвятся относительно этого корня;platform/design/commonв построении пути не участвуют.
Данные, тема и локализация (
data/
)
Данные оформления живут в каталоге
data/
рядом с
reeel/
:
data/theme/<mode>.json
наполняет ambient
$theme.*
,
data/strings/<locale>.arb
—
$strings.*
. Для сайтов (
kind: website
) там же лежит
data/web/
:
scripts/*.js
для элемента
Script
и
settings.scripts
,
html/*.html
для
HtmlEmbed
— синкается и отдаётся общим механизмом
/data/<path>
, отдельного правила нет.
Манифест может
явно
объявить файлы темы (
theme
) и фолбэк-локаль (
locales
) — на верхнем уровне как дефолт и с override в
configuration
(как
entry
). Если поля нет, файлы ищутся по конвенции (
data/theme/{light,dark}.json
,
data/strings/*
). Подробно —
Тема и локализация
и
структура проекта
.
Системные тема/язык.
Без своего переключателя проект следует за системой: тема —
system
(светлая/тёмная по настройке ОС), язык — лучшая из доступных локалей по приоритету ОС (фолбэк —
locales.default
). В редакторе превью симулирует системные настройки тогглом ☀️/☾ и селектором языка рядом с тогглом ориентации; на устройстве берётся реальная настройка ОС. Явный
theme.set(...)
/
strings.set(...)
в разметке перекрывает системный выбор.
Обязательные поля по платформам (
settings
)
Заготовка. Точный набор уточнят backend / worker при реализации сборок — помечайте как расширяемое , не закрепляйте лишнего раньше времени.
target
|
Ожидаемые поля
settings
(черновик)
|
|---|---|
ios
|
bundleId
,
displayName
, иконки, профиль подписи —
уточнит worker
|
android
|
applicationId
,
displayName
, иконки, keystore —
уточнит worker
|
macos
|
bundleId
,
displayName
, иконки, подпись —
уточнит worker
|
windows
|
appId
,
displayName
, иконки —
уточнит worker
|
linux
|
appId
,
displayName
, иконки —
уточнит worker
|
web
(PWA)
|
displayName
, base href, манифест PWA —
уточнит worker
|
tizen
|
appId
,
displayName
, профиль подписи Tizen —
уточнит worker
|
Поля
settings
по виду проекта (
kind
)
settings
— сырая карта внутри КОНФИГУРАЦИИ, и смысл её ключей задаёт
kind
, объявленный рядом. Пространств имён у ключей нет и не нужно: у конфигурации ровно один вид, поэтому настройки фильма и настройки приложения не встречаются в одной карте — они живут в разных конфигурациях одного проекта. Неизвестные ключи переживают загрузку и сохранение, так что чужое ничего не ломает.
Имена ключей выбираются так, чтобы читаться БЕЗ взгляда на
kind
:
frameSize
у фильма и (в будущем)
pageSize
у документа лучше, чем два разных
size
.
kind
|
Ключ | Тип | Значение |
|---|---|---|---|
app
|
bundleId
/
applicationId
|
string | Идентификатор установки; без него упакованная сборка отвергается |
app
|
displayName
/
appName
|
string | Имя приложения, видимое человеку |
website
|
origin
|
string |
Абсолютный адрес публикации (
https://reeel.dev
); относительный отвергается, а не чинится
|
website
|
redirectSlug
|
bool |
true
— адрес по слагу тоже отвечает
301
на
origin
. По умолчанию он отвечает сам и закрыт от индексации; подключённые домены уходят на
origin
без этой настройки — см.
Собственные домены
|
website
|
scripts
|
array |
Авторские скрипты на весь сайт:
[{ "src"|"url", "where", "async", "defer", "requiresConsent", "id" }]
. Выполняются раньше страничных
Script
и только на подключённом домене — см.
Скрипты сайта
|
motion
|
frameSize
|
[w, h]
|
Кадр, в который отдаёт эта конфигурация.
Прежнее имя
size
продолжает работать
|
motion
|
fps
|
number | Кадров в секунду на выводе |
motion
|
codec
|
"h264"
|
"hevc"
|
Чем кодировать.
h264
— то, что площадки рекомендуют сами (YouTube: «H.264, High Profile»), и файл они всё равно перекодируют у себя.
hevc
вдвое легче, но рекомендацией не является
|
motion
|
quality
|
"small"
|
"normal"
|
"high"
|
Насколько подробно писать — это только битрейт.
normal
— то, что площадки советуют для загрузки;
high
имеет смысл, если файл пойдёт в монтаж
|
Скрипты сайта (
settings.scripts
)
Только
kind: website
. Авторские скрипты уровня САЙТА — те, что одинаковы на всех страницах, — объявляются один раз здесь, а не элементом
Script
на каждой странице; эмитятся они раньше страничных:
{ "name": "site_web", "target": "web", "kind": "website",
"settings": {
"origin": "https://reeel.dev",
"scripts": [
{ "src": "data/web/scripts/site.js", "defer": true },
{ "src": "https://plausible.io/js/script.js", "defer": true,
"requiresConsent": "analytics",
"attrs": { "data-domain": "reeel.dev" } }
]
} }
| Поле | Описание |
|---|---|
src
|
Адрес скрипта: файл проекта полным путём от корня (
data/web/scripts/…
) или внешний
https://
-адрес
|
async
,
defer
|
Одноимённые атрибуты тега |
requiresConsent
|
"analytics"
или
"marketing"
: тег инертен до согласия посетителя на категорию
|
id
|
Атрибут
id
тега
|
attrs
|
Прочие атрибуты тега объектом:
data-*
,
type
(только
"module"
),
integrity
,
crossorigin
|
Все теги встают в конец тела страницы. Запись с полем вне этой таблицы — в том числе с ушедшими
url
и
where
— пропускается с предупреждением.
Исполняются авторские скрипты
на адресах самого сайта
: на адресе по слагу
<slug>.reeel.site
и на подключённом домене — хосте из
domains
или хосте
settings.origin
. Под корнем платформы (
*.app.reeel.dev
,
*.sys.reeel.dev
, прежняя зона сайтов
*.in.reeel.dev
) теги в HTML не эмитятся вовсе. Элементы
`Script`
и
`HtmlEmbed`
и категории
`Consent`
работают по тому же правилу.
Аналитика (
settings.analytics
)
Только
kind: website
. Счётчики
объявляются
: сниппет печатает платформа, ни элемента
Script
, ни файла с JavaScript для этого не нужно.
"settings": {
"origin": "https://reeel.dev",
"analytics": {
"yandexMetrika": { "id": 12345678, "webvisor": true },
"googleAnalytics": { "id": "G-XXXXXXX" }
}
}
| Счётчик | Поля |
|---|---|
yandexMetrika
|
id
— номер счётчика; флаги сниппета
webvisor
(по умолчанию
false
),
clickmap
,
trackLinks
,
accurateTrackBounce
(по умолчанию
true
);
ecommerce
— электронная коммерция (по умолчанию
false
;
true
или имя контейнера данных,
"dataLayer"
)
|
googleAnalytics
|
id
— идентификатор потока GA4 вида
G-…
|
Значение счётчика — всегда объект, не голое число: так в него добавляются поля. Неизвестный счётчик, неизвестное поле или
id
не той формы — предупреждение, счётчик пропускается.
-
Объявленный счётчик
всегда
ждёт согласия посетителя на категорию
analytics; снять метку нельзя. -
Работает там же, где авторские скрипты:
<slug>.reeel.site, подключённый домен, превью редактора, статический экспорт. -
Метрика ставит cookie на корневой домен (
.example.com), и настройки для этого у неё нет — поэтому счётчик наexample.comвидит иshop.example.comтем же посетителем. Статистике, которая важна, место на подключённом домене. -
Остальное из кода, который выдаёт Метрика, платформа печатает сама: адрес счётчика и технические поля (
ssr,referrer,url). Пикселя<noscript>нет намеренно: он посчитал бы посетителя, которого не спросили. -
Цели отправляет разметка:
analytics.goal("order_sent"), с параметрами —analytics.goal("order_sent", mapOf("price", 1200, "plan", "pro")). В Метрику это уходит какreachGoal, в Google Analytics — событием с тем же именем. -
Google Tag Manager сюда не входит: контейнер сам загружает произвольный код, то есть это авторский скрипт — элемент
Script.
Капча (
settings.captcha
)
Проверка «я не бот» для форм сайта объявляется так же, как счётчики:
"settings": {
"origin": "https://reeel.dev",
"captcha": {
"yandexSmartCaptcha": { "siteKey": "ysc1_…" }
}
}
| Сервис | Поля |
|---|---|
yandexSmartCaptcha
|
siteKey
— ключ
клиентской
части (
ysc1_…
);
invisible
(по умолчанию
false
) — виджета на странице нет, проверка запускается в момент отправки
|
- Разметка сервиса не называет: элемент `Captcha` читает это объявление. Поэтому у другой конфигурации того же сайта может быть другой сервис, а страницы останутся теми же.
-
Сюда идёт только клиентский ключ.
Серверный (
ysc2_…) — секрет сервера, который проверяет токен.project.jsonсинкается на платформу и виден всем участникам проекта; серверный ключ или полеserverKeyв нём отвергаются с предупреждением, капча при этом не подключается. -
Неизвестный сервис, неизвестное поле, ключ не той формы — предупреждение, капчи нет; форма с
Captchaувидит отказ (failed).
Кадр у моушна: зачем он в проекте, а не только в сцене
Формат доставки принадлежит проекту. Один вариант уходит в горизонт YouTube, другой — в вертикаль Shorts, и это две КОНФИГУРАЦИИ одного проекта с общими компонентами, а не два проекта:
{
"entry": "reeel/main.reeel",
"configurations": [
{ "name": "youtube", "kind": "motion", "target": "macos",
"entry": "reeel/youtube.reeel",
"settings": { "fps": 30, "frameSize": [1920, 1080] } },
{ "name": "shorts", "kind": "motion", "target": "macos",
"entry": "reeel/shorts.reeel",
"settings": { "fps": 30, "frameSize": [1080, 1920] } }
]
}
Общее — компоненты, ассеты, тема, строки — лежит в корне один раз: импорт считается от каталога файла, а начинающийся со
/
— от корня проекта, поэтому все корневые
.reeel
видят один пул.
Кто кого перебивает.
*Scene size:
важнее
settings.frameSize
: проект говорит, во что отдаётся ВСЁ, сцена вправе отличаться. Сцена, не объявившая ничего, берёт кадр у конфигурации — и только так фильм перестаёт зависеть от того, как растянуто окно редактора. Превью при этом показывается в том же кадре: верстать в форме окна, а отдавать в форме кадра — значит узнать о расхождении на готовом файле.
Конфигурация
kind: motion
без
frameSize
— предупреждение проверки, а не отказ: сцена может объявить кадр сама, и такой проект работает.
Кадры под площадки
Числа, которые площадки принимают сегодня. Одна строка — один КАДР; площадки перечислены внутри, потому что совпавшие числа — это не два формата, а два имени одного и того же. Частота 30 берётся везде; 24 остаётся выбором автора.
frameSize
|
площадки |
|---|---|
[1080, 1920]
|
YouTube Shorts, Instagram Reels, TikTok, VK Клипы, Facebook Reels, Snapchat |
[1920, 1080]
|
YouTube, VK Видео, Vimeo, Rutube |
[3840, 2160]
|
YouTube 4K, Vimeo 4K |
[1080, 1080]
|
Instagram, Facebook, LinkedIn |
[1080, 1350]
|
Instagram, Facebook — 4:5, самый высокий кадр, который лента показывает целиком |
Тот же список живёт в коде —
reeel_project/lib/src/motion_formats.dart
, — и экран вывода показывает его как выбор формата ВЫВОДА.
Сверено с первоисточниками 26.09.2026. Кадры и частоты — из рекомендаций YouTube для загрузок и справки Instagram по Reels и по разрешению в ленте : лента Instagram показывает целиком всё в пределах от 1.91:1 до 4:5 (это и есть 1080×1350), Reels просит 9:16 и не меньше 30 кадров, YouTube называет 16:9, частоты 24/25/30/48/50/60 и кодек H.264.
Добавить проекту вариант под площадку — работа агента, а не экрана вывода.
Экран за «молотком» ничего в
project.json
не пишет: он выводит то, что уже объявлено, и умеет вписать фильм в чужой кадр (целиком, с полями по краям). Новый кадр — это новая конфигурация, а её появление меняет проект, и решение об этом принимает тот, кто проект ведёт.
Агента поэтому просят прямо: «добавь вариант под Shorts» или «сделай этот проект горизонтальным». Он дописывает или ЗАМЕНЯЕТ конфигурацию:
{ "name": "shorts", "kind": "motion", "target": "macos",
"entry": "reeel/shorts.reeel",
"settings": { "fps": 30, "frameSize": [1080, 1920] } }
Своя точка входа у варианта не обязательна, но обычно нужна: вёрстка сделана под кадр, и вертикаль, показанная в горизонтальном кадре, ляжет с полями, а не перестроится. Вписывание — честное поведение вывода, а не замена вёрстке.
Связь с
config.json
project.json
— build-time источник истины проекта.
config.json
(текущий формат из ../frontend/configuration.md) при этом
не исчезает автоматически
: рассматривается как потенциальный
runtime-артефакт, выдаваемый нашим сервером
— например,
server_url
нашего сервера и иные данные, которых нет и не должно быть в пользовательском
project.json
.
Поле
entry
присутствует
в обоих
форматах и имеет одинаковый смысл — явный путь к точке входа относительно корня, содержащего
reeel/
(в
config.json
— относительно профиля
assets/<profile>/
). Рантайм сам берёт
dirname
/
basename
для определения корня контента и стартового view.
Таблица соответствия (направление миграции, не завершено ):
Поле
configs/*/config.json
|
Куда переходит | Примечание |
|---|---|---|
platform
|
→
configuration.target
|
Платформа сборки |
design
|
остаётся за
config.json
|
Тип оболочки приложения (isMobile/isTV/isDesktop,
CommonAppWidget
); это про UI-шелл, а не про источник reeel — в
project.json
его нет
|
entry
|
↔
entry
/
configuration.entry
|
Точка входа; одинаковый формат в обоих файлах |
assets
|
→ проект сам по себе |
Проект
и есть
профиль контента (
./reeel/
); отдельное поле не нужно
|
web_assets
|
→
configuration.settings
|
Профиль ассетов для встроенного локального сервера (отладка) |
local_server
|
→
configuration.settings
|
Флаг встроенного локального сервера (отладка) |
reeel_only_assets_fallback
|
→
configuration.settings
|
Бандл без сетевого фолбэка |
plugins
|
→
configuration.settings.plugins
|
Список пакетов плагинов |
server_url
|
остаётся за сервером |
Runtime-хост выдачи контента; кандидат на runtime
config.json
. Отдельно от
platform_api_url
(
api.reeel.dev
) — адреса платформы для sync/сборки в редакторе
|
Все поля текущегоconfig.jsonучтены в таблице. Полный отказ отconfig.jsonсейчас не фиксируется — решение зависит от того, что именно сервер будет выдавать в рантайме.
Валидация на сервере
При
commit
снапшота платформа (
api.reeel.dev
) валидирует
только манифестную часть
project.json
и игнорирует остальное (
theme
,
locales
,
designWidth
,
version
; top-level
entry
служит лишь дефолтом). Проверяется:
-
configurations— непустой массив; -
у каждой конфигурации есть уникальное
nameиtarget; -
entry(конфигурации или top-level) резолвится в реальный файл снапшота.
Нарушения возвращают
422 manifest_invalid
с
errors[]
(коды
empty_configurations
,
missing_configuration_name
,
duplicate_configuration_name
,
missing_target
,
entry_unresolved
, …). Неизвестный
target
— это
warning
, не отказ. Тот же контракт доступен как dry-run:
POST …/snapshots:validate
(всегда
200
).
Отдельно от манифеста:
slug проекта
имеет зарезервированные значения (
api
,
auth
,
identity
,
projects
, …) — их использование даёт
422 reserved_slug
при создании проекта. Полный список — в
platform-docs/reeel-editor-api.md
.
Эволюция схемы
-
Сейчас схема
v1 неявная
:
schemaVersionне пишется. Первое несовместимое изменение введёт явныйschemaVersion(2+); инструменты должны уметь читать манифест и при необходимости предлагать миграцию. - Новые поля добавляются как необязательные с разумными значениями по умолчанию.
-
Редактор валидирует
project.jsonперед синхронизацией (ProjectService.validate()), а сервер — приcommit(см. выше и sync_and_build.md ).