experience-cms-content-render について
React と Angular の uiBundle アプリで CMS コンテンツを描画する対話型スキルです。Salesforce CMS toolkit を導入し、型付き参照やレンダラー、配置用コードを生成しながら、初期構築とコンテンツ埋め込みを区別します。
主な機能
- 01React と Angular のアプリ検出
- 02CMS 識別情報とスキーマの処理
- 03型付き参照の登録
- 04フレームワーク別レンダラー生成
- 05Init と Embed の判定
活用シーン
- 01uiBundle アプリへの CMS 項目追加
- 02同じ型の CMS 項目群の描画
- 03初回利用時の CMS ランタイム構築
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill experience-cms-content-renderインストール後、Claude Code で/experience-cms-content-renderを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/angular/cms-content.component.tscms-item.service.tsDetailPage.component.tsMediaRenderer.component.tsTypeList.component.tsTypeRenderer.component.tsassets/react/DetailPage.tsxheuristicRenderer.tsxMediaRenderer.tsxTypeList.tsxTypeRenderer.tsxuseCmsItem.tsassets/shared/cmsContentType.tscmsCore.types.tsexternalRefs.tsmediaLabels.tsreferences/bulk-loading.mdcodegen-guardrails.mddetail-pages.mdembed-recipes.mdfailure-modes.mdheuristic-render-rules.mdinit-scaffold.mdinteraction-model.mdpackage-api.mdschema-sync.mdstyling-scopes.mdverify.md詳細
このスキルは、CMS コンテンツを uiBundle アプリにどのように表示するかを指示します。配信トランスポートを再実装するものではありません。npm パッケージ @salesforce/ui-bundle-template-feature-cms-toolkit が、HTTP、エンベロープのアンラップ、カタログキーのリマップ、チャネル解決、画像 URL の解決、エンティティのデコードなどのユーティリティを担います。このスキルはそのパッケージをインストールし、その上に React および Angular の uiBundle アプリ向けの薄いフレームワーク固有レイヤー(型付き参照、タイプごとのレンダラー、取得ライフサイクル、配置)を生成します。また、ファイルの状態から Init と Embed を推定し、ユーザーに伝えずに Embed プロンプト内で Init のスキャフォールディングを行うことはありません。
API 専用パッケージ — UI はスキルが作成します
ツールキットがエクスポートするのは関数と型のみで、コンポーネントやフックはありません。インポート先と完全なシグネチャについては、references/package-api.md を参照してください。3 つの読み取り関数(すべて <TBody> と options? を受け取り、失敗時にはすべて例外をスローします):
getCmsContentByUrl— 認証なしのパブリック CDN。貼り付けられたunauthenticatedUrlをそのまま(AS-IS)取得します。getCmsContentByKey— 1 件のアイテムを認証付き Connect で取得します。ツールキットがソースキーからターゲットキーへのリマップを行い、チャネルを解決します。getCmsContentByKeys— 複数のアイテムを 1 回の認証付き往復で取得し、キーごとにベストエフォートで処理します(references/bulk-loading.md)。
これらに加えて、同期ヘルパー resolveCmsImageUrl、resolveMediaUrl、decodeRichHtmlEntities と型付きエラークラスがあります。すべての読み取り関数は、すでにアンラップされた contentBody を返し、エンベロープの title も含まれます。スキルのコードが扱うのは TBody の型付けのみで、エンベロープ、{ items: [...] }、.value ラッパーは扱いません。
バンドル済みファイル(アセットとリファレンスはこのスキルとともに読み込まれます)
このスキルの assets/ テンプレートと references/ ドキュメントは SKILL.md とともに読み込まれます。相対パス(assets/shared/…、assets/<framework>/…、references/…)で参照し、それらの内容を利用可能なものとして扱ってください。基盤となるランタイムファイルはそのまま出力されます。{{…}} による置換が行われるのは cmsContentType.ts とレンダラーテンプレートのみです。テンプレートを記憶から再構成してはいけません。テンプレートには StrictMode のフック、サニタイザーとの接続部分、正しいパッケージ呼び出し箇所が含まれています。以下のすべての検出(フレームワーク、貼り付けられた URL、Init/Embed/drift)は、対象アプリのファイルを直接読み取ります。実行するスクリプトはありません。
対話モデル(選択肢を提示して尋ねる)
このスキルは対話型です。プロンプトで未確定のままになっているすべての判断(フレームワーク、Init、drift、コンテンツタイプ、レンダー先、候補バンドル)では、推奨の選択肢を最初にした 2~4 個のラベル付き選択肢を提示する選択式の質問で尋ね、回答を待ちます。自由記述だけの質問は決して行いません。自由記述を使うのは、コンポーネント名やリージョン名、URL、スラッグなど、自由回答が必要な場合のみです。質問文、選択肢、見出しの完全な定義については references/interaction-model.md を参照し、以下のすべての質問でその指示に従ってください。回答によってセッションを終了し、何もスキャフォールディングしない終了条件が 2 つあります。Init を辞退した場合と、検索結果がコンテンツ 0 件の場合です(HALT behaviour を参照)。
バイパスモード(非対話/自動化)。 プロンプトで対話モデルをバイパスするよう明示的に求められた場合(「非対話」「質問しない」「すべてに進む/はいと答える」「自動的に進める」など)、選択式の質問は一切せず、本来尋ねるはずの各質問に推奨の選択肢を選んで続行します。変わるのは回答の取得方法のみで、パイプラインのロジックは変わりません。また、終了条件も無効にはなりません。実際にコンテンツが 0 件なら処理を停止し、安全な既定値がない真の HALT(名前が指定されていないインプレース配置、解決できない候補バンドルなど)でも停止します。質問ごとの推奨回答については、references/interaction-model.md の Bypass mode を参照してください。
フレームワークの検出(最初に実行)
<appRoot>(package.json があるディレクトリ)の <appRoot>/package.json を読み込み、dependencies、devDependencies、peerDependencies の依存関係名をマージします。フレームワークはどの項目に含まれている場合もあります。
- react —
reactがあり、@angular/coreがない → React テンプレート。 - angular —
@angular/coreがあり、reactがない → Angular テンプレート。 - ambiguous — 両方ある → 停止し、対象とするフレームワークを選択肢(
references/interaction-model.md→ フレームワーク)から尋ねます:React/Angular。 - unknown — どちらもない、または読み取り可能な
package.jsonがない → 停止します。サポート対象の React/Angular アプリではないため、スキャフォールドを作成しません。
フレームワークに応じて、以下の各手順で使用するテンプレート群(assets/react/* と assets/angular/*)が決まります。assets/shared/* はフレームワークに依存せず、どちらでも使用します。
モード
Init(初回のスキャフォールド)。 初回の Embed 時に、フレームワークの基盤ファイル一式(Init と Embed の判定を参照)が存在しない場合、アプリのルートで npm i @salesforce/ui-bundle-template-feature-cms-toolkit@latest --min-release-age=0 を実行し、共有ランタイムを一度だけ書き込みます(先にユーザーに確認してください)。この npm install がツールキットを追加する唯一の方法です。すべてのインポートでは @salesforce/… 指定子を使用します。Init は既存ファイルを上書きしません。スキャフォールドが完全なツリーは何もせず、一部だけのツリーは差分として停止します(Init と Embed の判定を参照)。
ランタイムファイルは逐語的に出力すること(理由はバンドルファイルを参照):各基盤テンプレートを Read し、変更せずに src/cms/… へ Write します。すでに存在する出力先はスキップし、上書きしません。assets/shared/ の2つのファイルと、検出されたフレームワークの2つのファイルを常にコピーします。完全なツリー、コピー対象の表、フォールバックについては references/init-scaffold.md を参照してください。
Embed(プロンプトごとの配線)。 CMS アイテムを1つ指定するプロンプト(または同じ種類のグループ。グループを参照)と、配置が指定されている場合はその配置を受け取り、以下のパイプラインを実行します。配置も詳細ページのリクエストも指定されていない場合、手順4でどちらかを尋ねます。各手順はすべて冪等です。
Embed パイプライン
Identity → Schema Sync → Ref Registration → Render Target → Verify
1. 識別 — { unauthenticatedUrl | contentKey, fqn, title } を解決する
2 種類の ref があります(assets/shared/cmsCore.types.ts を参照):外部 ref — アイテムが unauthenticatedUrl(公開 CDN)を持つ場合 → CmsExternalRef(url)として登録し、getCmsContentByUrl で読み込みます。ただし、スタンドアロンメディアの場合は、URL がアセットそのものであり、直接使用して読み込みません(ルール 1/2。手順 3 で altText/title を取得します)。uiBundle-space ref — アイテムが contentKey のみを持つ場合 → CmsRef(contentKey)として登録し、getCmsContentByKey で読み込みます(ツールキットが content-metadata.json を使ってキーを再マッピングします)。入力から識別情報を解決します。
- URL が貼り付けられた場合 — 入力が Experience CDN 配信 URL に一致します(正規表現は
references/embed-recipes.mdを参照。大文字と小文字を区別せず、文章に埋め込まれた URL にも一致します)。その URL がunauthenticatedUrlです。検索をスキップ → 外部 ref。一致しない場合は、以下に進みます。 - contentKey が直接指定された場合(生の
contentKey、任意のchannelId、配信 URL なし):検索をスキップ →CmsRef。contentKeyはそのまま保存し、指定されたchannelIdは手順 3 のチャネル解決ゲートに従ってpublic/content-metadata.jsonに渡します。解決可能なチャネルがないCmsRef(カタログ、プロンプト、ユーザーから指定されたもののいずれにもチャネルがない場合)は HALT となります。チャネルなしでレンダリングしてはいけません。 - 自然言語での言及:experience-search-coordinate 機能に生のプロンプトを渡します(プロンプトでグループが指定されていない限り、単一選択です)。アイテムごとに
{ unauthenticatedUrl, fqn, title, altText, contentKey }が返されます。unauthenticatedUrlは uiBundle-space アイテムではnullなので、contentKeyを使用します。メディアの場合は、altText/titleをCmsExternalRefに引き継ぎます(手順 3)。
上記 3 つのいずれの場合も、コンテンツタイプが指定されていない場合は、一度だけ質問します(references/interaction-model.md → Content type)。アイテムごとの解決とエラー処理:
- コンテンツが 0 件 → STOP(終端)。 検索で何も見つからない場合は、HALT behaviour → Terminal stops に従ってターンを終了します。URL/contentKey を捏造したり、むやみに再試行したり、プレースホルダーを配置したりしてはいけません。
- 検索がキャンセル/エラー/利用不可 → HALT(回復可能)。 アイテムの
unauthenticatedUrlまたはcontentKey(判明していればchannelIdとタイプも)を貼り付けるようユーザーに依頼します。識別情報が提供された後にのみ再開します。推測してはいけません。 unauthenticatedUrlもcontentKeyもない → HALT(「'{title}' は配信 URL も contentKey も公開していないため、埋め込めません。」)。- それ以外の場合:
unauthenticatedUrlがある → 外部 ref。そうでなければcontentKeyがある → uiBundle-space ref。fqnが空 → コンテンツタイプについて質問します。
これらの停止時の質問文は references/interaction-model.md を参照してください。完全なカタログは references/failure-modes.md にあります。
グループ(N 個のアイテム、同じタイプ)。 アイテムが複数ある場合、または呼び出し元が複数のキー/URL を渡す場合は、グループとして扱います(コンテンツタイプは 1 つ。タイプに関する質問は 1 回だけ)。変更点:手順 2 は共通の fqn に対して1 回だけ実行します(アイテムごとではなく、<type>.ts を 1 つ、レンダラーを 1 つ作成します)。手順 3 では N 個の ref エントリーをループし、同じタイトルのアイテムが重複しないよう、ref 名に短い contentKey/URL ハッシュを付加します。手順 4 は Group render target に従います。ベストエフォート方式で処理し、識別情報/スキーマで問題があるアイテムはスキップして報告します。1 つのアイテムの問題でバッチ全体を HALT にしてはいけません。contentKey と unauthenticatedUrl が混在するグループも問題ありません。それぞれの ref は独自の形状に基づいて振り分けられます。
2. スキーマ同期
OOTB sfdc_cms__news — 定義済みスキーマを使用し、取得しないでください。 メタデータスキーマの取得(ContentTypeBundle retrieve/MCP/SOQL)では、OOTB Salesforce コンテンツタイプを解決できません。そのため、fqn === 'sfdc_cms__news' の場合は取得を完全にスキップし、references/schema-sync.md に記載された固定の3フィールドセット(bannerImage(Image→CmsImageField/'image')、body(RichText→string/'richText')、excerpt(Text→string/'text'))から <Type>Body と <type>FieldTypes をそのまま生成します。sf project retrieve、MCP、SOQL は使用しません。その他のOOTBタイプは定義済みではないため、カスタムとして扱い、取得してください。
OOTB メディア — これも定義済みで、news と同様です。 fqn ∈ sfdc_cms__{image,audio,video,document} の場合も取得をスキップします。ref は(フレームワークごとに)共通の MediaRenderer にそのまま紐付けられます。生成される <type>.ts も heuristicRenderer もありません。レンダラーは ref の形状に応じて分岐します(外部の url → そのまま使用、contentKey → 取得して medium ごとに解決。Rule 2)。出力については、references/heuristic-render-rules.md の Media types と references/init-scaffold.md を参照してください。
カスタム fqn の場合は、references/schema-sync.md に従ってライブスキーマを取得します(DeveloperName のみを使用し、sfdc_cms__/c__ を削除)。各フィールドを lightningType にマッピングし、次にlowerCamel の CmsFieldType リテラルにマッピングします(Text→'text'、RichText→'richText'、…)。大文字・小文字の形式を変換し、PascalCase のトークンをコピーしないでください。
- Body モジュールとレンダラーの両方が存在する場合 → 再取得し、現在の
<Type>Bodyと差分を取り、<framework>/types/<type>.tsのマーカーより下のブロックのみを再生成します(マーカーより上はそのまま保持)。レンダラーは全体を書き換えます。 - どちらかが存在しない場合 →
assets/shared/cmsContentType.tsとフレームワークのレンダラーテンプレートから両方を生成し、フレームワークのtypes/ディレクトリに配置します。利用側は各レンダラーを直接パス(../types/{{TypeRenderer}})でインポートするため、更新するバレルはありません。 - マーカーが欠落または不正な形式の場合 → 処理を停止し、復元するようユーザーに依頼します。自動修復は行いません。取得に失敗した場合 → 手動入力が必要なフィールドを列挙する
TODOを付けて、マーカーより下のブロックを出力します。
詳細:references/schema-sync.md。
3. Ref の登録
EXT_<TITLE_SNAKE>(大文字のスネークケース、ASCII)を算出し、src/cms/shared/externalRefs.ts の external-refs-begin マーカーブロック内に追加します。Ref の型に対応する形式を使用し、存在しない場合はその satisfies 型をインポートします。配置の前に登録してください。ref('EXT_X') は keyof typeof externalRefs に対して型チェックされるため、未登録の ref は意図的にコンパイルエラーになります。**cmsType には必ず fqn の DeveloperName を設定し、satisfies の <K> と一致させてください。**この識別子を省略すると、誤った型を検出するガードが機能しなくなります。識別情報はそのまま保存してください(URL の再署名や contentKey の手動マッピングは行いません)。冪等性:同じ名前+同じ識別情報ならスキップ、同じ名前+異なる識別情報なら停止(衝突)します。2 つの Ref 型の形式とその理由については、references/embed-recipes.md を参照してください。
外部の url Ref に紐づくメディア — altText/title を取得(取得した本文はありません。ルール 2):検索時に引き継いだ情報を使って CmsExternalRef に設定します。image/document で両方ともない場合は、1 回だけ確認します(Media alt text)。audio/video/contentKey Ref では不要です。
チャンネルの解決(CmsRef のみ)— カタログを優先します。 CmsRef は必ずチャンネルを解決する必要があります。次の順で解決し、最初に該当した時点で終了します:(1)既存のカタログ — public/content-metadata.json に空でない channelId がある場合はそれを使用し、上書きしません。(2)プロンプト/識別情報 — contentKey とともに channelId が渡された場合は、public/content-metadata.json に { "channelId": …, "contents": [] } を書き込みます。ファイルがない場合は作成します。(3)どちらもない場合 → ユーザーに確認し、channelId を尋ねてから書き込みます。(4)それでも解決しない場合 → 停止(「'{title}' はチャンネルを解決できない contentKey です。レンダリングに失敗します。channelId を指定するか、public/content-metadata.json に追加してください。」)。プレースホルダーや偽のチャンネルは決して書き込まないでください。**競合:**プロンプトのチャンネルが空でないカタログのチャンネルと異なる場合は、どちらを優先するか選択肢として確認します(references/interaction-model.md → Channel conflict:カタログを維持する場合は書き込みなし、プロンプトを使用する場合は channelId のみ上書き)。確認を得た場合に限りカタログを上書きし、黙って上書きしないでください。外部 Ref は URL にチャンネルを含むため、確認は不要です。CMS_CHANNEL_ID_FALLBACK は引き続き、注意事項付きのオプトイン上書きに限って使用します(assets/shared/externalRefs.ts を参照)。形式、場所、優先順位については、references/embed-recipes.md を参照してください。
4. レンダーターゲット — 詳細ページまたはインプレース埋め込み
コンテンツのレンダー先を、次の順に特定します。
- プロンプトでページ/ルートが指定されている → 詳細ページ分岐。質問は不要です。
- プロンプトでインプレースの配置場所(ホームページ、サイドバー、ヒーローなど)が指定されている → 配置分岐。質問は不要です。
- どちらも指定されていない → 選択肢から1つ選ぶ質問をして、回答を待ちます(
references/interaction-model.md→ レンダーターゲット):"'{title}' をどのようにレンダーしますか?" — 専用の詳細ページ/ルート / 既存のビュー内にインプレースで配置。インプレースの場合は、続けて自由回答形式で "'{title}' はどこにレンダーしますか? コンポーネント、ページ、または領域を指定してください。" と質問し、回答に対して配置分岐を実行します。
レンダー先を決して推測しないでください。暗黙に詳細ページにしたり、指定または回答されたターゲットなしにインプレース埋め込みを行ったりしてはいけません。ターゲットが未回答の場合は処理を停止します。プロンプトでページと配置場所の両方が指定されている場合は、どちらを優先するか尋ねます。
グループのレンダーターゲット(N 件)。 デフォルトでは、各項目に詳細ページを作成します。次のとおり特定します。(1)プロンプトでリスト/グリッド/1 ページが指定されている場合、またはグループ全体のインプレース領域が 1 つ指定されている場合 → リスト/グリッド分岐。(2)プロンプトでページ/ルートが指定されている場合 → 詳細ページ分岐をループし、項目ごとに 1 ページとスラッグを作成します。(3)どちらも指定されていない場合 → 1 回質問し、デフォルトは詳細ページとします(references/interaction-model.md → グループのレンダーターゲット):各項目に詳細ページを作成 / 1 ページにリスト/グリッドを 1 つ作成。ループモードではスラッグの重複を排除します。
リスト/グリッド分岐 — ターゲットの特定によって選択された場合に限ります。 フレームワークのリスト用ラッパー(assets/<framework>/TypeList.*)を生成し、各 ref にレンダラーを 1 つずつ作成して、カードごとのフェッチ分離を適用します。その後、配置分岐を使ってノードを 1 つ配置するか、詳細ページ分岐を使って専用ルートを作成します。リストとグリッドの切り替えには別のコンポーネントではなく、className CSS を使います(references/styling-scopes.md)。
配置分岐。 指定されたターゲットにタイプごとのレンダラーを挿入し、必要に応じてインポートを追加します。冪等です。スニペット、レシピ、アンカーによる停止条件については、references/embed-recipes.md を参照してください。
詳細ページ分岐。 入力(PageName、urlSlug、ターゲット ref、ルーターファイル)を確認し、ページを生成し(assets/<framework>/DetailPage.* を基に)、ルーターのマーカーブロックにルートを挿入します。追記のみで、冪等かつ衝突安全です。完全なアルゴリズムと前提条件による停止条件については、references/detail-pages.md を参照してください。
スタイリング。 このスキルは CSS ファイルを作成しません。表示スタイルはインラインで、プロンプトに基づいてレンダーターゲットに適用します。指定がない場合は、デフォルトの読みやすいレイアウト(中央寄せの約 700px 幅のカラム、大きな文字、十分な余白)を使います。インプレース埋め込みは控えめにします。クラスフックについては、references/styling-scopes.md を参照してください。
5. 検証
このスキルが書き込みまたは編集したファイルのみを型チェックします。対象を限定し、ループは行いません。 React/Vite の場合は npx tsc --noEmit を 1 回、Angular の場合は npx tsc --noEmit -p tsconfig.json を 1 回実行し、この実行でスキルが変更したファイルの診断のみを読みます。未変更ファイルの診断はすべて既存のものなので、無視してください。修正パスは最大 1 回です。 スキルが生成したファイルにある診断を修正し、tsc をもう 1 回実行します。逐語的な基盤ファイルは決して編集しないでください。 そこにある診断はほぼ常にツールキットが解決されていないことが原因です。パッチを当てず、環境に関する注記として報告してください。lint、build、ng build、開発サーバーは実行しないでください。tsc/tsconfig がない場合はスキップし、その旨を記載してください。その後、書き込み/変更したファイル、インストール、型チェックの結果、ref の識別情報、レンダラーのタイプ、レンダーターゲット、停止条件を報告します。この実行で ref が CmsRef(認証チャネル)であるメディアを作成した場合は、一時的な制限に関する注記(内部アプリでは認証チャネルのメディアをまだレンダーできず、今後のリリースで対応予定です。パブリック URL の CmsExternalRef は現在レンダーできます)を追加してください。停止せず、情報として伝えてください。修正ポリシーの全文と報告チェックリストについては、references/verify.md を参照してください。
Codegen のガードレール
以前のスキャフォールドで再現された不具合を防ぐための5つのルールです。生成されるアプリはすべて、この5つを満たす必要があります(違反はブロッキングバグです)。これらは assets/ テンプレートに組み込まれているため、編集時も必ず維持してください。以下は1行版です。全文は references/codegen-guardrails.md にあります。
- ルール 1 — 正しいツールキットの読み取りを呼び出し、トランスポートを再実装しない。
CmsExternalRef→getCmsContentByUrl(url)をそのまま使用します。CmsRef→getCmsContentByKey/getCmsContentByKeysを使用します。URL の再構築、contentKeyの手動での再マッピング、経路の取り違えは絶対にしないでください。**例外 — 外部のurlref にある単独メディアは取得しません。**そのunauthenticatedUrlがアセットなので、そのまま要素のsrc/hrefに使用します(ルール 2)。ニュース/構造化データのCmsExternalRefは引き続き取得します。CmsRefにチャネルが必要な場合は、解決済みの channelId と空のcontents: []を含むpublic/content-metadata.jsonを書き込みます(存在しない場合は作成し、チャネルがすでに存在する場合は上書きしません)。そして、プレースホルダー/偽のチャネルは絶対に書き込まないでください(ツールキットはカタログがない場合はオープンに失敗しますが、存在していて誤っている場合はそうなりません。デプロイパイプラインが実際のcontentsを設定します)。 - ルール 2 — ref の形状に続いてメディアの種類から
srcを解決する。 最初に ref の形状で分岐します。外部のurlref はref.urlをそのまま使用します(取得なし、リゾルバーなし、alt/title は ref から取得)。contentKeyref は本文を取得し、ツールキットの同期リゾルバーで解決します。画像 →resolveCmsImageUrl(sfdc_cms:media)、音声/動画/ドキュメント →resolveMediaUrl(sfdc_cms:media.url)(サブジェクトのみを指定し、手動でプレフィックスを付けないでください)。メディアの振り分けは ref の形状とref.cmsTypeに基づき、本文の形状には基づきません。 - ルール 3 — 共有/重複排除された取得状態は React 19 StrictMode でも維持する。
useCmsItemはモジュールレベルのinflightマップで、マウント→クリーンアップ→再マウントをまたいで重複取得を排除します。クリーンアップではローカルのcancelledフラグを設定し、共有リクエストを中断してはなりません(Angular のCmsItemServiceも同様です)。「一度は動くが、再マウントすると失敗する」はブロッキングバグです。 - ルール 4 — RichText を注入前にデコードし、フレームワークごとに経路を1つだけ使う。 まず
decodeRichHtmlEntitiesを実行し、その後、単一の箇所で注入します(React はheuristicRenderer.tsx、Angular はcms-content.component.tsの[innerHTML])。デコードとエスケープの両方を実行してはなりません。 検出対象はタグの開始(<tag/<//<!)または<(RICH_TEXT_RE)です。単独の<やvalue.includes('<')では検出しません。 - ルール 5 — スカラー値のフィールドはプリミティブ型にし、
{ value }のラッパーは使用しない。 Delivery はスカラー値をプレーンなプリミティブとして返します(オブジェクトなのは画像のみで、CmsImageFieldです)。したがって、body.bodyを読み取り、body.body.valueは読み取らないでください(.valueによるアンラップ結果は常にundefinedです)。実際のペイロードから形状を導出してください。アイテムのtitleはcontentBody内ではなく、エンベロープのルートにあります。
HALT の動作
次の場合、スキルは HALT します(書き込みは行わず、失敗したファイル、理由、最短の修正方法を報告してユーザーの応答を待ちます)。フレームワークが不明または曖昧、マーカーがない/不正、unauthenticatedUrl と contentKey のどちらもないコンテンツ、異なるアイデンティティとの ref 名の衝突、レンダー先が未回答/解決不能、ルーターの対象が曖昧、スラッグの衝突、詳細ページの前提条件が不足、Init/foundation の不整合、検索がキャンセル/エラーになった場合(復旧可能 — ユーザーがアイデンティティを貼り付けます)。複数アイテムの選択は HALT せず、Groups ごとにレンダリングします。全カタログは references/failure-modes.md を参照してください。
終了条件 — セッションはスキャフォールドを何も生成せずに終了します(復旧可能な HALT とは異なります)。(1)Init が拒否された — Run Init? に「No」と回答したため、ランタイムをスキャフォールドできず、スキルは Embed を実行できません。(2)検索結果がゼロ — experience-search-coordinate で何も見つからず、そのアイテムはまだこの uiBundle スペースで作成されていません。まずそこで生成してから再実行するようユーザーに伝えてください。どちらの場合も、書き込み、インストール、推測は行いません。詳細は references/interaction-model.md → 終了条件 を参照してください。
Init と Embed の検出
対象の uiBundle ルート(src/ があるディレクトリ)に対して、検出されたフレームワークの必須基盤セットに含まれるすべてのパスを確認し(単一のカナリアファイルではありません)、存在する数に応じて分類します(フレームワークごとのファイル一覧とバンドル解決ルールについては、references/init-scaffold.md を参照してください)。
- embed — すべて存在する → Embed パイプラインを実行します。
- init — すべて存在しない → 選択肢を提示して確認します(
references/interaction-model.md→ Run Init?):Yes, scaffold it / No。Yes の場合 → Init を実行します。No の場合 → 終了します(HALT behaviour → Terminal stops を参照):Init が拒否されたことを伝え、何も書き込まず、インストールもせず、ターンを終了します。Embed に進んだり、一部だけスキャフォールドしたりしてはいけません。 - drift — 一部は存在し、一部は存在しない → 停止します(ランタイムが不完全な状態で Embed を実行してはいけません)。存在するファイルと不足しているファイルの一覧をそのまま報告し、確認します(
references/interaction-model.md→ Complete a partial runtime?):Yes, write the missing files / No。yes の場合 → 不足しているファイルのみをそのまま書き込み(Read→Write、既存ファイルはスキップ。references/init-scaffold.mdを参照)、再確認します。存在するファイルには決して触れず、「更新」もしません。no の場合 → 停止します。既存ファイルを黙って補完、上書き、またはリセットしてはいけません。
ランタイムがすでにスキャフォールド済みであることを示すプロンプトは、Embed であることを強く示します。それでも確認を実行しますが、Init は実行しないでください。
Embed では差分のみを扱い、ランタイムを再出力してはいけません。 Embed を検出したら、実行中に書き込めるのは新しい <framework>/types/<type>.ts とレンダラーまでで、さらに shared/externalRefs.ts とレンダー対象のマーカーブロック内を編集できます。存在する基盤ファイルはすべてバイト単位で変更してはいけません。再スキャフォールド、再コピー、「更新」、再度の npm install は行わないでください。Init テンプレートを再出力すること(例:externalRefs を {} にリセットすること)は、処理を妨げる不具合です。基盤ファイルに問題があるように見える場合は、Init のドリフトとして停止し、黙って上書きしてはいけません。
一括読み込み
デフォルトでは項目ごとに読み込みます。各レンダラーが個別に読み込むため、カードごとの失敗を分離できます。既知の contentKey のセットを認証済みの1回のラウンドトリップで読み込むには、getCmsContentByKeys を直接呼び出してください。スキャフォールドするバッチローダーはありません。各キーの結果はベストエフォートです。一括処理の対象は contentKey のみです(外部 URL は個別に読み込みます)。references/bulk-loading.md を参照してください。
対象外
動的なフィードの検出(「最新ニュース10件」)、相互参照の監査ツール、組織間でのメディア/画像参照の再マッピング、React と Angular の uiBundles 以外のフレームワーク。
ファイルとリファレンス
テンプレート — assets/shared/:cmsCore.types.ts、externalRefs.ts、cmsContentType.ts(基盤 + タイプごとの本文)、mediaLabels.ts。assets/react/:useCmsItem.ts、heuristicRenderer.tsx(レンダラー + RenderField ディスパッチャー)、TypeRenderer.tsx、TypeList.tsx、DetailPage.tsx、MediaRenderer.tsx。assets/angular/:cms-item.service.ts、cms-content.component.ts、TypeRenderer.component.ts、TypeList.component.ts、DetailPage.component.ts、MediaRenderer.component.ts。
ドキュメント(references/):package-api.md(ツールキット API とコントラクト — 正確な仕様)、interaction-model.md(文言と終了条件)、init-scaffold.md(ランタイムのツリー、コピー対象、基盤セット)、schema-sync.md(取得と再生成)、verify.md(修正ポリシーとレポート)、codegen-guardrails.md(ルール1~5の全体)、embed-recipes.md(URL の正規表現と配置)、heuristic-render-rules.md、detail-pages.md、styling-scopes.md、bulk-loading.md、failure-modes.md。