# F3.I2.T7 — Contrato de shell aplicado aos módulos + teste estrutural (um shell, sem html/aside/style)

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

## O que foi implementado

- `LayoutContractTest`: toda view de módulo usa **um** shell e não contém `<html>/<head>/<style>/style="…"`/`x-app-layout`/slots de região.
- Aplicado a `Noticias`, `Demo`, `Blueprint`; stubs de view reescritos para o contrato.

## Arquivos para revisar

- `tests/Feature/LayoutContractTest.php`
- `src/modules/Publicacoes/Noticias/Layout/Views/**`
- `src/modules/Desenvolvimento/Demo/Layout/Views/**`
- `src/modules/Exemplos/Blueprint/Layout/Views/**`
- `src/core/Stub/Module/Layout/Views/view.blade.stub`
- (removido) `src/core/Stub/Module/Layout/Views/cms/page.blade.stub`

## 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 teste é um **gate**: comentário com literal do shell conta no `substr_count`.
- [ ] Views CMS não declaram `<h1>` (título é do shell).

## 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-18 | agente | 🔧 | **A** (alto) `BlueprintSaudacaoAction` devolvia string visível **fixa em pt_BR** (a view renderiza `{{ $mensagem }}`; `ModuleI18nTest` só varre views → o literal escapava) → chave `blueprint::index.greeting` nos 4 locales + `__()` na Action; teste vermelho→verde `BlueprintTest::test_the_greeting_follows_the_url_locale` (`fact_01k79vkx`); **B** (médio) o gate contava o literal do shell em **comentários Blade** (falso negativo: view sem shell passa se um comentário citar `<x-layout::portal>`) → `countShells()` remove `{{-- --}}` antes de contar + teste `the_shell_count_ignores_blade_comments` (`fact_00dxj3xq`); **C** (médio) `src/core/Stub/README.md` citava `cms/page.blade.stub` (removido) → corrigido (`fact_01y6273z`); **D** (médio) `partials/cover.blade.php` usa `<img>` cru sem `width/height/decoding` na URL externa legada (ADR-026) → task `task_01doc455`; **E** (médio) o gate cobre só `src/modules/*` — views de página de service (`src/services/Layout/View/cms/*`) ficam sem gate → task `task_01kyxn03`; **F** (baixo) `Demo/public/show.blade.php` resolve `Hasher` via `app()` e calcula hash na view → task `task_01hpxxr3`; **G** (baixo) FQN de classes em Blade → task `task_01o3tbg3`. Evidência: 16 testes/537 asserções OK; `make check` verde (20 OK · 0 FAIL). |

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