# F3.I2.T3 — Shells como componentes (props padrão + slot único; Engine na raiz; grid em CSS)

| Campo | Valor |
|---|---|
| **Fase** | F3 — Layout & UX |
| **Issue** | F3.I2 — Layout engine com slots (plano 10 · ADR-016) |
| **kd task** | `task_00mww34h` (`closed`) |
| **Âncora** | `wiki/implementation/03-layout-ux.md` |
| **Status da revisão** | 🔧 com correções — A (alto) corrigido; B/C/D baixos registrados |

## O que foi implementado

- `<x-layout::portal>` e `<x-layout::cms>` com `@props` de defaults determinísticos e **um** slot de conteúdo.
- Grid **em CSS** (mobile-first `fr`+`gap`; `.align-rows` via subgrid com `@supports`); `@extends` legado removido.

## Arquivos para revisar

- `src/layout/Components/portal.blade.php`
- `src/layout/Components/cms.blade.php`
- `src/layout/Portal/partials/*.blade.php`
- `src/layout/Portal/regions/*.blade.php`
- `src/layout/Cms/dashboard.blade.php`
- `src/layout/Cms/partials/*.blade.php`
- `tests/Feature/LayoutShellTest.php`
- `tests/Feature/LayoutContractTest.php`

## Pontos de revisão

### PHP 8.4
- [ ] `declare(strict_types=1)`; tipos nativos (union/enum/`readonly`); sem `mixed` desnecessário.
- [ ] Dependências **injetadas** (nenhum `new`/`app()`/`resolve()` no corpo); sem traits/abstract/generics.
- [ ] `match` em vez de cadeias de `if`; `?->`/`??`; sem código morto; PHPStan `max` limpo.

### Laravel 12
- [ ] Nativo primeiro (Eloquent casts/scopes, `FormRequest`, `Gate`, scheduler, events) — sem reimplementar.
- [ ] Sem closure em rota; resources/rotas nomeadas; `config()` só lê, `env()` só em `config/`.
- [ ] Migração **idempotente**; `down()` → `backup_*` (nunca `DROP`); sem `orderBy` cru quando `latest/oldest` serve.

### HTML semântico + a11y (eMAG/WCAG 2.2 AA)
- [ ] Um `<main>`, headings em nível correto, landmarks; só ARIA quando necessário.
- [ ] Alvos ≥ 24 px (≥ 44 px em ponteiro grosso); foco visível; rótulos em todos os campos; contraste (axe).
- [ ] Funciona **sem JS**; `sniffCSS` sem `fail`/`warn` fora do ledger.

### CSS moderno
- [ ] `oklch`, `clamp`, `rem`/`fr`/`dvh`/`%`; `@layer` (a11y por último); `@property`; subgrid com fallback.
- [ ] Sem `!important`, sem `style="…"` inline, sem hex/rgb (exceto justificado); sem CDN.

### Decisões do projeto (ADRs)
- [ ] Respeita a ADR do escopo (ver cabeçalho) e não escapa das demais (001, 002, 004, 013, 015…).
- [ ] Determinismo: relógio/id injetados, ordem explícita, dados idempotentes (ADR-001).

### i18n (ADR-012)
- [ ] Nenhuma string visível fixa; 4 locales com paridade de chaves; `route()`/`localized_url()` (nunca path literal).

### Observabilidade/segurança/testes
- [ ] Span + `log_event('{module}.{action}')` com atributos `scalar|null`, **sem PII** (ADR-024).
- [ ] Teste que falha sem a mudança (vermelho→verde) + evidência no `kd`; `make quality` 2×.

### Específicos desta task

- [ ] O módulo preenche **só** o centro; campos padrão (title/description/breadcrumbs/canonical/robots/image/schemas).
- [ ] Grid sem medição JS (CLS 0); ordem de foco = ordem visual.

## Comandos úteis

```bash
make quality            # lint + PHPStan max + gates + testes
make e2e                # Playwright + axe (se a task toca UI)
make check-boundaries   # fronteiras core/services/layout/modules
```

---

> Revisar contra `AGENTS.md`, `wiki/adr/` e o plano citado. Registrar achados no `kd` (`kd write --anchor`) e, se houver correção, teste de regressão.

## Notas da revisão

| Data | Revisor | Resultado | Observações |
|---|---|---|---|
| 2026-10-17 | agente (protocolo v2.0) | 🔧 | 1 alto corrigido (A) com teste vermelho→verde (PHPUnit + e2e); 3 baixos no `kd` |

### Evidência
- **Gates (§10):** `make quality-2x` EXIT=0 (**407×2** — +1 teste) · PHPStan max `No errors` · `make e2e` **79/79** · `SNIFF_A11Y_STRICT=1 make a11y` OK (`fail=0`).
- **Contrato de estrutura:** `LayoutContractTest` (um shell por view, sem `<html>`/`<head>`/`<style>`/`style=`/`<h1>` de módulo) verde; grid em CSS (`data-preset`/`data-left`/`data-right`); DOM = visual verificado no e2e (sidebar.x < main.x).

### Achados

| ID | Sev. | Dimensão | Descrição | Disposição |
|---|---|---|---|---|
| **A** | Alto | D7 | O `<main id="cms-content">` do shell do CMS **não tinha `tabindex="-1"`**, embora o portal tenha (`main#main-content`) e o e2e o asserciasse (`support/portal.ts:47`). Sem alvo focável, o skip-link do painel não movia o foco ao conteúdo (spec 04 §4 "`main` recebe o foco"; WCAG 2.4.1) — e **sem gate** cobrindo o CMS. | **Corrigido**: `tabindex="-1"` no `<main>` do CMS; testes `LayoutShellTest::the_cms_shell_content_receives_focus_for_the_skip_link` e asserção em `07-cms-shell.spec.ts` (vermelho→verde). `kd`: `fact_00ktzi0u`. |
| **B** | Baixo | D3 | Rodapé usa `now()->year` direto (ADR-001 pede `Core\Contracts\Clock` injetado). | Nota: `risk_003v3cyz`. |
| **C** | Baixo | D2/D14 | Brand do CMS usa `url('/')` (não a rota nomeada) e repete o literal `config('app.name').' · CMS'` em `cms.blade.php` e `header.blade.php`. | Nota: `risk_00h2gtga`. |
| **D** | Baixo | D7 | `<x-cms::card>` renderiza o título como `<header>` (sem `heading` `h2`) — seções do card sem semântica de título. | Nota: `risk_006skhy0`. |

> Resultado: `✅ revisado` · `🔧 com correções` · `⏭️ delegado` (com justificativa).
