# 34 — Listas, filtros e formulários do painel

**Fase:** P5.6.S3 · **Área:** CMS (domínio do painel) · **Spec:** `tests/34-cms-listas-formularios.spec.ts`

## História

A redação abre uma listagem (Notícias é a tela canônica) e reconhece o mesmo padrão de todas as
telas de conteúdo: a **busca fica sempre visível**, os **filtros avançados** moram num `<details>`
nativo (aberto quando um deles está ativo), o cartão de filtros **resume o que está aplicado** com
remoção individual e informa a contagem **“x–y de z”** com a página atual. O recorte inteiro vive
na URL (`GET`): compartilhar o endereço reproduz a lista, e “Limpar filtros” volta à rota da tela
sem perder o caminho.

Quando o recorte não devolve nada, o estado vazio não é um beco: oferece a **ação primária** da
tela (criar) e, porque há filtro, a ação **Limpar filtros**. A tabela tem `<caption>`, cabeçalho
fixo quando rola, `aria-sort` com link real de ordenação e ações de linha **rotuladas** no desktop
(e `sr-only` no ponteiro grosso); a linha inteira nunca é clicável.

No formulário, a barra de ações fica no rodapé da tela (sticky, nunca `fixed` sobre o conteúdo) e
o operador vê **“Alterações não salvas”** assim que digita; tentar sair pede confirmação do
navegador (`beforeunload`). Um segundo envio no mesmo task é descartado — sem desabilitar ou remover
o botão que carrega `name`/`value` — e o formulário entra em `aria-busy` com o estado “Enviando…”.
O resumo de erro (`role="alert"`) e o foco no primeiro campo inválido continuam valendo.

## Critérios de aceite

1. O filtro é um `<form method="get">`; `/noticias?status=publicado` recarrega com o `<details>`
   aberto, o resumo “Filtros ativos” e a contagem `Exibindo x–y de z`.
2. “Remover filtro: Situação” navega para `/noticias` (rota preservada, chave removida) e o resumo
   desaparece; a contagem continua coerente com a lista completa.
3. Busca sem resultado (`q=e2e-nada-encontrado`) mostra o estado vazio com “Nova notícia” e “Limpar
   filtros”; limpar devolve `/noticias` sem estado vazio.
4. **Sem JavaScript**, o `<details>` abre no clique nativo, o select de situação é enviado e a
   listagem filtrada mostra o resumo — o caminho de filtro não depende de JS.
5. Digitar no formulário revela “Alterações não salvas”; ao fechar a aba o Chromium emite o
   diálogo `beforeunload` (tratado no teste).
6. Dois `requestSubmit` no mesmo task produzem **um** `POST`, o segundo evento chega com
   `defaultPrevented = true`, o formulário fica `aria-busy="true"` e o payload contém
   `action=save` (submitter preservado). O `PUT` é interceptado: nada é gravado.
7. Listagem, estado vazio e formulário passam no axe A/AA (WCAG 2.2).

## Dados e isolamento

- Nenhuma escrita chega ao servidor: o `POST /home` é interceptado em `page.route` e respondido com
  `303 /home`; a listagem usa a semente demo (`status=publicado` tem registros).
- O spec roda no `cms-desktop` e no `cms-mobile`; o bloco sem JS cria o próprio contexto. Nada de
  estado global (tema, home publicada, ativação de módulo) é mutado — sem `cms-serial`.

## Fora de escopo

- Ordenação por coluna nas telas que não expõem ordenação (Notícias/Páginas/Destaques mantêm a
  ordem determinística do servidor; a prova de ordenação real está em Acesso → Usuários).
- Novas consultas/filtros: o `GET` só transporta o que o controller já aceitava.
- Edição otimista/`lock_version` (já coberto pelas histórias de cada módulo).
