# Plano 16 — Widgets, Banners e Layout por Grupo de URL (viabilidade)

> **Milestone:** M3→M4 (transversal) · **Depende de:** 09, 10, 12 · **Habilita:** evolução do front
> **Objetivo:** entregar o que o `front-v7-proposal.md` pede — widgets laterais, banners, presets
> e temas — de forma **centralizada**, sem layout próprio por módulo, sem HTML/CSS no banco e
> sem quebrar acessibilidade ou responsividade.

---

## 1. O que a proposta pede

1. Controlar, por **grupo de URL** (ex.: `/noticias`), o que aparece lateralmente ao conteúdo.
2. Widgets com conteúdo do próprio módulo ou de outros módulos.
3. Banners acima/abaixo do conteúdo, mais um banner fixo ou temporário.
4. Arrastar/posicionar visualmente dentro de um **iframe responsivo e real**.
5. Configuração consistente em todas as páginas do grupo.
6. Widgets/banners respeitam WCAG 2.2 AA + eMAG por padrão.
7. Temas centralizados: CMS e Portal (cores/tipografia), padrão global e por módulo.
8. Conteúdo do widget configurável (em algum grau).
9. Presets gerais definidos pelo suporte, cobrindo todos os aspectos, extraídos dos
   **modelos 1..6** da `prefeituras_v5`.
10. Conteúdo sempre agnóstico de UI.
11. Acessibilidade (dislexia, espaçamento, alto contraste P&B) **sempre** prioritária.
12. Usabilidade para usuário não técnico.
13. **Empilhamento ordenado** com espaçamento fixo (ex.: 2 widgets à direita, um acima do outro).
14. **Overrides locais em JSON** de tokens do widget (cor, borda, espaçamento…) — **nunca** CSS.
15. **Meta-serviço** de layout por grupo de URL; o módulo só define conteúdo central.

## 2. Veredito de viabilidade

| # | Capacidade | Veredito | Como |
|---|---|---|---|
| 1 | Widget lateral por grupo de URL | ✅ viável | `LayoutProfile` ligado a rota/grupo (ADR-016 + plano 10) |
| 2 | Conteúdo de outros módulos | ⚠️ via Core | porta `WidgetSource` no Core; **nunca** import direto (ADR-013) |
| 3 | Banners acima/abaixo + fixo/temporário | ✅ viável | slots `main.before`/`main.after` + `inicio`/`fim` + cache |
| 4 | Drag & drop em iframe real | ⚠️ estruturado | iframe same-origin + **reordenar na pilha**; sem posição absoluta |
| 5 | Consistência no grupo | ✅ viável | perfil único por grupo + resolução determinística |
| 6 | A11y/responsividade por padrão | ✅ viável | garantido **no componente**, não na config |
| 7 | Tema CMS × Portal centralizado | ✅ viável | `ThemeScope` (`cms`/`portal`) em JSONB → CSS vars |
| 8 | Padrão estético global × módulo | ✅ viável | catálogo **fechado** de variantes; sem CSS livre |
| 9 | Conteúdo do widget configurável | ⚠️ por schema | formulário gerado do schema do tipo |
| 10 | Presets dos modelos 1..6 | ✅ viável | `PresetSchema` + extração real dos tokens |
| 11 | Conteúdo agnóstico de UI | ✅ já é regra | ADR-017 + ADR-016 |
| 12 | A11y sempre prioritária | ✅ viável | camada `@layer a11y` **por último** + `data-a11y-*` pré-paint |
| 13 | Empilhar 2 widgets com gap fixo | ✅ viável | slot é **pilha ordenada**; `gap` do design system |
| 14 | Overrides locais em JSON | ✅ viável | só **tokens semânticos**, nunca CSS; a11y vence |
| 15 | Meta-serviço por grupo de URL | ✅ viável | `services/Layout` + `src/layout`; módulo não tem layout |
| 16 | Usabilidade não técnica | ⚠️ com cuidado | modo simples × avançado, presets e defaults fortes |

**Resumo:** o modelo de **pilha ordenada** elimina o risco do “canvas livre”. O único ponto que
exige disciplina é o grau de configurabilidade (item 16).

## 3. Arquitetura que torna viável

### 3.1. Slot = pilha ordenada (não posição absoluta)
Cada região (esquerda/direita/topo/base) é um **slot que empilha** widgets em ordem. Dois widgets
à direita = posições `1` e `2`, com o **`gap` do slot** (token do design system) entre eles.
- Reordenar = mudar `ordem` (dado), não coordenadas.
- `gap`, alinhamento e largura são do **slot/preset**, nunca por pixel.
- Responsivo por construção: a pilha vira coluna/linha conforme o breakpoint/container query.

