# P1 — Estrutura geral do site e do CMS

| Campo | Valor |
|---|---|
| Programa | F7+ — operação, estrutura e conteúdo |
| Status | `implementado e revisado` — ficha `_to_revision/P1-estrutura-site-cms.md` |
| Depende de | P0 |
| Habilita | P2, P3, P5, P6, P7 |
| Tamanho | **G** (4–6 sessões) |
| ADRs | ADR-016, ADR-017, ADR-036, ADR-012, ADR-022; emenda à ADR-016 se a porta de navegação exigir |

## 1. Objetivo

Dar ao painel o controle da **estrutura geral**: identidade institucional, tema/tokens do portal e
do CMS, regiões e posição do shell administrativo, menus do site e padrões de SEO. É a camada que
transforma “editar `config/*.php` e fazer deploy” em “editar dado, com validação e auditoria”.

**Resultado observável:** o operador troca nome/logo/cores, posição da sidebar e a árvore do menu
principal; o portal e o painel refletem a mudança no próximo request, sem deploy, sem CSS inline e
sem quebrar acessibilidade ou contraste.

## 2. Estado atual (não recriar)

- Tema/tokens: `src/layout/Engine/Theme{Resolver,Tokens,Schema}.php`, `config/design.php`,
  `GET /theme.css` com `ETag`/`304`.
- Shell do CMS: `src/layout/Cms/Shell/Cms{Shell,ShellResolver,SidebarPosition}.php`,
  `config/cms.php`; regiões e posição já são dado de configuração (arquivo), com ordem de DOM.
- Preferências de a11y: `A11yPreferences`/`A11yFontScale`, `@layer a11y` por último.
- Perfil de layout e widgets: `src/services/Layout/**`, `src/core/Contracts/WidgetRegistry.php`.
- **Não existe:** editor de tema, editor do shell, menus do site, settings de SEO/identidade em banco.

## 3. Escopo fechado

### S1 — Seções de configuração `theme`, `cms`, `seo` e `institution` ampliada

- Declarar no `SettingsSchema` (P0):
  - `institution`: nome, razão social, CNPJ, endereço, telefone, e-mail, horário, site, redes
    sociais (lista validada de `{rede,url}` com allowlist de redes), DPO/ouvidoria (e-mails).
  - `theme`: `color_primary`, `color_accent`, `color_link`, `radius`, `font_content` — exatamente
    as chaves do `ThemeSchema`; nada de CSS livre. O payload pode agrupar `portal`, `cms` e
    `widgets`; `widgets` é um mapa por id/tipo com tokens semânticos validados (D1/ADR-046).
  - `cms`: `title`, `logo` (media id ou URL same-origin/`https`), `color_primary`,
    `color_accent`, `regions.header/footer/sidebar`, `sidebar.position`, `sidebar.width`.
  - `seo`: `title_suffix`, `default_description`, `default_image` (media id), `analytics_id`
    (GA opt-in), `verification` (lista de `{provider,content}`).
- `ThemeResolver` e `CmsShellResolver` passam a ler **settings** com fallback para `config/`;
  valor inválido é ignorado (a camada anterior vence), com log estruturado.
- Testes de precedência: default → global → preset → URL → instância; a11y sempre por último.
- `SeoSettings` passa a ler settings; `analyticsId()` continua devolvendo `null` por padrão.

### S2 — Editor de tema/tokens

- Tela `Configurações → Aparência` no módulo `Infraestrutura/Configuracoes`.
- Campos gerados do `ThemeSchema`: cor (validação de cor CSS sem `;{}url()@`), escala de raio,
  fonte (lista fechada), com preview de amostra **same-origin** (bloco HTML com os tokens, sem
  `<style>` inline; usa `/theme.css` já publicado).
- Salvar → invalida `settings.theme` + `theme.css` (`ETag` muda); a página revalida, sem esperar 1 h.
- A11y: o editor não escreve `!important`, não remove a camada `a11y`; teste de contraste por axe
  na amostra e no portal real.
- 4 locales para labels/help/erros; nenhuma string visível hardcoded.

### S3 — Editor do shell do CMS

- Tela `Configurações → Painel` (identidade, regiões, posição da sidebar, largura).
- `CmsShellResolver` lê settings sobre `config/cms.php`; `null` herda; inválido ignora.
- Regras de estrutura: a posição é CSS (propriedades lógicas, CLS 0); a **ordem do DOM acompanha**
  a posição (foco = visual); `orbital` usa Popover nativo e funciona sem JS.
- Testes: `CmsShellTest` para cada posição, região desligada, valor inválido, logo inexistente,
  largura fora da escala; axe no painel em desktop e mobile.

### S4 — Menus do site (módulo + porta Core)

