Skip to content

APIリファレンス

サンドボックスが公開する すべて の完全リスト — グローバル、コンポーネント、useYumina() の全フィールドとメソッド、型定義、ブロックされたブラウザAPIの代替。

これは リファレンスドキュメント であり、チュートリアルではありません。まず カスタムUIガイド を読んで全体像を掴み、特定のシグネチャを調べる時にここに来てください。

このページの内容はすべて packages/app/sandbox/ の実装から派生しており、エディタに同梱されるサンドボックスバージョンと一致します。


サンドボックスのグローバル

これらの名前は import文なしでルートコンポーネントツリーのどこでも利用可能 です。

名前種類内容
ReactmoduleフルReact(useStateuseEffectuseRefuseMemouseCallbackuseLayoutEffectFragment、…)
useYuminahookプラットフォームSDK — useYumina() SDK を参照
useAssetFonthookアセットライブラリからカスタムフォントを読み込む — useAssetFont() を参照
Iconsobject1400以上のLucideアイコンコンポーネント:<Icons.Heart /><Icons.Sword />。完全カタログ:https://lucide.dev/icons
Chatcomponentフルチャットビルディングブロック — <Chat> を参照
MessageListcomponent入力なしのメッセージ — <MessageList> を参照
MessageInputcomponent入力バーのみ — <MessageInput> を参照
ChatCanvascomponent<Chat /> のレガシーエイリアス — <ChatCanvas> を参照
exportsmoduleobjectCJSスタイルのエクスポートフォールバック。通常無視してよい

Reactや上記の名前をimportしないでください — サンドボックスによって注入されます。import React from "react" と書いてもコンパイル時に静かに削除されますが冗長です。

自分のファイルはimport可能 — マルチファイルのルートコンポーネントはESモジュール構文を使います:import StatBar from "./stat-bar"。拡張子 .tsx.ts.jsx.js は省略可。


useYumina() SDK

コンポーネント関数内で呼び出します:

tsx
function MyWorld() {
  const api = useYumina()
  // api.variables, api.sendMessage(...), ...
}

目的別にグループ化した完全な表面:

状態読み取り(同期)

最新のゲーム状態を読みます。これらが変化するたびにコンポーネントが再レンダリングされます。

