---
id: UI-CMS
tipo: ui
onda: 1
status: concluido
unidades: [UI-CMS]
fontes:
  - V2/layouts/cms/
depende_de: [UI-CMS, SVC-ACTIVITYLOGGER, SVC-GALLERY, SVC-SEARCH]
consumidores: []
criticidade: alta
dono: UiCms
---

# UI CMS — catálogo de componentes Blade

> **Alvo de reimplementação.** Este documento descreve o **DOM/estado/comportamento** do
> legado (Bootstrap 5, jQuery, AdminLTE). A reconstrução usa Blade + Tailwind 4 +
> daisyUI 5 + Lucide, com o shell `Layout\Cms`/`Layout\Portal` e JS mínimo vendorizado.
> Preserve ids, eventos, aria, mensagens e contratos; troque classes CSS e bibliotecas.
> Ver [`09_reimplementacao/pilha-alvo-public-v7.md`](../09_reimplementacao/pilha-alvo-public-v7.md).

> Guia: contrato de cada partial/componente do namespace `v2-cms`. O chrome e as telas
> estáticas estão em `01-cms-design-system.md`; aqui cada componente tem props, slots,
> DOM, eventos, consumidores, quando usar e o que quebra sem cada variável.

## 0. Ficha de identidade

| Campo | Valor |
|---|---|
| Efeito primário | Fornece 23 blades reutilizáveis (tabela, filtros, paginação, badges, modais, editor de código, botões, timeline) que padronizam listagens e formulários do CMS e injetam CSS/JS nos stacks do layout. |
| Entrada | `@include('v2-cms::partials.x', [...])`, `@component('v2-cms::partials.x', [...]) … @endcomponent`, `@component('v2-cms::components.box')`; arrays de colunas/linhas/itens. |
| Saída | HTML Bootstrap 5 + pushes em `css`/`scripts`/`js`/`modals`. |
| Efeitos colaterais | Query a `v2_conexao_tags` no render de `conexao-tags-modal`; query a `ActivityLoggerService::getHistory()` em `activity-footer`; consumo de `session('created_item')`; `localStorage`/`fetch` no modal de tags; `CustomEvent('sle:editor-change')` do editor. |
| Particularidade central | Convivem três famílias: **markup puro** (table, breadcrumb, pagination, badges), **componente com slot** (`@component`, exige slot) e **componente com push de JS** (filter-card, code-editor, seo-button, conexao-tags). Misturar `@include` onde o partial espera `$slot` gera erro 500. |
| Como se integra | Namespace `v2-cms` (`V2/Core/Providers/V2ServiceProvider.php:39`); nenhum componente é registrado como *anonymous component* (`<x-…>` não existe aqui). |
| Se removido, quebra | 14 listagens (filter-card/pagination), 14 usos de box, 10 de action-create, 18 de activity-footer, editor de páginas/landing (code-editor), telas de Conexões (modal de tags). |
| Superfície pública | `v2-cms::partials.{table,filter-card,pagination,breadcrumb,status-badge,badge,action-create,table-actions,empty-state,alert,button,card,modal,form-group,code-editor,activity-footer,creation-success-modal,_confirm-modal,conexao-tags-modal}`, `v2-cms::components.{box,seo-button,buttons.back,buttons.save}`. |

## 1. Efeito

### 1.1 O que a inclusão de cada família faz

| Família | Diretiva correta | O que acontece | Se usar a diretiva errada |
|---|---|---|---|
| Markup puro (`table`, `breadcrumb`, `pagination`, `status-badge`, `table-actions`) | `@include` | renderiza HTML com os arrays/variáveis do escopo | `@component` sem slot funciona, mas `$slot` não é usado |
| Componente com slot (`alert`, `badge`, `button`, `card`, `modal`, `box`, `filter-card`, `form-group`) | `@component … @endcomponent` | `$slot` recebe o HTML interno | com `@include`, `$slot` fica indefinido → `Undefined variable: slot` (erro 500) |
| Componente com push (`filter-card`, `code-editor`, `seo-button`, `conexao-tags-modal`, `creation-success-modal`) | `@include`/`@component` conforme o caso | além do markup, empurra CSS/JS para os stacks do layout | se o layout não emitir o stack, o comportamento não inicializa (ver §3.4) |

`@component` no Laravel 5.5 entrega `$slot` como `Illuminate\Support\HtmlString`
(`vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesComponents.php:73-79`).
Isso tem duas consequências diretas: (a) `@include` nunca define `$slot`; (b) comparações
como `$slot !== ''` são **sempre verdadeiras**, porque o objeto nunca é a string vazia —
é a razão de `filter-card` sempre exibir o botão "Mais filtros" (§3.3).

### 1.2 Efeito por consumidor

| Consumidor | Componentes usados | Efeito observável |
|---|---|---|
| Listagens de módulo (14 telas) | `filter-card` + `pagination` + `breadcrumb` + `action-create` + `status-badge` | busca/filtros em uma linha, tabela no corpo, paginação `sm` centralizada |
| Telas de edição (7 telas) | `code-editor` + `activity-footer` + `breadcrumb` | textarea sincronizada com CodeMirror; timeline de auditoria no rodapé |
| Conexões (Associados, Parceiros, UnidadeRegional) | `conexao-tags-modal` (+ `status-badge`) | modal de gestão de tags com criação/exclusão via `fetch` |
| Dashboard e busca | `components.box` | card de seção; no dashboard, o único ponto que usa `box` |
| Layout global | `creation-success-modal` | modal automático após `session('created_item')` |

### 1.3 O que os componentes NÃO fazem

- Nenhum componente faz validação client-side, máscara ou submissão: formulários são
  HTML puro; `masks.js` atua por `data-mask` no input, não pelo partial `form-group`.
- `table` não pagina, não ordena e não formata células: só imprime
  `data_get($row, $column['key'])`.
- `pagination` não calcula páginas: depende de `$paginator` e de `$elements` injetados por
  `LengthAwarePaginator::links()`.
- `table-actions` não executa exclusão: emite botão para `#confirmModal`, cujo handler
  **não** faz parte do componente.
- Nenhum componente emite `aria-live` de erro de formulário; erros vêm do `$errors` da
  página.

## 2. Abordagem

### 2.1 Modelo de composição

```mermaid
flowchart TD
  page["View de módulo"] --> fc["partials.filter-card (push js)"]
  page --> tbl["tabela manual ou partials.table"]
  page --> pg["partials.pagination (via links())"]
  page --> act["partials.action-create / table-actions"]
  page --> sd["partials.status-badge / badge"]
  page --> ce["partials.code-editor (push css+scripts)"]
  page --> af["partials.activity-footer → v2-service-activitylogger::cms.timeline"]
  page --> box["components.box (slot + tools + footer)"]
  page --> seo["components.seo-button (push modals+js)"]
  page --> tags["partials.conexao-tags-modal (push scripts, query no render)"]
  page --> cm["partials._confirm-modal (sem JS)"]
  app["layouts.app"] --> csm["partials.creation-success-modal"]
  app --> stacks["@stack('css'|'scripts'|'js'|'modals')"]
  fc --> stacks
  ce --> stacks
  seo --> stacks
  tags --> stacks
```