- Módulo `Infraestrutura/Menus` (token `menus`), grupo `Infraestrutura`, permissions
  `[VIEW, EDIT]`. Rotas CMS: `/cms/menus`, `/cms/menus/{menu}/itens`.
- Tabelas (schema do módulo, `uuidv7()`):

| Tabela | Colunas |
|---|---|
| `menus` | `id`, `slug` único (case-insensitive), `nome`, `descricao` nullable, timestamps |
| `menu_items` | `id`, `menu_id` FK cascade, `parent_id` FK null (auto-referência), `position` int, `type` enum (`route|url|page`), `target` jsonb, `label` jsonb (LocaleCast), `icon` nullable (Lucide), `active` bool, `opens_new_tab` bool, timestamps |

- Regras: árvore de **até 3 níveis**; ciclo proibido; excluir pai com filhos bloqueado; reordenar
  por `position` com renumeração determinística; `route` só aceita rota existente e da área Portal;
  `url` só `http(s)` same-origin ou externo explícito (sem `javascript:`/`data:`); `page` referencia
  id de página publicado (P5; até lá, tipo desabilitado).
- Porta Core `Core\Contracts\Navigation` (nome provisório) com `tree(string $menu): array` e
  `flat(string $menu): array`; o módulo registra a implementação no `register()`; o `layout`
  consome a porta e **não importa o módulo**. Sem módulo ativo → menu vazio + log (nunca 500).
- Render: `<nav aria-label>`, `<ul>/<li>`, `aria-current="page"`, link atual destacado, skip link
  preservado, ícones via `<x-ui::icon>` (Lucide), nenhum link morto (`Route::has`), links internos
  com locale (ADR-012; `route()`/`localized_url()`).
- Teclado: foco visível, submenu abre/fecha sem JS (`<details>` ou popover), sem hover-only.
- Cache do menu por `menu` + locale, invalidado na escrita.

### S4.1 — Layout por área/módulo (D6)

- Ação **“Configurar layout da área/módulo”** disponível nas telas de módulo para quem tem a
  permissão da superfície de layout (Suporte/admin), não uma permissão nova por módulo.
- Abre a mesma superfície de `LayoutProfile`/slots, escopada à rota/módulo; edita **posições de
  widgets**, não blocos de conteúdo. A ordem é a pilha do slot; sem posição absoluta/pixel.
- Precedência: perfil da área/módulo → configuração global definida pelo Suporte **na área de
  Suporte** → default do preset. Sem perfil, o global vale; widget de módulo inativo não aparece.
  A **UI** da área de Suporte que edita o perfil global `*` é **P3** — esta fatia só entrega a ação
  nas telas de módulo e o fallback (P1.S4b).
- O módulo só declara o escopo e os `WidgetSource` que oferece; `layout` não importa módulo.
- Ação registrada em `config/layout.php`/settings e auditada; cache invalidado na escrita.

### S5 — SEO de base e identidade publicada

- `title_suffix`, descrição padrão, imagem social padrão (Media → `<picture>`/`og:image`),
  `verification` e `analytics_id` lidos de settings pelo `services/Seo`.
- `robots.txt`/`sitemap.xml` continuam dinâmicos e com `ETag`; áreas restritas invisíveis (ADR-043).
- Nenhuma requisição externa nova; GA só com ID configurado e consentimento (ADR-002/019).
- Teste: sem settings, o `<head>` é idêntico ao atual; com settings, o `@graph`/canonical não
  quebram; imagem social usa Media (nunca URL crua).

### S6 — Fechamento

- E2E: trocar tema → portal muda sem deploy; trocar posição da sidebar → foco/ordem; editar menu →
  navegação do portal; remover item → some sem quebrar rota.
- `make check` 2× + `make check E2E=1`; axe desktop/mobile; sniffCSS estrito.
- Atualizar `INVENTARIO.md` e a tabela de estado.

## 4. Fora de escopo

- Home por seções, páginas estáticas, banners e widgets (**P5**).
- Usuários/perfis (**P2**) e suporte (**P3**).
- Upload/edição de imagens (Media já existe; o editor só referencia `media_id`).
- Editor de CSS/HTML, posição absoluta, “construtor de site” livre.
- Multi-tenant/regional (decisão de produto separada).

## 5. Fatias de sessão

