# F3.I2.T5 — Overrides de tema limitados por schema (escalas/enums; valor livre não quebra o layout)

| Campo | Valor |
|---|---|
| **Fase** | F3 — Layout & UX |
| **Issue** | F3.I2 — Layout engine com slots (plano 10 · ADR-016) |
| **kd task** | `task_012jkcyo` (`closed`) |
| **Âncora** | `wiki/implementation/03-layout-ux.md` |
| **Status da revisão** | ✅ revisado — sem correções de código; 3 baixos + 2 observações no `kd` |

## O que foi implementado

- `config/design.php` + `Engine/ThemeSchema`: só tokens em escalas/enums ou cor CSS **validada**.
- Valor fora do schema é **ignorado** (camada anterior vence) — o painel nunca quebra o layout.

## Arquivos para revisar

- `config/design.php`
- `src/layout/Engine/ThemeSchema.php`
- `src/layout/Engine/ThemeResolver.php`
- `src/layout/Engine/ThemeTokens.php`
- `src/layout/Http/ThemeStylesheetController.php`
- `tests/Feature/ThemeOverrideTest.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

- [x] Validação de cor (hex/rgb/hsl/oklch/…) sem aceitar injeção de CSS — **nomes** (`red`, `white`, `transparent`, `currentColor`) são **rejeitados por projeto** (schema fechado); o item original dizia “nome” (corrigido aqui).
- [x] Campo desconhecido é descartado silenciosamente (`ThemeResolverTest`, `ThemeOverrideTest`).

## 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) | ✅ | Nenhuma violação de invariante/ADR/segurança. 3 baixos + 2 observações registrados; ficha (item “nome”) corrigida. |

### Evidência
- **Gates (§10):** `make quality-2x` EXIT=0 (**407×2**) · PHPStan max `No errors` · `make docs-check` OK.
- **D11 — injeção:** `isColor` rejeita `; { } < > \`, `url(`, `expression(`, `@`; a rota `/theme.css` é `global` (sem prefixo de locale) e o corpo depende só de config (sem input do pedido). Defesa em profundidade pelo `@property` (`<color>`).
- **D1/D2:** `strict_types`; `match`; `final`/`readonly`; sem trait/abstract; rota sem closure (`route:cache` ok); `env()` só em `config/`.
- **D13:** `ThemeOverrideTest` (8) + `ThemeResolverTest` (4) cobrem schema, injeção, escala, cadeia de precedência (inclui URL), cache/`ETag`/304, `@layer overrides` e link condicional.

### Achados

| ID | Sev. | Dimensão | Descrição | Disposição |
|---|---|---|---|---|
| **A** | Baixo | D14 | O item da ficha dizia “oklch/rgb/hsl/**nome**”, mas `ThemeSchema::isColor` **rejeita cores por nome** (`red`/`white`/`transparent`/`currentColor`) — comportamento correto (schema fechado, `decision_0012b86g`). | Item da ficha corrigido; nota `risk_01aws2yx`. |
| **B** | Baixo | D2 | O shell liga `/theme.css` quando `design.overrides` está **preenchido**, mesmo se todos os valores forem inválidos → request extra sem efeito. | Nota `risk_01l9ajqq`. |
| **C** | Baixo | D11 | `/theme.css` com `Cache-Control: public, max-age=3600` e URL fixa: mudança do painel leva até 1 h para aparecer (sem cache-busting). | Nota `risk_00qafbph`. |
| **D** | Observação | D1 | `ThemeResolver` é documentado como “puro” mas lê `config()` em `defaults()`/`ThemeSchema::keys()`. | Nota `fact_01flzjs5`. |
| **E** | Observação | D14 | ADR-016 §47 lista borda/densidade/espaçamento; o schema do tenant cobre só cor/raio/tipografia (o resto é do `PresetSchema` futuro). | Nota `risk_01115fpq`. |

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