### 2.2 Convenções de props

| Convenção | Componentes | Regra |
|---|---|---|
| `$x = $x ?? default` para tudo | `table`, `modal`, `alert`, `badge`, `button`, `card`, `form-group`, `code-editor`, `filter-card`, `activity-footer`, `creation-success-modal` | variável ausente não quebra |
| `$x` **sem** default | `alert.$title`, `button.$id`, `status-badge.$active`, `table-actions.$editRoute`, `empty-state.$icon/$title/$message`, `action-create.$route`, `seo-button.$model` | ausente → `Undefined variable` (erro 500) |
| `$slot` obrigatório | `alert`, `filter-card`, `box`, `card`, `modal` | precisa de `@component … @endcomponent` |
| `$slot` opcional | `badge` (`$label ?? $slot ?? ''`), `button` (`{{ $slot ?? '' }}`), `form-group` (só para `type=select`) | pode ser `@include` |
| Prefixo de ícone **sem** `bi-` | `alert`, `badge`, `button`, `card` | renderizam `bi bi-{{ $icon }}` |
| Classe de ícone **completa** | `action-create` (`bi-plus-circle` default), `empty-state` (`bi-newspaper` no docblock) | renderizam `{{ $icon }}` cru |
| Sem ícone configurável | `status-badge`, `table-actions`, `breadcrumb`, `pagination`, `table` | ícones fixos |

### 2.3 Trechos de código pivô

```blade
{{-- V2/layouts/cms/partials/table.blade.php:2-20 — contrato de colunas: label, key,
     class/tdClass, default e sortable (decorativo). --}}
@php
    $id = $id ?? 'table-' . uniqid();
    $columns = $columns ?? [];
    $rows = $rows ?? [];
    $emptyMessage = $emptyMessage ?? 'Nenhum registro encontrado.';
    $striped = $striped ?? true;
    $hover = $hover ?? true;
@endphp
<table class="table @if($striped)table-striped @endif @if($hover)table-hover @endif mb-0"
       id="{{ $id }}" aria-label="{{ $ariaLabel ?? 'Tabela de registros' }}">
    @foreach($columns as $column)
        <th scope="col" class="{{ $column['class'] ?? '' }}">
            @if(isset($column['sortable']) && $column['sortable'])
                <a href="#" onclick="event.preventDefault()" role="button"
                   aria-label="Ordenar por {{ $column['label'] }}">
```

`data_get($row, $column['key'], $column['default'] ?? '-')` (`:35`) aceita arrays e
objetos; `key` ausente imprime `-`. `sortable` **não ordena** — apenas troca o rótulo por
um link inerte; nunca use como contrato de ordenação.

```blade
{{-- V2/layouts/cms/partials/filter-card.blade.php:44-56 — "tem filtros avançados" é
     sempre verdadeiro sob @component, pois $slot é HtmlString, não string. --}}
$hasAdvancedSlots = $slot !== '';
$hasAdvanced = $showDateRange || $hasAdvancedSlots;

$hasActiveFilters = !empty($searchValue) || !empty($dateFrom) || !empty($dateTo);
$showExpanded = $expandByDefault || $hasActiveFilters;

$collapseId = 'filter-advanced-' . md5($action . $searchName);
...
<form method="{{ $method }}" action="{{ $action }}" class="row g-2 align-items-end" role="search">
```

Duas invariantes: o id do bloco avançado é derivado de `md5($action . $searchName)`
(dois filter-cards com mesma action e mesmo nome de busca geram **ids duplicados**), e o
`<form>` não tem `@csrf` — só `GET` funciona.

```blade
{{-- V2/layouts/cms/partials/table-actions.blade.php:31-41 — o botão Excluir aponta para
     um modal global por id e carrega a URL em data-action; não há handler aqui. --}}
@isset($deleteRoute)
    <button type="button" class="btn btn-{{ $size }} btn-outline-danger d-inline-flex align-items-center gap-1"
            title="Excluir"
            data-bs-toggle="modal"
            data-bs-target="#confirmModal"
            data-action="{{ $deleteRoute }}"
            data-label="{{ $deleteLabel ?? 'este registro' }}">
        <i class="bi bi-trash"></i>
        <span class="d-none d-md-inline">Excluir</span>
    </button>
@endisset
```

O contrato emitido é `#confirmModal` + `data-action` + `data-label`. O handler de
referência do repositório (`Noticias/Views/cms/includes/scripts.blade.php:166-175`)
escuta `[data-confirm-modal]` e lê `data-confirm-*`/`data-confirm-form-id` — atributos
**que este partial não gera**. Portanto `table-actions` + handler de Notícias não fecham
o ciclo (ver `LG-UI-CMS-03`).

```blade
{{-- V2/layouts/cms/partials/code-editor.blade.php:59-88 — CodeMirror sobre textarea
     escondida; cada mudança copia para o campo real e emite um CustomEvent. --}}
$(function() {
    var editorElement = document.getElementById('{{ $id }}-editor');
    var textareaElement = document.getElementById('{{ $id }}-source');
    if (!editorElement || !textareaElement) { return; }

    var editor = CodeMirror(editorElement, {
        value: textareaElement.value,
        mode: '{{ $cmMode }}',
        theme: 'material',
        lineNumbers: true,
        indentUnit: 4,
        readOnly: {{ $disabled ? 'true' : 'false' }}
    });

    editor.on('change', function() {
        textareaElement.value = editor.getValue();
    });

    editorElement.dispatchEvent(new CustomEvent('sle:editor-change', {
        detail: { editor: editor }
    }));
```

O `name` do formulário vive na `<textarea class="d-none" id="{id}-source">`
(`:111,115`); o CodeMirror é apenas visual. Integrações observam
`sle:editor-change` no `#{id}-editor` — o evento é disparado **uma vez**, na
inicialização, não a cada tecla (`:85-87` dentro do `$(function(){})`).

```blade
{{-- V2/layouts/cms/partials/activity-footer.blade.php:1-15 — busca o histórico se não
     vier pronto e engole qualquer exceção; sem histórico, nada é renderizado. --}}
@php
    if (!isset($history) && isset($model)) {
        try {
            $history = app(\V2\Services\ActivityLogger\ActivityLoggerService::class)->getHistory($model);
        } catch (\Exception $e) {
            $history = collect();
        }
    } else {
        $history = $history ?? collect();
    }
    $model = $model ?? null;
@endphp
@if($history->isNotEmpty())
    @include('v2-service-activitylogger::cms.timeline', ['model' => $model, 'history' => $history, 'title' => 'Últimas Alterações'])
@endif
```

