# F3.I4.T3 — Views com __(token::…) + teste estrutural das 4 locales

| Campo | Valor |
|---|---|
| **Fase** | F3 — Layout & UX |
| **Issue** | F3.I4 — i18n no módulo (ADR-012) |
| **kd task** | `task_01qhjspf` (`closed`) |
| **Âncora** | `wiki/implementation/03-layout-ux.md` |
| **Status da revisão** | 🔧 com correções |

## O que foi implementado

- Views (módulos e services Media/Security) usam o próprio namespace; manifesto guarda **chaves** (`token::menu.title`).
- `ModuleI18nTest` prova 4 locales, paridade, ownership e ausência de texto fixo; `LocaleUrlInvariantTest` prova o invariante na saída.

## Arquivos para revisar

- `tests/Feature/ModuleI18nTest.php`
- `tests/Feature/I18nTest.php`
- `src/layout/Lang/**`
- `src/services/*/Lang/**`
- `src/modules/*/*/Lang/**`
- `src/core/Localization/Locales.php` (`label()`)

## 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

- [ ] `localized_url()` no Core + gate contra path literal (`url('/x')`, `href="/x"`).
- [ ] `hreflang`/`x-default` no shell; datas via `intl`.

## 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) o corpus `fr` tinha **22 palavras sem diacrítico** (~30 strings) em 5 arquivos (`Noticias/{index,menu,show}`, `Media/viewer`, `Security/card`, `Demo/show`) — o teste de paridade só compara **chaves**, nunca valores, e a F3.I2.T8 já tinha corrigido o mesmo defeito na Home → corrigido (render conferido nos 4 locales) + **gate novo** `translations_keep_their_diacritics` (denylist por **palavra inteira**; vermelho com 49 ocorrências → verde) — `fact_00v3mbcq`, `fact_00z47w2y`; **B** (médio) o gate de texto fixo **não cobria as views de service** — que a própria ficha nomeia — nem os paths internos delas → estendido (`module_and_service_views_have_no_hardcoded_visible_text` + service views no check de paths), provado vermelho injetando texto em `Security/View/security-data-card.blade.php` — `fact_00np6o65`; **C** (médio) `LocaleUrlInvariantTest` rastreia só `/en` e `/en/noticias` → `task_01b6e7df`; **D** (baixo) `visibleTextNodes()` não trata parênteses aninhados em diretivas (`@if (filled(config(…)))`) → falso positivo latente (`risk_001dbwjw`); **E** (obs) as views do shell **Portal** não têm gate de texto fixo (só CMS partials/components) (`risk_007kcri0`). Verificados **limpos**: corpora `pt_BR`/`en`/`es` (varredura palavra a palavra), zero path literal (`url('/x')`/`href="/x"`), `localized_url()` nos call sites, `hreflang`/`x-default` no `services/Seo`, datas via `intl`, paridade de chaves (27 arquivos × 4 locales), ownership do namespace, elisão francesa (`d'`, `l'`, `qu'`) e isenção `Lang/*` no `name-check`. |

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