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