Falha silenciosa por design: sem serviço, sem model ou sem histórico, o rodapé
simplesmente não aparece. Quem consome não recebe erro nem aviso.

```blade
{{-- V2/layouts/cms/partials/creation-success-modal.blade.php:1-16 — modal automático
     com backdrop estático e sem botão de fechar; só "Voltar para Listagem". --}}
@if(session()->has('created_item'))
    @php $item = session('created_item'); @endphp
<div class="modal fade" id="creationSuccessModal" tabindex="-1" role="dialog"
     data-bs-backdrop="static" data-bs-keyboard="false" aria-hidden="true">
    <div class="modal-dialog modal-dialog-centered modal-sm">
        <div class="modal-content border-0 shadow-lg text-center p-4">
            <div class="modal-body py-4">
                <i class="bi bi-check-circle-fill text-success" style="font-size: 4rem;"></i>
                <h4 class="mt-3 fw-bold">{{ $item['type'] ?? 'Item' }} criado com sucesso!</h4>
                <p class="fw-semibold text-muted mb-3">{{ $item['title'] }}</p>
                <a href="{{ $item['route'] }}" class="btn btn-primary px-4 d-inline-flex align-items-center gap-2">
```

O produtor do side-channel é o controller, via `session()->flash('created_item',
['type','title','route'])` (ex.: `V2/Modules/Conexoes/Associados/Controllers/InstituicaoCmsController.php:174-178`).
`title` e `route` são obrigatórios: array sem essas chaves → `Undefined index` no modal.

## 3. Particularidades

### 3.1 Catálogo detalhado — listagem e navegação

#### `partials.filter-card`

| Aspecto | Contrato |
|---|---|
| Props | `searchPlaceholder` (default `Buscar por termo... (suporta regex)`), `searchValue` (default `request('search')`), `searchName` (default `search`), `action` (default `url()->current()`), `method` (default `GET`), `showDateRange` (bool), `dateFrom`/`dateTo` (default `request(...)`), `expandByDefault` (bool) |
| Slot | campos avançados (colunas Bootstrap dentro de `.row.g-2`), renderizados dentro do collapse |
| DOM | `form[role=search]`, `input#{searchName}`, `#filter-advanced-<md5>`, `input[name=date_from]`, `input[name=date_to]`, `.filter-toggle-label`, `.filter-toggle-icon` |
| Eventos | `shown.bs.collapse` no alvo → inicializa flatpickr nos `[data-picker="date"]` ainda não inicializados; `click` no toggle troca rótulo/ícone |
| Dados | `data-bs-toggle="collapse"`, `data-bs-target="#filter-advanced-…"`, `data-picker="date"` |
| Consumidores | 14 telas de listagem (Associados, Parceiros, UnidadeRegional, CertificadoDigital, GestaoPermissoes ×2, MailAudit, AssocieSe leads, FaleConosco, Historia, Agenda, Noticias, TrabalheConosco ×2) |
| Quando usar | toda listagem com busca e filtros; sempre com `@component` |
| Se faltar | sem `$slot` → `Undefined variable: slot`; sem `$action` usa a URL atual (perde filtros ao submeter de outra rota); `method=POST` sem `@csrf` → 419 |
| Fonte | `V2/layouts/cms/partials/filter-card.blade.php:24-53,56-132,134-175` |

```blade
{{-- uso canônico: 1 busca + 2 filtros, com slot de campos --}}
@component('v2-cms::partials.filter-card', [
    'searchPlaceholder' => 'Buscar por título, subtítulo ou resumo...',
    'searchValue'       => $searchTerm,
    'action'            => route('v2.noticias.cms.index'),
    'showDateRange'     => false,
])
    <div class="col-md-3">
        <label for="status" class="form-label small fw-medium text-muted mb-1">Status</label>
        <select name="status" class="form-select form-select-sm" aria-label="Filtrar por status">…</select>
    </div>
@endcomponent
```

#### `partials.table`

| Aspecto | Contrato |
|---|---|
| Props | `columns[]` (`label`, `key`, `class`, `tdClass`, `default`, `sortable`), `rows[]`, `id` (default `table-<uniqid>`), `ariaLabel` (default `Tabela de registros`), `emptyMessage` (default `Nenhum registro encontrado.`), `striped`/`hover` (default `true`) |
| Slot | nenhum (`@include`) |
| DOM | `div.table-responsive > table#id[aria-label]`; linha vazia com `colspan=count(columns)` e `<i class="bi bi-inbox">` |
| Consumidores | **nenhum** no código atual `[C]` (busca por `v2-cms::partials.table` só encontra o docblock de `table-actions`) |
| Quando usar | listagens homogêneas em que não é preciso célula customizada |
| Se faltar | não quebra: defaults cobrem tudo; `columns: []` gera `<thead>` vazio e `colspan="0"` |
| Fonte | `V2/layouts/cms/partials/table.blade.php:1-49` |

#### `partials.pagination`

| Aspecto | Contrato |
|---|---|
| Props | `$paginator` (LengthAwarePaginator) **e** `$elements` — ambos injetados por `$paginator->links('v2-cms::partials.pagination')` |
| Slot | nenhum |
| DOM | `nav[aria-label="Navegação de páginas"] > ul.pagination.pagination-sm`, `aria-current="page"` no item atual, `rel=prev/next`, `visually-hidden` "Anterior"/"Próxima"/"Pular páginas" |
| Consumidores | 13 telas, sempre via `->links(...)` |
| Quando usar | qualquer listagem paginada; `appends(request()->except('page'))` para preservar filtros |
| Se faltar | `@include` direto sem `$elements` → `Undefined variable: elements`; sem `hasPages()` a saída é vazia (páginas de 1 página não mostram nada) |
| Fonte | `V2/layouts/cms/partials/pagination.blade.php:1-53`; chamada real `Noticias/Views/cms/index.blade.php:242` |

#### `partials.breadcrumb`

| Aspecto | Contrato |
|---|---|
| Props | `items[]` com `label` e, opcionalmente, `route` (nome de rota **sem parâmetros**) |
| Slot | nenhum |
| DOM | `nav[aria-label="Breadcrumb"] > ol.breadcrumb`; último item `active` + `aria-current="page"`; microdata `BreadcrumbList`/`ListItem` + `meta[itemprop=position]` |
| Consumidores | 24 views |
| Quando usar | em `@section('page-breadcrumbs')` do layout |
| Se faltar | `items` default `[]` → breadcrumb vazio (nav sem itens); item sem `label` → `Undefined index`; rota com parâmetro obrigatório → `UrlGenerationException` |
| Fonte | `V2/layouts/cms/partials/breadcrumb.blade.php:1-21` |

#### `partials.status-badge` e `partials.badge`

