Матрица возможностей
Контракт текущей интеграционной версии, ещё не выпущенной. Сверен с исходниками 2026-10-18. В каждой строке указаны реализация и связанные тесты; наличие теста в списке не означает, что финальная приёмка пройдена. Дополнения текущей версии и оставшиеся условия приёмки перечислены ниже.
Термины:
- CMS — admin API и публичный API чтения (
/v1). - Генерируемый — автономный бэкенд, созданный
npm run backend:generateилиnpm run backend:deliver. - Только интерфейс — правило действует в интерфейсе администратора, но не обеспечивается сервером.
Структуры моделей
Заголовок раздела «Структуры моделей»| Возможность | CMS | Генерируемый | Где выполняется проверка | Доказательство |
|---|---|---|---|---|
| Модель с одной записью (только одна запись) | Модель — это collection или single (content_model.kind). Одиночная модель содержит не больше одной логической записи с учётом корзины. Второе создание отклоняется (409 single_entry_exists) с указанием существующей записи; из двух одновременных созданий выигрывает ровно одно. Добавление языка добавляет его к той же записи. Запись в корзине сохраняет своё место и освобождает его только при окончательном удалении. Коллекция становится одиночной, только пока содержит не больше одной записи с учётом корзины (single_entry_conflict); изменение обновляет контракт модели, поэтому создание, запланированное по старому виду, прерывается. Копия сайта переносит вид; её пробный прогон блокируется, если целевой сайт уже содержит запись, в том числе в корзине (single_entry_taken), или если копия содержит для неё больше одной записи. Single type из Strapi импортируется как одиночная модель. |
Модель помечена в схеме, второе создание отклоняется по имени, а база данных ограничивает таблицу одной строкой — с первой миграции и при смене вида. | Каждое создание, импорт и смена вида. | backend/src/lib/lifecycle/single-entry.ts. backend/test/single-entry-model.test.ts. backend/test/single-entry-import.test.ts. backend/test/graduate-private-and-single.test.ts. backend/test/import-strapi-components.test.ts |
| Папки контента (группировка навигации в админке) | У модели есть необязательная метка папки content_model.nav_folder: от 1 до 40 символов после обрезки пробелов, группировка без учёта регистра, плоская, существует неявно, пока её использует модель. Задаётся отдельной записью PUT /api/models/:id/folder с правом model:apply, а не через Применить, поэтому никогда не меняет ревизию модели и не блокирует сохранение. Список Контент показывает сначала модели без папки, затем папки по имени; каждая папка — кнопка с aria-expanded, набор закрытых хранится в браузере, а папка с открытой моделью открывается. Не входит во входные данные генерации, генерируемые бэкенды и публичное чтение. Копия сайта переносит её на создаваемые модели. API управления задаёт её через setModelFolder и отдаёт как folder. |
Не входит в генерируемые бэкенды. | Изменение папки модели и копирование сайта. | backend/src/lib/model-folder.ts. backend/src/api/model-folder.ts. backend/test/model-folder-route.test.ts. backend/test/model-folder-site-copy.test.ts. backend/test/management-models.test.ts. frontend/src/lib/content/model-folders.ts. frontend/src/routes/content/components/ModelFolderGroups.svelte. frontend/cypress/e2e/journey-model-nav-folders.cy.ts |
| Уникальное поле, объявленное автором | Уникальность отдельных текстовых и числовых полей и до восьми именованных сочетаний из двух–четырёх таких полей. Все поля сочетания должны храниться одинаково: либо общими, либо отдельно по языкам. Область — модель или отдельный язык. Незаполненное сочетание не резервируется. Сохранение, импорт, восстановление, публикация и изменение структуры используют атомарную проверку. См. Уникальные сочетания. | Одиночные и составные уникальные индексы; при области языка добавляется lang. |
Готовность к генерации и сборка отклоняют опубликованные дубли и общие цели один-к-одному до генерации. | backend/src/lib/lifecycle/unique-values.ts. backend/src/lib/graduate/unique-values.ts. backend/test/field-uniqueness.test.ts. backend/test/graduate-uniqueness.test.ts |
| Приватное поле (скрыто от публичного чтения) | Приватность задаётся для поля модели (custom_field.private) или отдельного поля компонента. Приватное поле, его связи и медиа не попадают в публичные списки и отдельные записи, ленты, поиск по сайту и сгенерированный публичный клиент. Публичные фильтры, сортировка, выборка и раскрытие связей, в которых указано такое поле, отклоняются так же, как при обращении к неизвестному полю. Ключи кеша учитывают набор приватных полей, поэтому после изменения приватности прежний кешированный ответ не используется. При переводе поля в приватные поисковые данные модели переиндексируются. Если библиотека компонента недоступна для чтения, поле этого компонента исключается целиком. Сохранённый запрос на публичном маршруте не может обращаться к приватному столбцу, в том числе через содержащий его столбец компонента: при привязке или правке возвращается 422 private_field_on_public_route, при генерации — saved_query_private_field. Для чтения модели с видимостью private по-прежнему нужен токен чтения, разрешающий доступ к этой модели. |
Приватные столбцы сохраняются для редакторов, но исключаются из всех ответов на чтение и запись. Связующая таблица приватной списковой связи доступна только редакторам. Сгенерированный сайт не показывает приватные значения, связи и медиа и исключает приватные поля из значений компонентов. Политика доступа для модели и операции (anonymous, session, service) не меняется. |
Публичное чтение и генерация. | backend/src/lib/private-fields.ts. backend/test/private-fields.test.ts. backend/test/public-feed.test.ts. backend/test/graduate-private-and-single.test.ts. backend/test/saved-query-graduation.test.ts. backend/test/public-private-routes.test.ts. backend/test/backend-policy.test.ts |
| Условная видимость поля | Типизированные условия по логическим полям, выбору, тексту, числам и датам. Группы И/ИЛИ содержат не более четырёх условий и трёх уровней. Редактор, сохранение записи и проверка модели используют одни правила. Скрытое значение сохраняется; видимость не меняет права доступа. См. Показывать только если. | Те же условия определяют обязательность поля. | Сохранение модели. | frontend/src/lib/eav/field-condition.ts. backend/test/field-conditions.test.ts. backend/test/mixed-components-site-copy.test.ts |
| Смешанное поле компонента | Обычные и смешанные компоненты можно вывести из использования и сохранить их прежнюю структуру. Существующие экземпляры остаются доступны для редактирования по своей версии структуры; выведенные компоненты нельзя добавлять и дублировать. Обновление экземпляра выполняется явно. Сохраняются ID экземпляра, состояние показа и список разрешённых компонентов. См. Версии структуры компонентов. | Генерация читает каждый блок по его собственному компоненту и объявляет пути медиа для каждого компонента. Блок компонента, который поле не допускает, ключ, который его компонент не объявляет, и одиночный блок там, где ожидается список, отклоняются. Скрытые блоки остаются в строках и исключаются из каждого ответа чтения и из сгенерированного сайта. | Сохранение, сохранение модели и генерация. | backend/src/lib/component-contract.ts. backend/src/lib/hidden-blocks.ts. backend/test/mixed-components.test.ts. backend/test/mixed-components-site-copy.test.ts. backend/test/hidden-blocks.test.ts |
| Авторизация на уровне полей | limits роли называют для каждой модели или для всех моделей (*) поля, которые роль может читать и писать; роль без ограничений имеет доступ ко всем полям. Нечитаемые поля исключаются из чтения экземпляров, списков значений и их итогов, поиска, содержимого версий, чтения страниц и библиотеки страниц, а предпросмотр несёт область чтения его автора. Сохранение сохраняет каждое хранимое значение, которое роль не видела. Восстановление, которое переписало бы поле, недоступное роли для записи, отклоняется (field_denied). Экспорт всего сайта отклоняется для роли, сужающей чтение (export_limited_role). Человек с несколькими ролями получает объединение по каждой операции. Менеджер ролей может выдавать только то, что имеет сам, а учётные данные управления сохраняют ограничения пользователя, который их создал. Переименование ключа поля переписывает списки полей всех ролей для модели. |
Не поддерживается. Реализовать это можно только собственными хуками в src/owned/backend.ts. |
Сохранение, восстановление, чтение и сохранение роли. | backend/src/lib/permission-limits.ts. backend/test/permission-limits.test.ts. backend/test/permission-limits-routes.test.ts. backend/test/permission-limits-lifecycle.test.ts. backend/test/permission-limits-pages.test.ts. backend/test/management-roles.test.ts |
| Авторизация на уровне языков | limits также называют языки для каждого действия: чтение, создание, изменение, удаление, восстановление, проверка, публикация и снятие с публикации. Сохранение, массовый пакет, переход workflow, публикация или снятие с публикации, затрагивающие язык вне роли, отклоняются целиком (locale_denied) и ничего не пишут. Принудительная публикация или снятие, одиночные или массовые, учитывают только языки, чья опубликованная версия изменилась бы, и отказ называет эти языки. Восстановление старой версии одного языка требует restore только для этого языка и меняет поля этого языка, собственные списки связей и адрес, но никогда общие значения или другие языки; восстановление всей записи требует всех языков. Изменённое общее поле требует права изменения основного языка; неизменённое — нет. Если роль разрешает удалять лишь некоторые языковые версии, пользователь может переместить в корзину отдельную разрешённую версию, но не всю запись. Предпросмотр никогда не переходит на язык, который автор не может читать. Ключ :own расширяет язык только для собственных строк человека. Суперадминистратор никогда не ограничивается. |
Не поддерживается. | Сохранение, workflow, корзина, чтение и предпросмотр. | backend/src/lib/permission-limits.ts. backend/test/permission-limits-lifecycle.test.ts. backend/test/permission-limits-routes.test.ts. backend/test/permission-limits-preview.test.ts. backend/test/permission-limits.test.ts |
| Поверхность | Один-к-одному | Многие-к-одному | Один-ко-многим | Многие-ко-многим |
|---|---|---|---|---|
| Редактор модели | Предлагается с описанием: Каждый связанный элемент принадлежит только одному объекту «<owner>» |
Предлагается | Предлагается с описанием: Каждый связанный элемент принадлежит только одному объекту «<owner>». Поля компонентов его не предлагают (relation_type_not_authorable). |
Предлагается |
| Ссылаются сюда (редактор цели) | Цель перечисляет записи, поле которых указывает на неё, по модели и полю источника, с пометками «Только на сайте», «Только черновик» и «Только просмотр». Редактор, которому можно читать цель и менять источник, добавляет или убирает связь; изменение создаёт новую черновую версию исходной записи с проверкой прав на неё, проверкой версии, правилами полей, кратностью, правилом одного владельца и правилом иерархии, и никогда не публикуется. Замена другой цели источника требует подтверждения, в котором названы обе (409 related_from_replace_unconfirmed). На стороне цели ничего не хранится. |
То же | То же, без шага замены | То же, без шага замены |
| Запись в CMS | Одна цель | Одна цель | Много целей | Много целей |
| Формы записи в CMS | Простой список или { set } заменяет упорядоченный список поля. { connect, disconnect, move } сначала удаляет, затем подключает в заданном порядке (простой id идёт в конец, { id, position } ставит его на место), затем перемещает связанные элементы; позиции — before, after, start, end. У связи «к одному» позиция или move отклоняется (relation_position_not_ordered) |
То же | То же | То же |
| Проверки записи в CMS | При изменённом списке: цель существует в сайте, относится к объявленной целевой модели (одной из принимаемых, если поле принимает несколько), доступна пишущему на чтение (включая чтение :own). Встречается один раз, а поле to-one содержит одну цель. Список, равный сохранённому, повторно не проверяется. Восстановление проходит те же проверки. Цель, на которую другая запись указывает в этом поле (опубликованно или в черновике), отвергается (409 relation_target_taken) |
То же, без отказа по правилу одного владельца | То же, что один-к-одному | То же, без отказа по правилу одного владельца |
| Публичное чтение CMS | Список { instance_id, model, slug, display } в снимке. Цель переведённой модели отдаётся с адресом и названием на языке, которым отдаётся запись, затем по цепочке запасных языков; при строгой политике цель без этого языка не отдаётся. Фильтр tag сравнивает тот же адрес |
То же | То же | То же |
| Генерируемая схема | Столбец <key>_id, @unique, внешний ключ |
Столбец <key>_id, внешний ключ |
Связующая таблица модели, объявившей поле, уникальный столбец цели, внешние ключи | Связующая таблица, внешние ключи |
| Генерируемое чтение | Столбец id | Столбец id | Маршрут связующей таблицы только для чтения | Маршрут связующей таблицы только для чтения |
| Генерируемая запись | Задаёт столбец id после проверки, что целевая строка существует. Иначе 422 relation_target_missing, ничего не записывается. Результат для одной модели не имеет целевой таблицы и отвергает любой ненулевой id. |
То же | PUT /api/<owner>/relations/<field> с set, connect, disconnect, по правилам CMS одним пакетом. Цель другого владельца — 409 relation_target_taken |
PUT /api/<owner>/relations/<field>, те же правила |
| Генерируемое удаление цели | Необязательный столбец становится null. Обязательный отклоняет удаление (409 relation_target_referenced) |
То же | Связь удаляется | Связь удаляется |
- Отсутствующая цель, цель другой модели и цель, недоступная пользователю для чтения, отвергаются одинаково (422
relation_target_not_found). Отказ перечисляет отвергнутые id, поэтому ответ не раскрывает, какие id существуют. - Строки, записанные до этих проверок, остаются читаемыми. Следующее сохранение, которое меняет это поле, должно им удовлетворять.
- Переводимые поля записываются через генерируемые маршруты переводов. При создании строка перевода один раз связывается с основной записью и языком, для которого сгенерирован контент сайта (
unknown_locale,translation_row_missing,locale_exists); при обновлении меняются только переведённые значения. - Удаление: строки связи удаляются при окончательном удалении любой из записей. Перемещение записи в корзину их сохраняет, восстановление возвращает. Запись в корзине нельзя выбрать для новой связи. Другая запись остаётся. Настройки на уровне поля нет. В генерируемом бэкенде всего сайта каждый идентификатор связи — внешний ключ с поведением выше. Идентификатор связи внутри JSON-значения компонента ключом не является.
- Разные связи в каждом языке (
localizedMembership, только переводимые модели, не внутри компонентов): каждый язык хранит свой упорядоченный список в строках, версиях и редакторе, и каждое правило связей («к одному», один владелец, иерархия, лимиты записей) проверяется по языку. Публичное чтение отдаёт каждому языку его собственный список; резервный язык отдаёт свой список и никогда не список другого языка. Восстановление возвращает список каждого языка, в том числе из версии, записанной до изменения опции. Включение опции копирует общий список во все языки; выключение сохраняет список основного языка и блокируется, если две записи после этого делили бы цель с одним владельцем или родители основного языка образовали бы цикл. Окончательное удаление языка удаляет его список. Копия сайта переносит список каждого языка. Генерируемый бэкенд хранит такое поле, включая «к одному», как строки связующей таблицы с локалью, а его маршрут связей читает и пишет список одного языка за раз. Доказательство:backend/src/lib/lifecycle/relation-sets.ts.backend/test/relation-localized-membership.test.ts.backend/test/relation-localized-membership-site-copy.test.ts.backend/test/graduate-localized-membership.test.ts.
Доказательство: backend/src/lib/lifecycle/service.ts (planRelations, checkRelationLists, resolveRelationWrites). backend/src/api/models.ts. backend/src/lib/relation-type.ts. backend/src/lib/graduate/templates.ts (relationTargetsExist). backend/test/relation-writes.test.ts. backend/test/lifecycle-atomicity.test.ts. backend/test/models-apply.test.ts. backend/test/graduate-relation-writes-worker.test.ts. backend/test/graduate-relation-load.test.ts. backend/test/graduate-relation-schema.test.ts. backend/test/relation-cardinality.test.ts. backend/test/graduate-junction-http.test.ts. frontend/src/routes/model-builder/[id]/tabs/config-types/RelationConfig.svelte.test.ts.
Что проверяет только интерфейс, а что — сервер
Заголовок раздела «Что проверяет только интерфейс, а что — сервер»| Настройка | Обеспечивает ли сервер? |
|---|---|
| «hidden» поля в редакторе | Нет. Значение всё равно хранится и отдаётся. |
Условие показа (ui.showWhen) |
Частично. Скрытое значение всё равно хранится и отдаётся. Сервер проверяет обязательность поля только тогда, когда выполняется условие его показа, и отклоняет невыполнимые условия. |
| «read-only» поля | Удалено из редактора модели. Сохранённые значения старой настройки игнорируются. |
| Валидация поля: обязательность, длина, шаблон, диапазон и шаг чисел, количество элементов, даты, варианты select — для полей модели и для каждого поля внутри значения компонента | Переданные значения проверяются при сохранении тем же модулем, что и в редакторе. По умолчанию черновик может не содержать обязательных значений; строгий режим требует их уже при сохранении. При публикации проверяется точный снимок со всеми обязательными значениями (422 field_validation_failed, каждое поле названо полным путём: seo.meta_title, faqs[2].question). Значение компонента проверяется по определению, которое называет его поле. Список там, где ожидается один экземпляр, отклоняется. Экземпляр, который не является объектом, отклоняется. Ключ, которого определение не объявляет (кроме variant_id, enabled, id, _id), отклоняется. Медиа внутри компонента принимается в любой форме, которую читает генерируемый автономный бэкенд. Формы — это id, { media_id, url }, список из них и список из одного элемента у одиночного поля. Оно хранится как id или список id. Необязательный одиночный компонент, где все значения пустые, считается незаполненным. Скрытые поля, производные поля, которые никто не может редактировать, и поля страницы, принадлежащие компоновщику, пропускаются, как и в редакторе. Восстановление версии проверяется по текущей модели и отклоняется тем же 422, ничего не записав. Импорт проверяется по значениям, которые пишет каждая фаза. Где задание планирует связи и медиа, количество проверяется по запланированным связям. Пробный прогон заранее сообщает каждую проблему блокирующей строкой по пути и языку, до любой записи. Проверка таблицы так же проверяет каждую изменённую строку до первого сохранения. |
| Поведение связи при удалении | Удалено из редактора модели. Удаление фиксировано (см. «Связи»). |
Правила кадрирования медиа у поля (aspectRatio, minWidth, minHeight) |
Да, для кадрирования, которое отправляет редактор. Правило, изменённое позже, не блокирует несвязанное сохранение. Импорт повторно не проверяется. |
settings.permissions, черновики, версионирование и сортировка по умолчанию у модели |
Прежние настройки удалены. Поддерживается settings.validation_mode: draft по умолчанию или strict, требующий заполнить обязательные поля уже при сохранении. |
Права ролей на модель, владение :own, права на переходы workflow, права на публикацию и снятие с публикации |
Да |
Видимость модели (public / private) в публичном API CMS |
Да |
| Размер rich text (256 KiB) | Да |
Доказательство: backend/src/lib/lifecycle/field-rules.ts. frontend/src/lib/eav/content-validator.ts. backend/src/lib/import/rule-findings.ts. backend/test/field-rules.test.ts. backend/test/field-rules-save.test.ts. backend/test/component-rules-route.test.ts. backend/test/import-field-rules.test.ts. backend/test/import-strapi-field-rules.test.ts. backend/test/spreadsheet-import.test.ts. frontend/src/routes/content/[id]/component-rules.svelte.test.ts. backend/test/media-framing.test.ts. backend/test/transition-requires-write.test.ts. backend/test/ownership-enforcement.test.ts. backend/test/rich-field-contract.test.ts.
Редакционные операции
Заголовок раздела «Редакционные операции»| Возможность | CMS | Доказательство |
|---|---|---|
| Публикация по языкам | У каждой языковой версии свой этап согласования и своя опубликованная версия. Панель Процесс позволяет опубликовать, снять с публикации или проверить отдельную языковую версию. Групповая публикация или снятие с публикации затрагивает все языки. Групповой переход между двумя неопубликованными этапами, например Одобрить · Все языки или Вернуть · Все языки, сохраняет публикацию и статус Опубликовано у уже опубликованных языковых версий. Снять отдельную языковую версию с публикации можно через Снять с публикации: {language}. Публичное чтение, предпросмотр, адреса, перенаправления и сгенерированные сайты используют собственную опубликованную версию каждого языка; при генерации каждый язык получает содержимое и адрес из своей версии. Если основной язык не опубликован, запись генерируется из опубликованных языковых версий с сохранением того же id. Общие значения берутся из опубликованной версии первого опубликованного языка в порядке, заданном на сайте; язык по умолчанию идёт первым. Копия сайта переносит указатель на опубликованную версию каждого языка; импорт, его квитанции и отмена восстанавливают эти указатели. Для планирования отдельной языковой версии плагину нужна возможность workflow:locale. |
backend/test/locale-publication.test.ts, backend/test/locale-publication-route.test.ts |
| Поиск, публикация в соцсетях, перенаправления и карта сайта | Настройки поиска и публикации в соцсетях для каждого языка хранятся в каждой версии, вместе с типом страницы для структурированных данных. Site Chrome хранит значения по умолчанию, сведения о том, кто ведёт сайт, коды подтверждения для поисковых систем, политику карты сайта и robots и до 256 перенаправлений адресов и 32 шаблонов с ответами 301, 302, 307, 308, 410 или 451. Единый сервис перенаправлений отклоняет циклы через любые сочетания правил, неоднозначные шаблоны, зарезервированные и живые адреса; изменённый шаблон требует проверки затронутых живых страниц; публикация по адресу, занятому перенаправлением, отклоняется с указанием правила. Оба рендерера используют один сопоставитель, один генератор карты сайта (индекс частей по 5 000 адресов с изображениями) и один сериализатор JSON-LD. | backend/test/redirect-service.test.ts, backend/test/redirect-runtime-parity.test.ts, backend/test/redirect-lifecycle.test.ts, backend/test/sitemap-runtime-parity.test.ts, backend/test/sitemap-public-route.test.ts, backend/test/structured-data.test.ts |
| Поиск по актуальному контенту сайта | Выключен, пока его не включат в разделе Настройки → Код и публикация → Поиск по сайту (site:infra). GET /v1/_search отвечает опубликованными страницами, записями на сайте-блоге и до 16 выбранными публичными моделями, на запрошенном языке по цепочке запасных языков сайта. Черновики, приватные модели, записи в корзине и языки, скрытые от поисковых систем, никогда не появляются. Для каждого слова применяется выбранный режим сопоставления: целые слова (по умолчанию), формы английских слов (стемминг Porter2 и для сохранённого текста, и для запроса, на любом сайте; решается для каждого результата по языку, на котором он отдан: en, en-* и en_* совпадают по формам слов, любой другой язык — по целым словам, в одном запросе) или части слов; сначала идут совпадения в заголовке, затем самые новые. Все формы сохраняются с каждой версией, поэтому при смене режима сопоставления ничего не перестраивается, а курсоры и ключи кеша учитывают этот режим и цепочку запасных языков. В разделе «Совпадение и ранжирование» тип может вместо этого искать по 16 выбранным полям, у каждого вес 1–10 (заголовок 10, остальные 1 по умолчанию); результат ранжируется по наибольшему весу среди выбранных полей, где есть все слова, затем по новизне. Веса читаются во время поиска, поэтому ничего не перестраивается. Слова каждой версии сохраняются в том же пакете, который сохраняет версию; опубликована ли строка и какой у неё адрес, решается во время поиска, поэтому публикация, снятие с публикации, корзина и смена адреса действуют сразу. Выключенный поиск отвечает 404 search_off. Управляемые и генерируемые сайты строят по нему /search, /<language>/search и раздел Поиск по сайту во время запроса; чтобы обновить поиск, ничего не пересобирается. GET /v1/_search/suggest возвращает до 10 совпадений, сначала по заголовку, из тех же строк, с тем же совпадением, тегами кеша и очисткой, где последнее слово совпадает с началом слова; каждое поле поиска показывает их, пока посетитель печатает (combobox для клавиатуры и программ чтения с экрана, обычная GET-форма без JavaScript), и ничего не записывает о запросе. |
backend/src/lib/search/public-search.ts. backend/src/lib/search/projection.ts. backend/src/lib/search/suggest-script.ts. backend/src/lib/search/english-stem.ts. backend/test/public-search.test.ts. backend/test/search-projection.test.ts. backend/test/english-stem.test.ts. frontend/src/lib/sites/search-suggest-script.test.ts |
| Корзина | Удаление перемещает всю запись, на всех языках, в корзину. Опубликованный контент отклоняется, пока его не снимут с публикации. Записи в корзине пропадают из обычных списков, поиска, выбора связей, дашбордов, экспорта и предпросмотра. У каждого списка контента и страниц рядом есть корзина, только по моделям, которые человек может читать. Восстановление требует права delete на модель, возвращает запись черновиком с версиями, переводами, связями и медиа и сначала показывает занятые адреса и связи, которые больше не работают. Окончательное удаление — отдельное подтверждаемое действие. Через 30 дней после перемещения в корзину записи удаляются окончательно. Языковую версию, к которой не привязана история, можно переместить в корзину отдельно («Переместить этот язык в корзину»): она пропадает из текущей версии, переключателя языков и публичного чтения, остальное остаётся редактируемым и публикуемым, повторное добавление этого языка отклоняется (language_in_trash), восстановление возвращает её черновиком. У записей блога своя корзина рядом со списком. Контент в корзине, ссылающийся на элемент, не блокирует снятие этого элемента с публикации; он помечен «В корзине». Импорт отклоняет запись, чей адрес принадлежит записи в корзине, повторный импорт в запись из корзины и ссылку на неё. |
backend/src/lib/lifecycle/trash.ts. backend/src/api/content_lifecycle.ts. backend/test/content-trash.test.ts |
| Массовые действия | Перевод на этап workflow, публикация или снятие с публикации через переопределение либо перемещение явно перечисленных id в корзину, их восстановление или окончательное удаление. Запрос принимает не больше 25 id. Каждая запись проходит проверки своего одиночного маршрута, включая собственную ревизию и проверенные зависимые элементы. Результаты в порядке входа. Повтор с тем же ключом воспроизводит зафиксированные записи, а не выполняет их заново, даже после запроса, остановившегося на полпути. Ключи привязаны к человеку. Общие маршруты /many и /bulk отвечают 410. relate добавляет или убирает до 10 связанных записей в одном поле многие-ко-многим каждой записи (Записи → Категории и теги) собственным сохранением этой записи с проверкой версии; ничего не публикует, запись, где изменение уже есть, получает unchanged, а поле один-ко-многим отклоняется (batch_relation_exclusive). |
backend/src/api/content_lifecycle.ts (/batch). backend/test/content-batch.test.ts. backend/test/content-batch-relate.test.ts |
| Владение правкой | Пользователь, первым открывший запись на выбранном языке, получает временную блокировку редактирования этой языковой версии. Остальные видят, кто сейчас редактирует запись, и наблюдают за изменениями общего черновика в форме только для чтения. Перехватить передаёт редактирование одним действием. Блокировка переходит сразу: прежний редактор видит, кто перехватил редактирование, а его следующее сохранение или переход отклоняется с 423 lock_lost. Его несохранённый текст остаётся в личном черновике восстановления и снова предлагается, когда пользователь вновь получает блокировку. Чтобы получить или перехватить блокировку языковой версии, нужно право изменения на этом языке, а если записи на нём ещё нет — право создания; иначе возвращается locale_denied. При доступе только к собственным записям пользователь также должен быть владельцем этой языковой версии, иначе возвращается not_owner. Из других сессий сохранение, добавление языка, переход по процессу согласования, назначение исполнителя, восстановление версии, принудительная публикация, перемещение в корзину и окончательное удаление этой записи и языка отклоняются с 423 locked. При массовом действии из списка отклоняется только заблокированная запись. Собственный открытый редактор не блокирует тому же пользователю действия из списка контента и через учётные данные управления; для другого пользователя эти действия блокируются. Плагины, импорт, публикация по расписанию и применение таблицы не блокируются; от перезаписи изменений редактора по-прежнему защищает проверка версии. Блокировка проверяется при подготовке операции записи и повторно внутри её пакета операций в базе данных, поэтому одновременный перехват приводит к отказу сохранения. Блокировка действует две минуты после последнего продления, не продлевается в скрытой вкладке и снимается при закрытии вкладки. Истёкшая блокировка не мешает работе. При наблюдении за другим редактором форма проверяет обновления каждые 10 секунд и показывает более новый общий черновик; скрытая вкладка запросов не отправляет. Пользователи, открывшие другие языки той же записи, перечислены в одной строке. Эти действия не журналируются, не подсчитываются и не сохраняются в истории. На одного пользователя и сайт допускается не больше 20 открытых редакторов. Если до фиксации автосохранения или удаления общего черновика блокировка перешла к другому пользователю, операция отклоняется с 423 lock_lost и ничего не меняет. При отклонённом автосохранении правки остаются в личном черновике восстановления. При отклонённом удалении появляется Не удалено — редактирование перехвачено. Ваша работа сохранена., а работа на экране также сохраняется в черновике восстановления. Редактор страниц работает так же: страница на выбранном языке доступна только для чтения, пока её редактирует другой пользователь; редактирование можно перехватить одним действием Перехватить; при потере блокировки набор изменений сохраняется в личном черновике. |
backend/src/lib/presence.ts. backend/src/lib/lifecycle/editing-lease.ts. backend/src/api/presence.ts. backend/test/editing-ownership.test.ts. backend/test/editor-presence.test.ts. frontend/src/lib/presence/presence.svelte.test.ts. frontend/src/lib/drafts/draft-discard-refused.svelte.test.ts. frontend/cypress/e2e/journey-editor-presence.cy.ts |
| Общий черновик | После короткой паузы несохранённые изменения автоматически записываются в один общий черновик для записи и языка. Менять его может текущий редактор, получивший блокировку. Читать — любой пользователь с правом чтения записи; при доступе только к собственным записям пользователь должен быть владельцем этой языковой версии, иначе возвращается not_owner. Во всех ответах с черновиком, включая ответы о конфликтах, остаются только поля и языки, доступные пользователю для чтения. Право чтения проверяется независимо от права записи: пользователь, который может записать черновик, но не может прочитать соответствующую запись или язык, не получает содержимое черновика (current: null при конфликте, draft: null при перехвате через API управления). Если текущий редактор видит не все поля, он записывает только доступные ему поля; остальные значения сохраняются. Пользователь, чья роль не разрешает читать название страницы, получает её черновик с пустым названием. Если роль не разрешает читать секции страницы, запрос черновика отклоняется с field_denied. После перезагрузки черновик доступен на любом устройстве. Каждый запрос на запись указывает заменяемую ревизию; если она устарела, редактор предлагает выбрать, какой вариант оставить. Редактор показывает Сохранение черновика…, Черновик сохранён, Нет сети — сохранено здесь (изменения хранятся на устройстве и отправятся после восстановления связи или после нажатия Повторить), Изменено на другом устройстве или Черновик не сохранён. Черновик не является версией. При сохранении записи новая версия создаётся, а общий черновик удаляется одним пакетом операций. Переход по процессу согласования и принудительная публикация сначала сохраняют несохранённые изменения; принудительное снятие с публикации этого не делает. Общий черновик хранится 30 дней после последнего изменения. Редактор страниц так же сохраняет набор изменений в общем черновике страницы. Для новой записи сохраняется личный черновик пользователя. |
backend/src/lib/working-drafts.ts. backend/src/api/presence.ts. backend/test/editing-ownership.test.ts. frontend/src/lib/drafts/draft-sync.svelte.test.ts. frontend/src/lib/drafts/DraftStatus.svelte.test.ts. frontend/cypress/e2e/journey-cross-device-drafts.cy.ts |
| Назначение ревьюера | Один исполнитель процесса и необязательный срок на всю запись, с проверкой доступных действий текущего этапа. Независимые владельцы языков и редакционные задачи для нескольких людей имеют собственные назначения, состояние и ревизию; они не заменяют исполнителя процесса. Устаревшая ревизия возвращает 409. Та же запись фиксирует событие назначения. Перемещение очищает назначение, если не называет следующего исполнителя. Интерфейс отмечает неактивных исполнителей и исполнителей без доступа. В очереди ревью используйте assigned=me. Назначение уведомляет нового исполнителя. Обязательные задачи должны быть завершены для публикуемого снимка. Изменения другого языка не отменяют согласование текущего; изменения проверенных или общих значений требуют повторного ревью. |
backend/src/lib/lifecycle/assignment.ts. backend/test/workflow-assignment.test.ts |
| Уведомления ревью | Обсуждения хранят версию, язык и точный путь поля: ответы, упоминания пользователей и команд, закрытие и повторное открытие всей ветки. Доступ к полю, языку и уведомлениям проверяется заново. Уведомления в админке о назначении, запросе ревью и возврате на доработку записываются в том же пакете, что и переход workflow; уникальный ключ (вид, запись, язык, ревизия workflow, получатель) означает, что повторный или конкурирующий переход пишет одно уведомление на получателя, а отклонённый переход не пишет ничего. Переходы из редактора, management API и плагинов используют один путь (reviewNoticesFor). Каждый получатель, включая явно назначенного исполнителя, должен быть активным участником, который может читать модель (область «свои строки» учитывается по каждой затронутой строке) и затронутые языки; одного права изменять или проверять недостаточно. Переход одного языка уведомляет только тех, кто вдобавок может проверять каждый перемещённый язык; автору при возврате или исполнителю нужен доступ на чтение хотя бы к одному из них. При чтении доступ к записи и языку проверяется заново, ссылка ведёт на запись этого языка и показывает её текущий этап. Импорт по умолчанию тихий; задача импорта хранит review_notify (quiet или notify, меняется с data:import, пока задача не завершена), и уведомляющий импорт пишет только уведомления в админке. Перенесённая история не уведомляет никогда. Не больше 50 получателей на уведомление; почта необязательна. |
backend/src/lib/review/context.ts. backend/src/lib/review/notifications.ts. backend/src/lib/review/notification-read.ts. backend/test/review-feedback-notifications.test.ts. backend/test/review-locale-notifications.test.ts. backend/test/import-notify-policy.test.ts |
| Фильтры и сортировка списка по полям | Одна модель, последние сохранённые значения, включая черновики: field_filters допускает до четырёх условий в группах И/ИЛИ глубиной до трёх уровней. Скалярные сравнения, наличие связей и настроенные поля отображения связей внутри сайта учитывают текущий доступ к цели. field_sort задаёт одно скалярное поле или поле отображения одиночной связи с одной целевой моделью; пустые и недоступные значения идут последними, равные значения различаются по ID. Для точек доступны точный радиус, прямоугольная область и порядок по расстоянию. Форматированный текст, медиа, компоненты и JSON не поддерживаются. Строки, счётчики, ID совпадений, представления и массовый выбор используют единый SQL-план без ограниченного перебора кандидатов. Курсор привязан ко всему запросу; лишние параметры отклоняются. |
backend/src/lib/content-fields/. backend/src/lib/scoped-crud.ts. backend/test/content-field-query.test.ts. backend/test/content-views.test.ts. backend/test/content-bulk.test.ts |
| Обрезка и точка фокуса | В медиаполях верхнего уровня параметры кадрирования хранятся для каждой ссылки на файл в долях исходного изображения. Каждое использование имеет своё кадрирование, даже если одно изображение добавлено в поле дважды. Байты файла не меняются. Параметры сохраняются при изменении других полей, фиксируются в версиях и переносятся при восстановлении и переносе пакетов. Замена файла тоже сохраняет кадрирование, предупреждая об изменении соотношения сторон. Публичный API CMS возвращает эти параметры, и они входят во входные данные генерации. Точка фокуса изображения по умолчанию применяется только при новом использовании; копия сайта переносит её. Внутри компонента параметры хранятся в значении каждого использования ({ media_id, crop?, focal? }; без кадрирования — просто id) и сохраняются при копировании сайта и подготовке автономного бэкенда. Сгенерированный сайт выдаёт масштабированные и обрезанные копии через Cloudflare Image Transformations на хостах, указанных владельцем. Для остальных хостов используется исходный URL и кадрирование через CSS. В секциях страниц кадрирование задаётся для каждого слота изображения (контракт страницы версии 4). Управляемый рендерер применяет те же правила начиная с версии 4 встроенного шаблона; хосты преобразования задаются при установке (MANAGED_IMAGE_TRANSFORM_ORIGINS). |
backend/src/lib/lifecycle/media-framing.ts. backend/src/lib/graduate/site-media-presentation.ts. backend/test/media-framing.test.ts. backend/test/media-default-focal.test.ts. backend/test/component-media-framing.test.ts. backend/test/media-default-focal-bundle.test.ts. backend/test/image-delivery.test.ts. backend/test/media-mime.test.ts. backend/test/import-bundle-spine.test.ts |
| Теги медиа | Плоские теги медиатеки, принадлежащие сайту. Теги не зависят от папок. Имя уникально в сайте без учёта регистра, до 64 символов. Общий диалог медиа фильтрует по всем выбранным тегам или по «Без тегов» вместе с поиском. В нём можно назначить теги выбранным изображениям, переименовать и удалить теги. Удаление тега никогда не удаляет изображение. Тег, который используется, удаляется только после подтверждения с числом изображений. Для чтения требуется право чтения медиа. Изменения требуют media:update. Копия сайта переносит имена тегов каждого изображения. Тег без изображений не переносится. Импорт из Strapi не создаёт тегов. Публичный API теги не отдаёт. |
backend/src/api/media-tags.ts. backend/src/lib/media/tags.ts. backend/test/media-tags-route.test.ts. backend/test/media-tags-bundle.test.ts |
| Папки медиа | Логические папки медиатеки, принадлежащие сайту. Папка никогда не перемещает и не переименовывает сохранённый файл и не меняет его адрес. Каждый файл находится в одной папке или в Без папки. Папки вкладываются не глубже 5 уровней, на сайте не больше 500 папок, имя — от 1 до 80 символов, уникальное внутри родителя без учёта регистра. Общий диалог медиа — в медиатеке и в каждом выборе медиа — показывает Все медиа, Без папки и дерево папок с количествами. При открытии папки показываются только файлы в ней, без файлов из подпапок. Новые загрузки и сохранённые сгенерированные изображения попадают в показанную папку. Выбранные файлы (не больше 200) перемещаются вместе. Переименование и перемещение отправляют прочитанную ревизию; устаревшая отклоняется с media_folder_changed. Перемещение папки в саму себя или ниже неё отклоняется (media_folder_cycle), как и перемещение глубже 5 уровней (media_folder_too_deep). Удаление папки удаляет её поддерево после одного подтверждения, которое называет число папок и файлов; ни один файл не удаляется, каждый попадает в «Без папки». Удаление отклоняется, если любое из этих чисел изменилось. Для чтения требуется право чтения медиа. Изменения требуют media:update; API управления использует тот же сервис с media:write. Копия сайта и перенос с историей переносят всё дерево, включая пустые папки, в media-folders.json (пакет 1.10), и каждый файл называет свою папку. Импорт перестраивает дерево с новыми id, сначала родителей, по одному элементу с квитанцией на папку, и повторно использует папку, которая уже есть по тому же пути без учёта регистра. Отмена удаляет созданную импортом папку, только пока она не изменена и не содержит ни файлов, ни папок; иначе папка остаётся, как и теги. Импорт Strapi 4 или 5 воссоздаёт дерево папок по сущностям папок архива и их явным связям с родителем и файлами, только для папок, в которых лежат выбранные файлы, и никогда по путям файлов; папка, которую нельзя создать в этих пределах, блокирует пробный запуск и никогда не переименовывается (см. «Папки медиа Strapi»). Ни публичный API, ни генерируемый сайт, ни генерируемый автономный бэкенд не переносят папки. |
backend/src/lib/media/folders.ts. backend/src/api/media-folders.ts. backend/test/media-folders.test.ts. backend/test/media-folders-transfer.test.ts. backend/src/lib/import/media-folder-apply.ts. backend/src/lib/import/undo-media-folder.ts. backend/test/management-media-folders.test.ts. frontend/src/lib/components/custom-ui/image-select/MediaFolders.svelte.test.ts |
| Медиафайлы | Сайт выбирает допустимые форматы изображений, документов, аудио, видео и загрузок. SVG считается изображением после проверки поддерживаемой статической структуры; скрипты, внешние ресурсы и неподдерживаемая разметка отклоняются. Для растровых изображений и операций с целым файлом действуют указанные в документации ограничения. Большие разрешённые файлы переносятся частями в собственном формате. См. Медиа и ограничения размера. | backend/src/lib/page-contract/media-kinds.ts. backend/src/lib/media/media-type.ts. backend/src/lib/media/duplicate.ts. backend/src/lib/media/media-settings.ts. backend/test/media-type.test.ts. backend/test/media-files.test.ts. backend/test/media-asset-renderers.test.ts. backend/test/management-api.test.ts. backend/test/plugin-operations.test.ts. backend/test/media-formats.test.ts. backend/test/media-stream-upload.test.ts |
| Редактирование изображений | Медиатека → Изменить → Обрезать и повернуть: повороты на четверть оборота и свободная обрезка указателем (угловые маркеры 44px), касанием или с клавиатуры (стрелки перемещают, Alt+стрелки меняют размер, R поворачивает, Enter сохраняет). Сохранить как копию загружает изменённые байты обычной загрузкой как новый файл со своим дайджестом, размером, габаритами и сведениями об использовании; он записывает edited_from, берёт теги, альтернативный текст и папку оригинала и сразу выбирается. Оригинал сохраняется. Заменить оригинал (за ⋯) сначала перечисляет, где используется изображение, затем выполняет обычную замену файла после одного подтверждения; сохранённая смена формата тоже является заменой и показывает ту же проверку. API управления и потоковая загрузка принимают edited_from; экспорт, импорт и копия сайта переносят его, перепривязывая к скопированному оригиналу или отбрасывая, если оригинала нет в пакете. |
frontend/src/lib/components/custom-ui/image-select/ImageEditor.svelte. frontend/src/lib/components/custom-ui/image-select/image-edit.ts. backend/src/lib/media/upload.ts. frontend/src/lib/components/custom-ui/image-select/ImageEditor.svelte.test.ts. backend/test/media-files.test.ts. backend/test/management-media.test.ts. frontend/cypress/e2e/journey-media-edit.cy.ts |
| Приватные медиа и права на медиа | Приватные файлы остаются в закрытом хранилище. Защищённый форматированный текст может содержать приватные изображения и вложения; при выдаче проверяются текущие права читателя. Именованные варианты изображений проходят ту же проверку. Публичные контексты отклоняют приватные файлы. Собственный формат переноса сохраняет приватность файлов и их вариантов. См. Приватные файлы. | backend/src/lib/media/private-storage.ts. backend/src/lib/media/visibility.ts. backend/src/lib/media/capability.ts. backend/src/lib/media/delivery.ts. backend/src/api/private-media.ts. backend/test/media-private.test.ts. backend/test/media-private-content.test.ts. backend/test/media-private-stream.test.ts. backend/test/media-private-transfer.test.ts. backend/test/media-private-protected.test.ts. backend/test/graduate-media-private.test.ts. backend/test/graduate-private-files-worker.test.ts. backend/test/graduate-private-files-node.test.ts. backend/test/media-private-placement-race.test.ts. backend/test/media-permissions.test.ts. frontend/cypress/e2e/journey-media-private.cy.ts |
| Внешние источники медиа | Удалённый плагин с правом media:source объявляет до четырёх media_sources. Общий диалог медиа тогда показывает Источники рядом с библиотекой (стрелки переключают, элементы управления 44px на телефонах): выберите источник, ищите, листайте через Показать ещё и нажмите Добавить у файла. Превью загружаются с origin плагина без referrer. Каждый источник объявляет modes (copy, reference или оба), а запрос добавления явно указывает один из них; режим не определяется автоматически (media_source_mode_unsupported, 422, для режима, которого источник не объявил). Источник с обоими режимами показывает Добавить и Привязать без копии; источник с одним режимом показывает одно действие. Добавление копии копирует файл в сайт: ядро снова читает элемент через обмен вызова, получает его с единственного origin плагина без учётных данных и без перенаправлений и применяет обычные правила загрузки (виды, форматы, лимиты размера сайта и проверку дубликатов). Новый файл сохраняет указание авторства и происхождение из источника (media.attribution: имя источника, автор, лицензия, ссылки, плагин, id источника и элемента), показанные в его деталях; опубликованный контент для копии никогда не загружается из плагина. Привязка (reference) ничего не загружает: у строки провайдер хранения external, её адрес — delivery_url элемента на одном из объявленных источником delivery_origins, у записи в реестре объектов нет контрольной суммы, а тот же плагин, источник и элемент подчиняются on_duplicate. Метаданные, теги, папка и кадрирование привязанного файла редактируются как у любого файла; его байты заменить или отредактировать нельзя (media_external_bytes_unavailable); удаление убирает только строку библиотеки, а правило «используется» сохраняется. Опубликованные страницы, публичное чтение, управляемый рендерер и кадр предпросмотра (чья политика изображений перечисляет origin доставки) используют URL доставки; выпуск и генерируемые сайты его не копируют, манифест медиа записывает его как external с URL как есть; копия сайта и история переносят идентичность, URL и метаданные без байтов, а импорт воссоздаёт привязку. Существующий контент продолжает отображать сохранённый URL, когда плагин удалён или отключён; детали сообщают, что источник недоступен, а новые привязки из него отклоняются. Состояния загрузки, пустое, офлайн, отказ, недоступность и права называются в диалоге. Просмотр требует media:upload и не аудируется. API управления, CLI и MCP предлагают listMediaSources, browseMediaSource и importMediaSourceItem (media:write). Экспорт, импорт и копия сайта переносят attribution в каждой строке медиа. Сохранение записывает в версию credit файла только для чтения ({ author, source, license, url }, никогда не id плагина, источника или элемента); публичное чтение (схема ответа r8) и сгенерированные сайты его отдают, а встроенные управляемый и сгенерированный рендереры показывают под изображением небольшую строку авторства. |
backend/src/lib/plugins/media-sources.ts. backend/src/lib/media/sources.ts. backend/src/lib/media/external.ts. backend/src/api/media-sources.ts. frontend/src/lib/components/custom-ui/image-select/MediaSources.svelte. plugins/media-source-reference. backend/test/media-sources.test.ts. backend/test/media-sources-reference.test.ts. backend/test/media-sources-reference-roundtrip.test.ts. backend/test/media-sources-bundle.test.ts. backend/test/media-credit.test.ts. frontend/src/lib/components/custom-ui/image-select/MediaSources.svelte.test.ts. frontend/cypress/e2e/journey-media-sources.cy.ts |
| Комментарии | Комментарии можно включить для любой публичной модели (content_model.settings.comments); модель постов блога — один из примеров. Ветка относится к одной языковой версии записи и допускает один уровень ответов. Комментарии посетителей поступают через Worker приёма в формате JSON или через обычную отправку формы, содержат ключ запроса и обязательное согласие и ожидают модерации. Email необязателен и хранится приватно. Если комментарии для модели выключены, на страницах её записей форма закрыта, новые комментарии не принимаются, а существующие скрыты от публичного чтения, но остаются в списке модерации. API модерации (/api/comments) позволяет просматривать, одобрять, отклонять, помечать как спам и удалять комментарии. Нужно право публикации; для удаления дополнительно нужно право удаления в модели записи, к которой относится комментарий. Если другой модератор уже изменил состояние комментария, возвращается конфликт. Операции не затрагивают другие сайты. Доступна одна реакция — helpful, не больше одной на пользователя и комментарий, только для одобренного комментария. Посетитель определяется по дайджесту с ключом, отдельному для каждого комментария; вошедший пользователь — по учётной записи, с проверкой права чтения модели. Публичная сортировка: oldest (по умолчанию), newest или best (сначала реакции helpful, затем возраст комментария, затем id); для каждого порядка используется собственный курсор страниц. API управления, CLI и MCP позволяют получать списки, читать и модерировать комментарии, публиковать импортированные комментарии, удалять их и включать или выключать комментарии для моделей. Копия сайта переносит состояние и email комментария; опубликованный комментарий после импорта ожидает модерации. Отмена импорта удаляет перенесённые комментарии. Управляемый рендерер и сгенерированный сайт показывают комментарии на страницах всех записей моделей, для которых они включены, и читают их при каждом запросе. |
backend/src/lib/comments/service.ts. backend/src/lib/comments/reactions.ts. backend/src/api/comments.ts. backend/test/comments-models.test.ts. backend/test/comments-route.test.ts. backend/test/comments-contract.test.ts. backend/test/comments-site-copy.test.ts. backend/test/blog-comments.test.ts. backend/test/graduate-site-comments.test.ts |
| Согласованные релизы (плагин Scheduling) | До 25 непересекающихся команд для записей и языков одного сайта выполняются одним атомарным пакетом CMS, в пределах допустимого числа SQL-запросов. Проверки учитывают итоговое состояние публикации. При отказе или откате ни один указатель опубликованной версии не меняется. Разные языки одной записи могут входить в один релиз. Scheduling отправляет одну команду с зафиксированными версиями, ревизиями процесса и ключом повтора; при неизвестном результате повторяет её без изменений. Доставка вебхуков, очистка кеша и автоматические перенаправления остаются отдельными результатами. Развёртывание и итоговая приёмка ещё предстоят. | backend/src/lib/lifecycle/service.ts; backend/test/coordinated-release-events.test.ts; plugins/scheduling/src/release-runner.ts |
| Политики плагинов, меняющие контент | Политика сохранения или восстановления может ответить transform, называя только поля. Преобразованный кандидат проходит ту же проверку, что и сохранение редактора (правила полей, канонический форматированный текст, связи, медиа, настройки поиска), и сохраняется с записью о том, какая политика что изменила; ответ, равный кандидату, — простое разрешение. Две политики, по-разному меняющие одно поле, отклоняются (plugin_policy_transform_conflict); поле, которого нет в модели, часть связи или медиа не в том поле, другая запись или transform в ответе политики для операции, которая не сохраняет предложенный контент, недопустимы (plugin_policy_transform_invalid). Отказ другой политики по-прежнему отклоняет операцию. Повторный ключ воспроизводит сохранённый преобразованный результат, не спрашивая снова. Transform при восстановлении может менять только значения и настройки поиска. Политики удаления вызываются перед корзиной и окончательным удалением, политики восстановления — при выходе из корзины, политики переходов — при каждом переходе, команде публикации плагина и импорте, который публикует, политики снятия с публикации — когда контент уходит офлайн. Сохранение через API управления отвечает преобразованием и перечисляет новые виды политик. |
backend/test/plugin-policy-transforms.test.ts. backend/test/plugin-policy-fixture.test.ts |
| Предпросмотр со ссылками на поля | Редактор открывает представление сайта рядом с полями. Текущий управляемый рендерер и поддерживаемые сгенерированные рендереры блога открывают обычное поле текста или форматированного текста поверх выбранного значения; изменения остаются в черновике до сохранения. Другие поля и старые рендереры переводят фокус на поле формы. Мост проверяет origin, источник, nonce, привязку поля и права записи; на опубликованных страницах моста редактора нет. См. Редактирование в предпросмотре. | backend/src/lib/page-contract/field-links.ts. backend/src/lib/field-link-runtime.ts. backend/test/preview-field-links.test.ts. frontend/src/routes/content/[id]/EntryPreviewPane.svelte.test.ts |
| Целостность API управления | Команды с ключом сначала занимают ключ: двойник, отправленный, пока выполняется первая, получает 409 idempotency_in_progress с Retry-After, а затем воспроизводит точный первый ответ (включая правку метаданных медиа); другое тело под тем же ключом — повторно использованный ключ; отклонённая команда освобождает ключ; занятие, запрос которого остановился, никогда не перехватывается просто потому, что прошло время: каждая команда решает его своим доказательством (один пакет с ответом, собственная запись или записанная позиция задания), иначе оно остаётся в ожидании. Изменения настроек и предложения для двойников пишутся и аудируются один раз. Часть импорта привязывается к SHA-256 своих первых байтов до сохранения чего-либо; другие байты для этой части отклоняются (import_part_conflict). Экспорт контента хранится как неизменяемые страницы, собирается в один файл с его дайджестом и размером, и только после этого страницы удаляются; два соревнующихся продвижения никогда не теряют и не удваивают страницу. Чтение идёт байтовыми диапазонами и ограничено, а хранимый файл, не совпадающий с записанным, отклоняется. |
backend/src/lib/management/keyed.ts. backend/src/lib/management/idempotency.ts. backend/test/management-integrity.test.ts. backend/test/management-idempotency.test.ts. backend/test/management-data-transfer-export.test.ts. backend/test/management-data-transfer.test.ts |
| Импорт WordPress | Страницы WordPress сохраняют заголовки, код, таблицы и длинный структурированный текст. Предоставленные источником дополнительные данные и поддерживаемые SEO-поля переносятся по явным сопоставлениям. Пропущенные и неподдерживаемые данные перечисляются. WXR и подключённый источник используют одну проверку импорта и квитанции. См. Импорт WordPress. | backend/src/lib/import/wordpress-models.ts. backend/src/lib/import/wordpress-acf.ts. backend/src/lib/import/wordpress-seo.ts. backend/test/import-wordpress-fidelity.test.ts. backend/test/import-wordpress-real-plugins.test.ts. backend/test/import-wordpress.test.ts. backend/test/import-wordpress-source.test.ts |
| Компоненты и одиночные типы Strapi | Компонент Strapi становится определением компонента, которым владеет сайт (слаг category-name); идентичное определение сайта используется повторно, а любой другой занявший слаг отклоняется по имени. Динамическая зона становится смешанным списком компонентов в порядке источника: id строк Strapi отбрасываются, а медиа и связи внутри пересопоставляются. Одиночный тип становится моделью с одной записью, каждый язык — своим локализованным компонентом. Определения планируются и проверяются до записи любого из них. Повторный импорт работает по правилу «создать или пропустить». Копия сайта переносит определения, пересопоставляет связи внутри них и отклоняет связь с записью, которую не несёт. Отмена удаляет определения, только когда их ничего не использует. Доказано на записанных архивах Strapi 4.25.9 и 5.51.1. |
backend/src/lib/import/strapi-components.ts. backend/test/import-strapi-components.test.ts |
| Папки медиа Strapi | Сущности plugin::upload.folder архива Strapi 4 или 5, их связи parent и связь folder каждого файла — единственный источник. Проверка перечисляет дерево с глубиной и числом файлов. Файл, у которого folderPath называет папку, но связи нет, импортируется без папки (Без папки) и попадает в отчёт (media_folder_link_missing); связь с папкой, которой нет в архиве, блокирует пробный запуск (media_folder_missing). Пробный запуск блокирует папку с непригодным именем (media_folder_name_invalid, 1–80 символов), отсутствующим родителем (media_folder_parent_missing), циклом (media_folder_cycle), глубиной больше 5 уровней (media_folder_too_deep), соседа, отличающегося только регистром (media_folder_name_collision), или превышение лимита в 500 папок (media_folders_limit), а также потомков заблокированной папки (media_folder_ancestor_blocked). Ничего не переименовывается и не переносится к другому родителю. Создаются только папки, нужные выбранным файлам, с их предками, через сервис папок библиотеки, по одной квитанции на каждую; папка с тем же путём переиспользуется без квитанции, повторный запуск ничего не создаёт. Каждый новый файл помещается в свою папку при создании. Отмена следует правилам копии сайта выше. Роли и права не меняются. Доказано на записанных архивах папок Strapi 4.25.9 и 5.51.1. |
backend/src/lib/import/strapi-folders.ts. backend/test/import-strapi-media-folders.test.ts. backend/test/fixtures/strapi/v5-folders |
| Перенос сайта с историей | Собственный формат .deewan (dee-wan-transfer, версия 1) переносит данные частями, при необходимости с историей, событиями workflow, корзиной, назначениями, метками авторов, папками и используемыми медиа. Проход экспорта обрабатывает не более 100 записей, подготовки — не более 80 записей, сборки — не более 32 МиБ; часть файла — 5 МиБ, запись — до 8 МиБ, архив — до 50 000 МиБ (52 428 800 000 байт). Проверяются контрольные суммы, сопоставления, закрытое хранение и квитанции. Прежние пределы 64 МиБ и 20 000 строк относятся к старым буферизованным форматам. См. Импорт и миграция. |
backend/src/lib/export/site-history.ts. backend/src/lib/import/bundle-history-handlers.ts. backend/src/lib/lifecycle/imported-history.ts. backend/test/site-history-transfer.test.ts. backend/test/principal-mapping.test.ts |
| Публичные фильтры по полям | Поддерживается равенство для полей text, select, boolean, number и date. Поддерживаются gt и lt (исключающие границы) и gte и lte (включающие) для полей number и date. Принимается один sort и выборочные fields. Принимается не больше 4 фильтруемых полей со значениями до 255 символов. Повтор filter[field]= даёт до 10 вариантов для одного поля: ИЛИ внутри поля, И между полями. expand= называет до 3 полей связей, на один уровень: каждая запись несёт до 25 опубликованных и доступных для чтения целей на поле, а GET /v1/_related/:model листает остальные курсором, привязанным к источнику, полю, языку и токену. API отклоняет запросы, превышающие эти ограничения, и не сокращает их автоматически. Курсоры несут свой запрос и отклоняются при любом другом запросе. Предел числа строк на D1 не заявляется и не измерялся. Для точек доступны точный радиус и порядок по расстоянию Хаверсина, а также прямоугольные области с включёнными границами, в том числе через линию перемены дат. Права, счётчики и курсоры проверяются тем же способом. |
backend/src/public/index.ts. backend/src/lib/public-read/relation-expansion.ts. backend/test/public-field-filters.test.ts. backend/test/public-filter-alternatives.test.ts. backend/test/public-relation-expand.test.ts |
Что CMS принимает, но генерация отклоняет
Заголовок раздела «Что CMS принимает, но генерация отклоняет»Генерация проверяет каждую модель и отклоняет перечисленные ниже структуры с указанием причины. В разделе Настройки → Код и публикация → Дополнительно — вебхук сборки и развёртывание → Standalone-бэкенд они теперь перечислены до сборки (GET /api/sites/:siteId/generation-readiness). Отказы уровня контента тоже видны до сборки: GET /api/sites/:siteId/generation-readiness/content проходит опубликованный контент ограниченными страницами и называет то, что отвергнет сборка. Окончательная проверка конфликтов в пределах всего сайта выполняется при сборке.
| Отказ | Почему CMS это приняла |
|---|---|
relation_type_unsupported |
Вид связи вне четырёх. Достижимо через API или импорт. |
relation_bounds_unsatisfiable |
Лимиты выбора, которым не может удовлетворить ни один список, например не меньше 3 и не больше 1. |
relation_required_cycle |
Обязательные связи к одному, ведущие обратно к своей модели. Первую запись создать невозможно. |
ambiguous_relation, ambiguous_component |
Связь или компонент без цели можно сохранить. |
unsupported_field_type |
Сохранение принимает любое имя типа. Достижимо через API или импорт. |
invalid_field_config, empty_model |
Сохранение не проверяет JSON конфигурации поля и не требует ни одного поля. |
column_name_collision, model_name_collision |
Редактор резервирует только id, created_at и updated_at. Slug вроде blog-post и blog_post конфликтуют. |
relation_target_absent |
Цель связи может указывать на модель вне сайта. |
relation_arity_unsupported, relation_target_model_mismatch, duplicate_row_identity |
Уровень контента: строки, записанные до проверок связей в CMS, или импортом. |
required_value_missing, unsupported_value_type, unpublished_reference |
Уровень контента: импорт и контент, сохранённый до того, как правила полей стали проверяться на сервере. |
Доказательство: backend/src/lib/graduate/preflight.ts. backend/test/graduate-preflight.test.ts. backend/test/generation-readiness-route.test.ts. backend/test/graduate-plan.test.ts.
Rich text
Заголовок раздела «Rich text»Поле rich text хранит один форматированный документ как канонический HTML, до 256 KiB: абзацы, заголовки, списки, цитаты, блоки кода, горизонтальные линии, жирный, курсив, подчёркивание, зачёркивание, inline-код, нижний и верхний индекс, ссылки, изображения, таблицы (до 12 столбцов и 200 строк, с необязательными строкой заголовков, заголовками строк и подписью), галереи изображений (2–24 изображения из библиотеки), блоки аудио, видео и файлов, ссылающиеся на медиа библиотеки, и встраивания, сохранённые как id провайдера, канонический https-адрес и название: YouTube и Vimeo встроены, плюс до 20 точных https-источников, которые администратор разрешил в «Настройки → Интеграции» (источники встраиваний). Каждая запись (сохранение в редакторе, восстановление, management API, плагины, импорт таблиц, копия сайта) читает значение строго и отклоняет всё остальное с указанием поля, языка, элемента или атрибута, никогда не повторяя содержимое. Блок галереи, аудио, видео или файла должен ссылаться на медиаэлемент этого сайта нужного вида. Импорт из Strapi преобразует Markdown (включая таблицы GFM) в тот же HTML, сохраняет сырой HTML лишь в той мере, в какой его вмещает документ, и сообщает о выброшенном находкой rich_markup_dropped.
Активный плагин может объявить rich_blocks: закрытый набор элементов управления (text, textarea, rich, number, boolean, select, url, media, relation и повторяемые группы; до 3 уровней вложенности и 50 элементов) с правилами, декларативным деревом отрисовки и шагами миграции; без HTML, CSS и скриптов. Блок хранится как <figure data-block="plugin" data-type data-version data-values>. Сохранение отклоняет добавленный блок, который не объявляет ни один активный плагин (rich_block_unregistered), более новую версию (rich_block_version_unknown) и значения, которые отклоняет его объявление (rich_block_values_invalid); более старая версия принимается, если её мигрированные значения проходят проверку, а неизменённый блок сохраняется после удаления его плагина. Его медиа учитываются в закреплениях версии, а его слова попадают в поиск. GET /v1/_rich-blocks отдаёт активные определения и перестаёт их отдавать, когда плагин отключён. Адаптер отрисовки хоста экранирует каждое связанное значение, отклоняет небезопасные адреса, мигрирует старые блоки, ничего не рисует публично для блока, который не рисует ни один адаптер, и рисует именованную заглушку в предпросмотре. Копия сайта сохраняет версии блоков и пересопоставляет ID медиа и связей через подсчитанный список rich-relations.json с контрольной суммой. Копия и перенос истории требуют подходящего исходного объявления; при export_rich_relation_schema_unavailable восстановите его и повторите экспорт. Установки плагинов и выданные им права не переносятся на целевой сайт. Объявление с inline: true сохраняет структурированный строчный элемент как <span data-block="plugin"> внутри абзаца. Он использует те же поля, версии, обработку ссылок и правила рендеринга.
| Где показывается rich text | Что его защищает |
|---|---|
| Редактор rich text в админке | Редактор выдаёт канонический документ. Он создаёт абзацы, заголовки 1–6, жирный, курсив, подчёркивание, зачёркивание, inline-код, нижний и верхний индекс, блоки кода, цитаты, списки, горизонтальные линии, переносы строк, ссылки, встроенные изображения, таблицы (до 12 столбцов и 200 строк тела, строка заголовков, заголовки строк, подпись), галереи из 2–24 изображений библиотеки, блоки файлов, аудио и видео из медиабиблиотеки и встраивания по вставленной ссылке YouTube, Vimeo или разрешённого https-источника — в виде заглушек, у которых «Просмотр» открывает новую вкладку. Выравнивание абзацев и заголовков влево, по центру, вправо и по ширине сохраняется в канонических данных. Vendor snippet выполняется в отдельном изолированном фрейме; произвольные фреймы, стили и классы родительского документа отклоняются. Всё, что в нём есть, но чего документ вместить не может, удаляется до сохранения, а сервер отклоняет всё остальное. |
| Генерируемый сайт на Astro и управляемый рендерер | Секции страниц не содержат разметки: контракт страницы её отвергает. Абзац может нести структурированные фрагменты с жирным, курсивом, ссылкой и списком (встроенное форматирование), и Astro экранирует их текст. Тела записей блога и краткие описания «Последние записи» рендерит функция контракта страницы renderRichDocument (backend/src/lib/page-contract/rich-document.ts), одна и та же в обоих рендерерах. Таблицы прокручиваются в собственном блоке, галереи — сетка, аудио и видео проигрываются со ссылкой для скачивания, а встраивание — это один фрейм, которым владеет CMS (sandbox, список allow, политика referrer, ленивая загрузка, 16:9), с youtube-nocookie.com, player.vimeo.com или источника, который сайт разрешает сейчас (GET /v1/_embeds); встраивание из неразрешённого источника рисуется простой ссылкой. frame-src страницы управляемого сайта называет только два плеера и разрешённые источники. |
| Режим «Структура» в редакторе страниц | Краткие описания «Последние записи» показываются как текст, полученный из того же документа. |
| Публичный API CMS, API генерируемого бэкенда и TypeScript-клиент | Проверенный канонический HTML. Медиаблоки и встраивания — это данные (<figure data-block=…>), а не плееры: рендерите значение через renderRichDocument или собственный рендерер того же документа. Передайте источники из GET /v1/_embeds, чтобы показать фрейм настроенного встраивания. |
Доказательство: backend/test/rich-document-contract.test.ts. backend/test/rich-plugin-blocks.test.ts. backend/test/rich-plugin-blocks-bundle.test.ts. backend/test/rich-document-render.test.ts. backend/test/rich-renderer-parity.test.ts. backend/test/rich-field-contract.test.ts. backend/test/rich-blocks-bundle.test.ts. backend/test/version-media-pins.test.ts. backend/test/rich-text-boundaries.test.ts. backend/test/import-strapi-markdown.test.ts. frontend/src/lib/components/custom-ui/rich-editor/rich-editor-adversarial.svelte.test.ts. backend/test/graduate-site-build.test.ts (страница экранирования).
Нет в 1.0
Заголовок раздела «Нет в 1.0»Публичные фильтры по произвольным полям связанных элементов, $or, полнотекстовый поиск и несколько исполнителей процесса для всей записи недоступны. В обычном списке доступны условия по настроенному скалярному полю отображения связи внутри сайта; см. дополнения ниже. Решающий источник — граница продукта 1.0.
Возможности, добавленные в октябре
Заголовок раздела «Возможности, добавленные в октябре»Эти изменения есть в текущем коде и прошли целевые локальные проверки. Итоговые браузерные и визуальные проверки ещё предстоят; это не приёмка продакшена. Код папок записей есть и прошёл целевые проверки служб/переноса; итоговая интеграционная приёмка ещё предстоит. Папки моделей выше — отдельная возможность.
| Возможность | Поведение текущего кода | Доказательство |
|---|---|---|
| Операции плагинов с контентом и медиа | Делегированное чтение записи/версий, обычные создание/обновление/корзина, типизированные связи и метаданные/байты медиа используют службы жизненного цикла и текущие права человека. | backend/test/plugin-operations.test.ts; backend/test/management-plugin-operations.test.ts |
| Группы фильтров и контекстный выбор | Четыре конечных условия, И/ИЛИ, три уровня групп; единая семантика списка, счётчиков, представлений и массовых действий. Связи и варианты select могут зависеть от соседних полей, пользователя и записи; сохранение перепроверяет зависимости. | backend/src/lib/content-fields/contract.ts; backend/test/content-field-query.test.ts; backend/test/contextual-choices.test.ts |
| Форматированный контент | Выравнивание абзацев/заголовков; связи и повторяемые группы плагинов; типизированные vendor snippet с HTML/CSS/JavaScript выполняются в изолированных фреймах админки и обоих рендереров. | backend/test/rich-editorial-completion.test.ts; backend/test/rich-plugin-blocks.test.ts; backend/test/rich-snippets-route.test.ts |
| Обслуживание аккаунта | Новый адрес подтверждается до смены email того же аккаунта; сохраняются идентичность, членство, способы входа и история, сессии завершаются. Запросы сброса используют короткоживущие одноразовые почтовые подтверждения и одинаковые ответы для известных/неизвестных адресов. | backend/test/account-maintenance.test.ts |
| Обновления и интерфейс плагина | Сравнение манифеста, версии и прав перед обновлением; новые права не выдаются. Проверенные credentialless-фреймы origin плагина используют объявленные операции и привязанный канал без учётных данных админки. | backend/test/plugin-schema.test.ts; backend/test/plugin-ui-extensions.test.ts |
| Области записей | Ограниченные правила роли по модели для read/create/update/delete с контекстом пользователя. Списки, счётчики, связанные чтения, версии, экспорт, массовый выбор совпадений и атомарные записи применяют текущие правила вместе с правами модели, владельца, полей и языков. | backend/src/lib/document-rules.ts; backend/test/document-scopes.test.ts |
| Структурированные таблицы | CSV/XLSX поддерживают создание, обновление и создание либо обновление через общий механизм проверки JSON-коллекций. По умолчанию — обновление; сопоставление по ID записи или подходящему уникальному полю. До применения видны новые записи, изменения и отказы. Структурированные связи, медиа и вложенные компоненты проходят обычные проверки прав, значений, структуры модели и версий. Пустая структурированная ячейка сохраняет значение; JSON null очищает необязательное поле. | backend/test/spreadsheet-scope.test.ts; backend/test/spreadsheet-collection.test.ts; backend/test/spreadsheet-xlsx-import.test.ts |
| Подключённый WordPress и настройки | Ограниченная загрузка HTTPS REST того же origin создаёт обычный WXR для прежнего импорта. Явная замена охватывает название, слоган, логотип, иконку, SEO-описание и настройку индексации из источника, с проверкой ревизии целевого сайта, квитанцией и отменой только принадлежащих импорту изменений. | backend/test/wordpress-connect.test.ts; backend/src/lib/import/wordpress-site-configuration.ts; backend/src/lib/import/site-configuration-carry.ts |
| Помощь AI | Vision-модели получают доступные пользователю байты изображения; автор проверяет alt и подпись до сохранения. После исходного сохранения обновляются только выбранные переводы с явным согласием на замену, включая ручные значения. | backend/test/ai-media-descriptions.test.ts; frontend/src/lib/ai/translate-saved.test.ts |
| Папки записей | Вложенные папки сайта, одно размещение группы переводов, список/счётчики в пределах доступа и отдельная история перемещений. Размещение не создаёт версию контента. Копия/история пересопоставляет размещение с квитанциями и отменой. | backend/src/lib/entry-folders.ts; backend/src/lib/entry-folder-transfer.ts; backend/test/entry-folders.test.ts |
Дополнения от 17 октября: интеграции и работа редактора
Заголовок раздела «Дополнения от 17 октября: интеграции и работа редактора»Перечисленные возможности есть в исходниках. Проверки их совместной работы для текущего состояния кода ещё не завершены. Ссылки на тесты показывают покрытие, а не подтверждают успешный прогон.
| Возможность | Что добавлено | Исходники |
|---|---|---|
| События вебхуков | Успешные изменения контента, этапов работы, релизов, файлов и моделей ставят уведомления в существующую очередь. Отказ и повтор уже выполненной команды не создают новое событие. | backend/src/lib/outbound-events.ts; backend/src/lib/lifecycle/events.ts |
| Связи компонентов с несколькими моделями | Вложенные, повторяемые и смешанные компоненты сохраняют модель и идентификатор выбранной записи при сохранении, восстановлении, переносе и рендеринге. | backend/src/lib/component-relations.ts |
| Страницы плагинов | Объявленные страницы, пункты навигации и блоки на главной используют изолированный фрейм плагина и операции с проверкой прав. | backend/src/lib/plugins/ui-surfaces.ts |
| Работа с формами | Просмотр сохранённых ответов по правам доступа, отдельные назначения для форм, текстовые шаблоны и подтверждения с согласия отправителя связаны с прежними сроками хранения, удалением и повторами доставки. | backend/src/api/forms.ts; backend/src/lib/forms/delivery.ts |
| Медиатека и поля файлов | Прямой доступ к библиотеке; типизированные поля файлов, закрытые по умолчанию, собственная история и явное включение в публичный API. | backend/src/lib/media/asset-metadata.ts |
| Порядок коллекции | Необязательный порядок общий для всех языков. Перемещение относительно видимых записей сохраняет взаимный порядок скрытых. Публичный клиент запрашивает его через order=manual. |
backend/src/lib/manual-order.ts |
| Службы CMS для плагинов | Доступ к комментариям и модерации, перенаправлениям, восстановлению из корзины, удалению неиспользуемых файлов и одобренным почтовым шаблонам сохраняет обычные проверки соответствующих служб. | backend/src/api/plugin-v1-services.ts; backend/src/api/plugin-v1-mail.ts |
| Метаданные от плагинов | Объявленные метатеги, ссылки и JSON-LD хранятся с версией контента. Основные SEO-настройки имеют приоритет; при рендеринге запрос к плагину не нужен. | backend/src/lib/plugins/renderer-metadata.ts; backend/src/lib/seo/head-runtime.ts |
| Структурированные элементы внутри текста | Плагин может объявить строчный элемент. Его данные остаются внутри абзаца и проходят обычную обработку версий, ссылок и рендеринга. | backend/src/lib/page-contract/rich-document.ts |
| Опубликованная иерархия | Цепочка предков и хлебные крошки используют опубликованные названия, принятые маршруты и языковые правила сайта. Черновик не подменяет опубликованное название. | backend/src/lib/public-read/published-list.ts |
| Стартовый проект Astro | Команда создаёт исходники, которыми владеет разработчик, с подключением к актуальному публичному API и текущими контрактами страниц и рендерера. | backend/src/lib/managed-renderer/reference-starter.ts |
| Поддерживаемый релиз | Ещё нужны релиз с тегом, развёртывание Scheduling и реальные проверки публикации, предпросмотра, медиа, почты и восстановления на Cloudflare. Одних исходников для приёмки недостаточно. | Границы продукта |
Эти дополнения включают загрузку файлов через формы и выбор получателя письма плагина по записи. Файлы хранятся закрыто: скачать их можно только с правом просмотра ответа, а срок хранения и удаление совпадают с правилами формы. В утверждённом шаблоне можно выбрать автора записи или назначенного проверяющего. Проверки работы этих возможностей и приёмка с реальными сервисами ещё не завершены.
Возможности текущей интеграционной версии
Заголовок раздела «Возможности текущей интеграционной версии»Эти возможности реализованы в коде ещё не выпущенной интеграционной версии. Полный браузерный прогон, проверка живого интерфейса на телефоне и компьютере и приёмка внешних сервисов остаются отдельными требованиями.
- Перенос сайта и истории частями поддерживает большие разрешённые медиафайлы, контрольные суммы и продолжение.
- Страницы WordPress сохраняют заголовки, код, таблицы, длинный текст и явно сопоставленные данные источника.
- Защищённый форматированный текст поддерживает приватные изображения, вложения и варианты изображений с проверкой доступа.
- Адреса сохраняют буквы, цифры и комбинируемые знаки Unicode и автоматически создаются из заголовков на родном языке.
- Именованное уникальное сочетание объединяет от двух до четырёх полей; запись проверяется атомарно.
- Метаданные блога могут использовать текущие сведения об авторах, категориях и тегах; режим снимка остаётся явным выбором.
- Обычные и смешанные компоненты можно вывести из использования, сохранить прежнюю структуру и обновлять экземпляры по своему решению.
- Блоки плагинов поддерживают вложенные поля форматированного текста с ограниченной глубиной.
- Обычные столбцы связей показывают доступные подписи, количество и связанные значения.
- Типизированные условия видимости объединяются в группы И/ИЛИ: до четырёх условий и трёх уровней.
- Необязательное переводимое поле может брать отсутствующее значение из опубликованного резервного языка; намеренно пустое значение остаётся пустым. См. Резервный язык для поля.
- Проверенный статический SVG доступен как изображение.
- Сгенерированное приложение поддерживает до 32 именованных ролей с правами на операции моделей, обычной ролью при регистрации и прежними ролями admin/user.
- DeepInfra остаётся провайдером ИИ по умолчанию; установка может выбрать совместимый с OpenAI адрес и явные списки моделей текста, изображений и распознавания изображений.
- Предпросмотр связанных черновиков показывает сохранённые связанные черновики в пределах прав читателя; публичное чтение по-прежнему требует публикации.
- Поиск контента ищет по сохранённому форматированному тексту, включая черновики, и показывает совпавшее поле и короткий фрагмент с учётом прав доступа.
- Адреса сайтов для браузерных запросов задают CORS, предварительные запросы и разрешённые заголовки публичного API отдельно для каждого сайта.
- Замена медиа через API управления заменяет файл через существующий сервис, сохраняя ID медиа и ссылки на него.
- Управление аккаунтами автономного приложения позволяет создавать аккаунты с выбранной ролью и удалять аккаунты без связанных данных. Сессии отзываются; последнего администратора удалить нельзя.
- Редактирование в предпросмотре открывает обычные поля текста и форматированного текста поверх поддерживаемого представления. Старые рендереры по-прежнему переводят фокус на поле формы.
- Импорт из WordPress сохраняет проверенные группы и повторители ACF как редактируемый JSON. Для будущих публикаций явно выбирают расписание или сохранение намерения в черновике.
- Обновление выбранного контента заранее показывает добавления, изменения и удаления в заполненных моделях, сохраняет исключённые данные и старые копии медиа и отклоняет конфликтующие правки. Задание можно продолжить или остановить; автоматической отмены замены нет.
Дополнения выдачи
Заголовок раздела «Дополнения выдачи»| Capability | Source contract |
|---|---|
| Запросы по полю отображения связи | Настроенное скалярное поле; сохранённые значения и права цели; общий SQL списка, количества, view и bulk. Для межсайтового поля: field_shared_display_unsupported. Проверки ID и пустоты сохраняются. |
| Оригиналы по языкам | Один ID медиа, текущий файл каждого языка, резервные языки и неизменные старые файлы версий. Редактор, API управления и перенос сохраняют языки. |
| Явные общие ссылки | Опубликованная публичная проекция исходника, текущая ревизия разрешения и проверка базы источника при каждом ответе. Отзыв и снятие с публикации дают null. |
| Пространственные запросы | Строгие широта/долгота; точный радиус и расстояние Хаверсина в SQL, включительные границы через антимеридиан и связанные курсоры. |
| Инкрементальная синхронизация | Разрешённые опубликованные записи и публичные медиа; постраничный снимок, упорядоченные изменения и удаления, подписанный курсор и явный сброс. |
backend/test/delivery-query.test.ts; delivery-originals.test.ts; delivery-shared-content.test.ts; delivery-sync.test.ts; delivery-sync-triggers.test.ts.
Стабильная версия ещё не выпущена (pre-MVP)
Коммит исходного кода: fe23db481c422b40e317b92fc98116b4c0c26a87 (с локальными изменениями)
Дата сборки (UTC): 2026-10-18T09:22:34.630Z
Коммит для обновления: не определён: в рабочей копии есть изменения. Примечания к выпуску