# História 27 — Páginas do site (tabela, CMS e regra de slug)

## Objetivo

Provar que o operador cria e edita uma **página estática** pelo painel: título por idioma, endereço
(slug) único e conteúdo rico em AST — e que um endereço que colidiria com uma **rota viva** do site
(`noticias`, `cms`, …) é recusado com erro no campo, sem gravar.

## Pré-condições

- `make up` + `php artisan db:seed` (o perfil demo recebe as permissões declaradas, incluindo
  `paginas.ver`/`paginas.editar`/`paginas.arquivar`).
- Migração `paginas.pages` aplicada (`php artisan migrate`).
- Assets compilados (`make check`).

## Passos

1. Entrar no painel (`cms.<host>/login`) e abrir `/paginas`; conferir o título da tela (do shell) e
   o axe.
2. Clicar **Nova página**; preencher **Título** no idioma padrão, o **Endereço (slug)** único da
   execução e a **Descrição de SEO**; clicar **Salvar página**.
3. Conferir o aviso de criação, o título/endereço no formulário de edição e rodar o axe de novo.
4. Tentar criar outra página com o endereço `noticias` (rota viva): o resumo de erros e o campo
   mostram a recusa e **nenhuma** página é gravada com esse endereço.
5. Sem JavaScript: abrir `/paginas/criar`, preencher título/slug e colar o **AST JSON** no campo de
   conteúdo (o editor rico não roda), salvar e conferir que o documento foi preservado.

## Contrato verificado

- Lista e formulário vivem **só** no painel (`cms.<host>/paginas`), com `paginas.ver` para ler e
  `paginas.editar` para gravar (ADR-033) — deny-by-default.
- A view usa **um** shell (`<x-layout::cms>`); o `<h1>` é do shell e o conteúdo é o `<main>`.
- Título, conteúdo e SEO têm um campo por locale (`pt_BR`, `en`, `es`, `fr`) e o conteúdo usa o
  editor único do projeto (TipTap, ADR-028) — nunca HTML no `jsonb`.
- O slug é normalizado (minúsculas/hífens), único **sempre** (inclusive arquivada) e nunca sombreia
  rota viva; a colisão responde 422 no campo, não 500.
- Publicar/despublicar/arquivar **existem** desde a S3.2: o spec publica a página criada e a arquiva
  no `finally` (a URL reservada não é reciclada), com `POST` + CSRF e auditoria; arquivada responde
  404 + `X-Robots-Tag: noindex` e sai do sitemap.
- Zero violações de acessibilidade (axe) na lista e no formulário **com JavaScript**. No caminho sem
  JavaScript o gate é estrutural (o axe injetado não é confiável com scripts desativados, como
  registra o spec 04).