| Aspecto | `status-badge` | `badge` |
|---|---|---|
| Props | `active` (**obrigatório**, bool), `activeLabel` (default `Ativo`), `inactiveLabel` (default `Inativo`), `showDot` (default `true`) | `variant` (default `secondary`), `label`, `pill`, `icon` (sem prefixo `bi-`) |
| Efeito visual | pill verde `bg-success` / cinza `bg-secondary`, bolinha de 6px | badge `bg-<variant>`, `rounded-pill` se `pill`, ícone opcional |
| Slot | não | fallback de `label` |
| Consumidores | 3 views (Associados, UnidadeRegional, SobreNos membros/board) | **nenhum** |
| Se faltar | `$active` ausente → erro 500 | não quebra (defaults) |
| Fonte | `partials/status-badge.blade.php:1-22`, `partials/badge.blade.php:1-10` |
| A11y | estado só por cor + texto; a bolinha é `<span>` sem `aria-hidden` | — |

#### `partials.action-create` e `partials.table-actions`

| Aspecto | `action-create` | `table-actions` |
|---|---|---|
| Props | `route` (**obrigatório**), `label` (default `Novo`), `icon` (default `bi-plus-circle`, classe completa) | `editRoute` (**obrigatório**), `deleteRoute`, `deleteLabel` (default `este registro`), `deleteId` (declarado no docblock, **não usado**), `extraActions` (HTML cru), `size` (default `sm`) |
| DOM | `a.btn.btn-primary.btn-sm` | `div.d-flex.justify-content-end` com `a.btn-outline-primary` e `button.btn-outline-danger[data-bs-target="#confirmModal"]` |
| Consumidores | 10 views | **0** (só o exemplo no docblock) |
| Quando usar | botão "Novo X" em `page-actions` | ações de linha, **somente** se a página tiver o modal e o handler |
| Se faltar | sem `route` → `Undefined variable: route` | sem `editRoute` → erro 500; `deleteId` não tem efeito (o handler de Notícias usa form id) |
| Fonte | `partials/action-create.blade.php:1-15`; `partials/table-actions.blade.php:1-42` |

#### `partials.empty-state`

| Aspecto | Contrato |
|---|---|
| Props | `icon` (**obrigatório**, classe completa ex. `bi-newspaper`), `title`, `message`, `actionRoute`+`actionLabel` (ambos para exibir botão), `hasFilters` (bool), `clearUrl` (default `url()->current()`) |
| DOM | `div.text-center.py-5` com `i.display-3`, `h2.h5`, `p`, e um CTA |
| Consumidores | **nenhum** `[C]` (o teste `CmsComponentUsageTest::empty_state_component_is_used` só verifica existência do arquivo) |
| Quando usar | listagem vazia; com `hasFilters=true` mostra "Limpar Filtros" e esconde o CTA de criação |
| Se faltar | `$icon`/`$title`/`$message` ausentes → erro 500 |
| Fonte | `partials/empty-state.blade.php:1-29` |

### 3.2 Catálogo detalhado — formulário e conteúdo

#### `partials.form-group`

| Aspecto | Contrato |
|---|---|
| Props | `id` (default `field-<uniqid>`), `label`, `name`, `type` (`text`/`textarea`/`select`), `value` (default `old($name, $default ?? '')`), `required`, `disabled`, `help`, `error`, `placeholder`, `rows` (textarea, default 3) |
| Slot | `<option>`s quando `type=select` |
| DOM | `label[for=id]`, `.form-control`/`.form-select`, `.is-invalid` quando `error`, `.form-text#id-help`, `.invalid-feedback` |
| A11y | associa `aria-describedby` **somente** quando há `help`; asterisco `*` com `aria-hidden`; `required`/`disabled` nativos |
| Consumidores | **nenhum** `[C]` |
| Quando usar | formulário simples de um campo; para layouts complexos os módulos escrevem HTML direto |
| Se faltar | não quebra; `name=''` gera campo sem submit; `error` não repete a mensagem do `$errors` |
| Fonte | `partials/form-group.blade.php:1-52` |

#### `partials.alert`

| Aspecto | Contrato |
|---|---|
| Props | `type` (default `info`; `success|danger|warning|info`), `dismissible` (default `true`), `icon` (nome sem `bi-`, com default por tipo), `title` (**obrigatório**) |
| Slot | corpo da mensagem |
| DOM | `div.alert.alert-<type>[.alert-dismissible.fade.show]`, `role="alert"`, `.btn-close[data-bs-dismiss=alert]` |
| Consumidores | 1 view (`Noticias/Views/cms/includes/_editor-content.blade.php:32`), **via `@include`** |
| Quando usar | mensagem inline persistente (não substitui `flash::message`) |
| Se faltar | `$title` ausente → `Undefined variable: title`; `$slot` ausente por `@include` → `Undefined variable: slot` |
| Fonte | `partials/alert.blade.php:1-19` |

O único consumidor real usa a diretiva errada: o `@include` em
`_editor-content.blade.php:31-33` roda quando existe qualquer flash na sessão e não
recebe `title` nem `slot` (`create.blade.php` define `$pageTitle`, não `$title`). Em
Laravel 5.5, variável indefinida vira `ErrorException` → 500. `[I]` (inferido do escopo do
`@include` + `handler` de erros do framework).

#### `partials.card` e `components.box`

| Aspecto | `card` | `box` |
|---|---|---|
| Props | `title`, `icon` (sem `bi-`), `class`, `headerClass`, `padding` (default `p-3`) | `title`, `id`, `class`, `bodyClass` |
| Slots | `$slot` (body), `$header`, `$footer` | `$slot` (body), `$tools`, `$footer` |
| DOM | `div.card.shadow-sm.border-0`, `header.card-header`, `footer.card-footer` | `section.cms-grid-item#box-<id>`, `header.cms-card-header > h3#box-<id>-title`, `div.cms-card-body`, `footer.cms-card-footer` |
| Conversão de classes | — | `box-primary|success|warning|danger|info|default` → `border-* border-top border-4`; `box ` removido (`components/box.blade.php:5-10`) |
| Consumidores | **nenhum** | 14 views (dashboard, busca e abas de Support/SLE/BasicPage) |
| Quando usar | card genérico | seção do dashboard/landing com título, ferramentas e rodapé; aceita classes legadas do AdminLTE |
| Se faltar | não quebra | não quebra (`title` opcional); com `title`, o `aria-labelledby` aponta para o `h3` gerado |
| Fonte | `partials/card.blade.php:1-23`, `components/box.blade.php:1-32` |

#### `partials.modal`

