# Plano 12 — CMS: Shell Administrativo Próprio + Views por Módulo

> **Milestone:** M4 · **Depende de:** 11 · **Habilita:** 13–16
> **Objetivo:** um CMS **próprio e independente**, inspirado no AdminLTE4 (como no
> `sucesu-cms/V2/layouts/cms/`), cuja filosofia é oferecer **“espaços para colocar as
> coisas”** — não controlar o conteúdo dos módulos. Cada módulo implementa suas próprias
> views de CMS.

---

## 1. Decisão de UI (ADR-009)

- O CMS é um **shell independente** em `src/layout/Cms` (namespace `Layout\Cms`; views `cms::`), que **não
  conhece módulos**: não acessa Models, rotas ou dados de módulo.
- Inspirado no AdminLTE4/`sucesu-cms`: sidebar, header, footer, breadcrumb, cards, tabelas,
  formulários, modais, alertas, empty states, paginação, status badges, ações e busca global.
- **Modernizado** (escopo do projeto): **Tailwind 4 + daisyUI + Lucide** (SVG vendorizado).
  **Sem Bootstrap, sem jQuery, sem CDN.**
- **Sem Livewire** (e sem UI reativa): Blade + formulários/validação nativos + JS pequeno
  vendorizado (progressive enhancement). UI reativa exigiria **nova ADR** (ADR-001/ADR-002).
- **Filament: não** (nem como dependência do domínio).

## 1.1 Shell como dado (ADR-036)

- **Identidade**: `title`, `logo` e cores (`--cms-*`, em `oklch()`) configuráveis por instância; `null`
  herda o tema do tenant. Publicadas em `GET /cms-theme.css` (same-origin, `ETag`) — **sem `<style>`
  inline** (ADR-022).
- **Regiões**: `header`, `footer` e `sidebar` podem ser **desligadas**; a sidebar vai para
  **esquerda, direita ou órbita** (menu acionável nativo, Popover API, com fallback sem JS).
- **Estabilidade**: a posição é resolvida em **CSS** (propriedades lógicas, sem medição em JS → CLS 0) e
  a **ordem do DOM acompanha a posição** (ordem de foco = ordem visual — WCAG 2.4.3).
- Precedência: `default (config) → instância`; preferência de usuário muda **estado**, nunca estrutura.
- Fonte: `config/cms.php` → `Layout\Cms\Shell\CmsShellResolver` → `<x-layout::cms>` (`CmsShellTest`).

## 1.2 Acesso ao painel (ADR-037)

- **Sessão nativa** sobre `cms_users` (guard `web` + provider `eloquent`): **sem** Fortify/Breeze/JWT.
- Rotas da **área CMS** (Core, sem closure): `GET /login`, `POST /login`, `POST /logout`, `GET /`
  (painel mínimo autenticado). O **domínio próprio** (`cms.<base>`) isola cookie/sessão do portal.
- **Argon2id** (ADR-020), `session()->regenerate()` no login, falha **genérica** (não revela se o
  e-mail existe) e logout por `POST` + CSRF.
- **Freio day-zero** (ADR-021): middleware `ThrottlePanelLogin` por **e-mail + IP** no store de cache
  (Valkey em dev/prod; `array` nos testes → determinístico); **429** com `Retry-After`.
- **Auditoria de graça** (ADR-035): eventos `Login`/`Logout`/`Failed` → `core.login`/`core.logout`/
  `core.login_failed`, sem credenciais no payload.
- Fora do MVP (backlog): gov.br SSO, 2FA, recuperação de senha, “lembrar-me”.

## 2. Filosofia “espaços, não controle”

O shell define **seções/stacks** que o módulo preenche — nada mais:

| Seção/stack | Uso |
|---|---|
| `page-breadcrumbs` | trilha de navegação |
| `page-actions` | botões de ação da tela |
| `content` | corpo da tela |
| `css` / `scripts` / `modals` | assets e modais do módulo |

- O módulo só faz `@extends('cms::layouts.app')` e preenche as seções.
- O shell oferece um **catálogo de partials/componentes**: breadcrumb, card/box, table,
  form-group, button, modal, pagination, status-badge, empty-state, filter-card, alert,
  badge, table-actions, seo-button.
- O shell **não** busca dados: o módulo entrega os dados já prontos à view.

