iframeブートストラップドキュメント
HyperGenのサンドボックス化されたiframeに読み込まれなければならない (MUST) 完全なHTMLドキュメント。HTMX、SSE拡張、postMessageブリッジ、基本スタイルを含む。
iframeブートストラップドキュメント
ブートストラップドキュメントは、サンドボックス化されたiframeに読み込まれる完全なHTMLページです。エージェント生成HTMLのランタイム環境を提供します: インタラクティビティのためのHTMX、ストリーミングのためのSSE拡張、ホスト通信のためのpostMessageブリッジ、CSS変数サポート付きの基本スタイル。
必須要素
適合するブートストラップドキュメントは以下のすべてを含まなければなりません (MUST):
| 要素 | 目的 |
|---|---|
| HTMXスクリプト | インタラクティビティのためのhx-*属性を処理 |
| HTMX SSE拡張スクリプト | ストリーミングのためのsse-connectとsse-swapを有効化 |
SSEルート要素(#hg-root) | SSEエンドポイントに接続し、ストリーミングされたフラグメントを受信するコンテナ |
| postMessageブリッジ | ホストからのhg:themeとhg:destroyメッセージを処理 |
| ResizeObserver | hg:resizeメッセージ経由でホストにコンテンツサイズの変更を通知 |
| 接続状態スクリプト | <html>のhg-connected / hg-disconnected CSSクラスを切り替え |
| 基本スタイル | リセット(box-sizing、margin: 0)、CSS変数デフォルト、ローディングインジケータースタイル |
最小限の適合ブートストラップ
以下はすべての要件を満たす最小限のHTMLドキュメントです。実装はこれに追加してもよいですが (MAY)、必須要素を削除してはなりません (MUST NOT)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://unpkg.com/htmx.org@2"></script>
<script src="https://unpkg.com/htmx-ext-sse@2"></script>
<style>
:root {
/* テーマ変数はホストからpostMessage経由で注入 */
}
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
line-height: 1.5;
color: var(--hg-text, #1a1a1a);
background: var(--hg-background, transparent);
}
.hg-indicator { display: none; }
.htmx-request .hg-indicator,
.htmx-request.hg-indicator { display: inline-block; }
.hg-disconnected body { opacity: 0.7; }
</style>
</head>
<body>
<div id="hg-root"
hx-ext="sse"
sse-connect="/api/agent/stream"
sse-swap="message"
hx-swap="innerHTML">
</div>
<!-- 接続状態 -->
<script>
(function() {
var root = document.documentElement;
function setConnected(v) {
if (v) {
root.classList.add("hg-connected");
root.classList.remove("hg-disconnected");
} else {
root.classList.remove("hg-connected");
root.classList.add("hg-disconnected");
}
}
setConnected(false);
document.body.addEventListener("htmx:sseOpen", function() {
setConnected(true);
});
document.body.addEventListener("htmx:sseError", function() {
setConnected(false);
});
document.body.addEventListener("htmx:sseClose", function() {
setConnected(false);
});
})();
</script>
<!-- postMessageブリッジ -->
<script>
(function() {
var hostOrigin = "*";
window.addEventListener("message", function(e) {
if (hostOrigin !== "*" && e.origin !== hostOrigin) return;
var msg = e.data;
if (!msg || typeof msg.type !== "string") return;
switch (msg.type) {
case "hg:theme":
if (msg.vars && typeof msg.vars === "object") {
Object.keys(msg.vars).forEach(function(key) {
document.documentElement.style.setProperty(key, msg.vars[key]);
});
}
break;
case "hg:destroy":
var root = document.getElementById("hg-root");
if (root) {
root.removeAttribute("sse-connect");
root.removeAttribute("hx-ext");
if (typeof htmx !== "undefined") {
htmx.trigger(root, "htmx:sseClose");
}
}
break;
}
});
// ResizeObserver
var ro = new ResizeObserver(function() {
window.parent.postMessage({
type: "hg:resize",
height: document.documentElement.scrollHeight,
width: document.documentElement.scrollWidth
}, hostOrigin);
});
ro.observe(document.body);
// ナビゲーションブリッジ
document.addEventListener("click", function(e) {
var target = e.target;
while (target && target !== document.body) {
if (target.getAttribute &&
target.getAttribute("data-hg-navigate")) {
e.preventDefault();
window.parent.postMessage({
type: "hg:navigate",
url: target.getAttribute("data-hg-navigate")
}, hostOrigin);
return;
}
target = target.parentElement;
}
});
// データブリッジ
document.addEventListener("hg:data", function(e) {
window.parent.postMessage({
type: "hg:data",
payload: e.detail
}, hostOrigin);
});
})();
</script>
</body>
</html>Sandbox属性の要件
iframe要素は以下のパーミッションでsandbox属性を使用しなければなりません (MUST):
<iframe sandbox="allow-scripts allow-same-origin allow-forms"
src="about:blank"
style="width: 100%; border: none;">
</iframe>必須パーミッション
| パーミッション | 理由 |
|---|---|
allow-scripts | HTMXがhx-*属性を処理しSSE接続を管理するためにJavaScriptが必要。 |
allow-same-origin | HTMXがエージェントサーバーにHTTPリクエスト(GET、POST)を行う必要がある。same-originなしではXHRとfetchがブロックされる。 |
allow-forms | HTMXのフォーム送信(<form>要素でのhx-post)にフォーム送信機能が必要。 |
明示的に拒否されるパーミッション
以下のパーミッションは、ホストアプリケーションに文書化された具体的な必要がない限り、付与してはなりません (MUST NOT):
| パーミッション | 付与時のリスク |
|---|---|
allow-top-navigation | iframeがページ全体をリダイレクトできる。 |
allow-popups | iframeが新しいブラウザウィンドウを開ける。 |
allow-modals | alert()、confirm()、prompt()がホストアプリケーションをブロックできる。 |
allow-pointer-lock | iframeがカーソルをキャプチャできる。 |
allow-downloads | iframeがファイルダウンロードをトリガーできる。 |
ホストアプリケーションは、必須の3つを超えるパーミッションをsandbox属性に追加してもよいです (MAY)。追加の各パーミッションはセキュリティ境界を弱め、文書化すべきです (SHOULD)。
Content Security Policy
ホストアプリケーションは、iframeの機能を制限するContent-Security-Policyでブートストラップドキュメントを提供すべきです (SHOULD)。
推奨CSP
default-src 'none';
script-src 'self' 'unsafe-inline' https://unpkg.com/htmx.org https://unpkg.com/htmx-ext-sse;
style-src 'self' 'unsafe-inline';
connect-src <agent-server-origin>;
img-src 'self' data: https:;
font-src 'self' data:;| ディレクティブ | 目的 |
|---|---|
default-src 'none' | 明示的に許可されていないものをすべて拒否。 |
script-src | インラインスクリプト(ブリッジに必要)と信頼されたCDNからのHTMXを許可。 |
style-src | インラインスタイル(テーマ注入とエージェント生成<style>ブロックに必要)を許可。 |
connect-src | HTTP/SSE接続を既知のエージェントサーバーオリジンに制限。データ漏洩に対する主要な防御。<agent-server-origin>を実際のオリジンに置換。 |
img-src | 同一オリジン、data URI、HTTPSからの画像を許可。 |
font-src | 同一オリジンとdata URIからのフォントを許可。 |
ブートストラップドキュメントがインラインスクリプトを使用し、エージェントがインラインスタイルを生成するため、script-srcとstyle-srcの両方に'unsafe-inline'が必要です。実装がブートストラップスクリプトにnonce やハッシュを使用できる場合、'unsafe-inline'よりもそれらを推奨すべきです (SHOULD)。
HTMXバージョン要件
- ブートストラップドキュメントはHTMX バージョン2.x(2.0.0以降)を読み込まなければなりません (MUST)。
- ブートストラップドキュメントはHTMX SSE拡張 バージョン2.x を読み込まなければなりません (MUST)。
- 両スクリプトはCDN(unpkg、cdnjs、jsdelivr)から読み込んでも、ローカルで提供してもよいです (MAY)。
- HTMXスクリプトはSSE拡張スクリプトの前に読み込まなければなりません (MUST)(拡張はHTMXコアに依存)。
CDN URL
リファレンス実装はunpkgでメジャーバージョンピニングを使用:
<script src="https://unpkg.com/htmx.org@2"></script>
<script src="https://unpkg.com/htmx-ext-sse@2"></script>本番デプロイメントでは特定のバージョンにピン留めすべきであり (SHOULD)(例: htmx.org@2.0.4)、追加のセキュリティのためにSubresource Integrity (SRI) ハッシュを使用してもよいです (MAY)。
接続状態CSSクラス
ブートストラップドキュメントは、SSE接続状態に基づいて<html>要素のCSSクラスを切り替えるスクリプトを含まなければなりません (MUST):
| クラス | 適用タイミング |
|---|---|
hg-connected | SSE接続がオープン(htmx:sseOpenイベント受信時)。 |
hg-disconnected | SSE接続がクローズまたはエラー(htmx:sseErrorまたはhtmx:sseClose受信時)。 |
初期状態はhg-disconnectedでなければなりません (MUST)(SSE接続が開く前に適用)。
エージェントはこれらのクラスを使用してコンテンツを条件付きでスタイルしてもよいです (MAY):
.hg-disconnected .status-indicator { color: red; }
.hg-connected .status-indicator { color: green; }ブートストラップの読み込み
ブートストラップドキュメントは2つの方法でiframeに読み込めます:
1. srcdoc経由(推奨)
ホストがブートストラップHTMLをサーバーサイドで生成し、iframeのsrcdoc属性として設定します。追加のHTTPリクエストを回避できます。
iframe.srcdoc = bootstrapHtml;2. src URL経由
ホストがiframeをブートストラップドキュメントを提供するURLに向けます。リクエストごとにブートストラップを動的に生成する必要がある場合に便利です。
iframe.src = "/api/agent/bootstrap";srcを使用する場合、サーバーはContent-Type: text/htmlと完全なブートストラップドキュメントで応答しなければなりません (MUST)。
カスタマイズポイント
実装は以下の方法でブートストラップドキュメントをカスタマイズしてもよいです (MAY):
sseEndpoint—sse-connectのURL。エージェントのSSEエンドポイントに設定しなければなりません (MUST)。sseEventName—sse-swapの値。デフォルトは"message"。swapStrategy— ルート要素のhx-swapの値。デフォルトは"innerHTML"。hostOrigin— postMessage検証に使用されるオリジン。デフォルトは"*"。本番デプロイメントでは実際のホストオリジンに設定すべきです (SHOULD)。themeVars—:rootスタイルブロックに設定される初期CSS変数値。extraHead—<head>に注入される追加コンテンツ(スタイルシート、メタタグ)。- HTMX CDN URL — 代替CDNまたはセルフホストHTMX URL。
ブートストラップドキュメントのパラメーター化されたバージョンについては、リファレンスサーバー実装(hypergen-server.ts、bootstrapHtml())を参照してください。