| Aspecto | Contrato |
|---|---|
| Props | `id` (default `modal-<uniqid>`), `title` (default `Modal`), `size` (`sm|lg|xl`), `backdrop` (default `true`), `keyboard` (default `true`) |
| Slot | corpo; `$footer` opcional |
| DOM | `div.modal.fade#id[aria-labelledby=id-label][data-bs-backdrop][data-bs-keyboard]`, `h5#id-label`, `.modal-body`, `.modal-footer` |
| Consumidores | **nenhum** `[C]` |
| Quando usar | modal declarativo simples; para confirmação use `_confirm-modal` |
| Se faltar | como usa `@if(isset($footer))`, não quebra; `@include` sem slot gera body vazio |
| Fonte | `partials/modal.blade.php:1-27` |

#### `partials.code-editor`

| Aspecto | Contrato |
|---|---|
| Props | `id` (default `cm-editor-<uniqid>`), `name`, `value`, `mode` (`htmlmixed|css|javascript|php`, default `htmlmixed`), `height` (default `250px`), `label`, `placeholder` (**declarado, não usado**), `disabled` |
| Slot | nenhum |
| DOM | `textarea[name][id=id-source].d-none` + `div#id-editor`; CodeMirror cria `.CodeMirror` dentro do div |
| CSS/JS | `@push('css')` carrega `codemirror.css` + tema `material` e um `<style>` com `#id-editor .CodeMirror`; `@push('scripts')` carrega `codemirror.js`, modos `xml/javascript/css/htmlmixed/clike/php` e inicializa com jQuery |
| Eventos | `CustomEvent('sle:editor-change', {detail:{editor}})` no `#id-editor`, disparado na inicialização; `ResizeObserver` → `syncHeight()` |
| Consumidores | 7 views: BasicPage `create`/`editor`, SLE `css-head`, `footer`, `header`, `js-foot`, `main` (10 chamadas) |
| Quando usar | editar HTML/CSS/JS/PHP de páginas e landing sections |
| Se faltar | sem jQuery → `$ is not defined`; sem `@stack('scripts')` → textarea fica oculta (`d-none`) e o conteúdo não é editável; sem `name` → nada é enviado |
| Fonte | `partials/code-editor.blade.php:1-117` (push css `:20-49`, push scripts `:51-106`) |

### 3.3 Catálogo detalhado — modais e widgets de ação

#### `partials._confirm-modal`

| Aspecto | Contrato |
|---|---|
| Props | nenhuma (modal singleton `#confirmModal`) |
| Slot | nenhum |
| DOM | `#confirmModal`, `#confirmModalLabel`, `#confirmModalIcon`, `#confirmModalTitle`, `#confirmModalBody`, `#confirmModalHint`, `#confirmModalConfirmBtn`, `#confirmModalBtnIcon`, `#confirmModalBtnText`; botões "Cancelar" (`btn-outline-secondary`) e confirmar (`btn-secondary`) |
| JS | **nenhum embutido** |
| Consumidores | 3 views (Parceiros, Historia, TrabalheConosco openings) |
| Quando usar | quando a página já tem um handler `[data-confirm-modal]` |
| Se faltar | incluir sem handler deixa o modal inerte (botão abre, confirmar não faz nada); incluir duas vezes (ou junto da cópia de Notícias) duplica ids |
| Fonte | `partials/_confirm-modal.blade.php:1-32` |

Handler de referência (módulo Notícias) — único no repositório:

```js
// V2/Modules/Publicacoes/Noticias/Views/cms/includes/scripts.blade.php:166-175
document.addEventListener('click', function(e) {
    var btn = e.target.closest('[data-confirm-modal]');
    if (!btn) return; e.preventDefault();
    var title = btn.getAttribute('data-confirm-title') || 'Confirmar';
    var body  = btn.getAttribute('data-confirm-body') || 'Confirmar esta ação?';
    // ...
    pendingConfirmFormId = btn.getAttribute('data-confirm-form-id') || 'delete-news-form';
```

Observe o `return` seguido de `e.preventDefault()` na mesma linha: o `preventDefault`
nunca executa quando o botão é encontrado (é inalcançável após `return`). `[C]`

#### `partials.creation-success-modal`

| Aspecto | Contrato |
|---|---|
| Props | nenhuma; lê `session('created_item')` (`type`, `title`, `route`) |
| Slot | nenhum |
| DOM | `#creationSuccessModal` (`data-bs-backdrop="static" data-bs-keyboard="false"`), texto `{type} criado com sucesso!`, link "Voltar para Listagem" |
| JS | `@push('scripts')` inicializa `bootstrap.Modal(..., {backdrop:'static', keyboard:false}).show()` no `DOMContentLoaded` |
| Consumidores | `layouts/app.blade.php:138` (todas as páginas do CMS) |
| Quando usar | apenas o controller, via `session()->flash('created_item', [...])` |
| Se faltar | sem `title`/`route` → `Undefined index`; sem `@stack('scripts')` o modal não abre |
| A11y | modal sem botão de fechar, `Esc` desabilitado; saída única pelo CTA — armadilha de teclado |
| Fonte | `partials/creation-success-modal.blade.php:1-29` |

#### `partials.conexao-tags-modal`

| Aspecto | Contrato |
|---|---|
| Props | nenhuma; o partial **consulta o banco** (`ConexaoTag::withCount(['parceiros','unidadesRegionais'])`, ordenado por `is_reserved desc, name`) e ignora `$tags` do controller |
| Slot | nenhum |
| DOM | `#conexaoTagModal`, `#new-tag-name` (maxlength 100), `#btn-create-tag`, `#tag-create-error`, `#tag-list-container`, `#tag-empty-state`, `.tag-item[data-tag-id]`, `.tag-name`, `.btn-delete-tag[data-tag-id][data-tag-name]` |
| Rotas | `v2.cms.conexao-tags.store` (POST JSON `{name}`) e `url('cms/conexao-tags')/{id}` (DELETE) — CSRF via `<meta name="csrf-token">` |
| Eventos/hooks | `Enter` no input cria tag; `confirm()` nativo antes de excluir; chama `window.refreshAvailableTags()` se existir |
| Consumidores | 4 views (Associados ×2, Parceiros, UnidadeRegional) + controller `ConexaoTagCmsController@index` (render do partial como resposta de fragmento) |
| Quando usar | telas de Conexões que precisam gerenciar tags |
| Se faltar | sem `v2_conexao_tags` → erro na query; sem rotas → `RouteNotFoundException` no `route()`; incluído 2× na mesma página (Associados) → `#conexaoTagModal` duplicado e listeners repetidos |
| Fonte | `partials/conexao-tags-modal.blade.php:1-68,70-203` |

#### `components.seo-button` e `components.buttons.{save,back}`