### 3.2. Widget/Banner = tipo registrado + conteúdo atômico
- `WidgetRegistry` (Core, global): cada tipo declara `id` namespaceado (`noticia.mais-acessadas`),
  `schema` de conteúdo (campos tipados), `slots` permitidos, `variantes`, contrato de
  acessibilidade, cache e **disponibilidade para embed**.
- Conteúdo é **estrutura atômica (JSON)**; renderização é Blade semântico. Nunca HTML (ADR-017).
- O **dono do widget** é o módulo de origem; a **colocação** é do perfil de URL de destino.

### 3.2.1. Widget de outro módulo (ex.: notícias dentro de legislação)
Exemplo: colocar “notícias mais acessadas” na coluna esquerda do grupo `/legislacao`.

1. `Noticias` implementa `NoticiaMaisAcessadasWidget implements WidgetSource` e registra no
   `WidgetRegistry` (Core) no seu `ModuleProvider::register()` — **sem** que ninguém importe `Noticias`.
2. O CMS de `/legislacao` lista os widgets disponíveis (registry global) e mostra
   `noticia.mais-acessadas`.
3. O editor grava no perfil de `/legislacao`:
   `{ "widget": "noticia.mais-acessadas", "slot": "main.left", "ordem": 1, "tokens": {…} }`.
4. Em runtime, `services/Layout` resolve o perfil; `src/layout` pede ao Core o widget pelo
   `id` e chama `render()` — **nenhum import entre módulos** (o Core faz a inversão).
5. Se `Noticias` estiver desabilitado, o slot renderiza vazio + log estruturado (resiliente).

> Regra: o widget é **provider** (módulo de origem) + **placement** (perfil de destino).
> `layout` consome `core`; o módulo de origem registra no `core`; o destino só referencia o `id`.

### 3.3. Overrides locais em JSON (só tokens, nunca CSS)
- A instância pode guardar um JSON enxuto com **valores semânticos**: `cor`, `borda`,
  `espacamento`, `variante`… — coisas que a config global não define.
- **Proibido** CSS, `style`, classes ou HTML no JSON; só tokens do design system.
- Herança/precedência: `default do tipo → global → preset → perfil de URL → instância`,
  e **`a11y` por último** (vence tudo).
- Exemplo: `{"cor":"secondary","borda":"suave","espacamento":"compacto"}` → CSS vars/classes.

### 3.4. Escopo por grupo de URL (meta-serviço) — módulo não tem layout
- `LayoutProfile`: configuração de slots/widgets/banners ligada a um **grupo de URL**
  (ex.: `/noticias` ou o prefixo de rota `noticias.*`).
- **`services/Layout`** (headless): persiste perfis e resolve `rota → perfil` (match mais
  específico vence; fallback global; cache por **nome de rota**, não por URL com parâmetros).
- **`src/layout`** consome o perfil e renderiza os slots (não há layout por módulo).
- O módulo apenas **declara o grupo de URL** no manifesto/`MODULE.md`; o CMS mostra um botão
  **“Configurar layout”** que abre o editor **já no escopo do grupo**.
- Resultado: implementação **centralizada e simplificada** — resolve o problema sem duplicar.

### 3.5. Temas e presets (plano 09)
- Dois escopos de tema: `cms` e `portal`; tokens em JSONB → CSS vars + tema daisyUI.
- `PresetSchema`: paleta, tipografia, raio, sombra, espaçamento, densidade, movimento e
  variantes default de widget/banner.
- Extração inicial dos modelos 1..6 (`prefeituras_v5`): `_theme_color.scss`
  (`heading/text/bg-default/bg-gray/white/dark/border/hover/footer/secondary`) + `_fonts.scss`.

### 3.6. Preview real (plano 12)
- **iframe same-origin** para a rota real do portal com *token de preview* curto; o CMS envia
  o rascunho por `postMessage`. WYSIWYG responsivo sem reimplementar o renderer.

### 3.7. Drag & drop estruturado (plano 12)
- Reordenar **dentro** de uma pilha e **mover entre** slots; nada de posição absoluta.
- **Alternativa de teclado obrigatória** (WCAG 2.2 SC 2.5.7): subir/descer e “mover para slot X”.

### 3.8. A11y sempre por último (ADR-010)
- `@layer theme, base, components, utilities, overrides, a11y;`.
- Preferências (`data-a11y-dyslexia`, `data-a11y-spacing`, `data-a11y-contrast`) aplicadas
  antes do primeiro paint (cookie/localStorage + SSR), com `forced-colors` e foco visível.

