Перейти к основному содержанию
С SDK 0.4 мини-приложение может пользоваться хромом хоста, как встроенные AI-чаты Exode: шапкой с меню «…», кнопкой «Назад», главной кнопкой у низа экрана, полем ввода с микрофоном, попапами, вибрацией, полноэкранным режимом и окном «Поделиться». В мобильном приложении Exode всё это нативное, в вебе — интерфейс школы.

Что умеет хост

При handshake хост передаёт в контексте список возможностей (capabilities). Проверяйте их через app.isSupported(capability) или хук useExodeSupports():
Хост до 0.4 ничего не объявляет. Тогда методы SDK падают обратно на веб (window.alert / confirm / prompt, navigator.share, navigator.clipboard, Fullscreen API, navigator.vibrate), а шапка, «Назад», главная кнопка и поле ввода ничего не делают — хуки возвращают isSupported: false, и приложение рисует свой интерфейс.

Края и тема

ExodeMiniAppProvider сразу включает нативный вид; у ExodeMiniApp его включают параметры конструктора: Переменные на <html>:
  • --exode-inset-{top,right,bottom,left} — что держать свободным у каждого края (больший из хрома хоста и safe area);
  • --exode-safe-area-inset-*, --exode-content-inset-* — safe area устройства и хром хоста по отдельности;
  • --exode-viewport-width, --exode-viewport-height, --exode-content-height — размер фрейма и высота без хрома;
  • --exode-accent и --exode-color-*: background, content, elevated, text, text-secondary, text-tertiary, accent, accent-foreground, separator, destructive, positive.
Атрибуты <html>: data-exode-platform (native / web), data-exode-os (ios / android / web), data-exode-scheme, data-exode-fullscreen, data-exode-orientation, data-exode-edge. Высоту экрана задавайте как min-height: var(--exode-content-height, 100dvh), а свои панели с position: fixed — с bottom: var(--exode-inset-bottom).

Без вспышки при загрузке

Хост кладёт тему и края в имя фрейма (window.name с префиксом exode-miniapp:) — SDK применяет их ещё до handshake. Приложению с SSR добавьте в <head> скрипт exodeBootScript() — тема и отступы окажутся на странице до первой отрисовки. Скрипт меняет атрибуты <html> до React, поэтому нужен suppressHydrationWarning:
Скрипт принимает те же параметры, что провайдер (по умолчанию { edgeToEdge: true, applyTheme: true }).

Шапка и меню «…»

  • title — заголовок страницы; null — название, которое школа дала странице.
  • Кнопки (left — до одной, right — до шести) хост показывает пунктами одного меню «…» справа, left первым: ряд кнопок у заголовка теснил бы длинное название. label — текст пункта (без него показывается id), icon — одна из иконок MiniAppIcon: add, more, settings, search, share, info, help, edit, history, filter, sort, refresh, close, check, delete, download, upload, favorite, notifications, user, calendar, list, grid, qr. Кнопка с другой иконкой отбрасывается.
  • В вебе в меню «…» за разделителем — пункты хоста «На главную» и «Обновить» (мини-приложение загружается заново). В окне страницы на ПК кнопки стоят иконками в шапке окна, label — подсказка.
  • Хук действует, пока компонент смонтирован: после размонтирования — заголовок страницы и никаких кнопок. Шапка у хоста одна — используйте один такой хук на экран (побеждает последний смонтированный).

«Назад»

Пока приложение держит «Назад», кнопка «Назад» шапки присылает backButton:pressed вместо ухода со страницы. На телефоне туда же ведут кнопка и свайп «Назад» браузера, а в приложении на iOS свайп назад выключается.

Главная кнопка

Кнопка закреплена у низа экрана (в приложении — нативная). Без text хост её не показывает. loading — спиннер вместо текста, disabled — приглушена и не нажимается, color и textColor — CSS-цвета фона и текста (по умолчанию — акцент школы). Одна главная кнопка на экран.

Поле ввода

Поле внизу экрана, как в чатах: клавиатура, вложения, микрофон.
События: composer:submit ({ text, files, voice? }), composer:voice ({ file, duration } — File и секунды), composer:change ({ text }), composer:stop, composer:scrollDown. Хук возвращает isSupported, canDictate, canRecordVoice, canVoiceOnly, canDictateAudio, canReview, canNoCancel, canAttach, а также setText(text) ('' очищает поле) и dismissKeyboard(). Пока поле показано, нижнее меню хоста скрыто. Поле у хоста одно — один такой хук на экран. В приложении протягивание ленты вниз прячет клавиатуру нативного поля, как в чатах.

