# F3.I2.T6 — Remover legado: zero CDN, Alpine/Livewire, print DOM, style inline; um caminho de impressão

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

## O que foi implementado

- Removidos: CDN (Google Fonts/GA/qrserver), Alpine/Livewire, motor “shadow print DOM”, `<style>`/`style="…"`/`!important`; um único caminho de impressão (`@media print`).
- `app_version()` inexistente → `SystemVersion`.

## Arquivos para revisar

- `resources/css/app.css` (deletado)
- `resources/js/app.js` (deletado)
- `resources/js/bootstrap.js` (deletado)
- `.pi/trash/resources-dead/**`
- `src/layout/Portal/Assets/css/print.css`
- `src/layout/Portal/partials/*.blade.php`
- `package.json` (axios/concurrently removidos)

## 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] Nenhuma referência residual a CDN/Livewire/Alpine — **inclui a CSP**: o `PlatformArea` ainda carregava `cdn.jsdelivr.net`/`fonts.googleapis.com`/`gstatic`/`unsafe-eval`/nonce (corrigido).
- [x] Sem `style=`/`!important` no tema do Portal/CMS (só em comentários).

## 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) | 🔧 | 2 bloqueadores (A/C) + 2 altos (B/D) corrigidos com teste; 1 baixo (E). O T6 deixou passar o **legado de CSP/nonce/CSRF** no HTML público. |

### Evidência
- **Gates (§10):** `make quality-2x` EXIT=0 (**410×2** — +3 `SecurityHeadersTest`) · PHPStan max `No errors` · `make e2e` **87/87** (+6: novo `14-security-headers`) · `SNIFF_A11Y_STRICT=1 make a11y` OK · `make check-gates` OK.
- **Antes/depois (curl):** antes — `nonce="…"` **diferente a cada request**, `<meta name="csrf-token">` por request e CSP do app com `cdn.jsdelivr.net`/`fonts.googleapis.com`/`unsafe-eval`; depois — HTML **determinístico** (2× mesmo sha256), sem token/nonce, CSP do edge estrita e `frame-ancestors *` só no embed.

### Achados

| ID | Sev. | Dimensão | Descrição | Disposição |
|---|---|---|---|---|
| **A** | Bloqueador | D3/D11 | `EnforceSecurityHeaders` chamava `Vite::useCspNonce()` → o HTML público trazia `nonce="…"` **por request** (varia o corpo → quebra `ETag`/cache, ADR-022) e era inútil (a CSP do edge não usa nonce). | **Corrigido**: nonce removido do app. `fact_00yn17bh`. |
| **B** | Alto | D5/D11 | `PlatformArea::getCspPolicy()` mantinha a CSP **legada**: `cdn.jsdelivr.net`, `fonts.googleapis.com`/`gstatic`, `unsafe-eval`/`unsafe-inline`, nonce — fora da allowlist (ADR-002) e contra a CSP estática do edge (ADR-020/022, spec 16). O `vendor-check` não varre `.php`, então não pegava. | **Corrigido**: CSP do app removida (edge é o único dono). `fact_017r3ops`. |
| **C** | Bloqueador | D3/D11 | O shell do portal emitia `<meta name="csrf-token">` por request → HTML **não-determinístico** e sessão anônima (ADR-022 nomeia este caso). Nenhum JS do portal lia a meta; o único POST (LGPD) usa `@csrf`. | **Corrigido**: meta removida. `fact_00j4lvy1`. |
| **D** | Alto | D11/D2 | O `AllowIframe` (rota `noticias.embed`) setava `frame-ancestors *` no app, mas o **Caddy sobrescrevia** a CSP e o `X-Frame-Options` do app bloqueava → o embed **não** era embutível (feature quebrada). | **Corrigido**: exceção movida para o edge (`@embed`/`@notEmbed`); `AllowIframe`/alias/middleware e `X-Frame-Options` do app removidos. `fact_01hf6gca`. |
| **E** | Baixo | D13 | Não havia gate para a CSP (`vendor-check` só varre `.blade.php/.js/.ts/.css/.html`). | Novo `e2e 14-security-headers` (gate estrutural). `risk_00b7wcy7`. |

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