## 4. Não-objetivos (explicitamente fora)

- **Posição absoluta / pixel** — quebra responsividade, a11y e o princípio de conteúdo agnóstico.
- **HTML/CSS/classes no banco** — é o `layouts.value`/`layouts.style` do legado.
- **Drag sem alternativa de teclado** (viola WCAG 2.2).
- **Layout próprio por módulo** — contradiz a centralização pedida.
- **Widget importando outro módulo diretamente** — o correto é consumir via
  `WidgetRegistry`/Core (embed permitido, import proibido).
- **CSS bruto por elemento** — substituído por catálogo de variantes + tokens.

## 5. Fases (menor retrabalho)

1. **Temas + presets + camada a11y** (sem DnD): escopo `cms`/`portal`, `PresetSchema`, `@layer a11y`.
2. **Slot como pilha + registry de widget/banner + overrides JSON + porta do Core**.
3. **Meta-serviço `LayoutProfile` por grupo de URL + botão “Configurar layout” no CMS**.
4. **Preview em iframe + drag & drop estruturado (teclado) + presets dos modelos 1..6 + agendamento**.

## 6. Atividades

- [ ] Ampliar ADR-016: pilha ordenada, overrides JSON, `LayoutProfile` por grupo de URL, `WidgetSource`.
- [ ] `@layer theme, base, components, utilities, overrides, a11y;` + preferências pré-paint.
- [ ] `ThemeScope` (`cms`/`portal`) no `services/Theme` + export de CSS vars + tema daisyUI.
- [ ] `PresetSchema` + extração inicial dos modelos 1..6 (`_theme_color.scss`/`_fonts.scss`).
- [ ] `WidgetRegistry`/`BannerRegistry` (schema, variantes, slots, **embed/permissões**) +
      override JSON de tokens.
- [ ] Porta `WidgetSource` no Core: módulo de origem registra; destino referencia o `id`.
- [ ] CMS lista widgets de **todos** os módulos disponíveis para o grupo de URL.
- [ ] `services/Layout` (`LayoutProfile`): persistência + resolução `rota → perfil` com cache.
- [ ] Módulo declara o grupo de URL no manifesto; CMS mostra “Configurar layout” no escopo do grupo.
- [ ] Rota de preview + token curto + iframe responsivo no editor.
- [ ] Drag & drop na pilha (reorder/mover) + botões de teclado “subir/descer/mover”.
- [ ] Agendamento de banner (`inicio`/`fim`) + invalidação de cache por evento.
- [ ] Testes: axe por tipo; teclado no reorder; a11y vence override; resolução de perfil; preview.

## 7. Testes e evidências

- [ ] Dois widgets empilhados à direita respeitam ordem e `gap` do design system.
- [ ] Widget/banner de cada tipo passa em axe/WCAG AA sem violações.
- [ ] Reordenar widget é possível **sem** mouse (só teclado).
- [ ] Override JSON muda cor/borda/espaçamento **sem** CSS/HTML no banco.
- [ ] Trocar preset muda o portal sem deploy; a11y (dislexia/contraste) continua vencendo.
- [ ] `/noticias` e suas páginas usam o mesmo perfil; rota fora do grupo cai no global.
- [ ] Widget de “últimas notícias” funciona via porta do Core, sem import entre módulos.
- [ ] Widget `noticia.mais-acessadas` aparece na coluna esquerda de `/legislacao` sem que
      `Legislacao` importe `Noticias`; desabilitar `Noticias` deixa o slot vazio (sem erro).
- [ ] Banner temporário aparece/some pela data, sem deploy.

## 8. Definition of Done

- [ ] Widgets e banners configuráveis por grupo de URL, com preview real e sem HTML/CSS no banco.
- [ ] Módulo **não** implementa layout: só conteúdo + grupo de URL.
- [ ] Temas CMS/Portal e presets centralizados, extraídos dos modelos 1..6.
- [ ] Acessibilidade sempre prioritária, provada por teste.
- [ ] Usuário não técnico configura o essencial sem tocar em CSS.

## 9. Riscos

| Risco | Mitigação |
|---|---|
| Vira “construtor de site” complexo | Pilha por slots + catálogo fechado de variantes |
| Drag & drop inacessível | Alternativa de teclado obrigatória + teste |
| Config demais confundir o usuário | Modo simples × avançado; presets e defaults fortes |
| A11y sobrescrita por tema/módulo/override | `@layer a11y` por último + testes de precedência |
| Perfil de URL ambíguo | Match mais específico vence + cache por nome de rota |
| Reincidir em HTML/CSS no banco | ADR-017 + gates + revisão |
