Для человека
Правила для ИИ-агента
Чтобы ChatGPT, Cursor или другой ИИ писал материалы в синтаксисе Демонстратора — скачайте пакет правил и отдайте его агенту.
Как пользоваться
В Cursor / Claude Code / VS Code
- Распакуйте zip в правила проекта (Cursor Skills,
.cursor/rules,AGENTS.md— как принято у вас). - В личном кабинете возьмите API-токен.
- Подключите MCP по конфигу ниже.
{
"mcpServers": {
"demonstrator-mcp": {
"url": "https://app.longread.agency/mcp/demonstrator/mcp",
"headers": {
"Authorization": "Bearer <токен>"
}
}
}
}
/mcp/demonstrator/mcp (не /sse). Без токена подключение не сработает.В ChatGPT / Gemini / NotebookLM
- Загрузите файлы из zip в Knowledge / файлы проекта / вложение чата.
- Попросите написать материал.
- Скопируйте готовый markdown в редактор Демонстратора.
Дополнительно (не обязательно)
Word/PowerPoint и загрузка медиа с диска — отдельный zip.
Для агента и даты файлов
Primary-источник правил — raw .md на этом origin: начни с SKILL.md и README.md, затем 01–06. Вкладки ниже — зеркало. Не меняй план без подтверждения пользователя. Без MCP — отдай готовый markdown, не вызывай API; медиа — CDN-заполнитель или URL от пользователя.
Даты обновления
Сравни updated: в своих копиях с таблицей. Если старше — заново возьми zip или raw .md. Не сравнивай по mtime.
Основной пакет
| Файл | Обновлён |
|---|---|
| README.md | updated: 2026-08-16 14:20 GMT+3 |
| SKILL.md | updated: 2026-08-16 14:20 GMT+3 |
| 01-syntax.md | updated: 2026-08-10 17:40 GMT+3 |
| 02-methodology.md | updated: 2026-08-10 17:40 GMT+3 |
| 03-mcp-tools.md | updated: 2026-08-17 13:33 GMT+3 |
| 04-css-generation.md | updated: 2026-08-11 03:40 GMT+3 |
| 05-html-widget-rules.md | updated: 2026-08-10 17:55 GMT+3 |
| 06-russian-typography.md | updated: 2026-08-06 17:35 GMT+3 |
Помощники (отдельный zip)
| Файл | Обновлён |
|---|---|
| helpers/README.md | updated: 2026-08-07 11:45 GMT+3 |
| helpers/source-cleanup.md | updated: 2026-08-15 16:20 GMT+3 |
| helpers/upload-local-media.ps1 | mtime: 2026-08-15 23:02:27 MSK |
Страница собрана: 2026-08-17 13:36:08 MSK
Синтаксис Демонстратора
Материал — один Markdown-файл. Интерактив — блоки :::тип … :::/тип.
Жёсткие правила
1. Открытие: :::имя в начале строки. Закрытие: только :::/имя в начале строки. Для каждого открытого блока — своё закрытие. Без исключений. 2. URL и текст тела — со следующей строки после :::имя. На строке :::имя — только имя, вариант и модификаторы. 3. Не выдумывай имена, варианты и модификаторы вне таблицы ниже. 4. %% в начале строки — комментарий. При рендере отбрасывается. 5. GFM-таблицы и автолинки поддерживаются. 6. Блок многострочный: :::имя, пункты ===, тело и :::/имя — отдельные строки. Не склеивай в одну строку (:::steps === … === … :::/steps — запрещено). 7. Смысл строки === зависит от блока (см. таблицу ниже). Не копируй паттерн === Заголовок из acc/steps в columns. 8. :::important — короткая врезка (обычно несколько предложений). Не оборачивай в important весь урок, статью или файл с # заголовками и несколькими разделами. 9. Русский текст — по 06-russian-typography.md: тире —, кавычки «ёлочки», многоточие …; не - и не "..." в прозе.
Строка === по типу блока
| Блоки | Строка === | Заголовок пункта / колонки |
|---|---|---|
acc, tab/tabs, flip, steps, compare, checklist | === Текст — текст обязателен | Текст на той же строке после === |
columns | пустой === — только разделитель колонок | Заголовок колонки — только ### внутри колонки |
test | === (часто пустой) или с текстом вопроса — по канону test | Не путай с заголовком колонок |
Если в превью видны сырые === или обрывки :::/… — сломан разделитель или fence: чаще всего === Заголовок внутри :::columns или незакрытый блок.
Полная таблица блоков
| Блок | Варианты (после имени) | Модификаторы | Размеры на строке ::: | Закрытие |
|---|---|---|---|---|
acc | — | — | — | :::/acc |
tab / tabs | — | — | — | :::/tab |
flip | — | — | — | :::/flip |
test | — | — | — | :::/test |
steps | grid | — | — | :::/steps |
checklist | duo | — | — | :::/checklist |
compare | — | — | — | :::/compare |
button | group | — | — | :::/button |
important | idea, problem, solution, info, rule | — | — | :::/important |
tmp | video, sim, dialog, hotspot, hint | — | — | :::/tmp |
embed | — | wide | — (размеры в теле) | :::/embed |
audio | — | — | — | :::/audio |
video | — | wide | WxH (напр. 960x540) | :::/video |
file | — | — | — | :::/file |
web_archive | — | wide | WxH | :::/web_archive |
ai_widget | — | wide | WxH | :::/ai_widget |
picfont | chat, right | — | — | :::/picfont |
slider | duo | wide | — | :::/slider |
hotspot | — | wide | — | :::/hotspot |
columns | — | wide | — | :::/columns |
Пиши :::columns … :::/columns.
:::comments — служебный. Не создавай вручную.
Картинка в обычном Markdown — . Не оборачивай в  URL внутри блоков, где синтаксис требует голый URL (:::audio / :::video / :::picfont / аватары в :::picfont chat и т.п.). Исключение: кадры :::slider и фон :::hotspot — там как раз .
Вложенность
- Внутри
:::acc,:::tab,:::flip,:::steps,:::compareразрешён один уровень вложенных блоков:::или картинок. - Внутри пункта
:::hotspotпосле координат — только плоский Markdown. Вложенные:::запрещены. - Не вкладывай блок того же типа в себя (
:::accвнутри:::acc). - Колонки глубже одного уровня не используй.
Заголовки
Только ATX уровней 1–3:
Заголовок H1
Заголовок H2
Заголовок H3
H4–H6 и setext не используй.
Текст
| Назначение | Синтаксис | Запрещено |
|---|---|---|
| Жирный | **текст** или __текст__ | — |
| Курсив | *текст* или _текст_ | — |
| Код | ` код ` | — |
| Ссылка | [текст](URL) | — |
| Картинка |  | [текст](URL) без ! — это ссылка, не картинка |
Списки: - пункт или * пункт; нумерованные 1. 2.. Маркер + в обычном списке не используй (в чеклисте + — отдельный синтаксис).
Цитата: >, >>, >>> в начале строки.
Разделитель: строка из минимум трёх дефисов ---.
Изображение
Канон:
- Текст в
![…]— подпись под картинкой (figcaption). - Пиши осмысленную подпись. Пустые
ипарсер принимает, но для обычных картинок и хотспотов не используй. - HTTPS-URL с любого хоста для markdown-картинок разрешены. Для демо бери CDN ниже. Без MCP: CDN-заполнитель или URL от пользователя.
Важное
Краткая форма (одна строка):
!!! Текст замечания.
Блочная форма:
:::important
Текст замечания.
:::/important
С вариантом:
:::important rule
Не храните пароли в чате.
:::/important
| Вариант | Когда | Что рисует система |
|---|---|---|
| *(без)* | Замечание | Иконка info, серый фон |
idea | Совет | Лампочка, жёлтый фон |
problem | Ошибка | Треугольник, красный фон |
solution | Решение | Галочка, зелёный фон |
info | Справка | Info, синий фон |
rule | Норма / принцип | Параграф §, акцентный фон |
Иконку система добавляет сама. Не вставляй эмодзи и символы иконок в текст блока.
Не используй important как обёртку всего материала. Каркас урока — обычный Markdown (# / ## / абзацы); important — точечный акцент внутри.
Медиа: единый формат тела
| Блок | Строка 1 тела | Строка 2+ | Пример открытия |
|---|---|---|---|
audio | URL | подпись (необязательно) | :::audio |
video | URL | подпись (необязательно) | :::video или :::video wide или :::video 960x540 |
file | URL | имя файла (обязательно для подписи) | :::file |
web_archive | URL | подпись (необязательно) | :::web_archive / wide / WxH |
ai_widget | URL | подпись (необязательно) | :::ai_widget / wide / WxH |
embed | URL === WxH одной строкой | — | :::embed или :::embed wide |
picfont | URL | текст рядом | :::picfont или :::picfont right |
picfont chat | после === Имя — голый URL аватара, затем текст реплики | — | :::picfont chat |
:::audio
https://cdn.longread.agency/demo/audio-sample.mp3
:::/audio
:::video
https://cdn.longread.agency/demo/video-sample.mp4
:::/video
:::file
https://cdn.longread.agency/demo/file-demo.pdf
Раздаточный материал
:::/file
:::web_archive
https://cdn.longread.agency/demo/archive-demo/index.html
:::/web_archive
:::ai_widget
https://cdn.longread.agency/demo/widget-demo.html
:::/ai_widget
Имя блока — ai_widget (не ai-widget). Голый URL HTML в абзаце или «ссылка в чат» не заменяют блок.
ИИ-HTML через MCP: create-widget → save-widget-draft → bake-widget → insert-ai-widget-block (fence = id) → poll archives/ по маркеру. Не сдавай по draft. Без вставки в материал задача не сдана. Правила — 05-html-widget-rules.md. Внешние картинки без расширения файла на CDN блокируются при bake.
:::embed
https://longread.media === 640x360
:::/embed
По умолчанию для embed без размеров в теле — 640×360.
:::picfont
https://cdn.longread.agency/demo/picfont-icon.webp
Короткий факт рядом с картинкой.
:::/picfont
:::picfont chat
=== Ассистент
https://cdn.longread.agency/demo/avatar-bot.webp
Ответ ассистента.
=== Пользователь
https://cdn.longread.agency/demo/avatar-user.webp
Вопрос пользователя.
:::/picfont
В :::picfont chat у каждой реплики:
1. === Имя (имя собеседника) 2. Следующая строка — только голый HTTPS-URL аватара (без !, без […], без подписи) 3. Далее текст сообщения
Нельзя (ломает чат — аватар становится огромной картинкой в пузыре):
=== Новичок

Текст реплики.
Нужно:
=== Новичок
https://cdn.longread.agency/images/….webp
Текст реплики.
При замене локальных путей / CDN-URL внутри :::picfont chat меняй только строку URL. Не превращай её в markdown-картинку .
Обычный :::picfont (не chat): тоже голый URL на первой строке тела, текст — со второй. Не .
Слайдер
:::slider
=== Кадр 1

Текст первого кадра.
=== Кадр 2

Текст второго кадра.
:::/slider
:::slider duo
=== Было

Интерфейс до изменений.
=== Стало

Новый интерфейс.
:::/slider
:::slider wide и :::slider duo wide допустимы.
Интерактивы
Аккордеон
:::acc
=== Заголовок пункта
Содержимое пункта.
=== Второй заголовок
Содержимое второго пункта.
:::/acc
Вкладки
:::tab
=== Вкладка 1
Содержимое первой вкладки.
=== Вкладка 2
Содержимое второй вкладки.
:::/tab
Карточки flip
:::flip
=== Термин
Определение на обороте.
=== Второй термин
Второй ответ.
:::/flip
Допустимо однострочно: лицо === оборот.
Хотспот
:::hotspot

=== 30% 40% Зона A
Пояснение зоны A.
=== 1 70% 55% Зона B
Маркер с цифрой 1.
:::/hotspot
=== 30% 40% Имя— точка.=== 1 70% 55% Имя— маркер с цифрой.- Координаты — проценты от левого верхнего угла.
- Без строк
===блок = обычное изображение.
Кнопка
Кнопки — только внешние ссылки. Не используй их как навигацию по материалу или проекту: у экспорта проекта есть встроенный плеер.
:::button
Подробнее на Longread.Media === https://longread.media
:::/button
:::button group
=== Longread.Media === https://longread.media
=== Ecourse.Studio === https://ecourse.studio
:::/button
Шаги
:::steps
=== Откройте файл
Дважды щёлкните по ярлыку.
=== Сохраните файл
Ctrl+S.
:::/steps
:::steps — столбец со стрелками ↓. :::steps grid — сетка без стрелок.
Чеклист
Маркер в начале строки задаёт тон пункта:
| Маркер | Вид | Когда |
|---|---|---|
+ | Зелёная галочка ✓ | Плюс, возможность, «как надо», факт-преимущество |
- | Красный крест ✗ | Минус, ошибка, «как не надо» |
Список возможностей / фич / преимуществ — только +. Не ставь - «по привычке списка»: - в чеклисте = негатив.
:::checklist
+ Правильный пункт
- Неправильный пункт
Пояснение без маркера
:::/checklist
:::checklist duo
=== + Правильно
Верный подход.
=== - Неправильно
Ошибочный подход.
:::/checklist
Сравнение
Ровно две секции ===:
:::compare
=== Плохо
Неструктурированный текст.
=== Хорошо
Текст с заголовками.
:::/compare
Тест
Открытие — только строка :::test. Режим — следующая строка тела: [test] / [verification] или [think] / [narrative].
| Нельзя | Надо |
|---|---|
:::test[test] | :::test затем строка [test] |
:::test test | то же |
режим на одной строке с ::: | режим внутри тела |
Режим [test] / [verification]: нужен минимум один + и один -.
:::test
[test]
===
Вопрос?
+ Правильный вариант
- Неправильный вариант
ye: Верно!
no: Подумайте ещё.
:::/test
Режим [think] / [narrative]: вариантов с + нет.
:::test
[think]
===
Что вы думаете?
- Вариант 1
- Вариант 2
- Вариант 3
ok: Спасибо за ответ!
:::/test
Строки ye:, no:, ok: необязательны: если их нет, система подставит дефолтный текст. Пиши их явно, когда нужен свой текст обратной связи.
Заглушка
| Открытие | Вид заглушки |
|---|---|
:::tmp | Карточка 2:1, подпись «Заглушка» |
:::tmp video | Подпись «Видео» |
:::tmp sim | Подпись «Симулятор» |
:::tmp dialog | Подпись «Диалог» |
:::tmp hotspot | Подпись «Маркеры» |
:::tmp hint | Текстовая плашка без картинки |
:::tmp
Скоро здесь будет схема
:::/tmp
Колонки
:::columns
70-30
===
Первая колонка.
===
Вторая колонка.
:::/columns
- Первая строка тела — соотношение:
50-50,70-30,2:1,1:1:1. Не50/50. - Разделитель колонок —
===на отдельной строке без текста после===. - Заголовок внутри колонки —
###или**…**, не=== Заголовок. - Широкий ряд:
:::columns wide.
| Нельзя | Надо |
|---|---|
50/50 | 50-50 или 1:1 |
=== Заголовок как разделитель | === пустой; заголовок — ### |
Служебное
Строка <<текст>> — метка задачи. Рендерится как текст в угловых кавычках.
CDN-заполнители
| Блок | URL |
|---|---|
| Изображение | https://cdn.longread.agency/demo/image-demo.webp |
| Хотспот | https://cdn.longread.agency/demo/hotspot-demo.webp |
| Аудио | https://cdn.longread.agency/demo/audio-sample.mp3 |
| Видео | https://cdn.longread.agency/demo/video-sample.mp4 |
| Файл | https://cdn.longread.agency/demo/file-demo.pdf |
| Веб-архив | https://cdn.longread.agency/demo/archive-demo/index.html |
| ИИ-HTML | https://cdn.longread.agency/demo/widget-demo.html |
| Picfont | https://cdn.longread.agency/demo/picfont-icon.webp |
| Аватар бот | https://cdn.longread.agency/demo/avatar-bot.webp |
| Аватар пользователь | https://cdn.longread.agency/demo/avatar-user.webp |
| Слайдер | …/slider-1.webp, slider-2.webp, slider-3.webp |
| Слайдер duo | …/slider-before.webp, …/slider-after.webp |
| Обложка проекта | https://cdn.longread.agency/demo/project-cover.webp |
Методика: какой блок выбрать
Используйте эту таблицу для выбора блока под задачу.
Быстрый выбор
| Задача | Блок |
|---|---|
| FAQ, глоссарий, однородные пункты «заголовок → текст» | :::acc |
| Равнозначные панели, сценарии | :::tab |
| Термин ↔ определение, самопроверка | :::flip |
| Проверка знаний | :::test |
| Процесс с порядком, инструкция | :::steps |
| Критерии, плюсы/минусы, «как надо» | :::checklist |
| Контраст «плохо ↔ хорошо» | :::compare |
| Важная оговорка, идея, правило | !!! или :::important |
| Кликабельные точки на схеме | :::hotspot |
| Две–три колонки вёрстки | :::columns |
Когда что использовать
Аккордеон (:::acc)
Используйте для однородных пунктов: заголовок открывает пояснение по клику.
Подходит для:
- Глоссария: термин — заголовок, определение — тело.
- FAQ: вопрос — заголовок, ответ — тело.
- Подсказок к шагам, когда порядок не важен.
Не путайте с :::steps. Аккордеон не нумерует пункты.
Вкладки (:::tab)
Используйте для равнозначных панелей с короткими ярлыками.
Подходит для:
- Вариантов: теория / практика, условие / решение.
- «До и после» без морали «плохо/хорошо».
Не используйте вкладки для контраста «плохо/хорошо». Для этого — :::compare или :::checklist duo.
Карточки (:::flip)
Используйте для самопроверки: вспомнил → перевернул → проверил.
Лицо — тезис. Оборот — развёрнутый ответ.
Глоссарий из 10+ терминов лучше делать аккордеоном.
Тест (:::test)
[test]/[verification]— один правильный ответ. Обязателен минимум один+и один-.[think]/[narrative]— опрос. Нет правильного ответа. Нейтральныйok:.
Не ставьте [test] с искусственным + там, где подходит [think].
Шаги (:::steps)
Используйте для процесса с порядком: инструкция, алгоритм, «сначала → потом».
:::steps— со стрелками между шагами.:::steps grid— сетка без стрелок.
Не используйте :::steps для норм без последовательности. Для этого — :::important rule или чеклист.
Чеклист (:::checklist)
Используйте для критериев, требований, списка «что проверить» или списка возможностей.
+в начале строки — зелёная галочка (плюс / фича / «как надо»).-в начале строки — красный крест (минус / ошибка).- Список преимуществ или возможностей продукта — все пункты с
+. Не используйте-как обычный маркер списка. - Одна колонка:
:::checklist. - Две колонки:
:::checklist duo. Тональность:=== + …/=== - ….
Отличие от :::compare: чеклист — набор пунктов с ✓/✗. Сравнение — ровно 2 текстовые колонки.
Тест (:::test)
- Открытие только
:::test. Режим[test]или[think]— отдельной строкой внутри блока. - Не пишите
:::test[test]и не клеите режим к имени блока.
Сравнение (:::compare)
Используйте для контраста подходов. Ровно 2 колонки с заголовками.
Важное (:::important)
- Краткая оговорка:
!!! текст. - Блок:
:::important idea/problem/solution/info/rule. - Иконку система рисует сама. Не вставляйте эмодзи и символы иконок в текст.
- Только короткая врезка. Не оборачивайте весь урок или статью в
:::important.
Хотспот (:::hotspot)
Используйте для схемы или фото с кликабельными точками.
Требуется реальная картинка . Без маркеров блок ведёт себя как обычное изображение.
Колонки (:::columns)
Используйте для параллельной вёрстки: текст + врезка, две равные колонки.
Пишите :::columns … :::/columns.
Соотношение: пресет (2:1, 1:1:1) или проценты через дефис (70-30). Не 50/50. Разделитель — пустой ===. Заголовки внутри — ###.
Не заменяйте колонками :::compare или :::checklist duo. В колонках нет семантики «плохо/хорошо».
Не путать
| Хотели | Не ставьте | Ставьте |
|---|---|---|
| Последовательность шагов | :::acc с «Шаг 1» в заголовках | :::steps |
| Норма / принцип | :::steps | :::important rule |
| Контраст «плохо / хорошо» | Две вкладки | :::compare |
| Много пунктов ✓/✗ | :::compare | :::checklist или :::checklist duo |
| Глоссарий 10+ терминов | Пачка :::flip | :::acc |
| Опрос без верного ответа | [test] с искусственным + | [think] |
| Две колонки | 50/50 в первой строке | 50-50 или 1:1 |
| Заголовок в колонке | === Заголовок | пустой === + ### в теле |
| Свой кликабельный UI (не штатный Q&A) | :::test / костыль из compare | :::ai_widget (после BRIEF, см. 05) |
Anti-patterns: wrong → right
Шаги как аккордеон — нельзя
Так нельзя:
:::acc
=== Шаг 1
Откройте файл.
=== Шаг 2
Сохраните файл.
:::/acc
Так надо:
:::steps
=== Откройте файл
Дважды щёлкните по ярлыку.
=== Сохраните файл
Ctrl+S или меню «Файл».
:::/steps
Норма как steps — нельзя
Так нельзя:
:::steps
=== Правило 1
Не храните пароли в чате.
:::/steps
Так надо:
:::important rule
Не храните пароли в чате.
:::/important
Плохо/хорошо вкладками — нельзя
Так нельзя:
:::tab
=== Плохо
Длинный абзац без структуры.
=== Хорошо
Список и заголовки.
:::/tab
Так надо:
:::compare
=== Плохо
Длинный абзац без структуры.
=== Хорошо
Список и заголовки.
:::/compare
Колонки со слэшем — нельзя
Так нельзя:
:::columns
50/50
===
Левая.
===
Правая.
:::/columns
Так надо:
:::columns
50-50
===
### Левая
Текст.
===
### Правая
Текст.
:::/columns
=== Заголовок в колонках — нельзя
Так нельзя (в превью торчит сырой ===):
:::columns
1:1
=== Левая
Текст.
=== Правая
Текст.
:::/columns
Так надо — пустой ===, заголовок через ### (см. пример выше).
Опрос с фейковым «верным» — нельзя
Так нельзя:
:::test
[test]
===
Как вам курс?
+ Отлично
- Нормально
ye: Верно!
no: Неверно.
:::/test
Так надо:
:::test
[think]
===
Как вам курс?
- Отлично
- Нормально
- Сложно
ok: Спасибо за ответ!
:::/test
Режим теста на строке открытия — нельзя
Так нельзя:
:::test[test]
===
Вопрос?
+ Да
- Нет
:::/test
Так надо:
:::test
[test]
===
Вопрос?
+ Да
- Нет
:::/test
Фичи чеклистом с минусами — нельзя
Так нельзя (все пункты красные ✗):
:::checklist
- Поддерживает мультимодальные запросы
- Использует Query Fan-Out
:::/checklist
Так надо (зелёные ✓):
:::checklist
+ Поддерживает мультимодальные запросы
+ Использует Query Fan-Out
:::/checklist
Весь урок в important — нельзя
Так нельзя: открыть :::important idea в начале файла и закрыть :::/important в конце, а внутри — #, ##, steps, compare.
Так надо: обычный Markdown-каркас; :::important только на короткий акцент (несколько предложений).
Блок в одну строку — нельзя
Так нельзя:
:::steps === Шаг 1 Текст. === Шаг 2 Текст. :::/steps
Так надо:
:::steps
=== Шаг 1
Текст.
=== Шаг 2
Текст.
:::/steps
То же для :::compare и :::checklist duo.
Размещение и объём
- Перед интерактивом добавьте короткую подводку — связующий текст.
- Между блоками — связный текст. Не ставьте несколько интерактивов подряд без пояснений.
- Внутри
:::acc,:::tab,:::flipне ставьте заголовки###. Структура — только через===и текст. - В подводках не используйте штампы: «далее», «ниже», «удобно».
Не выдумывайте несуществующих типов блоков. Используйте только идентификаторы из допустимого списка.
MCP-инструменты Демонстратора
Карта инструментов (имя → зачем). Полные схемы параметров не дублируются здесь.
Параметры инструментов
После подключения MCP клиент вызывает tools/list. Сервер отдаёт канонические inputSchema (required, типы, enums, лимиты).
- Бери аргументы только из
tools/list/ подсказок клиента — не из этой страницы и не из догадок. - Этот файл — ориентир «какой tool выбрать». Контракт вызова — у MCP.
- Без токена схемы недоступны (
401). - Нет нужного tool в
tools/list→ стоп, спроси пользователя. Не обходи дырку delete+create / массовой перезаписью. - Soft-delete в корзину, перенос, смешивание материалов,
replace_allповерх живого контента — только после описания процедуры и прямого подтверждения пользователя (см.README.md, блок СТОП). - Вне MCP (не ищи в
tools/list): permanentdelete-project/delete-resource, снятие share-ссылок, invite/ACL пространства, suggestions propose/apply (WIP), restore из entity-snapshot / backup ZIP.
Подключение к MCP
Подпишись на remote MCP-шлюз на сервере Демонстратора. Клонировать репозиторий и запускать node локально не нужно.
Токен уже лежит в конфиге MCP-клиента в IDE (mcp.json → headers.Authorization, значение после Bearer ). В Личном кабинете он генерируется один раз при первичной настройке — не запрашивай его заново и не ищи в переменных окружения.
{
"mcpServers": {
"demonstrator-mcp": {
"url": "https://app.longread.agency/mcp/demonstrator/mcp",
"headers": {
"Authorization": "Bearer <токен_из_mcp.json>"
}
}
}
}
Эндпоинты шлюза:
| Метод | URL | Назначение |
|---|---|---|
GET | https://app.longread.agency/mcp/demonstrator/health | Проверка живости |
GET | https://app.longread.agency/mcp/demonstrator/sse | SSE (keepalive; Cursor сюда url не ставь) |
POST | https://app.longread.agency/mcp/demonstrator/mcp | JSON-RPC / streamable HTTP — это URL для IDE |
- В конфиге Cursor / Claude Code в поле
urlуказывай…/mcp, не…/sse. - Заголовок
Authorization: Bearer <токен>обязателен наPOST /mcp. - Без токена —
401. - Токен привязан к аккаунту: права и квоты — как в UI Демонстратора.
- MCP и публичный сайт — только
app.longread.agency. Другие шлюзы Демонстратора не используй. - Share
urlотдавай с хостомhttps://app.longread.agency. Если в ответеlocalhost/127.0.0.1— подставь этот host (token тот же) и проверь ссылку. Loopback человеку не отдавай.
Happy path: материал с нуля
1. list-projects → project_id (или create-project). 2. create-material — title + project_id. 3. Markdown по 01-syntax.md / 02-methodology.md. 4. Запись тела — только через Yjs collab-мост, не прямым PATCH в БД:
- полное тело:
apply-material-point-patchсmode: "replace_all"+content
или save-material-content (тот же POST /api/collab/materials/:id/patch);
- точечно (
find_replace_once/range_patch) — только при закрытой вкладке; при открытом редакторе API вернёт 409; - title вместе с телом:
save-material-content+title; theme —apply-material-theme. - Переименование без записи тела (например, снять суффикс «(копия)» после
duplicate-material):rename-material(material_id+title). Контент не трогается.
5. Проверить verify.ok и verify.integrity.ok / get-material. source: yjs_room или db_fallback. Не сдавай по одному verify.ok.
Запрещено писать markdown через raw PATCH /api/materials с полем content (tool update-material-content удалён): открытый редактор перезапишет старым Yjs-документом.
Полный маршрут и сценарий без MCP — в README.md.
Каталог (обзор)
Ниже — имя и краткое действие. Схемы полей — в tools/list.
Проекты и пространства
| Инструмент | Действие |
|---|---|
list-projects | Получить список проектов |
create-project | Создать проект |
update-project | Обновить проект |
list-spaces | Получить список пространств |
get-space | Получить пространство |
update-space | Переименовать пространство |
list-space-members | Список участников (только чтение) |
Создание пространства, приглашения, смена состава/ролей, permanent delete-project — только через UI, не через MCP.
Структура
| Инструмент | Действие |
|---|---|
list-sections | Список разделов (с sort_order, среди разделов) |
create-section | Создать раздел |
update-section | Обновить раздел (в т.ч. одиночный sort_order) |
reorder-sections | Порядок только среди разделов (sort_order) |
delete-section | Удалить раздел (soft delete) |
list-materials | Список материалов (с sort_order) |
search-materials | Полнотекстовый поиск по материалам |
create-material | Создать материал |
duplicate-material | Клонировать материал (суффикс «(копия)») |
rename-material | Переименовать материал (только title, контент не меняется) |
update-material-placement | Переместить материал / один sort_order |
reorder-materials | Порядок уроков внутри раздела (или среди корневых) |
get-project-sequence | Смешанный порядок карточек проекта: раздел ↔ корневой прототип |
patch-project-sequence | Задать смешанный порядок (как DnD в ЛК) |
delete-material | Удалить материал (soft delete) |
Порядок: три разных оси (не путать)
| Что меняешь | Tool | Где видно |
|---|---|---|
| Раздел ↔ корневой прототип вперемешку | get-project-sequence / patch-project-sequence | сетка карточек проекта в ЛК |
| Порядок разделов между собой | reorder-sections (sort_order) | среди разделов |
| Порядок уроков внутри раздела | reorder-materials + section_id | внутри раздела |
Можно чередовать: раздел → прототип → раздел → прототип. Это штатный project_sequence, не запрет продукта.
Стартовый/пустой sequence (миграция + UI-fallback) часто выглядит как «сначала все разделы, потом корневые» — дефолт, не закон. Не сочиняй «разделы всегда сверху» / «без разделов только плоский список».
Дерево слева sequence почти не отражает: смешанный список — в сетке карточек.
patch-project-sequence: items: [{ "item_type": "section"|"material", "item_id": N }, …] сверху вниз. В items только корневые материалы (без section_id). Перед вызовом — подтверждение пользователя (см. СТОП в README.md).
reorder-sections / reorder-materials не умеют смешивать типы — для этого только sequence.
Медиа
| Инструмент | Действие |
|---|---|
list-resources | Список ресурсов (фильтр: projectid, materialid, q) |
presign-media-upload | Init upload audio/video/file/web_archive → resource_id + upload_url (PUT с машины клиента) |
complete-media-upload | Complete после PUT → id, status, full_url (CDN), markdown_snippet |
upload-media-from-fs | Загрузка по file_url (HTTPS) или file_path уже на сервере MCP — не Windows-path клиента |
get-resource-status | Статус обработки медиа (poll до ready / failed) |
insert-media-block | Вставить медиа-блок; display_name / name → имя в медиатеке |
rename-resource | Переименовать ресурс |
Permanent удаление ресурса — только UI (delete-resource в MCP нет).
Upload и insert: осмысленный filename / display_name. «Без названия» в медиатеке — брак; сразу rename-resource.
Kind и CDN URL — не путать
В markdown / обложку / логотип ставь только full_url (или url) из ответа upload / list-resources / get-resource-status. Не собирай CDN-путь руками и не «чини» подстроки в URL.
| Назначение | kind | Как залить | Типичный full_url |
|---|---|---|---|
Картинка для показа (логотип, обложка, иллюстрация, ) | image | curl multipart (ниже) или upload-media-from-fs с kind: image | https://cdn.longread.agency/images/{hash}.webp (растр) или …/{hash}.svg (SVG) |
| Скачиваемый файл (PDF, ZIP, DOCX…) | file | presign → PUT → complete | …/files/originals/{id}.{ext} — так и должно быть |
Аудио / видео после ready | audio / video | presign → … | …/audio/processed/… или …/video/processed/… |
| ИИ-HTML | web_archive / bake | см. виджеты | …/archives/{id}/… |
Частая ошибка: логотип/обложку залить как file (получится files/originals/…), потом «исправить» на выдуманный files/processed/…. Пути files/processed/ нет. Для показа — перезалей как image и возьми новый full_url с /images/…webp.
Картинка-image: jpeg / png / webp / gif / svg. SVG принимается как image без растеризации — сервер санитизирует (вырезает скрипты/обработчики) и хранит как …/images/{hash}.svg. Растр (jpeg/png/webp/gif) пережимается в …/images/{hash}.webp. SVG как file — только для скачивания, не для показа.
Как загрузить локальный файл → Object Storage → CDN
MCP не читает файлы с диска агента (C:\…). Не вызывай upload-media-from-fs с Windows-path. insert-media-block файл не загружает. Сначала upload, потом insert.
Запрещено
- Писать свой upload (Python, Node).
- Искать
upload-local-media.ps1вне текущего workspace. - Спрашивать пользователя «как залить».
- Класть файлы на VPS «под from-fs».
- Передавать
file_base64в MCP. - Заливать файл с кириллицей в пути или имени (сначала скопируй в ASCII-имя).
- Подменять в URL
originals↔processed/images/ выдуманные сегменты. - Искать токен в переменных окружения шелла: там может лежать старый/отозванный токен. Токен для
curl— тот же Bearer, что стоит в конфиге MCP-клиента в IDE (серверdemonstrator-mcp/user-demonstrator-mcp), или спроси у пользователя.
Дерево решений: как загрузить файл
Файл — картинка для показа? (PNG/JPEG/WebP)
├─ Файл локально на твоём диске (C:\...)
│ → Shell curl multipart (↓ см. «Картинка — curl»).
│ → НЕ вызывай recipe-upload-and-insert / upload-media-from-fs с file_path.
│ → НЕ вызывай presign-media-upload (пресайн не для image).
│
├─ Файл уже на публичном HTTPS
│ → recipe-upload-and-insert с file_url + kind: image
│
└─ Файл на сервере MCP (не твоя машина)
→ upload-media-from-fs с file_path
Файл — аудио / видео / file / web_archive?
├─ Файл локально на твоём диске
│ → presign-media-upload → Shell curl PUT → complete-media-upload
│ → ИЛИ recipe-upload-and-insert если не нужно вручную PUT
│
├─ Файл на публичном HTTPS
│ → recipe-upload-and-insert с file_url
│
└─ Файл на сервере MCP
→ upload-media-from-fs с file_path
#### Выбор пути
| Что на диске агента | Как |
|---|---|
Картинка для показа (image) | Shell curl multipart (ниже) |
| audio / video / file / web_archive | MCP presign-media-upload → Shell PUT → MCP complete-media-upload |
| Уже HTTPS URL | MCP upload-media-from-fs с file_url + material_id/project_id (+ верный kind) |
После upload вызови MCP insert-media-block или вставь  в content. Processing-медиа: get-resource-status до ready / failed. На каждом upload передай material_id или project_id.
ASCII-only на диске. Путь и имя файла для upload — только латиница/цифры (C:\work\media\fig-01.png). Кириллица в папках или имени файла ломает curl у агентов. Русское имя в медиатеке — после upload: rename-resource или display_name / name в insert-media-block.
#### Картинка — curl
Токен — тот же Bearer, что стоит в конфиге MCP-клиента в IDE (сервер demonstrator-mcp / user-demonstrator-mcp). Не ищи токен в переменных окружения шелла — там может лежать старый/отозванный токен после ротации. Если в конфиге IDE токена нет — спроси у пользователя. Prod API: https://app.longread.agency.
Если POST /api/resources отвечает 401 — токен не тот или ротирован: стоп и спроси у пользователя актуальный токен. Не перебирай варианты.
curl.exe -sS -X POST "https://app.longread.agency/api/resources" `
-H "Authorization: Bearer ТОКЕН_ИЗ_КОНФИГА_MCP" `
-H "Accept: application/json" `
-F "file=@C:\work\media\fig-01.png;filename=fig-01.png;type=image/png" `
-F "material_id=123" `
-F "original_name=fig-01.png"
1. Из JSON возьми id и full_url (или url) — должен быть путь /images/…webp. 2. Вызови insert-media-block (kind: image) или вставь . 3. Задай читаемое имя. Иначе вызови rename-resource.
Для image нет presign-media-upload. Нет MCP-tool, который читает локальный png. Если upload-local-media.ps1 уже лежит в текущем workspace — можно вызвать его вместо curl. Иначе делай curl. Не сканируй диск.
#### audio / video / file / web_archive
1. MCP presign-media-upload (kind, filename, size, material_id или project_id). 2. Shell: curl.exe -sS -X PUT "<upload_url>" -H "Content-Type: …" --data-binary "@C:\path\to\file". 3. MCP complete-media-upload (тот же material_id / project_id). 4. MCP insert-media-block.
kind: file — только для скачиваемых вложений. Не для логотипа/обложки/блока картинки.
#### Уже публичный HTTPS
MCP upload-media-from-fs: только file_url (https://…). Не C:\…. Для показа картинки — kind: image.
Медиатека проекта
Медиатека = resource_links на project_id / material_id.
- Upload без
material_id/project_id— MCP отклоняет вызов. - При записи content через collab backend линкует твои ресурсы из markdown в материал/проект.
- Чужой CDN URL / demo-заполнитель без upload в медиатеку не попадает.
Что не делать
- Не писать свои upload-скрипты сверх
curl/ MCP выше. - Не искать helper вне текущего workspace.
- Не считать
insert-media-blockзаменой upload. - Не вызывать несуществующие base64-tools (
upload-media-resource/upload-resource). - Не класть медиа на диск VPS «под from-fs».
- Не подставлять Windows-path в
upload-media-from-fs. - Не загружать без
material_id/project_id. - Не заливать картинку для показа как
fileи не править CDN-путь вручную.
ИИ-HTML (:::ai_widget)
| Инструмент | Действие |
|---|---|
create-widget | Создать черновик ИИ-HTML. Обязательно name (или caption / original_name) — имя в медиатеке |
get-widget | Получить ИИ-HTML; у linear_dialog — ещё archetype + scenario |
save-widget-draft | Сохранить HTML в черновик (свободная вёрстка). Не для правок линейного диалога |
assemble-linear-dialog | Собрать или обновить линейный диалог из полного scenario JSON (без Studio AI). Фича ai_widget_templates (тестеры) |
bake-widget | Собрать ИИ-HTML в готовый архив |
fork-widget | Форкнуть ИИ-HTML в другой материал |
insert-ai-widget-block | Вставить :::ai_widget в материал |
Имя в медиатеке
Ресурс без нормального original_name в UI выглядит как «Без названия» / бессмысленный дефолт. Это брак.
create-widget: всегда передайname(коротко по смыслу: «Квиз онбординг», не uuid).- Upload: осмысленный
filename; если в библиотеке плохое имя — сразуrename-resource. insert-media-block: можноdisplay_name/name— MCP переименует ресурс.
Не готово: виджет/файл в медиатеке без читаемого имени.
Путь создания ИИ-HTML (только IDE + MCP)
локальный .html + маркер + отладка → вопрос «bake на CDN?» → «да» →
create-widget → save-widget-draft → bake-widget (ready) →
insert-ai-widget-block (fence = id, не draft) →
poll archives/ до совпадения маркера → get-material
1. Запиши и отладь HTML в workspace (widgets/<slug>.html). Добавь уникальный маркер. 2. Спроси пользователя про bake/insert. Жди прямого «да». Не вызывай bake-widget на каждой правке. 3. create-widget → save-widget-draft (HTML с диска) → bake-widget. 4. insert-ai-widget-block — в блок только CDN entry_url после bake (ready). Draft URL и bare id в материал запрещены. 5. Poll: get-resource-status / archive-info → HTML с archives/ (entry_url, учти ?v=). Маркер должен совпасть с локальным файлом. get-widget и draft — не критерий сдачи. 6. get-material — fence на месте. Путь к локальному файлу — пользователю.
В create-widget всегда передай name (имя в медиатеке). Без имени — не готово; при ошибке — rename-resource.
Шаблон: линейный диалог (assemble-linear-dialog)
Когда нужен диалоговый плеер, не пиши HTML с нуля — собери сценарий JSON и вызови assemble-linear-dialog. Shell канонический; агент заполняет данные (пул портретов, локации, реплики с парой слотов).
Предпочтительный HAPPY PATH: recipe-linear-dialog (создаёт виджет + собирает диалог + валидирует схему, ловит portrait_url и пропущенный id).
scenario.json на диске → recipe-linear-dialog (или create-widget → assemble-linear-dialog) →
вопрос «bake на CDN?» → «да» → bake-widget → insert-ai-widget-block (CDN URL)
#### Схема сценария (и частые ошибки)
Эталонный полный пример: docs/templates/linear-dialog/scenario.example.json. Основные поля:
| Поле | Правильно | Частая ошибка |
|---|---|---|
archetype | "linear_dialog" внутри scenario | кладут в корень manifest'а, не в scenario |
| Персонаж — портрет | imageUrl + portrait: "auto" | portrait_url (несуществующий ключ) |
| Персонаж — ID | id: "p1" | нет поля id |
| Шаг — ID | id: "s1" обязательно | пропускают id |
| Шаг — локация | locationId обязательно | нет locationId → 422 |
| Шаг — портреты | leftPortraitId, rightPortraitId (два слота) | один слот или нет пары |
| Шаг — кто говорит | speakerSide: "left" или "right", плюс speaker = id персонажа | нет speaker или неясно |
| Шаг — следующий | next: "s2" или null у последнего | все null — плеер остановится после первого |
locations[] | обязателен (минимум одна, можно с пустым imageUrl) | не передают — 422 «опционально» в старых текстах ошибочно |
locations[].id | id: "loc1" | нет id |
1. Сохрани scenario локально (widgets/<slug>-scenario.json). Начни с docs/templates/linear-dialog/scenario.example.json — не угадывай поля. 2. На сцене всегда два слота картинок; characters[] — N вариаций (ракурсы/эмоции). На каждой реплике задай пару портретов и кто говорит (speakerSide: left|right). 3. Демо-медиа CDN: avatar-bot.webp, avatar-user.webp, slider-1.webp на cdn.longread.agency/demo/. 4. create-widget с name → assemble-linear-dialog (widget_id + scenario). Не нужен save-widget-draft. 5. Bake/insert — только после прямого «да» (как у свободного HTML). Insert пишет CDN entry_url, не id. 6. При 403 ai_widget_templates_forbidden — шаблоны только у тестеров; не обходи через свободный HTML без согласования.
Правка существующего линейного диалога
Тот же assemble-linear-dialog — создаёт и обновляет. Отдельного «refine» в MCP нет (Studio AI через MCP не вызываем).
get-widget → взять scenario → править JSON (локально или в ответе) →
assemble-linear-dialog (полный scenario) → при необходимости снова bake после «да»
1. get-widget (widget_id) → поля archetype: linear_dialog и scenario. 2. Если scenario пуст — вытащи из HTML #vn-scenario или восстанови из локального widgets/<slug>-scenario.json. 3. Внеси правки в полный scenario (реплики, портреты пула, leftPortraitId/rightPortraitId/speakerSide, локации, порядок steps / next). Не отправляй diff / SEARCH-REPLACE по shell. 4. Обнови локальный widgets/<slug>-scenario.json. 5. assemble-linear-dialog с тем же widget_id и полным scenario — пересоберёт draft. 6. Запрещено для диалога: save-widget-draft с ручным HTML плеера; правка CSS/JS shell; вызов /api/ai/widgets/*/refine*. 7. Повторный bake-widget / insert — только после прямого «да» пользователя (как при создании).
Чат-боты без MCP этот путь не используют. Они отдают HTML/markdown кодом и bake не делают.
Не готово (IDE): только bare id в fence; bake без «да»; bake без локального файла; bake без insert; сдача по draft при отстающем archive; fence с draft_url; ресурс в медиатеке без читаемого имени.
Серверный контракт — 05-html-widget-rules.md.
Корзина
| Инструмент | Действие |
|---|---|
list-project-trash | Список удалённого |
restore-project-trash | Восстановить (items: [{type, id}] или all: true) |
Share-ссылки
Создание и обновление — да. Снятие / unpublish ссылок через MCP нельзя (только UI).
| Инструмент | Действие |
|---|---|
get-material-share-links | Ссылки материала |
create-material-share-link | Создать ссылку материала |
update-material-share-link | Обновить уже созданную ссылку материала (auto_update) |
get-project-share-links | Ссылки проекта |
create-project-share-link | Создать или обновить (refresh_only: true — без ротации токенов) |
get-section-share-links | Ссылки раздела |
create-section-share-link | Создать или обновить (refresh_only) |
republish-comments-snapshot | Обновить HTML-snapshot для публичной ссылки с правками. Не пишет тело урока (content) |
Ответ create/get содержит url — полный канонический URL.
Типы ссылок: preview (просмотр), preview_comments (с комментариями).
Comment/share ≠ editor body. republish-comments-snapshot, share-links с preview_comments и get-material-preview-with-comments — про публичный review/HTML. Текст урока в редакторе правят только save-material-content / apply-material-point-patch. В ответах материала нет внутренних publish-кэшей — только content.
Чтение и экспорт
| Инструмент | Действие |
|---|---|
get-material | id, title, content, theme, version, placement (whitelist) |
get-material-export-data | Markdown + тема материала |
get-section-export-data | Раздел + все материалы с контентом |
get-project-export-data | Дерево проекта для HTML-ZIP |
get-material-preview-with-comments | Read-only HTML snapshot+overlay; не источник для правки markdown |
Предложения правок
Сейчас вне MCP (недоделано; вернём позже). Пиши тело сразу через collab (save-material-content / apply-material-point-patch).
Запись контента
Источник правды при открытом редакторе — Yjs collab, не строка в БД. Все правки тела материала — через collab-мост.
| Инструмент | Действие |
|---|---|
apply-material-point-patch | Канон. replace_all / range_patch / find_replace_once → /api/collab/materials/:id/patch |
save-material-content | Полная замена тела тем же collab-мостом (replace_all) + опционально title/theme. По умолчанию verify_readback: true |
apply-material-theme | Тема (palette / contentStyle / customOverlayCss) |
Коридор при открытом редакторе
| Режим | Вкладка закрыта (db_fallback) | Вкладка открыта (живая комната) |
|---|---|---|
replace_all / save-material-content | ок | ок → yjs_room (live в Коде, тост MCP). 409 возможна только при редкой гонке reconnect — повтори |
range_patch / find_replace_once | ок (одна-две мелкие правки) | 409 point_patch_blocked_editor_open |
Серия точечных патчей поверх живого Yjs даёт дубли #/##, обрыв+повтор куска, голые === / :::/…. При открытой вкладке: range_patch и find_replace_once заблокированы (409), replace_all / save-material-content пишут в Yjs, контент приходит в Код как remote change (тост «обновлён с сервера (MCP)»). Не «добивай» пятью find_replace_once после 409.
Future (чат-ассистент по открытому материалу): настоящий agent-WS peer с мелкими Y.Text ops «как юзер» — отдельно от массовой авторской переписки урока; массовые правки всё равно через replace_all + integrity.
Режимы apply-material-point-patch
| Режим | Когда использовать |
|---|---|
replace_all | Замена всего контента (основной путь «записать материал»; единственный при открытом редакторе) |
range_patch | Правка по смещению — только при закрытой вкладке |
find_replace_once | Замена по тексту — только при закрытой вкладке; не сериями на большом diff |
Все write-операции используют expected_version (tool подставит сам, если не передан).
Ответ содержит source:
yjs_room— материал был открыт в редакторе; текст ушёл в Yjs, потом в БД;db_fallback— вкладка редактора не была открыта (нет живых WS).
replace_all / save-material-content при открытой вкладке: контент пишется в Yjs → приходит в Код как remote change (тост «обновлён с сервера (MCP)»). 409 (editor_open_room_not_ready / collab_room_push_failed) возможна только при редкой гонке reconnect — повтори запрос.
Точечный патч при открытой вкладке → 409 point_patch_blocked_editor_open. Используй один replace_all целым телом.
После записи смотри в ответе:
1. verify.ok — байтовое равенство отправленного и прочитанного (не «материал логически целый»). 2. verify.integrity.ok (и/или integrity в ответе collab patch) — структура: нет дублей #/##, нет голых === / orphan :::/…, нет незакрытых блоков, нет повторённого куска. 3. source и хвост get-material глазами при сомнении.
Не сдавай, если verify.ok true, а integrity.ok false. Надёжнее один финальный replace_all / save-material-content целым файлом, чем много мелких find_replace_once.
Не пишите тело через raw PATCH «в базу». Вторичных «workspace»/publish body-полей в ответах нет — править нечего, кроме content через collab.
Расхождение с readback: сначала факты, потом диагноз
Не списывай расхождение «отправил — прочитал» на «потерялось при ре-синхронизации Yjs», пока не назвал факты последнего write-вызова.
1. Был ли write-вызов. Если save-material-content / apply-material-point-patch на этот material_id не вызывался, правка в бэкенд не уходила — Yjs тут ни при чём. Ищи, что вообще не записали (или записали не в тот материал). 2. source ответа (обязательно назвать):
db_fallback→ живой Yjs-комнаты не было (вкладка закрыта), текст шёл напрямую в БД. Ре-синхронизации Yjs физически нет — «Yjs затёр правку» неверный вывод. Смотриexpected_versionи конфликт 409 (version_conflict).yjs_room→ вкладка была открыта, текст шёл через Yjs. Только здесь возможна гонка reconnect/перезаписи — и то лишь при открытой вкладке.
3. new_version / updated_at. Если версия материала не сдвинулась на момент записи (например, запись «~12:30», а updated_at «~09:29») — запись не дошла до БД. Сверь material_id и был ли ответ ok: true.
Антипаттерн: «потерялось при ре-синхронизации Yjs» при source: db_fallback или при отсутствии write-вызова в истории. Это блокер-регрессия, не «известная проблема».
Темы
| Инструмент | Действие | |
|---|---|---|
list-themes | Список пресетов: minimalism, draft, journal, neon, airy, modern | |
apply-material-theme | Применить тему. Palette / contentStyle / customOverlayCss; также bgImageResourceId + bgImageMode (pattern \ | frame) для фона-картинки из медиатеки |
inherit-project-themes | Сбросить темы разделов/уроков проекта на наследование от темы проекта |
apply-material-theme принимает произвольный theme object (merge через MERGE_KEYS). Фон-картинка:
bgImageResourceId— id image-ресурса (илиnull, чтобы убрать);bgImageMode—pattern(плитка) илиframe(фиксированный кадр); по умолчаниюpattern.
Не подставляй картинку фона через customOverlayCss (url(...)) — используй поля темы. URL на shared/export резолвит платформа (bg_image_url).
Правила CSS для customOverlayCss — см. раздел «Правила CSS-генерации».
Правила CSS тем (customOverlayCss)
CSS для apply-material-theme → customOverlayCss. Это ограничения, не гайд по дизайну.
Фон страницы (shared / export)
В shared колонка текста — body (max-width 720px), а холст страницы — html на весь viewport. Класс .longread-theme-custom вешается и на html, и на body; у колонки background: transparent !important.
Канон shared красит холст так:
html:has(body.shared-view) {
background-color: var(--longread-bg);
background-image: var(--longread-bg-image, none);
background-size: var(--longread-bg-image-size, 960px auto);
}
Специфичность html:has(body.shared-view) выше, чем у .longread-theme-custom. Один только background: …gradient… на классе не перекрывает канон: на html остаётся solid --longread-bg и background-image: none.
| Задача | Как | |
|---|---|---|
| Плоский фон | --longread-bg — только solid #hex (нижний слой и для color-mix). | |
| Картинка из медиатеки | Поля темы bgImageResourceId + bgImageMode (pattern \ | frame). Не пиши url(...) руками в overlay. |
| Градиент на весь viewport | --longread-bg-image + --longread-bg-image-size: 100% 100% на .longread-theme-custom (см. ниже). | |
| Не делать | Селекторы html / body; gradient(...) в --longread-bg; считать задачу решённой после одного background: …gradient… на классе без переменных и без проверки shared. |
Градиент на всё окно (shared / export)
1. theme.bg / --longread-bg — только solid. 2. Визуальный градиент на весь viewport задавай через переменные, которые читает канон холста:
.longread-theme-custom {
--longread-bg-image:
radial-gradient(
ellipse 80% 50% at 70% 0%,
color-mix(in srgb, var(--longread-accent) 40%, transparent),
transparent 70%
),
linear-gradient(
180deg,
color-mix(in srgb, var(--longread-surface) 85%, var(--longread-accent)) 0%,
var(--longread-bg) 100%
);
/* иначе дефолт 960px auto — плитка, не «на всё окно» */
--longread-bg-image-size: 100% 100%;
}
3. Без --longread-bg-image холст shared останется плоским, даже если на .longread-theme-custom есть background: …gradient…. 4. Не ставь селекторы html / body. Не клади gradient(...) в --longread-bg. 5. --longread-bg-image — тот же хук, что у картинки из медиатеки (bgImageResourceId). Свой градиент в переменной перебивает page-image — авторская ответственность. С bgImageMode: frame full-bleed градиент через эту var может вести себя иначе; проверяй shared без frame либо не мешай с frame. 6. Перед сдачей — shared-view (hard refresh): у html computed background-image содержит gradient, background-size — на весь холст (100% 100% или эквивалент), а не пятна только на узкой колонке.
Антипаттерн: background: radial-gradient… на .longread-theme-custom без --longread-bg-image / --longread-bg-image-size и без проверки shared.
Штатный фон-картинка (тема)
bgImageResourceId— id ресурсаkind=imageв медиатеке (илиnull/ пусто = без картинки).bgImageMode:pattern— плитка (wallpaper),background-sizeот холста 960px; на узком экране crop, не scale-down.frame— один кадр fixed-слоем#longread-page-bg(неbackground-attachment: fixed).- URL резолвит платформа (
bg_image_url/theme.bgImageUrlна shared). ВcustomOverlayCssне дублируй картинку черезurl(...). - Solid
--longread-bgостаётся под картинкой (в т.ч. под прозрачным PNG). - Свой
--longread-bg-image(градиент) в overlay перебивает page image — авторская ответственность.
Цвета — только переменные
Цвета темы материала задаются через --longread-*. В customOverlayCss для любого цвета (color, background, background-color, border-color, outline-color, box-shadow с цветом, text-shadow с цветом, fill, stroke) пиши только var(--longread-…).
| Переменная | Назначение |
|---|---|
--longread-bg | Базовый фон страницы (solid) |
--longread-text | Текст |
--longread-surface | Фон карточек / поверхностей |
--longread-surface-text | Текст на surface |
--longread-headings | Заголовки |
--longread-accent | Акцент |
--longread-accent-pale | Бледный акцент |
--longread-border | Границы |
--longread-positive | «Хорошо» |
--longread-negative | «Плохо» |
--longread-important-bg | Фон important |
Отступы темы (не цвета, но тоже из темы): --longread-content-padding, --longread-block-padding, --longread-block-margin.
Запрещено (цвета)
- Голые
#hex,rgb(),rgba(),hsl(), именованные цвета (white,red, …) в свойствах цвета. - Свои
--my-*/ хардкод «под палитру клиента» вместо--longread-*. - Менять значения переменных в
:root/html/bodyиз overlay (палитра — полями theme API, не CSS).
/* Верно */
.longread-theme-custom h1 { color: var(--longread-headings); }
.longread-theme-custom .block.btn a {
background: var(--longread-accent);
color: var(--longread-surface);
}
/* Запрещено */
.longread-theme-custom h1 { color: #1f3249; }
.longread-theme-custom .card { background: white; }
Исключение: прозрачность через color-mix / oklch от var(--longread-*) — ок. Чистый hex — нет. Иной цвет без переменной — только после прямого «да» пользователя.
Self-lint перед apply-material-theme: в CSS нет # / rgb( / rgba( / hsl( для цветов (кроме редкого согласованного исключения).
Селекторный контекст
- Каждое правило — с префикса
.longread-theme-custom. - Запрещены селекторы:
body,html,:root.
Не ломай вёрстку
Без прямого «да» пользователя не трогай: display, position, flex, grid, width, height, overflow.
Не стилизуй без «да»: .test-options, radio/checkbox внутри теста, их ::before / ::after / ::marker.
Ограничения:
.block.important—padding-left≥3em; не ломай::before(иконка)..flip-front/.flip-back— не убирайpadding-bottom.
Безопасный коридор по умолчанию: color, background, border*, border-radius, box-shadow, text-shadow, font-*, letter-spacing, padding/margin (осторожно), transform.
Классы блоков (шпаргалка селекторов)
Стилизуй только нужное. Цвета — снова только var(--longread-*).
| Блок | Селекторы |
|---|---|
| Заголовки | h1…h6 |
| Важное | .block.important, модификаторы .idea / .problem / .solution / .info / .rule |
| Аккордеон | .accordion, .accordion-item, .accordion-head, .accordion-body |
| Вкладки | .tabs-block, .tabs-nav, .tabs-panel |
| Карточки | .flip-front, .flip-back |
| Тест | .test-block, .test-question, .test-feedback |
| Шаги | .steps-item, .steps-num, .steps-title, .steps-body |
| Чеклист | .block.checklist, .checklist-item |
| Сравнение | .compare-col-good, .compare-col-bad |
| Кнопка | .block.btn a |
| Прочее | .block.img img, .block.audio, table/th/td, .block.embed, .comments-block |
Вложенные blockquote (три уровня) — не ломай структуру; цвета уровней тоже через --longread-*.
Правила ИИ-HTML (:::ai_widget)
Имя блока — ai_widget (подчёркивание). Не пиши ai-widget, aiwidget или «просто ссылка на HTML».
ИИ-HTML — самодостаточный HTML. Он обязан стоять в материале как блок :::ai_widget … :::/ai_widget. Без этого блока в редакторе виджета для пользователя нет. Сверстать, залить на CDN и прислать URL — не выполнение задачи.
ИИ-HTML всегда часть материала. Он берёт CSS-переменные темы (--longread-*) у родительского лонгрида и живёт в его вёрстке. Без лонгрида нет переменных темы, блочных отступов и рендер-контекста. Не делай из ИИ-HTML отдельную страницу.
Перед create / bake
Не изобретай ИИ-виджет «от себя». Сначала кратко: зачем виджет, что делает пользователь, чем не закрывается штатными блоками (:::test, :::tabs, …). Дождись согласия («да» / BRIEF ок). Потом локальный .html и цикл ниже. Bake/insert — только после отдельного прямого «да».
Обязательный финал (только IDE-агент с MCP)
Это правило — для Cursor / Claude Code / VS Code с MCP. Чат-боты без MCP (Gemini, ChatGPT и т.п.) bake не делают. Они отдают HTML/markdown кодом. См. сценарий 2 в README.md.
С MCP работа в материале готова после вставки :::ai_widget. Bake на CDN — не на каждой итерации.
Цикл отладки (канон)
локальный .html (+ маркер) → открыть в браузере / править на диске → …
→ спросить «запечь на CDN и вставить в материал?» → ждать прямого «да»
→ create-widget (если ещё нет) → save-widget-draft → bake-widget
→ insert-ai-widget-block (fence = id) → poll archives/ до маркера
Пока пользователь не сказал «да» на bake — правь только локальный файл. Не вызывай bake-widget «на всякий случай». Не пеки после каждого фикса.
После правок уже вставленного виджета снова правь локальный .html. Отладь локально. Снова спроси перед следующим save-widget-draft + bake-widget. Не bake каждый раз сам.
После bake/insert: poll archives/ (не draft). Затем get-material. В content должен быть :::ai_widget с id.
Локальная копия на ПК (обязательно при MCP)
Интерактив через MCP в Демонстраторе — ок. На диске рабочей машины всё равно нужна HTML-копия. По ней смотришь и правишь без share/CDN-ссылок.
Обязательно:
1. Сохрани полный HTML в workspace (widgets/ или interactives/), например widgets/quiz-onboarding-<widget_id>.html (или slug без id, пока виджета ещё нет). 2. Отладь локально (открой файл в браузере). Это основной цикл. 3. В ответе укажи локальный путь. 4. Bake/insert — только после прямого вопроса и прямого «да» пользователя.
Запрещено: bake на каждой правке; только MCP/CDN без файла на диске; «открой по ссылке» как единственный способ смотреть интерактив.
Запрещено считать задачу сделанной (IDE + MCP)
- Отдать пользователю только CDN/URL HTML («вот ссылка, вставь сам»).
- Остановить работу на
bake-widgetбезinsert-ai-widget-block(когда пользователь уже разрешил bake). - Поставить в fence
draft_url. Сдать по свежемуget-widget, покаarchives/без маркера или мигает. - Оставить в медиатеке виджет/медиа без читаемого имени («Без названия», пусто, uuid).
- Вписать голый URL в абзац /
/:::embedвместо:::ai_widget. - Написать опечатку в типе:
:::ai-widget,:::aiwidget. - Не оставить локальный
.htmlна ПК. - Запечь на CDN без прямого подтверждения пользователя.
Чат-бот без MCP
Отдай готовый HTML и/или фрагмент markdown с :::ai_widget … :::/ai_widget. Bake, CDN и вопрос «запечь?» к тебе не относятся. Пользователь вставит сам.
Вёрстка и отступы (не липнуть к потолку)
ИИ-HTML живёт внутри материала (iframe / карточка блока). Не делай из него полноэкранный лендинг вплотную к краям.
Обязательно:
- У корневого контейнера (
body→ первый wrapper) внешний отступ со всех сторон ≥16px(лучше20–24px). Не ставьmargin: 0; padding: 0наhtml, bodyбез компенсации. - Первый визуальный блок (картинка, заголовок, карточка) не должен липнуть к верхнему краю виджета.
- Карточка с картинкой сверху: padding у внешней обёртки или
marginу карточки от краёв iframe. Full-bleed картинка внутри карточки ок, если сама карточка отстоит от края виджета.
Запрещено:
html, body { margin: 0; padding: 0; } /* и сразу hero/img на всю ширину без обёртки с отступом */
.card { margin: 0; } /* карточка вплотную к верху iframe */
.hero { width: 100%; margin: 0; } /* картинка к потолку виджета */
Нужно (идея):
body {
margin: 0;
padding: 20px; /* воздух от краёв блока в лонгриде */
box-sizing: border-box;
}
.card {
overflow: hidden;
border-radius: 12px;
/* картинка может быть edge-to-edge ВНУТРИ card — снаружи уже есть padding body */
}
Виджет без верхнего воздуха — брак вёрстки, переделай до bake / insert.
Русский текст внутри виджета — по 06-russian-typography.md (—, «ёлочки», …).
Скролл внутри виджета
По умолчанию без скролла. Короткие реплики, обратная связь и описание ситуации должны целиком помещаться в кадр. Не ставь overflow: auto|scroll и фиксированную высоту «наугад», если контент можно уложить или разбить на шаги.
Скролл — только явный замысел. Пример: длинный текст, который пользователь сознательно читает или открывает дополнительно.
| Контент | Скролл |
|---|---|
| Реплика персонажа | Нет |
| Обратная связь к ответу | Нет |
| Короткое описание ситуации | Нет |
| Развёрнутое объяснение последствий | Да (если иначе не влезает) |
| Уточнение деталей по кнопке | Да |
Запрещено: весь экран кейса / квиза в одном скролл-боксе «на всякий случай». Скролл ради ленивой вёрстки короткого текста.
Тема и цвета (обязательно)
Внутри :::ai_widget цвета темы приходят как --longread-* на :root (сервер / THEME). Любой цвет в CSS виджета — только var(--longread-…).
| Переменная | Назначение |
|---|---|
--longread-bg | Фон |
--longread-text | Текст |
--longread-surface | Фон карточек |
--longread-surface-text | Текст на карточках |
--longread-headings | Заголовки (если есть) |
--longread-accent | Акцент |
--longread-accent-pale | Бледный акцент |
--longread-positive | Зелёный |
--longread-negative | Красный |
--longread-important-bg | Фон important |
--longread-border | Границы |
button {
background: var(--longread-accent);
color: var(--longread-surface);
}
Запрещено
- Голые
#hex,rgb()/rgba()/hsl(),white/redи т.п. в свойствах цвета. - Свои палитры и
--brand-*вместо--longread-*. - «Красивый» цвет мимо темы материала.
Self-lint перед сдачей HTML: в <style> нет цветовых литералов вне var(--longread-*) (кроме согласованного исключения).
Готовых CSS-китов нет — классы свои, цвета только из переменных.
<style id="widget-escape"> — CSS внутри переживает переинжекцию темы; цвета там всё равно через --longread-*, не hex.
Запрещено сервером
<script src="...">— внешние скрипты удаляются при bake.- URL изображений без распознаваемого расширения:
.webp,.png,.jpg,.jpeg,.gif,.svg.
Разрешено
- Инлайн
<script>безsrc. Синтаксис JavaScript должен быть валидным. - Инлайн
<style>.
Ограничение размера
500 КБ. Сервер отклоняет HTML больше этого лимита.
Изображения
Только с CDN проекта. URL должен заканчиваться на одно из: .webp, .png, .jpg, .jpeg, .gif, .svg.
MCP-путь (IDE)
1. Сохрани и отладь HTML локально (widgets/<slug>.html). Это основной цикл. Перед bake поставь в HTML уникальный маркер (комментарий / data-build). 2. Спроси: «запечь на CDN и вставить в материал?» Жди прямого «да». Не bake без ответа. 3. create-widget — получи id черновика (material_id / project_id). Всегда передай name — читаемое имя в медиатеке (не uuid, не пусто). 4. save-widget-draft — HTML с диска + theme + assets. 5. bake-widget — дождись статуса ready (ключ archives/{id}/…). 6. insert-ai-widget-block — в fence только CDN entry_url (после ready). Не bare id и не draft_url. 7. Poll archive: get-resource-status / archive-info → entry_url / archives/…. Маркер в archive HTML должен совпасть с маркером локального файла. Пока не совпало — poll. Не сдавай по get-widget / draft. 8. get-material — проверь fence. Пользователю укажи путь к локальному .html.
Шаблон линейного диалога
Для диалогового плеера предпочитай assemble-linear-dialog вместо свободного HTML:
1. Сценарий JSON на диске (widgets/<slug>-scenario.json) — эталон docs/templates/linear-dialog/scenario.example.json. characters[] = пул портретов; на реплике — leftPortraitId, rightPortraitId, speakerSide. 2. create-widget → assemble-linear-dialog (widget_id + scenario). save-widget-draft не нужен. 3. Bake/insert — только после прямого «да». Insert = CDN URL. 4. Фича ai_widget_templates — по умолчанию тестеры; при 403 не обходи без согласования.
Правка уже собранного диалога (тот же tool):
1. get-widget → scenario (+ локальный *-scenario.json). 2. Правишь JSON (реплики / портреты / пары слотов / локации) — полный объект, не патч HTML. 3. Снова assemble-linear-dialog с тем же widget_id. 4. Не вызывай save-widget-draft и не правь shell плеера руками. 5. Повторный bake — только после «да»; в материал снова CDN entry_url.
Имя в медиатеке обязательно. Если создал без имени — сразу rename-resource. «Без названия» / пустой ярлык — брак сдачи.
| Слой | В материал? |
|---|---|
Draft / draft_url / bare id / свежий get-widget | нет |
archives/{id}/ CDN entry_url при ready | да |
После bake CDN может кратко отдавать старое поколение на том же URL. Держи poll archive. Не ставь draft в материал. Повторный bake — только после явного «да».
Повторные правки: снова локально → снова вопрос перед bake. Не пеки на каждой итерации.
Не подменяй insert ссылкой на CDN или draft. Не пропускай локальный файл.
Русская типографика
Весь русский текст, который ты пишешь в материалы Демонстратора (markdown уроков, подписи, :::important, тексты внутри ИИ-HTML), подчиняется русской типографике. Смысл не менять — только оформление.
Английский, код, URL, имена блоков :::тип — не трогать этими правилами.
Обязательно
| Что | Как |
|---|---|
| Тире между словами / пояснение | Длинное — (U+2014) с пробелами: Редактор — инструмент |
| Дефис | Только в словах и номерах без пробелов: онлайн-курс, какой-то |
| Кавычки | «ёлочки»; вложенные — „лапки“ |
| Многоточие | Символ … (U+2026), не три точки «на глаз» и не ... в прозе |
| Число и единица | Неразрывный пробел: 3 дня, 10–15 минут, 690 ₽ |
| Сокращения | и т. д., т. п. — с неразрывными пробелами где нужно |
Не ставь дефис - вместо тире между частями предложения. Не ставь прямые кавычки "..." в русском тексте.
Неразрывный пробел в материалах
В markdown материалов редактора:
- Предпочтительно символ Unicode NBSP (U+00A0), не литерал
(в редакторе сущность часто видна как текст). - Ставь после коротких предлогов/союзов/частиц, чтобы не оставлять их в конце строки: и, а, но, в, во, к, ко, с, со, о, об, от, из, за, на, до, по, при, для, без, или, что, как, то, не, ни, бы, же, ли, уже, ещё, у.
- Между числом и единицей / в
и т. д.— тоже NBSP.
Если среда агента плохо вставляет U+00A0 — хотя бы соблюдай тире, кавычки и многоточие; пробелы после коротких слов по возможности.
Где применять
| Контекст | Типографика |
|---|---|
| Текст урока / блока / подписи | Обязательно |
Русский текст внутри :::ai_widget / локального .html | Обязательно |
Комментарии %%, код, JSON, CSS, латиница | Не применять «ёлочки» к коду |
Антипаттерны
Редактор - это инструмент(дефис вместо —)"важный термин"вместо «важный термин»подождите...вместоподождите… как видимая строка в теле материала
Чеклист перед сдачей русского текста
- [ ] Тире — «—», не
- - [ ] Кавычки — « » / „ “
- [ ] Многоточие — …
- [ ] Числа с единицами и короткие слова — без «висячих» разрывов (NBSP где уместно)