| Aspecto | `seo-button` | `buttons.save` | `buttons.back` |
|---|---|---|---|
| Props | `model` (**obrigatório**; lê `$model->metadata['seo']`), `id` (sufixo do modal) | `label` (default `Salvar`), `id` (default `btn-submit`), `class` | `label` (default `Voltar`), `url` (default `URL::previous()`), `class` |
| Slot | não | não | não |
| DOM | `button[data-bs-target=#modalSeo<sufixo>]`, modal `#modalSeo…` com campos `metadata[seo][title|description|keywords|json_ld]` | `button#btn-submit[type=submit].btn-primary` | `a.btn-outline-secondary` |
| Push | `modals` (markup) + `js` (move o modal para dentro do **primeiro** `<form>` da página no submit) | — | — |
| Consumidores | **0** | **0** | **0** |
| Quando usar | telas de edição que já têm form e enviam `metadata[seo][...]` | rodapé de formulário | voltar sem perder o histórico |
| Se faltar | sem `model` → `$seoData` vazio (não quebra), mas o `<form>` é obrigatório para o modal ser salvo; sem form, o JSON-LD nunca é enviado | não quebra | não quebra |
| Fonte | `components/seo-button.blade.php:1-96`; `components/buttons/save.blade.php:1-4`; `components/buttons/back.blade.php:1-4` |

`seo-button` só anexa o modal ao form no evento `submit`
(`components/seo-button.blade.php:82-92`), e `document.querySelector('form')` pega o
**primeiro** form do documento: em páginas com mais de um formulário, os campos de SEO
podem ir para o formulário errado.

### 3.4 Contrato de stacks (o que cada push exige)

| Componente | Stack | Precisa de |
|---|---|---|
| `filter-card` | `js` | Bootstrap Collapse + `flatpickr` global (`window.flatpickr`) |
| `code-editor` | `css`, `scripts` | jQuery (`$(function(){})`), `@stack('scripts')` no layout |
| `seo-button` | `modals`, `js` | `@stack('modals')` **depois** de `@stack('js')` (ordem do layout) e Bootstrap Modal |
| `conexao-tags-modal` | `scripts` | `fetch`, meta CSRF, rotas de tags |
| `creation-success-modal` | `scripts` | Bootstrap Modal |
| `sidebar` | `js` | `localStorage`, `MutationObserver`, Bootstrap Collapse |

Se um layout alternativo não emitir esses stacks, o markup aparece mas nada inicializa
(sintoma típico: CodeMirror não aparece, "Mais filtros" não abre, modal de tags não
carrega a lista dinâmica).

### 3.5 Armadilhas

1. **`@include` em componente com slot** — `$slot` indefinido: `filter-card`, `alert`,
   `box`, `card`, `modal`. Caso real: `alert` (ver §3.2).
2. **`$slot !== ''`** — em `filter-card` é sempre verdadeiro; o toggle "Mais filtros"
   aparece mesmo sem campos avançados e o bloco mostra um `<hr>` órfão.
3. **IDs derivados** — `filter-advanced-<md5(action+searchName)>`, `table-<uniqid>`,
   `box-<id>`, `modalSeo<sufixo>`, `cm-editor-<uniqid>`, `field-<uniqid>`: dois
   componentes iguais na mesma página sem `id` explícito geram markup válido, mas os ids
   de `filter-card` **se repetem** quando action e nome de busca coincidem.
4. **`uniqid()` no servidor** — usado em `table`, `modal`, `form-group`, `code-editor`;
   não é determinístico e quebra snapshots/testes de HTML.
5. **HTML cru sem escape** — `table-actions.$extraActions` (`:25`), `box.$tools`,
   `card.$header`, `modal.$footer`, `badge.$label` (escapado), `alert.$slot` (escapado).
   `{!! !!}` aparece em `extraActions`; não passe entrada de usuário.
6. **`status-badge` sem default de `$active`** — é o único badge que falha por omissão.
7. **`empty-state` nunca usado e `table-actions` sem consumidor** — componentes
   "aspiracionais": existem no namespace, testes só checam existência de arquivo
   (`tests/Unit/V2/Layouts/Cms/CmsComponentUsageTest.php:10-60`).
8. **`partials.badge` e `partials.status-badge` coexistem** com semânticas diferentes
   (genérico vs ativo/inativo). Não unificar sem verificar os 3 consumidores de
   `status-badge`.
9. **`activity-footer` engole exceções** — não use como fonte de verdade de auditoria;
   ele esconde falhas do `ActivityLoggerService`.
10. **`conexao-tags-modal` faz query no render e ignora `$tags`** — chamar o controller
    `@index` (que passa `compact('tags')`) não evita a query; em telas com muitos
    vínculos isso é 1 query extra por render.

### 3.6 Divergências e dívidas

| Divergência | Evidência | Impacto |
|---|---|---|
| Docblock de `seo-button` diz "modal Bootstrap 3" | `components/seo-button.blade.php:3` | o markup é BS5 (`data-bs-*`) |
| `seo-button` não faz parte do fluxo de `metadata` de nenhum módulo | busca por `v2-cms::components.seo-button` retorna 0 | componente órfão |
| `table-actions` declara `deleteId` mas não o emite | `partials/table-actions.blade.php:17,31-40` | impossível identificar o item no handler |
| `partials.table` docblock inexistente; contrato só no código | `partials/table.blade.php` | risco de uso incorreto de `sortable` |
| `alert` consumido por `@include` | `Noticias/.../_editor-content.blade.php:32` | 500 quando há flash na sessão |
| `_confirm-modal` sem handler global | `partials/_confirm-modal.blade.php` | botões de 3 módulos dependem de JS local inexistente |

## 4. Integrabilidade

### 4.1 Contrato de uso (resumo por componente)

| Item | Obrigatório | Tipo | Efeito |
|---|---|---|---|
| `@component('v2-cms::partials.filter-card', [...])` | sim p/ filtros | componente | form + toggle + flatpickr |
| `->links('v2-cms::partials.pagination')` | sim p/ paginação | chamada | nav acessível |
| `@include('v2-cms::partials.breadcrumb', ['items'=>…])` | não | include | trilha + microdata |
| `@include('v2-cms::partials.status-badge', ['active'=>…])` | não | include | pill de status |
| `@include('v2-cms::partials.action-create', ['route'=>…])` | não | include | botão "Novo" |
| `@component('v2-cms::components.box', ['title'=>…])` | não | componente | seção com header/tools/footer |
| `@include('v2-cms::partials.code-editor', ['name'=>…,'value'=>…,'mode'=>…])` | não | include | editor + textarea oculta |
| `@include('v2-cms::partials.activity-footer', ['model'=>$m])` | não | include | timeline (condicional) |
| `@include('v2-cms::partials._confirm-modal')` + handler | não | include + JS | confirmação de exclusão |
| `session()->flash('created_item', ['type','title','route'])` | não | side-channel | modal de sucesso |

### 4.2 Receitas de uso

Listagem completa (filtro + tabela + paginação + ações + estado vazio):

