# F3.I2.T1 — Slots/regiões e presets determinísticos

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

## O que foi implementado

- `Layout\Engine\LayoutPreset`/`PresetConfig` (enum tipado) e `SlotRegistry`/`SlotProvider`/`SlotConfig` (ordem determinística).
- Regiões do portal (`main-header`/`main-footer`/`main-left`/`main-right`) e colapso de região vazia.

## Arquivos para revisar

- `src/layout/Engine/LayoutPreset.php`
- `src/layout/Engine/PresetConfig.php`
- `src/layout/Engine/SlotRegistry.php`
- `src/layout/Engine/SlotProvider.php`
- `src/layout/Engine/SlotConfig.php`
- `src/layout/Components/slot.blade.php`
- `src/layout/Portal/regions/*.blade.php`
- `src/layout/LayoutServiceProvider.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

- [ ] Ordem de slots/prest stable (sort explícito, empate por token).
- [ ] Prest inválido nunca quebra o shell (fallback).

## 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) | 🔧 | **A** (médio) corrigido (task `error_01bnpvx3` fechada); **C** teste adicionado; **B/D** baixos registrados |

### Evidência
- **Gates (§10):** `make quality-2x` EXIT=0 (**403×2** — +1 teste) · PHPStan max `No errors` · Pint/Prettier/ESLint PASS.
- **Determinismo (o cerne da task):** `LayoutEngineTest` cobre ordem explícita, empate por classe, visibilidade, limite e slot vazio — todos **verdes**. Fallback de preset inválido confirmado.

### Achados

| ID | Sev. | Dimensão | Descrição | Disposição |
|---|---|---|---|---|
| **A** | Médio | D1/D14 | `SlotConfig::$order` **nunca era lido**: `SlotRegistry::resolve()` ordenava só por `provider->order()` + classe. O plano 10 §3 promete "`SlotRegistry` … respeita `SlotConfig` (visível? **ordem**? limite?)" e §5 o teste "provedor aparece na **ordem configurada**". | **Corrigido**: `SlotConfig::$order` virou **mapa** `class-string<SlotProvider> => int`; `resolve()` aplica o override por provedor com fallback em `order()`. Teste `LayoutEngineTest::it_applies_the_configured_provider_order` (vermelho→verde). Task `error_01bnpvx3` **fechada**; `kd`: `fact_00zikw2l`. |
| **B** | Baixo | D12/D14 | `PresetConfig::$label` é **pt_BR fixo** e **nunca lido** (dead code). Se o CMS exibir presets (plano 10 §5), deve virar **chave i18n** (ADR-012). | Nota: `risk_00jse6xz`. |
| **C** | Baixo | D13 | Faltava teste para o ponto explícito da ficha "**preset inválido nunca quebra o shell (fallback)**" (o comportamento já existia em `portal.blade.php`, sem cobertura). | **Corrigido**: `LayoutShellTest::an_unknown_preset_falls_back_to_the_default_preset`. Nota: `fact_0077dcys`. |
| **D** | Baixo | D6 | `SlotRegistry` instancia provedores a cada `resolve()`/`has()` (o shell chama `has()` e o slot chama `resolve()` → **dupla instanciação** por request); sem cache. | Nota: `risk_003dknza`. |

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