Gh Bot Comment
jetstreamapp/jetstream
Post GitHub PR/issue comments, reviews, and review replies as the Jetstream bot account instead of the user's personal account.
Renders, embeds, or displays existing Salesforce CMS content in a React or Angular uiBundle app: installs the CMS toolkit, registers a typed reference, generates a framework-matched renderer from…
$ npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install forcedotcom/sf-skills experience-cms-content-render --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/forcedotcom/sf-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/experience-cms-content-render .claude/skills/experience-cms-content-render && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "experience-cms-content-render" agent skill from https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-render into .claude/skills/experience-cms-content-render/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "experience-cms-content-render", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-renderType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install forcedotcom/sf-skills experience-cms-content-render --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/sf-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/experience-cms-content-render .agents/skills/experience-cms-content-render && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "experience-cms-content-render" agent skill from https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-render into .agents/skills/experience-cms-content-render/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "experience-cms-content-render", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install forcedotcom/sf-skills experience-cms-content-render --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/sf-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/experience-cms-content-render .cursor/skills/experience-cms-content-render && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "experience-cms-content-render" agent skill from https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-render into .cursor/skills/experience-cms-content-render/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "experience-cms-content-render", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/forcedotcom/sf-skills.git --path skills/experience-cms-content-render--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install forcedotcom/sf-skills experience-cms-content-render --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/sf-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/experience-cms-content-render .gemini/skills/experience-cms-content-render && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "experience-cms-content-render" agent skill from https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-render into .gemini/skills/experience-cms-content-render/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "experience-cms-content-render", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install forcedotcom/sf-skills experience-cms-content-renderInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/forcedotcom/sf-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/experience-cms-content-render .github/skills/experience-cms-content-render && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "experience-cms-content-render" agent skill from https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-render into .github/skills/experience-cms-content-render/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "experience-cms-content-render", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install forcedotcom/sf-skills experience-cms-content-render --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/forcedotcom/sf-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/experience-cms-content-render .opencode/skills/experience-cms-content-render && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "experience-cms-content-render" agent skill from https://github.com/forcedotcom/sf-skills/tree/main/skills/experience-cms-content-render into .opencode/skills/experience-cms-content-render/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "experience-cms-content-render", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
experience-cms-content-renderRenders, embeds, or displays existing Salesforce CMS content in a React or Angular uiBundle app: installs the CMS toolkit, registers a typed reference, generates a framework-matched renderer from…
Experience Cms Content Render is an agent skill from forcedotcom/sf-skills. Renders, embeds, or displays existing Salesforce CMS content in a React or Angular uiBundle app: installs the CMS toolkit, registers a typed reference, generates a framework-matched renderer from the live schema, and wires the render path (delivery API, contentKey, RichText, image resolution). Prefer over general UI-bundle skills like experience-ui-bundle-frontend-generate (page/layout shell only) for the CMS-rendering part, even under src/. Triggers on prompts like 'render the CMS article on the home page'…
Its SKILL.md is about 6.3k tokens, which your agent loads only when the skill is triggered. The skill folder holds 33 other files, including reference files and assets (for example `assets/angular/DetailPage.component.ts`, `assets/angular/MediaRenderer.component.ts` and `assets/angular/TypeList.component.ts`).
It sits in Sales & Support, covering CRM management and Blog and article writing. It works with Salesforce, Angular and React. The repository describes itself as: Salesforce's curated collection of agent skills for building applications. Optimized for Agentforce Vibes, compatible with all AI tools. The licence is Apache-2.0.
5 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 3c15867. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships script files (TypeScript, from the files we listed), which the agent can run.
Shell commands in SKILL.md call:
npmnpxsfFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npm and npx, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Experience Cms Content Render loads about 6.3k tokens when it runs, and up to ~26k if it reads all its reference files. Until then it costs about 220 tokens; SKILL.md has 2,985 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from forcedotcom/sf-skills at commit 3c15867, republished under its Apache-2.0 licence (© forcedotcom). 2,985 words, ~6,294 tokens.
.claude/skills/experience-cms-content-render/SKILL.md (or your agent's skills folder). This skill also uses 29 other files; get the full folder from GitHub.This skill directs how to render CMS content into a uiBundle app; it does NOT re-implement the delivery transport. The npm package @salesforce/ui-bundle-template-feature-cms-toolkit owns HTTP, envelope-unwrap, catalog key-remap, channel resolution, image-URL resolution, entity decoding and other utilities. The skill installs that package, then generates the thin framework-specific layer around it (typed refs, per-type renderers, fetch lifecycle, placement) for React and Angular uiBundle apps, inferring Init vs Embed from file state and never scaffolding Init inside an Embed prompt without telling the user.
The toolkit exports functions and types only: no components, no hooks. Import target and full signatures: references/package-api.md. The three reads (all take <TBody> + options?, all throw on failure):
getCmsContentByUrl — public CDN, unauthenticated; the pasted unauthenticatedUrl fetched AS-IS.getCmsContentByKey — one item, authenticated Connect; the toolkit remaps source→target key and resolves the channel.getCmsContentByKeys — many, ONE authenticated round-trip, best-effort per-key (references/bulk-loading.md).Plus sync helpers resolveCmsImageUrl, resolveMediaUrl, decodeRichHtmlEntities and the typed error classes. Every read returns the already-UNWRAPPED contentBody with the envelope title lifted in; skill code only types TBody and never handles the envelope, { items: [...] }, or a .value wrapper.
This skill's assets/ templates and references/ docs load alongside SKILL.md; reference them by relative path (assets/shared/…, assets/<framework>/…, references/…) and treat their contents as available. Foundation runtime files are emitted VERBATIM; only cmsContentType.ts and the renderer templates carry {{…}} substitution. Never reconstruct a template from memory — the templates encode the StrictMode hook, the sanitizer seam, and the correct package call sites. Every detection below (framework, pasted-URL, Init/Embed/drift) READS the target app's files directly; there is no script to run.
This skill is interactive. At every decision the prompt leaves open (framework, Init, drift, content type, render target, candidate bundle) ASK with an option-select question of 2–4 labeled options, recommended first, and wait — never a bare free-text prompt. Use free-text only for open answers: a component/region name, a URL, a slug. Full wording, options, and headers: references/interaction-model.md; follow it for every ASK below. Two answers are terminal (end the session, scaffold nothing): declining Init, and a search that returns zero content (see HALT behaviour).
Bypass mode (non-interactive / automation). When the prompt explicitly asks to bypass the interaction model ("non-interactive", "don't ask", "proceed/yes to all", "auto-proceed"), ask NO option-select question: answer every would-be question with its recommended option and continue. This changes only the answer source, not pipeline logic, and does NOT override the terminal stops — a genuine zero-content result still stops, as does any true HALT with no safe default (unnamed in-place placement, unresolvable candidate bundle). Per-question recommended answers: references/interaction-model.md → Bypass mode.
Read <appRoot>/package.json (<appRoot> = the dir with package.json) and merge dependency names from dependencies, devDependencies, and peerDependencies; a framework may sit in any bucket:
react present, @angular/core absent → React templates.@angular/core present, react absent → Angular templates.references/interaction-model.md → Framework): React / Angular.package.json → HALT, not a supported React/Angular app; do not scaffold.The framework selects the template family for every step below (assets/react/* vs assets/angular/*); assets/shared/* is framework-agnostic and used by both.
Init (one-time scaffold). On the first Embed, when the framework's foundation set (see Init vs Embed detection) is absent, run npm i @salesforce/ui-bundle-template-feature-cms-toolkit@latest --min-release-age=0 in the app root and write the shared runtime once (prompt the user first). This npm install is the ONLY way the toolkit is added; every import uses the @salesforce/… specifier. Init never overwrites: a fully-scaffolded tree is a no-op; a partial tree is drift and HALTS (see Init vs Embed detection).
Emit runtime files VERBATIM (rationale under Bundled files): Read each foundation template and Write it unchanged to src/cms/…, skipping any destination that already exists (never overwrite). Copy the assets/shared/ pair always, plus the detected framework's pair. Full tree, copy-set table, and fallback: references/init-scaffold.md.
Embed (per-prompt wiring). Given a prompt naming one CMS item (or a same-type group — see Groups), plus a placement when stated, run the pipeline below. When neither a placement nor a detail-page request is stated, step 4 asks which. Every step is idempotent.
Identity → Schema Sync → Ref Registration → Render Target → Verify{ unauthenticatedUrl | contentKey, fqn, title }Two ref types (see assets/shared/cmsCore.types.ts): a foreign ref — item has an unauthenticatedUrl (public CDN) → register a CmsExternalRef (url), read via getCmsContentByUrl — except standalone media, whose url is the asset, used directly, not fetched (Rule 1/2; capture altText/title at step 3); a uiBundle-space ref — item has only a contentKey → register a CmsRef (contentKey), read via getCmsContentByKey (toolkit remaps the key via content-metadata.json). Resolve the identity from the input:
references/embed-recipes.md; case-insensitive, matches a URL embedded in prose). That URL IS the unauthenticatedUrl; skip search → foreign ref. No match → fall through below.contentKey, optional channelId, no delivery URL): skip search → CmsRef. Store the contentKey verbatim; route any given channelId into public/content-metadata.json per the step-3 channel-resolution gate. A CmsRef with no resolvable channel (none in the catalog, prompt, or from the user) HALTs — never render without one.{ unauthenticatedUrl, fqn, title, altText, contentKey } per item; unauthenticatedUrl is null for uiBundle-space items, so use contentKey. For media, carry altText/title onto the CmsExternalRef (step 3).In all three, an unnamed content type → ask once (references/interaction-model.md → Content type). Per-item resolution and error handling:
unauthenticatedUrl OR its contentKey (+ channelId and type if known); resume only once they provide an identity. Never guess.unauthenticatedUrl nor contentKey → HALT ("'{title}' exposes no delivery URL and no contentKey — cannot embed.").unauthenticatedUrl present → foreign ref; else contentKey → uiBundle-space ref; fqn empty → ask the content-type question.Question wording for these stops: references/interaction-model.md; full catalogue: references/failure-modes.md.
Groups (N items, same type). More than one item, or a caller passing multiple keys/urls, is a group (one content type; ask the type question ONCE). Deltas: step 2 runs ONCE for the shared fqn (one <type>.ts + one renderer, never per item); step 3 loops N ref entries, suffixing the ref name with a short contentKey/url hash so same-title items don't collide; step 4 follows Group render target. Best-effort: skip and report an item that fails identity/schema, never HALT the whole batch on one. A group mixing contentKey and unauthenticatedUrl is fine — each ref dispatches by its own shape.
OOTB sfdc_cms__news — use the predefined schema, do NOT fetch. The metadata schema fetch (ContentTypeBundle retrieve / MCP / SOQL) cannot resolve OOTB Salesforce content types, so for fqn === 'sfdc_cms__news' skip retrieval entirely and generate <Type>Body + <type>FieldTypes from the fixed three-field set — bannerImage (Image→CmsImageField/'image'), body (RichText→string/'richText'), excerpt (Text→string/'text') — verbatim from references/schema-sync.md; no sf project retrieve, MCP, or SOQL. Other OOTB types are not predefined; treat them as custom and fetch.
OOTB media — predefined too, parallel to news. For fqn ∈ sfdc_cms__{image,audio,video,document} also skip retrieval: the ref binds to the shared verbatim MediaRenderer (per framework) — no generated <type>.ts, no heuristicRenderer. The renderer forks by ref shape (foreign url → used directly; contentKey → fetch + resolve by medium; Rule 2). Emission: references/heuristic-render-rules.md → Media types, references/init-scaffold.md.
For a custom fqn, fetch the live schema (bare DeveloperName, strip sfdc_cms__/c__) via references/schema-sync.md, mapping each field to a lightningType, then to the lowerCamel CmsFieldType literal (Text→'text', RichText→'richText', …; translate the casing, never copy the PascalCase token).
<Type>Body, regenerate ONLY the below-marker block of <framework>/types/<type>.ts (above-marker preserved verbatim); rewrite the renderer in full.assets/shared/cmsContentType.ts and the framework renderer template into the framework's types/ dir; consumers import each renderer by direct path (../types/{{TypeRenderer}}), so there is no barrel to update.TODO listing fields to fill manually.Details: references/schema-sync.md.
Compute EXT_<TITLE_SNAKE> (upper-snake, ASCII) and append inside the external-refs-begin marker block of src/cms/shared/externalRefs.ts, emitting the form matching the ref type and importing its satisfies type if absent. Register BEFORE placement: ref('EXT_X') is typed against keyof typeof externalRefs, so an unregistered ref is a compile error by design. Always set cmsType to the fqn DeveloperName and match the <K> in satisfies — omit this discriminator and the wrong-type guard silently disappears. Store the identity verbatim (never re-sign a URL or hand-map a contentKey). Idempotency: same name + same identity → skip; same name + different identity → HALT (collision). Both ref-type forms + rationale: references/embed-recipes.md.
Media on a foreign url ref — capture altText/title (no fetched body; Rule 2): set them on the CmsExternalRef from the search hand-off; both absent for image/document → ASK once (Media alt text). audio/video/contentKey refs skip it.
Channel resolution (CmsRef only) — catalog-primary. A CmsRef MUST resolve a channel; resolve in order, stop at the first hit: (1) existing catalog — a non-empty channelId in public/content-metadata.json → use it, never overwrite; (2) prompt/identity — a channelId came with the contentKey → write public/content-metadata.json ({ "channelId": …, "contents": [] }), creating it if absent; (3) neither → ASK the user for the channelId, then write it; (4) still unresolved → HALT ("'{title}' is a contentKey with no resolvable channel; rendering will fail. Provide the channelId or add it to public/content-metadata.json."). Never write a placeholder/fake channel. Conflict: a prompt channel that differs from a non-empty catalog channel → ASK which wins as an option-select (references/interaction-model.md → Channel conflict: keep catalog = no write; use prompt = overwrite only channelId); overwrite the catalog only on confirmation, never silently. Foreign refs carry their channel in the URL — no gate. CMS_CHANNEL_ID_FALLBACK remains only as a cautioned, opt-in override (see assets/shared/externalRefs.ts). Shape, location, precedence: references/embed-recipes.md.
Resolve WHERE the content renders, in order:
references/interaction-model.md → Render target): "How should I render '{title}'?" — Dedicated detail page/route / In place in an existing view. In-place → follow up with an open answer "Where should '{title}' render? Name a component, page, or region.", then run Placement against the answer.Never guess a target: no detail page implicitly, no in-place embed without a stated/answered target; an unanswered target HALTs. A prompt naming both a page and a placement → ask which takes precedence.
Group render target (N items). Default is a detail page PER item. Resolve: (1) prompt asks for list/grid/one page, or names ONE in-place region for the whole group → List/Grid branch; (2) prompt asks for pages/routes → loop Detail Page branch, one page + slug per item; (3) neither → ASK once, default detail pages (references/interaction-model.md → Group render target): A detail page for each item / One list/grid on a single page. Loop mode dedupes slugs.
List/Grid branch — only when target resolution selected it. Generate the framework's list wrapper (assets/<framework>/TypeList.*), one renderer per ref so per-card fetch isolation applies, then place ONE node via the Placement branch or its own route via the Detail Page branch. List vs grid is className CSS, not a second component (references/styling-scopes.md).
Placement branch. Insert the per-type renderer at the named target, adding imports if missing. Idempotent. Snippets, recipes, and anchor HALTs: references/embed-recipes.md.
Detail Page branch. Confirm inputs (PageName, urlSlug, target ref, router file), generate the page (from assets/<framework>/DetailPage.*), and insert the route in the router's marker block. Append-only, idempotent, collision-safe. Full algorithm + prerequisite HALTs: references/detail-pages.md.
Styling. The skill scaffolds NO CSS file; presentation is inline and prompt-driven on the render target. No direction → a default reading layout (centered ~700px column, large type, ample whitespace); in-place embeds stay conservative. Class hooks: references/styling-scopes.md.
Typecheck ONLY the files this skill wrote or edited — bounded, never a loop. Run npx tsc --noEmit ONCE (React/Vite) or npx tsc --noEmit -p tsconfig.json ONCE (Angular), then read ONLY the diagnostics in files the skill touched this run; every diagnostic in an untouched file is pre-existing — ignore it. At most ONE fix pass: fix a diagnostic in a file the skill generated and re-run tsc once; never edit a verbatim foundation file (a diagnostic there is almost always the toolkit unresolved — report it as an environment note, never patch). Do NOT run lint, build, ng build, or the dev server; no tsc/tsconfig → skip and note it. Then report files written/modified, install, typecheck result, ref identity, renderer type, render target, and any HALTs. If this run scaffolded media whose ref is a CmsRef (authenticated channel), add the temporary-limitation note (internal apps can't render authenticated-channel media yet — a future release; a public-URL CmsExternalRef renders today); inform, don't HALT. Full fix policy + report checklist: references/verify.md.
Five rules encoding defects a prior scaffold reproduced; every generated app MUST satisfy all five (a violation is a blocking bug), and they are baked into the assets/ templates — keep them true when editing. One-line form below; full text in references/codegen-guardrails.md.
CmsExternalRef → getCmsContentByUrl(url) AS-IS; CmsRef → getCmsContentByKey/getCmsContentByKeys. Never reconstruct a URL, hand-remap a contentKey, or cross the paths. Exception — standalone media on a foreign url ref is NOT fetched: its unauthenticatedUrl is the asset, used directly as the element src/href (Rule 2); news/structured CmsExternalRefs still fetch. When a CmsRef needs a channel, write public/content-metadata.json with the resolved channelId and empty contents: [] (create if absent; never overwrite a present channel) — and never write a placeholder/fake channel (the toolkit fails open on a missing catalog, not a present-but-wrong one; the deploy pipeline fills real contents).src by ref shape, then by medium. Fork first on ref shape: a foreign url ref uses ref.url directly (NO fetch, NO resolver, alt/title off the ref); a contentKey ref fetches the body and resolves via the toolkit's SYNC resolvers — image → resolveCmsImageUrl(sfdc_cms:media), audio/video/document → resolveMediaUrl(sfdc_cms:media.url) (subject only, never hand-prefix). Media dispatch is by ref.cmsType and ref shape, never by body shape.useCmsItem dedupes via a module-level inflight map across mount→cleanup→remount: cleanup sets a local cancelled flag and MUST NOT abort the shared request (Angular's CmsItemService mirrors this). "Works once, fails on remount" is a blocking bug.decodeRichHtmlEntities FIRST, then inject at the single site (React heuristicRenderer.tsx; Angular [innerHTML] in cms-content.component.ts). Never both decode AND escape. Detection matches a tag-open (<tag/<//<!) OR < (RICH_TEXT_RE), never a bare < or value.includes('<').{ value } wrappers. Delivery returns scalars as PLAIN primitives (only images are objects, CmsImageField), so read body.body, never body.body.value (the .value unwrap is always undefined). Derive shapes from a real payload; the item title sits at the envelope ROOT, not in contentBody.The skill HALTS (no write; reports the failing file, reason, and shortest fix; awaits user) on: ambiguous/unknown framework; missing/malformed markers; content with neither unauthenticatedUrl nor contentKey; ref-name collision with a different identity; render target unanswered/unresolvable; ambiguous target router; slug collision; missing detail-page prerequisite; Init/foundation drift; a cancelled/errored search (recoverable — user pastes an identity). A multi-item selection renders per Groups, not a HALT. Full catalogue: references/failure-modes.md.
Terminal stops — the session ENDS with nothing scaffolded (distinct from a recoverable HALT): (1) Init declined — "No" to Run Init?, so the runtime can't be scaffolded and the skill can't Embed. (2) Zero search content — experience-search-coordinate finds nothing, so the item isn't authored in this uiBundle space yet; tell the user to generate it there first, then re-run. Neither writes, installs, nor guesses. Details: references/interaction-model.md → Terminal stops.
Against the target uiBundle root (the dir with src/), check every path in the detected framework's mandatory foundation set (not a single canary), then classify by how many are present (per-framework file lists + bundle-resolution rules: references/init-scaffold.md):
references/interaction-model.md → Run Init?): Yes, scaffold it / No. On Yes → run Init. On No → TERMINAL (see HALT behaviour → Terminal stops): state Init was declined, write/install nothing, end the turn; do not fall through to Embed or partially scaffold.references/interaction-model.md → Complete a partial runtime?): Yes, write the missing files / No. On yes → write ONLY the missing files VERBATIM (Read→Write, skips existing; references/init-scaffold.md), then re-check; never touch or "refresh" the present ones. On no → stop. Never silently complete, overwrite, or reset a present file.A prompt stating the runtime is already scaffolded is a strong Embed signal; still run the check, but do NOT run Init.
Embed touches ONLY the delta — never re-emit the runtime. Once Embed is detected, the run writes AT MOST the new <framework>/types/<type>.ts + renderer, plus edits inside marker blocks of shared/externalRefs.ts and the render target. Every present foundation file stays BYTE-FOR-BYTE untouched — do not re-scaffold, re-copy, "refresh", or re-npm install; re-emitting the Init template (e.g. resetting externalRefs to {}) is a blocking bug. A foundation file that looks wrong → HALT as Init drift, never silently overwrite.
Per-item by default: each renderer reads independently, isolating per-card failures. To read a KNOWN set of contentKeys in ONE authenticated round-trip, call getCmsContentByKeys DIRECTLY; there is no batch-loader to scaffold. Best-effort per-key results; only contentKeys batch (foreign URLs read individually). references/bulk-loading.md.
Dynamic feed discovery ("latest 10 news"); cross-ref audit tooling; media/image-reference remapping across orgs; frameworks beyond React + Angular uiBundles.
Templates — assets/shared/: cmsCore.types.ts, externalRefs.ts, cmsContentType.ts (foundation + per-type body), mediaLabels.ts. assets/react/: useCmsItem.ts, heuristicRenderer.tsx (renderer + RenderField dispatcher), 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.
Docs (references/): package-api.md (toolkit API + contract — ground truth), interaction-model.md (wording + terminal stops), init-scaffold.md (runtime tree, copy set, foundation sets), schema-sync.md (retrieval + regen), verify.md (fix policy + report), codegen-guardrails.md (full Rule 1–5), embed-recipes.md (URL regex + placement), heuristic-render-rules.md, detail-pages.md, styling-scopes.md, bulk-loading.md, failure-modes.md.
© forcedotcom, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 29 other files (references, assets) in skills/experience-cms-content-render of forcedotcom/sf-skills.
Open the folder on GitHubat commit 3c15867
Experience Cms Content Render next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Experience Cms Content Render this skillforcedotcom/sf-skills | 1.1k | — | ~6.3k | Automated safety check: Pass | Apache-2.0 | |
| Gh Bot Commentjetstreamapp/jetstream | 125 | — | ~616 | Automated safety check: Pass | Custom licence | |
| Sf ApexJaganpro/sf-skills | 424 | — | ~2k | Automated safety check: Pass | MIT | |
| Sf DebugJaganpro/sf-skills | 424 | — | ~1.3k | Automated safety check: Pass | MIT | |
| Sf Datacloud RetrieveJaganpro/sf-skills | 424 | — | ~1.4k | Automated safety check: Pass | MIT | |
| Churn Riskindranilbanerjee/digital-marketing-pro | 854 | 1 repos | ~2.4k | Automated safety check: Pass | MIT |
jetstreamapp/jetstream
Post GitHub PR/issue comments, reviews, and review replies as the Jetstream bot account instead of the user's personal account.
Jaganpro/sf-skills
Generates and reviews Salesforce Apex code with 150-point scoring.
Jaganpro/sf-skills
Salesforce debug log analysis and troubleshooting with 100-point scoring.
Jaganpro/sf-skills
Salesforce Data Cloud Retrieve phase. An agent skill from Jaganpro/sf-skills.
indranilbanerjee/digital-marketing-pro
Score customer segments for churn risk from behavioral signals — email engagement decline, purchase recency, usage drops, support sentiment — producing a 0-100 risk scorecard with four tiers…
indranilbanerjee/digital-marketing-pro
Import leads into Salesforce, HubSpot, Zoho, or Pipedrive with validation, deduplication against existing CRM records, lead scoring, consent and compliance checks, and source attribution — then push…
forcedotcom/sf-skills
Declared architecture snapshot for one Agentforce agent: planner, topics, actions, flows, Apex, prompt templates, and NGA plugins.
forcedotcom/sf-skills
Data Cloud 360° view of a single Agentforce session. An agent skill from forcedotcom/sf-skills.
forcedotcom/sf-skills
Apply a Salesforce sandbox post-copy automation JSON config against a target org.
forcedotcom/sf-skills
Apply a Salesforce sandbox post-copy automation JSON config against a target org.
forcedotcom/sf-skills
Apply SLDS-compliant UI using the correct blueprints, styling hooks, utility classes, and icons.
forcedotcom/sf-skills
Lightning Web Components with PICKLES methodology and 165-point scoring.
Works with
Categories
Renders, embeds, or displays existing Salesforce CMS content in a React or Angular uiBundle app: installs the CMS toolkit, registers a typed reference, generates a framework-matched renderer from…. Experience Cms Content Render is an agent skill from forcedotcom/sf-skills. Renders, embeds, or displays existing Salesforce CMS content in a React or Angular uiBundle app: installs the CMS toolkit, registers a typed reference, generates a framework-matched renderer from the live schema, and wires the render path (delivery API, contentKey, RichText, image resolution).
Experience Cms Content Render fits situations like: prompts like render the CMS article on the home page; embed this CMS blog post here; show the CMS video in the hero; display the CMS image in the banner.
Run `npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a claude-code`. Or copy the skill folder (skills/experience-cms-content-render in forcedotcom/sf-skills) into .claude/skills/experience-cms-content-render in your project. Claude Code loads it when a task matches its description.
Run `npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a codex`. Or copy the skill folder (skills/experience-cms-content-render in forcedotcom/sf-skills) into .agents/skills/experience-cms-content-render in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add forcedotcom/sf-skills --skill experience-cms-content-render -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/experience-cms-content-render, .gemini/skills/experience-cms-content-render, .github/skills/experience-cms-content-render and .opencode/skills/experience-cms-content-render in your project.
Going by SKILL.md and its folder, Experience Cms Content Render needs TypeScript for the scripts in its folder and the command-line tools its instructions call (npm, npx and sf). Our summary lists: Node.js.
SKILL.md contains no URLs. Its commands use npm and npx, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Experience Cms Content Render is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.3k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 19k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Experience Cms Content Render: Gh Bot Comment (jetstreamapp/jetstream, 125 stars), Sf Apex (Jaganpro/sf-skills, 424 stars), Sf Debug (Jaganpro/sf-skills, 424 stars) and Sf Datacloud Retrieve (Jaganpro/sf-skills, 424 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
forcedotcom (a GitHub organization) maintains it in forcedotcom/sf-skills, which has 1,058 GitHub stars. The repository holds 248 skills in this directory. The repository was last updated on October 3, 2026.
Source: forcedotcom/sf-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.