本文へスキップ

章 12.4 · 部 III · 実践

Sandbox:エージェント、埋め込み、プリセット

AIエージェント向けのWebMCPツール、自分のアプリに入れるPostextSandboxコンポーネントとそのpropsとCSS、プリセットバンドルの形式

更新日 2026-10-1613分所属する章: 12 Sandboxenescadefritptzhjaar

かんたんな説明

このページは、Webサイトを作る人や、AIアシスタントを使う人向けです。ブラウザーで動くアシスタントは、用意されたツールを通してSandboxの本を読み、変更できます。Sandboxを1つのコンポーネントとして自分のWebサイトに入れることもでき、このページではその設定項目と必要なスタイルを挙げます。最後の節では、Sandboxで開けるサンプルの本のファイルを説明します。

#エージェント:WebMCP

Sandboxは、WebMCP(document.modelContext)を通じて、ブラウザーで動くエージェントに専用のツールを渡します。アシスタントは画面をクリックしてたどる代わりに、本そのものを直接扱えます。設定は要りません。Sandboxが画面にある間はツールが登録されており、このサイトのほかのページもそれぞれいくつかのツールを登録します(ドキュメントとレシピ集の検索、ページをMarkdownとして読む、レシピをSandboxで開く、言語とテーマの切り替え)。

ツールは、デザイナーが目で確かめながら繰り返す手順、つまり「変える、ページを見る、また変える」のために作られています。

  • 見る — sandbox_get_state, sandbox_render_page, sandbox_inspect_pages, sandbox_inspect_point, sandbox_inspect_comic, sandbox_get_warnings:本とその章、ページの範囲、そして画面で起きていること(選択範囲、通知、回答待ちの確認)。任意のページを指定した幅のJPEG、PNG、WebPにしたもの、または細部を見るための切り抜き(求めれば印刷プレビュー付き)。同じページのデータ(ブロックと行、各段がどこまで埋まっているか、どの行が間延びしているか、語が分割された行はどれか)。ページ上の一点にあるもの(ソースの行、リンクとその行き先、表のセル)。漫画のページのコマと吹き出し。そして、画面にどの章とどのビューアーが出ていても、任意の章または本全体について検査が見つけたもの。
  • 変える — sandbox_set_config, sandbox_get_config, sandbox_find_setting, sandbox_edit_text, sandbox_write_text, sandbox_read_text, sandbox_search_text, sandbox_select_text, sandbox_undo_text, sandbox_manage_chapters, sandbox_save_resource, sandbox_save_font, sandbox_edit_comic, sandbox_import_word:パスで指定する任意の設定(bodyText.fontSize、page.margins.top、headings.levels[level=2].fontSize、header.elements[id=titleOdd].italic)、厳密な検索と置換による本文(取り消しもできます)、章、画像、SVGの図、動画、書式つきの表、フォント、漫画のコマと吹き出し、スタイルを対応づけて読み込むWord文書。変更すると、各値の変更前と変更後、それぞれの章が何ページになったかが返ります。設定については、エンジンが解決した値と、それに連動して変わったほかの設定も返ります。エンジンが解決できない値は拒否され、何も変わりません。
  • 表示する — sandbox_set_view, sandbox_go_to_page, sandbox_run_viewer:ビューアーのタブ、サイドパネル、章か本全体か、拡大率、合わせ方と見開き、読者側の文字サイズ、印刷プレビュー、ページに描くガイド、Folioの拡大鏡、そして画面に出ているページ(ページ番号、本文の一節、アンカー、EPUBの目次の項目で指定)。PDFやEPUBの再生成、印刷、ページ上の動画の再生。
  • 保存する — sandbox_list_books, sandbox_open_book, sandbox_save_book, sandbox_import_book, sandbox_export, sandbox_word_templates, sandbox_answer:サンプルと自分のプロジェクトとその表紙、.postextファイルの読み込み、開いている本(または開いていない本)のPDF、EPUB、.postext、Word、Markdown、設定のJSONへの書き出し、Word読み込みのテンプレート、そしてSandboxからの確認への回答。