## 3. Estrutura

```
src/layout/Cms/
├── layouts/            # app.blade.php, header, sidebar, footer
├── partials/           # catálogo de "espaços" (breadcrumb, card, table, form-group, modal…)
├── components/         # <x-cms::...>
├── staticPages/        # login, 403/404/500/503, maintenance
├── Assets/             # CSS/JS do shell (Tailwind + JS mínimo)
└── CmsServiceProvider.php
```

Módulo (só views de CMS, sem tocar no shell):

```
src/modules/{M}/Layout/Cms/   # views do módulo, extends cms::layouts.app
```

## 4. Navegação e busca

- **Sidebar = manifesto** (`PanelMenu`, Core): grupos (pasta do módulo) → módulos → `subModulesLevel1`.
  Regras: ordenação por `position` (empate pelo nome → determinístico), **negação por padrão**
  (`{module}.ver`, ADR-033), módulo inativo e grupo `Suporte` filtrados (ADR-034/ADR-033) e item cuja
  rota **não existe** ou é de **outra área** é omitido — nunca link morto, nunca link para fora do
  painel (`PlatformArea::domain()` é a fonte única do domínio da área).
- **Título de grupo** é chave de tradução (`layout::shell.group_{snake}`), coberto por teste nas 4
  locales (nenhum grupo pode ficar sem rótulo).
- **O `<h1>` da tela é do shell** (vem do `title` do módulo): nenhuma tela do painel nasce sem título
  e nenhuma nasce com dois (`LayoutContractTest`).
- **Navegação rápida (Ctrl+K)**: o mesmo dado servido no HTML, dentro de um **popover nativo** —
  abre e continua navegável **sem JS**; com JS ganha filtro por texto traduzido, setas e `Esc` com
  devolução de foco.

## 5. Editor de texto rico único — TipTap (ADR-028)

- **TipTap é o único editor de texto rico** do projeto (ADR-028). Vendorizado via npm + Vite,
  **sem CDN**, **sem Filament**. AST JSON persistido em `jsonb`; **nunca HTML** no banco.
- Renderização pública: `src/services/RichText` (headless) converte **AST → HTML semântico** com
  **um método por nó/mark** no `ContentASTParser` (+ teste de contrato por nó), sem
  traits/abstract/genéricos (ADR-001); resumos/SEO usam `toText()`. **Sem estilo inline**: atributos
  viram classes (`richtext-*`) e o CSS do serviço é importado pelos bundles do Portal e do CMS.
- **Extensões**: parágrafo, `h2`–`h4`, citação, listas, linha horizontal, tabela, colunas,
  imagem/mídia, YouTube; marcas negrito/itálico/sublinhado/tachado/marca-texto/link/defasado;
  alinhamento e recuo. **Sem nó de HTML livre**; blocos especiais (FAQ, carrossel, cards,
  passo-a-passo, tabela interativa) **descartados** (simplicidade/segurança/a11y).
- **Imagens** passam pelo serviço Media (ADR-026) e viram `<picture>` (AVIF/WebP com
  `width`/`height`, CLS 0) no portal, servidas na mesma origem por `media.stream` (o endpoint do
  S3/RustFS não é exposto — ADR-030); o AST guarda a **chave do objeto**, nunca a URL.
  **Links** viram `<a>` reais com `rel`/`target`/`hreflang`/`aria-label`, nunca URL crua; link
  interno recebe o locale pelo invariante de URL (ADR-012). **YouTube** entra como link (a CSP não
  autoriza embed de terceiros).
- **Link para conteúdo do portal** integra o serviço **Search** (modal centralizado, sem overlay
  de página inteira, popover de pré-visualização do destino).
- Editor **por locale** (aba por locale: pt_BR/en/es/fr) para o autor transcrever o **conteúdo** (`jsonb` + `LocaleCast`), com indicador de tradução faltante (ADR-012); campos traduzíveis
  em JSONB. **Implementado**: um `<textarea>` com o AST JSON como campo do formulário (funciona
  **sem JS**) que o TipTap assume com JS; upload de imagem/PDF pelo serviço Media (AVIF + WebP
  via libvips, chaves de objeto) e preview split-screen renderizado pelo **mesmo parser** do portal
  (`richtext.preview`). Aba por locale fica para o passo final do T2 (`LocaleCast`).
