# Capability: Create from designer HTML (HtmlComponent)

## Intent phrases

- build from designer HTML
- HTML dump
- create from HTML prototype
- see-how-it-works prototype
- layout-fidelity LP
- import HTML landing page
- HtmlComponent from HTML
- custom HTML page
- load HtmlComponent external scripts

## Requires capabilities

`pages` or `articles`

## Prerequisites

- Read `cms-edit://customer/pages` (mixed-page fork at the top)
- Read `cms-edit://customer/routing` and `cms-edit://customer/components-index`
- `cms_edit ["schema", "htmlComponent"]` — use this space’s field IDs (HSD dual-writes `htmlContent` / `cssContent` / `jsContent` plus `markdownContent` when present). **`externalScripts` is the same field id on every space.** If `schema` does **not** list `externalScripts`, **stop** and ask for Contentful migration 28 (`28-add-html-component-external-scripts.js` in se-core-product — not HSD’s location-meta script) — do not stuff vendor/long JS into `jsContent` (Contentful Text is ~50k characters).
- Designer HTML / prototype available (file, paste, or Drive). **Do not** paste a multi-megabyte dump with `data:` URIs into Contentful.

## Paste-ready agent prompt

> Follow `cms-edit://customer/task-create-html-component`. Per section: use a registered type only if layout and interaction match (FAQ → FAQs, closing band → CTA Banner if we accept site chrome). Keep custom scroll/stack/diagrams as HtmlComponent(s), merged when they share JS. Libraries only via `externalScripts`. Extract media. Preview on develop. Draft only.

## When to use this playbook

Use this when the source is a **designer HTML dump**, interactive prototype, LP, or a block that needs a **third-party JS library**.

**Do not** run `task-create-from-document` as “map every section to cards” just because copy looks like a card grid.

If the package is a normal doc/Figma brief with no custom CSS/JS/scroll, use `task-create-from-document` instead.

## Per-section decision

For **each** source section, ask: do **layout and interaction** match a registered type?

| Fit | Action |
|-----|--------|
| Strong (same pattern) | Use the registered type. Example: FAQ accordion → **FAQs** + **FAQ** items (site type also emits FAQPage schema). |
| Medium (copy matches, chrome differs) | Ask the human: site chrome (Tiles, Testimonials, CTA Banner, Hero: background media) vs keep HtmlComponent. |
| Weak / none | HtmlComponent. Custom sticky scroll, stacked cards with a rail, ride-cover animations, photo collages, bespoke diagrams, vendor JS. |

Mock **nav and footer** in the HTML: strip. The page **template** supplies chrome.

**HSD HTML LPs:** default template **General page template - no newsletter**. Do not attach a newsletter template unless the source includes that block.

## Split HtmlComponents (mixed pages)

Mixed pages are expected: registered types **plus** one or more `htmlComponent` entries.

**Do split** when:

- A section can be a registered type so editors change copy without touching HTML
- Remaining custom HTML would exceed field limits (below)
- Adjacent custom blocks **do not share** JS, CSS, sticky state, or in-page jump targets
- Independent `cmsLabel`s help (`CareEngine — ride compare` vs `CareEngine — stack`)

**Do not split** when:

- One behaviour spans the markup (sticky rail + stacked cards; pinned scroll-cover). Splitting breaks `hcRoot`-scoped JS and sticky containing blocks
- Shared SVG sprite, one stylesheet, or one library is used by several sections
- You would only be mirroring every `<section>` with duplicated CSS

Rules:

1. Registered match → registered type.
2. Merge consecutive custom sections that share CSS/JS into **one** HtmlComponent.
3. Start a new HtmlComponent only at a clean boundary (no shared script, no sticky parent).
4. Never one HtmlComponent per `<section>` by default.

Caregiver Hub-style LPs that are custom chrome throughout may stay **one** HtmlComponent.

## Field limits (HSD `htmlComponent`)

| Field | Typical max |
|-------|-------------|
| `htmlContent` / `rawHtml` | 200,000 characters |
| `cssContent` / `customCss` | 50,000 |
| `jsContent` / `customJs` | 20,000 |

Designer dumps with embedded `data:image` / `data:video` are often **megabytes**. They **cannot** be saved as-is.

## External scripts (non-negotiable)

Put third-party libraries in **`externalScripts`**, not `<script src>` in HTML and not `createElement('script')` inside `customJs` / `jsContent`.

Allowed values:

- Same-origin paths under `/html-components/` that end in `.js` (no `..`, no query, no hash). Prefer files in the site repo at `public/html-components/<feature>/….js`.
- HTTPS URLs whose hostname is already listed on the site’s `CmsRendererConfig.htmlComponentExternalScriptHosts`. If the site sets CSP, that host must also be in `script-src`.

If the host is **not** allowlisted, **stop** — do not invent a CDN load and do not patch renderer config without a human.

Rejected (renderer drops these; preview/dev may warn): `http:`, `javascript:`, protocol-relative `//…`, path `..`, query/hash on same-origin paths.

Max **10** URLs per entry. Duplicate URLs on one page share one `next/script` id. Externals load with `scriptStrategy` (default `afterInteractive`). **`customJs` / `jsContent` runs only after every listed src is ready**; if any src fails, inline JS is not injected.

### How to set `externalScripts`

It is an **Array of Symbol**, not a string. Scalar `set` is rejected.

```text
cms_edit ["set", "@cN", "externalScripts", "--json", "[\"/html-components/feature/lib.js\"]"]
```

