# `MishkaCmsCore.Runtime.PageBuilder.CardPreview`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/page_builder/card_preview.ex#L5)

What a palette card looks like before you drop it — the small document shown when the pointer rests
on a card.

## It previews the drop, not the component

The source is the card's own `data-template`: the exact HEEx a drag carries, presets and all. So a
card whose preset says "outline, small" previews outline and small, and what the preview shows is
what the page gets. Rendering some other example of the component would be a nicer picture and a
worse answer.

## Why this is cheap enough to fire on hover

Three measurements decided the design, against the real 171-component install:

  * the rendered fragment is **757–1,826 bytes** — a whole `<.component …/>` expanded. That is what
    travels over the socket per hover;
  * rendering it costs **~2 ms warm** and 0.5–1.4 s the first time a component is asked for, which
    is its module being compiled. So the first hover is asynchronous with a placeholder and every
    later one is instant — and `MishkaCmsCore.Runtime.States.CardPreviewState` means "later" covers
    the rest of the session;
  * the CSS costs **nothing**. The site's compiled bundle is served at `/assets/css/<site>/<hash>`
    as `immutable`, and the canvas frame has already downloaded it by the time anyone hovers a
    card. The preview links the same URL and the browser answers from cache. Inlining it instead
    would have put 194 KB on every hover.

Compare the honest alternative: `RuntimePreview.render_example/3` builds a standalone document with
its own generated CSS — 335 KB and ~1 s each, every time. That is right for a modal somebody opened
on purpose and wrong for something that follows the pointer.

## The admin's Content-Security-Policy reaches inside the frame

A `srcdoc` document inherits the embedding page's policy, and the admin's is enforced:
`style-src-elem 'self' 'nonce-<n>'`, with no `'unsafe-inline'`. Two consequences, both of which
fail SILENTLY — the preview simply draws unstyled, which reads as a broken component:

  * every `<style>` this module writes carries the page's nonce, read in the LiveView process and
    passed in;
  * the frame is `sandbox="allow-same-origin"`. Under a bare `sandbox=""` the document has an
    opaque origin, `'self'` matches nothing, and the linked bundle is refused. It is granted
    same-origin and NOT `allow-scripts`: with nothing able to run inside, the origin buys the frame
    no capability, and it is also what lets the panel read the drawn height and size itself to fit.

`RuntimePreview` cannot make the same trade — it runs Tailwind's browser build inside its frame,
and `allow-scripts` together with `allow-same-origin` defeats the sandbox entirely — which is why
that one inlines its CSS and this one links it.

## The cache holds the body, never the document

Only the rendered markup is remembered. The surrounding document — which carries the stylesheet's
content-hashed URL — is assembled on every read, at a cost of one string join.

That is what makes a recompile safe. A cached DOCUMENT would keep pointing at the previous bundle's
hash, and an immutable URL that no longer exists fails silently: the preview draws unstyled and
looks like a broken component rather than a stale cache. Holding the body alone means a new hash is
picked up the moment it exists, with nothing to invalidate.

The markup itself can still go stale when a component is recompiled, which is why the cache listens
for that and drops what changed.

## What it does not do

It does not render a component that needs data — one whose template reads `@id` or another required
attr raises, and the preview says so in the same words the Preview tab uses rather than showing a
blank box. That is a real answer: the component needs configuring before it will draw.

See `MishkaCmsCore.Runtime.RuntimePreview` for the heavyweight previews.

# `document`

```elixir
@spec document(String.t(), String.t() | nil, String.t() | nil) ::
  {:ok, String.t()} | {:error, String.t()}
```

The preview document for `template`, as a string ready for an iframe's `srcdoc`.

Returns `{:ok, html}`, or `{:error, message}` when the template will not render — the caller shows
the message, because "this needs configuring first" is worth saying.

# `width`

```elixir
@spec width() :: pos_integer()
```

The width the preview is laid out at, so the caller can scale the frame to fit.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