本を変更するツールは組版が終わるのを待つので、次の画像はいつも新しいページのものです。ページは、画面にどのタブが出ていても、ツールのために組まれて描かれます。取り消せない操作(章、リソース、プロジェクトの削除、元の状態への復元、Word文書による本文の上書き、手元の本の置き換え)には重大な操作の印が付いており、ブラウザーが先に確認を求められます。画面で操作するときと同じく、すべてはお使いのブラウザーの中にとどまります。ツールが届くのは、Sandbox自身が届く範囲だけです。

WebMCPは草案の段階です。Chromeではオリジントライアルとして、またchrome://flags/#enable-webmcp-testingで使えます。WebMCPのないブラウザーでは、同じツールがwindow.postextWebMcpにあり(listTools()、executeTool(name, args))、ページ内でスクリプトを実行できるエージェントが使えます。Sandboxを埋め込むときは、webmcp={false}でツールを登録しないようにできます(Propsを参照)。

WebMCPのページでは、エージェントに頼めることと各ツールの呼び出し方を例とともに説明しています。

#単体での利用

Sandboxは単体のReactパッケージ(postext-sandbox)としても提供されており、任意のReactアプリケーションに組み込めます。

npm install postext-sandbox postext
import { PostextSandbox } from 'postext-sandbox';
 
function App() {
  return (
    <PostextSandbox
      initialMarkdown="# Hello World"
      initialConfig={{ layout: { layoutType: 'double' } }}
      onMarkdownChange={(md) => console.log(md)}
      onConfigChange={(cfg) => console.log(cfg)}
    />
  );
}

#Props