フィールド意味
variablesRecord<string, unknown>セッションスコープのゲーム変数。例:{ health: 80, gold: 150 }
globalVariablesRecord<string, unknown>すべてのセッションで共有されるグローバル変数
worldNamestring現在のワールドの名前
worldIdstring現在のワールドのUUID
sessionIdstring現在のプレイセッションのUUID
currentUser{ id, name?, image? } | null生のアカウント:id、表示名、アカウントアバター。ログアウト時は null。「プロフィールを見る」のようなアカウントレベルのUIに使う。ワールド内のロールプレイレンダリングには user を使う
user{ name: string; avatar: string | null }ロールプレイされているプレイヤー — マクロと同じペルソナ vs アカウントの分岐。プレイヤーがペルソナをアクティブにしているとき、user.name はペルソナ名、user.avatar はペルソナアバター。それ以外はアカウントにフォールバック。ワールド内チャットバブル、キャラクターカード、プロフィールパネルに使いたいのはこちら
mode"session" | "guest-preview""session" は実際のプレイセッション。"guest-preview" はログアウト状態のハブプレビュー — 状態を変更するアクションはno-opとなり、サインインプロンプトを親に表示
capabilities{ canSendMessage, canPersistSession, canUseSessionApis, requiresAuth }現在の mode が許可するもの。no-opになるボタン(ゲストプレビューのSendボタンなど)を無効化したり、インラインの「サインインして続行」CTAをレンダリングするのに読む
languagestringホストからのアクティブなi18n言語コード("en""zh" …)。ホストのi18nextインスタンスに依存せずカード内で翻訳を選ぶのに使う
messagesArray<Record<string, unknown>>完全なメッセージ履歴 — SandboxMessage を参照
isStreamingbooleanAIが返信生成中は true
streamingContentstringAIからのライブストリーミングテキスト(頻繁に更新)
streamingReasoningstringAIからのライブ「thinking」/reasoningテキスト(reasoningモデルのみ)
pendingChoicesstring[]ルールによって出力された選択ボタンラベル
errorstring | null現在のエラーメッセージ(APIエラー、生成エラー)または null
readOnlyboolean他者のセッションを表示しているときは true<Chat /> は自動的に入力を非表示
checkpointsArray<Checkpoint>保存されたチェックポイント — Checkpoint を参照
greetingContentstring | nullワールドエントリから計算された挨拶テキスト(<Chat /> が空状態コンテンツとして使用)
canvasMode"chat" | "custom" | "fullscreen"現在のキャンバスモード
selectedModelstring現在選択されているAIモデルID
userPlanstringユーザーのサブスクリプションプラン("free""go""plus""pro""ultra""internal"
preferredProvider"official" | "private"公式API vs ユーザー独自の鍵
entriesReadonlyArray<SandboxEntry>ワールドのロアブックエントリ — 有効なもののみ、position でソート済み。ロアブックの検索SandboxEntry を参照

ゲームアクション(fire-and-forget)

これらのメソッドは何も返しません。意図を親アプリにポストするだけです。

メソッド何をするか
sendMessage(text)プレイヤーとしてメッセージを送信し、AI応答をトリガー
setVariable(id, value, options?)変数を設定。options{ scope?: string; targetUserId?: string }scope で変数スコープを選ぶ(global/personal用)、targetUserId でマルチプレイヤーの特定プレイヤー用の変数を書き込める
executeAction(actionId)ルールエンジンで定義された名前付きアクションを発火(例 "attackBoss"
switchGreeting(index)異なる挨拶バリアントにインデックスで切り替え
clearPendingChoices()選択肢を選ばずにペンディングの選択ボタンを破棄
setComposerDraft(text)text をチャットコンポーザーにドロップしてフォーカス。送信しない。 送信前にプレイヤーにメッセージを確認・編集させたいときに使う(会話の口火を準備するNPC対話ボタンなど)。サンドボックスローカル — 親への往復なし — なので同梱の <MessageInput> / <Chat> コンポーネントと併用する場合のみ機能

チャット制御

デフォルトのチャットバーができることすべてを公開し、カスタムUIでも同じことができるようにしたもの。

メソッド何をするか
editMessage(messageId, content)既存メッセージを編集。Promise<boolean> を返す。成功時 true
deleteMessage(messageId)メッセージを削除。Promise<boolean> を返す
regenerateMessage(messageId)AIに指定した返信の再生成を依頼(fire-and-forget)
continueLastMessage()最後のAIメッセージから生成を継続(fire-and-forget)
stopGeneration()現在のストリームを中断(fire-and-forget)
restartChat()すべてのメッセージをクリアし状態をリセット、新規開始
swipeMessage(messageId, "left" | "right")メッセージのAI代替(スワイプ)を切り替え。Promise<Record<string, unknown>> を返す

セッションとブランチ

メソッド何をするか
revertToMessage(messageId)会話を messageId の直前まで巻き戻す。Promise<void> を返す
branchFromMessage(messageId)指定メッセージで新規セッションをフォーク(そのメッセージまでとそれを含むメッセージと状態スナップショットをクローン)。Promise<string | null> を返す — 新セッションID、失敗時は null(ストリーミング中、マルチプレイヤールーム、メッセージ欠落で失敗)
getBranchContext()現在のブランチスライス(自身、親、兄弟、子)を取得。Promise<BranchContext> を返す。毎回再取得、クライアントキャッシュなし。BranchContext を参照
createSession(worldId)ワールド用の新規セッションを開始。新セッションIDを持つ Promise<string> を返す
deleteSession(sessionId)セッションを削除。Promise<void> を返す
listSessions(worldId)ワールドのすべてのセッションをリスト。Promise<Array<Record<string, unknown>>> を返す

チェックポイント

チェックポイントは現在のセッション内の名前付きスナップショットで、巻き戻し可能。

メソッド何をするか
saveCheckpoint()現在のセッション状態を新規チェックポイントとして保存。Promise<void> を返す(その後 checkpoints フィールドがプッシュバックされる)
loadCheckpoints()親に checkpoints 配列のリフレッシュを依頼。Promise<void> を返す
restoreCheckpoint(checkpointId)セッションを保存済みチェックポイントに復元。Promise<void> を返す
deleteCheckpoint(checkpointId)チェックポイントを削除。Promise<void> を返す

オーディオ

メソッド何をするか
playAudio(trackId, opts?)エントリで定義されたオーディオトラックを再生。opts{ volume?, fadeDuration?, chainTo?, maxDuration?, duckBgm?, loop? } — duration(fadeDurationmaxDuration)は単位、chainTo は次に再生するtrackIdを選択、duckBgm は再生中BGMを下げる、loop はこのトラックの loop 設定を今回の再生だけ上書きする
stopAudio(trackId?, fadeDuration?)トラックを停止(trackId 省略ですべて停止)。fadeDuration は秒単位。要素を破棄する —— 同じ位置から再開したい場合は pauseAudio を使う
pauseAudio(trackId)トラックをその場で一時停止し、再生位置を保持する
resumeAudio(trackId)pauseAudio で一時停止したトラックを再開する
onAudioEnded(cb)「ループしないトラックの再生終了」を購読 — cb(trackId)。解除用の関数を返す。自作プレイヤーの自動次曲送りに使う
setAudioVolume(type, volume)type"bgm" または "sfx"volume は 0-1
getAudioVolume(type)現在の音量(0-1)を同期的に返す

UI/ナビゲーション

メソッド何をするか
toggleImmersive()没入(フルスクリーン)モードを切り替え
openPersonaManager()プレイヤーのペルソナ管理を開く — ワールドを離れずにペルソナを切り替え / 作成 / 編集(コンポーザーの「+」メニューと同じパネル)。アクティブなペルソナはアカウント全体で共有され、切り替えは次のメッセージから反映
copyToClipboard(text)クリップボードにコピー(navigator.clipboard.writeText の代替)
navigate(path)親に "/app/hub" のようなパスへのルーティングを依頼(window.location = ... の代替)
showToast(message, type?)親UIにトーストを表示。type"success""error""info"(デフォルト)

永続ストレージ(ワールド単位)

localStorageの代替。worldId でスコープされ、ワールド同士は互いのキーを読めません。

メソッド何をするか
storage.get(key)読み取り。Promise<string | null> を返す
storage.set(key, value)書き込み(文字列のみ)。Promise<void> を返す
storage.remove(key)削除。Promise<void> を返す

複雑なデータが必要? 入出力で JSON.stringify / JSON.parse を。

ロアブックの検索

カード内からワールドのロアブックへの読み取り専用アクセス。サイドLLMプロンプトを組み立てる時の検査や手選びエントリ、ゲーム内ジャーナルビューア構築、デバッグパネル配線に有用。

フィールド/メソッド何をするか
entriesReadonlyArray<SandboxEntry> — 有効なすべてのエントリを position で事前ソート。SandboxEntry を参照
getEntry(name)正確な名前 で1つのエントリを検索。SandboxEntry | null を返す。localhost ではミス時に利用可能な名前と共に一度限り警告ログ — エントリ名変更を忘れたとき便利

ほとんどの場合これらを直接触る必要はありません:ai.complete()includeLorebook: "matched" を渡せばサーバが伝承を組み立てます(下記)。entries / getEntry を使うのは外科的制御が必要なとき — 例 「このNPCは tavern タグのエントリだけを知っている」

生のAIコンプリーション

メインチャットパイプラインの でLLMを呼び出す。「サイドパネルのNPC内的独白」「AI生成のアイテム説明」「カード内電話チャット」などに使う。メッセージ履歴に書き込みません、状態更新もトリガーしません、greetingsも消費しません。

tsx
const api = useYumina()
const text = await api.ai.complete({
  messages: [
    { role: "system", content: "You are a surly merchant." },
    { role: "user", content: "Price me an iron sword." },
  ],
  onDelta: (chunk) => setStreaming((s) => s + chunk),  // optional, per-token
  model: "claude-sonnet-4-6",                           // optional, defaults to selectedModel
  maxTokens: 500,                                       // optional, default 2048, max 8192
  temperature: 0.7,                                     // optional
  includeLorebook: "matched",                           // optional — see below
})

完全な応答を持つ Promise<string> を返す。120秒のクライアント側タイムアウト。

制限とコスト

制限ソース
1コールあたり最大メッセージ数50サーバがHTTP 400で拒否
最大合計コンテンツ全メッセージ合計50,000文字サーバがHTTP 400で拒否
maxTokens デフォルト2048省略時のデフォルト
maxTokens 上限8192より大きな値は静かにクランプ
temperature 範囲0-2、デフォルト1.0範囲外はクランプ
デフォルトモデルプレイヤーの selectedModelmodelselectedModel も未設定なら anthropic/claude-sonnet-4.6 にフォールバック
レート制限メインチャットと共有 — サイドコールとメインチャットターンは同じ毎分予算に対してカウントオーバー時 HTTP 429 + INSUFFICIENT_CREDITS スタイルコードを返す
クレジットメインチャットと同じトークン単位の課金。BYOKユーザーはサーバクレジット控除をスキップ するが自身のプロバイダには支払うエンドポイント "side-completion" でログ
認証セッションは現在のプレイヤーに属する必要あり、そうでなければコールはHTTP 404で失敗

includeLorebook — ワールド伝承の自動注入

サイドコールはメインチャットのプロンプト組み立てをバイパスするので、伝承を与えない限りモデルはあなたのキャラクターが誰か分かりません。includeLorebook を渡すと、サーバはワールドのエントリから構築したシステムメッセージを前置きします。

動作
omitted / false注入なし(デフォルト)。ワールドコンテキスト不要な翻訳、要約、分類などに使う
true / "all"有効な非greetingエントリすべてを position でソートして注入。予測可能、より大きなトークンコスト
"matched"messages 内の last user message に対してメインチャットと同じキーワードマッチャーを実行。alwaysSend エントリは常に含まれ、キーワードトリガーエントリは関連時のみ追加。キャラクターに留まるサイドコールに推奨

これがないと、「キャラクターに留まる」サイドコールはモデルが名前だけから性格をでっち上げ — カード内ペルソナがメインチャットから漂流します。"matched" ならNPCとの電話チャットがメインチャットと同じワールド伝承+キャラクタープロフィールを参照します。

tsx
// A phone chat that stays in canon
api.ai.complete({
  messages: [
    { role: "system", content: "Stay strictly in character as Balder. Reply in one or two short lines." },
    ...history,
    { role: "user", content: userText },
  ],
  includeLorebook: "matched",  // server pulls Balder's profile + relevant world lore
})

より細かい制御が必要なら — 名前で特定エントリを注入、または特定タグのエントリのみ — includeLorebook の代わりに api.entries をイテレートして自分でシステムメッセージを組み立てます:

tsx
const tavernLore = api.entries
  .filter((e) => e.tags?.includes("tavern"))
  .map((e) => `【${e.name}】\n${e.content}`)
  .join("\n\n")

api.ai.complete({
  messages: [
    { role: "system", content: `You are the tavern keeper.\n\n${tavernLore}` },
    { role: "user", content: userText },
  ],
})

"matched" モードの注意点:最後のユーザーメッセージのキーワードしかスキャンしません(全履歴ではない)、ゲーム変数依存の条件ゲート付きエントリはサイドコールでは発火しません(マッチャーには空状態スタブが見える)。精度がトークンより重要なら true を使ってすべてを強制的に含めましょう。

コンテキスト注入

次の メインチャットAIターンに 単発 のコンテキストメッセージを注入。一度使われたら消費される、見える チャットメッセージは作られない。「電話メッセージ」「NPCのオフステージ対話」「環境変化」など、メインAIが知るべきがプレイヤーにはチャットバブルとして見せたくないものに最適。

tsx
api.injectContext("You just received a cryptic text: 'Tonight, 9pm, usual place.'", { role: "system" })
// On the player's next message, the main AI will see this as a system message.

options{ role?: "system" \| "user" }(デフォルトは "system")。

モデルピッカー

フィールド/メソッド何をするか
selectedModel現在のモデルID
userPlanユーザーのプランティア
preferredProvider"official" または "private"
setModel(modelId)モデルを切り替え(fire-and-forget)
getModels()Promise<{ models, pinnedModels, recentlyUsed }> を返す。modelsArray<{ id, name, provider, contextLength }>
pinModel(modelId) / unpinModel(modelId)モデルをピン留め/解除

アセット

メソッド何をするか
resolveAssetUrl(ref)@asset:xxx 参照をCDN URLに変換。純粋な文字列変換、ネットワークなし。HTTP/HTTPS URLはそのまま通過

マークダウン

メソッド何をするか
renderMarkdown(text)マークダウンを 安全なHTML に変換(HTMLエンティティはエスケープ、危険なタグは剥がす、整形は保持)。結果をカスタムバブル内の dangerouslySetInnerHTML に渡せば安全 — 下の例を参照
tsx
<div dangerouslySetInnerHTML={{ __html: api.renderMarkdown(msg.rawContent) }} />

コンポーネント

<Chat>

プラットフォームのフルチャット体験。これが日常のビルディングブロック — propsゼロでデフォルトのチャットになる。

含まれるもの:メッセージリスト、自動スクロール、ストリーミングカーソル、スワイプコントロール、メッセージアクション(編集/削除/再生成)、入力バー、選択ボタン、モデルピッカー、読み取り専用モード、greetingプレースホルダ。

tsx
<Chat renderBubble={(msg) => <MyBubble {...msg} />} />

Props

Prop説明
renderBubble?(props: BubbleProps) => ReactNode各メッセージバブルの見た目をカスタマイズ。省略時はデフォルトのmarkdownレンダリングにフォールバック
className?string外側コンテナに追加するCSSクラス
children?ReactNodeメッセージリストの にレンダリングされるコンテンツ(固定HUDヘッダーなど)

BubbleProps

renderBubble コールバックが受け取る msg オブジェクト:

フィールド意味
contentHtmlstring事前レンダリングされた安全なHTML(markdownはすでに変換済み)。通常は dangerouslySetInnerHTML に流す
rawContentstringレンダリング前の生markdownテキスト(ディレクティブテキスト含む)
role"user" | "assistant" | "system"メッセージの出所
messageIndexnumberリスト内位置(0 = 最初、通常greeting)
isStreamingbooleanこのメッセージがストリーミング中は true
stateSnapshotRecord<string, unknown> | nullこのメッセージが生成された時点のゲーム状態(「当時のHP/locationは何だったか」に有用)
variablesRecord<string, unknown>現在(最新)のゲーム変数
renderMarkdown(text) => stringヘルパ:任意のmarkdownテキストを安全なHTMLに変換

<MessageList>

メッセージストリームだけ(スクロール、ストリーミングカーソル、スワイプコントロール付き)。入力バーなし

tsx
<MessageList />

renderBubble は取らない — バブルをカスタマイズするには <Chat renderBubble={...} /> を使うか、<MessageList> を完全に省略して api.messages を直接読む(ビジュアルノベルパターン)。

<MessageInput>

入力バーだけ(モデルピッカー、選択ボタン、continue/restartメニュー、ストリーミング状態付き)。

tsx
<MessageInput />

api.readOnlytrue のとき自動で隠れる。

<ChatCanvas>

レガシーエイリアス<Chat /> と同一。古いワールドは引き続き動作し、新コードは <Chat /> を優先すべき。


useAssetFont()

アップロードされたフォントアセットを @font-face として読み込み、CSS font-family 値にそのまま入れられる文字列を返す。

tsx
const fontFamily = useAssetFont("@asset:my-font-id", {
  family: "Cinzel",
  fallback: "serif",
})
return <div style={{ fontFamily }}>Ancient runes</div>

シグネチャ

ts
useAssetFont(
  assetRef: string | null | undefined,
  options?: AssetFontOptions
): string

フォントは非同期で読み込まれる。読み込み中、フックは options.fallback(デフォルト "serif")を返す。準備できると再レンダリングが発火し、完全なファミリー文字列(名前衝突を避けるためサフィックス付き)になる。

AssetFontOptions

フィールド説明
family?stringフォントファミリー名。省略時はファイル名または assetRef から推測
fallback?string読み込み中表示されるフォールバックフォント。デフォルト "serif"
filename?string | null元のファイル名、フォーマット推測に使う
mimeType?string | nullMIMEタイプ、フォーマット推測に使う
format?"opentype" | "truetype" | "woff" | "woff2" | null明示的フォーマットオーバーライド
weight?string | numberfont-weight
style?stringfont-style(例 "italic"
stretch?stringfont-stretch
display?FontDisplayfont-display(デフォルト "swap"

SandboxMessage

api.messages 各要素の形:

ts
interface SandboxMessage {
  id: string
  sessionId: string
  role: "user" | "assistant" | "system"
  content: string
  status?: "complete" | "streaming" | "failed"
  errorMessage?: string | null
  authorUserId?: string | null          // who sent it (multiplayer)
  authorNameSnapshot?: string | null    // their display name at send time
  stateChanges?: Record<string, unknown> | null   // diff of variable updates from this message
  stateSnapshot?: Record<string, unknown> | null  // full state at message generation
  swipes?: Array<{ content, stateSnapshot }>      // alternative AI replies
  activeSwipeIndex?: number
  model?: string | null
  tokenCount?: number | null
  generationTimeMs?: number | null
  compacted?: boolean                   // hidden in the "older messages" section
  attachments?: Array<{ type, mimeType, name, url }> | null
  createdAt: string                     // ISO-8601
}

Checkpoint

ts
interface Checkpoint {
  id: string
  name: string
  messageCount: number
  createdAt: string   // ISO-8601
}

SandboxEntry

api.entriesapi.getEntry() で公開される、単一の読み取り専用ロアブックエントリ:

ts
interface SandboxEntry {
  id: string
  name: string
  content: string
  keywords: string[]
  position: number
  section: "system-presets" | "examples" | "chat-history" | "post-history"
  enabled: boolean
  role: string                            // "system" | "character" | "lore" | etc.
  tags?: string[]
}

これはエンジン内部の WorldEntry のスリムビュー — プロンプト組み立てにカードが必要なフィールドだけ。ランタイムは無効なエントリを事前フィルタし、position で事前ソートするので、カードがそれをやる必要はない。

BranchContext

ts
interface BranchNode {
  id: string
  name: string | null
  parentSessionId: string | null
  branchedFromMessageId: string | null
  messageCount: number
  updatedAt: string   // ISO-8601
  createdAt: string   // ISO-8601
}

interface BranchContext {
  current: BranchNode          // the session you're in
  parent: BranchNode | null    // the branch you forked from, or null at the root
  siblings: BranchNode[]       // other branches forked from the same parent, oldest first
  children: BranchNode[]       // branches forked off `current`, oldest first
}

ブロックされたブラウザAPI

コードは sandbox="allow-scripts" のクロスオリジンiframe内で動作し、allow-same-originありません。つまり:

  • 親アプリのCookie/localStorageへのアクセス不可
  • 認証付きネットワークリクエスト不可
  • 直接の window.parent 操作不可

以下のAPIは 完全にブロック または SDKブリッジ経由で透過的にリダイレクト されます。

リダイレクト(レガシーコードは動作し続ける)

あなたが書いたもの実際に起きること
fetch('/api/...')親の認証付きfetchを通してプロキシ
fetch('/cdn/...')許可(CSPが認める)
fetch('any other URL')拒否(throw)
localStorage.getItem/setItem/removeItem/clearapi.storage 経由でルート、ワールド単位スコープ
sessionStorage.*同じ
navigator.clipboard.writeText()api.copyToClipboard() と等価
navigator.clipboard.readText() / read() / write()拒否(throw)
window.location.pathname / href / assign / replace合成オブジェクト。pathname は常に /app/chat/{sessionId}。代入/assignreplace でナビゲーション発火
window.location.reload()セッション再読み込みにブリッジ
window.__yuminaToggleImmersive()api.toggleImmersive() と等価

推奨される使い方

新コードを書くときは SDKを直接使う — リダイレクトは古いワールド用に存在しますが、SDKの方がクリーンで安定です:

こう書かないでこう書く
fetch('/api/sessions', { method: 'POST' })api.createSession(worldId)
fetch('/api/sessions/' + sid, { method: 'DELETE' })api.deleteSession(sid)
localStorage.getItem("k")await api.storage.get("k")
window.location = "/app/hub"api.navigate("/app/hub")
navigator.clipboard.writeText(t)api.copyToClipboard(t)

利用可能なブラウザAPI

サンドボックスはネットワークや共有オリジンに到達しないものについては寛容です。以下は任意のブラウザと同じく機能し、SDKラッパーは不要です。

APIカードでの典型的用途
<input type="file"> + FileReader.readAsDataURL / readAsTextプレイヤーに画像/音声/テキストファイルを選ばせる → データURLや文字列として変数に保存。Recipe: Player-Uploaded Images を参照
URL.createObjectURL / revokeObjectURLBlob 用の一時的インメモリURLを生成(保存前プレビューなど)
<canvas> + getContext("2d") + toDataURL / toBlob変数保存前に画像をリサイズ、クロップ、合成
<img><audio><video>ローカルオリジンURL、@asset:... 解決済みURL、data:/blob: URLをレンダリング
IntersectionObserverResizeObservermatchMediarequestAnimationFrame標準のレイアウト/アニメーションプリミティブ
crypto.randomUUIDcrypto.subtleクライアント側状態のハッシュとID生成
WebAudioAudioContext軽量なオーディオ合成や解析
Notificationnavigator.vibratescreen.orientationブラウザレベルの許可で制限、サンドボックス自体ではない

一目で:API全体

1つの表、一度スキャン。

useYumina()
├── State reads
│   ├── variables, globalVariables, personalVariables, roomPersonalVariables
│   ├── worldName, worldId, sessionId
│   ├── currentUser (account), user (persona-aware)
│   ├── room, mode, capabilities, language
│   ├── messages, permissions, entries
│   ├── isStreaming, streamingContent, streamingReasoning
│   ├── pendingChoices, error, readOnly, greetingContent, canvasMode
│   ├── checkpoints
│   └── selectedModel, userPlan, preferredProvider
├── Game actions
│   ├── sendMessage(text)
│   ├── setVariable(id, value, options?)
│   ├── executeAction(actionId)
│   ├── switchGreeting(index)
│   ├── clearPendingChoices()
│   └── setComposerDraft(text)              // prefill, no send
├── Chat control
│   ├── editMessage(id, content) → Promise<boolean>
│   ├── deleteMessage(id) → Promise<boolean>
│   ├── regenerateMessage(id)
│   ├── continueLastMessage()
│   ├── stopGeneration()
│   ├── restartChat()
│   └── swipeMessage(id, direction) → Promise
├── Sessions / branching
│   ├── revertToMessage(id) → Promise<void>
│   ├── branchFromMessage(id) → Promise<string | null>
│   ├── getBranchContext() → Promise<BranchContext>
│   ├── createSession(worldId) → Promise<string>
│   ├── deleteSession(id) → Promise<void>
│   └── listSessions(worldId) → Promise<Array>
├── Checkpoints
│   ├── saveCheckpoint() → Promise<void>
│   ├── loadCheckpoints() → Promise<void>
│   ├── restoreCheckpoint(id) → Promise<void>
│   └── deleteCheckpoint(id) → Promise<void>
├── Audio
│   ├── playAudio(trackId, opts?)
│   ├── stopAudio(trackId?, fadeDuration?)
│   ├── pauseAudio(trackId)
│   ├── resumeAudio(trackId)
│   ├── onAudioEnded(cb) → unsubscribe
│   ├── setAudioVolume(type, volume)
│   └── getAudioVolume(type) → number
├── UI / navigation
│   ├── toggleImmersive()
│   ├── openPersonaManager()
│   ├── copyToClipboard(text)
│   ├── navigate(path)
│   └── showToast(message, type?)
├── Storage
│   ├── storage.get(key) → Promise<string | null>
│   ├── storage.set(key, value) → Promise<void>
│   └── storage.remove(key) → Promise<void>
├── Lorebook
│   ├── entries (ReadonlyArray<SandboxEntry>)  // sorted by position, enabled only
│   └── getEntry(name) → SandboxEntry | null
├── AI
│   └── ai.complete({ messages, onDelta?, model?, maxTokens?, temperature?, includeLorebook? }) → Promise<string>
│        // includeLorebook: true | "all" | "matched" — auto-inject world lore
├── Context injection
│   └── injectContext(message, { role? })
├── Model picker
│   ├── setModel(modelId)
│   ├── getModels() → Promise<{ models, pinnedModels, recentlyUsed }>
│   ├── pinModel(id), unpinModel(id)
├── Assets
│   └── resolveAssetUrl(ref) → string
└── Markdown
    └── renderMarkdown(text) → string   // safe HTML

Sandbox globals (no import)
├── React
├── useYumina, useAssetFont
├── Icons  (1400+ Lucide icons)
├── Chat, MessageList, MessageInput, ChatCanvas (legacy alias)
└── Tailwind utility classes (CSS-level)

Blocked / redirected
├── fetch('/api/...') → proxied
├── localStorage / sessionStorage → api.storage
├── window.location → synthetic + navigate
└── navigator.clipboard → copyToClipboard

Browser APIs that work as-is
├── <input type="file"> + FileReader      // player file uploads → data URL
├── <canvas>, URL.createObjectURL          // image processing
├── IntersectionObserver, ResizeObserver, matchMedia, rAF
├── crypto.randomUUID, crypto.subtle
└── WebAudio (AudioContext)

次へ:実例付きの解説には カスタムUIガイド に戻るか、構築したいものに近いテンプレートは Recipes を参照。