```blade
@include('v2-cms::partials.breadcrumb', ['items' => [
    ['label' => 'Conexões'], ['label' => 'Associados'],
]])

@component('v2-cms::partials.filter-card', [
    'searchPlaceholder' => 'Buscar por nome, e-mail ou CPF...',
    'action' => route('v2.associados.cms.instituicoes.index'),
])
    <div class="col-md-4">
        <label for="status" class="form-label small fw-medium text-muted mb-1">Status</label>
        <select name="status" id="status" class="form-select form-select-sm">…</select>
    </div>
@endcomponent

{{-- tabela escrita à mão nos módulos; o partial table não é usado --}}
{{ $instituicoes->appends(request()->except('page'))->links('v2-cms::partials.pagination') }}
```

Editor de conteúdo com auditoria:

```blade
@include('v2-cms::partials.code-editor', [
    'id' => 'landing-css',
    'name' => 'custom_css',
    'value' => $page->custom_css,
    'mode' => 'css',
    'height' => '320px',
    'label' => 'CSS customizado',
])

@include('v2-cms::partials.activity-footer', ['model' => $page])
```

O `id` deve ser único na página; `mode` fora do mapa cai em `htmlmixed`
(`code-editor.blade.php:11-16`).

Confirmação de exclusão com o modal global:

```blade
@include('v2-cms::partials._confirm-modal')

@push('scripts')
<script>
document.addEventListener('click', function (e) {
    var btn = e.target.closest('[data-action][data-bs-target="#confirmModal"]');
    if (!btn) return;
    e.preventDefault();
    document.getElementById('confirmModalBody').textContent = 'Excluir ' + btn.dataset.label + '?';
    // handler mínimo: submete um form com a URL de data-action
});
</script>
@endpush
```

### 4.3 Anti-uso

- **`@include('v2-cms::partials.filter-card')`** → `Undefined variable: slot`; o
  partial exige `@component … @endcomponent`.
- **`@include('v2-cms::partials.pagination', ['paginator' => $x])`** → falta `$elements`;
  use `->links('v2-cms::partials.pagination')`.
- **`@include('v2-cms::partials.alert')` sem `title`/slot** → 500 (caso real em Notícias).
- **`@component('v2-cms::partials.modal')` sem `footer`** funciona; sem `$slot` gera body
  vazio — não é erro, mas não há como passar conteúdo por `@include`.
- **Passar `icon => 'bi-newspaper'` em `badge`/`card`/`button`/`alert`** → sai
  `bi bi-bi-newspaper` (ícone inexistente). Nesses quatro o prefixo é adicionado pelo
  partial; em `action-create`/`empty-state` a classe é crua.
- **Confiar em `sortable`** do `partials.table` → nenhuma ordenação acontece.
- **Reutilizar `#confirmModal` em página que já tem a cópia de Notícias** → ids duplicados.
- **Chamar `conexao-tags-modal` em página sem as rotas `v2.cms.conexao-tags.*`** →
  `RouteNotFoundException` no render.

## 5. Catálogo de arquivos

| Arquivo | Papel | Variáveis | Observação |
|---|---|---|---|
| `partials/action-create.blade.php` | botão "Novo" | `route`* , `label`, `icon` | 10 consumidores |
| `partials/activity-footer.blade.php` | timeline de auditoria | `model`, `history` | 18 consumidores; falha silenciosa |
| `partials/alert.blade.php` | alerta inline | `type`, `title`*, slot | 1 consumidor (uso incorreto) |
| `partials/badge.blade.php` | badge genérico | `variant`, `label`, `pill`, `icon` | 0 consumidores |
| `partials/breadcrumb.blade.php` | trilha | `items[]` | 24 consumidores |
| `partials/button.blade.php` | botão | `variant`, `size`, `icon`, `label`, `id`*, `loading` | 0 consumidores |
| `partials/card.blade.php` | card com header/footer | `title`, `icon`, `class`, `header`, `footer` | 0 consumidores |
| `partials/code-editor.blade.php` | CodeMirror | `id`, `name`, `value`, `mode`, `height`, `label`, `disabled` | 7 consumidores |
| `partials/conexao-tags-modal.blade.php` | gestão de tags | — | 4 consumidores; query no render |
| `partials/creation-success-modal.blade.php` | modal pós-create | `session('created_item')` | incluído pelo layout |
| `partials/_confirm-modal.blade.php` | modal de confirmação | — | 3 consumidores; sem JS |
| `partials/empty-state.blade.php` | estado vazio | `icon`*, `title`*, `message`*, `hasFilters` | 0 consumidores |
| `partials/filter-card.blade.php` | busca + filtros | ver §3.1 | 14 consumidores |
| `partials/form-group.blade.php` | campo de formulário | ver §3.2 | 0 consumidores |
| `partials/modal.blade.php` | modal genérico | `id`, `title`, `size`, `backdrop`, `keyboard` | 0 consumidores |
| `partials/pagination.blade.php` | paginação | `paginator`, `elements` | 13 consumidores |
| `partials/status-badge.blade.php` | pill ativo/inativo | `active`* | 3 consumidores |
| `partials/table-actions.blade.php` | ações de linha | `editRoute`*, `deleteRoute`, `deleteLabel`, `extraActions` | 0 consumidores |
| `partials/table.blade.php` | tabela | `columns`, `rows`, … | 0 consumidores |
| `components/box.blade.php` | seção/card | `title`, `class`, `bodyClass`, slots | 14 consumidores |
| `components/seo-button.blade.php` | botão + modal SEO | `model`* | 0 consumidores |
| `components/buttons/save.blade.php` | submit salvar | `label`, `id`, `class` | 0 consumidores |
| `components/buttons/back.blade.php` | voltar | `url`, `label`, `class` | 0 consumidores |

`*` = obrigatório sem default.

## 6. Acessibilidade e responsividade

| Componente | A11y implementada | Lacuna |
|---|---|---|
| `filter-card` | `role="search"`, `<label class="visually-hidden">` + `aria-label`, `aria-expanded`/`aria-controls` no toggle, botões com `aria-label`, datas com `<label>` | ids de `date_from`/`date_to` derivados de `md5($collapseId)` — únicos por card; `<label for>` do campo de busca usa o **name** como id |
| `pagination` | `nav` rotulado, `aria-current="page"`, `rel`, `visually-hidden` para setas | item `disabled` é `span` dentro de `li.disabled` (sem `aria-disabled`) |
| `status-badge` | texto além da cor | bolinha decorativa sem `aria-hidden` |
| `table` | `<th scope="col">`, `aria-label` na tabela | `sortable` anuncia "Ordenar por X" mas não ordena |
| `code-editor` | `<label for="#{id}-editor">` | o alvo é um `div` (não focável) e a textarea real fica `d-none`; leitor de tela não alcança o conteúdo |
| `_confirm-modal` | `aria-labelledby`, botão fechar com `aria-label` | sem foco inicial explícito; título dinâmico sem `aria-live` |
| `creation-success-modal` | — | sem fechar/`Esc`; foco preso ao CTA |
| `conexao-tags-modal` | `aria-label` no input/botões, `title` no botão excluir | erro de criação em `div` sem `role="alert"`; `confirm()` nativo |
| `box` | `aria-labelledby` quando há `title` | — |
| `alert` | `role="alert"`, `btn-close` rotulado | exige `title`, senão 500 |