プロパティ型説明
initialMarkdownstring最初に表示するMarkdownの内容
initialConfigPostextConfig最初に使うレイアウトの設定
classNamestringルートコンテナーのCSSクラス
labelsPartial<SandboxLabels>UIラベルの置き換え。組み込みの英語の既定値の上にマージされます。別の言語で組み込むときは、翻訳したラベルをlocaleと一緒に渡します
localestringUIの言語(既定は'en')。ローカライズされた既定のリソースタイプと例のリソースを選び(1つの言語のプリセットやファイルには、その言語のリソースタイプが使われます)、設定で指定がない場合にプレビューが使うハイフネーションの言語を与えます
presetSources{ url: string; private?: boolean }[]リモートのプリセットの配信元。組み込みのプリセットの後に順に試します。各urlはindex.jsonを配信するベースURLです(プリセットバンドルの形式を参照)。private: trueはその配信元のプリセットに非公開のタグを付け、そのdefaultフラグを自動読み込みの対象にします。応答しない配信元からは何も追加されません
hashBundlesRecord<string, (id: string, lang: string | null) => string | string[] | null>ホスト独自のフラグメントのキーでリンクする本。{ recipe: (slug, lang) => /cookbook/$&#123;slug&#125;/$&#123;lang&#125;/$&#123;slug&#125;.postext }とすると、#recipe=<slug>&lang=esでそのバンドルが開きます。ページと同じオリジンにある.postextファイルのURL、順に試す複数のURL(最初に応答したものが使われます)、またはホストが配信しないidにはnullを返します。初回はファイルを、リンクを記録したプロジェクト(recipe:<slug>:<lang>)として読み込みます。2回目以降は、そのプロジェクトを編集内容ごと開くか、現在配信されているファイルで置き換えるかを確認します。その後、フラグメントは#project=<id>に書き換えられます
onConfigChange(config) => void設定が変わったときのコールバック
onMarkdownChange(markdown) => voidMarkdownが変わったときのコールバック
webmcpbooleanマウントされている間、ブラウザーのエージェント向けにSandboxのWebMCPツールを登録します。既定値はtrue
themeToggleReactNodeテーマ切り替えのカスタムコンポーネント。アクティビティバーの下端に配置されます
languageSwitcherReactNode言語切り替えのカスタムコンポーネント。アクティビティバーの下端に配置されます
homeUrlstring設定すると、アクティビティバーの上端にこのURLを指すホームのロゴリンクを表示します
homeLinkReactNodeホームの位置に置くカスタムノード。既定のロゴリンクを置き換えます(homeUrlより優先されます)

#CSSの要件

このパッケージは、CSSカスタムプロパティを参照するTailwind CSSのユーティリティクラスを使います。ホストアプリケーションは次の変数を用意する必要があります。

  • --background、--foreground:基本色
  • --surface:一段浮いた面の色。--surface-2:ホバーした行と強調表示した選択肢(--backgroundにフォールバック)
  • --slate:控えめなテキストの色
  • --rule:境界線と区切り線の色。--rule-strong:ホバーしたコントロールの濃い罫線(--slateにフォールバック)
  • --brand:アクセントカラー。作業中のパネルの目印、フォーカスリング、スイッチ、選択した選択肢に使います
  • --brand-hover:ホバー時のアクセントカラー
  • --brand-soft:選択した選択肢の背後に敷く半透明のアクセントの塗り(--surfaceにフォールバック)
  • --brand-contrast:オンのスイッチのつまみ(--backgroundにフォールバック)
  • --destructive:破壊的な操作とエラーの色
  • --accent-blue:リンクとアクセントの色
  • --font-sans:UIのフォントファミリー
  • --font-mono:等幅のフォントファミリー(コードエディター用)
  • --font-display、--brand-blue、--brand-gilt:アクティビティバーにある既定のロゴマークの書体と色(省略可。フォールバックはGeorgia、#2b4acb、#d8a21a)

このウェブサイトでは、--brandをダークテーマで金色、ライトテーマで青にしています。以前のバージョンは--giltと--gilt-hoverを読んでいましたが、現在のSandboxはこれらを使いません。

ポップオーバー、メニュー、ツールチップはdocument.body上のポータルに描画されるため、変数はSandboxを包む要素だけでなく、:root(またはbody)に定義してください。ホストのTailwindのビルドは、パッケージが使うユーティリティクラスが生成されるよう、パッケージのソースを走査する必要があります(Tailwind 4では@source "…/postext-sandbox/src/**/*.tsx")。

#プリセットバンドルの形式

付録です。プリセットの配信元は(HTTPで配信される)ディレクトリーで、そのルートにはプリセットのディレクトリーを列挙したindex.jsonがあります。各ディレクトリーにはpreset.jsonマニフェストと、それが参照するMarkdown、リソース、フォントのファイルが入ります。リポジトリーのscripts/presets/にあるツールはまさにこの形でバンドルのひな形を作り、Sandboxから書き出した.postextファイルは、そうしたディレクトリーの1つを(index.jsonを除いて)zip圧縮したものです。

<presets-root>/
  index.json
  <preset-dir>/
    preset.json
    chapters/              one .md per chapter, in order (a version 1 manifest names a single document instead)
    resources/             figures: .svg / .png / .jpg / .jpeg / .webp / .gif (+ optional .pdf print masters)
    fonts/                 .otf / .ttf / .woff2 single faces

#index.json

{
  "version": 1,
  "presets": [
    {
      "id": "acme-journal",          // stable id, [a-z0-9-]
      "dir": "acme-journal",         // directory name relative to index.json
      "name": "Acme Journal",
      "description": "Two-column journal article",   // optional
      "locale": "en",                 // optional
      "default": true,                // optional, at most one
      "locales": ["en", "es"],        // optional: every locale the bundle carries
      "openLocale": "en",             // optional: the locale it opens in when none is asked for
      "thumbnail": "thumbnail.jpg",   // optional: preview shown in the picker
      "license": "CC BY 4.0",         // optional: shown as a tag
      "credits": "Text and images: ESO",  // optional: one line under the description
      "tags": ["magazine", "two-column"], // optional
      "binding": "left"               // optional: the edge the website's shelf draws the spine on ("right" for a right-bound book)
    }
  ]
}

#preset.json

{
  "version": 2,
  "id": "acme-journal",
  "name": "Acme Journal",
  "description": "Two-column journal article",       // optional
  "locale": "en",                                     // optional
  "default": true,                                    // optional
  "configVersion": 8,                                 // optional: the configuration rules `config` is written for
  // The same optional showcase fields as the index entry:
  // "locales", "openLocale", "thumbnail", "license", "credits", "tags"
 
  // The book's chapters, in order — or one list per locale
  "chapters": [
    { "title": "Introduction", "file": "chapters/01-introduction.md" },
    { "title": "Methods", "file": "chapters/02-methods.md" }
  ],
  // "chapters": { "en": [ … ], "es": [ … ] },
  // Version 1 manifests use a single document instead and are still accepted:
  // "version": 1, "markdown": "article.md"  (or a locale → file map)
 
  // Optional PostextConfig JSON (page, margins, columns, typography...).
  // Do NOT put customFonts here: fonts come from the `fonts` array below.
  "config": { },
 
  "resources": [
    {
      "id": "fig-1-1",                 // referenced from markdown (:ref)
      "typeId": "figure",              // resource type id (figure, table, ...)
      "kind": "svg",                   // "svg" | "bitmap" | "table"
      "file": "resources/fig-1-1.svg", // svg/bitmap only, relative to preset.json
      "width": 240.5,                  // optional, pt (svg) / px (bitmap)
      "resolution": 300,               // optional, bitmap only: ppi at its natural print size
      "pdfFile": "resources/fig-1-1.pdf", // optional, svg only: vector print master (see notes)
      "height": 180,                   // optional
      "caption": "Caption text",
      "altText": "Accessible description",            // optional
      "safeArea": { "x": 0.18, "y": 0.25, "width": 0.5, "height": 0.6 }, // optional, svg/bitmap only: the part always shown, in fractions of the picture
      "placement": { "position": "top", "span": "column" },  // optional
      "note": "Source: ...",                          // optional
      "table": { "model": { "rows": [[{ "content": "A", "isHeader": true }]], "headerRowCount": 1 } }
                                                      // kind "table" only
    }
  ],
 
  "fonts": [
    {
      "name": "Some Family",           // family name used by the config
      "variants": [
        { "weight": 400, "style": "normal", "file": "fonts/SomeFamily-Regular.otf" },
        { "weight": 400, "style": "italic", "file": "fonts/SomeFamily-Italic.otf" },
        { "weight": 700, "style": "normal", "file": "fonts/SomeFamily-Bold.otf" }
      ]
    }
  ]
}

補足:

  • preset.jsonのidはindex.jsonのエントリーと一致させる必要があります。マニフェストのdescription、locale、defaultは、プリセットを読み込んだ時点でindexの値を上書きします。ショーケース用のフィールドlocales、openLocale、license、credits、tagsも同様です。openLocaleはlocalesのいずれかで、リンクやクリックで言語が指定されないときに本を開く言語です(ホームページの書棚もこの言語にリンクします)。原典が読者の言語でない場合に設定します。『紅樓夢』のショーケースはzh-Hantを設定しており、英語圏の読者全員に英訳が開くことはありません。設定しない場合はインターフェースの言語で決まります。選択画面では、locales(またはlocale)のすべての言語とライセンスがタグとして、クレジットが説明の下に表示され、行のチェックマークの列の代わりにthumbnail(プリセットのディレクトリーからの相対パスで指定する小さなページ画像)が表示されます。プリセットから複製した本(自分用に複製)は、これを表紙として引き継ぎます。apps/web/public/presets/にある公開ショーケースのプリセットはすべてのフィールドを使い、各テキストと画像のライセンスと出典を記したCREDITS.mdを同梱しています。
  • configVersion(省略可)は、configとlocalizedの設定がどの設定規則に基づいて書かれているかを示します。postext 1.5以降のすべての書き出しは8を設定します。これのないマニフェストはpostext 1.4以前で書き出されたもので、見出しの改ページ、数式のサイズ、インラインの図の前後のアキ(本文中と囲みの中)、見出しの文字書式、ドロップキャップ、コロンの行の下の余地、囲みの分割、ダッシュと複合語のハイフンでの改行、行末不ぞろいのテキスト、見出し直下の分割、段落コンテナーの下のアキは、1.4の組み方どおりに読み込まれます(作業内容の保存の「postext 1.4以前に保存された見出しの改ページ」「postext 1.5より前に保存された数式のサイズ」「postext 1.5より前に保存されたインラインの図」「postext 1.5より前に保存された囲み内のインラインの図」「postext 1.5より前に保存された見出しの文字書式とドロップキャップ」「postext 1.5より前に保存されたコロンの後のリスト」「postext 1.5より前に保存された囲みの分割」「postext 1.5より前に保存されたダッシュと行末不ぞろいのテキスト」「postext 1.5より前に保存された複合語」「postext 1.5より前に保存された見出し直下の分割」「postext 1.5より前に保存された段落コンテナー」を参照)。3から7の値を持つもの(1.5のプレリリース)は、それより後の規則の固定だけを受けます。3の場合は数式のサイズ、インラインのアキ、規則6の5つの固定、規則7の2つ、規則8の3つ、4の場合はインラインのアキと規則6、7、8の固定、5の場合は規則6、7、8の固定、6の場合は規則7と8の固定、7の場合は規則8の固定だけです。現在の規則に合わせてマニフェストを手で書く場合は8を設定します。
  • view(省略可)は、Sandboxがバンドルをどう開くかを指定します。{ "canvasScope": "book" }はCanvasで本全体を組みます(本と章を参照)。省略するとCanvasは1章ずつ表示します。設定されていれば、書き出した本にも含まれます。200章を超える本は、指定にかかわらず1章ずつ開きます。
  • start(省略可)は、バンドルがもっと長い本の一部だけを収めているときに、本がどこから始まるかを示します。{ "pageIndexOffset": 57, "pageNumbering": { "startAt": 58 }, "headings": { "h1": 3 } }なら、58ページ(偶数ページ)、第4章から始まります。最初の章のcontinuation(buildDocument)と同じものです。Sandboxはそこから本をレイアウトし、プロジェクトとして保存するときは本と一緒に保持し、書き出すときに書き戻します。これを編集するパネルはありません。localizedの項目は、その版のためのstartを持てます。
  • localized(省略可)は、多言語バンドルの言語ごとの上書きを保持します。{ "es": { "config": { … }, "resources": [ { "id": "fig-1", "caption": "…", "note": "…", "altText": "…" } ] } }のように書きます。章を選んだ言語の上書きが、共有のconfig(最上位のキーは丸ごと置き換え。典型的には、キャプションの接頭辞を翻訳したresourceTypes)とresources(キャプション、注記、代替テキスト、表のセルをidでマージ)の上に適用されます。エントリーのない言語は共有の文言を使います。エントリーには独自のviewを含めることもでき、その版では共有のものより優先されます。『紅樓夢』は中国語の版を本全体で開き、全体の描画がずっと遅いJolyの英訳は1章ずつ開きます("en": { "view": { "canvasScope": "chapter" } })。
  • chapters(バージョン2)は本の順に並べた{ title, file }のリスト、または言語→リストのマップです。markdown(バージョン1)はファイル名、または言語→ファイルのマップです。どちらの場合も、Sandboxは要求された言語に一致するエントリーを選び、その言語の章ファイルだけを読み込みます。選ぶ順序は、大文字と小文字の違いを除いてタグが完全に一致するもの、次に言語と字体が同じもの(zh-TWはzh-Hansより先にzh-Hantを読みます)、次に字体が異なる同じ言語(素のzhをキーにしたバンドルでは、zh-TWの読者に中国語が渡ります)、次にプリセット自身のlocale、最後に最初のエントリーです。章のtitleが空の場合は、ファイルの最初のレベル1見出しから取ります。Sandboxはつねにバージョン2で書き出します。
  • width / heightは、svgとbitmapのリソースでは省略できます。ない場合は読み込み時にファイルから読み取ります。ビットマップにはresolution(原寸で印刷したときのppi)とfileResolution(ファイルに記載された解像度。設定でlayout.bitmapResolution: 'file'を指定しているときは読み込み時に読み取ります)も指定できます。
  • pdfFile(svgリソースのみ)は、同じ図をベクターで収めた1ページのPDFを指します。典型的には、SVGの元になったIllustratorのオリジナルやPDF書き出しです。PDFの書き出しでは、SVGの代わりにそのページをそのまま(フォント、グラデーション、色空間を保って)埋め込みます。CanvasとHTMLのプレビューは引き続きSVGを使います。単色モードでは無視されます。単色モードの色替えの処理はSVGのマークアップにしか作用しないためです。ファイルがなくても失われるのはマスターだけで、図は残ります。リソースの詳細でSVGのソースを編集するとマスターは外れ(マスターは編集前のソースに合わせて作られたものです)、以後、PDFの書き出しは編集後のSVGに従います。マスターを戻すには、元の状態に戻します(本パネルの元に戻す…)。マスターのないSVGは、PDFバックエンドのサブセット(基本図形、パス、グループ、use、クリップパス、単色の塗りと線、不透明度、文書が読み込めるフォント(まずカスタムフォント、次にGoogleがホストするファミリー)で組まれたtext / tspanの連なり)に収まる場合はベクターパスとして出力され、それ以外は600 dpiでラスタライズされます。
  • SVGのtextは、どの出力でも本のフォントで描画されます。プレビューとEPUBは、SVGを表示する前に、その文字が指定するフェイスをSVGに埋め込みます(<img>からはページのフォントが見えないため)。カスタムファミリーはアップロードしたファイルを、Googleのファミリーはその文字を収めるFontsourceのファイルを埋め込みます。PDFの書き出しは、同じ埋め込みフェイスを使い、選択可能な実際のテキストとして組みます。font-familyにはファミリー名を、フォントパネルに表示されるとおりに正確に書いてください。リソースのマニフェストの項目に"inlineFonts": falseと書くと、埋め込みを無効にできます(svg.inlineFonts)。
  • SVGのソースエディターでは、誤って図を壊さないよう、テキストノード(色付きで表示)の中でしか入力できません。ヘッダーの鍵ボタンを押すと、座標、色、構造を含むソース全体を編集できるようになり、再びロックするとテキストノードを探し直します。
  • table.modelはPostextのTableModelです。rowsはセル(contentと、省略可能なcolSpan、rowSpan、isHeader、align、verticalAlign)を行ごとに並べた配列、headerRowCountは先頭のヘッダー行の数、columnWidthsは列の相対的な幅の比を表す省略可能な配列です。table.styleIdでは、設定のtableStylesのいずれかを指定できます(省略可)。
  • フォントファイルは、プリセットのidとファイルパスから導いたidでIndexedDBに保存され、プリセットを適用したときにそのファミリーがconfig.customFontsに追加されます。index.jsonがない場合や形式が正しくない場合は、エラーにはならず、プリセットがないものとして扱われます。
  • <preset-dir>/fingerprint.jsonは省略可能で、開発用のルートでは仮想のファイルです。そのファイルがない場合、GETは{ "fingerprint": "<sha1 hex>", "files": <n> }をCache-Control: no-store付きで返します。値はディレクトリーのファイルのメタデータ(4階層までのすべてのファイルの相対パス、更新日時、サイズ。ドットファイルとnode_modulesは除外)から計算し、ファイルの内容は使いません。Sandboxはこれをポーリングしてバンドルの編集を追跡します(本パネルを参照)。静的ホストは、同じ形の実際のfingerprint.jsonを配信することも、まったく配信しないこともできます。配信しない場合、そのプリセットは追跡されません。