# INT-001-01 — PublicMedia, resolve de mídia, CmsHtml

| Campo | Valor |
|---|---|
| **status** | feita |
| **spec** | [`docs/specs/integracao/001-ponte.md`](../specs/integracao/001-ponte.md) §§4–5, §7–8 |
| **depende de** | CMS feito (`ReplacementImage::publicUrl` já existe e tem testes) |
| **primo** | resolução de path na saída PHP; `BrandPresenter::ensureHtml` |

## Objetivo

Um helper de URL pública e um de HTML que presenters (`002+`) vão usar. O painel continua compilando. Nenhuma rota pública muda o que o React mostra.

## Fazer

1. `App\Support\PublicMedia`:
   - `url(mixed $path): ?string` — a mesma regra de `ReplacementImage::publicUrl` (hoje em `app/Filament/Support/ReplacementImage.php`).
   - `resolve(array $payload, array $keys): array` — percorre arrays associativos e listas; só converte as keys pedidas; valor vazio → `null`.
2. `ReplacementImage::publicUrl` **delega** para `PublicMedia::url` (não duplicar o `if`).
3. `App\Support\CmsHtml::ensureHtml(string $value): string` (vazio; HTML já com `<` intacto; texto puro vira `<p>` escapado).
4. Testes PHPUnit em `tests/Feature/Integracao/` (ou `tests/Unit/` se o arquivo não precisar de Laravel HTTP). Os testes CMS que já chamam `ReplacementImage::publicUrl` **continuam verdes**.

## Testes

- `url`: `/images/...` igual; `https://...` igual; `layout/x.webp` → `/storage/layout/x.webp`; `null` / `''` → `null`.
- `resolve`: em `[['image' => 'blog/a.webp', 'title' => 'Oi']]` com keys `['image']`, a imagem vira `/storage/blog/a.webp` e `title` permanece `Oi`.
- `ensureHtml`: texto puro escapado dentro de `<p>`; string que já tem `<h2>` não é envelopada.

## Fora

`HandleInertiaRequests`, types TS, controllers, `CmsHtml.tsx`, ligar qualquer feature.