Responsividade: os componentes usam o grid do Bootstrap (`col-md-*`) e ocultam rótulos
em telas pequenas (`d-none d-md-inline` em `action-create` não existe; em `table-actions` e
`filter-card` sim). Não há CSS próprio de componente fora de `cms-layout.css`.

## 7. Assets e build

| Componente | Assets adicionados | Origem |
|---|---|---|
| `code-editor` | `assets/vendor/codemirror/lib/codemirror.css`, `theme/material.css`, `codemirror.js`, `mode/{xml,javascript,css,htmlmixed,clike,php}` | `partials/code-editor.blade.php:21-22,52-58` |
| `filter-card` | flatpickr (já global no layout) | `app.blade.php:38,104-105` |
| `creation-success-modal`, `conexao-tags-modal` | nenhum (usa `bootstrap.bundle` global) | `app.blade.php:101` |
| `components.box` | `cms-layout.css` (`.cms-grid-item`, `.cms-card-*`) | `cms-layout.css` |

Não há build step; os caminhos são fixos em `public/assets/vendor/...`.

## 8. Lacunas

| ID | Lacuna | Impacto | Onde procurar |
|---|---|---|---|
| `LG-UI-CMS-03` | Handler canônico de `#confirmModal` para `table-actions` (nenhum módulo usa o par) | exclusão via `table-actions` não funciona | `partials/table-actions.blade.php:31-40`, `Noticias/.../scripts.blade.php:152-193` |
| `LG-UI-CMS-06` | `deleteId` declarado e nunca emitido em `table-actions` | handler não sabe qual registro excluir | `partials/table-actions.blade.php:17,36-37` |
| `LG-UI-CMS-07` | `$tags` passado por `ConexaoTagCmsController@index` é ignorado pelo partial | query duplicada; o controller não controla o render | `partials/conexao-tags-modal.blade.php:1-5`, `V2/Core/Controllers/ConexaoTagCmsController.php:28` |
| `LG-UI-CMS-08` | Componentes sem consumidor (`table`, `card`, `modal`, `button`, `badge`, `form-group`, `empty-state`, `seo-button`, `buttons.*`, `table-actions`) — manter ou descartar? | esforço de reimplementação sem uso observável | busca em `V2/` |
| `LG-UI-CMS-09` | Contrato de `$model->metadata['seo']` para `seo-button` não é consumido por nenhum form | JSON-LD nunca é editado pelo CMS | `components/seo-button.blade.php:9-10,30-76` |

## 9. Checklist de paridade

- [ ] `PC-UI-CMS-16` — `@component('v2-cms::partials.filter-card', [...])` renderiza um `<form role="search">` com busca + Filtrar + Limpar + toggle "Mais filtros", e o bloco avançado é colapsável. Fonte: `filter-card.blade.php:54-132`.
- [ ] `PC-UI-CMS-17` — O id do bloco avançado é `filter-advanced-` + `md5($action . $searchName)`. Fonte: `filter-card.blade.php:52`.
- [ ] `PC-UI-CMS-18` — `showDateRange=true` renderiza `date_from` e `date_to` com `data-picker="date"` e inicializa flatpickr ao expandir. Fonte: `filter-card.blade.php:104-121,148-167`.
- [ ] `PC-UI-CMS-19` — `$paginator->links('v2-cms::partials.pagination')` produz `nav[aria-label="Navegação de páginas"]` com `aria-current="page"` e não renderiza nada com uma página. Fonte: `pagination.blade.php:1-53`.
- [ ] `PC-UI-CMS-20` — `status-badge` com `active=true` mostra "Ativo" e `bg-success`; `false` mostra "Inativo" e `bg-secondary`; `$active` é obrigatório. Fonte: `status-badge.blade.php:13-22`.
- [ ] `PC-UI-CMS-21` — `breadcrumb` marca o último item como `active` + `aria-current="page"` e só transforma em link os itens anteriores com `route`. Fonte: `breadcrumb.blade.php:6-18`.
- [ ] `PC-UI-CMS-22` — `components.box` converte `box-primary` em `border-primary border-top border-4` e gera `id="box-<id>"` com `aria-labelledby` quando há `title`. Fonte: `components/box.blade.php:1-11`.
- [ ] `PC-UI-CMS-23` — `code-editor` mantém o valor em `<textarea class="d-none" id="{id}-source" name="{name}">` e espelha a edição do CodeMirror. Fonte: `code-editor.blade.php:80-82,108-117`.
- [ ] `PC-UI-CMS-24` — `activity-footer` não renderiza nada sem histórico e não propaga exceção do logger. Fonte: `activity-footer.blade.php:1-15`.
- [ ] `PC-UI-CMS-25` — `creation-success-modal` só aparece quando `session('created_item')` existe e mostra `"{type} criado com sucesso!"` + `{title}` + link `Voltar para Listagem`. Fonte: `creation-success-modal.blade.php:1-17`.
- [ ] `PC-UI-CMS-26` — `conexao-tags-modal` cria tag via POST JSON em `v2.cms.conexao-tags.store` e exclui via DELETE, atualizando a lista sem reload. Fonte: `conexao-tags-modal.blade.php:70-203`.
- [ ] `PC-UI-CMS-27` — `_confirm-modal` **não** contém `<script>` e depende de handler externo para `[data-confirm-modal]`/`#confirmModalConfirmBtn`. Fonte: `_confirm-modal.blade.php:1-32`.
- [ ] `PC-UI-CMS-28` — `$slot` é `HtmlString`: `@include` em `alert`/`filter-card`/`box`/`card`/`modal` produz erro de variável indefinida. Fonte: `ManagesComponents.php:73-79`, `alert.blade.php:10-13`.

## 10. Evidências consultadas

- `V2/layouts/cms/partials/*.blade.php` (19 arquivos, leitura integral).
- `V2/layouts/cms/components/{box,seo-button}.blade.php`, `components/buttons/{back,save}.blade.php`.
- `V2/layouts/cms/layouts/app.blade.php:138-143`; `search/index.blade.php`; `dashboard.blade.php:75-220`.
- Consumidores verificados por busca: `V2/Modules/**/Views/**` (ocorrências de `v2-cms::partials.*` e `v2-cms::components.*`), `V2/Core/Controllers/{ConexaoTagCmsController,SearchController}.php`.
- `vendor/laravel/framework/src/Illuminate/View/Concerns/ManagesComponents.php:60-80` (tipo de `$slot`).
- `tests/Unit/V2/Layouts/Cms/CmsComponentUsageTest.php:1-62`.
- Referência cruzada: `01-cms-design-system.md` (stacks, CSP, tema), `_indices/parcial-UI-CMS/componentes.csv`.