Только голос

Для тренажёров речи: ни поля, ни клавиатуры — только кнопки, которые говорят, что делают. Нажать — слушать (на кнопке — ваш placeholder), «Отправить» — отправить, крестик — сбросить; пауза в речи ничего не отправляет. Вложения в этом режиме не предлагаются, busy превращает кнопку в «Остановить» (composer:stop).
  • mic: 'dictation' — пока говорят, услышанное приходит в composer:change, целиком — в composer:submit. Где речь в текст не распознаётся (браузер без Web Speech API), остаётся обычное поле.
  • mic: 'voice' — запись приходит в composer:voice.
  • recordAudio (composer.dictation.audio) — с диктовкой приходит и запись речи (voice в composer:submit), например для модели, которая слушает произношение. Где хост не может её записать, voice не будет.
  • liveTranscript — хост показывает услышанное над кнопками. Не задано — показывает, только если нет recordAudio.
  • review (composer.voiceOnly.review) — сказанное попадает в поле хоста на правку, отправляет пользователь. Хост без этой возможности отправит сразу.
  • noCancel (composer.voiceOnly.noCancel) — для тренажёров, которые считают попытки: ни крестика, пока слушает, ни «Заново» при правке. В браузере это действует для диктовки; у записи голосом (mic: 'voice') крестик рекордера остаётся.

Попапы

  • show — от одной до трёх кнопок (по умолчанию одна close). Типы кнопок: default, ok, close, cancel, destructive; для default и destructive нужен text, у остальных текст хоста. id по умолчанию — тип кнопки.
  • prompt принимает title, message, placeholder, value, multiline, required, minLength, maxLength, okText, cancelText.
  • В приложении алерт с одной-двумя кнопками и prompt — нативные. Ответа ждут сколько угодно, без таймаута.
  • Скрытое мини-приложение (пользователь ушёл на другую страницу) попап не покажет — придёт null.

Весь экран и поворот

  • На телефоне весь экран — без шапки и нижнего меню хоста, на ПК — Fullscreen API самого фрейма (выход по Esc приложение узнает из viewport:changed).
  • Поворот экрана (orientation) есть только в приложении. Без этой возможности SDK пробует screen.orientation.lock браузера — обычно он работает только в полноэкранном режиме.
  • Без хука: app.viewport.requestFullscreen(), app.getViewport(), app.getInsets() и событие viewport:changed (MiniAppViewport).

Вибрация

В приложении — нативная вибрация телефона, в вебе — вибрация браузера; где её нет, вызов ничего не делает. Хост без возможности haptic заменяется на navigator.vibrate (браузер разрешает её только после жеста пользователя).

Ссылки, «Поделиться», буфер обмена

  • Хост открывает только ссылки http:, https:, mailto: и tel: и передаёт адрес так, как его разобрал браузер (new URL(url).href). Ссылки с другой схемой и невалидные адреса хост молча игнорирует. В вебе ссылка открывается в новой вкладке.
  • У share адрес проверяется так же: невалидный отбрасывается, и если не остаётся text, share вернёт false. В приложении открывается нативное окно «Поделиться», в вебе — navigator.share. Без возможности share и без «Поделиться» браузера share возвращает false — текст при этом не копируется.
  • copyText в приложении пишет в буфер нативно, в вебе — через navigator.clipboard.
  • Скрытое мини-приложение ссылку не откроет, а share и copyText вернут false.

Силуэт загрузки и devtools

Пока грузится код мини-приложения, хост рисует силуэт загрузки страницы. Строку силуэта не нужно писать руками: devtools SDK (0.9+) измеряют первый экран и собирают её сами. В продакшен-сборку они не попадают — это отдельный модуль, в @exode-team/sdk/miniapp и miniapp.global.js их нет. Из консоли браузера, без изменений в коде (во фрейме школы переключите консоль на контекст фрейма):
В коде — только для разработки:
Из модуля доступны и сами функции: measureSkeleton({ root }) — строка, copySkeleton() — строка в консоль и буфер, showSkeleton(line?), hideSkeleton(). Если у приложения есть свой скелетон, пометьте его корень атрибутом data-exode-skeleton — devtools возьмут силуэт с него (скелетон должен быть на экране во время замера). Готовую строку вставьте в поле «Силуэт загрузки» страницы школы.
Обновлено: 2026-10-22 23:06 UTC