- **Toolbar**: `aria-pressed`/`disabled` por botão, barras **contextuais** (tabela e mídia), atalhos
  nativos do TipTap e nada de HTML livre (nó inexistente — ADR-028 §9).
- **Proibido** injetar classes do CMS no conteúdo salvo; estilo só por `.prose`/tokens.
- **Preview** fiel (split-screen) respeitando `LayoutPreset`/`LayoutProfile` e os slots reais
  (plano 16). Renderização determinística → `ETag`/`304` (ADR-022).

## 6. Editor de layout/tema

- Consome `LayoutProfile` (plano 16) e `Theme` (plano 09): reordenar/ocultar slots, tokens de
  tema — tudo dado, sem deploy.

## 7. Atividades

- [ ] Shell do CMS (`app`/`header`/`sidebar`/`footer`) em Tailwind + daisyUI, acessível.
- [ ] Catálogo de partials/componentes do shell (os “espaços”).
- [ ] `CmsServiceProvider` registrando o namespace de views `cms::`.
- [ ] Sidebar do manifesto + permissões; busca global Ctrl+K acessível.
- [ ] Auth nativo Laravel + sessão/cookies endurecidos (ADR-020).
- [ ] Views de CMS do módulo piloto (plano 13) usando **apenas** as seções do shell.
- [ ] Editor de texto rico **TipTap** (ADR-028): componente Blade sob `/cms`, bundle vendorizado,
      AST→`jsonb`, `ContentASTParser` + renderers concretos (`toHtml`/`toMarkdown`/`toText`).
- [ ] Inserção de imagem via serviço Media (ADR-026) → `<picture>` no portal; links acessíveis;
      cross-link via serviço Search; preview split-screen (por locale).
- [ ] Formulários de módulo incluem componentes de configuração/uso dos services
      (ex.: `<x-service-seo::input>`, `<x-service-media::input>`) — ADR-015.
- [ ] Telas “Gerenciar módulos/flags” e “Editar layout/tema” (dado).
- [ ] Anti-abuso nos formulários (ADR-021): honeypot, time-trap, limite por formulário+sessão;
      CSRF nativo; `429` amigável (RFC 7807) com `Retry-After`.
- [ ] Logs estruturados de ações administrativas (auditoria) via `AuditWriter`.

## 8. Testes e evidências

- [ ] E2E: login → criar notícia → publicar → visível no portal.
- [ ] E2E: reordenar slot e refletir no portal sem deploy.
- [ ] axe sem violações nas telas de admin; teclado-only opera listagem e formulário.
- [ ] Teste: conteúdo salvo não contém classes do CMS nem `<script>`; é JSON (`jsonb`) válido.
- [ ] Teste de contrato: cada nó/mark do TipTap gera a tag semântica esperada no `toHtml()`.
- [ ] E2E: criar notícia com link, imagem e tabela → portal mostra `<a>`, `<picture>` e `<table>`
      semântica; editor opera por teclado; modal de link centralizado (sem overlay).
- [ ] Teste de fronteira: um módulo usa o shell **sem** importar o CMS (só `cms::`).
- [ ] Teste: cada ação de escrita gera log de auditoria com ator e `request_id`.
- [ ] Teste: spam de formulário é bloqueado sem punir sessão legítima; POST sem CSRF é rejeitado.

## 9. Definition of Done

- [ ] CMS próprio, independente, acessível e dirigido por manifesto/permissões.
- [ ] Módulo implementa sua view de CMS **sem** tocar no shell.
- [ ] Conteúdo estruturado com preview fiel; layout/tema editáveis sem deploy.
- [ ] Zero dependência pesada de UI (sem Livewire/Filament, sem Bootstrap/jQuery/CDN).

## 10. Riscos

| Risco | Mitigação |
|---|---|
| Shell “controlar” o módulo | Fronteira: shell só oferece seções/partials; módulo entrega dados |
| Reescrever o CMS do zero demorar | Catálogo de partials + começar por Notícias |
| Editor livre virar vetor de XSS | JSON atômico + renderer semântico + **sem nó de HTML livre** + testes |
| Interatividade exigir framework reativo | JS pequeno vendorizado (progressive enhancement); UI reativa exigiria nova ADR |