On `create from-json`, put the same array under `fields.externalScripts`. Run `schema htmlComponent` first — unknown keys fail (`html` vs `htmlContent`).

Then set HTML/CSS/JS. Hosted MCP **rejects** `--content` / `--content-base64` / `--json` over **8192 bytes**. Do not stub-create then patch. Write files on disk, then:

```
cms_edit_request_staged_upload  kind=text  mimeType=text/html|text/css|text/javascript
# run curlCommand
cms_edit ["set", "@cN", "htmlContent", "--staged", "<id>"]
```

Dual-write `rawHtml` / `customCss` / `customJs` when `schema htmlComponent` lists both ids.

Contentful Text fields themselves cap around **50k characters**. Put vendor/long JS in `externalScripts` (`/html-components/….js` in the site repo), not `jsContent`.

`diff` prints bytes + sha256 + a 20-line head for long Text — not the full body. Confirm integrity with `["read", "@cN", "--hash"]` (full body only on `read` without `--hash`).

## Media first (mandatory)

1. Extract binary media from `data:` URIs and local files. Upload via `task-media-reuse-and-upload` (search/reuse first).
2. Rewrite HTML `src` / `poster` to Contentful or ImageKit URLs. Never leave base64 in the field.
3. Keep a `sourceName → assetId` map for the plan.
4. `markdownContent` = user-visible copy and structure (headings, paragraphs, lists, FAQ Q&A, outbound URLs). Omit layout chrome and JS behaviour.

## HtmlComponent authoring (HSD)

Hosted MCP cannot read `docs/ai-page-authoring/*`. Follow these rules here:

1. **Two-layer section shell** — outer `container-cols-grid` + `container-rows-grid` + spacing; inner `col-start-2 col-span-1` + `content-cols-grid`. Background colour on the **outer** section. No private bleed / `max-width` + padding shells.
2. **No mock nav or footer.**
3. **No external fonts** — DM Sans is already loaded.
4. **Colours** — site CSS variables / token classes only. Prefer `darkest-blue` for body text. Do not invent hex.
5. **Typography** — `h1`–`h5`, `p1`–`p3`. Body = darkest-blue at `p2`.
6. **Orange is precious** — eyebrows, stats, rare accents; not default links.
7. **Icons** — `#hsd-icon-*` from the site sprite. Do not invent one-off SVG icon systems when a catalogue icon exists.
8. **Analytics** — every `<section>`: `data-analytics-section`. Controls: `data-analytics-event` + `data-analytics-label`.
9. **JS** — scope all queries to injected `hcRoot`.
10. **`isHero`** — `true` if this block contains the page `<h1>`. **`fullWidth`** — as needed for full-bleed sections (HSD may not apply viewport breakout; grid still required).
11. **Draft only** — never publish unless the human explicitly asks.
12. Dual-write legacy + core field IDs when `schema htmlComponent` shows both.
13. Follow the site HTML style guide (`docs/cms-guidelines/html-component-style-guide.md` when available).
14. Set `cmsLabel` immediately (never leave “New Generic”).

## Steps

1. Inventory sections. For each: registered / ask / HtmlComponent. Merge custom blocks that share CSS/JS.
2. List vendor libraries. Copy same-origin files into `public/html-components/<feature>/` or confirm the HTTPS host is already allowlisted.
3. Post a soft-proof (ordered stack + `externalScripts` URLs) and get a light human **yes**.
4. Media pipeline: extract, upload, rewrite URLs.
5. `list --type template` — pick **no newsletter** unless the source includes a newsletter.
6. Create or open the page (`create page` or existing slug). `indexed: false` for experiments.
7. Add registered components (`add`, `set` / `rtf`) where decided.
8. For each HtmlComponent: `add htmlComponent` (or `create from-json --staged` / `--dry-run --strict` with this space’s field keys — run `schema htmlComponent`; do not guess `html` vs `htmlContent`). Do not put HTML/CSS/JS over 8k in `--json`.
9. Set `externalScripts` with `--json`. Set HTML/CSS/JS via `--staged` when over 8k. Set `cmsLabel` immediately (never leave “New Generic”).
10. `diff` → `read --hash` → `save` (draft).
11. `cms_edit ["preview", "urls", "<slug>"]` — staging is `website.devBaseUrl` (develop preview). Follow `task-preview-verify`. Confirm libraries load (no `createElement('script')`, no dropped-URL warnings).

## Confirmation gates

1. Soft-proof (mixed stack + script URLs) + human yes
2. Media extracted (no `data:` in saved HTML)
3. `schema htmlComponent` field IDs used
4. Libraries only via `externalScripts`
5. `diff` before `save`

## Out of scope

- Publish
- Flattening custom interaction into cards/lists because copy looks similar
- One HtmlComponent per `<section>` by default
- Rewriting source copy
- Pasting the raw designer file into `htmlContent`
- Adding a new CDN host to `htmlComponentExternalScriptHosts` without a human
- Scalar `set` of a single URL string onto `externalScripts`

## Related resources

- `cms-edit://customer/pages`
- `cms-edit://customer/task-create-from-document` (ordinary briefs, not HTML dumps)
- `cms-edit://customer/task-create-page`
- `cms-edit://customer/task-media-reuse-and-upload`
- `cms-edit://customer/task-media-pipeline-prepare`
- `cms-edit://customer/task-preview-verify`
- `cms-edit://customer/task-publish-handoff`
- `cms_edit ["help", "fields-html-component"]`