| Sessão | Entrega | Prova | Depende |
|---|---|---|---|
| **P1.S1** | seções `theme`/`cms`/`seo`/`institution` no schema + leitura com fallback | precedência e valor inválido; sem settings = comportamento atual | P0 |
| **P1.S2** | editor de tema + `/theme.css` revalidando | mudança de token no portal; ETag muda; axe | S1 |
| **P1.S3** | editor do shell + `CmsShellResolver` por settings | 3 posições, regiões, inválido ignorado; DOM/foco | S1 |
| **P1.S4** | módulo Menus + tabelas + porta Core + render | árvore 3 níveis, ciclo, link morto, locale, teclado | S1 |
| **P1.S4b** | ação “Configurar layout da área/módulo” + fallback global | posição de widget por área; global quando ausente; módulo inativo | S1 |
| **P1.S5** | SEO base + identidade publicada | head sem settings idêntico; com settings, `@graph`/imagem ok | S1 |
| **P1.S6** | E2E, docs, 2× check | evidência no `kd` | S2–S5 |

## 6. Contratos e dados

- **Settings:** seções `institution`, `theme`, `cms`, `seo` (schema P0).
- **Porta:** `Core\Contracts\Navigation` (ou `MenuRegistry`), implementada pelo módulo `Menus`.
- **Tabelas:** `menus`, `menu_items` (migrações do módulo).
- **Rotas:** `configuracoes.cms.*`, `menus.cms.*`.
- **Permissões:** `configuracoes.ver/editar`, `menus.ver/editar`.
- **Cache:** `settings.{section}`, `navigation.{menu}.{locale}`, `theme.css` por `ETag`.
- **Eventos:** `configuracoes.updated`, `menus.updated` (auditoria + log + span).

## 7. DoD

- [x] Identidade, tema, shell e menus são **dado** editável no painel, com schema e auditoria.
- [x] Nenhum CSS/HTML no banco; nenhum `<style>` inline; `theme.css` same-origin com `ETag`.
- [x] Menu sem link morto, com locale, árvore máxima de 3 níveis e ciclo bloqueado.
- [x] Ação “Configurar layout da área/módulo” edita só posições de widgets, com fallback global do Suporte.
- [x] `layout` não importa módulo; a porta Core é o único caminho.
- [x] A11y: axe zero A/AA; foco = ordem visual; menu opera sem JS; contraste preservado.
- [x] 4 locales em todas as telas; nenhuma string visível hardcoded.
- [x] `make check` 2× + `E2E=1`; cobertura ≥90%; PHPStan max 0.

## Fechamento — ficha de revisão

> **Obrigatório antes de fechar:** criar `_to_revision/P1-estrutura-site-cms.md` e seguir
> `_to_revision/README.md` + `_to_revision/PROTOCOLO.md` (v2.0). A ficha não é gate de build; é a
> entrada da fila de revisão profunda.

- Cabeçalho: fase, `kd` épico/tasks/slices, âncoras (este arquivo, ADRs e código) e status
  `⬜ pendente` → `🟡 em revisão` → `✅ revisado` / `🔧 com correções`.
- Conteúdo: o que foi implementado, arquivos/superfícies a revisar, checklist D1–D14 aplicável,
  riscos e observações.
- Fechamento: preencher “Notas da revisão” com data/revisor/resultado e achados (`kd write
  --anchor`), atualizar a fila em `_to_revision/README.md` e `_to_revision/_inventario.md`.
- Achado com correção exige teste vermelho→verde; a fase só é `closed` no `kd` com a ficha
  registrada.

## 8. Riscos

| Risco | Mitigação |
|---|---|
| Tema quebrar contraste/legibilidade | schema fechado, amostra com axe, `@layer a11y` por último, nenhum CSS livre |
| Menu virar árvore infinita/ciclo | limite de 3 níveis + validação de pai + teste de ciclo |
| Link interno perder locale | `route()`/`localized_url()`; teste de invariante de URL |
| Porta Core virar dependência circular | módulo registra a implementação; layout só resolve o contrato |
| Shell mudar estrutura e quebrar a11y | posição é CSS + DOM acompanha; teste de foco/ordem |
| Cache de menu stale | invalidação síncrona na escrita + teste de dois requests |

## 9. Retomada

```bash
kd prime
kd ask "tema shell menus configurações estrutura"
kd task list --ready
```

Ler: P0 → `src/layout/Engine/*` → `src/layout/Cms/Shell/*` → `src/services/Layout/*` →
`src/core/Contracts/WidgetRegistry.php` → `src/core/Internal/Navigation/PanelMenu.php` →
`wiki/plan/10-layout-engine-slots.md` e `wiki/plan/16-front-builder-widgets-banners.md`.

Referências irmãs (UX/desenho, não código): `../cms/core/Area/Super/Pages/ManageTenantTheme.php`
(identidade/tema), `../lotep/V2/Modules/Support/SupportConfigs/**` e
`../sucesu-cms/V2/Modules/Support/SupportConfigs/**` (categorias de configuração e tema). Rejeitar
Filament, key/value livre, `config.json` e fail-open.
