# SEED engenharia — Componentes v1.43 (**SI-31, 2026-09-05: o seletor de identidade do §65 ACOMPANHA a GL-R2 — 31 glifos, grade de 6 colunas com 5 linhas cheias + 1 célula, bancada `banco-identidade.html` v0.4; a régua SI5 passa de "linhas inteiras" a "linhas inteiras EXCETO a última", por decisão dele (MANIFESTO §150, verbatim: *"T-SET: glifo novo + régua da grade aceita última linha incompleta (recomendado)"*).** Por quê: 31 é primo — nenhuma grade de 2 a 30 colunas fecha em linhas inteiras; a régua da v1.40 era consequência de 28 e de 30, não princípio. Na última linha incompleta, célula sem vizinha abaixo: ↓ deixa o foco onde está — padrão WAI-ARIA APG para grid (consultado em 2026-09-05: *"If focus is on the bottom cell in the column, focus does not move"*); até a v0.3 o script mandava o foco à última célula, trocando de coluna sem avisar (o mesmo valia para ↑ na primeira linha — corrigido junto, exposto a veto). `gen-banco-identidade.py` v0.4 (`TOTAL_GLIFOS = 31`; tinha ABORTADO em 30, como devia), `suite-identidade.mjs` **33 · 0 · 0 · 1** (nasce a SI-OP3c, que mede a última linha; eram 32), MD5 da bancada `26023b0632d65e7e21b974e839e2617c` idêntico em duas gerações. Registro completo no §65.13.) · v1.42 (**GL-R2, 2026-09-05: dois glifos pelos modelos em imagem dele (MANIFESTO §150) — nasce `transmission-tower` (torre de transmissão; destaque Subestações; 30 → 31 glifos ativos) e o `generator` é REDESENHADO (mesmo nome, mesma célula; supersede em §45.4).** Vereditos verbatim: *"T-TORRE: segue anexo imagem de uma torre para utilizar. use como modelo"* · *"T-GMG: redesenhar agora — segue foto anexa de exemplo para usar"* · *"T-TRACO: 1,25 (recomendado)"*. Os anexos não estão em disco; a descrição deles registrada no §150 foi o modelo. Densidades desenhadas no grid 24 / traço 2 / round-round e renderizadas a 24 px REAIS (captura do `<svg>` no Chrome, DPR 1, posição inteira, ampliação 6× sem suavização) em `preview/gate-apoio-glifos-r2/glifos-r2.html`: torre D1 (uma travessa + X — a T1 do estudo §149), D2 (duas + X), D3 (três travessas decrescentes + X + chão — o modelo completo); gerador G1 (contêiner + chaminé + porta + raio), G2 (tudo como na foto, indicadores no alto), G2b (raio grande + indicadores no painel da porta + pés). Régua decisiva, MEDIDA na captura de 24 px: pelo menos 1 px de fundo entre elementos vizinhos (cobertura de tinta abaixo de 50 %). Aplicados **D3** — perfil da coluna central x = 12: topo, três travessas, cruzamento do X e chão separados por 1 / 1 / 1 / 2 / 2 px; só coube com as travessas em y 6 / 9 / 12 (coordenada INTEIRA: com traço 2 a linha ocupa exatamente 2 linhas de pixel; a T1 em 8,5 esparramava em 3) e o X começando 2 unidades abaixo da última travessa (14 → 21) — a 1ª tentativa, com o X partindo da travessa, fundiu os dois numa corrida de 5 px; e **G2b** — indicadores `r 0,5` preenchidos medem 3 px e ficam a 1 px da parede e da porta; o desenho antigo tinha dois pontos `r 1` a 3,5 de distância, que com traço 2 se sobrepunham (o "blob" da filmadora). Escolhas EXPOSTAS A VETO com as demais densidades renderizadas ao lado; D2 (vãos 1 / 2 / 4 px) é a reserva declarada se ele achar os vãos de 1 px da D3 finos demais. `getBBox` medido no Chrome: torre 2,5…21,5 × 3…21, gerador 3…21 × 2…22 (piso IC2 1…23); pontas todas em .0/.5. Aliases da torre: torre, alta-tensao, transmissao, linha, lt — `subestacao` continua da `substation`, que permanece no set (a cabine ainda é objeto legítimo do domínio; só o destaque troca de glifo). Lucide `main` sem `transmission-tower` (404 em 2026-09-05). Denominadores no MESMO movimento: bancada `banco-icones.html` 31 (três strings), `suite-icones.mjs` IC-01 = 31, `gen-destaques-instagram.py` guarda 31 e `subestacoes` → `transmission-tower`, mais o traço óptico do destaque `TRACO_DESTAQUE = 1,25` (T-TRACO; o set segue traço 2 a 24 px — o parâmetro vive só na extração para a peça). Placares MEDIDOS: `suite-icones.mjs` 25 verdes · 0 vermelhos (IC-01 = 31) · `render-icones.mjs` 5 · 0 (R-01 getBBox 31/31 dentro de 1…23). O seletor de identidade §65 acompanha em movimento próprio (T-SET: última linha incompleta aceita).**) · v1.41 (**IC-P2, 2026-09-05: a coluna "Lucide" do `solar-panel` no inventário §45.3 dizia `—`; o Lucide TEM `solar-panel` (conferido no `main` em 2026-09-05 pela frente FV-P5). Corrigida a linha por ordem dele (*"IC-P@: corrija"*, MANIFESTO §149). Só a coluna de referência muda — o desenho do set não.**) · v1.40 (**SI-30, 2026-09-05: o seletor de identidade do §65 ACOMPANHA o set do §45 — 30 glifos, grade 6 × 5 (era 7 × 4 = 28), bancada `banco-identidade.html` v0.3.** Executa a consequência que a v1.39 deixou registrada como não executada (*"30 não fecha em 7 colunas (6 × 5 fecha)"*) — consequência óbvia, aplicada pela sessão e EXPOSTA A VETO dele. O achado que vale mais que o número: o `INVENTARIO` do `gen-banco-identidade.py` era lista de 28 nomes copiada à mão do §45.3 (só desenhos, categorias e aliases vinham da bancada) e envelheceu em silêncio quando o set mudou; agora o inventário INTEIRO é DERIVADO de `banco-icones.html` (a fonte que a IC-01 valida e que o `gen-destaques-instagram.py` já lê) e reordenado na ordem do §45.3 — os 28 antigos saem na mesma sequência, `hard-hat` na 6ª e `solar-panel-plus` na 13ª; fica declarado só o denominador `TOTAL_GLIFOS = 30`, com abort se a bancada divergir. A régua do SI5 é linhas inteiras, não 7 colunas: 30 = 2·3·5, e 6 × 5 é o vizinho do 7 × 4 aprovado em 2026-08-17. `suite-identidade.mjs` 32 · 0 · 0 · 1 antes e depois (9 asserts mudam o valor medido; limiar da SI-OP3b passa a ser lido do DOM); `suite-icones` 25 · 0; `guarda-barra-provas` 9 · 0 · 15 · 24; `paridade-previews` 825 · 0 (209 · 15); regeneração dupla MD5 `a2f5a8591e070c69069c8e2d84839c74`; medido no Chrome: 30 células 44 × 44 em 5 linhas de 6, nenhuma vazia, nenhum glifo cortado. Detalhe e alternativas descartadas no §65.12; SI-P1 segue aberta.**) · v1.39 (**FV-P5, 2026-09-05: o set do §45 ganha dois glifos de domínio — `hard-hat` (destaque Obras) e `solar-panel-plus` (destaque SEED Plus); 28 → 30 glifos ativos.** Veredito dele, verbatim (MANIFESTO §147): *"FV-P%: pode criar"*. Origem: as capas de destaque do Instagram (`marca-seed.md` §12.31, MANIFESTO §146) usam o set §45 e Obras e SEED Plus saíam só com rótulo. Criados pela árvore NM4 ③ na régua IC1/IC3/IC5/IC6 — grid 24 × 24, traço 2, round/round, `currentColor`, coordenadas com 1 decimal; `getBBox` medido no Chrome: `hard-hat` 2,5…21,5 × 5…19 e `solar-panel-plus` 2,5…21,5 × 3…19 (piso IC2 é 1…23). Desenho PRÓPRIO; o vocabulário de mercado entrou só como referência de leitura (Lucide `hard-hat`, tags helmet/construction/safety, conferido em 2026-09-05; badges `-plus` do Lucide como `file-plus`/`shield-plus`). Sentido, pelo `sobreaseed.md`: Obras = execução em campo (§11, "+1.000 obras entregues"); SEED Plus = plano de manutenção para usinas solares (§6) — daí o painel (o objeto do plano) com o sinal de mais (o sufixo da marca), e não o sol, que é o glifo do pilar Energia Solar exibido AO LADO no perfil. Alternativas renderizadas e descartadas (folha `preview/gate-apoio-fv-p5/`): `crane` (guindaste — a 24 px lê como "T" com argola antes de guindaste) e `wrench-plus` (chave — lê "conserto/corretiva", e chave é vocabulário utilitário do Lucide, NM4 ②). Denominadores atualizados no mesmo movimento: `suite-icones.mjs` IC-01 28 → 30 (25/25 ✓), `render-icones.mjs` 30/30 em 1…23 (5/0), `gen-destaques-instagram.py` (guarda 28 → 30; `obras` → `hard-hat`, `seed-plus` → `solar-panel-plus`). ⚠ Consequência NÃO executada, decisão dele: o seletor de identidade do §65 (`banco-identidade`, grade 7 × 4 = 28, asserção SI5 `28 % 7 == 0`) segue com os 28 glifos de agosto — 30 não fecha em 7 colunas (6 × 5 fecha). Desenhos EXPOSTOS A VETO dele.**) · v1.38 (**§90 e §91 EMENDADAS no mesmo dia (2026-08-24, tarde), por informação nova dele: o app de campo com cadastro OFFLINE e sincronização — OS e ponto — é decisão tomada, não hipótese.** Isso NÃO contradiz a decisão (c) do gate, que guardou exatamente a peça comum aos dois mundos, mas revela o que faltava: nascem **OF10** (quatro estados por item — enviado · não enviado · **sincronizando** · falhou; a sincronização é evento COM RESULTADO que não some sozinho; o pendente é alcançável antes de sair do local; e nunca "sincronizado" otimista) e **OF11/§90.11** — a OF-P1 deixa de ser hipótese e ⚠ **o PONTO tem regra própria acima da LGPD**: a Portaria MTP 671/2021 admite marcação offline com sincronização posterior, mas exige criptografia de 128 bits em trânsito E em repouso, comprovante ao trabalhador por marcação em PDF com assinatura PAdES (peça que não existe no inventário — **PT-P1**) e registro que não se altera. ⭐ E a emenda resolve uma COLISÃO REAL entre duas cláusulas aprovadas no mesmo gate: a CE4 manda travar o objeto que gera documento com ART, e **não se adquire trava sem rede** — a **CE4-f** separa COLETA (offline-first, sem trava) de EMISSÃO (online, com trava), e isso não afrouxa nada, porque dado que chega por sincronização não se mescla em silêncio: entra com autoria e horário, e quem assina decide. Nasce ainda a **CE9**, o conflito detectado na SINCRONIZAÇÃO — chega em lote, tarde e sem ação em curso, então é aviso assíncrono e nunca `alertdialog`, nunca resolve sozinho, nunca descarta, e não vira trinta diálogos em sequência.**) · anterior v1.37 (**§90, §91 e §92 NOVAS (2026-08-24, F7.7-P5): os TRÊS ESTADOS que o §6.1 do mapa marcava ❌ AUSENTE desde a F7.1 passam a `estável` por cinco decisões dele no gate. **§90 OFFLINE (OF1–OF9)**: offline NUNCA se declara por flag — `navigator.onLine` devolvendo `true` não garante nada e desabilitar por ela travaria quem usa VPN, que é a nossa gente de campo — e são DOIS estados, "você está sem conexão" × "não foi possível alcançar o sistema", continuação direta da MC3 que ele decidiu na §89. A fila de ações fica como OF-P1 **com a obrigação de LGPD escrita ANTES** de alguém implementá-la, porque fila grava dado pessoal no dispositivo. **§91 CONFLITO DE EDIÇÃO (CE1–CE8)**: detectar é obrigatório e é do servidor (ETag/If-Match/412 da RFC 9110 — aviso na interface não impede perda, informa); regime padrão OTIMISTA com aviso de presença que não bloqueia, e ⭐ **exceção de TRAVA EXCLUSIVA para objeto que gera documento com ART**, porque quem assina responde pelo que está lá e conteúdo mesclado em silêncio é risco que a interface não pode criar; três saídas no conflito, com as destrutivas sob MC4 e MC5. **§92 LISTA LONGA (LL1–LL6), que FECHA A BU-P1**: a regra nasceu de COBAIA porque a lista mais longa do acervo tem 38 itens e não exercita o problema; limiar MEDIDO em **2.000 linhas** (um layout forçado custa 222–395 ms contra orçamento de 200 ms de INP); paginação é o padrão da lista de trabalho por ACHABILIDADE, não por desempenho; e ⭐ **contenção nativa rende 5,87×–25,47× em blocos e NADA em tabela** — mas `getComputedStyle` devolve `auto` nas duas, logo **esta regra não tem conferência por declaração, só por tempo** (LL-P1), terceira vez que a régua óbvia mente nesta casa.**) · anterior v1.36 (**§89 NOVA (2026-08-24, F7.7-P4): a MICROCÓPIA DE INTERFACE é `estável` por CINCO decisões dele no gate — MC1 rótulo = verbo + substantivo · MC2 sentence case · MC3 voz do erro IMPESSOAL (com "nós" só quando a falha é da SEED, porque o produto emite documento com ART) · MC4 confirmação com verbo específico (ele preferiu (a) à minha recomendação, e o custo enunciado no gate ficou escrito) · MC5 o corpo do diálogo declara a CONSEQUÊNCIA, não faz pergunta retórica. Mais 13 regras que a evidência ou o nosso canon já fechavam — a §10 da marca já proibia emoji e exclamação, então a §89 só propaga. A rodada de pesquisa (12 fontes, incluindo o guia de localização pt-BR da Microsoft e o guia do GNOME Brasil) mostrou que três "consensos" extraídos do produto medido eram DIVERGÊNCIA entre guias maduros: copiar o vizinho teria importado escolha alheia como lei. A §89.3 é a parte que só fonte brasileira resolvia: o registro verbal por elemento (infinitivo em botão, 3ª pessoa em tooltip, imperativo em instrução) e a distinção "não é possível" × "não foi possível". Sem guarda automatizada ainda — MC-P2 lista o que é mecanicamente verificável.**) · anterior v1.35 (**§88 NOVA (2026-08-23, VIGÉSIMA SEXTA parte): a **TELA A13 — DOCUMENTO/LEITURA** é `estável` por gate dele (*"aprovo"*) e **A F7.6 FECHA — 6 TELAS DE 6, produzidas em DOIS DIAS de gates encadeados** sobre o consolidado de 2026-08-22: A19 §83 · A20 §84 · A17 §85 · A18 §86 · A14 §87 · A13 §88 — seis suítes, **101 PASS · 0 FAIL** somados. A A13: canvas chapado C25 (o canvas É a tela), coluna de leitura 660px no registro da marca §4.1 (16px Light 1.65, "aproveitar a tela" recusado), figura com tokens de dado + fallback DG1, e **o PAPEL como contrato**: @media print no padrão DI com a TINTA VIGENTE — a errata do banco-dataviz-di virou LEI (a suíte reprova hex aposentado; o PDF real medido nos bytes: zero). RT/CREA/ART como placeholders de template (Lei 5.194). Divergência declarada: rota, não overlay. Da F7 resta a F7.7.**) · anterior v1.34 (**§87 NOVA (2026-08-23, VIGÉSIMA QUINTA parte): a **TELA A14 — FEED/ATIVIDADE** é `estável` por gate dele (*"aprovado"*), suíte **16 · 0 · 0**. A INVERSÃO C33 — exceção escrita do CP2 desde a F7.1 — aplicada pela primeira vez em tela, com a condição de admissão MEDIDA (agrupador sunken sem sombra e não-acionável; objetos = peças claras acionáveis); os DOIS PESOS do §56 em composição real (evento = linha com de→para inline; comentário = BLOCO não-cartão); e o **C27 timeline ganha a primeira spec** (fio + marcador; o do comentário distinto), fechando a lacuna "existe no painel sem spec". Escopo = P3 do consolidado: atividade GLOBAL (histórico de projeto/manutenção), não a central §33. Divergência declarada: rota, não overlay.**) · anterior v1.33 (**§86 NOVA (2026-08-23, VIGÉSIMA QUARTA parte): a **TELA A18 — CONFIGURAÇÕES** é `estável` por gate dele (*"aprovo"*). Era um dos DOIS arquétipos SEM leitura — fechado pela volta dirigida de 2026-08-22. A decisão que o consolidado deixou em aberto foi tomada e gateada: **DOIS REGIMES DE GRAVAÇÃO** — formulário salva EXPLÍCITO (barra de dirty; o botão confirma "Salvo." no lugar; salvar NUNCA desabilitado) e switch aplica IMEDIATO com anúncio (o §8 já dizia "configuração é Switch"). Mais: "Sair" isolado no rodapé da navegação · chip de estado que ESPELHA o grupo · preset Padrão/Personalizado com engrenagem condicional · indisponível com correção inline · lateral vira ABAS <600px (provado em navegador real). Contratos CF-01…CF-11 na `suite-configuracoes.mjs` (**18 · 0 · 1 [n/a] provado**) — incluindo **CF-11, o primeiro check de LGPD em suíte** (reprova se dado pessoal real entrar na demo). Primeira tela da F7.6 que NASCE conforme à HV1. Registro: a régua CF-09a saiu com `&&…||` traiçoeiro e foi reescrita antes da promoção — régua que passa por acaso é régua que mente.**) · anterior v1.32 (**§85 NOVA (2026-08-23, VIGÉSIMA TERCEIRA parte): a **TELA A17 — BUSCA/RESULTADOS** é `estável` por gate dele (*"fora isso está aprovado"*), com a correção do "fora isso" aplicada ANTES da promoção: **ele pegou a HV1 de novo, a olho nu** — a paginação sem resposta ao sobrevoo (*"pelo que definimos era pra ter um efeito sobre o botao?"* — TINHA: §71, nascido de gate DELE em 2026-08-19) — e o conserto foi RETROATIVO às três telas da F7.6 (hover neutro = degrau sunken · hover de fundo de marca = véu 8% · pressionado = véu 12%, monotônico, sob a receita do §71.2; hover só em `@media (hover:hover)`, `:active` fora — toque pressiona). Contratos BU-01…BU-12 na `suite-busca.mjs` (**15 · 0 · 1 [n/a] provado em navegador real**): query ecoada + contagem sempre visível (D4-b) · realce por SUBLINHADO+peso 800 medido no pixel · chips que desmarcam a origem e voltam à página 1 (TD2/TD3/PG4) · zero-resultado D9 · debounce do espelho falado MEDIDO por espera real · gaveta mobile. ⚠ **49º defeito de instrumento**: o check CSSOM reprovou as 3 telas CORRETAS — style rule também tem `.cssRules` (CSS Nesting), vazia mas truthy, e o flatMap ingênuo a trocava por lista vazia, engolindo os `:active` de topo. Suítes das irmãs cresceram junto: A19 **22·0·0** · A20 **15·0·1**. Pendência nova **BU-P1** (virtualização de lista longa — regra ausente no acervo).**) · anterior v1.31 (**§84 NOVA (2026-08-23, VIGÉSIMA SEGUNDA parte): a **TELA A20 — ONBOARDING/PRIMEIRO USO** é `estável` por gate dele (*"a imagem da onboarding está ok. aprovo"*). A taxonomia Z1 do §25 posta VIVA em composição, no hospedeiro painel §48: TRÊS estados não intercambiáveis (primeiro-uso educativo com EEny + critério dito · esvaziado · sem-permissão **SEM EEny — Z5 provado por suíte**), contratos ON-01…ON-09, `suite-onboarding.mjs` **14 · 0 · 1 [n/a] declarado** (o [n/a] é container query = render, PROVADA em navegador real a 390px). Régua nova de mascote: **medir o bounding box do pose por PIXEL antes de embutir** (o pose-05, escolhido por peso, era o EEny DE COSTAS ocupando ~35% do canvas) e crop de APRESENTAÇÃO no viewBox do embed, desenho intocado. Esclarecimento de termo no gate: "byte-idêntico" = fidelidade embed↔asset, nunca igualdade entre poses — 8 poses, 8 MD5 distintos, medidos a pedido dele.**) · anterior v1.30 (**§83 NOVA (2026-08-23, VIGÉSIMA PRIMEIRA parte): a **TELA A19 — AUTENTICAÇÃO** existe e é `estável` — **fecha a CD-P5** (aberta desde o §66). Primeira entrega da **F7.6**, na ordem aprovada no consolidado de 2026-08-22 (P1–P5, "aprovo"). A tela compõe o §66/§2/§1/gabarito F7.1 SEM componente novo; 14 contratos AU-01…AU-14 medidos por COMPORTAMENTO na `suite-autenticacao.mjs` (**21 · 0 · 0**). Gate visual dele em 2026-08-23 com uma correção, verbatim: *"a tela de login deve ter o icone da seed no lugar do botao turquesa. fazendo essa correção, está aprovado"* — o símbolo OFICIAL entrou por INJEÇÃO DE SCRIPT byte-idêntica (claro=principal · escuro=mono-negativo, tabela §5.1 da marca), e a AU-12 passou a conferir o path contra o asset. Sustenta a spec a LEITURA DIRIGIDA de 2026-08-22 (`09-pesquisa/leitura-f76-bordas.md`): os vizinhos REPROVAM nas normas que o §66 já absorveu (CU: autocomplete ausente, submit desabilitado, zero h1/main, placeholder-como-rótulo). Achados de produção: **container query não estiliza o próprio container** (pego por medição em navegador real; o container subiu ao body) · o Chrome CLI inventou um overflow a 390px que o navegador real desmente (reincidência da armadilha do marco v1.9) · o `G:` rejeita junction e o jsdom passou a viver fora do stream (env `JSDOM_DIR`; pendência AU-P1 para as suítes antigas). Pendências novas: AU-P1 (ambiente das suítes), AU-P2 (variante com faixa CP19), AU-P3 (multi-empresa pós-login).**) · anterior v1.29 (**§82 CRESCE para 5 cláusulas (2026-08-22, VIGÉSIMA parte): nascem **MM1-d** (arquivo de produção declara sangria ≥ 3 mm) e **MM1-e** (conteúdo a ≥ 3 mm do corte), com o veredito da sangria — *"acredito que 3+3"* — e o MÉTODO dele: *"deixar o desenho dentro da area real, e a parte da sangria vc cresce o desenho"*. Consequência: passam a existir **DOIS TIPOS DE ARQUIVO**, gate e produção, cada um declarando o próprio papel no artefato. Pasta de prova de 7 para **11 fixtures**; produção **15 PASS · 0 FAIL · 0 [n/a]**. Canon **v5.10 §7.4-e**.**) · anterior v1.28 (**§82 NOVA (2026-08-22, DÉCIMA OITAVA parte): nasce o **CONTRATO MM1** — a guarda do piso de traço em milímetros, de **ALVO DE PASTA**, com 3 cláusulas (piso ≥ 0,25 mm · alvo físico coerente com a razão em px · ≥ 3 pesos distintos) e pasta de prova de 7 fixtures escrita ANTES (**13 PASS · 3 FAIL · 5 [n/a]**, batendo com o previsto). Decisão de arquitetura: **quem é peça impressa é decidido pelo que o ARTEFATO DECLARA** (`<meta name="alvo-fisico">`), nunca por nome de arquivo. E a **MR-P8 fechou**: pasta A4 de 105,7 para **300 DPI exatos**, com o piso aplicado. Na estreia a guarda achou **um defeito real do gerador**: o piso estava sendo violado pelo ARREDONDAMENTO (0,24980 mm) — virou esclarecimento normativo no canon **v5.8**.**) · anterior v1.27 (**§81 NOVA (2026-08-22, DÉCIMA SÉTIMA parte): a **MEDIDA FÍSICA DO CARTÃO** é `90×50 mm`, o padrão BRASILEIRO — veredito dele, verbatim: *"escolho B"*. Canon `marca-seed.md` **v5.7 §7.4-d**. O achado: os 89×51 mm eram `3,5×2 polegadas`, medida de EUA/Canadá que entrou como **default de ferramenta, nunca como decisão de marca**. Custo medido: **uma linha nova no teto da §7.4** (84px de 590 = 14,2%), porque a tabela é indexada pela RAZÃO da peça. Custo zero em impressão: o piso de 0,25 mm sai em **0,250 mm** nos três formatos testados. GR1 **15·0·0**; acervo dos 7 formatos **21·0·0**, intacto. Pendência nova **MR-P10: SANGRIA**.**) · anterior v1.26 (**§80 PASSA A `estável` (2026-08-22, DÉCIMA SEXTA parte): o **PISO DE TRAÇO EM MILÍMETROS** existe. Veredito dele, verbatim: *"saída B — piso de 0,25 mm por ESCALA, e só para peça IMPRESSA"* → canon `marca-seed.md` **v5.6 §7.4-c**. Aplicado: o plano mais fino do cartão sai de **0,180 mm** (2% de folga sobre o piso digital reverso) para **0,250 mm** (42%), com os **4 pesos preservados** — escala, não grampo, porque grampo achataria a hierarquia de peso, que é uma das três condições do §7.1. Peça de TELA fica fora, e isso está no código: `ppmm` só é passado nas chamadas de cartão. GR1 na pasta: **15 PASS · 0 FAIL · 0 [n/a]**.**) · anterior v1.25 (**§80 NOVA (2026-08-22, DÉCIMA QUINTA parte): o **TRAÇO DO GRAFISMO EM MILÍMETROS** — a lei do traço é em PIXEL, logo o traço físico de peça impressa é função da resolução de exportação. Medido: o verso do cartão APROVADO passa o piso digital reverso por 0,004 mm (2%), e a variante do verso nos 7 formatos REPROVA nos quatro planos. Três saídas com imagem, recomendação de piso de 0,25 mm por ESCALA — **nada entrou no canon: piso é valor de marca e espera veredito**.**) · anterior v1.24 (**§79 NOVA (2026-08-21, DÉCIMA QUARTA parte): o teto do grafismo no **TOPO** é de **LARGURA**, não de altura — `F ≤ w / 6,5625`, por veredito dele: *"o ponto é medir a aplicação na largura se atende ao desenho selecionado para o topo, a altura nao importa"*. A altura da peça não entra e a razão de aspecto não gateia nada; a % da altura passa a ser DIAGNÓSTICO. Canon `marca-seed.md` **v5.5 §7.4-b**.**) · anterior v1.23 (**§78 NOVA (2026-08-21, DÉCIMA TERCEIRA parte): o decisor APROVOU a emenda de canon MR-P4 e o `marca-seed.md` passou a **v5.4** — o grafismo agora tem **DOIS REGIMES** (`DETALHE DE LINHA` ≤~8% e `SERRA-CENA` com **tetos POR FORMATO**), e a guarda GR1 passou a ler o canon novo no mesmo dia: 5 fixtures novas escritas ANTES, inércia 7 de 7 nas antigas, e as peças largas de 11·4·0 para **15·0·0** — fechamento POR CANON, sem afrouxar limite nenhum.**) · anterior v1.22 (**§77 NOVA (2026-08-21, DÉCIMA SEGUNDA parte): o contrato **GR1** do grafismo **GANHA INSTRUMENTO** — `validacao/guarda-grafismo.mjs` reescrita do zero (a original nunca foi propagada), alvo de pasta sem filtro de nome, medição por renderização diferencial julgada no pixel, pasta de prova `render-audit/prova-gr1/` escrita ANTES com 7 fixtures e referência **10 PASS · 3 FAIL · 8 [n/a]**, e placar em 18 peças reais. Os 4 FAILs de L2/L3/L4/L5 são SANCIONADOS pelo decisor e esperam a emenda **MR-P4**. Nasce o **47º defeito** de instrumento.**) · anterior v1.21 (**§76 NOVA (2026-08-20/21, DECIMA PRIMEIRA parte): o GRAFISMO DA MARCA entra como componente — a **SERRA EM ARCOS** no rodape (**PADRAO**, 4 planos, TOQUE EXATO, 12 pecas medidas) e a **MONTANHA** no topo (**OPCAO** do criador, 3 planos, oclusao com folga). ⚠ **DOIS REGIMES DE REGRA INDEPENDENTES**, por veredito do decisor: *"considere que o topo segue uma regra e o rodape outra regra"* — o toque exato e regra do RODAPE, nao lei da casa. Escada **E1-nevoa** aprovada no tema escuro; **supersede formal do `#0A6B60`**, cor de marca criada sem autorizacao e retirada por ordem dele (ΔE76 = 1,03 contra o token `#006C62`). Contrato **GR1** escrito com as tres clausulas (area ≤10% · glifo velado 0px · faixa de rodape ≤~8%) mas **HOJE SEM INSTRUMENTO**: a guarda nunca foi propagada e todo placar GR1 antigo e historico e irreproduzivel (**GR-P3**). Registro completo no MANIFESTO §103; dimensoes novas no mapa §22.15.** · **§75 (2026-08-20, DECIMA parte, SEGUNDA rodada) (2026-08-20, DECIMA parte, SEGUNDA rodada): nasce o contrato **TP1 — a tipografia e os recursos que o artefato declara REALMENTE CARREGAM**, `validacao/guarda-tipografia-viva.mjs`, alvo de pasta: **30 PASS · 21 FAIL · 0 [n/a]** no acervo (0 TP1-a · 21 TP1-b · 14 TP1-c) mais **85 achados de PORTABILIDADE** e **56 de PRIMEIRA ESCOLHA**. Ele nasceu de o Rafael perguntar se "uma imagem que nao carregou" mudava a avaliacao: **NAO mudava** (cor, tamanho, peso, retangulo dentro do cartao e pixel do fundo IDENTICOS com e sem imagem), mas a investigacao achou que o `seed-design-system.html` depende de **47 recursos de REDE** e que **0 de 45 imagens e 0 de 2 familias tipograficas carregam** — logo TODO recorte daquele artefato ja mostrado ao decisor saiu com a tipografia de RESERVA. ⚠ A regua obvia MENTE: `document.fonts.check()` devolve `true` com a fonte FALHANDO; as honestas sao `[...document.fonts].length` (0 antes, 7 depois) e a LARGURA EM CANVAS (348 = 348). ⚠⚠ **Esta guarda esteve errada CINCO vezes** (48, 46, 22 com 3 falsos, 6 de 6, e finalmente correta) e o numero de passes E o achado: **quando uma regua nova reprova EM BLOCO, a primeira hipotese nao e "o acervo esta ruim", e "a regua esta medindo outra coisa"**. Nasce a pasta `prova-tp1` (**3 · 3 · 0**). §75.6 registra as DUAS DECISOES DE COR que esperam gate e NAO foram aplicadas: **(i)** no amarelo, o pedido dele (manter o amarelo em 18px) nao era entregue por nenhuma das cinco opcoes porque **a ONDA decorativa cruza a segunda linha** — a saida e (E+), cartao `#005048` + onda `#00352F`, **6,56 e 8,54**, zero hex novo; e o kicker com `opacity:.9` ja reprovava em **4,01** real contra 4,60 declarado. **(ii)** no dourado, **a recomendacao (D) da folha v14.1 CONTRARIAVA o `seed-tokens.md` v1.5 de 2026-08-11, aprovado por gate dele** — regua nova: *antes de propor mudanca de VALOR de cor de marca, leia a linha do canon sobre aquela cor*. A saida preserva o hex: `#F9B11C` intocado mede **7,48** sobre `#242E34` ou como AREA com rotulo escuro. Pendencias novas: RD-P1, CC-P11, CC-P12, TP-P1. Anterior: v1.19 — **§74 NOVA (2026-08-20, DECIMA parte): o contrato **BT1** (barra de provas travada no topo, §64.1, de 2026-08-16) FECHA POR INSTRUMENTO — nasce `validacao/guarda-barra-fixa.mjs`, de ALVO DE PASTA, com quatro clausulas das quais TRES sao COMPORTAMENTAIS (a pagina e rolada de verdade). O motivo de existir: a **BT-P2** foi declarada FECHADA em 2026-08-17 sobre uma LISTA ESCRITA A MAO de **dez** artefatos, e o acervo tem **trinta e quatro** — o Rafael achou o resto a olho nu (*"acredito ter outros"*). Placar: **22 PASS · 11 FAIL · 18 [n/a]** -> **33 · 0 · 18**, medido a 1440x900 E a 320x900 (9 BT1-a · 10 BT1-b · 0 BT1-c · 2 BT1-d). LICAO: **pendencia de contrato TRANSVERSAL nao fecha por lista — fecha por instrumento de ALVO DE PASTA.** A primeira tentativa de conserto foi REVOGADA pelo print (a barra travou ABAIXO do titulo; o no foi MOVIDO para primeiro filho de `<body>`), e duas suites reprovaram o conserto com razao (tokens fantasma em seis arquivos: 38·1 -> 39·0 e 92·1 -> 93·0). Nasce a pasta de prova `prova-bt1` (**1 PASS · 4 FAIL · 2 [n/a]**), que pegou DOIS defeitos do proprio instrumento antes do acervo. Nasce tambem a regra de forma de entrega **DEMONSTRACAO VIVA E EXTRAIDA, NUNCA REDIGITADA** (`validacao/extrai-vivo.mjs`), com o **44o defeito**: a v1 lia CSS por REGEX e chave dentro de COMENTARIO desalinhou o parser — 3 de 5 quadros sairam sem tokens; reescrita por CSSOM, 5 de 5. Pendencias novas: BT-P3 (o BT2, seletor de LARGURA, nunca foi medido) e IN-P2 (nenhum censo diz quais `.mjs` leem CSS por texto em vez de CSSOM). Anterior: v1.18 — **§73 NOVA (2026-08-20, NONA parte): a HV-P5 FECHA — nasce `--seed-text-brand-strong` (gemeos v1.19, claro `#006C62` / escuro `#66D1C2`, ZERO hex novo) e o contrato **HV1-e: tinta cujo piso foi verificado num UNICO fundo nao serve a estado**. O numero que fecha a decisao: para `#098475` alcancar 4,5 o fundo precisa de luminancia >= 0,978 — o cinza mais escuro que serve e `#FDFDFD` — logo NAO EXISTE superficie de interacao possivel para a tinta de marca atual. Os 90 casos eram **100** depois de consertada a regua (**41o defeito**: a HV1-c lia a tinta DO CONTROLE, nao a de quem PINTA a letra — 1 artefato correto condenado e 12 defeitos reais invisiveis), e nao estavam em 7 classes: estavam em **5 arquivos, 64 num so**. Consertar o pior obrigou a abrir o `seed-design-system.html`, onde o **42o defeito** (a varredura de contraste escolhia alvo por PREFIXO DE NOME e deixava 17 dos 51 HTMLs de fora) escondia **862** folhas de texto reprovadas em repouso: **862 -> 46**. Nasce ainda o **43o defeito** (a ferramenta de recorte nao desligava rolagem suave — era o 33o, num terceiro instrumento). Pendencias novas: HV-P6, SE-P1, CC-P9, CC-P10, ES-P1, TK-P1. Anterior: v1.17 — **§72 REVISTA (2026-08-20, oitava parte): nasce a clausula **SH1-d — O CONTEUDO NAO VAZA DA PROPRIA CAIXA**, de o Rafael apontar num anexo a 390px o rotulo saindo POR FORA DA BORDA de dois botoes do `tela-gantt` que as tres clausulas anteriores davam como limpos. Causa medida: `display:flex` da `flex-shrink:1` por padrao e o `min-width:44px` do piso de ALVO da SC 2.5.8 SUBSTITUIU a protecao `min-width:auto` do flexbox — duas regras boas que juntas produzem uma ruim; conserto `flex-wrap:wrap` + `flex:0 0 auto`, com quatro alternativas descartadas registradas no proprio molde. Dois defeitos de instrumento morreram no caminho: o **39o** (`clientWidth` de inline nao-substituido vale ZERO — 168 falsos vazamentos, 124 deles em `code`) e o **40o** (`opacity:0` NAO tira o elemento do hit-test — 2 falsos FAIL que so apareciam a 390x844). Placares: acervo **204 PASS · 0 FAIL** a 1440px e **198 PASS · 6 FAIL** a 390px, este confirmado em DUAS alturas de janela; pasta de prova a **42 PASS · 22 FAIL** em 16 fixtures. A **SH-P8 foi reescrita**: dizia "18 FAIL em 4 arquivos" e o numero medido e **6 FAIL em 2 arquivos**. Anterior: v1.16 — **§71 e §72 (2026-08-19, sétima parte): nasce HV1 — RESPOSTA AO PONTEIRO e PRESSIONADO DISTINTO — de o Rafael RECUSAR uma pergunta que eu não devia ter feito; medido, 619 de 1.410 controles ativos não davam NENHUMA resposta ao mouse, 1.350 tinham o pressionado igual ao sobrevoo e 90 violam a norma de contraste HOJE, com 0 PASS em 51 arquivos. E nasce SH1 — COLISÃO, COBERTURA E CORTE —, que FECHA a SH-P3: 204 PASS · 0 FAIL a 1440px, com o espaçamento da SC 1.4.12 medido pela primeira vez e CINCO defeitos de instrumento mortos no caminho. Gêmeos de token a v1.18. Anterior: v1.15 — ** §70 reescrito (2026-08-19, sexta parte): as duas confissões do CT1 FECHAM — CT-P1 (media só o tema claro) e CT-P2 (media só o repouso). Nascem CT1-d e CT1-e: 2 temas × 4 estados × 2 larguras, 1.480 medições, 0 fora do centro, NENHUM artefato alterado. Tema escuro só é medido em quem PROMETE tema escuro, e a promessa é medida por assinatura de cor, não por grep. §70.5 supersede · §70.6 prova de inércia (a dimensão antiga reproduz 23·0·28) · §70.7 pendências CT-P3 a CT-P7. Anterior: v1.14 — ** §65.9, §65.10, §65.11 e §70 (2026-08-19, gate v13.3 respondido pelo Rafael): a SI-P5 FECHA e a resposta não foi nenhuma das três alternativas — aquele quadrado é a EMPRESA LOGADA e o ERP é MULTI-EMPRESA (SI17-e: cor por empresa, glifo constante; `SEED energia` na posição 3, escolhida por distância de luminância 3,80); a PS-P6 FECHA com os contratos SI19 (pessoa é círculo, empresa é quadrado) e SI20 (foto e logo SUBSTITUEM o fundo, nunca se deitam sobre a cor de entidade), e a matriz dos QUATRO estados nasce em `banco-identidade.html`; a PN-P6 FECHA — a linha da planilha sobe de 40 para 44px, alvo medido 234×44; a CC-P8 FECHA — o ponto da legenda deixa de ser o CARACTERE `●` e vira forma, trocando o critério de SC 1.4.3 para SC 1.4.11 sem mexer em uma cor sequer; e nasce o §70 com o contrato CT1 e a guarda `guarda-centro-alvo.mjs` (189 controles, 0 fora do centro) a partir de um defeito que o RAFAEL viu a olho nu e que NENHUMA das 60+ guardas via.** **§65.8 e §62.8 (2026-08-19): nasce o contrato SI18 (2026-08-19): nasce o contrato SI18 para o ponto de cor `.ent` — spec PRÓPRIA, porque ele NÃO é o §65.7 mal-feito — e a PS-P5 é REVOGADA pela medição: o `.mini-avatar` carrega um CLIENTE, não uma pessoa.** **§67.7 (2026-08-19): a IL-P2 EXECUTA — a grade do §48 passa a cumprir o padrão `grid` da ARIA e o §67; nasce o contrato IL15 e a guarda que TECLA para medir; o defeito principal era TECLADO e não estava escrito na pendência.** **§65.7 (2026-08-19): a SI-P4 EXECUTA — o `.sigla` ganha bloco ÚNICO nas OITO telas que o carregam (o censo dizia quatro), nascem os contratos SI17-a/b/c/d e a `guarda-si17.mjs` que os mede por COMPORTAMENTO; nasce a SI-P5, de produto.** Fase 3 FECHADA 7/7 ✅ · **FASE 6 — 6A, 6B e 6C FECHADOS: §48 Painel `estável` desde 2026-08-13, PN1–PN54. INVERSÃO DO HERO na mesma data: o azul sai, a âncora passa a TURQUESA VIVO `turquesa-400`, a ação volta à família da marca e o conflito com o §3.5 de `marca-seed.md` DESAPARECE — S4 retirado, S3 revisto, S11 novo. Preview v0.8, cinco camadas verdes (139 · 37 · 142 · 6 · 78). Segue proposta apenas a tinta da família (S6) — pendência PN-P1, agora menor**)

> **Arquivo canônico de componentes do design system SEED v2.** Cumulativo: cresce por blocos ao longo da Fase 3 (7 blocos — estrutura completa no rodapé e no `seed-ds-roadmap.md`). Cada componente traz: papel, anatomia rotulada, variantes, tamanhos, estados completos, tokens de componente (camada 3), microcopy no tom SEED, acessibilidade e código duplo (HTML/CSS + React/TypeScript). Quando o site DS no Lovable existir, este conteúdo migra pra lá e este arquivo vira fonte de sincronização.
>
> **Dependências:** tokens v1.2 (`seed-tokens.css/.json/.md`) — componentes consomem SEMANTICOS, nunca hex. Identidade: `marca-seed.md` v5.0. Previews validáveis: `seed-componentes-preview.html` (botão) · `seed-formfield-preview.html` · `seed-feedback-preview.html` (Bloco 3) · `seed-superficies-preview.html` (Bloco 4) · `seed-cn-preview.html` (§33 — pattern CN) · `seed-navegacao-preview.html` (Bloco 5).
>
> **Supersede:** a seção "components" do `seed-design-system.html` v1.0 (botões com 4 variantes sem estados) fica obsoleta para botões a partir deste arquivo; o HTML segue fonte para os componentes ainda não migrados.
>
> **Data:** 2026-08-18 · **v1.08: o GATE VISUAL reverteu a MR-P1, e a reversão melhorou o número.** O Rafael vetou a tinta escura sobre a marca — *"ter o turquesa com o texto Branco dentro no tema claro é algo visualmente bonito"* — e com o branco fixado a pergunta passou a ser a SUPERFÍCIE: branco sobre turquesa-400 mede **2,71**, sobre turquesa-600 mede **4,60**. Nascem `surface-brand-strong` e `text-on-brand-strong` (gêmeos v1.17); a marca chapada `#11B0A0` fica reservada para área SEM texto. A referência de mercado foi MEDIDA e reprova: o botão primário do ClickUp mede **3,07**. Fecharam também a **LS-P6** (a barra de massa cobria conteúdo em 17 de 17 larguras, 177 sobreposições → **0**) e a **TK-P1** (seis degraus, não quatro — o placar estava cortado em 8 de 12 e me pegou mesmo declarado). Seis pendências NOVAS de artefatos nunca medidos. Detalhe no **§64.16**.
>
> **Data:** 2026-08-18 · **v1.07: a AD-P1 FECHA por pesquisa e sem gate, a isenção de componente INATIVO entra nas guardas, e a forma de PERGUNTAR muda.** Instrução do Rafael: pergunta de gate vai com **exemplo visual antes/depois**, e escolha de solução **não se pergunta — pesquisa-se e aplica-se**. A AD-P1 (branco sobre o destrutivo no escuro, **2,76**) fecha com token novo `text-on-action-destructive` — branca no claro (5,19) e vermelho-900 no escuro (5,26) —, escolhido pelo RITO: o Material Design 3 inverte a tinta de erro com o tema, o IBM Carbon não clareia o fundo de perigo; adotado o M3 por coerência com o que esta casa já faz no botão primário. Guarda **MR2** com prova de reprovação pelo caso fundador. **CC-P3** fechada: a SC 1.4.3 isenta componente INATIVO, e as guardas passaram a honrar a isenção **imprimindo a contagem**. A pergunta sobre a regeneração das nove bancadas virou **medida**: 0 pixels de diferença em 163.800. Detalhe completo no **§64.15**.
>
> **Data:** 2026-08-17 · **v1.06: a MR-P1 EXECUTA e o conserto é no GÊMEO outra vez, nasce e fecha a MR-P2 no tema oposto, o ANEL vaza em elemento pequeno e arredondado (25º defeito), e o acervo tem 34 artefatos, não 29.** Medido: **um único token de tinta servia TRÊS fundos de marca** com necessidades opostas — o gêmeo de JSON descrevia esse token como *"texto sobre action.primary"*, e ele estava sendo consumido também sobre a marca chapada e sobre a marca profunda. Branco sobre a chapada media **2,71** no CLARO (MR-P1) e tinta escura sobre a profunda media **1,45** no ESCURO (**MR-P2**, nova): dois defeitos simétricos, um em cada tema, da mesma causa. Conserto: **três tintas, uma por fundo** (gêmeos **v1.15**), com o botão primário mantendo exatamente a aparência de antes. Censo por pixel em **48 artefatos × 2 temas**, por guarda nova de alvo de pasta: **49 textos assentam sobre a marca chapada e ZERO violam**. Também nasceram e fecharam a **BT-P7** (token fantasma na barra de provas de seis bancadas: o estado ligado do alternador **não pintava**, e nenhuma guarda tinha visto porque esse estado só existe **depois do clique**) e a **BC-P1** (nove bancadas estavam **atrás** do `base-bancada.css`, inclusive no conserto do `.vh` que o §64.6 registra como feito). Detalhe completo no **§64.14**.
>
> **Data:** 2026-08-17 · **v1.05: a BT-P6 FECHA na CAUSA, a CD-P7 FECHA por DECISÃO, e nasce a MR-P1 — um número de FUNDAÇÃO que nunca existiu.** A BT-P6 não era descuido do artefato: **não havia semântico** para "superfície de marca sutil" e o `tela-chat` era o ÚNICO consumo daquela primitiva em 29 artefatos — nasce `--seed-surface-brand-subtle` nos três gêmeos (12,79 claro · 10,40 escuro · piso 4,50) e os TRÊS FAIL do tema escuro somem. **CD-P7 fechada por decisão do Rafael:** os 93 não são defeito, porque o Understanding da SC 1.4.11 diz verbatim que borda não é obrigatória em controle com conteúdo visível, e consertar destruiria a escala terciário↔fantasma. **MR-P1:** o `seed-tokens.md` declarava que branco sobre `--seed-surface-brand` dá 3,22 e construía a regra "só ≥24px" nisso; medido por dois métodos independentes, é **2,71** — reprova até AA-large, logo **branco ali não é permitido em nenhum tamanho**. Decisão acatada: tinta escura. Execução pendente porque **os casos no acervo não foram contados** (o instrumento cobria 6 dos 29 artefatos). **SI-P4, IL-P2, PS-P5 e a spec do ponto de cor decorativo: DECIDIDAS**, execução pendente. Detalhe completo no **§64.13**.
>
> **Data:** 2026-08-17 · **v1.04: A CD-P7 GANHA RITO COMPLETO E VEREDITO PROPOSTO — e a rodada de NORMAS inverteu a pergunta.** R1: o Understanding da SC 1.4.11 diz verbatim que *"if a control has visible content (such as text or a sufficiently contrasting icon) … a border or other indication of the overall boundary of the hit area is **not required**"* — logo botão com rótulo legível **não** precisa de borda a 3:1. R2: o Carbon (IBM) pratica exatamente essa fronteira — o **ghost** não declara borda em nenhum estado, o **tertiary** tem borda com token próprio porque nele a borda **é** o identificador. R3: em **forced-colors** o navegador descarta `background-color`, então o risco real não é "borda abaixo de 3,00" e sim **"nenhuma borda nem outline declarada"** — e aí está o número novo: **28 de 29** artefatos do acervo não têm nenhuma regra de botão para forced-colors (nasce **CD-P9**). Veredito proposto: os 93 achados **não** são defeito de 1.4.11, com a exceção estreita do botão cujo ÚNICO identificador é a borda (quantos são disso **não foi medido** — nasce **CD-P10**). **A decisão é de gate e está acumulada no fechamento.** Detalhe completo no **§64.12**.
>
> **Data:** 2026-08-17 · **v1.03: A GI2 forma 4 vira GUARDA TRANSVERSAL de alvo de PASTA — a BT-P3 FECHA na forma superseder (um instrumento, não quatro cópias) e a pasta ganha UM DEFEITO MEDIDO EM ABERTO.** `validacao/guarda-gi2.mjs` mede **29 artefatos**: **15 PASS · 1 FAIL · 35 [n/a]**, os [n/a] em três categorias declaradas. O FAIL é a **BT-P6**: `tela-chat.html`, `.msg--propria .msg__balao`, fundo primitivo `--seed-turquesa-50` com texto semântico `--seed-text-primary` = **1,14** no tema escuro contra piso 3,00, reprodutível 3/3 — mesma forma dos defeitos que o gate achou em 2026-08-16 (1,02 e 1,00). **A frase "o acervo está sem defeito medido em aberto" deixa de valer**, e isso está dito. Levada crua à pasta, a guarda deu 7 FAIL e **seis eram falso positivo**: quatro consertos (A contentor sem texto próprio · B primitiva com override de tema INVERTE · C fundo é o ANEL · D sem anel não há veredito) estão registrados no §64.11.4. O **24º defeito de instrumento**: número que **varia entre execuções** do mesmo alvo — 1,84 / 2,56 / 2,95 no mesmo `.tagok` — não é medida, e só foi visto porque a medição foi **repetida três vezes**. Detalhe completo no **§64.11**.
>
> **Data:** 2026-08-17 · **v1.02: NASCE A AUDITORIA TRANSVERSAL DE CONSUMIDOR (§69, contratos AC1–AC4) — a EA-P1 FECHA e a auditoria da SI-P4 e da IL-P2 ACABA.** Primeira guarda do acervo que compara um artefato com OUTRO artefato, e não com um contrato. Alvo de PASTA, três classes de consumo (CÓPIA reprova · REIMPL emite `[achado]` porque a pergunta é de desenho · VERSÃO confere documento contra artefato) e cinco regimes de comparação por campo, escolhidos depois de RITO de 3 rodadas que descartou o diff textual (condenaria 100% das amostras) e o diff visual (devolve lista, não veredito). Placar: **7 PASS · 0 FAIL · 5 [achado] · 12 [n/a]**. Correções que a medição impôs à SI-P4: o culpado é o `.sigla` (24px · raio 6px · glifo) e são **QUATRO** telas, não três — `tela-tabela` faltava; e o `.ent` de 12px **sem** glifo NÃO é o §65, então condená-lo forçaria conserto errado em 14 elementos corretos. Achado novo, vira **PS-P5**: o `.mini-avatar`, círculo de iniciais de PESSOA, pinta-se com a paleta de ENTIDADE, quando o §62 fechou paleta própria de 8 pares. E a lição de instrumento mais cara da rodada: na primeira versão a guarda **não pegava o defeito que a gerou** — o cargo "Diretor" passava. *Guarda que não é testada contra o caso fundador não está provada* (§69.4). Detalhe completo no **§69**.
>
> **Data:** 2026-08-17 · **v1.01: O GATE DAS QUATRO BANCADAS É OBSERVADO — BT-P1, PS-P4 e DH-P6 FECHAM.** O Rafael aprovou escolhendo, entre três opções explícitas, a que diz **"Aprovo — gate OBSERVADO"** sobre as folhas de captura de `banco-escolha` **v0.4**, `banco-data` **v0.4**, `banco-prioridade` **v0.2** e `banco-pessoa` **v0.1** (`render-audit/folha-banco-*.html`). O rito inteiro foi REEXECUTADO antes de promover e coincidiu integralmente com o §80 do MANIFESTO: `suite-escolha` **238 · 0 · 29** · `suite-data` **31 · 0 · 1** · `suite-prioridade` **18 · 0 · 0** · `suite-pessoa` **44 · 0 · 1** · `render-promocao` **168 · 0** · BT7 **5/5/5/3 · 0** · `contraste-composicao` **8 · 0** · `auditoria-borda-campo` **2 · 0** por artefato. Achado do caminho: **ERRATA DE VERSÃO** em três cabeçalhos — §61 dizia `banco-data` v0.5, §62 dizia `banco-pessoa` v0.2, §63 dizia `banco-prioridade` v0.3, e nenhuma dessas versões existe em artefato, gerador, MANIFESTO ou mapa de cobertura; corrigido o DOCUMENTO, não o artefato, com as quatro fontes registradas (§64.10.3). Detalhe completo no **§64.10**.
>
> **Data:** 2026-08-17 · **v0.99: A VARREDURA BT7 FECHA — 34 artefatos medidos, 120 PASS · 0 FAIL, nenhum defeito aberto no acervo; a BT-P4 FECHA.** Os SEIS artefatos que ainda reprovavam foram consertados na CAUSA: `tela-quadro` e `tela-gantt` pelo MESMO conserto do `.vh` (texto visualmente oculto em `position:absolute` sem ancestral posicionado ancora no `body`, toma a coordenada ROLADA dentro de uma faixa que rola e **estende o `scrollWidth` do documento** — medido `right=479` num viewport de 390; virou `position:fixed`); `tela-referencia` (**84px** · `.busca` com `flex:1` e sem `min-width:0`, piso de conteúdo de 187px); `tela-painel` (**35px** · `.escala-ctrl` `inline-flex` sem `flex-wrap`, 307px num pai de 224px); `banco-tokens` (**27px/97px** · `.type-row` sem quebra e `1fr`, que É `minmax(auto,1fr)`, impedindo a célula da rampa de encolher); e `banco-dataviz-di` (**61px** · tabela de 4 colunas num `.nota` de 302px, mais quatro `<pre>` que rolavam SEM caminho de teclado). Nascem o **19º** e o **20º** defeitos de instrumento — região rolável procurada pela CLASSE `.rolavel` (e o `#quadro` do Kanban é região rolável sem essa classe), e nome/foco lidos por RECEITA de atributo (`aria-label` só, ignorando `aria-labelledby`; `role="region"`+`tabindex="0"` exigidos ao pé da letra, reprovando um `tablist` operável por roving tabindex) em vez de por COMPORTAMENTO. **A `render-painel.mjs` é achada MORRENDO, não reprovando**, por procurar `aria-current="page"` depois do supersede das lentes para `radiogroup` — três blocos inteiros (R5b, R6, R7) nunca tinham sido medidos; consertada, 37·0. **O teto de 3 MB da varredura era ESTIMATIVA**: medido, o `banco-dataviz-dp` (7,5 MB) leva **2,05 s** e passa — teto a 9 MB, e a varredura vai de 33 para 34 artefatos. A `suite-escala-figura` roda pela 1ª vez e acha o `.gt-conectoras` do gantt com teto de largura por ACASO (sem `max-width`) — corrigido, 78·0. Nova subseção **§64.8**; **BT-P4 FECHA**; nasce **E-P1** (os 8 artefatos de e-mail não foram medidos e não há decisão sobre se o BT7 se aplica a eles — ausência de medição, não aprovação). Bateria completa reexecutada: **nenhum FAIL aberto em nenhuma camada**. As nove telas + `banco-dominio` são **promovidas a `estável` por gate DECLARADO**, com a nota de rastreabilidade que a distinção exige. Registro integral no `validacao/MANIFESTO.md` §79 (v11.7). · **v0.98: A VARREDURA BT7 EM TODA A PASTA — 33 artefatos medidos, 11 reprovaram, 5 consertados.** Nasce a **`suite-reflow.mjs`**, primeira guarda do projeto cujo alvo é a **pasta** e não um artefato: contrato transversal copiado em N suítes é N cópias para desatualizar, e as bancadas antigas — sem suíte em playwright — ficariam de fora para sempre. **Consertados:** `tela-site` (a barra de provas era `position:static`, `top=-900` depois de rolar — **fecha a pendência nomeada desde 2026-08-16** — e a topbar estourava 44px a 320) · `tela-chat` (a barra é `sticky` z-index 60 e o painel de chat é `absolute` com **o mesmo** z-index: empate de camada, e a 320px o chat cobre a barra inteira; **a barra de provas é instrumento e não disputa camada com a tela** — z-index 9000) · `banco-data` (**178px** de excesso a 390: o calendário empilha os atalhos abaixo de 470px, com **exceção declarada e geométrica** ao piso de 44px — 7 colunas × 44 = 308 não cabe em 288) · `banco-escolha`, `banco-pessoa` e `banco-prioridade`. **E o `gen-site.py` não compilava neste contêiner** — f-string com aspas escapadas, sintaxe de Python 3.12 (PEP 701), o **mesmo** defeito de ambiente já registrado para o `gen-shell.py` no §74: *defeito de ambiente que reaparece num segundo gerador é classe, não acidente*. **PROMOÇÃO:** §59, §61, §62 e §63 vão a `estável` pelo gate declarado ("aprovo. siga"), com **nota de rastreabilidade honesta** no §78 do MANIFESTO. **As telas NÃO foram promovidas** — quatro delas (`painel`, `quadro`, `gantt`, `referencia`) têm **defeito de reflow medido e aberto**, e artefato com defeito aberto não promove. · **v0.97: PROMOÇÃO — §65, §66, §67 e §68 vão a `estável` pelo gate visual do Rafael (verbatim: "aprovado"), fechando SI-P2, CD-P1, IL-P1 e ET-P2.** E o **rito de promoção** — que é reexecutar todas as camadas contra o artefato real — achou o que o gate não tinha como ver: **as quatro bancadas rolavam na horizontal a 320px**, e a `banco-identidade` já a 390. *Ninguém tinha medido abaixo de 1200.* Nasce o **BT7** no §64: nenhuma bancada rola na horizontal a 320px, e o que exige duas dimensões (tabela de dados, grade de glifos) vive em **região rolável FOCÁVEL e ROTULADA** — porque a exceção do SC 1.4.10 autoriza a rolagem, não a inacessibilidade dela. Guarda `BT7` nas quatro suítes, medindo em **duas** larguras. **E o conserto corrigiu um contrato:** o SI5 lia o número de colunas de um atributo fixo, que mentia no reflow **e já mentia com o filtro ativo** — as colunas passam a ser **medidas** pelo layout, e o nome acessível do grupo diz o número real (`SI-OP3b`). **18º defeito de instrumento:** o seletor da auditoria de sobreposição casava `.cx` como *cartão* numa bancada e *caixinha de dígito* na outra, e comparava **pai com filho** — seis achados falsos. *Escopo segue sendo a variável que mais produz erro de medição: por posição, por texto, por tipo de nó e agora por seletor.* Placar de promoção: **32 · 30 · 23 · 25** nas quatro suítes · **25 · 0** e **5 · 0** nos ícones · **40 combinações** de render com **0 achados**. · **v0.96: O PRIMEIRO GATE DAS QUATRO BANCADAS NOVAS — e ele achou quatro coisas que nenhuma das camadas automatizadas via.** (1) **Dois glifos do set canônico do §45 estavam mal desenhados** — *“recycle”* e *“growth”*, verbatim do Rafael: *“parecem errados, infantilizados”*. O `recycle` eram dois galos soltos que não fechavam triângulo; o `growth` era o `trending-up` do acervo 40×40 normalizado por 0,6, com ziguezague raso. **Os dois passavam na `suite-icones` (25·0) e na `render-icones` (5·0)** — legibilidade de forma não é medível pelas guardas que existem, e é essa a razão de o gate visual ser camada e não formalidade. Redesenhados pela régua IC1/IC3, com supersede formal no §45.4 e `getBBox` remedido. (2) **O contraexemplo das seis caixinhas do §66 era um ESPANTALHO**: só avançava o foco, sem voltar apagando no Backspace. Corrigido para a **melhor forma de mercado** — e o argumento ficou **mais forte**, porque a tese nunca foi *“caixinhas não funcionam”* e sim **quanto custa fazê-las funcionar**: quatro remendos de JS contra zero, e o quarto (colar) ninguém escreve. (3) **Comentário também é conteúdo**: a higienização do §68 varria só elementos, e o `<!--StartFragment-->` do clipboard do Windows atravessava — o Word emite folhas de estilo inteiras dentro de comentário condicional. (4) **A decisão da borda de campo mudou ao ler o gêmeo inteiro**: o `--seed-border-interactive` já existe desde a **v1.12** como *fronteira de componente interativo*, criado com os mesmos números que remedi — o `--seed-field-border` local do §13 fica **supersedido** por ele, e não promovido ao gêmeo. *O token local é melhor no claro (4,74 × 3,38) e **pior no escuro** (3,21 × 4,50).* **Duas guardas novas, CD10-c e ET8-b, ambas provadas capazes de reprovar antes de serem aceitas.** · **v0.95: A FILA DA F7.5 FECHA NO ALCANÇÁVEL — nascem o §65 (identidade de entidade, SI1–SI16), o §66 (credenciais, CD1–CD16), o §67 (edição em linha, IL1–IL14) e o §68 (editor de texto rico, ET1–ET14), fechando C6, C7, C13, C14 e C15.** Quatro bancadas, quatro suítes, **94 PASS · 0 FAIL**. **As três decisões que carregam a rodada:** (1) a paleta de identidade é **FECHADA em seis** (CP23) — no produto de referência, com paleta livre e tinta branca fixa, **6 dos 13** pares medidos **reprovam** (3,03–5,83); classe fechada transforma *“todas as cores passam?”* em **seis medições**, a mesma saída que fechou a PS-P3. (2) O código de uso único é **UM campo, nunca seis caixinhas** — o SC 3.3.8 exige que dê para **colar**, e a bancada **exerce** o contraexemplo em vez de citá-lo: colado o mesmo código nos dois desenhos, o campo único retém **6 de 6** dígitos e as caixinhas **1 de 6**. (3) Na edição em linha, **clicar fora CONFIRMA** — entre um erro reversível (gravou, e o desfazer do PN28 volta) e um irreversível (sumiu o que se digitou), o sistema escolhe o reversível. **E dois defeitos de instrumento, ambos achados antes de qualquer artefato ser tocado:** o **16º** — o parser de cor não entendia **hexadecimal** e a guarda emitiu **PASS com razão 232,67**, fisicamente impossível (regra nova: *número fora de [1,00…21,00] é MEDIDOR QUEBRADO, não veredito*) — e o **17º**, em que a guarda varreu o `<body>` inteiro e **reprovou o artefato lendo a tabela de contratos que EXPLICA o contrato** (regra nova: *guarda mede o CONTROLE, nunca o texto que descreve o controle*). **Achado de fundação:** o `--seed-field-border` do §13 (4,74:1) **não existe no gêmeo `seed-tokens.css`** — vive só no bloco local da `banco-formfield.html`; os campos novos consomem `--seed-border-interactive` (3,38 / 4,50), medido, e a promoção do token ao gêmeo fica na **CD-P2**. · **v0.92: A PR-P2 É RESPONDIDA (e a resposta é NEGATIVA), nasce a `banco-prioridade.html` — e o botão de tema estava MORTO nas TRÊS bancadas desta sessão.** A PR-P2 perguntava se o set canônico de 28 glifos do §45 daria as quatro formas da escala ordinal. **Não dá — ele não tem nenhuma:** os 28 são de domínio elétrico ou de UI básica. *Mas a decisão já existia:* a árvore do **NM4** manda usar o Lucide quando o glifo é utilitário e não está no set SEED. **O set não precisou crescer e a escala não precisou mudar — a pendência se resolveu pela regra que o §45 já tinha escrito.** A escala vira uma **escada de formas** (duplo-acima › acima › plano › abaixo › círculo tracejado): *a direção É o dado*, então a ordem sobrevive ao cinza e ao daltonismo sem depender da cor — contra as **quatro bandeiras em quatro cores** do produto de referência (C131), que em cinza viram a mesma bandeira. Placar **17 · 0 · 0**. **E o achado que atinge três artefatos:** o botão “Escuro” das bancadas setava `data-tema="escuro"` — o atributo **traduzido**. O gêmeo de tokens escuta `[data-theme="dark"]`. *Botão morto vestido de PROVA*, em `banco-escolha`, `banco-data` e `banco-prioridade` — e a guarda de contraste no tema escuro **passava medindo o tema claro duas vezes**. Consertado nos três; depois do conserto os números do escuro finalmente diferem dos do claro (6,20 contra 4,77). **Atributo de contrato não se traduz.** · **v0.91: NASCEM O §62 (seletor de pessoa, PS1–PS16) E O §63 (prioridade, PR1–PR12), fechando C4 e C11 — e a sexta volta acha o MESMO defeito num segundo componente.** Navegação em modo somente leitura, nove dispositivos (C125–C133). **O achado que vira expectativa:** na lista de pessoas do produto de referência, cada item é `generic` — **sem `listbox`, sem `option`, sem `aria-selected`** — e o campo de busca é `textbox` simples, sem `combobox`, sem `aria-expanded`, sem `aria-controls`. *É o C122 outra vez, noutro componente: dois de dois seletores auditados repetem o padrão.* A regra da quinta volta deixou de ser precaução e virou expectativa: **neste produto a semântica de escolha é sistematicamente ausente.** Dois achados de fronteira: a busca do seletor de pessoa **também convida** quem não está no espaço (C126), e o menu de prioridade mistura a prioridade do OBJETO com a **prioridade PESSOAL** de cada um, separadas só por um título (C132). E um achado negativo que vira contrato: **o menu de prioridade não sabe desatribuir** (C133) — o campo mostra “Vazio”, e voltar a vazio não é oferecido. O PatternFly confirma que **“none” é estado, não ausência de estado**. C5 e C8 ficam com **bloqueio nomeado**: os dois só se alcançam escrevendo. · **v0.90: O GATE ACHOU UM CALENDÁRIO INOPERÁVEL COM PLACAR VERDE — e a guarda que devia pegá-lo tinha sido escrita para NÃO OLHAR ali.** Verbatim do Rafael: *"não consegui ter ação no calendário. não consegui fazer seleção de intervalo de data com o mouse, nem clicar em nenhuma data, quanto mais fazer o teste com tab"*. A v0.1 tinha **25 PASS · 0 FAIL** e a grade era **estática**: o clique só movia o foco e escrevia numa live region invisível. *Uma grade estática não é um seletor de data — é a FOTO de um.* **E o pior:** a guarda `BTN` ("nenhum botão morto") excluía explicitamente `table[role="grid"]`, `.cal__nav`, `.cal__hoje` e `.cd__gatilho` — os quatro lugares onde o defeito estava. *Guarda escrita para não olhar onde o defeito mora é pior que guarda nenhuma: ela produz um verde que impede a pergunta.* Nascem **cinco guardas de OPERABILIDADE** (DH-OP1…DH-OP5) que medem AGINDO — clicam, passam o mouse, navegam — e comparam estado antes e depois. A grade passa a ser renderizada por JS a partir de constantes fixas; o "hoje" continua fixo, o que passou a existir é a interação. Mais **dois defeitos de instrumento** (nono e décimo) e um de artefato achado pelo próprio teste: o `mouseenter` redesenhava o DOM e **destruía o elemento sob o cursor**, entrando em laço. Placar: **30 · 0 · 1 [n/a]**, e desta vez o verde quer dizer alguma coisa. · **v0.89: A DH-P1 FECHA — nasce a `banco-data.html`, e a bancada acha DOIS alvos abaixo do piso e DOIS defeitos no próprio instrumento.** Placar **25 · 0 · 1 [n/a]**. **Decisão de método: o "hoje" da bancada é FIXADO** em 2026-08-16, declarado no gerador — um calendário que consulta o relógio muda de bytes todo dia, e isso quebraria a âncora que este projeto usa para provar que um arquivo é o que se pensa que ele é. **Defeitos reais do artefato:** o gatilho do calendário media **40px** porque o `min-height:44px` estava na CAIXA, e caixa não é alvo — *alvo se mede no retângulo do controle, não no contêiner*; e a célula de dia media **42px**. **Sétimo defeito de instrumento, e é o mais sutil da série: a suíte rodava em UTC.** O DH14 existe porque `new Date("2026-08-16")` é lido como UTC e devolve o dia ANTERIOR a oeste de Greenwich — e num contêiner em UTC a armadilha **não dispara**: a guarda passava sem provar nada. Rodando em `America/Sao_Paulo`, ela mostra o que tem de mostrar: `new Date(string)` daria **15**. *Guarda que roda em UTC não pega defeito de fuso.* **Oitavo:** a guarda do DH17 procurava a palavra "recorrência" na página inteira e a achava **na própria tabela de contratos**, na linha que documenta que recorrência NÃO pertence ao componente — *ela reprovava o artefato por dizer a coisa certa.* · **v0.88: NASCE O §61 — DATA, INTERVALO E HORA (DH1–DH18), fechando C1 e C2. E a rodada zero produziu a PRIMEIRA leitura em que a árvore de acessibilidade CONTRADIZ a tela.** A navegação (quinta volta do estudo, C117–C124) abriu o seletor de data do produto de referência em modo somente leitura. Na captura, o calendário é impecável. Na árvore, **cada dia é um `button` cujo nome acessível é SÓ O NÚMERO** — sem `grid`, sem `gridcell`, sem `aria-selected` no dia escolhido: o leitor de tela anuncia *"botão 2"*. *Copiar a forma teria copiado o defeito, porque o defeito não aparece na captura.* **Regra nova: onde o dispositivo é de ESCOLHA, leia a ÁRVORE junto com a TELA.** E o canon diverge outra vez, agora em três: o **APG** especifica calendário em `role=grid` com teclado completo; o **GOV.UK/NHS** manda **não usar calendário** quando o usuário já sabe a data, e sim campos de texto; o **USWDS** faz os dois, e monta intervalo como **dois seletores independentes**. A régua sai do PROBLEMA: *pergunte se o usuário JÁ SABE a data* — sabe, o texto basta; não sabe, o calendário é que responde; **e os dois existem sempre**. O intervalo fica como **um controle com dois campos e um calendário compartilhado** (DH10), contra os dois seletores do USWDS, porque separados ninguém valida a relação — e o próprio USWDS admite que **não valida coerência entre início e fim**. Um contrato nasce declaradamente **sem leitura** (DH12): o vão entre início e fim não pôde ser observado, porque preenchê-lo seria escrita. · **v0.87: A SG-P10 FECHA e o §60 encerra SEM PENDÊNCIA — dez fechadas numa sessão.** Decisão do gate: **B**, fronteira por GRUPO. O painel e a lista adotam a anatomia da bancada — contêiner com borda, fundo `surface-subtle` e 3px de respiro; segmentos sem borda e sem fundo; `inline-flex`, para o grupo **encolher para o conteúdo** e a fronteira delimitar o CONTROLE em vez da linha. O operador do §53 tinha uma **terceira** anatomia (`overflow:hidden`, `gap:0`, rótulos colados) e também converge: *ter algum contêiner não é convergir; convergir é adotar A MESMA anatomia.* **Três defeitos achados aplicando a decisão, e todos pelas guardas:** (1) `AC-08` pegou `8px` e `6px` digitados onde existem tokens (`radius-md`, `radius-3`) — a higiene é de CLASSE, não interessa que o número esteja certto; (2) **regressão de ESPECIFICIDADE** — a regra nova `.lentes [role=radio]` tem o mesmo peso 0,2,0 de `[role=radio][aria-checked=true]`, e por ser posterior **apagou o fundo do selecionado em silêncio**; o placar acusou separação **1,00** contra piso 3,00; (3) **sexto defeito do instrumento** — com o segmento em `background: transparent`, o `getComputedStyle` devolve `rgba(0,0,0,0)` e a guarda lia **preto**, reprovando artefato correto. Agora ela compõe o **fundo EFETIVO** subindo pelos ancestrais, que é o que o olho faz. Placares finais: escolha **233 · 0 · 29 [n/a]** · painel **146 · 0** · contraste **78 · 0** · contêiner **66 · 0**. · **v0.86: O GATE ESCOLHE O RETO — e a aplicação da decisão expõe uma divergência MAIOR que a que acabou de fechar.** SG-P9 decidida (*"pilula contra reto, escolho reto"*): o segmento passa a **6px**, o valor que já vinha do §53, e a família fica com **uma geometria** — altura 44px desde a SG-P7, raio 6px desde agora. A regra é **específica** (`.lentes [role=radio]` e irmãs), não uma troca no `button` genérico do preview: *o gate decidiu sobre o SEGMENTADO, não sobre todos os botões — alargar a decisão além do que foi decidido é a forma silenciosa de decidir sozinho.* **Mas aplicar o raio revelou o que o raio escondia:** o painel desenha oito **botões com fronteira própria e vão entre eles**; a bancada desenha **um GRUPO com fronteira única** e segmentos sem borda. Mesmo raio, mesma altura, **anatomias diferentes** — e é a mesma família de divergência que esta sessão inteira passou fechando, só que uma camada acima. *Divergência de valor esconde divergência de estrutura: igualar o número não iguala a coisa.* Vira **SG-P10**, com amostra comparativa, porque decidir por mim seria alargar de novo. Placares: escolha **233 · 0 · 29 [n/a]** em cinco alvos · painel **146 · 0** · contraste **78 · 0**. · **v0.85: A SG-P2 FECHA — nasce a `banco-escolha.html`, e ela existe para provar exatamente DUAS coisas.** A bancada chega por último de propósito: a SG-P1 já tinha provado onze contratos com **consumidor real em produção**, então o trabalho que sobrava era estreito e declarado — provar o **SG7** (contagem por segmento) e o **SG14** (multi-select que não fecha), os dois sem instância no acervo, e **fechar a última divergência de geometria**. Placar da bancada: **137 · 0 · 0** — ela exerce TODOS os catorze contratos, e é o único artefato do acervo em que nenhum sai `[n/a]`. **A altura já tinha convergido em 44px; o raio não**, e ele vira a **SG-P9**: pílula (§48) contra reto (§53), lado a lado, mesmo conteúdo — e com uma **terceira fileira estreita de propósito**, porque é na QUEBRA de linha que os dois divergem de verdade. *Mostrar a diferença é medição que o olho faz; afirmar qual é melhor seria opinião.* **Mais dois defeitos de instrumento, o quarto e o quinto da série:** a guarda do SG7 testava `/\b\d+\b/` no texto do segmento e **nunca casava** — em `"Em campo"+"7"` o texto concatenado vira `"Em campo7"` e não há fronteira de palavra entre `o` e `7`; ela devolvia `[n/a]` sobre uma bancada que TINHA a instância. E, mesmo funcionando, media a FORMA ("tem número?") em vez do CONTRATO ("o número muda ao filtrar?") — agora ela TROCA a escolha e mede. A sonda do SG14 deixava a lista ABERTA sobre a fileira de chips e derrubava a guarda seguinte: *quem mexe, arruma*. Agregado das cinco alvos: **215 · 0 · 29 [n/a]**. · **v0.84: O GATE APROVA e o §60 vira `estável` — mas a auditoria de promoção acha um contrato que NUNCA tinha sido testado, e ele estava violado.** Gate visual do Rafael em 2026-08-16 (*"verificado, tudo ok"*) sobre `tela-painel` v0.11, `tela-tabela` v0.2 e `tela-lista` v0.2: a **SG-P8 fecha**. Antes de promover, perguntei quais dos catorze contratos tinham **prova** e não só texto — e quatro não tinham guarda nenhuma: **SG3** (proibição sem instrumento é só uma frase), **SG7** e **SG14** (sem consumidor no acervo) e **SG8**. Ao escrever a guarda do SG8 ela achou a violação: o botão `✋ Mover` levava o glifo **dentro do nome acessível** — o leitor de tela anunciava *"mão levantada Mover"*. *Contrato sem guarda é contrato sem prova, mesmo quando existe consumidor.* E a guarda nasceu errada pela **terceira vez na série**: usava `textContent`, que **não é** nome acessível (inclui a subárvore `aria-hidden`), e por isso continuou reprovando o conserto certo. Promoção **parcial e declarada**: SG1–SG6b e SG9–SG13 vão a `estável` porque têm consumidor medido; **SG7 e SG14 continuam `rascunho`** — não existe instância deles no acervo, e é isso que a SG-P2 passa a significar. Placares: escolha **96 · 0 · 29 [n/a]** · painel **146 · 0** · contraste **78 · 0** · contêiner **66 · 0**. Selo do painel a **v0.12** — mudança de um atributo, invisível na tela, mas bytes que mudam com selo parado foi o defeito da v8.9. · **v0.83: AS QUATRO PENDÊNCIAS DE GATE FECHAM — e a medição derruba a revisão que eu ia propor.** SG-P4/SG-P5: os quatro grupos de escolha exclusiva do §48 (lentes · escala de tempo · gesto no vazio · modo do mapa) passam a `radiogroup` + `role="radio"` + `aria-checked`, com **um mecanismo de roving só** para os quatro — antes eram três mecanismos diferentes. SG-P7: alvo do X do chip sai de **24×24** para 44px reais, alvo do X da condição de 32 para 44, `"+N"` com teto declarado, anúncio com contagem, `"Limpar"` com piso de dois. **SG-P6 é o achado da edição:** eu ia relaxar o SG6 ("fundo E peso") porque o fundo sozinho media **4,60** e **4,57** contra piso **3,00** — e a medição em `forced-colors: active` mostrou a separação caindo para **1,00**, com o selecionado ficando **indistinguível** do repouso. *O SG6 estava certo pelo motivo errado: dizia "não só cor" pensando em daltonismo, e quem cobra a fatura é o alto contraste.* Não relaxa: vira **SG6-b**, medido nos TRÊS modos, com cor de sistema — depois do conserto mede **19,04**. O **SG5** esse sim cede, com número: as oito lentes cabem em 390/768/1180/1440px em 4/2/1/1 linhas, alvo mínimo **62×44px**, sem vazar nem cortar — o teto é de **aperto medido**, não de contagem. E a SG5 revista **achou defeito que a antiga não alcançava**: o segmento do operador do §53 media 62×**32px**. **Duas guardas afirmavam a forma errada:** a `PN6-01` exigia `aria-current` (por isso cinco camadas atravessaram o defeito — a guarda não estava cega, estava apontada para o lado errado), e o meu próprio instrumento casava as passadas extra **por posição** em vez de por identidade. Placares: escolha **88 · 0 · 21 [n/a]** (4 arquétipos) · painel **146 · 0** · contraste **78 · 0** · contêiner **66 · 0**. · **v0.82: A SG-P1 FECHA — e a auditoria acha SEIS controles em QUATRO arquivos, não três em três.** Instrumento novo: `validacao/suite-escolha.mjs`, **52 PASS · 22 FAIL · 21 [n/a]**. O achado que justifica a ordem "auditar antes de produzir": **o acervo já resolvia a mesma pergunta de TRÊS jeitos incompatíveis** — `tela-painel.html` com `role="group"` + 8 `<button>` + `aria-current="page"` (a saída do eBay, *explicitamente descartada* no §60.1), `tela-lista.html` com `radiogroup` nativo (conforme SG1), e ainda uma terceira no MESMO arquivo do painel: a "Escala de tempo" é escolha **exclusiva** de 1 entre 4 feita com quatro `aria-pressed` — a exclusividade mora no script e **nunca chega à tecnologia assistiva**, que anuncia quatro interruptores independentes. Produzir a bancada primeiro teria criado a QUARTA variação. **Três defeitos da própria spec, medidos:** o **SG5** (teto de 5) é violado pelo seu primeiro consumidor, que tem **oito** lentes `estável` e aprovadas em gate; o **SG6** ("fundo E peso") é violado por **2 de 2** dos segmentados, e a medição mostra que o fundo sozinho entrega **4,60** e **4,57** de separação de valor contra piso **3,00** — a letra é mais estrita que o próprio motivo; e a tabela de consumidores **apontava o §53 para o arquivo errado** (o construtor FC1–FC6 vive em `tela-lista.html`, não em `tela-tabela.html`). **O pior defeito é o que parecia resolvido:** o X do chip declara alvo de 44px por `::before` absoluto, mas o botão é `position:static` — o alvo ancora em `<html>` e o alvo real mede **24×24px**. Supersede em §60.6; três decisões de gate abertas. · **v0.81: NASCE O §60 — ESCOLHA SEGMENTADA E MÚLTIPLA (SG1–SG14), fechando C3 e C12.** A R1 achou uma **DIVERGÊNCIA DE CANON**: cinco fontes de peso dão **quatro semânticas diferentes** para o mesmo componente — o Primer proíbe `radiogroup` (*ele requer botão de salvar*), o useUI o exige, o Workday usa `aria-pressed`, o eBay usa `aria-current`. A seção resolve com **três saídas e uma régua: pergunte o que MUDA** — a forma de ver o mesmo dado é segmentado, quanto do dado aparece é interruptor, o dado é aba. E ela chega **depois dos consumidores**: o §48, o §53 e o §56 já usam esses controles sem spec, então o primeiro trabalho não é produzir — é **auditar se os três resolvem o mesmo problema do mesmo jeito** (SG-P1). *É o inverso do §54, que existia como artefato sem seção.* · **v0.80: A BANCADA DO §59 NASCE e a seção ganha seus NÚMEROS.** `banco-dominio.html` com sete estados, formatação por `Intl.NumberFormat` em pt-BR e parser derivado do formatador. **Achado real de contraste que QUASE virou conveniência:** o separador entre adorno e campo media **1,11** e a saída fácil seria declará-lo decorativo — não cabia, porque a diferença entre as duas superfícies é de apenas **1,08** e a linha é o ÚNICO sinal. **Quarto achado seguido da mesma família**, e a régua ganha teste: *antes de declarar decorativo, meça se existe OUTRO sinal*. A guarda `FMT-02` prova o VD5 com o caso que separa um ERP correto de um perigoso: em pt-BR **`1240.5` é doze mil quatrocentos e cinco**. Placares: domínio **28 · 0** · contraste **152 · 0** (76 pares). · **v0.79: ABRE A F7.5 — NASCE O §59, CAMPOS DE VALOR DO DOMÍNIO (VD1–VD16), fechando C9 e C10.** A rodada zero produziu um veredito de TIPO NOVO: para estes campos a leitura visual **não se aplica** — o produto de referência não tem campo de potência em kWp —, e a base é canon + norma + domínio. *"Não tem leitura" significa três coisas diferentes — não foi visitado, não existe instância, ou não se aplica — e tratá-las como a mesma leva a "vamos navegar" para um problema que a navegação não resolve.* Contratos que estruturam a seção: **VD1** (o usuário digita só dígitos; a unidade é adorno, régua literal do USWDS), **VD6** (nunca `type=number`, pelo comportamento inconsistente entre navegadores), **VD7** (o navegador só FORMATA — não há API de parsing, e ler de volta exige parser derivado do formatador) e **VD12** (`autocomplete` **não se aplica**, e inventar token seria a falha F107 — a orientação do W3C é que campo fora da lista de propósitos passa por não-aplicabilidade). A seção nasce só com relações; a bancada é o próximo movimento. · **v0.78: §57 E §58 VÃO A `estável`.** Veredito do Rafael depois do conserto dos dois defeitos do A5: **"tudo ok, pode prosseguir"**. A F7.4 fica com **dois dos três arquétipos entregues e promovidos**; o terceiro, **A15 carga de trabalho, está BLOQUEADO por ausência de instância** — o workspace de referência não tem visualização de carga criada, e criar uma seria ação de escrita. **Dois arquétipos, dois bloqueios de ambiente** (o A6 não carrega, o A15 não existe), ambos registrados com o motivo na §14 do estudo. A quarta volta de navegação rendeu cinco dispositivos (C112–C116), e um deles — **C113, navegação temporal por setas mais rótulo do período** — **CONFIRMA o GT17**, escrito no mesmo dia a partir de um defeito do gate: a leitura que eu não tinha convertido em contrato estava a uma volta de distância. · **v0.77: DOIS DEFEITOS DO GATE VIRAM GUARDA, e nasce o GT17.** (a) O menu do divisor abria **estourado**: usava a classe `.menu-mover`, que existe no gabarito A7 e **nunca foi copiada para o molde do A5** — *classe reaproveitada entre gabaritos é dependência, e dependência não copiada é referência órfã*. (b) O gráfico parecia travado; as barras clicavam e a região rolava, mas **rolagem horizontal com roda de mouse exige Shift**, e sem controle explícito o usuário conclui que a tela está morta. Nasce o **GT17: eixo que rola precisa de CONTROLE, não só de overflow — overflow é mecanismo, controle é affordance**. Oito guardas novas (POP e EIX); a suíte do gantt vai de 35 para **43 · 0**. · **v0.76: O GABARITO A5 NASCE e o §58 ganha seus NÚMEROS.** A spec tinha sido escrita só com relações; o `tela-gantt.html` mediu tudo — escala de **6px por dia** declarada sob o CP32, barra de 20px, base de 5px, losango de 14px, três larguras de painel. **Três achados reais de contraste, e os três mudaram o artefato:** a barra nasceu com `surface-brand` e o rótulo branco media **2,71** (turquesa de MARCA não é turquesa de AÇÃO); o marco nasceu com `entity-2` a **2,61**, e `entity-5` resolvia o claro mas reprovava o **escuro a 2,37** — cor fixa precisa passar nos dois temas; e o contorno do caminho crítico é **fronteira externa**, medido contra a plotagem. A guarda que vale mais é o bloco **ESC**: mede a barra em duas larguras de janela e exige que não mude — o CP32 aplicado a figura montada em HTML. Placares: gantt **35 · 0** · composição **27 · 0** · contraste **128 · 0** (64 pares). · **v0.75: NASCE O §58 GANTT E LINHA DO TEMPO (GT1–GT16) e a PN-P2 FECHA** — aberta desde a Fase 6 como "bloco 6D" (PN42–PN45: dependências com lag, caminho crítico, linha de base, marco). A fronteira com o §48 está declarada: a lente temporal (PN47, PN48, PN49) fica onde está e **não é reescrita**; o §58 acrescenta a camada de RELAÇÃO entre itens. Dois contratos merecem nota: o **GT11** manda a alternativa textual ser TABELA COMPLETA e não resumo, porque o estado da arte reconhece que dependência complexa **não expõe relação programática completa** — um fornecedor declara isso em VPAT —, e a tabela É a relação escrita; e o **GT8** obriga o lag a ser declarado no rótulo da conectora, seguindo o alerta do canon de que *lag que vira hábito esconde duração de base errada*. A seção nasce **só com relações**: os números vêm na produção do gabarito A5, pelo método do §55 — nenhuma medida é afirmada antes de medida. · **v0.74: ABRE A F7.4 — NASCE O §57 QUADRO/KANBAN (QD1–QD14) e o gabarito A7.** Decisão de fronteira que CONTRARIA o produto de referência: o C57 observou coluna com fundo tingido, e mantemos a **trilha TRANSPARENTE** do CP2 — seis colunas tingidas criariam seis superfícies cromáticas competindo com os cartões, contra o CP19 (*o croma vem das peças*); a identidade fica no cabeçalho. O contrato que estrutura a seção é o **QD6**: **mover sem arrastar é o caminho PRINCIPAL**, porque o SC 2.5.7 exige alternativa por PONTEIRO ÚNICO e é explícito em que isso **não é teclado** (SC 2.1.1, separado) — a técnica G219 do W3C foi escrita com este exemplo literal. Terceiro achado seguido de `border-default` reprovando o SC 1.4.11 (**1,37** e **2,61** na borda do convite). Placares: quadro **35 · 0** · composição **32 · 0** · contraste **108 · 0** (54 pares). · **v0.73: O §56 VAI A `estável` e a F7.3 fecha o que era possível fechar.** Segunda rodada de gate (a primeira foi sobre o arquivo errado — mesmo selo em duas versões diferentes do gabarito, defeito registrado na v8.9 do MANIFESTO). Veredito do Rafael: **"tudo ok, continue"**. Com isso **todas as seções da F7.3 estão `estável`**: §49 detalhe, §54 botão dividido, §55 chat e §56 atividade. Segue fora **A6 formulário**, bloqueado por defeito do ambiente de referência e não por falta de trabalho. · **v0.72: NASCE O §56 — ATIVIDADE E COMENTÁRIOS (CM1–CM12), e a fronteira do CP2 se dissolve.** A F7.3 abriu com a dúvida de que o comentário fosse *cartão dentro do cartão do objeto* — peça em peça, proibida pelo CP2 — e com a saída pronta de escrever uma exceção `CP2-c`. A **navegação real de 2026-08-16** (autorizada, somente leitura) desfez a premissa: **o comentário é BLOCO, não cartão** (medido no gabarito: borda 0, sombra none, raio 0), e a exceção fica DESCARTADA. A leitura rendeu o achado que estrutura a seção: **dois pesos no mesmo fluxo** — comentário com avatar e ações, evento de sistema como LINHA sem ações. Mais o **CM5**, dispositivo novo: no hover o cabeçalho TROCA o metadado pela ação, em vez de revelá-la ao lado (distinto do PD9). O §54 ganha seu **primeiro uso em tela**, no composer, com o teclado do menu button inteiro. Placares: detalhe **73 · 0** · composição **33 · 0** · contraste **84 · 0** (42 pares × 2 temas). **ACHADO DE CONTRASTE:** o marcador do evento nasceu com `border-default` (**1,49** e **2,32**, reprova o SC 1.4.11 nos dois temas) — daria para declarar decorativo, mas seria conveniência, e ele recebeu a tinta do texto que marca. · **v0.71: PROMOÇÃO EM BLOCO E FECHAMENTO DE DÍVIDA.** O Rafael aprovou em 2026-08-16 ("aprovo") a promoção de **§49 a §55** a `estável`, após gate visual sobre as SEIS telas do projeto — e o gate achou **dois defeitos que as cinco camadas automatizadas atravessaram**, ambos consertados e virados guarda ANTES da promoção (nasce o **PD19**). Nasce também o **§54 BOTÃO DIVIDIDO (BD1–BD9 + MN9)**, que existia como artefato desde 2026-08-15 e **não existia como seção canônica** — a dívida A-P1, medida na leitura de volta da v8.4 do MANIFESTO, fecha aqui pelo rito completo de três rodadas, e não por transcrição do comentário do CSS. Três contratos NOVOS vieram da pesquisa e não estavam no artefato: **BD7** (o gatilho nunca executa ação), **BD8** (sem variante destrutiva; destrutiva só uma vez e por último) e **BD9** (no toque e no degrau estreito o botão dividido COLAPSA em botão de menu — o padrão não é recomendado para toque, pelo problema do dedo grosso). · **v0.70: NASCE O §49 — DETALHE DE REGISTRO (PD1–PD18), e com ele a pendência PN-P3 FECHA depois de aberta desde a Fase 5.** Primeira seção do projeto escrita já sob a RODADA ZERO (conferir leitura visual antes de especificar): os sete dispositivos vêm da §13.4 do `estudo-clickup-completo.md` (C50–C56). Gate do Rafael de 2026-08-15 sobre amostra A/B navegável: *"modal A por padrão, e opção do modelo B caso o usuário prefira… no modelo A, deixa opção de expandir a janela depois que ela for aberta"* — daí saem PD1 (dois regimes, modal padrão), PD17 (dois degraus da janela por BOTÃO, não por arraste: arraste cai sob o SC 2.5.7, mesmo raciocínio que virou slider no §51) e PD18 (a alternância vive DENTRO do detalhe, porque com o modal aberto o fundo é inerte e um controle fora dele seria inalcançável). **A pesquisa achou uma LACUNA DE CANON:** não há padrão publicado para troca de conteúdo dentro de um diálogo já aberto — trocar o título com o foco parado é silencioso para leitor de tela —, e o PD3 é decisão nossa, declarada. Nasce o gabarito **A8** `tela-detalhe.html` com gerador, molde e a suíte própria `suite-detalhe.mjs` (47 · 0), mais `contraste-f73.py` (28 pares × 2 temas, **56 · 0**). **Achado real da medição de contraste:** a fronteira dos botões usava `border-default`, que mede **1,49** sobre branco e **2,32** no escuro e reprova o SC 1.4.11 nos dois temas — trocada por `border-interactive` (3,38 · 4,50). O §49 nasce em `rascunho`: o gate visual do Rafael é o ato que promove. · **v0.69: SUPERSEDE PARCIAL DO CD1 (decisão do Rafael no pacote PN-P7/CP-P2, verbatim: "o que vai prevalecer é o que construímos agora… acredito ser a opção A") — quando o card é PEÇA sobre canvas, a separação é o PAR SOMBRA+ANEL do CP17; o outlined literal permanece legítimo só em contexto interno a outra peça (well do CD5). O conflito era latente desde 2026-08-14 (CP17 nasceu sem emendar o CD1) e foi ACHADO pelo inventário medido do retroativo PN-P7/CP-P2 — a razão de os previews de componente terem ficado FORA daquele lote (migrar artefato de spec sem supersede escrito inverteria a ordem decisão→artefato). Consequência: nasce a pendência CP-P4 (retrofit dos previews de componente sob este supersede), registrada no MANIFESTO v7.7. Na mesma edição, o fecho do §47 registra o FECHAMENTO do retroativo: seed-site-preview v0.2 consome `border-interactive` com 0,00% de diferença medida em pixel (promoção de expressão, não de cor). Nenhum par de cor novo; nenhum preview alterado POR ESTE ARQUIVO (os artefatos migraram no pacote v7.6, pela via molde/gerador). · **v0.68: SUPERSEDE DA EXCEÇÃO DO RAIL (gate visual de 2026-08-15 sobre o preview v0.3).** Veredito do Rafael: o rail se apresenta "de forma descolada, como uma barra flutuante, um bloco montado como os outros — todos independentes, igual o ClickUp usa". A exceção nomeada do CP1 (rail encostado na borda, de topo a base, sem raio) morre; o rail vira PEÇA sobre o canvas: radius-lg, calha sp-3 nos quatro lados, SEM borda com o porquê medido (peças brancas precisam do 1px porque medem 1,21 contra o canvas; o gradiente do rail mede 5,82/8,62 claro · 2,70/1,82 escuro — a cor é a fronteira). A mudança expôs e corrigiu um DEFEITO LATENTE: a barra branca do item ativo (SH5) desenhava FORA do box do rail desde o SH15-b (left:-8px com overflow clipando; probe de pixel: x=-3 com o box em x=0) — invisível em render, invisível para a suíte (SH5-04 só checa presença de regra). Corrigida para a borda interna e provada em cor renderizada (rgb 255,255,255 no pixel). Nota do §46.1 atualizada; supersede formal no CP1 do seed-composicao.md v1.4; shell no preview v0.4 (suíte 72·0 · render 18·0 · contraste 51·0); MANIFESTO v7.1. · **v0.67: a PENDÊNCIA SH-P1/PN-P5 FECHA — nasce `--seed-border-interactive` nos gêmeos v1.12.** Gatilho: a regra GI2 da própria pendência ("fecha quando a camada 2 for editada por outro motivo") disparou com a CP-P3, e o Rafael decidiu fechar na mesma janela (2026-08-14: "vamos resolver logo, não tem tempo pra depois"). O token: fronteira de componente interativo (SC 1.4.11), `cinza-500` `#788F9D` promovido de empréstimo declarado a semântico — MESMO valor renderizado (promoção de expressão, não de cor), medido contra as seis superfícies dos dois temas (3,11–5,51, todos ≥3,0), slot dark declarado de propósito. Alternativa descartada com número: `cinza-600` passa (3,21–4,74) mas mudaria cor aprovada em gate sem motivo. Quatro trechos deste arquivo atualizados: a pendência do §46.2 (fechamento anexado ao registro histórico), o fecho do §46, o fecho do §47 (o campo de formulário ganha o semântico à disposição; migração do artefato é retroativo classe PN-P7) e a linha PN-P5 do §48.9. O shell consome desde o preview v0.3 (suíte 68·0 · render 18·0 · contraste 47·0). Racional completo: `seed-tokens.md` v1.12 §3 e MANIFESTO v6.9. · **v0.66: fechamento documental da CP-P3 — duas correções de redação no §46, nenhuma decisão de componente muda.** **(1) Errata E-CF-05 fechada (regra GI2):** a linha SH15 da tabela do §46.1 ainda descrevia o rail da era `cinza-900` ("e o item ativo usa `#006C62` fixo") — DUAS gerações atrás do artefato: a E-CF-04 (v0.65) consolidou o parágrafo "Rail" do §46.2 e esqueceu a linha da tabela, exatamente a classe "duas redações do mesmo fato no mesmo arquivo" que a E-6C-01 ensinou a caçar. A linha passa a declarar o estado atual (gradiente `--seed-rail-background` + pílula clara SH15-b) preservando a REGRA original do SH15, que nunca mudou — só o valor mudou, duas vezes. **(2) O parágrafo "Rail" do §46.2 é atualizado:** a redação "gradiente admitido pelo CP20 — adoção pendente de gate visual" ficou FALSA após o gate de 2026-08-14 (veredito G2 adotou) e a regeneração da CP-P3 (executou): o rail consome `--seed-rail-background` (gêmeos v1.11) e os overlays de chrome do SUP-6, medidos em composição (4,88/6,82 · 4,68/6,50). Placares da regeneração no MANIFESTO v6.8; a errata E-CP-01 (C1 consumindo a categórica) fecha lá e no `seed-composicao.md` v1.2 §9. · **v0.65: a Fase 2 da composição de tela toca este arquivo em quatro pontos, nenhum deles mudando decisão de componente.** **(1) SUP-1:** o contrato de montagem (ex-SH16) vira lei no canônico novo `seed-composicao.md` §1 (CP1); o §46.1 ganha a nota de referência e o cabeçalho corrigido de "SH1–SH14" para "SH1–SH15" (**errata E-CF-03 fechada** — mesma classe da E-6C-01: título divergindo do conteúdo). **(2) Errata E-CF-04 fechada:** o parágrafo "Rail" do §46.2 dizia 64px, `surface-inverse` e implicava `cinza-900` — três divergências contra o SH3 (80px medidos), o SH15 (superfície fixa) e a decisão cromática do MANIFESTO v6.3 (`turquesa-700`); parágrafo consolidado, pela regra "quem edita o arquivo fecha as erratas dele" (GI2). **(3) SUP-2 aplicado no §32:** presença/status online entra no DS — correção de registro de uma fronteira `estável` cruzada por artefato (C2, MANIFESTO v6.4) sem supersede escrito. **(4) SUP-3 aplicado no §25:** a taxonomia Z1 passa de cinco a SEIS tipos (entra a Bifurcação, dispositivo C36), com emenda declarada à Z2 (na bifurcação, 2 ações é obrigação e ambas primárias). Nenhum preview foi alterado; nenhum par de cor novo NESTE arquivo (as medições da fase vivem no `seed-composicao.md` §7 e em `marca-seed.md` v5.2 §3.6-b). · **v0.64: duas correções de rastreabilidade, nenhuma decisão do §48 muda.** **(1) CITAÇÃO ÓRFÃ no §48.7:** o guia de ligação do mapa era citado como `validacao/mapa-base-lovable.md`, caminho que deixou de existir quando a duplicata byte-idêntica de `validacao/` foi apagada e a âncora passou à raiz (MANIFESTO v4.0, §19-b). Motivo da mudança de caminho: o `LEIA-ME.md` da pasta a declara como a das *suítes e scripts executáveis*, e este arquivo é documentação de entrega ao produto — como ligar MapLibre e a chave de geocodificação no Lovable —, não artefato de validação; nenhum script o lê, as citações eram textuais. **Citação órfã é a mesma classe da referência órfã que a guarda `REF-01` pega no código:** aponta para algo que não existe e ninguém percebe até tentar abrir. **(2) ENTRADA DE HISTÓRICO AUSENTE, e o defeito é meu:** a v0.63 bumpou o TÍTULO do arquivo e **não escreveu a própria entrada nesta linha de histórico** — título dizia v0.63, histórico terminava na v0.62. É a irmã exata da errata E-6C-01 (título × selo divergindo dentro do mesmo artefato), agora num `.md`. A entrada da v0.63 está restaurada abaixo, e a lição vale para qualquer canônico: **quem bumpa o título escreve a entrada na mesma edição.** · **v0.63: registro da INVERSÃO DO HERO — o azul sai, a âncora passa a TURQUESA VIVO `turquesa-400` #11B0A0 (L 33,71% contra 6,03% do `azul-800`, croma +64%), com tinta única `turquesa-900` (4,99), anel `turquesa-600` (4,60) e véu de luz `turquesa-300` a 35% no canto do texto (ΔL 5,84).** A solução veio da §9.2 do `estudo-viver-de-ia.md`: `--primary: #00EBCF` — a primária da plataforma é um turquesa vivo a 1,1° de matiz do nosso `turquesa-300`; o navy dos prints é *superfície de tema escuro*, e nas duas rodadas anteriores eu havia copiado a superfície deles deixando o nosso turquesa como enfeite. **S3 revisto · S4 RETIRADO** (a ação volta à família da marca e o conflito com o §3.5 de `marca-seed.md` deixa de existir; a régua vigente é `turquesa-600` #098475 + branco = 4,60, o hex que o §3.5 nomeia) **· S11, S11-b e S12 novos** (âncora cromática; a palavra "escuro" sai do contrato do PN21c porque era descrição de implementação, não invariante; e a fronteira de 3,0 passa a distinguir COMPONENTE de CONTEÚDO — a pílula interativa deve 3,0 pelo SC 1.4.11, o chip de status é isento porque a norma exige dele o texto pelo 1.4.3, medido em 6,58). Ação dentro da âncora desceu a `turquesa-800` por decisão do Rafael (texto 9,37 · fronteira 3,45; o `700` reprovaria a 2,33). **Defeito da rodada, pego pela quinta camada: token fantasma** — o molde passou a consumir `turquesa-400/600/700` e a lista `PRIMITIVOS` do gerador ainda era a da era do azul; variável CSS indefinida é valor inválido, e 15 textos foram medidos com razão 1,0–1,26, incluindo branco sobre branco. Detector **`NV-01`** criado nesta suíte (existia na de navegação e faltava aqui). Preview v0.8, cinco camadas verdes **139 · 37 · 142 · 6 · 78**. PN-P1 encolheu de três decisões para uma e meia. · v0.62: PROMOÇÃO DO §48 PAINEL A `estável` — BLOCO 6C FECHADO, e com ele a FASE 6 chega a 3/3 (6A Shell · 6B Página institucional · 6C Painel).** Gate visual do Rafael aprovado em 2026-08-13 sobre o preview **v0.6**, depois das quatro camadas automatizadas: `suite-painel.mjs` **133/133** · `render-painel.mjs` **37/37** · `render-edicao.mjs` **142/142** · `render-contraste.mjs` **6/6** · `contraste-painel.py` **70/70** — **primeiro bloco do projeto validado em CINCO camadas**, e a quinta nasceu de um defeito que as outras quatro não podiam ver (contraste de token verde com composição renderizada reprovando a 1,48). **RESSALVA FORMAL DA PROMOÇÃO, escrita no cabeçalho do §48 para não se perder:** o `estável` cobre os **contratos** PN1–PN54; **não** cobre as três decisões de MARCA da camada visual — **S3** (escuro estrutural `azul-800` #004C61, croma 0,380), **S4** (a ação usa o escuro estrutural: primária sobre claro `azul-800`+branco 9,52, pílula branca dentro do escuro 13,70, `azul-400` no tema escuro — **em conflito declarado com o §3.5 de `marca-seed.md`**, que manda ação sempre em turquesa/verde) e **S6** (tinta da família, `text-primary` #0B3330 × branco 13,72). As três seguem **PROPOSTA**, vivem no preview (a tinta como camada alternável) e são a pendência **PN-P1**, que bloqueia o resto do DS — ERP, e-mail, documentos e site — até ser registrada em `marca-seed.md` e refletida nos gêmeos de token v1.8 → v1.9. **Próximo bloco, nesta ordem: as decisões de marca (PN-P1), porque nada mais avança sem elas.** Demais pendências herdadas e nomeadas em §48.9: PN-P2 (bloco 6D — dependências FS/SS/FF/SF com lag, caminho crítico, linha de base, marco) · PN-P3 (§49 painel de detalhe) · PN-P4 (malha nacional: a canônica cobre só MG/ES/BA, 1.348 municípios) · PN-P5 (SH-P1, aberta desde o 6A) · PN-P6 (três medidas de composição sem guarda automatizada) · PN-P7 (retroativos nos outros 40 HTML). **Nenhum artefato executável foi alterado nesta promoção** — os cinco placares acima são os da rodada 3, re-executados contra o preview v0.6 final e conferidos por leitura de volta a partir do pacote entregue. · v0.61: o §48 (Painel) é escrito por inteiro — PN1–PN54 — e o bloco 6C fica pronto para o gate visual único.** Entram nesta edição: a **edição direta** (PN22–PN41, mais PN34b, PN35b e PN41b–e) sob a norma **WCAG 2.2 SC 2.5.7 Dragging Movements (AA)**, governada por uma regra só — *onde o item cai é o valor do campo* (PN33); os **cinco contratos que o gate do Rafael produziu** (PN46 política de trava é do domínio · PN47 pré-requisito de lente passa de "todos" para "alguém" + gaveta de não agendados · PN48 régua de tempo em dois níveis · PN49 criar arrastando na faixa vazia · PN50 menu de contexto e cartão de edição); a **lente mapa em dois modos** (PN51–PN54, cobertura padrão × operação, com fallback declarado e alternativa textual como conteúdo); a **camada visual** reorganizada em PN21a–h; e a seção **48.8 com dez supersedes formais**, cada um com o número que o motivou. **PN42–PN45 ficam RESERVADOS e vazios** — são o bloco 6D (dependências FS/SS/FF/SF com lag, caminho crítico, linha de base, marco) —, e o vão de numeração é intencional: os cinco artefatos executáveis já citam PN46–PN54, e renumerar invalidaria âncoras MD5 recém calculadas. **Subseções reordenadas** com autorização do Rafael (a ordem era 48.1 → 48.2 → 48.4 → 48.5 → 48.3, com "Os 7 testes" depois do lote 2 e o PN21 como linha de tabela órfã sem cabeçalho). **Cinco camadas de validação verdes contra o preview v0.6:** `suite-painel.mjs` **133 PASS · 0 FAIL** · `render-painel.mjs` **37 · 0** · `render-edicao.mjs` **142 · 0** · `render-contraste.mjs` **6 · 0** · `contraste-painel.py` **70 · 0**. **Camada nova: `render-contraste.mjs`**, nascida de uma lição medida — *contraste de TOKEN não substitui contraste de COMPOSIÇÃO*: todos os pares do script Python estavam verdes enquanto, no tema escuro, a ação primária em `azul-800` media **1,48** de superfície contra o cartão e a secundária tinha texto `azul-800` sobre cartão escuro (1,48, ilegível); nenhum par estava errado, a combinação estava. **Errata E-6C-01:** o `<title>` do preview declarava *preview v0.3* enquanto o selo na tela declarava *v0.5* — duas versões no mesmo arquivo, e a guarda `HIG-03` passava porque media apenas presença. Grave porque o título da aba é o primeiro sinal de arquivo fresco (três relatos de "não funciona" nesta sessão foram cache). Conserto **estrutural**: fonte única `VERSAO_PREVIEW` no `gen-painel.py`, consumida por `@@VERSAO_TITULO@@` e `@@VERSAO@@` — divergir deixa de ser possível; preview a **v0.6** porque o arquivo corrigido tem de ser distinguível na tela. **Guarda de regressão permanente creditada ao Rafael: `HIG-06`** (título e selo declaram a mesma versão), provada contra artefato com defeito plantado — **reprova em exatamente 1**. **Pendência que bloqueia o resto (PN-P1):** as decisões de MARCA S3 (escuro estrutural `azul-800` #004C61, croma 0,380), S4 (a ação usa o escuro estrutural, em conflito declarado com o §3.5 de `marca-seed.md`) e S6 (tinta da família, `text-primary` #0B3330 × branco 13,72) vivem hoje no preview — a tinta como camada alternável — e precisam de registro em `marca-seed.md` mais gêmeos de token v1.8 → v1.9. Demais pendências nomeadas em §48.9: PN-P2 (6D) · PN-P3 (§49 painel de detalhe) · PN-P4 (malha nacional: a canônica cobre só MG/ES/BA, 1.348 municípios) · PN-P5 (SH-P1, aberta desde o 6A) · PN-P6 (três medidas de composição sem guarda automatizada: massa escura ≤32% da primeira dobra, hero ≤35% da altura, página ≥248 de luminância média) · PN-P7 (retroativos nos outros 40 HTML). **Nota de determinismo:** o selo carimba data e hora, então regenerar o preview muda o MD5 sem mudar comportamento — conferido, a cadeia de geração reproduz 209.288 bytes e 3.730 linhas idênticas com **um único diff, a linha do selo**; para o preview, a âncora é o arquivo entregue, não a receita. · v0.47: **promoção do §45 Iconografia a `estável` — BLOCO 7 FECHADO 1/1 e, com ele, a FASE 3 INTEIRA: 7/7 BLOCOS.** Gates cumpridos, nomeados um a um: (a) **suite jsdom `suite-icones.mjs` 25/25 ✓**; (b) **render `render-icones.mjs` 5 verificações / 0 achados** (getBBox IC2 de 28/28 dentro de 1…23 · `currentColor` resolvido em cor renderizada · célula ≥44px · faixa 360px sem overflow · dark re-renderiza o glifo); (c) **contraste** — 6 pares non-text ≥3:1 medidos por script ANTES da spec; (d) **regressão total ancorada por MD5**, e não mais por tamanho de arquivo: **445 verdes jsdom** (feedback 55 · espera 44 · rotulagem 28 · sobreposição 25 · empty 14 · superfícies 80 · CN 45 · navegação 90 · dados 39 · ícones 25) **+ 43 verificações render / 0 achados** (38 `suite-render` sobre CINCO previews + 5 `render-icones`) **+ 4 scripts de contraste, todos os pares no gate WCAG**; (e) **gate visual do Rafael APROVADO em 2026-08-07**. **O fato de método central deste marco NÃO é a iconografia — é o DEFEITO DE PROPAGAÇÃO DO BLOCO 5, corrigido aqui:** o `seed-navegacao-preview.html` v0.40 havia sido sobrescrito no repositório do Drive pelo v0.37 defasado em 2026-08-06 22:54, e o defeito passou despercebido porque a propagação era *afirmada* e nunca *lida de volta* — consequências medidas: a suite de navegação rodou 58/58 em vez de 90 (verde e errada) e a `suite-render` ABORTOU no bloco R8 com `TypeError` procurando `#cp-dialog`. O v0.40 sobreviveu por acaso, na pasta de downloads dos cards de entrega, e foi resgatado de lá. **Dois artefatos de validação do Bloco 5 foram PERDIDOS e RECONSTRUÍDOS a partir desta spec** (§36–§39, íntegra e `estável`): a `suite-navegacao.mjs` de 90 testes (58 originais 5A/5B preservados + 32 novos BC-01…04 · PG-01…08 · ST-01…09 · CP-01…10 · NV-01) e o `contraste-navegacao.py` (5 pares — os 5 medidos batem com o registrado no marco v1.2). **Declaração de proveniência obrigatória: o 90/90 e o resultado de contraste do Bloco 5 são EVIDÊNCIA NOVA de 2026-08-07, não a evidência histórica do marco v1.2** — aquele placar foi executado contra artefatos que nunca chegaram ao repositório e hoje não é reproduzível. **Emenda ERRATA-NAV-01** (falso-positivo de suite, não bug de componente): os guardas `TB-24`/`MN-25` ("nenhum botão morto") foram escritos na v0.37 e desconheciam os 12 botões que 5C/5D/5E trouxeram no v0.40 — a lista de exclusão foi estendida SEM enfraquecer o guarda, porque cada um dos 12 passou a ter teste de liveness dedicado (BC-03 · PG-05 · PG-06 · ST-04 · ST-05 · ST-08 · ST-09 · CP-02 · CP-07). **Nasce a âncora por hash:** `validacao/MANIFESTO.md` — tabela de nome · versão · bytes · MD5 · consumidor · placar de todo artefato canônico, mais a **REGRA DE PROPAGAÇÃO vigente: todo artefato salvo é verificado por LEITURA DE VOLTA (título/versão + MD5 conferidos contra a tabela), nunca por afirmação de que foi salvo**. Racional: sem hash publicado, "regressão byte-perfeita" não tem referente — foi por isso que 21 de 21 arquivos passaram verde por tamanho enquanto um era a versão errada. **Regra de transporte nova:** o conector do Google Drive não serve para transportar artefato de regressão (base64 reemitido corrompeu silenciosamente 3 vezes num único arquivo — um byte extra e duas trocas de caractere Unicode); anexo em zip vira o padrão. **Correção de documentação:** a `suite-render.mjs` consome CINCO previews, não quatro (4 no laço + dados nos blocos R9/R10; aritmética 7×4 + R7 2 + R8 2 + R9 4 + R10 2 = 38). Pendências novas: alinhar a skill `seed-ds-ui` com a árvore de decisão NM4 (quando usar glifo do set SEED × quando usar Lucide) + o mapa de equivalências SEED↔Lucide (mesmo padrão da GI2 do marco v1.3) · normalizar o esquema de caminho das suites (hoje 4 pastas, misturando `import.meta.url` e caminho absoluto embutido) — **adiada de propósito, porque editá-las agora mudaria o MD5 recém-ancorado**. Próximo: marco v1.4 (padrão delta) e abertura da fase seguinte. · v0.46: **abre o Bloco 7 (Iconografia) — §45 em `rascunho`: guidelines IC/NM + inventário do set canônico (lotes 7A+7B em modo autônomo).** Consolidado IC1–IC7 + NM1–NM6 aprovado pelo Rafael em 2026-08-07 ("aprovo") após 3 rodadas encadeadas (R1 canon: Lucide design rules/Material icon grid+keylines/Carbon sizes → R2 mercado/stack: shadcn+lucide-react (o consumo real do GI4), Heroicons, Phosphor → R3 normas/fora-do-circuito: WAI decorative images, contraste non-text 3:1, prática CFPB/Carbon de aliases de busca). **Nota de continuidade:** o chat do lote estourou o contexto antes de persistir os artefatos; nada do trabalho autônomo havia sido gravado no Drive — o lote foi RE-EXECUTADO nesta sessão sob o mesmo consolidado aprovado (as decisões são o artefato gateado; inventário/vereditos eram produção autônoma ainda sem gate, re-produção legítima). **Base medida do acervo (script sobre o HTML v1.0):** 54 `<svg>`; grid nominal de 24 ícones nomeados em 40×40 stroke 1.8 (= **1.08px equivalente a 24px** — abaixo do peso da UI) + 5 glifos de pilares + 3 de valores em 24×24. **Set canônico resultante: 28 glifos ativos** = 23 do acervo normalizados (40→24, fator 0.6, stroke 2, round/round) + `substation` (pilar aproveitado) + `bolt`/`generator` REDESENHADOS na régua (fill→stroke; motor idx14 simplificado) + `transformer`/`lightning-rod` NOVOS (gaps dos pilares Subestações MT e SPDA fechados). **7 vereditos de governança**: renome `add`→`circle-plus` (NM1 conceito-variante, supersede) + 6 aposentadorias por duplicata (pilares/valores → glifos do set). Contraste non-text medido por script ANTES da spec: 6 pares consumidos ≥3:1 ✅ (text-primary×surfaces 13.35–15.59 · text-secondary 5.06 · feedback-success 4.60). Preview NOVO `seed-icones-preview.html` v0.46 (grid com metadados data-*, busca por aliases PT/EN — consumo da ⌘K §39 —, escala IC4, provas antes/depois de peso e SEED×Lucide, dark/grayscale/360px, zero elemento morto) + suite nova `suite-icones.mjs` **25/25 ✓** (1 correção de escopo de teste na 1ª execução: alias `subestacao` é legítimo em substation E transformer — falso-positivo de suite, não bug) + **render `render-icones.mjs` 5 verificações/0 achados** (getBBox IC2 de 28/28 dentro de 1…23 · currentColor resolvido · célula ≥44px · 360px sem overflow · dark re-renderiza). Pendente: regressão total + gate visual do Rafael → promoção FECHA A FASE 3 → marco v1.4. · v0.45: **promoção do lote 2 — §42 toolbar, §43 lista de dados e §44 guia de integração a `estável`: BLOCO 6 (DADOS) FECHADO 5/5** (§40 tabela · §41 seleção · §42 toolbar · §43 lista de dados/degradação · §44 guia). Gates: suite 39/39 + render 38/0 + regressão total 519 verdes + gate visual do Rafael aprovado em 2026-08-06 ("ok"). **Nota do marco: os DOIS gates humanos do bloco passaram SEM errata — primeira vez na F3; a camada render absorveu as classes mecânicas (2 bugs reais pegos por ela no lote 1) e os detectores herdados pegaram as reincidências sozinhos.** Pendências encerradas no bloco: indeterminate (§10/B2, via SL2) · busca composta EUI (B2, via TD1) · densidade ERP→mobile (B2, via DT2+LD2). Pendências novas registradas: alinhar a skill `seed-ds-ui` à camada semântica (GI2) · "Show Details" dos low ocultos (LD5, com demanda) · variante de paginação com total desconhecido (PG5/DT8, com demanda). Próximo: marco v1.3 (padrão delta) → **Bloco 7 (Iconografia) fecha a Fase 3**. · v0.44: **lote 2 do Bloco 6 — §42 toolbar de dados (TD) + §43 lista de dados/degradação (LD) + §44 guia de integração (GI) em `rascunho`.** Pesquisa: EUI SearchBar/FilterGroup + Innovaccer/shadcn-blocks (chips de filtro aplicado com contagem viva) + Dynamics (toolbar inline×stacked) para o TD; MDN/eBay evo/Ben Myers/Fedotov (dl com grupos em div; dl×table para pares rótulo-valor) + **Fiori pop-in/importância de coluna** (fonte já desta sessão) + Setproduct (régua tabela×cards×lista) para o LD; **leitura obrigatória da skill `seed-ds-ui`** para o GI — 1 ponto de coerência real achado e documentado (a skill consome primitivos+aliases shadcn; o DS entrega semânticos `--seed-*` → cadeia de 3 camadas formalizada no GI2 + pendência de alinhamento da skill registrada, não é conflito bloqueante). Nenhum par novo (tudo herdado, incl. o chip do §22). Preview v0.44 + suite 6C/6D (**39/39 ✓**; **os detectores do marco v1.2 pegaram sozinhos o mesmo token fantasma e a whitelist desatualizada na 1ª execução — as lições 9.3/9.4 agora são automáticas**) + render R10 (chips pintados; degradação cards renderizada no 360: linha=bloco, coluna low oculta, zero overflow) — **38 verificações, 0 achados**. Teste de integração TD-04 prova a SL5 (filtro limpa seleção). Pendente: gate do Rafael → promoção fecha o BLOCO 6. · v0.43: **promoção do lote 1 — §40 tabela e §41 seleção a `estável`.** Gates: suite 27/27 + render R9 (2 bugs reais pegos pela camada: sticky×border-collapse, guarda DT-13; piso da compacta) + regressão total verde + gate visual do Rafael aprovado em 2026-08-06 ("correto"). Pendência do indeterminate (§10, aberta desde o B2) formalmente encerrada pela SL2. · v0.42: **Bloco 6 (Dados) aberto — LOTE 6A+6B: §40 tabela base (DT1–DT8) + §41 seleção/ações em massa (SL1–SL5) em `rascunho`.** Estrutura do bloco aprovada pelo Rafael (5 sub-blocos 6A–6E em 2 lotes; códigos DT/SL/TD/LD/GI). Pesquisa 3 rodadas por sub-bloco (~18 fontes: corpus Roselli de tabelas — sortable/aria-sort/responsivo/"Don't Turn a Table into an ARIA Grid", 2023 — · Carbon data table/batch actions · **Fiori responsive×grid table com importância de coluna e pop-in** (a fonte da degradação mobile, que fica para o 6D) · TanStack row selection (a stack do Lovable: indeterminate, ids estáveis, shift-range) · Polaris/Gmail). **7 pares novos MEDIDOS antes da spec** (`contraste-dados.py` ✅): seleção 12.92/11.23 · header 6.03/8.92 · seta ativa 4.23 · borda 4.28. Preview NOVO `seed-dados-preview.html` + `suite-dados.mjs` **27/27 ✓** (5 correções de teste na 1ª execução: dispatch nativo de checkbox alterna o checked; nó morto pós-render — falso-positivos de suite, lição 9.4 confirmada) + **render R9: a camada pegou 2 BUGS REAIS antes do gate humano** — `border-collapse:collapse` quebrando o sticky do th (correção canônica: `separate`; guarda DT-13) e a densidade compacta rendendo 37px porque o controle de 36px segurava o piso (controles reduzidos na compacta; 33px medido) — mais 1 asserção do próprio auditor refinada (o deslocamento inicial do sticky é o caption rolando, legítimo). Render total: **36 verificações, 0 achados**. Pendente: gate do Rafael no lote → promoção → lote 2 (6C+6D+6E). · v0.41: **promoção em lote dos §36–§39 a `estável` — BLOCO 5 (NAVEGAÇÃO) FECHADO 6/6** (§34 tabs · §35 menu · §36 breadcrumb · §37 paginação · §38 stepper · §39 ⌘K). Fecha a 2ª metade do marco v1.2 e valida o 1º lote do modo autônomo: 4 itens produzidos sem paradas, 2 erratas do gate visual do Rafael (ação terminal do wizard; token fantasma da palette), ambas com teste-guarda + detectores permanentes nas duas camadas. Gates: consolidados BC/PG/ST/CP aprovados → specs + preview v0.40 + suite jsdom **90/90 ✓** + **suite-render 32 verificações/0 achados** (Ctrl+K real em `:modal`, 360px em pixels, fundo opaco, tokens fantasma) → regressão total **381 verdes** (A–E 166 + F 80 + CN 45 + Bloco 5 90, sobre os canônicos v0.23b/v0.32b) → gate visual do Rafael aprovado em 2026-08-06. Pendências encerradas neste bloco: AC5 (TB1) · fronteira do §20 (ST1) · promessa do §4.11 (CP2) · ponto DW1 (CN6, na 1ª metade). Próximo: marco v1.2 (padrão delta) → Bloco 6 (Dados). · v0.40: **1º LOTE DO MODO AUTÔNOMO — 5C+5D+5E completos: §36 breadcrumb (BC) · §37 paginação (PG) · §38 stepper (ST) · §39 command palette (CP), todos em `rascunho`.** Produção contínua sem paradas (nenhum ponto de decisão genuíno surgiu; 1 quase-ponto resolvido com régua dupla documentada no ST — evidência GOV.UK/DWP contra indicadores em serviço público × wizard Fiori para ERP, precedente do B3). Pesquisa: 3 rodadas por sub-bloco (~20 fontes novas — detalhadas em cada §). **Nenhum par de cor novo nos 4 itens** (tudo herdado e declarado por seção). Preview v0.40 (4 demos novas) + suite jsdom **87/87 ✓** (3 correções de escopo de teste na 1ª execução: whitelists TB-24/MN-25 e independência de ordem do ST-06 — falso-positivos de suite) + **suite-render estendida com cenários do lote: Ctrl+K REAL abrindo `:modal` nativo com foco no input e activedescendant conferido em pixels; 360px com stepper vertical, paginação compacta e zero overflow — 28 verificações render, 0 achados**. Regressão jsdom completa verde na mesma execução (A–E 166 + F 80 + CN 45 sobre os canônicos v0.23b/v0.32b). **Errata do gate visual do lote (2026-08-06, crítica do Rafael — "o item 4 não finaliza com check"):** na última etapa o Avançar era botão morto — o wizard nunca CONCLUÍA, e a etapa final nunca ganhava o ✓; a própria ST4 já apontava o desenho certo (Fiori: a Revisão termina em SUBMISSÃO explícita). Corrigido: na última etapa o botão primário vira "Concluir proposta"; concluir fecha o processo (4/4 ✓, corrente removida, mensagem de envio, variante-texto vira "Concluído — 4 de 4"). Regra que fica na ST4: **o wizard SEMPRE termina numa ação terminal nomeada — nunca num Avançar inerte**. Testes-guarda ST-08/09. **Errata 2 do lote (mesma data — screenshot do Rafael: "olha como o Ctrl+K abre"):** a palette abria TRANSPARENTE — o CSS consumia `var(--seed-surface-overlay)` e o token não existia neste preview (token fantasma: variável indefinida = valor inválido = fundo transparente, conteúdo da página vazando através do modal). Corrigido: token definido nos dois temas (branco / #273137) e a regra dark unificada na mesma fonte. Classe nova de defeito ganhou **detector genérico nas DUAS camadas**: NV-01 na suite jsdom (todo var(--seed-*) consumido tem definição — protege qualquer extensão futura) e R5b na suite-render (varre os 4 previews) + verificação de fundo OPACO renderizado da palette (rgb branco confirmado em pixels). Régua permanente: **token consumido sem definição é bug de build, não de tema — as duas suites reprovam**. Suite jsdom 90/90 ✓ · render 32 verificações, 0 achados. Pendente: leitura das decisões pelo Rafael + gate visual dirigido + promoção em lote (→ v0.41 + marco v1.2). · v0.39: **camada nova de validação — GATE DE GEOMETRIA AUTOMATIZADO (`suite-render.mjs`) + auditoria retroativa dos 4 previews canônicos + MODO LOTE autorizado pelo Rafael.** Contexto: descoberto Chrome real no ambiente de execução → nasce a suite-render (Chrome headless medindo o RENDERIZADO: recorte de anel de foco em overflow, alvos de toque reais, overflow horizontal 360px, links em estilo default de user-agent, erros de console, empilhamento por elementFromPoint, screenshots light/dark/grayscale/360 por preview). Auditoria retroativa (26 verificações × 4 previews): estrutura aprovada — zero recorte de foco (valida a errata TB-28), zero link default, zero overflow 360, empilhamento real confirmado (popover sobre drawer na CN; menu sobre a página). **2 erratas REAIS de implementação de preview** (classe idêntica à errata v0.13 — specs já mandavam, previews divergiam): (a) feedback: gatilhos ⓘ/toggletip com style inline 28px SOBRESCREVENDO a classe de 44px sem hitbox → ::after inset -8px (alvo visual 28 mantido, alvo real ~44, padrão W4); (b) superfícies: `+N` do avatar 32px SEM a "hitbox herdada" que o §32/teste 7 declara textualmente → ::after inset -6px. **Previews corrigidos viram os novos canônicos: `seed-feedback-preview.html` v0.23b e `seed-superficies-preview.html` v0.32b** — regressões futuras apontam para eles; suites jsdom re-executadas TODAS verdes sobre os corrigidos (A–E 166 + F 80 + CN 45 + nav 58 = 349). Transparência do auditor: 2 falso-positivos do detector corrigidos (hitbox via ::after não era vista; 403 de fontes = proxy do ambiente de execução, não bug) e 2 tolerâncias documentadas com porquê (chip 28px = densidade spec'd §22 com X interno em hitbox 44; checkbox 20px dentro de linha §28 cujo alvo real é a linha 44px). **MODO LOTE (autorizado 2026-08-06):** produção autônoma de sub-blocos completos (pesquisa→consolidado→spec rascunho→preview→suite jsdom→suite render→regressão→auto-auditoria de percepção), com paradas APENAS em ponto de decisão genuíno; interação do Rafael comprimida a 3 momentos por lote (leitura das decisões · gate visual dirigido com screenshots · aprovação em lote); nada promove a estável sem ele. Piloto: 5C+5D+5E. · v0.38: **promoção do §35 menu/dropdown a `estável` — 5B fechado (2/5 do Bloco 5).** Gates: consolidado MN1–MN8 → spec v0.37 + preview + suite → 3 erratas/emendas do gate visual do Rafael, todas com teste-guarda (Tab-fecha/Esc-global/focusout · setas opcionais APG no disclosure · anel de foco interno em container com overflow — esta última emendando também o §34, padrão AV5) → **regressão completa: A–F byte-perfeitas 246 + CN 45 + Bloco 5 58 = 349 verdes; contraste-navegacao.py ✅** → gate visual do Rafael aprovado em 2026-08-06. Réguas novas que ficam: "um menu aberto nunca sobrevive à saída do foco" e "anel de foco sempre interno dentro de overflow". Próximo: 5C — breadcrumb (BC) + paginação (PG). · v0.37: **5B — §35 menu/dropdown em `rascunho`.** Consolidado **MN1–MN8 aprovado** após 3 rodadas (~24 fontes: R1 NN/g Menu-Design Checklist + NN/g Dropdowns (comando ≠ atributo; "gray out em vez de remover") + Carbon Menu/Overflow Menu + Polaris ActionList → R2 Radix DropdownMenu (menu button APG 1:1) + Wootonn (input dentro de role=menu = conflito estrutural) → R3 **corpus Roselli 2017–2026 + APG Disclosure Navigation** (navegação nunca usa roles de menu) + Terrill Thompson + Fiori Menu Button/Action Sheet). Decisão central **MN1: fronteira semântica de 4 saídas** (ação=menu · navegação=disclosure de links · valor=select/combobox B2 · conteúdo rico=popover §24) com PROIBIÇÃO de input dentro de role=menu. **MN6 registra a exceção documentada da linha "sem disabled"**: item de menu indisponível fica visível, aria-disabled, navegável pelas setas e com motivo — critério: menu é inventário de comandos (NN/g), diferente da aba (TB8). **Pares novos MEDIDOS antes da spec** (`contraste-navegacao.py`, todos ✅): destrutivo ve-700/branco 7.18 · ve-700/ci-50 6.61 · ve-200/overlay 8.67 · atalho/indisponível ci-600/branco 4.74 · ci-300/overlay 6.94. Preview estendido para v0.37 (menu de ações, overflow ⋯ com alternáveis, disclosure de links) + suite seção 5B: **52/52 ✓** no total (2 correções de escopo de teste na 1ª execução: whitelist do TB-24 e dispatch de Esc — falso-positivos de suite, não bugs de componente). **Errata do gate visual (2026-08-06, crítica do Rafael — "Tab pula pra fora e Esc não fecha"):** dois sintomas, uma raiz. Esclarecimento de padrão: Tab SAIR do menu é o comportamento APG correto (setas navegam itens; Tab sai — Radix/Carbon/SO idem); o bug era o menu FICAR ABERTO ao sair — o APG manda "Tab closes the menu", e com o foco fora, o Esc (que morava dentro do menu) ficava surdo. Corrigido em 3 camadas: Tab fecha (APG literal) + Esc global fecha qualquer menu aberto com o foco onde estiver + focusout fecha ao perder o foco. Testes-guarda MN-26/27/28; régua na spec: **um menu aberto nunca sobrevive à saída do foco**. **Emenda 2 do gate visual (mesma data — "no disclosure não consegui navegar com as setas"):** não era bug — no disclosure os links navegam por Tab (são links comuns), o oposto do menu; mas o próprio APG Disclosure Navigation prevê o tropeço e traz **suporte opcional a setas** no exemplo oficial. Crítica do Rafael = evidência de campo com o perfil de usuário real → opcional adotado: ↓ no trigger abre focando o 1º link; ↓↑ movem entre links **sem wrap** (distinção deliberada do role=menu); **Tab continua navegando** (links permanecem no fluxo, sem roving). Testes-guarda MN-29/30. **Errata 3 do gate visual (mesma data — screenshot do Rafael: anel de foco cortado nas abas):** o tablist com `overflow-x:auto` (rolagem TB6) recorta o que desenha fora da caixa — o anel externo global (offset +2px) era decepado, pior na 1ª aba. Corrigido: **anel INTERNO (`outline-offset:-2px`) nas abas**, mesmo padrão das linhas de lista §28; régua que fica na spec: **dentro de container com overflow, o anel de foco é sempre interno**. Teste-guarda TB-28. Suite: 58/58 ✓. Pendente: regressão byte-perfeita e gate visual. · v0.36: **promoção do §34 tabs a `estável` — 5A fechado (1/5 do Bloco 5).** Gates cumpridos: consolidado TB1–TB8 aprovado → spec v0.35 + preview novo do bloco + suite 5A 27/27 ✓ (console limpo) → **regressão completa na mesma execução: A 55 + B 44 + C 28 + D 25 + E 14 + F 80 (byte-perfeitas) + CN 45 + 5A 27 = 318 verdes; contrastes ✅** → gate visual do Rafael aprovado em 2026-08-06 (rolagem/fade/setas de overflow, indicador renderizado, grayscale, skeleton do lazy, deep-link no browser real). Pendência AC5 formalmente encerrada pela TB1. Próximo: 5B — menu/dropdown (MN). · v0.35: **abre o Bloco 5 (Navegação) — 5A: §34 tabs em `rascunho`.** Ordem do bloco aprovada pelo Rafael em 2026-08-06 (5 sub-blocos: 5A tabs TB · 5B menu/dropdown MN · 5C breadcrumb BC + paginação PG · 5D stepper/wizard ST · 5E command palette CP — fronteira: sidebar/topbar é shell do F6, consome este bloco). Consolidado **TB1–TB8 aprovado** após 3 rodadas encadeadas (~24 fontes: R1 GOV.UK Tabs íntegra + NN/g Tabs Used Right + NN/g tabs×accordions + ONS + Carbon usage/accessibility + Spectrum tabs-overflow → R2 Radix (activationMode) + React Aria (keyboardActivation) + issue de campo #2915 → R3 APG Tabs Pattern íntegra + AcceDe + a11y-collective + Fiori Icon Tab Bar 4 versões + Inclusive Components). **TB1 resolve a pendência AC5** (régua tabs×accordion×mostrar-tudo×stepper×navegação). Preview novo do bloco `seed-navegacao-preview.html` v0.35 + `suite-navegacao.mjs` seção 5A: **27/27 ✓** (console limpo após guard de `scrollIntoView` ausente no jsdom — lição das APIs nativas). Nenhum par de cor novo (indicador tq-600/branco 4.60 e demais herdados §26/§28). Pendente: regressão A–F+CN byte-perfeita e gate visual (overflow/rolagem/indicador renderizado). · v0.34: **promoção do §33 Central de Notificações a `estável` — 1ª metade do marco v1.2 CONCLUÍDA.** Gates cumpridos na ordem: consolidado CN5–CN12 aprovado → spec v0.33 + preview novo + suite (45/45 ✓ após 3 erratas do gate visual antecipado do Rafael, todas com teste-guarda) → **regressão A–F byte-perfeita sobre os arquivos originais anexados pelo Rafael: A 55 + B 44 + C 28 + D 25 + E 14 (feedback v0.23) + F 80 (superfícies v0.32) + CN 45 = 291 verdes; contraste.py e contraste-superficies.py todos os pares ✅; suite-cn.mjs do Drive confirmada byte-idêntica à canônica** → validação visual formal do Rafael (2026-08-06): empilhamento renderizado, geometria do popover, show() nativo, tray 360px, dark e grayscale — **APROVADO**. A regressão intacta prova que a CN não tocou nada compartilhado dos previews canônicos. Próximo: abertura do Bloco 5 (Navegação), 2ª metade do marco v1.2. · v0.33: **abre o §33 — Central de Notificações (pattern F6) em `rascunho`, 1ª metade do marco v1.2.** É COMPOSIÇÃO pura: sino (§1) + badge dot (§21) + popover/tray (§24) + drawer standard (§31) + lista com os slots LS5 (§28) + empty esvaziado (§25) + toast (§17/CN2) — nenhum componente novo nasce. Base: 3 rodadas encadeadas (~26 fontes: R1 Carbon notifications pattern na íntegra (painel em conjunto com toasts; ordem cronológica; WCAG 2.2.3/2.2.4) + PatternFly notification badge/drawer v3–v6 (a spec de painel mais completa do canon) + NN/g + Smashing 2025 + UX Mag + evidência de campo Atlassian community (badge que zera ao abrir faz esquecer não-lidas; mark-all-read que não persiste quebra confiança) → R2 EUI Header (sino com prop notification; lista em EuiPopover OU EuiFlyout — as 2 superfícies) + EUI #4257 + Knock (modelo unseen/seen/read; badge default unseen p/ feed social) + Novu (#955 mark-all-read desabilitado sem itens; seen one-way) → R3 Fiori Notifications (cronológica "como inbox"; agrupamento é recurso de ALTO volume; dismiss ≠ processado; 2 linhas + Show More) + Sara Soueidan live regions 1–2 (região nomeada) + APG feed (proposta SEM consenso do TF — descartada) + APG alertdialog (aria-modal só quando bloqueia de fato)). Consolidado **CN5–CN12 aprovado pelo Rafael em 2026-08-06** — inclui a resolução do ponto em aberto do DW1: **o drawer da CN é regime STANDARD (CN6)**. Preview NOVO: `seed-cn-preview.html` v0.33 (justificativa: pattern F6 exige chrome simulado + simulador de background, e o preview separado preserva a regressão A–F byte-perfeita sobre os canônicos intactos); **suite nova `suite-cn.mjs` executada: 40/40 ✓ na primeira execução** (41/41 após a errata abaixo); **nenhum par de cor novo** (tudo herdado dos §17/§21/§24/§25/§26/§28/§30–§31, medidos). **Errata do gate visual (2026-08-06, crítica do Rafael):** na prova grayscale em dark, o link de teste da página sumia quando não-visitado — o `<a>` estava SEM estilo (cor default do browser, fora do sistema de tokens), violando "cor só via token" e o mecanismo-além-de-cor; corrigido com `--seed-text-link` (par já medido do §26: tq-600/branco 4.60 AA light · tq-300/dark-page 9.32 AAA dark) + **sublinhado sempre** (o sinal que sobrevive ao grayscale) + teste-guarda CN-41 na suite. **Errata 2 do gate visual (mesma data, crítica do Rafael — "botão 2 e 3 era pra fazer alguma coisa?"):** os simuladores de resolução e de erro só mudavam estado DENTRO do painel fechado — e, com o painel aberto, o próprio light-dismiss engolia a demonstração; ação sem resultado visível viola o ack imediato da Régua de Espera e o espírito do "nenhum botão morto". Corrigido: os simuladores abrem o painel para exibir o resultado (empty / slot de erro); testes-guarda CN-42/CN-43. **Errata 3 do gate visual (mesma data, crítica do Rafael — "não acontece nada quando clico no link"):** o link de teste da página apontava para `#app-chrome` (topo de página curta com topbar sticky = navegação sem efeito visível). Corrigido com a semântica certa: a PROVA de página-viva sob o drawer standard virou **botão com contador visível** (link navega, botão age) e o link ganhou **alvo real** no rodapé com destaque `:target` (borda no token + fundo, mecanismo além de cor) e `tabindex="-1"`; testes-guarda CN-44/CN-45, incluindo o contador incrementando COM o drawer standard aberto — prova executável do CN6. Na sequência, o CN-38 (botão morto) flagrou o botão novo fora da whitelist — falso-positivo de teste corrigido no escopo, não bug de componente (mesmo caso do accordion v0.27, registrado por transparência). **Suite final: 45/45 ✓.** Reconfirma a lição dos marcos: a suite valida comportamento — visibilidade percebida e cor default de user-agent são território do gate visual. Nota de nomenclatura acessível: as superfícies da CN chamam-se "Central de notificações" para não colidir com a landmark "Notificações" da região de toasts (§17). Pendente: **regressão A–F byte-perfeita** (anexar arquivos de `/validacao` + os 2 previews canônicos) e validação visual do Rafael antes da promoção. · v0.32: **promoção do §32 avatar a `estável` — BLOCO 4 (SUPERFÍCIES) FECHADO 7/7:** card §26 · divider §27 · lista §28 · accordion §29 · modal §30 · drawer §31 · avatar §32. Validação visual do Rafael em 2026-08-05, após 1 ciclo de crítica no avatar (2 defeitos reais pegos no gate visual: +N listava só os ocultos, quebrando a racional do AV5 — leitor de tela sem os 3 primeiros nomes — e popover sem light-dismiss do §24; emenda formal no AV5 + 3 testes-guarda). Validação executada final do bloco: suite F+G+H+I 80/80 ✓ + regressão A–E 166/166 ✓ = **246 verdes**. Balanço do bloco: 2 ciclos de crítica (empilhamento H + avatar I), ambos geometria/comportamento nativo — a lição do marco v1.0 confirmada duas vezes. Próximo: marco v1.1 pelo padrão delta. · v0.31: **abre o sub-bloco I — avatar (§32) em `rascunho`, último item do Bloco 4.** Base: 3 rodadas encadeadas (~14 fontes: R1 Atlassian/AUI (pessoa E entidade; badge com descrição textual; decorativo quando agrupado com texto) + Polaris (tamanhos; formatos de alt) + Zuora (consistência de forma) → R2 Radix Avatar (imagem só renderiza carregada; fallback delayMs ~600) + shadcn (AvatarGroup/Count; parsing de iniciais) + Radix Themes → R3 W3C WAI decorative images (primária) + eBay evo (informativo role=img+aria-label × decorativo sem nada; img sempre alt=""; nunca focável) + EUI (iniciais máx. 2; type user×space)). Consolidado AV1–AV5 aprovado pelo Rafael em 2026-08-05. Preview v0.31; suite F+G+H+I 78/78 ✓ + regressão A–E 166/166 ✓ (total 244 verdes); 4 pares novos medidos: iniciais 5.43 AA light / 8.07 AAA dark; ícone 3.93/8.07 ✓. · v0.30: **promoção do sub-bloco H a `estável`** — §30 modal e §31 drawer aprovados na validação visual do Rafael em 2026-08-05, após 1 ciclo de crítica: o gate visual pegou bug real de empilhamento (drawer standard sem z-index atravessado pela página; X encoberto pelo chrome) → emenda formal da escala de sobreposição (60<65<70<75<80) + teste-guarda na suite. Validação executada final: F+G+H 64/64 ✓ + regressão A–E 166/166 ✓ = 230 verdes. Régua de 3 degraus fechada; drawer destrava a CN (F6). Resta o sub-bloco I (avatar) para fechar o Bloco 4. · v0.29: **abre o sub-bloco H — modal (§30) + drawer (§31) em `rascunho`, fechando a régua de 3 degraus do §23.1.** Base: 3 rodadas encadeadas (~24 fontes: R1 APG dialog pattern+exemplo + NN/g modal×nonmodal/popup problems + Smashing decision tree 2026 + notas GOV.UK/MoJ → R2 MDN showModal/::backdrop (Baseline mar/2022) + Radix Dialog/Sheet + Vaul + EUI flyout + top layer → R3 Scott O'Hara "Use the dialog element (reasonably)" (primária — a mesma fonte que vetava em 2019 liberou) + accessuse.eu + M3 side sheets + M2 bottom sheets). Consolidado MD1–MD5 + DW1–DW4 aprovado pelo Rafael em 2026-08-05. Preview v0.29; suite F+G+H 63/63 ✓ + regressão A–E 166/166 ✓ (total 229 verdes); backdrop declarado decorativo; nenhum par de texto novo (overlay reusa §17/§2.8). Nota de método: jsdom não implementa showModal — o preview usa dialog nativo no browser e shim de teste no jsdom (foco/estado testados; trap/backdrop nativos ficam no gate visual, mesma lógica da geometria). · v0.28: **promoção do §29 accordion a `estável`** — validação visual do Rafael aprovada em 2026-08-05, após validação executada completa (suite F+G 47/47 ✓ + regressão A–E 166/166 ✓ = 213 verdes). Bloco 4: restam H (modal+drawer) e I (avatar). · v0.27: **abre o sub-bloco G — accordion (§29) em `rascunho`.** Base: 3 rodadas encadeadas (~22 fontes: R1 APG/W3C + GOV.UK DS/blog de acessibilidade + ONS + Bristol + NN/g ×4 + AcceDe + Stanford → R2 Radix + shadcn + Base UI → R3 Scott O'Hara (primária, quirks de `<details>`) + regra ACT W3C do `<summary>` + MDN `hidden="until-found"`/`beforematch` + SAP Fiori panel/DDS accordion). Consolidado AC1–AC6 aprovado pelo Rafael em 2026-08-05. Preview v0.27 (accordion adicionado); suite F+G executada 47/47 ✓ + regressão A–E 166/166 ✓ (total 213 verdes); **nenhum par de cor novo** (chevron e hover reusam pares já medidos no F). Correção de escopo na suite: whitelist do teste de botão-morto passou a reconhecer os botões do accordion (falso-positivo de teste, não bug de componente — registrado por transparência). · v0.26: **promoção do sub-bloco F a `estável`** — card §26, divider §27 e lista §28 aprovados na validação visual do Rafael em 2026-08-05, após validação executada completa: suite F 32/32 ✓ + REGRESSÃO A–E re-executada sobre o preview v0.23 canônico byte-idêntico (55+44+28+25+14 = 166/166 ✓) + contraste (Bloco 3 ✅ + 9 pares novos ≥AA, decorativos declarados). Total acumulado 198 testes verdes. Próximo: sub-bloco G (accordion). · v0.25: **abre o Bloco 4 (Superfícies) com o sub-bloco F — card (§26) · divider (§27) · lista (§28) em `rascunho`.** Base: 3 rodadas encadeadas (~25 fontes: R1 M3 cards/divider/lists + Carbon tile/structured-list/contained-list + NN/g cards + Nathan Curtis + Berkeley DAP + M1 dividers → R2 shadcn card + EUI panel/card/list-group + React Aria GridList/ListBox/useSeparator + Angular Material + MUI divider → R3 Inclusive Components/cards como fonte primária + Adrian Roselli + Intopia + MDN/WAI-ARIA 1.2 separator + SAP Fiori list/object card + gov.br DS). Consolidado CD1–CD5 · DV1–DV3 · LS1–LS5 aprovado pelo Rafael em 2026-08-04 — **códigos de decisão passam a 2 letras** (alfabeto de letra única esgotado no Bloco 3). Preview NOVO: `seed-superficies-preview.html` v0.25; suite F executada (32/32 ✓); 9 pares de texto medidos (todos ≥AA) + 7 pares decorativos declarados com justificativa; **regressão A–E pendente de re-execução** (arquivos canônicos de `/validacao` a anexar) antes da validação visual. · v0.24: **§25 empty state promovido a estável — BLOCO 3 COMPLETO: 11/11 em um único dia de trabalho (2026-08-04)**, com validação executada acumulada de 166 testes verdes (A 55 · B 44 · C 28 · D 25 · E 14) + ~40 pares de contraste medidos, 4 ciclos de crítica visual do Rafael absorvidos como emendas formais (R3-b/R3-c, X6-b + geometria, botão-morto de preview), 3 erratas estruturais descobertas pela suite (aria-invalid herdada consolidada, rótulo azul-700→800 do tokens, borda de chip) e 2 emendas de método permanentes (nota 1.13-b: dois regimes de anúncio; lição: suite valida comportamento, validação visual valida geometria). Sub-blocos: A severidade (alerta/banner/toast) · B espera (spinner/skeleton/progress + Régua) · C rotulagem (badge/chip) · D sobreposição (tooltip/popover) · E empty state. Próximo: fechamento de marco (roadmap v1.3 + checkpoint) antes do Bloco 4. · v0.23: **empty state (§25) em `rascunho` — o último item do Bloco 3.** Taxonomia de 5 tipos (primeiro-uso · esvaziado · zero-resultados herdando D8/D9 SEM CTA de criação · erro de carga recebendo o timeout do S5 · sem-permissão), régua de escala por container (Fiori), regra de marca nova: EEny permitido em primeiro-uso/esvaziado e PROIBIDO em erro/sem-permissão (Z5). Base: 3 rodadas (~12 fontes: NN/g com o caso ERP do painel de alertas, Polaris, GitLab Pajamas, Carbon pattern, Atlassian, Fiori empty states + illustrated message com 4 tamanhos, shadcn Empty, Mobbin, Setproduct) + heranças D8/D9, S5, §1, marca v5.0; consolidado Z aprovado pelo Rafael em 2026-08-04. Preview v0.23; validação executada aprovada: **suite E 13/13 + regressões A 55/55, B 44/44, C 28/28, D 25/25 = 165 testes verdes no bloco**; contraste do §25 integralmente herdado (texto primário/secundário e botão §1, já medidos). Ao promover o §25, o Bloco 3 fecha com **11/11**. · v0.22: **§23 tooltip · §24 popover promovidos a estável**, validados pelo Rafael em 2026-08-04 (um ciclo de crítica visual: X6-b ícone SVG + correção do bug de geometria do posicionamento; re-teste aprovado). Sub-bloco D concluído — 10/11 do Bloco 3; resta o empty state para fechar o bloco. · v0.21: **abre o sub-bloco D do Bloco 3 — tooltip (§23) · popover (§24) em `rascunho`**, com a régua da sobreposição leve em 3 degraus (tooltip = não-interativo hover/focus · popover = interativo leve por clique, light-dismiss · modal = Bloco 4) e a DECISÃO TOUCH como coração: tooltip não é portador de informação no toque — todo uso declara equivalente touch (X2). Fecha a variante "+N" do chip (W7). Base: 3 rodadas (~18 fontes: NN/g, M2/M3, Carbon tooltip+toggletip, Heydon/Inclusive Components, WCAG 1.4.13+SCR39, Radix/DHIS2/W3C sobre touch, Popover API Chrome/MDN/OpenUI, React Aria usePopover, Floating UI, Hidde, Fiori ResponsivePopover, Spectrum tray); consolidado X/Y aprovado pelo Rafael em 2026-08-04. **Errata da validação visual (2 achados do Rafael):** (a) X6-b — gatilho ⓘ trocado de caractere unicode para o ícone SVG do T3; (b) *bug de geometria* — tooltips e popovers abriam sem posicionamento ancorado (renderizavam na origem do container, quase invisíveis); corrigido com ancoragem + flip, e registrada a **lição de método permanente: a suite DOM valida comportamento, não geometria — a validação visual do Rafael é o gate complementar que cobre o que a suite não vê.** Preview v0.21; validação executada aprovada: **suite D 22/22 + regressões A 55/55, B 44/44, C 28/28 + pares do tooltip medidos (light 13.86 AAA · dark 11.49 AAA · borda dark 3.60)** — primeira suite do Bloco 3 verde na primeira execução, sem errata. · v0.20: **§21 badge · §22 chip promovidos a estável**, validados pelo Rafael em 2026-08-04 sobre o preview v0.19 (validação visual aprovada sem ressalvas; suite C 28/28 + regressões A/B). Sub-bloco C concluído — 8/11 do Bloco 3; segue o sub-bloco D (sobreposição leve: tooltip · popover, com a decisão estrutural mobile). · v0.19: **abre o sub-bloco C do Bloco 3 — badge (§21) · chip (§22) em `rascunho`**, com a separação tripla como decisão-mãe (badge numérico × badge de status × chip interativo — badge NUNCA clica). Base: 3 rodadas (~15 fontes: Carbon tag/status-pattern/discussões a11y, Atlassian badge+lozenge+tag, Polaris badge, Smart Patterns, M3 chips a11y primária, Angular Material chips, React Aria TagGroup/grid pattern, WCAG 2.5.8, gov.br tag) + heranças T1–T5/§1.5.1 sem re-pesquisa; consolidado V/W aprovado pelo Rafael em 2026-08-04. Preview v0.19; validação executada aprovada: **suite C 28/28 + regressões A 55/55 e B 44/44 + 8 pares de contraste novos** — a medição pré-suite pegou a borda do chip em cinza-300 reprovando com 1.91 (corrigida para cinza-600: 4.74 light / 3.60 dark) antes de qualquer código. · v0.18: **§18 spinner · §19 skeleton · §20 progress promovidos a estável**, validados pelo Rafael em 2026-08-04 após dois ciclos de crítica visual (R3-b: três regimes de entrada; R3-c: composição obrigatória ack+delay no refresh por ação — "agora está ok") sobre o preview v0.17 com suite executada (44 testes B + regressão A 55/55 + 9 pares de contraste). Sub-bloco B concluído; segue o sub-bloco C (rotulagem: badge · chip/tag). · v0.17: **abre o sub-bloco B do Bloco 3 — spinner (§18, com a Régua de Espera) · skeleton (§19) · progress (§20) em `rascunho`.** Base: 3 rodadas encadeadas (~22 fontes: NN/g ×3, Carbon ×4, Smashing, React Aria/Spectrum, shadcn/Radix, MUI/Angular Material, W3C ARIA25 íntegra, MDN progressbar, aria-busy, SAP Fiori busy/placeholder, EC Europa, Material m1/m2 primários, Viget/Chung/estudo acadêmico de skeleton), consolidado R/S/U + Régua aprovado pelo Rafael em 2026-08-04. Resolve a ponta do §17.5 (toast de processo → U6). Preview estendido para v0.17 (simulador de espera); validação executada: **suite B de 38 testes + suite A re-executada em regressão (55/55) + 9 pares de contraste novos (6 com gate, todos ✅; skeleton declarado decorativo com justificativa em §19.2)**. Nota de método da rodada: o único ✗ da primeira execução era bug DA SUITE, não do preview — asserções sobre live regions coalescem quando os sets são síncronos; o teste precisa de flush de microtask entre atualizações. Registrado como aprendizado permanente da validação executada (afeta como testamos §4, §6 e futuros consumidores do regime contínuo). **Emenda R3-b na validação visual:** a crítica do Rafael ('o delay de 1s vai parecer travado') foi medida contra a evidência e acatada — a régua ganhou 3 regimes de entrada (ação = ack imediato no controle; carga inicial = skeleton imediato; refresh = delay 1s), com supersede parcial do R3; preview e suite atualizados (3 testes novos). **R3-c na sequência:** 2ª crítica ('o refresh lento ainda demora') expôs que o demo rodava o regime 3 sem o ack do regime 1 — a régua agora obriga a composição (refresh por ação = ack imediato no controle + indicador de região com delay); demo corrigido, 2 testes novos, delay parametrizado em `--seed-wait-delay` com revisão por telemetria na F4. · v0.16: **§15 alerta · §16 banner · §17 toast promovidos de rascunho a estável**, validados pelo Rafael em 2026-08-04 sobre o preview v0.15 (validação visual sem ressalvas + suite executada de 55 testes e 24 pares de contraste, tudo aprovado). Sub-bloco A do Bloco 3 concluído; segue o sub-bloco B (espera: spinner · skeleton · progress). · v0.15: **abre o Bloco 3 (Feedback e status) com o sub-bloco A — alerta (§15) · banner (§16) · toast (§17) em `rascunho`**, mais as decisões transversais T1–T5 (§15.0: taxonomia de severidade com 4 níveis e vocabulário PT-BR "atenção" para warning) e a **nota 1.13-b** (emenda ao protocolo de live region: regime de mensagem discreta vs. atualização contínua). Base: 3 rodadas encadeadas + rodada de verificação primária (GOV.UK notification banner lido na íntegra, Material m1/m2 oficiais, Baymard, NN/g ×3, USWDS, Polaris, Atlassian, Spectrum/React Aria, APG, WCAG 4.1.3/2.2.1, SAP Fiori, gov.br DS — ~38 fontes), consolidado T/O/P/Q aprovado pelo Rafael em 2026-08-04. Preview novo: `seed-feedback-preview.html` v0.15 (dark toggle, grayscale, faixa 360px), aprovado na validação executada (suite de **55 testes DOM headless + 24 pares de contraste WCAG medidos**, light e dark) antes de chegar ao Rafael. **Errata da validação executada (3 achados):** (a) *bug Q5 corrigido no preview* — a restauração de foco só funcionava chegando ao toast via F6; chegando por Tab, fechar o último toast derrubava o foco no body (React Aria restaura nos dois caminhos) — corrigido com rastreio de foco de origem em qualquer entrada na região; (b) *errata para o seed-tokens.md v1.2, §5* — o par "azul-700 sobre azul-50 = 5.79" tem o NÚMERO certo e o STOP errado: 5.79 é azul-**800**/azul-50 (medido); azul-700/azul-50 dá 4.16 (reprova AA texto). Pendência de propagação: corrigir o rótulo na tabela do tokens quando o arquivo for editado; (c) *token de componente ajustado* — `feedback-info-text` light = azul-**800** (5.79 AA) e `feedback-info-solid` light = azul-**700** (4.16 sobre o fundo -50 e 4.61 sobre branco, ambos ≥3:1) — o desenho original (700/600) reprovava por 4.16<4.5 e 2.99<3.0. · v0.14: **Bloco 2 completo — os 7 finais (§8 switch · §9 radio · §10 checkbox · §11 combobox · §12 slider · §13 date picker · §14 upload) promovidos de rascunho a estável**, validados pelo Rafael em 2026-08-03 sobre o preview v0.13 (validação visual + suite executada de 63 testes: 62 aprovados, 1 falso-negativo de ambiente headless documentado). Com isso os 13 itens do Bloco 2 estão estáveis. · v0.13 (errata da validação executada): suite de 63 testes automatizados (DOM headless + contraste WCAG em script) rodada sobre o preview v0.12 flagrou 5 bugs de implementação e 2 imprecisões de documentação, todos corrigidos no preview v0.13 e refletidos aqui: (a) **emenda D4-b no §4** — nó visível da contagem separado da live region (supersede do markup D4 original de nó único, que defasava o visual em 300ms — o padrão GOV.UK que o §1.13 proíbe); (b) **regra nova no §2.6** — `aria-invalid` sempre com valor explícito `"true"` (nunca `toggleAttribute`, que grava valor vazio e não casa com `[aria-invalid="true"]` no CSS); (c) **nota L1-b no §12** — o campo pareado consome o parser canônico do §5 (o preview chamava um helper inexistente e o caminho "digitar o valor exato" quebrava); (d) preview alinhado ao que o spec JÁ exigia em §8.4 (falha `role="alert"` + live region de sucesso) e N4 (`role="alert"` para falha de upload); (e) comentário de contraste do readonly dark corrigido (§2.5): o par real é cinza-100 `#E3EBF0` sobre `#141D23` = **14.16:1** (o 9.72:1 anterior media um par que não é o renderizado). Nenhuma decisão B/C/D/E/F/G/H/I/J/K/L/M/N muda; status dos §8–§14 permanece rascunho aguardando validação visual do Rafael. · v0.12: entram os 7 finais na ordem da régua — **switch §8 · radio §9 · checkbox §10 · combobox §11 · slider §12 · date picker §13 · upload §14** — + protocolo de live region (§1.13) + fieldset/legend (§2.6b) + linha nova na régua (§7.1). v0.11: select (§7) com a régua unificada da família de escolha (emenda ao §3.1). v0.10: **textarea** (§6) + método no §1.12 + supersede do slot E. v0.9: **número** (§5). v0.8: **busca** (§4). v0.7: **input de texto** (§3), o **pacote mobile M4–M7** (fundamento §0.8, teste do polegar no §1.12, botão full-width, form-field emendado) e o **estado warning** (C5). Histórico: v0.6 2026-08-02 (form-field) · Fase 3 do rebranding do DS. Histórico: v0.5 2026-07-31 (Botão estável + regra do par light/dark). Previews validáveis: `seed-componentes-preview.html` (botão) e `seed-formfield-preview.html` (form-field).

---

## 0. Fundamentos de componente (valem para todos os blocos)

1. **Toda cor via token semântico** (`--seed-action-primary`), nunca primitivo, nunca hex. Dark mode vem de graça.
2. **Estados obrigatórios em todo componente interativo:** default · hover · active/pressed · focus-visible · disabled · loading (quando houver operação assíncrona). Componente sem os 6 documentados não é "pronto".
3. **Foco sempre visível:** `box-shadow: var(--seed-focus-ring)` — nunca `outline: none` sem substituto.
4. **Alvo de toque ≥44×44px** no tamanho padrão (WCAG 2.2 / mobile). Tamanhos densos (sm) são para interfaces desktop de alta densidade.
5. **Motion produtivo:** transições de estado usam `--seed-dur-productive-fast` (100ms) com `--seed-ease-productive`. Nada de animação expressiva em micro-interação.
   - ⭐ **`--seed-ease-out` = `cubic-bezier(.2,.8,.4,1)`** — FIXADO em 2026-08-26, por medição. Esta spec o usava **quatro vezes** (thumb do switch 120 ms · toast 160 ms · barra de progresso 200 ms · modal/drawer) e **nunca dizia o valor**, então o acervo tinha dois: `.2,.8,.4,1` em 3 artefatos e `.2,.7,.3,1` em 2.
   - ⚠ **A medição mostrou que os dois são a mesma curva.** Resolvendo o bézier em 21 instantes de um movimento de 120 ms, a diferença máxima é **1,5% do percurso** e, em tempo, **0,3 ms**. A 60 Hz um quadro dura 16,7 ms — **0,3 ms é 1/56 de um quadro.** *Não existe monitor onde essa diferença apareça.* Escolheu-se a de maior uso, que também é a que faz o conserto ser de 2 arquivos e não de 3.
   - ⭐⭐ **E o mesmo teste respondeu a pergunta que ninguém tinha feito: `--seed-ease-out` deve existir, ou era apelido de `--seed-ease-productive`?** Contra o `productive`, a diferença é **36,8% do percurso e 24,9 ms** — quase um quadro e meio, **83 vezes maior** que a diferença entre as duas candidatas. *São curvas diferentes de verdade, então o token merece existir.* Sem o número, essa pergunta se responderia por gosto.
6. **Status de maturidade** por componente: `rascunho` → `estável` → `depreciado`.
7. **Microcopy é parte do componente** — cada spec define as regras de texto no tom SEED (direto, acolhedor, sem floreio).
8. **Responsivo por padrão (v0.7 — diretriz de paridade mobile/app, 2026-08-02).** Todo componente especifica seu comportamento em superfície de toque: o que empilha, o que vira full-width, o que muda de tamanho. Largura sempre fluida dentro do layout (breakpoints oficiais nos tokens v1.2 §2.9; pior caso de teste: 360px). **Hover é progressivo, nunca portador único de informação** — não existe hover no toque; toda informação apresentada em hover precisa de equivalente acessível por toque/foco. Campos usam `--seed-fs-field-touch` (16px) em `pointer: coarse`. Motivo: apps serão produzidos e o uso mobile supera o desktop; nenhum bloco nasce desktop-first.

---

## 1. Botão — `estável`

### 1.1 Papel

Dispara uma ação. Não navega entre páginas (isso é link/âncora — pode *parecer* botão via variante, mas semanticamente `<a>`). Um grupo de ações tem **um único primário**; o resto desce na hierarquia.

### 1.2 Anatomia

```
        ┌───────────────────────────────────────┐
        │  [C] container                        │
        │   ┌────┐  ┌─────────────────┐         │
        │   │ Íc.│  │ Rótulo          │         │
        │   └────┘  └─────────────────┘         │
        │    (A)         (B)                    │
        └───────────────────────────────────────┘
```

| Parte | Elemento | Regras |
|---|---|---|
| A | Ícone (opcional) | 16px (sm), 20px (md/lg); à esquerda do rótulo por padrão (à direita só para direção: "Avançar →"); gap `sp-2` (8px); herda a cor do texto |
| B | Rótulo | Montserrat SemiBold (600); sentence case; nunca some no loading |
| C | Container | Radius `--seed-radius-md` (8px); padding horizontal por tamanho; borda 1px (transparente exceto outline) |

### 1.3 Variantes (5 + link)

| Variante | Quando usar | Fundo / texto (light) |
|---|---|---|
| **primary** · ALTA | A ação principal da tela/seção — **máx. 1 por contexto** | FILL SÓLIDO: `action-primary` (verde institucional, 4.60:1) / `text-on-brand` |
| **secondary** · MÉDIA | Ações relevantes não-principais; par do primário | TINT + BORDA DE MARCA: fundo `action-secondary-bg` (turquesa-50) + **borda turquesa-600 (4.60:1 — é ela que identifica o controle)** / texto turquesa-800 (8.73:1) |
| **outline** · MÉDIA-BAIXA | Ações neutras/utilitárias (cancelar, voltar, exportar) | BORDA NEUTRA: transparente + **borda cinza-600 (4.74:1)** / `text-primary` |
| **ghost** · BAIXA | Ações terciárias, barras de ferramenta, densidade (>3 ações no grupo → use ghost) | SÓ TEXTO: transparente / turquesa-700 (6.32:1); hover `action-ghost-hover` |
| **destructive** | Ações irreversíveis (excluir, cancelar contrato) | `action-destructive` / branco |
| **link** | Ação inline em texto corrido | transparente / `text-link`, sublinhado no hover; padding zero |

**A escada de mecanismos (por que as variantes não se parecem):** cada nível de ênfase muda o MECANISMO visual — alta = preenchimento sólido · média = tint + borda de marca · média-baixa = só borda neutra · baixa = só texto. Diferença de mecanismo sobrevive ao aperto de olhos e à escala de cinza; diferença só de tom, não (foi o defeito da v0.1, corrigido aqui).

Regras de grupo (padrão Carbon): **1 alta ênfase por vista**; grupos combinam 1 primário + N botões IGUAIS de ênfase menor (nunca primary+secondary+outline misturados no mesmo grupo); com mais de 3 ações, desça para ghost. **Nunca destructive como primário "de layout"** (ele é vermelho porque destrói, não porque é importante).

### 1.4 Tamanhos

| Tamanho | Altura | Padding H | Fonte | Uso |
|---|---|---|---|---|
| `sm` | 32px | 12px | 12px | Interfaces densas desktop (tabelas, toolbars) — não usar como CTA mobile |
| `full-width` (comportamento, v0.7) | herda lg/md | — | — | Em viewport de toque, CTA único de formulário/fluxo (enviar, confirmar, avançar) estica à largura do container (respeitando safe-areas). Padrão de app/checkout. Nunca para ações secundárias lado a lado |
| `md` (padrão) | **44px** | 20px | 14px | Tudo. Já nasce com alvo de toque WCAG |
| `lg` | 52px | 28px | 16px | Hero, CTA de landing, fim de formulário longo |

Icon-only: quadrado (44×44 no md), `aria-label` obrigatório.

### 1.5 Estados (os 6 obrigatórios)

| Estado | Primary (light) | Regra geral |
|---|---|---|
| default | `action-primary` | — |
| hover | `action-primary-hover` (turquesa-700) | escurece 1 stop; transição 100ms |
| active | `action-primary-active` (turquesa-800) | escurece 2 stops; sem "pulo" de layout |
| focus-visible | + `--seed-focus-ring` | anel 2px branco + 2px turquesa; visível em QUALQUER variante |
| disabled | `surface-sunken` + `text-disabled` | `cursor: not-allowed`; sem hover; **manter contraste legível do rótulo** (o usuário precisa ler o que está indisponível) |
| loading | fundo do estado default + spinner 16px | **spinner À ESQUERDA, rótulo permanece** (sem layout shift, sem perder contexto); `aria-busy="true"`; cliques ignorados |
| **selected** (só em toggles) | fill sólido turquesa-800 + texto/ícone branco + **troca de ícone** | estado persistente de alternância — ver §1.5.1; `aria-pressed="true"` |

#### 1.5.1 Botões de alternância (toggle) — sinais de estado (v0.4, alinhada à spec Material 3)

Botão que liga/desliga algo (mão levantada, filtro ativo, favorito, negrito no editor) tem o estado **selected**. O estado precisa ser identificável DE RELANCE, no próprio botão, sem interação — texto visível NÃO é requisito (ícone puro é a norma em toolbars).

**Sinais visuais no próprio botão (os que resolvem "está ligado?" sem tocar):**

1. **Placa do container:** desligado = transparente ou contorno sutil; ligado = **placa sólida** (turquesa-800, 8.13:1). Mecanismo, não tom.
2. **Miolo do glifo:** desligado = ícone em CONTORNO; ligado = o MESMO ícone PREENCHIDO. Mesma silhueta sempre — a mão continua a mesma mão. **Proibido:** trocar por ícone diferente, ou usar rasura/corte como indicador de seleção (regra literal do Material 3: outline para não-selecionado, filled para selecionado).
3. **Forma do container (opcional, recomendado em toolbars com vários toggles):** desligado = círculo; ligado = quadrado arredondado (radius-md). Terceiro sinal 100% visual — sobrevive em escala de cinza e para daltônicos (padrão M3 Expressive).

**Sinal textual (complementar, nunca o principal):** tooltip no hover / long-press no mobile, descrevendo a **AÇÃO** ("Abaixar a mão") — o visual mostra o estado, o texto diz o que o clique faz. Label visível só quando o layout já é de botão com texto.

**Acessibilidade:** `aria-pressed="true|false"` + `aria-label` sempre (ícone puro sem nome acessível é invisível pra leitor de tela).

**Exceção semântica — negação de mídia (microfone, câmera, som):** aqui o ícone cortado é CORRETO e esperado, porque a barra comunica o estado da mídia ("som desligado"), não a seleção do botão. Padrão: ativo = glifo normal em placa neutra; desativado = glifo cortado em **placa danger sólida** (vermelho-600) — dois sinais fortes, convenção universal de apps de chamada. Para estados de constrangimento real (mudo em reunião), somar indicador fora do botão.

**Toggle é ferramenta; configuração é Switch.** Preferência que persiste (notificações on/off) usa Switch (Bloco 2); botão-toggle é para ações de sessão e ferramentas.
### 1.6 Tokens de componente (camada 3 — nasce aqui)

Definidos apenas onde o semântico não basta:

```css
--seed-button-radius: var(--seed-radius-md);
--seed-button-font-weight: var(--seed-fw-semibold);
--seed-button-height-sm: 32px;
--seed-button-height-md: 44px;
--seed-button-height-lg: 52px;
--seed-button-gap: var(--seed-sp-2);
/* containers acessíveis (>=3:1) — a camada 3 existindo pra isso.
   REGRA (aprendida na correção v0.5): token de componente que referencia
   primitivo DEVE declarar o par dark — primitivo não troca de modo sozinho. */
--seed-button-secondary-border: var(--seed-turquesa-600);
--seed-button-secondary-text: var(--seed-turquesa-800);
--seed-button-outline-border: var(--seed-cinza-600);
--seed-button-ghost-text: var(--seed-turquesa-700);
--seed-button-toggle-on-bg: var(--seed-turquesa-800);
--seed-button-toggle-on-fg: #FFFFFF;

[data-theme="dark"] {
  --seed-button-secondary-border: var(--seed-turquesa-300); /* 9.32:1 */
  --seed-button-secondary-text: var(--seed-turquesa-100);   /* 10.64:1 no tint dark */
  --seed-button-outline-border: var(--seed-cinza-400);      /* 6.74:1 */
  --seed-button-ghost-text: var(--seed-turquesa-300);       /* 9.32:1 — era 2.7 com o primitivo fixo */
  --seed-button-toggle-on-bg: var(--seed-turquesa-300);
  --seed-button-toggle-on-fg: var(--seed-turquesa-900);     /* 7.39:1 */
}
```

### 1.7 Microcopy — regras de rótulo (tom SEED)

1. **Verbo + objeto, sentence case:** "Solicitar diagnóstico", "Enviar proposta", "Baixar relatório". Nunca "CLIQUE AQUI", nunca "Submeter".
2. **1 a 3 palavras.** Se precisa de mais, o problema é o contexto, não o botão.
3. **Destrutivo nomeia a consequência:** "Excluir proposta" (não "Confirmar", não "Sim"). Em diálogo de confirmação, o par é "Cancelar" + "Excluir proposta".
4. **Loading mantém o rótulo** — sem trocar para "Aguarde..." (o spinner já diz isso).
5. **Sem exclamação, sem urgência falsa** ("Aproveite JÁ!") — o tom SEED é direto e sincero.
6. Exemplos calibrados: ✅ "Ver proposta" · "Falar com engenheiro" · "Calcular economia" | ❌ "Saiba mais!!" · "Clique e descubra" · "OK".

### 1.8 Acessibilidade

`<button type="button|submit">` real (nunca div clicável) · foco visível sempre · `aria-busy` no loading · `aria-label` em icon-only · contraste do rótulo AA em todas as variantes/estados (auditado nos tokens: primary 4.60:1, destructive 5.19:1) · não comunicar estado só por cor (disabled também muda cursor e remove hover; loading tem spinner).

### 1.9 Código — HTML/CSS (peças, site, e-mails ricos*)

*\*em e-mail real, botão é tabela — ver sistema de e-mail (Fase 4).*

```html
<button class="seed-btn seed-btn--primary seed-btn--md" type="button">
  Solicitar diagnóstico
</button>
<button class="seed-btn seed-btn--primary seed-btn--md" type="button" aria-busy="true" disabled>
  <span class="seed-btn__spinner" aria-hidden="true"></span> Enviar proposta
</button>
```

```css
.seed-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--seed-button-gap, var(--seed-sp-2));
  font-family:var(--seed-font-sans);font-weight:var(--seed-fw-semibold);
  border:1px solid transparent;border-radius:var(--seed-radius-md);cursor:pointer;
  transition:background var(--seed-dur-productive-fast) var(--seed-ease-productive),
             border-color var(--seed-dur-productive-fast) var(--seed-ease-productive);
}
.seed-btn:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
.seed-btn--sm{height:32px;padding:0 12px;font-size:12px}
.seed-btn--md{height:44px;padding:0 20px;font-size:14px}
.seed-btn--lg{height:52px;padding:0 28px;font-size:16px}

.seed-btn--primary{background:var(--seed-action-primary);color:var(--seed-text-on-brand)}
.seed-btn--primary:hover:not(:disabled){background:var(--seed-action-primary-hover)}
.seed-btn--primary:active:not(:disabled){background:var(--seed-action-primary-active)}

.seed-btn--secondary{background:var(--seed-action-secondary-bg);
  border-color:var(--seed-button-secondary-border);color:var(--seed-button-secondary-text)}
.seed-btn--secondary:hover:not(:disabled){filter:brightness(.96)}

.seed-btn--outline{background:transparent;border-color:var(--seed-button-outline-border);color:var(--seed-text-primary)}
.seed-btn--outline:hover:not(:disabled){background:var(--seed-action-ghost-hover)}

.seed-btn--ghost{background:transparent;color:var(--seed-button-ghost-text)}
.seed-btn--ghost:hover:not(:disabled){background:var(--seed-action-ghost-hover)}

.seed-btn--destructive{background:var(--seed-action-destructive);color:#fff}
.seed-btn--destructive:hover:not(:disabled){background:var(--seed-action-destructive-hover)}

.seed-btn:disabled{background:var(--seed-surface-sunken);border-color:transparent;
  color:var(--seed-text-disabled);cursor:not-allowed}

.seed-btn__spinner{width:16px;height:16px;border-radius:50%;
  border:2px solid currentColor;border-top-color:transparent;
  animation:seed-spin .7s linear infinite}
@keyframes seed-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){.seed-btn__spinner{animation-duration:1.4s}}
```

### 1.10 Código — React/TypeScript (produtos: ERP, chat, site Lovable)

Padrão shadcn/cva; pressupõe os tokens mapeados no Tailwind (guia de integração no Bloco 4).

```tsx
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Loader2 } from "lucide-react";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 rounded-md font-semibold " +
  "transition-colors duration-100 focus-visible:outline-none " +
  "focus-visible:ring-2 focus-visible:ring-[var(--seed-border-focus)] focus-visible:ring-offset-2 " +
  "disabled:cursor-not-allowed disabled:bg-[var(--seed-surface-sunken)] disabled:text-[var(--seed-text-disabled)]",
  {
    variants: {
      variant: {
        primary:
          "bg-[var(--seed-action-primary)] text-[var(--seed-text-on-brand)] " +
          "hover:bg-[var(--seed-action-primary-hover)] active:bg-[var(--seed-action-primary-active)]",
        secondary:
          "bg-[var(--seed-action-secondary-bg)] border border-[var(--seed-button-secondary-border)] " +
          "text-[var(--seed-button-secondary-text)] hover:brightness-95",
        outline:
          "border border-[var(--seed-button-outline-border)] text-[var(--seed-text-primary)] " +
          "hover:bg-[var(--seed-action-ghost-hover)]",
        ghost:
          "text-[var(--seed-button-ghost-text)] hover:bg-[var(--seed-action-ghost-hover)]",
        destructive:
          "bg-[var(--seed-action-destructive)] text-white hover:bg-[var(--seed-action-destructive-hover)]",
        link:
          "h-auto p-0 text-[var(--seed-text-link)] underline-offset-4 hover:underline",
      },
      size: {
        sm: "h-8 px-3 text-xs",
        md: "h-11 px-5 text-sm",
        lg: "h-[52px] px-7 text-base",
        icon: "h-11 w-11",
      },
    },
    defaultVariants: { variant: "primary", size: "md" },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  loading?: boolean;
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, loading, disabled, children, ...props }, ref) => (
    <button
      ref={ref}
      className={cn(buttonVariants({ variant, size }), className)}
      disabled={disabled || loading}
      aria-busy={loading || undefined}
      {...props}
    >
      {loading && <Loader2 className="size-4 animate-spin" aria-hidden />}
      {children}
    </button>
  )
);
Button.displayName = "Button";
```

### 1.11 Decisões deste bloco (com alternativas descartadas)

| Decisão | Porquê | Descartado |
|---|---|---|
| `md` = 44px (não 40px) | Alvo de toque WCAG por padrão, sem gambiarras de hit-area | 40px + pseudo-elemento de expansão (complexidade que alguém esquece) |
| Loading mantém rótulo + spinner à esquerda | Sem layout shift; usuário não perde o contexto do que disparou | Substituir texto por spinner (padrão v1 informal) |
| Outline mantida como variante própria | Útil sobre cards/fotos onde o tonal (turquesa-50) pesa | Fundir outline+secondary (perderia o caso de uso) |
| Disabled com fundo sunken (não opacidade) | Contraste do rótulo continua legível | `opacity: .5` (rótulo ilegível, falha WCAG) |
| Link como variante do componente | API única no produto (`<Button variant="link">`) | Componente separado (duplicação) |
| Escada de MECANISMOS (v0.2, após crítica do Rafael) | Container de cada variante ≥3:1 (WCAG 1.4.11); v0.1 media 1.07 (secondary) e 2.53 (outline) — variantes indistinguíveis | Diferençar só por tom de tint (reprova em escala de cinza e baixa visão) |
| Tokens de componente com par light/dark obrigatório (v0.5, bug de dark pego pelo Rafael) | No dark, secondary media 1.35:1 e ghost 2.7:1 porque os tokens apontavam pra primitivos fixos | Confiar que "primitivo resolve" (primitivo não troca de modo; só semântico e componente-com-par trocam) |
| Estado selected com sinais visuais no próprio botão (v0.3→v0.4, dois refinamentos do Rafael) | Toggle é o caso nº1 de ambiguidade documentada (NN/g Mute; Google Meet erra); estado deve ler DE RELANCE sem texto | v0.3 sugeria label visível como sinal e redigia "ícone cortado" ambíguo — corrigido para: tooltip como sinal textual, mesmo glifo contorno↔preenchido (spec M3 literal), rasura só na exceção de negação de mídia |

### 1.12 Processo de avaliação de hierarquia e qualidade (método — vale para qualquer componente; **7 testes** desde a v0.7)

Aplicar SEMPRE que um componente tiver níveis de ênfase; nasceu da revisão do botão (v0.1→v0.2) e vira padrão dos blocos seguintes:

1. **Teste do container 3:1 (WCAG 1.4.11):** o elemento que identifica o controle (preenchimento OU borda) precisa de contraste ≥3:1 contra a superfície. Medir, não estimar.
2. **Teste da escada de mecanismos:** entre níveis adjacentes, muda o MECANISMO (fill → tint+borda → borda → texto), nunca apenas o tom.
3. **Teste do aperto de olhos / escala de cinza:** renderizar o conjunto em grayscale — os níveis continuam distinguíveis? (Remove a muleta do matiz; simula baixa visão e daltonismo.)
4. **Teste do par:** o par mais comum (alta + média ênfase) lado a lado — a hierarquia é óbvia em 1 segundo, à distância de braço?
5. **Regra do um:** 1 alta ênfase por vista; grupos com 1 primário + N iguais de ênfase menor; >3 ações → ghost.
6. **Teste do estado atual (toggles):** alguém que chega AGORA na tela sabe se está ligado ou desligado sem clicar? Se precisa clicar pra descobrir (o "teste WebEx"), reprovou — aplicar a regra dos 3 sinais (§1.5.1).
**Método de pesquisa (v0.9, calibrado com o Rafael em 2026-08-02 sobre o histórico de 5 itens):** todo item roda **NO MÍNIMO 3 rodadas de pesquisa, sempre sequenciais e encadeadas** — cada rodada abre declarando as pontas soltas que herda da anterior e busca APENAS o que a base acumulada ainda não responde (decisões já estáveis não se re-pesquisam). Arco típico observado no histórico: rodada 1 = canon dos design systems (~60–70% das decisões); rodada 2 = prática das grandes empresas e do mercado (confirmações + ~20%); rodada 3 = normas e fora-do-circuito (menor volume, maior gravidade — foi onde entraram WCAG 3.3.8/NIST, ISO 4217, APG spinbutton, WCAG 2.5.7). Rodada extra (4ª+) por gatilho, nunca por rito: crítica do Rafael expondo lacuna (caso 0800/EUA do form-field), decisões instáveis após 3 rodadas, ou domínio de alto risco (dinheiro, dados pessoais, segurança). Uma rodada que não herda pergunta aberta é redundante e não deve acontecer.

**§1.13 — Protocolo de live region SEED (v0.12, transversal):** todo anúncio dinâmico a leitores de tela segue UM padrão (nasceu na busca D4, confirmado no textarea F3, agora governa combobox, upload e switch async): região `role="status"` (polite) + `aria-atomic="true"`, **visualmente oculta e separada do nó visual** (canais independentes — os 4 bugs do GOV.UK vieram de misturar); anúncio com **debounce ~1s** (fala na pausa, não a cada evento); **frase completa e específica** ("14 resultados para 'inversor'", "proposta.pdf enviado"), nunca fragmento; erro que exige ação imediata usa `role="alert"`; **uma live region por componente**. Consumidores: §4, §6, §8 (pending), §11, §14.

**Nota 1.13-b (v0.15 — emenda, dois regimes de anúncio):** o §1.13 nasceu para **atualizações contínuas** (contagem de busca a cada tecla), onde anunciar cada evento defasaria o visual — daí os canais separados + debounce (D4-b). **Mensagens discretas** (toast, alerta injetado, banner dinâmico) são conteúdo novo inserido UMA vez: não há defasagem possível, e o padrão universal correto (Spectrum, React Aria, Radix, Base Web, gov.br) é **o próprio nó visível ser a live region**, injetado já populado. Regra de implementação que a suite cobra nos dois regimes: **o container com `role` precisa existir no DOM ANTES do conteúdo ser inserido** — inserir container e conteúdo juntos faz leitores de tela perderem o anúncio (WCAG 4.1.3, técnica documentada). Sem esta nota, a validação executada do Bloco 3 reprovaria o padrão correto por leitura literal do §1.13. Descartado: exigir canal separado também para mensagens discretas (duplicaria o DOM sem ganho e contraria todo o mercado verificado).

7. **Teste do polegar (v0.7 — mobile):** renderizar o componente em viewport de **360px** (Android BR mais comum) com teclado virtual aberto (altura útil ~450px): todos os alvos ≥44px ou com hit-area estendida documentada; nada essencial coberto pelo teclado; ordem de leitura íntegra em coluna única; texto do campo ≥16px (anti-zoom iOS); zoom 200% não quebra (WCAG 1.4.4). O preview traz a faixa 360px pré-renderizada, como a faixa grayscale.

O preview traz a faixa em escala de cinza pré-renderizada para o teste 3 ser feito a olho.

---

## 2. Form-field (campo de formulário) — `estável` · validado pelo Rafael em 2026-08-02 (v0.6)

> **Emenda v0.7 (2026-08-02, aprovada pelo Rafael — supersedes formais):** (a) estados passam de 8 para **9** com a entrada do `warning` (§2.4); (b) tokens de warning adicionados ao §2.5 com contraste medido; (c) contrato HTML ganha os atributos de teclado virtual (§2.6); (d) fonte do campo em toque = 16px (tokens v1.2). O que estas emendas substituem: "8 estados" onde citado → 9; tabela §2.6 anterior → tabela com as colunas mobile.

> **O que é este item:** o componente-BASE anatômico de todo o Bloco 2. Define a estrutura que envolve QUALQUER controle de entrada — rótulo, texto auxiliar, mensagens, contador — mais estados, tokens, contrato HTML, arquitetura de máscaras, microcopy e acessibilidade. Os 12 itens seguintes do bloco (input de texto, busca, número, textarea, select, combobox, checkbox*, radio*, switch*, slider, date picker, upload) HERDAM esta spec e documentam apenas o que diverge. (*grupos de escolha usam `fieldset/legend` no lugar de `label` — divergência documentada nos próprios itens.)
>
> **Base de evidência:** 4 rodadas de pesquisa (2026-08-02, ~30 fontes primárias cruzadas): Carbon, GOV.UK + família, Material M1–M3, Polaris, Atlassian, Spectrum, USWDS, W3C WAI/WCAG 2.2, NN/g, Baymard, SAP Fiori, gov.br DS, web.dev (Google), Uber Base Web, Stripe, fintechs (Nubank/Revolut/Monzo/Wise), Konjević/Smashing, ecossistema BR de máscaras. Decisões B1–B6 aprovadas pelo Rafael em 2026-08-02 após sobreviverem às 4 rodadas.

### 2.1 Papel

Estruturar a comunicação entre o sistema e a pessoa em QUALQUER entrada de dado: o que o sistema pede (rótulo), como preencher (ajuda), o que aconteceu (erro/sucesso) e quanto cabe (contador). O form-field não é um input — é a moldura que faz qualquer input ser compreensível, acessível e consistente em todos os produtos SEED (ERP, chat, site institucional, formulários de captação).

### 2.2 Anatomia — 5 slots

```
 (A) ┌ Rótulo ──────────────── (opcional) ┐   ← label + indicador de opcionalidade
 (B) │ Texto auxiliar pré-digitação*      │   ← só quando PRECISA ser lido antes (exceção B1)
 (C) ┌────────────────────────────────────┐
     │  [controle de entrada]             │   ← input/select/textarea/etc (slot genérico)
     └────────────────────────────────────┘
 (D) │ ⓘ Texto auxiliar  ou  ⚠ Erro       │   ← zona de mensagem (erro SUBSTITUI ajuda — B2)
 (E) │                          120/500   │   ← contador (só quando há limite)
```

| Slot | Elemento | Regras |
|---|---|---|
| A | Rótulo (`<label for>`) | **Sempre visível, sempre no topo** (nunca placeholder como rótulo; nunca flutuante; nunca à esquerda). Montserrat SemiBold 14px, `--seed-field-label` (13.86:1). Sufixo `(opcional)` em Regular, `--seed-field-optional-text`. Variante `label-hidden` (visually-hidden, permanece p/ leitor de tela) só quando o contexto torna o rótulo redundante — ex.: campo de busca com ícone e placeholder "Buscar…" — regra Polaris: usar com muito critério |
| B | Ajuda pré-digitação (exceção) | Entre rótulo e controle SOMENTE quando a instrução precisa ser lida ANTES de digitar (formato obrigatório complexo, decisão pré-preenchimento). Padrão é (D) |
| C | Controle | Slot genérico. Altura md **44px** (alvo de toque WCAG, igual botão), sm 32px (densidade desktop), lg 52px. Radius `--seed-radius-md`. Borda 1px `--seed-field-border` (4.74:1 — é ela que identifica o controle, WCAG 1.4.11). Placeholder: DESENCORAJADO; quando usado, só exemplo de formato prefixado "Ex.:" — nunca instrução, nunca rótulo |
| D | Zona de mensagem | Ajuda: `--seed-field-help-text` (6.55:1 — acima do piso AA, na direção do 7:1 que o GOV.UK adotou após pesquisa). Erro: ícone ⚠ + texto `--seed-field-error-text` (7.18:1), prefixo "Erro:" visually-hidden p/ leitores de tela. **Erro substitui a ajuda** (B2); a mensagem de erro DEVE conter a instrução completa de correção (regra editorial que fecha o buraco da substituição). Sem reserva de altura: shift de 1 linha aceito (M3: substituição existe justamente p/ minimizar bump de layout) |
| E | Contador | Só quando há limite real. JetBrains Mono 12px (dado de medição = mono, regra dos tokens v1.1). Formato `usados/limite`. **SUPERSEDE (v0.10):** a política completa do contador vive no §6.3 (padrão GOV.UK) — o campo NUNCA trunca com `maxlength` duro; exceder o limite vira estado de ERRO com a contagem do excesso. A versão v0.6–v0.9 desta linha ('ao exceder, vira mensagem de erro' com maxlength no exemplo) fica substituída: o maxlength duro truncava colagem silenciosamente — perda de dado pega na pesquisa do textarea |

**Espaçamento vertical (mapeado da referência 8/16dp do Material para a escala SEED):** rótulo→controle `sp-2` (8px) · controle→mensagem `sp-2` (8px) · campo→campo `sp-6` (24px) confortável / `sp-4` (16px) denso (ERP).

### 2.3 As 6 decisões estruturais (aprovadas 2026-08-02)

| # | Decisão | Porquê (placar de fontes) | Descartado |
|---|---|---|---|
| B1 | Ajuda ABAIXO do controle; exceção: instrução pré-digitação vai acima | 6×1 (Carbon, Material, Polaris, Spectrum, Atlassian, MUI vs GOV.UK); padrão da stack shadcn; label topo lido 28% mais rápido que lateral (Baymard) | Hint sempre acima (GOV.UK — contexto one-thing-per-page de governo, não ERP denso) |
| B2 | Erro SUBSTITUI a ajuda + regra editorial (erro contém a correção completa) | 5×1 (Carbon, M1/M3, Atlassian, Spectrum, uxpatterns vs GOV.UK); M3: substituir evita empurrar o layout | Empilhar ajuda+erro (dobra a altura no pior momento); reservar altura fixa (desperdiça vertical em ERP) |
| B3 | Produto (ERP/chat): marcar SÓ `(opcional)`. Formulários públicos de conversão: marcar AMBOS (`*` + `(opcional)`) | Produto: Polaris (proíbe asterisco), Carbon, GOV.UK, Uber ("marque só os menos comuns"). Público: Baymard mediu 32% de falha marcando só opcionais no e-commerce; só 14% marcam ambos | Regra única para os dois contextos (a indústria não tem consenso único — tem consenso POR contexto); asterisco no ERP (ruído: quase tudo é obrigatório) |
| B4 | Validação onBlur com reward-early/punish-late + error summary no submit | Formulação operacional Polaris: valida quando foco sai E há ≥1 caractere; campo em erro revalida a cada tecla; obrigatório intocado só marca no submit. Refinamento Fiori p/ ERP: entrada massiva agrega erros em registro form-level. NN/g: correção imediata com o campo fresco na memória | Validar só no submit (GOV.UK — governo); validar enquanto digita desde a 1ª tecla (validação prematura, irrita — Baymard) |
| B5 | Arquitetura de máscara: REGISTRY EXTENSÍVEL, valor canônico sem formatação, telefone em E.164 | Stripe: tolerante na entrada, estrito no armazenamento; Uber: seletor de país + número (2 inputs, menos ambíguo); libphonenumber/E.164 = padrão da indústria; Baymard: 64% não usam máscara localizada e deviam. Crítica do Rafael derrubou o enum fixo: 0800 e clientes EUA não cabiam | Enum fechado de máscaras BR (proposta da rodada 3 — fecharia a porta p/ 0800, EUA e máscaras futuras) |
| B6 | Estado success EXISTE, com parcimônia: só onde a confirmação carrega informação real | Atlassian/Spectrum/Stripe têm; Stripe marca válido e mede ganho de confiança; Uber alerta contra marcar prematuramente | Sem success (perde o ganho em campos críticos: e-mail, documento, disponibilidade); success universal (ruído verde decorativo — GOV.UK desaconselha com razão) |

### 2.4 Estados — 9 (os 6 obrigatórios do §0 + readonly, success e warning¹)

¹ *warning adicionado na emenda v0.7 (era 8 na v0.6 — supersede formal).*

| Estado | Sinais (light) | Regras |
|---|---|---|
| default | borda `--seed-field-border` (4.74:1) | — |
| hover | borda `--seed-field-border-hover` | transição 100ms produtiva |
| focus | `--seed-focus-ring` (anel, NÃO engrossar borda) | mecanismo de foco ≠ mecanismo de erro (GOV.UK removeu borda grossa no erro por confundir com foco) |
| filled | valor em `--seed-field-value` (13.86:1) | valor ≠ placeholder tem que ser óbvio (por isso placeholder é 4.74 e valor 13.86) |
| **error** | borda `--seed-field-border-error` (5.19:1) + ícone ⚠ + mensagem (7.18:1) + `aria-invalid` | NUNCA só cor (WCAG 1.4.1): são 3 sinais. Erro nunca aparece antes de interação real (B4) |
| **success** | borda `--seed-field-border-success` (4.60:1) + ícone ✓ + mensagem opcional | só onde confirma algo real (B6) |
| **warning** (v0.7) | borda `--seed-field-border-warning` (4.82:1) + ícone ▲ + mensagem (6.68:1) | **Guarda-corpo duro:** SÓ para plausibilidade — dado válido em formato, mas incomum ("consumo 10× acima da sua média — confira"). NUNCA para obrigatoriedade, NUNCA bloqueia submit. Sem o guarda-corpo, warning banaliza e treina a ignorar avisos (por isso o GOV.UK nem o tem). Origem: SAP Fiori/Carbon (ERP) |
| disabled | fundo `surface-sunken` + texto `text-disabled` + `cursor:not-allowed` | **USO RESTRITO** (USWDS: contraste baixo, invisível p/ teclado e leitor de tela) — preferir campo habilitado com validação, ou readonly. Nunca esconder informação necessária num campo disabled |
| **readonly** | fundo `surface-sunken`, SEM borda de controle, texto pleno (12.75:1), focável, copiável | estado de primeira classe (Fiori/USWDS/Polaris): exibe dado não-editável NESTE contexto (ex.: UC vinda do cadastro). Readonly ≠ disabled: readonly participa do fluxo (tab, cópia, leitor de tela); não recebe estados de validação (Fiori) |

*loading (9º, condicional):* campos com operação assíncrona (CEP consultando ViaCEP, combobox buscando) mostram spinner 16px no slot direito do controle + `aria-busy` — herda a regra do botão (sem layout shift).

### 2.5 Tokens de componente (camada 3) — par light/dark obrigatório, contrastes medidos programaticamente (2026-08-02)

```css
/* Estrutura */
--seed-field-height-sm: 32px;  --seed-field-height-md: 44px;  --seed-field-height-lg: 52px;
--seed-field-radius: var(--seed-radius-md);
--seed-field-gap: var(--seed-sp-2);          /* rótulo→controle e controle→mensagem */
--seed-field-stack-gap: var(--seed-sp-6);    /* campo→campo; sp-4 no denso */

/* Cor — light (medições sobre branco; campo assenta em superfície branca) */
--seed-field-bg: var(--seed-surface-page);               /* branco */
--seed-field-label: var(--seed-cinza-900);               /* 13.86:1 */
--seed-field-value: var(--seed-cinza-900);               /* 13.86:1 */
--seed-field-placeholder: var(--seed-cinza-600);         /* 4.74:1 — AA e distinto do valor */
--seed-field-optional-text: var(--seed-cinza-600);       /* 4.74:1 */
--seed-field-help-text: var(--seed-cinza-700);           /* 6.55:1 — direção GOV.UK 7:1 */
--seed-field-border: var(--seed-cinza-600);              /* 4.74:1 ≥3:1 (WCAG 1.4.11) */
--seed-field-border-hover: var(--seed-cinza-700);
--seed-field-error-text: var(--seed-vermelho-700);       /* 7.18:1 */
--seed-field-border-error: var(--seed-vermelho-600);     /* 5.19:1 */
--seed-field-success-text: var(--seed-turquesa-700);     /* 6.32:1 */
--seed-field-border-success: var(--seed-turquesa-600);   /* 4.60:1 */
--seed-field-readonly-bg: var(--seed-surface-sunken);    /* texto 12.75:1 sobre cinza-50 */
--seed-field-warning-text: var(--seed-dourado-700);      /* 6.68:1 (v0.7) */
--seed-field-border-warning: var(--seed-dourado-600);    /* 4.82:1 (v0.7) */

/* Par dark — REGRA v0.5: token que referencia primitivo DECLARA o par (primitivo não troca de modo).
   Medições: rótulo/mensagens sobre dark-default #141D23; conteúdo do campo sobre dark-raised #1D272D */
[data-theme="dark"] {
  --seed-field-bg: var(--seed-surface-raised);           /* #1D272D */
  --seed-field-label: var(--seed-cinza-100);             /* 14.16:1 */
  --seed-field-value: var(--seed-cinza-100);             /* 12.61:1 */
  --seed-field-placeholder: var(--seed-cinza-400);       /* 6.01:1 */
  --seed-field-optional-text: var(--seed-cinza-400);
  --seed-field-help-text: var(--seed-cinza-300);         /* 8.92:1 */
  --seed-field-border: var(--seed-cinza-500);            /* 4.50:1 (600 sumia; 400 gritava em massa de campos do ERP) */
  --seed-field-border-hover: var(--seed-cinza-400);      /* 6.01:1 */
  --seed-field-error-text: var(--seed-vermelho-300);     /* 8.49:1 */
  --seed-field-border-error: var(--seed-vermelho-400);   /* 5.52:1 */
  --seed-field-success-text: var(--seed-turquesa-300);   /* 9.32:1 */
  --seed-field-border-success: var(--seed-turquesa-300); /* 8.31:1 */
  --seed-field-readonly-bg: var(--seed-surface-sunken);  /* texto = field-value cinza-100 #E3EBF0 sobre #141D23: 14.16:1 (v0.13 — supersede do comentário 9.72:1, que media cinza-300 sobre a página, par que o readonly não renderiza) */
  --seed-field-warning-text: var(--seed-dourado-300);    /* 9.22:1 (v0.7) */
  --seed-field-border-warning: var(--seed-dourado-300);  /* 8.21:1 (v0.7) */
}
```

### 2.6 Contrato HTML do campo (a camada que os DS visuais não cobrem)

Todo campo declara o trio `type` + `inputmode` + `autocomplete` + `name/id` ESTÁVEIS. Serve três frentes de uma vez: teclado mobile correto, autofill do navegador (conversão) e legibilidade por máquinas — agentes de IA e crawlers entendem o formulário pelos atributos semânticos (a frente "IA/SEO" do site institucional).

| Dado | type | inputmode | autocomplete | enterkeyhint² | autocapitalize/autocorrect/spellcheck² | Nota |
|---|---|---|---|---|---|---|
| Nome | text | — | name | next | on / off / off | |
| E-mail | email | email | email | next | **off / off / off** | Android "corrige" e-mail p/ palavra |
| Telefone | tel | tel | tel | next | off / off / off | armazena E.164 (§2.7) |
| CPF/CNPJ/CEP/UC | **text** | **numeric** | — (cpf não tem token oficial; cep usa postal-code) | next | **off / off / off** | **NUNCA type=number** (só p/ quantidade incremental — web.dev); number quebra máscara, zeros à esquerda e leitores |
| Endereço | text | — | street-address / address-line1… | next | on / on / on | |
| Valores (R$, kWh) | text | decimal | — | next | off / off / off | JetBrains Mono no valor (dado de medição) |
| Senha | password | — | current-password / new-password | done | off / off / off | norma §3.5 |

² *Colunas adicionadas na emenda v0.7 (mobile): `enterkeyhint` rotula a tecla Enter do teclado virtual ("próximo" no meio do fluxo, "concluído"/`done` no último campo, `search` na busca); o trio `autocapitalize/autocorrect/spellcheck` desligado em identificadores impede o Android de "corrigir" UC/códigos para palavras. **SC 3.3.7 Redundant Entry (WCAG 2.2):** dado já fornecido no mesmo fluxo não pode ser exigido de novo por transcrição — auto-popular ou oferecer seleção (mata o "confirme seu e-mail").*

Regras: `name`/`id` nunca gerados aleatoriamente por render (autofill exige estabilidade — web.dev) · formulário real `<form>` com botão de submit · **botão de submit NUNCA desabilitado** por campos vazios (Atlassian): a validação e o error summary dizem o que falta.

### 2.6b Grupos de controles — fieldset + legend (emenda v0.12)

Quando o "campo" é um GRUPO (radios §9, checkboxes §10, grupos de switch §8), o slot A vira `<legend>` dentro de `<fieldset>`: a pergunta é a legend; ajuda e erro do form-field continuam valendo via `aria-describedby`. Regra declarada UMA vez aqui — as specs da família citam. (GOV.UK/NHS/USWDS unânimes; a legend é lida junto de cada opção pelos leitores, dando contexto sem repetição.)

### 2.7 Máscaras — registry extensível (decisão B5)

**Três princípios arquiteturais:**
1. **Máscara é apresentação.** O valor canônico armazenado NÃO tem formatação: CPF = 11 dígitos, CEP = 8, telefone = **E.164** (`+5533999999999`, `+14155552671`). A exibição formata; o dado viaja limpo (bancos, APIs, SMS esperam E.164 — padrão libphonenumber/Google).
2. **Tolerante na entrada, estrita no armazenamento** (Stripe). Colar `033 99999-9999`, `(33)999999999` ou `33999999999` → tudo aceito e normalizado. Nunca rejeitar por pontuação.
3. **Registry aberto.** Máscaras são registráveis por nome (`registerMask("placa-mercosul", …)`) — produto novo adiciona máscara sem alterar o componente. NÃO é enum fechado (alternativa descartada na rodada 3 pela crítica do Rafael: 0800 e clientes EUA não cabiam).

**Registry inicial:**

| Máscara | Exibição | Canônico | Notas |
|---|---|---|---|
| `cpf` | 000.000.000-00 | 11 díg. | valida dígitos verificadores |
| `cnpj` | 00.000.000/0000-00 | 14 díg. | idem |
| `cpf-cnpj` | dinâmica pelo comprimento | 11 ou 14 | campo único B2C+B2B (padrão BR) |
| `cep` | 00000-000 | 8 díg. | + consulta ViaCEP (loading no campo; falha da API NUNCA bloqueia — preenchimento manual sempre possível) |
| `tel-br` | (00) 0000-0000 / (00) 9 0000-0000 | E.164 +55… | detecta fixo/celular pelo comprimento |
| `tel-nao-geografico` | 0800 000 0000 | dígitos puros | 0800/0300/0500/4004 — sem DDD, sem E.164 de assinante, categoria própria |
| `tel-internacional` | seletor de país + número nacional | E.164 | padrão Uber (2 inputs); default Brasil; cobre EUA e qualquer país; trocar país MANTÉM o número digitado (Evil Martians); validação libphonenumber |
| `moeda` | R$ 1.234,56 (ou US$ por locale) | decimal | |
| `data` | DD/MM/AAAA | ISO 8601 | tolera colar 01012026 |

**Armadilhas de implementação (documentadas pela comunidade BR — bugs reais a evitar):** `maxLength` conta os caracteres DA MÁSCARA (CPF = 14, não 11) · reposicionar o cursor após formatação programática (o value re-setado joga o cursor pro fim) · autofill do navegador entrega valor sem máscara → normalizar no change, não só no keypress.

### 2.8 Microcopy — tom SEED (vocabulário PT-BR alinhado ao gov.br DS: rótulo, texto auxiliar, mensagem)

1. **Rótulo:** substantivo curto, sentence case, sem dois-pontos. "Unidade consumidora", "E-mail", "CNPJ". Nunca instrução no rótulo.
2. **Texto auxiliar:** 1 linha, diz FORMATO ou PORQUÊ. "Somente números." · "Está na sua fatura de energia, no canto superior." **Nunca indica obrigatoriedade** (regra Uber: esse espaço é de formato/erro).
3. **Erro = o que houve + como corrigir, na voz SEED (direto, acolhedor, sem culpar):** ✅ "CPF incompleto — digite os 11 números." · "CEP não encontrado — confira ou preencha o endereço manualmente." | ❌ "Erro no campo." · "Entrada inválida." · "Você digitou errado." Específico > genérico (Baymard: "CEP curto demais" > "Inválido").
4. **Dado pessoal explica o porquê (LGPD + prática fintech):** texto auxiliar do CPF: "Usamos seu CPF apenas para emitir a proposta." Coleta sem porquê visível é atrito e risco.
5. **`(opcional)` literal em minúsculas** no produto; formulário público de conversão usa `*` + legenda "Campos com * são obrigatórios" no topo (B3).

### 2.9 Acessibilidade

`<label for>` sempre (nunca div-rotulada) · `aria-describedby` na ordem **erro ANTES da ajuda** (`"err-id help-id"` — AUI: o erro é anunciado primeiro) · `aria-invalid` só após validação real, NUNCA no estado inicial (W3C ARIA21), e SEMPRE com valor explícito `"true"` — nunca `toggleAttribute`, que grava o atributo com valor vazio: `[aria-invalid="true"]` do CSS não casa e a borda de erro some (bug flagrado na validação executada de 2026-08-03) · mensagem de erro com prefixo "Erro:" visually-hidden · erro dinâmico anunciado (região `aria-live="assertive"` ou `role="alert"`) · ícones de estado com par textual (1.4.1) · foco: anel, nunca borda engrossada · contraste: TODOS os pares da §2.5 medidos ≥4.5:1 texto e ≥3:1 não-texto · zoom 400%: layout de coluna única resiste · toque ≥44px no md.

### 2.10 Recuperação de erro no formulário (nível form — regras que o form-field expõe)

1. **NUNCA limpar campos após erro** (Baymard/Stripe: re-digitar é gatilho de abandono nº1).
2. **Error summary no submit** para formulário com 3+ campos: bloco no topo com título instrucional + lista de links-âncora para cada erro; foco move pro summary (GOV.UK); autoscroll até o primeiro campo em erro (Baymard).
3. **Erro também no `<title>` da página** quando o submit recarrega (USWDS — leitores de tela anunciam antes de tudo).
4. **ERP/entrada massiva (Fiori):** validação por campo no blur (B4) + agregado de erros form-level com contador; teclado flui sem interrupção. Detalhe: ponte com o Bloco 3 (alert/banner) — o componente de summary nasce lá.

### 2.11 Código — HTML/CSS

```html
<div class="seed-field">
  <label class="seed-field__label" for="cpf">
    CPF <span class="seed-field__optional">(opcional)</span>
  </label>
  <input class="seed-field__control" id="cpf" name="cpf" type="text"
         inputmode="numeric" maxlength="14" placeholder="Ex.: 000.000.000-00"
         aria-describedby="cpf-err cpf-help">
  <p class="seed-field__help" id="cpf-help">Usamos seu CPF apenas para emitir a proposta.</p>
  <p class="seed-field__error" id="cpf-err" hidden>
    <span class="seed-visually-hidden">Erro:</span>
    <svg class="seed-field__error-icon" aria-hidden="true" viewBox="0 0 16 16" width="16" height="16"><path fill="currentColor" d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1Zm-.9 3.5h1.8v5h-1.8v-5Zm.9 8.4a1.1 1.1 0 1 1 0-2.2 1.1 1.1 0 0 1 0 2.2Z"/></svg>
    CPF incompleto — digite os 11 números.
  </p>
</div>
```

```css
[hidden]{display:none!important} /* ARMADILHA (bug real pego pelo Rafael na validação do preview v0.7, 2026-08-02): classes de mensagem usam display:flex, que vence o display:none que o navegador aplica ao atributo `hidden` — sem esta regra, erro e sucesso aparecem SEMPRE. No React o problema não existe (renderização condicional), mas todo consumo HTML/CSS puro precisa desta linha. */
.seed-field{display:flex;flex-direction:column;gap:var(--seed-field-gap)}
.seed-field__label{font-family:var(--seed-font-sans);font-weight:var(--seed-fw-semibold);
  font-size:14px;color:var(--seed-field-label)}
.seed-field__optional{font-weight:400;color:var(--seed-field-optional-text)}
.seed-field__control{height:var(--seed-field-height-md);padding:0 12px;
  font-family:var(--seed-font-sans);font-size:14px;color:var(--seed-field-value);
  background:var(--seed-field-bg);border:1px solid var(--seed-field-border);
  border-radius:var(--seed-field-radius);
  transition:border-color var(--seed-dur-productive-fast) var(--seed-ease-productive)}
.seed-field__control::placeholder{color:var(--seed-field-placeholder)}
.seed-field__control:hover:not(:disabled):not([readonly]){border-color:var(--seed-field-border-hover)}
.seed-field__control:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
.seed-field__control[aria-invalid="true"]{border-color:var(--seed-field-border-error)}
.seed-field__control[data-valid="true"]{border-color:var(--seed-field-border-success)}
.seed-field__control:disabled{background:var(--seed-surface-sunken);
  color:var(--seed-text-disabled);cursor:not-allowed}
.seed-field__control[readonly]{background:var(--seed-field-readonly-bg);border-color:transparent}
.seed-field__help{font-size:12px;color:var(--seed-field-help-text);margin:0}
.seed-field__error{display:flex;align-items:center;gap:6px;font-size:12px;
  font-weight:var(--seed-fw-semibold);color:var(--seed-field-error-text);margin:0}
.seed-field__counter{font-family:var(--seed-font-mono);font-size:12px;
  color:var(--seed-field-help-text);align-self:flex-end}
.seed-visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;
  padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
```

### 2.12 Código — React/TypeScript (padrão shadcn de composição; RHF pluga por cima)

```tsx
import * as React from "react";
import { cn } from "@/lib/utils";
import { CircleAlert, CircleCheck } from "lucide-react";

type FieldCtx = { id: string; helpId: string; errId: string;
  invalid?: boolean; valid?: boolean };
const FieldContext = React.createContext<FieldCtx | null>(null);
const useField = () => {
  const ctx = React.useContext(FieldContext);
  if (!ctx) throw new Error("Componentes Field* devem viver dentro de <FormField>");
  return ctx;
};

export function FormField({ id, invalid, valid, className, children }:
  React.PropsWithChildren<{ id: string; invalid?: boolean; valid?: boolean; className?: string }>) {
  const ctx = { id, helpId: `${id}-help`, errId: `${id}-err`, invalid, valid };
  return (
    <FieldContext.Provider value={ctx}>
      <div className={cn("flex flex-col gap-2", className)}>{children}</div>
    </FieldContext.Provider>
  );
}

export function FieldLabel({ optional, children }:
  React.PropsWithChildren<{ optional?: boolean }>) {
  const { id } = useField();
  return (
    <label htmlFor={id} className="text-sm font-semibold text-[var(--seed-field-label)]">
      {children}{" "}
      {optional && <span className="font-normal text-[var(--seed-field-optional-text)]">(opcional)</span>}
    </label>
  );
}

export function FieldControl(props: React.InputHTMLAttributes<HTMLInputElement>) {
  const { id, helpId, errId, invalid, valid } = useField();
  return (
    <input
      id={id}
      aria-invalid={invalid || undefined}
      aria-describedby={cn(invalid && errId, helpId) || undefined} /* erro ANTES da ajuda */
      data-valid={valid || undefined}
      className={cn(
        "h-11 rounded-md border bg-[var(--seed-field-bg)] px-3 text-sm",
        "text-[var(--seed-field-value)] placeholder:text-[var(--seed-field-placeholder)]",
        "border-[var(--seed-field-border)] hover:border-[var(--seed-field-border-hover)]",
        "transition-colors duration-100 focus-visible:outline-none",
        "focus-visible:ring-2 focus-visible:ring-[var(--seed-border-focus)] focus-visible:ring-offset-2",
        "disabled:cursor-not-allowed disabled:bg-[var(--seed-surface-sunken)] disabled:text-[var(--seed-text-disabled)]",
        "read-only:border-transparent read-only:bg-[var(--seed-field-readonly-bg)]",
        invalid && "border-[var(--seed-field-border-error)]",
        valid && "border-[var(--seed-field-border-success)]",
      )}
      {...props}
    />
  );
}

export function FieldHelp({ children }: React.PropsWithChildren) {
  const { helpId, invalid } = useField();
  if (invalid) return null; /* B2: erro substitui a ajuda */
  return <p id={helpId} className="text-xs text-[var(--seed-field-help-text)]">{children}</p>;
}

export function FieldError({ children }: React.PropsWithChildren) {
  const { errId, invalid } = useField();
  if (!invalid) return null;
  return (
    <p id={errId} role="alert"
       className="flex items-center gap-1.5 text-xs font-semibold text-[var(--seed-field-error-text)]">
      <span className="sr-only">Erro:</span>
      <CircleAlert className="size-4 shrink-0" aria-hidden />
      {children}
    </p>
  );
}

export function FieldSuccess({ children }: React.PropsWithChildren) {
  const { valid } = useField();
  if (!valid) return null;
  return (
    <p className="flex items-center gap-1.5 text-xs font-semibold text-[var(--seed-field-success-text)]">
      <CircleCheck className="size-4 shrink-0" aria-hidden />
      {children}
    </p>
  );
}
```

*Hook de validação B4 (reward-early/punish-late) e utilitários de máscara (registry + libphonenumber-js) são entregues no item "input de texto" deste bloco, que é o primeiro consumidor concreto.*

### 2.13 Decisões deste item (além de B1–B6, com alternativas descartadas)

| Decisão | Porquê | Descartado |
|---|---|---|
| Form-field como componente-base do bloco | 13 itens compartilham a mesma moldura; especificar 1 vez evita 13 variações | Especificar label/ajuda/erro dentro de cada item (deriva incoerência) |
| Ajuda em cinza-700 (6.55:1), não cinza-600 | GOV.UK escureceu o hint p/ 7:1 após usuários não lerem; 700 é o stop SEED mais próximo dessa direção mantendo hierarquia c/ o valor | cinza-600 (4.74 — passa AA mas repete o erro que o GOV.UK corrigiu) |
| Erro texto em vermelho-700 (7.18:1), borda em vermelho-600 | Texto de 12px pede folga acima do piso; borda só precisa 3:1 e o 600 casa com o botão destructive | Tudo em vermelho-500 (4.30:1 medido — serve p/ borda ≥3, mas REPROVA texto normal <4.5) |
| Borda dark = cinza-500 (4.50:1) | cinza-600 somia no dark; cinza-400 em massa de campos do ERP grita (outline de botão usa 400 porque botão é ação — campo é massa) | Reusar o token do botão outline (papéis diferentes: ação pontual vs grade de campos) |
| Foco = anel; erro = cor de borda + ícone + texto | Mecanismos distintos por estado (GOV.UK removeu borda grossa do erro por colidir com foco) | Engrossar borda no erro (colisão de sinal) |
| Contador em JetBrains Mono | "usados/limite" é dado de medição — papel exclusivo da mono nos tokens v1.1 | Montserrat (números dançam sem tabular) |
| ViaCEP com fallback manual sempre | API externa cai; endereço nunca pode ficar refém | CEP obrigatório via API (bloqueio real de cadastro) |

### 2.14 Aplicação dos 6 testes (§1.12)

1. **Container 3:1:** borda default 4.74:1 (light) / 4.50:1 (dark); erro 5.19/5.52; success 4.60/8.31 — MEDIDOS, tabela §2.5. ✅
2. **Escada de mecanismos:** estados não diferem só por tom — error = borda+ícone+mensagem; success = borda+ícone; readonly = REMOVE a borda e muda o fundo; disabled = fundo+cursor+texto; focus = anel. ✅
3. **Grayscale:** ícones ⚠/✓ e a presença/ausência de borda carregam os estados sem matiz — faixa cinza pré-renderizada no preview. ✅
4. **Teste do par:** campo default × campo em erro lado a lado no preview — distinguíveis a 1s de distância de braço. ✅
5. **Regra do um:** adaptação p/ formulário — UMA mensagem por campo por vez (B2 + regra Uber de nunca empilhar validações). ✅
6. **Estado atual:** campo em erro/sucesso/readonly identificável DE RELANCE por quem chega agora, sem interagir (3 sinais no erro; fundo+ausência de borda no readonly). ✅

---

## 3. Input de texto — `estável` · validado pelo Rafael em 2026-08-02 (v0.7)

> **Primeiro consumidor concreto do form-field (§2):** herda integralmente moldura, 9 estados, tokens, contrato HTML, máscaras (§2.7), microcopy (§2.8) e a11y (§2.9) — esta seção documenta APENAS o que é do input: os slots internos do controle, as decisões C1–C4, a subvariante senha (normativa), a escala de larguras e os utilitários prometidos na §2.12 (hook de validação B4 + registry de máscaras de referência).
>
> **Base de evidência:** 3 rodadas próprias (2026-08-02, somadas às 4 do form-field): Carbon, GOV.UK, USWDS, GC Canada, Vaadin, Designsystemet (NO), M3, NN/g, WebAIM/ONS/Scott O'Hara (rodada 1) · Ant Design, Salesforce Lightning, SAP Fiori input, Uber Base Web (rodada 2) · **WCAG 2.2 SC 3.3.8/3.3.7, NIST SP 800-63B rev.4, Animalia DS (BR), TOTVS PO UI (BR), Apple HIG** (rodada 3). Decisões C1–C5 + adições normativas aprovadas pelo Rafael em 2026-08-02 (C5 emendada no §2).

### 3.1 Papel e fronteiras (quando NÃO usar input)

Entrada livre de linha única. **Escolha vence digitação** (Apple HIG): se as respostas possíveis são conhecidas, use um controle de escolha. Régua de volume — **ATUALIZADA na v0.11 pela régua unificada da família de escolha (§7.1, decisão G1)**: 2–6 → radio/checkbox · 7–15 (teto ~20 no ERP denso) → select · acima, ou com busca → combobox · aberto de fato → input com sugestões. (A versão v0.7 desta linha citava só o corte Fiori de ~20; a régua do §7.1 a harmoniza com NN/g, USWDS, M3 e CMS.) Texto longo/multilinha → `textarea`. Busca → item próprio (padrões de teclado e clear diferentes).

### 3.2 Anatomia do controle — slots internos

```
┌──────────────────────────────────────────────────────┐
│ [ícone] [prefixo]  valor digitado   [sufixo] [✕] [⟳] │
└──────────────────────────────────────────────────────┘
   ①        ②            ③               ④      ⑤    ⑥
```

| # | Slot | Regras |
|---|---|---|
| ① | Ícone leading 20px | Decorativo/categórico (`aria-hidden`); nunca portador único de significado |
| ② | Prefixo | Símbolo/texto fixo NÃO editável (R$, +55, https://). Cor `--seed-field-help-text` |
| ③ | Valor | Herda §2. Dado de medição/identificador: JetBrains Mono, **alinhado à direita quando numérico em formulário de edição** (SAP Fiori — colunas de valores comparáveis no ERP) |
| ④ | Sufixo | Unidade/domínio (kWh, kW, %, @seed.com.br). Mesma cor do prefixo |
| ⑤ | Clear (✕) | Botão real 32×32px com hit-area estendida à altura do campo (≥24px WCAG 2.5.8, folga); ver C2 |
| ⑥ | Loading | Spinner 16px + `aria-busy` + texto assistivo "Consultando…" (padrão SLDS); substitui ⑤ durante a operação |

**Regra dura de DOM estável (armadilha Ant Design, bug real documentado):** os slots existem SEMPRE na árvore (vazios quando inativos) — adicionar/remover prefixo/sufixo/clear dinamicamente recria a estrutura e o input **perde o foco no meio da digitação**.

**Overflow:** valor maior que o campo rola horizontalmente (nativo); em desktop, `title` com o valor completo (tooltip de expansão — Apple HIG). Nunca truncar o dado armazenado.

### 3.3 Decisões C1–C4 (aprovadas 2026-08-02; C5 → emenda §2)

| # | Decisão | Porquê (fontes) | Descartado |
|---|---|---|---|
| C1 | Prefixo/sufixo internos para unidade/símbolo; **valor contém só o dado** | USWDS: unidade nunca dentro do value (quebra canônico e máscara); M3: prefixo=moeda, sufixo=unidade/domínio; não usar afixo em campo de resposta aberta | Unidade digitada no valor; afixo externo ao campo (Fiori — frágil em coluna estreita mobile); afixo como substituto de rótulo (Vaadin proíbe) |
| C2 | Clear como slot opcional, restrito: padrão em busca/filtros; opcional nos demais; NUNCA em dado crítico já validado | Carbon: aparece com conteúdo, tab stop, Esc limpa; WebAIM/WCAG: se existe p/ mouse, DEVE ser operável por teclado; Scott O'Hara: some em readonly/disabled, foco RETORNA ao campo | Clear universal (Ant permite — no ERP vira perda acidental); ✕ só-mouse decorativo (falha WCAG A) |
| C3 | Senha: subvariante normativa — ver §3.5 | WCAG 2.2 SC 3.3.8 (AA) + NIST SP 800-63B: virou norma, não preferência | — |
| C4 | Largura comunica o comprimento esperado: escala `xs·sm·md·full`, aplicada PELO LAYOUT, consistente POR GRUPO | Carbon: proporcional ao conteúdo; GC Canada: fixa p/ comprimento conhecido, ≤75 chars; Apple: larguras consistentes por grupo de campos; Fiori: aplicar via layout do form, não hardcode no componente | Tudo full-width (mente sobre o dado; padrão preguiçoso); largura campo-a-campo isolada (poluição visual — Apple) |

**Escala de larguras (aplicada via layout):** `xs` 120px (CEP, UC, sigla) · `sm` 200px (CPF, telefone, data) · `md` 320px (nome, e-mail) · `full` (endereço, campo dominante). Em viewport <640px, todas viram `full` em coluna única (fundamento §0.8).

### 3.4 Tipos cobertos (herdam o contrato §2.6)

text · email · tel · password · url + os mascarados do registry §2.7 (CPF, CNPJ, CPF/CNPJ, CEP+ViaCEP, telefones BR/0800/internacional, moeda, data). `search` e `number` são itens próprios do bloco.

### 3.5 Subvariante senha — NORMATIVA (WCAG 2.2 SC 3.3.8 AA + NIST SP 800-63B)

| Regra | Norma/fonte |
|---|---|
| **NUNCA bloquear colar/autofill** (senha e código de verificação) | WCAG 3.3.8 (técnica suficiente) + NIST "SHALL NOT prevent paste" — gerenciador de senha é aliado |
| `autocomplete="current-password"` / `"new-password"` sempre; nunca `off` em login | WCAG 3.3.8 (quebrar gerenciador = falha AA) |
| Toggle mostrar/ocultar: **botão semântico** 32px (hit-area = altura do campo), ícone olho, `aria-pressed`, `aria-label` "Mostrar senha" | NN/g (desmascarar apoia memória e conferência); auditoria: 9 em 10 sites usam div/anchor — o erro clássico |
| Mascarado por padrão em QUALQUER superfície (ERP roda em escritório aberto) | NN/g + política SEED |
| Nunca pré-popular campo de senha | Apple HIG |
| Sem "confirmar senha/e-mail" por redigitação | WCAG 3.3.7 Redundant Entry |
| Campo suporta 64+ caracteres, Unicode e espaços | NIST rev.4 |
| **Fronteira:** política de senha (mínimo 8/15, blocklist de vazadas, sem composição forçada, sem expiração periódica) pertence à feature de autenticação do produto — o componente garante o SUPORTE | NIST SP 800-63B |

### 3.6 Estados, tokens e a11y — herdados com 3 adições

Estados: os 9 do §2.4 tal qual. Tokens novos: **nenhum de cor** — afixos e ícones usam `--seed-field-help-text`/`currentColor` (decisão: menos tokens = menos manutenção; se afixo precisar de ênfase própria um dia, nasce token com par e medição). A11y além do §2.9: ① afixos são `aria-hidden` e a INFORMAÇÃO vai no rótulo — "Consumo médio (kWh)" — porque leitores de tela não anunciam prefixo/sufixo (Designsystemet); ② clear e toggle são botões no tab order, com foco visível e retorno de foco ao campo após limpar; ③ botão interno: 32×32px visível com hit-area estendida à altura do campo (WCAG 2.5.8 pede ≥24 — folga; exceção documentada ao fundamento 44px por ser controle INTERNO de um alvo que já tem 44px).

### 3.7 Microcopy — 2 regras além do §2.8

1. **Prevenção > correção (Animalia DS):** se só UM erro é possível no campo, o texto auxiliar já ensina a evitá-lo ("Somente números") — melhor que esperar o erro.
2. **Afixo repetido no rótulo, sempre:** o sufixo visual "kWh" é conforto de leitura; a informação oficial mora no rótulo "(kWh)".

### 3.8 Código — HTML/CSS (delta sobre §2.11)

```html
<div class="seed-field" style="max-width:320px"><!-- 320px direto, e nao `var(--seed-input-w-md,320px)`: o token nunca existiu, e exemplo de codigo num canonico e codigo que alguem copia — citar nele um `var()` que nao resolve e ensinar o defeito. Corrigido em 2026-08-26. -->
  <label class="seed-field__label" for="consumo">Consumo médio mensal (kWh)</label>
  <div class="seed-input">
    <span class="seed-input__affix" aria-hidden="true"><!-- prefixo (vazio = DOM estável) --></span>
    <input class="seed-field__control seed-input__control seed-input__control--num"
           id="consumo" name="consumo" type="text" inputmode="decimal"
           autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="next">
    <span class="seed-input__affix" aria-hidden="true">kWh</span>
    <span class="seed-input__actions"><!-- clear/toggle/loading --></span>
  </div>
  <p class="seed-field__help">Está na sua fatura de energia.</p>
</div>
```

```css
.seed-input{display:flex;align-items:center;gap:8px;height:var(--seed-field-height-md);
  padding:0 12px;background:var(--seed-field-bg);
  border:1px solid var(--seed-field-border);border-radius:var(--seed-field-radius);
  transition:border-color var(--seed-dur-productive-fast) var(--seed-ease-productive)}
.seed-input:hover{border-color:var(--seed-field-border-hover)}
.seed-input:focus-within{box-shadow:var(--seed-focus-ring)}       /* foco no WRAPPER */
.seed-input .seed-field__control{border:0;height:100%;padding:0;flex:1;min-width:0;
  background:transparent;outline:none;box-shadow:none}
.seed-input__affix{color:var(--seed-field-help-text);font-size:14px;flex:none}
.seed-input__affix:empty{display:none}
.seed-input__control--num{font-family:var(--seed-font-mono);text-align:right}
.seed-input__actions{display:flex;align-items:center;flex:none}
.seed-input__btn{display:flex;align-items:center;justify-content:center;width:32px;height:100%;
  background:none;border:0;color:var(--seed-field-help-text);cursor:pointer;border-radius:4px}
.seed-input__btn:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
.seed-input[data-invalid="true"]{border-color:var(--seed-field-border-error)}
.seed-input[data-warning="true"]{border-color:var(--seed-field-border-warning)}
.seed-input[data-valid="true"]{border-color:var(--seed-field-border-success)}
@media (pointer: coarse){.seed-input .seed-field__control{font-size:var(--seed-fs-field-touch)}}
```

### 3.9 Código — React/TypeScript (Input + hook B4 + registry de máscaras)

```tsx
/* ============ useFieldValidation — B4: reward early, punish late (Polaris/Konjević) ============ */
export function useFieldValidation(validate: (v: string) => string | null) {
  const [error, setError] = React.useState<string | null>(null);
  const [touched, setTouched] = React.useState(false);
  return {
    error, invalid: !!error, valid: touched && !error,
    onBlur: (e: React.FocusEvent<HTMLInputElement>) => {
      const v = e.target.value;
      if (v.trim() !== "") { setTouched(true); setError(validate(v)); }
      /* vazio + intocado: só marca no SUBMIT (Polaris) */
    },
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
      if (error) setError(validate(e.target.value)); /* punish late: revalida por tecla SÓ em erro */
    },
    validateNow: (v: string) => { setTouched(true); const r = validate(v); setError(r); return r; },
  };
}

/* ============ Registry de máscaras — B5 (implementação de referência) ============ */
type MaskDef = {
  format: (digits: string) => string;   /* apresentação */
  maxDigits: number;
  canonical?: (digits: string) => string; /* default: dígitos puros; telefone: E.164 */
};
const maskRegistry = new Map<string, MaskDef>();
export function registerMask(name: string, def: MaskDef) { maskRegistry.set(name, def); }
export function applyMask(name: string, rawValue: string) {
  const def = maskRegistry.get(name);
  if (!def) throw new Error(`Máscara não registrada: ${name}`);
  const digits = rawValue.replace(/\D+/g, "").slice(0, def.maxDigits); /* tolerante na entrada */
  return { display: def.format(digits), canonical: def.canonical?.(digits) ?? digits };
}
/* registro inicial — demais máscaras do §2.7 seguem o mesmo molde; telefone usa libphonenumber-js */
registerMask("cpf-cnpj", { maxDigits: 14, format: d => d.length <= 11
  ? d.replace(/(\d{3})(\d)/, "$1.$2").replace(/(\d{3})(\d)/, "$1.$2").replace(/(\d{3})(\d{1,2})$/, "$1-$2")
  : d.replace(/^(\d{2})(\d)/, "$1.$2").replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3")
     .replace(/\.(\d{3})(\d)/, ".$1/$2").replace(/(\d{4})(\d{1,2})$/, "$1-$2") });
registerMask("tel-0800", { maxDigits: 11,
  format: d => d.replace(/^(\d{4})(\d)/, "$1 $2").replace(/^(\d{4}) (\d{3})(\d{1,4})/, "$1 $2 $3") });
/* Nota de cursor (armadilha BR §2.7): ao formatar programaticamente, reposicionar o caret
   relativo aos DÍGITOS antes do cursor, não ao índice bruto da string. */

/* ============ Input — consome o form-field §2.12 ============ */
type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
  prefix?: React.ReactNode; suffix?: React.ReactNode;
  clearable?: boolean; onClear?: () => void; loading?: boolean; warning?: boolean;
};
export function Input({ prefix, suffix, clearable, onClear, loading, warning, className, ...props }: InputProps) {
  const { id, helpId, errId, invalid, valid } = useField();
  const ref = React.useRef<HTMLInputElement>(null);
  const showClear = clearable && !loading && !props.readOnly && !props.disabled &&
    String(props.value ?? "").length > 0;
  return (
    <div data-invalid={invalid || undefined} data-warning={warning || undefined}
         data-valid={valid || undefined}
         className={cn("flex h-11 items-center gap-2 rounded-md border px-3",
           "bg-[var(--seed-field-bg)] border-[var(--seed-field-border)]",
           "hover:border-[var(--seed-field-border-hover)] focus-within:ring-2",
           "focus-within:ring-[var(--seed-border-focus)] focus-within:ring-offset-2",
           "data-[invalid]:border-[var(--seed-field-border-error)]",
           "data-[warning]:border-[var(--seed-field-border-warning)]",
           "data-[valid]:border-[var(--seed-field-border-success)]",
           "pointer-coarse:text-[16px]", className)}>
      {/* slots SEMPRE presentes — DOM estável (Ant) */}
      <span aria-hidden className="flex-none text-sm text-[var(--seed-field-help-text)] empty:hidden">{prefix}</span>
      <input ref={ref} id={id} aria-invalid={invalid || undefined}
        aria-describedby={cn(invalid && errId, helpId) || undefined}
        className="h-full min-w-0 flex-1 bg-transparent text-sm text-[var(--seed-field-value)] outline-none placeholder:text-[var(--seed-field-placeholder)]"
        {...props} />
      <span aria-hidden className="flex-none text-sm text-[var(--seed-field-help-text)] empty:hidden">{suffix}</span>
      <span className="flex flex-none items-center">
        {showClear && (
          <button type="button" aria-label="Limpar campo"
            className="flex h-full w-8 items-center justify-center rounded text-[var(--seed-field-help-text)] focus-visible:ring-2 focus-visible:ring-[var(--seed-border-focus)]"
            onClick={() => { onClear?.(); ref.current?.focus(); /* foco RETORNA (O'Hara) */ }}>
            <X className="size-4" aria-hidden />
          </button>
        )}
        {loading && <Loader2 className="size-4 animate-spin" aria-hidden />}
        {loading && <span className="sr-only" aria-live="polite">Consultando…</span>}
      </span>
    </div>
  );
}

/* PasswordInput: Input + toggle normativo (§3.5) — aria-pressed, nunca bloquear paste,
   autocomplete current-password/new-password, mascarado por padrão. */
```

### 3.10 Decisões de implementação deste item

| Decisão | Porquê | Descartado |
|---|---|---|
| Foco no WRAPPER (`focus-within`) | Com slots internos, o anel precisa envolver o CONJUNTO; input interno sem borda própria | Anel só no input interno (anel "dentro" do campo — quebra a leitura de container) |
| Slots sempre no DOM (`empty:hidden`) | Bug real Ant: recriar estrutura derruba o foco durante a digitação | Renderização condicional dos slots |
| Zero token de cor novo | Afixo/ícone = papel de ajuda já tokenizado; menos manutenção | `--seed-input-affix` dedicado (duplicaria help-text sem caso de uso divergente) |
| Numérico = mono + direita SÓ em formulário de edição/ERP | Fiori: colunas de valores comparáveis; em formulário público de 1 coluna, esquerda normal | Direita universal (estranho em campo isolado de landing) |
| Clear limpa e NÃO valida | Limpar é intenção de recomeço; erro imediato no campo vazio pune (B4) | Validar no clear (flash de erro) |

### 3.11 Aplicação dos 7 testes (§1.12)

1. **Container 3:1:** herda §2 (borda 4.74/4.50; warning 4.82/8.21 — medidos). ✅ 2. **Escada de mecanismos:** warning ≠ error por cor E ícone (▲ vs ⚠) E comportamento (não bloqueia); clear/loading são mecanismos, não tons. ✅ 3. **Grayscale:** ▲/⚠/✓ distinguem os três estados de validação sem matiz. ✅ 4. **Par:** campo com afixo × sem afixo — hierarquia valor>afixo óbvia (13.86 vs 6.55). ✅ 5. **Regra do um:** uma mensagem por campo; um estado de validação por vez (warning cede ao error). ✅ 6. **Estado atual:** senha mostrada/oculta legível pelo ícone + aria-pressed. ✅ 7. **Polegar (360px, teclado aberto):** campo full-width em coluna única, fonte 16px, clear com hit-area da altura do campo, afixos não colapsam (min-width:0 no input) — faixa 360px no preview. ✅

---

## 4. Busca — `estável` · validado pelo Rafael em 2026-08-02 (v0.8)

> **Consome o form-field (§2) e o input (§3):** herda moldura, tokens, contrato HTML e o clear (C2). Esta seção documenta o que é da busca: as duas variantes por papel, a semântica de landmark, o feedback acessível de resultados, o estado zero-resultados e o comportamento por contexto/plataforma.
>
> **Base de evidência:** 3 rodadas (2026-08-02): Carbon (search + active search pattern), National Archives, ICDS/DWP, Baymard (search-within, query types, no-results), Pencil&Paper (enterprise), UXPin, GOV.UK a11y (caso Léonie Watson) + alphagov (debounce de live region) · ecossistema command palette (Linear/Slack/VS Code/cmdk-shadcn, Maggie Appleton), Algolia (4 guias, mobile) · **Material 3 SearchBar/SearchView (Android), EUI/Elastic, NN/g + Baymard no-results**. Decisões D1–D9 aprovadas pelo Rafael em 2026-08-02.

### 4.1 Papel — duas variantes, um componente

| | `busca-filtro` | `busca-navegacional` |
|---|---|---|
| Emprego | Filtrar dataset visível (tabelas do ERP, listas, chat) | Buscar e IR para resultados (site institucional, busca global) |
| Disparo | Tempo real com debounce (Carbon active search: roda a cada caractere, SEM botão) | Enter/botão → página ou superfície de resultados |
| Escopo | **Módulo/tabela atual por padrão** (Baymard: usuários esperam fortemente "buscar dentro de onde estou"; escopo global só explícito) | Global, com escopo opcional |
| Botão | Não existe | Existe (visível no site; ícone-submit no app) |

Régua tempo real × submit (D5, UXPin): dataset pequeno/médio de resposta rápida → tempo real; consulta pesada de servidor → Enter/submit.

### 4.2 Anatomia

```
busca-filtro:        [🔍] [campo………………………] [✕] [⌘K*]      *chip de atalho, só busca global desktop
busca-navegacional:  [🔍  campo……………………… ✕ ▐Buscar▌]   ← GRUPO ATADO: botão DENTRO da moldura
```

**Grupo atado na navegacional (v0.8, decisão da validação de 2026-08-02):** o botão Buscar vive DENTRO da moldura do campo (height 100%, radius contínuo do wrapper, sem gap) — campo e botão lidos como UMA peça. Porquê: botão externo de mesma altura nominal cria ilusão de desalinhamento (44px de verde cheio ao lado de 44px com borda fina) e mantém viva uma classe inteira de bugs de alinhamento; atado, o alinhamento é estrutural. Padrão National Archives/sites de busca. Descartado: botão externo com gap (a origem do defeito visual pego pelo Rafael em duas rodadas de validação). O foco do grupo usa `:focus-within` no wrapper (§3.10). · Lupa 20px no slot leading (`aria-hidden`) · campo herda §3 (DOM estável) · clear herda C2 integralmente + **Esc também limpa** (Carbon) · chip de atalho: slot de descobribilidade do futuro command palette (fronteira §4.11), tipografia JetBrains Mono 11px, rótulo POR PLATAFORMA (`⌘K` no Mac, `Ctrl+K` no Windows/Linux — mostrar ⌘ para quem não tem a tecla é ruído). **Comportamento intermediário (v0.8, decisão de 2026-08-02):** enquanto a palette não existe, `Ctrl/⌘+K` FOCA a busca global (com `preventDefault` — rouba o atalho do navegador — e `aria-keyshortcuts` declarado); o chip é um botão real que também foca. Quando a palette nascer (Bloco 5/6), o mesmo atalho passa a abri-la — a memória muscular treinada não se perde. Origem: pergunta do Rafael na validação do preview ("como fazer o ⌘K funcionar") — a resposta virou spec.

### 4.3 Decisões D1–D9 (aprovadas 2026-08-02)

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| D1 | Duas variantes por papel | Carbon separa active search (sem botão) de navegacional; empregos e teclado opostos | Componente único ambíguo |
| D2 | `label-hidden` legítimo + placeholder INSTRUTIVO | Carbon: lupa+placeholder bastam, evite rótulo; Baymard/Pencil&Paper: placeholder ensina O QUE é buscável — antídoto enterprise para "não sei o que tem no dataset" | Rótulo visível obrigatório (GOV.UK-family — mantido como opção da navegacional em site com heading); placeholder decorativo |
| D3 | `role="search"` + `type="search"` + `enterkeyhint="search"` | Landmark navegável por leitor de tela; teclado virtual com tecla Buscar | `div` sem landmark; type=text |
| D4 (emendada pela D4-b) | Live region anuncia SÓ a contagem, `polite`+`aria-atomic`, debounce ~300ms; contagem SEMPRE visível (incl. zero) | Caso GOV.UK/Léonie Watson: live region na lista inteira lê TODOS os resultados a cada tecla; alphagov: sem debounce, contagens obsoletas são anunciadas | Live region no container de resultados; assertive (interrompe a digitação) |
| D4-b (v0.13 — supersede do markup D4) | Contagem em DOIS nós: o visível (sem role) atualiza a cada tecla; o falado é nó separado visually-hidden `role="status"` debounced ~300ms — aplicação retroativa do §1.13 ("separada do nó visual") | Validação executada 2026-08-03: o nó único do markup original defasava o visual em 300ms e mostrava contagem obsoleta — exatamente os bugs GOV.UK que motivaram o §1.13 | Nó único visível+falado (markup D4 original) |
| D5 | Tempo real × submit por volume; escopo ao contexto atual por padrão | UXPin (custo de servidor); Baymard: 94% mobile não suportam "buscar dentro" → saltos de escopo e resultados irrelevantes | Tempo real universal; escopo global por padrão no ERP |
| D6 | Fronteiras: command palette → pattern futuro (roadmap); sugestões/autocomplete → anatomia do combobox; chip ⌘K como slot | Palette é composto (modal+combobox+lista) e pressupõe navegação visível saudável (Appleton: ponte GUI↔CLI, não band-aid); shadcn `Command`/cmdk já na stack quando chegar a hora | Palette como componente do Bloco 2; sugestões duplicadas na busca e no combobox |
| D7 | Plataforma: busca GLOBAL em toque expande em superfície full-screen ao focar; desktop = dropdown ancorado ao campo; filtro LOCAL permanece inline | M3 SearchBar/SearchView: full-screen no mobile, docked em tela grande; buscas anteriores aparecem na expansão | Dropdown minúsculo sobre teclado virtual; full-screen para filtro de lista (mata o contexto) |
| D8 | Barra composta com filtros estruturados → Bloco 6 (Dados); sintaxe de query avançada → camada de produto | EUI/Elastic (a dona do Elasticsearch) separa formalmente EuiFieldSearch do EuiSearchBar (query builder) | Filtros e sintaxe embutidos no componente de busca |
| D9 | Estado zero-resultados com declaração inequívoca + recuperação por contexto | NN/g: usuários frequentemente NEM PERCEBEM que não houve resultado; Baymard: ~50% dos sites sem caminho de recuperação, 68% beco sem saída; estratégias comprovadas incluem contato direto | "0 resultados" mudo; esconder a lista sem explicar |

### 4.4 Estados — nota estrutural

A busca NÃO valida conteúdo: os estados error/warning/success do §2.4 **não se aplicam** (não existe "busca errada" — existe busca sem resultados, que é estado da EXPERIÊNCIA, não do campo). Estados do campo: default, hover, focus, filled (com clear visível), loading (consulta em voo — spinner + "Consultando…"), disabled (uso restrito §2.4). Detalhe técnico: `type="search"` dispara clear NATIVO no WebKit — suprimir (`::-webkit-search-cancel-button{display:none}`) para não duplicar com o nosso clear (C2).

### 4.5 Feedback de resultados (D4)

Contagem visível junto aos resultados: **"12 resultados"** / **"12 resultados para 'fazenda'"** — sempre, inclusive **"0 resultados"** (Carbon). Espelho acessível: live region separada (`role="status"`, `aria-atomic="true"`), atualizada com debounce de ~300ms, contendo SÓ a frase de contagem.

### 4.6 Zero resultados (D9) — anatomia e microcopy por contexto

Declaração + eco da query + recuperação:

| Contexto | Microcopy padrão | Recuperação |
|---|---|---|
| ERP (filtro) | "Nenhum resultado para **'x'** em **Propostas**." | [Limpar busca] [Buscar em todo o sistema] — ataca o salto de escopo |
| Site | "Não encontramos nada para **'x'**." | Buscas alternativas/áreas do site + **"Fale com a engenharia"** (telefone/WhatsApp) — o beco sem saída vira lead |
| App | Idem site/ERP conforme a variante | Recentes + populares na superfície expandida (Algolia/M3) |

Nunca esconder que não houve resultado; nunca deixar a tela simplesmente vazia. O componente visual completo de empty state nasce no Bloco 3 — aqui fica a REGRA; lá, a peça.

### 4.7 Comportamento por contexto (tabela integrada — compromisso de 2026-08-02)

| Aspecto | Produto (ERP/chat) | Site institucional | App/toque |
|---|---|---|---|
| Variante | filtro, escopada ao módulo | navegacional (submit → resultados) | filtro OU navegacional, full-width |
| Acesso | toolbar; futuro ⌘K com chip visível | header, campo com botão | ícone que expande OU campo fixo; botão no lugar do atalho (não há Cmd no toque) |
| Ao focar (busca global) | dropdown ancorado ao campo | — | **superfície full-screen** (M3) com recentes |
| Sugestões | recentes no empty (quando houver combobox) | query suggestions (fronteira combobox) | desde o 1º caractere, 6–8 máx (Algolia) |
| Feedback | contagem + live region | página de resultados com contagem | skeleton/progress em rede lenta (Algolia) |
| Zero resultados | limpar/ampliar escopo | alternativas + Fale com a engenharia | recentes + populares |

### 4.8 Acessibilidade

`role="search"` no container (um por página com nome distinto se houver múltiplas: `aria-label="Buscar propostas"`) · label visualmente oculto SEMPRE presente (`<label class="sr">Buscar propostas</label>`) · clear e botão no tab order · Esc limpa com foco permanecendo no campo · live region conforme §4.5 · lupa `aria-hidden` · contraste herdado (nenhum par novo).

### 4.9 Microcopy

Placeholder instrutivo NOMEIA o buscável: "Buscar por cliente, UC ou proposta…" (produto) · "Buscar no site…" + rótulo oculto específico (site). Contagem sempre com a query ecoada quando há espaço. Zero-resultados: tabela §4.6 — tom SEED: direto, sem culpar ("Não encontramos nada para 'x'", nunca "Sua busca falhou").

### 4.10 Código — delta sobre §3 (HTML/CSS e React)

```html
<search class="seed-search" role="search" aria-label="Buscar propostas">
  <label class="seed-visually-hidden" for="busca-prop">Buscar propostas</label>
  <div class="seed-input">
    <svg class="seed-search__icon" aria-hidden="true" viewBox="0 0 20 20" width="20" height="20"><path fill="currentColor" d="M8.5 2a6.5 6.5 0 1 0 3.94 11.66l4.2 4.2 1.42-1.42-4.2-4.2A6.5 6.5 0 0 0 8.5 2Zm0 2a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Z"/></svg>
    <input id="busca-prop" type="search" inputmode="search" enterkeyhint="search"
           autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false"
           placeholder="Buscar por cliente, UC ou proposta…">
    <button class="seed-input__btn" type="button" aria-label="Limpar busca" hidden>…✕…</button>
  </div>
  <p class="seed-search__count" role="status" aria-atomic="true"></p><!-- só a contagem, debounced -->
</search>
```

```css
.seed-search input[type="search"]::-webkit-search-cancel-button{display:none} /* clear nativo suprimido — C2 assume */
.seed-search__icon{color:var(--seed-field-help-text);flex:none}
.seed-search__count{font-size:12px;color:var(--seed-field-help-text)}
```

```tsx
export function useDebouncedStatus(count: number | null, query: string, delay = 300) {
  const [msg, setMsg] = React.useState("");
  React.useEffect(() => {
    if (count === null) return;
    const t = setTimeout(() =>
      setMsg(query ? `${count} resultado${count === 1 ? "" : "s"} para "${query}"`
                   : `${count} resultado${count === 1 ? "" : "s"}`), delay);
    return () => clearTimeout(t); /* debounce: mata anúncio obsoleto (alphagov) */
  }, [count, query, delay]);
  return msg; /* renderizar no nó FALADO (visually-hidden role="status" aria-atomic) — o nó visível recebe a contagem crua a cada tecla (D4-b) */
}

export function SearchInput(props: Omit<InputProps, "prefix"> & { landmarkLabel: string }) {
  const { landmarkLabel, ...rest } = props;
  return (
    <search role="search" aria-label={landmarkLabel}>
      <label className="sr-only" htmlFor={rest.id}>{landmarkLabel}</label>
      <Input prefix={<SearchIcon className="size-5" aria-hidden />} clearable
        type="search" enterKeyHint="search" autoComplete="off"
        autoCapitalize="off" spellCheck={false} {...rest} />
    </search>
  );
}
```

### 4.11 Fronteiras registradas

Command palette (⌘K): pattern candidato do ERP, Bloco 5/6 — shadcn `Command` (cmdk) é a base quando chegar; pré-requisito: navegação visível saudável. · Dropdown de sugestões/autocomplete: anatomia do **combobox** (item deste bloco); busca-com-sugestões = composição dos dois. · Barra com filtros estruturados + sintaxe de query: Bloco 6 / camada de produto (EUI). · Sugestões geradas por usuários exigem moderação (caso Castorama: site fora do ar por sugestões impróprias) — regra de produto. · Peça visual de empty state: Bloco 3.

### 4.12 Aplicação dos 7 testes (§1.12)

1. Container 3:1: herda §2/§3 (nenhum par novo). ✅ 2. Mecanismos: filled = clear presente; loading = spinner+texto; zero-resultados = mensagem+ações (nunca só ausência). ✅ 3. Grayscale: lupa/✕/spinner independem de matiz. ✅ 4. Par: filtro × navegacional distinguíveis pelo botão/chip. ✅ 5. Regra do um: uma live region, uma frase de contagem. ✅ 6. Estado atual: query ecoada na contagem e no zero-resultados — quem chega agora sabe O QUE foi buscado. ✅ 7. Polegar 360px: full-width, fonte 16px, clear com hit-area plena, contagem visível acima da dobra com teclado aberto; busca global expande full-screen (D7). ✅

---

## 5. Número — `estável` · validado pelo Rafael em 2026-08-02 (v0.9)

> **Consome form-field (§2) e input (§3).** Documenta o que é do número: a proibição do `type="number"`, as duas variantes (livre e stepper), a localização de decimais na base, o dinheiro em centavos inteiros e a acessibilidade de spinbutton.
>
> **Base de evidência:** 3 rodadas (2026-08-02): dossiê GOV.UK contra type=number (+ MDN/espec HTML), NN/g steppers, Setproduct, Carbon NumberInput (issue #7819) e SAP UI5 (#1637) — os dois gigantes quebrados em separador decimal, React Aria NumberField, Semrush Intergalactic · HMRC currency, uxpatterns.dev currency, Société Générale, Wise money input, Workday (84 locales), SAP Fiori Step Input · ISO 4217 minor units, Modern Treasury, dinero.js, W3C ARIA APG spinbutton (+issues #704/#797), apps de pagamento (padrão observado e descartado). Decisões E1–E9 aprovadas pelo Rafael em 2026-08-02.

### 5.1 Papel, variantes e fronteiras

| | `número-livre` | `número-stepper` |
|---|---|---|
| Emprego | Medições e valores digitados: kWh, kWp, kVA, demanda, R$, % | Quantidades pequenas de ajuste fino: módulos, parcelas, itens, licenças |
| Régua (Fiori) | valor que raramente muda ou não segue passos | ajuste em incrementos definidos é o uso dominante |
| Stepper | NÃO tem | +/− nas laterais (horizontal — NN/g: precisão do dedo no toque) |

**Nunca número para o que não é quantidade** (Fiori + espec HTML): CEP, telefone, UC, IDs → input mascarado (§2.7). Datas/horas → date picker (item futuro).

### 5.2 As 9 decisões (E1–E9, aprovadas 2026-08-02)

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| E1 | **`type="number"` PROIBIDO no DS** — sempre `type="text"` + `inputmode` numeric/decimal | Dossiê GOV.UK: não ditável (Dragon), sem rótulo no NVDA, spin button confuso; scroll acidental MUDA O VALOR; 16+ dígitos arredondam silenciosamente (corrupção de dado); grandes viram exponencial irreversível; `pattern` não funciona; espec HTML: number só p/ incrementável | type=number "porque é número" (o erro nº 1 da web em campos numéricos) |
| E2 | Duas variantes; stepper com regras Fiori/Setproduct: min/max DESABILITA botão e seta correspondente (nunca esconde), sem layout shift 9→10, precision restringe na digitação, Shift+seta = passo 2×, valor inválido PERMANECE no campo com erro | Fiori Step Input (spec mais completa achada) + NN/g (horizontal no toque, +/− como rótulos, Fitts) + Baymard (nunca apagar entrada) | Stepper universal (ruído em medição digitada); vertical minúsculo nativo; apagar valor inválido |
| E3 | **Localização na base:** aceita vírgula E ponto na digitação; exibe no locale (1.234,56); canônico com ponto decimal | Carbon #7819 (rejeita vírgula) e SAP UI5 #1637 (ponto ESVAZIA o campo): IBM e SAP quebrados em sentidos opostos — quem não resolve na base sofre em issue pública; HMRC normatiza a tolerância (símbolo, espaços, separadores; máx 2 decimais em moeda) | Separador único rígido; resolver por produto (patch infinito) |
| E4 | Motor de implementação: **React Aria NumberField** com visual SEED | Parsing por locale em 30+ idiomas e sistemas de numeração, valida a digitação no locale, clamp/step, teclado mobile automático — headless, casa com shadcn; não reinventar parser | Parser próprio (reinventar o que a Adobe mantém); Intl.NumberFormat só na exibição (não cobre parsing de entrada) |
| E5 | Herdadas explícitas: JetBrains Mono + alinhado à direita em formulário de edição/tabela (§3/Fiori/SocGen); unidade no sufixo E no rótulo (C1); larguras xs/sm (C4) | Consistência com decisões já estáveis | — |
| E6 | Moeda: **formatar SÓ no blur; valor cru no focus**; atalhos opcionais k/m expandem milhares/milhões (ERP power-user: "2k" → 2.000) | uxpatterns: reformatar durante a digitação move o cursor; SocGen: velocidade p/ quem digita valores o dia todo | Live-format a cada tecla (guerra de cursor — a armadilha BR do §2.7 em dobro) |
| E7 | **Dinheiro NUNCA em float: canônico = centavos INTEIROS + código da moeda** (ISO 4217 minor units; BRL expoente 2: R$ 12,34 → `{centavos: 1234, moeda: 'BRL'}`) | Modern Treasury (infra de pagamentos) armazena tudo em minor units; dinero.js LANÇA EXCEÇÃO em float por princípio; aritmética binária corrói centavos | Float/decimal no front (corrupção silenciosa em soma de parcelas de proposta) |
| E8 | Stepper acessível padrão APG: input `role="spinbutton"` + `aria-valuemin/max/now`; botões +/− com `tabindex="-1"` (fora do tab — redundantes com as setas — mas operáveis por mouse e TalkBack/swipe); entrada inválida: valor permanece + `aria-invalid` (nunca aria-valuenow inventado) | W3C APG spinbutton + issues #704/#797; TalkBack incrementa por swipe vertical | Botões no tab order (2 paradas mortas por campo em formulário denso); zerar no inválido |
| E9 | Caixa-registradora (centavos-first dos apps de pagamento) **DESCARTADA como padrão** — registrada como candidata futura SE algum produto SEED tiver fluxo de pagamento | Prática observável (Nubank/Venmo/Cash App) SEM guideline primária publicada; contexto é transferência rápida em teclado próprio de fintech — o nosso é valor de proposta/fatura em formulário e ERP | Adotar por moda (padrão sem função no nosso contexto) |

### 5.3 Contrato HTML e parsing

`type="text"` + `inputmode="decimal"` (com decimais) ou `"numeric"` (inteiros) + `autocapitalize/autocorrect/spellcheck` off + `enterkeyhint` conforme posição (§2.6). Parsing tolerante: `1.234,56` · `1234,56` · `1234.56` · `R$ 1.234,56` · `2k` (com atalho ativo) → todos viram canônico `1234.56` (ou `{centavos:123456, moeda:'BRL'}` em dinheiro). **Regra de desambiguação de separador único (v0.9, bug pego em auto-teste antes da entrega):** com vírgula E ponto presentes, o ÚLTIMO é o decimal; com um separador só, o padrão `1.250` / `12.345.678` (grupos exatos de 3) lê como MILHAR e `1.2` / `1.25` como decimal — sem a regra, "1.250" de kWh viraria 1,25 (erro de 1000×). Ambiguidade residual honesta: `1.250` que o usuário QUIS como decimal de 3 casas é raro em nossos domínios (kWh/R$ usam 2 casas) e o eco formatado no blur dá a chance de correção. Exibição: `Intl.NumberFormat('pt-BR')` (ou locale do produto — Workday sustenta 84; nós nascemos com pt-BR e en-US pelos clientes EUA).

### 5.4 Microcopy

Rótulo carrega a unidade: "Consumo médio mensal (kWh)" · texto auxiliar ensina o formato SÓ quando ambíguo: "Use vírgula para decimais — ex.: 1.250,75" (prevenção Animalia) · erro de faixa diz os limites: "Informe entre 1 e 120 parcelas" (regra W3C: min/max invisíveis são jogo de adivinhação).

### 5.5 Código — essência (HTML/CSS delta + React)

```html
<!-- número-livre (medição) -->
<input class="seed-field__control seed-input__control--num" type="text" inputmode="decimal"
       autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="next">

<!-- número-stepper -->
<div class="seed-stepper" role="group">
  <button type="button" tabindex="-1" aria-label="Diminuir" class="seed-stepper__btn">−</button>
  <input class="seed-field__control seed-stepper__value" type="text" inputmode="numeric"
         role="spinbutton" aria-valuemin="1" aria-valuemax="120" aria-valuenow="12">
  <button type="button" tabindex="-1" aria-label="Aumentar" class="seed-stepper__btn">+</button>
</div>
```

```css
.seed-stepper{display:flex;align-items:stretch;border:1px solid var(--seed-field-border);
  border-radius:var(--seed-field-radius);overflow:hidden;width:max-content}
.seed-stepper:focus-within{box-shadow:var(--seed-focus-ring)}
.seed-stepper__btn{width:44px;border:0;background:var(--seed-surface-sunken);
  color:var(--seed-text-primary);font-size:18px;cursor:pointer}
.seed-stepper__btn:disabled{color:var(--seed-text-disabled);cursor:not-allowed}
.seed-stepper__value{border:0;width:72px;text-align:center;font-family:var(--seed-font-mono)}
```

```tsx
/* Motor: React Aria (E4). Esqueleto de consumo com visual SEED: */
import { NumberField, Group, Input, Button, Label } from "react-aria-components";
export function SeedNumberStepper(props: { label: string; min?: number; max?: number;
  step?: number; value?: number; onChange?: (v: number) => void }) {
  return (
    <NumberField minValue={props.min} maxValue={props.max} step={props.step ?? 1}
      value={props.value} onChange={props.onChange}
      formatOptions={{ maximumFractionDigits: 0 }}> {/* locale-aware: pt-BR/en-US */}
      <Label className="text-sm font-semibold text-[var(--seed-field-label)]">{props.label}</Label>
      <Group className="flex w-max items-stretch overflow-hidden rounded-md border border-[var(--seed-field-border)] focus-within:ring-2 focus-within:ring-[var(--seed-border-focus)]">
        <Button slot="decrement" excludeFromTabOrder aria-label="Diminuir"
          className="w-11 bg-[var(--seed-surface-sunken)] disabled:text-[var(--seed-text-disabled)]">−</Button>
        <Input className="w-20 border-0 bg-transparent text-center font-mono text-sm outline-none" />
        <Button slot="increment" excludeFromTabOrder aria-label="Aumentar"
          className="w-11 bg-[var(--seed-surface-sunken)] disabled:text-[var(--seed-text-disabled)]">+</Button>
      </Group>
    </NumberField>
  );
}
/* Moeda (E6+E7): <NumberField formatOptions={{style:'currency',currency:'BRL'}}> — React Aria
   formata no blur e edita cru; canônico emitido: Math.round(value*100) centavos + 'BRL'.
   Somas/operações com dinero.js — nunca float. */
```

### 5.6 Aplicação dos 7 testes (§1.12)

1. Container 3:1: herda; stepper com borda 4.74/4.50. ✅ 2. Mecanismos: variantes diferem por ESTRUTURA (botões), não por tom; min/max desabilita (fundo+cor+cursor). ✅ 3. Grayscale: +/− e desabilitado legíveis sem matiz. ✅ 4. Par: livre × stepper inconfundíveis. ✅ 5. Regra do um: um passo por clique; um valor por campo. ✅ 6. Estado atual: valor sempre visível; limites comunicados no erro/ajuda. ✅ 7. Polegar 360px: botões 44px nas LATERAIS (horizontal NN/g), fonte 16px, teclado decimal correto. ✅

---

## 6. Textarea — `estável` · validado pelo Rafael em 2026-08-02 (v0.10)

> **Consome o form-field (§2).** Documenta o que é do textarea: auto-grow, a política de limite de caracteres (que SUPERSEDE o slot E do §2.2), os canais separados de contagem, o resize acessível e o comportamento do Enter por contexto.
>
> **Base de evidência (3 rodadas encadeadas, 2026-08-02):** R1 — GOV.UK/NHS character count (o componente com a auditoria de a11y mais profunda do gênero: 4 bugs de leitores documentados e corrigidos + auditoria DAC), Carbon text area, `field-sizing: content` (CSS 2023+, Coyier/Walsh/Willison) e a técnica grid de fallback. R2 (herda "Enter faz o quê?") — Slack/WhatsApp (Enter envia + Shift+Enter quebra, com preferência invertível no Slack), GitLab Duo (fricção real documentada de prompts multilinha), mobile universal (Return quebra, botão envia). R3 (herda "crescer até onde e quem controla?") — WCAG 2.2 SC 2.5.7 Dragging (AA legal: arrasto exige alternativa de ponteiro único; teclado NÃO basta) + issue de a11y do próprio Carbon contra o resize handle. Decisões F1–F6 aprovadas pelo Rafael em 2026-08-02.

### 6.1 Papel e fronteiras

Entrada de texto livre multilinha: observações, descrições, mensagens, justificativas. **Fronteira (F6):** editor rich-text/markdown (negrito, listas, anexos inline) NÃO é textarea — é componente futuro fora do Bloco 2 (o chat SEED evoluirá para ele; o textarea é a fundação). Uma linha só → input (§3).

### 6.2 As 6 decisões (F1–F6, aprovadas 2026-08-02)

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| F1 | **Auto-grow por padrão**: `field-sizing: content` + `min-height: calc(3lh + 2·padding)` e `max-height: calc(8lh + 2·padding)` com scroll; fallback = técnica grid (::after espelhado) p/ navegadores sem suporte. Chat: mínimo 1lh | O campo acompanha o conteúdo — elimina o conflito altura fixa × texto longo; CSS nativo 2023+ sem JS | Altura fixa com scroll interno (esconde o que a pessoa escreveu); auto-grow ilimitado ("cresce como King Kong" — página inteira vira campo) |
| F2 | **Limite de caracteres SEM truncamento** (padrão GOV.UK): campo aceita ultrapassar; exceder → estado de ERRO com contagem do excesso ("Você ultrapassou o limite em 12 caracteres"); contador aparece a partir do threshold (75% do limite) quando o limite é alto (>100) | GOV.UK/NHS: permite colar a resposta inteira e editar; `maxlength` duro TRUNCA colagem silenciosamente = perda de dado; threshold elimina ruído em limites técnicos altos | maxlength duro (o padrão da web — e a política v0.6 do nosso slot E, superseded); contador sempre visível em limite de 32k (ansiedade inútil) |
| F3 | **Canais separados de contagem**: visual atualiza a cada tecla; leitor de tela ouve na PAUSA da digitação (live region própria com debounce); limite anunciado no `aria-describedby` desde o primeiro foco; contador com id separado do texto auxiliar | Os 4 bugs corrigidos do GOV.UK: anúncio abaixo do threshold, mensagens obsoletas enfileiradas, hint lido junto da contagem; auditoria DAC: auditor cego só soube do limite após 50 caracteres | Um nó só para ver e ouvir (a origem dos 4 bugs); contagem em aria-live assertive (interrompe a própria digitação) |
| F4 | Resize manual: **vertical-only, complemento desktop**; o auto-grow É a alternativa de ponteiro único exigida | WCAG 2.2 SC 2.5.7 (AA, legal): função por arrasto precisa de alternativa sem arrasto — teclado não satisfaz ESTE critério; com auto-grow, arrastar vira conveniência, nunca função exclusiva; Carbon tem issue aberta por não ter alternativa | Resize horizontal (quebra layout — Carbon proíbe); handle como único controle de altura (falha 2.5.7); remover o handle (perde a conveniência sem ganho) |
| F5 | **Enter por contexto**: formulário → Enter quebra linha (nativo), envio SÓ por botão; chat desktop → Enter envia + Shift+Enter quebra, COM preferência invertível (Enter quebra / Ctrl+Enter envia); mobile → Return quebra + botão envia SEMPRE | Padrão universal Slack/WhatsApp + a inversão que o Slack oferece e o GitLab documenta como necessidade real (prompts longos enviados incompletos); no toque não há convenção de envio por tecla | Enter-envia hardcoded (pune quem escreve longo); Enter-envia em formulário (submit acidental no meio da observação) |
| F6 | Fronteira rich-text registrada (§6.1) | Escopo do bloco é entrada de texto plano; editor é outra classe de componente | Markdown "básico" embutido (meio-editor: pior dos dois mundos) |

### 6.3 Contador — anatomia e política (a versão canônica; o slot E do §2.2 aponta para cá)

```
[textarea auto-grow]
Texto auxiliar (id próprio)                     87/400   ← visual: cada tecla; some abaixo do threshold
[região aria-live=polite, visualmente oculta]            ← falado: na pausa; frase completa ("Restam 313 caracteres")
```

Estados: normal (cinza `--seed-field-help-text`) → **excedido**: contador em `--seed-field-error-text` + campo `aria-invalid` + mensagem de erro do form-field com o excesso. Formatos de microcopy: "87/400" (visual) · "Restam N caracteres" / "Você ultrapassou o limite em N caracteres" (falado e erro) · erro de submit: "A observação deve ter no máximo 400 caracteres" (padrão NHS: específico, com o limite dito).

### 6.4 Contrato HTML

`<textarea>` nativo sempre (nunca div contenteditable) · SEM `maxlength` (F2) · `rows="3"` como fallback do min-height · `autocapitalize="on" autocorrect="on" spellcheck="true"` (texto humano — o oposto dos identificadores §2.6) · `enterkeyhint` AUSENTE em formulário (Enter quebra linha; "enviar" na tecla seria mentira) e `enterkeyhint="send"` apenas no chat mobile se o produto optar por envio pela tecla.

### 6.5 Código — essência

```css
.seed-textarea{
  field-sizing: content;                 /* auto-grow nativo (F1) */
  min-height: calc(3lh + 20px);
  max-height: calc(8lh + 20px);
  overflow-y: auto;
  resize: vertical;                      /* complemento desktop (F4) */
  padding: 10px 12px;
}
@supports not (field-sizing: content){  /* fallback: técnica grid ::after
  — wrapper .seed-grow com data-value espelhado; estilos idênticos nos dois filhos */ }
```

```tsx
export function Textarea({ limit, ...props }: React.TextareaHTMLAttributes<HTMLTextAreaElement> & { limit?: number }) {
  const { id, helpId, errId } = useField();
  const [len, setLen] = React.useState(String(props.defaultValue ?? "").length);
  const spoken = useDebouncedStatus(limit ? Math.abs(limit - len) : null,
    "", 1000); /* reaproveita o hook da busca §4.10: frase falada só na pausa (F3) */
  const over = limit ? len > limit : false;
  const show = limit ? len >= limit * 0.75 : false; /* threshold (F2) */
  return (<>
    <textarea id={id} rows={3} aria-invalid={over || undefined}
      aria-describedby={cn(over && errId, helpId, limit && `${id}-limit`) || undefined}
      className="seed-textarea w-full rounded-md border border-[var(--seed-field-border)] bg-[var(--seed-field-bg)] text-sm text-[var(--seed-field-value)]"
      onChange={e => { setLen(e.target.value.length); props.onChange?.(e); }} {...props} />
    {limit && <span id={`${id}-limit`} className="sr-only">Limite de {limit} caracteres.</span>}
    {show && <span aria-hidden className={cn("self-end font-mono text-xs",
      over ? "text-[var(--seed-field-error-text)]" : "text-[var(--seed-field-help-text)]")}>{len}/{limit}</span>}
    {limit && <span role="status" className="sr-only">{spoken && (over
      ? `Você ultrapassou o limite em ${len - limit} caracteres`
      : len >= limit * 0.75 ? `Restam ${limit - len} caracteres` : "")}</span>}
  </>);
}
```

### 6.6 Aplicação dos 7 testes (§1.12)

1. Container 3:1: herda §2. ✅ 2. Mecanismos: excedido = cor do contador + aria-invalid + mensagem (nunca só o número vermelho). ✅ 3. Grayscale: excesso legível pelo texto do erro, não pelo matiz. ✅ 4. Par: 87/400 normal × 412/400 excedido distinguíveis a 1s. ✅ 5. Regra do um: um contador, uma live region, uma frase. ✅ 6. Estado atual: quem chega vê quanto foi usado e se estourou, sem digitar. ✅ 7. Polegar 360px: auto-grow respeita o teclado aberto (max-height evita campo-página), fonte 16px, contador visível abaixo sem colidir com o teclado (GOV.UK: abaixo reaparece no scroll natural). ✅

---

## 7. Select — `estável` · validado pelo Rafael em 2026-08-02 (v0.11, mock do custom corrigido na validação)

> **Consome o form-field (§2).** Abre a família de ESCOLHA do bloco (select → combobox → checkbox → radio → switch) e carrega a régua que governa todos.
>
> **Base de evidência (3 rodadas encadeadas, 2026-08-02):** R1 (canon) — GOV.UK ("último recurso" em serviço público, com pesquisa), NN/g dropdowns (limiares <5 radios / >15 combobox), USWDS (<7), M3 (6), Carbon (3), CMS.gov (janela 7–15 + regras satélites), Parliament/Bristol/Pelican. R2 (herda: nativo × custom + mobile) — a própria stack: shadcn mantém DOIS selects (custom Radix e Native Select), comunidade documenta o trade-off e a regra de não misturar. R3 (herda: o custom é seguro o bastante?) — **Sarah Higley (Microsoft): teste de usabilidade real com leitores — recriar o `<select>` nativo é impossível (semântica por plataforma, teclado inconsistente, mobile inteiramente diferente); ARIA 1.2 foi o padrão menos bugado (= o que o Radix implementa)**; Angular Material recomenda nativo p/ a11y. Decisões G1–G7 aprovadas pelo Rafael em 2026-08-02.

### 7.1 A régua unificada da família de escolha (G1 — governa 5 componentes)

| Opções | Componente | Fonte da faixa |
|---|---|---|
| 1 escolha binária de AÇÃO com efeito imediato | switch (§8) | Fluent: switch = ação |
| 1 escolha binária de STATUS confirmada no submit (consentimento, "li e aceito") | checkbox único (§10) — nunca switch | Fluent: checkbox = status · emenda v0.12 |
| 2–6, uma escolha | radio (§9) | NN/g <5 · M3 6 · USWDS <7 → adotamos 6 |
| 2–6, várias escolhas | checkbox (§10) | CMS/NN/g: multi em dropdown é proibido (G5) |
| 7–15 (teto ~20 no ERP denso — Fiori) | **select** | janela CMS 7–15 |
| >15, ou qualquer volume com busca necessária | combobox (§11) | NN/g >15 · Fiori 20–200 |

Supersede: esta régua ATUALIZA a linha de volume do §3.1 (que citava só o Fiori). No formulário público vale o espírito GOV.UK (G7): antes de aceitar um select, reformule a pergunta para caber em radios; no ERP denso, o select curto é ferramenta legítima de densidade — o "último recurso" nasceu do one-thing-per-page de governo, não de toolbars com 12 filtros.

### 7.2 As 7 decisões (G1–G7, aprovadas 2026-08-02)

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| G1 | Régua unificada (§7.1) | 6 fontes harmonizadas; uma régua, cinco componentes, zero sobreposição | Réguas por componente (a do §3.1 já divergia da NN/g) |
| G2 | **Nativo estilizado = PADRÃO** (site, público, TODO toque); **custom Radix permitido no produto desktop** quando a opção exige conteúdo rico (ícone de status, metadado). NUNCA misturar os dois no mesmo formulário; híbrido POR PLATAFORMA (nativo no toque, custom no desktop) é recomendado — coerência com o dispositivo, não mistura | Higley: recriar o nativo é impossível; Angular Material: nativo = melhor a11y; no toque o nativo abre o picker do OS (roda iOS/lista Android) com zero JS; Radix implementa ARIA 1.2 — o padrão MENOS bugado no teste da Higley, o que torna o custom aceitável onde o conteúdo justifica | Custom universal (assume o risco Higley sem necessidade); nativo universal (opção rica do ERP não cabe em `<option>` — só texto) |
| G3 | Rótulo sempre; placeholder "Selecione…" (option `disabled selected hidden`) SÓ sem default sensato; **default inteligente** quando a maioria escolhe a mesma opção | CMS: nunca substituir o rótulo pelo placeholder; default correto poupa a interação inteira | "Selecione…" universal (esconde o default útil); primeiro item como default acidental |
| G4 | Selecionar NUNCA muda contexto (não navega, não submete, não recarrega) | CMS + WCAG 3.2.2 On Input | Select-como-navegação (o anti-padrão clássico) |
| G5 | Multi-seleção em dropdown PROIBIDA → checkboxes; `optgroup`/grupos só com categorias reais | CMS: usuários não entendem multi em dropdown | `<select multiple>` (ctrl+clique: ninguém descobre) |
| G6 | Fronteiras formais armadas: binário→switch · 2–6→radio · >15/busca→combobox (que herdará a semântica ARIA 1.2 testada) | A família nasce coordenada, não em disputa | Cada item redescobrir seus limites |
| G7 | "Último recurso" contextualizado por superfície (§7.1) | O contexto da pesquisa GOV.UK é serviço público | Importar o dogma sem o contexto (mataria o select no ERP sem ganho) |

### 7.3 Anatomia e estados (variante nativa — a padrão)

`<select>` nativo com `appearance:none` + chevron SVG próprio (o nativo não estiliza a seta) sobre a moldura do form-field: mesma altura (44px md), mesma borda (4.74:1), mesmos 9 estados NO TRIGGER (o painel de opções aberto é do OS/browser — não estilizamos, e isso é a FEATURE: é o painel que o usuário do dispositivo conhece). Dark: o trigger segue os tokens; o painel nativo segue o OS — documentado como comportamento esperado, não bug. Variante custom (Radix): trigger idêntico; painel usa `surface-overlay` + `shadow-overlay` (tokens de elevação v1.1); opção rica = ícone 20px + texto + metadado opcional em `--seed-field-help-text`.

### 7.4 Contrato HTML

`<select>` + `<option>` com `value` estável (nunca o texto como valor) · placeholder: `<option value="" disabled selected hidden>Selecione…</option>` · grupos: `<optgroup label>` · `autocomplete` quando aplicável (`country`, `address-level1`…) · form-field completo em volta (rótulo/ajuda/erro herdados).

### 7.5 Microcopy

Rótulo nomeia a coisa ("Concessionária"), placeholder instrui a ação ("Selecione…") — nunca o contrário. Opções em sentence case, ordenadas por LÓGICA do domínio (frequência de uso > alfabética > alfabética como fallback); nunca "Escolha uma opção acima" como erro — o erro do form-field diz o quê: "Selecione a concessionária".

### 7.6 Código — essência

```css
.seed-select{appearance:none;-webkit-appearance:none;
  height:var(--seed-field-height-md);padding:0 40px 0 12px;width:100%;
  font-family:var(--seed-font-sans);font-size:14px;color:var(--seed-field-value);
  background:var(--seed-field-bg);border:1px solid var(--seed-field-border);
  border-radius:var(--seed-field-radius);cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16'%3E%3Cpath fill='%234D606C' d='M4.3 6.3 8 10l3.7-3.7 1 1L8 12 3.3 7.3l1-1Z'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 12px center}
.seed-select:hover{border-color:var(--seed-field-border-hover)}
.seed-select:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
.seed-select:invalid{color:var(--seed-field-placeholder)} /* placeholder-option selecionada */
[data-theme="dark"] .seed-select{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' width='16'%3E%3Cpath fill='%23ACBECA' d='M4.3 6.3 8 10l3.7-3.7 1 1L8 12 3.3 7.3l1-1Z'/%3E%3C/svg%3E")}
```

```tsx
/* Nativo (padrão — G2): */
export function NativeSelect(props: React.SelectHTMLAttributes<HTMLSelectElement> & { placeholder?: string }) {
  const { id, helpId, errId, invalid } = useField();
  return (
    <select id={id} aria-invalid={invalid || undefined}
      aria-describedby={cn(invalid && errId, helpId) || undefined}
      className="seed-select" {...props}>
      {props.placeholder && <option value="" disabled hidden>{props.placeholder}</option>}
      {props.children}
    </select>
  );
}
/* Custom (produto desktop, opção rica): shadcn <Select> (Radix, ARIA 1.2) com
   SelectTrigger reestilizado nos mesmos tokens do trigger nativo e
   SelectContent em surface-overlay + shadow-overlay. Regra G2: nunca os dois
   no mesmo formulário; no toque, o custom degrada para o nativo. */
```

### 7.7 Aplicação dos 7 testes (§1.12)

1. Container 3:1: trigger herda (4.74/4.50); chevron 4.66:1 (cinza-700 sobre branco, decorativo com redundância do próprio trigger). ✅ 2. Mecanismos: placeholder ≠ valor por COR (4.74 vs 13.86 — mesmo mecanismo do input); aberto/fechado pelo painel, não por tom. ✅ 3. Grayscale: chevron + moldura carregam o papel sem matiz. ✅ 4. Par: nativo × custom lado a lado no preview — triggers idênticos (é o objetivo: o usuário não deve notar a diferença fechado). ✅ 5. Regra do um: uma escolha por select (multi é proibido — G5). ✅ 6. Estado atual: valor selecionado sempre visível no trigger; placeholder denuncia o não-preenchido pela cor. ✅ 7. Polegar 360px: nativo abre o picker do OS — a melhor experiência de toque possível por definição; trigger 44px full-width. ✅

---

## 8. Switch — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas, 2026-08-02):** R1 — NN/g toggles (efeito imediato, nunca com submit), UX Movement (sinal só no ON; system state × contextual state), UXTweak. R2 — M3 ("the preferred way to adjust settings on mobile"), iOS HIG, padrões de pending async. R3 — APG switch role, Roselli (mixed inválido), TalkBack/checkbox+role. Decisões K1–K6 conferidas pelo Rafael no consolidado de 2026-08-02.

### 8.1 Papel, fronteiras e a exceção declarada

Binário de AÇÃO com efeito IMEDIATO: liga e a coisa acontece (notificação ativada, módulo habilitado). **Exceção formal ao G4 (§7.2):** o G4 proíbe mudança de contexto ao selecionar VALOR; o switch é AÇÃO por contrato — o efeito imediato é a identidade (NN/g), e por isso ele NUNCA aparece em formulário com botão salvar (lá o binário é checkbox — régua §7.1 emendada). Filtros de busca/lista são estado contextual → checkbox, não switch.

### 8.2 Decisões K1–K6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| K1 | Efeito imediato sempre; proibido em form com submit | NN/g: misturar = usuário não sabe se salvou | Switch "que espera o salvar" (é um checkbox fantasiado) |
| K2 | Async: estado `pending` com spinner NO próprio switch; falha → REVERTE + toast com erro | Silêncio ou delay sem sinal = loop de reenvio | Confirmar otimista sem reverter (mente o estado) |
| K3 | Sinal de cor só no ON (`--seed-switch-on-bg` = action-primary); OFF neutro (cinza), nunca vermelho; nunca "ON/OFF" escrito no trilho | UX Movement: off colorido vira comando ambíguo; herda os 3 sinais do §1.5.1: cor + posição do thumb + movimento | Verde/vermelho (semáforo em setting é alarme falso) |
| K4 | Rótulo: o que o ON faz, verbo no infinitivo ou substantivo, ≤3 palavras ("Notificações por e-mail", "Modo escuro"), e NUNCA muda com o estado | APG: rótulo que muda quebra a referência do leitor de tela | "Ativado"/"Desativado" como rótulo dinâmico |
| K5 | Implementação: `<input type="checkbox" role="switch">` nativo; Space alterna; `mixed` é INVÁLIDO no role (vira false) | Roselli/APG; checked nativo funciona em todo AT; TalkBack anuncia certo | Div+ARIA do zero (reinventa foco/teclado) |
| K6 | Mobile/app: switch é o controle padrão de settings (M3/iOS); grupos de switch usam fieldset+legend (§2.6b) | Convenção de plataforma | Checkbox em lista de settings de app (estranho ao OS) |

### 8.3 Anatomia, tokens e estados

Trilho 44×24 + thumb 20 (hit area 44×44). Tokens de componente — **todos medidos em 2026-08-26, e a medição mudou dois valores da redação anterior**: `--seed-switch-off-bg` (**light cinza-500 `#788F9D` = 3,38:1 · dark cinza-600 `#617683` = 3,92:1**), `--seed-switch-on-bg` (= `--seed-action-primary`: turquesa-600 `#098475` **4,60:1** light / turquesa-**300** `#66D1C2` 10,16:1 dark), `--seed-switch-thumb` = o thumb DESLIGADO (branco / cinza-100) e **`--seed-switch-thumb-on`** = o thumb LIGADO (= `--seed-text-on-action-primary`: branco / `#00352F`), foco = anel padrão.

> ⚠ **O QUE A MEDIÇÃO ACHOU, e por que a redação anterior não podia achar.** A linha antiga mandava *"3:1 sobre a página: medir na produção do preview"*. A medição foi feita e deu **1,91** para o cinza-300 — reprovando a própria cláusula. Mas o defeito maior é que **medir contra a página é medir metade do componente**: o thumb é BRANCO, e branco sobre cinza-300 dá os mesmos 1,91 — *o thumb sumia dentro do trilho*, e a posição do thumb é o único canal que separa ON de OFF (K3: nunca "ON/OFF" escrito no trilho). **Cinza-500 conserta os dois pares de uma vez** (3,38 contra a página e 3,38 contra o thumb) e é o primeiro degrau da escala que atende — o cinza-400 ainda reprova, com 2,53. Nenhuma cor nova entrou (§14.1).
> ⭐⭐ **E no tema ESCURO quem reprovava era o estado LIGADO, não o desligado** — thumb cinza-100 sobre o trilho ligado dá **2,25**. Ninguém tinha visto porque a cláusula só mandava medir o OFF. A saída também não precisou de cor nova: *"o que vai sobre a superfície de ação"* já é um par medido e canonizado, o `--seed-text-on-action-primary`, e o thumb de um switch ligado é exatamente isso — **7,39:1**. No tema claro os dois thumbs coincidem em branco, então **a regra é uma só, expressa em dois tokens que colapsam no claro**.
> ⚠ **Dois erros de NOME nesta mesma linha, consertados junto:** ela apontava para `--seed-action-primary-bg`, que é o **token fantasma** da FF-P2/FF-P3 — removido do acervo —, e dizia "turquesa-400 sobre dark" quando `action-primary` no escuro é `#66D1C2` = turquesa-**300**. ⭐ *Spec que aponta para nome morto passa em toda guarda de valor, porque não há valor nenhum para conferir.* Estados: off · on · off-hover · on-hover · focus · pending (spinner 14px no thumb, `aria-busy`) · disabled · readonly (§2: exibe sem permitir — borda, não opacidade). Movimento do thumb: 120ms `--seed-ease-out`; `prefers-reduced-motion`: sem animação, posição muda seca.

### 8.4 Microcopy e live region

Rótulo à esquerda, switch à direita (padrão settings). Ajuda opcional abaixo do rótulo explica a CONSEQUÊNCIA ("Você receberá o resumo semanal"). Async: live region §1.13 anuncia "Notificações ativadas" / falha `role="alert"`: "Não foi possível ativar. Tente novamente." — e o switch VOLTA.

### 8.5 Código — essência

```html
<label class="seed-switch-row">
  <span class="seed-switch-label">Notificações por e-mail</span>
  <input type="checkbox" role="switch" class="seed-switch">
</label>
```
```css
.seed-switch{appearance:none;width:44px;height:24px;border-radius:999px;background:var(--seed-switch-off-bg);position:relative;cursor:pointer;transition:background .12s}
.seed-switch::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:999px;background:var(--seed-switch-thumb);transition:left .12s var(--seed-ease-out)}
.seed-switch:checked{background:var(--seed-switch-on-bg)}
.seed-switch:checked::after{left:22px}
.seed-switch:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
@media (prefers-reduced-motion:reduce){.seed-switch,.seed-switch::after{transition:none}}
```

### 8.6 Os 7 testes

1. Trilho off 3:1 sobre página (medir no preview). 2. Estado por cor + POSIÇÃO do thumb + movimento (3 sinais §1.5.1 — o teste WebEx que originou a regra). 3. Grayscale: posição do thumb carrega sozinha. 4. Par off × on distinguível a 1s (posição). 5. Um switch = uma ação. 6. Estado visível sem interação. 7. 44px de alvo; row inteira clicável. ✅

## 9. Radio — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas):** R1 — GOV.UK radios + NN/g default selection (conflito), USWDS (irreversibilidade; "None of the above"). R2 — radio cards (Setproduct "variante é fantasia, não componente", USWDS tile: não misturar tile+default). R3 — APG radio group (roving tabindex; setas movem E selecionam), GOV.UK conditional reveal (aria-expanded removido após pesquisa). J1–J6 conferidas no consolidado.

### 9.1 Papel e a régua

2–6 opções mutuamente exclusivas, todas visíveis (§7.1). Sim/Não = radio de 2 — nunca checkbox único ambíguo (J6). Grupo = fieldset+legend (§2.6b), sempre em coluna (GOV.UK: lado a lado confunde a associação rótulo-círculo).

### 9.2 Decisões J1–J6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| J1 | **Default por contexto**: configuração/produto com opção majoritária SEGURA → pré-selecionado (caso Modalidade A4 §7); pergunta factual sobre o usuário (formulário público) → SEM default + opção "Não sei"/"Nenhuma" quando fizer sentido | Radio não volta ao vazio (USWDS): default em pergunta factual FORJA resposta; default em setting poupa a interação (NN/g) — o conflito histórico NN/g×GOV.UK se resolve por contexto | Default universal (forja dados); nunca-default universal (pune settings) |
| J2 | Ordem por lógica do domínio; alfabética como fallback; frequência com cautela (viés de ancoragem) | GOV.UK | Ordem aleatória "pra ser neutro" |
| J3 | **Radio card = variante visual do MESMO grupo**: input real DENTRO, círculo VISÍVEL, borda 2px `--seed-action-primary-bg` no selecionado; nunca misturar cards e radios simples no mesmo grupo; card e default não se misturam | Setproduct: pular a semântica porque "parece card" quebra teclado e leitor; USWDS tile | Card sem círculo (vira botão ambíguo — ver teste 4) |
| J4 | Nativo `<input type="radio">` sempre que possível; custom exige roving tabindex COMPLETO (grupo = 1 tab stop; setas movem e selecionam; Tab entra no marcado) | APG; o nativo dá tudo de graça | Divs com tabindex em cada item (quebra o modelo de grupo) |
| J5 | Conditional reveal (campo que aparece ao marcar): SEM `aria-expanded` no radio (GOV.UK removeu — não suportado no role); o revelado vem IMEDIATAMENTE após na ordem do DOM | Pesquisa GOV.UK/NHS | aria-expanded (anúncio quebrado em AT) |
| J6 | Sim/Não = radio de 2 | Checkbox único não distingue "não" de "não respondi" | Checkbox "Sim" solitário |

### 9.3 Anatomia e tokens

Círculo 24px (hit 44), borda 2px `--seed-field-border`; marcado: anel interno 12px `--seed-action-primary-bg`. Card: padding 16, radius 8, borda 1px → 2px turquesa no selecionado + `--seed-surface-raised`. Item inteiro clicável (label envolve). Estados: idle · hover (borda hover) · checked · focus (anel) · disabled · erro (borda do GRUPO não muda — a mensagem do form-field aponta; GOV.UK).

### 9.4 Microcopy

Legend é a pergunta ("Qual solução você procura?"). Erro: "Selecione [a coisa]" — "Selecione a solução desejada". Opção de escape sempre por último ("Ainda não sei").

### 9.5 Código — essência

```html
<fieldset class="seed-field">
  <legend class="seed-field__label">Qual solução você procura?</legend>
  <label class="seed-radio-row"><input type="radio" name="sol" class="seed-radio"><span>Energia solar</span></label>
  <label class="seed-radio-row"><input type="radio" name="sol" class="seed-radio"><span>Gerador (GMG)</span></label>
</fieldset>
```
```css
.seed-radio{appearance:none;width:24px;height:24px;border:2px solid var(--seed-field-border);border-radius:999px;flex:none;display:grid;place-items:center;cursor:pointer}
.seed-radio::after{content:"";width:12px;height:12px;border-radius:999px;background:transparent;transition:background .1s}
.seed-radio:checked{border-color:var(--seed-action-primary-bg)}
.seed-radio:checked::after{background:var(--seed-action-primary-bg)}
.seed-radio:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
.seed-radio-row{display:flex;gap:12px;align-items:center;min-height:44px;cursor:pointer}
```

### 9.6 Os 7 testes

1. Círculo 2px 4.74:1. 2. Marcado = anel interno (forma) + borda (cor) — 2 sinais. 3. Grayscale ok (forma). 4. **Par crítico: radio card selecionado × botão outline** — o círculo visível dentro do card é o distintivo obrigatório (J3). 5. Um grupo = uma pergunta. 6. Marcado visível sem interação. 7. Linha 44px, coluna única no 360. ✅

## 10. Checkbox — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas):** R1 — GOV.UK/NHS checkboxes (exclusive "None", divisor "ou"), Carbon (indeterminate). R2 — Fluent (checkbox=STATUS × switch=AÇÃO — a fronteira da família), M3 parent-child, MUI. R3 — GDPR art. 7/LGPD art. 8 (pré-marcado inválido; granularidade por finalidade). I1–I6 conferidas no consolidado.

### 10.1 Papel

2–6 escolhas independentes visíveis (várias permitidas) OU binário de STATUS confirmado no submit (consentimento, "li e aceito" — régua §7.1 emendada). Grupo = fieldset+legend + hint "Selecione todas que se aplicam" (I1).

### 10.2 Decisões I1–I6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| I1 | fieldset+legend + hint padrão | GOV.UK; leitores leem a legend com cada item | Grupo sem pergunta explícita |
| I2 | Opção exclusiva "Nenhuma das anteriores" separada por divisor "ou"; marcá-la DESMARCA as outras (e vice-versa), com anúncio na live region | GOV.UK/NHS: sem ela, "nenhuma" é indistinguível de "não respondi" | Validação que rejeita conflito depois (deixa errar pra corrigir) |
| I3 | Indeterminate (`aria-checked="mixed"` via JS `el.indeterminate`) SÓ como pai selecionar-tudo (tabelas/árvores — ponte Bloco 6); nunca em formulário simples | Carbon/M3 parent-child | Indeterminate decorativo |
| I4 | Fronteira Fluent: STATUS/commit adiado → checkbox · AÇÃO imediata → switch (§8); filtros de lista → checkbox | Organiza a família inteira | "Switch porque é bonito" |
| I5 | **Consentimento: NUNCA pré-marcado, e GRANULAR — um checkbox por finalidade** (aceitar termos ≠ receber marketing); vale para toda captação SEED | GDPR art. 7 / LGPD art. 8: consentimento livre, informado, inequívoco; pré-marcado é juridicamente inválido | Opt-out pré-marcado (ilegal); checkbox único "aceito tudo" |
| I6 | Item inteiro clicável; caixa 24px, hit 44 | GOV.UK/WCAG 2.5.8 | Clicável só no quadradinho |

### 10.3 Anatomia e tokens

Caixa 24px radius 4, borda 2px; marcado: fundo `--seed-action-primary-bg` + check branco (SVG stroke 2.5); indeterminate: traço horizontal. Estados = §9.3. Check aparece com scale 90→100% 100ms (reduced-motion: seco).

### 10.4 Microcopy

Legend pergunta; consentimento SEMPRE nomeia a finalidade e o canal: "Aceito receber conteúdos sobre energia solar por e-mail" — nunca "Aceito os termos e comunicações".

### 10.5 Código — essência

```css
.seed-check{appearance:none;width:24px;height:24px;border:2px solid var(--seed-field-border);border-radius:4px;flex:none;display:grid;place-items:center;cursor:pointer;transition:background .1s,border-color .1s}
.seed-check:checked{background:var(--seed-action-primary-bg);border-color:var(--seed-action-primary-bg)}
.seed-check:checked::after{content:"";width:12px;height:7px;border-left:2.5px solid #fff;border-bottom:2.5px solid #fff;transform:rotate(-45deg) translateY(-1px)}
.seed-check:indeterminate{background:var(--seed-action-primary-bg);border-color:var(--seed-action-primary-bg)}
.seed-check:indeterminate::after{content:"";width:12px;height:2.5px;background:#fff}
.seed-check:focus-visible{outline:none;box-shadow:var(--seed-focus-ring)}
```

### 10.6 Os 7 testes

1. Borda 4.74:1; fundo marcado 4.54:1 com check branco. 2. Marcado = fundo + forma do check. 3. Grayscale ok. 4. Par checkbox × radio: quadrado × círculo (forma). 5. Um checkbox = uma afirmação. 6. Visível sem interação. 7. 44px, exclusive "ou" legível no 360. ✅

## 11. Combobox — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas):** R1 — GOV.UK/alphagov accessible-autocomplete (venceu teste com usuários da Orange: instrução inicial + contagem anunciada), APG combobox ARIA 1.2. R2 — React Aria/shadcn Command (async, shouldFilter off em server-side), React Spectrum (tray mobile). R3 — Higley part 2 (filtro que estreita confunde — mitigar com contagem; ARIA 1.2 menos bugado), Baymard country selector (typos/sinônimos). H1–H7 conferidas no consolidado.

### 11.1 Papel

Escolha em lista >15 itens ou qualquer volume com busca (§7.1). É o SELECT que cresceu: mesma semântica de valor, mais um input de filtro. Caso canônico SEED: concessionária, municípios (H6). NÃO é a busca do §4 (que encontra conteúdo); combobox ESCOLHE um valor de lista fechada (ou criável — H5).

### 11.2 Decisões H1–H7

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| H1 | Comportamento de referência: GOV.UK accessible-autocomplete — instrução inicial no describedby ("Digite para filtrar…") + contagem de resultados na live region (§1.13) | Único padrão VALIDADO em teste com usuários de AT (Orange) | Padrões nunca testados com gente real |
| H2 | ARIA 1.2: `role="combobox"` no INPUT + `aria-activedescendant` (foco DOM fica no input); botão de abrir fora do Tab, presente para o toque | Higley: 1.2 menos bugado; APG | ARIA 1.1 (pior no teste); foco que pula pra lista (perde edição) |
| H3 | Mobile: tray/superfície plena com input dentro (herda D7/M3) | Orange: "a lista não é percebida no mobile" — o tray força a percepção | Dropdown ancorado sob teclado virtual |
| H4 | Async: debounce 300–500ms + "Carregando…" anunciado + filtro do cliente DESLIGADO quando o servidor filtra + skeleton | shadcn shouldFilter; dupla-filtragem some com resultados válidos | Spinner mudo |
| H5 | Somente-lista (padrão: valida contra a lista, restaura ao sair) × criável: opção explícita **"Criar 'Nova Venécia'"** no fim — só onde valor novo é legítimo | Baymard: typo não pode virar cadastro fantasma | Criação implícita no blur |
| H6 | Progressive enhancement do select: mesmos tokens de trigger, mesma moldura | O usuário não deve renotar a troca ao crescer a lista | Visual próprio (família desagregada) |
| H7 | Multi: chips removíveis SEMPRE visíveis no campo + contagem anunciada; ≤6 → checkboxes (§10); `<select multiple>` proibido (G5 mantido — o problema era a INVISIBILIDADE, os chips resolvem) | Higley multiselect; resolução do conflito G5×H7 da análise cruzada | Multi escondido em "3 selecionados" |

### 11.3 Anatomia

Trigger = input do §3 com chevron; painel = `surface-overlay` + `shadow-overlay`, opção ativa `--seed-surface-sunken` + `aria-selected`; contagem oculta na live region; zero resultados herda D9 ("Nenhum resultado para 'x'" + ação de recuperação — na captação: "Não achou? Fale com a engenharia"). Filtro: contém + normalização de acentos (herda D-família); realce do trecho em `<mark>` com peso, não cor sozinha.

### 11.4 Contrato

`role="combobox" aria-expanded aria-controls aria-activedescendant` no input; `role="listbox"`/`option` no painel; setas navegam, Enter seleciona, Esc fecha e restaura; `autocomplete="off"` no input (o browser não pode competir com o painel).

**H8 — Tab confirma e avança (decisão do Rafael, 2026-08-02):** Tab com uma opção DESTACADA confirma a seleção e segue para o próximo campo — é o comportamento `confirmOnBlur` default do accessible-autocomplete do GOV.UK (nossa referência H1), validado em teste com usuários; poupa um Enter em toda passagem de formulário. **Salvaguarda obrigatória:** o destaque só existe por ação ativa do usuário (setas) ou correspondência exata única — NUNCA auto-destacar a primeira opção da lista (quem digita "vit" e aperta Tab só para pular o campo levaria "Vitória (ES)" sem pedir — captura acidental, o risco documentado do padrão). Supersede: a linha "Tab fecha e mantém" do APG genérico fica substituída pela variante testada do GOV.UK.

### 11.5 Os 7 testes

1. Herda §3 + painel overlay 3:1. 2. Ativa = fundo + aria-selected (nunca só cor). 3. Grayscale: realce em bold. 4. Par combobox × busca §4: chevron × lupa (ícone distingue papel). 5. Uma lista, uma contagem. 6. Valor escolhido visível no input. 7. Tray no toque; 16px. ✅

## 12. Slider — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas):** R1 — NN/g (aproximado basta; precisão = façanha motora; coarse+fine linked), Baymard (filtro numérico SEMPRE com campos; dual: track click não seta). R2 — M3 (discreto/ticks), Morningstar/Semrush (range+inputs). R3 — APG slider/multithumb, W3C (range REAL por baixo: TalkBack), WCAG 2.5.7 (herda F4), Angular Material (track 3:1). L1–L6 conferidas no consolidado.

### 12.1 Papel e a regra do par

Valor aproximado em faixa conhecida. **Padrão SEED: slider SEMPRE pareado com o campo número do §5** (mesmo componente, stepper opcional) — coarse no trilho, fine no campo (L1). Valor preciso exigido → só o §5, sem slider. Casos: filtros de faixa no ERP (potência, valor); simulador do site ("Quanto você paga de luz?") — aproximado e lúdico, pareado.

### 12.2 Decisões L1–L6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| L1 | Par obrigatório slider+número | NN/g/Baymard; o par resolve 2.5.7 por definição | Slider sozinho (precisão impossível) |
| L1-b (v0.13) | O campo pareado consome o PARSER CANÔNICO do §5 (parseFlex/E3) no blur — nunca parser próprio | Validação executada 2026-08-03: o preview chamava helper inexistente (`parseNum`) e o caminho "digitar o valor exato" quebrava com ReferenceError; um parser só = uma semântica de vírgula/milhar | Helper local por componente |
| L2 | Alternativas 2.5.7: clique no trilho posiciona (single) + teclado (setas passo, PageUp/Down 10×, Home/End) + campo pareado | F4 abriu o precedente; teclado NÃO basta sozinho pro 2.5.7 | Só arrasto |
| L3 | `input type="range"` REAL sempre — estilizado (princípio G2, "nativo primeiro") | W3C: swipe do TalkBack só funciona em range real | Div-slider com ARIA (quebra toque assistivo) |
| L4 | Dual thumb (faixa): 2 ranges sobrepostos, ambos no Tab, ordem constante; clique no trilho NÃO seta — instrui ("arraste as alças ou digite nos campos"); min/max interdependentes com folga mínima de 1 passo | APG multithumb; Baymard: clique ambíguo em dual frustra | Um thumb "esperto" que rouba o clique |
| L5 | `aria-valuetext` humanizado ("R$ 1.500", "75 kWp"); trilho ativo 3:1 sobre o inativo | Angular Material; leitor fala a unidade | "1500" seco |
| L6 | Ticks só discreto ≤10 passos | M3 | Régua milimetrada decorativa |

### 12.3 Anatomia e tokens

Trilho 6px radius 999 (**`--seed-slider-track` = cinza-500 `#788F9D` light / cinza-600 `#617683` dark**; ativo `--seed-action-primary`); thumb 24px branco com borda 2px turquesa, hit 44; foco = anel.

> ⚠ **Medido em 2026-08-26, junto com o switch, e a redação anterior reprovava:** cinza-300 dá **1,91:1** contra a página. Passa a cinza-500, **3,38:1** — o mesmo degrau e a mesma razão do `--seed-switch-off-bg`.
> ⭐ **E aqui a medição encontrou um limite que o switch não tinha: nenhum degrau atende os dois pares.** O trilho vazio precisa ficar longe do branco da página *e* longe do turquesa do preenchido, e o turquesa-600 mora no meio da escala — cinza-300 dá 2,40 contra o preenchido, cinza-500 dá 1,36. **A decisão não é escolher o menos pior; é ver o que cada par carrega.** O valor tem canal **textual** garantido pela linha seguinte desta mesma spec (*"valor corrente visível SEMPRE"*) e canal de **forma** no thumb de 24 px com borda. A divisão preenchido/vazio é o **terceiro** canal: fica **decorativa declarada, com o número escrito — 1,36:1 light / 2,59:1 dark** — que é o mesmo tratamento que o DS já dá a `card-border` e a `divider-color`. *Exigir 3:1 entre preenchido e vazio seria criar cláusula que o canon nunca teve, e cláusula nova é decisão do Rafael, não da sessão.* Rótulo do form-field + valor corrente visível SEMPRE (no campo pareado — nunca só em tooltip de hover, que não existe no toque).

### 12.4 Os 7 testes

1. Trilho ativo×inativo 3:1 medido; thumb 3:1 sobre ambos. 2. Posição + campo numérico (2 canais do valor). 3. Grayscale: trilho ativo mais ESCURO, não só matiz. 4. Par single × dual: nº de alças. 5. Um slider = um valor (ou uma faixa). 6. Valor legível sem interação (campo). 7. Thumb 24 hit 44; no toque o campo pareado é o caminho fino. ✅

## 13. Date picker — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas):** R1 — GOV.UK/NHS/NSW date input (memoráveis digitadas; NUNCA auto-advance; ano 4 dígitos), CMS (single-input flexível formata no blur), Scottish (calendar = progressive enhancement). R2 — shadcn Calendar/react-day-picker (locale pt-BR, range 2 meses, presets — ReUI), GitLab (digitar OU escolher, sempre). R3 — NN/g date input (picker frustra data distante: "165 cliques até 1990"), Baymard touch keyboards, icapps (picker inadequado p/ nascimento), Smashing birthday. M1–M6 conferidas no consolidado.

### 13.1 A régua por natureza da data (M1 — resolve o conflito 3-campos × máscara × picker)

| Natureza | Interface | Porquê |
|---|---|---|
| **Memorável/conhecida** (nascimento, emissão de documento) | Campo ÚNICO mascarado `dd/mm/aaaa` (registry B5), entrada flexível ("1/8/26" → "01/08/2026" no blur — herda E6) | A pessoa JÁ SABE a data: digitar vence qualquer navegação (NN/g/icapps); CMS valida o single-input |
| **A escolher** (agendar visita técnica; futuro <1 ano; dia da semana importa) | Input digitável + botão calendário (progressive enhancement) | Scottish/GitLab: os DOIS caminhos sempre; o calendário responde "que dia cai?" |
| **Faixa** (relatórios ERP "de/até") | Range picker 2 meses + presets ("Últimos 30 dias", "Este mês") + campos digitáveis | ReUI/NN/g Expedia; presets matam a navegação repetitiva |

**Alternativa descartada com contexto (M2):** os 3 campos separados do GOV.UK resolvem a ambiguidade dd/mm × mm/dd do mundo anglófono — o dd/mm/aaaa universal brasileiro não tem essa ambiguidade na mesma escala, e a máscara B5 já testou. Plano B documentado: se pesquisa com usuários SEED mostrar erro de formato, os 3 campos entram (com type=text inputmode=numeric — E1 vale lá também).

### 13.2 Decisões M3–M6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| M3 | Digitar NUNCA deixa de funcionar; o calendário jamais é o único caminho; grid com teclado APG completo (setas dia/semana, PageUp mês, Shift+PageUp ano) | GitLab/NHS/Scottish/APG dialog | Picker-only (prende quem digita rápido) |
| M4 | Calendário: react-day-picker (shadcn Calendar) locale pt-BR, semana começando domingo (BR), min/max, datas desabilitadas COM explicação no describedby | A11y de fábrica + a stack Lovable | Calendário próprio |
| M5 | Mobile: bottom-sheet (herda D7/H3); site simples pode usar `type="date"` nativo (princípio G2 — o picker do OS é excelente no toque) | GitLab GlFormDate | Grid desktop espremido no toque |
| M6 | Defaults: agendamento abre no mês ATUAL; nascimento NUNCA tem default; `autocomplete="bday"` quando aplicável (WCAG 1.3.5) | NN/g | "Hoje" pré-preenchido em nascimento |

### 13.2b Regra da máscara flexível (bug pego pelo Rafael na validação do preview, 2026-08-02)

A máscara de data ACEITA o separador digitado pelo usuário ("/", e tolerante a "." e "-") como delimitador de segmento — é isso que torna "1/8/26" possível: a barra declara "dia de um dígito, próximo segmento". Máscara que só aceita dígitos corridos e injeta barras por posição fixa QUEBRA a promessa da entrada flexível (o usuário fica preso a 01082026). Regras: segmentos limitados a 2/2/4; barra automática só entra ao DIGITAR (nunca ao apagar — senão o backspace briga com a máscara); completude (zeros e século) acontece no blur, nunca durante a digitação. Esta regra vale para TODA máscara segmentada do registry B5 (data, telefone com DDD digitado com espaço, etc.): separador digitado = intenção do usuário, sempre respeitado.

### 13.3 Microcopy

Ajuda com exemplo REAL: "Por exemplo: 15/03/2026" (NHS; nunca "dd/mm/aaaa" cru como placeholder — some ao digitar). Erros específicos: inexistente ("31/02 não existe — confira o dia"), fora da faixa ("Escolha uma data a partir de hoje"), formato ("Digite a data como 15/03/2026").

### 13.4 Os 7 testes

1. Grid: dia selecionado fundo turquesa-600 4.54:1; hoje = anel, não cor sozinha. 2. Selecionado = fundo + aria-selected; desabilitado = riscado + aria-disabled com motivo. 3. Grayscale: hoje (anel) × selecionado (fundo). 4. Par hoje × selecionado a 1s. 5. Um campo = uma data (range = dois valores NOMEADOS de/até). 6. Valor no input sempre. 7. Bottom-sheet, células 44px. ✅

## 14. Upload — `estável` · validado pelo Rafael em 2026-08-03 (preview v0.13 + suite de 63 testes)

> **Base (3 rodadas):** R1 — GOV.UK file upload (input nativo + "Choose file or drop"; JS como enhancement), Scottish (progress + confirmação), Queensland/NSW (erros; WCAG 2.2 redundant entry: erro NÃO força re-upload do que subiu), VA (variantes), Home Office (erros que sugerem correção). R2 — SaaS patterns (constraints ANTES; progresso real POR arquivo; retry individual; item visível com remover), NN/g drag-drop, react-dropzone/Uppy. R3 — W3C ARIA25 (progressbar + live region), Uploadcare/Carbon issues (anúncios nomeando arquivo), 4.1.3 Status Messages, segurança MIME server-side. N1–N7 conferidas no consolidado.

### 14.1 Papel

Anexar arquivo(s): fatura de energia na captação (o caso SEED nº 1), documentos no ERP, fotos de visita técnica no mobile.

### 14.2 Decisões N1–N7

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| N1 | Base: `<input type="file">` + rótulo; dropzone é ENHANCEMENT com o botão "Escolher arquivo" SEMPRE dentro — a alternativa 2.5.7 vem de fábrica; dropzone com Enter/Space abrindo o picker | GOV.UK; herda F4/L2 | Dropzone-only (falha 2.5.7 e teclado) |
| N2 | Restrições declaradas ANTES e persistentes no help: "PDF, JPG ou PNG · até 10 MB" (nunca só na rejeição); `accept` correto → celular oferece câmera/galeria; drag omitido graciosamente no toque | SaaS/NSW; Baymard teclados | Descobrir o limite no erro |
| N3 | Arquivo aceito = item visível (nome + tamanho + remover 44px); progresso REAL por arquivo (`role="progressbar"` + live region ARIA25); **retry INDIVIDUAL sem re-enviar o que já subiu** (WCAG 2.2 redundant entry); cancelar durante | NSW/W3C; "usuários não re-tentam, abandonam" | Barra única do lote; recomeço total |
| N4 | Canais §1.13: `role="status"` para enviado/concluído NOMEANDO o arquivo ("proposta.pdf enviado"); `role="alert"` para falha | Uploadcare/Carbon issue: silêncio pós-clique é o bug nº 1 | "Sucesso" sem dizer de quê |
| N5 | Erros específicos com correção: tipo ("fatura.docx não é aceito — envie PDF, JPG ou PNG"), tamanho ("fatura.pdf tem 14 MB — o limite é 10 MB"), rede ("Falha na conexão — tente de novo", com botão) | GOV.UK/QLD/Home Office | "Erro no upload" |
| N6 | Segurança/LGPD: accept/MIME no cliente = UX; validação REAL server-side (MIME de conteúdo, nome sanitizado, limite no servidor); fatura = dado pessoal → finalidade declarada no help da captação ("Usamos sua fatura só para dimensionar o sistema") | R3; LGPD §2.8 minimização | Confiar na extensão |
| N7 | Multi: `<ul>` com estado POR item; máximo declarado ("até 5 arquivos"); "Limpar tudo" | Carbon issue/VA | Contador opaco "3 arquivos" |

### 14.3 Anatomia e tokens

Dropzone: borda 2px TRACEJADA `--seed-field-border` radius 8, padding 24, ícone 24 + "Arraste a fatura aqui ou **Escolher arquivo**" (botão secundário §1 real); estado `dragover`: borda sólida `--seed-action-primary-bg` + `--seed-surface-sunken`. Item: linha com ícone do tipo, nome (truncado ao meio se longo), tamanho em mono, progresso 4px, remover/repetir 44px.

### 14.4 Os 7 testes

1. Tracejado 4.74:1; dragover muda borda E fundo. 2. Estados do item por ícone + texto + cor. 3. Grayscale: tracejado→sólido carrega o dragover. 4. Par item enviando × item com erro (barra × ícone+texto). 5. Uma live region para a fila toda (frases nomeadas). 6. Fila visível com estado por item. 7. Toque: dropzone vira botão grande; câmera via accept; remover 44px. ✅

---

## 15. Alerta — `estável` · validado pelo Rafael em 2026-08-04 (preview v0.15 + suite de 55 testes)

> **Base (3 rodadas + verificação primária, 2026-08-04):** R1 — GOV.UK notification banner (fonte primária lida na íntegra), NHS, Carbon notification/callout, USWDS alert (+ testes de acessibilidade publicados), Polaris banner, Atlassian section message/designing messages, Spectrum in-line alert, NN/g (indicators/validations/notifications · error-message guidelines · scoring rubric), APG alert/alertdialog. R2 — shadcn/Radix, GitLab Pajamas, Fluent 2 MessageBar. R3 — WCAG 4.1.3 Status Messages, SAP Fiori message strip/message handling, gov.br DS Message (vocabulário PT-BR), Baymard (confirmação de ação em teste com usuários). Decisões T1–T5 e O1–O5 conferidas pelo Rafael no consolidado de 2026-08-04.

### 15.0 Decisões transversais T1–T5 — taxonomia de severidade (governam §15–§17 e, adiante, badge/chip)

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| T1 | **4 severidades** com os pares já medidos nos tokens v1.2: **informação** (azul: azul-800/azul-50 = 5.79 AA — errata: o tokens v1.2 rotula este par como azul-700; a medição da validação executada mostra que 5.79 pertence ao azul-800) · **sucesso** (turquesa/verde: êxito É verde da marca, decisão v1.1) · **atenção** (dourado: dourado-800/dourado-50 = 9.05 AAA) · **erro** (vermelho interface-only: vermelho-700/vermelho-50 = 6.57 AA). Tokens semânticos: `feedback-{info\|success\|warning\|danger}-{bg\|text\|border\|solid}` | gov.br DS define exatamente estes 4 estados; nenhum sistema pesquisado usa menos | 5ª severidade "neutra" (Spectrum) — o cinza neutro vive como variante da informação, sem token novo |
| T2 | **Vocabulário PT-BR: warning = "atenção"** (informação · sucesso · atenção · erro). Componente = *alerta*; severidade nunca se chama "alerta" | gov.br chama o warning de "Alerta" — colisão direta com o nome do componente ("alerta de tipo alerta" contaminaria spec, código e conversa com cliente). "Atenção" preserva a semântica do C5 | Seguir gov.br literal (colisão); "aviso" (ambíguo entre info e warning) |
| T3 | **Ícone por severidade obrigatório** (2º dos 3 sinais §1.5.1), alinhado ao gov.br: check-circle (sucesso) · exclamation-triangle (atenção) · times-circle (erro) · info-circle (informação). Mesmo mapa valerá para badge/chip | gov.br DS; USWDS testa explicitamente que cor sozinha não carrega significado; rubrica NN/g exige ≥3 indicadores redundantes | Ícone opcional nas variantes semânticas |
| T4 | **Nota 1.13-b** (ver §1.13): mensagens discretas usam o próprio nó visível como live region; container com role existe ANTES do conteúdo | Ver nota; sem ela a suite reprovaria o padrão universal correto | Canal separado também para discretas |
| T5 | **Mapa polite/assertive:** `role="status"` para informação, sucesso e atenção; `role="alert"` SÓ para erro | Leitura canônica do WCAG 4.1.3 (polite para não-urgente, alert para crítico); atenção por definição não bloqueia (C5), então não justifica interromper | Fluent 2 (assertive em tudo exceto info — a própria doc admite que interrompe demais) |
| T5-b | **Títulos de severidade padronizados no serviço inteiro:** "Sucesso", "Atenção", "Erro" — sempre os mesmos, nunca variações criativas | GOV.UK: mesmo heading em todos os banners verdes = WCAG 3.2.4 (identificação consistente) + 1.4.1 (título carrega o significado sem depender de cor) | Título livre por tela |

### 15.1 Papel e fronteiras

Mensagem **contextual e persistente em página**, ligada a uma seção, objeto ou formulário (equivale a inline notification/Carbon, section message/Atlassian, message strip/Fiori, in-line alert/Spectrum). Não é sobreposição (toast §17) nem condição de sistema (banner §16). **Fronteira dura com o B4:** alerta NUNCA substitui o error summary de formulário, nem coexiste com ele (GOV.UK: nunca banner + error summary na mesma página — só o error summary).

### 15.2 Decisões O1–O5

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| O1 | Anatomia: ícone semântico + título opcional (T5-b) + corpo ≤2 linhas (mais que isso → link "ver detalhes") + até 1 ação (botão ghost/link §1) + fechar opcional. Largura fluida do container pai | Carbon: ação ghost, mensagem longa vira "View more"; largura acompanha o container | Alerta-card flutuante (confunde com toast) |
| O2 | Posição: imediatamente ACIMA da seção/objeto a que se refere. Nunca alerta órfão no topo para problema local | Atlassian: section message acima da área afetada; Fiori: message strip na área do objeto; NN/g: visibilidade = perto da fonte do problema | Tudo no topo da página (banner disfarçado) |
| O3 | Dismissível: informação e sucesso SIM; atenção e erro SÓ se a condição não exigir resolução | Polaris: default sempre dismissível; não dismissível quando contém informação crítica ou passo necessário. (GOV.UK declara pesquisa em aberto sobre dismissibilidade — esta é decisão de projeto, documentada como tal) | Tudo dismissível (fechar erro que continua existindo) |
| O4 | ARIA: presente no carregamento = SEM role dinâmico (conteúdo estático no fluxo de leitura). INJETADO após ação = T5. Nunca esconder/mostrar via CSS — remover do DOM | USWDS: não esconder visualmente para exibir depois (AT antigas percebem o oculto); nota 1.13-b para o injetado | `role="alert"` em tudo (spam assertivo) |
| O5 | Severidade dimensionada pelo impacto: alerta para o que NÃO bloqueia o fluxo; erro que exige decisão/resolução imediata → modal (Bloco 4), nunca alerta | NN/g error guidelines: toast/banner para interação mínima, modal reservado a erro severo; atenção nunca usa estilo de erro (C5 + rubrica NN/g) | Alerta vermelho para tudo ("wall of red" dessensibiliza) |

### 15.3 Anatomia e tokens

Container: fundo `--seed-feedback-{sev}-bg` (stop 50) + borda 1px `--seed-feedback-{sev}-border` + **barra lateral esquerda 4px** na cor sólida (`-solid`, stop 600/500) — 3º sinal de forma além de cor+ícone, e o que sobrevive em grayscale junto do ícone; radius 8; padding 12/16. Ícone 20px na cor `-text`. Texto `-text` (sucesso/erro = stop 700, atenção/informação = stop 800 — pares medidos na T1 e re-medidos na validação executada; a barra `-solid` da informação usa o stop 700, não o 600: 600 sobre azul-50 dá 2.99 e reprova o 3:1 por um centésimo). Dark: fundo vira o stop 900 da rampa com texto no stop 200 (pares medidos no preview). Fechar: 44×44 de alvo, ícone times, `aria-label="Fechar"`.

### 15.4 Microcopy

Título curto padronizado (T5-b) + corpo que diz O QUE e O QUE FAZER: "Atenção — 3 UCs sem fatura anexada. Anexe para calcular o rateio." Tom SEED: direto, sem culpar o usuário (NN/g), sem "sucesso!" exclamativo.

### 15.5 Código — essência

```html
<div class="seed-alert seed-alert--warning" data-severity="warning">
  <svg class="seed-alert__icon" aria-hidden="true"><!-- exclamation-triangle --></svg>
  <div class="seed-alert__body">
    <strong class="seed-alert__title">Atenção</strong>
    <p>3 UCs sem fatura anexada. Anexe para calcular o rateio.</p>
  </div>
  <button class="seed-alert__close" aria-label="Fechar">×</button>
</div>
```
```css
.seed-alert{display:flex;gap:12px;padding:12px 16px;border-radius:8px;border:1px solid;border-left-width:4px}
.seed-alert--info{background:var(--seed-feedback-info-bg);border-color:var(--seed-feedback-info-border);border-left-color:var(--seed-feedback-info-solid);color:var(--seed-feedback-info-text)}
/* success/warning/danger idem, trocando o grupo */
```
Injeção dinâmica (nota 1.13-b): o container-slot com `role="status"` (ou `role="alert"` para erro, valor SEMPRE explícito — regra do §2.6) já existe no DOM; o JS insere o markup DENTRO dele.

### 15.6 Os 7 testes

1. Borda do container sobre a página ≥3:1 (medir no preview). 2. Severidade por cor + ícone + título (3 sinais). 3. Grayscale: ícone + barra lateral 4px carregam sozinhos. 4. Par atenção × erro distinguível a 1s (triângulo × círculo-x, dourado × vermelho). 5. Um alerta por seção; mensagens combinam antes de multiplicar. 6. Estado visível sem interação. 7. 360px: empilha ícone/texto sem truncar; fechar 44px. *(Verificado: suite v0.15, aprovado.)*

---

## 16. Banner — `estável` · validado pelo Rafael em 2026-08-04 (preview v0.15 + suite; P2 em dois níveis, corrigido na verificação primária)

> **Base:** a mesma do §15, com peso em GOV.UK notification banner (primária: posicionamento pré-h1, largura de conteúdo, mecânica de foco type=success), USWDS site alert (cor em emergência), Atlassian banner (system-level, empurra conteúdo), NN/g banner blindness (via GOV.UK/NHS).

### 16.1 Papel — DOIS níveis, nunca misturados (P2 revisado)

A verificação primária expôs que "banner" no mercado são dois componentes: o GOV.UK notification banner tem **largura do conteúdo da página e vive imediatamente antes do h1**; o banner Atlassian/USWDS site alert é **full-width no topo da viewport**. A SEED formaliza os dois níveis num componente só com variante estrutural:

- **Banner de sistema** (`--nivel-sistema`): full-width, topo da viewport, EMPURRA o conteúdo (nunca sobrepõe — cobriria header/nav do ERP). Só para condição do sistema/serviço inteiro: indisponibilidade, manutenção, emergência. Raro por definição.
- **Banner de página** (`--nivel-pagina`): largura do conteúdo, imediatamente antes do h1. Para notícia relevante à página inteira ou **desfecho pós-redirect** ("proposta enviada" → volta pra listagem) quando não cabe página de confirmação.

Descartado: banner de página como variante do alerta (§15) — a mecânica ARIA é diferente (P4) e a posição é fixa (pré-h1), não contextual.

### 16.2 Decisões P1–P5

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| P1 | UM banner por vez, por nível; se concorrem, exibe só o de maior severidade (erro > atenção > informação > sucesso); mensagens combináveis combinam antes | GOV.UK primária: evitar mais de um — combinar ou mostrar só a maior prioridade; NN/g banner blindness: uso excessivo agrava a cegueira | Empilhar banners (mata a atenção) |
| P2 | Dois níveis (§16.1); nível de sistema nunca sobrepõe conteúdo | GOV.UK (página) + Atlassian/USWDS (sistema) — ver §16.1 | Full-width único para tudo (era o P2 original, corrigido) |
| P3 | Banner é landmark: `role="region"` + `aria-labelledby` no título — navegável por leitores de tela; persiste até resolução/dispensa; dispensa não reaparece na sessão | GOV.UK primária (markup exato); USWDS: aria-label(ledby) coloca o banner no menu de landmarks | Live region permanente (banner não é anúncio contínuo) |
| P4 | **Banner de página pós-redirect (sucesso/erro de ação): `role="alert"` + foco movido ao banner via JS no load** (`tabindex="-1"` + `focus()`), com opção de desligar; banner informativo: `role="region"`, sem foco. Remover o banner de sucesso ao navegar | GOV.UK primária: type=success → role="alert" + JS move o foco no load (existe `disableAutoFocus`); garante que AT perceba o desfecho | Foco em todo banner (rouba o início da leitura); autofocus HTML (dispara antes do AT montar) |
| P5 | Cor: mesmo emergência NÃO usa vermelho massivo full-width — a posição já carrega o peso; severidades T1; segue 70/20/10 da marca | USWDS: vermelho/laranja pesado em emergência produz medo/pânico; posição no topo já dá peso suficiente; vermelho é interface-only e nunca massa (tokens v1.2) | Banner vermelho sólido |

### 16.3 Anatomia e tokens

Mesma pele do alerta (§15.3: fundo -bg, borda, barra 4px, ícone, título T5-b) com as diferenças estruturais: nível sistema é full-bleed (barra lateral vira **barra superior 4px**, padding lateral acompanha o gutter do layout + `--seed-safe-*` em app); nível página respeita a largura da coluna de conteúdo (GOV.UK: se o conteúdo ocupa dois terços, o banner ocupa dois terços). Fechar 44×44 quando dismissível (P3).

### 16.4 Os 7 testes

1. Borda/contraste medidos. 2. 3 sinais. 3. Grayscale: barra + ícone. 4. Sistema × página distinguíveis a 1s (full-bleed × coluna). 5. Um por nível (P1). 6. Visível sem interação. 7. 360px: os dois níveis convergem visualmente (a coluna É a tela), texto quebra sem truncar. *(Verificado: suite v0.15, aprovado.)*

---

## 17. Toast — `estável` · validado pelo Rafael em 2026-08-04 (preview v0.15 + suite; teste de fogo do §1.13/1.13-b — aprovado)

> **Base:** R1 — Carbon (5s, empilhamento, top-right), M1/M2/M3 oficiais (janela 4–10s, uma ação, acima da bottom-nav, "pode permanecer até o usuário agir"), NN/g (caso dos 5 minutos), EUI (10s, ação atual vs histórica), GitLab Pajamas. R2 — React Aria/Spectrum toast (landmark "Notificações", F6, foco restaurado, fila com prioridade), Radix (região + hotkey), Sonner/shadcn (+ auditoria WCAG 2026: 4s default viola 2.2.1), react-toastify (alt+t), Base Web (sem duração se houver ação), Angular Material, regra de leitura Byrne-Haber (5s+1s/120 palavras → mínimo aceitável 6s). R3 — WCAG 2.2.1 Timing Adjustable + 4.1.3, GitLab a11y issue (ação crítica dentro de role=status viola 2.2.1), SAP Fiori message toast (inferior-central), Ionic (swipe orientado pela posição, positionAnchor), bug documentado iOS (toast na base coberto pelo teclado), Mobbin (base = alcance do polegar), Baymard (confirmação só em label pequeno é perdida por subgrupo, especialmente mobile). Decisões Q1–Q7 conferidas pelo Rafael em 2026-08-04.

### 17.1 Papel e a régua de uso (Q1, emendado com Baymard)

Confirmação **efêmera e sobreposta** do desfecho da ação ATUAL (salvou, enviou, copiou). **O toast é o SEGUNDO sinal do desfecho; o primeiro é a própria UI refletindo o novo estado** (linha aparece na tabela, botão vira "salvo") — Baymard mediu usuários perdendo confirmações que existiam só como aviso pequeno. NUNCA: portador único de erro que exige ação (NN/g: usuária esperou 5 minutos por um erro que sumiu em 5s), erro de validação (é B4), informação histórica, saudação de sessão (EUI), marketing.

### 17.2 Decisões Q2–Q7

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| Q2 | Sucesso/informação = auto-dismiss; atenção = duração estendida; **erro = SEM auto-dismiss** (persiste até fechar). Erro em toast só para falha de operação assíncrona sem página-contexto (herda K2 e N4) | Auditoria shadcn: crítico = duration Infinity + dismissal manual; WCAG 2.2.1; M3: "pode permanecer até o usuário agir" | Erro auto-dismiss (perda de informação crítica) |
| Q3 | Duração: **8s default** (sucesso/informação) · **10s atenção** · erro persiste. Pausa em hover E em foco E com página oculta (`visibilitychange`); timer reinicia ao retomar | Janela oficial Material 4–10s; regra de leitura: mínimo aceitável 6s; 4s do Sonner reprova 2.2.1; EUI registrou como bug pausar só em mouseover sem foco | 4–5s (rápido demais); 10s fixo (atrasa a fila no uso denso do ERP) |
| Q4 | Máx. UMA ação; nunca "Fechar" como ação; nunca o único caminho para a função; **toast com ação não auto-dismissa**. Undo é o caso canônico | M3: uma ação, duas = dialog, snackbar nunca é o único acesso; Angular Material: sem duração quando há ação (AT precisa navegar até ela); GitLab: ação crítica em role=status viola 2.2.1 — a função existe na página sem limite | Ação com timer correndo |
| Q5 | Container = landmark `role="region"` + `aria-label="Notificações"`, alcançável por **F6** (atalho de foco); foco NUNCA roubado ao aparecer; ao fechar o último toast focado, foco RESTAURADO ao ponto anterior | React Aria (F6/Shift+F6 + restauração) e Radix (região rotulada + hotkey) convergem; sem landmark, usuário de teclado não alcança a ação antes do timeout | Toast fora de landmark; autofocus no toast |
| Q6 | Empilhamento: **máx. 3 visíveis**, mais novo no topo, excedente em FILA | Carbon (empilha, novo no topo) + React Aria (fila com limite); operações em lote do ERP perderiam confirmações no modelo um-por-vez | Material "um por vez" (descartado com porquê: lote ERP); ilimitado (avalanche) |
| Q7 | Posição: **desktop inferior-direita** · **mobile inferior-central**, acima de bottom-nav/teclado (offset via `visualViewport` + `--seed-safe-bottom`), swipe-para-baixo dismissa | EUI/GitLab: inferior-direita em produto denso (não cobre header/nav/cabeçalho de tabela — perfil dataviz SEED); Fiori: inferior-central; M2: acima da bottom-nav; Mobbin: base = polegar; bug iOS documentado do teclado motiva a regra do visualViewport (vira teste da suite); Ionic: swipe orientado pela posição | Top-right (Carbon — colide com a área de ações de página do ERP); top mobile (longe do polegar) |

### 17.3 ARIA e live region (o teste de fogo)

Segue T5 + nota 1.13-b: a REGIÃO (`role="region"`) existe no DOM desde o load; cada toast é inserido dentro dela já populado, com `role="status"` (sucesso/informação/atenção) ou `role="alert"` (erro) — valor sempre explícito (§2.6). `aria-atomic="true"` no toast. O botão fechar fica FORA do texto anunciado (aria-label próprio). Frase completa e específica nomeando o objeto ("Proposta #482 enviada", nunca "Sucesso").

### 17.4 Anatomia e tokens

Card 320–400px (mobile: `min(100vw - 32px, 400px)`) sobre `--seed-surface-overlay` + `--seed-shadow-overlay` (elevação de sobreposição, tokens v1.2 — no dark a borda sutil assume a separação); barra lateral 4px + ícone na severidade (T1/T3); texto `--seed-text-primary`; ação = botão ghost; fechar 44×44. Entrada/saída 160ms `--seed-ease-out`; `prefers-reduced-motion`: aparece/some seco. Gap de pilha 8px.

### 17.5 Microcopy

Passado para desfecho ("Proposta #482 enviada"), gerúndio para processo em andamento — regra herdada do Fluent, mas o toast de progresso ("Enviando…") fica DECIDIDO NO SUB-BLOCO B junto do progress (ponta solta declarada no consolidado). Sem "com sucesso" (implícito — EUI writing).

### 17.6 Os 7 testes

1. Card sobre página ≥3:1 pela borda/sombra (medir). 2. Severidade por cor + ícone + texto. 3. Grayscale: ícone + barra carregam. 4. Sucesso × erro a 1s (persistência + ícone). 5. Uma região para todos os toasts; um anúncio por toast. 6. Fila visível (contador "+2" quando há excedente). 7. 360px: inferior-central acima do teclado simulado; swipe; fechar 44px. *(Verificado: suite v0.15, aprovado.)*

---

## 18. Spinner — `estável` · validado pelo Rafael em 2026-08-04 (2 ciclos de crítica → emendas R3-b/R3-c; suite v0.17)

> **Base (3 rodadas, 2026-08-04):** R1 — NN/g (progress indicators: <1s nada, loop 2–10s; skeleton 101), Carbon (loading lg/sm, inline loading 4 estados, um indicador por vez), Smashing. R2 — React Aria useProgressBar (spinner = progressbar circular), shadcn/Radix, EUI (herança). R3 — SAP Fiori busy indication (delay padrão de 1s em todos os controles; bloquear o mínimo), EC Europa (sempre com mensagem; ≤1 lg por página; nunca <1s), issue de a11y do UI5 (equivalente textual anunciado), aria-busy (escopo = a região que muda; segura anúncios parciais).

### 18.1 A RÉGUA DE ESPERA (transversal do sub-bloco B — governa §18–§20; análoga à régua do §7.1)

| Espera | Indicador |
|---|---|
| **< 1s** | **NADA** — indicador que pisca é ruído (NN/g; Fiori: delay padrão 1s; EC) |
| **1–10s, fração desconhecida** | **Spinner** para ação/processo local · **skeleton** para carga de ESTRUTURA de conteúdo |
| **> 10s ou fração conhecida** | **Progress determinate** + texto de status; pode nascer indeterminada e converter (Carbon/m2) |

**R3-b (emenda da validação visual do Rafael, 2026-08-04 — supersede parcial do R3): a ENTRADA do indicador tem TRÊS regimes, não um.** A crítica "o delay de 1s vai parecer travado" foi medida contra a evidência e está correta em dois dos três casos — o R3 original colapsava situações distintas:

1. **Ação do usuário** (clicar salvar, alternar switch): reconhecimento IMEDIATO ≤100ms — o próprio controle entra em loading (spinner sm do §1), SEM delay. Fonte: os 3 limites de Nielsen (0,1s = reação instantânea obrigatória; o limite de 1s pressupõe que o clique JÁ foi reconhecido); Carbon inline loading mostra o estado ativo imediatamente após a ação.
2. **Carga inicial de tela/região VAZIA:** skeleton IMEDIATO, sem delay — não há UI antiga para segurar a percepção; 1s de branco lê como travamento. Fonte: Carbon usa skeleton "on initial page load"; o delay do Fiori pressupõe explicitamente "mostrar a UI por um segundo" — ou seja, UI existente.
3. **Refresh sobre conteúdo EXISTENTE** (tabela recarregando, filtro aplicado): delay de 1s antes do indicador (Fiori/EC/NN/g) — a UI antiga permanece visível e utilizável; flash de overlay em resposta rápida seria pior que a espera.

**R3-c (2ª crítica do Rafael na mesma validação — "o refresh lento ainda demora"): os regimes COMPÕEM, não se excluem.** O regime 3 quase nunca existe sozinho: refresh de região no ERP quase sempre nasce de uma AÇÃO do usuário (filtro, recarregar) — e então o regime 1 SE APLICA JUNTO: o controle disparador reconhece em ≤100ms (loading do §1) e o indicador de REGIÃO entra só se a espera passar de 1s. O vácuo percebido não era o delay — era refresh sem ack no controle, que a régua agora PROÍBE: **toda espera do regime 3 disparada por ação exige ack imediato no controle disparador.** Refresh iniciado pelo sistema (dado chegando por conexão viva) é o único caso de delay sem ack — e ali o usuário não pediu nada, então não há clique órfão. Nielsen fecha: o limite de 1s sem indicador pressupõe o clique reconhecido em ≤0,1s. O delay de 1s fica parametrizado (`--seed-wait-delay`, default 1000ms) e será revisitado com telemetria real do ERP na Fase 4 — pendência declarada, não decisão aberta.

Complemento mantido: uma vez exibido, permanência mínima de ~400ms (anti-flash de saída — inferência de engenharia declarada). Descartado: delay único de 1s para tudo (o R3 original — reprovado na validação visual); regime 3 sem ack no controle (reprovado na 2ª crítica).

### 18.2 Decisões R1–R6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| R1 | Dois tamanhos com papéis: **lg** para seção/página com overlay semitransparente BLOQUEANTE; **sm** inline (célula, linha) — e o spinner do botão É o loading do §1 (herda) | Carbon: lg = takeover com overlay; inline desabilita os controles envolvidos contra ação repetida | Tamanho único |
| R2 | NUNCA nu: sempre frase junto ("Carregando propostas…"); máx. UM lg por página | EC: sempre com mensagem; não mais de um por página (salvo sm em componentes); UI5 a11y: equivalente textual anunciado | Spinner mudo |
| R3 | Delay 1s cancelável — **restrito ao regime 3 (refresh sobre conteúdo existente); ver emenda R3-b no §18.1** para ação do usuário (imediato no controle) e carga inicial (skeleton imediato) | Fiori/EC/NN/g, contextualizados pela emenda | Exibição imediata em refresh (flash); delay único para tudo (R3 original, superseded pela R3-b) |
| R4 | ARIA: SVG decorativo (`aria-hidden`); a região que carrega recebe `aria-busy="true"` (segura anúncios parciais) + live region `role="status"` pré-existente (1.13-b) anuncia UMA frase no início e UMA no fim ("Propostas carregadas") | aria-busy: um anúncio coerente ao voltar a false; sem live region + indicador visível, ninguém percebe nada | Anunciar cada mutação; role no SVG |
| R5 | `prefers-reduced-motion`: sem rotação — indicador estático + a frase carrega o sinal | WCAG 2.3.3; padrão §0.5/§8.3 | Rotação sempre |
| R6 | UM sinal de espera por região: nunca spinner sobre skeleton nem dois indicadores no mesmo alvo | Carbon: evitar múltiplos simultâneos; redundância é ruído | Empilhar sinais |

### 18.3 Anatomia e tokens

lg 32px / sm 16px; traço 3px/2px em `--seed-action-primary-bg` sobre trilho `--seed-border-subtle`; rotação 900ms linear contínua; overlay do lg: página com véu (`--seed-surface-page` a 60%) + spinner centrado + frase abaixo. Frase em `--seed-text-secondary`.

### 18.4 Os 7 testes

1. Traço sobre trilho ≥3:1 (medir). 2. Espera sinalizada por movimento + frase (nunca só cor). 3. Grayscale: movimento + frase carregam. 4. lg×sm distinguíveis pelo escopo (overlay × inline). 5. Um lg por página; uma live region por região. 6. Estado visível: a frase diz O QUE carrega. 7. 360px: overlay cobre a viewport com frase legível; sm cabe na linha de tabela densa. *(Verificado: suite v0.17, aprovado.)*

---

## 19. Skeleton — `estável` · validado pelo Rafael em 2026-08-04 (suite v0.17)

> **Base:** R1 — NN/g Skeleton Screens 101 (só carga de página; nunca processo; frame-display proibido; <10s), Carbon (1–3s; só container/dados; nunca toast/modal/dropdown; carga progressiva em lotes; vai-e-vem reservado ao skeleton). R2 — shadcn Skeleton, padrões CSS shimmer. R3 — Fiori placeholder loading, **evidência conflitante tratada em S4**: Viget 2017 (skeleton PIOR que spinner em percepção no teste mobile deles) × estudo acadêmico 2018 (skeleton MELHOR em velocidade percebida e navegação) × Chung (a FORMA do motion modula: onda lenta E→D percebida mais curta que pulso; amostras pequenas = pista, não prova).

### 19.1 Decisões S1–S6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| S1 | Papel RESTRITO: carga inicial de ESTRUTURA de conteúdo (tabela, card, lista, dashboard — o dia a dia do ERP). NUNCA processo (upload/submit/conversão → progress §20); NUNCA toast/modal/dropdown/overflow (dentro de modal pode; o modal não) | NN/g; Carbon | Skeleton universal |
| S2 | Fidelidade estrutural: espelha o layout REAL (nº de colunas verdadeiro; contagem de linhas plausível ao que vem) e trava o layout (anti-shift). Frame vazio proibido | "3 cards no skeleton, 7 no conteúdo" quebra confiança; NN/g: frame-display não informa nada | Blocos genéricos |
| S3 | Motion: shimmer em ONDA esquerda→direita, LENTA (~1.8s/ciclo); `prefers-reduced-motion` → estático. Direções reservadas: barra corre E→D; vai-e-vem é exclusivo do skeleton | Chung (onda > pulso; lenta > rápida — como pista); Carbon (reserva de direção) | Pulse; shimmer rápido |
| S4 | **Postura declarada:** a SEED adota skeleton pelo benefício ESTRUTURAL (expectativa de layout + anti-shift + carga progressiva em lotes), NÃO pela promessa de "parecer mais rápido" — a evidência de percepção é CONFLITANTE (Viget contra × acadêmico a favor × Chung modulando) e fica registrada assim. Janela alvo: 1–3s | Honestidade epistêmica do método; Carbon 1–3s | Vender skeleton como bala de prata |
| S5 | Shimmer NUNCA eterno: timeout de 15s → estado de erro (alerta §15, severidade erro no slot `role="alert"`) ou empty state (item 11) | Shimmer perpétuo esconde falha de API — sempre parear com timeout e fallback | Buraco negro de carregamento |
| S6 | ARIA: blocos decorativos (`aria-hidden="true"`); container com `aria-busy="true"` + anúncio discreto do R4 (início/fim). AT nunca "lê" retângulos | aria-busy no escopo que muda | Skeleton legível por AT |

### 19.2 Anatomia e tokens

Bloco: `--seed-cinza-100` (light) / `#38464F` border-subtle-dark (dark), radius 6; shimmer = gradiente 90° com highlight `--seed-cinza-50`/`#4D606C` varrendo E→D em 1.8s. Alturas casam com a tipografia real da célula/linha. Dataviz: esboço de skeleton de gráfico registrado, forma final adiada para F5/tokens v1.3 (ponta declarada). Contraste: blocos são DECORATIVOS (o sinal informativo é aria-busy + anúncio + shimmer) — pares medidos e reportados sem gate, com esta justificativa.

### 19.3 Os 7 testes

1. Container/página ok; blocos decorativos isentos com justificativa. 2. Espera por forma (estrutura) + movimento + anúncio. 3. Grayscale: nasce cinza. 4. Skeleton × conteúdo real distinguíveis a 1s (blocos × texto). 5. Um skeleton por região (R6). 6. Estrutura visível = expectativa honesta (S2). 7. 360px: colunas do skeleton colapsam IGUAL ao conteúdo real. *(Verificado: suite v0.17, aprovado.)*

---

## 20. Progress — `estável` · validado pelo Rafael em 2026-08-04 (suite v0.17) · resolve a ponta do §17.5

> **Base:** R1 — NN/g (>10s exige estimativa explícita), Carbon progress bar (handoff indeterminado→determinado; direção E→D), Material m1 PRIMÁRIO (nunca regride; uma barra para o todo) e m2 (handoff). R2 — React Aria useProgressBar (valuetext localizado; label obrigatório), Radix/shadcn (omitir value = indeterminado), Angular Material (não alterar valuemin/max padrão), MUI (aria-valuetext quando não é %). R3 — **W3C ARIA25 íntegra** (técnica oficial do 4.1.3: progressbar NÃO é live region; canal polite separado visually-hidden), MDN progressbar, marcos 25/50/75 (nunca cada 1%), Fiori (bloquear o mínimo). Fronteira: stepper "passo 2 de 5" é NAVEGAÇÃO → Bloco 5 (registrado no roadmap).

### 20.1 Decisões U1–U6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| U1 | Uso: fração conhecida OU >10s. Pode nascer indeterminada e CONVERTER quando o dado chega; o valor NUNCA regride (clamp); operações sequenciais = UMA barra do todo | m1 primário: "always fill from 0% to 100% and never decrease"; "the progress as a whole, not each individual operation"; Carbon/m2: handoff | Barra por sub-operação; regressão visual |
| U2 | ARIA: `role="progressbar"` + `aria-valuenow/min/max` (0/100 padrão, NÃO alterar); **indeterminada = OMITIR valuenow**; rótulo obrigatório (`aria-labelledby` no label visível); `aria-valuetext` quando o formato humano não é % ("7 de 12 faturas") | MDN; Angular Material (mudar min/max quebra AT); React Aria/MUI (valuetext) | Div animada sem role |
| U3 | **Anúncio = regime CONTÍNUO do §1.13** (par simétrico da 1.13-b): o progressbar NÃO anuncia sozinho — canal SEPARADO visually-hidden `role="status"` pré-existente anuncia MARCOS (25/50/75/100), nunca cada 1%, e a conclusão NOMEIA o objeto. O §14 (N3/N4) passa a referenciar este componente como canônico | W3C ARIA25: os values comunicam programaticamente mas não são anunciados ao mudar — a live region polite separada é quem fala; cada 1% soterra | Anunciar todo %; confiar só no role |
| U4 | `aria-busy="true"` na região que a operação reconstrói (tabela em lote); controles internos desabilitados durante | Escopo correto do aria-busy | aria-busy no body |
| U5 | >10s: barra + texto de status (percentual/etapa em **JetBrains Mono** — dado de medição, tokens v1.2 §tipografia); o resto da UI permanece interativo sempre que possível | Fiori: bloquear o mínimo; NN/g >10s = estimativa explícita | Overlay global em operação longa |
| U6 | **Toast de processo (fecha a ponta do §17.5):** operação longa em BACKGROUND vive num toast PERSISTENTE (herda Q2/Q4) contendo esta progressbar; título em gerúndio ("Enviando 12 faturas…"); aos 100% CONVERTE em toast de desfecho (sucesso, aí sim com auto-dismiss 8s). O nó-raiz do toast de processo NÃO leva role vivo — quem anuncia é o canal U3 interno (senão o atomic re-falaria o toast inteiro a cada %) | Fluent (gerúndio→substantivo; processos similares combinam num toast) + U1 (uma barra do todo) + §1.13 (uma live region por componente, do tipo certo) | Toast de processo com auto-dismiss; role="status" no raiz (re-anúncio a cada update) |

### 20.2 Anatomia e tokens

Trilho 8px radius 999 em `--seed-border-subtle`; preenchimento `--seed-action-primary-bg`, transição de largura 200ms `--seed-ease-out` (reduced-motion: seca); label acima à esquerda, valor à direita em mono; indeterminada: segmento ~30% varrendo E→D em loop (direção reservada da barra — Carbon).

### 20.3 Os 7 testes

1. Preenchimento×trilho e trilho×página ≥3:1 (medir light/dark). 2. Progresso por comprimento + número + anúncio. 3. Grayscale: comprimento carrega. 4. Determinada×indeterminada a 1s (número presente × varredura). 5. Uma barra por processo (U1); um canal de anúncio (U3). 6. Estado visível: label + valor sem interação. 7. 360px: barra full-width, label/valor empilham sem truncar; toast de processo legível. *(Verificado: suite v0.17, aprovado.)*

---

## 21. Badge — `estável` · validado pelo Rafael em 2026-08-04 (preview v0.19; suite C)

> **Base (3 rodadas, 2026-08-04):** R1 — Atlassian (badge = SÓ inteiros; status = lozenge; rótulo = tag — a separação tripla), Carbon (badge indicator com/sem número; status indicator pattern: consolidação pela maior atenção), Polaris badge (tons; passado; texto visually-hidden; vocabulário fechado), Smart Patterns (badge sempre estático). R2 — shadcn/stack (herança). R3 — WCAG 1.4.1/3.2.4 via T5-b; gov.br (tag como sinônimo registrado).

### 21.1 A separação tripla (decisão-mãe do sub-bloco C)

**Badge** = NÃO-interativo (nunca clique, cursor pointer ou navegação), dois papéis: **numérico** (contadores) e **de status** (o "lozenge"). **Chip** (§22) = interativo. Critério de 1 segundo: se clica, é chip; se só informa, é badge. Vocabulário: anglicismos badge/chip mantidos (correntes no stack shadcn/Lovable e no time); "tag" do gov.br registrado como sinônimo de chip. Descartado: badge clicável (híbrido que confunde e falha teclado — Polaris: badge exibe informação, nunca ações).

### 21.2 Decisões V1–V6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| V1 | Sempre estático; interação = chip ou botão | Polaris; Smart Patterns | Badge clicável |
| V2 | Variantes: **numérico** (inteiros; trunca "99+"; **dot** sem número para novidade não-contada) e **status** (severidades T1–T5 + tom NEUTRO cinza para estados sem carga: "Rascunho", "Arquivado") | Atlassian (só inteiros); Carbon (badge sem número quando a contagem não importa); Polaris (tons + neutro) | Badge genérico sem papéis |
| V3 | Texto: 1 palavra (2 para estado composto: "Parcialmente pago"), PASSADO para desfechos; vocabulário de status FECHADO por domínio — variação nova só se nenhuma existente serve | Polaris (uma palavra; passado; não criar alternativas) + T5-b (WCAG 3.2.4) | Status criativo por tela |
| V4 | A11y: significado por cor/ícone → texto visually-hidden contextual; numérico expõe rótulo completo ("3 notificações não lidas") | Polaris (texto oculto lido por AT dá o significado em contexto) | Badge mudo para AT |
| V5 | Consolidação de múltiplos status = severidade de MAIOR atenção (erro > atenção > informação > sucesso — ordem do P1) | Carbon status pattern (verde+amarelo+vermelho por baixo → consolidado vermelho) | Consolidar pela maioria |
| V6 | 3 sinais: cor + texto sempre; ícone recomendado, OBRIGATÓRIO em atenção e erro; pares de contraste HERDAM os do alerta (T1, medidos) | §1.5.1; T3 | Severidade só por matiz |

### 21.3 Anatomia e tokens

Status: pílula radius 999, fundo `-bg`, texto `-text` (pares T1/errata azul-800), ícone 14px opcional, altura 22px, padding 2/10, peso 600, font-size 12px; neutro = cinza-100/cinza-700 (light) · #38464F/cinza-200 (dark). Numérico: círculo/pílula em `--seed-feedback-danger-solid` com texto branco quando é contador de pendência, ou neutro para tally sem urgência; JetBrains Mono no número. Dot: 8px, mesma cor semântica do que sinaliza.

### 21.4 Os 7 testes

1. Pares herdados (medidos) + numérico ve-500/branco 4.30 ≥3:1 para não-textual e texto 12px bold medido no preview. 2. Cor+texto(+ícone). 3. Grayscale: texto e ícone carregam. 4. Badge × chip a 1s (sem borda/cursor × com). 5. Vocabulário fechado (V3). 6. Sempre visível sem interação (é a natureza dele). 7. 360px: pílula não trunca o rótulo; dot legível ao lado de ícone de nav. *(Verificado: suite v0.19, aprovado.)*

---

## 22. Chip — `estável` · validado pelo Rafael em 2026-08-04 (preview v0.19; suite C)

> **Base:** R1 — Carbon tag (read-only/dismissible/selectable/operational; borda sinaliza interatividade; nunca link; nunca multifunção), M3 chips com página de a11y primária (4 tipos; indicador secundário de interatividade; 2 focusables no máximo; reflow vs menu; Delete no teclado físico), Angular Material (setas+Delete; checkmark não se esconde; nunca aninhar interativos). R2 — React Aria TagGroup (padrão GRID ARIA: setas/home/end, remoção por botão ou Backspace, gestão de foco, anúncios em live region — extensivamente testado em AT), shadcn/stack. R3 — WCAG 2.5.8 (alvo mínimo 24×24 AA), gov.br tag (PT-BR), BC Design System (tag = React Aria TagGroup estilizado).

### 22.1 Decisões W1–W8

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| W1 | 3 tipos SEED: **filtro** (selecionável, alternativa a grupo de checkbox/switch), **entrada** (dismissível, valor inserido pelo usuário — o multi-select do combobox §11 consumirá este) e **ação contextual** (raro; ação primária/recorrente = botão §1). "Suggestion" do M3 DESCARTADO (predição/chat sem caso no ERP; o tipo ação cobre se surgir) | M3 (tipos e critérios); chips aparecem dinamicamente em grupo, botões aparecem consistentemente | Os 4 tipos do M3 |
| W2 | Interatividade VISÍVEL: chip tem borda + cursor + hover/pressed que o badge NUNCA tem | Carbon (borda no container sinaliza interatividade de relance); M3 (indicador secundário exigido para baixa visão/cognitivo) | Chip com cara de badge |
| W3 | Filtro selecionado = **checkmark visível** + mudança de cor — nunca só cor | Angular Material: esconder o checkmark degrada a identificação visual do selecionado | Seleção só por preenchimento |
| W4 | Remoção: X com `aria-label="Remover {valor}"`; alvo do X ≥**24×24** (WCAG 2.5.8) no desktop denso; em superfície touch, hitbox estendida ≥44px INVISÍVEL (pseudo-elemento) — visual compacto, alvo generoso | ODG (aria-label nomeando o valor; remoção anunciada); WCAG 2.5.8 | X 16px sem hitbox; chip de 44px de altura (mata a densidade) |
| W5 | Teclado = padrão GRID (React Aria): grupo ROTULADO; setas navegam (roving tabindex); Delete/Backspace remove o focado; após remover, foco → chip SEGUINTE (anterior se era o último; grupo/campo se esvaziou); live region ÚNICA do grupo anuncia ("Filtro Coelba removido") — regime discreto 1.13-b | React Aria TagGroup; Angular | Cada chip como tab-stop (Tab-maratona); foco morto pós-remoção |
| W6 | Touch: X SEMPRE visível — **divergência declarada do M3** (que dispensa o X quando remover é a única ação, via tecla Delete — pressupõe teclado físico; no toque não existe Delete: sem X, sem caminho) | M3 lido contra o contexto (ERP mobile = dedo) | M3 literal (beco sem saída no touch) |
| W7 | Overflow: default **reflow** (todos visíveis, empurrando conteúdo — filtro ativo nunca se esconde); variante densa "+N" abre a lista em popover — ESBOÇADA, spec final espera o §popover (sub-bloco D; ponta declarada) | M3 (reflow vs menu; exibir todos sem rolagem) | Rolagem horizontal de chips (esconde filtros ativos — mortal em ERP) |
| W8 | Proibições: chip nunca é LINK para outra página; nunca multifunção (máx.: corpo selecionável + X = 2 focusables); nunca aninhar controles dentro | Carbon; M3 (2 focusables); Angular (não aninhar) | Chip-canivete |

### 22.2 Anatomia e tokens

Altura 28px (denso) / 32px (confortável), radius 999, borda 1px em **cinza-600** (token de componente `chip-border`; par light 4.74 e dark 3.60, ambos ≥3:1 — o `border-default` cinza-300 REPROVOU com 1.91 na medição e foi descartado: a borda é o sinal de interatividade do W2, não decoração), texto `--seed-text-primary`, fundo `--seed-surface-page`; hover: fundo `--seed-surface-subtle`; selecionado (filtro): fundo `--seed-action-primary-bg`, texto `--seed-action-primary-text`, checkmark 14px; X: ícone 14px em botão de 24×24 com hitbox touch estendida; focus ring padrão. Grupo: flex com wrap (reflow W7), gap 8px, rótulo visível ou aria-label.

### 22.3 Os 7 testes

1. Borda do chip: cinza-600 medido — 4.74 light / 3.60 dark ✓ (cinza-300 reprovado com 1.91); selecionado branco/tq-600 4.60 ✓; numérico branco/ve-500 4.30 ✓ (não-textual/negrito 12px); neutro ci-700/ci-100 5.43 ✓. 2. Seleção por checkmark+cor; remoção por X+anúncio. 3. Grayscale: checkmark e borda carregam. 4. Filtro × entrada a 1s (checkmark × X). 5. Uma live region por grupo. 6. Selecionado visível sem interação. 7. 360px: reflow empilha; X com hitbox 44 tocável de polegar. *(Verificado: suite v0.19, aprovado.)*

---

## 23. Tooltip — `estável` · validado pelo Rafael em 2026-08-04 (crítica visual → X6-b + ancoragem; suite v0.21)

> **Base (3 rodadas, 2026-08-04):** R1 — NN/g tooltip guidelines (nunca essencial à tarefa; ícones deveriam ter labels), M2/M3 (plain/rich; não repetir texto visível; não cobrir o alvo; tap-and-hold como cortesia mobile), Carbon tooltip + toggletip (nunca interativos dentro; caixa máxima; a régua hover/focus × click). R2 — Radix (mantenedores: tooltip ARIA não funciona em touch; desenhar sem tooltip onde possível), React Aria, ustwo (híbrido hover/tap documentado). R3 — WCAG 1.4.13 Understanding + SCR39 (dismissible/hoverable/persistent; Esc; F95 como falha mais comum; timer de auto-hide reprova), Heydon Pickering/Inclusive Components (a dupla rótulo × descrição; o toggletip), DHIS2/W3C (touch sem pista de que o tooltip existe), APG tooltip com aviso declarado de "sem consenso da task force".

### 23.1 A régua da sobreposição leve (governa §23–§24 + fronteira com o Bloco 4)

1. **Tooltip** — NÃO-interativo, curto, suplementar; hover/focus. 2. **Popover** (§24) — interativo leve ou texto persistente; CLIQUE; light-dismiss; não-modal. O toggletip é a variante de texto do popover. 3. **Modal/dialog** — decisão obrigatória, form, backdrop, foco preso → Bloco 4 (Hidde: "se está considerando backdrop no popover, talvez seja um dialog"; Heydon: conteúdo interativo — até um OK — é dialog).

### 23.2 Decisões X1–X6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| X1 | Dois papéis com ARIA distinta: **rótulo** de controle só-ícone (`aria-labelledby` — o tooltip É o nome, 1–2 palavras) × **descrição** suplementar (`aria-describedby`). NUNCA informação essencial; NUNCA repetir texto visível | Heydon (Primary Label × Auxiliary description); NN/g; M2 | Tooltip como esconderijo de informação obrigatória |
| X2 | **DECISÃO TOUCH:** tooltip não é portador de informação no toque — TODO uso declara equivalente touch: (a) controle com ação própria → rótulo VISÍVEL no mobile (paridade estrutural); long-press exibe o rótulo como cortesia (M3), nunca canal único; (b) gatilho informativo dedicado (ⓘ) → no touch o MESMO gatilho vira toggletip por tap | Radix, DHIS2 (touch não acessa e não tem pista), Heydon, M3 — convergência incomum de 4 fontes independentes | Ícone mudo no touch; híbrido como única fonte |
| X3 | 1.4.13 completo: **hoverable** (corredor — mover o mouse PARA o tooltip não fecha), **persistent** (SEM auto-hide por timer), **dismissible** (Esc fecha sem mover o foco). Delay de abertura 500ms; grupo aquecido: adjacentes abrem sem delay | WCAG 1.4.13/SCR39; TestParty (F95 = sumir ao tentar ler; timer 2–3s reprova); caso Blueprint (sem Esc = violação) | Auto-hide por timer; tooltip que foge do mouse |
| X4 | `role="tooltip"`; NUNCA recebe foco; máx. ~2 linhas (mais = popover). Fato declarado: o APG segue "work in progress, sem consenso" — ancoragem no 1.4.13 + prática convergente | O próprio APG; ARIA 1.2 estável nos atributos, comportamento em evolução; Carbon (caixa máxima) | Fingir spec fechada |
| X5 | NUNCA em elemento `disabled` (não dispara eventos nem recebe foco — o tooltip não abre); o porquê-desabilitado vai para helper text visível ou o controle fica habilitado com validação | "Disabled-button trap" documentado; filosofia do §0 (estado explicado visivelmente) | Tooltip explicando botão morto |
| X6-b | *(emenda da validação visual do Rafael, 2026-08-04)* Gatilho informativo usa o **ícone SVG info do T3** (barra + ponto, sem círculo próprio — o container circular já é o círculo), NUNCA o caractere unicode ⓘ: além do círculo duplicado, caractere de fonte renderiza inconsistente entre plataformas | Crítica visual medida e acatada | Caractere ⓘ (círculo dentro de círculo; drift tipográfico) |
| X6 | Visual: fundo cinza-900 + texto branco no light (par **13.86 AAA**, medido) e cinza-100 + texto cinza-900 no dark (**11.49 AAA**; borda cinza-600 contra a página 3.60); radius 6; 4–8px do alvo; seta opcional; flip anti-overflow; nunca cobre o alvo; reduced-motion: seco | M3 (posição dinâmica; não cobrir o pai); pares medidos na suite v0.21 | Tooltip claro-sobre-claro |

### 23.3 Os 7 testes

1. Pares do X6 medidos ✓. 2. Rótulo por texto (o tooltip É texto). 3. Grayscale: nasce alto-contraste. 4. Tooltip × popover a 1s (sem interativos/sem foco × com). 5. Um tooltip aberto por vez (abrir outro fecha o anterior). 6. Informação nunca SÓ no tooltip (X1/X2). 7. 360px: o equivalente touch existe declarado (rótulo visível ou toggletip). *(Verificado: suite v0.21, aprovado.)*

---

## 24. Popover — `estável` · validado pelo Rafael em 2026-08-04 (suite v0.21) · fecha a variante "+N" do W7

> **Base:** R1 — Carbon (caret; toggletip usa o popover flexível + disclosure), Fiori (sap.m.ResponsivePopover: DIALOG no smartphone com X, popover em tablet/desktop; não-responsivo só com pouquíssimo conteúdo). R2 — HTML Popover API (Chrome 114/Firefox 125/Safari 18.3 — baseline jan/2025: top layer, light-dismiss, popover="auto" fecha os demais, Esc restaura o foco ao invoker), React Aria usePopover (foco entra no mount, restaurado no unmount; fora fica oculto de AT), Floating UI (modal × não-modal), Spectrum (menu vira TRAY no mobile). R3 — Hidde (popover é não-modal; backdrop = provável dialog), Heydon (toggletip anuncia APÓS o clique via live region — describedby não serve), OpenUI explainer (notificação/manual não recebe foco imediato).

### 24.1 Decisões Y1–Y6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| Y1 | Papel: sobreposição NÃO-modal por CLIQUE com interativos leves (mini-filtros, ações rápidas, o "+N" do W7, toggletip); light-dismiss (Esc/clique fora); UM por vez | Popover API: light-dismiss nativo; auto fecha os demais autos | Popovers empilhados |
| Y2 | Fronteira: decisão obrigatória/form longo/backdrop/foco preso → dialog (Bloco 4) | Hidde; Heydon (régua §23.1) | Popover-que-queria-ser-modal |
| Y3 | Foco: entra no popover ao abrir; VOLTA ao gatilho ao fechar (Esc, fora, ação) | React Aria; MDN (Esc restaura ao invoker) | Foco morto atrás do popover |
| Y4 | Mobile: popover pequeno ancorado se mantém; conteúdo denso (lista rolável, várias ações, >~40% da viewport) converte em **tray/bottom-sheet** no breakpoint touch (<640px): sobe da base, alça + X, `--seed-safe-bottom`, MESMO conteúdo | Fiori ResponsivePopover (dialog com X no smartphone); Spectrum (menu → tray no mobile) | Popover de 500px espremido; sheet para tudo |
| Y5 | Implementação: **HTML Popover API** (`popover="auto"`) como base, com fallback manual de semântica idêntica onde a API faltar; **toggletip** = popover de texto anunciado via live region APÓS o clique (regime 1.13-b) — NUNCA `aria-describedby` no toggletip | Baseline jan/2025; Heydon: com describedby o leitor teria a informação ANTES do clique e o botão pareceria não fazer nada | Reimplementar top layer na mão; toggletip com describedby |
| Y6 | Visual: pele de overlay do §17.4 (surface-overlay + sombra; dark = borda separa), caret opcional, largura máx. 320px ancorado; pares herdados (medidos) | Consistência de sobreposição; economia de medição | Terceira linguagem visual |

### 24.2 "+N" dos chips (fecha a ponta do W7)

O grupo denso de chips com excedente mostra `+N` como GATILHO de popover listando os chips ocultos (removíveis lá dentro com a mesma mecânica W5, live region do grupo original anunciando). No touch/tray, os alvos crescem para o padrão sheet.

### 24.3 Os 7 testes

1. Pares herdados ✓. 2. Abertura por clique + caret ancorando. 3. Grayscale: borda+sombra. 4. Popover × tooltip × modal a 1s (régua §23.1). 5. Um por vez. 6. Foco visível dentro; retorno visível ao fechar. 7. 360px: conversão em tray com alça, X 44px e safe-area. *(Verificado: suite v0.21, aprovado.)*

---

## 25. Empty state — `estável` · validado pelo Rafael em 2026-08-04 (1 ciclo de crítica: regra do botão-morto de preview; suite E 14/14) · fecha o Bloco 3

> **Base (3 rodadas, 2026-08-04):** R1 — NN/g (empty states em aplicações complexas; o painel de alertas vazio de um ERP deixa o usuário sem saber se houve erro ou falta de acesso — o texto existe para matar essa dúvida), Polaris (componente é para PÁGINA vazia; nunca culpar o usuário; orientado a ação), Carbon empty states pattern, Atlassian, GitLab Pajamas (o mais prescritivo: tipos, título ≤5 palavras sem ponto, zero-resultados SEM CTA com copy exata). R2 — shadcn Empty (composição Header/Media/Title/Description/Content; taxonomia de 5 tipos), Mobbin, Setproduct. R3 — SAP Fiori (empty states por container: tile → card → página; illustrated message com 4 tamanhos de ilustração: dot 45px, spot 128px, dialog, scene; até 2 CTAs; mensagem orientada a solução). Heranças aplicadas sem re-pesquisa: D8/D9 (zero-resultados), S5 (timeout do skeleton), botão §1 (CTA), marca v5.0 (Grafismo v2/EEny).

### 25.1 Taxonomia Z1 — seis tipos, nunca um "vazio genérico" *(SUP-3 de 2026-08-14: era cinco; entra a Bifurcação, dispositivo C36 do confronto)*

| Tipo | Regra | Fonte |
|---|---|---|
| **Primeiro uso** | Educativo + CTA de criação primário; secundária "saiba mais" opcional | Polaris |
| **Esvaziado/concluído** | Tom de conclusão ("Tudo em dia"), sem urgência; CTA opcional | shadcn |
| **Zero-resultados** | HERDA D8/D9: reflete a query, sugere ajuste, ação = limpar filtros; **SEM CTA de criação** ("não achou" nunca vira "crie um novo" por engano) | GitLab (copy exata: "Edite sua busca e tente de novo") + D8/D9 |
| **Erro de carga** | Recebe o timeout do S5 quando a região falhou: mensagem orientada a solução + "Tentar novamente"; severidade via alerta §15 no slot `role="alert"` | S5; NN/g |
| **Sem permissão** | Explica o porquê + quem contatar; nunca finge que a área não existe | shadcn (no-access) |
| **Bifurcação** *(novo, SUP-3)* | O vazio existe porque uma decisão estrutural ainda não foi tomada, e a decisão muda tudo o que vem depois. Apresenta as opções com as consequências visíveis. **Exatamente duas ações, as duas primárias** — escolha entre iguais, não ação com escape (exceção declarada à Z2). Ilustração continua `aria-hidden` (Z5); a descrição de uma frase carrega a diferença entre as opções EM TEXTO (Z4) — sem isso, leitor de tela escolhe às cegas. Cartões em `radius-xl`. **Quando é errado:** se 90% escolheriam a mesma opção, aplica-se o padrão e oferece-se a troca depois — bifurcar é empurrar ao usuário uma decisão que o produto podia tomar. *Alternativa descartada: tratá-la como variante de "primeiro uso" — primeiro uso educa e convida a UMA ação; a bifurcação exige escolha entre caminhos incompatíveis, e o mesmo tipo faria o CTA de criação aparecer onde é errado* | C36 (medido: cartões 350×438, duas ilustrações mostrando o resultado de cada escolha) |

> **Emenda à Z2 (mesmo supersede):** o teto de "até 2 ações" vale para os cinco primeiros
> tipos; **no tipo bifurcação, 2 é obrigação, não teto, e a hierarquia primária/secundária não
> se aplica.**

### 25.2 Decisões Z2–Z6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| Z2 | Anatomia: mídia opcional + **título ≤5 palavras sem ponto** + descrição em frase completa com o próximo passo + **até 2 ações** (primária = botão §1; secundária = ghost/link) | GitLab; Fiori (até 2 CTAs) | Parágrafo de desculpas; 3+ ações |
| Z3 | Régua de escala por container: linha/tile = título+descrição · card pequeno = +CTA · card médio = +ilustração pequena (spot) · página = composição completa (scene). Componente de página NUNCA em célula | Fiori (escala explícita por container + 4 tamanhos de ilustração); Polaris (empty state de página ≠ elemento) | Ilustração gigante em slot de tabela |
| Z4 | Microcopy: encorajador, nunca culpa; orientado a SOLUÇÃO; proibido o vazio seco ("Sem dados") — sempre porquê + próximo passo; zero-resultados reflete a query | Polaris; NN/g (caso ERP); Mobbin | "Nenhum registro encontrado." e só |
| Z5 | Ilustração segue marca v5.0 (Grafismo v2, sem recriar assets): **EEny permitido em primeiro-uso e esvaziado; PROIBIDO em erro e sem-permissão** (severidade não é lugar de mascote). Ilustração DECORATIVA (`aria-hidden`) — a informação mora no texto | Fiori (ilustração adiciona personalidade, a mensagem carrega); regra de mascote v5.0 estendida — **regra de marca nova, martelada pelo Rafael em 2026-08-04** | Mascote em tela de erro; informação só na imagem |
| Z6 | Comportamento: conteúdo ESTÁTICO — sem live region própria (quem anuncia a transição é o fluxo causador: busca D4, carregamento R4; §1.13 uma live region por componente); se existe conteúdo oculto (arquivados), o caminho até ele permanece visível; CTA com toda a mecânica do §1 | GitLab (arquivados); §1.13 | Anúncio duplicado; esconder o caminho dos arquivados |

### 25.3 Anatomia e tokens

Bloco centralizado; título Montserrat 700 1.05rem `--seed-text-primary`; descrição `--seed-text-secondary` ≤2 frases; gap 12; padding vertical 32 (página) / 16 (card); ícone 40px em `--seed-cinza-300`/dark cinza-600 quando não há ilustração; ilustração spot ≤128px. Erro de carga: o alerta §15 senta ACIMA do bloco. Sem cor de severidade no empty em si (neutro por natureza — a severidade mora no alerta).

### 25.4 Os 7 testes

1. Pares herdados (texto primário/secundário medidos nos tokens). 2. Tipo carregado por texto (título+descrição), nunca só pela imagem. 3. Grayscale: por construção (Z5 — informação no texto). 4. Zero-resultados × primeiro-uso a 1s (sem CTA de criação × com). 5. Um empty por região; sem live region duplicada. 6. Estático e visível por natureza. 7. 360px: bloco centralizado, CTA full-width herdando o §1 v0.7, ilustração colapsa antes do texto. *(Verificado: suite v0.23, aprovado.)*

---

## 26. Card — `estável` (v0.26) · CD1–CD5 · validação executada 198/198 + visual aprovada em 2026-08-05

> **Base (3 rodadas, 2026-08-04):** R1 — M3 cards (elevated/filled/outlined com legibilidade equivalente — escolha é estilística), NN/g (sombra como signifier de clique), Carbon tile×card (tile é fundação sem estilo de conteúdo; card constrói em cima), Nathan Curtis (card inteiro clicável × zonas interativas), Berkeley DAP (heading + affordance obrigatórios). R2 — shadcn (6 sub-componentes; CardTitle migrou de h3→div para NÃO quebrar hierarquia de headings), EUI (card sobre panel; title em span configurável; regras de nesting), React Aria. R3 — **Inclusive Components/cards (fonte primária: pseudo-content trick + redundant click)**, Adrian Roselli (block links: wrapper `<a>` vira bloco ilegível em AT), **Intopia (teste de usabilidade real: usuários com dislexia/baixa visão falham ao selecionar texto sob o trick)**, Fiori object card.

### 26.1 Decisões CD1–CD5

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| CD1 | Default **outlined** (borda + superfície); **elevated** (par superfície+sombra §2.8 dos tokens; no dark a borda assume) restrita a destaque deliberado, NUNCA em grade densa | M3: 3 tipos = mesma função, escolha estilística; perfil ERP denso (Fiori/EUI): sombra multiplicada vira ruído e falha no grayscale; signifier de clique da SEED é mecanismo medido (hover/focus/sublinha), não sombra permanente | Elevated default; filled (superfície tonal colide com 70/20/10 — cor pontua, não forra) |

> **⚠ SUPERSEDE PARCIAL DO CD1 — decisão do Rafael em 2026-08-15 (pacote
> PN-P7/CP-P2, MANIFESTO v7.6→v7.7), verbatim: *"o que vai prevalecer é o que
> construímos agora, se alguma informação antiga ainda não foi atualizada,
> ela não serve para referência se já temos atualização para ela. acredito
> ser a opção A."*** O conflito era latente desde o nascimento do CP17
> (2026-08-14): o CD1 declarava borda como mecanismo default de separação do
> card, e o CP17 (lei de composição, gate G1) declara que **toda PEÇA se
> separa do canvas pelo par sombra+anel** — anel de 1px na tinta
> `border-subtle` DENTRO da declaração de sombra, slot `border` livre para
> estado. Resolução: **quando o card é PEÇA sobre canvas (o caso de tela
> composta — bloco de painel, cartão de formulário, a tabela inteira), o
> CP17 prevalece e a separação é o par.** O que o CD1 PRESERVA: a difusa do
> par é a `shadow-raised` sutil (0 1px 2px @ 10%), não a "sombra
> multiplicada" que o porquê original temia — o racional anti-ruído do ERP
> denso segue respeitado; "elevated como destaque deliberado" segue valendo
> para a sombra FORTE; e o outlined literal (borda no slot border)
> permanece legítimo apenas em contexto INTERNO a outra peça (o well do CD5,
> contornos dentro de cartão), onde o elemento não é peça sobre canvas.
> **Consequência declarada:** os previews de componente que demonstram o
> card outlined como default (superfícies e herdeiros) ficam DEFASADOS desta
> decisão até o retrofit próprio — pendência **CP-P4**, registrada no
> MANIFESTO v7.7 (não entrou no lote PN-P7/CP-P2 de propósito: migrar
> artefato de spec sem o supersede escrito seria inverter a ordem
> decisão→artefato). O mecanismo permanece UM só por contrato (CP17 do
> `seed-composicao.md`): dois mecanismos convivendo em tela seria
> reintroduzir a dispersão que aquele arquivo mata.

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| CD2 | Anatomia composável de 6 zonas TODAS opcionais: container → header (título+descrição+ação) → media → content → footer; **título sem heading fixo** — nível de heading é do consumidor | shadcn (changelog: h3→div por ARIA); EUI (span default + titleElement); Zuora/Carbon | Heading h3 injetado (quebra hierarquia em dashboards de níveis variados) |
| CD3 | Card clicável em 3 regimes: **(a) estático** (default) · **(b) navegável** — UM link real no título, área estendida via `::after` do link, hover+`:focus-within` medidos, CTA decorativo `aria-hidden`; **PROIBIDO quando o corpo tem dado que o usuário copia** (código, nº de UC, medição — o pseudo-content bloqueia seleção de texto e copiar dado é fluxo real no ERP) · **(c) com ações** — sem área estendida; máx. 2 ações visíveis + overflow (regra do um preservada); secundárias sobem no z-index | Inclusive Components (primária); Intopia (falha real de seleção documentada em teste); Roselli; Berkeley; Curtis; NN/g | Wrapper `<a>` no card inteiro (AT lê bloco único ilegível); `onclick` em div (sem teclado); redundant-click como default (JS p/ cursor + lógica duplicada — vira exceção documentada) |
| CD4 | Card selecionável = **composição com radio/checkbox do Bloco 2**, não componente novo: input nativo dentro do card-label, grupo em `fieldset+legend`, selected herda sinais do Bloco 1 (borda de ação + anel `:has(:checked)`) | EUI CheckableCard (fieldset/legend obrigatórios); Carbon selectable tile | Componente independente (duplicaria estados já specados) |
| CD5 | **Nesting proibido no mesmo estilo:** card dentro de card não repete borda/sombra — interior usa divider/spacing ou sub-painel `well` (surface-sunken) | EUI panel nesting ("painéis iguais aninhados destroem a hierarquia") + EUI form layouts (anti-padrão documentado) | Card russo (borda dentro de borda) |

### 26.2 Anatomia e tokens (camada 3 — pares light/dark declarados)

`--seed-card-bg` = surface-raised (branco / #1D272D) · `--seed-card-border` = border-subtle (cinza-100 / #38464F) — **borda DECORATIVA declarada** (1.21 light / 1.75 dark, medidos): a separação é carregada por espaço + raio + mudança de superfície + tipografia; contorno não é portador único de informação (mesmo racional do skeleton §19.2) · `--seed-card-radius` = 12px (radius lg) · padding interno 16px (12 no content vertical) · elevated: `--seed-shadow-raised` + no dark borda `#38464F` visível (par de elevação §2.8). Interação medida: título-link tq-600/branco **4.60 AA** light · tq-300/dark-page **9.32 AAA** dark, sublinhado sempre; focus ring padrão (§1); hover do navegável = sombra raised (reforço, nunca canal único — a sublinha do link é o sinal permanente).

### 26.3 Os 7 testes

1. Pares medidos: título-link 4.60/9.32 ✓; textos herdam primário/secundário (13.86/6.55 light · 14.16/8.92 dark) ✓; bordas declaradas decorativas com justificativa. 2. Clique sinalizado por sublinha+cursor+hover, nunca só tom. 3. Grayscale: contorno+raio+peso carregam (provado no preview). 4. Estático × navegável × selecionável a 1s (nada × sublinha no título × checkbox visível). 5. Regra do um: 1 gesto primário; máx. 2 secundárias. 6. Selecionado visível sem interação (borda de ação + input marcado). 7. 360px: padding 16, CTA full-width herdando §1 v0.7, área de toque ≥44. *(Validação executada completa: suite F 32/32 + regressão A–E 166/166; visual aprovada 2026-08-05.)*

---

## 27. Divider — `estável` (v0.26) · DV1–DV3 · validação executada + visual aprovada em 2026-08-05

> **Base:** R1 — M3 divider (full-width × inset; "use sparingly"; itens repetitivos dispensam divider — margem basta), M1 dividers (overuse = ruído; whitespace/subheader como alternativas), Linzi Berry (inset consistente na tela inteira). R2 — MUI (vertical = div com ARIA), Material Web ("decorative by default and not announced"), React Aria useSeparator. R3 — **MDN/WAI-ARIA 1.2 separator (fonte primária): `<hr>` tem role implícito `separator` e É anunciado por leitores de tela** — linha sob cada item vira ruído puro para AT.

### 27.1 Decisões DV1–DV3

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| DV1 | **Decorativo por default** (div `aria-hidden` ou border no vizinho); a variante **semântica** (`<hr>`, separator implícito) só quando marca quebra temática REAL. **Supersede: o uso livre de `<hr>` do `seed-design-system.html` v1.0 fica obsoleto** — todo `<hr>` novo declara intenção semântica | MDN/ARIA 1.2 (primária, verificada — decisão vira supersede); Material Web adota o mesmo default | `<hr>` sempre (polui a árvore de AT nas listas densas do ERP) |
| DV2 | Régua de separação em 3 degraus: **espaço em branco → inset → full-width**; inset separa itens relacionados (com âncora); full-width separa seções distintas, com parcimônia; quando convivem, a diferença reforça hierarquia; **recuo do inset consistente na tela inteira**, mesmo sem âncora em todos os itens | M3 + M1 (explícitos); Linzi Berry (Lyft) | Divider default entre todos os itens (LS4) |
| DV3 | Vertical: elemento com `role="separator"` + `aria-orientation="vertical"` quando semântico; decorativo dispensa ambos. 1px de espessura | ARIA 1.2 (orientation default é horizontal); MUI/React Aria | hr rotacionado |

### 27.2 Tokens

`--seed-divider-color` = border-subtle (**1,21 sobre surface-page clara / 1,75 sobre surface-subtle escura** — **decorativo declarado**) · `--seed-divider-strong` = border-default **nos dois temas** (**1,49 sobre surface-page clara / 2,84 sobre surface-page escura** — decorativo declarado) para full-width enfático · margens 12px vertical · inset alinha ao início do texto após a âncora.

> ⚠⚠ **CORRIGIDO EM 2026-08-26, e o defeito é de uma classe que nenhuma guarda pegava: a spec afirmava um contraste que não existe.** A redação anterior dava **1,91** ao `divider-strong` no claro. Medidas as **cinco** superfícies do tema contra o `border-default`, nenhuma produz 1,91 — page/raised/overlay dão **1,49**, subtle 1,37, sunken 1,23. O número foi **transplantado**: 1,91 é o que o `border-subtle` **escuro** dá sobre a página escura, e também o que o cinza-300 dá sobre branco. ⭐ *O número estava certo em algum lugar do arquivo; só estava grudado no token errado.*
> ⭐ **E o irmão só batia porque foi medido contra outro fundo.** O "1,75 dark" do `divider-color` não sai da `surface-page` (que dá 1,91) — sai da `surface-subtle`. Os dois temas foram medidos contra fundos diferentes, sem que a linha dissesse. ⭐⭐ **Régua: contraste sem fundo declarado não é um número, é uma família de números** — e foi variando o fundo, uma condição por vez (§14.5), que os três valores se explicaram. Daqui em diante esta linha declara o fundo de cada medida.
> ⚠ **O par dark ficou decidido pelo que sobrou.** A v1.22 recusou emitir este token justamente porque o consumidor vivo (`banco-superficies.html`) usa cinza-600 no escuro, que não é o `border-default` dark — e emitir qualquer lado criaria contradição. Com o 1,91 desmentido, o cinza-600 se revela **dialeto local**, não valor derivado de spec, e vale a regra simétrica que o irmão já escreve: `divider-color` = border-subtle nos **dois** temas, logo `divider-strong` = border-default nos **dois**. O consumidor foi repontado para o token.

### 27.3 Os 7 testes

1. Pares decorativos declarados com justificativa (a informação de agrupamento mora no espaço+conteúdo). 2. Separação nunca depende só da linha (spacing coexiste). 3. Grayscale: já nasce acromático. 4. Inset × full a 1s (recuo). 5. Regra do um: um regime de divider por lista. 6. n/a (estático). 7. 360px: full-width não estoura o container; inset preserva o alinhamento. *(Suite F ✓.)*

---

## 28. Lista — `estável` (v0.26) · LS1–LS5 · validação executada + visual aprovada em 2026-08-05 · pronta para a central de notificações

> **Base:** R1 — M3/M2/M1 lists (1–3 linhas; >3 → card; conteúdo distintivo à esquerda; condensação legítima no desktop), Carbon list/structured list (wrap, não truncar; >25 itens → tabela). R2 — **React Aria (a tricotomia: ul/li estático · GridList/ARIA grid quando há filhos interativos · ListBox para seleção pura — com nota explícita de quando usar cada)**, Angular Material (proíbe controles interativos dentro de options). R3 — Fiori list card/object list (células condensadas; lida/não-lida por peso), WCAG 2.5.8 herdado.

### 28.1 Decisões LS1–LS5

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| LS1 | Dois modos com semântica distinta: **estática** = `ul/li` puro (zero interação) · **interativa com filhos interativos** = padrão ARIA **grid** (role=grid/row/gridcell, roving tabindex, setas/Home/End, UM tab-stop no grupo, Enter abre, ação por item com `tabindex=-1` e `aria-label` nomeando) · seleção pura sem filhos → listbox (registrado; caso raro no ERP) | React Aria (referência do stack Lovable/shadcn); Angular Material; APG grid | Modo único "li clicável" (inacessível); cada ação como tab-stop (Tab-maratona, anti-W5) |
| LS2 | Item de **1 a 3 linhas, teto duro** — acima é card (§26). Anatomia: leading (ícone/avatar — slot nasce agora, componente avatar chega no sub-bloco I) + corpo (primário/secundário/terciário) + trailing (meta em JetBrains Mono ou ação). Distintivo à esquerda. Truncamento evitado: wrap; exceção densa usa tooltip §23 | M3/M1; Carbon; Fiori | Truncar por padrão; 4+ linhas em lista |
| LS3 | Densidades: **default touch-first** (item ≥44px — teste do polegar) + **densa** desktop opt-in (36px). A degradação densa→toque da TABELA fica no Bloco 6 (pendência declarada, não invadida) | Premissa mobile M1–M3; M1 (condensar quando mouse domina); WCAG 2.5.8 | Densa como default (mata o toque) |
| LS4 | Divider em lista: **default SEM divider** (spacing separa); inset opcional herdando DV2, sempre decorativo | M3 ("margem basta em itens repetitivos"); DV1 | Zebra (território da tabela, B6) |
| LS5 | Slots da CENTRAL DE NOTIFICAÇÕES declarados desde já: **não-lida por peso tipográfico + dot** (nunca só cor), timestamp no trailing, severidade T1–T5 no leading, marcar-como-lida anuncia na **live region única do grupo nomeando o item** (regime discreto 1.13-b), foco pós-ação permanece na linha, erro de carga usa slot `role="alert"` pré-existente (herda S5/Z) | CN1–CN4 (marco v1.0); Fiori (peso); W5 adaptado | Re-spec da lista quando a CN chegar |

### 28.2 Anatomia e tokens

Item: min-height 44 (densa 36) · gap 12 · leading 32px · primário 600 .9rem `text-primary` · secundário .82rem `text-secondary` · trailing JetBrains Mono .75rem `text-muted` · hover `--seed-list-hover` (surface-subtle / #273137) com cursor — reforço, nunca canal único (foco tem anel medido §1) · não-lida: primário 700 + dot 8px vermelho-500 (badge §21 dot). Pares medidos: primário 13.86/14.16 · secundário 6.55/8.92 · meta 4.74 light / 7.95 dark (elevated) · texto sobre hover 12.75 — todos ≥AA ✓.

### 28.3 Os 7 testes

1. Pares medidos acima ✓. 2. Não-lida = peso+dot; interação = foco+hover+cursor. 3. Grayscale: peso carrega (provado). 4. Estática × interativa a 1s (cursor+hover × nada). 5. Uma live region por grupo (1.13-b). 6. Estado não-lida visível sem interação. 7. 360px: item 44px, ação com hitbox estendida ~44, trailing não colide. *(Validação executada completa: suite F 32/32 + regressão A–E 166/166; visual aprovada 2026-08-05.)*

---

## 29. Accordion — `estável` (v0.28) · AC1–AC6 · validação executada 213/213 + visual aprovada em 2026-08-05

> **Base (3 rodadas, 2026-08-05):** R1 — APG (button-no-heading, aria-expanded/controls, region até ~6 painéis, Tab nativo + Enter/Espaço), GOV.UK (Show all/Hide all; blog de acessibilidade: reprovações formais em WCAG 1.3.1 — título azulado lido como link — e 3.2.4 — estilos múltiplos de accordion), ONS/Bristol (quando não usar; nesting proibido), NN/g ×4 (multi-aberto obrigatório; estado persiste; 5 cenários de evitar; mobile é o caso mais forte). R2 — Radix (type single/multiple, collapsible, setas/Home/End de graça, animação via `--radix-accordion-content-height`), shadcn/Base UI. R3 — **Scott O'Hara (primária: quirks do `<summary>` — role inconsistente entre browsers, confirmado pela regra ACT do W3C)**, MDN `hidden="until-found"` + `beforematch` (Baseline 2025), SAP Fiori panel (nesting not recommended; chevron rotaciona).

### 29.1 Decisões AC1–AC6

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| AC1 | Semântica APG: `<hN><button aria-expanded aria-controls></hN>` + painel `id` recíproco; **nível do heading é do consumidor** (eco CD2); painel `role="region"`+`aria-labelledby` **até ~6 seções** (acima, div simples); teclado Tab/Enter/Espaço, setas como enhancement do Radix | APG unânime nas fontes; botão-no-heading habilita navegação por headings no leitor de tela | Header sem heading; `aria-disabled` prendendo painel aberto |
| AC2 | Implementação **ARIA+JS** (padrão do stack Radix/shadcn/Lovable), NÃO `<details>/<summary>`; painel fechado usa **`hidden="until-found"`** e o evento **`beforematch` sincroniza `aria-expanded`** quando o find-in-page revela | O'Hara + regra ACT (role do summary inconsistente entre browsers); GOV.UK também é custom; until-found recupera o find-in-page que era a única vantagem do details (Baseline 2025: Chrome/Firefox, Safari a caminho) | `<details>` puro (quirks + sem single/multiple); div onclick |
| AC3 | **Multi-aberto é o DEFAULT**; exclusivo (`single`) é variante justificada por fluxo de leitura; **collapsible sempre** (tudo pode fechar) | NN/g explícito ("garanta abrir múltiplas seções"; estado permanece até o usuário mudar); GOV.UK/Radix | Single como default (fecha o que o usuário abriu — anti-controle); regime "um sempre aberto" |
| AC4 | **A linha inteira é o botão** (≥48px), chevron rotacionando no trailing (`aria-hidden`; estado vive no `aria-expanded`), slot de meta em JetBrains Mono; **título nunca com cara de link** (lição 1.3.1 GOV.UK); **um estilo de accordion por produto** (3.2.4); **nenhum controle interativo dentro do button** — ações por item são exceção documentada como elementos irmãos (eco CD3-c) | Blog GOV.UK (2 reprovações formais viram regra); HTML proíbe interativo em button; causa nº 1 de summary sem nome na ACT é ícone-só clicável | Só o ícone clicável; ações dentro do button |
| AC5 | Régua de uso: nunca esconder conteúdo crítico ao fluxo; <~4 seções → headings simples; usuário precisa da maioria → tudo aberto; **"Mostrar tudo/Ocultar tudo" a partir de 4 seções** (omitido em coluna estreita); fronteiras declaradas: tabs/stepper→B5, tree view→fora; **nesting proibido** (eco CD5) | NN/g/ONS/Bristol; GOV.UK (Show all); Fiori (nesting not recommended) | Accordion para conteúdo essencial; accordion dentro de accordion |
| AC6 | Tudo fechado por default; variante "aberto por default" quando o conteúdo é primário; animação ~200ms ease-out com `prefers-reduced-motion` desligando (chevron 180ms); divider entre itens via CSS herdando DV1 (zero `<hr>` na árvore); **persistência entre visitas = decisão de produto/Lovable** (mesmo tratamento da CN no marco v1.0) | Fiori (fechado default); ONS (`open:true`); shadcn/Radix (animação); GOV.UK (`rememberExpanded` registrado, não adotado no DS) | Persistência no DS |

### 29.2 Anatomia e tokens

Container herda card outlined (§26: `--seed-card-bg` + borda decorativa declarada) · item+item separado por `--seed-divider-color` via CSS · botão 48px min, padding 12/16, hover `--seed-list-hover` · chevron 20px `text-muted` — **par já medido no F: cinza-600/branco 4.74 AA light · cinza-300/raised 7.95 AAA dark** (indicador de estado, 1.4.11 ✓) · meta JetBrains Mono `text-muted` · foco: anel interno (inset) para não vazar do container. **Nenhum par de cor novo neste sub-bloco.**

### 29.3 Os 7 testes

1. Pares reusados já medidos ✓ (nenhum número novo sem medição). 2. Estado por chevron rotacionado + `aria-expanded`, nunca só cor. 3. Grayscale ✓ (provado no preview). 4. Multi × exclusivo a 1s (seções abertas simultâneas). 5. Regra do um: um estilo de accordion por produto (3.2.4). 6. Aberto/fechado visível sem interação (chevron). 7. 360px: linha 48px inteira clicável; "Mostrar tudo" omitido em coluna estreita. *(Validação executada 213/213 + visual aprovada em 2026-08-05.)*

---

## 30. Modal — `estável` (v0.30) · MD1–MD5 · validação executada 230/230 + visual aprovada em 2026-08-05 (1 ciclo de crítica)

> **Base (3 rodadas, 2026-08-05):** R1 — APG dialog (trap, foco inicial: título tabindex=-1 quando conteúdo longo; alertdialog; exemplo oficial usa 100% da tela no mobile), NN/g ("diálogos curtos e diretos; nunca quando a decisão depende de informação bloqueada"), Smashing decision tree 2026 (evite bloquear; drawer antes de modal), MoJ/GOV.UK (GOV.UK não tem modal no DS; MoJ adotou p/ usuários profissionais com foco no 1º campo, validado em pesquisa). R2 — **MDN showModal/::backdrop (primárias: Baseline "widely available" mar/2022; top layer; inert automático; retorno de foco nativo)**, Radix Dialog (stack), artigo top-layer (LIFO). R3 — **Scott O'Hara "Use the dialog element (reasonably)" (primária: o autor do veto de 2019 liberou o nativo)**, accessuse.eu, CSUN 2019 (foco inicial).

### 30.1 Decisões MD1–MD5

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| MD1 | Base: **`<dialog>` nativo + `showModal()`**; React = Radix Dialog (Lovable). Top layer (fim do z-index), `::backdrop`, fundo `inert` (teclado E leitor), Esc e retorno de foco de graça. `role="alertdialog"` p/ confirmação destrutiva/erro bloqueante | O'Hara liberou o nativo (2023); MDN Baseline desde mar/2022 | div+ARIA custom (custo sem ganho hoje); `show()` p/ modal ("modal quebrado": sem trap/backdrop/inert) |
| MD2 | **Régua fecha os 3 degraus (§23.1): modal SÓ p/ decisão obrigatória, confirmação destrutiva ou tarefa curta e focada.** Regra inversa: tarefa longa ou que exige consultar a página → drawer ou página | NN/g; Smashing 2026; GOV.UK nem tem modal (sinal de parcimônia) | Modal como contêiner conveniente (treina fechar por reflexo) |
| MD3 | Esc + X visível sempre; **backdrop fecha SÓ modal sem formulário** (com form: não — perda de dados); foco inicial: 1º campo em form (MoJ) · título `tabindex="-1"` se conteúdo longo (APG) · **botão NÃO-destrutivo em confirmação destrutiva** (APG); retorno ao trigger nativo; nome via `aria-labelledby` obrigatório | APG + MoJ + accessuse.eu | Fechar só no X; foco inicial no botão destrutivo |
| MD4 | Tamanhos sm (confirmação) / md (form curto) com max-height + scroll no body; **<640px modal de tarefa ocupa a tela** (APG: esconde movimento de fundo do mobile); confirmações seguem centradas. **Empilhamento proibido** — 1 por vez; exceção única: confirmação de descarte sobre form sujo (replace, não pilha) | APG example; top layer é LIFO mas pilha quebra contexto/AT | Pilha de modais |
| MD5 | 3 zonas: header fixo (título+X) · body scrollável · footer fixo (ações). Tudo dentro herda: form→B2, espera→§18/§19, erro→§15, ações→B1. Nenhum componente nasce dentro do modal | EUI (pinned header/footer) | Modal com anatomia própria |

### 30.2 Tokens

Superfície `--seed-surface-overlay` + `--seed-shadow-overlay` (pares §2.8/§17 já medidos) · radius 12 (herda card) · **backdrop rgba(36,46,52,.5) light / rgba(0,0,0,.6) dark — DECORATIVO declarado** (a separação modal↔página é dada por inert+trap+sombra+deslocamento; o escurecimento é reforço, nunca canal único) · animação 160ms ease-out com `prefers-reduced-motion` desligando. **Nenhum par de texto novo.**

### 30.3 Os 7 testes

1. Pares reusados ✓; backdrop decorativo declarado. 2. Modalidade sinalizada por trap+inert+sombra, não só escurecimento. 3. Grayscale ✓ (sombra+borda de zona carregam). 4. Confirmação × tarefa a 1s (sm centrado × zonas com form). 5. Um modal por vez (MD4). 6. Aberto é auto-evidente. 7. 360px: tarefa em tela cheia, X de 44px, footer acessível ao polegar. *(Validação executada 230/230 + visual aprovada em 2026-08-05.)*

---

## 31. Drawer — `estável` (v0.30) · DW1–DW4 · validação executada + visual aprovada em 2026-08-05 · destrava a central de notificações (F6)

> **Base:** R1/R2 — **EUI flyout (referência ERP: "alternativa a modal quando a ação não é transiente; forms complexos sem perder o estado"; `ownFocus` espelha os dois regimes; header/body/footer pinned)**, M3 side sheets (standard × modal), Radix Sheet (=Dialog com `side`), Vaul. R3 — M2 bottom sheets (descartado como forma mobile do drawer), MDN show()/showModal().

### 31.1 Decisões DW1–DW4

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| DW1 | Drawer = superfície lateral p/ contexto/tarefa SEM perder o estado da página, em **2 regimes: modal-drawer (default: backdrop+trap) e standard (não-modal: página interativa)**. CN consome drawer; o regime dela é decisão da spec da CN (F6) | EUI ownFocus; M3 standard×modal side sheets; caso central: detalhe de registro do ERP e comparação lado-a-lado | Regime único |
| DW2 | Direita default; larguras sm/md; teto ~50% da viewport desktop; **<640px vira painel de largura TOTAL deslizando da direita** (continuidade espacial; safe-area) | Não colide com o tray §24 (degrau do popover p/ conteúdo curto) | Bottom sheet como forma mobile do drawer (criaria 2 destinos verticais concorrentes p/ o polegar: tray × drawer) |
| DW3 | Implementação: **o mesmo `<dialog>` posicionado** — modal=`showModal()`; standard=`show()` (corretamente sem trap/backdrop; Esc fecha com foco dentro); scroll lock só no modal. React: Radix Sheet | MDN (show × showModal é exatamente a distinção modal×não-modal) | Biblioteca própria de posicionamento |
| DW4 | Estrutura herda MD5 (3 zonas) e os empréstimos dos Blocos 1–3; **form longo prefere drawer a modal** (EUI) | EUI | Anatomia própria |

### 31.2 Tokens

Herda §30 integralmente (superfície/sombra/backdrop decorativo declarado); largura sm 400 / md 560; slide 200ms ease-out com reduced-motion; `--seed-safe-bottom` no rodapé mobile. **Nenhum par novo.**

**Escala de sobreposição (emenda do gate visual, 2026-08-05):** `z-index` explícito **65** em modal e drawer, consolidando a escala do sistema: toolbar/app-chrome 60 < **modal/drawer 65** < toast 70 (feedback de ação permanece visível sobre a superfície) < popover 75 (pode abrir de dentro de modal/drawer) < tooltip 80. O 65 governa o regime standard (`show()` fica FORA do top layer) e qualquer chrome fixo da página; no browser, `showModal()` vive no top layer nativo, acima de tudo — e toasts disparados com modal aberto gravam na central de notificações (decisão do marco v1.0). **Origem: bug real pego na validação visual do Rafael** — drawer standard sem z-index era atravessado por conteúdo da página e tinha o X encoberto pelo chrome fixo; a suite não cobre empilhamento (jsdom sem layout), reconfirmando a lição do marco v1.0.

### 31.3 Os 7 testes

1–2. Idem §30 ✓. 3. Grayscale ✓. 4. Modal-drawer × standard a 1s (backdrop presente × página viva). 5. Um drawer por vez; drawer e modal não empilham (a exceção MD4 é só confirmação de descarte). 6. Regime evidente sem interação. 7. 360px: largura total, X 44px, footer no polegar com safe-area. *(Validação executada + visual aprovada em 2026-08-05, com emenda de empilhamento originada no gate visual.)*

---

## 32. Avatar — `estável` (v0.32) · AV1–AV5 · validação executada 246/246 + visual aprovada em 2026-08-05 (1 ciclo de crítica) · Bloco 4 fechado 7/7

> **Base (3 rodadas, 2026-08-05):** R1 — Atlassian/AUI (avatar de pessoa E entidade — "projetos, repositórios, espaços"; badge sobreposto exige descrição textual; "quando agrupado com texto que o descreve, é efetivamente decorativo"), Polaris (escala de tamanhos; formatos de alt: nome da pessoa/empresa; `alt=""` com nome adjacente), Zuora (forma consistente por produto). R2 — Radix Avatar (Image só renderiza carregada; Fallback com `delayMs` anti-flash ~600ms), shadcn (AvatarGroup/AvatarGroupCount; "parseie nomes direito: John Smith → JS, não JO"), Radix Themes (ícone como fallback final). R3 — **W3C WAI decorative images (primária: alt repetindo nome adjacente = "ruído audível")**, eBay evo (informativo `role="img"`+`aria-label` × decorativo sem role/label; `img` interno sempre `alt=""`; avatar nunca focável por si), EUI (iniciais inteligentes máx. 2; `type` user×space; contraste gerenciado).

### 32.1 Decisões AV1–AV5

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| AV1 | **Fallback em cadeia: imagem → iniciais → ícone genérico.** Imagem só renderiza carregada; fallback com atraso ~600ms anti-flash. **Iniciais PT-BR: 1ª do primeiro + 1ª do ÚLTIMO nome, máx. 2, maiúsculas, ignorando partículas (da, de, do, dos, das, e)** — "João da Silva"→JS. Ícone só sem nome | Radix/shadcn; regra de partículas é nossa (sem equivalente no canon — PT-BR first) | 2 primeiras letras do primeiro nome |
| AV2 | **Cor: NEUTRO ÚNICO** (fundo cinza de superfície + iniciais em texto), sem hash de cor por nome. Pares MEDIDOS: iniciais **5.43 AA** light (ci-700/ci-100) · **8.07 AAA** dark (ci-100/#38464F); ícone 3.93/8.07 ✓ | T1–T5 reservou cores p/ severidade — avatar vermelho ao lado de badge §21 real fabricaria falsa leitura de status no ERP denso; grayscale nasce ✓ | Hash de cor (EUI dataviz); cores de severidade; turquesa de fundo (70/20/10) |
| AV3 | **Forma distingue o quê: círculo = pessoa · quadrado arredondado (radius 8) = entidade** (UC, empresa, projeto), consistente por produto. Tamanhos: **sm 24 · md 32 (= slot leading da lista, LS2) · lg 40 · xl 64** | Atlassian/EUI (user×space); Zuora (consistência); pessoa×UC é informação real de varredura no ERP | Forma única |
| AV4 | **Nunca focável nem interativo por si; decorativo é o caso dominante:** nome adjacente (lista/comentário/header) → `img alt=""`, sem role/label (o texto carrega). Informativo só quando sozinho → `role="img"`+`aria-label` com o NOME. Badge sobreposto exige descrição textual do estado. Interação pertence ao pai (linha/botão) | W3C WAI (primária); eBay evo; Polaris; AUI | `alt` genérico ("avatar do usuário" — pior que vazio); avatar-link |
| AV5 | **Grupo/stack: anel da superfície (decorativo), máx. 3 visíveis + contador "+N" REAL** — botão com rótulo ("mais N pessoas") + `aria-expanded`, herdando o padrão de overflow do W7 (§22): abre popover §24 com lista §28 contendo **TODOS os nomes do grupo** (não só os ocultos) e com light-dismiss integral (clique-fora e Esc, com retorno de foco ao +N); os empilhados ficam `aria-hidden` porque os nomes vivem na lista — o que EXIGE lista completa. **Emenda do gate visual (2026-08-05, crítica do Rafael):** a 1ª versão listava só os ocultos, quebrando a própria racional da decisão (leitor de tela nunca receberia os 3 primeiros nomes — WAI: experiência equivalente = os nomes) e omitia o clique-fora que o §24 já exige; rótulo do +N corrigido para "Ver todas as N pessoas" | shadcn AvatarGroup/Count; composição pura W7→§24→§28 — nada novo nasce | Stack infinito; +N texto morto; **lista só dos ocultos (emenda)** |

### 32.2 Anatomia e tokens

Fundo `--seed-cinza-100` / dark `#38464F` (o mesmo par de superfície neutra do skeleton §19) · texto ci-700 / ci-100 (pares acima) · pessoa `border-radius:50%` · entidade radius 8 · tamanhos 24/32/40/64 com fonte 10/12/14/22 · anel do stack = 2px da superfície do card (decorativo declarado — a contagem real está no +N) · sobreposição −8px · ícone genérico 60% do container. Fora do DS: upload/crop → produto · skeleton circle → §19. **Presença/status online é DENTRO do DS desde 2026-08-14 (SUP-2, dispositivo C2 do confronto):** o mecanismo é declarado em atributo (`data-estado`), consome os tokens de feedback existentes e obedece à regra do ouro (sem fill em elemento colorido pequeno e isolado) — **anel obrigatório**. *Registro honesto: uma fronteira `estável` deste § foi cruzada por artefato (MANIFESTO v6.4) sem supersede escrito; este texto corrige o registro, não muda a decisão. Alternativa descartada: reverter o C2 — o mecanismo já está implementado, medido, e presença sem contrato produziria uma bolinha verde diferente em cada produto da SEED.*

### 32.3 Os 7 testes

1. 4 pares medidos ✓ (5.43/8.07 texto; 3.93/8.07 ícone). 2. Pessoa×entidade por FORMA, não cor. 3. Grayscale: neutro nasce ✓. 4. Pessoa × entidade × contador a 1s (círculo × quadrado × borda+mono). 5. Regra do um: máx. 3 no stack, resto no +N. 6. n/a (não-interativo; +N tem estados de botão §1). 7. 360px: md 32 no slot da lista; +N é alvo de 32 com hitbox herdada — único interativo do conjunto. *(Validação executada 246/246 + visual aprovada em 2026-08-05, com emenda AV5 originada no gate visual.)*

---

## 33. Central de Notificações — `estável` (v0.34) · CN5–CN12 · pattern F6 · suite `suite-cn.mjs` 45/45 ✓ · regressão A–F 291 verdes · gate visual aprovado 2026-08-06

> **Natureza:** pattern de COMPOSIÇÃO do F6, não componente novo — consome sino (icon-button §1) + badge dot (§21) + popover/tray (§24) + drawer (§31) + lista com os slots LS5 (§28) + empty esvaziado (§25) + toast (§17) + severidades T1–T5. As regras do marco v1.0 (CN1–CN4) permanecem intactas e NÃO foram re-pesquisadas: toasts de BACKGROUND gravam no centro, de primeiro plano não; régua editorial "só o que o usuário RESOLVE, nunca marketing"; persistência/tempo-real/preferências = produto (Lovable/Supabase, CN4).
>
> **Base (3 rodadas encadeadas, 2026-08-06, ~26 fontes):** R1 — **Carbon notifications pattern (íntegra: painel usado em conjunto com toasts; cronológica; nunca reenviar; WCAG 2.2.3/2.2.4 AAA; Carbon ainda não estabilizou o componente — sinal de pattern)**, **PatternFly notification badge + drawer v3–v6 (a spec mais completa do canon: unread por peso bold; mark all read; clear all com ressalva "varia por produto"; ≤3–4 categorias; badge attention só p/ erro crítico)**, NN/g indicators/notifications, Smashing 2025 (fadiga), UX Mag (modelo âncora), ui-patterns, evidência de campo Atlassian community (2 dores documentadas: badge que zera na abertura faz o usuário esquecer pendências; mark-all-read que não persiste destrói a confiança). R2 — EUI Header (`EuiHeaderSectionItemButton` com prop `notification` dot/contador; lista de alerts em **EuiPopover OU EuiFlyout** — exatamente os 2 degraus da CN) + issue #4257 (flyout como MVP do notification center), Knock (modelo unseen→seen→read; badge default unseen como anti-ruído de feed; filtro All/Unread; reverse chronological), Novu (seen one-way por IntersectionObserver; **#955: mark-all-read desabilitado sem itens**; #4846 flicker de estado). R3 — **SAP Fiori Notifications (cronológica por timestamp "como um inbox"; agrupamento por tipo é recurso de ALTO volume com bulk por grupo; maior prioridade do item define a do grupo; título 2 linhas + Show More; DISMISS ≠ PROCESSADO)**, Sara Soueidan live regions partes 1–2 (região nomeada dá contexto à contagem; live region não carrega rich text), ariaNotify/AOM (limitações estruturais — contexto), APG feed pattern (proposta SEM consenso do task force desde ARIA 1.1 — descartada), APG alertdialog (aria-modal só quando o conteúdo externo está de fato bloqueado), Harvard DAS (não mover foco em atualização não-crítica). Consolidado aprovado pelo Rafael em 2026-08-06.

### 33.1 Decisões CN5–CN12

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| CN5 | **Arquitetura em 2 superfícies:** sino no chrome (z 60) abre o **painel rápido** = popover §24 (tray <640px por herança Y4) com as **5 mais recentes** + header + footer "Ver todas (N)" → **drawer §31** com a lista completa. O toast (CN2) segue sendo o anúncio de chegada; o painel nunca re-anuncia o que o toast já falou | EUI: popover OU flyout do mesmo botão de header; Carbon: painel *em conjunto* com toasts; 5 itens mantêm o popover sem scroll (degrau leve do §23.1) | Superfície única (popover com scroll infinito = drawer disfarçado); página dedicada como destino primário (perde o estado que motivou o drawer) |
| CN6 | **Drawer da CN em regime STANDARD** (`show()`, não-modal, página interativa) — **resolve o ponto em aberto do DW1**. Esc fecha; foco entra no X ao abrir e **retorna ao SINO ao fechar** (âncora estável — o trigger "Ver todas" morreu junto com o popover); z 65 (emenda v1.1); <640px largura total (DW2), regime inalterado | O caso de uso É consulta lado-a-lado (ler → navegar à fonte → comparar) — o caso central que criou o standard no DW1; PatternFly: drawer de notificações = "ver a lista **sem sair da tela atual**"; APG: aria-modal só quando o fundo está de fato bloqueado — trap aqui seria mentira semântica | Modal-drawer (trap sem tarefa que o justifique); regime por prop do consumidor (regra do um: a CN tem UM comportamento) |
| CN7 | **Badge do sino reflete NÃO-LIDA, nunca "vista": abrir o painel NÃO limpa o badge.** Limpa por leitura item a item ou "Marcar todas como lidas". Dot default; contador e vermelho seguem CN1/V5. **O conceito "vista/seen" NÃO existe no DS** | Dor real (Atlassian community): badge que zera na abertura → usuário esquece pendências; PatternFly: unread no badge só quando existe marcar-como-lida E chegada é infrequente — a régua editorial CN garante ambas; Knock admite que unseen é otimização de feed social — o nosso contexto é ERP acionável | Modelo seen/unseen (Knock default) — certo p/ feed de marketing, errado p/ pendência de trabalho |
| CN8 | **Semântica de lida:** navegar à fonte (ação primária da linha) marca lida; ação secundária por item = toggle "Marcar como lida/não lida"; **"Marcar todas como lidas"** no header das 2 superfícies, **desabilitado sem não-lidas**. **Sem "Limpar tudo" e sem dispensar destrutivo** — item sai quando o produto o considera resolvido (CN4). **Lida ≠ resolvida: itens lidos permanecem** | PatternFly: clicar na linha = lida; Novu #955: mark-all desabilitado sem itens; Fiori: dismiss ≠ processado — e a régua CN é "só o que o usuário resolve", então sobrar até resolver é o correto; Atlassian: mark-all-read que não persiste é a quebra de confiança nº 1 (persistência é dura — CN4) | "Clear all" (risco de descartar pendência acionável); dispensar com undo (fila de undo sem demanda) |
| CN9 | **Ordenação cronológica inversa, SEM agrupamento por categoria na v1.** Severidade T1–T5 no leading já diferencia. Fronteira declarada: se o volume crescer, a 1ª evolução é agrupar por DATA (Hoje/Anteriores), nunca por tipo | Carbon: cronológica; Fiori: timestamp é o default ("como um inbox"), agrupamento é recurso de alto volume — a régua editorial mantém o volume baixo por construção; PatternFly limita a 3–4 categorias (o custo do agrupamento) | Agrupamento por tipo/prioridade na v1 (complexidade de launchpad corporativo sem demanda); ordenar por prioridade (esconde o recente) |
| CN10 | **Item = lista §28 com os slots LS5, sem slot novo:** ícone T1–T5 no leading · título ≤2 linhas com wrap · **timestamp relativo em JetBrains Mono no trailing com absoluto acessível (`<time datetime>` + `title`)** · não-lida por peso 700 + dot 8px vermelho-500 · live region única do grupo (1.13-b). **Chegada com painel ABERTO: prepende + live region polite nomeando ("Nova notificação: …"), SEM toast; painel FECHADO: o toast é o anúncio (CN2) — nunca os dois** | LS5 nasceu para isso (zero re-spec); Fiori/Carbon: 2 linhas; Soueidan: live region nomeada dá contexto; anúncio duplicado = dupla fala (regra do um aplicada ao canal sonoro) | `role="feed"` do APG (proposta sem consenso do TF desde 2017, suporte AT irregular, e jogaria fora a lista §28 já validada); timestamp só relativo (inacessível p/ auditoria e AT) |
| CN11 | **Composição dos estados (já decididos — amarração):** vazio = empty §25 tipo *esvaziado*, "Tudo em dia" (EEny permitido, Z5), sem live region própria; erro de carga = slot `role="alert"` pré-existente com ação de recuperação (herda S5/Z via LS5); loading = Régua de Espera (carga inicial → skeleton imediato) | Tudo herdado; a decisão é declarar a composição | — |
| CN12 | **Acessibilidade do sino:** icon-button §1 com **nome acessível dinâmico** — "Notificações" / "Notificações, N não lida(s)" — + `aria-expanded` + `aria-haspopup="dialog"`. Badge dot/contador **decorativo declarado** (o número vive no nome). Mudança de contagem NÃO anuncia — a chegada já foi anunciada pelo toast (T5). **Nomenclatura anti-colisão: as superfícies da CN chamam-se "Central de notificações"** — a landmark "Notificações" pertence à região de toasts (§17) e nomes acessíveis duplicados confundiriam a navegação por landmarks | Soueidan (o nome carrega o contexto); Harvard DAS (não interromper por atualização não-crítica); a colisão de nomes foi detectada na composição — registro para todo consumidor | Live region no badge (dupla fala com o toast); contagem só visual (AT sem o número); as duas superfícies chamadas "Notificações" (colisão com §17) |

### 33.2 Anatomia, tokens e fronteiras

Painel rápido: popover §24 (surface-overlay + shadow-overlay), largura ~380px desktop, header com título + ghost "Marcar todas como lidas", máx. 5 linhas, footer "Ver todas (N)" full-width ≥44px. Drawer: §31 integral em standard (sm 400; <640px total; z 65; X 44px). Linha: §28 densidade default (≥44px). Empty/erro/skeleton: §25/S5/§19. **Nenhum par de cor novo — todos os pares consumidos já foram medidos nos §17/§21/§24/§25/§26/§28/§30–§31** (primário 13.86/14.16 · secundário 6.55/8.92 · meta mono 4.74/7.95 · hover 12.75 · severidades T1/errata azul-800 · dot vermelho-500 decorativo-redundante ao peso). Fora do DS (reafirmado): preferências de notificação · push/e-mail · o que conta como "resolvido" · persistência/tempo-real (CN4) · agrupamento por data (fronteira de evolução, CN9) · tabela de notificações (B6).

### 33.3 Os 7 testes

1. Nenhum par novo; herdados medidos ✓. 2. Não-lida = peso+dot (nunca só cor); severidade = ícone+cor (T3). 3. Grayscale: peso e ícones carregam (provado no §28/§15). 4. Painel rápido × drawer a 1s (popover ancorado 5 itens × superfície lateral com lista completa). 5. Regra do um: uma live region nomeada por grupo; um canal de anúncio por chegada (toast OU live region). 6. Não-lidas visíveis sem interação (badge + peso + dot). 7. 360px: tray herdado do §24, drawer largura total, linhas ≥44px, X 44px, hitbox estendida no marcar-como-lida. *(Suite `suite-cn.mjs` 45/45 ✓; regressão A–F byte-perfeita = 291 verdes acumulados; gate visual do Rafael aprovado em 2026-08-06 — empilhamento renderizado, geometria do popover, `show()` nativo, tray 360px, dark e grayscale.)*

---

## 34. Tabs — `estável` (v0.36) · TB1–TB8 · Bloco 5/5A · suite `suite-navegacao.mjs` 27/27 ✓ · regressão 318 verdes · gate visual aprovado 2026-08-06

> **Base (3 rodadas encadeadas, 2026-08-06, ~24 fontes):** R1 — **GOV.UK Tabs (íntegra: usar para usuários frequentes de sistema; NUNCA navegação de página; fragmento na URL guarda a aba e o back funciona; do not disable; sem quebra de linha; heading no painel duplicando o rótulo; matriz tabs×accordion×details)**, **NN/g "Tabs, Used Right"** (alternar vistas do MESMO contexto; 1 linha; rótulos claros; ícone sempre com rótulo), **NN/g "Tabs vs. Accordions"** (tabs = poucas seções longas; accordion = muitas curtas), ONS (nada crítico escondido em aba), Carbon usage+accessibility (rolagem com setas; 2 tab-stops; wrap; line×contained), Spectrum tabs-overflow (não truncar rótulo). R2 — Radix Tabs (`activationMode`; shadcn herda), React Aria (`keyboardActivation`), issue Radix #2915 (espaço em modo manual — bug de campo), comparativo MUI/PrimeReact/Radix/React Aria (anatomia converge no APG). R3 — **APG Tabs Pattern (íntegra: automática recomendada com painéis pré-carregados sem latência; manual quando não — o critério é latência; teclado completo; tabpanel `tabindex=0` sem focável inicial)**, AcceDe Web, a11y-collective (manual quando a troca tem consequência pesada), **Fiori Icon Tab Bar (contador inline "(N)" após o rótulo, nunca acima; aba-filtro exige contador; overflow em menu; title case; cor semântica nunca decorativa)**, Inclusive Components (base do GOV.UK). Consolidado aprovado pelo Rafael em 2026-08-06.

### 34.1 Decisões TB1–TB8

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| TB1 | **Papel e fronteira tríplice — RESOLVE AC5:** tabs alternam vistas do MESMO contexto, mesma hierarquia, sem ordem. Régua: poucas seções longas + troca rápida + 1 por vez = **tabs §34** · muitas seções curtas / leitura em ordem / mais de uma aberta = **accordion §29** · conteúdo a COMPARAR = não esconda · passos sequenciais = **stepper §38** · trocar de página = **shell F6 (link + `aria-current`), nunca tabs** | NN/g vs-accordions; GOV.UK (matriz + "não use como navegação"); subux | Escolha livre do consumidor (o AC5 existia por falta de régua) |
| TB2 | **Anatomia APG integral:** `tablist` nomeado + `tab` button (`aria-selected`, `aria-controls`) + `tabpanel` (`aria-labelledby` recíproco, `tabindex=0` quando não abre com focável). **Deep-link recomendado**: aba corrente no fragmento da URL nas páginas de detalhe do ERP (back/refresh funcionam); mecânica de rota = produto/Lovable | APG; GOV.UK (fragmento + back — dor real de sistema interno) | `role="tab"` em link de navegação (mentira semântica) |
| TB3 | **Dois regimes de ativação com régua objetiva:** **automática** (default) quando o painel está pré-carregado; **manual** OBRIGATÓRIO quando o painel carrega sob demanda (setas movem foco; Enter/Espaço ativa). Painel lazy entra com skeleton (Régua de Espera). O critério é latência, nunca gosto | APG literal; React Aria/Radix expõem exatamente os 2 modos | Regime único; escolha estética por produto |
| TB4 | **Teclado APG:** 1 tab-stop (roving: só a selecionada `tabindex=0`); ←→ com wrap; Home/End; Tab sai para o painel. **Vertical fora do escopo v1** (fronteira: `aria-orientation` + ↑↓ se surgir demanda) | APG + Carbon | Vertical na v1 (sem demanda) |
| TB5 | **Variante única: LINHA** — selecionada com indicador 3px `action-primary` + peso 700 + cor; não-selecionada peso 600 `text-secondary`. Três sinais; grayscale sobrevive por barra+peso | Regra do um; contained do Carbon serve à grade IBM, sem paralelo aqui; escada de mecanismos do B1 | Variante contida (emenda futura com pesquisa própria, se houver demanda); selecionada só por cor |
| TB6 | **Overflow por ROLAGEM** com fade + setas no desktop (visíveis só com overflow; `tabindex=-1` — a navegação primária é pelas setas do teclado no tablist); toque = momentum nativo. **Rótulo nunca trunca nem quebra linha.** Editorial: ≤6 abas | Carbon+Fiori (rolagem); Spectrum (não truncar); GOV.UK/NN/g (multi-linha = complexidade excessiva) | Menu "Mais" do Fiori (esconde abas; rolagem mantém tudo no polegar); truncamento+tooltip do Carbon (rótulo ilegível é falha editorial) |
| TB7 | **Conteúdo:** rótulo 1–2 palavras title case (nunca caixa alta); ícone opcional SEMPRE com rótulo; **contador inline via badge §21 obrigatório em aba-filtro de lista** (decorativo declarado; o total pertence ao conteúdo do painel); **heading no início de cada painel duplicando o rótulo** | Fiori (inline, nunca acima; filtro exige contador; title case); NN/g; GOV.UK (heading no painel) | Contador acima do rótulo; ícone-só |
| TB8 | **PROIBIDO desabilitar aba** — sem estado disabled na spec: remova a aba ou explique a ausência dentro do painel. **Divergência declarada com Carbon** | GOV.UK; coerência com a linha do DS desde B1/B2 (desabilitado esconde o porquê) | disabled à la Carbon (beco sem saída) |

### 34.2 Anatomia, tokens e fronteiras

Aba: `min-height` 44px, padding 10/14, radius 6 no topo; **anel de foco interno (offset -2px — errata: containers com overflow recortam anel externo)**; indicador 3px `action-primary` sobre a borda inferior do tablist (`border-subtle`). Selecionada: `text-primary` 700; repouso: `text-secondary` 600; hover: `list-hover`. Contador: badge §21 neutro em JetBrains Mono. Skeleton: §19. **Nenhum par novo** — indicador tq-600/branco 4.60 (não-textual ≥3:1 ✓) e tq-300/dark-page 9.32; textos ci-900/ci-700/branco e ci-100/ci-300/dark já medidos (§26/§28). Fora do DS: shell de navegação (F6) · roteamento/keep-alive do estado do painel (produto) · abas fecháveis/reordenáveis (sem demanda; se surgir, pesquisa própria — APG aria-actions ainda experimental) · orientação vertical (fronteira TB4).

### 34.3 Os 7 testes

1. Nenhum par novo; herdados medidos ✓. 2. Selecionada = barra+peso+cor (nunca só cor). 3. Grayscale: barra+peso carregam. 4. Automático × manual a 1s (o skeleton denuncia o lazy). 5. Regra do um: uma variante, um indicador, um tab-stop. 6. Estado visível sem interação (indicador + peso). 7. 360px: alvos 44px, rolagem por toque, rótulos íntegros. *(Suite 5A 27/27 ✓, console limpo; regressão total 318 verdes; gate visual do Rafael aprovado em 2026-08-06 — overflow/rolagem, indicador renderizado, grayscale, skeleton, deep-link em browser real.)*

---

## 35. Menu / Dropdown — `estável` (v0.38) · MN1–MN8 · Bloco 5/5B · suite `suite-navegacao.mjs` 58/58 ✓ · `contraste-navegacao.py` ✅ · regressão 349 verdes · gate visual aprovado 2026-08-06

> **Base (3 rodadas encadeadas, 2026-08-06, ~24 fontes):** R1 — **NN/g Menu-Design Checklist** (hover-open frustra; Fitts; 1 nível; **"gray out em vez de remover + explique o porquê"**), **NN/g Dropdowns** (comando ≠ seleção de atributo; Mac HIG proíbe dropdown-box p/ comandos), NN/g listbox×dropdown, NN/g expandable menus, **Carbon Menu** (menu = ações; form/filtro = dropdown; destrutivo separado no FIM; atalhos exibidos), Carbon Overflow Menu usage+a11y, **Polaris ActionList** (dentro de popover; **verbo+substantivo**; destructive; seções; help text). R2 — Radix DropdownMenu (menu button APG 1:1: roving, typeahead, checkable, Esc — shadcn herda), **Wootonn** (setas do menu × cursor do input = conflito estrutural de ARIA; saída é dialog/popover), Ariakit. R3 — **corpus Adrian Roselli 2017–2026** ("Don't Use ARIA Menu Roles for Site Nav" / "Be Careful Using 'Menu'" / "Link + Popover Navigation") **+ APG Disclosure Navigation Menu (Higley)**: navegação usa link+disclosure, nunca roles de menu — posição hoje do próprio APG; Terrill Thompson (1ª regra do ARIA); Piccalilli; **Fiori Menu Button + Action Sheet** (menu button aposentou o action sheet; 1 ação = botão; nunca ícone-só; importância top-down). Consolidado aprovado pelo Rafael em 2026-08-06.

### 35.1 Decisões MN1–MN8

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| MN1 | **Fronteira semântica de 4 saídas:** itens executam AÇÃO = **menu §35** (`role="menu"`, APG menu button) · itens NAVEGAM = **disclosure de links** (botão `aria-expanded` + popover §24 com `<a>`, zero roles de menu — o shell F6 consome isto; Tab navega E ↓↑ opcionais do APG sem wrap) · itens selecionam VALOR = select/combobox (B2) · conteúdo rico/inputs = popover §24. **PROIBIDO input dentro de `role="menu"`** | Roselli→APG disclosure nav; NN/g/Mac HIG; Carbon; Wootonn (conflito de setas) | role=menu p/ navegação (anti-padrão testado); componente "faz-tudo" |
| MN2 | **Trigger:** botão §1 + `aria-haspopup="menu"` + `aria-expanded` + caret; caso nomeado **overflow "⋯"** (icon-button nomeado). **1 ação = botão simples.** Split button fora da v1 (fronteira) | Carbon/Fiori; sem demanda p/ split | Split na v1; abrir por hover (NN/g) |
| MN3 | **Teclado APG integral:** Enter/Espaço/↓ abrem focando o 1º; ↑ abre no último; setas com wrap; Home/End; **typeahead**; Esc fecha→trigger; **ativar AÇÃO executa, fecha e devolve o foco ao trigger** | APG; Radix 1:1 | Subconjunto sem typeahead |
| MN4 | **Itens:** `menuitem` verbo+substantivo; ícone sempre com rótulo; atalho no trailing (JetBrains Mono + `aria-keyshortcuts`); **`menuitemcheckbox`/`menuitemradio`** p/ opções de exibição — **alternáveis PERMANECEM abertos ao alternar** (opção ≠ ação; ajustar 2+ opções sem reabrir) | Polaris; Fiori; Carbon; Radix (padrão preventDefault em checkable é prática da stack) | Checkable fora da v1; fechar ao alternar (pune o segundo ajuste — Fitts) |
| MN5 | **Organização:** grupos `role="group"` nomeados; separadores (divider §27); **destrutivo SEMPRE no fim, após separador, vermelho interface**; importância top-down; **máx ~7 itens, 1 nível — SUBMENU PROIBIDO na v1** (fronteira: demanda real → pesquisa própria) | Carbon/Polaris; NN/g (2 níveis frustram; Fitts); vermelho interface-only | Submenu; destrutivo no topo |
| MN6 | **Item indisponível — EXCEÇÃO DOCUMENTADA da linha "sem disabled":** visível, `aria-disabled="true"`, **navegável pelas setas** (descobrível por AT), texto `text-muted` deliberadamente legível (4.74 — não usamos a isenção da WCAG), **motivo em help text**. Critério da exceção: menu é INVENTÁRIO de comandos — remover muda o mapa mental; a aba (TB8) não é inventário | NN/g literal; APG aria-disabled focável | Remover o item (NN/g); `disabled` nativo fora das setas (invisível p/ AT — divergência declarada com Carbon) |
| MN7 | **Superfície:** popover §24 (z 75, light-dismiss, flip); largura mín. 200px; itens ≥44px; **<640px tray por herança Y4, sem regra nova**; ativou ação → fecha; feedback (toast) é do consumidor | Carbon; §24 resolveu; Fiori (superfície plena no phone) | Action sheet como componente (Fiori o aposentou) |
| MN8 | **Sem live region própria** — estado vive nos roles; anúncio do resultado pertence ao consumidor | Regra do um no canal sonoro (linha CN10/CN12) | Live region de abre/fecha (dupla fala) |

### 35.2 Anatomia, tokens e fronteiras

Trigger: botão §1 (qualquer variante) + caret rotativo. Painel: overlay branco/#273137, radius 10, sombra overlay, padding 6. Item: 44px, radius 6, ícone 18px decorativo, rótulo 600, atalho mono ci-600/ci-300, check `action-primary` (alternáveis). Destrutivo: `--seed-danger-item` **ve-700 light / ve-200 dark**. **Pares novos medidos (`contraste-navegacao.py` ✅): 7.18 · 6.61 · 8.67 · 4.74 · 6.94.** Fora do DS: split button (fronteira MN2) · submenu (MN5) · context menu por clique-direito (sem demanda; se surgir, herda esta spec com trigger próprio) · navegação do shell (F6, consome a saída disclosure do MN1).

### 35.3 Os 7 testes

1. Pares novos medidos ✅. 2. Destrutivo = posição+separador+ícone+cor (nunca só cor); indisponível = aria-disabled+motivo (nunca só cinza). 3. Grayscale: posição/separador/check carregam. 4. Menu de ações × disclosure de links a 1s (roles×links). 5. Regra do um: um nível, uma live-region nenhuma, um canal de feedback. 6. Estado alternável visível (check). 7. 360px: tray herdado, itens 44px, overflow "⋯" 44px. *(Suite 5B em `suite-navegacao.mjs` — total 57/57 ✓ após erratas do gate visual: Tab/Esc/focusout no menu + setas opcionais APG no disclosure; tray renderizado, flip e sombra = gate visual.)*

---

## 36. Breadcrumb — `estável` (v0.41) · BC1–BC5 · Bloco 5/5C · lote autônomo 1

> **Base (3 rodadas, 2026-08-06):** R1 — **NN/g "Breadcrumbs: 11 Design Guidelines"** (localização na hierarquia, NUNCA histórico da sessão; caminho único em poli-hierarquia; começa em Início; perto do título; ≥3 níveis) + NN/g 2007 + **GOV.UK Breadcrumbs** (nav rotulado + ol; antes do `<main>`; não usar em estrutura rasa nem jornada linear/transação) + National Archives DS (colapso mobile: primeiro+último com expansão) + UMN/UMich (atual sem link). R2 — shadcn/Bootstrap (aria-current="page" convergente). R3 — **APG breadcrumb pattern** (nav nomeado + ol + separador via CSS decorativo; issue #3047 sobre o último item — resolvida à la USWDS) + **USWDS** (14 testes a11y; separadores ocultos de SR; último item `<span>` com aria-current) + W3C DS + MDN pt-BR (vocabulário "trilha de navegação"/"migalha de pão").

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| BC1 | **Localização, nunca histórico:** trilha mostra a posição na hierarquia (caminho único). Usar com ≥3 níveis; NÃO usar em estrutura rasa, jornada linear (stepper §38) ou dentro de transação | NN/g (path-based fica longo e não serve a quem chega de link externo); GOV.UK | Trilha de sessão; breadcrumb em wizard |
| BC2 | **Anatomia:** `nav` com `aria-label="Trilha de navegação (breadcrumb)"` + `ol`; separador `›` decorativo (`aria-hidden`); **último item = página atual, `<span>` SEM link, `aria-current="page"`**; começa em "Início" (sem duplicar com o menu global) | APG+USWDS (resolução do #3047: atual sem link — link para si mesmo é ruído); NN/g (Home no começo, sem duplicação) | Atual como link (USWDS/UMN contra); separador lido por SR |
| BC3 | **Colapso:** quando não cabe, mostra **primeiro + "…" + último**; o "…" é botão nomeado que EXPANDE inline e foca o primeiro nível revelado. Nunca truncar rótulos | National Archives DS (padrão oficial); expansão inline evita menu para 2–3 itens | Menu dropdown no "…" (peso do §35 para caso trivial); truncar rótulo |
| BC4 | **Posição:** topo da página de conteúdo, antes do `<main>`, abaixo do topbar — o skip-link pula a trilha junto | GOV.UK (racional do skip-link); NN/g (perto do título) | Trilha no rodapé |
| BC5 | **Fronteira:** o breadcrumb NÃO é navegação primária (o shell F6 é); em página raiz de módulo não aparece (nível <3) | NN/g (secundária por definição) | Breadcrumb como substituto de menu |

**Tokens:** links via `--seed-text-link` (par §26); atual `text-primary` 700; separador `text-muted` decorativo; "…" com hitbox W4. **Nenhum par novo.** Os 7 testes: 1 herdados ✓ · 2 atual por peso+sem-sublinhado+aria-current · 3 grayscale ok (peso) · 4 trilha × tabs a 1s (hierarquia × vistas) · 5 um caminho só · 6 posição visível · 7 360px colapsado com alvos 44. *(Suite 5C: BC-01–04 ✓; colapso renderizado no gate.)*

---

## 37. Paginação — `estável` (v0.41) · PG1–PG5 · Bloco 5/5C · lote autônomo 1

> **Base (3 rodadas, 2026-08-06):** R1 — **Carbon Pagination** (usar >25 itens; itens-por-página + faixa do intervalo; sempre colada embaixo da tabela; NUNCA para jornada linear; variantes simple/advanced) + Carbon data-table + Michelin DS (truncamento; topo+fundo se conteúdo maior que a tela; fronteira com infinite scroll: conteúdo que atualiza muito/sem ordenação). R2 — **EUI Pagination** (modo `compressed` responsivo em xs/s — números viram texto estático + setas; **"filtre primeiro, pagine depois"**; contagem total sempre visível; `pageCount 0` p/ total desconhecido). R3 — heranças normativas: nav nomeado + `aria-current="page"` (mesma base APG do §36); select §7; botão §1.

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| PG1 | **Modelo de tabela ERP completo:** itens por página (select §7: 10/25/50) + **faixa "X–Y de N" em JetBrains Mono** + janela de páginas + anterior/próxima. Colada na base da tabela/lista. Usar quando >~25 itens | Carbon (o modelo advanced é o caso ERP); EUI (contagem total sempre) | Paginação "simples" como default (esconde o total); jump-to-page numérico na v1 (sem demanda) |
| PG2 | **Janela com reticências:** primeira e última SEMPRE visíveis + corrente ±1; reticências decorativas (`aria-hidden`). Corrente = fundo `action-primary` + `aria-current="page"` | Michelin/EUI (truncamento padrão); primeira/última ancoram o total | Todas as páginas (explode com 300+); só setas no desktop |
| PG3 | **Compacta <640px:** números somem, fica "Página X de Y" + setas — mesma informação, footprint de polegar. Automático por CSS, não prop | EUI compressed (responsivo por default em xs/s) | Janela completa no mobile (alvos minúsculos) |
| PG4 | **Estados:** anterior desabilita na 1ª, próxima na última (fim de curso é o motivo VISÍVEL — não é a classe de disabled mudo do B1/TB8: o contexto explica); trocar itens-por-página **volta à página 1** | Carbon/EUI convergem; voltar à página 1 evita intervalo fantasma | Esconder as setas no fim (layout pula) |
| PG5 | **Fronteiras:** ordenação/filtro/busca = tabela (B6 — "filtre primeiro, pagine depois" vira régua editorial de lá); infinite scroll/load-more = padrão de FEED, fora do ERP (se surgir no site institucional, F6); total desconhecido (só setas) = variante registrada, spec no B6 junto da tabela | EUI (a régua); Michelin (fronteira do infinite) | Especificar infinite scroll aqui |

**Tokens:** corrente = par do botão primário §1 (4.60 ✓ herdado); números `text-secondary`; faixa mono `text-muted` (4.74 herdado). **Nenhum par novo.** Os 7 testes: 1 herdados ✓ · 2 corrente por fundo+peso+aria-current · 3 grayscale ok · 4 paginação × tabs a 1s · 5 uma janela, uma corrente · 6 posição visível sem interação · 7 360px compacta com alvos 36+hitbox. *(Suite 5C: PG-01–08 ✓; compacta renderizada conferida na render R8 ✓.)*

---

## 38. Stepper / Wizard — `estável` (v0.41) · ST1–ST6 · Bloco 5/5D · lote autônomo 1 · resolve a fronteira do §20

> **Base (3 rodadas, 2026-08-06):** R1 — Carbon Progress Indicator + **evidência GOV.UK/NSW/DWP** (pesquisa de campo: muitos usuários NÃO VEEM indicadores de progresso, e parte dos que veem acha ruído; "comece testando SEM; se precisar, 'Etapa X de Y' simples; nunca % enganosa") + eleken (padrões de mercado). R2 — Zuora/Michelin (linear × não-linear; ≥2 etapas não justifica; horizontal/vertical) + EUI Steps. R3 — **SAP Fiori Wizard floorplan** (3–8 etapas; walkthrough + página de RESUMO read-only antes da submissão; não usar com 2 etapas nem tarefa rotineira) + `aria-current="step"` (ARIA 1.1) + heranças B2/B4 (validação, error summary).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| ST1 | **Papel — resolve a fronteira do §20:** progress de etapas é NAVEGAÇÃO de processo, não progressbar (§20 mede tempo/quantidade; §38 mede posição em fluxo). Usar em fluxo de **3–8 etapas dependentes**; 2 etapas = só botões; tarefa rotineira/familiar = não usar (só adiciona cliques) | Fiori literal (3–8; rotina não usa); a fronteira estava declarada no §20 desde o B3 | Stepper de 2 etapas; stepper como progressbar |
| ST2 | **RÉGUA DUPLA por contexto (o quase-ponto-de-decisão do lote, precedente B3):** **ERP/wizard interno** = indicador completo (usuário recorrente, tarefas longas — Fiori); **formulário público de conversão** = começar SEM indicador; se o teste pedir, **texto "Etapa X de Y — rótulo"**, nunca o gráfico completo | Conflito real de evidência: DWP/GOV.UK (indicador ignorado/ruído no público) × Fiori (wizard consagrado no ERP) — os dois têm razão nos seus contextos | Um padrão único para os dois mundos (contraria uma das duas evidências) |
| ST3 | **Anatomia:** `ol` (ordem é semântica); etapa = marcador 28px + **rótulo textual SEMPRE** (nunca só número); estados: **concluída** (✓, fundo action-primary, CLICÁVEL — voltar livre) · **corrente** (`aria-current="step"`, anel + peso 700) · **futura** (número, NÃO-interativa — é `<li>` texto, não botão desabilitado: sem beco do TB8) · conclusão anunciada no nome (" — concluída" oculto visual) | aria-current="step" (ARIA); Fiori (voltar livre); rótulo sempre (eleken: sem rótulo o usuário não sabe o que vem); futura como texto evita o disabled mudo | Futuras clicáveis (linear é linear — não-linear é outra régua, fronteira); só números |
| ST4 | **Comportamento:** avançar VALIDA a etapa corrente (herda B4: erro bloqueia com mensagem/summary); **voltar preserva os dados**; última etapa = **Revisão read-only** que termina em **AÇÃO TERMINAL nomeada ("Concluir/Enviar") — nunca um Avançar inerte** (errata do gate visual); concluída a submissão, todas as etapas exibem ✓ | Fiori (summary page é parte do floorplan); B4 herdado | Avançar sem validar; perder dados no voltar |
| ST5 | **Mobile:** horizontal vira **vertical** <640px (rótulos íntegros, conexão vertical); alternativa densa = a variante texto do ST2 | Carbon/Zuora (vertical oficial); rótulo nunca some | Horizontal espremido com rótulo truncado |
| ST6 | **Fronteiras:** wizard COMPLETO multi-página (rotas, persistência de rascunho, branching) = pattern F6/produto — aqui vive o INDICADOR + a régua de comportamento; stepper numérico de input = §5 (nome colide, componente não) | O F6 já lista "formulários longos multi-etapa" | Especificar o floorplan inteiro aqui |

**Tokens:** concluída = par botão primário §1 (4.60 ✓); corrente anel `action-primary` + texto `text-primary`; futura `text-secondary` (4.74+); conexão `border-subtle` decorativa. **Nenhum par novo.** Os 7 testes: 1 herdados ✓ · 2 estados por ✓+anel+peso, nunca só cor · 3 grayscale ok · 4 stepper × breadcrumb a 1s (processo × hierarquia) · 5 uma corrente por vez · 6 posição visível · 7 360px vertical com alvos 44 nas concluídas. *(Suite 5D: ST-01–07 ✓; vertical renderizado conferido na render R8 ✓.)*

---

## 39. Command Palette ⌘K — `estável` (v0.41) · CP1–CP6 · Bloco 5/5E · lote autônomo 1 · fecha o Bloco 5 · resolve a fronteira do §4.11

> **Base (3 rodadas, 2026-08-06):** R1 — precedentes Linear/Vercel/GitHub/Raycast (techinterview: registry por id estável; <100ms; guarda do atalho em inputs). R2 — **cmdk** (a base do shadcn Command — a stack do Lovable): combobox correto out-of-the-box; **CommandDialog = cmdk dentro de dialog real** (trap/Esc/retorno são do dialog, não do cmdk); Command.Empty como FRASE; virtualizar só em milhares de itens; distinção shadcn: palette=Command+Dialog · valor em form=combobox · ações de linha=menu (reforça MN1). R3 — heranças normativas integrais: **combobox ARIA 1.2 do §11** (input mantém o foco; `aria-activedescendant` move a seleção virtual; `aria-expanded`/`aria-controls`) + **modal §30** (showModal, trap nativo, foco devolvido) + **busca §4** (o atalho Ctrl/⌘K JÁ reservado e treinado desde o v0.8 — a memória muscular migra sem custo, exatamente como o §4.11 previu) + live region nomeada (1.13-b).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| CP1 | **Semântica = COMPOSIÇÃO de 2 componentes já normatizados:** modal §30 (showModal, trap, Esc, retorno ao gatilho) contendo **combobox §11** (foco fica no input; setas movem `aria-activedescendant`; `aria-expanded` reflete resultados). Nada novo nasce | cmdk/shadcn implementam exatamente isto; §11 e §30 já pesquisados com fonte primária | Listbox com foco real nas opções (perde a digitação contínua); role=menu (é busca+seleção, não menu — MN1) |
| CP2 | **Abertura:** Ctrl/⌘K global (cumpre a promessa do §4.11 — o mesmo atalho que focava a busca passa a abrir a palette; rótulo por plataforma herdado) + gatilho visível no chrome. **Toggle**: Ctrl+K com a palette aberta fecha. Guarda: dentro de input/textarea só abre com o modificador explícito | §4.11 (decisão de 2026-08-02 sendo executada); techinterview (guarda de inputs) | Atalho novo (joga fora a memória treinada); só atalho sem gatilho visível (indescobrível) |
| CP3 | **Conteúdo:** grupos rotulados (Ações · Navegação · Recentes) fora da semântica de option; rótulo **verbo+substantivo** (herda MN4); atalho exibido em mono decorativo; **máx ~8 visíveis com scroll**; empty = FRASE ("Nenhum resultado. Tente outro termo." — herda o tipo zero-resultados do §25: SEM CTA de criação); live region nomeada anuncia "N resultados" | cmdk (Empty como frase); Fiori/Polaris herdados; 1.13-b | "0 results" seco; CTA de criar no zero-resultados (Z herdado) |
| CP4 | **Teclado:** ↓↑ movem a seleção virtual (com fim de curso, sem wrap — consistente com o disclosure), Home/End, Enter executa e FECHA devolvendo ao gatilho, Esc fecha. Digitação filtra em tempo real (client-side; fonte assíncrona = produto com CommandLoading/skeleton §19) | Combobox §11 (modelo já decidido); cmdk | Wrap nas setas (numa lista filtrada curta, fim de curso orienta melhor) |
| CP5 | **Fronteiras:** registry de comandos, índice de busca, recentes persistidos, fuzzy avançado, telemetria = **produto/Lovable** (ids estáveis como contrato); o DS entrega o componente, a régua editorial e o contrato de acessibilidade | techinterview (registry por id — sobrevive a rename); CN4 como precedente de fronteira | Fuzzy/scoring no DS |
| CP6 | **Mobile:** <640px a palette abre em largura total (herda DW2); sem tecla ⌘, a entrada é o gatilho visível (o chip do §4 já esconde o atalho onde não existe) | Paridade estrutural; §4 (rótulo por plataforma) | Palette desktop-only |

**Tokens:** opção ativa = `list-hover` + anel interno (herdado); kbd chips = §4; grupos `text-muted`; backdrop = §30. **Nenhum par novo.** Os 7 testes: 1 herdados ✓ · 2 ativa por fundo+anel (nunca só cor) · 3 grayscale ok · 4 palette × busca §4 a 1s (comandos+navegação × conteúdo) · 5 regra do um: uma live region nomeada, um atalho, uma ativa · 6 gatilho visível com atalho exibido · 7 360px largura total, opções 44px. *(Suite 5E: CP-01–10 ✓; Ctrl+K REAL + `:modal` nativo + activedescendant conferidos em pixels na render R8 ✓.)*

---

## 40. Tabela — `estável` (v0.43) · DT1–DT8 · Bloco 6/6A · lote autônomo 2

> **Base (3 rodadas, 2026-08-06):** R1 — Carbon data table (alturas de linha pareadas com a toolbar; texto à esquerda incl. headers, numérico variável à direita, identificadores discretos à esquerda; sort por caret; inline actions por overflow; quando NÃO usar: substituto de planilha) + NN/g herdado. R2 — **TanStack Table** (a base da stack Lovable/shadcn) + EUI. R3 — **corpus Adrian Roselli** ("Don't Turn a Table into an ARIA Grid Just for a Clickable Row" 2023; Sortable Table Columns 2021–24: botão no th + `aria-sort` sem role extra, gap de anúncio de NVDA → live region OPCIONAL desligada; A Responsive Accessible Table: wrapper `role=region`+`tabindex=0` nomeado; multi-sort = lacuna do ARIA #283) + **Fiori** (responsive table = nível de LINHA, ≤200 itens; grid table = desktop cell-level >1000, NÃO-responsiva — a nossa §40 segue a filosofia responsive; importância de coluna/pop-in ficam para o §43/6D).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| DT1 | **`<table>` NATIVA — nunca `role="grid"` na v1:** caption sempre; `th scope="col"`; wrapper de rolagem `role="region"` + `aria-labelledby` + `tabindex="0"`. Grid real (edição em célula/2D) = componente FUTURO com pesquisa própria | Roselli 2023 (grid por linha clicável é o anti-padrão); Fiori (grid table é outra classe, não-responsiva); a semântica nativa dá leitura de cabeçalho de graça | role=grid "para o teclado ficar rico" (quebra a leitura de tabela dos SRs); div-table |
| DT2 | **3 densidades por atributo** — confortável 48 (default) · densa 40 · compacta 32 — **com trava touch: <640px qualquer densidade volta a ≥44px** (a pendência ERP→mobile do B2 se resolve aqui); na compacta, os CONTROLES internos reduzem junto (o piso é deles — achado da render R9) | Carbon (4 alturas; pareamento com toolbar); paridade mobile (premissa estrutural); medição renderizada 49/41/33 | Densidade livre por CSS do consumidor; compacta no touch |
| DT3 | **Alinhamento:** texto à esquerda (headers junto); **numérico variável à direita em JetBrains Mono**; identificador discreto (Nº) à esquerda em mono `text-secondary`; datas à esquerda; nunca centralizar coluna | Carbon literal (incl. a exceção do identificador); mono = regra da marca p/ dados | Centralizar; header desalinhado da coluna |
| DT4 | **Zebra OPCIONAL (default OFF)** — hover de linha + borda de linha são o default; zebra recomendada editorialmente em tabelas largas (≥6 colunas) e **nunca sobrepõe a linha selecionada** | Carbon (opcional); o hover+divider cobre o rastreio na largura típica do ERP | Zebra default (ruído na tabela estreita) |
| DT5 | **Ordenação:** BOTÃO dentro do `th` + `aria-sort` no th (ÚNICA — multi-sort é lacuna do ARIA #283, fora da v1); ciclo asc↔desc (sem 3º estado de remoção); **seta SEMPRE visível** (neutra esmaecida ↕, ativa colorida ↑↓); **live region de ordenação DESLIGADA por default** (o `aria-sort` é o canal; o gap do NVDA é conhecido — ligável por prop, decisão do produto) | Roselli integral (incl. o trade-off do anúncio, documentado em teste com JAWS/NVDA/VO); Carbon | th clicável cru (sem botão = sem teclado); seta só no hover (affordance invisível); multi-sort |
| DT6 | **Cabeçalho fixo:** `position:sticky` no th com fundo próprio (pares medidos 6.03/8.92), z 1 (abaixo de tudo do sistema); **`border-collapse:separate`** obrigatório (collapse quebra o sticky — bug de engine, guarda DT-13) | Roselli fixed headers; errata pega pela render R9 | Header que some no scroll de tabela longa |
| DT7 | **Célula:** wrap por default; truncamento é decisão POR COLUNA com acesso ao valor completo; **célula vazia mostra "—"** (nunca muda); badge §21 para status; ações de linha = overflow "⋯" §35 | Roselli (célula vazia anuncia); Carbon (inline actions) | Truncar tudo com tooltip (Fiori: tooltip não existe no toque) |
| DT8 | **Fronteiras:** ordenação/filtro no servidor, virtual scroll, >~200 linhas por página, export = produto; edição em célula = grid futuro; degradação mobile por importância de coluna (pop-in Fiori) = **§43/6D**; toolbar de busca/filtros = **§42/6C** | Fiori (≤200 na responsive; grid p/ milhares); recorte do lote | Especificar tudo num § só |

**Tokens/pares (`contraste-dados.py` ✅):** header ci-700/ci-50 **6.03** light · ci-300/sunken **8.92** dark; seta ativa tq-600/ci-50 **4.23** (≥3 ✓); demais herdados. Os 7 testes: 1 medidos ✓ · 2 ordenação por seta+aria-sort (nunca só cor) · 3 grayscale ok (setas/peso) · 4 confortável×compacta a 1s · 5 uma coluna ordenada, um header fixo · 6 estado de ordenação visível sem interação · 7 360px ≥44 forçado + rolagem do wrapper focável. *(Suites: jsdom 6A ✓ · render R9: sticky colado e imóvel, densidades 49/41/33, 360 ✓.)*

---

## 41. Seleção e ações em massa — `estável` (v0.43) · SL1–SL5 · Bloco 6/6B · lote autônomo 2 · encerra a pendência do indeterminate (§10)

> **Base (3 rodadas, 2026-08-06):** R1 — **Carbon data table selection/batch actions** (barra de ações aparece com ≥1 selecionado; header-checkbox seleciona A PÁGINA; "selecionar todas" de todas as páginas é ação da barra; Cancelar sai do modo; radio para seleção única; issue #7565 documenta o contrato página×dataset) + Polaris (IndexTable bulk). R2 — **TanStack row selection** (indeterminate no header; **ids ESTÁVEIS como chave da seleção** — sobrevive a ordenação/paginação; shift-range na discussão #3068 como prática da stack). R3 — checkbox §10 herdado (o estado indeterminate pendente desde o B2) + 1.13-b (regime contínuo).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| SL1 | **Coluna de seleção = 1ª:** checkbox §10 nomeado citando o item ("Selecionar proposta N — cliente"); **hitbox = a célula inteira** (label envolvente); clicar na LINHA não seleciona (linha-clique é navegação/ação — seleção só no checkbox) | Carbon (checkbox da linha); Gmail (separação clique×seleção evita seleção acidental) | Linha inteira selecionando (conflita com navegar-ao-detalhe) |
| SL2 | **Selecionar-tudo do header com INDETERMINATE** (encerra a pendência do §10): 3 estados; marca/desmarca **A PÁGINA visível**, nunca o dataset; página cheia dispara a **oferta "Selecionar todas as N"** (barra própria); desmarcar no header desfaz SÓ a página | Carbon batch guidance literal (contrato página×dataset do #7565); TanStack (toggleAllPage vs All); Gmail | Header marcando o dataset silenciosamente (destrutivo invisível em ação de massa) |
| SL3 | **Barra de ações em massa SUBSTITUI a toolbar** quando há seleção: contagem "N selecionadas" + ações (máx. ~4; **destrutiva por último**) + **Cancelar** (limpa e sai do modo); fundo `action-primary` (par herdado §1) | Carbon ("batch action mode" com saída explícita); MN5 herdado p/ a destrutiva | Barra flutuante extra (2 toolbars simultâneas); ações sem Cancelar |
| SL4 | **Anúncio da contagem = regime CONTÍNUO do 1.13-b:** live region nomeada ("Seleção da tabela") com **debounce ~400ms** — shift-range e select-all coalescem num anúncio só; "Seleção limpa" no cancelamento | 1.13-b (contagem que muda rápido é atualização contínua, nunca rajada de mensagens) | Anúncio por clique (metralhadora no SR) |
| SL5 | **Seleção por ID ESTÁVEL:** sobrevive a ordenação e paginação (page 2 chega com as marcas); **filtro/busca que muda o conjunto LIMPA a seleção** (o "tudo" mudou de significado — com o anúncio); shift+clique seleciona intervalo na página. Persistência entre sessões = produto | TanStack (ids como chave); Carbon #7496 (selecionar através de filtro errado é bug documentado); #3068 (shift-range) | Seleção por índice (ordena e seleciona o item errado); manter seleção através de filtro |

**Tokens/pares (`contraste-dados.py` ✅):** linha selecionada ci-900/tq-50 **12.92** light · ci-100/tq-900 **11.23** dark; borda tq-600/tq-50 **4.28** (≥3 ✓); barra = par do botão primário §1. Os 7 testes: 1 medidos ✓ · 2 seleção por fundo+borda+checkbox (3 sinais) · 3 grayscale ok · 4 selecionada×hover a 1s · 5 uma barra, uma live region, uma oferta · 6 contagem visível sem interação · 7 360px hitbox=célula, barra empilha. *(Suites: jsdom 6B ✓ incl. página×dataset e shift-range · render R9: fundo pintado + barra visível.)*

---

## 42. Toolbar de dados — `estável` (v0.45) · TD1–TD5 · Bloco 6/6C · encerra a pendência "busca composta EUI" (B2)

> **Base (3 rodadas, 2026-08-06):** R1 — Carbon (toolbar da tabela: busca + settings + botão primário; pareamento de altura com as linhas) + Dynamics 365 (subpadrões inline×stacked; campos de filtro só de INPUT RESTRITO). R2 — **EUI SearchBar** (busca livre + filtros estruturados na MESMA barra; sintaxe `campo:valor` é evolução de produto, não do DS) + **EUI FilterGroup** (botão de filtro com contagem de ativos + popover selecionável) + shadcn-blocks (chips de filtro aplicado com **contagem viva "N de M"** — o sinal de que o filtro fez algo real). R3 — Innovaccer table filters (1 chip por dimensão; limpar tudo) + herança integral: busca §4, chip §22 (X 24px + hitbox 44), popover §24, paginação §37 ("filtre primeiro").

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| TD1 | **Anatomia:** busca (§4, live com debounce do produto) + botões de filtro por DIMENSÃO (`aria-expanded` + popover §24 com checkboxes §10 + **contagem de ativos no botão**) + contador **"N de M"** sempre visível + settings/ações à direita. <640px empilha (stacked) | EUI FilterGroup; shadcn-blocks (o contador é a prova do filtro); Dynamics (stacked no estreito) | Sintaxe de query no input (EUI SearchBar avançado = produto); painel lateral de filtros (peso de F6) |
| TD2 | **Filtros aplicados = CHIPS §22** numa linha própria sob a toolbar: `"Dimensão: valor"`, X remove **e desmarca a origem** (uma fonte de verdade), + "Limpar filtros" quando ≥1 | Innovaccer (1 chip/dimensão); §22 herdado integral (hitbox, remoção anunciada) | Chip que remove sem desmarcar o checkbox (estado fantasma) |
| TD3 | **Régua "filtre primeiro, pagine depois"** (herdada do §37/EUI e formalizada AQUI): filtro/busca resetam para a página 1; o contador convive com a faixa da paginação (papéis distintos: conjunto × janela) | EUI literal; PG4 já mandava voltar à página 1 | Filtro mantendo página (intervalo fantasma) |
| TD4 | **Integração SL5 executada:** mudar filtro/busca **LIMPA a seleção** (o "tudo" mudou de significado) — teste de integração TD-04 na suite | SL5 (decisão do lote 1); Carbon #7496 (selecionar através de filtro é bug documentado) | Seleção sobrevivendo ao filtro |
| TD5 | **Fronteiras:** query language, filtros salvos/visões, painel avançado, export = produto (F6); campos de filtro são sempre de input restrito (checkbox/select/data — nunca texto livre por dimensão) | Dynamics (input restrito); recorte | Filtro de texto livre por coluna |

**Pares:** todos herdados (chip §22, busca §4, contador mono ci-600 4.74). Os 7 testes: 1 herdados ✓ · 2 filtro ativo por chip+contagem (nunca só cor no botão) · 3 grayscale ok · 4 busca×filtro a 1s · 5 uma linha de chips, um contador · 6 filtros visíveis sem abrir nada (chips) · 7 360px empilhado, X do chip 44. *(Suite 6C ✓ incl. TD-04; render R10: chip pintado + "4 de 16" ✓.)*

---

## 43. Lista de dados e degradação responsiva — `estável` (v0.45) · LD1–LD5 · Bloco 6/6D · fecha a paridade mobile dos dados

> **Base (3 rodadas, 2026-08-06):** R1 — **Setproduct 2026** (régua: tabela = COMPARAR atributos consistentes; cards = item rico e variado; lista = raso 1 linha) + **Fiori responsive table** (pop-in por IMPORTÂNCIA de coluna: High fica, Medium migra para pares rótulo-valor, Low some com "Show Details"; ≤200 itens; nível de LINHA). R2 — shadcn/Tailwind (data-attributes + variantes responsivas). R3 — **MDN `<dl>`** (Baseline; grupos em `<div>` permitidos; quirks de SR documentados e não-bloqueantes) + eBay evo (todo dt pareado; sem nesting) + Ben Myers/Fedotov (dl > table para pares rótulo-valor) + Roselli responsivo (wrapper de rolagem como fallback UNIVERSAL — já no DT1).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| LD1 | **Lista de dados = `<dl>` com grupos em `<div>`** (padrão MDN), nomeada; dt em caixa alta discreta, dd com peso; **numérico em mono**; para LEITURA de um registro (detalhe), nunca para comparar linhas (comparar = tabela §40) | MDN/eBay/Fedotov (semântica de par rótulo-valor); Setproduct (a régua tabela×lista) | `<table>` de 2 colunas p/ pares (semântica errada); divs cegos |
| LD2 | **Degradação tabela→mobile em DOIS regimes:** (a) **fallback universal** = wrapper de rolagem horizontal do DT1 (sempre presente, custo zero); (b) **modo cards OPT-IN** (`data-mobile="cards"`): <640px cada linha vira card com o rótulo da coluna migrando via `data-label` (o pop-in do Fiori em CSS puro) | Roselli (fallback); Fiori (pop-in por importância); opt-in porque nem toda tabela merece card (comparação morre no card — Setproduct) | Cards como default (mata a comparação); duplicar o DOM (tabela+cards) |
| LD3 | **Importância de coluna (Fiori):** cada coluna declara `high` (fica no card) · default (migra com rótulo) · **`low` (some no card)** — decisão EDITORIAL por tabela, com a régua: identificador+valor+status são high; metadados são low | Fiori literal (High/Medium/Low); a coluna que some precisa ser decisão, não acidente | Espremer tudo no card (ilegível); sumir colunas sem critério |
| LD4 | **No modo cards:** thead oculto ACESSIVELMENTE (clip — o SR ainda tem a tabela), célula = linha `rótulo: valor` com o rótulo via `::before`/`data-label`; seleção e ações sobrevivem (checkbox e ⋯ ficam) | A tabela continua tabela para AT; o rótulo visual migra | `display:none` no thead (some para todos); perder a seleção no mobile |
| LD5 | **Fronteiras:** "Show Details" do Fiori (expandir os low ocultos) = evolução com demanda; virtualização/agrupamento = produto; a régua de QUANDO usar card×tabela×lista fica no §40/DT8 + aqui | Recorte honesto | Spec do Show Details sem caso real |

**Pares:** herdados. Os 7 testes: 1 ✓ · 2 rótulo migrado por texto (nunca só posição) · 3 grayscale ok · 4 dl×tabela a 1s (leitura×comparação) · 5 um regime por tabela · 6 low declarado visível na spec da tabela · 7 360px: card sem overflow, alvos vivos. *(Suite 6D ✓; render R10: linha=bloco, low oculta, zero overflow, medidos em pixels.)*

---

## 44. Guia de integração Tailwind/shadcn — `estável` (v0.45) · GI1–GI5 · Bloco 6/6E · fecha o Bloco 6 · documento de consumo

> **Base:** leitura obrigatória da skill **`seed-ds-ui`** (o contrato org-side de produção React/TS/Tailwind/shadcn para o Lovable) + `seed-tokens.md` v1.2 + prática shadcn/cva corrente. Este § é DOCUMENTO (sem preview próprio): define o contrato DS→código que a skill e o Lovable consomem.

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| GI1 | **Cadeia de 3 camadas:** primitivos (`--turquesa-400`… do tokens.md) → **semânticos `--seed-*`** (a ÚNICA camada que componente consome — regra vigente desde o B1) → **aliases shadcn** (`--primary`, `--background`… apontam para `--seed-*`, nunca para hex). Mudou o semântico, shadcn herda | seed-ds-ui já declara aliases; o DS formaliza que o alvo do alias é o SEMÂNTICO, não o primitivo | Alias→primitivo (pula a camada de intenção; quebra dark de graça) |
| GI2 | **Coerência com a skill `seed-ds-ui` (achado da leitura):** a skill hoje publica primitivos + aliases; a camada `--seed-*` entra COMO FONTE dos aliases na próxima edição da skill — **pendência registrada no backlog, não conflito** (os valores hex são idênticos; muda a indireção) | Uma fonte de verdade por cor; skill é consumidor do DS, não fork | Superseder a skill daqui (a skill tem dono e ciclo próprios) |
| GI3 | **Tailwind:** v4 = `@theme` mapeando `--color-*`→`var(--seed-*)`; v3 = `tailwind.config.ts` `colors:{...:'var(--seed-*)'}`. **Proibido**: cor Tailwind default (`bg-teal-500`), hex em classe, `dark:` manual por componente (o dark vive nos tokens — trocar `data-theme` resolve tudo) | seed-ds-ui (token-only, zero hardcoded); é como os previews do DS funcionam desde o B1 | Paleta Tailwind default "parecida" |
| GI4 | **Receita cva:** variantes de componente = `cva` com classes que só referenciam tokens; estados (hover/focus/selected) = os mesmos mecanismos do DS (anel interno em overflow, 3 sinais, hitbox W4 via pseudo). O §40–§43 têm par TanStack/shadcn nomeado (Table+TanStack, Checkbox, Command…) | Padrão shadcn; paridade 1:1 com as decisões do DS | Variantes por CSS solto |
| GI5 | **Contrato Lovable:** ids estáveis (CP5/SL5) · componentes em `src/components/seed/` (pasta da skill) · fontes self-hosted em produção (C.1) · a suite-render como referência de aceitação visual (as réguas de errata valem no produto: token fantasma, anel interno, ação terminal) | seed-ds-ui (estrutura); C.1/C.5 do ESTADO_ATUAL | Contrato implícito |

**Validação deste §:** não há preview — a prova é a coerência (tokens citados existem no tokens.md v1.2; a skill referenciada foi lida nesta data; as réguas citadas têm teste-guarda nas suites). Pendência registrada: alinhar a skill `seed-ds-ui` à camada semântica na próxima edição dela.

---

## 45. Iconografia — `estável` (v0.47) · IC1–IC7 + NM1–NM6 · Bloco 7 · suite `suite-icones.mjs` 25/25 ✓ · render 5/0 · regressão total 445 jsdom + 43 render (ancorada por MD5) · gate visual do Rafael aprovado 2026-08-07 · **FECHA A FASE 3**

> **Papel:** guidelines de construção (IC) + nomenclatura e governança (NM) + inventário do set canônico SEED. Seção única do bloco (decisão de formato aprovada em 2026-08-07). **Fronteira registrada:** se o set expandir de verdade (F5 dataviz / F7 aplicações), nasce `seed-icones.md` por supersede formal, com versão própria (NM5).
> **Base:** 3 rodadas encadeadas (R1 Lucide design rules + Material icon grid/keylines + Carbon sizes → R2 shadcn/lucide-react — o consumo real do §44/GI4 — + Heroicons/Phosphor → R3 WAI decorative images + non-text contrast 3:1 + aliases CFPB/Carbon). Preview: `seed-icones-preview.html` v0.46.

### 45.1 Decisões IC — Fundamentos do ícone

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **IC1** | **Grid canônico 24×24** (`viewBox="0 0 24 24"`); o acervo 40×40 migra na normalização (fator 0.6) | Lucide — o set que os pares shadcn do §44/GI4 consomem — desenha em 24×24; Material usa 24dp; grid igual = mistura SEED×Lucide sem drift de peso. Fato medido: o 40×40 com stroke 1.8 rende 1.08px a 24px — mais fino que toda a UI | Manter 40×40 (incompatível lado a lado com Lucide); 32 do Carbon (ninguém na stack consome) |
| **IC2** | **Área viva/padding: piso mecânico = padding ≥1px** (conteúdo dentro de 1…23, medível por `getBBox()`); keylines Material (quadrado 18, círculo 20, retângulos 20×16) como **guia óptico de construção, não asserção** | O piso de 1px é a régua do Lucide — piso mais rígido (2px Material) reprovaria ícones Lucide legítimos na suite. Keyline como teste mecânico geraria falso-positivo em formas assimétricas | Padding 2px Material como asserção (conflita com Lucide); sem piso (ícone colado na borda colide com texto vizinho) |
| **IC3** | **Stroke: valor ÚNICO 2px no grid 24** · cap `round` · join `round` · radius 2px · stroke centrado. Renderização proporcional nos tamanhos de uso (SVG default), **com trava de piso 16px** | Régua Lucide literal (2/24, round/round, radius 2) = convivência perfeita com o set utilitário do GI4. Decisão MEDIDA: o 1.8/40 atual (=1.08/24) fica abaixo do peso do resto da UI. A trava de 16px vem do Carbon: traço fino quebra no tamanho glyph | Régua por tamanho (re-otimizar stroke a 16/20/24 como Carbon/Heroicons): 3–4 variantes por ícone sem time dedicado; Lucide prova que valor único + piso funciona. `absoluteStrokeWidth` como default (peso dispara em tamanhos grandes) |
| **IC4** | **Tamanhos de uso: 16 / 20 / 24** (escala de UI) **+ 32/40 display**. Travas: 16 = piso absoluto, só inline com texto pequeno/células densas; 20 = campos e ações secundárias; 24 = navegação e ações primárias (padrão); ≥32 = só empty state/destaque/marketing | Interseção Carbon (16/20/24/32) × prática Lucide × Material (20/24/40/48); os previews dos Blocos 1–6 já usam 16–24 de fato | Escala livre por tela (drift de densidade); tamanho único (mata o inline denso do ERP) |
| **IC5** | **Cor SÓ via `currentColor`** — o ícone herda o token semântico do contexto (`--seed-text-*`, `--seed-feedback-*`); **zero hex/rgb dentro do glifo** (asserção IC-07/IC-08 na suite). Contraste non-text **3:1 medido por script** sobre os fundos canônicos | Regra estrutural do DS desde o B1 (cor só via token) aplicada ao grão do glifo; os 16×#11B0A0 / 17×#098475 / 6×#FAD61D hardcoded do acervo eram exatamente o drift que o B1 combateu. Pares medidos nesta data: 13.35–15.59 (primary), 5.06 (secondary), 4.60 (success) — todos ≥3:1 ✅ | Cor por classe no glifo (duplica a fonte); presets de cor "de marca" dentro do SVG (quebra dark de graça) |
| **IC6** | **Anatomia:** wrapper único canônico — `fill="none"` no `<svg>`, formas em stroke; **preenchimentos internos pontuais permitidos** (`fill="currentColor"` em núcleos ≤ r2, ex.: sol, gerador) e nada além de `currentColor`/`none`; sem `width/height` fixos no arquivo-fonte (o consumo dimensiona); coordenadas com 1 decimal máx. | O wrapper único torna todo glifo intercambiável e testável pela mesma asserção; fills pontuais são prática Lucide (ex.: dot) e preservam a leitura em tamanhos pequenos | Glifos duotone/filled como variante padrão (dobra o set sem demanda — fica como fronteira F5+); precisão de 3+ decimais (ruído de diff) |
| **IC7** | **Acessibilidade:** ícone **decorativo por padrão** — `aria-hidden="true"` + `focusable="false"` no próprio arquivo-fonte; ícone **informativo** (sem texto adjacente) = wrapper com `role="img"` + `aria-label` no PONTO DE USO, nunca no arquivo. Regra vigente mantida: ícone informativo é sempre SVG do set, nunca caractere unicode (X6-b, marco v1.0) | eBay evo + WAI decorative images (o padrão do §32/AV): o nome vive no texto/rótulo, o glifo decora; `focusable="false"` custa zero e blinda legado IE/Edge | `<title>` interno como canal único (suporte irregular leitor×browser vs. `aria-label`); `role="presentation"` (aria-hidden basta e é o padrão do ecossistema) |

### 45.2 Decisões NM — Nomenclatura e governança

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **NM1** | **Nomes em inglês internacional, kebab-case, conceito-variante** (`chevron-down`, `check-circle`) — nomeando o **objeto, não a ação** (`arrow-right`, não `go-forward`). **Aliases PT-BR obrigatórios nos metadados** (a busca da ⌘K §39 consome os dois idiomas). **Supersede aplicado: `add` → `circle-plus`** (conceito-variante: o glifo do acervo é círculo+cruz; `plus` nu no Lucide é outro desenho — a auto-auditoria do lote pegou a imprecisão antes do gate) | Convenção Lucide literal → mapa 1:1 entre o vocabulário SEED e o utilitário; evita dupla convenção na mesma camada de código (o acervo já está em inglês — fato). Aliases com busca é o padrão CFPB/Carbon | Nomes PT-BR (colide com Lucide lado a lado; quebra substituição 1:1); ação-primeiro (o mesmo glifo serve várias ações) |
| **NM2** | **Metadados mínimos por ícone:** nome canônico · categoria · aliases PT/EN · equivalência Lucide (ou —) · origem · versão de entrada · status (`ativo`/`aposentado`). Vivem na tabela-inventário 45.3 | Sem metadado, a busca da ⌘K e a auditoria não têm o que consumir; governança nasce barata agora ou cara depois | Metadado em arquivo paralelo (fragmenta a fonte antes de existir demanda) |
| **NM3** | **Categorias fixas (6):** ações · navegação · status/feedback · **domínio SEED** (energia/solar/elétrico/engenharia) · comunicação/contato · arquivos/dados | Cobre os 43 componentes + o acervo real; "domínio SEED" é a categoria que justifica o set existir | Taxonomia aberta (categorias proliferam e a busca degrada) |
| **NM4** | **Relação set SEED × Lucide (1ª classe, dialoga com §44/GI4):** **Lucide é o vocabulário UTILITÁRIO padrão do produto** (os pares shadcn nomeados já o consomem via `seed-ds-ui` — fato do GI4); **o set SEED é a camada de DOMÍNIO E MARCA**. Árvore "criar × usar": ① existe no set SEED → usa SEED; ② não existe no SEED mas existe no Lucide → usa Lucide (produto/UI); ③ não existe em nenhum → **cria no set SEED pela régua IC**, entrando com nome+aliases+versão; ④ **proibido** importar de 3º set (Phosphor/Heroicons/Material). Em peças de marketing/documentos, o SEED tem precedência quando o glifo existir. IC1/IC3 adotam a régua Lucide DE PROPÓSITO: mistura sem drift de peso | Substituir Lucide por um set SEED completo (~1.500 glifos) é inviável e destruiria o GI4; ignorar a fronteira deixa cada tela decidindo sozinha. A convivência com régua física idêntica é o único desenho em que os dois sets são indistinguíveis em peso | Set SEED total (custo proibitivo); Lucide para tudo (mata a camada de marca nos pilares/domínio); mistura livre de sets (drift que o Bloco 1 inteiro combateu) |
| **NM5** | **Versionamento:** o set versiona junto do `seed-componentes.md` enquanto viver no §45; cada ícone carrega versão de entrada. **Fronteira mantida:** se o set expandir de verdade (F5/F7), nasce `seed-icones.md` por supersede formal, com versão própria | Um arquivo cumulativo é a decisão de formato aprovada; versão por ícone dá rastreabilidade sem burocracia | Versão semântica própria do set desde já (peso sem demanda) |
| **NM6** | **Deprecação:** nada deletado antes do substituto existir (regra estrutural vigente); ícone aposentado ganha **supersede formal na tabela 45.4** (nome antigo → novo · motivo), sai do preview, permanece listado como `aposentado` até a morte formal do HTML v1 (F8) | Regra do DS aplicada ao grão do ícone; a F8 já prevê o supersede das 30 seções do HTML v1 — o inventário do §45 vira o insumo | Deleção silenciosa (quebra rastreabilidade); manter aposentados no preview (polui o set vivo) |

**Obrigação identificada para a skill `seed-ds-ui` (padrão GI2 — pendência DELA, não supersede daqui):** quando a skill for editada, expor a árvore NM4 (SEED → Lucide → criar) e o mapa de equivalências do inventário, para o código de produção saber quando importar de `lucide-react` e quando usar o componente do set SEED.

### 45.3 Inventário do set canônico — 31 glifos ativos (28 na v0.47 + 2 na v1.39, FV-P5 + 1 na v1.42, MANIFESTO §150)

| Nome | Categoria | Aliases (busca ⌘K) | Lucide equiv. | Origem | Veredito 7B | Versão · status |
|---|---|---|---|---|---|---|
| `battery` | domínio | bateria, armazenamento, carga | `battery` | HTML v1.0 idx36 | normalizar | v0.46 · ativo |
| `bolt` | domínio | raio, energia, eletrico, zap | `zap` | redesenho idx31 | redesenhar | v0.46 · ativo |
| `building` | domínio | predio, empresa, comercial, edificio | `building-2` | HTML v1.0 idx35 | normalizar | v0.46 · ativo |
| `drop` | domínio | gota, agua, hidrica | `droplet` | HTML v1.0 idx33 | normalizar | v0.46 · ativo |
| `generator` | domínio | gerador, gmg, grupo-gerador, motor | `—` | redesenho idx14 → **REDESENHADO em 2026-09-05 pelo modelo em foto dele** (MANIFESTO §150, T-GMG; supersede em §45.4): contêiner de cantos arredondados, chaminé com tampa no topo à direita, porta do painel à esquerda com dois indicadores, raio grande no centro-direita, pés | redesenhar | **v1.42** · ativo |
| `hard-hat` | domínio | obras, capacete, campo, canteiro, execucao | `hard-hat` | novo — FV-P5 (destaque Obras, 2026-09-05) | criar (NM4 ③) | v1.39 · ativo |
| `leaf` | domínio | folha, sustentabilidade, verde, eco | `leaf` | HTML v1.0 idx32 | normalizar | v0.46 · ativo |
| `lightbulb` | domínio | lampada, ideia, iluminacao, luz | `lightbulb` | HTML v1.0 idx30 | normalizar | v0.46 · ativo |
| `lightning-rod` | domínio | para-raios, spda, protecao | `—` | novo — gap pilar | criar | v0.46 · ativo |
| `plug` | domínio | tomada, plugue, conexao, eletrico | `plug` | HTML v1.0 idx37 | normalizar | v0.46 · ativo |
| `recycle` | domínio | reciclagem, reuso, ciclo | `recycle` | HTML v1.0 idx40 | normalizar | v0.46 · ativo |
| `solar-panel` | domínio | painel, placa, modulo, fotovoltaico | `solar-panel` *(IC-P2: a coluna dizia `—`; o Lucide TEM `solar-panel` — conferido em 2026-09-05 no `main`; corrigido por ordem dele, MANIFESTO §149)* | HTML v1.0 idx29 | normalizar | v0.46 · ativo |
| `solar-panel-plus` | domínio | seed-plus, manutencao, plano, usina, ufv, plus, painel | `—` | novo — FV-P5 (destaque SEED Plus, 2026-09-05) | criar (NM4 ③) | v1.39 · ativo |
| `substation` | domínio | subestacao, cabine, media-tensao, mt | `—` | HTML v1.0 idx13 | normalizar | v0.46 · ativo |
| `sun` | domínio | sol, solar, energia | `sun` | HTML v1.0 idx28 | normalizar | v0.46 · ativo |
| `transformer` | domínio | transformador, trafo, subestacao | `—` | novo — gap pilar | criar | v0.46 · ativo |
| `transmission-tower` | domínio | torre, alta-tensao, transmissao, linha, lt | `—` *(o Lucide `main` não tem `transmission-tower` — HTTP 404 conferido em 2026-09-05; o `utility-pole` dele é poste com travessa, outro objeto)* | novo — MANIFESTO §150 (T-TORRE, 2026-09-05: torre de transmissão desenhada pelo modelo em imagem dele; destaque Subestações) | criar (NM4 ③) | v1.42 · ativo |
| `circle-plus` | ações | adicionar, novo, mais, criar, add, plus | `circle-plus` | HTML v1.0 idx49 | renomear | v0.46 · ativo |
| `search` | ações | busca, buscar, procurar, lupa | `search` | HTML v1.0 idx50 | normalizar | v0.46 · ativo |
| `settings` | ações | configuracoes, ajustes, engrenagem | `settings` | HTML v1.0 idx38 | normalizar | v0.46 · ativo |
| `arrow-right` | navegação | seta, avancar, proximo, direita | `arrow-right` | HTML v1.0 idx48 | normalizar | v0.46 · ativo |
| `dashboard` | navegação | painel, dashboard, visao-geral, grid | `layout-dashboard` | HTML v1.0 idx51 | normalizar | v0.46 · ativo |
| `home` | navegação | casa, inicio, residencial | `house` | HTML v1.0 idx34 | normalizar | v0.46 · ativo |
| `check` | status/feedback | confirmado, ok, sucesso, feito | `check` | HTML v1.0 idx47 | normalizar | v0.46 · ativo |
| `email` | comunicação | e-mail, mensagem, mail, envelope | `mail` | HTML v1.0 idx44 | normalizar | v0.46 · ativo |
| `location` | comunicação | local, endereco, pin, mapa, map-pin | `map-pin` | HTML v1.0 idx42 | normalizar | v0.46 · ativo |
| `people` | comunicação | pessoas, equipe, clientes, usuarios | `users` | HTML v1.0 idx41 | normalizar | v0.46 · ativo |
| `phone` | comunicação | telefone, ligar, contato | `phone` | HTML v1.0 idx43 | normalizar | v0.46 · ativo |
| `calendar` | arquivos/dados | calendario, agenda, data | `calendar` | HTML v1.0 idx45 | normalizar | v0.46 · ativo |
| `document` | arquivos/dados | documento, arquivo, relatorio, file | `file-text` | HTML v1.0 idx46 | normalizar | v0.46 · ativo |
| `growth` | arquivos/dados | crescimento, grafico, tendencia, alta | `trending-up` | HTML v1.0 idx39 | normalizar | v0.46 · ativo |

### 45.4 Aposentadorias e renomes (supersedes formais do acervo HTML v1.0)

| Item do acervo | Motivo | Supersede |
|---|---|---|
| (pilar Energia Solar idx12) | duplicata de `sun` §45 | → sun |
| (pilar Projetos idx15) | duplicata de `document` | → document |
| (pilar Consultorias idx16) | duplicata de `lightbulb` | → lightbulb |
| (valor Energia Solar idx19) | duplicata de `sun` (variante fill) | → sun |
| (valor Eficiência idx20) | duplicata de `bolt` (variante fill) | → bolt |
| (valor Sustentabilidade idx21) | duplicata de `drop`/`leaf` | → drop |
| add (idx49) | nome de ação viola NM1; glifo círculo+cruz | → circle-plus (renomeado) |
| `recycle` **v0.46** | **Gate visual de 2026-08-16:** *"parecem errados, infantilizados"*. O desenho era **dois galos soltos mais duas linhas desconectadas** — não fechava triângulo e não lia como reciclagem em tamanho nenhum | → **`recycle` v0.48**, redesenhado pela régua IC1/IC3: triângulo equilátero de circunraio **8,6**, três braços de 8% a 90% de cada lado, cabeça de seta de **2,6** a **32°**. `getBBox` medido em **4,4…18,8** (piso IC2 é 1…23) |
| `growth` **v0.46** | **Mesmo gate.** Era o `trending-up` do acervo 40×40 normalizado por 0,6: ziguezague raso e "cabeça" em L pequena, que lia como rabisco | → **`growth` v0.48**, realinhado à geometria Lucide que o IC1/IC3 adota **de propósito** (§45.3 já declarava a equivalência `trending-up`): polilinha `21.4 7.6 → 13.4 15.6 → 8.6 10.8 → 2.6 16.4` + colchete `16 7.6 → 21.4 7.6 → 21.4 13`. `getBBox` **2,6…21,4** |
| `generator` **v0.46** | **Veredito dele de 2026-09-05 (MANIFESTO §149/§150, T-GMG):** o desenho — caixa 2,5…15,5 com dois pontos preenchidos r 1 a 3,5 de distância (com traço 2 os pontos de 4 px se sobrepunham) e um bocal à direita — **lia como filmadora a 154 e 360 px** nos destaques do Instagram (diagnóstico em `preview/gate-apoio-traco/estudo-torre.html`); ordem verbatim: *"T-GMG: redesenhar agora — segue foto anexa de exemplo para usar"* | → **`generator` v1.42**, redesenhado pela foto dele (descrita no §150): contêiner `3…21 × 5…20` rx 2, chaminé `x 18` até `y 2` com tampa `16,5…19,5`, porta `x 10`, raio em polilinha `16 8 · 13,5 12,5 · 17,5 12,5 · 14 17` (família do `battery-charging` do Lucide, que lê a 24 px), dois indicadores `r 0,5` preenchidos no painel da porta (`6,5 · 8,5` e `6,5 · 12,5`: 3 px cada a 24 px, 1 px de fundo até a parede e até a porta — medido na captura), pés `7` e `17` de `20` a `22`. `getBBox` **3…21 × 2…22**. Densidades G1 (sem indicadores nem pés) e G2 (indicadores no alto, como na foto — o raio encolhe a 6 unidades e vira rabisco a 24 px) ficam renderizadas em `preview/gate-apoio-glifos-r2/`, expostas a veto |

> **Nota de método sobre estes dois supersedes.** Nenhum dos dois glifos foi *importado*: o `recycle`
> foi **desenhado aqui**, pela régua IC, e o `growth` foi **realinhado** à geometria do Lucide que o
> IC1/IC3 adota de propósito para que os dois sets convivam sem drift de peso. A árvore **NM4** não
> foi acionada — ela decide *criar × usar*, e aqui o glifo já existia no set SEED; o que houve foi
> **correção de desenho**, com a versão do ícone subindo de `v0.46` para `v0.48`.
>
> **O que reprovou foi o OLHO, e nenhuma camada automatizada tinha como pegar:** a `suite-icones`
> (25 · 0) mede anatomia, nomenclatura e acessibilidade; a `render-icones` (5 · 0) mede `getBBox`,
> `currentColor`, alvo e overflow. **Os dois desenhos velhos passavam nas duas.** *Legibilidade de
> forma não é medível pelas guardas que existem hoje* — e isso fica registrado como a razão de o gate
> visual ser camada, não formalidade.

### 45.5 Os 7 testes (adaptados à natureza de guideline+set)

1 ✓ funciona (glifos renderizam nos 2 temas — R-02/R-05) · 2 sem cor-única (currentColor + mecanismo do contexto) · 3 grayscale ok (traço carrega a forma) · 4 nome do glifo entendido a 1s no grid · 5 um peso por sistema (stroke 2 único — IC-03) · 6 decorativo declarado (A11Y-01) · 7 360px sem overflow e alvo ≥44px no consumo (R-03/R-04).

**Validação executada:** `suite-icones.mjs` 25/25 ✓ (IC 8 · NM 8 · A11Y 4 · interação 5) + `render-icones.mjs` 5 verificações/0 achados (**getBBox de 28/28 dentro de 1…23** — o piso IC2 é asserção de render, não de jsdom). Contraste: 6 pares ≥3:1 medidos por script antes da spec.

**Promoção a `estável` (2026-08-07, v0.47) — os gates, um a um:** suite jsdom 25/25 ✓ · render 5 verificações / 0 achados · 6 pares de contraste non-text ≥3:1 · **regressão total ancorada por MD5 (não por tamanho de arquivo): 445 verdes jsdom + 43 verificações render / 0 achados + 4 scripts de contraste no gate** · gate visual do Rafael APROVADO em 2026-08-07. Com esta promoção o **Bloco 7 fecha 1/1 e a Fase 3 fecha 7/7 blocos**. *Nota de rastreabilidade honesta:* a data do gate visual (2026-08-07) é a do briefing em que o Rafael autorizou esta promoção; se o gate tiver ocorrido em outra data, corrigir aqui por errata. *Nota de contexto:* esta mesma execução resgatou o `seed-navegacao-preview.html` v0.40 (sobrescrito no repositório) e reconstruiu 2 artefatos de validação perdidos do Bloco 5 — ver o log da v0.47 no cabeçalho e o `CHECKPOINT_v1_4_bloco7_iconografia.md`.

## 46. Shell de aplicação — **`estável`** (v0.50) · SH1–SH15 · pattern F6 · suíte `suite-shell.mjs` 68/68 ✓ · render 18/0 · contraste 32/0 · **gate visual aprovado 2026-08-11 — FECHA O BLOCO 6A**

> **Natureza:** pattern de **COMPOSIÇÃO** do F6, não componente novo — consome menu/dropdown
> (§35), tabs (§34), breadcrumb (§36), command palette ⌘K (§39), drawer (§31), avatar (§32),
> badge (§21), lista (§28), central de notificações (§33) e o botão (§1). **Nenhum componente
> nasce aqui.** É a mesma natureza do §33, o primeiro pattern F6 do sistema, e a fronteira que o
> Bloco 5 declarou desde 2026-08-06: *"sidebar/topbar é shell do F6, consome este bloco"*.
>
> **PRINCÍPIO-MÃE, decidido com o Rafael em 2026-08-11: CONTRATO POR REGIÃO, COMPOSIÇÃO LIVRE.**
> As cinco regiões são especificadas até o último detalhe; **qual delas o produto liga, e o que
> coloca dentro, é decisão do projeto do ERP — não do Design System.** *Racional dele, adotado:*
> engessar a composição limita em vez de ajudar, e não gera problema de padronização — o que
> carrega a identidade é cor, tipografia, traço, densidade e comportamento do item ativo, não o
> arranjo das caixas. *O que o DS não abre mão:* **cada região se comporta igual em toda
> composição** (SH14) e **acessibilidade não é opcional em arranjo nenhum** (SH2, SH5, SH7, SH8).
>
> **Escopo do produto, decidido em briefing antes da pesquisa:** o ERP é **web, responsivo,
> desktop-prioritário**, usado pela equipe — escritório como uso principal, campo como uso real.
> O menu **muda por perfil**. A navegação é **por ÁREA DO NEGÓCIO** (processo), não por
> ferramenta. O app nativo do cliente é **fronteira declarada**: quando existir, o DS entrega
> tokens e componentes, nunca layout de navegação, porque brigar com a convenção do iOS e do
> Android é perder. O site institucional é o **bloco 6B**, e nasce mobile-first.
>
> **Base (3 rodadas encadeadas, 2026-08-11):** **R1 canon interno** — o §33 como molde de pattern
> F6; o Bloco 5 inteiro como insumo (o shell não inventa, arruma); MN1 (as quatro saídas
> semânticas) e MN6 (comando indisponível fica visível), que o SH6 precisa distinguir; DW1 (os
> dois regimes do drawer) e CN6 (o precedente de standard); a escala de sobreposição
> 60<65<70<75<80; o §40 (densidade compacta medida em 33px). **R2 mercado e stack** — componente
> Sidebar oficial do shadcn/ui, que é o consumidor real do Lovable, com `SidebarProvider` e os
> três modos `offcanvas`/`icon`/`none`; blocos de sidebar colapsável com `Collapsible` do Radix e
> threading por `border-l`; prática consolidada de SaaS enterprise (topo para contexto global,
> lateral para navegação primária, porque as duas camadas fazem trabalhos diferentes e param de
> disputar espaço); SAP Fiori launchpad shell bar (logo, back, título, busca corporativa,
> notificações, Me Area). **R3 normas e fora-do-circuito** — ARIA11/W3C sobre landmark repetido
> exigindo nome único; MDN sobre `role="navigation"`; `aria-current="page"` como critério AAA de
> baixo custo; o corpus de acessibilidade de SPA (foco preso na navegação após troca de rota é o
> defeito nº 1, e o alvo do foco não pode ser landmark, link nem botão); Material Design
> navigation rail (3–7 destinos, posicionado abaixo da top bar). Consolidado aprovado pelo Rafael
> em 2026-08-11.
>
> **REFERÊNCIA DE ARQUITETURA, e o que dela foi herdado.** O Rafael apresentou um layout de
> referência (`preview-clientes-v2.html`, 58.741 B, md5 `69f00537763e14ef907d2f8130c26a92`) no
> padrão de um produto de mercado, com instrução explícita: **"apenas ideia para entender o
> layout de construção"**. Regra que sai daqui e vale para todo bloco futuro: **de um layout de
> referência herda-se ESTRUTURA e COMPORTAMENTO; cor, tipografia e forma vêm do DS, sempre.**
> *Herdado:* o rail como camada acima da sidebar · a sidebar ser contextual ao rail · árvore de
> quatro níveis com fio de conexão · busca fixa e centralizada com o atalho impresso dentro ·
> seletor de contexto no topo à esquerda · contadores no fim da linha · ações que aparecem no
> hover · densidade compacta. *Descartado por não ser do sistema:* rail preto puro `#000000` (a
> superfície mais escura do DS é `#0B1419`), verde de sucesso `#2DBA7C` (o do DS é turquesa),
> gradiente terminando em `#D94B7D`, badge rosa, e neutros deslocados (`#9AA8B0` onde o DS tem
> `#90A6B3`; `#CBD3D8` onde tem `#ACBECA`) — a mesma deriva de hex digitado que custou as erratas
> E4 e E16 na Fase 5.

### 46.1 Decisões SH1–SH15

> **O contrato de montagem de tela (ex-SH16) é LEI DO SISTEMA e vive no `seed-composicao.md`
> §1 (CP1)** — canvas `surface-subtle`, peça `surface-raised` + `radius-lg` + par sombra+anel,
> calha `sp-3`, nada encosta em nada. **A exceção nomeada do rail foi SUPERSEDIDA no gate de
> 2026-08-15** (*"um bloco montado como os outros, todos independentes, igual o ClickUp"*): o
> rail é peça sobre o canvas como as demais — radius-lg, calha nos quatro lados, sem borda
> (a própria cor é a fronteira: 5,82/8,62 claro · 2,70/1,82 escuro contra o canvas). Registro
> completo no CP1 do `seed-composicao.md` v1.4. O shell é a primeira instância
> do contrato, não a dona dele (supersede SUP-1, 2026-08-14; fecha a errata E-CF-03 — o
> cabeçalho desta seção dizia "SH1–SH14" com a tabela terminando em SH15).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **SH1** | **Cinco regiões nomeadas: rail · topbar · sidebar contextual · conteúdo · painel lateral.** Obrigatórias: **conteúdo** + **ao menos uma região de navegação**. As demais o produto liga conforme a tarefa | Composição é escolha de tarefa, não de identidade. O §33 já opera assim: não obriga ninguém a ter central de notificações — diz que, **se** tiver, é assim | Composição fixa em N arranjos canônicos (engessa arquitetura de produto de dentro do DS) · nenhuma região obrigatória (tela sem navegação nem landmark é beco sem saída) |
| **SH2** | **Toda região de navegação é landmark com NOME ACESSÍVEL ÚNICO**, e o nome **não contém a palavra "navegação"** — o leitor de tela já anuncia o papel. Nomes canônicos: "Áreas" (rail) · "Seções" (sidebar) · "Trilha" (breadcrumb) · "Conta" (menu do avatar) | ARIA11/W3C: landmark repetido exige nome único e significativo. O shell tem **três a quatro** `nav` simultâneos — sem nome, o usuário de leitor de tela recebe uma lista de navegações idênticas e não tem critério para escolher | `nav` anônimo · nome contendo "navegação" (redundância verbosa) · nomes iguais em regiões diferentes |
| **SH3** | **RAIL — contrato:** destinos de topo · **3 a 12** · **largura 80px** · ícone do set §45 **+ rótulo sempre visível, ≤9 caracteres por palavra** · um ativo por vez · ordem estável · **altura total, à esquerda da topbar** | O Material limita a 3–7, mas calibrado para tablet, com o rail substituindo a barra inferior; num ERP de desktop o rail é a espinha do produto. **Teto de 12 declarado como calibrado aqui, NÃO derivado de norma.** **Largura e limite de caracteres são MEDIDOS** (`medir-rail.mjs`): Montserrat 9px caixa-alta mede **7,01px por caractere**; um rail de 64px tem 52px úteis e comporta 7 caracteres — três dos seis rótulos de exemplo transbordavam. 80px dão 68px úteis = **9 caracteres**. *O rail cedeu de 64 para 80px porque "Comercial" (9) é nome legítimo de área de ERP — quem tem de caber é o negócio.* Rótulo truncado não é rótulo, logo o limite recai sobre a **palavra**. Altura total porque o rail é o nível MAIS ALTO de navegação — ficar dentro da moldura da topbar contradiz a hierarquia que ele representa | Rail só com ícone (rótulo em tooltip não é descoberto; o §45 mede glifos a 24px, não a 24px sem contexto) · rail abaixo da topbar (a posição que o Material recomenda, semanticamente errada aqui) · teto de 7 (herdaria calibração de outro contexto — o erro da errata E7) |
| **SH4** | **SIDEBAR — contrato:** árvore de **até 4 níveis**, contextual à região acima. Quatro travas: **(a)** um ramo aberto por vez · **(b)** o ramo do item ativo abre sozinho ao carregar · **(c)** em modo estreito, níveis 2+ abrem em **flyout** · **(d)** quinto nível vira **tabs §34 dentro da tela** | O teto de 2 níveis foi proposto e **derrubado pelo Rafael com contraexemplo** (*Financeiro → Faturamento → Faturas emitidas*), e a referência confirma 4 na prática. As travas existem porque profundidade sem elas vira parede de 60 linhas. O flyout é **disclosure de links**, nunca `role="menu"` — distinção que o MN1 pagou caro para estabelecer | Árvore livre sem travas · nível 3 como navegação secundária dentro da página (solução de Fiori e Carbon; cai porque *explorar é ver a estrutura sem entrar nela*, e navegação secundária só aparece depois de já ter chegado) |
| **SH5** | **Item ativo: `aria-current="page"` + marca que SOBREVIVE AO GRAYSCALE** — barra lateral de 3px mais peso tipográfico, nunca só fundo colorido | Indicar o item atual exige as duas coisas: destaque visual e `aria-current` no link. E o **DF4** faz do grayscale gate estrutural do sistema — fundo colorido sozinho morre em cinza e em mono impresso | Só fundo colorido · só `aria-current` (invisível para quem enxerga) · negrito sozinho (some em densidade compacta) |
| **SH6** | **Navegação por perfil OMITE o item; ação indisponível MOSTRA com motivo** | O **MN6** decidiu que comando indisponível fica visível, com motivo, porque *"menu é inventário de comandos"*. Navegação é outra coisa: mostrar "Faturamento" a quem não pode entrar não ajuda ninguém **e revela a estrutura interna da empresa**. A distinção preserva o MN6 no domínio dele | Item bloqueado na sidebar (ruído permanente + vazamento organizacional) · omitir também na ação (o usuário não entende por que a operação falhou) |
| **SH7** | **Troca de rota move o foco para o CONTEÚDO** (`tabindex="-1"`, nunca o landmark em si), com **live region persistente** anunciando a tela | É o defeito nº 1 de aplicação de página única: o foco fica preso na navegação, o conteúdo troca e o usuário de leitor de tela não sabe. O alvo do foco **não pode** ser landmark, link nem botão — as três quebram por evidência documentada. O DS já tem o protocolo de live region no §1.13 | Não fazer nada (o comportamento padrão, e é o defeito) · focar o `main` como landmark (lê o conteúdo inteiro) · focar o primeiro link (some do fluxo de tabulação ao receber `tabindex="-1"`) |
| **SH8** | **Skip link como PRIMEIRO elemento focável**, visível ao receber foco | WCAG 2.4.1. Num ERP com dezenas de itens de menu, tabular por toda a navegação a cada tela é inviável — e o SH4 permite quatro níveis | Skip link permanentemente oculto (existe e não funciona) |
| **SH9** | **BUSCA fixa e centralizada na topbar, com o ATALHO IMPRESSO DENTRO DO CAMPO.** Ela abre a command palette §39 — é a mesma busca, com dois caminhos de entrada. **Escopo: navega, não consulta dado** | *Supersede da decisão anterior deste consolidado, que tirava o campo e deixava só o atalho.* O campo torna o atalho **descoberto por quem lê**, em vez de decorado por quem foi treinado — e a referência do Rafael mostrou isso antes de a spec ser escrita. Busca de registro depende de índice no ERP: é produto, não DS | Só o atalho (aposta em treinamento) · campo que não abre o §39 (dois mecanismos de busca, duas fontes de verdade) · busca de registro no shell (fronteira de produto) |
| **SH10** | **TOPBAR — contrato de três zonas:** identidade e contexto à **esquerda** · busca ao **centro** · ações globais e conta à **direita**. Slots nomeados; o produto escolhe o que entra. **O título da tela vive no CONTEÚDO, não na topbar** | Prática consolidada de produto enterprise: topo para contexto global, lateral para navegação primária — assim as duas camadas param de disputar o mesmo espaço. O Fiori põe o título na shell bar e paga com uma barra que faz duas coisas | Título na topbar (duplica o `h1` do conteúdo e cria duas fontes de verdade sobre onde o usuário está) · topbar com conteúdo livre (vira depósito) |
| **SH11** | **Seletor de contexto existe, no topo à esquerda** | Estava reservado e desligado por falta de decisão de produto; a referência do Rafael decide que existe | Seletor dentro da sidebar (some quando a sidebar some) · nascer sem ele (redesenho depois) |
| **SH12** | **Em tela estreita, rail e sidebar colapsam num ÚNICO drawer §31 em regime MODAL** | O CN6 escolheu **standard** porque o caso é *consultar sem sair da tela*. Navegação é o oposto: você escolhe e sai. Em tela estreita o drawer cobre o conteúdo — página interativa atrás seria mentira semântica, e o APG só admite `aria-modal` quando o fundo está de fato bloqueado | Drawer standard (incoerente com a tarefa) · rail persistente no celular (come 64 dos 360px) · sidebar espremida (não vira app, vira ERP ruim no celular) |
| **SH13** | **PAINEL LATERAL de detalhe: drawer §31 em regime STANDARD** — inspecionar um registro sem sair da lista | Aqui o caso **é** consulta lado a lado: ler → navegar à fonte → comparar. É o caso central que criou o regime standard no DW1 e que o CN6 já exerceu | Modal (perde o contexto da lista, que é justamente o que motivou o painel) · navegação para tela cheia (perde a posição de rolagem e a seleção) |
| **SH15** | **CHROME NÃO SEGUE O TEMA.** A superfície do rail e a tinta sobre ela são **fixas nos dois temas** — desde a CP-P3 (2026-08-14) a superfície é o gradiente `--seed-rail-background` (`turquesa-700 → turquesa-800`, CP20 adotado no gate G2) com tinta branca, e o item ativo é a **pílula clara** `turquesa-100` com tinta `turquesa-900` (SH15-b) | **Achado da medição, não do desenho.** O rail consumia `surface-inverse`, que significa *"o oposto do tema"*: no escuro ele virava uma barra **clara** (`#F2F6F9`) dentro de uma interface escura, e o rótulo sobre o item ativo media **1,48**. O rail não quer ser o oposto do tema — quer ser estruturalmente escuro **nos dois**, como o chrome de aplicação que ele é. É o simétrico do **DG12** da Fase 5 (*tinta sobre dado não segue o tema, porque o objeto contrastado é claro nos dois modos*). Medido no estado atual: branco sobre os **dois extremos do gradiente** 6,32/9,37 · pílula 5,31/7,87 de fronteira · tinta da pílula 11,37. *(Errata **E-CF-05** fechada na v0.66: esta linha ainda descrevia o valor da era `cinza-900` — "e o item ativo usa `#006C62` fixo" — duas gerações atrás do artefato: a E-CF-04 consolidou o parágrafo do §46.2 e esqueceu esta linha. A REGRA nunca mudou; o VALOR mudou duas vezes: cinza-900 → turquesa-700 (v6.3) → gradiente (CP-P3).)* | `surface-inverse` (inverte por definição — era o defeito) · rail claro no claro e escuro no escuro (perde a âncora visual que faz o chrome ser reconhecível entre temas) |
| **SH14** | **Uma região NUNCA faz o trabalho de outra.** Se rail e sidebar coexistem, o rail é destino de topo e a sidebar é contexto | Duas navegações com o mesmo papel deixam o usuário de leitor de tela sem critério de escolha — quebra o SH2 por dentro. É a trava que permite a composição ser livre sem virar deriva | Sidebar como segundo rail · rail com itens de contexto |

### 46.2 Anatomia, tokens e fronteiras

**Rail:** largura **80px** (SH3 — medido: 68px úteis = 9 caracteres; "Comercial" cabe); superfície
**fixa nos dois temas** (SH15 — o rail NÃO consome `surface-inverse`, que foi o defeito medido a
1,48), hoje o **gradiente da rampa da marca** via semântico próprio `--seed-rail-background`
(`turquesa-700 #006C62 → turquesa-800 #005048`; CP20 do `seed-composicao.md`, **adotado no gate
G2 de 2026-08-14 e executado na CP-P3** — extremos medidos 6,32/9,37, tinta passa nos dois, nunca
na média); hover e pressionado do item consomem os overlays de chrome do SUP-6
(`--seed-chrome-overlay-hover/-active`, medidos **em composição**: 4,88/6,82 e 4,68/6,50);
ícone 24px do §45 + rótulo em micro-caps; item ativo pela regra do SH5; alvo de toque ≥44px.
*(Errata E-CF-04 fechada na v0.65: este parágrafo dizia 64px, `surface-inverse` e implicava
`cinza-900`. Atualizado de novo na v0.66 pela CP-P3: a redação "gradiente admitido — adoção
pendente de gate visual" ficou falsa após o gate.)*
**Topbar:** altura 60px; `surface-raised` com borda inferior `border-subtle`; z-index **60** (a
mesma faixa de chrome da escala 60<65<70<75<80 fixada no Bloco 4). **Sidebar:** largura 280px,
`surface-page`; indentação progressiva com fio de conexão em `border-subtle`; contador opcional
no fim da linha em mono; linha na densidade **compacta** do §40 (33px medidos), com alvo real de
toque garantido pela linha inteira, como no §28. **Conteúdo:** `main` com `tabindex="-1"` para o
SH7. **Painel lateral:** §31 integral em standard, z-index 65.

**Nenhum par de cor novo** — mas **compor pares medidos separadamente não garante que a
combinação passe**, e o `contraste-shell.py` provou isso: a primeira execução deu **22 PASS ·
8 FAIL**. Correções, todas por medição: o rail deixou de consumir `surface-inverse` (SH15) · o
contador e o *placeholder* saíram de `text-muted` (3,38 e 3,11 no claro) para `text-secondary`
(6,55 e 6,03) · o item ativo da sidebar deixou de ser texto de marca (4,23) e passou a **texto
primário com barra em `text-brand`** — a cor não carrega o estado, quem carrega é a barra e o
peso (SH5) · a sigla do seletor de contexto saiu de branco sobre `surface-brand` (2,71) para
branco sobre `surface-brand-deep` (6,32). Placar final: **32 PASS · 0 FAIL**.

> **PENDÊNCIA SH-P1 — buraco na camada semântica, achado por este bloco.** **Nenhum token
> semântico de borda passa 3:1 contra `surface-raised` no tema claro:** `border-default` mede
> **1,49** e `border-strong` **2,53**. O campo de busca precisa de contorno que o identifique
> como componente (1.4.11), e a única saída medida foi consumir o **primitivo `cinza-500`**
> (3,38 no claro · 4,50 no escuro). Isso é **empréstimo declarado**, não regra nova — e viola a
> lei "componente consome semântico". *A correção certa é um token semântico novo na camada 2*,
> e ela não pertence a este bloco: mexer nos três gêmeos por causa do shell seria decidir a
> camada de tokens dentro de um pattern. Fecha quando a camada 2 for editada por outro motivo,
> pela regra GI2.
>
> **✅ FECHADA em 2026-08-14, pela via prevista (gatilho GI2 + decisão do Rafael: *"vamos
> resolver logo"*):** a camada 2 foi editada pela CP-P3 (gêmeos v1.11) e, na mesma janela, os
> gêmeos v1.12 promovem o empréstimo a semântico — **`--seed-border-interactive`** = `cinza-500`
> `#788F9D` nos dois temas, medido contra as **seis superfícies** onde borda de componente
> aparece (3,38/3,38/3,11 claro · 4,50/5,51/5,05 escuro, todos ≥3,0), slot dark declarado
> (coincidência medida, não invariância). O campo de busca do shell consome o semântico desde o
> preview v0.3 — **nenhum primitivo é mais consumido por exceção no §46**. *Alternativa
> descartada com número:* `cinza-600` também passa (3,21–4,74), mas mudaria a cor RENDERIZADA
> que o gate visual já aprovou, sem motivo — a pendência pedia promoção de expressão, não de
> valor. A metade "separação de peça" já havia fechado no CP17 (par sombra+anel).

**Fora do shell, declarado:** que menus existem e o que há dentro deles (**projeto do ERP**) ·
qual perfil vê o quê (**regra de negócio**) · persistência do estado da sidebar
(**produto — Lovable/Supabase**, mesma fronteira do CN4) · busca de registro (**produto**) ·
dashboard e composição de painel (**bloco 6C**) · site institucional (**bloco 6B**) · app nativo
do cliente (**fora do DS**: tokens e componentes sim, layout de navegação não) · **o que o slot
de IA da topbar abre** (sub-bloco próprio da F6 — o slot existe, o destino não é especificado
aqui).

**Consequência declarada da navegação por PROCESSO:** como o mesmo objeto é alcançável por mais
de um caminho, o **breadcrumb §36 deixa de ser decoração** e passa a responder *por qual caminho
eu cheguei* — é ele que desambigua, não a sidebar.

### 46.3 Os 7 testes

1. **Nenhum par de cor novo**; todos herdados e medidos.
2. **Item ativo nunca depende só de cor** — barra + peso + `aria-current` (SH5).
3. **Grayscale:** rail, sidebar e ativo permanecem legíveis (DF4 como gate estrutural).
4. **Regra do um:** um item ativo por região; uma região por papel (SH14).
5. **Landmarks:** três a quatro `nav` simultâneos, todos com nome único, nenhum contendo a
   palavra "navegação" (SH2).
6. **Troca de rota:** foco no conteúdo e anúncio na live region, sem foco em landmark, link ou
   botão (SH7); skip link é o primeiro focável (SH8).
7. **360px:** rail e sidebar colapsam em drawer modal único; nada transborda; alvos ≥44px (SH12).

*(Estado: **`estável` desde 2026-08-11**, por "aprovo" explícito do Rafael após o gate visual
sobre o preview v0.1. **Quatro camadas cumpridas:** `suite-shell.mjs` **68 PASS · 0 FAIL** —
*prova de que as guardas são reais:* contra um preview com 5 defeitos deliberados, reprova em
**8**; `render-shell.mjs` em Chrome real **18 PASS · 0 FAIL**; `contraste-shell.py` **32 PASS ·
0 FAIL** após as correções que a própria medição exigiu; e o gate visual humano. **Pendência que
o bloco levou consigo: SH-P1**, que é da camada de tokens, não do shell — **fechada em
2026-08-14 pelos gêmeos v1.12** (`border-interactive`; fechamento completo na própria
pendência, acima).)*

---

## 47. Página institucional — esqueleto, camada de máquina e blocos de conteúdo — **`estável`** (v0.55) · MK1–MK22 · pattern F6 · suíte `suite-site.mjs` 66/66 ✓ · render 23/0 · contraste 42/0 · **gate visual aprovado 2026-08-12 — FECHA O BLOCO 6B**

> **Natureza:** pattern de **COMPOSIÇÃO** do F6, como o §46 e o §33 — nenhum componente novo
> nasce. Consome o botão (§1), o formulário (Bloco 2), o breadcrumb (§36), o menu como
> *disclosure de links* (§35/MN1), o accordion (§29) e a iconografia (§45).
>
> **O QUE ESTE LOTE ENTREGA E O QUE NÃO ENTREGA.** O bloco 6B foi dividido em dois lotes por
> decisão do Rafael em 2026-08-12, porque o conteúdo se encaixa **dentro** do esqueleto e a ordem
> inversa obrigaria a redesenhar. **Lote 1 (esta seção):** contrato dos tipos de página,
> cabeçalho institucional, rodapé, o padrão de **página de serviço autônoma** e a **camada de
> máquina** (título, descrição, canônica, dados estruturados, orçamento de peso). **Lote 2 (§47.5,
> escrito em 2026-08-12):** hero, prova social, números, card de solução, depoimento, case em
> vídeo, FAQ e bloco de contato por serviço — **MK13 a MK22**. **O preview e o gate visual do 6B são únicos, no fim do lote 2** —
> decisão do Rafael: um esqueleto sem conteúdo pareceria cru e o gate visual precisa de uma página
> de verdade.
>
> **PRINCÍPIO HERDADO DO §46, e que vale igual aqui: CONTRATO POR REGIÃO, COMPOSIÇÃO LIVRE.** As
> regiões e os tipos de página são especificados; **quais páginas existem, quais serviços ganham
> página e o que cada uma diz é decisão do projeto do site — não do Design System.**
>
> **Escopo do produto, decidido em briefing antes da pesquisa (2026-08-12):** o site institucional
> **nasce mobile-first** — é onde está o acesso em massa. O site **informa para converter**: cada
> parte é estruturada para o visitante reconhecer a própria dor e pedir contato **para aquele
> serviço**, e o Rafael quer **direcionar tráfego pago para a página do serviço sem precisar criar
> landing page separada** (o que não impede ter landing pages depois). **Formulário por serviço**,
> não um formulário geral. **Foco B2B**, com exceção de energia solar, que atende também B2C. O
> site novo é **ruptura visual declarada**, reaproveitando conteúdo, algumas imagens e a estrutura
> de mega menu do site atual.
>
> **FRONTEIRA REGISTRADA PARA NÃO VOLTAR (Rafael, 2026-08-12):** o site será **construído do
> zero**; o site atual não será corrigido. Logo, **dívida técnica, nome de pasta, slug e erro de
> digitação do site atual estão FORA DE ESCOPO.** O `estudo-site-atual.md` serve como inventário
> de conteúdo e **diagnóstico de padrão a não repetir** — não como lista de correções.
>
> **Base (3 rodadas encadeadas, 2026-08-12):** **R1 canon interno** — o §46 inteiro (SH2
> landmarks nomeados, SH5 ativo que sobrevive ao grayscale, SH7 foco na troca de rota, SH8 skip
> link) migra sem alteração; MN1 (navegação é *disclosure de links*, nunca `role="menu"`); DF4
> (grayscale como gate estrutural); o pacote mobile v1.2; e o **`estudo-site-atual.md` v1.0**
> (canônico de referência, ancorado no `MANIFESTO.md` v2.6), que forneceu a linha de base medida
> citada nas decisões abaixo. **R2 mercado** — corpus de página de conversão B2B de 2026:
> essencial acima da dobra em desktop e mobile, cada seção como mini-ponto de conversão, prova
> junto do CTA, e o dado incômodo de que página sem barra de navegação converte **até 2×** mais em
> teste controlado. **R3 normas** — schema.org (`Organization`, `LocalBusiness` e subtipos,
> `Service` com `serviceType`/`areaServed`/`provider`, `BreadcrumbList`, `FAQPage`), a regra do
> Google de **nunca marcar o que não está visível ao usuário**, o orçamento de peso de referência
> para mobile e as metas de campo de Core Web Vitals. Consolidado aprovado pelo Rafael em
> 2026-08-12.

### 47.1 A linha de base medida — o que NÃO repetir

Números do `estudo-site-atual.md`, produzidos por leitura do backup do site atual. Não são
crítica ao site antigo: são o **piso** contra o qual o novo é medido, e a razão de várias
decisões abaixo existirem como regra explícita em vez de ficarem implícitas.

| Medida | Site atual |
|---|---|
| Tags `<img>` com `alt` | **0 de 419** |
| Páginas com `<h1>` real | **16 de 22** (as outras usam classe CSS para *parecer* título) |
| Tags `<nav>` no site inteiro | **0** |
| Título e descrição próprios por página | **0** — a classe `SetSeo` existe e nunca é instanciada |
| Dados estruturados, Open Graph, canônica | **0 de cada** |
| Páginas institucionais no sitemap | **0 de 22** |
| Zoom | **bloqueado** (`maximum-scale=1, user-scalable=no`) em toda página |
| Peso | esqueleto fixo de **~2,5 MB**, com páginas de até **8,8 MB** de mídia |

**Diagnóstico central do estudo, e o que ele ensina:** o servidor devolve o **mesmo HTML vazio
para qualquer URL** — o roteamento nunca lê a URL pedida — e o conteúdo só chega numa segunda
requisição feita por JavaScript. *Lição de padrão:* **a página tem de existir na primeira
resposta.** Isso não é escolha de plataforma (que é do projeto do site); é requisito de padrão.

### 47.2 Decisões MK1–MK12

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **MK1** | **Quatro tipos de página, cada um com contrato próprio:** **institucional** (quem somos, carreira) · **serviço** (autônoma, ver MK8) · **conteúdo** (artigo, vídeo, case) · **utilitária** (contato, obrigado, erro, legal) | O site atual tem 22 páginas **sem tipagem nenhuma** — mesmo esqueleto e mesmo título para todas, o que é a causa de o título genérico nunca ter incomodado ninguém: não havia onde declarar outro | Contrato único para tudo (produziu exatamente o site atual) · tipagem por assunto em vez de por comportamento (assunto muda, comportamento não) |
| **MK2** | **Toda página declara: um `<h1>` único e REAL · `<title>` próprio · meta descrição própria · URL canônica · `lang="pt-BR"`** | Linha de base: 6 de 22 páginas sem `<h1>` real, e **nenhuma** com título ou descrição próprios. *Classe de defeito a evitar:* **capacidade construída e nunca ligada** — a classe `SetSeo` do site atual estava pronta e nunca foi instanciada, o que é pior que não existir, porque parece resolvido | `<h1>` simulado por classe CSS (quebra 1.3.1 e a hierarquia de leitura) · título por template genérico |
| **MK3** | **Toda `<img>` de conteúdo tem `alt` descritivo; decorativa tem `alt=""` EXPLÍCITO** | 419 tags, zero `alt`, e **nem a distinção** entre conteúdo e decoração sendo feita. O `alt=""` explícito é o que diz ao leitor de tela "pule isto de propósito" — omitir o atributo faz o leitor tentar adivinhar pelo nome do arquivo | `alt` opcional · `alt` gerado do nome do arquivo |
| **MK4** | **Zoom NUNCA é bloqueado.** `maximum-scale` e `user-scalable=no` são proibidos | Está no ar hoje em todas as páginas, e o público massivo é celular. Bloquear zoom não é preferência estética: é impedir alguém de ler | — (não há caso legítimo) |
| **MK5** | **Toda região de navegação é `<nav>` com nome acessível único**, e o nome não contém a palavra "navegação" | Zero `<nav>` no site inteiro. Herda o **SH2** do §46 sem alteração — a página institucional tem cabeçalho, mega menu, trilha e rodapé, todos navegação | `div`/`ul` genérico · `nav` anônimo |
| **MK6** | **Cabeçalho institucional em TRÊS MODOS:** **completo** (institucional e conteúdo: itens de nível 1 expostos + mega menu) · **reduzido** (**serviço**: logo + uma ação primária + gatilho do mega menu, sem os itens de nível 1 expostos) · **mínimo** (utilitária: logo e saída) | **Resolve um conflito medido.** Página sem barra de navegação converte **até 2×** mais em teste controlado, e a recomendação de mercado é remover a navegação da página de conversão. Mas a página de serviço **precisa pertencer ao site** — é isso que evita manter landing pages paralelas, que é o requisito do Rafael. O modo reduzido mantém a navegação a um toque **sem competir com o CTA** | Remover a navegação (a página deixa de pertencer ao site e você mantém duas coisas) · cabeçalho completo na página de serviço (paga o custo de conversão sem necessidade) |
| **MK7** | **Mega menu: *disclosure de links*, nunca `role="menu"`.** Máximo **2 colunas**, cada grupo com título próprio, imagem opcional por item | O **MN1** já decidiu a fronteira semântica das quatro saídas, e o §46 já consome a saída *disclosure*. O teto de 2 colunas vem do inventário: o mega menu atual usa 3 grupos, e o único com 2 colunas é "soluções" — que é o caso real de agrupamento duplo (técnicas + fotovoltaico por segmento) | `role="menu"` (anti-padrão testado, corpus Roselli/APG) · 3+ colunas (o inventário não sustenta a necessidade) |
| **MK8** | **PÁGINA DE SERVIÇO AUTÔNOMA, em cinco movimentos: conversão → dor → prova → escopo → conversão.** Precisa se sustentar para quem cai de anúncio, sem contexto anterior. **Um sinal mínimo de prova acompanha o CTA de topo — um número, não parede de logos.** **O formulário existe UMA VEZ; o CTA do fim é âncora que leva até ele** | Sequência definida pelo Rafael. Dor → prova → escopo é **PAS** (problema, agitação, solução), o mesmo framework que a skill `seed-ds-mensagem` já usa; a conversão vem **antes** porque quem chega de tráfego pago já está no meio do PAS — fazer essa pessoa rolar para achar o formulário friciona quem já está pronto, e quem não está rola e encontra a explicação. O sinal de prova no topo tem número: **contagem de clientes nomeada acima da dobra produziu +22%**, o maior ganho de qualquer formato de prova testado. **Formulário único** porque duplicá-lo cria duas fontes de verdade, duas medições e o erro clássico de a pessoa preencher o de baixo depois que o de cima já falhou | Dor → prova → escopo → conversão (minha proposta inicial: friciona quem já decidiu) · dois formulários na mesma página · parede de logos e metodologia no topo |
| **MK9** | **Variante de PÚBLICO dentro da página de serviço, não página duplicada.** No fotovoltaico o recorte é por **SEGMENTO** — casa, comércio, indústria, área rural | Achado do estudo: **o site atual já resolve exatamente assim**, com quatro páginas por segmento. Segmento é mais operacional que "B2B/B2C" — o visitante se reconhece em "na minha indústria", não em "sou B2B" | Duas páginas B2B/B2C do mesmo serviço (competem entre si no buscador e dobram a manutenção) · uma página tentando falar com os dois ao mesmo tempo |
| **MK10** | **Dados estruturados por TIPO de página:** `Organization` na home e na institucional · **`Service` em cada página de serviço**, com `serviceType`, `areaServed` e `provider` apontando para a `Organization` · `BreadcrumbList` onde há trilha · `FAQPage` **só onde há FAQ real e visível** | O schema.org tem o tipo `Service` exatamente para isto, e página de serviço é onde a maioria subimplementa. A regra do Google é dura: **nunca marcar conteúdo que não está visível ao usuário** — marcação fabricada é pior que marcação ausente | `LocalBusiness` em toda página (dilui o sinal de entidade) · `FAQPage` sem FAQ visível na página |
| **MK11** | **`areaServed` declara MG, ES e BA.** A marcação **nunca afirma sede onde não há** | A orientação é explícita: um negócio não deve fingir ter instalações de onde não opera de fato. A SEED atende três estados a partir de duas bases | Uma `LocalBusiness` por município atendido (fabricaria presença física e é o erro mais comum de marcação local) |
| **MK12** | **Orçamento de peso por página, MEDIDO no gate:** HTML < 50KB · CSS < 60KB · JS < 150KB comprimido · imagens acima da dobra < 200KB. **Metas de campo:** LCP < 2,5s · INP < 200ms · CLS < 0,1. **Toda imagem, vídeo e iframe com dimensão explícita** | Orçamento de referência para mobile, e as metas são os limiares de "bom" avaliados no percentil 75 de usuários reais. Dimensão explícita é o que impede o deslocamento de layout. Linha de base: esqueleto fixo de 2,5 MB e páginas de até 8,8 MB de mídia | Orçamento aspiracional sem medição no gate (não é orçamento, é desejo) |

### 47.3 Anatomia e fronteiras

**Cabeçalho:** herda a zona de identidade/ações do SH10 (§46), mas **não** é o shell de aplicação
— não há rail nem sidebar contextual. **Rodapé:** navegação secundária em `<nav>` nomeado,
identificação da empresa e links legais; é o lugar canônico do que não merece o cabeçalho.
**Conteúdo:** um `<main>` por página, com `tabindex="-1"` se houver troca de rota sem recarga
(herda o **SH7**). **Skip link** como primeiro focável (herda o **SH8**).

**Nenhum par de cor novo** — o lote 1 é esqueleto e camada de máquina; a cor entra com os blocos
de conteúdo, no lote 2, e é lá que a medição de contraste tem objeto.

**Fora do escopo deste lote, declarado:** hero, prova social, números, card de solução,
depoimento, case em vídeo, FAQ e bloco de contato (**lote 2**) · copy de qualquer página
(**projeto do site**) · quais serviços ganham página e o que o formulário pergunta (**projeto do
site**) · escolha de plataforma e de framework (**projeto do site**) · `llms.txt`, `robots`
liberando rastreadores de IA, presença no índice Bing e validação de pré-renderização (**Fase
8**, ver a precisão de fronteira abaixo) · dívida do site atual (**fora de escopo por decisão**).

> **PRECISÃO DE FRONTEIRA COM A FASE 8, aprovada pelo Rafael em 2026-08-12.** O
> `seed-ds-roadmap.md` colocava toda a camada GEO na **F8**. Cumprir isso ao pé da letra faria o
> 6B escrever os padrões de página **sem** a camada de máquina — e o site novo poderia ser
> construído antes da F8, repetindo o defeito que o estudo acabou de medir. A divisão fica:
> **o que é CONTRATO DE PÁGINA vem para o 6B** (título, descrição, canônica, dados estruturados
> daquela página, orçamento de peso); **o que é PUBLICAÇÃO E DESCOBERTA fica na F8** (`llms.txt`,
> `robots` para rastreadores de IA, índice Bing, site do DS, validação de pré-renderização).
> Não é supersede: nenhuma decisão da F8 deixa de valer — o 6B diz *o que toda página tem*, a F8
> diz *como o conjunto é publicado*.

### 47.5 Lote 2 — blocos de conteúdo · MK13–MK22

> **Escrito em 2026-08-12**, depois do lote 1 (MK1–MK12) e com o mesmo princípio: **contrato por
> bloco, composição livre**. O que cada bloco *diz* é do projeto do site; o que ele *é* está aqui.
>
> **Base adicional deste lote (R2/R3, 2026-08-12):** corpus de prova social em página de conversão
> B2B; documentação do Lighthouse e do web.dev sobre o **padrão fachada** para incorporação de
> terceiros; comparações de peso de incorporação de vídeo; e a **deprecação dos resultados ricos de
> FAQ pelo Google em 7 de maio de 2026**, que trocou o racional de uma decisão do lote 1 (ver a
> errata no fim desta seção).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **MK13** | **HERO: um `<h1>` real, uma promessa, um CTA, um visual.** Ficam **fora** do hero: história da empresa, parede de logos e metodologia | O essencial precisa caber numa tela, no desktop e no celular. O hero é a única região onde a **regra do um** (§1.12) vale para tudo ao mesmo tempo | Carrossel de hero (o visitante vê um slide e vai embora; os demais são custo sem leitura) · dois CTAs concorrentes |
| **MK14** | **PROVA SOCIAL EM DOIS NÍVEIS: mínima** no topo — **um número nomeado, junto ao CTA** — e **completa** abaixo (logos, depoimentos, cases) | Contagem de clientes nomeada acima da dobra produziu **+22%**, o maior ganho entre os formatos de prova testados. Parede de logos no topo não converte e ainda rouba a atenção do CTA | Prova só no fim da página · parede de logos acima da dobra · prova genérica ("milhares de clientes") |
| **MK15** | **NÚMEROS declaram fonte e data.** Número sem procedência não entra na página | O site atual afirma números que o banco de dados não sustenta: a tabela de depoimentos está **vazia** e o portfólio é **100% dado de demonstração** (`estudo-site-atual.md`). Numa página de engenharia, número inventado é risco de responsabilidade, não enfeite | Números redondos sem origem · número atualizado à mão sem data (envelhece em silêncio) |
| **MK16** | **CARD DE SOLUÇÃO: título + uma linha de dor + link-com-seta.** Não é mini-anúncio; é uma **porta** | Herda o **A10** do `estudo-viver-de-ia.md` (link-com-seta como terceiro nível de ação, abaixo de primário e secundário). Card com botão compete com o CTA da página e dilui o único objetivo dela | Card com botão primário · card com parágrafo (vira página dentro da página) |
| **MK17** | **DEPOIMENTO exige atribuição: nome, cargo e empresa.** Sem os três, é texto, não prova. Depoimento **nunca** é gerado nem ilustrativo | O site no ar hoje exibe depoimentos que são **copy de exemplo do CMS**. Em B2B, depoimento anônimo tem valor **negativo**: sinaliza que não houve autorização, e o leitor lê isso | Só o primeiro nome · avatar genérico de banco de imagens · depoimento sem empresa |
| **MK18** | **CASE É PEÇA ENDEREÇÁVEL: página própria no domínio da SEED**, com título, cliente, solução, resultado e vídeo | Requisito do Rafael: mandar o link do case direto ao cliente na conversa. **Link do domínio da SEED constrói a autoridade da SEED**; link do YouTube constrói a do YouTube. E case sem endereço próprio não é enviável | Case como item de lista sem âncora (não dá para mandar o link) · enviar o link do YouTube |
| **MK19** | **VÍDEO POR PADRÃO FACHADA, COM MEDIÇÃO — E NEUTRO EM FORNECEDOR.** Contrato de quatro cláusulas: **(a)** miniatura estática + botão de play, e **nada de terceiro carrega antes do clique**; **(b)** ao carregar, o player emite **início e marcos de 25/50/75/100%**; **(c)** a página que hospeda o bloco vive **no domínio da SEED** (MK18); **(d)** se o fornecedor exibir sugestões ao fim da reprodução, elas ficam **restritas ao próprio canal da SEED** — e, quando o fornecedor não permitir sequer isso, o bloco declara a limitação em vez de fingir que a controla. **O Design System NÃO escolhe fornecedor de vídeo** | A incorporação padrão custa centenas de KB de JavaScript, abre várias conexões e disputa a linha principal **mesmo que ninguém clique**; o `loading="lazy"` nativo **apenas adia** o custo. A fachada carrega ~**224×** mais rápido e é a recomendação explícita do Lighthouse. *A medição não briga com a fachada:* só quem clica é medível de qualquer forma, então adiar o player não perde dado nenhum. **Neutralidade de fornecedor decidida pelo Rafael em 2026-08-12:** quem serve o arquivo — YouTube, hospedagem dedicada de plataforma de curso, ou servidor próprio — é decisão de **produto**, revisável sem tocar no padrão. O que o DS garante é o comportamento na página. *Sobre a atribuição por pessoa, que parecia depender do fornecedor:* **não depende** — como a página é da SEED e o colaborador está autenticado nela, a página atribui o progresso; o player só precisa emitir os eventos | Incorporação direta · `loading="lazy"` como solução (adia, não elimina) · fachada sem API (seria performance sem medição, e o requisito é medir) · **exigir hospedagem dedicada** (resolveria os relacionados e os cookies, mas amarra o padrão a uma mensalidade e a um fornecedor, para um ganho que o caso da SEED não pede) · **proibir o YouTube** (custo zero e medição atendida; o único preço real é a grade de sugestões do próprio canal) |
| **MK20** | **FAQ: accordion §29, perguntas reais, com `FAQPage` marcado — pela CAMADA DE MÁQUINA, não pelo Google** | **Os resultados ricos de FAQ deixaram de aparecer no Google em 7 de maio de 2026**, inclusive para os sites de governo e saúde que eram a última exceção desde 2023. A marcação **continua válida** e segue sendo lida por Bingbot — cuja base alimenta a busca do ChatGPT e o Copilot —, pelo PerplexityBot e por indexadores de assistente de voz. Para uma empresa que quer ser citada quando alguém pergunta a um modelo sobre subestação em Minas, **isso vale mais que o dropdown que morreu**. Vale a regra do lote 1: **só com FAQ visível na página** | FAQ inventada para render marcação (foi o abuso que matou o recurso) · remover a marcação porque o dropdown acabou (confunde recurso de busca com leitura de máquina) |
| **MK21** | **BLOCO DE CONTATO POR SERVIÇO, dentro da página, com o mínimo de campos que qualifica.** É o **único** formulário da página (MK8) | Cada campo a mais reduz conversão, mas o caso da SEED pede qualificação — então o mínimo é **por serviço**, não universal. Um formulário de fotovoltaico residencial e um de subestação não perguntam a mesma coisa | Formulário universal (não qualifica) · formulário em página separada (perde a intenção no caminho) · dois formulários na mesma página |
| **MK22** | **O BLOCO DE VÍDEO É O MESMO no site e na plataforma de treinamento interna.** Um contrato, dois consumidores: página de case (externo) e aula (interno) — mesma fachada, mesmos eventos, mesma medição | O Rafael descreveu os dois usos na mesma frase, e são **o mesmo objeto**: vídeo empacotado no domínio da SEED, com progresso medido. Dois componentes divergiriam em seis meses e mediriam diferente, o que impede comparar | Player próprio para o treinamento (duplica manutenção e cria duas medições incomparáveis) |

> **ERRATA MK-E1 — contra o racional do MK10, no lote 1.** O MK10 mandou marcar `FAQPage` sem
> dizer para quê, e a suposição implícita era o **resultado rico** do Google — que **não existe
> mais desde 7 de maio de 2026**. **A decisão permanece; o racional muda:** a marcação serve à
> camada de máquina (Bing, e por consequência a busca do ChatGPT e o Copilot; Perplexity;
> indexadores de assistente), não à aparência na busca do Google. *Classe de defeito:* **decisão
> certa pelo motivo errado** — sobrevive ao teste enquanto o motivo não é questionado, e cai junto
> com ele. Parente da E19 da Fase 5 (regra herdada de outro consumidor sem remedir o contexto).

> **ERRATA MK-E2 — capacidade AFIRMADA e não VERIFICADA, contra a 1ª redação do MK19.** A
> primeira versão mandava **"vídeos relacionados desligados ao fim"**, com o racional de que a
> grade final poderia levar o cliente a um concorrente dentro da própria página de case. **A
> capacidade não existe no YouTube:** desde 2018 o parâmetro que servia para desligar passou a
> apenas **restringir as sugestões ao mesmo canal** — não há como removê-las. O risco real é,
> portanto, muito menor do que o racional supunha (as sugestões são do canal da SEED, não de
> concorrentes), mas **a spec afirmava um controle que o meio não entrega**. O MK19 passou a
> exigir a restrição ao próprio canal e, onde nem isso for possível, a **declaração explícita da
> limitação**. *Classe de defeito:* **capacidade afirmada e não verificada** — prima da MK-E1
> (decisão certa pelo motivo errado), com a diferença de que aqui o motivo estava certo e o
> **mecanismo** é que não existia. *Como foi pega:* pergunta do Rafael — *"por que não podemos
> usar o YouTube?"* — sobre uma proibição que a spec nunca fez, mas que a redação sugeria.

> **FRONTEIRA DE LGPD, declarada — MK19 e MK22.** Medir **o que um cliente assistiu** e medir **o
> que um colaborador assistiu** são coisas juridicamente diferentes: a segunda é monitoramento de
> pessoa identificada em relação de trabalho, e exige base legal, transparência com o colaborador
> e finalidade declarada. **O Design System declara o MECANISMO** — o bloco de vídeo expõe os
> eventos de progresso. **O que se coleta, de quem, por quanto tempo e para quê é decisão de
> produto e de conformidade**, nunca de padrão. É a mesma separação que o **DP12** fez no mapa
> entre destino externo e interno.

> **Origem do vídeo do Viver de IA — lacuna FECHADA em 2026-08-12.** O `estudo-viver-de-ia.md`
> descrevia o player daquela plataforma **apenas como interface** (§3.19), e a medição de código
> da v1.2 mirou tokens, tipografia e movimento — **nenhum dos dois olhou de onde o vídeo vinha**.
> A lacuna ficou declarada como não respondida, e **o Rafael a fechou por medição direta**: o menu
> de contexto do player identifica **Panda Video**, hospedagem brasileira voltada a plataforma de
> curso. Isso explica o player com marca própria e sem grade de sugestões ao fim.
> *Consequência para o DS:* **nenhuma** — o MK19 é neutro em fornecedor de propósito. O achado
> vale como referência do que uma hospedagem dedicada entrega, e está registrado no
> `estudo-viver-de-ia.md`.


### 47.4 Os 7 testes

1. **Nenhum par de cor novo** neste lote; a medição de contraste tem objeto no lote 2.
2. **Escada de mecanismos:** item ativo do cabeçalho por marca + peso + `aria-current`, nunca só
   cor (herda o SH5).
3. **Grayscale:** cabeçalho, trilha e estado ativo permanecem legíveis (DF4 como gate estrutural).
4. **Regra do um:** um `<h1>` real por página · um `<main>` · **um formulário por página de
   serviço** · um item ativo por região de navegação.
5. **Landmarks:** todas as navegações com nome único, nenhum contendo a palavra "navegação"
   (MK5/SH2).
6. **Camada de máquina:** título, descrição e canônica próprios; dados estruturados do tipo certo
   e **só do que está visível**; nada de marcação fabricada (MK10).
7. **Mobile primeiro, medido:** zoom livre (MK4) · alvos ≥44px · **orçamento de peso e metas de
   campo verificados no gate** (MK12) · dimensão explícita em toda mídia · **nenhuma incorporação
   de terceiro carrega antes do clique** (MK19) · **nenhum número, depoimento ou case sem
   procedência** (MK15, MK17) — este último é teste documental, não visual, e reprova a página que
   afirma sem poder sustentar.

*(Estado: **`estável` desde 2026-08-12**, por "aprovo" explícito do Rafael após o gate visual.
**Quatro camadas cumpridas em 2026-08-12** sobre o preview
`seed-site-preview.html` v0.1, que exercita o contrato numa **página de serviço** — o tipo mais
exigente, por ser autônoma: `suite-site.mjs` **66 PASS · 0 FAIL** — *prova de que as guardas são
reais:* contra um preview com 5 defeitos deliberados (zoom bloqueado, `LocalBusiness` no lugar de
`Service`, número sem fonte, player sem API, `nav` sem nome) ela **reprova em 6**;
`render-site.mjs` em Chrome real **23 PASS · 0 FAIL**, medindo CTA acima da dobra em três
larguras, alvos de toque e — o teste que só o render faz — **zero requisições externas antes de
qualquer clique**, com o iframe aparecendo apenas após o clique na fachada; `contraste-site.py`
**42 PASS · 0 FAIL** após as correções que a própria medição exigiu; e o **gate visual humano**.
**Pendência que o bloco leva consigo: nenhuma própria** — a SH-P1 do §46 (falta de token
semântico de borda que passe 3:1 contra `surface-raised` no claro) valia aqui também, porque
o campo de formulário do §47 consumia o mesmo empréstimo de primitivo. **Com a SH-P1 fechada
(gêmeos v1.12, 2026-08-14), o §47 tem o semântico `--seed-border-interactive` à disposição —
a migração do artefato do §47 é retroativo da classe PN-P7** (o preview do site não foi
regenerado nesta janela; regra GI2 vale para quando for).
**✅ O retroativo FECHOU em 2026-08-15 (PN-P7 executado, MANIFESTO v7.6; aprovado pelo Rafael
na v7.7):** o `seed-site-preview.html` foi a v0.2 com `input, select, textarea` consumindo o
semântico — e a prova em pixel mediu **0,00% de diferença** contra o artefato anterior
(mesmo valor renderizado #788F9D: promoção de expressão, não de cor). Suíte 66·0 · render
23·0 · contraste 42·0 contra o artefato final.)*

> **O que a medição de contraste corrigiu, e a lição é reaproveitável.** A primeira execução deu
> **39 PASS · 3 FAIL**, todas no tema escuro e todas pela mesma causa: eu havia usado
> **`surface-brand-deep` como cor de TEXTO** (item ativo do mega menu, link-com-seta, marca). Ele
> é **superfície**: no tema escuro vira um verde quase preto e mede **1,99** contra a página. A
> correção é consumir **`text-link`** para texto de marca (**4,60** no claro · **10,16** no
> escuro) e **`text-brand`** para a barra do depoimento (**4,23** · **9,32**; `surface-brand`
> media 2,50 no claro). *Classe de defeito:* **token usado fora do papel declarado** — o nome
> `brand-deep` sugere "a versão escura da marca", e a leitura natural é que sirva para texto;
> o papel dele é superfície. Parente do SH15 do §46, onde `surface-inverse` invertia com o tema.

---

## 48. Painel — grade, lentes, frescor, edição direta e mapa — **`estável`** (v0.62) · **validado pelo Rafael em 2026-08-13** · PN1–PN54 · pattern F6 · **cinco camadas de validação verdes: `suite-painel.mjs` 133/133 · `render-painel.mjs` 37/37 · `render-edicao.mjs` 142/142 · `render-contraste.mjs` 6/6 · `contraste-painel.py` 70/70** · preview **v0.6** · **BLOCO 6C FECHADO**

> **⚠ FRONTEIRA DA PROMOÇÃO, e ela é dura.** O que este `estável` cobre são os **contratos** PN1–PN54: grade, lentes, frescor, blocos de dado, edição direta, mapa. **A camada visual (§48.3) é PROPOSTA**, não canônico.
>
> **A ressalva se pagou em menos de 24 horas, e o registro disso é útil.** A promoção foi escrita em 2026-08-13 excluindo três decisões de marca; na mesma noite o Rafael retirou o azul (*"eu tinha aceitado para podermos parar com os testes"*) e a **âncora escura virou turquesa vivo**. Duas das três decisões mudaram de valor ou saíram — e **nada** do `estável` precisou ser tocado. Se o azul tivesse entrado no canônico no dia anterior, isto seria errata em `marca-seed.md`, nos três gêmeos de token, no sistema de e-mail (`estável`) e nos 40 HTML do repositório.
>
> **Estado atual das decisões de marca:** **S3** revisto — a âncora é `turquesa-400` #11B0A0, não mais `azul-800`. **S4 RETIRADO** — a ação volta à família da marca, e o conflito com o §3.5 de `marca-seed.md` **deixa de existir**: nada a superseder na marca. **S6** (tinta da família) segue **PROPOSTA**, como camada alternável pelo botão "Tinta". Ver §48.8 e a pendência **PN-P1** do §48.9, que encolheu na mesma proporção. *Quem consumir este §48 hoje para escrever código usa a régua de ação do `marca-seed.md` §3.5 — que é, desde esta rodada, a MESMA do preview.*

> **Natureza:** pattern de **COMPOSIÇÃO** do F6, como o §33, o §46 e o §47 — **nenhum componente
> novo nasce aqui**. Consome o shell (§46), tabela (§40), lista de dados (§43), toolbar (§42),
> tabs (§34), os gráficos da Fase 5 e os estados de espera do Bloco 3.
>
> **Este bloco cumpre uma fronteira declarada há duas fases.** O **DF6** do `seed-dataviz.md`
> (Fase 5, `estável` desde 2026-08-09) escreveu: *"dashboard/layout de painel = F6 — a F5 entrega
> a peça, não o painel"*. É esta seção.
>
> **O QUE ESTE BLOCO ENTREGA, EM TRÊS RODADAS.** O 6C foi produzido em três rodadas, e o
> registro da ordem importa porque cada uma nasceu de um gate do Rafael sobre a anterior.
> **Rodada 1 (2026-08-12):** a grade, o comutador de visualização com pré-requisitos de dado, a
> gramática do frescor, a variante equipe × cliente, o consumo do movimento (PN1–PN12) e os
> quatro blocos de dado que não são gráfico (PN13–PN20). **Rodada 2 (2026-08-13, manhã):** o
> supersede do PN3 para **oito lentes** e a **camada visual** PN21. **Rodada 3 (2026-08-13,
> tarde e noite):** a **edição direta** (PN22–PN41), os **contratos que o gate produziu**
> (PN46–PN50) e a **lente mapa em dois modos** (PN51–PN54). **Preview e gate visual são únicos,
> no fim.**
>
> **Escopo do produto, decidido em briefing antes da pesquisa (2026-08-12):** o ERP atende
> **quatro setores** — Admin, Engenharia, Comercial e Qualidade — com áreas dentro deles; **a
> estrutura de setores e áreas e o desenho do workflow são do PROJETO DO ERP**, declarados fora
> deste bloco pelo Rafael. O painel serve **dois públicos**: a equipe (prioritário) e o cliente,
> que terá área própria com produtos e serviços contratados, arquivos e contratos, monitoramento
> de usina e um sistema de **indicação de leads com acompanhamento de status**. O painel é
> **fixo** — o que varia é a **forma de exibir a mesma informação**, no padrão do ClickUp. O
> painel precisa **poder** mostrar informação em tempo real. Referências visuais: ClickUp
> (comutador) e a plataforma do `estudo-viver-de-ia.md` (KPI, ranking, progresso, camada visual)
> — ambas já estudadas e ancoradas; nenhuma referência externa nova.
>
> **A DISTINÇÃO QUE DEFINE O TAMANHO DO BLOCO, e ela não estava clara no briefing.** O que o
> Rafael descreveu **não é painel configurável — é comutador de visualização**. *Configurável* é
> o usuário arrastar blocos e escolher o que aparece: caro, e explicitamente descartado.
> *Comutador* é **a mesma informação em formatos diferentes**, com a tela permanecendo fixa.
> Separar as duas coisas cortou o custo do bloco pela metade e é a razão de o PN1 e o PN3
> coexistirem sem conflito.
>
> **Base (3 rodadas encadeadas, 2026-08-12):** **R1 canon interno** — **DF6** (esta fronteira) e
> **DM10** (tempo real é produto); a **Régua de Espera** do Bloco 3 em três regimes compostos
> (ação · carga inicial · refresh sobre conteúdo, com delay de 1s e permanência mínima); os
> **dois regimes de live region** do §1.13 (discreto × contínuo, uma por componente); o §46
> inteiro (SH2 landmarks nomeados, SH5 ativo que sobrevive ao grayscale); o **DP12** (destino
> externo × interno); o §40 (densidade compacta medida em 33px); e a **§6b do
> `estudo-viver-de-ia.md`** — a auditoria que mostrou que a "camada de movimento" **não era
> lacuna de token** (existem 4 durações + 4 easings + `prefers-reduced-motion` desde os
> primitivos) e sim **de consumo**. **R2 mercado** — comutador de visualização no ClickUp, Notion
> e Linear: as lentes mostram os mesmos itens com filtro e layout próprios sobre a mesma fonte;
> trocar de lente não altera os itens; e a adequação da lente **depende do dado**. **R3 normas** —
> **WCAG 2.2.2 (nível A)**: informação que se atualiza sozinha, começa automaticamente e é
> apresentada em paralelo com outro conteúdo exige mecanismo para **pausar, parar, esconder ou
> controlar a frequência**; painel com intervalo escolhível (30s / 5min / manual) satisfaz pelo
> controle de frequência. **Base adicional da rodada 3 (edição direta):** **WCAG 2.2, SC 2.5.7
> Dragging Movements (AA)** — toda funcionalidade que usa arrasto tem de ter **caminho de
> ponteiro único** alternativo; e a documentação do ClickUp sobre gantt × timeline (ver PN3c).
> Consolidados aprovados pelo Rafael em 2026-08-12 e 2026-08-13.

### 48.1 Decisões estruturais · PN1–PN12

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **PN1** | **O painel é uma GRADE de 12 colunas**, com blocos de largura declarada (4, 6, 8 ou 12). **Layout fixo por tela**, definido no projeto do ERP | Painel configurável — arrastar, redimensionar, escolher o que aparece — foi **descartado pelo Rafael** e é várias vezes mais caro: o DS deixaria de desenhar a tela para desenhar as peças mais a grade que as recebe. Grade declarada dá liberdade de composição sem arrasto | Painel arrastável · larguras livres em pixel (impede reflow previsível a 360px) |
| **PN2** | **Todo bloco do painel é `<section>` com cabeçalho próprio e nome acessível. Um bloco = uma pergunta respondida** | Painel é uma página com muitas regiões; sem nome, o leitor de tela entrega uma pilha de conteúdo sem estrutura. Herda o **SH2** do §46. A regra "uma pergunta" é o que impede o bloco-depósito | Bloco sem título · dois assuntos no mesmo bloco |
| **PN3** | **COMUTADOR DE VISUALIZAÇÃO — OITO lentes** *(SUPERSEDE 2026-08-13; 1ª redação: cinco)*: `lista` · `tabela` · `kanban` · `gantt` · `tempo` (linha do tempo por responsável) · `calendario` · `carga` (carga de trabalho) · `mapa`. **Os oito nomes entre acentos graves são os identificadores canônicos** — o atributo `data-lente` do preview usa exatamente estes | É o que o Rafael pediu, no padrão do ClickUp. **A tela é fixa; o que troca é a lente.** *O supersede tem duas causas, ambas de 2026-08-13:* **(a)** a documentação do ClickUp provou que gantt e linha do tempo são lentes DIFERENTES (ver PN3c) — a 1ª redação as tratava como sinônimos; **(b)** decisão do Rafael: *os templates estruturais nascem AGORA, e a alocação de pessoa precisa estar prevista mesmo que o ERP não a use de imediato* — o que supersede a proposta anterior de deixar linha do tempo e carga de trabalho "para depois". O **mapa** entra como lente porque OS tem endereço e usina tem coordenada, e o §4d (DP) já entrega malha IBGE e projeção: ver a mesma lista de OS num mapa é o mesmo dado noutra forma. No modo cliente, o mapa herda o **PN10/DP12**: só as usinas do próprio cliente | Painel configurável · lente única por tela · adiar a alocação de pessoa (o template nasceria sem o lugar dela e obrigaria redesenho — a mesma razão da ordem lote 1 → lote 2) · tratar como lentes o que é tipo de conteúdo ou tela própria no ClickUp (formulário, documento, whiteboard, mapa mental, chat) ou integração de produto (incorporações) · o rótulo **"quadro"** como nome canônico (ver **S2** em §48.8) |
| **PN3b** | **A LENTE DESENHA A FORMA QUE O NOME PROMETE.** Linha do tempo é **barra posicionada e dimensionada por data**, sobre escala de datas. Calendário é **grade de mês** — sete colunas iguais, primeiro dia da semana calculado, evento no seu dia. Kanban é **coluna por estado**. Nenhuma delas pode ser uma lista com outro rótulo | **Achado no gate visual do Rafael.** O preview v0.1 renderizava "linha do tempo" e "calendário" como **listas** — passava em todo teste de comportamento e **mentia para o olho**. É a mesma classe do **botão morto** que o projeto já proíbe: *"responder significa DEMONSTRAR, não explicar"* (§A.3 do ESTADO_ATUAL, emendado no marco v1.7). *Guardas permanentes:* `PN3b-01…08` na suíte e `R5b-01…08` no render, este último medindo **geometria** — barras em posições e larguras distintas, dentro da faixa, e as sete colunas do calendário com a **mesma largura** | Lente que só troca o rótulo · lente "em breve" (é botão morto com outro nome) |
| **PN3c** | **GANTT ≠ LINHA DO TEMPO — são perguntas diferentes sobre as mesmas tarefas.** **Gantt** responde *"o que trava o quê"*: uma faixa por TAREFA, barra posicionada e dimensionada por data, e **as dependências DESENHADAS** — conector do fim da predecessora ao início da dependente. **Linha do tempo** responde *"quem está ocupado quando"*: uma faixa por RESPONSÁVEL, com as tarefas da pessoa empilhadas dentro da faixa dela (sub-linhas quando se sobrepõem). Gantt sem dependência desenhada não é gantt | Fonte: documentação oficial do ClickUp (Gantt = tarefas conectadas, sequenciamento, caminho crítico, reagendar cadeias; Timeline = cronograma linear por recurso, para roadmap e gestão de alocação). *Registro de origem:* o preview v0.2 implementava um gantt sem dependências com o rótulo de linha do tempo — a distinção veio de pergunta do Rafael (*"tem o gantt e tem a linha do tempo, existe diferença"*) e foi respondida pela fonte, não por dedução | Tratar como sinônimos (o defeito de origem) · gantt sem conector de dependência (é linha do tempo de tarefa com o nome errado) |
| **PN4** | **O DADO habilita a lente, não a tela.** Pré-requisitos declarados: `lista` e `tabela` não exigem nada · `kanban` exige campo de estado com valores finitos em **todos** os itens · `gantt` exige **ao menos uma** ordem com início E fim · `tempo` exige ao menos uma agendada **E** alguém com responsável · `calendario` exige ao menos uma com data · `carga` exige responsável · `mapa` exige coordenada ou endereço geocodificável em **todos**. **Lente sem pré-requisito não é oferecida** — nunca oferecida-e-vazia; e o motivo da ausência é dito com o nome do campo que falta | Responde a dúvida do Rafael (*"as telas serão montadas dizendo que é permitido, a não ser que o sistema consiga entender sozinho"*) **sem as duas saídas ruins**. Regra estável, sem lista de exceções para manter. *O quantificador "ao menos uma" é SUPERSEDE de 2026-08-13 — ver **PN47** e **S8** em §48.8:* a 1ª redação exigia o dado em TODOS os itens e uma única ordem sem data derrubava o gantt inteiro | Marcação manual por tela (vira lista viva de manutenção, e lista de exceções apodrece) · inferência mágica sem regra escrita (falha em silêncio) · oferecer a lente e mostrar vazio (o usuário conclui que não há dado, quando o que falta é campo) |
| **PN5** | **A lente NUNCA altera o conjunto.** Filtro e ordenação **viajam** com a troca de lente; o que muda é a forma | Se a lente filtrar, o usuário perde o rastro do que sumiu e passa a desconfiar do painel. É o que a evidência de mercado descreve: trocar de visualização não muda os itens, só como aparecem | Filtro embutido na lente · lente que esconde item sem pré-requisito (esconder é filtrar) |
| **PN6** | **A lente ativa é estado de navegação:** `aria-current`, marca visual que **sobrevive ao grayscale**, e **uma lente padrão declarada por tela** | Herda o **SH5** do §46 sem alteração. A lente padrão existe para que a tela abra resolvida, não perguntando | Só cor de fundo (morre em cinza) · última lente usada como padrão global (o mesmo usuário quer kanban numa tela e calendário noutra) |
| **PN7** | **GRAMÁTICA DO FRESCOR.** Todo bloco com dado que se atualiza declara três coisas: **momento da última leitura** · **estado atual** (atualizado / atualizando / falhou) · **idade do dado quando ela importa** | O **DM10** e o **DF6** já declararam que tempo real, websocket e persistência são **produto**. O DS declara a **gramática**, nunca o transporte — mesma precisão de fronteira que o §47 fez com a camada GEO. Sem isso, *"está gerando 0 kW"* e *"não sei o que está gerando"* viram a mesma tela — e num painel de usina essa diferença é a diferença entre chamar o técnico e não chamar | Indicador global de "ao vivo" no topo do painel (não diz **qual** bloco está velho) · dado velho sem marca · esconder o valor enquanto atualiza (pisca e perde a referência) |
| **PN8** | **CONTROLE DE FREQUÊNCIA OBRIGATÓRIO em painel que se atualiza sozinho** — intervalo escolhível, com opção **manual** | **WCAG 2.2.2, nível A**: informação que se atualiza automaticamente e é apresentada em paralelo com outro conteúdo exige mecanismo para pausar, parar, esconder **ou controlar a frequência**. Um painel com intervalo escolhível satisfaz pela frequência. **Painel em tempo real sem isso não é AA — não é nem A** | Atualização fixa e não interrompível · pausar sem retomar (o usuário fica com dado velho sem saber) |
| **PN9** | **Atualização automática NÃO move o foco, NÃO reordena o que está sendo lido e NÃO fala sozinha.** Só o que o usuário pediu para acompanhar anuncia; o resto atualiza em silêncio, com o frescor do PN7 dando a evidência visual. **O painel tem UMA live region** (`role="status"`, `aria-live="polite"`), e todo anúncio — de edição, de filtro, de zoom, de fallback de mapa — sai por ela | Um painel que anuncia cada mudança em live region é **inutilizável** com leitor de tela. Herda os dois regimes do §1.13: o discreto (o nó visível é a live region) serve ao bloco acompanhado; o contínuo, com debounce e marcos, ao resto. *A regra da região única foi reafirmada na rodada 3:* o rótulo do zoom (`"15 d na tela"`) é **estado visível**, não anúncio — quem anuncia a mudança é a região única | Toda atualização em `aria-live` · reordenar a lista enquanto o usuário lê · rolar automaticamente ao chegar dado novo · uma live region por controle (duas regiões competindo produzem fala sobreposta) |
| **PN10** | **Variante EQUIPE × CLIENTE declarada, com UM contrato só.** O painel do cliente vê **apenas o que é dele**; identificação de terceiros nunca aparece — **inclusive dentro de agregados** | Herda a estrutura do **DP12** (destino externo × interno). Área do cliente com produtos contratados, arquivos, contratos, monitoramento e indicação de leads é **o mesmo painel com escopo diferente**, não um segundo produto. A ressalva do agregado é a que costuma vazar: "você está entre os 3 maiores clientes" revela o tamanho dos outros | Dois painéis (divergem em seis meses) · mesmo painel sem escopo declarado (vazamento por omissão) |
| **PN11** | **CONSUMO DO MOVIMENTO declarado por papel:** registro **produtivo** para o que serve à tarefa (troca de lente, abrir e fechar bloco, hover) · registro **expressivo** só para momento significativo (entrada de bloco novo, confirmação de ação). **Atualização de dado NÃO anima** | Fecha a lacuna nº 5 do censo do `estudo-viver-de-ia.md` — que a **auditoria da Fase 5 provou não ser lacuna de token**: existem 4 durações, 4 easings e o bloco `prefers-reduced-motion` desde os primitivos (errata E-EST1). O que faltava era **declarar o consumo**, e é isto. Dado que pisca a cada leitura é ruído: chama atenção para o que não mudou de importância | Animar a troca de valor · registro expressivo em interação de tarefa (300–400ms numa troca de lente parece o sistema travando) |
| **PN12** | **Densidade: compacta por padrão no ERP, confortável no painel do cliente** | O §40 já mediu a compacta em **33px** e o gate do Bloco 6 a aprovou. Equipe treinada ganha com densidade — mais informação por tela, menos rolagem; cliente eventual perde, porque densidade exige familiaridade | Densidade única para os dois públicos |

### 48.2 Blocos de dado que não são gráfico · PN13–PN20

> **Escrito em 2026-08-12.** Estes quatro blocos — KPI, ranking, progresso e timeline — são os
> **buracos vermelhos** que o censo de lacunas do `estudo-viver-de-ia.md` (§6) encontrou e que a
> **auditoria de impacto** do marco da Fase 5 (§6b daquele arquivo) classificou como **aditivos**:
> nenhum exige supersede em algo `estável`. O achado estrutural do censo foi que os buracos **não
> eram gráficos** — eram **componentes de dado que não são gráficos**, uma classe intermediária
> sem dono entre o Bloco 6 (dados) e o DG (gráficos core). É esta seção.
>
> **Três dos quatro são INVÓLUCRO, não desenho** — e isso está medido, não suposto: o **DG16** já
> especifica a gramática do cartão-KPI, e o **DG15** mais o **DG17** já especificam a barra de
> ranking e o caso de uso dela. O DS **promove o que já existe**; inventa só onde havia vazio de
> verdade (timeline).
>
> **Base adicional deste lote (R2/R3, 2026-08-12):** APG do W3C sobre propriedades de faixa
> (`aria-valuenow`, `aria-valuemin`, `aria-valuemax`, `aria-valuetext`); documentação do MDN sobre
> o papel `progressbar` e o efeito dele sobre os descendentes; a distinção normativa entre
> `progress` (conclusão de tarefa) e `meter` (quantidade escalar em faixa conhecida); e a
> orientação de anunciar progresso em **marcos**, não a cada ponto percentual.

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **PN13** | **KPI — o número é o PROTAGONISTA.** Valor em `fs-h2` peso 800 · rótulo em micro-caps mono · variação em **badge-pílula suave** (fundo `feedback-*-bg`, texto `feedback-*-text`). Vive **fora de `<figure>`**: é bloco autônomo do painel, não gráfico | **O DG16 já escreveu esta gramática e está `estável` desde 2026-08-10.** O 6C **promove, não inventa** — a auditoria de impacto da Fase 5 já havia mostrado que a lacuna era "menos vazia do que parecia". Reescrever produziria duas fontes da mesma regra, que é o problema que o DS existe para matar (DF7) | Recriar a gramática aqui (deriva garantida) · KPI dentro de `<figure>` (obrigaria o fallback textual do DG1 para um número que **já é** texto) |
| **PN14** | **O KPI declara o PERÍODO e a BASE de comparação.** *"R$ 486k · +12% vs. mês anterior"* — nunca "+12%" solto | Variação sem base é número sem significado: +12% contra o mês passado, contra o ano passado e contra a meta são três afirmações diferentes. É o irmão do **MK15** do §47 (número declara fonte e data) — mesma regra, outro contexto. E a seta de variação nunca vai sozinha: a escada de mecanismos do §1.12 exige sinal **mais** texto, senão morre em escala de cinza | Variação sem referência · seta colorida como único sinal · comparação implícita ("o usuário sabe qual é") |
| **PN14b** | **A COR DA VARIAÇÃO VEM DO SENTIDO DO INDICADOR, NUNCA DO SINAL.** Cada KPI declara a polaridade — *maior é melhor* (geração, usinas em operação) ou *menor é melhor* (chamados abertos, tempo de resposta, perdas) —, e a pílula é positiva quando a variação **melhora** o indicador. A **seta** carrega o sinal; a **cor** carrega o juízo | **Achado na inspeção do render, não previsto na spec.** O preview saiu com *"-3 chamados abertos"* em vermelho, porque eu havia pintado pelo sinal da variação. **Reduzir chamados é bom.** Sem a polaridade declarada, todo indicador em que menos é melhor sai pintado ao contrário — e num painel de O&M isso é a maioria deles: falhas, chamados, tempo de parada, perdas. *Efeito colateral bom:* separar seta (sinal) de cor (juízo) dá **dois mecanismos** onde antes havia um, o que é a escada do §1.12 de graça | Pintar pelo sinal da variação (o defeito) · omitir a seta e deixar só a cor (morre em escala de cinza) |
| **PN15** | **RANKING — lista ordenada com barra embutida:** índice em mono · rótulo · **barra fina em pílula completa** (`rx` = metade da altura) · valor numérico à direita. **A barra é proporcional ao MAIOR VALOR DA LISTA, e a base é declarada** | O **DG15** já define a pílula da barra fina e o **DG17** já nomeia o caso de uso: *"ranking de usinas com a usina em foco destacada"*. Faltava a **lista que hospeda**, não a barra. A base proporcional precisa ser dita porque duas listas lado a lado com bases diferentes enganam quem compara, e ninguém percebe | Barra proporcional a um teto arbitrário ou a 100% (achata o ranking inteiro quando os valores são próximos) · ranking sem o valor numérico (a barra sozinha não se lê, e a lista vira decoração) |
| **PN16** | **O ranking mostra quem está bem E quem não está, no MESMO bloco:** contador de exceção no cabeçalho, **sempre com prescrição** | Padrão medido no `estudo-viver-de-ia.md` (§3.7 e §3.12): o card de ranking traz *"4 sem acesso"* no canto, e a exceção vem sempre com o próximo passo ao lado. **Dado + diagnóstico + próximo passo, nunca dado sozinho.** Num painel de O&M, "as 5 usinas que mais geraram" sem "as 3 que não reportaram" é meia informação | Ranking só dos melhores (esconde justamente o que o gestor precisa ver) · exceção sem prescrição (produz ansiedade sem ação) |
| **PN17** | **PROGRESSO DE CONCLUSÃO usa `progressbar` — e isso NÃO contraria o DM4** | O **DM4** escolheu `role="meter"` para o medidor e **descartou explicitamente `progressbar`**. São semânticas opostas, com fonte normativa: **`progress` indica conclusão de tarefa** (*"você completou 30%"*); **`meter` indica quantidade escalar dentro de faixa conhecida** (*"2GB de 10GB usados"*). O DM4 decidiu sobre medidor de valor; este bloco decide sobre progresso de tarefa. **O registro explícito é obrigatório** — sem ele, parece exceção silenciosa, e foi exatamente isso que a auditoria de impacto da Fase 5 mandou evitar | `meter` para progresso (semântica errada: obra 40% concluída não é "40 de 100 unidades usadas") · adotar `progressbar` sem declarar a distinção (a exceção silenciosa que a auditoria antecipou) |
| **PN18** | **NADA SEMÂNTICO VIVE DENTRO DO `progressbar`.** Rótulo, fração e porcentagem ficam **fora**, associados por `aria-labelledby`. Progresso indeterminado **OMITE** `aria-valuenow` | Armadilha documentada no MDN: o navegador aplica **`role="presentation"` a todos os descendentes** de um `progressbar`, porque a API de acessibilidade não representa elementos semânticos dentro dele. Um título ou uma fração colocados na barra **deixam de existir** para o leitor de tela — e o desenvolvedor não tem como perceber, porque a tela continua certa. Sobre o indeterminado, o APG é explícito: se o valor é desconhecido, **omita** `aria-valuenow` | Fração ou rótulo dentro da barra · `aria-valuenow="0"` para indeterminado (afirma "0%", que é falso e diferente de "não sei") |
| **PN19** | **Progresso anuncia em MARCOS — 25 / 50 / 75 / 100% — nunca a cada ponto** | Anunciar cada ponto percentual sobrecarrega o usuário de leitor de tela a ponto de inutilizar a tela. Herda o **regime contínuo** de live region do §1.13, que já prevê debounce e marcos, e é a mesma cadência que o **MK19** do §47 adotou para o vídeo. **A conclusão é o marco que nunca se omite** — é o único que sempre importa | Anunciar cada atualização · omitir a conclusão · anunciar progresso que o usuário não pediu para acompanhar (herda o PN9) |
| **PN20** | **TIMELINE — lista ordenada no tempo, MAIS RECENTE PRIMEIRO**, com data em mono, marcador de evento e agrupamento por período. **Cada entrada é ENDEREÇÁVEL** | Território limpo: nada no DS tocava histórico ordenado no tempo, e o ERP precisa dele para histórico de obra, de manutenção e de comissionamento. **Mais recente primeiro** porque num painel o que importa é o que mudou por último. **Endereçável** pelo mesmo motivo do **MK18** (case em vídeo): um evento de histórico precisa de link próprio para ser citado num laudo, num e-mail ou numa conversa com o cliente | Timeline como decoração vertical sem semântica de lista (leitor de tela recebe uma pilha de texto) · ordem cronológica direta (útil em narrativa, não em painel) · entrada sem endereço (não dá para referenciar) |

*Registro de honestidade:* o KPI, o ranking e o progresso **já tinham gramática escrita** no DG15,
DG16 e DG17 — esta seção entrega o **invólucro**. Só a **timeline** é desenho novo. Dizer isso
importa porque a impressão de que um bloco "criou quatro componentes" é falsa, e quem ler daqui a
um ano precisa saber onde procurar a regra de origem.

### 48.3 Camada visual · PN21a–PN21h

> **Origem:** pedido explícito do Rafael em 2026-08-13 — *as técnicas do `estudo-viver-de-ia.md`
> estavam entendidas e não estavam refletidas no resultado*. Todos os valores abaixo são os
> **medidos** na §9.4 e na §2 daquele estudo ou medidos no gate desta rodada, nunca impressão de
> olho. A adaptação preserva as leis SEED: **paleta 2018 intocada** (a monocromia navy do estudo,
> catalogada lá como **D1**, segue rejeitada), gráfico com anatomia acessível (**D2**), regra do
> dourado e variante `-stroke` continuam valendo por cima.

| # | Regra | Porquê / número medido | Descartado |
|---|---|---|---|
| **PN21a** | **Sombra premium multicamada COM ANEL:** duas difusas + anel de 1px na mesma declaração (`0 0 0 1px`) | Receita §9.4-3 do estudo. O anel é o que separa o cartão do fundo quando a difusa é sutil; sem ele o cartão flutua sem contorno e a borda tem de voltar como traço, que é o que o DS quer evitar | Borda de 1px como único separador · sombra de uma camada (lê como carimbo) |
| **PN21b** | **VÉU RADIAL DE LUZ é a única forma de gradiente — nunca gradiente de cor. O teto de alfa é POR SUBSTRATO, não único:** sobre superfície **clara**, ≤ .07 · em camada `::before` de substrato **escuro**, ≤ .16 | *Emenda medida no gate de 2026-08-13.* Sobre o `cinza-900` da marca, véu de acento a .04 move o pixel **5 níveis de 255** e a .09 move **12** — invisível no monitor do usuário; a **.16 move 21**, que é o mínimo perceptível ali. Sobre superfície clara o teto continua baixo, porque o que se acumula lá é **sujeira, não luz** | Teto único de alfa para os dois substratos (a 1ª redação; deixava o escuro sem nenhuma luz visível) · gradiente de cor |
| **PN21b-2** | **O véu de ACENTO (turquesa) pertence ao substrato ESCURO. Sobre superfície clara o véu é NEUTRO** — cinza da marca a 3% | Medido no gate: véu de acento sobre claro **desvia um canal só** — turquesa a 7% tira 16 níveis de R contra 5 de G — e o olho lê *tinta por cima*, não profundidade. O véu do estudo é quase-neutro (desvio R+10 G+9 B+8); o do claro passou a cinza a 3% (desvio uniforme R+6 G+6 B+6) e a área encolheu | Véu turquesa sobre a página clara (a hipótese testada e reprovada) |
| **PN21b-3** | **O SUBSTRATO DA PÁGINA É ESCURO e a "página clara" é um VÉU BRANCO TRANSLÚCIDO por cima** (.995 → .985 → .965 ao longo da altura) | §9.4-1 do estudo, medido no código da plataforma (.96 → .84 → .58): a página clara é **literalmente iluminada sobre escuro**, e é daí que vem a profundidade. **As duas redações anteriores fizeram o inverso** — página clara chapada com véu escuro por cima — e é por isso que a tela lia sombria em vez de viva | Página clara opaca com véu escuro por cima (as duas redações reprovadas) |
| **PN21c** | **UM hero escuro por página**, com anatomia fixa: micro-rótulo mono → saudação ciente da hora → data viva em mono caps → apoio → **UMA linha de ação** (exceção com prescrição + botão da solução ao lado) | §3.2 do estudo, relido no gate. **A redação anterior empilhava CINCO camadas** (stats E exceção E prescrição E controles) e o hero media **456px contra 268px** do da plataforma. Os stats saíram por serem **os mesmos números dos KPIs logo abaixo** — dado repetido na mesma dobra. **Controle de painel vive em régua CLARA, fora do escuro editorial** | Dois blocos escuros competindo na mesma página · stats no hero repetindo KPI · seletor de frequência dentro do hero |
| **PN21d** | **MONO como camada semântica obrigatória:** todo dado, medição, data, contagem e rótulo de KPI em JetBrains Mono; micro-rótulo = caps + tracking largo + corpo pequeno + cor secundária | Lei dos tokens v1.1 (dado de medição = mono), aqui **promovida a obrigação** do painel. É também a base da amplitude tipográfica: o número gigante só se lê como número porque o rótulo ao lado é micro | Dado em Montserrat · micro-rótulo sem caps/tracking |
| **PN21e** | **INSIGHT-COMO-TÍTULO em todo gráfico do painel** (A1 do estudo) — o título é a conclusão, com **itálico marcando o sujeito** (B2), **no máximo um por título** | O título que descreve o eixo ("OS por estado") obriga o leitor a concluir sozinho; o título que conclui ("*Em campo* concentra 6 das 14 ordens") entrega a leitura. O limite de um itálico existe porque dois sujeitos marcados no mesmo título anulam a marcação | Título descritivo de eixo · dois ou mais itálicos por título |
| **PN21f** | **Tipografia consome a escala canônica:** `fs-display-fluid` (clamp 40→64px) + `ls-display` no hero; `fs-caption` no micro-rótulo. **Amplitude hero/micro-rótulo = 6,1×** | *Defeito medido:* a 1ª redação usou 1,7rem (27px) no hero e **desperdiçou a escala** — o DS já tinha o display fluido e o tracking negativo, e o hero não os consumia. Amplitude é o que faz a hierarquia existir sem borda nem cor | Tamanho digitado em rem no hero · amplitude achatada |
| **PN21g** | **Glow do acento turquesa JAMAIS sobre superfície de dado.** Destaque de ação usa **sombra projetada**, não glow | *Emenda do gate:* com a ação virando **pílula branca** sobre o escuro (ver **S4**), o glow turquesa perdeu o portador — brilho de acento atrás de um botão branco vira **halo sujo**. O destaque passou a sombra projetada. A proibição que importa (glow nunca sobre KPI, gráfico, tabela, mapa) continua medida na suíte | Glow sobre gráfico ou KPI (vira ruído sobre dado) · glow atrás de pílula branca |
| **PN21h** | **Movimento consome os 8 tokens dos primitivos** (PN11): nenhuma duração ou easing solto em `transition`; `prefers-reduced-motion` desliga transform e transição do cartão | O DS já tinha os tokens desde os primitivos; o que faltava era o consumo. Duração literal em CSS é o caminho por onde a camada de movimento deriva sem ninguém perceber | `transition: .2s ease` (valor solto) |

**Fronteira desta camada:** a camada visual inteira **vale no preview e não é canônica**. Depois
da inversão do hero (abaixo), restou **uma** decisão de marca em aberto — a **tinta da família**
(**S6**), que vive como camada alternável pelo botão "Tinta". O tom da âncora (**S3**) e a régua
de ação (**S4 retirado**) deixaram de ser conflito: a ação voltou para a família turquesa, que é
onde o `marca-seed.md` §3.5 sempre a colocou. Ver **PN-P1** em §48.9.

> ## SUPERSEDE DA ÂNCORA — o azul sai, entra o turquesa vivo (2026-08-13, noite)
>
> **Origem, nas palavras do Rafael:** *"a questão do azul eu tinha aceitado para podermos parar
> com os testes, pois não estava acertando. Retome as cores da SEED. Aquela caixa no topo que está
> azul quero que use um turquesa com cor viva, não pode ser escura nem cinza, senão fica muito
> escuro e parece velório."* E a instrução de método que destravou a solução: *"veja como o estudo
> do Viver de IA soluciona isso."*
>
> **A RESPOSTA ESTAVA NO ESTUDO, e eu não a tinha lido assim.** `estudo-viver-de-ia.md` **§9.2**
> (medição do código da plataforma): `--primary: 173 100% 46%` = **#00EBCF**. A primária deles
> **não é o navy dos prints** — é um **turquesa vivo**, a **1,1° de matiz** do nosso
> `turquesa-300` e a 0,9° do `turquesa-600`. O navy é *superfície de tema escuro*; o acento é
> turquesa. A §9.2 diz textualmente: *"o sistema é superfície quase-preta + acento turquesa — que
> é, essencialmente, o território do nosso dark mode."*
>
> **O erro de leitura que isso expôs:** nas duas rodadas anteriores eu copiei a **superfície**
> deles (escuro) e deixei o nosso turquesa como enfeite. Para uma marca cujo primitivo é turquesa,
> é o inverso do certo. **A inversão: o turquesa é a SUPERFÍCIE de ênfase, e a tinta é escura da
> própria família.** E o "vivo" que o Rafael pedia não depende de escuro — vem de três mecanismos
> que o próprio §8 do estudo nomeia: **P1** (varredura de 4–6 pontos de luminância dentro do
> próprio matiz), **P2** (sombra em rampa + anel) e **P4** (uma âncora por página contra branco).
> Os três funcionam sobre superfície clara.
>
> **O diagnóstico era luminância, não matiz** — e é isso que explica o "velório":
>
> | | `azul-800` (antes) | `turquesa-400` (agora) |
> |---|---:|---:|
> | Luminância | **6,03%** | **33,71%** — 5,6× mais claro |
> | Croma | 0,380 | **0,624** — +64% |
> | Tinta que carrega | branco 9,52 | `turquesa-900` **4,99** |
>
> Um hero a 6% de luminância é quase preto, e nenhuma quantidade de croma corrige isso.
>
> **Três consequências que a medição obrigou, e nenhuma era previsível de olho:**
>
> **(a) A luz inverteu de canto.** Com tinta branca, a luz apagava o texto e tinha sido empurrada
> para a metade vazia do hero. Com tinta escura a luz **ajuda** — 5,75 no ponto de luz contra 4,99
> na base —, então ela volta para o canto onde o texto vive. **E não existe poço:** descer um
> degrau para `turquesa-500` derrubaria a tinta a **4,20** e reprovaria. O `turquesa-400` é o
> **piso** da varredura, não o meio dela. Véu de `turquesa-300` a .35 → **ΔL 5,84 pontos**, dentro
> da faixa de 4–6 que o P1 mede, com croma preservado em 0,553.
>
> **(b) O anel virou obrigatório.** A base contra a página branca mede **2,71** e reprova o piso
> de fronteira de 3,0. Anel `turquesa-600` na mesma declaração da sombra (padrão PN21a) = **4,60**.
>
> **(c) A tinta é ÚNICA, e a hierarquia deixa de usar cor.** Baixar a tinta escura por opacidade
> reprova: **3,91** a .85 e **2,99** a .70. Dentro do hero há uma tinta só, e a hierarquia vem de
> **corpo e peso** — que é literalmente o que o estudo §2.1 diz: *"o contraste vem de forma e
> peso, não de matiz"*. Por consequência, **o itálico do sujeito perdeu a cor**: sobre o turquesa
> vivo nenhuma cor da paleta passa AA (branco 2,71 · `amarelo-200` 1,90 · `turquesa-100` 3,36), e
> a marcação passa a **itálico + peso 800** na mesma tinta — mecanismo que, ao contrário da cor,
> sobrevive ao grayscale. O amarelo **sai** do hero.
>
> **Ganho de estrutura que não estava no pedido: o hero passou a ter UMA face.** `turquesa-400`
> mede **6,86** contra a página escura e **5,61** contra o cartão escuro; a tinta segue em 4,99
> nos dois temas. Antes, `azul-800` media **1,41** contra a página escura e obrigava a subir para
> `azul-700` — duas composições para manter em sincronia. **A classe de defeito "camada sem
> variante de tema" ficou impossível aqui, por construção**, e não por disciplina.
>
> **A ação dentro do hero desceu um degrau, por decisão do Rafael no gate seguinte** (*"baixa pra
> 800"*): pílula `turquesa-800` #005048 — texto branco **9,37**, fronteira **3,45** contra a base
> e **3,98** contra o ponto de luz. É troca de **margem por matiz**: o 900 dava 4,99 de fronteira
> (66% de folga sobre o piso) e o 800 dá 3,45 (15%), e em troca a pílula lê turquesa em vez de
> quase-preta. **O 800 é o piso: `turquesa-700` mede 2,33 e reprova.** A pílula **branca** nunca
> foi opção — 2,71.

### 48.4 Edição direta · PN22–PN41

> **Escrita em 2026-08-13, rodada 3.** Esta seção existe porque o painel do ERP **não é só
> leitura**: o gestor reagenda uma OS arrastando a barra, reatribui puxando o cartão para outra
> faixa, muda o estado soltando na coluna. Sem contrato, cada tela implementaria o arrasto de um
> jeito e o teclado de nenhum.
>
> **A regra que unifica cinco lentes é uma só (PN33): ONDE O ITEM CAI É O VALOR DO CAMPO.**
> Coluna do kanban = estado · faixa do tempo e da carga = responsável · dia do calendário = data ·
> posição no gantt = datas. O que é **derivado** nunca se arrasta (PN22) e o mapa é **leitura**
> (PN39).
>
> **Norma que governa a seção: WCAG 2.2, SC 2.5.7 Dragging Movements (AA)** — toda funcionalidade
> que use movimento de arrasto tem de oferecer **caminho de ponteiro único** alternativo. É por
> isso que o PN23 não é conveniência: é conformidade.

| # | Decisão | Porquê / número medido | Descartado |
|---|---|---|---|
| **PN22** | **O DERIVADO NUNCA SE ARRASTA.** Carga de trabalho, ranking, KPI e barra de status são **resultado de cálculo**: não têm campo de destino, e por isso não têm alça | Arrastar uma barra de carga significaria *"trabalhe menos"* — o gesto não tem para onde escrever. Oferecer a alça e ignorar o gesto é a classe do **botão morto**. *Guarda:* `E-15` mede que nenhuma barra da carga é arrastável | Alça em bloco derivado · alça que não faz nada (botão morto) |
| **PN23** | **CAMINHO DE PONTEIRO ÚNICO PARA TUDO QUE SE ARRASTA.** Menu **"Mover sem arrastar"** aplica a MESMA mutação, com o mesmo desfazer; criar tem **botão explícito**, sem depender de passar o mouse | **SC 2.5.7 (AA).** Além da norma: em toque real, arrastar dentro de faixa rolável compete com o gesto de rolagem — o caminho alternativo é o que faz a tela funcionar no celular. *Guardas:* `E-22`, `E-23`, `I-17`, `I-19` | Arrasto como caminho único (reprova AA) · alternativa que aplica mutação diferente (duas verdades) |
| **PN24** | **CONTRATO DE TECLADO ÚNICO, IGUAL EM TODA LENTE:** **Enter/Espaço** pega o item e o anúncio **ensina o contrato** (*"setas movem, Enter solta, Esc cancela"*) · **setas anunciam o DESTINO antes de confirmar** · **Enter** solta aplicando a mesma mutação do arrasto · **Esc** cancela sem aplicar | Arrasto sem teclado exclui quem não usa ponteiro. O anúncio que **ensina** existe porque um contrato de teclado que ninguém descobre é um contrato que não existe. Anunciar o destino **antes** de confirmar é o que torna o gesto reversível sem custo. *Guardas:* `E-18…E-21` | Teclado diferente por lente · mover sem anunciar destino (o usuário confirma no escuro) |
| **PN25** | **O ÍMÃ É A UNIDADE DA ESCALA:** modo hora → passo de 1 hora · dia, semana e mês → passo de 1 dia. **Datas inteiras, sem hora residual** | Precisão maior que a escala é **precisão inventada**: arrastar num gantt de mês não pode gravar 14h37. *Guardas:* `E-07` (datas inteiras), `F-19` (em escala de hora o arrasto move horas) | Ímã no pixel · granularidade fixa independente da escala |
| **PN26** | **A CASCATA DE DEPENDÊNCIA É MOSTRADA EM FANTASMA ANTES DE SOLTAR e DECLARADA EM NÚMERO no anúncio** | Mover uma ordem que trava outras três muda quatro datas. Se o usuário só descobre depois, o desfazer vira obrigatório em vez de opcional. O número no anúncio é o que dá ao usuário de leitor de tela a mesma informação que o fantasma dá ao olho. *Guarda:* `E-08` | Aplicar cascata em silêncio · mostrar cascata só depois de soltar |
| **PN27** | **MUTAÇÃO OTIMISTA:** o gesto aplica na hora, na tela; confirmação e conflito de servidor são **produto** (fronteira do DM10) | Esperar o servidor a cada arrasto torna o gesto inutilizável. O DS entrega o contrato de tela: aplicar, anunciar, oferecer desfazer. Compõe com o **PN30** (refresh adiado) | Aguardar confirmação para desenhar · aplicar sem desfazer |
| **PN28** | **DESFAZER DE 8 SEGUNDOS, com contagem visível, cobrindo TAMBÉM criação e exclusão. O anúncio diz o RESULTADO, não o percurso** | *"OS-2288 reagendada para 17/08→19/08"* é resultado; *"arrastada 3 colunas"* é percurso, e percurso não se verifica. **O desfazer que não cobre criação é meia verdade** — foi o defeito que apareceu ao ligar a linha de criação. *Guardas:* `E-02…E-05`, `E-29`, `G-14`, `G-15`, `E-41` (o lote em massa volta inteiro) | Toast sem contagem (o usuário não sabe quanto tempo tem) · desfazer só de edição · anúncio de percurso |
| **PN29** | **ITEM TRAVADO NÃO GANHA ALÇA E DIZ O MOTIVO**, com `aria-disabled` e caminho alternativo | Alça que existe e não obedece é botão morto. Motivo invisível transforma trava em bug aos olhos de quem usa — *foi literalmente o relato do gate* (ver **PN46**). *Guarda:* `E-17b` | Alça inerte · trava silenciosa |
| **PN30** | **REFRESH PEDIDO DURANTE EDIÇÃO É ADIADO, NUNCA SILENCIOSO** — o item sob edição é imune, e o adiamento é dito | Sobrescrever o que o usuário está arrastando é perder trabalho sem aviso. Compõe com o **PN9**: adiar é decisão de tela, não de transporte. *Guarda:* `E-26` | Aplicar refresh sobre item em edição · descartar o refresh sem avisar |
| **PN31** | **A ALÇA TEM ALVO DE TOQUE PRÓPRIO (≥44px de área efetiva) e a faixa ROLA sob o ponteiro perto das bordas** (autoscroll, margem de 48px) | Alvo de 44px é regra do DS desde o §1. O autoscroll existe porque reagendar de 15/08 para 30/09 exige sair da janela visível — sem ele, o gesto é impossível sem soltar e rolar. *Guarda de origem:* o link endereçável da timeline media **14px** de altura e só o render pegou | Alça de 14px · faixa que não rola durante o arrasto |
| **PN32** | **ORDENAÇÃO ATIVA DESLIGA O ARRASTO DE REORDENAR, E DIZ POR QUÊ** | Arrastar para reordenar sob ordenação automática produz um resultado que o próprio sistema desfaz no próximo redesenho. Desligar **e explicar** é honesto; desligar em silêncio é bug percebido. *Guardas:* `E-24`, `E-25` (o gesto de fato não muda nada) | Permitir os dois (o sistema desfaz o gesto) · desligar sem motivo visível |
| **PN33** | **ONDE O ITEM CAI É O VALOR DO CAMPO.** Coluna do kanban = `estado` · faixa do tempo e da carga = `resp` · dia do calendário = `data` · posição e largura no gantt = `inicio`/`fim` | É a regra que unifica cinco lentes com **um** motor de arrasto. Sem ela, cada lente teria semântica própria de soltura e o contrato de teclado do PN24 não poderia ser único. *Guardas:* `E-01`, `E-11`, `E-13` | Semântica de soltura por lente · zona de soltura sem campo declarado |
| **PN34** | **CRIAR NO VAZIO, COM O CONTEXTO JÁ PREENCHIDO:** nascer na coluna do kanban já traz o estado daquela coluna; clicar num dia vazio do calendário cria **naquela data** | O contexto do gesto **é** informação; pedir de novo num formulário em branco desperdiça o que o usuário já disse. *Guardas:* `E-28`, `E-30` | Criar sempre em branco · criar sem herdar a zona |
| **PN34b** | **O RESULTADO TEM DE SER VISTO:** o que acabou de mudar é **realçado por 1.600ms** e **trazido ao campo de visão** (vertical e horizontalmente) | **Relato literal do gate.** Um item criado ou agendado fora do enquadramento faz o usuário concluir que **nada aconteceu** — e o sistema estava correto. *Lição de método permanente:* o que nasce fora do campo de visão é lido como "não funcionou". *Guardas:* `I-07`, `I-08` | Aplicar sem realçar · realçar sem rolar até o item |
| **PN35** | **MOVER ≠ REDIMENSIONAR:** punhos nas pontas mudam **só a borda arrastada**; o gesto mexe em **`left`/`width`, NUNCA em `transform`**; a borda oposta não se move | *Defeito do gate:* ao esticar, a barra inteira se deslocava. Causa: `transform` translada o elemento todo. **Regra:** redimensionamento é geometria de caixa, não transformação. E o anúncio distingue os dois gestos, senão o usuário de leitor de tela não sabe qual aconteceu. *Guardas:* `E-09`, `E-10`, `H-06…H-09` (nenhum `transform` no gesto; a largura cresce **durante** o gesto) | `transform` no redimensionamento · punho que move a barra · anúncio idêntico para mover e esticar |
| **PN35b** | **A MESMA ETAPA PODE TER VÁRIOS PERÍODOS NA MESMA LINHA.** Modelo: **segmento `-1`** é o principal (`inicio`/`fim`, lido por calendário, vencimentos e carga) e **`0..n`** são adicionais, que existem só no eixo do tempo. O gesto age sobre o **segmento tocado**, não sobre a ordem inteira | Pergunta do Rafael no gate: *"posso trabalhar na mesma linha/etapa em mais de um período?"* — e a resposta do domínio é sim: manutenção que para no fim de semana e retoma na segunda é **uma etapa, dois períodos**. As duas alternativas eram piores: recusar o gesto (o dado real não cabe) ou criar outra ordem (duplica a etapa e mente na contagem). *Guardas:* `J-01…J-08` — o extra se identifica no rótulo (*"período 2"*), move-se sozinho, e o menu dele oferece **remover o período**, não excluir a ordem | Recusar o segundo período · criar ordem nova (o conjunto cresceria mentindo) · mover o extra arrastando o principal |
| **PN36** | **DEPENDÊNCIA É LISTA, não campo único.** Criada **puxando do nó** até a barra da predecessora; **removida com gesto deliberado** (duplo clique ou botão direito); **ciclo é recusado e explicado**; ligar uma terceira predecessora **acrescenta**, não substitui | Uma ordem pode depender de várias — o campo escalar é o defeito de modelagem que aparece na primeira obra real; o helper aceita o formato antigo para não quebrar dado herdado. **Clique simples removia por acidente** ao passar sobre o fio: ação destrutiva não mora em alvo invisível que cruza a área. *Guardas:* `E-31…E-33`, `I-12` (clique simples NÃO remove), `I-21…I-24` (duas predecessoras no dado, cada uma com fio próprio) | `dep` escalar · remoção por clique simples · substituir a predecessora ao ligar outra · aceitar ciclo |
| **PN37** | **A LENTE TABELA É PLANILHA:** setas navegam entre células, **Enter** abre editor **na própria célula**, e a **alça copia o valor para as células abaixo** | O ERP tem preenchimento repetitivo (mesmo responsável para dez OS). Sem planilha, isso são dez formulários. Herda o §40; a alça é o gesto que o usuário já conhece de fora. *Guardas:* `E-34…E-38` | Editar só em formulário · setas que rolam a página em vez de navegar células |
| **PN38** | **SELEÇÃO E AÇÕES EM MASSA:** a barra de ações **só aparece com seleção**, a ação muda todas de uma vez e o **desfazer reverte o lote inteiro** | Herda o §41 (seleção) e o §42 (toolbar). A barra que existe vazia ocupa espaço e não responde. O desfazer parcial é pior que nenhum: deixa o conjunto num estado que o usuário não pediu. *Guardas:* `E-39…E-41` | Barra sempre visível · desfazer item por item depois de uma ação em massa |
| **PN39** | **O MAPA É LEITURA: o pin NÃO se arrasta.** A localização vem do cadastro da usina, não do gesto | Mover o pin significaria mudar a coordenada da usina — que é cadastro, não agendamento. Arrastar ali seria editar o cadastro por acidente. *Guarda:* `E-16` | Pin arrastável · mapa como editor de cadastro |
| **PN40** | **FILTRO É DA VISÃO, NÃO DA LENTE:** chips removíveis para o que está ativo · **contagem honesta** (*"5 de 14 · 9 ocultas"*) · vazio que **explica que é filtro** e diz quantas existem · **o mesmo recorte em qualquer lente** · limpar devolve o conjunto inteiro | Contagem que não diz o que ficou de fora faz o usuário concluir que o dado desapareceu. O recorte igual em toda lente é o **PN5** aplicado ao filtro. *Defeito medido:* criar ou excluir mexe no **tamanho** do conjunto e a contagem continuava dizendo "14 ordens" — **contagem que não acompanha a mutação é contagem que mente**. *Guardas:* `F-01…F-07`, `I-16`, `I-26` (criar fora do filtro ativo **limpa o filtro e diz por quê**, em vez de criar no vazio) | Filtro por lente · contagem só do que sobrou · vazio genérico ("nada aqui") |
| **PN41** | **A ESCALA É DENSIDADE, NÃO RECORTE:** hora · dia · semana · mês mudam a largura por unidade e a granularidade dos rótulos — **o conjunto desenhado é o mesmo**. Fins de semana marcados; **linha do HOJE única** | Zoom que troca o intervalo **esconde dado**; zoom que troca a densidade só aproxima o olho. *Guardas:* `F-08…F-11`, `F-22`, `H-01…H-05` (cada escala tem **uma coluna por unidade**, alinhada à borda da unidade — mês começa no dia 1) | Escala que muda o intervalo · escala que só troca o cabeçalho (a mesma classe do PN3b) |
| **PN41b** | **ESCALA DE HORA**, com a unidade da faixa **parametrizada**: 24 unidades por dia no modo hora, 1 nos demais | *Pedido do gate:* a manutenção de campo tem janela (*"a equipe entra 8h e sai meio-dia"*) e sem hora o gantt não responde a pergunta que o cliente faz. O ímã acompanha (**PN25**) | Gantt só em dias (não responde à pergunta do campo) · hora fixa com dia derivado |
| **PN41c** | **A JANELA ABRE NO HOJE**, não no início da faixa | Faixa rolável de 60 dias abre no dia 1 e o usuário rola até achar hoje — em toda troca de escala. *Guarda:* `F-18` | Abrir no início da faixa |
| **PN41d** | **O ZOOM SE DECLARA EM UNIDADES VISÍVEIS, não em pixels:** *"8 h na tela"*, *"15 d na tela"*; o px por unidade é **derivado da largura disponível**. No fim do curso o botão **desabilita**, não vira clique morto | É a pergunta que o usuário faz de verdade (*"quero ver as 8 horas do turno"*, *"quero a semana inteira"*). Derivar o px da largura é o que faz o mesmo ajuste funcionar em **1180px e em 390px** — px fixo por unidade quebra num dos dois. *Guardas:* `F-21…F-26`; o `F-25` mede que **a quantidade prometida no rótulo é a que de fato cabe na tela** | Px fixo por unidade · zoom em porcentagem (não responde à pergunta) · botão de zoom que continua clicável no fim do curso |
| **PN41e** | **PANORÂMICA EM MODO EXPLÍCITO:** dois botões — **Criar** × **✋ Mover** — mais atalhos que sempre valem (segurar espaço, Shift+arrasto, botão do meio). Só o **fundo** faz panorâmica; barra, punho e nó continuam sendo edição; limiar de **4px** antes de virar panorâmica | **Dois gestos disputavam o mesmo pixel:** arrastar no vazio criava período (PN49) e também deveria deslocar a janela. Em vez de adivinhar a intenção, o modo é explícito, como em ferramenta de desenho — seta cria, mão navega. *Lição de método permanente: dois gestos não podem dividir o mesmo pixel.* *Guardas:* `F-27…F-29` (a panorâmica **não rouba** o gesto de edição da barra) | Adivinhar a intenção pelo contexto · panorâmica em qualquer ponto (mataria a criação) · modo implícito por tecla apenas (não há tecla no toque) |

### 48.5 Gantt profissional · PN42–PN45 — **RESERVADO, produção no bloco 6D**

**Estes quatro códigos estão reservados e deliberadamente vazios.** Não são omissão: são o
escopo do **bloco 6D**, separado deste por decisão de tamanho — o 6C entrega o gantt que reagenda
e encadeia; o 6D entrega o gantt que **planeja**.

| # | Escopo reservado |
|---|---|
| **PN42** | Os quatro tipos de dependência (**FS**, **SS**, **FF**, **SF**) mais **folga/lag** — hoje o preview implementa apenas fim→início |
| **PN43** | **Caminho crítico** e folga calculada |
| **PN44** | **Linha de base** (baseline) e comparação plano × realizado |
| **PN45** | **Marco** como losango sem duração |

*Por que reservar em vez de renumerar:* as cinco camadas de validação e o preview v0.6 já citam
`PN46`…`PN54` nos comentários e nas guardas. Renumerar para fechar o vão exigiria reescrever os
quatro artefatos executáveis e invalidaria as âncoras MD5 recém-calculadas, trocando um vão
documentado por um risco de deriva. **O vão é intencional e está declarado aqui.**

### 48.6 Contratos que o gate de 2026-08-13 produziu · PN46–PN50

> Estas cinco decisões **não estavam previstas**: nasceram do gate visual do Rafael sobre o
> preview em uso real. Cada uma tem o relato de origem registrado, porque o relato é a evidência.

| # | Decisão | Porquê / relato de origem | Descartado |
|---|---|---|---|
| **PN46** | **A POLÍTICA DE TRAVA É DO DOMÍNIO, NÃO DO DESIGN SYSTEM.** O DS entrega o **contrato** (travado + motivo visível + `aria-disabled` + caminho alternativo); a **regra** vem de fora, como parâmetro | *Relato:* **"a coluna Concluída não move"** — lido como defeito. E era, no sentido que importa: *"concluída não move"* estava **soldado no DS**, que passou a legislar sobre workflow. Uma empresa quer travar concluídas; outra quer reabrir. **O preview expõe as duas políticas (botão "Travar concluídas") para exercer os dois lados** — é assim que se prova que o contrato funciona ligado E desligado. *Guardas:* `E-17a` (sem política, nada trava — o DS não inventa regra de negócio), `E-17b` (com política, o travado não ganha alça e **diz** o motivo) | Regra de workflow dentro do DS · política sem os dois lados testados (metade do contrato sem prova) |
| **PN47** | **PRÉ-REQUISITO DE LENTE PASSA DE "TODOS TÊM O DADO" PARA "ALGUÉM TEM O DADO", e quem não tem vai para a GAVETA DE NÃO AGENDADOS** — visível, contada e arrastável de volta para o tempo. **No gantt, toda ordem tem LINHA; o que falta é a barra** | *Relato:* uma **única** ordem sem data derrubava o gantt inteiro — a lente sumia e o usuário não sabia por quê. A gaveta resolve os dois lados: a lente aparece e desenha quem tem, e o que falta fica **à vista em vez de escondido**. Dar linha a quem não tem data é o que permite **agendar arrastando na própria linha** (ver **S8**). *Guardas:* `F-12…F-14`, `I-01`, `I-02` | Exigir o dado em todos (o defeito) · esconder o não agendado (o usuário conclui que não existe) · lente vazia |
| **PN48** | **RÉGUA DE TEMPO EM DOIS NÍVEIS — contexto em cima, unidade embaixo — com UMA COLUNA POR UNIDADE, alinhada à linha de grade** | *Relato/medida:* a régua antiga espalhava N rótulos por interpolação; em escala de hora dava para ver *"12/08 19h"* a cada 6 horas e **nenhuma coluna dizia que hora era**. Régua e faixa deixaram de medir o mesmo. Dois níveis é o que permite **ler** a coluna em vez de adivinhar: hora↔dia · dia↔mês/ano · semana↔mês · mês↔trimestre. *Guardas:* `G-01…G-05`, `H-01…H-03` | Rótulos por interpolação · um nível só · régua com granularidade diferente da faixa |
| **PN49** | **CRIAR PERÍODO ARRASTANDO NA FAIXA VAZIA**, com **semente visível seguindo o ponteiro**, **rascunho desenhado durante o arrasto**, e **clique sem arrastar também criando**. **O convite aparece onde o gesto age** e promete exatamente o que vai acontecer | *Relato:* a linha vazia não convidava a nada, e onde convidava, o gesto não agia. **Lição de método permanente: convite que aparece onde o gesto não age é pior que convite nenhum.** A dica é específica por linha: linha **sem data** promete *agendar*; linha **já agendada** promete *outro período* (PN35b); a **linha de criação** no fim do gantt promete *criar*. *Guardas:* `G-06…G-10`, `I-09…I-11`, `I-13…I-18` (a faixa da linha de criação é marcada **sempre**, não só no hover) | Faixa vazia sem convite · convite genérico igual em toda linha · convite em zona inerte |
| **PN50** | **MENU DE CONTEXTO (botão direito) EM TODO ITEM + CARTÃO DE EDIÇÃO por clique**, com o menu levando o **foco para a primeira ação** e oferecendo abrir, duplicar, desagendar, concluir e excluir — **excluir reversível, e dizendo isso** | Arrasto cobre data, responsável e estado; o resto precisa de porta. O menu é o mesmo em todas as lentes, senão o usuário reaprende a cada troca. **O cartão de edição aqui é a versão mínima e é o gancho declarado do §49** (painel de detalhe): existe para exercer o contrato, não para especificá-lo. *Guardas:* `G-11…G-17` | Menu diferente por lente · menu que abre sem levar o foco (teclado fica órfão) · excluir irreversível |

### 48.7 Lente mapa · PN51–PN54

> **A lente mapa tem duas perguntas diferentes por trás**, e é isso que produz os dois modos:
> *"onde estão as ordens?"* (leitura, laudo, impresso) e *"como eu chego lá?"* (navegação em
> campo). Guia de ligação para o produto em `mapa-base-lovable.md` *(a âncora deste guia MUDOU DE CAMINHO em 2026-08-13 — era `validacao/`, passou à raiz: a pasta `validacao/` é declarada no `LEIA-ME.md` dela como a dos artefatos EXECUTÁVEIS, e este é documentação de entrega ao produto; ver §19-b do MANIFESTO v4.0)*. **Recomendação
> aprovada pelo Rafael:** MapLibre GL + provedor de tiles; Google apenas para geocodificação, se
> necessário.

| # | Decisão | Porquê / número medido | Descartado |
|---|---|---|---|
| **PN51** | **DOIS MODOS DECLARADOS: `cobertura` (PADRÃO) e `operacao`.** Cobertura é a **malha IBGE do §4d** — projetada, imprimível, sem rede. Operação é **mapa base navegável** com tiles | O modo é do **contexto**, não da preferência: laudo, e-mail e papel são cobertura; ir até a usina é operação. Cobertura é padrão porque é a que **sempre funciona** e porque é a que entra em documento. *Guardas:* `M-01`, `M-02` | Um modo só (nenhum dos dois serve os dois casos) · operação como padrão (quebra em papel e sem rede) |
| **PN52** | **O ESTILO DO MAPA BASE CONSOME TOKENS, e o MARCADOR É PEÇA DO DS** — a mesma forma no mapa base e na lista textual | Mapa com a paleta do fornecedor é o único lugar da tela onde a identidade não se aplica, e é justo o que o cliente fotografa. Marcador próprio garante que o mesmo símbolo apareça nos dois modos e na alternativa textual. *Guardas:* `M-09`, `M-10` (cada usina carrega **coordenada geográfica** além da posição projetada) | Estilo default do fornecedor · marcador do fornecedor · símbolo diferente entre mapa e lista |
| **PN53** | **FALLBACK OBRIGATÓRIO E DECLARADO:** sem biblioteca, sem chave, sem rede ou em impressão, o modo operação **cai para a cobertura** e **DIZ o que aconteceu**. **Nenhuma chave de API vive no artefato** — a fonte de tiles vem da configuração do ambiente | **Equipe em campo com sinal ruim é o caso normal, não a exceção.** O mapa não fica em branco nem mente: cai para a cobertura oficial e anuncia. A chave fora do artefato é requisito de segurança e de LGPD, não conveniência. *Guardas:* `M-04`, `M-05` (o fallback **diz** o que aconteceu, em vez de mostrar retângulo vazio), `M-07`, `M-08` | Retângulo vazio · fallback silencioso · chave embutida no HTML |
| **PN54** | **ALTERNATIVA TEXTUAL SEMPRE PRESENTE, NOS DOIS MODOS, COMO CONTEÚDO** — lista de usinas com marcador, nome, município/UF e contagem de OS ativas; **nunca só `aria-label`** | **Mapa interativo não é navegável por leitor de tela** — e a lista não é enfeite de acessibilidade: é a forma mais rápida de ler o mesmo dado para qualquer pessoa, e a única que sobrevive ao copiar-e-colar num e-mail. *Guardas:* `M-03`, `M-06` | `aria-label` descrevendo o mapa · alternativa só no modo cobertura · alternativa escondida atrás de botão |

### 48.8 Supersedes formais desta rodada

**Regra do projeto:** todo contrato substituído recebe supersede formal, com o número que o
motivou — nunca nota solta. Estes dez são os desta rodada.

| # | O que dizia | O que passa a dizer | Número que motivou |
|---|---|---|---|
| **S1** | **PN3, 1ª redação:** cinco lentes (lista · tabela · quadro · linha do tempo · calendário) | **Oito lentes**, com `gantt` e `tempo` separados e `carga` e `mapa` acrescentados | A documentação do ClickUp separa gantt (tarefas conectadas) de timeline (recurso), e o Rafael decidiu que o template estrutural nasce com o lugar da alocação de pessoa |
| **S2** | O rótulo **"quadro"** como nome da lente kanban | **`kanban`** é o identificador canônico; "quadro" fica como sinônimo de fala, não de código | Os artefatos usam `data-lente="kanban"`; manter dois nomes para a mesma lente é a porta da deriva |
| **S3** *(revisto na mesma data — ver S11-b abaixo)* | **Tom do escuro estrutural: `cinza-900`** (croma 0,063) | ~~**`azul-800` #004C61**~~ **SUPERSEDIDO NA MESMA NOITE por `turquesa-400` #11B0A0** (croma 0,624, L 33,71%) — a âncora deixou de ser escura. O texto abaixo preserva a cadeia de medições que levou ao azul, porque ela documenta quatro tentativas reprovadas que não devem ser repetidas: ~~**`azul-800` #004C61** (croma 0,380, L 6,03%), com luz em `azul-700` #006783 e poço em `azul-900` #003241. **No tema escuro o hero SOBE para `azul-700`** | Cadeia de quatro medições no mesmo gate: **(a)** o hero SEED já era quase 3× mais claro que o do estudo (L 9,12% × 3,25%) e lia mais morto, porque tinha **metade do croma** (0,087 × 0,175) — *escuro dessaturado lê como fumaça, escuro saturado lê como tinta*; **(b)** `turquesa-900` #00352F (croma 0,208) resolveu o croma sem navy alheio, branco em 13,55; **(c)** *"precisamos usar uma cor mais clara"* → `turquesa-800` (L 6,21%, croma 0,314), teto da luz em `turquesa-600` porque acima disso (500 = 3,22) o branco reprovaria; **(d)** *"essa cor é muito escura e deixa tudo preto e branco; o deles usa um azul"* → a escala turquesa esbarra num teto (no 700 o micro-rótulo cai a **4,11**), e o **azul-800 entrega 21% mais croma na mesma luminância**. No tema escuro, `azul-800` media **1,41** contra a página e **1,24** contra o cartão — deixava de ancorar; `azul-700` dá 2,65 e 2,19 com branco em 6,43, e `azul-600` separaria melhor (3,69) mas derrubaria o branco a 4,61. **Regra que fica: no escuro, elevação é LUZ, não sombra** |
| **S4** ⚠ **RETIRADO em 2026-08-13, na mesma data em que nasceu** | **`marca-seed.md` §3.5:** *"ação primária é sempre família turquesa/verde — nunca azul"* — **e esta regra PERMANECE VIGENTE, intocada** | **O supersede foi RETIRADO.** Com a âncora virando turquesa vivo (S11), a ação voltou à família da marca e **não há mais nada a superseder em `marca-seed.md`**: a régua vigente é `turquesa-600` #098475 + branco = **4,60**, e o #098475 é o hex que o próprio §3.5 nomeia. O texto abaixo fica como **registro histórico da tentativa**, não como contrato: ~~**A ação não tem cor própria: ela usa o ESCURO ESTRUTURAL.** Primária sobre claro = `azul-800` + branco (**9,52**) · primária **dentro** do escuro = pílula **branca** + `azul-900` (**13,70**) · secundária no escuro = ghost translúcido + branco (9,52) · secundária sobre claro = outline `azul-800` (9,52) · terciária = link com seta (4,60). **No tema escuro a ação SOBE na escala: `azul-400`** (superfície 5,71 · texto `azul-900` 5,56). **O turquesa continua no sistema como ACENTO** — itálico do sujeito, séries de dataviz, pontos de estado —, o que muda é que ele deixa de carregar a ação | Gate do Rafael com o print da plataforma: *"a cor do botão tem que seguir o mesmo padrão da caixa grande"*. **Tentativa reprovada pela medição, registrada para não se repetir:** baixar a primária do escuro para `azul-500` deixaria o botão menos "neon" (superfície 4,24) mas o texto cai para **4,13**, abaixo do piso de 4,5 — e **nenhum** tom escuro da paleta salva (`azul-900` 4,13 · `cinza-900` 4,17 · `turquesa-900` 4,08 · branco por cima 3,32). `azul-400` é o degrau **mais baixo** que sustenta texto pequeno no escuro. **⚠ Este supersede vale no PRODUTO DIGITAL e é decisão de MARCA: exige registro do Rafael em `marca-seed.md` antes de ser canônico** |
| **S5** | *"Ordem concluída não move"* como regra do DS | **PN46:** o DS entrega o contrato de trava; a política é parâmetro do domínio | Relato do gate *"a coluna Concluída não move"* leu como defeito uma regra de workflow embutida no DS |
| **S6** | **Tinta neutra:** `text-primary` e `text-secondary` na família cinza | **PROPOSTA, não canônico:** tinta da família do escuro estrutural — `text-primary` **#0B3330** (× branco **13,72**) · `text-secondary` **#2F5A54** (× branco **7,75**) · bordas e superfícies esverdeadas; no escuro **#DDECE8 / #A3C4BE / #0F1F1C / #16302C** | O estudo (§2.1) usa tinta *da mesma família do escuro estrutural*, medida em croma **0,192** e saturação 83% contra **0,063** e 31% da nossa. Como texto e bordas ocupam a maior parte da área, tinta neutra faz a tela **inteira** ler cinza mesmo com o hero colorido — foi o que o gate apontou. **Vive como camada alternável no preview (botão "Tinta"), não no canônico: muda o DS inteiro** (ERP, e-mail, documentos, site) e depende de decisão de marca. *Defeito medido a registrar:* a 1ª versão desta camada foi escrita **sem variante de tema** e no escuro impunha tinta clara sobre cartão escuro — o painel virava ilegível. **Regra permanente: toda sobrescrita de token semântico precisa das DUAS faces; um bloco só é meia implementação** |
| **S7** | **Largura da faixa de tempo em px fixo por dia** | **PN41d:** a faixa é **função da escala e da largura disponível** — declara-se quantas unidades devem caber, e o px por unidade é derivado | O mesmo ajuste tinha de funcionar em 1180px e em 390px; px fixo por unidade quebra num dos dois. Guarda `F-25` mede que a quantidade prometida no rótulo é a que cabe de fato |
| **S8** | **PN4:** o gantt exige início E fim em **todos** os itens | **PN47/`PN4-03e`:** o gantt exige **ao menos uma** ordem agendada, **dá LINHA a quem não tem data** (a barra é que não existe) e manda o resto para a gaveta | Uma única ordem sem data derrubava a lente inteira. *Consequência de projeto, não só de tolerância:* dar linha a quem não tem data é o que torna possível **agendar arrastando na própria linha** (PN49) |
| **S9** | Gesto em linha já ocupada = recusar, ou criar ordem nova | **PN35b/`I-06`:** gesto em linha ocupada **acrescenta um PERÍODO** à mesma etapa, sem criar ordem | Pergunta do Rafael (*"posso trabalhar na mesma linha/etapa em mais de um período?"*) contra o dado real da manutenção que para e retoma. Guarda `J-05` mede que o conjunto **não cresce** (14 → 14) |
| **S11** | **Âncora ESCURA** — a caixa de ênfase da página é escura, com texto claro; a ação usa o escuro estrutural (S3+S4) | **ÂNCORA CROMÁTICA:** `turquesa-400` #11B0A0 com **tinta única** `turquesa-900`, **anel** `turquesa-600` e **véu de luz** `turquesa-300` a .35 no canto do texto (ΔL 5,84). A ação volta à família: primária sobre claro **`turquesa-600` + branco 4,60** · dentro do hero **pílula `turquesa-800` + branco 9,37** (fronteira 3,45) · secundária sobre claro outline **`turquesa-700` 6,32** · no tema escuro a ação sobe para **`turquesa-400` + `turquesa-900`** (superfície 5,61 · texto 4,99) | *"Parece velório"* + a instrução de olhar o estudo. **§9.2 do `estudo-viver-de-ia.md`:** a primária da plataforma é **#00EBCF**, turquesa vivo a 1,1° do nosso `turquesa-300` — o navy é superfície, o acento é turquesa. Nós vínhamos copiando a superfície e deixando o turquesa como enfeite. Diagnóstico medido: **luminância**, não matiz — 6,03% × 33,71%. Reprovações que fecham as alternativas: `turquesa-500` derruba a tinta a 4,20 · pílula branca 2,71 · `turquesa-700` como pílula 2,33 · itálico em qualquer cor da paleta reprova (branco 2,71 · amarelo-200 1,90 · turquesa-100 3,36) |
| **S11-b** | **PN21c:** *"UM hero ESCURO por página"*; e a face escura do hero como composição própria | **UMA ÂNCORA DE ÊNFASE por página** — a palavra "escuro" sai do contrato, porque era descrição de implementação, não invariante. E a **face escura desaparece**: o mesmo par serve os dois temas | O tom mudou **cinco vezes em dois dias** (`cinza-900` → `turquesa-900` → `turquesa-800` → `azul-800` → `turquesa-400`), sempre com a guarda exigindo o valor da vez. O invariante real nunca foi a escuridão: é **uma âncora cromática da marca por página**. Ganho medido: `turquesa-400` mede 6,86 × página escura e 5,61 × cartão escuro, contra 1,41 do `azul-800` — **duas composições viraram uma**, e a classe "camada sem variante de tema" ficou impossível aqui por construção |
| **S12** | Fronteira de 3,0 exigida de **toda** pílula que pousa no hero | **COMPONENTE × CONTEÚDO.** A fronteira de 3,0 (SC 1.4.11) é exigida de **pílula interativa** (`btn-solucao`, `btn-ghost-hero`); o **chip de status é ISENTO**, com o motivo escrito no CSS | Erro meu de enquadramento, corrigido no gate: eu havia reprovado o chip branco a **2,71** pelo mesmo critério da ação. O 1.4.11 alcança *"informação visual necessária para IDENTIFICAR COMPONENTES e seus estados"* — o chip é **conteúdo** (rótulo de status, não focável, não acionável), e dele a norma exige o **texto** pelo 1.4.3, medido em **6,58**. E o estudo §2.4 fecha por outro lado: *"hierarquia por PREENCHIMENTO, não por forma"* — todo anel que passasse dos dois lados obrigaria a escurecer o chip, e o `turquesa-900` (4,99) ficaria **mais escuro que o próprio botão** (800), invertendo a hierarquia; o anel semântico, que seria o correto, **reprova** (`feedback-danger-text` 2,65). *Guardas:* `PN21-05e` (pílula interativa só com fill medido), `PN21-05f` (**o chip não é interativo — é o que sustenta a isenção; no dia em que virar botão, a suíte reprova**), `PN21-05g` (a isenção está declarada no artefato, não só aqui) |
| **S10** | **PN21g, 1ª redação:** glow do acento turquesa em elemento de destaque de ação | **Sombra projetada** no destaque de ação; o glow sai. A proibição que importa — **glow jamais sobre superfície de dado** — permanece | Com a ação virando **pílula branca** sobre o escuro (S4), o glow turquesa perdeu o portador: brilho de acento atrás de botão branco vira **halo sujo** |

### 48.9 Fronteiras declaradas e pendências

**Fora deste bloco, e a maior parte por decisão explícita do Rafael:** quais blocos existem em
cada tela e como o workflow se organiza (**projeto do ERP**) · a estrutura de **setores e áreas**
— Admin, Engenharia, Comercial, Qualidade — declarada pelo Rafael como desenho do ERP · qual
perfil vê o quê (**regra de negócio**) · quais KPIs cada tela mostra, o que entra na timeline e
como o progresso é calculado (**projeto do ERP**) · cálculo e agregação de indicador
(**produto**) · **transporte de tempo real**, websocket, persistência e resolução de conflito de
edição concorrente (**produto — Lovable/Supabase**, fronteira do DM10) · BI exploratório ad-hoc
(**fora de escopo desde o DF6**, revisável) · **geocodificação de endereço** e contratação de
provedor de tiles (**produto**).

**Ainda sem dono declarado, e não entram por decisão:** os itens 🟡 do censo do
`estudo-viver-de-ia.md` — **comparação com referência fora de gráfico** (semântica órfã desde o
supersede do DM8), **agenda/sessão/vaga**, **hero de identidade + stats** e
**documento-cerimônia**. Entrar sem consumidor real repetiria o erro que o §7 daquele estudo
mandou evitar.

**PENDÊNCIAS ABERTAS, nomeadas para não se perderem:**

| # | Pendência | Bloqueia |
|---|---|---|
| **PN-P1** *(encolheu em 2026-08-13)* | **Restou UMA decisão de marca e meia.** A **S6** (tinta da família — `text-primary` #0B3330 × branco 13,72, `text-secondary` #2F5A54 × branco 7,75, bordas e superfícies esverdeadas) segue **proposta**, como camada alternável pelo botão "Tinta". A **S11** (âncora `turquesa-400` + régua de ação na família) precisa de **registro** em `marca-seed.md` e nos **gêmeos de token v1.8 → v1.9**, mas **não precisa de decisão** — ela é convergente com o §3.5 vigente, e por isso é a meia. **O S4 saiu da pendência: não há conflito de marca a resolver** | A S6 bloqueia o canônico inteiro (ERP, e-mail, documentos, site) porque muda tinta, bordas e superfícies. A S11 bloqueia só a propagação dos 40 HTML (PN-P7). **Segue sendo o próximo bloco** |
| **PN-P2** | **PN42–PN45** (dependências FS/SS/FF/SF com lag · caminho crítico · linha de base · marco) | **FECHADA em 2026-08-16 pelo §58** (GT1–GT16). Os quatro números seguem reservados e vazios nesta numeração de propósito — renumerar invalidaria âncoras MD5 de cinco artefatos executáveis que já citam PN46–PN54. O conteúdo vive no §58, e a fronteira está declarada lá: o §48 tem a lente temporal (PN47/PN48/PN49), o §58 acrescenta a camada de RELAÇÃO entre itens |
| **PN-P3** | **§49 Painel de detalhe** — modal, gaveta lateral e página cheia, servindo o sistema inteiro. Hoje existe só a versão mínima do **PN50** | Bloco próprio |
| **PN-P4** | **MAPA NACIONAL.** A malha canônica cobre só **MG/ES/BA** (1.348 municípios, IBGE 2025, Albers). Um espelho de terceiro com as 27 UFs foi testado (`raw.githubusercontent`, `giuliano-macedo/geodata-br-states`) e **não declara edição**. Duas saídas: usar com a pendência registrada, ou o Rafael fornece o shapefile oficial. **Falta também a regra de escala** (município → UF → país) | Atendimento fora de MG/ES/BA na lente mapa |
| **PN-P5** | **SH-P1**, aberta desde o 6A: nenhum token de borda semântico passava 3:1 contra `surface-raised` no tema claro. **✅ FECHADA em 2026-08-14 (gêmeos v1.12):** nasce `--seed-border-interactive` (`cinza-500` promovido, ≥3,0 nas seis superfícies dos dois temas); o shell consome desde o preview v0.3. Registro completo no §46.2 e no `seed-tokens.md` v1.12 §3 | — (fechada; resta só o retroativo do artefato do §47, classe PN-P7) |
| **PN-P6** | **Três medidas de composição da camada visual sem guarda automatizada:** massa escura **≤32%** da primeira dobra · hero **≤35%** da altura · página lê branca (luminância média **≥248**). São decisões declaradas e medidas por inspeção, **não** por script — nenhuma das cinco camadas as verifica hoje | Nada; é dívida de instrumento. Candidata natural a uma sexta camada de composição |
| **PN-P7** | **Retroativos nos outros 40 HTML do repositório** (camada visual e régua de ação) | Chat separado, a pedido do Rafael |

### 48.10 Os testes

1. **Nenhum par de cor novo** — o painel compõe peças já medidas; a medição do bloco confere os
   pares **em uso**, e a lição do §46 e do §47 vale: compor pares medidos separadamente **não
   garante** que a combinação passe.
2. **Escada de mecanismos:** lente ativa por `aria-current` + marca visual + peso, nunca só cor;
   variação de KPI por seta (sinal) + cor (juízo), nunca cor sozinha (PN14b).
3. **Grayscale:** lente ativa, estado de frescor e severidade permanecem legíveis (DF4).
4. **Regra do um:** uma lente ativa por bloco · **uma pergunta por bloco** · **UMA live region no
   painel inteiro** (PN9/§1.13) · um itálico por título-insight (PN21e) · **um hero escuro por
   página** (PN21c).
5. **Landmarks e nomes:** todo bloco é `section` nomeada; nenhum nome repetido no painel.
6. **Frescor e norma:** todo bloco que se atualiza declara última leitura e estado; **existe
   controle de frequência com opção manual** (PN8, WCAG 2.2.2 nível A); atualização não move
   foco nem anuncia sem pedido (PN9).
7. **Escopo do cliente:** no modo cliente, **nenhum identificador de terceiro** aparece — nem em
   agregado. Teste documental além de visual, como o de procedência do §47.
8. **Blocos de dado:** nenhum KPI sem período e base de comparação (PN14) · barra de ranking com
   base proporcional declarada e valor numérico à vista (PN15) · **nada semântico dentro do
   `progressbar`** e `aria-valuenow` omitido quando indeterminado (PN18) · toda entrada de
   timeline endereçável (PN20).
9. **Edição direta:** todo gesto de arrasto tem **caminho de ponteiro único** (PN23, SC 2.5.7) ·
   contrato de teclado idêntico em toda lente (PN24) · **nada derivado tem alça** (PN22) · ímã na
   unidade da escala (PN25) · **nenhum `transform` no redimensionamento** (PN35) · desfazer de 8s
   cobrindo criação e exclusão (PN28) · o item mutado é **realçado e trazido à vista** (PN34b).
10. **Contagem honesta:** a contagem acompanha filtro **e** mutação — criar ou excluir muda o
    total exibido no mesmo instante (PN40).
11. **Mapa:** dois modos declarados, cobertura como padrão (PN51) · estilo por token e marcador
    do DS (PN52) · **fallback que diz o que aconteceu** e nenhuma chave no artefato (PN53) ·
    **alternativa textual como conteúdo nos dois modos** (PN54).
12. **Convite e gesto no mesmo lugar:** toda zona que convida a um gesto executa aquele gesto, e
    a promessa da dica é a ação que acontece (PN49) · **dois gestos nunca dividem o mesmo pixel**
    (PN41e) · ação destrutiva não mora em alvo invisível que cruza a área (PN36).

### 48.11 Estado da validação

*(Estado: **`estável`**. **Gate visual do Rafael APROVADO em 2026-08-13 sobre o preview v0.6** — o quarto e último gate do bloco, depois das quatro camadas automatizadas. Com ele o **bloco 6C FECHA** e a Fase 6 vai a 3/3 blocos concluídos (6A §46 Shell · 6B §47 Página institucional · 6C §48 Painel). **A promoção é dos contratos PN1–PN54; as decisões de marca S3, S4 e S6 seguem proposta — ver a ressalva no cabeçalho do §48 e a pendência PN-P1 em §48.9.** Este bloco é o primeiro do projeto validado em **cinco** camadas, e a quinta (`render-contraste.mjs`) nasceu de um defeito que as outras quatro não podiam ver.)*

**Rodada 1 — 2026-08-12, preview v0.1 → v0.2.** `suite-painel.mjs` **67 PASS · 0 FAIL** — *prova
de que as guardas são reais:* contra um preview com 5 defeitos deliberados (lente desabilitada em
vez de oculta, frequência sem opção manual, pílula pintada pelo sinal, `aria-valuemin` ausente,
base proporcional não declarada) ela **reprova em 6**; `render-painel.mjs` **17 PASS · 0 FAIL**;
`contraste-painel.py` **44 PASS · 0 FAIL** após as correções que a própria medição exigiu. **O
gate visual reprovou o v0.1 e produziu o PN3b** — as lentes "linha do tempo" e "calendário"
desenhavam listas. Preview a **v0.2**, suíte a **75**, render a **25**; contra a versão em que as
duas lentes voltam a ser lista, a suíte **reprova em 6**.

**Rodada 2 — 2026-08-13, preview v0.3.** Oito lentes (S1) e camada visual (PN21).
`suite-painel.mjs` v2 **107 PASS · 0 FAIL**; *prova:* contra um preview com **4 defeitos
plantados** — gantt sem dependências, véu a 35%, dois heros, `transition` com duração solta — ela
**reprova exatamente nos 4**. `render-painel.mjs` v2 **37 PASS · 0 FAIL**, medindo em Chrome real
3 conectores de dependência com extensão em pixels, 5 faixas de responsável com **sub-linhas de
sobreposição em tops distintos**, marca de capacidade no mesmo x, excedente com largura real,
contorno do mapa com bbox medido e 5 pontos dentro do viewBox. `contraste-painel.py` v2 **70 PASS
· 0 FAIL**, incluindo os pares do hero escuro (branco × `cinza-900` = 13,86; itálico
`turquesa-300` = 7,57; botão-solução = 5,11).

**Rodada 3 — 2026-08-13, preview v0.5 → v0.6. Cinco camadas, todas verdes, todas re-executadas
contra o artefato final:**

| Camada | Ferramenta | Placar | O que ela mede |
|---|---|---|---|
| `suite-painel.mjs` v4 | Node + jsdom | **133 PASS · 0 FAIL** | Estrutura, ARIA, tokens, classes fantasma, referências fantasma, higiene |
| `render-painel.mjs` v3 | Chrome headless | **37 PASS · 0 FAIL** | Colapso da grade em 3 larguras, geometria das oito lentes, frescor vivo |
| `render-edicao.mjs` v1 | Chrome headless | **142 PASS · 0 FAIL** | Os 33 contratos de edição direta, filtro, escala, mapa e criação — gesto por gesto, com pixels |
| `render-contraste.mjs` v1 | Chrome headless | **6 PASS · 0 FAIL** | Contraste de **composição** renderizada nos dois temas (368 textos medidos), não de par de token |
| `contraste-painel.py` v2 | Python | **70 PASS · 0 FAIL** | Pares declarados, calculados fora do navegador |

**Camada nova desta rodada, e a razão dela:** o `render-contraste.mjs` nasceu de uma lição
medida — **contraste de TOKEN não substitui contraste de COMPOSIÇÃO**. Os pares do
`contraste-painel.py` estavam todos verdes enquanto, no tema escuro, a ação primária em
`azul-800` media **1,48** de superfície contra o cartão (sumia) e a secundária tinha **texto**
`azul-800` sobre cartão escuro (1,48 — ilegível). Nenhum par de token estava errado; a
**combinação** estava. A camada varre todo texto sobre superfície opaca nos dois temas e reporta o
pior caso: hoje **5,56** em `button`.

**Rodada 4 — 2026-08-13 (noite), preview v0.6 → v0.7 → v0.8. A inversão do hero.** Cinco
camadas re-executadas contra o v0.8 final: `suite-painel.mjs` **139 PASS · 0 FAIL** (era 133;
entram `NV-01`, `PN21-05c/05d/05e/05f/05g`) · `render-painel.mjs` **37 · 0** ·
`render-edicao.mjs` **142 · 0** · `render-contraste.mjs` **6 · 0** · `contraste-painel.py`
**78 · 0** (era 70; entram `PC-H5b`, `PC-H6`…`PC-H11`). **Nenhum artefato canônico foi
tocado** — cinco arquivos de preview e validação, e nenhum dos 40 HTML do repositório.

> **DEFEITO REAL DA RODADA, e quem o pegou foi a quinta camada: TOKEN FANTASMA.** Na primeira
> execução após a inversão, o `render-contraste.mjs` reprovou **15 textos com razão
> 1,0–1,26**, incluindo **branco sobre branco**. Causa: o molde passou a consumir
> `turquesa-400/600/700` e a lista `PRIMITIVOS` do `gen-painel.py` **ainda era a da era do
> azul** — e **variável CSS indefinida é valor inválido, então o fundo simplesmente não
> pintou**. É a classe **NV-01** do §47 (nascida quando a command palette abriu
> transparente), que tinha detector na suíte de navegação e **faltava nesta**. Corrigido na
> causa e **detector criado aqui**: `NV-01` — todo `var(--seed-*)` consumido tem definição no
> bloco de tokens. Pega na suíte jsdom, antes de qualquer render. *Lição de método: guarda
> genérica criada num bloco não se propaga sozinha para os outros — quando uma classe de
> defeito ganha detector, o detector precisa ser levado para toda suíte que possa sofrer dela.*

**Errata E-6C-01 (2026-08-13, achado na conferência de ambiente): título e selo declaravam
versões diferentes.** O `<title>` do preview dizia *"preview v0.3"* e o selo na tela dizia
*"preview v0.5"* — duas declarações de versão no mesmo arquivo, discordando. A guarda `HIG-03`
passava verde porque media apenas **presença** de versão no título. **O defeito importa porque o
título da aba é o primeiro sinal de arquivo fresco** — três relatos de *"não funciona"* nesta
sessão foram cache, e um título que mente cega justamente o instrumento usado para detectar cache.
*Correção estrutural, não sintomática:* a versão passa a ter **fonte única** (constante
`VERSAO_PREVIEW` no `gen-painel.py`), consumida por dois marcadores do molde
(`@@VERSAO_TITULO@@` no título, `@@VERSAO@@` no selo, este com data e hora) — divergir deixa de
ser possível por construção. Preview bumpado a **v0.6** porque o artefato corrigido tem de ser
distinguível do defeituoso **na tela**. *Guarda de regressão permanente, creditada ao Rafael:*
**`HIG-06` — título e selo declaram a MESMA versão**; contra um artefato com a versão do título
plantada de volta em v0.3, a suíte **reprova em exatamente 1**, e a mensagem nomeia os dois
valores.

**Nota de determinismo, para quem for reconferir hashes:** o selo carimba **data e hora de
geração**, então regenerar o preview produz um MD5 diferente **sem nenhuma mudança de
comportamento** — conferido: a cadeia
`malha-projetada.json → gen-mapa-lente.py → mapa-lente.json → gen-painel.py + painel-template.html + seed-tokens.css → seed-painel-preview.html`
reproduz **209.288 bytes e 3.730 linhas idênticas, com um único diff: a linha do selo**. Para o
preview, **a âncora é o arquivo entregue, não a receita**.

> **Registro de defeitos e lições — rodada 2 (2026-08-13). Todos viraram guarda ou nota de método.**
> **(1) RDP em anel fechado come o fecho.** A simplificação Ramer–Douglas–Peucker do contorno
> externo do mapa, aplicada ao anel inteiro, ancorava o primeiro e o último vértice **no mesmo
> ponto** e degenerava o fecho. Conserto no `gen-mapa-lente.py` v2.2: dividir o anel em duas
> metades, simplificar cada uma e recosturar. *Classe:* algoritmo de polilinha aplicado a
> polígono sem tratar a topologia.
> **(2) Token fantasma evitado antes do build.** O hero usou `--seed-tq-300`, que **não existe**
> — o primitivo é `--seed-turquesa-300`. Pego por conferência de nome contra o CSS canônico antes
> da geração (classe NV-01 do §47: nome de token digitado de memória).
> **(3) Instrumento calibrado no dado quebra com o dado.** O `R4-02` do render v1 exigia a menor
> barra do ranking entre 30% e 50% — faixa que descrevia os dados ilustrativos antigos, não a
> propriedade. Com dados novos (58/486 = 11,9%) reprovou sem haver defeito. Recalibrado para a
> propriedade: largura renderizada ≈ razão do próprio dado. *Mesma família da lição do 6A
> (instrumento errado invalida a medição), variante temporal.*
> **(4) Dado de exercício precisa conter o caso que a guarda mede.** O `R7-03` (sub-linhas de
> sobreposição) reprovava porque **nenhuma pessoa tinha duas OS sobrepostas** — o mecanismo
> existia e a suíte estrutural passava, mas a geometria não tinha o que mostrar. A OS-2301 foi
> movida para colidir com a OS-2296 do mesmo responsável. *Regra: todo comportamento especificado
> precisa de pelo menos um exemplar no dado ilustrativo.*
> **(5) `--single-process` do pacote `@sparticuz/chromium` trava element-screenshot.** Os args
> padrão do pacote (feitos para Lambda) deadlockam o Chrome em `element.screenshot()` sequencial.
> Args enxutos (`--no-sandbox`) resolvem. Nota de ambiente para qualquer captura futura.

> **Duas correções que a medição impôs, e as duas são reaproveitáveis fora deste bloco.**
> **(1) A regra do dourado vale fora do dataviz.** O ponto de "dado antigo" do frescor usava
> `feedback-warning-solid` (`#F9B11C`) e media **1,85** contra o cartão no tema claro. A **regra
> do dourado** da §3c veta o dourado como *fill solitário* — e um ponto de 8px é exatamente isso.
> Passou a consumir `feedback-warning-text`: **9,84** no claro, **10,09** no escuro. A regra
> nasceu no dataviz e se aplica a **qualquer** preenchimento pequeno e isolado.
> **(2) Barra fina de dado consome a variante `-stroke`.** O preenchimento cheio `chart-cat-1`
> media **2,97** contra o trilho no claro — reprovava 3:1 por 0,03. A variante `chart-cat-1-stroke`
> existe exatamente para *"linha fina e marcador pequeno, o pior caso do 1.4.11"* e mede **4,23**.
> Vale para a barra do ranking (PN15) e para a de progresso (PN17).
>
> **A correção do PN3b expôs um segundo defeito, de raiz diferente.** Ao desenhar o calendário
> de verdade, os eventos caíram **um dia antes** e a grade saiu com sete larguras diferentes.
> Causa: `new Date("2026-08-12")` interpreta a string como **UTC** e desloca no fuso do
> navegador. A primeira tentativa de conserto foi **somar um dia** — remendo sobre o sintoma, que
> teria quebrado em qualquer fuso diferente. O conserto real é **decompor a data em números** e
> fazer a aritmética em UTC puro; e as colunas exigem `minmax(0, 1fr)`, senão o conteúdo do dia
> empurra a coluna. *Classe de defeito:* **remendo sobre sintoma em vez de causa** — sobrevive ao
> teste na máquina de quem escreveu e falha na de quem usa.
>
> **E um defeito de alvo de toque que só o render pegou:** o link de cada evento da timeline —
> justamente o que torna a entrada **endereçável** (PN20) — media **14px** de altura. Alvo de
> toque ≥44px vale para ele como para qualquer controle.

> **LIÇÕES MEDIDAS NA RODADA 3 (2026-08-13). Catorze, e nenhuma é sobre o componente — todas são
> sobre COMO se descobre que algo está errado.** Registradas aqui porque valem para qualquer
> bloco futuro.
> **(1) Instrumento calibrado no dado quebra com o dado** — faixa fixa em vez de propriedade
> (reincidência da lição 3 da rodada 2; virou regra).
> **(2) Feições que se tocam, simplificadas em separado, deixam de se tocar.** No mapa, a
> simplificação independente de cada fronteira abria vão entre municípios vizinhos. Conserto:
> RDP com **nós de junção preservados**; vão medido **4,9px → 0,000px**.
> **(3) Filtro pela grandeza errada.** Contar vértices **não mede extensão**: a divisa BA–ES tem
> **6 arestas** e desaparecia num filtro de complexidade.
> **(4) Classe CSS derivada de rótulo humano quebra em silêncio.** `"Em campo"` virava
> `.st-Emcampo`, que nunca existiu no CSS. **Detector permanente criado** (`PN21-04d`/`PN21-04e`):
> toda chave de estado emitida pelo script tem regra CSS correspondente, e a classe **não** é
> derivada do rótulo.
> **(5) Referência órfã mata o script inteiro.** `getElementById` para id inexistente derruba
> tudo abaixo dele, e a tela fica parada sem erro visível. Detectores **`REF-01`/`REF-02`**: todo
> `getElementById` e todo `querySelector` de id fixo no script têm alvo existente no documento.
> **(6) Camada de sobrescrita de token sem variante de tema é meia implementação.** A camada de
> tinta (S6) nasceu só com a face clara; no escuro o painel inteiro ficou ilegível.
> **(7) Contraste de TOKEN não substitui contraste de COMPOSIÇÃO** — o `render-contraste.mjs`
> nasceu disso.
> **(8) O que nasce fora do campo de visão é lido como "não funcionou"** → PN34b (realce +
> rolagem até o item mutado).
> **(9) Convite que aparece onde o gesto não age é pior que convite nenhum** → PN49.
> **(10) Ação destrutiva não pode morar em alvo invisível que cruza a área** → PN36 (o fio de
> dependência apagava por acidente ao clique simples).
> **(11) Dois gestos não podem dividir o mesmo pixel** — nó de dependência × punho; criar ×
> panorâmica → **modo explícito** (PN41e).
> **(12) `viewBox` e área têm de medir o mesmo**, senão a camada esticada desalinha tudo (o fim
> do conector não coincidia com a barra dependente; guarda `I-20`).
> **(13) Todo comportamento especificado precisa de um exemplar no dado ilustrativo** (a lição 4
> da rodada 2, confirmada duas vezes: sub-linhas de sobreposição e ordem com duas predecessoras).
> **(14) Defeitos do INSTRUMENTO têm o mesmo peso que defeitos do artefato.** Quatro medidos:
> coordenada de viewport em vez de rect da área · medir **durante** transição CSS · escolher ponto
> de clique sem verificar o que está sob ele · medir no sentido em que o navegador **já está no
> fim do curso**. Uma suíte errada reprova artefato bom e aprova artefato ruim — a auditoria do
> instrumento é parte da validação, não etapa opcional.

> **Nota de método da rodada 3, registrada por ordem do Rafael:** duas vezes nesta sessão um lote
> de edições imprimiu "OK" **sem que o arquivo tivesse sido salvo** — o `write` fica no fim do
> script e o abort intermediário deixava tudo no meio. **Regra permanente: se um lote de edições
> abortar, RE-EXECUTAR O LOTE INTEIRO; "OK" impresso não significa arquivo salvo, e só a leitura
> de volta por MD5 significa.**
---

> **PROMOÇÃO A `estável` — 2026-08-16.** As seções §49 a §55 foram promovidas em bloco pelo Rafael
> ("aprovo") após o gate visual sobre as seis telas do projeto. **O gate encontrou dois defeitos
> reais que as cinco camadas automatizadas atravessaram**, e os dois viraram guarda permanente
> antes da promoção: (a) no regime MODAL a lista de fundo acompanhava a navegação entre irmãos —
> verbatim, *"está mexendo as duas janelas… era pra mexer só a que abriu sobreposta"* —, o que
> gerou o **PD19**; (b) a topbar não degradava por CONTÊINER, só por viewport, e no estreitamento
> o rótulo da marca quebrava em duas linhas e a busca empurrava o avatar. Guardas novas: `SNC-01`
> a `SNC-03`, `TOP-01` a `TOP-04`. **Nenhuma seção é promovida sem que o defeito que o gate achou
> tenha virado teste** — é a regra que o projeto pratica desde a Fase 6.

## 49. Detalhe de registro — painel e modal — `estável` (F7.3, 2026-08-16) · PD1–PD19 · gabarito A8 · fecha a pendência PN-P3

> **O que esta seção resolve, e por que ela demorou.** A **PN-P3** está aberta desde a Fase 5: todo
> ERP é *lista → detalhe*, e o nosso caminho morria na lista. A seção não foi escrita antes por um
> motivo declarado — não havia LEITURA VISUAL do arquétipo, só extração de valores, e a F7.2 provou
> caro o que isso produz (números certos, composição errada). A leitura chegou em 2026-08-15, na
> §13.4 do `estudo-clickup-completo.md`, com sete dispositivos observados: **C50** modal que navega
> entre irmãos sem fechar, com posição no conjunto em cabeçalho fixo · **C51** propriedades em
> grade de duas colunas · **C52** campos customizados em lista com ícone de tipo e rótulo truncado ·
> **C53** ações do campo reveladas no hover · **C54** corpo truncado com "Expandir" · **C55** rail
> de seções DENTRO do modal · **C56** contadores de metadado como ícone+número.
>
> **Base (3 rodadas encadeadas + rodada zero, 2026-08-15).**
> **Rodada zero — leitura visual:** confirmada na §13.4 antes de qualquer spec. Regra da fase.
> **R1 canon:** **Carbon** dá a régua por VOLUME, não por gosto — modal quando um subconjunto
> pequeno é editável; se há campos suficientes para exigir rolagem, painel lateral ou página
> inteira; e o side panel existe para *manter o usuário em contexto com a página*. **Fiori** dá o
> object page (seções e subseções com navegação por âncora ou aba) e duas réguas operacionais que
> entraram inteiras: a paginação entre irmãos só aparece quando a lista de origem tem **ao menos
> dois itens**, e ao excluir o item corrente seleciona-se o anterior — se não sobra nada, fecha.
> Fiori também manda usar **o mesmo layout em exibição e edição**: o conteúdo não muda de lugar.
> **R2 mercado/stack:** Radix Dialog entrega os dois regimes (modal e não-modal) com controle fino
> de foco, o que barateia a decisão PD1 — é o mesmo primitivo. Nota de fronteira registrada: o
> Radix foi adquirido pela WorkOS e a cadência caiu em alguns componentes; o Base UI é hoje a
> camada de primitivos mais ativa, e o shadcn/ui é o consumo dominante. Não muda decisão aqui;
> muda o que vigiamos. **R3 normas / fora do circuito:** o cabeçalho fixo do C50 é risco de norma,
> não detalhe estético — a falha **F110** do WCAG 2.2 descreve exatamente o sticky que cobre o
> elemento focado, e o SC 2.4.11 é AA; o conserto canônico é `scroll-padding`. O hover do C53 é a
> armadilha maior: o Spectrum corrigiu este caso em novembro de 2025 trocando `visibility:hidden`
> por `opacity:0`, porque `visibility:hidden` e `display:none` **removem da árvore de
> acessibilidade** e o botão deixa de ser alcançável por Tab. O "Expandir" do C54 precisa de
> `aria-expanded` + `aria-controls` e de rótulo que nomeie o quê.
>
> **A LACUNA QUE A PESQUISA REVELOU, e é o achado da rodada:** **não existe canon para troca de
> conteúdo DENTRO de um diálogo já aberto.** Todo o corpus trata abertura e fechamento. O anúncio
> do nome do diálogo acontece quando o foco entra nele; trocar o texto do título com o foco parado
> **não produz anúncio nenhum**. Sem instrumento próprio, navegar entre irmãos é silencioso para
> leitor de tela. O PD3 é decisão nossa, declarada como tal.
>
> **Gate de 2026-08-15 (Rafael, sobre amostra A/B navegável):** *"modal A por padrão, e opção do
> modelo B caso o usuário prefira, então vamos ter as duas opções. no modelo A, deixa opção de
> expandir a janela depois que ela for aberta."* Daí saem o PD1 (dois regimes, modal como padrão),
> o PD17 (dois degraus da janela) e o PD18 (alternância a partir do próprio detalhe).

### 49.1 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **PD1** | **Três regimes, escolhidos por VOLUME de conteúdo, com o MODAL como padrão.** Modal centrado (registro que cabe sem rolagem longa) · painel lateral **não-modal** (o contexto da lista importa durante a leitura) · página inteira (objeto com muitas seções e edição global). O painel é **preferência do usuário**, persistida por usuário e não por sessão | Copiar o modal do produto de referência como regime único. Carbon é explícito: campos suficientes para exigir rolagem já pedem painel ou página. E descarta manter **dois desenhos** do detalhe: o markup é UM só, movido de hospedeiro (ver PD16) |
| **PD2** | **A paginação entre irmãos só existe com conjunto ≥ 2**, exibe a posição (`9/10`) e nas pontas fica **desabilitada** | Seta sempre presente e inerte: com um registro só, um controle que não faz nada é botão morto. Régua literal do Fiori |
| **PD3** | **Trocar de irmão NÃO devolve o foco ao topo**; o foco permanece no controle acionado e a troca é anunciada por live region `polite` no formato "Registro N de M — título" | Reabrir o diálogo a cada irmão (perde rolagem e re-anuncia tudo) e confiar no `aria-labelledby`, que só é lido na abertura. **Sem canon — decisão declarada** |
| **PD4** | **Registro excluído → seleciona o anterior; conjunto vazio → fecha** | Manter o detalhe aberto sobre um registro que não existe mais |
| **PD5** | **Cabeçalho do detalhe é FIXO**, e o contêiner de rolagem declara `scroll-padding-top` **maior** que a altura dele | Sticky sem compensação: é a falha F110 do SC 2.4.11. Medido no gabarito: cabeçalho 68px, `scroll-padding-top` 76px — 64px reprovava por 4px, e "quase" não passa numa norma cujo teste é o foco sumir atrás da barra |
| **PD6** | **Ausência de valor é SEMPRE travessão**, na grade e na lista de campos | A palavra "Vazio". O produto de referência usa os dois tratamentos no mesmo registro (C52) — divergência registrada e **não copiada**; o DT7 já escolheu o travessão |
| **PD7** | **A grade de propriedades responde ao CONTÊINER** (CP27): 1 coluna até 520px, 2 acima, 3 a partir de 900px | Breakpoint de viewport. Consequência que vale a lei: expandir a janela faz **caber mais**, nunca deixa o dado **maior** |
| **PD8** | **Altura de linha do campo é FIXA; quem cede por falta de largura é o RÓTULO**, com o texto completo em `title` e equivalente por foco (X2 do §23) | Truncar o valor primeiro. O valor é o dado; o rótulo é a etiqueta dele, e etiqueta curta ainda identifica |
| **PD9** | **Ações do campo reveladas no hover E no `:focus-within`, com `opacity:0` — nunca `visibility:hidden` nem `display:none` — e FORA do fluxo** (`position:absolute`) | Os dois primeiros removem da árvore de acessibilidade (SC 2.1.1). E manter as ações no fluxo reserva largura permanente para o que está invisível: medido, 88px que faziam o valor truncar sempre no painel de 460px |
| **PD10** | **O corte do corpo é disclosure de verdade**: `<button>` com `aria-expanded` + `aria-controls` resolvendo em elemento real, rótulo que nomeia o quê ("Expandir descrição") e muda com o estado; corte por `line-clamp` | Link, `<details>`/`<summary>` (perde o controle do clamp por número de linhas) e o rótulo "Expandir" solto, que fora do contexto visual não informa nada |
| **PD11** | **O rail de seções é navegação por ÂNCORA**, não abas: documento único rolável, `aria-current` em exatamente uma seção, nome acessível em cada botão | Abas: fragmentariam o objeto e quebrariam a rolagem única que o PD10 pressupõe. Fiori admite âncora **ou** aba; âncora é o caso quando as seções são partes do mesmo objeto |
| **PD12** | **Contador de metadado leva nome textual** ("3 anexos"); o ícone não é rótulo | O número nu: não diz de quê. Herda o §21 |
| **PD13** | **O detalhe tem URL própria** e, ao fechar, **o foco volta à linha de origem** — se ela saiu do DOM, ao lugar mais próximo dela | Estado só em memória: impede compartilhar o registro e devolve o foco ao topo do documento, fazendo perder o lugar na fila |
| **PD14** | **Regime modal:** `showModal()` nativo, `aria-modal`, fundo inerte, Esc fecha. **Regime painel: NÃO-MODAL** — sem trap de foco, com a lista viva ao lado | Painel lateral com trap: contradiz a razão de ele existir. Para diálogo não-modal a norma pede atalho global que mova o foco entre o diálogo e a página |
| **PD15** | **Campos editam por edição direta**, herdando PN22–PN41 do §48 sob o SC 2.5.7 | Modo de edição global na v1. Fiori: o conteúdo não muda de lugar entre exibição e edição |
| **PD16** | **UM só markup de detalhe no documento**, movido entre os dois hospedeiros | Clonar o conteúdo por regime. Dois desenhos vivos é como um deles envelhece sozinho — e a troca de regime passaria a perder o estado do registro |
| **PD17** | **A janela tem DOIS DEGRAUS DECLARADOS** (padrão e expandido), operados por **botão de dois estados** com `aria-pressed`, rótulo e ícone acompanhando. O degrau é preferência do usuário e **sobrevive à troca de registro**. No degrau estreito o botão **some** | Alça de arraste como caminho único: cai sob o SC 2.5.7 e exigiria alternativa sem arraste de qualquer forma — o mesmo raciocínio que transformou o resizer de coluna do §51 em slider. Fiori admite arraste **com tamanhos predefinidos e encaixe**; adotamos os tamanhos e dispensamos o arraste. Descarta também a janela que cresce até o conteúdo: com registro curto o botão ficaria inerte |
| **PD19** | **A lista de origem só ACOMPANHA quando está viva.** No regime painel o realce segue a navegação entre irmãos — a lista está visível ao lado e a sincronia é o serviço. No regime **modal** ela **não acompanha**; a marcação é aplicada no **fechamento**, junto com o foco, e aponta **onde a leitura parou** | Sincronizar sempre. Achado do gate de 2026-08-16, verbatim: *"quando navego com a seta pra cima ou para baixo, ele está mexendo as duas janelas… era pra mexer só a que abriu sobreposta"*. Sob o véu do modal o fundo está **inerte**: movimento ali não informa nada e compete com o conteúdo que tem o foco. Descarta também devolver o foco à linha que **abriu** a leitura — quem navegou do 3 ao 7 espera voltar ao 7 |
| **PD18** | **A alternância de regime vive DENTRO do cabeçalho do detalhe** | Um controle de preferência fora do detalhe. Com o diálogo modal aberto o fundo é **inerte**, e o controle ficaria inalcançável exatamente quando seria usado — medido nesta produção |

### 49.2 Anatomia

```
┌─ cabeçalho FIXO (PD5) ──────────────────────────────────────────────┐
│ [↑][↓]  9/10  «chip de status»  Título do registro …   [⤢][×]       │
├──────────────────────────────────────────────────┬──────────────────┤
│ contadores de metadado (PD12)                    │  rail de seções  │
│ grade de propriedades — 1 · 2 · 3 col (PD7)      │  (PD11, âncora)  │
│ DESCRIÇÃO + [Expandir descrição] (PD10)          │   ▤              │
│ CAMPOS DO DOMÍNIO — lista, ícone de tipo,        │   ✉ ②            │
│   rótulo truncável, ações no hover/foco (PD8/PD9)│   ∿              │
│ COMENTÁRIOS                                       │                  │
└──────────────────────────────────────────────────┴──────────────────┘
```

**Os dois hospedeiros, com a mesma anatomia:**

| | Modal (padrão) | Painel lateral |
|---|---|---|
| Elemento | `<dialog>` com `showModal()` | `<aside>` irmão da região de conteúdo |
| Fundo | inerte | vivo — a lista continua operável |
| Largura | 860px · expandido 1360×940 | largura de peça lateral |
| Grade de propriedades | 2 col · 3 col no expandido | 1 col |
| Botão de tamanho | presente | ausente (não há dois tamanhos a oferecer) |

### 49.3 Acessibilidade

- O diálogo é **nomeado pelo título do registro** (`aria-labelledby` apontando para o `<h2>` que
  exibe o nome). O nome muda a cada irmão; o **anúncio** não vem daí, vem da live region do PD3.
- **Foco entra** no detalhe ao abrir, **volta ao gatilho** ao fechar (PD13).
- **Nenhuma ação depende de hover** (PD9): tudo que o mouse revela, o teclado revela por
  `:focus-within`, e o alvo tem 44px **no eixo curto também** — regra que este projeto já reprovou
  quatro vezes por medir só um dos eixos.
- **Cabeçalho fixo × foco**: `scroll-padding-top` maior que o cabeçalho (PD5, SC 2.4.11).
- **Contraste**: 28 pares medidos nos dois temas, **56 · 0** (`contraste-f73.py`). Nenhum par novo:
  tudo é consumo de token canônico. **Um achado real da medição**: a primeira versão usava
  `border-default` na fronteira dos botões, que mede **1,49** sobre branco e **2,32** no escuro —
  reprova o SC 1.4.11 nos dois temas. Trocado por `border-interactive` (**3,38** e **4,50**), que
  nasceu na v6.9 exatamente para este papel.

### 49.4 Estados

| Estado | Tratamento |
|---|---|
| Valor ausente | travessão, nos dois blocos (PD6) |
| Rótulo longo | trunca com reticências, `title` com o texto completo, altura fixa (PD8) |
| Corpo longo | `line-clamp` + disclosure nomeado (PD10) |
| Primeiro/último do conjunto | seta da ponta **desabilitada**, nunca inerte sem sinal (PD2) |
| Conjunto vazio | o detalhe fecha (PD4); a lista mostra o zero-resultados do §25, com saída |
| Degrau estreito | o detalhe vira **página**: ocupa a tela, o botão de tamanho some (PD17) |

### 49.5 Números MEDIDOS na produção do gabarito

A spec nasce com **relações**; os números são medidos no artefato — método do §55.

| Medida | Valor |
|---|---:|
| Largura do modal, degrau padrão | **860px** |
| Largura × altura, degrau expandido | **1360 × 940px** |
| Colunas da grade: painel · modal · expandido | **1 · 2 · 3** |
| Tipografia do rótulo e do valor nos dois degraus | **12px e 14px — idênticas** |
| Altura do cabeçalho fixo · `scroll-padding-top` | **68px · 76px** |
| Altura da linha de campo do domínio | **36px** |
| Alvo dos botões do cabeçalho e do rail de seções | **44 × 44px** |
| Alvo das ações de campo | **44 × 36px** (a linha inteira é o alvo de leitura) |
| Fronteira de controle × superfície (claro · escuro) | **3,38 · 4,50** |

### 49.6 Validação

| Camada | Placar |
|---|---|
| `suite-detalhe.mjs` (render) — as guardas do §49 | **54 · 0** (47 na produção + 7 nascidas do gate de 2026-08-16) |
| `suite-composicao.mjs` (render, 2 temas) | **33 · 0** |
| `suite-container.mjs` — blocos CQ e RF, com alvo explícito | **15 · 0** |
| `contraste-f73.py` — 28 pares × 2 temas | **56 · 0** |
| Fidelidade do gerador | reprodução byte-perfeita conferida por MD5 |

> **Escopo declarado da `suite-container.mjs`:** ela foi escrita para os gabaritos de dado A2/A3 e
> cobra árvore com `role=tree` (ARV-01…03), que é estrutura do A2 e não do A8 — o escopo do detalhe
> é lista plana, e dar hierarquia à sidebar só para satisfazer um teste seria inventar estrutura
> para agradar instrumento. Os blocos que se aplicam (CQ container query, RF reflow) rodam com o A8
> passado como alvo explícito. **Isto é decisão registrada, não placar vermelho tolerado.**

### 49.7 Pendências desta seção

| # | Pendência | Por que fica aberta |
|---|---|---|
| **PD-P1** | O **§56 comentários** não é decidido aqui. A fronteira do C85 — comentário como cartão dentro do cartão do objeto — é **peça em peça**, que o CP2 proíbe no caso base | Há **um** dispositivo observado, e ele é justamente o caso difícil. A rodada zero exige leitura visual do arquétipo, e uma leitura de um caso não é leitura do arquétipo. **Navegar antes** |
| **PD-P2** | O regime **página inteira** (3º degrau do PD1) não tem gabarito | Nenhum registro do escopo atual exige. Nasce quando houver caso SEED que o peça, sob a mesma régua de volume |
| **PD-P3** | **Persistência** da preferência de regime e do degrau da janela | É contrato de produto, não de DS: o gabarito demonstra o estado; onde ele mora é decisão do ERP |
| **PD-P4** | **Deep link** do PD13 declarado, não exercitado no gabarito | O gabarito é arquivo único sem rota; provar URL própria exige aplicação |

---

## 50. Agrupamento de linhas — `estável` (F7.2, 2026-08-15) · AG1–AG8 · gabarito A2

> **Base (3 rodadas, 2026-08-15):** **R1 canon** — Fiori (agrupamento existe nas tabelas
> responsiva e analítica, com formato de cabeçalho de grupo declarável; a régua de
> personalização é por número de colunas, ~20) · Polaris IndexTable (*subheaders* com props de
> acessibilidade próprias) · Oracle OTM (seleção pelo cabeçalho de grupo abrange o grupo inteiro) ·
> HTML nativo (`<tbody>` como rowgroup, `th scope="rowgroup"/"colgroup"`). **R2 stack** — TanStack
> `grouping` com `groupedColumnMode: 'reorder' | 'remove'`. **R3 normas** — MDN `row` role
> (`aria-expanded` em linha pertence ao padrão **grid/treegrid**, não à tabela estática) ·
> **Adrian Roselli, "Table with Expando Rows"** (o padrão funciona em `<table>` nativa: disclosure
> `<button>` com `aria-expanded`, `display:table-row` × `none`, linhas reveladas DEPOIS do
> controle na ordem do código, `aria-controls` opcional — JAWS retirou o anúncio em 2019) ·
> APG treegrid (*"o código deste exemplo não é para produção"*) + Roselli (*treegrid é widget
> composto, suporte pobre, "não vi em produção"*) · JAWS 2024 issue #791 (**tabela aninhada não
> navega** em JAWS + Chrome/Edge; NVDA navega).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| AG1 | **Um `<tbody>` por grupo** — rowgroup NATIVO. O cabeçalho de grupo é uma `<tr>` com um único `<th colspan scope="colgroup">` | A semântica de agrupamento já existe no HTML; ARIA nova aqui seria "bad ARIA" sobre estrutura que o browser já expõe | `<div role="rowgroup">` (recria o que é nativo) · tabela dentro de tabela (**JAWS 2024 não navega** — #791) |
| AG2 | **O disclosure é um `<button aria-expanded>` DENTRO do cabeçalho de grupo — nunca `aria-expanded` na `<tr>`** | `aria-expanded` na linha empurra a tabela para o padrão **grid/treegrid** (MDN), e o **DT1 proíbe grid na v1**. O botão é disclosure puro, padrão com suporte universal | `role="treegrid"` (APG: não é para produção; suporte pobre) · linha inteira clicável (anti-padrão do DT1) |
| AG3 | **Nome acessível do disclosure = rótulo + contagem** (`"Metalúrgica Andrade, 3 faturas"`), e o estado vem do `aria-expanded` — nunca do texto | Roselli: o leitor de tela **não anuncia quantas linhas apareceram**; a contagem no nome administra a expectativa antes do clique | Nome só com o rótulo (o usuário expande às cegas) · anunciar "expandido/recolhido" no texto (duplica o que a AT já diz) |
| AG4 | **As linhas do grupo vêm DEPOIS do controle na ordem do código**, e o par de visibilidade é **`display:table-row` × `display:none`** | `display:block` numa `<tr>` **destrói a semântica de linha** (Roselli, *Tables, CSS Display Properties, and ARIA*); e linha revelada acima do controle deixa o usuário de AT sem saber onde ela apareceu | `visibility:hidden` (ocupa espaço) · `hidden` no atributo (mesma coisa, mas some da guarda CSS) |
| AG5 | **`aria-controls` é OPCIONAL e declarado como tal** — presente aqui, com a nota de que JAWS retirou o anúncio em 2019 e sua ausência não cria barreira | Manter a relação declarada custa nada e serve se o suporte voltar; prometer que ela funciona hoje seria falso | Depender de `aria-controls` para a navegação funcionar |
| AG6 | **Contador junto ao rótulo (C26), com o ZERO EXPLÍCITO** — `0` aparece, não some | Zero é informação: *"este grupo existe e está vazio"* é diferente de *"este grupo não existe"*. Medição do produto de referência: eles mostram `NOVO CLIENTE 0` | Esconder o contador no zero (o usuário passa a duvidar se o grupo sumiu) |
| AG7 | **Grupo de contagem zero revela o vazio de escala mínima (Z3)** — uma linha `"Nenhum item neste grupo"`, nunca o nada | Botão que não revela nada é botão morto — o mesmo defeito que o §25 já proíbe em preview, aqui em produção | Disclosure desabilitado (esconde a razão) · grupo zero oculto (contradiz o AG6) |
| AG8 | **Um nível de agrupamento na v1.** Subgrupo entra por demanda, com pesquisa própria | `<tbody>` **não aninha**, e a saída seria tabela dentro de tabela — que reprova em JAWS (#791). A profundidade que o ERP pede hoje é 1 | Agrupamento em árvore dentro da tabela (é outro arquétipo: `treegrid`, descartado no AG2) |

**Seleção dentro do grupo:** ver a extensão do §41 (SL6). **Pares de cor:** todos herdados —
cabeçalho de grupo em `surface-subtle` com `text-primary` (**12,63** claro / **14,02** escuro),
contador em `text-muted` (**5,61** / **6,80**), subtotal em `text-secondary` (**7,13** / **9,10**).
*(Instrumento: `contraste-f72.py`; comportamento: `suite-container.mjs`, guardas AG-01…AG-08.)*

---

## 51. Personalização de colunas — `estável` (F7.2, 2026-08-15) · PC1–PC7 · gabarito A3

> **Base (3 rodadas, 2026-08-15):** **R1** — Carbon (#10166: redimensionar coluna foi triado como
> **"not pursuing"** no core; #4750/#4751 especificam reorder/resize e declaram *"drag and drop is
> not accessible"*, prevendo caminho de teclado) · Fiori (diálogo de personalização com **mover
> para cima/baixo**; régua de ~20 colunas separando o diálogo simples do complexo). **R2** —
> **React Aria** `ResizableTableContainer` + `ColumnResizer`: **Enter entra em modo de
> redimensionamento, setas ajustam, e o leitor de tela opera o resizer como *slider***, com suporte
> a `forced-colors`; custo declarado pela própria doc: dentro do container as larguras **deixam de
> ser do algoritmo nativo** e passam a ser calculadas em JS · TanStack `columnSizing` /
> `columnOrder`, com aviso sobre bibliotecas de DnD em markup `<table>`. **R3** — SC 2.5.7
> (arraste tem de ter alternativa de ponteiro único) · SC 2.5.8 (alvo ≥24px — o resizer de 1px do
> produto de referência **reprova**).

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| PC1 | **O resizer é `role="slider"`**: `aria-valuemin`/`max`/`now`, nome acessível citando a coluna (*"Largura da coluna Cliente"*), setas ajustam em passos, `Home`/`End` vão aos limites | É a única semântica que descreve o que a operação faz (mover um valor numa faixa) e é a que a stack já implementa e testou com AT | `div` arrastável sem papel (invisível para AT) · botão que abre um campo numérico (perde a manipulação direta) |
| PC2 | **Alvo de acerto ≥24px sobre linha visível de 1px** (SC 2.5.8), com o alvo invisível por `::before` | O mesmo mecanismo do resizer de split do CP29 — um contrato, dois usos. O produto de referência usa 1px sem área ampliada e **reprova a norma** | Handle visível de 8–12px (ruído em tabela densa, e ainda reprova) |
| PC3 | **Redimensionar é DESLIGADO abaixo de 640px** | Gesto de precisão não sobrevive ao dedo, e no estreito a decisão certa é **qual coluna aparece** (LD3), não qual é a largura dela | Manter o resizer no toque com alvo maior (rouba área da própria coluna) |
| PC4 | **Reordenar coluna tem os três caminhos do CP30**: arraste · "mover para…" no menu do cabeçalho · teclado, com anúncio em live region | SC 2.5.7 exige ponteiro único **além** do teclado; e a medição do Carbon diz que **todos os usuários testados esperavam arrastar** — o arraste fica, como caminho rico | Só arraste (norma) · só diálogo (frustra a expectativa medida) |
| PC5 | **O PAINEL DE COLUNAS é o caminho completo sem arraste**: mostra/oculta, ordem (mover ↑↓), e declara a **importância LD3** de cada coluna | Um lugar só resolve reorder acessível, visibilidade e a decisão editorial do LD3 — que hoje vive espalhada. É o diálogo do Fiori com o nosso vocabulário | Três controles separados (o usuário não relaciona) · esconder a importância (a coluna que some no estreito vira acidente) |
| PC6 | **Largura mínima por coluna, e a largura é estado do GABARITO** (persiste por tela, não por sessão global) | Sem mínimo, uma coluna vira 8px e some sem aviso; e persistência global faria a mesma coluna nascer torta em outra tela | Sem mínimo · persistência entre usuários (é preferência pessoal) |
| PC7 | **Fronteiras:** o cálculo de largura em JS (custo do React Aria) só entra quando o resize é ligado — **tabela sem resize continua no algoritmo nativo do browser**; salvar visões nomeadas = produto | Não se paga o custo de layout de quem não usa o recurso; e "visões salvas" é decisão de produto, não de DS | Ligar o container de resize sempre (custo sem contrapartida) |

**Nota de integração, medida no produto de referência:** resize e reorder **combinados** já
produziram bug documentado (a largura "lembra" a posição antiga da coluna — Carbon IoT #1016).
A ordem correta é **reordenar → recalcular larguras a partir da ordem nova**, e a suíte guarda o
par. *(Guardas: `suite-container.mjs`, RSZ-01…RSZ-04.)*

---

## 52. Árvore — `estável` (F7.2, 2026-08-15) · AR1–AR8 · fecha a lacuna C16

> **Base (3 rodadas, 2026-08-15):** **R1/R3** — APG *Treeview* e a régua do APG para quando uma
> treeview com colunas é aceitável: (a) as colunas **cabem sem rolagem horizontal**, (b) as colunas
> são **apresentação**, não navegação, e (c) a linha inteira pode ser anunciada **como uma única
> string** sem perda. Fora dessas três condições, o arquétipo é **tabela**, não árvore ·
> APG *Roving tabindex* · Roselli (treegrid: composto, suporte pobre — descartado). **R2** — a
> árvore já existia no shell (§46) **sem spec própria**: era o item C16 do mapa de cobertura,
> marcado "parcial (vive no §46)".

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| AR1 | **`role="tree"` no contêiner, `role="treeitem"` em cada nó, `role="group"` no `<ul>` filho** | Semântica de árvore não existe em HTML; aqui a ARIA é o caminho certo, não remendo | `<details>` aninhado (não expõe nível nem posição) · lista com links (perde a hierarquia) |
| AR2 | **Roving tabindex: exatamente UM `treeitem` no tab sequence** | A árvore é widget composto — um tab-stop, navegação por setas. Vários tab-stops fazem o teclado atravessar dezenas de nós | `tabindex="0"` em todos (a sidebar vira armadilha de Tab) |
| AR3 | **Setas: ↑↓ movem, → expande (ou entra), ← recolhe (ou sobe ao pai)**; `Espaço`/`Enter` ativam | Padrão APG, e é o que o usuário de teclado já sabe de qualquer explorador de arquivos | Setas só movendo (expandir só com clique quebra o CP30) |
| AR4 | **`aria-expanded` SÓ nos nós que agrupam.** Folha com o atributo mente sobre a estrutura | O atributo em folha faz a AT anunciar um nó expansível que nunca expande | Pôr em todos "por simetria" |
| AR5 | **Contador junto ao rótulo (C26) no nó, com zero explícito**, em `text-muted` | Responde "quanto tem aqui" antes de expandir — numa hierarquia de 3–4 níveis, evita navegação exploratória | Contador só no nó agrupador (o item folha é onde a contagem decide o clique) |
| AR6 | **Item ativo tem TRÊS sinais**: fundo, peso e barra lateral de 3px em `text-brand` — nunca só cor | Regra dos três sinais do Bloco 1, herdada; o grayscale tem de sobreviver | Só fundo (some no grayscale e no alto contraste) |
| AR7 | **A régua árvore × tabela é normativa** (APG): se as colunas precisam ser navegadas, **é tabela agrupada (§50)** — não se usa árvore com colunas | É a diferença entre um widget que funciona e um treegrid que nenhuma AT lê bem | Árvore com colunas "porque parece uma tabela hierárquica" |
| AR8 | **Fronteiras:** seleção múltipla em árvore, arrastar nó e carregamento assíncrono de subárvore entram por demanda, com pesquisa própria | Cada um traz norma nova (2.5.7 para o arraste, live region para o assíncrono) | Especificar sem caso real |

*(Guardas: `suite-container.mjs`, ARV-01…ARV-03, nas duas telas.)*

---

## 53. Filtro composto — `estável` (F7.2, 2026-08-15) · FC1–FC6 · fecha a lacuna C21

> **Base (3 rodadas, 2026-08-15):** **R1/R2** — GitLab Pajamas (**3 a 5 dropdowns** é o teto do
> filtro simples; acima disso, *filter builder*) · a régua de mercado da **escalada progressiva**
> (filtros simples por padrão; o construtor só para quem precisa — forçar todo mundo pela gramática
> completa é o erro clássico) · Airtable/FT/Yahoo (sobreposição que cresce na vertical, uma
> condição por linha) · a estrutura que funciona é **árvore (AST) com indentação visual no lugar
> dos parênteses**, não CNF/DNF de dois níveis fixos. **R3** — `fieldset`/`legend` como agrupamento
> nativo de controles relacionados; grupo de rádios para operador exclusivo.

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| FC1 | **Dois regimes, com escalada explícita:** o regime SIMPLES é o §42 (chips + popover por dimensão, teto de 5 dimensões); o regime COMPOSTO só aparece quando o usuário pede | O construtor é a ferramenta certa para poucos e a errada para todos; e o §42 já resolve 90% dos casos com um clique | Substituir o §42 pelo construtor · esconder o construtor (quem precisa não acha) |
| FC2 | **Profundidade máxima 2** (grupo raiz + um subgrupo) | Além disso a condição deixa de ser legível como frase, e o custo de manter a AST na cabeça passa a ser do usuário. A gramática que resta é **texto** — fronteira de produto | Profundidade livre (o resumo em linguagem natural fica impossível) |
| FC3 | **Cada grupo é `fieldset` + `legend`; o operador é um `radiogroup`** com dois rádios (Todas = E · Qualquer = OU), e o operador aparece **uma vez por grupo**, nunca repetido entre linhas | O agrupamento fica exposto à AT sem ARIA; e "E" repetido em cada linha é o padrão que faz o usuário achar que pode misturar E e OU no mesmo nível | Operador por condição (produz expressão ambígua) · `select` de dois itens (mais cliques, menos visível) |
| FC4 | **A indentação visual substitui os parênteses**, com fio de 3px na cor da marca marcando o subgrupo | É como a estrutura é lida em qualquer editor de código; parênteses em UI viram erro de digitação | Mostrar a expressão com parênteses (transfere sintaxe ao usuário) |
| FC5 | **Resumo em linguagem natural com "N de M"**, sempre visível — inclusive com o painel recolhido | O contador é a prova de que o filtro fez algo (§42/TD1); e o resumo é o que permite conferir a lógica sem reabrir o construtor | Só o número · só a expressão (ilegível para quem não escreveu) |
| FC6 | **Fronteiras:** sintaxe textual de consulta, filtros salvos e filtro por coluna em texto livre = **produto**; campos de condição são de input restrito (select, data, número) | Herdado do TD5 — texto livre por dimensão produz filtro que ninguém consegue depurar | Query language no DS |

**Estado vazio próprio:** filtro composto que não casa nada tem vazio **com a saída dentro do
texto** — qual operador trocar, qual condição remover. Vazio genérico ("nenhum resultado") deixa o
usuário sem caminho de volta. *(Guardas: `suite-container.mjs`, FC-01…FC-03.)*

---

## 40-b. Tabela — EXTENSÃO F7.2 · DT9–DT12 *(`rascunho`; o §40 v0.43 segue `estável` no que não é tocado)*

> **Contexto.** O §40 fechou em 2026-08-06 com uma fronteira explícita no DT5: *"multi-sort é
> lacuna do ARIA #283, fora da v1"*. A F7.2 reabre exatamente essa fronteira, por decisão do
> Rafael em 2026-08-15 ("entra agora"), porque o gabarito A2 exige **agrupar por X e ordenar
> dentro por Y** — que já é ordenação de dois níveis na prática. As decisões abaixo **estendem**
> o §40; nenhuma o contradiz.

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| DT9 | **Ordenação multi-coluna com TETO DE TRÊS.** Gesto duplo: `Shift`+clique **e** item "adicionar à ordenação" no menu do cabeçalho (CP30: ponteiro único não é o mesmo que teclado) | TanStack expõe `maxMultiSortColCount` justamente porque o ilimitado é ingovernável; três é o que cabe num resumo legível | Ilimitado · só `Shift`+clique (modificador não é caminho de ponteiro único) |
| DT10 | **A ORDEM é dita por NÚMERO ao lado da seta** (1, 2, 3), e o estado completo é anunciado em **live region**. `aria-sort` continua em cada `th`, e a lacuna fica **declarada**: ele diz a direção, não a posição (ARIA #283) | Sem o número, a única pista da ordem é a posição do cabeçalho na tela — que muda com reorder de coluna (§51) e não existe para quem não vê | Ordem só pela posição visual · inventar `aria-sort="ascending 2"` (valor inválido) |
| DT11 | **FONTE ÚNICA de ordenação:** o cabeçalho e o painel de ordenação editam o MESMO estado | Fato medido no Fiori: *"usar a ordenação do cabeçalho substitui TODAS as opções de ordenação do diálogo"* — duas fontes de verdade, e o usuário perde a configuração sem aviso | Diálogo e cabeçalho independentes (o defeito medido) |
| DT12 | **Numeração de linha (C22) é OPT-IN, POSICIONAL e nunca endereço.** O cabeçalho da coluna existe e é **oculto só visualmente** (a célula permanece na matriz — célula `aria-hidden` quebra a tabela); o endereço estável é o identificador do registro. **`aria-rowindex`/`aria-rowcount` NÃO entram**: são de tabela **paginada virtual**, e usá-los para reposicionar linhas é uso errado documentado | Medição do produto de referência: numeração a **3,32** de contraste (reprova) e endereço instável — ao ordenar, "linha 14" vira outra coisa, o que é **enganoso em ambiente colaborativo**. Nossa numeração usa `text-muted` (**5,61**/**6,80**) | Numeração sempre ligada · numeração como referência compartilhada · `aria-rowindex` "para dar posição" (Roselli: em teste com usuários, o salto de numeração **confundiu**) |

**Exceção bidimensional de reflow, declarada aqui (CP28):** a tabela pode rolar nos dois eixos
dentro do wrapper `role="region"` focável e nomeado do DT1. **Alternativa de leitura declarada:**
a degradação por importância de coluna (LD3) mais o identificador estável em cada linha.

---

## 41-b. Seleção — EXTENSÃO F7.2 · SL6–SL8 *(`rascunho`)*

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| SL6 | **Seleção em TRÊS níveis: grupo → página → conjunto.** O checkbox do cabeçalho de grupo abrange **o grupo**; o do cabeçalho da tabela abrange **a página**; o conjunto só pela oferta explícita "Selecionar todas as N". Cada nível tem `indeterminate` real | O SL2 já proibia o header marcar o dataset em silêncio; o grupo é o nível que faltava, e é o que o agrupamento do §50 torna necessário. Precedente medido: seleção pelo cabeçalho de grupo é o padrão em ERP de logística | Checkbox de grupo marcando a página · nível de grupo sem indeterminate (o estado do meio some) |
| SL7 | **Variante FLUTUANTE da barra de ações em massa (C32), nomeada e restrita:** só onde **não há toolbar** para substituir (SL3 segue sendo o caso base). A região **reserva espaço no rodapé** para que a barra nunca cubra a última linha | Duas barras simultâneas é o que o SL3 evitou; mas em tela sem toolbar a barra precisa de lugar. O defeito oposto está documentado no mercado: barra de massa que **não aparece no mobile** (Polaris #4156) — vira guarda nossa | Barra flutuante como padrão · barra fixa no topo (some no scroll de lista longa) |
| SL8 | **Ações promovidas:** no máximo ~4 visíveis, **destrutiva por último**, `Cancelar seleção` sempre presente; o resto vai para overflow | Herda o SL3 e o padrão de *promoted bulk actions*; sem saída explícita o usuário fica preso no modo de seleção | Todas as ações visíveis · sem cancelar |

---

## 42-b. Toolbar de dados — EXTENSÃO F7.2 · TD6 *(`rascunho`)*

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| TD6 | **A régua declara `container-name: regua` e reempilha por CONTAINER QUERY, não por media query** | Medição do produto de referência: a barra de ferramentas de visualização tem **sete pontos de quebra do próprio container**. Uma toolbar que consulta a janela quebra ao ser posta numa gaveta de 300px — e **nenhuma suíte que redimensiona a janela vê isso** (CP27/CP28) | Media query na toolbar (verde-e-errado dentro de gaveta) |

---

## 43-b. Degradação responsiva — EXTENSÃO F7.2 · LD6 *(`rascunho`)*

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| LD6 | **A importância de coluna do LD3 é executada por CONTAINER QUERY** (`conteudo`): a coluna `low` some quando a REGIÃO encolhe. O colapso da TELA (rail, miolo, alvos ≥44px) continua em media query | É a fronteira normativa do CP27/CP28 aplicada ao dado: densidade é do contêiner, reflow é do viewport (SC 1.4.10 é medido contra o viewport) | Tudo em media query (a mesma tabela num painel estreito não degrada) · tudo em container query (perde-se a prova de 1.4.10) |

---

---

## 54. Botão dividido — `estável` (F7.3, 2026-08-16) · BD1–BD9 + MN9 · fecha a dívida A-P1

> **Como esta seção nasceu tarde, e por que isso está escrito.** O botão dividido foi produzido em
> 2026-08-15 como artefato — os códigos `BD1–BD6` e `MN9` viviam **só** no comentário de CSS do
> `banco-navegacao.html`, e nenhum arquivo canônico os mencionava. Foi o inverso exato da ordem que
> este projeto pratica desde o marco v1.2: **decisão escrita → artefato produzido**. A leitura de
> volta da edição v8.4 do MANIFESTO mediu a lacuna (zero ocorrências de `BD1` em qualquer `.md`) e
> abriu a dívida **A-P1**. Ela fecha aqui, e fecha pelo caminho certo: **rito completo de três
> rodadas**, e não transcrição do comentário — reconstruir spec a partir do artefato é reconstrução
> de memória, que este projeto já pagou caro na v7.0. Decisão do Rafael em 2026-08-16: *"pode
> resolver logo de uma vez, vamos evitar jogar as coisas pra frente."*
>
> **Base (3 rodadas encadeadas, 2026-08-16).**
> **R1 canon — o que o padrão É.** A definição é estável e antiga: um botão com dois componentes,
> rótulo e seta, em que clicar no rótulo dispara a ação padrão e clicar na seta abre a lista das
> demais; é um híbrido entre botão e menu, que agrupa comandos relacionados e ainda oferece acesso
> de um clique à escolha padrão. A NN/g é explícita sobre o requisito visual: **a seta que sinaliza
> o menu precisa estar claramente separada do rótulo** — sem essa separação o padrão não é
> reconhecido. Vários sistemas convergem em três regras de uso: as ações do menu têm de pertencer
> **ao mesmo fluxo** da ação padrão; a variante **destrutiva não existe** para o botão dividido, e
> uma ação destrutiva, se houver, aparece **uma só vez e sempre por último** no menu; e quando não
> há ação dominante, o certo é um botão de menu simples, não um dividido — *sem ação dominante não
> há padrão que valha proteger*.
> **R2 mercado/stack — o que o padrão FAZ no teclado.** O comportamento é o do menu button do
> WAI-ARIA, e é o mesmo em React Aria, Radix e nos sistemas de referência: o gatilho leva
> `aria-haspopup`, `aria-expanded` e `aria-controls`; **Seta ↓, Espaço ou Enter abrem o menu e
> movem o foco para o PRIMEIRO item; Seta ↑ abre e vai para o ÚLTIMO**; dentro do menu as setas
> circulam, Home e End vão às pontas, letras navegam por inicial; **Esc fecha e devolve o foco ao
> gatilho**. Com o menu aberto, o gatilho não recebe foco enquanto o usuário percorre os itens.
> **R3 normas e fora do circuito — onde o padrão FALHA.** A crítica prática é dura e entrou como
> contrato: **o botão dividido não é recomendado para toque**, pelo problema do dedo grosso, e
> mesmo no desktop **as duas zonas precisam atender ao tamanho mínimo de alvo**. E há uma
> exigência de aprendizado: o padrão só funciona em aplicação de uso repetido; em fluxo de uma vez
> só, o usuário não percebe o menu secundário.

### 54.1 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **BD1** | **DOIS BOTÕES NATIVOS lado a lado**, dentro de um contêiner que dá a aparência de peça única | Um botão só com duas zonas de clique. *Um clique que faz coisas diferentes conforme ONDE caiu é o oposto de um alvo previsível*, e o leitor de tela não teria como anunciar as duas ações — há um nome acessível por elemento |
| **BD2** | **O fio separador é `border-left` do segundo botão**, nunca pseudo-elemento nem sombra | `background` e `box-shadow` são **descartados em `forced-colors`**, e ali o fio sumiria — junto com a única pista de que existem duas ações. Borda sobrevive |
| **BD3** | **O gatilho tem `min-width:44px`**: é alvo próprio, não apêndice do botão principal | Largura por conteúdo. Com 36px ele mediu **37 × 44** e reprovou o R4 da `suite-render`. *O eixo curto de um alvo pode ser qualquer um dos dois* — lição que o stepper já tinha dado na mesma fase, e que se repetiu quatro vezes |
| **BD4** | **O gatilho é menu button**: `aria-haspopup="menu"`, `aria-expanded` alternando, `aria-controls` apontando para o menu | `aria-expanded` sozinho: sem `aria-haspopup` a tecnologia assistiva não identifica o elemento como abridor de menu |
| **BD5** | **Teclado do WAI-ARIA, inteiro:** ↓/Espaço/Enter abrem e focam o **primeiro** item; ↑ abre e foca o **último**; setas circulam; Home/End vão às pontas; **Esc fecha e devolve o foco ao gatilho** | Implementar só o clique. Metade do padrão de teclado é pior que nenhum: cria expectativa e frustra |
| **BD6** | **A ação do corpo é a MAIS FREQUENTE do conjunto**, e as do menu pertencem ao mesmo fluxo | Usar o dividido quando não há ação dominante — aí o certo é botão de menu simples. Sem dominante não há padrão a proteger |
| **BD7** | **O gatilho NUNCA executa ação** — ele só abre e fecha o menu | Gatilho que dispara a ação padrão *e* abre o menu: torna o resultado do clique imprevisível |
| **BD8** | **Sem variante destrutiva.** Uma ação destrutiva, se existir, aparece **uma vez** no menu e **por último** | Agrupar duas ações destrutivas atrás de um gatilho de 44px |
| **BD9** | **Em contexto de TOQUE e no degrau estreito, o botão dividido COLAPSA**: vira botão de menu único, com a ação padrão como primeiro item | Manter as duas zonas no toque. A norma do alvo continua cumprida, mas duas zonas coladas de 44px convidam ao erro — e o padrão pressupõe uso repetido, que o toque casual não tem |
| **MN9** | **Cabeçalho de seção do menu com ação secundária à direita.** O rótulo nomeia o grupo por `aria-labelledby`; a ação é `<button>` de verdade e fica **FORA da varredura de `menuitem`** | Tratar a ação como opção da lista. Ela não é uma escolha do menu — é atalho para **gerenciar** a lista, e virar `menuitem` faria a contagem de opções mentir |

### 54.2 Anatomia

```
┌──────────────────────┬─────┐
│  Criar ordem         │  ▾  │   ← BD1: dois <button> reais
└──────────────────────┴─────┘
   ação padrão (BD6)     │ └ min-width 44px (BD3)
                         └── border-left 1px (BD2)
```

A **anatomia com BORDA** foi decidida no gate de 2026-08-15: a proposta alternativa (botão sem
contorno) foi medida, apresentada em amostra A/B e **reprovada**. Verbatim do Rafael: *"esse da
foto que mandei está melhor"*. **Não reabrir sem pedido dele.**

### 54.3 Estados e tokens

| Estado | Tratamento |
|---|---|
| Repouso | fundo `action-primary`, tinta `text-on-brand`, raio 8 nas pontas externas |
| Hover | escurecimento por sobreposição, aplicado a **cada metade em separado** — o hover diz qual metade receberá o clique |
| Foco | anel `border-focus` com `outline-offset:2px` e `z-index:1`, para o anel não ser cortado pela metade vizinha |
| Menu aberto | `aria-expanded="true"`, e o *caret* gira 180° |
| Desabilitado | `aria-disabled` nas duas metades; nunca só numa |

### 54.4 Validação

| Guarda | O que prova |
|---|---|
| `suite-navegacao.mjs` | presença estrutural, ARIA do gatilho e a regra do MN9 fora da varredura de `menuitem` |
| `render-*` R4 | as duas metades com **44px nos dois eixos** — foi esta guarda que reprovou o gatilho a 37 × 44 |
| Escala de cinza do preview | o fio sobrevive sem cor; é ele que diz que há duas ações |

### 54.5 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **BD-P1** | O colapso do BD9 está escrito e **não tem gabarito** | Nenhuma tela do projeto usa botão dividido ainda; o colapso nasce junto com o primeiro uso real, medido |
| **BD-P2** | Navegação por **letra inicial** dentro do menu (parte do BD5) não é exercitada na bancada | A bancada tem três itens curtos; a navegação por inicial só é observável com lista maior |

---

## 55. Chat — `estável` (F7.3, 2026-08-15) · CH1–CH14 · arquétipo A23

> **Contexto para leitor novo.** O A23 (chat/mensageria) estava marcado como **AUSENTE** no
> `mapa-cobertura-ds.md` e **DENTRO do escopo** pela decisão D5 do Rafael de 2026-08-15: *"vamos
> ter construção de app de chat, mesmo que não fique dentro do ERP (…) estamos criando DS para
> suprir templates, blocos que padronizam o uso para qualquer tipo de criação"*. Esta é a
> **primeira spec do projeto escrita a partir de LEITURA VISUAL** — os dispositivos C41–C49 da
> §13 do `estudo-clickup-completo.md`, observados navegando no produto de referência — e não de
> extração de valores. A distinção importa: onde o número for necessário, ele será medido na
> produção do gabarito, e a spec diz apenas a relação.
>
> **Base normativa (rodada de 2026-08-15):** ARIA23 do W3C (`role="log"` para atualização
> sequencial: `aria-live` polite implícito, `aria-atomic` false, região nomeada por
> `aria-labelledby`, e o campo de entrada relacionado por `aria-controls`) · MDN log role
> (`aria-atomic="true"` só quando o usuário precisa ouvir a região inteira a cada mudança) ·
> Sara Soueidan (*live region é último recurso, não primeiro* — onde houver alternativa
> estrutural, ela vence).

### 55.1 O bloco

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **CH1** | O chat tem **duas formas declaradas**: **PAINEL** (peça lateral que encosta e recolhe, sobre a tela viva) e **TELA** (arquétipo próprio, ocupando a região de conteúdo). A mesma conversa cabe nas duas | Observado nas duas formas no produto de referência, com o mesmo conteúdo. Painel serve "perguntar sobre isto"; tela serve "trabalhar aqui dentro" | Só modal (perde o contexto de origem) · só tela (obriga a sair do trabalho para perguntar) |
| **CH2** | Na forma PAINEL, o fechamento é **recolhimento lateral** (`»`), nunca `×`, e o painel volta ao mesmo estado | O gesto promete volta; `×` promete descarte. Painel que preserva conversa não pode usar o símbolo de fechar | `×` (mente sobre o que acontece) |
| **CH3** | O painel é **peça** no vocabulário do CP2 — separa-se do canvas pelo par sombra+anel (CP17) e não empurra o conteúdo: **sobrepõe-se a ele** | É peça lateral, não coluna do shell; se empurrasse, mudaria o layout da tela de trabalho a cada abertura | Coluna do miolo (reflui a tela toda) |

### 55.2 O histórico

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **CH4** | A área de mensagens é **`role="log"`**, nomeada por `aria-labelledby`, com `aria-atomic="false"` | ARIA23: é o papel desenhado para atualização sequencial em que o novo entra no fim. O `aria-live` polite é implícito — declarar `assertive` interromperia a leitura a cada mensagem | `aria-live="assertive"` (interrompe) · `role="feed"` (é para conteúdo infinito paginado, não para conversa) · nenhuma live region (a mensagem chega em silêncio) |
| **CH5** | **Mensagem própria e alheia diferem por ALINHAMENTO E SUPERFÍCIE, nunca só por cor** | Regra dos três sinais, herdada: o grayscale tem de sobreviver. E quem usa leitor de tela precisa do autor no texto, não na tinta | Só cor de balão |
| **CH6** | Cada mensagem declara **autor e hora no texto acessível**, ainda que a hora seja visualmente discreta e o autor seja omitido em mensagens consecutivas do mesmo autor | Agrupar visualmente é bom; agrupar no nome acessível apaga a autoria da terceira mensagem em diante | Autor só no primeiro balão do grupo, inclusive para AT |
| **CH7** | **Estado de envio é do sistema, não decoração**: enviando · enviada · falhou, com a falha trazendo ação de repetir. Chip na taxonomia do §22 | Mensagem que falhou e parece enviada é perda de informação silenciosa | Ícone sem rótulo · falha sem ação |

### 55.3 O composer — o achado que obriga spec própria

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **CH8** | **O composer é um CONTÊINER, não um campo.** Ele tem borda, raio maior que o dos demais controles, e hospeda dentro de si: o chip de escopo, a área de digitação e um rodapé de ações em três zonas (entrada à esquerda, modelo/modo à direita, enviar) | Observado (C43/C46): o `input` é uma parte do componente, não o componente. O nosso **§8 Campo** assume `input` com rótulo e mensagem e **não cabe aqui** — daí a spec própria | Tratar como variante do §8 (o rodapé não teria onde morar) |
| **CH9** | **O foco pinta o CONTÊINER inteiro**, não a linha de digitação | Se só a linha acender, o usuário de teclado não sabe que as ações do rodapé fazem parte do mesmo alvo | Anel só no `textarea` |
| **CH10** | **O escopo da conversa é um CHIP DENTRO do composer**, visível e removível — nunca implícito | Observado (C47). "O assistente provavelmente está olhando minha lista" e "está olhando ESTA lista, e eu posso tirar" são estados diferentes, e só o segundo é verificável pelo usuário | Escopo no cabeçalho (some ao rolar) · escopo implícito |
| **CH11** | O campo relaciona-se ao histórico por **`aria-controls`**, e o envio é por **`Enter`**, com `Shift+Enter` para quebra de linha — as duas teclas anunciadas na descrição do campo | ARIA23 recomenda a relação explícita; e a convenção de Enter/Shift+Enter é forte demais para ser reinventada, mas fraca demais para ser adivinhada por quem nunca usou | Enter como quebra e botão como único envio |

### 55.4 O estado vazio e as sugestões

| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| **CH12** | **O vazio do chat traz identidade, modo e sugestões** — não é um "nenhuma mensagem". As sugestões **citam o objeto de contexto pelo nome** | Observado (C46): é a prova, antes da primeira mensagem, de que o assistente sabe onde está. Barato de fazer, caro de omitir | Vazio genérico · sugestões descontextualizadas |
| **CH13** | **A sugestão muda de FORMA conforme o bloco que a hospeda**: cartão na TELA, linha no PAINEL. O conteúdo é o mesmo | Observado (C44/C45), e é a leitura mais útil da rodada: cartão pede largura, linha sobrevive em 400px. É o CP27 aparecendo como troca de componente, não só de espaçamento | Cartão nos dois (quebra no painel) · linha nos dois (desperdiça a tela) |
| **CH14** | **Fronteiras declaradas:** anexo, gravação de áudio, menção a pessoa, thread e reação **não entram nesta versão**. Cada um traz norma própria (upload, mídia com controle, `aria-activedescendant` no menu de menção) e entra com pesquisa dedicada | Especificar sem caso real foi o que produziu, em outras seções, spec que ninguém consome | Prever tudo agora |

**O que esta spec NÃO decidiu de saída, e por quê:** nenhum valor numérico foi escrito antes da
produção. A §13 do estudo é leitura de COMPOSIÇÃO, e tratar estimativa visual como medida seria
repetir, ao contrário, o erro que originou aquela seção.

### 55.5 Números — MEDIDOS na produção do gabarito (2026-08-15)

Todos extraídos por render do `tela-chat.html`, não estimados:

| O quê | Medido | Consequência |
|---|---:|---|
| Largura do painel | **420px** | acima de 380px o balão respira; abaixo, a mensagem de três linhas vira cinco |
| O painel SOBREPÕE | **provado** | a largura da tabela atrás é idêntica com o painel aberto e recolhido — o CH3 deixa de ser promessa |
| Raio do composer | **12px** (`radius-6`) | maior que o do balão (8px) e que o do botão (6px): o contêiner tem o raio mais alto da tela, como manda o CH8 |
| Borda do composer | **1px** | a borda pertence ao contêiner, não ao campo interno |
| Raio do balão | **8px** (`radius-4`), com o canto do lado do autor em **2px** | o canto "mordido" marca a origem sem usar cor |
| Raio do chip de escopo | **pílula** | escopo é estado, não controle de contorno |
| Altura do composer | **144px** com escopo e rodapé | é o custo real de o composer ser contêiner, e ele precisa caber no painel de 420px |
| `role` do histórico | **`log`**, `aria-atomic="false"`, sem `aria-live` explícito | o polite é implícito do papel — declará-lo seria redundância que atrapalha auditoria |
| `aria-controls` do campo | **aponta para o log** | técnica ARIA23 cumprida |
| **CH13, a troca de forma** | `display` vai de **`block` para `grid`** e a altura do item de **38px para 86px** quando o contêiner passa de 560px | a sugestão troca de COMPONENTE, não de espaçamento — é o CP27 no seu caso mais forte |

**Errata prevista:** estes números entram como `rascunho` junto com a spec e só se tornam contrato
no gate visual. Se o gate mudar a largura do painel, a tabela acima é reescrita por supersede
formal — não por edição silenciosa.

---


> **PROMOÇÃO DO §56 — 2026-08-16, segunda rodada de gate.** O primeiro gate desta seção foi feito
> sobre o arquivo ERRADO: as versões v0.1 e v0.2 do `tela-detalhe.html` traziam o mesmo selo, e o
> Rafael abriu a antiga — onde os comentários eram seção estática, sem ação. Corrigido o selo
> (v0.2) e refeito o gate, o veredito foi **"tudo ok, continue"**. A lição virou regra permanente,
> registrada na v8.9 do MANIFESTO: *mudança de conteúdo do artefato sobe a versão do preview — o
> MD5 confere identidade, o selo comunica identidade.*

## 56. Atividade e comentários — `estável` (F7.3, 2026-08-16) · CM1–CM12 · integrado ao gabarito A8

> **A fronteira que esta seção existia para resolver — e ela caiu do lado fácil.** A F7.3 abriu com
> uma dúvida estrutural registrada: o dispositivo **C85** do `estudo-clickup-completo.md` descrevia
> o comentário como *cartão dentro do cartão do objeto*, o que é **peça em peça** e o **CP2 proíbe
> no caso base**. A saída seria escrever uma exceção (`CP2-c`) ou rebaixar o comentário a linha.
> A rodada zero barrou a produção: **um dispositivo observado não é leitura do arquétipo**, ainda
> mais quando esse dispositivo É a fronteira em disputa.
>
> A navegação real aconteceu em **2026-08-16**, autorizada pelo Rafael, em modo somente leitura. E
> a leitura desfez a premissa: **o comentário NÃO é cartão.** É bloco com avatar, autoria, texto e
> rodapé — **sem borda, sem sombra, sem raio** —, dentro de um painel que é a peça. Medido no nosso
> gabarito depois de implementado: `border 0px · box-shadow none · border-radius 0px`.
> **Não há peça dentro de peça, e o CP2 não precisa de exceção. A `CP2-c` fica DESCARTADA.**
>
> **Base.** **Rodada zero:** navegação real (4 telas, sessão logada da SEED, somente leitura, sem
> reprodução de dado de cliente). **R1/R2/R3:** herdadas do §49 no que se aplica — disclosure
> (`aria-expanded` + `aria-controls`), revelação por `opacity` e nunca `visibility` (Spectrum,
> nov/2025), e o padrão de menu button do WAI-ARIA para o botão dividido do composer.

### 56.1 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **CM1** | **O comentário é BLOCO, não cartão**: avatar, autoria, horário, texto, rodapé — sem borda, sem sombra, sem raio | Cartão. Ele criaria peça dentro de peça, que o CP2 proíbe, para separar coisas que o espaço já separa |
| **CM2** | **DOIS PESOS no mesmo fluxo, e são duas anatomias declaradas.** *Comentário*: bloco com avatar e ações. *Evento de sistema*: **linha** compacta, com marcador, sem avatar e **sem ações** — ele registra, não conversa | Uma anatomia só, com variação de estilo. Evento com botão de responder convida a conversar com o log |
| **CM3** | **A atividade é painel IRMÃO do corpo**, com rolagem própria | Ser mais uma seção do corpo rolável: a conversa some quando o registro é longo, e é justamente nela que se decide o que fazer |
| **CM4** | **A atividade acompanha o registro corrente** e é substituída junto com ele ao navegar entre irmãos (PD3) | Painel de atividade global |
| **CM5** | **No hover e no `:focus-within`, o cabeçalho TROCA o metadado pela ação**: as ações de gestão entram **no lugar** do horário | Empilhar ações ao lado do horário — o cabeçalho do comentário é estreito, e disputar espaço ali faz a autoria quebrar em duas linhas. **Dispositivo distinto do PD9**, onde as ações são reveladas sem tirar nada do lugar |
| **CM6** | **Menção é TEXTO em cor de link, inline** | Chip. Chip é a taxonomia de status do §22, e usar a mesma forma para pessoa faria a régua mentir |
| **CM7** | **Sequência de eventos colapsa atrás de um disclosure**, com `aria-expanded` + `aria-controls`, e o rótulo diz **quantos** itens aparecem | Mostrar tudo. Uma sequência de eventos automáticos empurra a conversa para fora da tela |
| **CM8** | **Ações permanentes no rodapé** (curtir, reagir, Responder) e **ações de gestão no cabeçalho, reveladas**. Duas zonas, dois propósitos | Zona única: reagir é frequente e precisa estar sempre visível; atribuir e excluir não |
| **CM9** | **O composer é FIXO no rodapé do painel** | Composer no fim da lista: escrever passaria a depender de rolar até o fim de algo que cresce |
| **CM10** | **O envio é sempre EXPLÍCITO**, por botão dividido (§54): ação padrão "Comentar" e variantes no menu | Enter envia. Num campo onde se escreve parágrafo técnico, Enter enviando é perda de trabalho |
| **CM11** | **Filtro "só comentários"** esconde os eventos; **o contador do cabeçalho conta COMENTÁRIOS e não muda ao filtrar** | Contador de itens visíveis: um número que muda quando se filtra deixa de ser referência |
| **CM12** | **A autoria nunca quebra em duas linhas**; quem cede por falta de largura é o **horário** (LD7) | Deixar o nome quebrar. Sem saber **quem** disse, o comentário não serve; sem o minuto exato, serve |

### 56.2 Números MEDIDOS na produção

| Medida | Valor |
|---|---:|
| Largura do painel de atividade (regime modal) | **340px** |
| Limiar em que a atividade empilha abaixo do corpo (container query `peca`) | **760px** |
| Altura máxima do painel empilhado | **340px** |
| Alvo das ações de gestão do comentário | **44 × 32px** |
| Alvo do filtro, do disclosure e do botão dividido | **44px** no eixo curto |
| Pares de cor medidos (com §54) | **42 pares × 2 temas = 84 · 0** |

### 56.3 Acessibilidade

- O painel é `<section>` nomeada por `aria-labelledby`; a lista é `<ol>` — **a ordem importa**.
- **Nada depende de hover**: `:focus-within` revela as mesmas ações, e o alvo tem 44px de largura.
- O disclosure de sequência e o filtro declaram estado (`aria-expanded`, `aria-pressed`) e o filtro
  **anuncia** a mudança em live region.
- O **botão dividido** do composer implementa o teclado do WAI-ARIA por inteiro: ↓ abre no primeiro
  item, ↑ no último, setas circulam, Home/End vão às pontas, **Esc fecha e devolve o foco ao
  gatilho**; e o **gatilho nunca executa a ação padrão** (BD7).
- **Contraste:** um achado real. O marcador do evento de sistema nasceu com `border-default`, que
  mede **1,49** sobre branco e **2,32** no escuro — reprova o SC 1.4.11 nos dois temas. Daria para
  declará-lo decorativo (um bullet não porta informação), mas seria conveniência: ele é a única
  pista de que ali começa um item, e **pista que some no monitor ruim não é pista**. Recebeu a mesma
  tinta do texto que marca (`text-muted`) e passa com folga.

### 56.4 Validação

| Camada | Placar |
|---|---|
| `suite-detalhe.mjs` — blocos **CM** (11 guardas) e **BD** (8 guardas) | parte dos **73 · 0** |
| `suite-composicao.mjs` (render, 2 temas) | **33 · 0** |
| `suite-container.mjs` — blocos CQ e RF | **13 · 0** |
| `contraste-f73.py` — 42 pares × 2 temas | **84 · 0** |

### 56.5 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **CM-P1** | **Thread de respostas** não tem leitura: nenhum comentário do ambiente visitado tinha resposta encadeada, então não há contador de respostas nem anatomia de tópico observados | Escrever de dedução repetiria o erro que a rodada zero existe para impedir. Nasce quando houver caso real para navegar |
| **CM-P2** | **Anexo dentro do comentário** foi visto no fluxo (evento "anexou arquivo") mas não como conteúdo do comentário | Idem |
| **CM-P3** | **Comentário resolvido / atribuído** aparece no menu de variantes do composer e não tem estado visual especificado | Depende de decidir se resolver **oculta** o tópico ou só o marca — decisão de produto, não de DS |

---


> **PROMOÇÃO DE §57 E §58 A `estável` — 2026-08-16.** Veredito do Rafael sobre os dois gabaritos,
> depois do conserto dos dois defeitos que ele achou no A5: **"tudo ok, pode prosseguir"**. Com isso
> a **F7.4 fica com dois dos três arquétipos entregues e promovidos**; o terceiro, **A15 carga de
> trabalho, está BLOQUEADO por ausência de instância** no ambiente de referência — o workspace não
> tem visualização de carga criada, e criar uma seria ação de escrita, proibida no modo somente
> leitura. Registrado na §14.1 do `estudo-clickup-completo.md`.

## 57. Quadro / Kanban — `estável` (F7.4, 2026-08-16) · QD1–QD14 · gabarito A7

> **Base.** **Rodada zero:** o arquétipo TEM leitura visual — §13.5 do `estudo-clickup-completo.md`,
> dispositivos **C57–C59** —, somada às medições de valor da §2.3 e ao contrato de arquétipo já
> escrito no `seed-composicao.md` (CP2, tipo **trilha**).
> **R1 canon:** o quadro é colunas de etapa, cartões de item e **teto de trabalho em curso** (WIP) —
> o limite máximo de cartões por coluna, cuja função é forçar a terminar antes de começar. Colunas
> devem espelhar o fluxo real, e não um "A fazer / Fazendo / Feito" genérico, senão o gargalo não
> aparece.
> **R2 stack:** não há Kanban no shadcn/ui base; o padrão de mercado é o **dnd-kit**, cujo sensor de
> teclado move cartão por setas e confirma com Espaço ou Enter. Os componentes de 2026 tratam quatro
> superfícies: estrutura de colunas, cartão arrastável com reordenar e mover entre colunas,
> primitivos de acessibilidade (teclado e anúncio para leitor de tela) e o shell em volta.
> **R3 normas:** o **SC 2.5.7 Dragging Movements** (AA, WCAG 2.2) exige que toda função operada por
> arraste seja alcançável por **ponteiro único sem arrastar** — e a norma é explícita em que isso
> **não é a mesma coisa que teclado**, coberto em separado pelo SC 2.1.1, *porque quem usa tela de
> toque pode não ter teclado físico*. A técnica **G219** do W3C foi escrita com este exemplo
> literal: mover uma tarefa num quadro. O padrão recomendado é "mover para…" em menu, ou
> tocar-e-colocar.

### 57.1 A decisão de fronteira — e ela contraria o produto de referência

O **C57** observou que lá **a coluna tem fundo tingido** na cor do status, e o próprio estudo
registrou a divergência com a §2.3 (que dizia "transparente"), deixando a escolha para o gate.

**Mantemos a lei do CP2: a trilha é agrupador TRANSPARENTE** — sem fundo, sem raio, sem separação.

O motivo não é apego ao texto. Com quatro a seis colunas tingidas, a tela ganha **seis superfícies
cromáticas competindo com os cartões**, que são o que carrega o dado — e o **CP19** diz que *o croma
vem das peças*. A identidade da coluna resolve-se onde a própria leitura já a resolvia: no
**cabeçalho**, com a pílula saturada de status e o contador fora dela.

*Alternativa descartada com o porquê, não por omissão. Se o gate quiser o tingido, a mudança é de
uma linha no molde — mas ela reabre o CP19, e isso precisa ser dito antes, não depois.*

### 57.2 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **QD1** | **A coluna é TRILHA (CP2)**: sem fundo, sem raio, sem sombra, sem borda | Coluna como peça. Peça é o que **se separa do canvas**, e a coluna não se separa de nada — chamá-la de peça esvaziaria a definição |
| **QD2** | **A identidade da etapa vive no cabeçalho**: pílula saturada de status + contador **fora** dela | Contador dentro da pílula. Isso obrigaria a medir contraste de número sobre seis fundos diferentes, e o número passaria a herdar a cor do status — que não é informação dele |
| **QD3** | **O cartão é PEÇA**: par sombra+anel (CP17) e `radius-lg` | Cartão sem separação: sem ele, a trilha vira uma lista de textos soltos sobre o canvas |
| **QD4** | **Metadado do cartão em LINHAS, cada uma com ícone + nome acessível + valor** | Rodapé horizontal (C58). Ele comporta três metadados; um quadro de engenharia carrega responsável, prazo, potência e campo de domínio, e acima de três o rodapé quebra ou trunca |
| **QD5** | **Teto de trabalho em curso (WIP) por etapa.** Excedido, o contador muda de tinta **e** ganha ícone com texto acessível | Sinal só por cor. E descarta não ter teto: sem ele o quadro mostra o trabalho, mas não mostra o **gargalo**, que é a razão de o quadro existir |
| **QD6** | **Mover sem arrastar é o caminho PRINCIPAL**: menu "Mover para…" em todo cartão, sempre presente | Arraste como caminho único — reprova o SC 2.5.7. E descarta tratar o teclado como se fosse a alternativa: a norma pede **os dois**, porque quem usa toque pode não ter teclado |
| **QD7** | **Teclado completo:** Espaço pega, setas escolhem a etapa, Enter solta, Esc cancela — **e cada passo é anunciado** em live region | Mover sem anúncio. Movimento que só se vê não existe para quem não vê |
| **QD8** | **A rolagem horizontal do quadro é exceção bidimensional DECLARADA** ao SC 1.4.10: região focável, nomeada, com encaixe | Rolagem no documento inteiro. A exceção da norma vale para o conteúdo que **exige** duas dimensões, e vale para a região — não para a página |
| **QD9** | **Largura de trilha FIXA** | Coluna elástica: a largura mudaria conforme o número de etapas, e o mesmo cartão teria tamanho diferente em cada quadro |
| **QD10** | **Dentro do cartão não nasce outro cartão**: o seletor de campo do domínio é **linha com fronteira** | Peça dentro de peça (CP2) |
| **QD11** | **Alvo de soltura em borda TRACEJADA, nunca cor saturada** (CP30) | Realce cromático: ele desaparece na escala de cinza, e o preview tem o controle que prova isso |
| **QD12** | **A coluna vazia NÃO colapsa** e mostra o convite (C59) | Colapsar. A coluna vazia é **lugar de pôr coisa** — o vazio aqui é convite, não erro (§25, caso C) — e colapsar faria o quadro mudar de forma a cada movimento |
| **QD13** | **No degrau estreito, uma trilha por vez, com encaixe** — densidade por container query (CP27) | Miniaturizar as colunas: quatro colunas de 80px não são um quadro |
| **QD14** | **Abrir o cartão leva ao detalhe (A8)**; o quadro não edita o registro | Editar tudo no cartão. O cartão é a face do objeto, não o objeto |

### 57.3 Números MEDIDOS na produção

| Medida | Valor |
|---|---:|
| Largura da trilha (fixa) | **288px** |
| Calha entre trilhas · entre cartões | **12px** · **8px** |
| Alvo do gatilho "Mover para" | **44 × 32px** |
| Alvo do convite da coluna vazia e do "+" da etapa | **44px** no eixo curto |
| Raio do cartão · da trilha | **12px** · **0px** |
| Transbordo horizontal do documento | **0px** (quem rola é a região declarada) |
| Pares de cor medidos (F7.3 + F7.4) | **54 pares × 2 temas = 108 · 0** |

### 57.4 Validação

| Camada | Placar |
|---|---|
| `suite-quadro.mjs` — blocos TRI · CAR · WIP · PTR · TEC · REG · VAZ · VIV | **35 · 0** |
| `suite-composicao.mjs` (render, 2 temas) | **32 · 0** |
| `contraste-f73.py` — 54 pares × 2 temas | **108 · 0** |
| Console e erros de página | limpo |

> **Nota sobre a `suite-composicao.mjs`:** a guarda `MONO-03` (coluna de valor à direita e em
> monoespaçada, contrato PN21d) passou a ser **condicional à existência de tabela**, e isso está
> **impresso** na saída, não silenciado. A distinção importa e não é a mesma coisa que "pular quando
> o elemento falta": quando **há** tabela, a coluna de valor tem de existir e obedecer; quando não
> há tabela, o contrato não se aplica. O quadro empilha cartões, não linhas — não há coluna a
> alinhar. Por isso o gabarito A7 fecha em **32**, e não em 33.

### 57.5 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **QD-P1** | **Arraste com ponteiro** (o 1º caminho do CP30) não está implementado no gabarito | Os caminhos **obrigatórios** pela norma são o de ponteiro único e o de teclado, e ambos estão prontos e provados. O arraste é conveniência, e implementá-lo sem `dnd-kit` no gabarito criaria código que o produto não vai usar |
| **QD-P2** | **Raias horizontais** (swimlanes) não especificadas | Nenhum caso SEED atual exige. Entra quando houver — e o CP2 já prevê a trilha, que é o mesmo tipo em outro eixo |
| **QD-P3** | **Reordenar dentro da mesma coluna** — o menu move entre etapas, não muda a posição | O quadro SEED ordena por prazo, não à mão. Reabre se surgir caso de prioridade manual |
| **QD-P4** | **A5 gantt** e **A15 carga de trabalho**, os outros dois arquétipos da F7.4 | O gantt tem leitura (C77–C81); a carga **não tem**. Próximo movimento da fase |

---


## 58. Gantt e linha do tempo — `estável` (F7.4, 2026-08-16) · GT1–GT17 · gabarito A5 · fecha a PN-P2 (PN42–PN45)

> **A pendência que esta seção fecha.** A **PN-P2** está aberta desde a Fase 6, registrada no §48.9
> como "bloco 6D": **PN42–PN45 — dependências FS/SS/FF/SF com lag · caminho crítico · linha de base
> · marco**. Os quatro números foram deixados **reservados e vazios** na numeração do §48, de
> propósito, porque cinco artefatos executáveis já citavam PN46–PN54 e renumerar invalidaria âncoras
> recém-calculadas. A dívida vence aqui.
>
> **Fronteira com o §48, declarada.** O §48 (painel) já tem a **lente temporal**: PN47 (pré-requisito
> de lente e gaveta de não agendados), **PN48** (régua de tempo em dois níveis) e PN49 (criar
> arrastando na faixa vazia). O §58 **não reescreve nada disso** — ele acrescenta a camada de
> **relação entre itens** que a lente não tem: dependência, folga, caminho crítico e linha de base.
> *Quem já lê o §48 não precisa reaprender a régua; quem lê o §58 encontra só o que é novo.*
>
> **Base.** **Rodada zero:** o arquétipo tem leitura visual — **C77–C81** (§13.8 do estudo).
> **R1 canon:** quatro tipos de dependência — término-início, início-início, término-término e
> início-término —, com **lag** (espera) ou **lead** (adiantamento); o **caminho crítico** é a
> cadeia mais longa de tarefas ligadas, e atraso nela atrasa o projeto inteiro; tarefas fora dele
> têm **folga**. A **linha de base** guarda o plano original para comparar com o realizado, e o
> **marco** é um ponto no tempo, não uma duração — representado por losango. Um alerta do canon
> entrou como contrato: *se você adiciona lag o tempo todo para o cronograma "fechar", suas durações
> de base provavelmente estão erradas — lag deve refletir espera real, não folga disfarçada.*
> **R2 stack:** os componentes de mercado tratam o gantt como **treegrid** (a tabela de nomes é uma
> árvore expansível) e implementam navegação por teclado sobre ela.
> **R3 normas:** a acessibilidade de gráfico complexo não se resolve com um `aria-label`. O consenso
> pede **camadas**: descrição breve do que o gráfico diz · **tabela de dados completa** como
> alternativa, para o leitor explorar por conta própria · `<title>`/`<desc>` no SVG · navegação por
> teclado ponto a ponto com nome e valor em cada marca (SC 4.1.2) · live region para mudanças.
> Um fornecedor declara em VPAT que a conformidade do gantt fica em **AA parcial**, e nomeia a
> limitação: *relações de dependência complexas podem não expor relação programática completa*.
> **Isso é fato do estado da arte, e é por isso que o GT11 existe.**

### 58.1 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **GT1** | **O gantt é SPLIT de dois painéis com rolagem vertical sincronizada**: tabela de nomes à esquerda, área de plotagem à direita, divisor redimensionável (C77) | Uma coisa só. Sem a tabela, não há como ler nome, responsável e data de quem está fora da janela de tempo visível |
| **GT2** | **O divisor tem alternativa sem arraste** (CP30 · SC 2.5.7): posições declaradas em menu, além do arraste | Divisor só arrastável. Mesmo raciocínio que virou slider no §51 e menu no §57 |
| **GT3** | **Régua de tempo em DOIS NÍVEIS**, herdando o **PN48** do §48 sem alteração; granularidade escolhida em controle nomeado | Reescrever a régua. Duas réguas para a mesma coisa produzem dois números e nenhuma decisão |
| **GT4** | **Marcador de AGORA é linha vertical contínua** na tinta de alerta, atravessando a plotagem (C78) — e leva **nome acessível** | Marcador só desenhado. Ele é a referência que dá sentido a "atrasado", e quem não vê a linha precisa da informação |
| **GT5** | **A barra é peça de dado, não peça de UI**: sem par sombra+anel, sem raio de peça. Ela pertence à figura | Tratar barra como cartão. A plotagem é figura de dado, governada pelo **CP32** |
| **GT6** | **A figura obedece ao CP32**: 1 unidade de `viewBox` = 1px; ganhar largura significa **mais tempo visível**, nunca barra mais grossa nem tipografia maior | Escalar o desenho. É o defeito de 2,16× que o gate pegou no dataviz |
| **GT7** | **Quatro tipos de dependência** — TI, II, TT, IT — desenhados como conectora com seta, cada uma com **origem e destino nomeados** no texto acessível | Só término-início. Obra elétrica tem paralelismo real: comissionamento II com energização, por exemplo |
| **GT8** | **Lag é DECLARADO no rótulo da conectora** ("TI + 5 d"), nunca implícito no espaço entre as barras | Lag invisível. O canon é explícito: lag que vira hábito esconde duração de base errada |
| **GT9** | **Caminho crítico é REALCE, não filtro**: as barras da cadeia mais longa recebem tratamento próprio **com mecanismo além de cor** | Esconder o resto. Caminho crítico só significa alguma coisa contra o que **não** é crítico |
| **GT10** | **Linha de base é uma SEGUNDA BARRA**, mais fina e recuada, sob a barra do realizado — e um controle liga e desliga a comparação | Trocar a cor da barra. A comparação é entre **duas durações**, e uma cor não mostra duas durações |
| **GT11** | **A alternativa textual é uma TABELA COMPLETA**, não um resumo: cada item com início, término, duração, folga, predecessor e tipo de ligação | `aria-label` no SVG. O estado da arte reconhece que dependência complexa não expõe relação programática completa; a tabela **é** a relação, escrita |
| **GT12** | **A tabela de nomes é `treegrid`**: hierarquia expansível, com uma linha no tab sequence (roving) | Lista plana. Obra tem etapa e subetapa, e a árvore é o que permite fechar o que não interessa |
| **GT13** | **Data com cor por estado no VALOR**, não em chip separado (C80) — e com mecanismo além de cor | Chip de status ao lado. Numa coluna estreita, o chip rouba a data; e o estado É da data |
| **GT14** | **Marco é LOSANGO**, ponto no tempo sem duração, com rótulo próprio | Barra de um dia. Marco não tem duração, e desenhá-lo como barra faz o olho ler duração onde não há |
| **GT15** | **Gaveta de não agendados** acoplada à visualização (C81), com abas de motivo e contagem — herda o PN47 do §48 | Esconder o item sem data. Toda visualização por eixo tem o problema do item sem posição, e escondê-lo é como se ele não existisse |
| **GT16** | **A rolagem horizontal da plotagem é exceção bidimensional DECLARADA** (SC 1.4.10): região focável e nomeada, como no §57 | Rolagem no documento |

### 58.2 O que este arquivo NÃO decide

- **Recalcular cronograma** (predecessor atrasa e sucessores deslizam) é regra de **domínio**, não de
  DS. O DS desenha o resultado; quem o calcula é o produto.
- **Criar dependência arrastando** de uma barra a outra é conveniência; o caminho obrigatório é o
  formulário de ligação, pelo SC 2.5.7.
- **Nivelamento de recurso** e **carga de trabalho** são o arquétipo A15, que **não tem leitura
  visual** e está fora deste texto.

### 58.3 Números MEDIDOS na produção do gabarito

A seção nasceu só com relações; os números vieram na produção do gabarito A5,
`tela-gantt.html`, exatamente como o §55 fez.

| Medida | Valor |
|---|---:|
| **Escala declarada (CP32)** | **6px por dia** — não muda com a largura da tela |
| Janela do gabarito · largura da plotagem | 01/06/2026–31/12/2026 · **1.284px** |
| Altura da faixa e da linha do treegrid | **34px** (iguais, por construção) |
| Altura da barra do realizado · da linha de base | **20px** · **5px** |
| Lado do losango do marco | **14px** (quadrado rotacionado 45°) |
| Espessura da conectora · da seta | **1,5px** · polígono de 6×8px |
| Espessura do marcador de agora | **2px** |
| Larguras declaradas do painel de nomes | **240 · 340 · 460px** |
| Alvo do divisor e dos itens da gaveta | **44px** no eixo curto |

**Três achados reais na medição de contraste, e os três mudaram o artefato:**

1. **A barra nasceu com `surface-brand`** (#11B0A0, o turquesa vivo do hero) e o rótulo branco sobre
   ela media **2,71** — reprova o piso 4,5 do SC 1.4.3. Trocada por `action-primary`, o token que
   existe para carregar texto (4,60). **Turquesa de MARCA e turquesa de AÇÃO não são o mesmo token,
   e a diferença é o texto que cada um sustenta.**
2. **O marco nasceu com `entity-2`** (#D49400) e media **2,61** contra a plotagem — reprova o
   SC 1.4.11. `entity-5` resolvia o claro (6,74) e **reprovava o escuro (2,37)**: cor fixa precisa
   passar nos dois temas. Ficou `text-brand`, que vira sozinha (4,60 · 8,31). **Cor de identidade
   categórica não é automaticamente cor de elemento de interface.**
3. **O contorno do caminho crítico** mede menos que 3,0 contra a própria barra. Ele é **fronteira
   externa**: a cor adjacente que o SC 1.4.11 alcança é a da plotagem, contra a qual ele desenha —
   **5,59**. E o que distingue crítico de não-crítico não é o contorno sozinho: é o par contorno +
   **pictograma**, e o pictograma sobrevive à escala de cinza.

### 58.4 Validação

| Camada | Placar |
|---|---|
| `suite-gantt.mjs` — blocos ESC · SPL · DEP · CRI · BAS · ALT · TRG · EST · MRC · GAV · REG · VIV · **POP** · **EIX** | **43 · 0** (35 na produção + 8 nascidas do gate de 2026-08-16) |
| `suite-composicao.mjs` (render, 2 temas) | **27 · 0** |
| `contraste-f73.py` — 64 pares × 2 temas | **128 · 0** |
| Console e erros de página | limpo |

> **Dois defeitos do gate de 2026-08-16, ambos virados guarda antes de qualquer promoção.**
> **(a)** O menu do divisor abria **estourado** — verbatim: *"o botão está abrindo estourado"*. A
> causa: ele usa a classe `.menu-mover`, que existe no gabarito A7 e **nunca foi copiada para este
> molde**; sem regra, sobravam só os estilos inline, sem `font-size` e sem âncora. **Classe
> reaproveitada entre gabaritos é dependência, e dependência não copiada é referência órfã** — a
> mesma família que a guarda `REF-01` pega em código e o `NV-01` em token. Guardas `POP-01…03`.
> **(b)** O gráfico parecia **travado** — verbatim: *"o gantt tbm nao clica nem da pra mexer"*. As
> barras clicavam e a região rolava; o que faltava era o **controle**. Daí o **GT17**. Guardas
> `EIX-01…05`.
>
> **A guarda que vale mais é o bloco `ESC`.** Ela mede a largura e a altura de uma barra em **duas
> larguras de janela** e exige que sejam idênticas, junto com o tamanho do texto — é o **CP32**
> aplicado a uma figura montada em HTML. Sem ela, bastaria trocar `px` por `%` para o gantt voltar a
> ser o defeito de 2,16× que o gate pegou no dataviz.
>
> **Nota sobre a `suite-composicao.mjs`:** as guardas `ST-01/02/03` (chip de status pintado com o
> token do tema) passaram a ser **condicionais à existência de chip**, e isso está **impresso** na
> saída. O gantt não usa chip por decisão escrita — o **GT13** manda o estado viver no valor da
> data. Mesma régua do `MONO-03` no §57: contrato que não se aplica ao arquétipo é declarado, não
> silenciado. Por isso o A5 fecha em **27**.

### 58.4 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **GT-P1** | ~~gabarito A5~~ | **FECHADA em 2026-08-16**: `tela-gantt.html` produzido com gerador, molde e suíte própria (35 · 0) |
| **GT-P4** | **Só dois dos quatro tipos de ligação** (TI e II) têm exemplar na tela | TT e IT existem como contrato e não como pixel. A asserção do gerador exige mais de um tipo — subir para os quatro exige um caso de obra que os use de verdade, e inventar um só para preencher a tela seria dado sem referente |
| **GT17** *(contrato NOVO, do gate de 2026-08-16)* | **Eixo que rola precisa de CONTROLE explícito**, não só de `overflow`: recuar, avançar e "Hoje", com passo declarado em unidade de tempo e posição na janela exibida | Confiar na barra de rolagem. **Achado do gate**, verbatim: *"o gantt tbm nao clica nem da pra mexer"*. Rolagem horizontal com roda de mouse exige Shift — quem tem mouse comum não move o eixo e conclui que a tela está travada. **Overflow é mecanismo; controle é affordance**, e o C79 já observava o controle de tempo na barra do produto de referência. O passo é UM MÊS em px da escala declarada, nunca uma fração da largura visível: fração faria o passo mudar de tamanho conforme a janela |
| **GT-P5** | **Arraste de barra** (mover data arrastando) não implementado | Os caminhos obrigatórios são o de ponteiro único e o de teclado; o arraste é conveniência, e o gabarito não é o lugar de escrever a mecânica que o produto vai reimplementar |
| **GT-P3** | **A15 carga de trabalho** segue sem leitura visual | Exige navegação antes, como o A6 |

---


## 59. Campos de valor do domínio — monetário, medida e duração — **`estável`** (F7.5 · **promovido em 2026-08-17 pelo gate do Rafael — verbatim: "aprovo. siga"**; ver a nota de rastreabilidade no §78 do MANIFESTO: o gate foi **declarado**, e não há registro de quais capturas ele abriu — se não tiver ocorrido neste artefato, corrigir por errata) · VD1–VD16 · bancada `banco-dominio.html` · fecha C9 e C10 · `suite-dominio` **28 · 0** · BT7 medido e verde em 2026-08-17

> **Rodada zero, e o veredito aqui é de um TIPO NOVO.** As fases anteriores perguntavam se o
> arquétipo tinha leitura visual no produto de referência. Para estes componentes a pergunta **não
> se aplica**: o ClickUp não tem campo de potência em kWp nem de energia em kWh — **estes campos são
> do domínio SEED**, e não existem lá para serem observados. A base correta não é navegação: é
> **canon de entrada numérica + normas + o domínio**.
>
> *Isto é diferente dos bloqueios do A6 e do A15, que são de ambiente. Aqui não há o que visitar.*
> **Registrar a distinção importa:** "não tem leitura" pode significar três coisas — não foi
> visitado, não existe instância, ou **não se aplica** —, e tratá-las como a mesma coisa é como se
> chega a "vamos navegar" para um problema que a navegação não resolve.
>
> **Base.** **R1 canon:** a régua do USWDS é literal e vira o contrato central — *use prefixo ou
> sufixo quando o campo se refere a uma unidade ou valor monetário, mas **a entrada do usuário deve
> conter apenas dígitos**, não a unidade nem o símbolo*; e não use prefixo quando o campo é aberto o
> bastante para admitir resposta que não seja número. **R2 stack:** o React Aria é explícito sobre
> por que **não** usar `<input type="number">` — comportamento inconsistente entre navegadores e
> plataformas, opções de formatação localizada limitadas e stepper difícil de estilizar. E traz o
> fato que decide a implementação: **o navegador só FORMATA, não faz PARSING** — o `Intl.NumberFormat`
> gera a string, e ler de volta exige parser próprio construído a partir do formatador.
> **R3 normas:** rótulo associado (1.3.1), instruções (3.3.2), erro que identifica o problema (3.3.1)
> e sugere a correção (3.3.3), e validação que **não depende só de cor**. Sobre o **1.3.5 Identify
> Input Purpose**, a resposta oficial do W3C é a que aplicamos no VD12.

### 59.1 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **VD1** | **O usuário digita SÓ DÍGITOS.** A unidade é adorno do campo, não conteúdo digitável | Deixar o usuário escrever "R$ 1.240,00 kWp". Régua literal do USWDS, e ela evita o parser ter de adivinhar o que é unidade e o que é valor |
| **VD2** | **A posição da unidade é convenção, não estética:** moeda é **prefixo** em pt-BR (`R$`), unidade de medida é **sufixo** (`kWp`, `kWh`, `kVA`, `V`, `A`) | Escolher a posição por gosto. A posição vem do idioma e da unidade — há moedas que são sufixo, e trocar isso confunde quem lê rápido |
| **VD3** | **A unidade é `aria-hidden`, e o NOME ACESSÍVEL do campo a inclui** ("Potência em quilowatt-pico") | Deixar o leitor de tela ler "erre cifrão". Símbolo é atalho visual; o nome acessível é o que informa |
| **VD4** | **Formata no `blur`, guarda o valor CRU.** A submissão envia número, nunca a string formatada | Guardar "1.240,00". O separador é apresentação; o dado é `1240` |
| **VD5** | **Locale pt-BR declarado:** decimal é **vírgula**, milhar é **ponto** | Deixar implícito. O mesmo número lido em en-US é mil vezes outro, e um ERP de engenharia não pode ter essa ambiguidade |
| **VD6** | **`inputmode="decimal"`, nunca `<input type="number">`** | O `type=number` — comportamento inconsistente entre navegadores, formatação localizada limitada e stepper impossível de estilizar. O `inputmode` traz o teclado numérico no toque sem trazer os defeitos |
| **VD7** | **Formatar com `Intl.NumberFormat`; LER de volta com parser próprio derivado do formatador** | Regex artesanal. O navegador **só formata** — não há API de parsing —, e um regex escrito à mão erra em locale que ele não previu |
| **VD8** | **Alinhamento à direita e `tabular-nums`** (herda o PN21d) | Alinhar à esquerda. Coluna de valor só é escaneável quando as casas se alinham |
| **VD9** | **Lista FECHADA de unidades do domínio SEED**, declarada no canônico: `R$` · `kWp` · `kWh` · `kW` · `kVA` · `V` · `A` · `%` | Unidade livre por texto. Unidade digitável vira "Kwp", "kwp", "KWP" no mesmo banco |
| **VD10** | **Duração aceita entrada COMPOSTA e normaliza**: "3d 4h" → 28h (jornada declarada) | Só horas. Cronograma de obra fala em dias; apontamento fala em horas, e obrigar conversão mental é onde o erro entra |
| **VD11** | **Erro identifica o problema E sugere a correção**, com mecanismo além de cor (SC 3.3.1 e 3.3.3) | "Valor inválido". Não diz o que está errado nem o que fazer |
| **VD12** | **`autocomplete` NÃO se aplica a estes campos, e isso é DECLARADO** | Inventar token. A orientação oficial do W3C é literal: se o campo pede informação **fora da lista** de propósitos, o critério **não é aplicável** e o campo passa. Inventar `autocomplete="kwp"` seria a falha **F107** — token que não corresponde a propósito reconhecido |
| **VD13** | **O passo do stepper é da UNIDADE**, declarado por campo (R$ 0,01 · kWp 0,01 · h 0,5) | Passo 1 para tudo. Passo errado é pior que passo nenhum: transforma ajuste fino em digitação |
| **VD14** | **Valor exibido FORA de campo usa a MESMA formatação** do campo | Duas formatações no mesmo produto — a tela mostra `1.240,00` e o campo abre `1240.00` |
| **VD15** | **Conversão entre unidades NÃO é do Design System** | Converter kWh↔kWp no componente. Isso é regra de engenharia, com premissa (irradiação, perdas) que o DS não conhece e não pode inventar |
| **VD16** | **Campo vazio ≠ zero.** Vazio é ausência e exibe travessão em leitura (herda o **PD6**) | Preencher com 0. Potência zero e potência desconhecida são coisas diferentes, e um relatório que soma as duas mente |

### 59.2 Anatomia

```
   Potência                              ← rótulo visível (1.3.1)
  ┌──────────────────────────────┬─────┐
  │                   1.240,00   │ kWp │  ← sufixo, aria-hidden (VD2, VD3)
  └──────────────────────────────┴─────┘
   Use ponto para milhar e vírgula para decimal.   ← instrução (3.3.2)

  ┌─────┬────────────────────────────────┐
  │ R$  │                   486.200,00   │  ← prefixo (VD2)
  └─────┴────────────────────────────────┘
```

O adorno vive **dentro da fronteira do campo** e o texto digitado **não passa por baixo dele** —
o contêiner carrega a borda e o estado de foco, e o `<input>` entra sem borda própria. É o terceiro
dos três caminhos possíveis, e o único que não deixa o texto vazar sob o adorno.

### 59.3 Erro — os três casos, com a frase

| Situação | Mensagem |
|---|---|
| Fora da faixa | *"A potência precisa ficar entre 0,01 e 9.999,99 kWp. Você digitou 12.000,00."* |
| Formato irreconhecível | *"Não consegui ler esse valor. Use vírgula para o decimal — por exemplo, 1.240,50."* |
| Obrigatório vazio | *"Informe a potência em kWp. Se ainda não souber, deixe a obra sem potência em vez de preencher com zero."* |

**As três dizem o que houve E o que fazer** (SC 3.3.1 + 3.3.3). A terceira carrega o **VD16**: ela
existe para impedir que "não sei" vire zero.

### 59.4 Números MEDIDOS na produção da bancada

| Medida | Valor |
|---|---:|
| Altura do campo (com e sem adorno) | **40px**, igual nos dois |
| Alvo do stepper | **44px** de largura |
| Recuo do texto contra o adorno | **8px** de cada lado |
| Casas decimais por família | monetário **2** · potência **2** · energia **0** · duração **1** |
| Passos declarados | R$ **100,00** · kWp **0,01** · kWh **10** · duração **0,5h** |
| Jornada do VD10 no gabarito | **8h** (parâmetro do produto, VD-P2) |
| Pares de cor medidos (F7.3 + F7.4 + F7.5) | **76 pares × 2 temas = 152 · 0** |

**Um achado real na medição, e ele quase virou conveniência.** O separador entre o adorno e o campo
nasceu com `border-subtle` e mediu **1,11** — reprova o SC 1.4.11. A saída fácil seria declará-lo
decorativo, como se fez com o separador entre campos no §56. **Aqui não cabia, e a razão é medida:**
a diferença entre as duas superfícies (`surface-subtle` do adorno e `surface-raised` do campo) é de
apenas **1,08** — a superfície **não separa nada**. A linha é o **único** sinal de que ali termina o
adorno e começa o campo, e sinal único não pode medir 1,11. Trocado por `border-interactive`.

> **Quarto achado seguido da mesma família.** O padrão está firme: *`border-default` e
> `border-subtle` são fronteiras de LEITURA; onde a fronteira precisa ser VISTA, o token é
> `border-interactive`.* E a régua para decidir se cabe declarar decorativo agora tem um teste:
> **meça se existe OUTRO sinal.** Se a superfície separa, a linha é reforço; se não separa, a linha
> é o sinal, e sinal se mede.

### 59.5 Validação

| Camada | Placar |
|---|---|
| `suite-dominio.mjs` — blocos ANA · TEC · FMT · DUR · ERR · PAS · LEI · UNI · VIV | **28 · 0** |
| `contraste-f73.py` — 76 pares × 2 temas | **152 · 0** |
| Console e erros de página | limpo |

> **A guarda que vale mais é o bloco `FMT`**, e em particular a `FMT-02`. Ela prova o **VD5** com o
> caso que separa um ERP correto de um ERP perigoso: **em pt-BR o ponto é separador de MILHAR**.
> Digitar `1240.5` não significa mil duzentos e quarenta e meio — significa **doze mil quatrocentos
> e cinco**, e a guarda verifica que o parser lê assim e reprova pelo teto. *Um parser que
> "adivinha" trocaria o valor de um contrato por mil vezes ele mesmo.*
>
> **Uma calibração de instrumento, declarada:** a primeira execução acusou dois falsos porque os
> blocos `ERR` e `PAS` liam o estado deixado pelos blocos anteriores. A suíte passou a **recarregar
> a página entre blocos** — *guarda que depende da ordem de execução das outras não é guarda, é
> sequência*.

### 59.5 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **VD-P1** | ~~bancada~~ | **FECHADA em 2026-08-16**: `banco-dominio.html` produzido com gerador, molde e suíte própria (28 · 0) |
| **VD-P4** | **Máscara durante a digitação** não implementada — a formatação acontece no `blur` | Formatar enquanto se digita conflita com métodos de entrada compostos, e o canon alerta para isso. Reabre se o gate pedir |
| **VD-P2** | **Jornada de trabalho do VD10 não é decidida aqui** | "1 dia" são 8h, 8h48 ou 24h conforme o regime, e isso é parâmetro do produto, não do DS. O componente recebe a jornada; não a define |
| **VD-P3** | **C1–C8 e C11–C15** (intervalo de datas, data+hora, multi-select com chips, seletor de pessoa, de entidade, de ícone, editor rico, menção, prioridade, toggle group, senha, OTP, edição inline) | Restante da F7.5. Vários **têm** leitura visual na §13 e no estudo — a rodada zero de cada um decide se navega ou não |

---


## 60. Escolha segmentada e múltipla — **`estável`, SG1–SG14 e SG6-b** (F7.5, 2026-08-16) · **validado pelo Rafael em 2026-08-16** na v0.3 · **RE-GATE OBSERVADO em 2026-08-17 na v0.4: a BT-P1 FECHA — insumo nomeado, ver §64.10.1** · fecha C3 e C12 · bancada `banco-escolha.html` **v0.4** · quatro camadas verdes: `suite-escolha` **238 · 0 · 29 [n/a]** em cinco alvos · `suite-painel` **146 · 0** · `contraste-painel` **78 · 0** · `suite-container` **66 · 0** · **SEM PENDÊNCIA ABERTA — dez fechadas em 2026-08-16**

> **Rodada zero.** Os dois itens **TÊM** leitura visual, e ela é recente: **C114** (§14) — segmentação
> por estado como **par de pílulas com contagem**, `1 On-line · 7 Off-line`, adjacentes, a ativa com
> fundo, *não é filtro em barra, é recorte binário do mesmo conjunto* · **C63** — escopo como filtro
> na sidebar, com contagem · **C74** — cada widget tem toolbar própria, com **chips de filtro
> removíveis**. Não é preciso navegar.
>
> **E esta seção tem uma particularidade: ela chega DEPOIS dos consumidores.** O §48 já usa
> segmentação para trocar de lente, o §56 usa um interruptor "só comentários", o §53 usa chips de
> filtro. Os três funcionam — e nenhum tinha spec. *É o inverso do §54, que existiu como artefato
> sem seção; aqui a seção chega para nomear o que três artefatos já fazem, e a primeira coisa que
> ela faz é medir se eles fazem a mesma coisa.*
>
> **Base (3 rodadas, 2026-08-16). E o achado da R1 é uma DIVERGÊNCIA DE CANON.** Cinco fontes de
> peso dão **quatro semânticas diferentes** para o mesmo componente:
> **(a)** o **Primer** diz explicitamente para **não** tratar como `radiogroup` — *radiogroup requer
> um botão de salvar* —, usa botões com Tab entre eles e **setas que não movem foco**;
> **(b)** o **useUI** manda `role="radiogroup"` + `role="radio"` + `aria-checked`, com **um único tab
> stop** e setas movendo dentro — *"o grupo é um radiogroup, não um tablist: não há painéis aqui, e
> prometer painéis enganaria a tecnologia assistiva"*;
> **(c)** o **Workday** usa **toggle button** com estado pressionado;
> **(d)** o **eBay** usa botões comuns no tab order com **`aria-current`**, avisando que *segmented
> buttons não são substitutos de abas e não operam como abas*.
> O **React Aria** confirma a fronteira útil: se a escolha **troca a vista do mesmo conteúdo**, use
> grupo de alternância ou radiogroup; se **troca o conteúdo**, use abas.
>
> **R3 normas:** o estado selecionado precisa se distinguir **por mais que cor** — diferença de
> valor claro/escuro é o que sobrevive à deficiência de visão de cores.

### 60.1 A decisão que a divergência obriga: TRÊS SAÍDAS, com régua

Não há "o jeito certo" no canon. Há três problemas diferentes que a mesma forma visual serve, e a
semântica tem de seguir o **problema**, não a aparência.

| Se o controle… | Então é… | Semântica |
|---|---|---|
| **troca a VISTA do mesmo conteúdo**, com 2 a 5 opções mutuamente exclusivas, aplicando na hora | **segmentado** (SG1) | `role="radiogroup"` + `role="radio"` + `aria-checked`, **um tab stop** e setas dentro |
| **liga e desliga um recorte** do mesmo conjunto (binário) | **interruptor de recorte** (SG2) | um `<button>` com `aria-pressed` |
| **troca o CONTEÚDO** e tem painel associado | **abas** (§34) | `tablist`/`tab`/`tabpanel` — e **não** é este componente |

> **A régua em uma frase: pergunte o que muda.** Muda a **forma de ver** o mesmo dado → segmentado.
> Muda **quanto** do mesmo dado aparece → interruptor. Muda **o dado** → abas.
>
> **Por que descartamos a saída do Primer** (botões com Tab entre eles): num quadro com quatro
> lentes, quatro tab stops custam quatro pressionamentos para atravessar um controle só — e o
> próprio canon avisa que *um controle de quatro opções não deve custar quatro pressionamentos para
> ser ultrapassado*. **Por que descartamos `aria-current`** (eBay): ele diz "este é o atual" sem
> dizer que os outros são **escolhas**; `aria-checked` num radiogroup diz as duas coisas.
>
> **O alerta do Primer, porém, entra como contrato (SG4):** radiogroup em formulário sugere que a
> escolha só vale ao salvar. Aqui **a escolha aplica na hora**, e isso precisa estar dito no nome do
> grupo — não implícito.

### 60.2 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **SG1** | **Segmentado é `radiogroup` com um único tab stop** e setas movendo dentro (roving). **`<input type="radio">` nativo satisfaz o contrato** e é a forma PREFERIDA quando cabe — o roving, as setas e o `checked` vêm de graça do HTML; `role="radio"` + `aria-checked` + roving em JS é a forma para quando o nativo não cabe | Um tab stop por segmento. Quatro opções custariam quatro pressionamentos para atravessar |
| **SG2** | **Recorte binário é um `<button>` com `aria-pressed`**, não um segmentado de duas opções | Segmentar "com/sem". Ligar e desligar é um controle, não uma escolha entre dois |
| **SG3** | **Nunca `tablist` sem painel.** Se não há painel, o papel promete o que não existe | `role="tablist"` por parecer aba |
| **SG4** | **O nome do grupo diz o que a escolha faz** ("Ver como", "Filtrar por") **e que ela aplica na hora** | Grupo sem nome. Sem ele, o leitor de tela anuncia as opções sem contexto — e o alerta do Primer sobre "salvar" fica de pé |
| **SG5** | **REVISTO 2026-08-16 · o teto é de APERTO MEDIDO, não de contagem.** O segmentado cabe enquanto a fileira, em toda largura de uso, **não vazar, não cortar e não reduzir nenhum alvo abaixo de 44px** — quebrar em mais de uma linha é permitido quando as opções são de mesma natureza. Passado esse ponto, `select`. O grupo precisa oferecer **Home e End**, que é o que impede a fileira longa de custar N setas | **DESCARTADO: o teto fixo de 5.** Ele foi violado pelo primeiro consumidor — as oito lentes do §48, `estável` e aprovadas em gate **três dias antes de o contrato existir** —, e mantê-lo exigiria converter pixel aprovado em `select` por uma regra que nasceu depois dele. Medido: 8 opções em 390/768/1180/1440px = 4/2/1/1 linhas, alvo mínimo 62×44px, sem vazar nem cortar |
| **SG6** | **REVISTO 2026-08-16 · o selecionado se distingue por diferença de VALOR MEDIDA ≥ 3,00** entre os dois fundos, não por matiz. O peso é reforço **opcional** | **DESCARTADO: exigir mudança de `font-weight`.** Ela altera a largura do texto e faz a fileira **refluir a cada seleção** — custo de layout real. Medido: 4,60 (lentes) e 4,57 (operador), ambos acima do piso 3,00 do SC 1.4.11 |
| **SG6-b** | **NOVO 2026-08-16 · e ele é a razão de o SG6 não ter sido simplesmente relaxado.** A separação tem de sobreviver a **`forced-colors: active`**, onde o agente de usuário SUBSTITUI `background-color` e `border-color`. O meio é **cor de sistema** — `Highlight` / `HighlightText` para o selecionado, `ButtonFace` / `ButtonText` para o repouso, com `forced-color-adjust: none` | **DESCARTADO: confiar no fundo da marca.** Medido antes do conserto: a separação cai de **4,60 para 1,00** e o selecionado fica **indistinguível** do repouso. Depois: **19,04**. *O SG6 original estava certo pelo motivo errado — dizia "não só cor" pensando em daltonismo, e quem cobra a fatura é o alto contraste* |
| **SG7** | **Contagem por segmento é opcional e, quando existe, conta o CONJUNTO — não muda com o filtro aplicado** | Contador que acompanha o próprio recorte. Herda o **CM11**: número que muda ao filtrar deixa de ser referência |
| **SG8** | **Segmento com ícone e rótulo visível usa SÓ o rótulo no nome acessível**; ícone sozinho exige nome próprio | Concatenar ícone e texto no nome |
| **SG9** | **Multi-select produz CHIPS REMOVÍVEIS**, cada um com nome que diz o que remove | Lista de marcados escondida atrás do gatilho. O chip é o que torna o filtro visível — e removível de um clique |
| **SG10** | **O chip de filtro é do §22, com o X interno em alvo de 44px** | Reinventar o chip |
| **SG11** | **Acima de um teto declarado, os chips colapsam em "+N"**, e o "+N" abre a lista completa | Vazar a linha. Herda o comportamento de excedente do §32 |
| **SG12** | **Remover um chip anuncia o que saiu e quantos restam** em live region | Remoção silenciosa |
| **SG13** | **"Limpar tudo" existe quando há dois ou mais chips**, e é a última ação da fileira | Limpar com um chip só: aí o próprio X já é o limpar |
| **SG14** | **Multi-select não fecha a lista ao escolher**; escolha única fecha | Fechar a cada marcação. Quem escolhe vários teria de reabrir a cada item |

### 60.3 Os consumidores — AUDITADOS (SG-P1, fechada em 2026-08-16)

Esta subseção nasceu, na v0.81, como uma lista de **três consumidores a verificar**. O que segue a
substitui: é o resultado medido. **Instrumento:** `validacao/suite-escolha.mjs` (nova nesta edição),
Chromium headless a 1440×900, sobre os quatro arquétipos que hoje têm controle de escolha.
**Placar: 52 PASS · 22 FAIL · 21 [n/a].** Decomposição por arquivo em §60.5.

A auditoria achou **seis controles em quatro arquivos** — não três em três. E a tabela original
errava em dois pontos, corrigidos aqui: apontava o **§53 para `tela-tabela.html`** quando o
construtor FC1–FC6 vive em **`tela-lista.html`**, e **não listava** os dois controles do
`tela-painel.html` que não têm marcação de "atual" visível ao instrumento antigo.

| # | Controle | Arquivo | Seção · estado | Forma medida hoje | Veredito |
|---|---|---|---|---|---|
| 1 | **Lentes** (8) | `tela-painel.html` | §48 · `estável`, gate 2026-08-13 | `role="group"` + 8 `<button>` + `aria-current="page"` · 8 tab stops · 44,00px · raio 9999px | **DIVERGE do SG1** |
| 2 | **Escala de tempo** (4) | `tela-painel.html` | §48 PN41 · `estável` | `role="group"` + 4 `<button aria-pressed>`, exclusivos | **DIVERGE — 3ª forma** |
| 3 | **Gesto no vazio** (2) | `tela-painel.html` | §48 · `estável` | 2 `<button aria-pressed>`, exclusivos | **DIVERGE do SG2** |
| 4 | **Operador do grupo / do subgrupo** (2 cada) | `tela-lista.html` | §53 FC3 · `estável` | `role="radiogroup"` + `<input type="radio">` nativos · 32,00px · raio 0px | **CONFORME ao SG1** |
| 5 | **Chips de filtro** (3) | `tela-tabela.html` | §42/§53 · `estável` | `<span class="chip">` + `<button class="x">` de **24×24px** | **6 FAIL** |
| 6 | **"Só comentários"** | `tela-detalhe.html` | §56 · `estável` | `<button aria-pressed>` | **CONFORME ao SG2** ✅ |

#### O achado central: o acervo já resolvia a mesma pergunta de TRÊS jeitos

A divergência de canon que a R1 achou **fora** da casa — Primer × useUI × Workday × eBay, quatro
semânticas para o mesmo componente — **já estava reproduzida dentro da casa**, e ninguém tinha
medido. Para a mesma pergunta *"escolha exclusiva que aplica na hora"*, o acervo responde:

* **`tela-painel.html`, lentes** — fileira de `<button>` com `aria-current="page"`. É a saída do
  **eBay**, e é exatamente a que o §60.1 descarta por escrito. Pior: o token `page` é de
  **navegação entre páginas**; nem dentro do padrão eBay ele caberia aqui, onde nada navega.
  Custo medido: **8 tab stops** para atravessar um controle só.
* **`tela-lista.html`, operador** — `radiogroup` com rádios **nativos**. É a saída do **useUI**, e é
  a que o §60.1 escolheu. Um tab stop, setas movendo dentro, tudo de graça pelo HTML.
* **`tela-painel.html`, escala de tempo e gesto no vazio** — fileira de `aria-pressed` **mutuamente
  exclusivos**. Não é nenhuma das quatro saídas do canon: a exclusividade existe só no script. A
  tecnologia assistiva anuncia *quatro interruptores independentes*, não *uma escolha entre quatro*.
  É a forma mais silenciosa das três, porque cada botão isolado passa em qualquer guarda de ARIA.

> **É por isso que a SG-P1 vinha antes da SG-P2.** A bancada `banco-escolha.html` produzida antes
> desta medição teria nascido como a **quarta** variação do mesmo controle, e o §60 teria
> documentado uma convergência que não existe. *Spec que chega depois do uso começa por auditar o
> uso* — a regra estava escrita; esta é a primeira vez que ela paga.

#### E a exclusividade precisa ser PROVADA, não inferida

A guarda que acha o item 2 nasceu com **falso positivo**: "fileira de `aria-pressed` com exatamente
um ligado" também descreve uma barra de interruptores **independentes** em que só um está ligado por
acaso — foi o que a barra de controles do preview de `tela-chat.html` e `tela-shell.html` fez. Uma
guarda que acusa o inocente é pior que guarda nenhuma. A versão final **prova por clique**: liga
outra opção e mede se a primeira desligou sozinha. Com a prova, os dois falsos positivos caem e os
dois achados reais ficam de pé. **Regra: exclusividade é comportamento, e comportamento se mede
agindo — nunca se lê da marcação.**

### 60.4 Números — o que a SG-P1 já mediu, e o que segue a medir

Método do §55: a spec nasceu só com relações; a auditoria trouxe os primeiros números. **Estes são
medidos, não escolhidos** — são o retrato do acervo hoje, e a bancada da SG-P2 é que decide quais
viram canon.

| Medida | `tela-painel.html` (lentes) | `tela-lista.html` (operador) |
|---|---|---|
| Altura do segmento | **44,00px** | **32,00px** |
| Raio | **9999px** (pílula) | **0px** (reto) |
| Peso, repouso → selecionado | 600 → **600** (não muda) | 700 → **700** (não muda) |
| Fundo, repouso → selecionado | `#ffffff` → `#098475` | transparente → `#098475` |
| **Separação de VALOR entre os dois fundos** | **4,60** | **4,57** |
| Texto sobre o selecionado | **4,60** (piso 4,50) | — |

> **Duas geometrias para a mesma família:** pílula de 44px e retângulo de 32px. Nenhuma das duas é
> errada isoladamente; as duas juntas são a prova de que não havia canon. A escolha é da SG-P2.

Alvo do X, medido: **24×24px** no chip (`tela-tabela.html`) e **32×32px** na condição
(`tela-lista.html`). Piso do SG10: **44px**. Detalhe do defeito em §60.6.

**Seguem a medir na bancada:** espessura e cor da fronteira do grupo · hover e foco · teto de chips
antes do `"+N"` · e todos os pares **nos dois temas** — nada acima foi medido no tema escuro.

### 60.5 Placar da SG-P1, por suíte

Placar agregado sem decomposição não é auditável (regra da v8.7).

| Arquivo | PASS | FAIL | `[n/a]` | O que os `[n/a]` querem dizer |
|---|---:|---:|---:|---|
| `tela-painel.html` | 16 | **9** | 5 | não tem chip removível — contrato declarado inaplicável, não pulado |
| `tela-lista.html` | 20 | **7** | 3 | não tem fileira de chips |
| `tela-tabela.html` | 8 | **6** | 4 | não tem controle segmentado |
| `tela-detalhe.html` | 8 | **0** | 9 | só tem interruptor; **único arquivo limpo** |
| **Agregado** | **52** | **22** | **21** | + 15 medidas |

Rodam também, com zero achado, `tela-quadro.html` · `tela-gantt.html` · `tela-chat.html` ·
`tela-shell.html` · `banco-dominio.html`: **achado negativo é achado** — nenhum destes tem controle
de escolha, e isso agora está medido em vez de suposto.

### 60.6 Supersede formal — o que a auditoria muda, e o que ela cobra do gate

#### (a) CORRIGIDO sem gate — fato, não decisão

**SUPERSEDE §60.3 (v0.81) → §60.3 (v0.82).** A tabela de consumidores da v0.81 dizia
*"Chips de filtro (§53, filtro composto) — `tela-tabela.html`"*. **Está errado:** o construtor
FC1–FC6 do §53 vive em **`tela-lista.html`** (`fieldset` + `legend` + `radiogroup`, FC3), e os chips
de `tela-tabela.html` são da toolbar do §42. A v0.81 também listava **três** consumidores; são
**seis**. *Motivo do erro: a tabela foi escrita a partir da memória da seção, não de uma busca no
acervo. É a lição da v7.0 outra vez — reconstrução de memória é inferior ao arquivo real.*

#### (b) DEFEITO MEDIDO, conserto pendente de autorização

**SG10 · o alvo de 44px do X do chip existe no código e não cobre o botão.** `tela-tabela.html`
declara `.chip .x::before { position:absolute; width:44px; height:44px }` — a intenção certa. Mas
`.chip .x` é `position:static`: o absoluto ancora no primeiro ancestral posicionado, que é `<html>`.
**O alvo de 44px está no canto da página.** Alvo real do botão: **24×24px**, contra piso 44px.
Conserto: uma declaração — `.chip .x { position: relative }`. O X da condição em `tela-lista.html`
mede **32×32px** e não tem nem a tentativa.

> **Esta é a família de defeito mais cara do projeto: o que PARECE resolvido.** Uma leitura do CSS
> vê "44px" e passa. Só a medição do retângulo real acusa. **Regra nova: alvo estendido por
> pseudo-elemento absoluto só conta se o instrumento provar que o elemento-dono é posicionado.** A
> guarda `SG10` da `suite-escolha.mjs` já nomeia a âncora do `::before` na mensagem de FAIL.

**SG12 · a remoção anuncia o que saiu e não quantos restam.** Medido:
`"Filtro removido: Frente: Subestação MT."` — sem o número. *E a própria suíte tropeçou aqui:*
a primeira versão lia a **primeira** live region do DOM e pegava o contador `"Mostrando 12 de 148"`,
que é outro dispositivo (TD1). Veredito sobre o texto errado. A versão final lê **a região que
mudou**. *Guarda que depende de qual elemento vem primeiro no DOM não é guarda, é sorte.*

**SG13 · "Limpar filtros" persiste com um chip só** — medido descendo a fileira até 1.
**SG11 · não há teto nem `"+N"`** em nenhum dos dois arquivos: a fileira vaza.

#### (c) A PRÓPRIA SPEC ERRA — dois contratos que a medição derruba

**SG5 (teto de 5 opções) contra o §48 (oito lentes).** O §48 é `estável` e passou pelo gate do
Rafael em 2026-08-13 com **oito** lentes. O SG5 foi escrito em 2026-08-16 sem olhar para ele. **Não
é o artefato que está fora do contrato: é o contrato que nasceu sem consultar o consumidor.**
O motivo do SG5 é *"não espremer sete opções numa fileira apertada"* — e o painel não espreme:
`.lentes` é `flex-wrap: wrap`, e o PN4 **esconde** a lente que o conjunto de dados não sustenta, de
modo que o número visível é menor que oito na maioria dos casos. A régua útil não é a **contagem**,
é o **aperto**. Proposta à decisão do gate, com a alternativa descartada nomeada:

> **SG5 revisto (proposta):** *o teto não é de contagem, é de aperto medido.* Segmentado cabe
> enquanto a fileira couber em **uma linha sem reduzir o alvo abaixo de 44px**; passado isso,
> `select` — **ou** quebra em duas linhas quando as opções são de mesma natureza e a quebra não
> muda o significado. **Descartado:** manter o teto fixo em 5, que exigiria converter as oito lentes
> em `select` e desfazer pixel aprovado em gate por uma regra que nasceu depois dele.

**SG6 ("fundo E peso") contra 2 de 2 dos segmentados.** Nenhum dos dois muda o peso ao selecionar
(600→600 e 700→700). O **motivo** do SG6 é *"diferença de valor é o que sobrevive à deficiência de
visão de cores e à escala de cinza"* — e a medição diz que **o fundo sozinho já entrega**: **4,60**
e **4,57** de separação de luminância, contra piso **3,00** do SC 1.4.11. *Contrato violado por 100%
dos seus consumidores é contrato mais estrito que o problema que ele resolve.*

> **SG6 revisto (proposta):** *o selecionado se distingue por diferença de VALOR medida ≥ 3,00
> entre os dois fundos — não por matiz.* O peso é reforço **opcional**. **Descartado:** exigir a
> mudança de peso, porque `font-weight` altera a largura do texto e a fileira **reflui a cada
> seleção** — os segmentos mudariam de tamanho ao serem escolhidos, o que é custo de layout real em
> troca de um sinal que a medição mostra já existir.

#### (d) A DECISÃO DO GATE — tomada em 2026-08-16

Os controles divergentes estão em seções `estável`, e convertê-los muda **comportamento de teclado
aprovado** (de 8 tab stops para 1) e **marcação anunciada** — a mesma forma do **CP-P5**, que só
andou com o *"conserta"* explícito. **O Rafael autorizou:** converter os três controles do §48
(SG-P4/SG-P5) e consertar os cinco defeitos dos removíveis (SG-P7). O **SG-P6** ele delegou
("analise e tome a melhor decisão"), e o que segue registra a decisão tomada e **por que ela é o
oposto da que eu tinha proposto**.

#### (e) SG-P6 · A MEDIÇÃO DERRUBOU A PROPOSTA — e essa é a lição da edição

Em §60.6(c) eu propus **relaxar o SG6**: o fundo sozinho media **4,60** e **4,57** de separação de
valor contra piso **3,00**, logo o `e PESO` seria mais estrito que o problema. O argumento estava
correto **dentro do modo que eu tinha medido**. Antes de aplicar, medi os outros:

| Modo | Fundo, repouso → selecionado | Separação | Veredito |
|---|---|---:|---|
| normal | `#ffffff` → `#098475` | **4,60** | passa |
| escuro | `#ffffff` → `#098475` | **4,60** | passa |
| **`forced-colors: active`** | `#ffffff` → **`#ffffff`** | **1,00** | **o selecionado SOME** |

Sob alto contraste do sistema o agente de usuário **substitui** `background-color` e
`border-color`. A distinção inteira do selecionado evaporava — nos três controles do painel e nos
dois operadores da lista. **O SG6 estava certo pelo motivo errado:** o texto dizia *"não só cor"*
pensando em deficiência de visão de cores e escala de cinza, e quem cobrava a fatura era um modo
que ninguém tinha medido.

Mas o remédio também não era o que o contrato dizia. `font-weight` sobreviveria ao modo, sim — só
que ao custo de a fileira **refluir a cada seleção**. O meio certo são as **cores de sistema**, que
são as únicas preservadas: `Highlight` / `HighlightText` é exatamente o par que o sistema reserva
para *"isto está selecionado"*. Depois do conserto a separação mede **19,04**.

> **Regra nova, e ela corrige uma que eu tinha acabado de escrever.** Em §60.6(c) escrevi:
> *"contrato violado por 100% dos seus consumidores é suspeito de ser mais estrito que o problema"*.
> Continua verdade — mas **a suspeita não é veredito**. Antes de relaxar, **meça o modo em que o
> contrato ganha a vida**. Um contrato pode estar certo pelo motivo errado, e relaxá-lo pelo motivo
> declarado destrói a proteção real. *Irmã direta da regra da v9.8 — antes de declarar uma fronteira
> decorativa, meça se existe outro sinal — aplicada na direção oposta.*

O **SG5**, esse sim, cedeu — e com número: as oito lentes cabem em 390, 768, 1180 e 1440px, em
4/2/1/1 linhas, alvo mínimo **62×44px**, sem vazar e sem cortar. **E a SG5 revista achou defeito
que a antiga não alcançava:** o segmento do operador do §53 media **62×32px**, abaixo do piso de
44. A guarda SG10 não o via porque só cobria o X de chip e de condição. *Contrato reescrito para
medir o problema certo acha o que o contrato anterior não alcançava.*

#### (f) DUAS GUARDAS APONTAVAM PARA O LADO ERRADO

**`PN6-01` (`suite-painel.mjs`) AFIRMAVA o defeito.** Ela dizia, e passava:
*"a lente ativa usa aria-current"*. É por isso que cinco camadas de validação atravessaram o caso:
**a guarda não estava cega — estava apontada para o lado errado.** Superseded por seis guardas
(`PN6-01a`…`PN6-01f`) que testam o **problema** (radiogroup · role=radio · um tab stop · nenhum
`aria-current` sobrando · nome que diz "aplica na hora" · nenhuma LEITURA órfã de `aria-current`
no script) em vez da forma. Nasce também a `PN6-03`, do SG6-b.

**E o instrumento novo errou duas vezes, das duas em família conhecida.** A `suite-escolha.mjs`
casava as passadas extra (aperto por largura · forced-colors) com os grupos da passada principal
**por posição no array** — e consultas diferentes produzem ordens diferentes: o operador nativo do
§53 nem aparecia na segunda consulta, e o índice escorregou, atribuindo a um grupo a medida de
outro. E o detector do `"+N"` procurava `[class*=mais-n]`, um nome que a guarda inventou e que não
batia com o `chips-mais` do artefato — **guarda que procura um seletor que ninguém combinou
reprova o conserto correto**. Consertos: casar por **chave estável**, e procurar o **contrato**
(`aria-expanded` na fileira) em vez do nome.

> **Regra nova: guarda que casa dois conjuntos por índice não é guarda, é coincidência.** Irmã da
> regra da v9.4 (*guarda que depende da ordem de execução das outras não é guarda, é sequência*) e
> da v8.9 (*o gate abriu o arquivo errado*): as três são a mesma doença — **veredito correto sobre
> o objeto errado**.

#### (g) O QUE FOI EXECUTADO — e onde

Tudo no **molde e no gerador**, nunca no HTML gerado: artefato editado à mão volta atrás no
próximo `gen-*`.

| Arquivo | O que mudou |
|---|---|
| `validacao/painel-template.html` | Os QUATRO grupos viram `radiogroup`; **um** helper de roving (`marcar` + `rovingTeclado`) substitui três mecanismos; `@media (forced-colors: active)`; anel de foco do `[role=radio]`; `lenteAtual()` passa a ler `aria-checked` |
| `validacao/gen-painel.py` | Selo do preview **v0.10 → v0.11** |
| `validacao/suite-painel.mjs` | `PN6-01` superseded por 6 guardas · `PN6-02`/`AC-01` com o seletor novo · **`PN6-03`** (forced-colors) |
| `validacao/tela-tabela-template.html` | `.chip .x{position:relative}` + `::before` centrado · `"+N"` com `TETO_CHIPS` declarado · anúncio com contagem · `"Limpar"` com piso de 2 |
| `validacao/tela-lista-template.html` | X da condição a 44px com dono posicionado · segmento do operador a **44px** · `@media (forced-colors)` · anúncio com contagem |
| `validacao/gen-tela-lista.py` | Nome do radiogroup ganha *"— aplica na hora"* (SG4) · selo **0.1 → 0.2** |
| `validacao/gen-tela-tabela.py` | Selo **0.1 → 0.2** |
| `validacao/suite-escolha.mjs` | Guarda `SG2/SG1` prova exclusividade **por clique** · SG5 e SG6 revistos · SG6-b · passadas extra por chave |

**Cinco camadas, depois do conserto:** `suite-escolha` **88 · 0 · 21 [n/a]** (4 arquétipos) ·
`suite-painel` **146 · 0** · `contraste-painel` **78 · 0** · `suite-container` **66 · 0** (2 telas).
*Falta o gate visual do Rafael — a única camada que mede se o controle é OPERÁVEL, e não só
conforme.*

### 60.7 A promoção, e os TRÊS níveis de prova

O gate visual do Rafael saiu em 2026-08-16 sobre os três previews: **"verificado, tudo ok"**. Antes
de escrever `estável`, uma pergunta que o projeto ainda não tinha feito a si mesmo: **quais destes
contratos têm PROVA, e quais têm só texto?**

A resposta obrigou a distinguir **três níveis de evidência**, e a distinção fica registrada porque
eles não valem o mesmo:

| Nível | O que significa | Contratos |
|---|---|---|
| **1 · Consumidor em produção** | O contrato é exercido por artefato que já estava em uso, e a medição o confirmou. É o nível mais forte: ninguém construiu o caso para o contrato passar | SG1 · SG2 · SG4 · SG5 · SG6 · SG6-b · SG8 · SG9 · SG10 · SG11 · SG12 · SG13 |
| **2 · Bancada** | O contrato é exercido por artefato que **nós construímos para prová-lo**. Vale — mas é mais fraco, porque quem escreve o teste e quem escreve o caso são o mesmo | **SG7** · **SG14** |
| **3 · Ausência de violação** | O contrato é uma **proibição**, e a guarda informa que ninguém a viola | **SG3** (zero `tablist` no acervo) |

> **Proibição sem instrumento é só uma frase.** O SG3 existia desde a v0.81 e **nada o media**. Agora
> há guarda, e ela mede a cada rodada em vez de supor. *Passar por ausência é veredito mais fraco que
> passar por instância, e a diferença fica dita.*

#### O SG8 nunca tinha sido testado — e estava violado

*"Segmento com ícone e rótulo visível usa SÓ o rótulo no nome acessível."* O contrato existia desde a
v0.81; nenhum consumidor parecia exercê-lo, então nenhuma guarda foi escrita. Ao escrever a guarda
**para poder promover**, ela achou o caso: o botão **`✋ Mover`** do "Gesto no vazio" levava o glifo
num nó de texto, e o nome acessível saía `"✋ Mover"` — o leitor de tela anunciava *"mão levantada
Mover"*. Conserto: `<span aria-hidden="true">✋</span>`.

> **Regra: contrato sem guarda é contrato sem prova — mesmo quando existe consumidor.** A SG-P1 mediu
> se os consumidores **concordavam entre si**; não mediu se cada contrato **tinha instrumento**. São
> perguntas diferentes, e a segunda só foi feita na hora de promover. *Auditoria de concordância não
> substitui auditoria de cobertura.*

### 60.8 A bancada — e por que o escopo dela é estreito

A `banco-escolha.html` nasce **por último**, e isso é decisão, não atraso. Quando o §60 foi escrito
(v0.81), quatro artefatos já usavam controles de escolha sem spec; a SG-P1 mandou **auditar antes de
produzir**, e a auditoria achou o acervo resolvendo a mesma pergunta de **três jeitos
incompatíveis**. *Produzir a bancada primeiro teria criado a quarta variação, e o §60 teria
documentado uma convergência que não existia.*

Com onze contratos já provados por consumidor real, o que sobrava para a bancada era estreito:

1. **Provar o SG7 e o SG14**, os dois sem instância no acervo.
2. **Fechar a última divergência de geometria** — o raio.
3. Servir de **referência única do mecanismo de roving** (antes eram três mecanismos).

**Placar: 137 PASS · 0 FAIL · 0 `[n/a]`.** É o único artefato do acervo em que **nenhum contrato sai
`[n/a]`** — o que é o próprio critério de uma bancada estar completa.

#### SG-P9 · o raio, e por que ele vai a gate em vez de eu decidir

A SG-P1 mediu duas geometrias para a mesma família. A **altura convergiu em 44px** (o §53 media 32 e
subiu na SG-P7). O **raio não**: **9999px** (pílula, §48) contra **6px** no item e **8px** no grupo
(reto, §53).

A bancada põe os dois **lado a lado, com o mesmo conteúdo**, e acrescenta uma **terceira fileira
estreita de propósito** — porque o SG5 revisto permite o segmentado **quebrar em linhas**, e é na
quebra que os dois se comportam diferente: o arredondamento total do **grupo** tem de envolver duas
linhas; o reto não tem esse problema.

> **Isto está na bancada para ser OLHADO, não afirmado.** Eu poderia escrever "a pílula fica estranha
> quebrando" — seria opinião com cara de medição. Pôr as duas quebrando lado a lado é medição que o
> olho faz, e o olho é do Rafael. *É o formato que ele mesmo pediu no §49: "me mostre visualmente a
> diferença, é mais fácil para eu decidir".*

### 60.9 SG-P9 fechada — e o que a decisão descobriu

**Decisão do gate, 2026-08-16:** *"pilula contra reto, escolho reto"*. O segmento passa a **6px**, o
valor que já vinha do §53. A família de escolha do SEED fica com **uma geometria**: altura **44px**
(convergida na SG-P7) e raio **6px**.

A regra escrita é **específica** — `.lentes [role="radio"]`, `.escala-ctrl [role="radio"]`,
`.mapa-modos [role="radio"]` —, e não uma troca no `button` genérico do preview.

> **O gate decidiu sobre o SEGMENTADO, não sobre todos os botões do painel.** Trocar o raio genérico
> teria sido mais limpo de escrever e teria mudado dezenas de controles que ninguém avaliou.
> *Alargar a decisão para além do que foi decidido é a forma silenciosa de decidir sozinho.*

#### SG-P10 · aplicar o raio revelou o que o raio escondia

Com o raio igualado, ficou visível que os dois **não desenham a mesma coisa**:

| | `tela-painel.html` (hoje) | `banco-escolha.html` (bancada) |
|---|---|---|
| Fronteira | **uma por segmento** — oito bordas | **uma por grupo** — segmentos sem borda |
| Fundo do grupo | nenhum (fileira nua) | `surface-subtle`, com 3px de respiro |
| Espaçamento | `gap: 6px` entre botões soltos | `gap: 4px` dentro de um contêiner |
| Leitura | oito botões que por acaso estão juntos | **um controle** com oito posições |

Altura igual, raio igual, **anatomia diferente**. É a mesma família de divergência que a SG-P1 achou
e que esta seção inteira passou fechando — só que **uma camada acima**: não no valor, na estrutura.

> **Regra nova: divergência de VALOR esconde divergência de ESTRUTURA.** Igualar o número não iguala
> a coisa. Quando dois artefatos convergem em todas as medidas e ainda parecem diferentes, o que
> resta é anatomia — e anatomia não aparece em placar de contraste, alvo ou raio, porque nenhuma
> guarda mede *"isto lê como um controle ou como oito?"*.

A leitura de conjunto importa: **um controle com oito posições** e **oito botões que por acaso estão
juntos** dizem coisas diferentes ao olho, mesmo com a semântica de `radiogroup` correta nos dois.
Mas mudar a anatomia do §48 altera pixel aprovado em gate, e a decisão é dele.

### 60.10 SG-P10 fechada — a anatomia converge, e três defeitos aparecem no caminho

**Decisão do gate, 2026-08-16: B.** O grupo carrega **uma** fronteira; os segmentos perdem a
própria. O painel, a lista e a bancada passam a desenhar a mesma coisa:

| | Antes | Agora (canon) |
|---|---|---|
| Fronteira | uma por segmento (painel) · `overflow:hidden` com rótulos colados (lista) | **uma por grupo**, nos três |
| Fundo do grupo | nenhum · nenhum | `surface-subtle`, 3px de respiro |
| Segmento | borda e fundo próprios | borda **transparente**, fundo transparente |
| Largura do grupo | `flex` — esticava até o fim da linha | **`inline-flex`** — encolhe para o conteúdo |

> **O operador do §53 tinha uma TERCEIRA anatomia** — contêiner com `overflow:hidden`, `gap:0` e
> rótulos colados de borda a borda. Ele já tinha *um* contêiner, e por isso quase passou.
> *Ter algum contêiner não é convergir; convergir é adotar A MESMA anatomia.*

**Por que `inline-flex`:** com a fronteira do grupo visível, `display:flex` (nível de bloco) esticava
o contêiner até o fim da linha e deixava fundo vazio à direita do último segmento — a fronteira
passava a delimitar a **linha**, não o **controle**. Com `flex-wrap`, a largura disponível segue
sendo o limite, e a quebra medida na SG5 não muda.

#### Os três defeitos que apareceram ao aplicar — e todos foram pegos por guarda

**(1) `AC-08` · token em vez de px.** Eu digitei `8px` e `6px`, e existem
`--seed-radius-md` e `--seed-radius-3` com esses valores. A guarda de higiene é de **classe**: não
interessa que o número esteja certo, interessa que o componente consuma a camada de token — senão a
escala muda e o segmentado não acompanha. Mesma família do GI2.

**(2) Regressão de ESPECIFICIDADE, e ela é silenciosa.** A regra nova
`.lentes [role="radio"] { background: transparent }` tem peso **0,2,0** — o **mesmo** de
`[role="radio"][aria-checked="true"] { background: action-primary }`. Empate resolve por ordem, e a
nova era posterior: **o selecionado perdeu o fundo e ficou idêntico ao repouso.** O placar acusou
separação de **1,00** contra piso **3,00**.

> **Regra nova: regra escrita DEPOIS, com o MESMO peso, desfaz a anterior em silêncio.** Não há erro
> de sintaxe, não há aviso, e a cor certa continua escrita no arquivo — só não vence. *Só a medição
> do valor EFETIVO mostra; ler o CSS mostraria as duas regras, ambas corretas.*

**(3) Sexto defeito do instrumento: `transparent` lido como PRETO.** Com o segmento em
`background: transparent`, o `getComputedStyle().backgroundColor` devolve `rgba(0,0,0,0)` — e a
leitura ingênua disso é preto. A guarda do SG6 passou a comparar `#000000` com `#000000` e reprovou
um artefato correto.

> **Regra nova: quando o contrato é sobre o valor PERCEBIDO, a guarda mede o valor EFETIVO.** O que o
> usuário vê não é o fundo declarado: é o fundo composto com o que aparece por baixo. A guarda agora
> **sobe pelos ancestrais compondo o alfa**, que é o que o olho faz. *Mesma família do `textContent`
> tratado como nome acessível (v10.2) — ler o valor declarado quando o contrato é sobre o percebido é
> medir outra coisa com o nome certo.*

### 60.11 Pendências

**Nenhuma.** As dez pendências do §60 fecharam em 2026-08-16.

| # | Pendência | Como fechou |
|---|---|---|
| **SG-P1** | Auditar os consumidores existentes | 6 controles em 4 arquivos; instrumento permanente |
| **SG-P2** | Bancada `banco-escolha.html` | 137 · 0 · 0 — SG7 e SG14 provados |
| **SG-P3** | Excedente em tela estreita | **Respondida por medição:** não há excedente |
| **SG-P4** | Converter as lentes do §48 | 8 tab stops → 1 |
| **SG-P5** | Escala, gesto e modo do mapa | um helper de roving para os quatro |
| **SG-P6** | Revisar SG5 e SG6 | SG5 cede com número; SG6 **não** cede — vira SG6-b |
| **SG-P7** | Alvos, anúncios, teto e piso | 24→44px · 32→44px · `"+N"` · contagem · piso de 2 |
| **SG-P8** | Gate visual dos três previews | *"verificado, tudo ok"* |
| **SG-P9** | Raio do segmentado | *"escolho reto"* — 6px |
| **SG-P10** | Anatomia do grupo | *"B com certeza"* — fronteira por grupo |

> **Gate pendente:** os previews mudaram depois do gate da SG-P8 — `tela-painel` **v0.14**,
> `tela-lista` **v0.3**, `banco-escolha` **v0.2**. As mudanças foram **autorizadas** (SG-P9 e SG-P10),
> mas *autorização não é verificação*: o olho ainda não viu o resultado montado. Fica como
> conferência de abertura da próxima sessão, não como pendência do §60.
>
> **Atualização 2026-08-16:** a `banco-escolha` está hoje na **v0.4** — barra de controles fixa (BT1),
> seletor de largura (BT2) e conserto da contagem no tema escuro, que media **1,00** e passou a medir
> **4,60 / 7,39**. O re-gate destas versões é a **BT-P1**, no §64.6, para não haver duas listas
> disputando a mesma pendência.



| # | Pendência | Estado | Nota |
|---|---|---|---|
| **SG-P1** | Auditar os consumidores existentes | ✅ **FECHADA** | 6 controles em 4 arquivos |
| **SG-P2** | Bancada `banco-escolha.html` | ✅ **FECHADA** | 137 · 0 · 0 — SG7 e SG14 provados |
| **SG-P3** | Excedente em tela estreita | ✅ **RESPONDIDA por medição** | Não há excedente: quebra em linhas mantendo 62×44px |
| **SG-P4** | Converter as lentes do §48 | ✅ **FECHADA** | 8 tab stops → 1 |
| **SG-P5** | Converter escala, gesto e modo do mapa | ✅ **FECHADA** | um helper de roving para os quatro |
| **SG-P6** | Revisar SG5 e SG6 | ✅ **FECHADA** | SG5 cede com número; SG6 **não** cede — vira SG6-b |
| **SG-P7** | Alvos, anúncios, teto e piso dos removíveis | ✅ **FECHADA** | 24→44px · 32→44px · `"+N"` · contagem · piso de 2 |
| **SG-P8** | Gate visual dos três previews | ✅ **FECHADA** | *"verificado, tudo ok"* |
> **Gate pendente:** os previews mudaram depois do gate da SG-P8 — `tela-painel` **v0.14**,
> `tela-lista` **v0.3**, `banco-escolha` **v0.2**. As mudanças foram **autorizadas** (SG-P9 e SG-P10),
> mas *autorização não é verificação*: o olho ainda não viu o resultado montado. Fica como
> conferência de abertura da próxima sessão, não como pendência do §60.
>
> **Atualização 2026-08-16:** a `banco-escolha` está hoje na **v0.4** — barra de controles fixa (BT1),
> seletor de largura (BT2) e conserto da contagem no tema escuro, que media **1,00** e passou a medir
> **4,60 / 7,39**. O re-gate destas versões é a **BT-P1**, no §64.6, para não haver duas listas
> disputando a mesma pendência.



| # | Pendência | Estado | Nota |
|---|---|---|---|
| **SG-P1** | Auditar os consumidores existentes | ✅ **FECHADA** | 6 controles em 4 arquivos |
| **SG-P2** | Bancada `banco-escolha.html` | ✅ **FECHADA** | 137 · 0 · 0 — SG7 e SG14 provados |
| **SG-P3** | Excedente em tela estreita | ✅ **RESPONDIDA por medição** | Não há excedente: quebra em linhas mantendo 62×44px |
| **SG-P4** | Converter as lentes do §48 | ✅ **FECHADA** | 8 tab stops → 1 |
| **SG-P5** | Converter escala, gesto e modo do mapa | ✅ **FECHADA** | um helper de roving para os quatro |
| **SG-P6** | Revisar SG5 e SG6 | ✅ **FECHADA** | SG5 cede com número; SG6 **não** cede — vira SG6-b |
| **SG-P7** | Alvos, anúncios, teto e piso dos removíveis | ✅ **FECHADA** | 24→44px · 32→44px · `"+N"` · contagem · piso de 2 |
| **SG-P8** | Gate visual dos três previews | ✅ **FECHADA** | *"verificado, tudo ok"* |
| **SG-P9** | RAIO do segmentado | ✅ **FECHADA** | *"escolho reto"* — 6px. A pílula fica na bancada marcada como `superseded`, porque **decisão sem a evidência ao lado vira folclore em três meses** |
| **SG-P10** | **ANATOMIA do grupo: fronteira por segmento ou fronteira por grupo** | **ABERTA — única do §60** | Descoberta ao aplicar a SG-P9: raio e altura iguais, estrutura diferente. O painel desenha oito botões com borda própria; a bancada desenha um contêiner com segmentos sem borda. Decisão de gate — altera pixel aprovado, e nenhuma guarda existente mede "isto lê como um controle ou como oito?" |



## 61. Data, intervalo e hora — **`estável`** (F7.5 · promovida em 2026-08-17 por gate **declarado**, verbatim *"aprovo. siga"* · **RE-GATE OBSERVADO em 2026-08-17: a BT-P1 e a DH-P6 FECHAM — insumo nomeado, ver §64.10.1**) · DH1–DH18 · fecha C1 e C2 · bancada `banco-data.html` **v0.4** *(errata corrigida: este cabeçalho declarava v0.5, versão que nem o artefato, nem o gerador, nem o MANIFESTO, nem o mapa de cobertura têm — §64.10.3)* · `suite-data.mjs` **31 · 0 · 1 [n/a]** · **BT7 consertado em 2026-08-17** (178px de excesso a 390px: o calendário empilha os atalhos abaixo de 470px, com exceção declarada ao piso de 44px na célula do dia)

> **Rodada zero: NÃO VISITADO — e a visita mudou a seção.** O dispositivo **existe** no produto de
> referência (o §13 já tinha lido a data *renderizada* — C80, cor por estado), mas o **seletor**
> nunca tinha sido aberto. Navegação autorizada, modo somente leitura: o seletor foi aberto, lido e
> fechado; **nenhuma data foi escolhida, alterada ou limpa**. Oito dispositivos observados,
> **C117–C124**, no §15 do `estudo-clickup-completo.md`.
>
> **E a volta estreou um método que rendeu na primeira tentativa.** Além da captura de tela, foi
> lida a **árvore de acessibilidade** da mesma região. As duas **não coincidem** — e o achado é o
> **C122**, negativo: cada dia é um `button` cujo nome acessível é **só o número** (`button "2"`),
> sem `role="grid"`, sem `gridcell`, sem `columnheader`, e **sem `aria-selected` no dia escolhido**.
> Na tela o dia 31 tem fundo sólido; na árvore ele não carrega estado nenhum.
>
> *Copiar a forma teria copiado o defeito, porque o defeito não aparece na captura.*
>
> **Base (3 rodadas, 2026-08-16), e o canon diverge em TRÊS.**
> **(a) ARIA APG** — calendário é `role="grid"` sobre `<table>` (`row`/`columnheader`/`gridcell` vêm
> implícitos de `tr`/`th`/`td`); cabeçalho de coluna com `abbr` trazendo o dia por extenso;
> `aria-selected="true"` **só** na célula escolhida; com campo vazio, o foco entra no dia de hoje;
> teclado ←/→ dia, ↑/↓ semana, Home/End na semana, PageUp/PageDown mês, Shift+PageUp/Down ano; e o
> campo de texto recebe `aria-describedby` apontando para a descrição do formato.
> **(b) GOV.UK / NHS** — **não** use calendário quando o usuário **já sabe** a data: use campos de
> texto, com `inputmode="numeric"` e `autocomplete` (`bday-day`/`bday-month`/`bday-year`) para o
> SC 1.3.5. O calendário é para quando a data **não é sabida**.
> **(c) USWDS** — faz os dois (campo + botão de calendário), e monta o intervalo como **dois
> seletores independentes**, com `data-min-date`/`data-max-date`. Admite duas coisas: que **não
> valida a coerência** entre início e fim, e que o componente passa no SC 3.3.2 **com ressalva** —
> falta instrução visível de como usar o botão de calendário.
>
> **R3 normas:** SC 3.3.2 (o formato esperado tem de estar **visível**, não só no placeholder) ·
> SC 1.3.5 (identificar o propósito do campo) · **SC 3.3.7 Redundant Entry, nível A** (o que o
> usuário já informou na sessão não se pede de novo).

### 61.1 A régua: pergunte se o usuário JÁ SABE a data

A divergência não tem "jeito certo" — tem **dois problemas diferentes** que a mesma forma serve, e
foi assim que o §60 se resolveu. Aqui a pergunta é outra:

| Se o usuário… | Então o caminho primário é… | E o outro… |
|---|---|---|
| **já sabe** a data (nota fiscal, nascimento, prazo em contrato) | **digitar** — teclado é mais rápido que caçar num calendário | o calendário fica disponível, e ninguém é obrigado a abri-lo |
| **não sabe** e precisa do contexto (que dia da semana? quantos dias faltam? cai no fim de semana?) | **o calendário** — a pergunta que ele responde não cabe num campo de texto | o campo continua lá, aceitando digitação |

> **Os dois existem SEMPRE, e nenhum é opcional.** O GOV.UK está certo sobre a data sabida e o APG
> está certo sobre a data descoberta; escolher um dos dois seria acertar metade dos casos.
> **No ERP da SEED os dois casos convivem na mesma tela:** a data de emissão de uma nota o usuário
> sabe e digita; o agendamento de uma OS depende de que dia da semana cai e de quem está livre.

### 61.2 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **DH1** | **O campo de TEXTO existe sempre e aceita digitação.** O calendário é complemento, nunca substituto | Calendário sozinho. Para data sabida, obriga a navegar meses para chegar onde a digitação chegaria em 8 teclas |
| **DH2** | **O calendário existe sempre e é alcançável por teclado.** Gatilho com nome próprio, não um ícone mudo | Campo de texto sozinho. Ele não responde "que dia da semana é" nem "quantos dias faltam" |
| **DH3** | **O formato esperado é VISÍVEL** (SC 3.3.2) — em texto de apoio, não só no placeholder, que **some ao digitar** justamente quando o usuário mais precisa dele | Formato só no placeholder. É a ressalva que o próprio USWDS registrou contra si |
| **DH4** | **pt-BR: entrada em `dd/mm/aaaa`**, `inputmode="numeric"`, e o parser aceita separador `/`, `.` ou `-`. Exibição pode ser compacta; **entrada não** | Aceitar `mm/dd`. Herda o **VD5** — em pt-BR `1240.5` é doze mil quatrocentos e cinco, e a mesma armadilha de locale vale para data |
| **DH5** | **A grade é `role="grid"` sobre `<table>`**, com `<th>` para os dias da semana e `abbr` com o nome por extenso | Pilha de `<button>`. É exatamente o que o produto de referência faz (**C122**) |
| **DH6** | **O nome acessível de cada dia é COMPLETO** — dia, mês, ano e dia da semana —, nunca só o número | `button "2"`. **Contraexemplo medido no C122:** o leitor de tela anuncia "botão 2", sem mês, sem ano, sem saber o que já está escolhido |
| **DH7** | **`aria-selected="true"` só na célula escolhida**; **hoje** se marca por `aria-current="date"` — são coisas diferentes e precisam de marcas diferentes | Marcar hoje e o escolhido do mesmo jeito. Quem não vê perde a diferença entre "onde estou" e "o que escolhi" |
| **DH8** | **Teclado do APG, inteiro:** ←/→ dia · ↑/↓ semana · Home/End na semana · PageUp/PageDown mês · Shift+PageUp/Down ano · Enter/Espaço escolhe · Esc fecha **sem escolher** | Só setas. Sem PageUp/Down, mudar de ano custa dezenas de teclas |
| **DH9** | **A grade tem UM tab stop** (roving), como o SG1 | Um tab stop por dia. Quarenta e dois tab stops para atravessar um calendário |
| **DH10** | **Intervalo é UM controle: dois campos de texto e UM calendário compartilhado** | **Dois seletores independentes** (USWDS). Separados, ninguém é dono da relação — e o próprio USWDS admite que **não valida a coerência entre início e fim** |
| **DH11** | **A coerência início ≤ fim é validada, e a mensagem diz QUAL dos dois corrigir** e sugere a correção | "Intervalo inválido". Herda o **VD11**: erro que identifica o problema E sugere a saída |
| **DH12** | **O vão entre os extremos é DESENHADO na grade**, e os dois extremos se distinguem do meio | Marcar só as pontas. *Contrato **decidido sem leitura** — ver §61.4* |
| **DH13** | **A hora é campo SEPARADO e opt-in**; não existir hora não é erro, e existir o campo não a torna obrigatória | Data e hora no mesmo campo. **C118**: no produto de referência a hora é um terceiro campo de texto, e a data não a carrega por padrão |
| **DH14** | **ISO 8601 na camada de dado; a string ISO NUNCA passa por `new Date(string)`** | Confiar no parser do agente de usuário. Regra já canônica do projeto — `new Date("2026-08-16")` desloca por UTC e devolve o dia anterior a oeste de Greenwich |
| **DH15** | **Atalho relativo mostra a data ABSOLUTA que resolve**, ao lado do rótulo | "2 semanas" sozinho. **C119**: o relativo é ambíguo, e o produto responde mostrando o resultado antes do clique. Irmão do **FC5** — o número é a prova de que o comando fez o que diz |
| **DH16** | **"Hoje" é o hoje do USUÁRIO, e o fuso fica declarado** quando o dado é compartilhado | Hoje do servidor. Numa empresa em MG, ES e BA o fuso é o mesmo, mas o dado que sai em relatório não pode depender disso |
| **DH17** | **Recorrência NÃO é deste componente** | Regra de recorrência dentro do seletor. **C124**: até o produto de referência a põe atrás de um passo a mais |
| **DH18** | **Data já informada na sessão não se pede de novo** (SC 3.3.7, nível A): o segundo campo pré-carrega ou oferece o valor | Redigitar. É critério de nível A, o mais baixo — falhar nele é falhar no piso |

### 61.3 A fronteira: o que este componente NÃO decide

* **Regra de negócio de prazo** — o que conta como atraso, se sábado é dia útil, qual feriado vale.
  O componente exibe e coleta; quem decide é o produto.
* **Recorrência** (DH17) · **fuso como configuração** · **calendário de trabalho**.
* **Conversão de unidade de tempo** — herdado do VD15: conversão é regra de engenharia.

### 61.4 O contrato que nasce SEM LEITURA, e isso está dito

O **DH12** — como o seletor desenha o vão entre início e fim — **não pôde ser observado**. A tarefa
aberta tinha só a data de vencimento, e **preencher a data inicial seria escrita**, fora do modo
autorizado. Não havia outra instância à mão com os dois extremos.

> **Achado negativo é achado.** O DH12 fica marcado como **decidido por canon e norma, sem leitura
> visual** — e é o único dos dezoito nessa condição. Se uma volta futura achar uma instância com
> intervalo preenchido, ele é o primeiro a ser reconferido. *Registrar de qual contrato não se tem
> leitura é o que separa uma spec incompleta de uma spec que não sabe onde está incompleta.*

### 61.5 Números — a MEDIR na produção da bancada

Método do §55: a seção nasce só com **relações**. Ficam a medir: altura e alvo da célula de dia ·
largura da grade · espaçamento entre semanas · o par de cor de hoje, do escolhido, do vão, do
hover e do foco, **nos dois temas e em `forced-colors`** (herdando o **SG6-b**) · alvo do gatilho do
calendário · e a distinção entre dia do mês corrente e dia vizinho.

**Nenhuma medida é afirmada aqui.**

### 61.6 A bancada — e as três decisões que ela obrigou

**`banco-data.html` · `suite-data.mjs` 25 PASS · 0 FAIL · 1 `[n/a]`.**

#### (a) O "hoje" da bancada é FIXO, e isso é decisão, não conveniência

Um calendário que consulta o relógio **muda de bytes todo dia**. Isso quebraria a âncora
(bytes + MD5) que este projeto usa para provar que um arquivo é o que se pensa que ele é, e nenhuma
guarda poderia afirmar *"o dia 16 está marcado como hoje"*. A data de referência entra pelo
**gerador, declarada** — nunca de `Date.now()`.

> *É a mesma razão pela qual as suítes esperam por CONDIÇÃO e não por tempo: estado que depende do
> relógio não é verificável.* O selo da bancada diz qual é o "hoje" dela, na primeira linha.

E o intervalo de exemplo é escolhido por **asserção**, não por gosto: o gerador **aborta** se `hoje`
não cair **dentro** do intervalo, porque é a única forma de provar, **na mesma imagem**, que "hoje" e
"escolhido" têm marcas diferentes (DH7).

#### (b) Dois alvos abaixo do piso, e o motivo do primeiro vale como regra

O gatilho do calendário media **40px**. A caixa declarava `min-height: 44px` — mas isso é a altura
**externa**: descontadas as bordas e o alinhamento, o botão ficava com 40. A célula de dia media
**42px**.

> **Regra: alvo se mede no retângulo do CONTROLE, não no contêiner que o abriga.** Declarar 44 no pai
> é a versão sutil do defeito do `::before` da SG-P7 — o número certo, no elemento errado.

#### (c) A ambiguidade que o DH15 existe para expor apareceu na própria bancada

Dois atalhos de nomes diferentes — **"Amanhã"** e **"Próxima segunda"** — resolvem para a **mesma
data**, porque o "hoje" fixado cai num domingo. A suíte registra isso como medida, não como falha:
*é exatamente a ambiguidade que torna a data resolvida necessária.* Sem ela, o usuário clicaria em
dois botões diferentes esperando resultados diferentes.

### 61.7 O gate achou um calendário inoperável — com 25 PASS · 0 FAIL

**Verbatim, 2026-08-16:** *"não consegui ter ação no calendário. não consegui fazer seleção de
intervalo de data com o mouse, nem clicar em nenhuma data, quanto mais fazer o teste com tab"*.

A `banco-data.html` v0.1 tinha a grade **estática**, emitida em HTML pelo gerador. O clique num dia
movia o foco e escrevia numa live region — **nada mudava na tela**. Não dava para escolher um
intervalo, não dava para mudar de mês, e o gatilho do calendário não abria nada.

> **Uma grade estática não é um seletor de data — é a FOTO de um.** E a foto passou em vinte e cinco
> guardas, porque todas elas mediam **marcação**: papel, nome, estado, alvo, teclado sob foco
> programático. *Nenhuma perguntou se o usuário consegue usar.*

#### E a guarda que devia pegar isso tinha sido escrita para não olhar ali

A `suite-data.mjs` v0.1 tinha uma guarda `BTN` — *"nenhum botão morto"*, regra do projeto desde
sempre. E ela excluía, por escrito:

    !b.closest('table[role="grid"]') && !b.closest('.cal__nav') &&
    !b.classList.contains('cal__hoje') && !b.classList.contains('cd__gatilho')

**Os quatro lugares onde o defeito estava.** Eu escrevi a exclusão porque aqueles botões "não são
CTA comum" — e com isso removi do exame exatamente o que precisava ser examinado.

> **Regra nova: guarda escrita para NÃO OLHAR onde o defeito mora é pior que guarda nenhuma.** Sem
> guarda, a pergunta continua aberta. Com uma guarda que desvia o olhar, o verde **impede a
> pergunta**. *Toda exceção numa guarda precisa de motivo escrito — e o motivo não pode ser "esse
> caso é chato de medir".*
>
> **E há exclusão legítima:** a guarda nova exclui `[disabled]`, porque a plataforma **define** que
> botão desabilitado não responde. *Excluir pelo que o contrato diz é método; excluir por onde o
> defeito mora é fuga.*

#### As cinco guardas que nascem disso

| # | O que mede, AGINDO |
|---|---|
| **DH-OP1** | clicar num dia muda estado **observável** — não só a live region |
| **DH-OP2** | o vão é **pré-visualizado no ponteiro**, antes do segundo clique |
| **DH-OP3** | o segundo clique fecha o intervalo **e escreve nos campos de texto** |
| **DH-OP4** | a navegação de mês **muda o mês**, e "Hoje" volta |
| **DH-OP5** | o atalho relativo **preenche**, não só anuncia |

E a `BTN` passa a clicar em **todo** botão do componente, zerando a live region antes de cada clique.

#### Dois defeitos de instrumento a mais, e um de artefato achado pelo teste

**Nono (instrumento):** a exclusão da `BTN`, acima. **Décimo (instrumento):** a primeira versão da
`BTN` nova media *"o texto do anúncio MUDOU"* — e dois campos de mesmo rótulo dizem legitimamente a
**mesma frase**. Ela acusou quatro botões corretos de mudos. *Comparar com o valor anterior confunde
"não respondeu" com "respondeu igual".* Agora a região é **zerada** antes de cada clique: o teste é
**presença**, não diferença.

**E um defeito de artefato que só um teste de comportamento acha:** a prévia do vão chamava
`desenha()` no `mouseenter`, e `desenha()` reescreve o `innerHTML` — **o elemento sob o cursor era
destruído**, o `mouseenter` disparava de novo no elemento novo, e o calendário entrava em laço,
tremendo. *Redesenhar em resposta ao ponteiro destrói o alvo do próprio ponteiro.* A prévia passou a
**mutar atributos** das células existentes, sem recriar nada.

#### E o calendário foi para o TOPO da página

Ele vinha depois de sete campos de exemplo — **cerca de trinta e três tab stops**. Agora são
**quinze**, e a grade é a primeira coisa substantiva do arquivo. *O que a bancada existe para provar
vem primeiro; o catálogo de estados vem depois.*

### 61.8 Nível de prova, declarado

Aplicando a régua da v10.2 — *ao promover, declare o NÍVEL de prova de cada contrato*:

| Nível | Contratos |
|---|---|
| **1 · Consumidor em produção** | **nenhum.** Nenhum artefato do acervo tem seletor de data |
| **2 · Bancada** | DH1–DH16 — provados pela `banco-data.html`, que **nós** construímos para prová-los |
| **3 · Ausência de violação** | DH17 (recorrência não vive no componente) |
| **Fora do componente** | DH18 — SC 3.3.7 é contrato de TELA: o componente aceita valor inicial; quem o pré-carrega é a tela |

> **A seção segue `rascunho`, e não é formalidade.** Sem consumidor em produção e sem gate visual,
> o que existe é um componente que passa nos testes que ele mesmo trouxe. *Placar verde não diz se o
> contrato sobreviveu ao mundo ou só ao caso que escrevemos para ele.*

### 61.8 Pendências

> **Aviso de supersede (2026-08-17):** a linha **DH-P6** desta tabela está **SUPERSEDIDA** pelo
> **§64.10.4** — ela FECHOU com o re-gate **observado** da `banco-data` **v0.4**. A tabela abaixo fica
> como histórico; o estado vigente é o do §64.10.4.

| # | Pendência | Por que fica aberta |
|---|---|---|
| **DH-P1** | Bancada `banco-data.html` | ✅ **FECHADA** — 25 · 0 · 1 `[n/a]`. Nível de prova: **2 (bancada)** |
| **DH-P5** | Gate visual na `banco-data.html` **v0.1** | ✅ **FECHADA — e REPROVOU.** *"não consegui ter ação no calendário"*. Cinco guardas de operabilidade nasceram do achado |
| **DH-P6** | **RE-gate** da `banco-data.html` — hoje **v0.4** | O calendário funciona e passa em 31 guardas — mas quem disse que a v0.1 estava quebrada foi o olho, não o placar. Além do conserto de operabilidade, a v0.4 traz barra fixa (BT1), seletor de largura (BT2) e o conserto do **vão branco no tema escuro**, que media **1,02** e passou a medir **10,52 / 7,98** (§64.2). **Nada promove antes.** Mesma pendência que a **BT-P1** |
| **DH-P2** | **DH12 sem leitura visual** | O vão do intervalo não pôde ser observado sem escrever. Reconferir na próxima volta que achar instância |
| **DH-P3** | **Calendário de trabalho e feriado** | Parâmetro do produto, não do DS — irmão do VD-P2 (jornada). Fica nomeado para não voltar como surpresa |
| **DH-P4** | **Máscara durante a digitação** | Mesma pendência do VD-P4: a máscara ao vivo não está implementada, e a decisão de fazê-la vale para os dois |

---


## 62. Seletor de pessoa — **`estável`** (F7.5 · promovida em 2026-08-17 por gate **declarado**, verbatim *"aprovo. siga"* · **GATE OBSERVADO em 2026-08-17: a PS-P4 FECHA — insumo nomeado, ver §64.10.1**) · PS1–PS16 · fecha C4 · C5 e C8 com bloqueio nomeado (PS-P2, que segue ABERTA) · bancada `banco-pessoa.html` **v0.1** *(errata corrigida: este cabeçalho declarava v0.2, versão que nem o artefato, nem o gerador, nem o MANIFESTO, nem o mapa de cobertura têm — §64.10.3)* · `suite-pessoa.mjs` **44 · 0 · 1 [n/a]** · **BT7 consertado em 2026-08-17**

> **Rodada zero: NÃO VISITADO.** O §13 tinha lido o avatar *renderizado* (C85) e o cartão de pessoa
> (C115), mas **nenhum seletor** tinha sido aberto. Navegação autorizada, modo somente leitura:
> menus abertos, lidos e fechados; **nenhuma atribuição feita**. Seis dispositivos, **C125–C130**,
> no §16 do `estudo-clickup-completo.md`. **LGPD:** a lista traz nomes reais de colaboradores;
> nenhum foi transcrito.
>
> **E o achado principal é o mesmo da volta anterior, noutro componente.** Na árvore de
> acessibilidade, cada pessoa da lista é `generic` — **sem `role="listbox"`, sem `role="option"`,
> sem `aria-selected`** em quem já está escolhido —, e o campo de busca é um `textbox` simples,
> **sem `role="combobox"`, sem `aria-expanded`, sem `aria-controls`**. Na tela a lista é exemplar:
> agrupada, com avatares, com o escolhido destacado. Na árvore é um bloco de nomes soltos.
>
> **Dois de dois seletores auditados repetem o padrão** (C122 no calendário, C130 aqui). *A regra da
> quinta volta — onde o dispositivo é de ESCOLHA, leia a ÁRVORE junto com a TELA — deixou de ser
> precaução e virou expectativa.*
>
> **Base (3 rodadas, 2026-08-16).** **R1 · ARIA APG (combobox):** o campo é `role="combobox"` com
> `aria-controls` apontando o popup e `aria-expanded`; **o foco do DOM permanece no campo** e o foco
> da tecnologia assistiva anda por **`aria-activedescendant`**; a opção escolhida leva
> `aria-selected="true"`; teclado ↓/↑ para andar, `Esc` para fechar sem alterar, `Enter` para
> aceitar. Para grupos, o APG traz o exemplo de **listbox com opções agrupadas** —
> `role="group"` com nome próprio. **R2 · mercado:** o produto de referência agrupa em
> "Responsáveis" e "Pessoas", põe **"Eu"** no topo, e usa o mesmo campo para buscar **e convidar**.
> **R3 · normas:** SC 4.1.2 (nome, papel e valor) é o que o C130 viola; SC 2.4.3 (ordem de foco) é o
> que o `aria-activedescendant` preserva.

### 62.1 A régua: quem escolhe pessoa está escolhendo RESPONSABILIDADE

Um seletor de pessoa num ERP de engenharia não é um `select` bonito: **atribuir é transferir
responsabilidade técnica.** Isso muda três coisas em relação a um seletor genérico — quem pode
aparecer na lista, o que acontece quando a pessoa sai da empresa, e o fato de que a escolha
**precisa ser desfazível** e visível a quem audita.

### 62.2 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **PS1** | **O campo é `role="combobox"`** com `aria-expanded` e `aria-controls` apontando a lista | `textbox` solto ao lado de uma lista sem vínculo. É o **C130** medido: sem o vínculo, quem não vê não sabe que existe lista |
| **PS2** | **A lista é `listbox`; cada item é `option` com `aria-selected`** | Itens `generic`. Sem papel, o leitor de tela anuncia nomes soltos em vez de "lista de 8, 1 selecionada" |
| **PS3** | **O foco do DOM fica no campo; a AT anda por `aria-activedescendant`** | Mover o foco real para dentro da lista. Isso quebra a digitação contínua, que é o modo natural de filtrar |
| **PS4** | **Grupos são `role="group"` com nome próprio** ("Já atribuídos", "Equipe") | Cabeçalho só visual. O agrupamento é informação; sem papel ele é decoração |
| **PS5** | **Multi-select produz CHIPS removíveis** — herda SG9–SG13 do §60, inclusive o teto e o `"+N"` | Reinventar. A família de chips já é canon |
| **PS6** | **Teclado: ↓/↑ anda · `Enter` escolhe · `Esc` fecha SEM alterar · `Backspace` no campo vazio remove o último chip** | Fechar aplicando. `Esc` que confirma é a armadilha clássica de picker |
| **PS7** | **O avatar é `aria-hidden`; quem informa é o NOME** | Concatenar. Herda o **SG8** e o defeito real do `✋ Mover` |
| **PS8** | **Avatar sem foto cai para INICIAIS sobre fundo derivado do nome** — determinístico, nunca aleatório | Cor sorteada. A mesma pessoa mudaria de cor entre telas |
| **PS9** | **O indicador de presença, quando existe, tem rótulo textual** | Bolinha muda. Cor sozinha não diz "disponível" |
| **PS10** | **"Eu" aparece primeiro, e é rotulado como tal** | Só o nome próprio. **C127**: o atalho para si mesmo é o caso mais frequente |
| **PS11** | **Buscar e CONVIDAR são ações distintas**, ainda que no mesmo campo: convidar exige confirmação explícita | Convidar por Enter. **C126** mostra os dois no mesmo campo; num ERP, convidar cria acesso — não pode ser efeito colateral de digitar |
| **PS12** | **Pessoa INATIVA continua exibível onde já foi atribuída, marcada como inativa; e não é oferecida para novas atribuições** | Sumir com quem saiu. O histórico de quem assinou o quê é registro técnico — herda o VD16: ausência e vazio não são a mesma coisa |
| **PS13** | **A lista carrega em página, com estado de carregando declarado**; nunca corta em silêncio | Cortar em N sem dizer. Herda a regra do teto declarado (SG11) |
| **PS14** | **Vazio de busca diz o que fazer** ("Ninguém com esse nome. Digite um e-mail para convidar.") | "Nenhum resultado" |
| **PS15** | **Remover uma pessoa anuncia quem saiu e quantos restam** | Remoção silenciosa. Herda o **SG12** |
| **PS16** | **Alvo de 44px por linha da lista**, e a linha inteira é clicável | Alvo só no nome |

### 62.3 A fronteira, e os dois bloqueios nomeados

* **C5 · seletor de entidade colorida** (espaço/lista com glifo) — o seletor vive atrás da ação
  **mover**, que é escrita. A entidade *renderizada* foi vista; o **seletor** não. **Bloqueio
  nomeado: só se alcança escrevendo.**
* **C8 · campo de menção `@`** — abrir a lista de menção exige **digitar no compositor de
  comentário**, e o produto persiste rascunho. **Mesmo bloqueio.**

> *Achado negativo é achado.* Os dois não são "não deu tempo": são **inalcançáveis no modo
> autorizado**, e ficam assim registrados. Quando a spec deles for escrita, ou o modo muda, ou a
> base é canon + norma sem leitura — declarado, como o DH12.

**Também fora:** permissão (quem pode ser atribuído é regra de produto) · notificação ao atribuir ·
carga de trabalho (A15, bloqueado por ambiente desde a quarta volta).

### 62.4 Números — a MEDIR na bancada

Altura da linha · diâmetro do avatar e sua escala de tipo para as iniciais · alvo do X do chip
(herda os 44px do SG10) · contraste das iniciais sobre os fundos derivados, **em todos os fundos que
a derivação puder gerar** · altura máxima da lista antes de rolar. **Nenhuma medida é afirmada
aqui.**

### 62.5 Pendências — a lista de origem (2026-08-16, manhã)

| # | Pendência | Por que nasceu |
|---|---|---|
| **PS-P1** | **Bancada `banco-pessoa.html`**, com gerador, molde e suíte | Nível de prova ao escrever a spec: **nenhum** — não havia consumidor no acervo |
| **PS-P2** | **C5 e C8 sem leitura** | Bloqueio nomeado: só se alcançam escrevendo |
| **PS-P3** | **A derivação de cor das iniciais** | O contraste tem de passar em **todas** as cores que a função puder gerar, não numa amostra. É medição de classe, não de caso |

*O estado ATUAL de cada uma está em §62.7, abaixo — esta tabela fica como registro de origem.*

### 62.6 A bancada `banco-pessoa.html` — e a decisão que torna a PS-P3 DEMONSTRÁVEL

A bancada nasceu em 2026-08-16 com **44 PASS · 0 FAIL · 1 [n/a]** em `suite-pessoa.mjs`, cobrindo
PS1–PS16 mais sete guardas de **operabilidade** (PS-OP1…PS-OP7), duas de `forced-colors` e a guarda
transversal GI2/BT3 do §64.

> **Por que sete guardas de operabilidade, e não só as de estrutura.** Em 2026-08-16 a
> `banco-data.html` **v0.1** passou em **25 PASS · 0 FAIL** com o calendário **inoperante** — nenhuma
> data clicável. Quem reprovou foi o olho do Rafael, não o placar. Desde então, guarda de componente
> de escolha **clica, digita e tecla** antes de aprovar: aqui, ↓ move o cursor, o clique na
> extremidade da linha escolhe, `Esc` fecha, `Backspace` remove, o filtro filtra, o `"+N"` abre e a
> carga em página carrega.

#### A paleta das iniciais é FECHADA — e é isso que fecha a PS-P3

O **PS8** manda que o avatar sem foto caia para iniciais sobre fundo **derivado do nome**,
determinístico. A **PS-P3** exigia mais: *o contraste tem de passar em **todas** as cores que a
função puder gerar, não numa amostra.*

**Alternativa descartada — e é a que a maioria dos produtos usa:** hash do nome → **matiz livre**
(HSL). Ela gera uma classe **infinita** de cores. Contraste de classe infinita **não se mede**: se
torce para dar certo, e no dia em que um nome cair num amarelo claro com tinta clara ninguém vai
saber por quê.

**Decisão (2026-08-16):** o hash indexa um conjunto **FECHADO de oito pares** (fundo, tinta), com os
dois lados na mesma família da rampa. Duas consequências que se ganham juntas:

1. **A PS-P3 vira demonstrável por ENUMERAÇÃO** — oito pares se medem; classe infinita, não.
2. **O par não se rompe na inversão de tema** (contrato **BT3** do §64), porque fundo e tinta são do
   mesmo par fixo. É o caso que o **BT4** tolera de propósito — primitiva em superfície de
   identidade —, com o número **impresso** no placar em vez de tolerado em silêncio.

| Par | Fundo | Tinta | Contraste medido |
|---|---|---|---:|
| turquesa claro | `--seed-turquesa-100` | `--seed-turquesa-900` | **11,37** |
| turquesa médio | `--seed-turquesa-200` | `--seed-turquesa-900` | 9,35 |
| amarelo claro | `--seed-amarelo-100` | `--seed-amarelo-900` | **11,54** |
| amarelo médio | `--seed-amarelo-200` | `--seed-amarelo-900` | 9,73 |
| azul claro | `--seed-azul-100` | `--seed-azul-900` | 11,50 |
| azul médio | `--seed-azul-200` | `--seed-azul-900` | 9,38 |
| cinza claro | `--seed-cinza-100` | `--seed-cinza-900` | 11,49 |
| cinza médio | `--seed-cinza-200` | `--seed-cinza-900` | **9,33** (o pior) |

Piso do projeto para texto: **4,50**. Medido nos **dois temas** — os valores não mudam, e é essa
estabilidade que o BT4 exige que seja impressa.

> **Detalhe de implementação que é contrato, não gosto:** a derivação **não** pode usar o `hash()` do
> Python — ele é randomizado por processo (`PYTHONHASHSEED`), então a mesma pessoa mudaria de cor
> entre duas gerações do mesmo arquivo, que é exatamente o defeito que o PS8 proíbe. A derivação usa
> soma de códigos de ponto: estável, reproduzível em JS, e portanto igual no gerador e em produção.

#### O estado ABERTO é impresso EM FLUXO — e isso é decisão de bancada

A primeira versão da página imprimia os quatro seletores abertos com o popup em
`position:absolute` — que é o comportamento **real** do componente. Resultado observado no render:
cada popup cobriu o cartão vizinho e as duas seções seguintes, e a bancada ficou **ilegível
justamente nos estados que ela existe para mostrar**.

**Regra que fica:** bancada é catálogo de estados **lado a lado** (CP31); sobreposição é
comportamento de **tela**. Os estados impressos abertos vão em fluxo (`.sel--fluxo`), e a
sobreposição real continua exercida pelo **Estado 1**, que abre no clique.

#### Décimo quinto defeito de instrumento — coordenada de elemento recortado

A guarda **PS-OP2** (a linha inteira é clicável) pegava o retângulo da última opção e clicava em
`right − 6` **sem rolar antes**. A lista tem `max-height:264px; overflow:auto`, e a última opção
estava em `y=865`, fora da área visível do popup, que termina em `y=782`. **O clique caiu na página,
não no controle**, e a guarda reprovou um artefato correto com *"o alvo é só o texto"*.

> **Regra nova: coordenada de elemento recortado por contêiner rolável não é a coordenada do
> controle.** Rola-se primeiro, mede-se depois — e a guarda agora **verifica** que o ponto medido cai
> sobre a opção antes de emitir veredito.

### 62.7 Pendências — atualizadas em 2026-08-16

> **Aviso de supersede (2026-08-17):** a linha **PS-P4** desta tabela está **SUPERSEDIDA** pelo
> **§64.10.4** — ela FECHOU com o gate **observado** da `banco-pessoa` **v0.1**. A **PS-P2** segue
> ABERTA e não foi tocada. A tabela abaixo fica como histórico; o estado vigente é o do §64.10.4.

| # | Pendência | Estado |
|---|---|---|
| **PS-P1** | Bancada `banco-pessoa.html`, com gerador, molde e suíte | ✅ **FECHADA** — 44 · 0 · 1. Nível de prova: **2 (bancada)**. Continua sem consumidor em produção, e isso está dito: quem escreve o teste escreve o caso |
| **PS-P3** | A derivação de cor das iniciais | ✅ **FECHADA por DECISÃO + ENUMERAÇÃO** — paleta fechada de 8 pares, medida nos dois temas, pior par **9,33** contra piso 4,50 |
| **PS-P2** | **C5 e C8 sem leitura** | **ABERTA** — bloqueio nomeado: os dois só se alcançam **escrevendo** no produto de referência, e o modo autorizado é somente leitura |
| **PS-P4** | **Gate visual da `banco-pessoa.html` v0.1** | **NOVA, e ABERTA** — quatro camadas verdes não substituem o olho. Foi o gate que reprovou a `banco-data` v0.1 com 25 PASS |


---


### 62.8 PS-P5 — **REVOGADA PELA MEDIÇÃO**: o `.mini-avatar` não é avatar de pessoa

> **Escrito em 2026-08-19, ao executar a PS-P5.** Seção autossuficiente, e ela **supersede formalmente**
> a linha PS-P5 do §64.13.4 e do §69.6.

**O que a pendência mandava fazer.** A PS-P5 nasceu em 2026-08-17, da auditoria transversal do §69, com
este enunciado: *"o `.mini-avatar`, círculo de iniciais de PESSOA, pinta-se com a paleta de ENTIDADE,
quando o §62 fechou paleta própria de 8 pares"*. A decisão do Rafael foi migrá-lo para a paleta de
**PESSOA**, *"não a de entidade do CP23"*.

**O que a medição encontrou, em 2026-08-19, antes de tocar em qualquer coisa:**

| tela | quantos `.mini-avatar` | o que as iniciais são | o que a cor é | o que mais tem |
|---|---|---|---|---|
| `tela-lista` | **ZERO no DOM** | — | — | folha de estilo **sem consumidor**: `.pessoa`, `.mini-avatar` e cinco posições de cor, mantidas por cópia entre telas |
| `tela-tabela` | 6, na coluna **Cliente** | **MA** = Metalúrgica Andrade · **FB** = Fazenda Boa Vista · **HS** = Hospital Santa Rita | posição de **ENTIDADE** (1 a 5) | um **ponto de presença** (`disponivel` / `ocupado` / `ausente`) |
| `tela-referencia` | 5, na coluna **Cliente** | idem | idem | idem |

**Ou seja: ele NÃO é um avatar de pessoa.** Ele carrega o **CLIENTE**, que é uma ENTIDADE, com as
iniciais do cliente. A paleta que ele usa — a de entidade — é **a paleta certa** para o que ele
representa. *Migrar para a paleta de PESSOA seria pintar uma empresa com a cor de uma pessoa.*

> **A PS-P5, como escrita, está REVOGADA. Não se executa uma decisão cuja premissa a medição derrubou.**
> É o mesmo movimento de 2026-08-18, quando o RITO reprovou o conserto que a própria pendência CD-P9
> mandava aplicar. *O que a auditoria viu foi o NOME da classe — `pessoa`, `mini-avatar` —, e o nome
> mentia sobre o dado.*

**O que a medição achou de verdade, e vira pendência nova:**

| # | Pendência que nasce | O que está medido |
|---|---|---|
| **PS-P6** | **Um CLIENTE está desenhado com a anatomia de uma PESSOA.** Círculo de 26px com **INICIAIS** e **ponto de presença**, na coluna Cliente de duas telas `estáveis`. Três problemas, e os três são de **PRODUTO**: **(a)** o **SI1** do §65 descartou explicitamente *"inicial da palavra sobre cor"* como identidade de entidade — *"duas entidades com a mesma letra e cores próximas ficam idênticas, e a inicial não sobrevive a renome"* —, e é exatamente o que está em uso; **(b)** o **ponto de presença** é estado de PESSOA: uma empresa não fica "ocupada"; **(c)** o elemento inteiro é `aria-hidden`, então a presença é comunicada **só por cor** e **para ninguém** — ela não está escrita em lugar nenhum da linha. Vai a gate com recorte, **não se conserta por conta própria** |

**O que FOI feito, e é inerte:** o `tela-lista` teve o bloco `.pessoa` / `.mini-avatar` **removido** —
eram **zero** elementos no DOM, zero em `class="pessoa"` e zero menções em script. **Prova de inércia: 0
de 3.294.000 pixels em 4 comparações, duas execuções.**

> **Regra que fica: REGRA DE ESTILO SEM ELEMENTO É DRIFT ESPERANDO DATA.** Ela não pinta hoje e pintaria
> errado amanhã, e foi por cópia entre telas que ela chegou ali. *Foi essa mesma folha morta que fez a
> auditoria contar o `.mini-avatar` em três telas quando ele existe em duas.*

## 63. Prioridade e escalas ordinais — **`estável`** (F7.5 · promovida em 2026-08-17 por gate **declarado**, verbatim *"aprovo. siga"* · **RE-GATE OBSERVADO em 2026-08-17: a BT-P1 FECHA — insumo nomeado, ver §64.10.1**) · PR1–PR12 · fecha C11 · bancada `banco-prioridade.html` **v0.2** *(errata corrigida: este cabeçalho declarava v0.3, versão que nem o artefato, nem o gerador, nem o MANIFESTO, nem o mapa de cobertura têm — §64.10.3)* · `suite-prioridade.mjs` **18 · 0 · 0** · **BT7 consertado em 2026-08-17** (tabelas em região rolável focável e rotulada)

> **Rodada zero: NÃO VISITADO**, resolvido na sexta volta. Três dispositivos, **C131–C133**.
>
> **Base (3 rodadas).** **R1/R2 · produto de referência:** quatro níveis nomeados com **bandeira
> colorida por nível** — *Urgente* (vermelho), *Alta* (amarelo), *Normal* (azul), *Baixa* (cinza).
> **A escala não é saturação de uma cor só: são quatro matizes**, e o menor usa cinza — "sem ênfase"
> em vez de "menos vermelho". Na árvore é `list` › `listitem` › `button`: **não é `radiogroup`**, e a
> exclusividade não chega à AT. **R3 · PatternFly:** separa **status** (estado do sistema) de
> **severity** (criticidade), e para severity usa escala de até seis pontos —
> *critical, important, moderate, minor, none, undefined* — com **ícones de FORMAS diferentes**,
> peso visual crescente, ordenação do mais severo ao menos, e a regra explícita de que
> *"status e severidade se comunicam melhor pela combinação de texto, cor E ícone"*.

### 63.1 A decisão: quatro níveis, e "sem prioridade" é um DELES

O produto de referência tem um buraco medido (**C133**): o menu **sabe atribuir e não sabe
desatribuir**. O campo exibe *"Vazio"*, e voltar a vazio não é oferecido em lugar nenhum do menu.

> **"Sem prioridade" é um ESTADO da escala, não a ausência dela.** O PatternFly chega à mesma
> conclusão por outro caminho ao incluir `none` entre os pontos. *Escala que não oferece o degrau
> zero obriga o usuário a mentir: ele escolhe "Baixa" para uma tarefa que simplesmente não foi
> priorizada, e o relatório passa a contar prioridades que ninguém atribuiu.*

### 63.2 Os contratos

| # | Contrato | O que ele descarta, e por quê |
|---|---|---|
| **PR1** | **A escala tem quatro níveis mais "Sem prioridade"**, e o zero é oferecido no mesmo menu | Escala sem degrau zero. **C133** medido: o usuário é obrigado a inventar um nível |
| **PR2** | **Escolha exclusiva é `radiogroup`** — herda o SG1 inteiro | `list` de `button`. **C131/C132**: a exclusividade fica no script e não chega à AT |
| **PR3** | **Cada nível se distingue por TEXTO, COR e FORMA** — nunca por dois dos três | Só cor e rótulo. O canon é explícito: os três juntos |
| **PR4** | **As formas são distintas entre si, não a mesma forma em tamanhos** | Bandeira igual em quatro cores. É o que o produto de referência faz, e o daltonismo apaga a escala inteira |
| **PR5** | **A ordem de exibição é do MAIS severo ao menos**, sempre | Ordem alfabética ou por frequência |
| **PR6** | **O nível mais baixo não usa a matiz de alerta** — usa neutro | "Baixa" em vermelho claro. Vermelho pálido ainda lê como alerta |
| **PR7** | **A prioridade do OBJETO e a prioridade PESSOAL são componentes DIFERENTES** | Misturar no mesmo menu. **C132** medido: as duas convivem separadas só por um título, e o usuário não tem como saber qual está mexendo |
| **PR8** | **O valor exibido fora do menu traz rótulo textual**, não só o glifo | Só a bandeira na tabela. Em densidade alta o glifo sozinho vira ruído colorido |
| **PR9** | **Contraste do glifo ≥ 3,00 contra a superfície** e sobrevivendo a `forced-colors` | Confiar na cor da marca. Herda o **SG6-b**, medido |
| **PR10** | **O nome acessível diz o NÍVEL e a escala** ("Prioridade: Alta, 2 de 4") | "Alta". Sem a escala, quem não vê não sabe se alta é o topo |
| **PR11** | **Alterar a prioridade anuncia o valor novo E o anterior** | Mudança silenciosa. É campo que dispara trabalho de gente |
| **PR12** | **A escala é FECHADA e vive no canônico** — produto não inventa nível | Nível customizado por espaço. Escala ordinal comparável entre projetos é o que permite relatório |

### 63.3 Fronteira

Regra de escalonamento (o que acontece quando algo fica urgente), SLA, notificação e ordenação
automática são **produto**. Este componente exibe e coleta um nível.

### 63.4 Números — a MEDIR na bancada

Tamanho do glifo · alvo da linha do menu · os quatro pares de cor contra as superfícies em que o
glifo aparece (tabela, cartão, detalhe), **nos dois temas e em `forced-colors`** · e a distinção
entre os quatro em **escala de cinza**, que é onde a escolha de quatro matizes se prova ou cai.

**Nenhuma medida é afirmada aqui.**

### 63.5 A PR-P2 respondida — e a resposta estava no §45 desde sempre

**A pergunta:** o set canônico de 28 glifos do §45 tem quatro formas suficientemente distintas para
uma escala ordinal? **A resposta, medida no inventário do §45.3: não tem NENHUMA.**

Os 28 são `battery · bolt · building · drop · generator · leaf · lightbulb · lightning-rod · plug ·
recycle · solar-panel · substation · sun · transformer · circle-plus · search · settings ·
arrow-right · dashboard · home · check · email · location · people · phone · calendar · document ·
growth` — glifos de **domínio elétrico** e de **UI básica**. Nenhuma bandeira, nenhum triângulo de
alerta, nenhuma escada de setas.

> **E a decisão já existia — faltava aplicá-la.** A árvore do **NM4** (§45) diz: ① existe no set SEED
> → usa SEED; ② não existe no SEED mas existe no Lucide → **usa Lucide**, o vocabulário utilitário do
> produto; ③ não existe em nenhum → cria no SEED pela régua IC; ④ importar de terceiro set é
> proibido. **Prioridade é UI, não domínio nem marca: caso ②.**
>
> *O set não precisou crescer e a escala não precisou mudar. A PR-P2 parecia exigir uma decisão nova
> e só exigia ler a regra que o §45 já tinha escrito — o que é um bom sinal sobre o §45, e um aviso
> sobre abrir pendência antes de reler o canon de casa.*

Os glifos seguem a régua física do **IC1/IC3** (24×24, stroke 2, cap e join round) — a mesma do
Lucide de propósito, para que os dois convivam sem drift de peso.

### 63.6 A escada, e o que ela descarta

    Urgente  ⌃⌃   Alta  ⌃   Normal  —   Baixa  ⌄   Sem prioridade  ○

**A direção É o dado.** Duplo-acima › acima › plano › abaixo é ordinal **na forma**, então a escala
sobrevive à escala de cinza e ao daltonismo sem depender da cor — que é exatamente o que o PR3 e o
PR4 pedem, e o que a suíte mede comparando o `d` dos paths entre si.

**DESCARTADO: quatro bandeiras em quatro cores**, que é o que o produto de referência faz (C131). Em
cinza as quatro viram a mesma bandeira e a escala inteira desaparece.

**DECIDIDA (PR-P4) no gate de 2026-08-16 — vence o OCTÓGONO.** Verbatim do Rafael: *"o octógono
representa melhor prioridade e urgência"*. O *Urgente* **quebra a escada** e passa a ser um octógono
(o signo de PARE, reconhecido fora deste sistema); os quatro níveis restantes seguem na escada de
direção — acima, plano, abaixo, vazio.

> **SUPERSEDE FORMAL (2026-08-16) — PR4-a.** A escada íntegra de cinco degraus
> (`⌃⌃ · ⌃ · — · ⌄ · ○`), que era o desenho da v0.1 da bancada, fica **SUPERSEDIDA** pela escada com
> topo em octógono (`⯃ · ⌃ · — · ⌄ · ○`). **O que se ganha:** o nível mais alto para de ser "mais um
> degrau" e vira uma exceção visível a três metros de distância — que é o comportamento que a
> urgência precisa ter numa lista longa. **O que se perde, e fica dito:** a ordem entre o octógono e
> o galo duplo deixa de ser dedutível pela forma e passa a depender de convenção aprendida; o
> octógono não é geometricamente "maior" que o galo, é *outra coisa*. **Mitigação medida:** a ordem
> continua legível sem a forma porque a suíte cobra rótulo textual em cada nível (PR5) e a ordem
> declarada no DOM (PR6) — a forma é reforço, não é o único canal.
> **Alternativa descartada, e por quê:** manter a escada íntegra preservava a dedutibilidade, mas
> nivelava o Urgente com os demais exatamente na situação em que ele precisa saltar. **O A/B esteve
> impresso lado a lado na bancada** (formato que fechou a SG-P9) e o gate escolheu com os dois à
> vista, não por descrição.
>
> A escada íntegra **continua impressa** na bancada, rotulada `SUPERSEDIDA`, porque decisão sem a
> evidência ao lado vira folclore em três meses.

### 63.7 O botão de tema estava MORTO — em três bancadas

O botão "Escuro" das bancadas setava `data-tema="escuro"`. **O gêmeo de tokens escuta
`[data-theme="dark"]`**, em inglês, porque o atributo é o gancho *dele*. O botão setava algo que
ninguém ouvia.

Consequências, as duas graves:

1. **Botão morto vestido de PROVA** — em `banco-escolha.html`, `banco-data.html` e
   `banco-prioridade.html`. O projeto proíbe botão morto desde sempre; este passou porque *parecia*
   funcionar (o `aria-pressed` alternava).
2. **A guarda de contraste no tema escuro passava medindo o tema CLARO duas vezes.** Verde sem
   exercer a condição — a mesma família do defeito do UTC (v10.7). Depois do conserto, os números do
   escuro finalmente diferem: **6,20 contra 4,77** no Urgente.

> **Regra nova: atributo de CONTRATO não se traduz.** Este projeto escreve tudo em pt-BR — specs,
> contratos, nomes de guarda, mensagens — e é justamente por isso que o hábito pegou aqui, onde o
> nome pertence a **outra camada**. `data-theme="dark"` é da fronteira com o gêmeo de tokens; o
> idioma dela não é escolha nossa. *O `banco-dominio.html`, escrito antes desta sessão, sempre usou
> o atributo certo — o erro foi meu, e foi por reflexo.*

### 63.8 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **PR-P1** | Bancada `banco-prioridade.html` | ✅ **FECHADA** — 17 · 0 · 0. Nível de prova: **2 (bancada)** |
| **PR-P2** | As quatro formas contra o set do §45 | ✅ **RESPONDIDA** — o set não tem nenhuma, e o **NM4** já mandava usar Lucide. Nem o set cresce nem a escala muda |
| **PR-P4** | **A forma do topo: escada ou octógono** | ✅ **FECHADA no gate de 2026-08-16** — *"o octógono representa melhor prioridade e urgência"*. Supersede PR4-a escrito em §63.6; a escada íntegra segue impressa como comparação |
| **PR-P3** | **Prioridade pessoal** (PR7) | Componente próprio, ainda não especificado. Nomeado para não voltar como surpresa |

---

## 64. Bancada e tema — regras transversais · **BT1–BT6** (F7.5, 2026-08-16) · nascidas do gate de 2026-08-16 · valem para TODA bancada e TODA tela

**O que esta seção é.** As seções anteriores especificam *componentes*. Esta especifica o **suporte**
onde os componentes são avaliados (a bancada) e a **inversão de tema** (claro ↔ escuro), que é
transversal a todos eles. Ela nasceu de um gate visual em 2026-08-16 no qual o Rafael apontou quatro
coisas em quatro capturas de tela — duas de ergonomia da bancada e duas de tema escuro. As quatro
viraram contrato, porque **defeito que o olho pega e que não vira guarda volta**.

> **Vocabulário, para quem chega sem contexto.**
> **Bancada** (`banco-*.html`) = catálogo dos ESTADOS de um componente, feito para avaliar; **tela**
> (`tela-*.html`) = caso de uso montado (lei CP31 do `seed-composicao.md`). **Token primitivo** = um
> degrau da rampa de cor, ex. `--seed-turquesa-100`; ele é um valor fixo e **não muda** quando o tema
> inverte. **Token semântico** = um papel, ex. `--seed-action-primary` ou `--seed-surface-raised`;
> ele **é redefinido** no tema escuro. A regra **GI2** do projeto ("componente consome SEMÂNTICO, não
> primitivo") já existia — o que faltava era alguém medir se ela estava sendo cumprida.

---

### 64.1 Os contratos

| # | Contrato | Por quê |
|---|---|---|
| **BT1** | A barra de controles de **todo artefato produzido** — bancada OU tela — é **fixa no topo**: `position:sticky; top:0`, com faixa de fundo própria e sombra de separação | Verbatim do gate: *"deixe sempre o topo ... sempre travado no topo pra quando rolar a página ele ficar fixo no topo"*. Bancada é comprida por definição — ela imprime todos os estados. Se o seletor de tema rola para fora, avaliar o tema escuro no meio da página exige subir, trocar, descer e reencontrar o lugar |
| **BT2** | Toda **bancada** oferece **seletor de largura de página** — `Total · 1280 · 1024 · 768 · 390px` — limitando o `max-width` do contêiner de conteúdo | Avaliar aperto sem redimensionar a janela. Sem isto, "cabe em 390?" vira uma pergunta que só o autor da suíte consegue responder, e o gate humano fica dependente do placar |
| **BT3** | **O par de estado tem de sobreviver à inversão.** Fundo e texto de um mesmo estado medem **≥ 3,00** de contraste nos DOIS temas | É a formulação MEDÍVEL do GI2. O defeito real nunca é "usou primitiva": é primitiva de um lado e semântico do outro — o par se separa quando um dos dois inverte e o outro não |
| **BT4** | Primitiva de rampa em **superfície de marca** (herói, trilho, selo) é **tolerada** — desde que fundo E texto sejam do mesmo par fixo, e o número seja **impresso** no placar | Superfície de marca é justamente aquilo que não deve inverter. Proibir a primitiva ali seria proibir a marca. Mas tolerar em silêncio faz o achado sumir: o placar imprime `primitiva só em superfície de marca` com o pior par medido |
| **BT5** | **Atributo de CONTRATO não se traduz** — `data-theme="dark"`, não `data-tema="escuro"` | Regra já escrita em §63.7, repetida aqui porque é da mesma fronteira. O idioma do projeto é pt-BR; o gancho do gêmeo de tokens não é escolha nossa |
| **BT6** | Medição de tema roda em **página recém-carregada**, e a suíte declara isso | Número que depende de quais guardas rodaram antes não é medição, é resíduo (a prova está em §64.4) |
| **BT7** | **Nenhuma bancada rola na horizontal a 320px.** O que pode rolar é a parte que exige layout bidimensional — tabela de dados, grade de glifos —, e só dentro de uma **região focável e rotulada** (`role="region"` + `tabindex="0"` + `aria-label`) | **SC 1.4.10 Reflow (AA).** A norma proíbe a **página** rolar nas duas direções e abre exceção nomeada para *"partes do conteúdo que exigem layout bidimensional"*. Mas região rolável **sem foco de teclado não se rola sem mouse**, e sem nome a AT anuncia uma região anônima. Nasceu no rito de promoção de 2026-08-17: **as quatro bancadas novas transbordavam a 320px**, e três delas também a 390 ou 768 — ninguém tinha medido abaixo de 1200 |

> ### SUPERSEDE FORMAL — o estado de VERIFICAÇÃO do BT1 e do BT2 (acrescentado em 2026-08-20, décima parte)
> Esta subseção define os contratos; ela **não** os declara verificados. O estado de verificação de
> cada um, em 2026-08-20, é:
>
> · **BT1 — VERIFICADO POR INSTRUMENTO.** `validacao/guarda-barra-fixa.mjs`, de **alvo de pasta**,
>   mede as quatro cláusulas BT1-a…d em todo `.html` do diretório: **33 PASS · 0 FAIL · 18 [n/a]** a
>   1440×900 **e** a 320×900. **A especificação completa, o censo dos onze reprovados com número, o
>   conserto canônico e a pasta de prova `render-audit/prova-bt1` estão em §74** — que SUPERSEDE
>   qualquer afirmação anterior de que a pendência **BT-P2** estivesse fechada. *A BT-P2 havia sido
>   declarada fechada em 2026-08-17 sobre uma lista escrita à mão de **dez** artefatos, num acervo de
>   **trinta e quatro**; onze reprovavam.*
>
> · **BT2 — NÃO VERIFICADO.** Nenhum instrumento mede se a bancada oferece seletor de largura de
>   página. A `guarda-barra-fixa.mjs` declara explicitamente **não** julgar a composição da barra.
>   Fica como pendência **BT-P3** (§74.9). *Este parágrafo existe para que "BT2 está escrito" nunca
>   volte a ser lido como "BT2 está cumprido" — que é exatamente o erro que a BT-P2 cometeu.*

---

### 64.2 Os dois defeitos de tema escuro que o gate achou — com número

Ambos tinham a mesma raiz e nenhum dos dois foi pego por guarda: as suítes rodavam no tema claro.

**① O vão do intervalo no calendário ficava BRANCO** (`banco-data.html`).
Verbatim: *"veja o anexo do calendário, o período de data fica branco"*. O preenchimento entre a data
inicial e a final usava `--seed-turquesa-100` — primitiva, `#CDF3EC` idêntico nos dois temas. No tema
escuro o número do dia (texto semântico, que inverteu) sobre essa faixa mediu **1,02**. A faixa
aparecia como uma cicatriz clara atravessando um calendário escuro.

*Conserto:* o vão passa a ser derivado de semântico, e por isso inverte junto —
`color-mix(in srgb, var(--seed-action-primary) 20%, var(--seed-surface-raised))`.
*Medido depois:* **#cee6e3 → 10,52** no claro e **#2c494b → 7,98** no escuro.

**② A contagem no segmento selecionado SUMIA** (`banco-escolha.html`).
Verbatim: *"no outro exemplo, a quantidade de item no menu não aparece"*. O número usava
`--seed-turquesa-900` como cor de texto sobre o fundo de ação do segmento selecionado. No escuro o
fundo de ação virou justamente aquele tom: **`#00352F` sobre `#00352F` = 1,00**. Não era "pouco
contraste": era o mesmo valor exato dos dois lados.

*Primeira tentativa, DESCARTADA por medição:* véu de `text-on-brand` a 22% atrás do número. Mediu
**~3,0** — ou seja, **pior que véu nenhum** no tema claro, onde antes havia 4,6. Trocar um problema
de escuro por um problema de claro não é conserto.
*Conserto em vigor:* pílula **vazada** — `background:transparent`, `border:1px solid currentColor`,
`color:currentColor`. Ela herda a cor do texto do segmento, então o par nunca se separa, em nenhum
tema. *Medido depois:* **4,60** no claro e **7,39** no escuro.

> **A lição comum aos dois:** o defeito não é usar primitiva, é **parear** primitiva com semântico.
> Um lado inverte, o outro não, e o par se rompe. Onde os dois lados são do mesmo par fixo
> (superfície de marca), nada se rompe — daí o BT4 tolerar em vez de proibir.

---

### 64.3 A guarda GI2, e as três formas erradas antes da certa

A guarda que nasceu deste achado teve de ser reescrita **três vezes na mesma rodada**. Fica registrado
inteiro porque cada erro é de uma família que já custou caro antes.

**Forma 1 — texto, com recorte por posição.** A guarda procurava `var(--seed-<rampa>-<n>)` no CSS e
recortava o "corpo do componente" com `split('=====').pop()` — supondo que o bloco de tokens vinha
primeiro e que a última fatia entre réguas de comentário era o componente.
*Medido:* essa fatia é **4.393 de 30.016 bytes** em `tela-lista.html` (15%) e **15.283 de 84.617** em
`tela-painel.html` (18%). **A guarda olhava um sétimo do arquivo e declarava "só semânticos" sobre os
outros seis** — inclusive sobre `.rail-item.ativo`, que consome `--seed-turquesa-100/900`.
*Mesma família do 8º defeito de instrumento:* guarda escrita para não olhar onde o defeito mora é
pior que guarda nenhuma. *Correção:* o recorte não é por posição no arquivo, é por **seletor** —
primitiva é legítima onde o token é DEFINIDO (`:root`, `html[data-theme=…]`, `[data-theme=…]`) e é
consumo em qualquer outro lugar.

**Forma 2 — texto, agora enxergando o arquivo inteiro.** Reprovou `tela-painel.html`, que é `estável`
e aprovada pelo olho. *Medido por pixel:* as seis primitivas do painel pintam o herói, o botão de
solução e o selo de versão — superfícies de marca, par fixo dos dois lados, **pior par 4,60**. Não
havia defeito. **Uma guarda que condena artefato correto força conserto errado** — é tão cara quanto
a que não vê o defeito. *Correção:* a pergunta certa não é "existe primitiva?", é "o par sobrevive à
inversão?" — e essa pergunta só se responde MEDINDO nos dois temas (BT3).

**Forma 3 — medição, mas com o fundo lido errado.** O compositor de fundo subia pelos ancestrais
lendo `backgroundColor` — e é **cego a `background-image`**. O trilho lateral das telas é um
gradiente; o compositor pulava-o e devolvia o fundo da página. Resultado: texto branco lido como se
estivesse sobre `#f2f6f9`, **1,09** — um defeito gravíssimo que não existe. O trilho é pintado de
`#005e55`, e o número real é **7,55**. *Correção:* o fundo passa a ser lido do **PIXEL efetivamente
pintado** — captura do elemento e cor modal do histograma.

**Forma 4, em vigor.** Recorte por seletor + medição por pixel nos dois temas + piso 3,00 + o número
impresso mesmo quando passa. Placar depois da correção: `banco-escolha` **238 · 0 · 29**,
`banco-data` **31 · 0 · 1**, `banco-prioridade` **18 · 0 · 0**.

---

### 64.4 Por que a medição roda em página LIMPA (BT6)

Na forma 3 da guarda, `tela-detalhe.html` mediu **2,08** no escuro; medida em isolamento, a mesma
regra no mesmo arquivo mediu **11,37**. A diferença não era do artefato: no topo do laço a suíte
clica `.linha-abrir` para revelar o registro (sem isso o §56 sai como ausente), e a cortina do
registro aberto escurece o trilho por baixo.

Nenhum dos dois números é "errado" — eles medem **estados diferentes**. O erro é a guarda não dizer
qual estado ela mede. Por isso a medição de tema abre **carga nova**, no estado neutro do artefato, e
isso está escrito no código junto da medida.

---

### 64.5 Veredito sobre as quatro telas `estável` — achado NEGATIVO, declarado

A guarda de classe apontou primitiva de rampa em quatro artefatos já `estável`. Medido por pixel, nos
dois temas:

| Arquivo | Seletor | Claro | Escuro | Veredito |
|---|---|---|---|---|
| `tela-painel.html` | `.hero`, `.hero-sub`, `.hero .micro`, `.hero .data-viva`, `.hero-nota` | 4,99 (5,90 no micro) | 4,99 | superfície de marca — par fixo |
| `tela-painel.html` | `.btn-solucao` | 9,37 | 9,37 | superfície de marca — par fixo |
| `tela-painel.html` | `.selo-versao` | **4,60** (pior par do arquivo) | 4,60 | superfície de marca — par fixo |
| `tela-lista.html` · `tela-tabela.html` · `tela-detalhe.html` | `.rail-item.ativo` | 11,37 | 11,37 | trilho de marca — par fixo |

**Nenhum defeito.** Em todos, fundo e texto são primitivas do mesmo par, e o par não se rompe porque
nenhum dos dois lados inverte. As quatro telas seguem `estável` **sem conserto e sem novo gate** — e
o registro fica aqui para que a próxima leitura da guarda não reabra a mesma pergunta.

> **Fato × inferência, declarado:** os números da tabela são FATO (medidos por pixel, Chromium 1194,
> viewport 1440×900, `data-theme` alternado no `<html>`). A afirmação "superfície de marca não deve
> inverter" é DECISÃO de projeto, não medição — ela vem do `marca-seed.md`, e é ela que torna o
> achado tolerável em vez de defeito.

---

### 64.7 O BT7 nasce no rito de promoção — e o que ele diz sobre o seletor de largura

O **BT2** deu à bancada um seletor de largura de página (Total · 1280 · 1024 · 768 · 390). Ele limita
o `max-width` do **conteúdo**, não o **viewport** — e é isso que ele deve fazer, porque o que responde
é o contêiner (CP27).

**Mas ninguém tinha medido o viewport estreito de verdade.** No rito de promoção de 2026-08-17, com
o navegador aberto em 390 e 320px, as **quatro** bancadas novas rolaram na horizontal:

| Artefato | 390px | 320px | Causa |
|---|---|---|---|
| `banco-identidade` | **37px** | **107px** | `table.dados` do contraexemplo + grade de `repeat(7, 44px)` = 348px num contêiner de 288 |
| `banco-credencial` | 0 | **39px** | seis caixas de 44px + 5 calhas = 304px, e a tabela de contratos |
| `banco-edicao-linha` | 0 | **32px** | `grid-template-columns: 180px 1fr` — o `1fr` não encolhe abaixo do `min-content` do gatilho |
| `banco-texto-rico` | 0 | **36px** | `minmax(340px, 1fr)` numa coluna de 288px |

**As três causas, e o conserto de cada uma:**

1. **`minmax(Xpx, 1fr)` força X mesmo sem X disponível.** Conserto: `minmax(min(Xpx, 100%), 1fr)`.
2. **`1fr` não encolhe abaixo do conteúdo mínimo.** Conserto: `minmax(0, 1fr)` na coluna e
   `min-width: 0` no filho que trunca.
3. **Grade de largura fixa não reflui.** Conserto: `repeat(auto-fit, 44px)` com teto igual à largura
   das sete colunas — desenha sete enquanto couber, quebra para menos quando não couber.

> **E o conserto 3 obrigou a corrigir um contrato:** o **SI5** dizia que ↓ desce *"± o número de
> colunas"*, lendo o `data-colunas` fixo. Com a grade refluindo, esse número passa a **mentir** — e
> ele já mentia antes, sempre que o **filtro** deixava menos de sete ícones visíveis. **As colunas
> passam a ser MEDIDAS pelo `offsetTop` dos itens visíveis**, e o nome acessível do grupo passa a
> dizer o número real: *"2 de 28 glifos do set canônico, 2 por linha"*. Guarda nova `SI-OP3b`.
>
> *Regra: número de layout que vive em atributo é cópia — e cópia de layout desatualiza no primeiro
> reflow.*

**Décimo oitavo defeito de instrumento, na mesma rodada.** O script de auditoria de sobreposição
varria `.cx, .amostra-cx, .cx-amostra` — e `.cx` é **cartão** na `banco-texto-rico` e **caixinha de
dígito** na `banco-credencial`. Pior: ele comparava **pai com filho** e chamava conter de sobrepor.
Resultado: **seis achados que não existiam**, em todas as larguras. *Corrigido: o conjunto é só o
cartão, e par em linha de ancestralidade é descartado.* Placar final do render de promoção:
**40 combinações (4 artefatos × 5 larguras × 2 temas) · 0 achados**.

> **Regra: nome curto de classe é ambíguo entre artefatos — guarda transversal seleciona por
> ESTRUTURA, não por nome curto.** É o mesmo defeito de escopo do 8º, 14º e 17º, agora entrando pelo
> seletor.

### 64.8 A varredura FECHA — e o que ela ensinou sobre guarda transversal (2026-08-17, v0.99)

A `validacao/suite-reflow.mjs` é a primeira guarda do projeto cujo **alvo é a pasta**, e não um
artefato. Ela saiu de **82 PASS · 10 FAIL · 40 [n/a] em 33 artefatos** (v0.98) para
**120 PASS · 0 FAIL · 34 [n/a] em 34 artefatos**. O relato completo — com a medida e a alternativa
descartada de cada conserto — está no `validacao/MANIFESTO.md` **§79**. Aqui ficam só as regras que
passam a valer para o sistema inteiro.

**O contrato BT7 se parte em TRÊS medidas**, porque elas falham por motivos diferentes e se consertam
de jeitos diferentes:

| Guarda | Pergunta | Critério |
|---|---|---|
| `BT7-<largura>` | a **página** rola na horizontal a 320 e a 390px? | SC 1.4.10 Reflow (AA) |
| `BT7-teclado` | existe **caminho de teclado** até cada faixa que rola? | SC 2.1.1 Teclado |
| `BT7-nome` | a faixa que **exige** nome — papel `region`/`group`/`tablist`, ou parada de teclado que só existe para rolar — **tem** nome acessível? | SC 4.1.2 / 1.3.1 |

**Como cada uma mede, e por que a forma anterior estava errada (19º e 20º defeitos de instrumento):**

1. **Região rolável se detecta por COMPORTAMENTO, não por classe.** A primeira forma procurava
   `.rolavel`; o `#quadro` do Kanban é região rolável, focável e nomeada desde o **QD8** e **não tem
   essa classe** — saía como `[n/a]` justamente no artefato que mais exercia o contrato. Agora o
   critério é `overflow-x` em `auto`/`scroll` **e** `scrollWidth > clientWidth`.
2. **Nome acessível se CALCULA**, na ordem da especificação *accname*: `aria-labelledby` →
   `aria-label` → `title` → `<caption>`. Três artefatos (`tela-lista` com `rot-lista`, `tela-tabela`
   com `rot-tab`, `banco-dados` com `tbl-title`) nomeiam a região por `aria-labelledby` e eram
   acusados de anônimos. **Falso negativo puro** — um leitor de tela anuncia os três corretamente.
3. **Alcance de teclado tem DUAS formas legítimas.** Ou a caixa é ela mesma parada de teclado
   (`tabindex >= 0`), **ou** ela contém algo focável — o navegador rola a caixa ao mover o foco para
   dentro. Exigir a receita `role="region"` + `tabindex="0"` reprovava o `role="tablist"` do
   `banco-navegacao`, que é operável por *roving tabindex*.

> **Regra que vale daqui para a frente:** *guarda que pergunta "tem este atributo?" está medindo uma
> IMPLEMENTAÇÃO, não um CONTRATO.* Foi o mesmo erro do **18º** defeito (seletor por nome curto de
> classe), do **19º** (região por classe) e do **20º** (nome e foco por receita) — **três vezes no
> mesmo dia**. Medir comportamento custa mais linhas e é a única forma de a guarda sobreviver a uma
> reescrita legítima do artefato.

**Prova de que as duas guardas novas sabem reprovar** (regra do projeto: guarda que nunca reprovou não
é guarda). Injeção controlada, revertida em seguida: removido o `aria-labelledby` de `tela-lista` →
`BT7-nome` **FAIL** (4·1); removido o `tabindex="0"` de um `<pre>` de `banco-dataviz-di` →
`BT7-teclado` **FAIL** (3·1).

**Três armadilhas de CSS que o BT7 expôs, e que valem para qualquer artefato novo:**

- **`1fr` é `minmax(auto, 1fr)`**, e esse `auto` é o piso `min-content`. Coluna que precisa encolher
  abaixo do conteúdo pede `minmax(0, 1fr)` — enquanto o piso estiver lá, **nenhum** ajuste de recuo
  resolve. *(Medido no `banco-tokens`: célula da rampa com piso de 60,81px — 44,81 de texto do hex
  mais 16 de recuo — cinco delas pedindo 304px num contêiner de 272.)*
- **Texto visualmente oculto (`.vh`) em `position:absolute`** sem ancestral posicionado ancora no
  `body`; dentro de uma faixa que rola, ele toma a coordenada **rolada** e **estende o `scrollWidth`
  do documento**. *(Medido: `right=479` num viewport de 390, `right=385` num de 320 — exatamente os
  89px e 65px de excesso das duas telas.)* **O padrão do sistema passa a ser `position:fixed`**, que
  não contribui para o `scrollWidth` em hipótese nenhuma e não muda nada do que a tecnologia
  assistiva lê.
- **Faixa que não cabe se resolve com `flex-wrap`, nunca com `overflow-x`.** Trocar um defeito de
  SC 1.4.10 por um de SC 2.1.1 não é conserto. `overflow-x` só entra quando o conteúdo **exige** duas
  dimensões — e aí vem acompanhado de foco e de nome, que é o que o `BT7-teclado` e o `BT7-nome`
  cobram.

**E duas regras de instrumento, vindas de suítes que ninguém rodava:**

- **Suíte que MORRE é pior que suíte que reprova.** A `render-painel.mjs` procurava a lente ativa do
  painel por `aria-current="page"` **depois** de as lentes terem virado `role="radiogroup"` +
  `aria-checked` (supersede SG1/SG4 do §48). O `querySelector` devolvia `null`, o processo estourava,
  e **tudo que vinha depois deixava de ser medido em silêncio** — os blocos R5b (geometria das
  lentes), R6 (relógio de frescor) e R7 (faixas, carga, mapa) rodaram **pela primeira vez** depois do
  conserto, placar 37·0. *Contrato superado tem de ser caçado em TODOS os instrumentos, não só
  naquele que acusou a mudança.*
- **Teto de largura obtido por ACASO de layout volta a esticar.** A `suite-escala-figura`, rodada pela
  primeira vez nesta edição, reprovou o `FIG-03` do SVG `.gt-conectoras` do gantt: ele passava
  `FIG-01` (não amplia) e `FIG-02` (não espreme) e não tinha **regra** de teto — dependia do atributo
  `width` em px e do pai com `width` em px. No dia em que alguém trocar a largura do pai por `%`, o
  desenho volta a esticar e **as duas guardas que passam continuariam passando**. Conserto:
  `max-width:100%`, que não muda um pixel hoje (medido: 1284px nas três larguras) e muda o que
  acontece depois.

---

### 64.9 O EM-BT7 nasce, e a BANCADA também é artefato (2026-08-17, v1.00)

Esta subseção registra três coisas que passam a valer para o sistema inteiro. O relato completo, com
a medida e a alternativa descartada de cada uma, está no `validacao/MANIFESTO.md` **§80**.

---

#### 64.9.1 **EM-BT7** — o contrato irmão do BT7 para a família de e-mail

**A pendência E-P1 tinha veredito pendente e agora tem veredito: o BT7 vale para e-mail em PARTE —
a régua de largura TRANSFERE, o conserto NÃO TRANSFERE.**

*(Para quem lê sem contexto: **E-P1** era a pendência que perguntava se o contrato de reflow **BT7**
— §64.7, "nenhum artefato rola na horizontal a 320 nem a 390 CSS px", critério SC 1.4.10 da WCAG 2.2
nível AA — se aplica aos 8 artefatos de e-mail do acervo: `et-*` (transacionais), `en-*`
(newsletter/comercial), `ea-*` (assinatura) e `seed-email-*` (base e componentes). A `suite-reflow.mjs`
varre só `banco-*` e `tela-*`; os de e-mail nunca tinham sido medidos. *Ausência de medição, não
aprovação.*)*

| Guarda | Pergunta | Critério | Existe no BT7? | Existe no EM-BT7? |
|---|---|---|---|---|
| `EM-BT7-390` / `EM-BT7-320` | a página rola na horizontal? | SC 1.4.10 via EN 301 549 §10.1.4.10 | sim | **sim** — mesmos números |
| `BT7-teclado` | há caminho de teclado até a faixa que rola? | SC 2.1.1 | sim | **não** — ver abaixo |
| `BT7-nome` | a faixa que exige nome tem nome acessível? | SC 4.1.2 / 1.3.1 | sim | **não** — ver abaixo |
| `EM-BT7-outlook` | — | — | — | **`[n/a]` impresso em toda rodada** |

**Por que a régua transfere.** O **WCAG2ICT** é a ponte oficial do WCAG para fora do navegador: ele
aplica a SC 1.4.10 a documentos não-web trocando "viewport" por *"a área de exibição efetiva do
conteúdo dentro de uma aplicação ou dispositivo"* e **mantendo os 320 CSS px**. A **EN 301 549**,
cláusula 10 (documentos não-web), traz em **10.1.4.10** o mesmo texto, com a exceção nomeada que
inclui **tabelas de dados**. E a técnica canônica de mercado — a **fluid hybrid**, `width:100%` +
`max-width:600px` mais **ghost tables** em condicional `mso` para o Outlook do Windows — existe
justamente **para permitir o reflow**: os 600px do e-mail são um **teto**, não um piso.

**Por que o conserto NÃO transfere.** Os três consertos de CSS da v0.99 — `minmax(0,1fr)`,
`flex-wrap` e `.vh` em `position:fixed` — pressupõem **grid, flexbox e posicionamento**, que o
e-mail não tem. E o remédio do BT7 para a exceção (`overflow-x:auto` + `role="region"` +
`tabindex="0"`) é **pior que o defeito** aqui: o Outlook do Windows roda o motor do Word, que ignora
`overflow`, e vários clientes removem `tabindex` e papel ARIA. Trocaria um defeito **medido** por um
**não mensurável**.

**O remédio do EM-BT7 é geometria dentro da `@media (max-width:620px)`** que a casca canônica já
tem — recuo e corpo menores na tabela de dados. Medido: levou os três artefatos com tabela de
**30px/100px** e **23px/93px** de excesso a **0px/0px** nas duas larguras.

**Exceção a piso declarada com a GEOMETRIA que a obriga:** três colunas de valor **atômico**
(número+unidade, moeda) mais 14px de recuo de cada lado em cada célula = **84px só de recuo**, dentro
de uma faixa de 320 − 40 = **280px**. Por isso o corpo cai a **12px** e o recuo a **6px** — só abaixo
de 620px, só dentro de `[data-tabela-dados]`. Acima de 620 nada muda, e o Outlook ignora `@media`.

**TEMPLATE × AMOSTRA — regra nova de medição.** Molde com sentinela `{{nome}}` **não se mede como
e-mail**: `{{desvio_pct}}%` é um token de 14 caracteres que não quebra e que não existe no e-mail
entregue. Medido no `et-alerta-geracao`: **55px** de excesso a 320 com sentinelas, **0px** com elas
substituídas. Havendo par `-amostra`, é ele que dá o veredito e o molde sai `[n/a]` **com o número
impresso**; não havendo, a suíte substitui e **declara** a substituição.

**FRONTEIRA DE AGENTE, declarada e impressa em toda rodada:** a guarda mede em Chromium. Ela **não
mede o Outlook para Windows**, onde a largura é a da ghost table de 600px e o reflow não acontece.
É o caso da ressalva expressa do WCAG2ICT — *"se um tipo de documento não-web e seus agentes de
usuário disponíveis não suportam reflow, pode não ser possível a um documento desse tipo satisfazer
este critério"*. **Limite do AGENTE, não defeito do artefato.** Isso é gate visual humano.

**Guarda:** `validacao/suite-email-reflow.mjs` — **16 PASS · 0 FAIL · 26 [n/a]** em 14 artefatos ×
2 larguras. Reexecutadas depois do conserto: `suite-email` **179 · 0** · `contraste-email`
**14 pares, 0 reprovações**.

---

#### 64.9.2 A BANCADA também é artefato, e o instrumento dela também tem contrato

As quatro bancadas da BT-P1/PS-P4 passaram o **rito de promoção inteiro** — suíte própria, BT7,
render em cinco larguras e dois temas, contraste, escala de figura — **com um defeito de
acessibilidade dentro**: a fronteira do `<select>` da **barra de provas** media **1,37** no claro
(`#C8D6DF` sobre `#F2F6F9`) e **2,61** no escuro (`#4D606C` sobre `#141D23`), contra o piso **3,00**
da SC 1.4.11.

Nenhuma guarda o via porque **todas olhavam o espécime**. A causa foi a `base-bancada.css` consumir
`--seed-border-default`, que **não é** semântico de fronteira de controle — o próprio gêmeo escreveu
isso na v1.12 ao criar o `--seed-border-interactive`: *"nenhum outro semântico de borda passava no
claro (default 1.49, strong 2.53)"*. *Alternativa descartada:* `--seed-border-strong` (`#90A6B3`),
que mede **2,53** — continua abaixo do piso.

> **Regra nova, transversal:** *a bancada é artefato, e a barra de provas é interface. O que o
> sistema cobra do componente, cobra do suporte onde ele é avaliado.* Mesma família do botão de tema
> que estava morto em três bancadas (§64.2 / MANIFESTO §72): instrumento que não se mede é
> instrumento que mente.

**E a consequência de método, cumprida:** os quatro artefatos mudaram **depois** de as folhas de
captura terem sido geradas. O rito foi **reexecutado** e as folhas **regeradas**, para que o gate
cubra a versão real — em vez de virar errata depois. *"Aprovado" cobre o que foi olhado, não o que
estava por perto* (§64.7 / MANIFESTO §77.4).

---

#### 64.9.3 A FOLHA DE CAPTURA vira parte do rito de promoção

**Contrato novo: bancada não vai a gate sem folha de captura.** Gate sem folha vira "abre o arquivo e
procura", e foi assim que a `banco-data` v0.1 passou em **25 PASS · 0 FAIL** com o calendário
**inoperante** (§61.7) — quem reprovou foi o olho, não o placar.

A folha (`validacao/folha-captura.mjs` → `render-audit/folha-<nome>.html`) imprime cada bloco de
estado **lado a lado nos dois temas**, com o tema **anotado em cada figura** — lei CP31, bancada
imprime **estados**, não fluxo —, e traz no cabeçalho **o que a guarda não consegue medir** e por
isso é o que o olho precisa julgar: legibilidade de **forma**, densidade, ritmo visual, e se o estado
impresso é o estado que o componente realmente tem.

A folha é **DESENHO, não instrumento operável**: sem `button`, sem `input`, sem `tabindex` (§67.4).

**Defeito da própria folha, achado na primeira geração e consertado:** o recorte começava **atrás da
barra fixa** do BT1 e comia o título de cada bloco. A altura da barra passou a ser **medida**, nunca
suposta, e a barra é detectada por **comportamento** (`position` sticky/fixed encostada no topo), não
por nome de classe.

---

#### 64.9.4 SUPERSEDE FORMAL das pendências do §64.6

As linhas abaixo **supersedem** as correspondentes da tabela do §64.6. A tabela antiga fica como
histórico; o estado vigente é este.

| # | Estado em 2026-08-17 (v1.00) |
|---|---|
| **E-P1** | ✅ **FECHADA.** Veredito dado com o RITO de três rodadas (R1 WCAG2ICT · R2 fluid hybrid/Outlook · R3 EN 301 549 §10.1.4.10). Nasce o **EM-BT7** (§64.9.1). 12 artefatos consertados; `suite-email-reflow` 16 · 0 · 26. |
| **CD-P2** | ✅ **FECHADA.** `--seed-field-border` vira **alias** de `--seed-border-interactive`. Medido: **4,39 → 3,13** no claro, **5,05 → 5,05** no escuro; os dois acima do piso 3,00, margem do claro caindo de 1,39 para **0,13** — consequência medida, registrada. O nome local **não** foi apagado: são 32 consumos, e trocar 32 lugares sem gate seria o irreversível onde cabia o reversível (§75). As variantes de estado (`-hover`, `-error`, `-success`, `-warning`) **não** migram: não têm semântico no gêmeo. |
| **CD-P3** | ✅ **FECHADA.** `validacao/auditoria-borda-campo.mjs` criada e executada em 9 artefatos × 2 temas: **10 PASS · 0 FAIL**. O único FAIL de **campo** era o `<select>` da barra de provas (§64.9.2), consertado. |
| **BT-P1 · PS-P4** | ABERTAS **só no gate.** O trabalho de preparação acabou: rito completo nas quatro bancadas e folhas de captura entregues. |
| **BT-P3** | ABERTA, **sem avanço nesta sessão** — a fila foi consumida pelos três primeiros itens da ordem, e os defeitos de instrumento 21º/22º/23º custaram o tempo restante. *Ausência de trabalho, declarada.* |
| **CD-P6** *(nova)* | `--seed-surface-sunken` (`#E3EBF0`) mede **2,80** contra `--seed-border-interactive` — abaixo do piso 3,00. O comentário do gêmeo declara *"seis superfícies, todas ≥3"*; recontado por comando, o tema claro tem **oito**, e três ficam abaixo: `brand` **1,25**, `brand-deep` **1,87** (as duas sob a tolerância do **BT4**, superfície de marca) e `sunken` **2,80** (**não** coberta pelo BT4). **Nenhum campo do acervo está sobre ela hoje — foi medido.** Alterar token do gêmeo pede rodada própria. |
| **CD-P7** *(nova)* | **93 botões** do acervo com borda abaixo de 3,00. **Achado, não veredito:** a SC 1.4.11 exige 3:1 da informação visual **necessária** para identificar o componente, e botão tem rótulo, forma e muitas vezes preenchimento. Decidir se a borda dele é identificador necessário é **desenho**, não medida. *Guarda que condena artefato correto força conserto errado* (§64.3, forma 2 da GI2). |
| **E-P2** *(nova)* | A tabela de dados de e-mail cabe hoje por **geometria**. Ganhando uma quarta coluna, não cabe. A técnica que preservaria o significado ao empilhar — rótulo repetido por linha, porque `::before`/`content` não funciona em e-mail — exige marcação nova no componente EC e gate próprio. |

---

### 64.10 O GATE OBSERVADO das quatro bancadas — BT-P1, PS-P4 e DH-P6 FECHAM (2026-08-17, v1.01)

> **O que esta subseção registra, para quem lê sem ter visto a conversa que a gerou.** As quatro
> bancadas do bloco de entrada e seleção — `banco-escolha.html` (§60), `banco-data.html` (§61),
> `banco-prioridade.html` (§63) e `banco-pessoa.html` (§62) — esperavam, desde 2026-08-16, a única
> coisa que nenhuma guarda automatizada entrega: **o olho do Rafael Sant'Ana, CEO e decisor único**.
> *Bancada*, neste sistema, é o artefato HTML que imprime **todos os estados** de um componente lado a
> lado para inspeção (lei **CP31**: bancada imprime **estados**, não **fluxo** — ela não é uma tela do
> produto). *Gate* é a aprovação humana que autoriza promover uma spec a `estável`. A pendência do
> re-gate das três primeiras chamava-se **BT-P1**; a da `banco-pessoa`, **PS-P4**; e a **DH-P6**
> (§61.8) era a mesma pendência dita do lado do §61, para não haver duas listas divergentes.

---

#### 64.10.1 Por que este gate é OBSERVADO, e não DECLARADO

**A distinção entre gate OBSERVADO e gate DECLARADO existe para tornar errata possível**, e nasceu no
§78 do `validacao/MANIFESTO.md` depois de uma rodada em que quatro seções foram promovidas com um
*"aprovo. siga"* que não dizia **qual artefato** havia sido aberto:

- **gate DECLARADO** — o Rafael aprovou e **não há registro** de qual insumo ele olhou. A promoção
  vale; o que fica registrado é que uma errata futura é possível **sem contradizer** o registro.
- **gate OBSERVADO** — há registro de que o insumo nomeado do gate foi olhado. Errata aqui seria
  contradição, não complemento.

**Este gate é OBSERVADO.** O registro é a própria pergunta que o produziu: a aprovação foi colhida
numa escolha de três opções explícitas — *"Aprovo — gate OBSERVADO"*, *"Aprovo — gate DECLARADO"* e
*"Ainda não olhei — deixa aberto"* — sobre a pergunta *"Você já olhou as folhas de captura das quatro
bancadas (`banco-escolha` v0.4, `banco-data` v0.4, `banco-prioridade` v0.2, `banco-pessoa` v0.1) em
`render-audit/folha-banco-*.html`?"*. **Ele escolheu a primeira.** O insumo está nomeado por arquivo e
por versão, a natureza do gate foi decidida por ele e não por mim, e a alternativa "declarado" estava
na mesa e foi **recusada**.

*Fato vs. inferência, declarado: é **fato** que a opção escolhida nomeia as quatro folhas e as quatro
versões, e que a opção "declarado" foi oferecida e não escolhida. É **inferência minha** que ele abriu
as quatro folhas e não três — a granularidade da pergunta foi **de conjunto**, não de artefato. Quem
quiser granularidade por artefato precisa de quatro perguntas, e essa é a lição desta rodada sobre a
forma de perguntar.*

---

#### 64.10.2 O placar do rito, REEXECUTADO do zero antes de promover

**Regra aplicada: medição antes da spec — número lembrado não promove nada.** Todo o rito foi
reexecutado neste contêiner contra os artefatos **lidos da pasta** no início desta sessão, e não
contra os números registrados no §80 do MANIFESTO. Os dois conjuntos **coincidem integralmente**, o
que é a evidência de que os artefatos da pasta são os mesmos que o rito de 2026-08-17 mediu.

| Camada de prova | `banco-escolha` v0.4 | `banco-data` v0.4 | `banco-prioridade` v0.2 | `banco-pessoa` v0.1 |
|---|---|---|---|---|
| Suíte própria (jsdom + render) | **238 · 0 · 29** em 5 alvos · **138 · 0 · 0** no artefato isolado | **31 · 0 · 1** | **18 · 0 · 0** | **44 · 0 · 1** |
| **BT7** (SC 1.4.10 · não rola a 320/390) via `suite-reflow` | **5 · 0** | **5 · 0** | **5 · 0** | **3 · 0 · 1 [n/a]** |
| Rito de promoção `render-promocao` (5 larguras × 2 temas) | ✅ | ✅ | ✅ | ✅ — **agregado 168 · 0** nos quatro |
| Contraste na composição `contraste-composicao` (fundo lido do pixel, 2 temas) | **2 · 0** | **2 · 0** | **2 · 0** | **2 · 0** — agregado **8 · 0** |
| Fronteira de campo `auditoria-borda-campo` (piso 3,00 da SC 1.4.11) | **2 · 0** | **2 · 0** | **2 · 0** | **2 · 0** |
| Folha de captura (§64.9.3) | `render-audit/folha-banco-escolha.html` | `…folha-banco-data.html` | `…folha-banco-prioridade.html` | `…folha-banco-pessoa.html` |

**Achado colhido de graça, que alimenta a CD-P7 com número por artefato.** A
`auditoria-borda-campo.mjs` emite **achado, não veredito**, para borda de **botão** (§64.9.4). Nas
quatro bancadas, por tema, ela contou: `banco-escolha` **15** · `banco-data` **32** ·
`banco-prioridade` **6** · `banco-pessoa` **2** — **55 botões** com borda abaixo de 3,00. *Fronteira
declarada: 55 é a contagem **nestes quatro artefatos**, não no acervo; o total de acervo registrado na
CD-P7 é **93**, e a diferença está nos outros artefatos, que não foram remedidos aqui.* **Nenhum
desses 55 é defeito nem aprovação enquanto a CD-P7 não tiver veredito.**

**Nota de ambiente, para quem reproduzir.** A `suite-escolha` varre cinco alvos (a bancada mais
`tela-painel`, `tela-lista`, `tela-tabela` e `tela-detalhe`). Rodada numa cópia de trabalho que tinha
só a bancada, ela devolveu **138 PASS · 4 FAIL** — e os quatro FAIL eram **arquivo ausente**, não
defeito de artefato. *Isso é um caso do princípio do §64.9.2: **alvo que não está lá não é alvo que
reprova**, e suíte que conta ENOENT como FAIL produz o mesmo mal da suíte que morre — número plausível
sobre coisa que não foi medida.* Trazidos os quatro alvos **por nome**, o placar foi a **238 · 0 · 29**.

---

#### 64.10.3 ERRATA DE VERSÃO em três cabeçalhos — o documento declarava versão que o artefato não tem

**Achado desta sessão, por medição, antes de qualquer promoção.** Os cabeçalhos das seções §61, §62 e
§63 declaravam versões de bancada **uma minor acima** das que existem:

| Seção | Versão que o cabeçalho declarava | Versão MEDIDA no artefato | Corrigido para |
|---|---|---|---|
| §61 · `banco-data.html` | v0.5 | **v0.4** | **v0.4** |
| §62 · `banco-pessoa.html` | v0.2 | **v0.1** | **v0.1** |
| §63 · `banco-prioridade.html` | v0.3 | **v0.2** | **v0.2** |

**Como foi medido, e por que o artefato ganha.** Quatro fontes independentes foram conferidas por
comando, e **três concordam** contra **uma**:

1. **O artefato** — `<title>` e o `<p class="selo">` de cada bancada. Medido: `banco-escolha` **v0.4**,
   `banco-data` **v0.4**, `banco-prioridade` **v0.2**, `banco-pessoa` **v0.1**.
2. **O gerador** — o histórico de versão comentado em `validacao/gen-banco-*.py` para em **v0.4**,
   **v0.4**, **v0.2** e **v0.1**. Nenhum gerador conhece as versões v0.5 / v0.2 / v0.3 alegadas.
3. **O `validacao/MANIFESTO.md`** — §76 (`v0.3 → v0.4`), §78, §79 e §80 dizem, em nove ocorrências
   distintas, `banco-escolha` v0.4 · `banco-data` v0.4 · `banco-prioridade` v0.2 · `banco-pessoa` v0.1.
4. **O `mapa-cobertura-ds.md` §11.3** — as mesmas quatro versões.

Os três cabeçalhos eram a **única** fonte divergente do acervo. **Alternativa descartada, com o
porquê:** subir os artefatos para as versões alegadas (v0.5 / v0.2 / v0.3) foi considerada e
**recusada** — mudança de versão sem mudança de conteúdo é etiqueta, não versão (o §73 diz *"mudança de
conteúdo sobe a versão"*, e não o contrário); e, decisivo, as **folhas de captura que o Rafael olhou
neste gate** foram geradas a partir dos artefatos v0.4 / v0.4 / v0.2 / v0.1 — *conferido por mtime: as
quatro folhas são ~3 s mais novas que os quatro artefatos*. Reetiquetar o artefato depois do gate faria
o "aprovado" cobrir um número que não existia quando ele olhou, que é exatamente o que a distinção
observado/declarado foi criada para impedir.

**Causa provável, declarada como inferência e não como fato:** os três cabeçalhos foram escritos na
sessão da **manhã** de 2026-08-17 antecipando uma regeração que a sessão da **tarde** não produziu — e
a tarde registra que um `mv`+`rm` em lote destruiu quatro cópias de trabalho, retrazidas **por nome** da
pasta, o que devolveria os geradores à versão anterior. *Não há evidência direta desse encadeamento; o
que é fato é a divergência e sua direção — exatamente +1 minor em cada um dos três.*

---

#### 64.10.4 SUPERSEDE FORMAL — o que passa a valer

As linhas abaixo **supersedem** as correspondentes do §64.9.4, do §64.6, do §61.8 e do §62.7. As
tabelas antigas ficam como histórico; o estado vigente é este.

| # | Estado em 2026-08-17 (v1.01) |
|---|---|
| **BT-P1** | ✅ **FECHADA.** Re-gate visual **OBSERVADO** de `banco-escolha` **v0.4**, `banco-data` **v0.4** e `banco-prioridade` **v0.2** (§64.10.1). Rito completo reexecutado antes de fechar (§64.10.2). As três specs (§60, §61, §63) permanecem `estável`, agora com gate **observado** em vez de declarado. |
| **PS-P4** | ✅ **FECHADA.** Gate visual **OBSERVADO** de `banco-pessoa` **v0.1** (§64.10.1). A spec do §62 permanece `estável`, agora com gate observado. **Não confundir com a PS-P2**, que segue ABERTA: C5 (seletor de entidade colorida) e C8 (menção `@`) são inalcançáveis no modo somente leitura, porque só abrem **escrevendo** no produto de referência. |
| **DH-P6** | ✅ **FECHADA** — era a BT-P1 dita do lado do §61.8. O calendário da `banco-data` v0.4 opera (31 guardas de operabilidade, medidas) **e** foi olhado. Fecha a linhagem que a **DH-P5** abriu quando o olho reprovou a v0.1 com 25 PASS · 0 FAIL no placar. |
| **Natureza do gate das quatro** | **OBSERVADO**, com o insumo nomeado por arquivo e por versão. Isto **supersede** a nota de rastreabilidade dos cabeçalhos de §61, §62 e §63, que dizia *"o gate foi declarado, e não há registro de quais capturas ele abriu"* — para **estes quatro artefatos nestas quatro versões** há registro. *A nota continua valendo, sem alteração, para os artefatos que ela cobria e que **não** entraram neste gate: `tela-referencia`, `tela-painel`, `tela-quadro`, `tela-gantt`, `banco-tokens` e `banco-dataviz-di`, promovidos por gate declarado na manhã de 2026-08-17.* |
| **Errata de versão** | ✅ **CORRIGIDA** nos cabeçalhos de §61, §62 e §63 (§64.10.3): v0.5→**v0.4**, v0.2→**v0.1**, v0.3→**v0.2**. Correção do **documento**, não do artefato, com as quatro fontes de medição registradas. |
| **Achado de instrumento, SEM conserto** | `suite-escolha` conta **arquivo ausente como FAIL** (4 FAIL medidos numa cópia sem os alvos de tela). Alvo ausente deve declarar `[n/a]`, nunca reprovar — mesma família da regra *"suíte que morre é pior que suíte que reprova"*. *Não consertado nesta rodada: o padrão vale para as 21 suítes e pede prova de reprovação própria. Registrado para não voltar como surpresa.* |

---

---

### 64.11 A GI2 forma 4 vira GUARDA TRANSVERSAL — a BT-P3 fecha na FORMA, e a pasta ganha UM defeito medido (2026-08-17, v1.03)

> **O que esta subseção registra, para quem a lê sem ter visto a conversa que a gerou.** O contrato
> **GI2** deste sistema diz: *componente consome token **semântico**, nunca **primitiva de rampa***.
> Primitiva é um token de cor cru (`--seed-turquesa-100`); semântico é um token de papel
> (`--seed-text-primary`). A diferença que importa é uma só: **semântico inverte quando o tema inverte;
> primitiva não.** A guarda que mede isso foi escrita **quatro vezes** (§64.3), e até hoje rodava em
> apenas três bancadas. A **BT-P3** pedia levá-la às quatro suítes antigas.

#### 64.11.1 SUPERSEDE FORMAL da FORMA da BT-P3 — um instrumento, não quatro cópias

**A BT-P3 pedia, ao pé da letra:** *"levar a guarda GI2 forma 4 às suítes antigas — `suite-painel`,
`suite-container`, `suite-dominio`, `suite-icones`"*. Isto é: **quatro cópias** da mesma guarda de ~130
linhas, uma em cada suíte.

**ALTERNATIVA DESCARTADA: as quatro cópias.** O motivo não é economia de trabalho — é o achado central
da **própria sessão** que fecha esta pendência. Em 2026-08-17 nasceu a `auditoria-consumidor.mjs`
(§69) exatamente porque **cópia colada de bloco canônico DIVERGE do canônico**: foi assim que o bloco
de assinatura EA ficou com o cargo velho num consumidor por três dias, e nenhum placar viu. **Espalhar
a mesma guarda por quatro arquivos é cometer, na CAMADA DE INSTRUMENTO, o defeito que a camada de
artefato acabou de ganhar guarda para não cometer.** E há precedente na casa: todo contrato
**transversal** deste projeto já se resolve com instrumento de **alvo de PASTA** — `suite-reflow.mjs`
para o BT7, `auditoria-borda-campo.mjs` para a SC 1.4.11, `contraste-composicao.mjs` para contraste em
composição. O GI2 é do §64, que **é** a seção dos contratos transversais.

**ESCOLHIDO:** `validacao/guarda-gi2.mjs` — **uma** guarda, alvo de PASTA, cobrindo os alvos das quatro
suítes antigas **e todo o resto do acervo**. A BT-P3 fecha com cobertura **maior** do que pedia:
**29 artefatos**, contra os 4 alvos que as quatro suítes cobririam.

> **FRONTEIRA DECLARADA, e ela importa:** este instrumento **não altera** as quatro suítes antigas.
> Quem rodar `suite-painel` isolada **não verá GI2 no placar dela**. A cobertura existe e está no
> instrumento novo; dizer que as suítes antigas passaram a medir GI2 seria **falso**. Rode os dois.

#### 64.11.2 O placar, e o que ele mudou no estado do acervo

**`15 PASS · 1 FAIL · 35 [n/a]` em 29 artefatos** — primeira medição de GI2 na pasta inteira.

Os 35 `[n/a]` não são silêncio: são **três categorias declaradas**, cada uma com o motivo impresso.
**14** de superfície (consumidor de primitiva **sem texto próprio**) · **5** sem anel (texto sem fundo
próprio ou com padding < 2px) · **3** com override de tema (a primitiva **inverte a mão**). O resto é
artefato que não consome primitiva nenhuma.

> **CONSEQUÊNCIA MATERIAL PARA O ESTADO DO PROJETO, e ela tem de ser dita sem rodeio.** Até esta
> subseção, o registro dizia que **o acervo inteiro estava SEM DEFEITO MEDIDO EM ABERTO**. **Isso deixa
> de ser verdade.** A `tela-chat.html` tem **um defeito medido, reprodutível em três execuções
> idênticas**, e ele é a pendência **BT-P6** abaixo. *A frase antiga não era mentira: era o alcance da
> medição de então. Guarda nova encontra defeito antigo — é para isso que ela existe.*

#### 64.11.3 BT-P6 (nova) — DEFEITO MEDIDO EM ABERTO na `tela-chat.html`

| O quê | Medida |
|---|---|
| Seletor | `.msg--propria .msg__balao` — o balão da mensagem **própria** no chat |
| Declara | `background: var(--seed-turquesa-50)` — **primitiva**, não inverte · `color: var(--seed-text-primary)` — **semântico**, inverte |
| Override de tema para este seletor | **não existe** (medido: zero ocorrências de `[data-theme="dark"]` para ele) |
| Medido no tema **escuro** | texto `#ddece8` sobre fundo `#e8fbf7` = **1,14** · piso **3,00** |
| Reprodutibilidade | **3 de 3** execuções, valor idêntico |

**É exatamente a forma do defeito que o gate do Rafael achou em 2026-08-16** — fundo primitivo de um
lado, texto semântico do outro; no escuro o texto clareia e o fundo não, e a mensagem fica **branca
sobre branca**. Os dois casos daquele dia mediram **1,02** e **1,00**; este mede **1,14**.

**Conserto indicado, NÃO executado nesta rodada, e o porquê:** trocar `--seed-turquesa-50` pelo
semântico de superfície equivalente, no `validacao/tela-chat-template.html`, e **regerar** por
`gen-tela-chat.py`. Não foi feito aqui por duas razões declaradas: (1) a `tela-chat` é artefato
**promovido**, e conserto em artefato promovido pede rodada com re-execução da suíte própria, do BT7 e
do contraste, mais folha de captura — a fila desta sessão não alcançou; (2) **qual** semântico entra é
escolha com consequência visual no balão, e o §64.9.2 já ensinou que a escolha do token semântico certo
é decisão, não substituição mecânica. *Ausência de conserto, declarada — não silêncio.*

#### 64.11.4 QUATRO consertos de instrumento — e o 24º defeito é NÚMERO QUE VARIA ENTRE EXECUÇÕES

Levada crua da bancada para a pasta inteira, a guarda devolveu **7 FAIL**. Investigados um por um,
**seis eram falso positivo**, e cada um por uma razão diferente e nomeável. *Sete consertos errados de
uma vez teriam sido o pior estrago já causado nesta pasta.*

| Conserto | O falso positivo, medido | A causa | A regra |
|---|---|---|---|
| **A** | `.fachada` do `tela-site` reprovou a **1,01** | é um contentor de vídeo 16/9 **sem texto próprio** — quem pinta texto ali são os filhos, com cor própria. A guarda leu a cor **herdada** do contentor contra o pixel dominante dele | **elemento sem texto próprio não é par texto/fundo.** Entra no relatório como consumidor de **superfície**, não como reprovação |
| **B** | `.seed-empty__icon` do `banco-feedback` (**1,91**) e o `h2` do `banco-formfield` (**2,04**) | os dois declaram primitiva **e** têm a regra irmã `[data-theme="dark"]` para si mesmos — `cinza-300`→`cinza-600` e `turquesa-600`→`turquesa-300` | **primitiva com override explícito de tema INVERTE a mão.** A premissa da guarda ("primitiva não inverte") não se aplica: condená-los seria condenar a solução |
| **C** | o `h2` devolveu fundo `"#aaadaf"` — cinza médio que **não existe** no artefato | numa caixa apertada, o **pixel dominante** é a mistura de texto e fundo pelo antialias | **o fundo de um texto é o ANEL em volta dele**, a moldura externa da captura, onde está o padding |
| **D** | `.tagok` do `banco-composicao` mediu **1,84**, depois **2,56**, depois **2,95** — **no mesmo artefato, sem tocar nele** | ele é `{color:var(--seed-turquesa-600);font-weight:700}`: **sem fundo próprio e sem padding**. A caixa é apertada em volta dos glifos, então o anel de 2px **é o próprio glifo**. `#cecfd0` é o cinza que o antialias produz misturando `#098475` com branco: **a guarda media o texto contra si mesmo** | **número que varia entre execuções do mesmo alvo NÃO É MEDIDA — é assinatura de medidor quebrado**, e este reprovava **três** artefatos. Elemento de texto sem fundo próprio ou com padding < 2px sai como **`[n/a]`** com o motivo |

> **A regra que o conserto D deixa, e ela é simétrica:** *guarda que não consegue medir não pode
> declarar aprovação — **e não pode declarar reprovação tampouco**.* O projeto já tinha metade dessa
> regra escrita. A outra metade custou três FAIL em artefatos corretos.
>
> **E a lição de método:** o conserto **C** parecia suficiente e **não era** — ele mentia **diferente a
> cada execução**, e a única coisa que revelou isso foi **rodar a mesma medida três vezes**. *Medida
> que não é repetida não está verificada.* Repetição entra no rito.

**Isto abre a BT-P5 (nova):** medir o fundo real de um texto **sem anel** exige ler o **ancestral que
pinta**, fora do retângulo do elemento — e isso é coordenada relativa a outra captura, que é a família
do 22º defeito de instrumento. Pede rodada própria. Hoje são **5 `[n/a]`** declarados por essa causa.

#### 64.11.5 A PROVA DE REPROVAÇÃO — e ela fechou o diagnóstico

Injetado em `banco-superficies.html` (que passava) um elemento com a **forma exata** do defeito — fundo
primitivo `--seed-turquesa-50` mais texto semântico `--seed-text-primary`, com padding para ter anel.
Resultado: **FAIL a 1,14 no escuro** — **o mesmo número da `tela-chat`**. A coincidência não é acaso:
ela prova que o mecanismo diagnosticado é o mecanismo real, e não uma coincidência de pixel. Revertido,
com **MD5 conferido** contra o arquivo da pasta.

#### 64.11.6 Estado das pendências desta subseção

| # | Estado |
|---|---|
| **BT-P3** | ✅ **FECHADA na FORMA superseder** (§64.11.1): um instrumento transversal de alvo de PASTA em vez de quatro cópias, cobrindo **29 artefatos** em vez de 4. *Fronteira: as quatro suítes antigas seguem sem GI2 no placar delas.* |
| **BT-P5** *(nova)* | **ABERTA.** A medida de par texto/fundo **não alcança** elemento de texto sem anel (sem fundo próprio ou padding < 2px). Hoje: **5 `[n/a]`** declarados. Alcançar exige ler o ancestral que pinta, fora do retângulo — família do 22º defeito, rodada própria |
| **BT-P6** *(nova)* | **ABERTA — DEFEITO MEDIDO.** `tela-chat.html`, `.msg--propria .msg__balao`, **1,14** no escuro contra piso 3,00, reprodutível 3/3. Conserto indicado e não executado, com o porquê (§64.11.3). **O acervo não está mais sem defeito medido em aberto** |

---

### 64.12 CD-P7 — o RITO de três rodadas sobre a borda de BOTÃO, e o veredito PROPOSTO (2026-08-17, v1.04)

> **O que esta subseção resolve, para quem a lê sem ter visto a conversa que a gerou.** A
> `validacao/auditoria-borda-campo.mjs` mediu, no acervo, **93 botões** cuja borda fica **abaixo do piso
> 3,00** da **SC 1.4.11 (Non-text Contrast)** da WCAG 2.2. Ela emitiu **`[achado]`, não veredito**, de
> propósito: a SC exige 3:1 da *"informação visual **necessária** para identificar o componente"*, e
> botão tem rótulo, forma e muitas vezes preenchimento. **Decidir se a borda do botão é identificador
> necessário é DESENHO, não medida** — e por isso a pendência **CD-P7** existe e exige RITO de três
> rodadas mais gate. Nenhum dos 93 é defeito nem aprovação enquanto não houver decisão.

#### 64.12.1 R1 · CANON — o Understanding responde a pergunta DIRETAMENTE

O texto normativo da **SC 1.4.11** é: *"The visual presentation of the following have a contrast ratio
of at least 3:1 against adjacent color(s): **User Interface Components** [and] **Visual information
required to identify** user interface components and states, except for inactive components."*

E o **Understanding SC 1.4.11** do W3C diz, verbatim, o que resolve a CD-P7:

> *"If a control has visible content (such as text or a sufficiently contrasting icon), which helps
> users identify the presence of the control, then **a border or other indication of the overall
> boundary of the hit area is not required**."*

O mesmo documento traz como caso que **PASSA**: *"A button without a visual boundary — the button's
text is sufficient to indicate the presence of the control."*

**Leitura direta: botão com rótulo legível NÃO precisa de borda a 3:1.** A borda só se torna
*"informação visual necessária"* quando **não há outro identificador** — nem rótulo legível, nem ícone
com contraste suficiente.

**Divergência de canon, registrada porque existe e porque a fronteira do que eu li importa.** A issue
**w3c/wcag#800** foi aberta por David MacDonald com a leitura **oposta**: *"palavras sozinhas não bastam
para identificar um controle interativo"* — se há fundo ou borda visíveis sugerindo um botão, esses
elementos precisariam dos 3:1. A issue foi discutida em 25/06/2019 e **fechada pelo PR #813**, que é o
que produziu o texto do Understanding citado acima. *Fronteira do que MEDI: li a abertura da issue e o
Understanding vigente; **não** consegui ler a thread completa nem o PR. Logo: é **fato** que o texto
vigente do Understanding diz que a borda não é obrigatória com rótulo visível, e é **inferência minha**
que o grupo resolveu a favor dessa leitura por meio daquele PR.*

#### 64.12.2 R2 · MERCADO — os maduros dão a borda ao TERCIÁRIO e NENHUMA ao fantasma

**Carbon Design System (IBM), especificação de botão, medida na fonte:**

| Variante | Borda | Fundo |
|---|---|---|
| **ghost** (fantasma) | **nenhuma borda declarada**, em nenhum estado — nem repouso, nem hover, nem foco, nem ativo | `transparent` |
| **tertiary** (terciário) | **tem** borda no repouso, com token próprio (`$button-tertiary`) | `transparent` |

*O Carbon não publica o número de contraste dessas bordas — nomeia o token e não declara a razão.
Registro isso como limite da fonte, não como omissão minha.*

**O que o padrão do mercado diz, então:** a borda **não** é obrigatória em botão identificado por
rótulo (o *ghost* prova por existência); e quando ela **é** o identificador — no *tertiary*, que tem
fundo transparente e portanto **nada além da borda** para se distinguir da página —, ela ganha token
próprio e passa a ser estrutural. **Isso é exatamente a fronteira que o Understanding descreve**, achada
independentemente por um sistema maduro.

#### 64.12.3 R3 · NORMAS — e aqui a pergunta MUDA, com número medido

**EN 301 549 cláusula 9** remete a WCAG 2.1 AA, então a régua da SC 1.4.11 é a mesma. A rodada de normas
**não** contradiz a R1. Mas ela abre um risco **diferente**, e este é o achado que a CD-P7 não previa.

Em **forced-colors** (o modo de alto contraste do sistema operacional), o navegador **sobrescreve
`background-color` e `box-shadow`**. A orientação canônica de Sarah Higley para o modo é declarar uma
moldura **transparente** de propósito, para que o sistema a torne visível: *"make it transparent
instead: `outline 3px solid transparent`"*, e ela registra que *"this trick also works with transparent
borders if, for example, your button has a distinct background color but no separately visible
borders"*.

**Consequência, e ela inverte o problema.** Um botão identificado **apenas pelo preenchimento**
desaparece em forced-colors — porque é justamente o preenchimento que o modo joga fora. Quem sobrevive é
quem declarou borda ou `outline`, **mesmo transparente**. Ou seja: **o risco real não é "borda abaixo de
3,00"; é "nenhuma borda nem outline declarada".**

**MEDIDO NO ACERVO, por comando, nesta rodada:**

| Medida | Número |
|---|---|
| Artefatos varridos | **29** |
| Artefatos que declaram alguma regra `@media (forced-colors: active)` | **12** |
| Artefatos que, dentro dessa regra, tratam **botão** | **1** — só o `tela-painel.html` (2 regras) |

**Vinte e oito de vinte e nove artefatos não têm nenhuma regra de botão para forced-colors.** Este número
é novo, não estava em pendência nenhuma, e é o que a rodada de normas entregou.

#### 64.12.4 VEREDITO PROPOSTO — três frases, e a decisão é do Rafael

**1 · Os 93 achados NÃO são defeito de SC 1.4.11**, com a fronteira escrita: um botão cujo **rótulo é
legível** (texto acima do piso de 4,50 do projeto) ou cujo **ícone** atinge 3:1 tem identificador
suficiente, e a borda dele **não** é *"informação visual necessária"*. É o que o Understanding diz
verbatim e é o que o Carbon pratica no *ghost*.

**2 · A exceção que continua valendo, e ela é estreita:** botão cujo **único** identificador é a borda
— fundo transparente, sem ícone, rótulo tipograficamente indistinto do texto corrido em volta — **está**
sob a SC 1.4.11 e a borda dele **tem** de medir 3,00. É o caso do *tertiary* do Carbon. *Quantos dos 93
caem nessa classe **NÃO foi medido nesta rodada** — a `auditoria-borda-campo` conta bordas, não
classifica identificadores. Ausência de medição, declarada; se o gate aprovar a fronteira, medir isso é
a primeira tarefa da rodada seguinte.*

**3 · Nasce uma obrigação NOVA, que não é a CD-P7 e é mais barata que ela:** todo botão declara
**`outline` (ou borda) transparente** para sobreviver a forced-colors, porque o modo descarta
`background-color`. Hoje **1 de 29** artefatos tem regra de botão para forced-colors. Isto é conserto
mecânico e de baixo risco, e vale para o acervo inteiro.

> **A pergunta de gate está acumulada no fechamento desta sessão.** Enquanto o Rafael não decidir, os 93
> continuam **`[achado]`**: nem defeito, nem aprovação. *Guarda que condena artefato correto força
> conserto errado — e "conserto" aqui significaria pôr borda a 3:1 em 93 botões que o canon diz não
> precisarem dela.*

#### 64.12.5 Pendências desta subseção

| # | Estado |
|---|---|
| **CD-P7** | **RITO COMPLETO (R1 canon · R2 mercado · R3 normas), veredito PROPOSTO, ABERTA só no GATE.** O trabalho de pesquisa acabou; a decisão é de desenho e é do Rafael |
| **CD-P9** *(nova)* | **ABERTA — e ela é a que tem número.** **28 de 29** artefatos do acervo **não** têm nenhuma regra de botão em `@media (forced-colors: active)`. Botão identificado só pelo preenchimento **desaparece** no modo de alto contraste, porque o modo descarta `background-color`. Conserto: `outline` transparente declarado. Mecânico, barato, e vale para a pasta inteira |
| **CD-P10** *(nova)* | **ABERTA.** Quantos dos 93 botões têm a borda como **único** identificador (fundo transparente · sem ícone · rótulo indistinto) **não foi medido**: a `auditoria-borda-campo` conta bordas, não classifica identificadores. É a primeira tarefa se o gate aprovar a fronteira do §64.12.4 |

---

### 64.13 A BT-P6 FECHA na causa, a CD-P7 FECHA por decisão, e nasce a MR-P1 (2026-08-17, v1.05)

> **Esta subseção SUPERSEDE o §64.11.3**, que descreve a BT-P6 como defeito ABERTO — ele foi escrito
> antes do conserto. Relato completo no `validacao/MANIFESTO.md` **§85**.

#### 64.13.1 BT-P6 — o conserto, e por que ele foi feito no GÊMEO

**O conserto óbvio seria trocar a primitiva por um semântico no artefato. Não havia semântico para
trocar.** Medido: o `tela-chat.html` era o **único** consumo de `--seed-turquesa-50` em **29 artefatos**,
e o gêmeo não tinha token semântico para *"superfície de marca sutil"* — tinta de marca sem virar
`surface-brand`. **O artefato alcançou a primitiva porque era a única coisa disponível.**

> **Regra nova:** *quando o artefato consome primitiva e é o ÚNICO consumidor dela no acervo, a primeira
> hipótese não é descuido — é LACUNA DE TOKEN.* Consertar o artefato sem olhar o gêmeo empurraria o
> defeito para o próximo componente que precisasse da mesma coisa.

| Passo | Feito | Medida |
|---|---|---|
| 1 | Nasce **`--seed-surface-brand-subtle`** nos três gêmeos | claro **`#E8FBF7`** · escuro **`#1A3833`** |
| 2 | Contra `--seed-text-primary` | **12,79** claro · **10,40** escuro · piso 4,50 |
| 3 | Molde consome o semântico; `seed-turquesa-50` sai da lista `NOMES` do gerador | token injetado e não consumido é peso morto |
| 4 | `tela-chat.html` regerada e remedida | `guarda-gi2` **0·0·2 [n/a]** · `suite-reflow` **3·0·1** · `contraste-composicao` de **0·2 FAIL** para **1·1** — os **três** FAIL do escuro sumiram |

`#1A3833` **não é valor novo**: é o que a camada de tokens já havia escolhido para superfície de marca
que assenta na página (`--seed-action-secondary-bg` no escuro). **Alternativas descartadas:** reusar
`--seed-row-selected-bg`/`--seed-action-secondary-bg` (o semântico estaria **mentindo** — balão não é
linha selecionada nem fundo de botão), ou usar `--seed-surface-subtle` como o balão **alheio** (apagaria
a distinção de tinta entre mensagem própria e alheia, que é desenho aprovado).

#### 64.13.2 CD-P7 ✅ FECHADA por decisão — os 93 não são defeito

O Rafael **questionou** a recomendação do §64.12 e a acatou depois da defesa: (1) o Understanding da
SC 1.4.11 diz **verbatim** que borda não é obrigatória em controle com conteúdo visível; (2) consertar
**destruiria a escala de botão** — o que distingue **terciário de fantasma é a borda**, e a 3:1 em todos
o fantasma **vira** terciário (o Carbon pratica a mesma fronteira: *ghost* sem borda, *tertiary* com);
(3) o risco real é outro e tem número — **28 de 29** artefatos sem regra de botão para `forced-colors`
(**CD-P9**).

**Fronteira que fica escrita:** botão com **rótulo legível** (acima do piso 4,50) ou **ícone** a 3:1 tem
identificador suficiente e a borda dele **não** é *"informação visual necessária"*. **Exceção estreita
mantida:** botão cujo **único** identificador é a borda — e **quantos são disso não foi medido**
(**CD-P10**).

#### 64.13.3 MR-P1 (nova) — o número que sustenta a regra de branco sobre a marca NUNCA EXISTIU

Rodando o `contraste-composicao.mjs` no `tela-chat.html` — alvo em que ele **nunca havia sido
apontado** — dois textos reprovaram o piso 4,50 da SC 1.4.3 no **claro**: `button "Recolher chat"`
(12px/600) e `div.avatar "RS"` (12px/700), ambos `#ffffff` sobre `#11b0a0` = **2,71**.

`#11B0A0` é `--seed-surface-brand`. O `seed-tokens.md` declarava **3,22:1** e concluía *"passa AA-large
(≥3.0)"* — e **sobre essa conclusão** foi construída a regra *"branco sobre `surface-brand` somente
≥24px ou ≥19px bold"*, com supersede formal da regra do `marca-seed.md` v4. **Duas medições
independentes derrubam o 3,22:** cálculo direto pela fórmula da WCAG 2.x = **2,74**; leitura por pixel =
**2,71**. **A 2,71, branco sobre a marca reprova até AA-large — não é permitido em nenhum tamanho.**

**A resposta já estava na casa, no tema escuro do mesmo artefato:** `#00352F` sobre `#11B0A0` = **4,99**.
Medido também `#0B3330` = **5,06** e `#005048` = **3,45**.

**DECISÃO DO RAFAEL: recomendação ACATADA** — a tinta sobre `--seed-surface-brand` passa a ser **escura**
(`turquesa-900` ou o texto primário), nos dois temas, e **não branca em nenhum tamanho**. Errata escrita
no `seed-tokens.md`. **Execução pendente**, e o motivo é explícito: muda **todo** botão e selo de marca do
acervo, a regra antiga está referenciada no `marca-seed.md`, e **quantos casos existem NÃO FOI CONTADO** —
o instrumento foi apontado até hoje para **6** dos **29** artefatos. *Contar vem antes de consertar.*

> **A lição de cobertura, e ela vale para todo eixo declarado limpo neste arquivo:** este defeito existia
> desde que a regra foi escrita, e **nenhuma guarda o viu porque nenhuma guarda tinha sido apontada para
> aquele alvo**. **Ausência de medição não é aprovação.** Antes de declarar um eixo limpo, liste **quais**
> alvos cada instrumento cobre e aponte-o para os que faltam. *Terceira vez que alargar o alvo de um
> instrumento existente rende mais que escrever spec nova — as outras duas foram a BT-P4 e a BT-P3.*

#### 64.13.4 SI-P4, IL-P2 e PS-P5 — DECIDIDAS, execução pendente

O Rafael aprovou a unificação e a chamou de **inegociável**: *"unificar é melhor… precisamos manter o
mesmo padrão, isso é inegociável."*

| # | Decisão | Estado |
|---|---|---|
| **SI-P4** | ✅ `.sigla` → **§65** | ✅ **EXECUTADA em 2026-08-19 — e eram OITO telas, não quatro.** As quatro desta linha mais `tela-chat`, `tela-gantt`, `tela-quadro` e `tela-referencia`, que **nenhuma lista escrita à mão continha** e que só apareceram quando a guarda passou a varrer a PASTA. Ver **§65.7** |
| **IL-P2** | ✅ grade do §48 → **§67** | ✅ **EXECUTADA em 2026-08-19.** Eram **sete** defeitos, não três — o principal era teclado, não semântica. Ver **§67.7** |
| **PS-P5** | ⛔ **REVOGADA PELA MEDIÇÃO em 2026-08-19 — ver §62.8.** O `.mini-avatar` **não é** avatar de pessoa: ele carrega o **CLIENTE**, que é uma ENTIDADE, com as iniciais do cliente, na coluna Cliente de `tela-tabela` e `tela-referencia`. A paleta de entidade é a **certa** para o que ele representa. Em `tela-lista` ele tinha **zero** elementos no DOM — folha morta, removida com prova de inércia. Nasce a **PS-P6**, que é julgamento de PRODUTO | — |
| **Ponto de cor decorativo** (`.ent` · 12px · `aria-hidden` · sem glifo) | ✅ **ganha spec própria** | ✅ **EXECUTADA em 2026-08-19 — §65.8, contrato SI18.** E eram **29** elementos em **7** telas, não 14. Quatro divergências medidas, entre elas um ponto que pintava **transparente** no `tela-lista` |

#### 64.13.5 Pendências

| # | Estado em 2026-08-17 (v1.05) |
|---|---|
| **BT-P6** | ✅ **FECHADA na causa.** Supersede o §64.11.3 |
| **CD-P7** | ✅ **FECHADA por decisão.** Supersede o §64.12.5 |
| **MR-P1** *(nova)* | **DECIDIDA, execução pendente.** Branco sobre `--seed-surface-brand` mede **2,71** e reprova até AA-large; a tinta passa a ser escura. **Contar os casos no acervo é a primeira tarefa** |
| **SI-P4 · IL-P2 · PS-P5 · spec do ponto de cor** | **DECIDIDAS, execução pendente** (§64.13.4) |
| **CD-P9 · CD-P10 · BT-P5 · AC-P1 · AC-P2 · CD-P6 · E-P2** | **ABERTAS**, sem decisão |

### 64.14 A MR-P1 EXECUTA no gêmeo, nasce e fecha a MR-P2, e o ANEL vaza em elemento pequeno (2026-08-17, v1.06)

> **Esta subseção SUPERSEDE o §64.13.3 e a linha da MR-P1 no §64.13.5**, que a descrevem como *decidida,
> execução pendente*. Relato completo, com placares e provas de reprovação, no `validacao/MANIFESTO.md`
> **§86**.

#### 64.14.1 A causa da MR-P1 era da CAMADA DE TOKEN — terceira vez

**O conserto óbvio seria trocar o branco por tinta escura em cada artefato. Seria conserto de sintoma.**
Medido: **um único token de tinta servia TRÊS fundos de marca com necessidades OPOSTAS**, e o gêmeo de JSON
descrevia esse token, com essas palavras, como *"texto sobre action.primary"* — ele declarava para qual
fundo havia sido feito, e estava sendo consumido em outros dois.

| Fundo | Valor | branco | escura `#00352F` |
|---|---|---|---|
| marca **chapada** `surface-brand` | `#11B0A0` nos dois temas | **2,71** ✗ | **4,99** ✓ |
| marca **profunda** `surface-brand-deep` claro | `#006C62` | **6,32** ✓ | **2,14** ✗ |
| marca **profunda** escuro | `#005048` | **9,37** ✓ | **1,45** ✗ |
| **botão primário** `action-primary` claro | `#098475` | **4,60** ✓ | **2,95** ✗ |
| **botão primário** escuro | `#66D1C2` | **1,83** ✗ | **7,39** ✓ |

**Nenhum valor único satisfaz as três linhas** — logo o token acertava o botão primário e errava os outros
dois, **um defeito em cada tema**. Conserto: **três tintas, uma por fundo**, nos gêmeos v1.15 —
`text-on-brand` (escura nos dois temas), `text-on-brand-deep` (branca nos dois) e
`text-on-action-primary` (inverte, com os valores que a tinta única tinha, de modo que **o botão primário
não muda de aparência**). Regra vigente e alternativas descartadas: `seed-tokens.md` **§5-b**.

#### 64.14.2 MR-P2 (nova, e FECHADA na mesma edição) — a irmã simétrica, no tema escuro

Medido por pixel no `tela-tabela.html`, tema escuro: `span.qtd "2"`, `span.qtd "1"` e `button "1"` (página
corrente do rodapé) mediam `#00352f` sobre `#005048` = **1,45**, piso 4,50. **Três textos.** Ninguém tinha
visto porque **o `contraste-composicao.mjs` nunca havia sido apontado para o `tela-tabela.html`** — ele
cobria 6 dos 34 artefatos do acervo.

#### 64.14.3 O CENSO da MR-P1, e os denominadores que estavam errados

| Momento | Textos sobre a marca chapada | Violando |
|---|---|---|
| antes, instrumento v0.1 | 45 | **15** |
| antes, instrumento v0.2 (alcance maior) | 49 | **17** |
| depois de tudo | **49** | **0** |

Duas correções de fato, medidas por comando: **o acervo tem 34 artefatos `banco-*`/`tela-*` (24 + 10), não
29** — o 29 era o alcance de uma cópia de trabalho; e **o `contraste-composicao.mjs` aceita VÁRIOS alvos por
invocação** (o `abertura-proxima-sessao.md` dizia que não; a restrição vale para a `suite-reflow.mjs`).
Recontagem da **CD-P9**: **22 de 34** artefatos não têm **nenhuma** regra de `forced-colors`, e os 12 que
têm cobrem **estado**, não fronteira de botão — logo **34 de 34** seguem sem regra genérica de botão.

Quinze das 17 violações vinham do token. **Duas eram tinta `#fff` literal em `style=`**, nas duas galerias
de cor do acervo (`banco-tokens.html`, rótulo da barra de hierarquia 70/20/10; `banco-dataviz-tokens.html`,
rótulo do stop 400 da rampa v0.1 rejeitada) — os dois artefatos **não têm gerador** e foram editados à mão,
com a edição declarada.

#### 64.14.4 25º DEFEITO DE INSTRUMENTO — o ANEL vaza em elemento pequeno e ARREDONDADO

O anel de 5px nasceu para matar os 29 falsos positivos do `span.av` (caixa **quadrada** de 32px). **Ele
resolveu o quadrado de 32px e não resolveu o círculo de 26px:** no `tela-tabela.html` o `span.mini-avatar`
é um círculo de 26px e o rótulo de duas letras, dilatado em 5px, **estoura o círculo pelos quatro cantos**.
Resultado: **seis falsos positivos** (1,00 no claro, 1,10 no escuro) sobre pares que medem, pela cor
declarada, **5,11 a 9,37**.

**Conserto de premissa, nos dois instrumentos de contraste:** quando o elemento pinta cor de fundo **opaca**
e sem imagem, o fundo atrás do texto dele **é essa cor declarada** — não há o que amostrar. O pixel continua
a régua para elemento que **não** pinta (captura expandida) e para elemento pintado por **gradiente**.
**Alternativa descartada:** reduzir a folga de 5px para 2px — a 2px o anel **vira o glifo**, que é o 24º
defeito. **Prova nos dois sentidos, no mesmo alvo:** o placar do `tela-tabela.html` foi de **3/7 FAIL** para
**1/3** — os seis falsos positivos sumiram e os quatro reais (três da MR-P2 a 1,45, um da MR-P1 a 2,71)
continuaram reprovando.

**Consequência para a PS-P5, e ela evita conserto errado:** a decisão de migrar o `.mini-avatar` para a
paleta de **pessoa** do §62 segue valendo (é token fora do semântico). Mas **o componente renderiza
corretamente hoje** — 5,11 a 9,37 nos dois temas. **Não há defeito de contraste no `.mini-avatar`**, e quem
abrisse a PS-P5 pelo placar em vez de pelo par declarado teria "consertado" cor que está certa.

#### 64.14.5 BT-P7 (nova, e FECHADA) — token fantasma na BARRA DE PROVAS de seis bancadas

O `validacao/base-bancada.css` pinta o estado ligado do alternador com `surface-brand` +
`text-on-brand` + `surface-brand-deep`. **Seis bancadas consumiam `surface-brand` e
`surface-brand-deep` e NÃO OS DEFINIAM** (`banco-credencial`, `banco-data`, `banco-edicao-linha`,
`banco-identidade`, `banco-prioridade`, `banco-texto-rico`): variável indefinida é valor inválido, **o fundo
não pinta**, classe **NV-01**. Consequência operável: **o alternador de tema e o de escala de cinza dessas
seis ficavam sem nenhuma marca visual de ligado** — na barra que é o instrumento do gate humano (§64.9.2).

**Nenhuma guarda tinha visto porque o estado ligado não existe no DOM estático:** o `aria-pressed` nasce
`false` e o manipulador o vira `true` **no clique**. Conserto na causa (os dois nomes entram nas listas
`NOMES` dos nove geradores) e **guarda nova permanente `validacao/guarda-barra-provas.mjs`, contrato BP1,
alvo de pasta, que CLICA**: **9 PASS · 0 FAIL · 15 `[n/a]`**, todos medindo **4,99**. Prova de reprovação
com o caso fundador: apagadas as duas definições, a BP1 reprova com *"o FUNDO NAO PINTA"*.

#### 64.14.6 BC-P1 (nova, e FECHADA pelo pré-voo) — nove bancadas estavam ATRÁS da folha base

Antes de regerar, cada gerador rodou para destino temporário e a saída foi comparada com o artefato em
disco **ignorando comentários**. Achou **duas divergências substantivas, nenhuma desta edição**:
`.vh { position:absolute }` onde a folha base diz **`fixed`** — e `fixed` **é o conserto do BT7/BT-P4**,
registrado como feito no §64.6 — e `.controles { z-index:50 }` onde a folha diz **9000**. A regeneração
desta edição levou as duas às nove bancadas. *Classe AC1 uma camada acima: o AC1 compara bloco canônico em
artefato, não folha base em artefato gerado — cobertura de um bloco não é cobertura da família.*

#### 64.14.7 DOIS ACHADOS NOVOS, medidos e NÃO consertados — os dois são pergunta de DESENHO

Apontar o `contraste-composicao.mjs` para `tela-lista` e `tela-gantt` pela primeira vez achou dois casos que
**não** vêm desta edição (a tinta deles não mudou) e que **não são de contraste**:

| # | Alvo | Medido | Diagnóstico |
|---|---|---|---|
| **GT-P6** | `tela-gantt`, `span "Execução"` dentro de `button.gt-barra` | **1,00** claro · **1,12** escuro, contra fundo de página | **SEGUNDA FORMA do 25º defeito**: quem pinta é o **ancestral** (`.gt-barra`, `position:absolute`, **20px** de altura) e o rótulo dilatado em 5px estoura a barra na vertical. Par real, pela cor declarada: **4,60** no claro e **7,39** no escuro — **passa**. *Não é defeito de artefato* |
| **LS-P6** | `tela-lista`, `td "Nenhuma fatura deste cliente no período filtrado."` | **1,22** nos **dois** temas | a barra de seleção em massa (`.massa`, `position:absolute`, `z-index:70`) **cobre** a linha de estado vazio. O pixel está certo: ele lê o **oclusor**. Mas **oclusão não é contraste** — reportá-la como contraste é erro de categoria |

| **AD-P1** *(nova)* | `banco-componentes` e `banco-tokens`, botão **destrutivo** no tema **escuro** | **2,76** · `#ffffff` sobre `#fc6f6a` (`action-destructive` escuro), piso 4,50 | **DEFEITO MEDIDO EM ABERTO**, e é a **mesma forma da MR-P1** um degrau adiante: no escuro o destrutivo é um vermelho **claro** e a tinta ali é branca. **Falta o token de tinta do destrutivo**, exatamente como faltavam as três tintas de marca. Não consertado: é decisão de identidade visual e pede rodada própria + gate |

**Nenhum dos dois primeiros foi consertado, e o motivo é a lei da casa:** *guarda que condena artefato correto força
conserto errado; quando a pergunta é de DESENHO e não de medida, emite-se **achado**, não veredito.* O GT-P6
pede conserto de **instrumento** (generalizar a cor declarada para o ancestral que pinta, parando no
primeiro `background-image`) — registrado como **CC-P2**. O LS-P6 pede decisão de **desenho** (a barra de
massa pode cobrir a linha de estado vazio?) e vai a gate.

#### 64.14.8 Pendências

| # | Estado em 2026-08-17 (v1.06) |
|---|---|
| **MR-P1** | ✅ **FECHADA.** Conserto na camada de token, censo em 48 artefatos × 2 temas com **0** violações, guarda nova **MR1**. Supersede o §64.13.3 |
| **MR-P2** *(nova)* | ✅ **FECHADA na mesma edição.** Mesma causa, tema oposto |
| **BT-P7** *(nova)* | ✅ **FECHADA**, com guarda nova **BP1** que mede por clique |
| **BC-P1** *(nova)* | ✅ **FECHADA** pelo pré-voo de regeneração |
| **BT-P5** | **PARCIAL.** Fechada no `guarda-tinta-marca.mjs` pela captura expandida (23 folhas alcançadas onde a pendência previa 5). **Aberta** no `contraste-composicao.mjs` |
| **GT-P6 · LS-P6 · CC-P2** *(novas)* | **ABERTAS**, §64.14.7 |
| **AD-P1** *(nova)* | **DEFEITO MEDIDO EM ABERTO**: branco sobre `action-destructive` no escuro mede **2,76**. Mesma forma da MR-P1, e pede a mesma resposta — token de tinta próprio, rodada e gate |
| **CC-P3** *(nova)* | o `contraste-composicao.mjs` não conhece a **isenção da SC 1.4.3 para componente INATIVO**: reprova botão `disabled` da galeria de estados. 9 achados em `banco-componentes`. Isenção nomeada pela norma, guarda cega a ela |
| **SI-P4 · IL-P2 · PS-P5 · spec do `.ent`** | **DECIDIDAS, execução pendente.** Ver a correção de premissa da PS-P5 em §64.14.4 |
| **CD-P9** | **ABERTA**, denominador recontado: 22 de 34 sem nenhuma regra de `forced-colors`; 34 de 34 sem regra genérica de botão |
| **CD-P10 · AC-P1 · AC-P2 · CD-P6 · E-P2 · CC-P1** | **ABERTAS** |
| **Gate** | as nove bancadas regeradas carregam `.vh` em `fixed` e `z-index` 9000, que **nunca passaram pelo olho do Rafael**. Inertes ao desenho, mas **conserto medido não é conserto visto** |

### 64.15 A AD-P1 FECHA por pesquisa, a isenção de componente INATIVO entra nas guardas, e o gate vira VISUAL (2026-08-18, v1.07)

> **Supersede a linha da AD-P1 no §64.14.7 e no §64.14.8**, que a descrevem como defeito medido em aberto.
> Relato completo no `validacao/MANIFESTO.md` **§87**.

#### 64.15.1 A regra de método que muda a forma de perguntar

Instrução do Rafael, verbatim: *"quando for me perguntar alguma coisa, me dê exemplos visuais… me diga qual
é o arquivo e o que tenho que ver nele, ou traga o print com a comparação"* e *"essa é a única solução? ou
é a melhor solução? pesquise sempre e aplique a melhor solução, não precisa me perguntar isso."*

**Leitura operacional, e ela vale para toda pergunta de gate deste projeto:**

1. **Pergunta que a MEDIÇÃO responde não é pergunta.** Meça e informe.
2. **Pergunta que a PESQUISA responde não é pergunta.** Faça o RITO, decida, aplique, registre as
   alternativas descartadas com número.
3. **O que sobra para o gate é julgamento de APARÊNCIA** — e vai com **recorte antes/depois por elemento,
   nos dois temas**, mais o arquivo e o que olhar nele. A folha
   `render-audit/gate-mr1/gate-visual.html` é o precedente e o molde.

#### 64.15.2 AD-P1 ✅ FECHADA — `--seed-text-on-action-destructive`

Nasce a tinta do destrutivo: `#FFFFFF` no claro (**5,19**) e `#521112` vermelho-900 no escuro (**5,26**);
no hover, 7,18 e 7,21. Antes, branco no escuro media **2,76**. **A tinta inverte com o tema**, como a do
botão primário. RITO: o **M3** faz assim (`on-error` inverte); o **Carbon** faz o oposto (não clareia o
fundo de perigo no escuro). Adotado o M3 por coerência com o que esta casa já pratica. Descartados com
número: cinza-900 (5,03) e turquesa-900 (4,92). Regra completa no `seed-tokens.md` **§5-c**.

**Guarda: contrato MR2**, no mesmo `guarda-tinta-marca.mjs`, com a superfície sob exame parametrizada.
**22 PASS · 0 FAIL · 74 `[n/a]`**, 50 textos sobre o destrutivo, zero violando. Prova de reprovação com o
caso fundador: devolvida a tinta literal, reprova 6; revertido, aprova.

#### 64.15.3 CC-P3 ✅ FECHADA — a isenção que a norma nomeia

A SC 1.4.3 isenta *"texto que faz parte de um componente de interface do usuário INATIVO"*. As duas guardas
de contraste não conheciam a isenção e reprovavam **9** botões `disabled` da galeria de estados. Agora a
sonda descarta folha dentro de `:disabled`/`[disabled]`/`[aria-disabled="true"]` **e imprime a contagem** —
isenção que não aparece no placar é a mesma doença da truncagem silenciosa.

#### 64.15.4 A regeneração das nove bancadas: a pergunta virou MEDIDA

O §64.14.8 mandava a regeneração a gate porque ela levou `.vh` em `fixed` e `z-index` 9000. **Medido:
excesso horizontal 0 px a 390 e a 320, antes e depois, e diferença pixel a pixel de 0 em 163.800 em três
bancadas.** A mudança é **provadamente invisível** e não precisa de olho humano.

#### 64.15.5 TK-P1 (nova) — o rótulo do degrau sobre o degrau

Na galeria de rampa do `banco-tokens.html`, **12 rótulos por tema** ficam abaixo do piso: `vermelho-500`
3,38 · `cinza-500` 3,38 · `dourado-500` 4,01 · `amarelo-500` 4,08. **A única tinta que resolve os quatro é
o preto puro** (4,89 · 6,21 · 6,02 · 6,17), **que não existe em nenhuma rampa da SEED**. Alternativa: mover
o rótulo para fora do degrau. **Pergunta de DESENHO — achado, não veredito.**

#### 64.15.6 Pendências

| # | Estado em 2026-08-18 (v1.07) |
|---|---|
| **AD-P1** | ✅ **FECHADA** por pesquisa e execução, com guarda **MR2** |
| **CC-P3** | ✅ **FECHADA** nas duas guardas |
| **Gate da regeneração** | ✅ **RESPONDIDO por medição** (0 pixels de diferença) |
| **LS-P6 · TK-P1** | **ABERTAS — as duas são de DESENHO e vão a gate com imagem** |
| **GT-P6 · CC-P2 · CC-P1 · CD-P9 · CD-P10 · BT-P5 · AC-P1 · AC-P2 · CD-P6 · E-P2** | **ABERTAS** |
| **SI-P4 · IL-P2 · PS-P5 · spec do `.ent`** | **DECIDIDAS, execução pendente** |

### 64.16 O GATE VISUAL REVERTEU a MR-P1, e a reversão melhorou o número (2026-08-18, v1.08)

> **Supersede o §64.15.2 e a linha da MR-P1 no §64.14**, no ponto em que mandam tinta ESCURA sobre a marca.
> Relato completo no `validacao/MANIFESTO.md` **§88**; regra vigente no `seed-tokens.md` **§5-d**.

#### 64.16.1 A forma nova: turquesa-600 com texto BRANCO

Verbatim do Rafael: *"ter o turquesa com o texto Branco dentro no tema claro é algo visualmente bonito…
vamos manter o texto branco."* Com o branco fixado, a pergunta passou a ser a **superfície**: branco sobre
turquesa-400 mede **2,71**; sobre turquesa-600 `#098475`, **4,60**. Escolhida a segunda.

Nascem `--seed-surface-brand-strong` (`#098475`, igual nos dois temas) e `--seed-text-on-brand-strong`
(`#FFFFFF`, igual nos dois temas), gêmeos **v1.17**. **`surface-brand` continua `#11B0A0`** e fica
**reservada para área SEM texto funcional** — é a âncora visual, não a superfície de rótulo.
**Fronteira do controle** (SC 1.4.11, piso 3,00) medida: 4,60 no claro, 4,05 na página escura, 3,31 na
superfície elevada escura.

**Migração:** 19 regras em 9 arquivos, por regra CSS; 17 geradores com os nomes novos; 19 artefatos
regerados com pré-voo. Depois: **zero** regras põem texto sobre a chapada. **Guarda: contrato MR3**,
**21 PASS · 0 FAIL · 75 `[n/a]`**, 65 textos, duas execuções idênticas.

**A referência de mercado foi MEDIDA e reprova:** o botão primário do ClickUp usa `#12a594` com branco a
12px/500 = **3,07**. *Referência é prova de prática, não de conformidade.*

#### 64.16.2 LS-P6 ✅ FECHADA — o problema era maior que a pergunta

Medido antes, em 17 larguras de 320 a 1440px: a barra de seleção em massa cobria conteúdo **nas 17**, com
**177 sobreposições** somadas. A altura dela varia de 98 a 218px conforme os botões quebram, contra reserva
fixa de 72px — reserva por constante teria de ser 230px e erraria em 15 das 17 larguras.
**Conserto na causa:** a barra sai de `absolute` e entra no fluxo como `sticky`; abaixo de 768px, onde
passa de 218px, deixa de flutuar (`static`). **Depois: 0 sobreposições nas 17**, visibilidade idêntica.

#### 64.16.3 TK-P1 ✅ FECHADA — e eram seis degraus, não quatro

Tinta preta nos degraus médios das seis rampas: vermelho 3,38→**4,89** · cinza 3,38→**6,21** ·
dourado 4,01→**6,02** · amarelo 4,08→**6,17** · turquesa 4,20→**6,51** · azul 4,13→**6,33**. A opacidade
dos rótulos saiu, porque reduzia o contraste **renderizado** sem aparecer na medida da cor **declarada**.
O preto puro entra no acervo **só aqui**, como tinta de rótulo de galeria.

> **LIÇÃO DE LEITURA DE PLACAR:** a primeira rodada consertou quatro porque a guarda imprimia *"CORTADA em
> 8 de 12"* e eu trabalhei sobre os oito visíveis. **O corte estava declarado e mesmo assim me pegou.**
> Placar cortado é placar incompleto — quem erra na leitura é o leitor.

#### 64.16.4 SEIS pendências novas, de artefatos que nunca tinham sido medidos

A pergunta *"isso pega em outro lugar?"* virou varredura dos 9 artefatos nunca medidos pelo
`contraste-composicao.mjs`. O padrão do TK-P1 é exclusivo do `banco-tokens.html` (1 de 34). Mas apareceram
**8 casos de componente**, todos anteriores a esta semana:

| # | Onde | Mede |
|---|---|---|
| **SU-P1** | `banco-superficies`, descrição de card sobre superfície de ação | **1,69** claro · **1,02** escuro (invisível) |
| **CP-P5** | `banco-composicao` escuro, 8× chip de anotação | **3,31** — a tinta de marca não clareia |
| **FF-P1** | `banco-formfield`, legenda de grupo sobre superfície de ação | **3,02** · **3,81** |
| **FB-P1** | `banco-feedback` escuro, badge de contagem | **2,76** — é o par da AD-P1, fecha com o token existente |
| **DD-P1** | `banco-dados` claro, 3× badge "Aprovada" | **4,28** |
| **DI-P1** | `banco-dataviz-di` claro, 2× rótulo "CSS" | **4,30** |

#### 64.16.5 Pendências

| # | Estado em 2026-08-18 (v1.08) |
|---|---|
| **MR-P1** | ✅ **FECHADA na forma da opção B.** Supersede o §64.15.2 |
| **LS-P6 · TK-P1** | ✅ **FECHADAS**, as duas por decisão no gate visual |
| **SU-P1 · CP-P5 · FF-P1 · FB-P1 · DD-P1 · DI-P1** *(novas)* | **ABERTAS**, §64.16.4 |
| **GT-P6 · CC-P2 · CC-P1 · CD-P9 · CD-P10 · BT-P5 · AC-P1 · AC-P2 · CD-P6 · E-P2** | **ABERTAS** |
| **SI-P4 · IL-P2 · PS-P5 · spec do `.ent`** | **DECIDIDAS, execução pendente** |

### 64.6 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **BT-P1** | **RE-gate visual** das três bancadas nas versões desta rodada — `banco-escolha` **v0.4**, `banco-data` **v0.4**, `banco-prioridade` **v0.2** | Os consertos foram medidos, não vistos. Quem reprovou a v0.1 do calendário foi o olho, não o placar (§61.7). **Nada promove antes** |
| **BT-P2** | BT1 nas TELAS, não só nas bancadas | ✅ **FECHADA no mesmo dia, por decisão do Rafael.** Verbatim: *"todo material que você produzir tem que ter a barra fixa, todos você já tem essa opção, só não é fixo"*. Aplicada e MEDIDA em nove telas (`tela-painel`, `lista`, `tabela`, `detalhe`, `quadro`, `gantt`, `shell`, `chat`, `referencia`) mais `banco-dominio`: depois de rolar, a barra mede `top=0`, `position=sticky`, altura 66px e **não é coberta** por nenhum elemento em nenhuma das dez. Suítes reexecutadas: painel 146·0 · detalhe 73·0 · quadro 35·0 · gantt 43·0 · shell 72·0 · composição 33·0 · contêiner 66·0 · domínio 28·0. **Fica aberto só o `tela-site.html`**, que não estava nesta cópia de trabalho |
| **E-P1** | **O BT7 vale para os artefatos de E-MAIL?** — sem veredito | Os 8 artefatos (`et-*`, `en-*`, `ea-*`, `seed-email-*`) **não foram medidos**: a `suite-reflow.mjs` varre só `banco-*` e `tela-*`, e a `suite-email.mjs` não rodou porque os alvos não estavam na cópia de trabalho. *Ausência de medição, não aprovação.* E-mail tem outra régua — tabela de layout, cliente sem CSS moderno, sem container query —, então estender o filtro por conta própria seria **decidir sem rodada**. Precisa do RITO de 3 rodadas |
| **BT-P3** | Levar a guarda GI2 (forma 4) às suítes antigas — `suite-painel`, `suite-container`, `suite-dominio`, `suite-icones` | Hoje ela roda em `suite-escolha`, `suite-data` e `suite-prioridade`. As antigas usam puppeteer-core. **Atualização 2026-08-17: o bloqueio de ambiente CAIU.** Elas rodam neste contêiner (`suite-container` **66·0**, `render-painel` **37·0**); bastou um shim local para o `@sparticuz/chromium`, pacote que existe só para devolver o caminho do binário do Chrome e cujo download real passa de 100 MB. O shim **não muda nenhuma medida** e vive em `node_modules`, nunca na pasta. Sobra trabalho de portabilidade, não risco |
| **BT-P4** | Levar o **BT7** ao resto do sistema — ✅ **FECHADA em 2026-08-17 (v0.99)** | A varredura (`validacao/suite-reflow.mjs`) cobre hoje **34 artefatos** e mede **120 PASS · 0 FAIL · 34 [n/a]**. Os SEIS que restavam foram fechados em duas levas. (1) `tela-quadro` (**89px**) e `tela-gantt` (**65px**) pelo **mesmo conserto do `.vh`**: texto visualmente oculto em `position:absolute` sem ancestral posicionado ancora no `body`, toma a coordenada **rolada** dentro de uma faixa que rola e **estende o `scrollWidth` do documento** — medido `right=479` num viewport de 390 e `right=385` num de 320; virou `position:fixed`, conserto de causa que curou as duas telas de uma vez. (2) Conserto próprio, cada um com a alternativa descartada registrada no `validacao/MANIFESTO.md` §79.3: `tela-referencia` (**84px** · `.busca` com `flex:1; max-width:520px` e **sem** `min-width:0`, piso de conteúdo medido em 187px → topbar que quebra abaixo de 640px), `tela-painel` (**35px** a 320 · `.escala-ctrl` `inline-flex` sem quebra, 307px num pai de 224px → `flex-wrap`), `banco-tokens` (**27px** a 390 e **97px** a 320 · `.type-row` sem quebra, e `1fr` — que **é** `minmax(auto,1fr)`, com piso `min-content` — impedindo a célula da rampa de encolher abaixo de 60,81px num contêiner de 272px → `minmax(0,1fr)` mais recuo de 4px, folga medida de 1,6px) e `banco-dataviz-di` (**61px** · tabela de 4 colunas de dado cruzado dentro de um `.nota` de 302px, mais **quatro `<pre>` que rolavam sem nenhum caminho de teclado** → tabela em `role="region" tabindex="0" aria-labelledby` e `<pre>` com `tabindex="0"` + `aria-label`). O `banco-dataviz-dp` (7,5 MB) **foi medido**: o teto de 3 MB era **estimativa** ("levaria minutos"), e a medida real é **2,05 s** para as duas larguras, com aprovação nos três contratos — teto elevado a 9 MB, ajustável por `TETO_MB=<n>`. *Fronteira declarada: a varredura cobre `banco-*` e `tela-*`; os 8 artefatos de e-mail não entram e não têm decisão sobre o BT7 — pendência **E-P1**, ausência de medição e não aprovação.* |


### 64.17 O MEDIDOR ESTAVA ERRADO EM DOIS DE SEIS, e o conserto achou um token FANTASMA que matava o tema escuro (2026-08-18, v1.09)

> **Leitor novo:** esta seção fecha seis pendências de contraste que vinham da varredura dos 9 artefatos
> nunca medidos (§64.16 e MANIFESTO §88.8) e acrescenta uma regra de consumo que vale para **toda** bancada
> e **toda** tela. Registro completo, com as provas, no `validacao/MANIFESTO.md` **§89**.

**CONTRATO NOVO — BT8. VALOR DE RESERVA EM `var()` É PROIBIDO PARA TOKEN DE COR.**

> **Declaração honesta de estado: a BT8 está ESCRITA e ainda NÃO TEM GUARDA.** A guarda é a pendência
> **NV-02**. Enquanto ela não existir, a BT8 é regra escrita e não medida — e *regra sem instrumento não é
> contrato cumprido*. O caso fundador dela já está medido e está abaixo.

```
PROIBIDO   color: var(--seed-action-primary-bg, #098475)
CERTO      color: var(--seed-action-primary)
```

**O porquê, medido:** o `banco-formfield.html` consumia `--seed-action-primary-bg` em **15** lugares. Esse
nome **não existe** no gêmeo — o nome canônico é `--seed-action-primary`, sem o sufixo `-bg`. Como havia
valor de reserva literal, o navegador **pintou assim mesmo**, com o turquesa escuro **fixo nos dois temas**.
Consequência: o **interruptor**, o **rádio**, a **caixa de seleção**, o **cartão de rádio**, o **controle
deslizante** e a **área de arrastar arquivo** nunca inverteram no tema escuro — seis componentes, e nenhum
instrumento tinha visto, porque o defeito se disfarçava de acerto (com o fundo preso no escuro, a tinta
branca media 4,60 e passava).

> **A regra em uma frase: token consumido e não definido tem de NÃO PINTAR.** O valor de reserva é
> anestésico — ele esconde a lacuna em vez de denunciá-la. É a NV-01 vista pelo outro lado.
> *Pendência **NV-02**: varrer os 34 artefatos atrás de `var(--token, <literal>)` cujo token não seja
> declarado. Ainda não rodada.*

**Assim que a superfície passou a inverter, apareceu o defeito que estava escondido atrás dela:** três
botões primários do mesmo artefato tinham `color:#fff` **literal** e passaram a medir **1,83** no escuro.
Migrados para `--seed-text-on-action-primary`: **4,60** claro · **7,39** escuro.

**AS SEIS PENDÊNCIAS, e o que cada uma era de fato**

| # | Onde | Era | Virou | Natureza |
|---|---|---|---|---|
| **SU-P1** | `p.seed-card__desc` · `banco-superficies` | 1,69 · 1,02 | **7,75 · 8,11** | **FALSO POSITIVO** — 26º defeito de instrumento |
| **FF-P1** | `legend.seed-field__label` · `banco-formfield` | 3,02 · 3,81 | **13,86 · 14,16** | **FALSO POSITIVO** — 26º defeito |
| **FB-P1** | `.seed-badge--count` · `banco-feedback` | 2,76 escuro | **5,19 · 5,26** | real — tinta `#fff` literal sobre superfície que clareia |
| **CP-P5** | `.tagok` · `banco-composicao` | 3,31 escuro | **4,60 · 8,31** | real — tinta saída da **rampa**, não do token semântico |
| **DD-P1** | `.seed-badge.ok` · `banco-dados` | 4,28 claro | **5,89** | real — **valor divergente do gêmeo** |
| **DI-P1** | `.amostra.via-css` · `banco-dataviz-di` | 4,30 claro | **6,51** | real — nenhuma tinta da casa passava |

**Duas nasceram e fecharam junto:** **CP-P6** (aviso vermelho com hex literal em atributo de estilo, 2,93 no
escuro → `--seed-feedback-danger-text`) e **FF-P2/FF-P3** (o token fantasma acima).

**O QUE ISSO ENSINA SOBRE CONSUMO DE TINTA — três formas do mesmo erro, todas vistas neste dia**

1. **Tinta literal** (`#fff`, `#C6393B` em `style=`): não inverte, e some do alcance de qualquer guarda que
   procure token.
2. **Tinta de RAMPA** (`var(--seed-turquesa-600)`): o degrau é fixo por definição; rampa é primitivo, não
   semântico. Quem carrega tema é o token semântico — aqui, `--seed-text-brand`.
3. **Tinta de token INEXISTENTE com reserva**: pinta, engana e trava o tema (BT8 acima).

**A regra positiva:** **toda tinta de componente sai de token SEMÂNTICO declarado nos DOIS blocos de tema do
artefato.** Se o artefato não declara o token, ele passa a declarar — não se inventa reserva.

**PRECEDENTE APLICADO SEM RITO NOVO:** o par do FB-P1 é **o mesmo par** que a AD-P1 resolveu com três
rodadas em 2026-08-18. *Rito não se repete para o mesmo par; repete-se a decisão, citando-a.*

**GATE:** a folha visual desta edição é `render-audit/gate-cc-v127/fechamento.html`, com recorte
antes/depois por elemento nos dois temas. **Uma única pergunta aberta**, e ela é de escopo de decisão já
tomada: se o **preto puro** `#000000` — liberado na TK-P1 para rótulo de galeria de cor — vale também para
o rótulo "CSS" da figura de prova de impressão do `banco-dataviz-di`. As três alternativas da casa foram
medidas e **nenhuma passa**: cinza-900 **4,30**, turquesa-900 **4,20**, branco **3,22**, contra piso 4,50.

### 64.18 CONTRASTE FORÇADO — contratos FC1 e FC2, e a BT8 ganha guarda (2026-08-18, v1.10)

> **Leitor novo:** esta seção fecha a **CD-P9** e a **CD-P11** nos **34 artefatos** do acervo e dá
> instrumento ao contrato **BT8**, que na §64.17 nasceu escrito e sem guarda. Registro completo, com as
> provas nos dois sentidos, no `validacao/MANIFESTO.md` **§90**.

**CONTRATO NOVO — FC1. TODO CONTROLE TEM FRONTEIRA QUANDO O NAVEGADOR DESCARTA O PREENCHIMENTO.**

Em `forced-colors: active` (o modo de contraste forçado do Windows e do Firefox) o navegador **descarta
`background-color` e `box-shadow`** e impõe a paleta do usuário. **Botão identificado apenas pelo
preenchimento simplesmente desaparece.**

**CONTRATO NOVO — FC2. O FOCO CONTINUA VISÍVEL NO MESMO MODO.** O anel de foco desta casa é
`--seed-focus-ring`, que é **`box-shadow`** — e `box-shadow` é justamente o que o modo joga fora. Sem
regra própria, **o foco some em 100% dos controles**.

**A REGRA, e ela é uma só, idêntica nos 34 artefatos:**

```css
@media (forced-colors: active) {
  button, [role="button"], input[type="button"], input[type="submit"],
  input[type="reset"], summary, a.seed-btn, a[class*="btn"] {
    border: 1px solid ButtonText !important;
  }
  :focus-visible { outline: 2px solid Highlight !important; outline-offset: 2px !important; }
}
```

**TRÊS DECISÕES DENTRO DELA, com o porquê:**

1. **A fronteira vai por `border`; o `outline` fica reservado ao FOCO.** *Alternativa descartada — e ela
   era o conserto escrito na própria pendência CD-P9: `outline: 1px solid transparent`.* Recusada porque
   em contraste forçado o `outline` é **o único mecanismo que ainda funciona para indicar foco**, e
   gastá-lo na fronteira deixaria o foco sem sinal. *A pesquisa revogou o conserto que já estava escrito.*
2. **`!important` é deliberado.** `.seed-btn{border:0}` tem especificidade maior que um seletor de tipo;
   medido, sem ele a FC1 continuava reprovando.
3. **A lista de seletores é a MESMA do contrato FC1 na guarda.** *Regra e régua têm de olhar para o mesmo
   conjunto, senão uma cobre o que a outra não mede.*

**ONDE SE ESCREVE — e isto vale para QUALQUER mudança transversal daqui em diante:**

| tipo de artefato | onde escrever | por quê |
|---|---|---|
| **gerado** (19 dos 34) | no **molde** (ou no gerador), e **regerar com PRÉ-VOO** | escrever no artefato é desfeito na próxima regeneração |
| **de mão** (14 dos 34) | no próprio arquivo | não há molde |
| **`banco-dataviz-dp`** | **nos dois**, e **NÃO regerar** | o gerador dele não reproduz o artefato — pendência **DP-P9** |

> **ARMADILHA DE MOLDE, medida:** **6 dos 20 moldes passam por `str.format()`** (`shell`, `site`,
> `tela-chat`, `tela-lista`, `tela-referencia`, `tela-tabela`). Neles **toda chave literal `{` `}` tem de
> vir DOBRADA**; sem isso o gerador morre com `KeyError`. Aconteceu nos seis, na primeira tentativa.

**A PROVA DE QUE NÃO MUDA NADA FORA DO MODO:** `validacao/prova-inercia.mjs` compara `antes/` com a raiz
**pixel a pixel**, nos dois temas, a 1440px e a 390px. Resultado: **0 de 13.176.000 pixels diferentes**.
*Por isso esta mudança não foi a gate: não há o que julgar com o olho.*

**PLACAR, duas execuções idênticas cada:**

| | antes | depois |
|---|---|---|
| **FC1** — controles ativos sem fronteira | **408 de 1.336** | **0 de 1.336** (68 PASS · 0 FAIL · 24 isentos) |
| **FC2** — controles focados sem outline | 100% (o anel é `box-shadow`) | **0 de 386** (68 PASS · 0 FAIL) |

**PROVA DE REPROVAÇÃO, com o caso fundador de cada cláusula:** removido o bloco do `tela-tabela.html`, a
FC1 reprova **22 controles sem fronteira**; removido do `banco-componentes.html`, a FC2 reprova **10 de 12
focados**. *E note: o primeiro caso fundador que tentei para a FC1 — o `banco-componentes` — **passou sem
o bloco**, porque os botões dele já tinham borda. **Caso fundador é o artefato onde o defeito EXISTE**, não
um qualquer que se tenha à mão.*

**A BT8 GANHA GUARDA — `validacao/guarda-valor-reserva.mjs`, contrato NV1.** Duas cláusulas: **NV1-a**
reprova `var(--x, …)` com `--x` nunca declarado (token FANTASMA); **NV1-b** emite `[achado]` para reserva
de cor sobre token que existe. Placar do acervo: **10 PASS · 0 FAIL · 0 `[achado]` · 86 `[n/a]`** — zero
fantasmas e zero reservas de cor. **Dois fantasmas reais foram achados e consertados na causa:** o ponto
de presença do `banco-pessoa` pintava com a superfície de **AÇÃO** em vez da de **SUCESSO** (idênticas no
claro, `#66D1C2` contra `#11B0A0` no escuro), e o `banco-prioridade` consumia `--seed-radius-md` sem
declarar.

## 65. Identidade de entidade — seletor de ícone e cor — **`estável`** (F7.5, 2026-08-16 · **promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado"**) · SI1–SI16 · fecha **C6** · bancada `banco-identidade.html` **v0.4** (31 glifos, 6 colunas: 5 linhas cheias + 1 — §65.12 e §65.13) · `suite-identidade.mjs` **33 · 0 · 0** · **§65.7: SI17, a identidade EM USO, `guarda-si17.mjs` 16 · 0 · 86**

> **O que este componente é, para quem lê sem ter visto a sessão que o gerou.** Quando um objeto de
> primeiro nível nasce no produto — um **espaço**, uma **lista**, uma carteira de clientes —, alguém
> precisa dar a ele a **identidade visual** que passará a representá-lo na sidebar, no breadcrumb e
> em toda referência cruzada: o quadradinho colorido com um glifo dentro. Este é o controle que
> produz esse par. Ele **não inventa vocabulário**: consome dois conjuntos que já eram canônicos e
> **fechados** neste sistema — as **seis** posições de cor de entidade do **CP23**
> (`seed-composicao.md`) e os **31** glifos do set canônico do **§45** (deste arquivo; eram 28 até
> a v1.38, 30 desde a FV-P5 da v1.39 e 31 desde a GL-R2 da v1.42 — o seletor acompanhou nas duas,
> em 2026-09-05: §65.12 e §65.13), com os
> aliases pt-BR/en que o **NM1** mandou existir e que, até aqui, só a ⌘K do §39 consumia.
>
> **Fronteira com o §62 (seletor de pessoa):** pessoa é **círculo**, entidade é **quadrado de raio
> pequeno**. A distinção de forma é anterior a este componente — vem do produto medido
> (`estudo-clickup-completo.md` §10b.3) e é a razão de as duas famílias nunca se confundirem numa
> lista mista. Este §65 herda a regra, não a cria.
>
> **Fronteira com o C5 (seletor de entidade colorida), que segue AUSENTE:** o §65 é o controle que
> **define** a identidade de uma entidade; o C5 é o controle que **escolhe entre entidades já
> existentes**. São dois. O C5 continua bloqueado pela **PS-P2** (só se abre escrevendo no produto de
> referência), e este §65 **não o fecha** — dizer que fecharia seria contar cobertura que não existe.

### 65.0 Rodada zero — a leitura visual existe, mas é do RESULTADO, não do controle

A rodada zero deste projeto pergunta, antes de qualquer spec: *o arquétipo tem leitura visual do
produto de referência? Se não, qual dos três vereditos — **não visitado**, **sem instância**, **não
se aplica**?*

**Veredito duplo, e é preciso dizer os dois:**

| O quê | Veredito | Base |
|---|---|---|
| O **controle** (a paleta e a grade de glifos, abertas na criação do espaço) | **NÃO VISITADO** | Ele só abre pelo fluxo de **criação/edição**, que é escrita. A navegação autorizada é **somente leitura** (mesma barreira nomeada da **PS-P2**, que bloqueia C5 e C8) |
| O **resultado** do controle (o quadrado de identidade em uso) | **MEDIDO** | `estudo-clickup-completo.md` §3.2 — *"quadrado 20×20, `border-radius: 5px`, cor própria, inicial em 12px/700"* — e §8.2/§8.3, onde os pares de cor foram medidos um a um |

**E o resultado medido é suficiente para decidir**, porque o defeito que ele revela é de
**vocabulário**, não de ergonomia: a paleta é **aberta** e a tinta é **branca fixa**, e por isso
**6 dos 13** ícones de identidade de espaço medidos **reprovam**, variando de **3,03** a **5,83**
contra o piso 4,5. Nenhuma escolha de layout do seletor conserta isso; só o fechamento da classe
conserta. *O que a visita traria é ergonomia — e ergonomia é o que o RITO abaixo resolve.*

> **Achado que a mesma medição entrega, e que é mais forte que o defeito:** dentro do mesmo produto
> existe a política certa. O ícone amarelo `#ffc53d` **não** usa branco: usa tinta escura, e mede
> **13,31**. *A política que funciona já mora lá — ela só não foi aplicada ao componente que mais
> aparece na tela.* É o mesmo padrão do C21 (chip de campo personalizado × chip de status).

### 65.1 RITO — três rodadas encadeadas

| Rodada | Fonte | O que trouxe para a spec |
|---|---|---|
| **R1 — canon** | ARIA APG: *Radio Group* e *Grid (layout grid)* | O `grid` existe para **agrupar widgets preservando a semântica dos filhos** e navegar em duas dimensões (setas nas quatro direções, `Ctrl+Home/End`, um único tab stop). O `radiogroup`/`listbox` é o padrão **quando os itens são mutuamente exclusivos**. Como a escolha aqui É exclusiva, o papel é `radiogroup` — e o que se importa do `grid` é só o **teclado bidimensional** (SI5) |
| **R2 — mercado/stack** | Adobe React Aria, *Accessible Color Descriptions* (Spectrum) · Atlassian *color picker swatches* · Salesforce Lightning *Color Picker* | **Valor numérico não é nome acessível.** Verbatim do artigo da Adobe: *"I can't imagine what color that is just by hearing those numbers"* — eles substituíram o anúncio de RGB por descrição legível (algoritmo em OKLCH, 13 matizes base + modificadores). Para uma paleta **fechada de seis**, o algoritmo é desnecessário: **são seis palavras** (SI4) |
| **R3 — normas** | WCAG 2.2: *Understanding SC 1.4.11 Non-text Contrast* · SC 1.4.1 *Use of Color* · SC 2.5.8 *Target Size (Minimum)* · SC 4.1.3 *Status Messages* | **Achado que muda a spec:** o 1.4.11 **não cobre a amostra de cor em si** — a cor ali *é* o conteúdo, e o que se aplica é o **1.4.1**, que exige que a cor não seja o único canal. O que o 1.4.11 **cobra** é o **indicador de estado** (o anel da escolhida) contra as **cores adjacentes** (SI11). Daí a spec ter dois pisos distintos, e não um só |

**Pisos, e por que são dois:**

- **4,50** para o par **glifo sobre a cor da posição** (SI3). O glifo dentro de um quadrado de 20px é
  pequeno **e carrega significado** — é ele que distingue duas entidades que dividem a mesma cor. O
  que carrega significado em tamanho de texto é medido **como texto**. É também o piso contra o qual
  o produto de referência reprova 6 dos 13.
- **3,00** para o **indicador de escolha** (SI11), que é elemento não-textual (SC 1.4.11).

### 65.2 Os contratos SI1–SI16

| # | Contrato | Por quê | Descartado, com o porquê |
|---|---|---|---|
| **SI1** | A identidade é um **PAR**: glifo **e** posição de cor. Nunca só cor, nunca só glifo | Acima de seis entidades a **cor repete** (CP23) e o glifo passa a ser o único distintivo; e na escala de cinza a cor cai antes do glifo | **Inicial da palavra sobre cor**, que é o que o produto de referência faz: duas entidades com a mesma letra e cores próximas ficam idênticas, e a inicial não sobrevive a renome |
| **SI2** | A paleta é **FECHADA** nas seis posições do CP23. Não existe cor livre | Com classe fechada, *"todas as cores passam?"* vira **seis medições**. Com classe aberta é amostra, e amostra sobre classe infinita não prova nada — é a lição que fechou a **PS-P3** | **Roda de matiz / campo hex**: classe infinita. **Medido no produto de referência: 6 de 13 reprovam (3,03–5,83)** |
| **SI3** | Cada posição carrega a **tinta DECLARADA**, não uma tinta calculada em render | Tinta por luminosidade oscila entre vizinhos e é instável na zona de cruzamento (o CP21 já registrou isso). Declarada, o par é auditável antes de existir tela | **Tinta calculada** (C21 do estudo): funciona, mas o cálculo tem de rodar em toda superfície e nenhuma delas prova a outra |
| **SI4** | Toda amostra tem **nome textual**; **hex não é nome** | R2/Adobe: número não descreve cor a quem ouve. Com paleta fechada bastam seis palavras — “turquesa vivo”, “dourado profundo”… | **Hex como `aria-label`** (prática comum e medida como insuficiente); **nome de marca inventado** (“Aurora”, “Cobre”): não é reconhecível fora de quem escreveu |
| **SI5** | As duas escolhas são **`radiogroup`**; a de ícone navega em **GRADE** — ↓/↑ andam **± o número de colunas**, Home/End vão às pontas; na última linha, incompleta desde a v0.4, célula **sem vizinha abaixo não move o foco** (padrão APG grid — §65.13) | A escolha é exclusiva: `radiogroup` é o que leva a exclusividade à AT. Mas a grade é bidimensional, e **seta que anda em fila numa grade mente sobre o layout**: ↓ tem de cair na célula visualmente abaixo | **`role="grid"`** — comunica estrutura e **não** comunica “escolha um”; é o defeito **C130** medido no §62. **`listbox`** — comunicaria seleção, mas o teclado seguiria linear e o sistema já tem o mecanismo de `radiogroup` em SG1/PR2: *um mecanismo, não mais um*. **Salto para a última célula** quando não há vizinha abaixo (o `Math.min` da v0.3) — troca de coluna disfarçada de descida, descartado na v0.4 |
| **SI6** | O nome acessível de cada opção traz a **posição na escala** — “Ícone subestação, 14 de 31”, “Cor turquesa vivo, 1 de 6” | Herda o **PR10**: o rótulo sozinho não diz onde a pessoa está num conjunto que ela não vê | Nome só com o rótulo (não localiza); contagem só no grupo (não acompanha o foco) |
| **SI7** | A busca consome os **ALIASES pt-BR e en** do inventário §45.3, com e sem acento | O **NM1** escreveu que os aliases existem para serem consumidos, e até aqui só a ⌘K os consumia. Quem cria um espaço de subestação digita “subestação”, não `substation` | Busca só pelo nome canônico em inglês (exige saber o vocabulário do set); busca difusa/fuzzy (imprevisível de auditar, e o set tem 31 itens) |
| **SI8** | O filtro **anuncia a contagem** — “7 de 31 ícones” — em região de status (SC 4.1.3) | Filtrar sem anunciar deixa quem não vê sem saber se sobrou algo | Anúncio por `alert` (interrompe); silêncio (o padrão do produto de referência) |
| **SI9** | Busca sem resultado tem **estado de vazio com caminho de volta** que funciona | §25 (estados não-felizes): vazio mudo é beco. E o caminho de volta é **botão**, não instrução | Só texto “nada encontrado” (não devolve ninguém); limpar sozinho ao não achar (destrói o que a pessoa digitou) |
| **SI10** | A prévia mostra o par **no tamanho de uso real** — 20px (sidebar), 32px (cabeçalho) e 48px (o próprio ajuste) | A decisão da pessoa é sobre **como fica na sidebar**, não sobre a amostra ampliada. Glifo que se lê a 48px pode virar mancha a 20px | Prévia só ampliada (esconde o problema que importa); prévia só a 20px (não deixa ver o desenho) |
| **SI11** | O **indicador de escolha** mede **≥ 3,00 contra as cores que o ENCOSTAM**, nos dois temas | SC 1.4.11 cobra o indicador de estado contra o **adjacente**. “Adjacente” é o que encosta — medido no pixel, não suposto (ver 65.4) | Piso 4,5 no anel (é elemento não-textual; exigir 4,5 reprovaria indicador legítimo); nenhum piso (o anel some no tema que ninguém abriu) |
| **SI12** | A escolha tem **DOIS canais** — anel **e** glifo `check` — nunca só cor | SC 1.4.1. Na escala de cinza duas posições de claridade próxima colidem; sob `forced-colors` o fundo do sistema substitui o nosso. **A forma sobrevive aos dois** | Só anel (morre em forced-colors); só opacidade/escala (não é canal para quem não enxerga a diferença) |
| **SI13** | Alvo **≥ 44×44px** em amostra e em célula de glifo | Piso do projeto, acima do SC 2.5.8 (24×24). Grade de 31 células é onde a tentação de encolher aparece primeiro | Célula de 32px “porque cabem mais” (o alvo é o que se erra, não o que se vê) |
| **SI14** | Mudar anuncia **o par completo**, não o campo mexido — “Identidade: subestação em azul profundo.” | As duas escolhas compõem **um** objeto. Anunciar só “azul profundo” obriga quem ouve a montar o par de memória | Anúncio por campo (dois anúncios para uma decisão) |
| **SI15** | A identidade inicial é **DERIVADA do nome e reproduzível** — soma de códigos de ponto —, nunca aleatória | Identidade que dança entre duas gerações do mesmo arquivo é o defeito que o PS8 já proibia para avatar. **E a derivação tem de bater entre as linguagens**: gera em Python, consome em JavaScript | `Math.random()` (nunca reproduz); **`hash()` do Python** (randomizado por processo — `PYTHONHASHSEED`; a armadilha está registrada no §74 do MANIFESTO) |
| **SI16** | **Nunca nasce uma sétima cor**: o seletor não tem “personalizar”, campo hex nem input de cor | É o CP23 literal. E é o que mantém a prova possível: um único caminho para cor livre devolve a classe ao infinito | “Só uma cor extra para o cliente X” — é assim que toda paleta fechada morre |

### 65.3 A anatomia, e os números medidos

**A paleta (CP23, enumerada nos dois temas — `suite-identidade` SI3):**

| Posição | Fundo | Tinta | Medido (claro e escuro) |
|---|---|---|---|
| E1 — turquesa vivo | `#11B0A0` | `#242E34` | **5,11** |
| E2 — dourado vivo | `#D49400` | `#242E34` | **5,30** |
| E3 — azul vivo | `#01B3E0` | `#242E34` | **5,63** |
| E4 — turquesa profundo | `#005048` | `#FFFFFF` | **9,37** |
| E5 — azul profundo | `#006783` | `#FFFFFF` | **6,43** |
| E6 — dourado profundo | `#7B5500` | `#FFFFFF` | **6,68** |

**Pior par: 5,11; piso 4,50.** Os números **não mudam** entre temas, e é isso que se quer: os tokens
`--seed-entity-*` são invariantes de tema por decisão registrada (gêmeo v1.11), então fundo e tinta
são do **mesmo par fixo** e o par **não se rompe na inversão** — contrato **BT3** do §64. O achado
sai **impresso** no placar em vez de tolerado em silêncio (**BT4**).

**O contraexemplo, impresso na bancada ao lado do canônico** (cinco dos treze pares medidos no
produto de referência, `estudo-clickup-completo.md` §8.2):

| Fundo | Tinta | Razão | |
|---|---|---|---|
| `#12a594` | branco fixo | **3,07** | reprova |
| `#f76808` | branco fixo | **3,03** | reprova |
| `#0091ff` | branco fixo | **3,23** | reprova |
| `#ab4aba` | branco fixo | 4,75 | passa |
| `#ffc53d` | **tinta escura** | 13,31 | passa |

**A grade:** 31 glifos em **6 colunas — 5 linhas cheias + 1 célula** (bancada v0.4, 2026-09-05; eram
30 em 6 × 5 na v0.3 do mesmo dia, e 28 em 7 × 4 até a v0.2 — ver §65.12 e §65.13). **A régua é
“linhas inteiras EXCETO a última”, por decisão dele** (MANIFESTO §150, verbatim: *“T-SET: glifo novo
+ régua da grade aceita última linha incompleta (recomendado)”*). A régua anterior — *linhas
inteiras*, `total % colunas == 0` — era consequência de 28 e de 30, não princípio: 31 é primo, e
nenhuma grade de 2 a 30 colunas fecha. O que o SI5 exige de verdade é ↓ com destino **previsível**,
e na última linha incompleta o destino previsível é *não há célula abaixo, o foco fica* — a regra do
padrão WAI-ARIA APG para grid (consultado em 2026-09-05: *“If focus is on the bottom cell in the
column, focus does not move”*), aplicada no script da bancada e medida pela guarda `SI-OP3c`. O
gerador segue exigindo pelo menos **duas linhas cheias** (grade, não fila) e declara a sobra no
resumo da geração. O 6 fica: é o desenho do gate de 2026-09-05, e 7 colunas dariam 4 cheias + 3
(mais sobra, no desenho que o 30 já tinha abandonado). Medido no Chrome a 1200 px na regeneração de
2026-09-05, nos dois temas: 31 células de **44 × 44**, 6 linhas — 6, 6, 6, 6, 6 e 1 —, nenhuma
cortada, nenhum glifo cortado.

**Alvo medido:** amostra **44×44**, célula **44×44**.
**Indicador de escolha medido no pixel:** anel de **2px**, `#0b3330` no claro e `#ddece8` no escuro,
**13,72** e **12,49** contra as faixas que o encostam.

### 65.4 Um defeito de instrumento, e um falso positivo evitado — os dois na mesma guarda

**Décimo sexto defeito de instrumento (achado nesta rodada, dentro da suíte, antes de qualquer
conserto ser aplicado ao artefato):** o parser de cor entendia `rgb()` e `color(srgb …)` — as duas
formas que os defeitos 5º e 12º já tinham ensinado — e **não entendia HEXADECIMAL**.
`getComputedStyle` devolve `rgb()` para propriedades de cor, mas o **valor de um token** lido por
`getPropertyValue('--seed-text-primary')` volta **como está escrito no gêmeo**: `#242E34`. O regex de
dígitos casava só o final (`34`) e devolvia `rgb(34, 0, 0)`.

*O número que saiu foi **232,67**.* Razão de contraste acima de **21** é **fisicamente impossível**
(branco sobre preto), e a guarda **emitiu PASS**.

> **Regra nova, e ela vale para toda guarda de contraste do projeto: número fora da faixa física
> [1,00 … 21,00] não é veredito, é MEDIDOR QUEBRADO.** A guarda passa a reprovar a si mesma quando
> mede fora da faixa, com essa palavra no placar. É a irmã da regra do 15º defeito — *guarda que mede
> fora do controle não declara veredito nenhum*; aqui, guarda que devolve número impossível também
> não.

**E o falso positivo, evitado por medição:** consertado o parser, a guarda comparou o anel de escolha
com a **cor da amostra** e reprovou o tema escuro em **2,23**. Estava prestes a forçar conserto num
artefato correto — o que o §64.3 (forma 2) registra como **tão caro quanto não ver o defeito**.
Medido no pixel, os dois **não se tocam**: entre a amostra e o anel há uma faixa de **2px** de
`surface-raised`. O SC 1.4.11 cobra 3:1 contra as cores **adjacentes**, e *adjacente é o que encosta,
não o que está por perto*.

*Correção:* a guarda varre a **linha de pixels** que sai do centro da amostra para fora, localiza a
faixa do anel pela cor computada e mede contra as duas faixas que o **encostam**. Se alguém remover a
faixa de respiro, a vizinha passa a ser a própria amostra e a guarda cobra isso sozinha — **porque
ela mede o que foi pintado, não o que foi declarado**.

### 65.5 A bancada e a suíte

`banco-identidade.html` **v0.1**, gerada por `validacao/gen-banco-identidade.py` sobre
`validacao/banco-identidade-template.html`. Barra de provas **fixa** e seletor de largura desde a
v0.1 (BT1/BT2). Estados impressos **lado a lado** (CP31) — o controle é o Estado 1 e é operante.

**FONTE ÚNICA DOS GLIFOS, com a fronteira dita:** o gerador **não copia** os 31 desenhos para dentro
de si — e, desde a v0.3 (§65.12), **nem a lista de nomes**: até a v0.2 a lista de 28 era escrita à
mão e envelheceu em silêncio no dia em que o set foi a 30. Ele lê `banco-icones.html` por `data-icon`
(nome, categoria, aliases e desenho), reordena na ordem do §45.3 e **aborta** se a contagem sair do
denominador declarado (31) ou se aparecer categoria fora da tabela — mesma doutrina do
`tokens_bloco.py`, porque lista duplicada é drift esperando data. O certo
seria um `seed-icones.json`, que o **NM5** já prevê; enquanto não existe, a dependência fica
registrada em **SI-P1**.

**`suite-identidade.mjs` — 28 PASS · 0 FAIL · 0 [n/a] · 1 medida.** Oito das guardas são de
**operabilidade**: elas clicam, digitam e teclam, porque em 2026-08-16 a `banco-data.html` v0.1
passou em 25 PASS · 0 FAIL com o calendário **inoperante**.

| Guarda | O que ela faz de fato |
|---|---|
| `SI-OP0` | monta o controle depois da carga em página |
| `SI-OP1` | **clica** numa amostra: escolhe, repinta a prévia e anuncia |
| `SI-OP2` | **clica** num glifo: troca a prévia nos três tamanhos |
| `SI-OP3` | **tecla** ↓ e prova por **geometria** (x mantido, y maior) que desceu uma linha |
| `SI-OP4` | **digita** “subestacao”, “subestação”, “trafo” e “zap” e confere o que sobra |
| `SI9` | **digita** um termo sem resultado, confere o vazio e **clica** no caminho de volta |
| `SI-OP6` | Home/End alcançam as pontas do set |
| `SI15` | **recalcula a derivação em JavaScript** e compara com o que o Python escreveu |

### 65.6 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **SI-P1** | O gerador depende de `banco-icones.html` como fonte dos 31 glifos (28 até 2026-09-05; 30 e 31 no mesmo dia) | O `seed-icones.json` do **NM5** não existe. Enquanto não existir, gerar a bancada de identidade exige que a bancada de ícones esteja na pasta — acoplamento entre artefatos que **não deveria** existir, e que só some com o arquivo de dados |
| **SI-P2** | ✅ **FECHADA — gate visual APROVADO em 2026-08-17.** O olho achou dois glifos mal desenhados no set do §45 (`recycle`, `growth`), que **passavam em 30 verificações automatizadas**; redesenhados, com supersede no §45.4. A seção é promovida a `estável` |
| **SI-P3** | O **controle** do produto de referência segue **não visitado** | Ele só abre por escrita (mesma barreira da PS-P2). O que falta é **ergonomia comparada**, não vocabulário — o vocabulário está decidido e medido. Reabrir só se a navegação de escrita for autorizada |
| **SI-P4** | ✅ **FECHADA — EXECUTADA em 2026-08-19, sem gate: a mudança foi provada INERTE** (0 de 26.352.000 pixels em 32 comparações, duas execuções idênticas). O bloco do `.sigla` passou a ser **único e idêntico** nas telas que o carregam, e o censo do §69.6 estava **incompleto**: não eram quatro telas, eram **OITO** — `tela-chat`, `tela-gantt`, `tela-quadro` e `tela-referencia` também o reimplementavam e não constavam de lista nenhuma. A execução, o contrato **SI17** e a guarda que o mede estão no **§65.7**. *O `.ent` de 12px sem glifo continua fora: ele não é este §65* |


### 65.7 SI17 — a identidade em USO: um bloco único nas OITO telas, e a guarda que mede COMPORTAMENTO

> **Escrito em 2026-08-19, ao executar a SI-P4.** Esta seção é autossuficiente: presume um leitor que
> nunca viu a conversa que a gerou.

#### 65.7.1 O que é o `.sigla`, e qual era o defeito

O **`.sigla`** é o quadrado de identidade de entidade **em uso nas telas** — 24×24px, raio 6px, glifo
`svg` de 14px, `aria-hidden`, na barra superior, ao lado do nome da entidade. Ele é o §65 **aplicado**: o
§65.1–§65.6 especifica o *seletor* de identidade (a bancada `banco-identidade.html`, onde a pessoa
ESCOLHE glifo e cor); o `.sigla` é o *resultado* dessa escolha, mostrado no produto.

**O contrato escrito, e ele estava no comentário do próprio `tela-shell`:** *"o produto escreve `1..6`, o
DS resolve fundo E tinta"*. **Medido em 2026-08-19, o contrato era FALSO em sete das oito telas:**

| tela | posições de cor declaradas | onde a cor morava | `data-ident` no HTML | glifo · rótulo |
|---|---|---|---|---|
| `tela-shell` | as SEIS | regras `[data-ident="N"]` | `4` | quadradinhos · "SEED engenharia" |
| `tela-lista` | **só a `4`** | idem | `4` | quadradinhos · "SEED engenharia" |
| `tela-tabela` | **só a `4`** | idem | `4` | quadradinhos · "SEED engenharia" |
| `tela-referencia` | **só a `4`** | idem | `4` | quadradinhos · "SEED engenharia" |
| `tela-detalhe` | **NENHUMA** | **na regra BASE**, fixa | **ausente** | **raio** · "SEED · Operação" |
| `tela-chat` | **NENHUMA** | **na regra BASE**, fixa | **ausente** | quadradinhos · "SEED engenharia" |
| `tela-gantt` | **NENHUMA** | **na regra BASE**, fixa | **ausente** | **raio** · "SEED · Operação" |
| `tela-quadro` | **NENHUMA** | **na regra BASE**, fixa | **ausente** | **raio** · "SEED · Operação" |

**As consequências, todas de contrato:**

1. **Em `lista`, `tabela` e `referencia`, escrever `data-ident` de `1`, `2`, `3`, `5` ou `6` NÃO PINTAVA
   NADA.** A regra base não tinha fundo: o quadrado ficava **transparente** e o glifo caía sobre a barra.
2. **Em `detalhe`, `chat`, `gantt` e `quadro` a identidade estava CONGELADA** na regra base. Não existia
   valor que o produto pudesse escrever para mudá-la — o que contraria o **SI1** (a identidade é um PAR
   que o produto escolhe).
3. **O `tela-shell`, a única que cumpria o contrato, tinha a tinta do padrão LITERAL** (`color:#FFFFFF`
   cru) onde cabe `--seed-text-on-brand-deep`, que vale `#FFFFFF` nos dois temas. *A guarda NV1 não pega
   este caso: ela caça `var(--x, literal)`, e aqui o literal está sozinho — é a lacuna **NV1-c**.*

**O CENSO ANTERIOR ESTAVA INCOMPLETO, e isso é a lição do achado.** O §69.6 (`auditoria-consumidor.mjs`)
registrava **QUATRO** telas. São **OITO**. As quatro que faltavam — `chat`, `gantt`, `quadro` e
`referencia` — só apareceram quando a guarda **SI17** varreu a PASTA inteira em vez de uma lista escrita à
mão. *É a regra "PLACAR QUE MEDE NOME NÃO MEDE COISA" na sua outra forma: **lista escrita à mão mede a
lista, não o acervo**. Instrumento de ALVO DE PASTA acha o que a lista esqueceu.*

#### 65.7.2 O bloco único, e as alternativas descartadas com número

O Rafael qualificou a unificação como **INEGOCIÁVEL**, verbatim: *"unificar é melhor… precisamos manter o
mesmo padrão, isso é inegociável"*. O que restava era **qual forma**, e a medição resolveu:

```css
.sigla { width:24px; height:24px; border-radius:6px; flex:none;
  display:flex; align-items:center; justify-content:center;
  background: var(--seed-surface-brand-deep); color: var(--seed-text-on-brand-deep); }
.sigla svg { width:14px; height:14px; }
.sigla[data-ident="1"] { background: var(--seed-entity-1); color: var(--seed-entity-1-ink); }
.sigla[data-ident="2"] { background: var(--seed-entity-2); color: var(--seed-entity-2-ink); }
.sigla[data-ident="3"] { background: var(--seed-entity-3); color: var(--seed-entity-3-ink); }
.sigla[data-ident="4"] { background: var(--seed-entity-4); color: var(--seed-entity-4-ink); }
.sigla[data-ident="5"] { background: var(--seed-entity-5); color: var(--seed-entity-5-ink); }
.sigla[data-ident="6"] { background: var(--seed-entity-6); color: var(--seed-entity-6-ink); }
```

**ALTERNATIVAS DESCARTADAS, com o motivo:**

- **Padrão transparente** (o que `lista`, `tabela` e `referencia` faziam): identidade que SOME quando o
  produto escreve uma posição que a tela não previu. É o defeito, não o conserto.
- **Padrão na posição 1**: mudaria a aparência de quem hoje não declara `data-ident` — e sem necessidade.
- **Cor na regra base, como `detalhe`/`chat`/`gantt`/`quadro`**: congela a identidade e contraria o SI1.
- **Geometria em token (`--seed-radius-3`, que hoje vale 6px), como o `tela-chat` fazia**: recusada
  porque as outras medidas da MESMA anatomia (24px do quadrado, 14px do glifo) são literais, e
  meia-tokenização é pior que nenhuma — o número passaria a mudar por um lado e não pelo outro. Os três
  números ficam **literais e travados pela cláusula SI17-d**.

**Os pares, medidos (razão de contraste da tinta declarada sobre a posição):**

| posição | fundo | tinta | mede |
|---|---|---|---|
| 1 turquesa vivo | `#11B0A0` | `#242E34` | **5,11** |
| 2 dourado vivo | `#D49400` | `#242E34` | **5,30** |
| 3 azul vivo | `#01B3E0` | `#242E34` | **5,63** |
| 4 turquesa profundo | `#005048` | `#FFFFFF` | **9,37** |
| 5 azul profundo | `#006783` | `#FFFFFF` | **6,43** |
| 6 dourado profundo | `#7B5500` | `#FFFFFF` | **6,68** |
| **padrão** `surface-brand-deep` | `#006C62` claro · `#005048` escuro | `#FFFFFF` | **6,32** · **9,37** |

> **CORREÇÃO DE NÚMERO, com supersede.** A rodada zero escrita em 2026-08-18 (§7-a da abertura daquela
> sessão, e §91.1 do MANIFESTO) dizia que o padrão media **13,55** sobre `#00352F`. **Está errado:**
> `--seed-surface-brand-deep` vale `#006C62` no claro e `#005048` no escuro, não `#00352F`. Os números
> certos são **6,32** e **9,37**, os dois acima do piso 4,50. *A decisão não muda — o valor citado, sim.*

#### 65.7.3 Os contratos SI17

| # | Contrato | Por quê | Descartado, com o porquê |
|---|---|---|---|
| **SI17-a** | Para cada posição de `1` a `6`, o fundo computado é **exatamente** o valor de `--seed-entity-N` e a tinta o de `--seed-entity-N-ink` | É o contrato do §65 levado à tela. Reprova posição que caia no padrão (a regra não existe) ou fique transparente | Verificar se a REGRA existe no texto: mede implementação, não contrato — regra escrita e sobrescrita por outra folha passaria |
| **SI17-b** | O par medido de cada posição cumpre o **piso 4,50** do SI3 | O SI3 já vale na bancada; sem esta cláusula ele não valeria em tela | Piso 3,00 (é texto/glifo, não indicador de estado) |
| **SI17-c** | **Sem** `data-ident`, o quadrado **ainda pinta** — marca profunda com a tinta dela, nunca transparente | Identidade que some quando o produto escreve fora da faixa é o defeito de origem | Padrão transparente (é o defeito); padrão na posição 1 (muda aparência sem motivo) |
| **SI17-d** | A **geometria** é a mesma nas oito: **24×24px**, raio **6px**, glifo **14×14px** | Sem ela, "unificar" seria só a cor, e o quadrado poderia divergir de tamanho sem ninguém medir | Deixar a geometria livre (era o estado anterior: uma das oito usava token de raio e as demais literal) |

#### 65.7.4 A guarda, e por que ela mede COMPORTAMENTO

`validacao/guarda-si17.mjs` — **ALVO DE PASTA**: varre todo `*.html` da raiz, e arquivo sem `.sigla` sai
declarado **`[n/a]` NOMEADO**, nunca em silêncio. Para cada arquivo e tema, ela abre o artefato no
navegador, **escreve `data-ident` de 1 a 6 no elemento real** e **lê o estilo computado** — e compara com
o valor computado do token, resolvido por uma sonda no mesmo documento. Token consumido e não definido
resolve para transparente: isso sai nomeado como **FANTASMA** (NV-01), não como comparação de cor.

**PROVA DE REPROVAÇÃO — obrigatória, e o caso fundador é o artefato onde o defeito EXISTE.** Rodada sobre
as cópias anteriores das quatro primeiras telas: **0 PASS · 8 FAIL**. Em `tela-lista` e `tela-tabela` a
SI17-a reprovou **5 das 6 posições** (a `4` passa, que é a única declarada) e a SI17-c reprovou o padrão
transparente; no `tela-shell`, que já cumpria a SI17-a, a SI17-c reprovou a tinta literal do padrão.
*Guarda que não é provada capaz de reprovar não foi consertada: foi cegada.*

**PLACAR, duas execuções idênticas: `guarda-si17.mjs` — 16 PASS · 0 FAIL · 86 `[n/a]`**, sobre **51
arquivos** da raiz × 2 temas, sem teto, sem amostragem e sem exclusão por custo. As 16 folhas medidas são
as **oito** telas × dois temas; os 86 `[n/a]` são os 43 arquivos sem `.sigla`, nomeados no placar.

#### 65.7.5 O que a execução tocou, e a prova de que não mudou um pixel

As oito telas são **GERADAS** (§64.18: artefato gerado recebe no MOLDE e é regerado com **PRÉ-VOO**).
Foram editados os oito moldes `validacao/*-template.html`, e as listas `NOMES` dos oito `gen-*.py`
ganharam os tokens que faltavam — **`--seed-entity-6`, `--seed-entity-6-ink` e vários `-ink` em sete dos
oito geradores, e `--seed-text-on-brand-deep` em seis**. *Sem isso o bloco novo consumiria token
FANTASMA e a posição 6 não pintaria em tela nenhuma — o defeito que a SI17-a agora nomeia.*

**PRÉ-VOO nas oito**, comparando o gerado com o disco ignorando comentários: só as linhas pretendidas.
**PROVA DE INÉRCIA** (`validacao/prova-inercia.mjs`, dois temas × 1440px e 390px): **0 de 26.352.000
pixels diferentes em 32 comparações**, em duas execuções. *Mudança provada inerte não vai a gate.*

> **Achado de instrumento que saiu daqui: o modo `INTEIRA=1` da prova de inércia (página inteira, que
> nasceu nesta execução) tem RUÍDO PRÓPRIO medido — 8 a 19 pixels na borda direita do `tela-shell` a
> 390px, que se reproduzem comparando o arquivo COM ELE MESMO.** São os cantos arredondados,
> antisserrilhados, de um controle que encostava na borda. O número que vale é o do clip de 900px, e o
> modo de página inteira serve para achar deslocamento abaixo da dobra, não para contar pixel de borda.

#### 65.7.6 Pendência que fica

| # | Pendência | Por que fica aberta |
|---|---|---|
| **SI-P5** | ✅ **FECHADA em 2026-08-19 pelo gate — e a resposta do Rafael não foi nenhuma das três alternativas que eu tinha desenhado.** Ver **§65.9**: aquele quadrado não é "uma entidade do sistema", é a **EMPRESA LOGADA** num ERP **multi-empresa**. "SEED · Operação" **nunca existiu** — o nome certo é **SEED energia** |

### 65.8 SI18 — o PONTO DE COR de entidade (`.ent`): spec própria, porque ele NÃO é o §65.7 mal-feito

> **Escrito em 2026-08-19.** Fecha a quarta decisão do §64.13.4 (*"o ponto de cor decorativo ganha spec
> própria e entra no canônico, para não ficar ad hoc"*). Seção autossuficiente.

#### 65.8.1 O que ele é — e por que unificá-lo com o `.sigla` seria ERRADO

O **`.ent`** é um quadrado de **12×12px**, raio **2px**, `aria-hidden="true"`, **SEM glifo**, que aparece
**ao lado do NOME da entidade** na árvore de escopo (a lateral que lista espaços, pastas, clientes e
obras). Ele **ecoa** a cor da entidade; o nome está escrito ao lado.

**Ele não é o `.sigla` do §65.7 mal-feito, e a diferença é de FUNÇÃO:**

| | `.sigla` (§65.7 · SI17) | `.ent` (§65.8 · SI18) |
|---|---|---|
| tamanho | 24×24px · raio 6px | **12×12px · raio 2px** |
| glifo | **sim**, `svg` de 14px | **não** |
| onde | barra superior, **representando** a entidade ativa | árvore de escopo, **ao lado do nome** de cada entidade |
| o nome está ao lado? | sim, e some a ≤900px (3º degrau do CP15) | **sempre** |
| censo | 8 telas · 1 elemento cada | **7 telas · 29 elementos** |

> **Por que a distinção importa, e ela é a razão desta spec existir:** a auditoria de consumidor (§69)
> classificou o `.ent` como *"reimplementação do §65"*. **Unificá-los poria glifo em 29 elementos onde
> ninguém pediu** — e o glifo existe no `.sigla` por uma razão que **não vale aqui**: lá ele é o segundo
> canal quando a cor repete acima de seis entidades (CP23) e quando o rótulo some no degrau estreito.
> No `.ent` o segundo canal **é o próprio nome**, que está sempre ao lado e nunca some.
> *Guarda que condena artefato correto força conserto errado — e aqui o "conserto" seria pior que o
> defeito.*

#### 65.8.2 As QUATRO divergências medidas, em 29 elementos e 7 telas

| # | Divergência | Onde | Consequência medida |
|---|---|---|---|
| 1 | a cor vinha por **atributo `style` em linha** — `style="background:var(--seed-entity-N)"` | `chat`, `detalhe`, `gantt`, `quadro` (16 elementos) | **o consumidor decidindo mapeamento de cor**, que é exatamente a classe de defeito que a errata **E-CP-01** fechou para o `.sigla` |
| 2 | `data-ident="4"` escrito **sem regra para a posição 4** | `tela-lista` | o ponto ficava **TRANSPARENTE** — medido `rgba(0, 0, 0, 0)`. A entrada "Condomínio Alto da Serra" aparecia **sem ponto nenhum** |
| 3 | raio **literal de 3px** | `tela-referencia` | contra `var(--seed-radius-1)` (2px) das outras seis |
| 4 | regra presa ao **ancestral** (`.no .ent`) | `tela-chat` | o mesmo componente só existia dentro de um lugar |

#### 65.8.3 O contrato SI18, e o bloco único

| # | Cláusula | Por quê | Descartado, com o porquê |
|---|---|---|---|
| **SI18-a** | para cada posição de `1` a `6`, o fundo computado é **exatamente** `--seed-entity-N` | mesmo contrato do SI17: o produto escreve a POSIÇÃO, o DS resolve a COR | `style` em linha (o consumidor decide a cor — é a divergência 1) |
| **SI18-b** | *(não se aplica)* | o componente **não carrega texto**: não há par de contraste a medir | exigir piso de contraste aqui reprovaria desenho correto |
| **SI18-c** | **sem** o atributo, o ponto ainda **pinta** — marca profunda, nunca transparente | ponto que some quando o produto escreve fora da faixa é a divergência 2 | padrão transparente (é o defeito) |
| **SI18-d** | geometria **12×12px, raio 2px, SEM glifo** | sem ela, "unificar" seria só a cor. A cláusula reprova **também** a presença de glifo: é o que separa este componente do §65.7 | raio literal de 3px (divergência 3); prender ao ancestral (divergência 4) |

```css
.ent { width:12px; height:12px; border-radius:var(--seed-radius-1); flex:none;
  background: var(--seed-surface-brand-deep); }
.ent[data-ident="1"] { background: var(--seed-entity-1); }
/* … 2 a 6, idênticas … */
```

**A cor sozinha não carrega informação aqui** — o nome está ao lado —, por isso o elemento é decorativo
e `aria-hidden`. Isso satisfaz a **SC 1.4.1** sem precisar de segundo canal.

#### 65.8.4 A guarda, o que ela achou e o que a execução mexeu

A régua do SI18 é a **mesma** do SI17: escrever a posição no elemento dentro do navegador e ler o estilo
computado. Por isso **não nasceu guarda nova**: a `validacao/guarda-si17.mjs` foi **PARAMETRIZADA** e
passou a carregar os dois contratos numa tabela. *A regra do projeto é explícita — não duplique guarda;
quando dois contratos têm a mesma régua, parametrize.*

> **EXCLUSÃO DECLARADA E IMPRESSA NO PLACAR:** `banco-composicao.html` tem uma classe `.ent` que é
> **outro componente** — um cartão de 36px com glifo, da bancada de composição. **Colisão de nome, não
> de contrato.** Ela sai da conta do SI18 nomeada, com o motivo.

**ACHADO QUE SÓ A EXECUÇÃO PODIA PRODUZIR:** ao trocar o raio literal do `tela-referencia` pelo token
`--seed-radius-1`, o raio computado caiu para **0px** — canto **vivo** onde havia canto de 3px. O
`gen-tela-referencia.py` **não emitia esse token**: era **FANTASMA**. Corrigido na lista `NOMES`.
*Token consumido e não definido não pinta — e aqui ele não pintou o canto.*

**PLACAR: `guarda-si17.mjs` (SI17 + SI18) — 30 PASS · 0 FAIL · 174 `[n/a]`**, 51 arquivos × 2 temas × 2
contratos, em duas execuções idênticas. **PROVA DE REPROVAÇÃO:** sobre as cópias anteriores, o SI18
reprova **as 7 telas nos 2 temas**.

**O que mudou de pixel, e é pouco de propósito:**

| tela | mudança | pixels |
|---|---|---|
| `tela-lista` | o ponto da posição 4 **passa a pintar** (era transparente) | **144** por tema — exatamente 12×12 |
| `tela-referencia` | raio 3px → 2px em 3 pontos | **60** por tema |
| `chat`, `detalhe`, `gantt`, `quadro`, `tabela` | `style` em linha → `data-ident`; mesma cor computada | **0** |

Os dois primeiros vão à folha de gate com recorte antes/depois — **o gate pode revertê-los**.

**Validação depois:** contraste **14 PASS · 0 FAIL** a 1440px e **14 · 0** a 390px · forced-colors FC1 e
FC2 **14 · 0** cada · BT7 reflow **30 · 0 · 3** nas sete · `suite-detalhe` 73 · 0 · `suite-gantt` 43 · 0
· `suite-quadro` 35 · 0.


### 65.9 SI-P5 FECHADA — o quadrado da barra superior é a EMPRESA LOGADA, e o ERP é multi-empresa

> **Escrito em 2026-08-19, a partir da resposta do Rafael no gate v13.3.** Fecha a **SI-P5**, aberta no
> §65.7.6 no mesmo dia. **Nenhuma das três alternativas que eu havia desenhado foi a escolhida** — e é
> por isso que esta seção existe: a pergunta estava certa, as opções estavam erradas, porque eu havia
> presumido que aquele quadrado era "mais uma entidade do sistema".

#### 65.9.1 O que eu media, e o que a coisa é

O `.sigla` da barra superior aparece em **oito telas**. Cinco traziam **"SEED engenharia"**; três
traziam **"SEED · Operação"**. As oito usavam a **posição de cor 4**. Eu tratei isso como uma violação
do **SI1** (*a identidade é um PAR: glifo E cor, nunca um só*) e levei a gate três alternativas —
duas entidades com cores distintas, uma entidade só, ou glifos distintos na mesma cor.

**Resposta do Rafael, verbatim (2026-08-19):**

> *"nenhuma das 3 alternativas. o que ocorre que nao existe SEED operação. Seria SEED energia. nao sei
> porque gerou exemplo com a palavra operação. pode mudar a cor, se estou certo, a palavra SEED
> engenharia aparece nesse ponto mostrando que estamos tratando da empresa SEED engenharia,
> representando a empresa que está logada no sistema, pois o ERP será multi empresa, quando mudamos
> para SEED energia a cor deve mudar, o glifo pode ser o mesmo. hoje temos essas duas empresas, mas
> quando tivermos mais, vamos ter outros nomes e mais cores para cada um."*

**Fato de produto que eu não tinha:** o ERP é **multi-empresa**. Aquele ponto da barra não identifica
um projeto, um cliente ou um assunto — identifica **a empresa em cujo contexto o usuário está
operando neste momento**. Hoje são **duas** (SEED engenharia e SEED energia); haverá mais.

> **"SEED · Operação" era dado sintético inventado por mim** numa sessão anterior, sem correspondência
> com a empresa real, e sobreviveu porque ninguém tinha olhado aquele canto. *Dado sintético que
> parece plausível é o mais perigoso: ele não é conferido, porque não chama atenção.*

#### 65.9.2 O contrato SI17-e — a empresa logada

| # | Cláusula | Por que ela existe |
|---|---|---|
| **SI17-e** | O `.sigla` da barra superior identifica a **EMPRESA LOGADA**, não uma entidade de conteúdo. **Cada empresa tem uma POSIÇÃO DE COR própria e exclusiva; o GLIFO é o MESMO para todas.** | Decisão literal do Rafael: *"quando mudamos para SEED energia a cor deve mudar, o glifo pode ser o mesmo"*. O glifo constante diz "isto é o seletor de empresa"; a cor diz **qual** empresa. O **SI1** continua satisfeito: o par existe, e é ele que muda — o que não muda é o **papel** do glifo |

**Implicação declarada para o ERP:** a posição de cor da empresa é **campo de cadastro da empresa**, não
constante de CSS. Quando entrar a terceira empresa, ela recebe a próxima posição livre da paleta
fechada de seis (CP23). **Da sétima em diante a cor repete** e quem distingue passa a ser o nome ao
lado — que é exatamente o que o SI1 já previa, e o motivo de o nome nunca poder sumir da barra.

#### 65.9.3 A cor escolhida, e por NÚMERO

`SEED engenharia` fica na posição **4** (turquesa profundo `#005048`) — é a família da marca e não se
mexe. Para `SEED energia` escolhi a posição **3** (azul vivo `#01B3E0`). **A escolha foi por medida, não
por gosto:** distância de luminância entre cada posição e a posição 4 —

| posição | distância de luminância até a 4 | veredito |
|---|---|---|
| **3** — azul vivo | **3,80** | ✅ **escolhida** — a maior distância; sobrevive à escala de cinza |
| 2 — dourado | 3,59 | descartada: o dourado encosta na **regra do ouro** da marca, e havia alternativa melhor **por número** |
| 1 — turquesa vivo | 3,45 | descartada: **mesma família** da marca; leria como "a mesma empresa em outro estado" |
| 5 | 1,46 | descartada: a **1,4** as duas empresas ficam **indistinguíveis em escala de cinza** |
| 6 | 1,40 | idem |

*O critério é o mesmo do SI12: quando a informação é "qual dos dois", o canal não pode ser só matiz —
tem de sobreviver ao cinza.*

#### 65.9.4 O que a execução tocou, e o que ficou provado

Três moldes (`tela-detalhe-template`, `tela-gantt-template`, `tela-quadro-template`) e seus três
geradores. O raio (`bolt`) saiu; entrou o **mesmo glifo de quatro quadradinhos** das outras cinco telas,
com `data-ident="3"`.

**Verificação no navegador, nas oito telas** — `ident` · fundo computado · glifo · rótulo:

| telas | ident | fundo | rótulo |
|---|---|---|---|
| `shell`, `lista`, `tabela`, `chat`, `referencia` | **4** | `rgb(0, 80, 72)` | **SEED engenharia** |
| `detalhe`, `gantt`, `quadro` | **3** | `rgb(1, 179, 224)` | **SEED energia** |

*As oito com o mesmo glifo — quatro quadradinhos.* Recorte antes/depois nos dois temas: bloco **D1** da
folha `render-audit/gate-v134/fechamento.html`.

---

### 65.10 SI19 e SI20 — pessoa é CÍRCULO, empresa é QUADRADO, e cada uma tem DOIS estados (fecha a PS-P6)

> **Escrito em 2026-08-19, a partir da resposta do Rafael no gate v13.3.** Fecha a **PS-P6**, que havia
> nascido no §62.8 quando a medição **revogou** a PS-P5 (o círculo de iniciais das telas de dado não
> carregava uma pessoa: carregava um **CLIENTE**).

#### 65.10.1 O defeito, e o nome que o causou

As telas `tela-tabela` (6 linhas) e `tela-referencia` (5 linhas) traziam, na coluna **Cliente**, um
`span.pessoa` contendo um `span.mini-avatar`: **círculo de 26px com as INICIAIS do cliente** e um
**ponto de PRESENÇA** (disponível / ausente / ocupado) grudado no canto.

**Dois erros, e o segundo é filho do primeiro:**

1. **O nome da classe dizia PESSOA e o conteúdo era EMPRESA.** Foi esse nome que fez a **PS-P5 nascer
   errada**: eu li `.pessoa` e escrevi uma pendência mandando migrar aquilo para a paleta de pessoa do
   §62. A medição derrubou a premissa. *Nome mentiroso não é cosmético: ele produz pendência errada, e
   pendência errada consome sessão inteira.*
2. **Presença é atributo de GENTE.** Ninguém marca a Metalúrgica Andrade como "ocupada". O ponto de
   presença ali era ruído com aparência de dado.

**Resposta do Rafael, verbatim (2026-08-19):**

> *"acho que no caso do cliente, para diferenciar podemos deixar quadrado com glifo, da mesma forma que
> o usuário pode trocar as letras pela foto, a empresa pode trocar o glifo pela logo. se adicionarmos a
> logo no cadastro do cliente isso vai ocorrer, ou o proprio cliente se dermos acesso a ele no dia que
> ele tiver acesso ao painel de cliente, podemo dar acesso a ele trocar a logo, o que influenciará
> nisso, mas o metodo é modelagem no ERP, o importante é saber o estado."*

#### 65.10.2 Os contratos

| # | Cláusula | Por que ela existe | Alternativa descartada |
|---|---|---|---|
| **SI19** | **A FORMA separa antes da cor: pessoa é CÍRCULO, organização é QUADRADO** de raio 6px | É o canal que sobrevive à escala de cinza, ao olho de longe e ao daltonismo. A distinção de forma é anterior a este componente (§62) — aqui ela vira contrato escrito e medido | separar por cor: reprovada pelo próprio SI12 (dois canais, nunca só cor) |
| **SI20** | **Foto e logo SUBSTITUEM o fundo — nunca se sobrepõem à cor de entidade.** Com imagem, o fundo vira superfície neutra com **fio de 1px** | Contraste de **imagem de terceiro** sobre **cor nossa** é impossível de provar; o que não se prova não entra. Superfície neutra com fio **é** medível, e o fio impede uma logo clara de sumir no tema claro | deixar a cor de entidade atrás da imagem: cria um par não mensurável em cada cliente novo |

**A matriz completa dos quatro estados** está desenhada e provada em `banco-identidade.html`, seção
*"Pessoa é círculo, empresa é quadrado"*:

| | sem imagem | com imagem |
|---|---|---|
| **pessoa** (círculo) | **INICIAIS** derivadas do nome (SI15) | a **FOTO**, substituindo fundo e iniciais |
| **empresa** (quadrado) | **GLIFO** padrão + posição de cor | a **LOGO**, substituindo fundo e glifo |

> *Quem escolhe entre os dois estados é o **CADASTRO**, nunca o componente.* É a resposta desenhada à
> frase do Rafael: *"o importante é saber o estado"*. O desenho está pronto para receber a logo no dia
> em que o ERP a tiver — ligar o campo não vira redesenho.

#### 65.10.3 INFERÊNCIA DECLARADA: o glifo da empresa é ÚNICO, não um por cliente

**Isto não é decisão do Rafael — é minha, e está aqui separada de propósito.** O Rafael decidiu
*"quadrado com glifo"*; **eu** decidi que o glifo é **sempre o mesmo** (`building`, do inventário de 28
do §45) e que **quem diferencia um cliente do outro é a POSIÇÃO DE COR**.

**Razão:** um ERP **deriva** iniciais do nome de uma pessoa (soma de códigos de ponto — SI15), mas não
deriva um **desenho** de uma razão social. Um glifo por cliente teria de ser escolhido à mão, cliente a
cliente, e isso é trabalho de cadastro que ninguém faz. **Se um dia for decidido o contrário**, o glifo
nasce como **campo do cadastro do cliente**, escolhido numa lista de 28 — vira **dado**, não CSS, e o
desenho já está pronto para recebê-lo.

**Legibilidade do `building` a 14×14px foi MEDIDA** antes de virar padrão, em 2026-08-19, ampliada 4×,
nos dois temas, contra cinco candidatos (`home`, `people`, `location`, `growth`, `substation`): lê como
prédio, sem empastar as janelas. *Glifo de inventário não dispensa medição de legibilidade no tamanho
de uso: 24px e 14px são componentes diferentes do mesmo desenho.*

#### 65.10.4 O que a execução tocou

Dois moldes (`tela-tabela-template`, `tela-referencia-template`) e dois geradores. A classe `.pessoa`
morreu e virou **`.org`**; a classe `.mini-avatar` **deixou de existir**; o quadrado é o **`.sigla` do
§65.7** — mesmo contrato, mesma guarda, nenhuma classe nova.

**As tuplas de dado dos dois geradores encolheram:** os campos `iniciais` e `presença` foram
**removidos**, e com eles a asserção `PRESENCAS`. *O contrato **C2** (três estados de presença) continua
vivo e provado no `.avatar` da barra superior e em `banco-pessoa.html` — o que morreu foi o uso indevido
dele numa empresa, não o contrato.*

**Alcance da guarda CRESCEU sem que ninguém a editasse:** a `guarda-si17.mjs` mede `.sigla` varrendo a
pasta, então `tela-tabela` foi de **1 para 7** quadrados medidos e `tela-referencia` de **1 para 6** —
**30 PASS · 0 FAIL · 174 [n/a]**, pior par de contraste **5,11** (piso 4,50). *É o argumento a favor de
guarda que varre pasta em vez de ler lista: o acervo cresce e a medida acompanha sozinha.*

**Recorte antes/depois nos dois temas:** blocos **A1, A2 e A3** da folha
`render-audit/gate-v134/fechamento.html`.

---

### 65.11 CC-P8 FECHADA — o ponto de status da legenda deixa de ser um CARACTERE

> **Escrito em 2026-08-19.** Decisão do Rafael no gate v13.3, verbatim: **"troca a bolinha"**.

Em `banco-dataviz-dg.html`, cartão *"O mês em números"*, os três marcadores da linha "status da
carteira" eram o **caractere U+25CF (`●`)** pintado com `color`. **Sendo TEXTO, caíam no SC 1.4.3**, que
exige **4,50:1**. Medidos os seis pares (3 pontos × 2 temas):

| ponto | tema claro | tema escuro | como TEXTO (piso 4,50) | como FORMA (piso 3,00) |
|---|---|---|---|---|
| ok · turquesa | 4,60 | **3,31** | ❌ escuro reprova | ✅ passa |
| atenção · rosa | **4,08** | 7,47 | ❌ claro reprova | ✅ passa |
| crítica · vermelho | 5,19 | 5,52 | ✅ passa | ✅ passa |

**O conserto trocou a NATUREZA do elemento, não a cor:** virou um quadradinho pintado de 12×12px, raio
2px, `aria-hidden`, **sem caractere nenhum**. Sendo objeto gráfico, o critério cabível passa a ser o
**SC 1.4.11 (3:1)** — e **os mesmos seis números** passam, com o pior par em **3,31**. *Nenhuma cor
mudou. Nada foi maquiado: mudou o que o elemento É, e com isso o critério que a norma manda aplicar.*

**ALTERNATIVA DESCARTADA:** subir a cor até 4,50 mantendo o caractere — descartada porque mudaria a cor
do status na carteira inteira para consertar um marcador **decorativo**, e porque o rótulo textual ao
lado ("4 usinas ok") já é quem carrega o significado (**DG16**: cor nunca sozinha).

**POR QUE A CLASSE NÃO É `.ent`:** o `.ent` do §65.8 é o ponto de cor de **ENTIDADE**, e a cor dele vem
da paleta fechada de seis (CP23). Aqui a cor vem da **taxonomia de status** (§22). Mesma geometria de
propósito — a linguagem de forma é uma só; **nome diferente porque a FONTE DA COR é outra**. Reusar
`.ent` obrigaria a **excluir** este arquivo da guarda SI18, e *toda exclusão declarada é uma dívida com
juros*. A classe chama-se **`.pt-status`**.

**Recorte antes/depois nos dois temas, ampliado 10×:** blocos **C1 e C2** da folha
`render-audit/gate-v134/fechamento.html`.


### 65.12 SI-30 (2026-09-05) — o set foi a 30 e o seletor ACOMPANHA: inventário derivado, grade 6 × 5

**Para quem lê sem ter visto a sessão.** Em 2026-09-05 a pendência **FV-P5** acrescentou dois glifos
de domínio ao set do §45 — `hard-hat` (destaque Obras) e `solar-panel-plus` (destaque SEED Plus) —,
e o set passou de **28 a 30** (v1.39). O changelog da v1.39 registrou a consequência sem executá-la:
*"o seletor de identidade do §65 segue com os 28 glifos de agosto — 30 não fecha em 7 colunas (6 × 5
fecha)"*. Esta subseção é a execução. **A decisão é consequência, não escolha nova:** o seletor
mostra *o set* (SI7 consome os aliases do §45.3; SI6 anuncia “N de TOTAL”), e um seletor com 28 num
set de 30 anuncia posição errada e esconde dois glifos que o produto já usa. Aplicada pela sessão pelo
critério de melhoria óbvia e **exposta a veto do Rafael** no mesmo movimento.

**O que se achou ao abrir o gerador, e que é mais importante que o número.** O `INVENTARIO` do
`gen-banco-identidade.py` era uma **lista de 28 nomes escrita à mão** (comentário original: *"na
ordem em que ele está escrito lá"*, o §45.3); só os desenhos, categorias e aliases vinham da bancada.
É a forma exata de drift que o §65.5 dizia combater — a lista duplicada envelheceu no dia em que a
fonte mudou, sem aviso. **Correção:** o inventário inteiro passa a ser **derivado** de
`banco-icones.html` (a mesma fonte que a `suite-icones.mjs` valida na IC-01 e que o
`gen-destaques-instagram.py` já lê) e reordenado na ordem do §45.3 — categoria na ordem da tabela,
nome em ordem alfabética dentro dela. Conferido nome a nome: os 28 antigos saem na **mesma
sequência** da lista à mão; `hard-hat` entra na 6ª posição e `solar-panel-plus` na 13ª, onde a tabela
os escreve; `substation` (a escolha inicial) passa de 12ª a 14ª. O que fica declarado no gerador é só
o **denominador** `TOTAL_GLIFOS = 30`, com abort se a bancada trouxer outro número ou categoria fora
da tabela — o mesmo número que a IC-01 e o `gen-destaques-instagram.py` declaram; os quatro lugares
mudam no mesmo commit.

**A grade: 6 × 5, e por quê.** A régua do SI5 é *linhas inteiras* (`total % colunas == 0`), não
“7 colunas”: o 7 era consequência do 28. Entre os divisores de 30, **6 × 5** é o vizinho imediato do
7 × 4 aprovado no gate de 2026-08-17 (uma coluna a menos, uma linha a mais); 10 × 3 e 15 × 2
esticam a grade na horizontal, 5 × 6 e 3 × 10 na vertical. Célula segue **44 × 44** (SI13). O
`data-colunas` passa a 6 e o limiar da guarda `SI-OP3b` (“menos que uma linha cheia”) passa a ser
**lido do DOM**, não escrito na suíte.

**Placar, com denominador (antes → depois, mesma máquina, 2026-09-05):**

| Instrumento | Antes | Depois |
|---|---|---|
| `gen-banco-identidade.py`, 2 gerações | MD5 `294885fa94a9305fab8cf4c82fbc185f` (= committado) | MD5 `a2f5a8591e070c69069c8e2d84839c74`, idêntico nas duas |
| `suite-identidade.mjs` | 32 PASS · 0 FAIL · 0 [n/a] · 1 medida | **32 · 0 · 0 · 1** (mesma contagem; mudam de valor medido SI6 “30 glifos”, SI5-grade “6 colunas”, SI13 “36 opções”, SI-OP3, SI-OP3b, SI-OP4, SI8-filtro “de 30”, SI9 “devolve os 30”, e as 4 derivadas do SI15) |
| `suite-icones.mjs` | 25 · 0 | 25 · 0 |
| `guarda-barra-provas.mjs` | 9 · 0 · 15 [n/a] · 24 alvos | 9 · 0 · 15 · 24 |
| `paridade-previews.py` | 825 · 0 (209 medidos · 15 isentos) | 825 · 0 (209 · 15) |
| medição no DOM (Chrome, 1200 px) | 28 células, 4 linhas, 7 na primeira | **30 células, 5 linhas, 6 na primeira, 0 vazias, 0 cortadas, todas 44 × 44** |

**Descartado, com o porquê:** manter 28 (o seletor mentiria sobre o set e anunciaria posição
errada); 7 colunas com sobra (quebra o SI5 — o ↓ da última coluna cai no vazio); derivar do §45.3
deste markdown em vez da bancada (a bancada é o que a IC-01 valida e o que os outros consumidores
leem — **uma** fonte para todos; a tabela do §45.3 segue a fonte humana, e o denominador fixo 30 é o
que amarra as duas). **O que não muda:** a **SI-P1** continua aberta (o `seed-icones.json` do NM5
segue sendo a resposta certa; derivar da bancada reduz o drift, não o acoplamento); os “28” do
§65.9 são narrativa datada de 2026-08-19 e ficam como história; o texto do §65.5 sobre a v0.1 é
histórico.

**Supersede parcial, no mesmo dia — ver §65.13.** A régua *linhas inteiras* desta subseção durou o
intervalo entre a FV-P5 e a GL-R2: com 31 glifos (primo) ela passou a *linhas inteiras exceto a
última*, por decisão dele (MANIFESTO §150). O inventário derivado, o denominador único e as
alternativas de coluna aqui registradas continuam valendo.

### 65.13 SI-31 (2026-09-05) — o set foi a 31, 31 é primo, e a régua SI5 passa a “linhas inteiras
exceto a última”
**Para quem lê sem ter visto a sessão.** No gate do traço (MANIFESTO §150) o Rafael decidiu dois
glifos pelos modelos em imagem que anexou: nasce `transmission-tower` (torre de transmissão, para o
destaque Subestações) e o `generator` é redesenhado sem somar (v1.42, GL-R2). O set do §45 foi de 30
a **31** — e 31 é **primo**: nenhuma grade de 2 a 30 colunas fecha em linhas inteiras, então a régua
SI5 registrada no §65.12 (*linhas inteiras*, `total % colunas == 0`) não tinha saída. A folha do
gate levou a pergunta como T-SET, e a resposta dele, verbatim: *“T-SET: glifo novo + régua da grade
aceita última linha incompleta (recomendado)”*. Esta subseção é a execução.
**O que o abort fez, e por que é mérito.** O `gen-banco-identidade.py` v0.3 PAROU em `TOTAL_GLIFOS =
30` quando a bancada de ícones foi a 31 — exatamente o comportamento desenhado no §65.12 (“aborta se
a bancada trouxer outro número: o set mudou, releia o canon”). Nenhuma bancada de 30 glifos foi
gerada sobre um set de 31.
**A régua nova, e o que ela ainda exige.** *Linhas inteiras exceto a última*: a última linha pode
ter de 1 a `colunas − 1` células. O gerador continua exigindo (a) pelo menos **duas linhas cheias**
— com uma só, ↓ não teria destino em coluna nenhuma e o SI5 seria letra morta — e (b) a sobra
**medida** por `divmod` e declarada no resumo da geração (`31 em 6 colunas (5 linha(s) cheia(s) +
última com 1)`). Colunas: **6**, as do gate de 2026-09-05; 7 daria 4 cheias + 3 (mais sobra, no
desenho que o 30 já tinha deixado); 31 × 1 e 1 × 31 fecham, mas viram fila — o defeito que o SI5
existe para impedir.
**Teclado na última linha — a decisão e a fonte.** Uma linha incompleta cria células **sem vizinha
abaixo** (na penúltima linha, colunas 2 a 6). Duas saídas defensáveis: (1) mandar o foco à última
célula existente — era o que o `Math.min(i + passo, n − 1)` da v0.3 fazia, por acidente de
implementação —, ou (2) não mover. Aplicada a **(2)**, pela fonte: WAI-ARIA Authoring Practices
Guide (APG, o guia do W3C de como um padrão de interface se opera por teclado e se anuncia à
tecnologia assistiva), padrão *Grid*, seção “Keyboard Interaction”, em
w3.org/WAI/ARIA/apg/patterns/grid/, consultado em 2026-09-05: *“Down Arrow: Moves focus one cell
down. If focus is on the bottom cell in the column, focus does not move.”* — e o espelho para Up
Arrow. A saída (1) é uma troca de coluna disfarçada de descida, o que o próprio SI5 chama de “seta
que mente sobre o layout”; e o APG só admite envolvimento (*wrap*) como opção de *layout grid*,
nunca salto diagonal. Consequência aplicada junto e **exposta a veto**: ↑ na **primeira** linha
também deixa de saltar à primeira célula (era o `Math.max(i − passo, 0)`), e ←/→ nas pontas deixam
de reanunciar o par — a tecla é consumida (a página não rola), nada muda, nada se anuncia.
**Placar, com denominador (antes → depois, mesma máquina, 2026-09-05):**

| Instrumento | Antes | Depois |
|---|---|---|
| `gen-banco-identidade.py`, 2 gerações | MD5 `a2f5a8591e070c69069c8e2d84839c74` (v0.3) | MD5 `26023b0632d65e7e21b974e839e2617c` (v0.4), idêntico nas duas |
| `suite-identidade.mjs` | 32 PASS · 0 FAIL · 0 [n/a] · 1 medida | **33 · 0 · 0 · 1** (nasce **SI-OP3c**; mudam de valor medido SI6 “31 glifos”, SI13 “37 opções”, SI8-filtro “de 31”, SI9 “devolve os 31”; SI-OP3, SI-OP6 e SI15 inalterados) |
| `suite-icones.mjs` (só leitura) | 25 · 0 | 25 · 0 |
| `guarda-barra-provas.mjs` | 9 · 0 · 15 [n/a] · 24 alvos | 9 · 0 · 15 · 24 |
| `paridade-previews.py` | 825 · 0 (209 medidos · 15 isentos) | 825 · 0 (209 · 15) |
| medição no DOM (Chrome determinista, 1200 px, dois temas) | 30 células, 5 linhas de 6 | **31 células 44 × 44, 6 linhas (6·6·6·6·6·1), 0 cortadas, 0 glifos cortados**; ↓ de `location`, `people`, `phone`, `calendar`, `document` (penúltima linha, colunas 2–6) **fica**; `email` ↓ `growth`; `growth` ↑ `email`; ↑ na primeira linha fica nas 6 colunas |

**A guarda nova, SI-OP3c.** Mede as colunas pelo `offsetTop` (como o script da bancada), prova por
identidade do elemento que ↓ sem destino fica, e por geometria que ↓/↑ com destino mantêm a coluna;
com a última linha cheia declara `[n/a]` — contrato não exercido, não aprovado em vão. **Defeito de
instrumento na estreia, registrado:** a primeira forma comparava `getBoundingClientRect` antes e
depois da tecla e reprovou `email → growth` (destino certo, mesma coluna), porque `focus()` numa
célula da última linha ROLA a página e o `y` de viewport diminuiu — o 15º defeito do §74 do
MANIFESTO em outra roupa. Corrigida para `offsetLeft`/`offsetTop` (coordenadas de layout) com a
causa medida, não ajustando o esperado ao obtido.
**Descartado, com o porquê:** tirar um glifo para voltar a 30 (o set é decisão dele, §150; o seletor
mostra o set); esperar um 32º glifo (adiar deixaria o SI6 anunciando “de 30” num set de 31 e
esconderia a torre que o destaque Subestações já usa); manter o `Math.min` (troca de coluna sem
aviso). **O que não muda:** a **SI-P1** continua aberta (a bancada de ícones segue sendo a fonte
enquanto o `seed-icones.json` do NM5 não existir); `substation` segue a 14ª (a torre entra na 17ª,
depois de `transformer`); Home/End (`battery`, `growth`) inalterados; a paleta e os seis pares (SI3)
não foram tocados.

## 66. Credenciais — senha e código de uso único — **`estável`** (F7.5, 2026-08-16 · **promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado"**) · CD1–CD16 · fecha **C13** e **C14** · bancada `banco-credencial.html` **v0.2** · `suite-credencial.mjs` **30 · 0 · 0**

> **Por que os dois campos moram na MESMA seção — e o que foi descartado.** C13 (senha com
> revelar) e C14 (campo de código/OTP) chegaram na fila como duas lacunas. Viraram **uma** seção
> porque são governados pela **mesma norma** e falham pelo **mesmo mecanismo**: o **SC 3.3.8
> Accessible Authentication (Minimum)** da WCAG 2.2 proíbe *teste de função cognitiva* em qualquer
> etapa da autenticação — memorizar, transcrever, soletrar, calcular. Bloquear o colar, atrapalhar o
> gerenciador de senha e exigir transcrição são o mesmo defeito nos dois campos, e o conserto é o
> mesmo.
> **Descartado:** duas seções (§66 e §67). Separá-las duplicaria os contratos idênticos — colar,
> `autocomplete`, mensagem de erro — e cópia de contrato é a classe de problema que o
> `tokens_bloco.py` existe para impedir. *Se um dia um dos dois ganhar contrato que o outro não pode
> ter, a separação nasce por supersede formal, não por conveniência.*

### 66.0 Rodada zero — NÃO VISITADO, com o motivo dito

| O quê | Veredito | Motivo |
|---|---|---|
| Tela de autenticação do produto de referência (arquétipo **A19**) | **NÃO VISITADO** | Toda a leitura do `estudo-clickup-completo.md` foi feita **dentro de uma sessão logada**. Ver a tela de login exigiria **sair da sessão** da conta corporativa — risco operacional real, e a autorização de navegação é de **leitura**, não de mexer em estado de conta |
| Fluxo de recuperação com código | **NÃO VISITADO — e bloqueado** | Ele só existe depois de **disparar um e-mail de verdade**. Disparar é escrita, e escrita não está autorizada (mesma barreira nomeada da **PS-P2**) |

**E aqui a ausência de leitura visual custa pouco**, e é preciso dizer por quê para não parecer
conveniência: campo de credencial é o arquétipo do sistema em que a **norma é mais densa e mais
específica** — o SC 3.3.8 chega ao ponto de dizer que colar tem de funcionar. Onde a norma decide,
copiar o desenho do vizinho não acrescenta; **mede-se contra a norma**. É o oposto do §57 (quadro),
em que a norma diz pouco e a leitura visual decidiu quase tudo.

### 66.1 RITO — três rodadas encadeadas

| Rodada | Fonte | O que trouxe |
|---|---|---|
| **R1 — canon** | W3C, *Understanding SC 3.3.8 Accessible Authentication (Minimum)* | Define teste de função cognitiva como *"memorização, transcrição, uso de ortografia correta, cálculos ou resolução de puzzles"*. **Verbatim que decide o C14:** *"um serviço que requer transcrição manual de código de verificação não é compatível. Como com usuários e senhas, deve ser possível ao usuário pelo menos COLAR o código."* E: bloquear paste **ou** exigir *"o 1º, 3º e 5º caractere"* **falha** o critério. Gerenciador de senha e copiar-colar são listados como **mecanismos conformantes** |
| **R2 — mercado/stack** | GOV.UK Design System — *Password input* · Cloud Four — *Simple One-Time Passcode Inputs* | GOV.UK: o revelar é `button` com `aria-label` “Show password”/“Hide password”, anúncio *“Your password is visible/hidden”*, **`type` volta a `password` no envio** (senão o navegador guarda o valor como texto e o oferece em autofill de campo comum), `spellcheck="false"` contra *spell-jacking*, **sem `maxlength`**, e o achado de pesquisa: *“ter um segundo campo não ajuda os usuários”* — o campo “confirme a senha” **sai**. Cloud Four: OTP é **um** `<input>` com `inputmode="numeric"`, `autocomplete="one-time-code"`, `pattern` e `maxlength`; a aparência de caixinhas se faz com **tipografia** (mono + `letter-spacing`), não com seis elementos |
| **R3 — normas** | WCAG 2.2: SC 1.3.5 *Identify Input Purpose* · SC 1.4.11 *Non-text Contrast* · SC 2.2.1 *Timing Adjustable* · SC 4.1.3 *Status Messages* | `autocomplete` correto é o que permite o preenchimento automático (1.3.5) e o que torna o gerenciador um mecanismo (3.3.8). A **borda do campo** é elemento não-textual e cobra 3:1 (1.4.11) — foi por aí que apareceu o achado do 66.4. O **prazo do código** cai na exceção *Essential* do 2.2.1: estender invalidaria a atividade; o contrato então não é estender, é **declarar** o prazo e oferecer **reenvio** |

### 66.2 Os contratos CD1–CD16

| # | Contrato | Por quê | Descartado |
|---|---|---|---|
| **CD1** | **Colar nunca é bloqueado** — senha e código | SC 3.3.8 literal. E é o contrato que a suíte prova **usando a área de transferência de verdade** (Ctrl+V), não lendo atributo | “Bloquear paste por segurança”: empurra a pessoa para senha memorizável, que é **menos** segura |
| **CD2** | `autocomplete` correto e obrigatório: `username` · `current-password` · `new-password` · `one-time-code` | É o que faz o gerenciador de senha funcionar — e gerenciador é **mecanismo conformante** do 3.3.8, além do SC 1.3.5 | `autocomplete="off"` em campo de senha (prática comum, hostil, e ignorada pelos navegadores modernos) |
| **CD3** | Senha com `spellcheck="false"` e `autocapitalize="none"` | *Spell-jacking*: o corretor pode enviar o conteúdo do campo a um serviço remoto. E capitalizar a primeira letra corrompe a senha em teclado móvel | Deixar o padrão do navegador (o padrão não conhece o contexto) |
| **CD4** | Senha **sem `maxlength`**; o limite vira mensagem de erro | Truncar em silêncio faz a pessoa **guardar uma senha que ela não digitou** — e descobrir no próximo login | `maxlength` “para proteger o banco” (o hash tem tamanho fixo; o limite é do formulário, não do dado) |
| **CD5** | **Sem campo “confirme a senha”** | Achado de pesquisa do GOV.UK: *“um segundo campo não ajuda”*. Com revelar disponível, ele é digitação dobrada sem ganho | Confirmação obrigatória (convenção herdada de quando não existia revelar) |
| **CD6** | O revelar é `button type="button"`, com `aria-controls` e **nome que diz o efeito** | Dentro de um `<form>`, botão **sem `type` é SUBMIT** — um “mostrar senha” que envia o formulário é defeito que só aparece no primeiro uso real. E “olho” mudo não é nome | `checkbox` “mostrar senha” (o estado do controle não é o estado do campo); ícone sem nome acessível |
| **CD7** | A visibilidade é **anunciada**, não só refletida no botão | Quem não vê a tela precisa saber que a senha está **exposta em texto** — é informação de segurança física (alguém atrás), não de UI | Só trocar o rótulo do botão (não é anúncio; a AT não relê o botão sozinha) |
| **CD8** | **Ao enviar, o campo volta a `type="password"`** | GOV.UK, verbatim: senão o navegador memoriza o valor como texto digitado e passa a oferecê-lo em autofill de **campos não-senha** | Deixar revelado (vazamento silencioso, e a pessoa não fez nada errado) |
| **CD9** | A senha começa **oculta**; revelar é ação deliberada | Padrão seguro. O estado inicial não pode depender de quem não escolheu nada | Começar revelada “porque é mais fácil” |
| **CD10** | O código é **UM campo** — nunca N caixinhas | Com `maxlength="1"`, colar o código deixa **um dígito**. Isso é transcrição manual obrigatória: falha do 3.3.8. Some-se seis paradas de tabulação para um dado só e o autofill do sistema sem alvo | **Seis caixinhas** — o desenho mais comum do mercado. Está **impresso na bancada, operante e na sua MELHOR forma**, e a suíte mede os dois (ver 66.4-b) |
| **CD11** | No código, `inputmode="numeric"`, `pattern` e `maxlength` **são legítimos** | Aqui o comprimento é **do sistema** (o código tem 6 dígitos por definição), não da pessoa. **É o oposto do CD4, e o porquê está dito** — a mesma propriedade é certa num campo e errada no outro | Tratar os dois campos com a mesma regra “sem maxlength” (regra sem motivo vira dogma) |
| **CD12** | O prazo do código é **declarado em texto**, e existe **reenvio** | SC 2.2.1, exceção *Essential*: estender o prazo invalidaria a atividade. O que se pode fazer é **não esconder** e dar caminho de saída | Contador regressivo animado (pressão, e vira ruído em leitor de tela); prazo só no e-mail |
| **CD13** | Erro de credencial **não diz qual metade errou**; erro de **formato** diz | “Essa senha está errada” confirma que o usuário existe — enumeração de contas. Mas “o código tem 6 dígitos” não revela nada e evita uma tentativa perdida. **A fronteira é: formato ajuda, existência não** | Mensagem específica sempre (vaza); mensagem genérica sempre (esconde erro de digitação do próprio usuário) |
| **CD14** | Força da senha com **texto e forma**, e ela **não bloqueia o envio** | Barra colorida sozinha é o mesmo defeito das quatro bandeiras em quatro cores do §63 (C131): em cinza viram a mesma barra. Aqui a **quantidade de blocos preenchidos** é a forma | Bloquear envio por “senha fraca” (regra de política do produto, não do DS — e empurra para senha decorável) |
| **CD15** | Caps Lock ligado é **aviso anunciado**, não erro | Com a senha oculta a pessoa vê pontinhos idênticos e **não tem como saber** por que falhou | Detectar só depois do erro (tarde); nada (o padrão, e é o motivo de metade dos “esqueci a senha”) |
| **CD16** | **Nenhuma transcrição parcial** (“digite o 1º, 3º e 5º caractere”) | Falha citada **nominalmente** no *Understanding* do SC 3.3.8, e incompatível com gerenciador de senha | O padrão bancário brasileiro de “letras da senha” (é exatamente o caso que a norma proíbe) |

### 66.3 Os números medidos

| Medida | Claro | Escuro |
|---|---|---|
| Erro de credencial (texto sobre fundo de perigo) | **6,58** | 9,47 |
| Erro de formato | 6,58 | 9,47 |
| Aviso de Caps Lock | 9,05 | 9,27 |
| Confirmação do código | 5,89 | 9,35 |
| Dica do campo · rótulo da força | 7,75 | 8,11 |
| **Borda do campo** (não-textual, piso 3,00) | **3,38** | **4,50** |

**A prova do CD10, medida em casa e não citada:** colado **o mesmo** `482913` nos dois desenhos, com
clipboard real e `Ctrl+V` —

| Desenho | O que ficou |
|---|---|
| Campo único (canônico) | `482913` — **6 de 6 dígitos** |
| Seis caixinhas (contraexemplo) | `4` · vazio · vazio · vazio · vazio · vazio — **1 de 6** |

### 66.4 O achado da BORDA — e a decisão, tomada depois de ler o gêmeo inteiro

A guarda de contraste reprovou a borda do campo: `--seed-border-default` (`#C8D6DF`) sobre
`--seed-surface-raised` (`#FFFFFF`) mede **1,49** no claro e **2,32** no escuro. Quando o fundo do
campo é **igual** ao da superfície em volta — que é o caso —, a borda é o **único** indicador de onde
se pode clicar, e o SC 1.4.11 cobra 3:1.

**Medidos os quatro candidatos do gêmeo, nos dois temas, contra a superfície elevada:**

| Token | Claro | Escuro |
|---|---|---|
| `--seed-border-default` | 1,49 ✗ | 2,32 ✗ |
| `--seed-border-strong` | 2,53 ✗ | 4,50 ✓ |
| `--seed-border-focus` | 3,22 ✓ | 8,31 ✓ |
| **`--seed-border-interactive`** | **3,38 ✓** | **4,50 ✓** |

**E aí a leitura do gêmeo mudou a decisão que eu ia tomar.** A primeira conclusão desta rodada foi
*"o §13 declara `--seed-field-border` (`cinza-600`, 4,74:1) e esse token não existe no
`seed-tokens.css`; logo, promova-o ao gêmeo"*. Errado — e o próprio gêmeo diz por quê. O comentário
da **v1.12** do `seed-tokens.css`, escrito ao criar o `--seed-border-interactive`:

> *"fronteira de COMPONENTE INTERATIVO (SC 1.4.11, piso 3:1) — nenhum outro semântico de borda
> passava no claro (default 1.49, strong 2.53) … medido contra as seis superfícies
> 3.38/3.38/3.11/4.50/5.51/5.05, todos ≥3."*

**O sistema já tinha resolvido esta pergunta, com os mesmos números que eu remedi.** Promover o
`--seed-field-border` ao gêmeo criaria um **segundo semântico para a mesma coisa** — exatamente o
drift que o `tokens_bloco.py` existe para impedir.

**Decisão, com supersede formal:** o **`--seed-border-interactive` é o semântico canônico de
fronteira de controle** em todo o sistema. O `--seed-field-border` do §13 — declarado **localmente**
dentro da `banco-formfield.html`, nunca no gêmeo — fica **SUPERSEDIDO** por ele.

**E a decisão não é só de consistência: é melhor NOS DOIS TEMAS somados.**

| | Claro | Escuro |
|---|---|---|
| `--seed-field-border` (local, `cinza-600`) | 4,74 | **3,21** |
| `--seed-border-interactive` (gêmeo, `cinza-500`) | 3,38 | **4,50** |

O token local é melhor no claro e **pior no escuro**; o do gêmeo é **equilibrado e passa nos dois**.
*Um par que se degrada na inversão é o defeito BT3 do §64 — e o token local se degrada.*

**Nada muda no gêmeo `seed-tokens.css` nesta rodada.** §65, §66, §67 e §68 já consomem o
`--seed-border-interactive`. O que sobra é **migrar a `banco-formfield.html`** do token local para o
semântico do gêmeo — artefato `estável`, então entra por auditoria e não por edição solta
(**CD-P2**).

### 66.4-b O contraexemplo estava fraco — o gate consertou, e o argumento ficou mais forte

**Apontamento do Rafael, verbatim:** *"eu tentei colar e não consegui, quando digito o cursor pula
pro próximo campo, mas quando clico no backspace não consigo apagar cada campo, era pra ir voltando e
apagando."*

Ele está certo, e o erro era meu: a primeira versão das seis caixinhas **só avançava o foco**.
Qualquer implementação séria de OTP em caixinhas escreve pelo menos três remendos — avançar ao
digitar, **voltar apagando no Backspace** e navegar por setas. Comparar o campo único com uma versão
**capenga** das caixinhas é **espantalho**, e argumento apoiado em espantalho cai na primeira vez que
alguém implementa direito.

**Corrigido: o contraexemplo agora tem os três remendos.** E é aqui que o conserto **fortalece** a
tese em vez de enfraquecê-la — porque o argumento verdadeiro nunca foi *"caixinhas não funcionam"*, e
sim **quanto custa fazê-las funcionar**:

| | Campo único | Seis caixinhas |
|---|---|---|
| Avançar ao digitar | — (não se aplica) | remendo 1 |
| Voltar apagando | nativo | **remendo 2** |
| Navegar por setas | nativo | **remendo 3** |
| **Colar o código** | **nativo — 6 de 6 dígitos** | **remendo 4, que ninguém escreveu — 1 de 6** |
| Paradas de tabulação | 1 | 6 |
| Alvo do `autocomplete="one-time-code"` | 1 campo | nenhum |

> **Regra de método que sai daqui: contraexemplo tem de estar na MELHOR forma que o desenho
> concorrente consegue ter.** Contraexemplo enfraquecido mede a nossa implementação dele, não a
> decisão que ele representa — e o placar verde vira autoengano.

A guarda **`CD10-c`** passa a medir isso: ela exercita os três remendos no contraexemplo e reprova se
algum faltar. *E foi provada capaz de reprovar antes de ser aceita:* removidos os remendos, o placar
vai a **26 · 1**.

### 66.5 Dois defeitos de instrumento na mesma rodada

**Décimo sexto — o parser de cor não entendia HEXADECIMAL** (nasceu no §65 e vale para toda guarda
do projeto; o registro completo está em 65.4). Resumo: `getComputedStyle` devolve `rgb()`, mas o
**valor de um token** lido por `getPropertyValue` volta como está escrito no gêmeo (`#242E34`). O
regex de dígitos casava só o final e a razão saiu **232,67** — **fisicamente impossível** — com a
guarda emitindo **PASS**. Regra nova: **número fora da faixa [1,00 … 21,00] é MEDIDOR QUEBRADO, não
veredito.**

**Décimo sétimo — a guarda leu a DOCUMENTAÇÃO do contrato como se fosse o contrato violado.** A
primeira forma do CD16 varria o `innerText` do `<body>` inteiro procurando “1º, 3º e 5º caractere”.
Ela achou — **dentro da própria tabela de contratos da bancada**, onde o CD16 cita a frase proibida
entre aspas para explicar o que proíbe. O artefato foi reprovado por explicar a si mesmo.

> **Regra nova: guarda mede o CONTROLE, nunca o texto que descreve o controle.** O escopo passou a
> ser rótulos, legendas, dicas e `placeholder` dos campos. *Mesma família dos defeitos de recorte (8º
> e 14º): escopo errado inventa achado ou esconde achado — aqui, inventou.*
>
> **E a guarda foi provada capaz de reprovar** antes de ser aceita: injetada a frase proibida num
> rótulo real, o placar vai a **25 · 1**. Guarda que nunca falha não é guarda.

### 66.6 A bancada e a suíte

`banco-credencial.html` **v0.1**, gerada por `validacao/gen-banco-credencial.py` sobre
`validacao/banco-credencial-template.html`. Barra fixa e seletor de largura desde a v0.1 (BT1/BT2).
**Nada nesta bancada envia nada a lugar nenhum**, e o selo diz isso.

**`suite-credencial.mjs` — 27 PASS · 0 FAIL · 1 medida.** É a **primeira suíte do projeto que usa a
área de transferência**: o contexto do navegador é aberto com permissão de clipboard, o código é
escrito nele e colado com `Ctrl+V`. *Colar não se prova lendo atributo.*

| Guarda | O que ela faz de fato |
|---|---|
| `CD1-senha` / `CD1-codigo` | escreve no clipboard e **cola** nos dois campos |
| `CD10-b` | cola o **mesmo** código nos dois desenhos e compara: 6/6 contra 1/6 |
| `CD7` | **clica** no revelar e confere tipo, nome do botão e anúncio |
| `CD8` | **envia** o formulário e confere que o tipo voltou a `password` |
| `CD15` | dispara `keyup` **com `modifierCapsLock`** — e a guarda **declara** que o modificador é sintético, em vez de fingir que ligou a tecla |
| `CD12-OP` | **clica** em reenviar e confere que o campo limpou e o prazo foi anunciado |
| `CD13-OP` | digita código incompleto, **clica** em confirmar e confere erro, `aria-invalid` e foco |

### 66.7 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **CD-P1** | ✅ **FECHADA — gate visual APROVADO em 2026-08-17.** O olho achou que o contraexemplo das seis caixinhas era **espantalho** (não voltava apagando no Backspace); corrigido para a melhor forma de mercado, com guarda `CD10-c`. A seção é promovida a `estável` |
| **CD-P2** | Migrar a `banco-formfield.html` do `--seed-field-border` **local** para o `--seed-border-interactive` do gêmeo | **Decidido em 2026-08-16 (66.4), com supersede formal:** o gêmeo já tem o semântico canônico de fronteira de controle desde a v1.12, criado com os mesmos números. O token local é melhor no claro (4,74 × 3,38) e **pior no escuro** (3,21 × 4,50). A migração é de artefato `estável`: entra por auditoria, junto com a **CD-P3** |
| **CD-P3** | Auditoria de **todos** os campos do sistema contra o piso 3,00 de borda | Esta rodada mediu `banco-formfield` (passa, 4,74/3,60) e os dois artefatos novos. As telas (`tela-detalhe`, `tela-lista`, `tela-painel`) não foram medidas nesta régua |
| **CD-P4** | ✅ **FECHADA em 2026-08-16, no gate.** Dado real num artefato `estável` | A `banco-formfield.html` trazia **dois** endereços de e-mail reais — linha **333** (estado `success`, campo `s4`) e linha **756** (campo `g3`). Os dois viraram `operador@exemplo.com.br`. *Achado colateral do gate:* a varredura foi feita porque o Rafael perguntou **onde** verificar — e a resposta honesta exigiu procurar, o que revelou a segunda ocorrência que o registro original não mencionava |
| **CD-P5** | O fluxo completo de autenticação (**arquétipo A19**) segue **AUSENTE** | O §66 entrega os **campos**; a **tela** de entrar/recuperar/criar conta não existe como `tela-*.html`. O mapa de cobertura registra A19 como arquétipo, e ele continua sem gabarito |



## 67. Edição em linha — **`estável`** (F7.5, 2026-08-16 · **promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado"**) · IL1–IL14 · fecha **C15** · bancada `banco-edicao-linha.html` **v0.2** · `suite-edicao-linha.mjs` **23 · 0 · 0**

> **O que é.** O valor exibido vira campo **no próprio lugar**, sem abrir formulário nem modal. O
> §48 (painel) já tinha o caso da **grade** — **PN37**: *"a lente Tabela é planilha: setas navegam
> entre células, Enter abre editor na própria célula"*. O que faltava era **o componente que a grade
> consome**, e que também serve fora dela: título de registro, campo do painel de detalhe (§49), item
> de lista (§43).
>
> **Fronteira declarada, e ela é o coração do §67:** esta seção especifica **a passagem** do estado
> de leitura para o de edição e de volta. **Qual controle aparece na edição não é decisão dela**
> (IL13) — monetário abre o §59, data abre o §61, escolha abre o §60, pessoa abre o §62, prioridade
> abre o §63. A bancada exercita **texto** e **número**, e **declara** os demais em vez de
> simulá-los: simular componente que já existe cria uma segunda verdade que ninguém mantém.

### 67.0 Rodada zero — SEM INSTÂNCIA ISOLÁVEL, e a diferença importa

O veredito aqui **não** é "não visitado". A edição direta **foi** lida no produto de referência — ela
é a origem do bloco **PN22–PN41** do §48, que nasceu de leitura visual em 2026-08-13. O que **não**
existe é uma instância do componente **isolado**: no produto ele aparece sempre dentro da grade, e
nunca como controle avulso com estados próprios.

**Veredito: SEM INSTÂNCIA ISOLÁVEL.** *Consequência de método:* o insumo de leitura visual desta
seção **já está gasto** no §48 — e é por isso que o §67 nasce citando PN27, PN28, PN29 e PN37 em vez
de reabrir a leitura. **Herdar decisão medida é mais barato e mais honesto que remedir mal.**

### 67.1 RITO — três rodadas encadeadas

| Rodada | Fonte | O que trouxe |
|---|---|---|
| **R1 — canon** | ARIA APG — *Button Pattern* · SC 2.1.1 *Keyboard* | Não existe padrão APG de "inline edit". O que existe é o padrão do **botão**: o valor em repouso precisa ser um controle de verdade para ser alcançável e anunciável (IL1). E o SC 2.1.1 é o que **elimina o duplo clique como caminho único** — duplo clique não tem equivalente de teclado |
| **R2 — mercado/stack** | PatternFly — *Inline edit* · Semrush Intergalactic — *InlineEdit a11y* | PatternFly: existe um **action-group explícito** de salvar/cancelar que só aparece na edição, e o padrão pede preservar o contexto visual — *"within the context of their current view"*, sem modal, sem transição destrutiva. Semrush: **Enter** abre e, dentro da edição, salva; **Esc** volta descartando; `aria-describedby` liga a mensagem de erro. **Nenhum dos dois define o comportamento do `blur`** — a decisão ficou em aberto no mercado, e por isso ela é decidida aqui com o porquê escrito |
| **R3 — normas** | WCAG 2.2: SC 3.2.2 *On Input* · SC 2.5.8 *Target Size* · SC 4.1.3 *Status Messages* | O SC 3.2.2 responde a pergunta que mais confunde: **salvar ao sair do campo NÃO é mudança de contexto** — contexto é viewport, foco ou conteúdo que muda o significado da página; *"uma mudança de conteúdo nem sempre é uma mudança de contexto"*. O que o critério proíbe é a mudança **automática** sem aviso prévio: submeter o formulário ao preencher um campo, ou **mover o foco sozinho** sem estar descrito antes. Daí o IL11 |

### 67.2 A decisão que mais custa: **clicar fora CONFIRMA**

O mercado não decide isso (R2), então a decisão é da casa, e ela tem lado.

Clicar fora é **o acidente mais comum** num campo em linha. As duas escolhas erram de formas
assimétricas:

| Escolha | O que o acidente causa | Recuperável? |
|---|---|---|
| `blur` **cancela** | **destrói** o que a pessoa digitou | **Não.** O texto nunca existiu em lugar nenhum |
| `blur` **confirma** | **grava** um valor que a pessoa talvez não quisesse | **Sim** — desfazer de 8 segundos, já contratado no **PN28** |

> **Entre um erro reversível e um irreversível, o sistema escolhe o reversível.** E o `Esc` continua
> existindo para quem quer descartar **de propósito** — a diferença entre descartar por decisão e
> descartar por acidente é a decisão.

**E há um efeito colateral que só aparece implementando:** o `blur` do campo dispara **antes** do
clique no botão ✕. Sem tratamento, cancelar seria precedido de um salvamento — *a ação faria o oposto
do que promete*. O contrato IL5 tem guarda própria para isso, e ela clica no ✕ de verdade.

### 67.3 Os contratos IL1–IL14

| # | Contrato | Por quê | Descartado |
|---|---|---|---|
| **IL1** | O valor em repouso é um **botão**, e o nome diz a **ação e o valor** — “Editar Descrição da ordem: Inspeção termográfica do QGBT” | Texto com `onclick` não é alcançável por Tab nem anunciado como acionável. E “Inspeção termográfica…” sozinho não diz que dá para editar | `<div onclick>` (invisível ao teclado); `contenteditable` no próprio texto (estado de edição sem fronteira visível, e sem lugar para confirmar/cancelar) |
| **IL2** | Entra por **clique simples** e por **Enter/Espaço** | SC 2.1.1. O botão nativo já dispara `click` nas duas teclas — não se acrescenta `keydown` paralelo, que abriria duas vezes | **Duplo clique como caminho único** (sem equivalente de teclado); ícone de lápis separado como único gatilho (alvo minúsculo ao lado de um alvo grande inerte) |
| **IL3** | Ao entrar, o foco vai ao campo e o texto fica **selecionado** | Substituir é o caso comum em campo de lista. Cursor no fim cobra uma seleção extra a cada edição | Cursor no fim (mais teclas para o caso comum) |
| **IL4** | **Enter** confirma · **Esc** cancela e restaura · **clicar fora CONFIRMA** | Ver 67.2 | `blur` cancela (perda irreversível); pedir confirmação em diálogo (mata a vantagem de editar em linha) |
| **IL5** | Confirmar e cancelar **também** existem como botões visíveis durante a edição | **Toque não tem Esc.** Teclado tem Enter/Esc; ponteiro e toque têm ✓ e ✕. Dois caminhos, **a mesma mutação** — a mesma lei do CP30 | Só teclado (exclui toque); botões sempre visíveis mesmo em repouso (numa lista de 20 linhas, 40 botões de ruído) |
| **IL6** | **Sem salto de layout**: a caixa de edição mantém a altura da linha (tolerância **2px**, declarada) | Numa lista longa, abrir a edição empurraria tudo o que está abaixo e a pessoa perde o lugar na tela. **Medido: 49,0 → 49,0px, deslocamento 0,0px** | Campo maior que o texto “para caber melhor” (empurra a lista) |
| **IL7** | Confirmar devolve o **foco ao gatilho** e anuncia o **resultado** | Componente que troca o próprio DOM sem devolver o foco larga o usuário no `body` — defeito clássico. E o anúncio é *"Responsável alterada para X"*: **resultado, não percurso** (PN28) | Foco no próximo campo (decide pelo usuário); nenhum anúncio |
| **IL8** | Erro de validação **não sai** do modo de edição | Fechar a edição no erro **descarta o que a pessoa digitou** — o mesmo dano que o `blur cancela`, agora causado pelo sistema. A mensagem se liga por `aria-describedby` (R2/Semrush) | Fechar e mostrar toast (o texto se perde) |
| **IL9** | **“Salvando” e “salvo” declarados**, e o valor **não pisca** de volta ao antigo | Mutação otimista, herdada do **PN27**: o gesto aplica na hora, na tela. Ver o valor voltar e avançar de novo lê como falha | Esperar o servidor para desenhar (o campo trava); nenhum estado (a pessoa não sabe se gravou) |
| **IL10** | A affordance aparece no **hover E no foco** — nunca só no hover | Quem navega por teclado **não passa o mouse**. Medido: borda `#788F9D`, **3,38** no claro e **4,50** no escuro contra o fundo | Só hover (exclui teclado); borda permanente (a lista vira grade de caixas) |
| **IL11** | Digitar **não** salva a cada tecla e **não** move o foco sozinho | SC 3.2.2: mudança automática exige **aviso prévio descrito**. Auto-avanço sem aviso é o mesmo defeito das caixinhas de OTP do §66 | Salvar a cada tecla (grava lixo intermediário e enche o histórico) |
| **IL12** | Alvo do gatilho **≥ 44px** de altura | Piso do projeto desde o §1, acima do SC 2.5.8 (24px). Medido: **784×44px** | 32px “por densidade” (o alvo é o que se erra, não o que se vê) |
| **IL13** | O controle da edição é **o do domínio** — §59 · §60 · §61 · §62 · §63 | Sem isso o componente vira **campo de texto universal**, e a data volta a ser string livre. A tabela de delegação está **impressa na bancada** | Texto livre para tudo (perde máscara, teclado e validação de cada domínio) |
| **IL14** | Campo que **não se edita não vira gatilho** — e **diz por quê** | Herda o **PN29**: alça que existe e não obedece é botão morto; trava invisível vira bug aos olhos de quem usa. Sem `button`, sem `tabindex`, com o motivo ao lado | `disabled` mudo (a pessoa clica e nada acontece, e ela não sabe se é bug) |

### 67.4 A galeria de estados, e por que ela é contrato

O catálogo operante mostra **um** estado por vez — é assim que o componente funciona. Mas a lei
**CP31** diz que bancada imprime os estados **lado a lado**, e ela existe por um motivo prático:
*avaliar o estado de erro não pode exigir que o avaliador reproduza o erro* — gate que depende de
reprodução não acontece.

Os **seis** estados — repouso · hover/foco · em edição · erro · salvando · bloqueado — são impressos
juntos, e são **cópias estáticas**. Isso não é detalhe de implementação: um `<button>` de verdade
impresso ali seria um **botão morto**, a classe de defeito que o projeto persegue desde a v1.4. A
galeria não tem `button`, não tem `input` e não tem `tabindex` — **e a suíte mede essa ausência**
(guarda `CP31`), assim como o gerador aborta se a marcação da galeria contiver qualquer um dos três.

### 67.5 A suíte

**`suite-edicao-linha.mjs` — 20 PASS · 0 FAIL · 1 medida.** Doze das vinte guardas **agem**:

| Guarda | O que ela faz de fato |
|---|---|
| `IL6` | mede o retângulo da linha **antes e depois** de abrir, e o deslocamento da linha seguinte |
| `IL3` | confere que o foco está no `input` **e que o texto está selecionado** (`selectionStart/End`) |
| `IL4-esc` | digita, aperta **Esc** e confere valor restaurado, foco de volta e anúncio |
| `IL4-enter` | digita, aperta **Enter** e confere valor, foco, **nome acessível atualizado** e anúncio de resultado |
| `IL4-blur` | digita e **clica fora**: prova que grava |
| `IL5` | digita e **clica no ✕**: prova que cancela mesmo com o `blur` disparando antes |
| `IL5-b` | digita e **clica no ✓**: prova que confirma |
| `IL8` | esvazia e confirma: a edição **continua aberta**, com `aria-invalid` e `aria-describedby` |
| `IL9` | espera as duas fases e confere **salvando → salvo** sem o valor piscar |
| `IL11` | digita e confere que o gatilho **ainda mostra o valor antigo** e o foco não fugiu |
| `IL2` | foca e aperta **Enter** no gatilho |
| `CP31` | conta os estados impressos e **procura controles mortos** entre eles |

### 67.6 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **IL-P1** | ✅ **FECHADA — gate visual APROVADO em 2026-08-17**, sem achado próprio. A seção é promovida a `estável` |
| **IL-P2** | ✅ **FECHADA — EXECUTADA em 2026-08-19.** E os defeitos eram **SETE**, não três: o pior — **a grade era INALCANÇÁVEL POR TECLADO** (56 células em `tabindex="-1"`, nenhuma em `"0"`; 40 Tabs sem entrar) — **não estava escrito na pendência**. A grade passa a cumprir o padrão `grid` da ARIA e o §67; o contrato **IL15** e a guarda que o mede por COMPORTAMENTO estão no **§67.7** |
| **IL-P3** | O **desfazer de 8 segundos** que sustenta a decisão do `blur` **não existe nesta bancada** | Ele é contrato do §48 (PN28) e vive no painel. A decisão do 67.2 **depende** dele: sem desfazer, `blur confirma` deixa de ser o lado seguro. *Isso está dito aqui para não virar suposição* — quem consumir o §67 fora do painel precisa levar o desfazer junto |
| **IL-P4** | Edição em linha de **valor de domínio** não foi exercida | O IL13 delega, e delegação declarada não é delegação medida. Exercitar exigiria montar §59/§60/§61/§62 dentro da bancada — trabalho de composição, não de spec |


### 67.7 IL15 — a grade do §48 em USO: o padrão `grid` da ARIA, e o defeito que ninguém tinha medido

> **Escrito em 2026-08-19, ao executar a IL-P2.** Seção autossuficiente: presume um leitor que nunca viu
> a conversa que a gerou.

#### 67.7.1 O que é a grade, e o que estava errado

O **§48** (`tela-painel`, dispositivo **PN37**) desenha uma **planilha**: tabela com célula editável,
setas que navegam e alça que preenche para baixo. O **§67** já especifica como se edita um valor em
linha — e a grade tinha **editor próprio**. Essa é a **IL-P2**, decidida pelo Rafael em 2026-08-17 junto
com a SI-P4 (*"unificar é melhor… precisamos manter o mesmo padrão, isso é inegociável"*).

**A pendência listava TRÊS divergências. Medido em 2026-08-19, eram SETE — e a mais grave não estava
escrita:**

| # | Defeito medido | Norma |
|---|---|---|
| **1** | **A GRADE ERA INALCANÇÁVEL POR TECLADO.** As **56** células tinham `tabindex="-1"` e **nenhuma** tinha `"0"`. Partindo do seletor "selecionar todas" da própria tabela e teclando **Tab 40 vezes**, o foco **nunca** entrava numa célula. Só o clique abria a grade | **SC 2.1.1** |
| 2 | a célula era `<td>` sem papel nem nome, e não `<button>` com o nome do **IL1** | SC 4.1.2 |
| 3 | sem nome acessível na forma `"Editar <rótulo>: <valor>"` | SC 4.1.2 · 2.4.6 |
| 4 | sem a affordance de lápis do **IL10** | IL10 |
| 5 | sem `role="grid"` — `tabindex` em `<td>` solto **não é** o padrão ARIA | SC 1.3.1 |
| 6 | o `title` da alça de preencher **entrava no nome acessível** da célula. Medido: *"Ana Lima arraste para copiar para baixo"* | SC 4.1.2 |
| 7 | faltavam **Home/End**, **Ctrl+Home/Ctrl+End** e **F2** | padrão `grid` |

> **A lição, e ela é a mesma da SI-P4 vista de outro ângulo: PENDÊNCIA ESCRITA É HIPÓTESE, NÃO CENSO.**
> A IL-P2 estava escrita desde 2026-08-17 com três itens de SEMÂNTICA. O defeito que realmente impedia
> uma pessoa de usar a grade era de **TECLADO**, e ele só apareceu quando alguém teclou Tab de verdade.
> *Guarda que pergunta "tem este atributo?" nunca teria achado: foi preciso PRESSIONAR a tecla.*

#### 67.7.2 RITO — três rodadas encadeadas

- **R1 — canon (padrão `grid` da ARIA / APG).** O padrão admite **duas** formas: *"a célula contém UM
  widget cuja operação não exige as setas, e as teclas de navegação da grade põem o foco NESSE widget"*
  ou *"a célula contém texto ou um único gráfico e as teclas de navegação põem o foco na CÉLULA"*.
  **As duas exigem os papéis `grid`/`row`/`gridcell`.** Teclado nomeado pelo padrão: setas · Home/End ·
  Ctrl+Home/Ctrl+End · **Enter ou F2** para editar · Esc para cancelar · `tabindex` rotativo.
- **R2 — mercado/stack.** Partir de uma `<table>` semântica e acrescentar `role="grid"` preserva as
  relações cabeçalho↔célula pelos `<th scope>`; quando a célula tem **um** controle, o `tabindex`
  rotativo mora **no controle**, não na célula; e o editor precisa de **nome acessível próprio**, porque
  o cabeçalho de coluna deixa de bastar no instante em que o texto da célula vira um campo nu.
- **R3 — normas.** SC 2.1.1 (teclado) · 1.3.1 (relações) · 4.1.2 (nome, papel, valor) · 2.4.6 (rótulos)
  · 3.2.2 (sem mudança automática) · 2.5.8 (tamanho de alvo).

**VEREDITO: o IL1 do §67 é exatamente a forma (a) do padrão, e fica CONFIRMADO.** O RITO **não** revogou
o que estava escrito — corrigiu o peso: a pendência tratava `role="grid"` como *"agravante"*, e ele é a
**PRECONDIÇÃO**; sem o papel, nenhuma das duas formas existe.

**ALTERNATIVA DESCARTADA:** a **forma (b)** — célula focável com texto dentro. É legítima na ARIA e seria
menos código, mas contradiz o **IL1**, que o Rafael já aprovou, e deixaria a célula **sem nome de ação**:
*"Ana Lima"* não diz que dá para editar.

#### 67.7.3 O contrato IL15

| # | Cláusula | O que a guarda faz de fato |
|---|---|---|
| **IL15-a** | a tabela declara `role="grid"` e tem cabeçalhos de coluna | lê o papel e conta `th[scope="col"]` |
| **IL15-b** | toda célula editável carrega **UM** gatilho (forma (a)) | conta gatilhos contra células |
| **IL15-c** | `tabindex` rotativo: **exatamente um** gatilho na sequência de Tab | conta os `tabindex="0"` |
| **IL15-d** | **a grade é ALCANÇÁVEL por Tab** | **pressiona Tab** até 25 vezes a partir do seletor "selecionar todas" e vê se o foco entra |
| **IL15-e/f** | seta direita anda uma coluna · seta baixo anda uma linha | **tecla** e lê a coordenada do foco |
| **IL15-g/h** | End e Home vão às pontas da **LINHA** | **tecla** e lê |
| **IL15-i/j** | Ctrl+End e Ctrl+Home vão às pontas da **GRADE** | **tecla** e lê |
| **IL15-k** | o `tabindex="0"` **acompanha** o foco | conta depois de navegar |
| **IL15-l** | nome do gatilho na forma `"Editar <rótulo>: <valor>"` | lê o nome acessível |
| **IL15-m/o** | **F2** e **Enter** entram na edição | **tecla** e confere que o foco caiu num campo |
| **IL15-n** | **Esc** cancela e devolve o foco ao **gatilho** (IL7) | **tecla** e lê onde o foco parou |
| **IL15-p** | a alça de preencher é `aria-hidden` — o `title` dela não entra no nome | lê o atributo |
| **IL15-q** | a affordance de lápis aparece **no foco**, não só no hover (IL10) | foca e lê a opacidade computada |
| **IL15-r** | célula travada **não** tem gatilho e **diz o motivo** (IL14) | conta gatilhos e motivos nas travadas |

`validacao/guarda-il15.mjs` — **ALVO DE PASTA**: varre todo `*.html` da raiz e mede todo artefato com
`table.planilha`; arquivo sem grade sai `[n/a]` **nomeado**. Ela troca a lente para "tabela" antes de
medir, porque a grade só existe nessa lente.

**PROVA DE REPROVAÇÃO:** rodada sobre a cópia anterior do `tela-painel.html`, reprova **6 cláusulas** —
`IL15-a` (sem papel), `IL15-b` (0 gatilhos em 56 células), `IL15-c` (0 de 0), **`IL15-d` (NUNCA em 25
pressionamentos)**, `IL15-p` (alça visível ao leitor de tela) e `IL15-q` (sem gatilho, logo sem lápis).

**PLACAR: 1 PASS · 0 FAIL · 50 `[n/a]`**, em **seis execuções idênticas**.

> **DEFEITO DE MEDIÇÃO CONSERTADO NA PRÓPRIA SESSÃO EM QUE A GUARDA NASCEU.** A primeira versão lia a
> opacidade do lápis **logo após** dar o foco, e a transição ainda estava correndo: o valor voltava
> `0.999599` e a guarda **reprovava um artefato correto em 3 de 6 execuções**. *Guarda intermitente é
> pior que guarda ausente: ela ensina a ignorar o placar.* A transição é desligada antes da leitura, e o
> que se mede passa a ser o valor de DESTINO.

#### 67.7.4 O que mudou no artefato, e o que ficou aberto

O `tela-painel` é **gerado**: a mudança foi no molde `validacao/painel-template.html` e o artefato foi
regerado. **A `suite-painel` reprovou o bloco novo no primeiro segundo** — `transition: opacity .12s`
tinha duração **solta**, e a guarda **PN21-11** exige token. Trocado por `var(--seed-dur-productive-fast)`.

**Placares depois:** `suite-painel` **146 PASS · 0 FAIL** · `guarda-il15` **1 · 0 · 50** ·
contraste **2 · 0** a 1440px e **2 · 0** a 390px · forced-colors FC1/FC2 **2 · 0** cada ·
reflow BT7 **3 · 0 · 1**.

| # | Pendência que nasce | Por que fica aberta |
|---|---|---|
| **PN-P6** | ✅ **FECHADA em 2026-08-19 pelo gate. Decisão do Rafael, verbatim: "sobe para 44".** A altura mínima da célula (`.planilha .cel-edit`) foi de **40px para 44px** no molde `validacao/painel-template.html`, e o painel foi regerado. **Medido depois:** a `guarda-il15` imprime o alvo em **234×44px** — a mesma régua que produziu a pendência. **Custo medido, e é o único:** no mesmo recorte de 360px de altura cabe **uma linha a menos** (recorte B2 da folha `render-audit/gate-v134/fechamento.html`). **ALTERNATIVA DESCARTADA:** manter 40px e declarar a planilha como exceção de densidade — descartada porque *toda exclusão declarada é uma dívida com juros*, e porque a linha inteira é o alvo, então os 4px não compram densidade real. **Aplicado à célula inteira, não só à coluna medida:** altura por coluna quebraria o alinhamento horizontal da planilha. *Placar depois: `suite-painel` **146 · 0**, `guarda-il15` **1 · 0 · 50**, contraste **2 · 0** nos dois temas.* |
| **CC-P7** | O contraste do `tela-painel` é medido na lente **padrão** (lista). **Nenhum instrumento troca a lente** antes de medir, então as outras **sete** lentes — inclusive a grade — seguem **não medidas** por contraste. *Ausência de medição não é aprovação* |


## 68. Editor de texto rico — toolbar mínima — **`estável`** (F7.5, 2026-08-16 · **promovido em 2026-08-17 pelo gate visual do Rafael — verbatim: "aprovado"**) · ET1–ET14 · fecha **C7** · bancada `banco-texto-rico.html` **v0.2** · `suite-texto-rico.mjs` **25 · 0 · 0**

> **O que é, e o que explicitamente NÃO é.** Este §68 é o **editor de CAMPO**: a descrição de uma
> ordem de serviço, a observação de um laudo, o corpo de um comentário. Toolbar mínima, marcas
> fechadas, registro tipográfico **de interface**.
>
> Ele **não** é o editor de **DOCUMENTO** (arquétipo **A13**), e essa fronteira não é preferência —
> ela está **medida**. O dispositivo **C25** do `estudo-clickup-completo.md` registra, no editor de
> documento do produto de referência: coluna de **660px**, texto **16px / line-height 24px** (=1,5) e
> uma **stack de fonte diferente da do resto do aplicativo**. *Interface quer 14px/1,15 e densidade;
> leitura corrida quer 16px/1,5 e medida de linha controlada — usar o mesmo registro nos dois piora
> os dois.* Um componente só para os dois casos não serviria bem a nenhum. **A13 continua AUSENTE do
> mapa de cobertura, e é assim que fica dito.**

### 68.0 Rodada zero — leitura visual MEDIDA, e ela decidiu a fronteira

**Veredito: MEDIDO** — e é o caso raro em que a leitura visual entrou na spec **decidindo o que fica
de fora**. Os dispositivos lidos: **C25** (registro tipográfico próprio da coluna de leitura),
**C101** (o documento abre como *overlay* com `×`, não como rota), **C103** (rail de ferramentas
vertical flutuante à direita), **C105** (toolbar flutuante **de bloco**, que aparece ao passar o
mouse), **C106** (callout como bloco tingido).

**Todos os cinco pertencem ao editor de DOCUMENTO.** Nenhum deles é do editor de campo — e é por isso
que eles aparecem aqui como **fronteira**, não como contrato: importar a toolbar flutuante de bloco
(C105) para um campo de descrição de OS traria um controle que só aparece com o mouse por cima,
sobre um campo de três linhas. *Leitura visual bem lida também serve para dizer o que não se copia.*

### 68.1 RITO — três rodadas encadeadas

| Rodada | Fonte | O que trouxe |
|---|---|---|
| **R1 — canon** | ARIA APG — *Toolbar Pattern* · SC 2.1.2 *No Keyboard Trap* | A toolbar é composite: **um único tab stop**, setas navegando dentro, `Home`/`End` nas pontas (ET2). E o 2.1.2 é o que decide o **Tab** (ET6): editor que insere tabulação prende quem navega por teclado |
| **R2 — mercado/stack** | PatternFly · Semrush Intergalactic · prática de editores (ProseMirror/Lexical/TipTap) | Do mercado vem a confirmação de que o estado do botão precisa **seguir o cursor**, não o último clique (ET3), e a lição estrutural: **o modelo do documento é da biblioteca de edição; o contrato é do DS**. Daí a fronteira ET-P1 |
| **R3 — normas** | WCAG 2.2: SC 1.3.1 *Info and Relationships* · SC 4.1.2 *Name, Role, Value* · SC 4.1.3 *Status Messages* | O 1.3.1 é o que transforma "usar `<strong>` e não `<b>`" de gosto em **norma**: a marcação tem de carregar a relação, não só a aparência. O 4.1.2 exige `role`/nome/valor na área editável (ET4) e nos alternadores (ET3). O 4.1.3 põe o contador em região de status (ET13) |

### 68.2 A decisão estruturante: **o conjunto de marcas é FECHADO em seis**

Negrito · itálico · lista com marcadores · lista numerada · link · limpar formatação.

**O motivo não é estético, é de custo em quatro lugares.** O valor deste campo **não fica na tela**:
ele é reimpresso na **proposta** (`seed-ds-proposta`), no **e-mail** (`seed-email.md`) e no **PDF**.
Toda marca que o editor aceita é uma marca que os **outros três meios precisam saber desenhar** — e
o e-mail é o mais restrito dos quatro. *Uma marca nova custa quatro implementações e quatro testes,
não um.*

**Descartado, com o porquê:** títulos e tabelas dentro do campo (são estrutura de **documento**, e o
documento é o A13) · cor de texto e realce (não invertem no tema escuro, e cor não é informação que
sobrevive à reimpressão) · imagem embutida (vira anexo, que é o §14) · Markdown como formato de
entrada (exigiria que a pessoa soubesse a sintaxe — o oposto de toolbar mínima).

### 68.3 Os contratos ET1–ET14

| # | Contrato | Por quê | Descartado |
|---|---|---|---|
| **ET1** | Conjunto de marcas **fechado em seis** | Ver 68.2. O gerador **aborta acima de oito** — acima disso a toolbar deixa de ser mínima e o custo deixa de ser visível | Toolbar “completa” estilo processador de texto (quatro implementações por marca) |
| **ET2** | A toolbar é `role="toolbar"`, nomeada, com `aria-controls` e **um tab stop**; setas andam dentro | APG. Seis botões na ordem de tabulação obrigariam seis `Tab` para chegar ao texto | Botões soltos (seis paradas antes do campo) |
| **ET3** | Cada alternador tem `aria-pressed` que **segue o cursor** | Botão que mostra o estado do último clique **mente** sobre o texto onde o cursor está — e botão que mente é pior que botão sem estado | `aria-pressed` alternando no clique (descasa do conteúdo na primeira vez que se move o cursor) |
| **ET4** | A área é `textbox` **multilinha**, com nome ligado (`aria-labelledby`) e descrição (`aria-describedby`) | `contenteditable` puro não se anuncia como campo. SC 4.1.2 | `div` editável sem papel (a AT não sabe que é campo) |
| **ET5** | Os atalhos **existem e estão escritos na tela** — e também no nome acessível de cada botão | *Atalho que ninguém descobre não existe.* E atalho escrito e inoperante é pior: promete e falha | Atalhos só na documentação |
| **ET6** | **Tab SAI do editor** e nunca insere tabulação | **SC 2.1.2.** Indentar lista com Tab é a convenção dos editores — e é exatamente o que prende quem navega por teclado. A indentação, quando for preciso, entra pela toolbar | Tab indenta (armadilha de teclado); Esc para sair (exige saber que existe) |
| **ET7** | A saída é **HTML semântico de lista branca**: `p`, `br`, `strong`, `em`, `ul`, `ol`, `li`, `a` | SC 1.3.1. `<b>` é aparência, `<strong>` é relação — e é a relação que sobrevive à reimpressão em três meios | `<b>`/`<i>` (não viajam); HTML livre (superfície de injeção e de drift) |
| **ET8** | O que se **cola** passa pela **mesma** higienização | Uma função só, usada nos dois caminhos: **duas implementações seriam duas verdades**, e a que ninguém testa é a que deixa passar | Limpar depois de colar (há uma janela com o conteúdo sujo já no DOM) |
| **ET9** | Link sem texto **não nasce**: a seleção vira o rótulo; e o `rel` entra sempre | URL crua lida letra a letra por leitor de tela; “clique aqui” não diz para onde vai | Pedir a URL primeiro e o texto depois (produz link sem rótulo quando a pessoa desiste no meio) |
| **ET10** | A dica do vazio **não é** o rótulo | Regra do §13: `placeholder` como rótulo some ao digitar e leva o nome do campo junto | Dica no lugar do rótulo (o padrão que o §13 já proibiu) |
| **ET11** | **Cor fixa colada não sobrevive** — nem como `style`, nem como atributo | `color:#333333` **não inverte** no tema escuro: é o **BT3** do §64 entrando pelo **conteúdo** em vez do CSS. E a higienização remove a **tag**, mantendo o **texto** — *nada do que a pessoa escreveu se perde; perde-se só o que o outro programa impôs* | Manter a cor “porque o usuário escolheu” (ele escolheu no outro programa, para o tema claro dele) |
| **ET12** | Alvo dos botões **≥ 44×44px** | Piso do projeto. Toolbar é onde a tentação de encolher aparece primeiro | 32px por densidade |
| **ET13** | Limite, se houver, é **anunciado** e não trunca em silêncio | Herda a doutrina do **CD4** (§66): truncar sem avisar entrega um texto que a pessoa não escreveu | Corte silencioso no envio |
| **ET14** | O valor tem **leitura em texto puro** derivada em fonte única | A lista, a busca e o e-mail curto consomem texto sem marcas. Sem uma derivação canônica, **cada consumidor inventa a sua**, e nascem três versões do mesmo texto | Cada consumidor removendo tags do seu jeito |

### 68.4 Os números medidos

| Par | Claro | Escuro | Piso |
|---|---|---|---|
| Texto do campo sobre a superfície | **13,72** | 12,49 | 4,50 |
| Botão da toolbar em repouso | 4,60 | 7,39 | 4,50 |
| Botão **pressionado** (sobre `action-primary`) | 4,60 | 7,39 | 4,50 |
| Barra inferior (contador e dica) | 7,13 | 9,10 | 4,50 |
| **Borda do editor** (não-textual) | **3,38** | 4,50 | 3,00 |

**Alvo dos seis botões: 44×44px.**

**A prova da higienização, medida** — colado o HTML que um editor de escritório produz
(`style="font-family:Calibri;color:#333333"`, `<b>`, `<font color="#FF0000">`, `<div>`, `<span>`):

| | Resultado |
|---|---|
| Tags que sobraram | `p` · `strong` · `em` |
| `style=` no resultado | **nenhum** |
| `color` no resultado | **nenhum** |
| Texto perdido | **nenhum** — “Escopo”, “três quadros de distribuição”, “5 dias úteis” e “Relatório em PDF ao fim” continuam lá |

### 68.4-b O gate achou o que a lista branca não cobria: **comentário também é conteúdo**

O Rafael colou de um editor real e o resultado trouxe **`<!--StartFragment-->`** para dentro do
campo. A higienização varria `no.querySelectorAll('*')` — **e comentário não é elemento**. Ele
sobrevivia inteiro, e a suíte não via, porque ela media *tags* e comentário não é tag.

**Não é enfeite:** `<!--StartFragment-->` e `<!--EndFragment-->` são marcadores que o clipboard do
Windows adiciona a todo `text/html`; e o Word emite **comentários condicionais**
(`<!--[if gte mso 9]><xml>…<![endif]-->`) com **folhas de estilo inteiras dentro deles**. Uma lista
branca de tags não vê nada disso.

*Correção:* a higienização passa a varrer os nós de comentário com `TreeWalker` **antes** de olhar os
elementos, e removê-los. Guarda nova **`ET8-b`**, que cola um `text/html` com os dois marcadores e um
bloco condicional do Word e cobra **zero** nós de comentário — conferindo, na mesma medida, que
**nenhum texto se perdeu**. *Provada capaz de reprovar:* desfeito o conserto, o placar vai a
**20 · 1**.

> **Regra nova: guarda que varre por TIPO DE NÓ erra em todos os tipos que não listou.** É a mesma
> família dos defeitos de recorte (8º, 14º, 17º) — o escopo errado, de novo, agora escolhido por
> tipo em vez de por posição ou por texto.

**E um achado de leitura, não de código:** o bloco *"o que se cola"* mostra o **código-fonte
escapado**. Selecioná-lo com o mouse e dar Ctrl+C leva **texto**, e o editor insere texto —
corretamente, mas o resultado *parece* quebrado. A bancada agora diz isso em uma linha, e o botão
**"Colar isto no editor"** é o caminho declarado: ele entrega o mesmo conteúdo como `text/html`, que
é o que o editor de escritório de fato coloca na área de transferência.

### 68.4-c A lista branca responde QUAIS tags — ela não responde EM QUE ORDEM

Segunda volta do mesmo gate, e o achado é de outra natureza. Colando **dentro** de um parágrafo
existente, a saída era:

```
<p>Texto que ja<p>Escopo…</p><p>Prazo…</p> estava no campo.</p>
```

**Bloco dentro de bloco.** HTML inválido — um `<p>` não pode conter um `<p>`. E a guarda **ET7
passava**, porque o **conjunto de tags** estava certo: `p`, `strong`, `em`. O que estava errado era o
**arranjo**.

*Por que aconteceu:* a colagem insere os nós com `range.insertNode`, e a **API do DOM não impõe
modelo de conteúdo** — só o **parser** de HTML impõe. Inserir programaticamente permite o que o
parser jamais produziria.

*Correção, usando o próprio parser:* reatribuir `innerHTML` faz o conteúdo ser **reanalisado**, e o
parser fecha o `<p>` de fora ao encontrar o de dentro. Depois disso, parágrafo vazio sai e texto
solto no topo é embrulhado — sem bloco, o consumidor (proposta, e-mail, PDF) não sabe onde o
parágrafo começa. A normalização roda **só na colagem**, nunca no `input`: o round-trip recria os
nós, e recriar nós enquanto alguém digita mataria o cursor a cada tecla.

> **Regra nova: lista branca é vocabulário; estrutura é outra medição.** Toda guarda que valida
> marcação por conjunto de tags precisa de uma irmã que valide **aninhamento**.

Guarda nova **`ET7-b`**: cola com o cursor **no meio** de um parágrafo — a posição que produzia o
defeito — e mede três coisas que só existem na estrutura (bloco aninhado, parágrafo vazio, nó de
texto solto no topo), conferindo também que **o texto antes e o texto depois do cursor sobrevivem**.
*Provada capaz de reprovar:* sem a normalização, ela acusa **2 aninhamentos** e o placar vai a
**21 · 1**.

### 68.5 Uma decisão de render, e por que ela está escrita

Os quatro primeiros botões usam **glifo tipográfico** — `B`, `I`, `•—`, `1—`. Os dois últimos usam
**palavra**: “Link” e “Limpar”.

*O motivo é um achado do render, não gosto:* `⛓` (link) e `⌫` (limpar) caem em **fonte de fallback**
e saem como retângulo vazio em parte das máquinas. O set canônico do §45 **não tem** glifo de link
nem de limpar formatação, e a árvore **NM4** manda usar Lucide nesse caso — mas importar dois SVGs
para dois botões de uma toolbar de seis é peso sem retorno. **Palavra curta é legível em qualquer
fonte, em qualquer tema, e não precisa de legenda.**

### 68.6 A suíte

**`suite-texto-rico.mjs` — 22 PASS · 0 FAIL · 1 medida.** A guarda central mede **o que sai**, não o
que aparece: um editor pode parecer perfeito e produzir `<b style="color:#333">`.

**Detalhe de método que vale para as próximas suítes:** a lista branca está escrita **nas duas
pontas** — no gerador e na suíte — de propósito. *Guarda que importa a régua do medido não mede nada:
mede a opinião dele sobre si mesmo.* Se alguém afrouxar a lista no gerador, a suíte reprova.

| Guarda | O que ela faz de fato |
|---|---|
| `ET7` | **seleciona** um trecho, **clica** em negrito e compara as tags da saída com a lista branca |
| `ET3` | move o cursor **para dentro** e **para fora** do negrito e confere o `aria-pressed` nos dois |
| `ET8` | dispara um `paste` real com `text/html` sujo e confere o que sobrou |
| `ET11` | confere que **nenhuma cor** sobreviveu **e que nenhum texto se perdeu** |
| `ET6` | aperta **Tab** e confere que o foco saiu e que nada foi inserido |
| `ET5-OP` | aperta **Ctrl+B** e confere que produziu `<strong>` |
| `ET2-OP` | aperta **→** e confere que o foco andou e que o tab stop acompanhou |
| `ET9` | clica em link **sem** seleção (não nasce) e **com** seleção (o texto vira rótulo, com `rel`) |

### 68.7 Pendências

| # | Pendência | Por que fica aberta |
|---|---|---|
| **ET-P1** | A bancada usa `document.execCommand`, que é **API depreciada** | O que se avalia nesta página são os **contratos** — o que a toolbar promete, o que o estado reflete, o que a higienização deixa passar —, não a engenharia do editor. Em produção o §68 é implementado sobre biblioteca de edição de verdade. **A fronteira está dita no código e aqui**, para não virar surpresa |
| **ET-P2** | ✅ **FECHADA — gate visual APROVADO em 2026-08-17.** O olho achou o `<!--StartFragment-->` atravessando a higienização, e a reprodução do caso revelou `<p>` dentro de `<p>`; guardas `ET8-b` e `ET7-b`. A seção é promovida a `estável` |
| **ET-P3** | O editor de **DOCUMENTO (A13)** segue **AUSENTE** | Fronteira declarada em 68.0. Ele precisa do registro tipográfico de leitura (C25: 660px, 16px/1,5, outra stack) e de estrutura de blocos — é outro componente, e provavelmente outro arquétipo de tela |
| **ET-P4** | A higienização **não foi auditada como fronteira de segurança** | Ela existe para proteger a **integridade visual e semântica** do valor. Sanitização contra injeção é responsabilidade do **produto**, no servidor, e isso precisa estar escrito onde o produto lê — não só aqui |
| **ET-P5** | Os **três consumidores** (proposta, e-mail, PDF) ainda não declararam que desenham as seis marcas | O ET1 assume que eles sabem. **Assumir não é medir** — a auditoria dos três é o que fecha o argumento de custo do 68.2 |

---

## 69. Auditoria transversal de CONSUMIDOR — regras transversais **AC1–AC4** (2026-08-17, v1.02) · fecha **EA-P1** · fecha a auditoria da **SI-P4** e da **IL-P2**

> **O que esta seção resolve, para quem a lê sem ter visto a conversa que a gerou.** Este sistema tem
> **blocos canônicos** — o artefato que DEFINE um componente — e **consumidores** — o artefato que o
> USA. Até 2026-08-17, **nenhuma guarda do acervo comparava consumidor com canônico**: as suítes medem
> um artefato contra um contrato; nenhuma media um artefato contra **outro**. A consequência tem data,
> nome e número: o bloco de assinatura corporativa (sub-bloco **EA** de `seed-email.md`) foi corrigido
> em **2026-08-14** — o cargo passou de *"Diretor"* para *"CEO"*, o telefone sintético virou o real e
> nasceu um campo de **endereço**. O consumidor `en-comercial-amostra.html` guardou a versão anterior
> até **2026-08-17**, quando **o olho do Rafael** pegou. Nem a `suite-email` (que valida marcação) nem
> o `contraste-email` (que valida cor) sabem qual é a versão corrente do bloco EA. Isso virou a
> pendência **EA-P1**. As pendências **SI-P4** (telas desenham entidade sem consumir o §65) e **IL-P2**
> (a grade do §48 tem editor próprio em vez do §67) são a **mesma classe** entrando por outro lado.
> Esta seção nasce para as três, e o instrumento é `validacao/auditoria-consumidor.mjs`.

---

### 69.1 O RITO de três rodadas — a régua não era óbvia, e a intuição estava errada

**R1 · CANON (W3C / WAI).** A WCAG 2.2, requisito de conformidade **5.2.2 "Full pages"**, diz
literalmente: *"Conformance (and conformance level) is for full web page(s) only, and cannot be
achieved if part of a web page is excluded."* E o argumento de **Hidde de Vries**, em *"Can components
conform to WCAG?"*, fecha o raciocínio: um componente **não conforma isoladamente**, porque
**customização**, **combinação** e **contexto** transcendem o componente — a responsabilidade é da
**página entregue**.
**Consequência direta:** o objeto de medida é o **CONSUMIDOR**, não o bloco canônico. Canônico
consertado com consumidor velho não é "quase conforme": é uma **página que reprova**. Daí o alvo ser
de **PASTA** e a varredura partir do consumidor.

**R2 · MERCADO / STACK.** Existem duas famílias de prática, e **nenhuma** resolve o caso desta casa:

| Prática | O que faz | Por que não serve aqui |
|---|---|---|
| **Visual diffing** (Sparkbox, *Finding and Fixing Design System Drift*) | captura as duas versões e lista as diferenças | O próprio texto que a propõe diz que o resultado exige **julgamento humano** para decidir de que lado está o erro. Guarda que precisa de humano para dar veredito **não é guarda** — é folha de captura, e a folha já existe (§64.9.3). E o diff visual **não distingue** cópia congelada de reimplementação divergente, que é exatamente a distinção que separa a EA-P1 da SI-P4/IL-P2 |
| **Analytics de uso de componente** (Omlet/Zeplin) | extrai nome de componente, caminho de arquivo, uso de props e grafo de dependência | Serve a codebase que **importa** o componente. Nossos consumidores **não importam nada**: carregam uma **cópia colada** do bloco. Em e-mail isso nem é escolha — cliente de e-mail não tem `import`, e é a razão de existir o **EM-BT7** |

**Fronteira declarada:** o mercado **não tem** instrumento para *"cópia colada de bloco canônico dentro
de artefato HTML"*. Este é próprio, e dizer o contrário seria emprestar autoridade que não existe.

**R3 · NORMAS.** **EN 301 549 V3.2.1, cláusula 9.0:** *"Requirements in clause 9 apply to web pages (as
defined in clause 3.1)"*, e *web page* é *"non-embedded resource obtained from a single URI using HTTP
plus any other resources that are used in the rendering"*. Mesma conclusão da R1 por outro caminho: a
unidade de obrigação é o **recurso entregue**. E a **Nota 3** da mesma cláusula dá o respaldo
**normativo** ao veredito `[n/a]`: se o recurso não existe na página, o requisito **não se aplica**.
Consumidor que legitimamente não tem o bloco **declara `[n/a]`** — não reprova, e não desaparece do
placar.

---

### 69.2 A decisão de desenho, com as alternativas descartadas

**ALTERNATIVA A — diff textual do bloco. DESCARTADA.** Ela condena artefato correto **por
construção**: por regra deste projeto o **molde** carrega sentinela de campo e a **amostra** carrega o
valor real, porque a amostra é a referência visual do gate. Um diff textual acusaria **100% das
amostras**. É a forma mais caseira de cometer *"guarda que condena artefato correto força conserto
errado"*.

**ALTERNATIVA B — diff visual. DESCARTADA como instrumento primário**, pelo motivo da R2: devolve
lista, não veredito. Não é inútil — é complementar, e quem a executa é a folha de captura no gate.

**ALTERNATIVA C — comparação ESTRUTURAL por CAMPO DECLARADO, com REGIME POR CAMPO. ESCOLHIDA.**
Cada bloco canônico declara seus campos, e cada campo declara **como** se compara:

| Regime | O que cobra | Exemplo medido |
|---|---|---|
| `idêntico` | texto normalizado igual ao do canônico | `alt` do logo · `href` do site · a tinta do endereço (`#40706a`) |
| `padrão` | casa uma expressão regular | no molde, a sentinela do campo |
| `presente` | existe e não é vazio, sem cobrar valor | a linha de endereço, cuja **ausência** criou a EA-P1 |
| `proibido` | **não** casa — é o único regime que mede decisão NEGATIVA | a assinatura leva forma **curta**: sem bairro e sem CEP (decisão de 2026-08-14) |
| `livre` | não se compara | dado sintético por regra de LGPD em bloco genérico |

**O regime muda por tipo de arquivo (molde vs. amostra), e é essa peça que faz a alternativa C
funcionar onde a A falha.** A normalização resolve entidades HTML (`&middot;` = `·`), colapsa espaço em
branco e compara `style` **por declaração ordenada**, nunca por string — ordem de declaração de CSS
não é desvio, e comparar string produziria FAIL por formatação.

**E a segunda decisão: TRÊS CLASSES DE CONSUMO, porque são TRÊS PERGUNTAS.**

| Classe | O consumidor… | A pergunta | Veredito |
|---|---|---|---|
| **AC-CÓPIA** | carrega uma **cópia** do bloco canônico | a cópia concorda com o canônico nos campos que devem concordar? | **PASS / FAIL** — é medida, e reprova |
| **AC-REIMPL** | desenha a mesma coisa com **marcação própria** | o consumidor satisfaz o contrato canônico? | **`[achado]`** com o desvio impresso, **nunca FAIL** — a resposta é de DESENHO, e unificar mexe em tela `estável`. Mesma disciplina que a `auditoria-borda-campo` aplica à borda de botão |
| **AC-VERSÃO** | é um **documento** que declara a versão de um artefato | a versão declarada é a que o artefato tem? | **PASS / FAIL** |

A classe **AC-VERSÃO** nasceu do **§81.3 do MANIFESTO**, onde **sete** lugares em **dois** documentos
declaravam versão de bancada que nenhum artefato tinha — invisível a **todas** as guardas do acervo,
porque nenhuma delas lê o documento. Ela cobra o **documento** e nunca o artefato: *o artefato é a
coisa; o documento é o registro da coisa.*

**Fronteira da v0.1, declarada:** mede **estrutura e texto**, em jsdom. **Não mede pixel** — pixel é
trabalho de `contraste-composicao.mjs` e `render-promocao.mjs`. E **não mede a correção do próprio
canônico**: ela mede **concordância** entre consumidor e canônico. Canônico errado com consumidor fiel
passa — e é por isso que esta guarda **soma** às suítes em vez de substituí-las.

---

### 69.3 Os contratos AC1–AC4, e o que cada um MEDIU

**Placar de 2026-08-17: `7 PASS · 0 FAIL · 5 [achado] · 12 [n/a]`.**

| # | Classe | Alvo | O que mediu | Resultado |
|---|---|---|---|---|
| **AC1** | CÓPIA | bloco de assinatura **EA** — canônicos `ea-assinatura.html` (molde) e `ea-assinatura-amostra.html` (amostra); **12 consumidores** varridos | **15 campos** por consumidor: `src`/`alt`/dimensão do logo, a régua de borda, nome, cargo+sufixo, telefone, e-mail, site (href e texto), **existência** do endereço, **forma curta** do endereço, e três tintas | **1 PASS** (`en-comercial-amostra.html`, conforme em 15 campos) · **11 `[n/a]`** (não carregam o bloco) |
| **AC2** | REIMPL | identidade de entidade do **§65** · 5 telas | anatomia de cada componente que consome cor de entidade: largura, raio, presença de glifo, posições do CP23 consumidas | **4 `[achado]`** · **1 `[n/a]`** (`tela-painel` não consome cor de entidade — medido, zero ocorrências) |
| **AC3** | REIMPL | edição em linha do **§67** na grade do §48 | contrato do gatilho canônico, campo a campo, contra a célula editável da grade | **1 `[achado]`** com **3 divergências** |
| **AC4** | VERSÃO | `seed-componentes.md` e `mapa-cobertura-ds.md` × **10 bancadas** | versão declarada no documento contra a versão medida no `<title>` e no selo do artefato | **8 + 8 declarações CONFEREM** · **2 PASS** |

#### AC2 — a premissa da SI-P4 estava PARCIALMENTE errada, e a medição corrigiu duas coisas

**Primeira correção: o culpado tem nome, e são QUATRO telas, não três.** A SI-P4 dizia
*"`tela-shell`, `tela-lista` e `tela-detalhe` desenham entidade sem consumir este §65"*. Medido: o
componente que reimplementa o §65 é o **`.sigla`** — **24×24px, raio 6px, com `svg` de 14×14px
dentro** —, e ele existe em **`tela-shell`, `tela-lista`, `tela-detalhe` E `tela-tabela`**. *É a mesma
correção que a SG-P1 fez ao §60 quando achou "seis controles em quatro arquivos, não três em três": a
lista escrita antes da medição erra na contagem e no endereço.*

**Segunda correção, e ela salva um artefato correto de conserto errado:** o `.ent` das telas — **12×12px,
raio `--seed-radius-1`, `aria-hidden`, SEM glifo** — **não é** o quadrado do §65. É um **ponto de cor
decorativo** em item de filtro. Uma guarda que dissesse *"todo consumidor de `--seed-entity-N` deve ser
um `.quad`"* condenaria **14 elementos corretos** em três telas e forçaria pôr glifo onde ninguém
pediu. *O §65 é cor **+ glifo**; o ponto é só cor. São dois componentes, não um bem-feito e um
mal-feito.*

**ACHADO NOVO, que não estava em nenhuma pendência — vira PS-P5.** O **`.mini-avatar`** de
`tela-lista` e `tela-tabela` é **círculo de 26px com as iniciais da pessoa** (medido: `MA`, `FB`, `HS`,
`CA`, `AV`, `PB`) e se pinta com **`--seed-entity-N` / `-ink`**, isto é, com as **seis posições de
ENTIDADE do CP23**. Mas o **§62 (PS-P3)** fechou a cor de iniciais de pessoa numa paleta **própria e
enumerada de oito pares** de primitivas de rampa (`turquesa-100/900`, `turquesa-200/900`,
`amarelo-100/900`, `amarelo-200/900`, `azul-100/900`, `azul-200/900`, `cinza-100/900`,
`cinza-200/900`), medida nos dois temas, pior par **9,33** contra piso 4,50. **Círculo é PESSOA e
quadrado é ENTIDADE** — a lei de forma do §62/§65 —, então pintar pessoa com token de entidade é usar
token **fora do seu semântico**: se a paleta de entidade mudar, os avatares de pessoa mudam com ela, e
ninguém pediu isso. **`[achado]` de fronteira, não veredito** — a decisão é de desenho.
*Achado menor de higiene, registrado no mesmo lugar: em `tela-lista` a regra `.mini-avatar[data-ident]`
existe com **0 elementos** no DOM — CSS sem consumidor.*

#### AC3 — as três divergências entre a grade do §48 e o §67, medidas

O canônico do §67 entrega o valor editável como **`<button class="gatilho">`** com nome acessível na
forma **`"Editar <rótulo>: <valor>"`**, contendo o valor em elemento próprio e a affordance de lápis
`aria-hidden`, com canal de anúncio na linha. Os cinco itens do contrato foram **lidos do próprio
artefato**, não transcritos daqui — instrumento que copia contrato de documento mede o documento.

A grade do §48 (`tela-painel.html`, PN37) divergiu em três:

| Contrato do §67 | Grade do §48 | Veredito |
|---|---|---|
| valor editável é `<button>` | é **`<td class="celula" tabindex="-1">`** | **DIVERGE** |
| nome acessível `"Editar <rótulo>: <valor>"` | **ausente** | **DIVERGE** |
| affordance de lápis | **ausente** | **DIVERGE** |
| canal de anúncio `aria-live` | **presente** | converge |

**E um agravante medido, que a IL-P2 não conhecia:** o consumidor **não tem `role="grid"`** — medido,
zero ocorrências. Sem role de grade, `tabindex="-1"` na célula **não é** o padrão ARIA de grid (roving
tabindex dentro de `role="grid"`): é **foco por script sem semântica**. Isso remove a única defesa
plausível do `<td>` focável, e é dado novo para a decisão.

**Escopo desta medida, declarado no próprio relatório:** a grade é montada por **script**, então a
célula não existe no DOM estático (medido: **0** células estáticas). A medida é sobre o **texto do
gerador**. Medir o DOM aqui devolveria zero, e zero seria *número plausível sobre coisa que não foi
medida* — o defeito de escopo que este projeto já cometeu cinco vezes.

---

### 69.4 A PROVA DE REPROVAÇÃO — quatro injeções, quatro quedas de placar, quatro reversões

**Regra do projeto: guarda tem de ser PROVADA capaz de reprovar.** Injeta o defeito, vê o placar cair,
reverte. Foram quatro, e **uma delas achou um buraco na própria guarda antes de ela ser commitada.**

| Prova | Defeito injetado | Placar |
|---|---|---|
| **1** | o defeito EXATO da EA-P1 reintroduzido em `en-comercial-amostra.html`: cargo de volta a *"Diretor"* e a linha de endereço apagada | **FAIL, 3 desvios** nomeados: `cargo+sufixo`, `endereco.existe`, `tinta.endereco` |
| **2** | endereço na forma **longa** (com bairro e CEP), violando a decisão de 2026-08-14 | **FAIL, 2 desvios** — incluindo o regime `proibido` citando a expressão que ele proíbe |
| **3** | `seed-componentes.md` alterado para declarar `banco-pessoa.html` **v0.9** | **FAIL, 1 desvio**, com **número de linha** (5116) e a instrução de corrigir o documento, nunca o artefato |
| **4** | um glifo `<svg>` posto dentro de um `.ent` decorativo de `tela-detalhe` | a classificação **virou** de *"ponto decorativo"* para *"REIMPLEMENTAÇÃO"* — a anatomia manda, não o nome da classe |

Todas as quatro foram **revertidas**, e a reversão foi **conferida por MD5** contra o arquivo da pasta:
`en-comercial-amostra.html`, `tela-detalhe.html` e `seed-componentes.md` voltaram idênticos.

> **O que a PROVA 1 achou na primeira tentativa, e é o registro mais importante desta seção.** Na
> primeira versão do instrumento, o campo `cargo` da **amostra** usava o regime `sufixo` — cobrava só
> que o texto terminasse em *"· SEED engenharia"*. Com esse regime, *"Diretor · SEED engenharia"*
> **PASSAVA**. Ou seja: **a guarda construída para a EA-P1 não pegava a EA-P1.** Ela pegou o endereço
> ausente e deixou passar o cargo errado — exatamente metade do defeito original.
> **Conserto de causa:** para consumidor em regime **amostra**, os cinco campos de pessoa (nome, cargo,
> telefone, e-mail, endereço) passam a regime **`idêntico`** contra `ea-assinatura-amostra.html`. A
> justificativa não é gosto, está escrita no próprio artefato consertado em 2026-08-17: *"o bloco
> abaixo agora é cópia VERBATIM de `ea-assinatura-amostra.html` v0.2"*. Se o contrato é cópia
> verbatim, o regime é `idêntico`; `sufixo` era um regime frouxo escolhido por medo de condenar dado
> variável, e o medo mediu menos do que devia.
> **Lição de instrumento, nova nesta pasta:** *guarda que não é testada CONTRA O DEFEITO QUE A GEROU
> não está provada.* Provar com um defeito qualquer não basta — o caso fundador é o teste obrigatório.

---

### 69.5 Dois defeitos de instrumento consertados antes de qualquer veredito

**1 · A suíte MORREU na primeira execução.** O regex que lê as regras de `<style>` apanhava também o
**comentário de CSS**, e o comentário do bloco de tokens — que tem chaves dentro — entrou como se fosse
**seletor**. O `querySelector` recebeu isso e estourou com `DOMException`. **Suíte que morre é pior que
suíte que reprova**: morte é ausência de medida disfarçada de silêncio. Conserto de causa: comentário
sai **antes** de qualquer parse; at-rules (`@media`, `@supports`) são descartadas; e seletor inválido
passa por um `achaSeguro` que **declara** que não pôde resolver, em vez de derrubar a corrida.

**2 · ESCOPO por REGRA CSS — a QUINTA reincidência da mesma família neste projeto.** A primeira versão
tratava cada regra `.sigla[data-ident="N"]` como um consumidor separado e resolvia o elemento com um
`querySelector` que devolvia o **primeiro** casamento. Resultado **medido**: o `.sigla` saiu
classificado como *"ponto decorativo"* nas posições 1, 2, 3, 5 e como *"REIMPLEMENTAÇÃO"* na posição 4
— **seis vereditos diferentes para UM componente**, porque a geometria (24px, raio 6px, `svg` de 14px)
vive na regra **base** `.sigla { }` e as regras por posição só trocam a cor. Conserto de causa: **o
consumidor é o COMPONENTE, não a regra.** O instrumento agrupa por **seletor base**, funde as
declarações de todas as regras daquele base (inclusive a de descendente, que é onde o glifo aparece) e
classifica **uma vez**, olhando **todos** os elementos que casam.

*Escopo continua sendo a variável que mais produz erro neste projeto: por posição no arquivo (8º, 14º),
por texto (17º), por seletor (18º, 19º, 20º), por coordenada (22º), por região amostrada (23º) e agora
**por regra CSS**.*

---

### 69.6 Pendências

| # | Pendência | Estado em 2026-08-17 |
|---|---|---|
| **EA-P1** | não existe guarda que compare consumidor com canônico | ✅ **FECHADA.** A guarda existe (`validacao/auditoria-consumidor.mjs`), mede **15 campos** do bloco EA em **12 consumidores**, e foi **provada capaz de reprovar o defeito que a gerou** (§69.4, prova 1) — inclusive depois de um conserto que a própria prova exigiu |
| **SI-P4** | a identidade em uso nas telas ainda é ad hoc | **A AUDITORIA ACABOU; a pendência fica ABERTA só na DECISÃO.** Medido: o `.sigla` (24px · raio 6px · glifo) reimplementa o §65 em **quatro** telas — `tela-shell`, `tela-lista`, `tela-detalhe` e **`tela-tabela`**, que não estava na lista. Unificar mexe em tela `estável`: é desenho, e vai a gate |
| **IL-P2** | a grade do §48 (PN37) ainda tem editor próprio | **A AUDITORIA ACABOU; a pendência fica ABERTA só na DECISÃO.** Medido: **3 divergências** de contrato, mais o agravante novo de **não haver `role="grid"`**. Unificar mexe em tela `estável` |
| **PS-P5** *(nova)* | o `.mini-avatar` (círculo de iniciais de PESSOA) se pinta com a paleta de **ENTIDADE** em `tela-lista` e `tela-tabela` | **ABERTA.** O §62 (PS-P3) fechou a cor de iniciais numa paleta **própria de 8 pares**, medida nos dois temas (pior par 9,33 · piso 4,50). Círculo é pessoa, quadrado é entidade: pintar pessoa com token de entidade é token fora do semântico. **`[achado]`, não defeito** — vai a gate. *Higiene junto: em `tela-lista` a regra tem **0** elementos no DOM* |
| **AC-P1** *(nova)* | o registro do AC1 cobre **um** bloco canônico | O bloco **EA** foi o primeiro porque foi o que doeu. Faltam entradas de CÓPIA para os outros blocos colados em consumidor — a começar pelo rodapé antispam e pelo esqueleto de `seed-email-base.html`. *Fronteira declarada: cobertura de UM bloco não é cobertura da família, e dizer o contrário seria contar cobertura que não existe* |
| **AC-P2** *(nova)* | o AC4 só reconhece **uma forma** de declaração de versão | A forma reconhecida é `` bancada `nome.html` vX.Y ``. Qualquer outra forma **não é conferida**, e o total de conferidas sai **impresso** no placar (8 e 8) justamente para que a cegueira apareça em vez de se esconder. Alargar a forma pede rodada própria, com prova de reprovação |


## 70. CT1 — o CENTRO DO DESENHO dentro do alvo · regra transversal (2026-08-19, estendida na sexta parte do mesmo dia) · `validacao/guarda-centro-alvo.mjs` **180 · 0 · 228** em 2 temas × 4 estados × 2 larguras

> **Escrito em 2026-08-19.** Esta seção existe porque **o Rafael viu um defeito que nenhuma das 60+
> guardas do projeto via** — e o viu num anexo de imagem, a olho nu, num botão de 44px.

### 70.1 O achado, e quem o achou

No gate v13.3 o Rafael anexou dois recortes da barra superior do `tela-shell` com **setas vermelhas**
sobre o botão de menu (☰) e sobre o sino, e escreveu:

> *"no anexo 1, acredito que o icone nao esteja centralizado dentro da caixa, pode ver nos que apontei
> com a seta."*

**Estava certo.** Medido: o desenho estava **acima e à esquerda** do centro geométrico do botão.

**A causa era de CSS, e é banal:** o botão declarava `display:flex` **sem** `align-items` e
`justify-content`, ou herdava uma `line-height` de texto que empurrava o `svg` para cima dentro da
caixa de linha. **Cada um desses controles passava em TODAS as guardas existentes**: tinha tamanho de
alvo ≥44px (SC 2.5.8), tinha nome acessível (SC 4.1.2), tinha contraste (SC 1.4.11), tinha foco visível.
*Nenhuma delas mede onde o desenho está DENTRO da caixa.*

> **A lição, e ela é sobre o método, não sobre CSS:** *um acervo com 60 guardas verdes não é um acervo
> medido — é um acervo medido NAQUILO QUE AS GUARDAS OLHAM.* O olho do Rafael cobriu uma dimensão que
> a bateria inteira não cobria. **Toda vez que ele apontar algo com o dedo, a resposta certa não é
> consertar o item: é perguntar qual RÉGUA estava faltando.**

### 70.2 O contrato CT1

| # | Cláusula | Régua |
|---|---|---|
| **CT1-a** | Em controle **de ícone** (o desenho é todo o conteúdo), o **centro da tinta** coincide com o **centro da caixa do controle**, nos dois eixos | distância entre centros ≤ **1,00px**, tolerância **declarada** |
| **CT1-b** | A cláusula vale nos **dois** viewports de referência — 1440px e 390px | um controle pode centrar num e descentrar no outro; medir só um é medir metade |
| **CT1-c** | Controle **com rótulo** ao lado do ícone está **FORA** desta conta | ali o problema é de **composição** (alinhamento entre duas coisas), não de **centro**. Medi-lo por centro reprovaria composição correta |

> ⚠ **DIVERGÊNCIA DE NOMENCLATURA, achada em 2026-08-19 (sexta parte) e declarada aqui em vez de
> consertada em silêncio.** Esta tabela usa `CT1-a/b/c` para *(centro nos dois eixos · dois viewports ·
> rótulo fora)*, e a **guarda** usa `CT1-a/b/c` para *(centro horizontal · centro vertical · o desenho
> cabe na caixa)*. **São dois esquemas de nome para o mesmo contrato**, e as mensagens de erro que o
> operador lê vêm da guarda. **Canônico, a partir de agora, é o esquema DA GUARDA** — porque é o que
> aparece no placar. Renomear a tabela exigiria reescrever as citações do §96 do MANIFESTO, e
> renomeação sem supersede formal é pior que divergência declarada: fica como pendência **CT-P6**
> no §70.7.

### 70.3 A guarda, e os três cortes que ela precisou aprender

`validacao/guarda-centro-alvo.mjs` varre **a pasta** (nunca uma lista), mede **189 controles de ícone**
em 51 arquivos e imprime **522 controles com rótulo descartados** — o descarte sai no placar, porque
*limite que não aparece no placar é truncagem silenciosa*.

> **A PRIMEIRA EXECUÇÃO CONDENOU 37 CONTROLES, E A MAIORIA ESTAVA CERTA.** Isso não foi acidente de
> percurso: foi a guarda medindo a coisa errada. Os três cortes abaixo nasceram **antes de qualquer
> artefato ser tocado** — porque *guarda que condena artefato correto força conserto errado*, e um
> conserto errado num artefato `estável` é muito mais caro do que uma guarda que demora um dia a mais.

| corte | o que ele exclui | por quê |
|---|---|---|
| **conteúdo ≤60% da caixa E texto visível ≤3 caracteres** | `.niv`, que tem rótulo dentro de um `<span>` | ali há texto; não é controle de ícone |
| **caixa aproximadamente quadrada** (razão 0,5–2,0) | barras e faixas largas | numa caixa 10:1 o "centro" horizontal não é requisito de nada |
| **sem `justify-content: space-*`** | `.gatilho`, com `space-between` | quem declara `space-between` está pedindo **distribuição**, não centro — condená-lo seria condenar a intenção declarada |

Depois dos cortes: **15 de 16 casos reais** sobraram, e todos eram defeito de verdade.

### 70.4 O que a execução tocou, e o placar

Bloco CT1 acrescentado a **seis moldes** e **um artefato feito à mão**:

| arquivo | o que passou a valer |
|---|---|
| `shell-template.html` | `.sino`, `.avatar` → `inline-flex`; `.abre-nav`, `.sino`, `.avatar` → `align-items/justify-content: center`, `line-height: 1` |
| `painel-template.html` | `.escala-ctrl button` |
| `site-template.html` | `.b-larg` |
| `banco-data-template.html` | `.cal__nav button` |
| `banco-escolha-template.html` | `.chips-mais` |
| `banco-pessoa-template.html` | `.chip .x` (só `line-height: 1`) |
| `banco-superficies.html` | `.seed-avatar-more` (só `line-height: 1`) |

**PLACAR: 23 PASS · 0 FAIL · 28 `[n/a]`**, a **1440px e a 390px**, 189 controles medidos, **0 fora do
centro**, tolerância 1,00px declarada. Os 28 `[n/a]` são arquivos **sem controle de ícone**, nomeados um
a um no placar.

**Recorte antes/depois nos dois temas, ampliado 8×:** blocos **E1, E2 e E3** da folha
`render-audit/gate-v134/fechamento.html`.

### 70.5 SUPERSEDE de 2026-08-19 (sexta parte) — **CT-P1 e CT-P2 fechadas**: nascem **CT1-d** e **CT1-e**

> **O que este supersede revoga:** a redação anterior do §70.5, que listava CT-P1 e CT-P2 como
> pendências abertas, e a afirmação implícita de que o placar `23 · 0 · 28` cobria o contrato CT1.
> **Ele cobria um oitavo dele** — uma das duas metades de tema, num dos quatro estados.
> **A régua não mudou. O número de vezes que ela é aplicada, sim.**

#### 70.5.1 As duas cláusulas novas

| # | Cláusula | Régua | Como o estado é produzido |
|---|---|---|---|
| **CT1-d** | O centro do desenho vale **nos dois temas** | mesma tolerância de 1,00px | `data-theme` + `prefers-color-scheme`, os dois juntos |
| **CT1-e** | O centro do desenho vale em **`:hover`, `:active` e `:focus-visible`** | mesma tolerância de 1,00px | **interação de verdade**: ponteiro movido até o centro · botão do mouse **pressionado** (e solto **fora** do controle, para o clique não se completar) · tecla `Tab` apertada **uma vez por passe** para fixar a modalidade de teclado do Chromium, seguida de `.focus()` |

**Cada estado é ASSERTADO antes de valer.** A guarda pergunta ao elemento `el.matches(':hover')`,
`el.matches(':active')` e `document.activeElement === el`. Se o estado **não pegou**, a medição
**não vira PASS nem FAIL**: sai `[n/a]` com o motivo. Isso não é zelo — é a diferença entre medir e
supor: alvo coberto por elemento opaco não recebe ponteiro, e `<div role="button">` sem `tabindex`
não recebe foco. Nos dois casos um PASS seria mentira, e um FAIL seria pior.

**`[n/a]` de tema tem régua própria, e ela é COMPORTAMENTAL.** *Tema que o artefato não promete não
se mede.* Antes de medir escuro, a guarda calcula uma **assinatura de cor** — `color`,
`background-color`, `border-top-color` e `outline-color` de **todos** os elementos, reduzidos a um
hash na própria página — sob tema claro e sob tema escuro. **Assinatura idêntica = o artefato não
promete tema escuro** → `[n/a]` nomeado, nunca PASS.
*Alternativa descartada, com o motivo:* `grep` por `[data-theme="dark"]` no arquivo. Descartada
porque **comentário e texto de bancada casam com o regex** (e há lei nesta casa contra comentário que
o próprio instrumento procura), e porque um artefato pode prometer tema **só** por
`prefers-color-scheme`, sem nenhum `data-theme` escrito. *Guarda que pergunta "tem este atributo?"
mede IMPLEMENTAÇÃO; esta pergunta "muda de cor?", que é o contrato.*

**Isenção de componente INATIVO, contada e separada de defeito.** `<button disabled>` não recebe
foco **por contrato** — é a mesma isenção que a SC 1.4.3 e a SC 1.4.11 dão ao componente inativo — e
sai como `[n/a] ISENTO`. Já `<div role="button">` sem `tabindex` que não recebe foco é **defeito de
teclado (SC 2.1.1)** e sai com esse nome. *Somar os dois na mesma linha do censo esconderia um
defeito atrás de uma isenção legítima: motivo agregado esconde causa.*

#### 70.5.2 O placar, separado por tema e por estado

*Placar que não separa o que tem regra diferente mente pela média.* **Duas execuções idênticas em
cada largura** — medida que não é repetida não está verificada.

| tema / estado | PASS | FAIL | `[n/a]` | medições a 1440px | medições a 390px |
|---|---|---|---|---|---|
| claro / repouso | 23 | **0** | 28 | 189 | 188 |
| claro / hover | 23 | **0** | 28 | 189 | 188 |
| claro / ativo | 23 | **0** | 28 | 189 | 188 |
| claro / foco | 23 | **0** | 28 | 187 | 186 |
| escuro / repouso | 22 | **0** | 29 | 182 | 181 |
| escuro / hover | 22 | **0** | 29 | 182 | 181 |
| escuro / ativo | 22 | **0** | 29 | 182 | 181 |
| escuro / foco | 22 | **0** | 29 | 180 | 179 |

**AGREGADO: 180 PASS · 0 FAIL · 228 `[n/a]`** em cada largura · **1.484 candidatos e 1.480 medições
efetivas a 1440px** · **1.476 e 1.472 a 390px** · **4.080 controles com rótulo descartados** a 1440px
e **3.896** a 390px · **367 medições casaram `:focus-visible`** a 1440px, **365** a 390px ·
**0 controles fora do centro** · tolerância 1,00px declarada.

**Nenhum artefato foi alterado.** Não havia o que consertar: o bloco CT1 escrito na quinta parte já
estava certo também no tema escuro e nos três estados de interação. *Isso é FATO medido, e é
diferente de "provavelmente estava certo", que era o que a CT-P1 dizia.*

**Onde estão os `[n/a]`, um por um** — *limite que não aparece no placar é truncagem silenciosa*:

| quantos | o que é | nomeados? |
|---|---|---|
| 28 por passe claro, 29 por passe escuro | arquivos **sem nenhum controle de ícone** (bancadas de token, família de e-mail, `tela-detalhe`, `tela-referencia`) | sim, um a um no placar |
| 5 arquivos | **não prometem tema escuro**: `banco-dataviz-tokens`, `ea-assinatura`, `ea-assinatura-amostra`, `seed-dataviz-di-prova-impressa`, `seed-design-system` | sim. É por isso que o passe escuro tem 1 `[n/a]` a mais e 7 medições a menos: o `seed-design-system` tem 7 controles de ícone |
| 4 em 1.484 | **ISENTOS por componente inativo**: `button.cd__gatilho` de "Vigência do contrato" (`banco-data.html`) e `button` "Página anterior" (`tela-tabela.html`), ambos `disabled`, em 2 temas | sim, com o seletor |
| **0** | **defeitos de teclado** | — |

#### 70.5.3 O 33º defeito de instrumento do catálogo — `scroll-behavior: smooth`

Na **primeira** execução da guarda estendida sobre o acervo, 14 medições saíram `[n/a]` com o motivo
*"o ponteiro não alcança"*: os **7 `button.page-btn` do `seed-design-system.html`** apareciam com
centro em **y = 27108** numa janela de 900px, em dois passes (claro/hover e claro/ativo).

**A causa não era o artefato: era o medidor.** Três arquivos do acervo declaram
`scroll-behavior: smooth` — `banco-navegacao.html`, `seed-design-system.html` e `tela-shell.html`.
Com rolagem suave, `scrollIntoView()` **anima**, e o retângulo lido no instante seguinte ainda é o de
**antes** da rolagem.

> **A lição, e ela é geral: `animation:none` e `transition:none` NÃO cobrem `scroll-behavior`.**
> Ela é uma **terceira** forma de animar, e tem de ser desligada **por nome**. Depois do conserto
> (`html{scroll-behavior:auto!important}` junto com as outras duas), as 14 medições voltaram e os
> passes de claro/hover e claro/ativo subiram de 182 para **189** — o mesmo número do repouso.
> *14 medições perdidas por defeito do medidor teriam ido para o placar como `[n/a]` justificado.*

#### 70.5.4 A prova de que a régua morde — `render-audit/prova-ct1/`

*Guarda que não encontra nada tem de ser provada capaz de encontrar alguma coisa, e guarda reescrita
tem de ser provada capaz de reprovar — uma prova por cláusula.* Nasceram **8 artefatos de prova**,
com dado **sintético** (um `<button>` de 44×44 com um quadrado turquesa de 18×18: nenhum dado de
cliente, nenhum dado pessoal), cada um com um defeito plantado ou um caminho de `[n/a]` a disparar:

| artefato | o que ele carrega | veredito esperado |
|---|---|---|
| `prova-d-escuro.html` | 20px fora do centro **só no tema escuro** | FAIL nos 4 estados de escuro, PASS nos 4 de claro |
| `prova-e-hover.html` | 20px fora **no `:hover`** | FAIL em `hover` **e em `ativo`** — enquanto o botão está pressionado sobre o controle, ele casa `:hover` **e** `:active` |
| `prova-e-ativo.html` | 20px fora **no `:active`** | FAIL só em `ativo` |
| `prova-e-foco.html` | 20px fora **no `:focus-visible`** | FAIL só em `foco`. *Se este passasse, provaria que a modalidade de teclado não foi fixada e que a guarda mede um foco que ninguém vê* |
| `prova-sem-escuro.html` | nenhuma regra de tema | `[n/a]` "não promete tema escuro" |
| `prova-coberto.html` | botão sob um `div` opaco `position:fixed` | `[n/a]` em `hover` e `ativo`; PASS em `repouso` e `foco` |
| `prova-sem-foco.html` | `<div role="button">` sem `tabindex` | `[n/a]` "DEFEITO DE TECLADO (SC 2.1.1)" |
| `prova-inativo.html` | `<button disabled>` | `[n/a]` "ISENTO: componente INATIVO" |

**Placar de referência da pasta de prova: `40 PASS · 12 FAIL · 12 [n/a]`**, idêntico em duas
execuções, com os **4 caminhos de `[n/a]` disparando** (2× ponteiro não produz `:hover` · 2× botão
pressionado não produz `:active` · 2× isento por inativo · 2× defeito de teclado).

> **Ali, FAIL é o resultado desejado — é um teste de teste.** Se essa pasta algum dia voltar
> **0 FAIL**, quem quebrou foi a guarda, não o acervo. O número que importa é **12**.
> O `LEIA-ME.md` da pasta é autossuficiente e explica cada artefato.

#### 70.5.5 Decisões tomadas sem consultar o Rafael, declaradas e separadas

*Fato dele e inferência minha não se misturam no mesmo parágrafo.* As duas foram para o gate visual
numa seção "vete se discordar" da folha `render-audit/gate-v135/fechamento.html`.

| # | Decisão | Alternativa descartada, com o custo MEDIDO |
|---|---|---|
| 1 | **Tolerância continua 1,00px também nos estados de interação** | 2,00px, sob o argumento de que hover e foco mexem em borda. Descartada: **custo de não afrouxar = zero** — 1.480 medições passaram a 1440px e 1.472 a 390px com 1,00px. *Afrouxar sem precisar é comprar cegueira de graça* |
| 2 | **Tema que o artefato não promete sai `[n/a]`, não PASS** | Contar os 5 artefatos sem tema escuro como PASS. O resultado geométrico seria idêntico e o placar ficaria **185 PASS** em vez de 180. Descartada: *medir o que não existe e chamar de aprovado é como um placar aprende a mentir*. Custo: 5 pontos menos de placar e 5 nomes a mais na lista |

### 70.6 Prova de INÉRCIA — a dimensão antiga tem de dar o mesmo número

A guarda foi **reescrita**, e a régua (a função `classifica`, com os três cortes de candidatura, a
caixa de conteúdo, o descarte de satélite e o recorte de 1px de leitor de tela) foi **copiada sem
tocar em uma vírgula**. Motivo: *guarda reescrita que muda de resultado na dimensão antiga não
estendeu nada — trocou de assunto.*

```
TEMA=claro ESTADO=repouso node validacao/guarda-centro-alvo.mjs
→ 23 PASS · 0 FAIL · 28 [n/a] · 189 controles de ícone · 522 com rótulo descartados
```

**Reproduziu na primeira execução, exatamente**, os mesmos números do §70.3 e do §70.4. A guarda
aceita `TEMA=` e `ESTADO=` justamente para que essa prova seja **repetível por quem vier depois**, e
não uma afirmação num registro.

**Bônus da reescrita, e ele conserta um `continue` mudo:** a versão anterior descartava em silêncio
três classes de controle — invisível, minúsculo (lado < 8px) e sem conteúdo visível. Elas agora saem
**nomeadas e contadas** no *censo de descarte*: a 1440px, **4.080** com rótulo · **592** invisíveis ·
**560** minúsculos · **48** sem conteúdo visível. *`continue` mudo é a forma mais barata de mentir
num placar* — e o conserto **não** mexeu em PASS nem em FAIL, só fechou a conta.

### 70.7 Pendências que ficam

| # | Pendência | Por que fica aberta |
|---|---|---|
| **CT-P3** | A guarda mede **um estado por passe**. **Combinação** de estados — foco por teclado **e** ponteiro em cima ao mesmo tempo, que é o caso real de quem navega por `Tab` e depois encosta o mouse — não é medida | Nasce em 2026-08-19. O número de combinações cresce em fatorial e **ainda não há evidência** de que alguma delas mova tinta. Declarada, não esquecida |
| **CT-P4** | Os **três cortes de candidatura** da CT1 (conteúdo ≤60% da caixa · caixa aproximadamente quadrada · sem `justify-content: space-*`) **não têm artefato de prova próprio** em `render-audit/prova-ct1/` | Nasce em 2026-08-19. Eles nasceram porque a guarda condenou 37 controles corretos, mas **o critério em si nunca foi provado capaz de errar** — nem de excluir de menos, nem de excluir demais |
| **CT-P7** | O **eixo de PONTEIRO está em ZERO em todos os outros contratos do acervo.** Contado: dos **52 instrumentos `.mjs`** da pasta `validacao/`, **nenhum** move o ponteiro além da guarda CT1 e do `corta-recorte.mjs`, ambos desta parte. Contraste, tamanho de alvo, cor forçada e reflow medem **só repouso** | Nasce em 2026-08-19, da própria medição que fechou a CT-P2. **O caso mais provável de esconder defeito real é o CONTRASTE em `:hover`**: fundo de hover é uma cor que ninguém deste acervo jamais mediu, e ela pode estar abaixo do piso sem que nenhum placar reclame. *O eixo de FOCO, ao contrário, já era coberto por 20 dos 52 — a afirmação larga de que "nenhum instrumento media estado" foi minha e a medição a revogou no mesmo dia* |
| **CT-P6** | **Divergência de nomenclatura** entre o §70.2 (que chama `CT1-a/b/c` de *centro nos dois eixos · dois viewports · rótulo fora*) e a guarda (que chama `CT1-a/b/c` de *centro horizontal · centro vertical · o desenho cabe*) | Nasce em 2026-08-19. **Canônico é o esquema da guarda**, porque é o que sai no placar que o operador lê. Consertar a tabela exige supersede formal e reescrita das citações do §96 do MANIFESTO — trabalho de meia hora que não muda medida nenhuma, e por isso não passou na frente da guarda de colisão |
| **CT-P5** | Achado de **aparência**, não de medida: `.sino` e `.abre-nav` do `tela-shell` **não têm nenhuma resposta visual ao `:hover`** — medido, `padding`, borda, `transform` e `line-height` saem idênticos em repouso, hover e ativo; só o `outline` do foco muda | Nasce em 2026-08-19, da conferência de md5 dos recortes da folha (três recortes de estado saíram byte-idênticos, e a investigação mostrou que o **fato** é que o CSS não muda nada). **Não viola contrato nenhum hoje.** É decisão de aparência, e aparência é do Rafael: foi ao gate v13.5 como pergunta opcional |

---

## 71. HV1 — a RESPOSTA AO PONTEIRO e o PRESSIONADO DISTINTO · regra transversal (2026-08-19, sétima parte) · `validacao/guarda-resposta-ponteiro.mjs`

> **Escrito em 2026-08-19.** Esta seção existe porque **o Rafael recusou uma pergunta que eu não devia
> ter feito** — e a recusa é uma correção de método, não de gosto.

### 71.1 A pergunta que criou o contrato

Na folha do gate v13.5 eu registrei um achado e o classifiquei como *"decisão de aparência, e aparência
é sua"*: o sino e o botão de menu (☰) da barra superior do `tela-shell` **não mudavam nada quando o
mouse passava em cima**. O Rafael respondeu:

> *"me questiono, nao deveria existir uma ação/efeito para cada tipo para existir essa diferenciação?
> o que o mercado sugere? pesquise e aplique, nao é decisao minha."*

> **A lição, e ela é sobre a fronteira entre gosto e engenharia:** eu tratei como *preferência* uma
> pergunta que a **pesquisa** responde. A regra deste projeto já dizia *"pergunta que a pesquisa
> responde não é pergunta"* — e eu a violei empurrando para o gate uma decisão que tem **canon de
> mercado convergente, resposta verificável e norma aplicável**.
> **Aparência é do Rafael. CONVENÇÃO DE INTERAÇÃO é pesquisa.** Distinguir as duas é o trabalho.

### 71.2 O RITO de três rodadas, com as fontes

| rodada | fonte | o que ela estabelece |
|---|---|---|
| **R1 canon** | Material Design 3 — *state layers* | estado é um **véu** com **opacidade por estado**: camadas **distintas**, não a mesma |
| **R1 canon** | Adobe Spectrum — *states registry* | `hover` e `down` (pressionado) são **estados INDEPENDENTES** |
| **R1 canon** | eBay Playbook | **hover 8%, press 12%** — o pressionado é o sobrevoo **mais 4%**; a mudança tem de ser *"subtle and distinct"* e *"consistent across components"* |
| **R2 mercado** | Sainsbury's Design System | seis estados de fundação, com **pressed** separado de hover, os dois "a darker shade of the brand colour" — mesma direção monotônica |
| **R2 mercado** | Carbon (IBM) | estado interativo tem **nome de token de propósito** (`layer-hover`, `background-hover`, `field-hover`), não valor solto |
| **R2 stack** | **shadcn/ui — o stack de produção do ERP da SEED** | define `hover:` em todas as variantes de botão e **não define `active:`**. *O stack mais usado do mercado **subespecifica o pressionado** — e este acervo herdou isso* |
| **R3 normas** | WCAG 2.2 | **a WCAG NÃO exige estado de sobrevoo**: *"WCAG does not require user interface components to have hover states"* |
| **R3 normas** | SC 1.4.3 e SC 1.4.11 | **se o estado existe**, a tinta tem de manter o piso de contraste contra o **NOVO** fundo: ≥4,5 com texto, ≥3,0 se for só ícone |

> **A conclusão separa o que é norma do que é convenção — e é essa separação que o contrato carrega:**
> **HV1-a** e **HV1-b** são **convenção de mercado**, canon convergente em cinco fontes, logo decisão de
> **engenharia**. **HV1-c é NORMA**, e é uma obrigação que só nasce **porque** se acrescentou o estado:
> *quem cria um fundo novo cria a obrigação de medir o contraste sobre ele.*
> É por isso que as três moram na **mesma** guarda: separá-las permitiria acrescentar hover e esquecer o
> contraste dele — que é exatamente a pendência **CT-P7**, aberta na parte anterior.

### 71.3 O contrato

| # | Cláusula | Régua | Natureza |
|---|---|---|---|
| **HV1-a** | O controle **ativo** responde ao ponteiro | a captura do elemento sob `:hover` **difere** da captura em repouso | convenção |
| **HV1-b** | O **pressionado** é distinto do sobrevoo | a captura sob `:active` **difere** da captura sob `:hover` | convenção |
| **HV1-c** | A **tinta sobrevive** ao fundo novo | `color` × fundo resolvido ≥ **3,0** (só ícone, SC 1.4.11) ou **≥4,5** (com texto, SC 1.4.3), no sobrevoo **e** no pressionado | **norma** |

**Componente INATIVO está ISENTO e sai CONTADO.** Controle `disabled`/`aria-disabled` **não deve**
responder ao ponteiro — exigir-lhe resposta seria exigir mentira. É a mesma isenção que a SC 1.4.3 e a
SC 1.4.11 dão ao componente inativo.

**Fronteira declarada, e é DECISÃO, não economia: a guarda roda a 1440px.** `:hover` é capacidade de
**ponteiro**; em aparelho de toque não existe sobrevoo, e a forma correta de tratar isso em CSS é
`@media (hover: hover)`. Medir sobrevoo numa janela de 390px de navegador de mesa mediria uma situação
que o usuário de celular nunca vive. Cobrir toque é medir **outra coisa** — o pressionado por toque — e
é a pendência **HV-P2**.

### 71.4 O JUIZ É O PIXEL — e isso foi comprado com um defeito de instrumento

A primeira versão da guarda julgava por `getComputedStyle`: montava uma assinatura com as propriedades
que pintam e reprovava quando ela não mudava. **Na primeira execução sobre o acervo, a própria guarda se
desmentiu:** o `button.linha-abrir` do `tela-tabela` saiu com assinatura **idêntica** entre repouso e
sobrevoo, e as duas capturas do elemento eram **diferentes**. A resposta visual estava fora do alcance da
assinatura — no fundo do **ancestral** (`tr:hover` pinta a linha inteira), em `::before`/`::after` e em
descendentes mais fundos que o primeiro nível.

> **LIÇÃO, e ela vale para toda guarda desta casa: propriedade computada do ELEMENTO não descreve a
> APARÊNCIA do elemento.** O que o usuário vê é a composição — ancestral, pseudo-elemento, descendente,
> empilhamento. A assinatura mede o que o CSS **declara** no nó; o contrato fala do que a tela
> **mostra**. **Quando o contrato é sobre aparência, o juiz é o pixel.**

A guarda captura o elemento nos **três** estados e compara os **bytes**. A assinatura de estilo continua
sendo calculada, mas virou **diagnóstico**, e conta dois casos que o pixel sozinho não sabe nomear:
**"declarou e não aparece"** (regra de hover que o olho não vê) e **"apareceu e não declarou"**.

⚠ **O REPOUSO é medido com o ponteiro FORA DA PÁGINA**, em `(-5, -5)`, e isso foi **verificado**: ali
`document.querySelectorAll(':hover')` devolve **zero** elementos, enquanto o canto inferior esquerdo do
`tela-shell` deixa **2** e o canto superior direito deixa **3**. *Repouso medido com o ponteiro dentro
da página não é repouso.*

### 71.5 O CENSO — o tamanho real do problema, medido

**1.410 controles ATIVOS, 51 arquivos, 2 temas, 1440px, 1.410 medições efetivas, 4.230 capturas:**

| cláusula | reprovações | proporção |
|---|---|---|
| **HV1-a** — sem nenhuma resposta ao ponteiro | **619** | **44%** dos controles ativos |
| **HV1-b** — pressionado indistinguível do sobrevoo | **1.350** | **96%** |
| **HV1-c** — contraste abaixo do piso no estado novo | **90** | **6%** — e estes são **violação de norma HOJE** |

**Placar: 0 PASS · 90 FAIL · 12 `[n/a]`.** *Nenhum dos 51 arquivos passa.* E isso confirma, com número,
o que a rodada R2 previu: **o stack de mercado subespecifica o pressionado, e o acervo herdou.**

As classes mais frequentes, para que a fila de trabalho já esteja pronta:
**HV1-a** — `button` (174) · `button.seed-btn` (72) · `summary` (48) · `button.no` (26) · `button.b-larg` (24).
**HV1-b** — `button` (196) · `button.seed-btn` (120) · `button.btn` (88) · `button.icell` (68) · `summary` (48).
**HV1-c** — `button.page-btn` (28) · `button.btn` (22) · `button.linha-abrir` (20) · `button.acc-trigger` (12) · `button.btn-focus` (4) · `button.pg-btn` (2) · `button.fachada` (2).

### 71.6 A GRAMÁTICA — HV1-d, e os dois tokens que nasceram

**Gêmeos de token vão a v1.18.** Dois semânticos de **propósito**, **sem nenhum hex novo**: os valores
são os da escala de superfície que o acervo já pratica.

| token | claro | escuro | propósito |
|---|---|---|---|
| `--seed-action-neutral-hover` | `#F2F6F9` | `#141D23` | sobrevoo de controle **neutro**, sobre qualquer superfície neutra |
| `--seed-action-neutral-active` | `#E3EBF0` | `#0B1419` | **pressionado** de controle neutro — um passo **adiante**, na mesma direção |

> **Por que NÃO se reusou `action-ghost-hover`, e o motivo é medido:** no tema escuro ele vale `#1D272D`,
> que é **exatamente** `surface-raised`. Um controle neutro **posado sobre superfície elevada** — a barra
> superior do shell — teria hover **invisível** no escuro. *O token não está errado para o propósito
> dele (botão fantasma sobre a PÁGINA); está errado para este outro propósito.* Consumidores medidos de
> `action-ghost-hover` hoje: **2 arquivos** (`banco-componentes`, `banco-tokens`), nos dois sobre a
> página — nenhum sobre superfície elevada. Nada muda para eles.

**Gramática por família de base:**

| base do controle | `:hover` | `:active` |
|---|---|---|
| superfície **neutra** | `action-neutral-hover` | `action-neutral-active` |
| **marca chapada** (`surface-brand-strong`, o `.avatar`) | `surface-brand-deep` + `text-on-brand-deep` (par medido **6,32** claro / **9,37** escuro) | o **mesmo** fundo + **anel interno** de `text-on-brand-deep` — muda o pixel **sem trocar o fundo**, o que preserva o contraste já medido |
| **rampa da marca** (o `.rail-item`) | `chrome-overlay-hover` | `chrome-overlay-active` — já existiam, véu branco medido **em composição** |

> **Alternativa descartada 1 — véu (`box-shadow: inset`), que é o mecanismo do Material Design 3.**
> Descartada por motivo **medido, não estético**: os instrumentos desta casa leem `background-color` para
> compor contraste (o `fundoResolvido` da guarda HV1 **aborta** em imagem/gradiente, e o
> `contraste-composicao` trata gradiente como caso especial). Um véu tornaria o contraste do estado
> **NÃO MEDÍVEL**. *Trocar um contrato medido por um contrato bonito é o oposto do que este projeto faz.*
> **Alternativa descartada 2 — `action-primary-active` no pressionado do avatar.** Medido: no tema
> escuro ele vale `#11B0A0` e o branco sobre ele mede **2,66** — reprovaria a SC 1.4.11.

### 71.7 O que a execução tocou nesta parte, e o defeito que a guarda pegou EM MIM

Bloco HV1 acrescentado a **`shell-template.html`** (`.topbar button`, `.topbar .avatar`), com os quatro
nomes de token na lista `NOMES` do `gen-shell.py`. **`tela-shell.html` regerado.**

| medida | antes | depois |
|---|---|---|
| HV1-a no `tela-shell`, por tema | **10** | **8** |
| HV1-b no `tela-shell`, por tema | **16** | **12** |
| HV1-c no `tela-shell` | 0 | **0** |
| CT1 no `tela-shell` (regressão) | 8 · 0 | **8 · 0**, 2 temas × 4 estados |

> **A guarda pegou, no mesmo minuto, um defeito que eu acabei de introduzir.** A primeira versão do
> bloco escrevia `.avatar:hover` (especificidade **0,2,0**), que **perde** de
> `.topbar button:hover` (**0,2,1**). Resultado: o avatar recebia o fundo **neutro** e ficava com tinta
> **branca** sobre `#F2F6F9` — a guarda mediu **1,09** contra o piso 4,5 e reprovou na hora.
> `.topbar .avatar` (**0,3,1**) conserta. *Este é o argumento inteiro a favor de a cláusula de contraste
> morar na MESMA guarda que a de resposta ao ponteiro: acrescentar estado é criar fundo novo, e fundo
> novo pede medida — no mesmo passe, não na próxima sessão.*

### 71.8 Pendências

| # | Pendência | Por que fica |
|---|---|---|
| **HV-P4** | **A gramática HV1-d ainda não foi aplicada ao acervo inteiro**: 619 casos de HV1-a e 1.350 de HV1-b seguem abertos fora do `tela-shell` | É mudança transversal em ~34 artefatos e 20 moldes, e cada um exige regeneração **mais** medição de contraste do estado novo. **A fila está pronta e impressa por classe no §71.5** — não é buraco, é lista de trabalho com número. Fazer às pressas produziria o defeito que a guarda acabou de pegar em mim, 34 vezes |
| **HV-P5** | Os **90 casos de HV1-c** são **violação de norma HOJE** e concentram-se em **7 classes** (`page-btn`, `btn`, `linha-abrir`, `acc-trigger`, `btn-focus`, `pg-btn`, `fachada`) | **É a prioridade absoluta da próxima parte**, acima de HV-P4: ali o hover **já existe** e **já mata** o contraste. Sete classes, não 1.350 controles |
| **HV-P1** | `<a href>` sem classe de botão **não entra** na população | link tem convenção própria (sublinhado) e é população inteira diferente |
| **HV-P2** | **toque não é medido** | `:hover` é capacidade de ponteiro; cobrir toque é medir o pressionado por toque, que é outra régua |
| **HV-P3** | a guarda compara **aparência**, não **legibilidade da mudança** | uma diferença de 1 unidade num canal de cor passa em HV1-a e é invisível ao olho. O piso perceptual não existe ainda |

---

## 72. SH1 — COLISÃO, COBERTURA, CORTE e VAZAMENTO · regra transversal (2026-08-19, sétima e oitava partes) · `validacao/guarda-colisao.mjs` · **FECHA a SH-P3**

> **Escrito em 2026-08-19.** Fecha a pendência **SH-P3**, aberta havia quatro partes, que dizia:
> *"nenhum instrumento mede COLISÃO — sobreposição de elementos irmãos dentro de uma barra que não
> estoura o documento. O SH-P2 foi achado por captura e olho, não por guarda."*

### 72.1 Por que a guarda de reflow não bastava

A `suite-reflow.mjs` (contrato BT7) mede se o **documento** estoura na largura. Mas conteúdo pode
encavalar **sem** estourar largura nenhuma: a barra continua com 1440px, o documento não tem rolagem
horizontal, e dois irmãos dentro dela estão um em cima do outro.
*Guarda que mede o continente não mede o conteúdo.*

### 72.2 A base normativa — e aqui, ao contrário do HV1, ela é direta

| fonte | formulação |
|---|---|
| `Understanding` da **SC 1.4.12 Text Spacing** | *"Overlapping text is a failure"* · *"Text Cut Off — the bottom portion of the words is cut off ... making that text unreadable"* |
| **F104**, falha técnica da WCAG 2.2 | *"part of the content clips and is unreadable when the user overrides the spacing of the text"* |
| **SC 1.4.10 Reflow** | *"no loss of content or functionality"* — e **isenta** explicitamente *"parts of the content which require two-dimensional layout"*, sendo **tabela de dados o exemplo canônico da própria norma** |

> **Consequência de alcance:** colisão **não é** questão de gosto nem de conveniência de layout. **É
> perda de conteúdo, e perda de conteúdo é falha normativa.** Por isso SH1 **reprova**, não "acha".

### 72.3 O contrato

| # | Cláusula | Régua |
|---|---|---|
| **SH1-a** | Irmãos **em fluxo** não se sobrepõem | dois filhos do mesmo pai, ambos visíveis e em fluxo normal, com **caixas de LINHA** que se cruzam em área > 1px² |
| **SH1-b** | Texto visível não fica **coberto** | `elementFromPoint` no centro do nó de texto tem de devolver ele mesmo, um ancestral ou um descendente. Terceiro que **pinta** → FAIL; terceiro **transparente** → `[achado]` |
| **SH1-c** | Texto não é **cortado em silêncio** | conteúdo que transborda a caixa **no eixo que esconde**, com um **nó de texto** de fato passando do recorte. `text-overflow: ellipsis` é **ISENTO e CONTADO** |
| **SH1-d** | O conteúdo **não vaza da própria caixa** | o retângulo de um nó de texto excede a **caixa de padding** do elemento por mais de 1px, **e esse elemento tem FRONTEIRA VISÍVEL** (borda, fundo pintado, ou é controle interativo). *Nasce em 2026-08-19, oitava parte* |

> **Por que a SH1-d precisou nascer, e por que ela não é uma variação da SH1-c:** o Rafael anexou um
> recorte a 390px com **duas setas vermelhas** sobre os botões "Anterior" e "Próximo" do `tela-gantt` e
> escreveu — *"o nome dentro do botão estoura para fora, isso tem solução?"* **Nenhuma das três cláusulas
> anteriores pegava aquilo:** não é colisão entre irmãos, não é texto coberto, e **não é texto cortado**,
> porque o `overflow` é `visible` e o texto fica **perfeitamente legível** — só está do lado de fora da
> caixa que deveria contê-lo.
> *A SH1-c mede o que o recorte ESCONDE; a SH1-d mede o que a caixa NÃO SEGURA. São defeitos opostos com
> a mesma causa — conteúdo maior que a caixa — e um instrumento que só olhasse `overflow:hidden` seria
> estruturalmente cego para metade do problema.*
> **A fronteira visível é o corte que dá sentido à cláusula:** texto que passa da caixa de um `div`
> transparente é layout normal e ninguém vê; texto que atravessa a **borda de um botão** salta aos olhos.

### 72.3-a A causa do defeito fundador: duas regras boas que, juntas, produzem uma ruim

Medido no `tela-gantt` a 390px: caixa de conteúdo **50×42**, texto **56×14** → vaza **6px**. E a 1440px
sob o espaçamento da SC 1.4.12: caixa **49×42**, texto **67×14** → vaza **6px à esquerda e 12px à
direita**. *O defeito existia nas duas larguras — ele viu na estreita.*

A causa é uma armadilha clássica de flexbox, e vale escrita porque vai reaparecer:

1. `display:flex` dá ao item **`flex-shrink: 1` por padrão** — o item pode encolher;
2. o `min-width: auto`, que normalmente **protege** o item de encolher abaixo do próprio conteúdo, foi
   **substituído** por `min-width: 44px`, que é o **piso de ALVO** da SC 2.5.8;
3. com 44px valendo como mínimo e o encolhimento ligado, a caixa fica **menor que o texto**; e como
   `overflow` é `visible` e "Anterior" é palavra única sem ponto de quebra, o texto **sai por fora**.

> **A lição: o piso de alvo, escrito como `min-width`, desligou sem querer a proteção que o flexbox dava
> de graça.** Duas regras corretas, cada uma defensável, produziram um defeito na interseção.
> *Nenhuma revisão de uma regra por vez encontraria isso — só a medição do resultado.*

**O conserto, em duas linhas:** `flex-wrap: wrap` no pai (se a linha não couber, os botões **descem**) e
`flex: 0 0 auto` nos botões (nunca encolhem abaixo do conteúdo). O alvo de 44×44 segue garantido por
`min-width`/`min-height`.

| alternativa descartada | motivo |
|---|---|
| `overflow-wrap: anywhere` | quebraria "Anterior" no meio da palavra — resolve a geometria e **piora a leitura** |
| rótulo curto no mobile ("Ant." / "Próx.") | degrada o **nome acessível** e o entendimento |
| `font-size` menor | fura a escala tipográfica e o piso de legibilidade |
| `overflow: hidden` no botão | **transformaria vazamento em CORTE** — o defeito oposto e pior, e a própria SH1-c reprovaria. *Esconder o sintoma no eixo errado não é conserto* |

**Dois espaçamentos, e o placar separa os dois:** `padrao` (o artefato como é) e **`1412`** (o override
da SC 1.4.12: `line-height: 1.5`, `letter-spacing: .12em`, `word-spacing: .16em`, `2em` depois de
parágrafo, todos `!important` porque o usuário os aplica por folha de **usuário**). **Antes desta guarda,
nenhum instrumento do acervo media essa dimensão.**

### 72.4 SETE defeitos de instrumento numerados (mais dois sem número), todos de FALSO POSITIVO, todos com fixture

A primeira execução acusou **398 colisões, 122 textos cobertos e 304 textos cortados**. Depois de sete
consertos de **régua** — nenhum de artefato — o acervo saiu com **0 · 0 · 0 · 0** a 1440px. *Falso
positivo de guarda nova quase nunca é bug de código: é a régua medindo uma COISA DIFERENTE da que o
contrato nomeia.*

| # | defeito | o que produzia, MEDIDO | o conserto |
|---|---|---|---|
| — | **caixa de inline multilinha** | `getBoundingClientRect()` de um inline devolve a **união** das linhas; dois `<strong>` em linhas diferentes têm uniões que se cruzam sem nada se sobrepor. **106 falsos `strong × strong`** | `getClientRects()`: **uma caixa por linha**, e a comparação é linha contra linha |
| **34º** | **`<details>` FECHADO tem retângulo e não tem pixel** | a tabela equivalente do `banco-dataviz-dm`, que existe **para leitor de tela**, saía 5× por passe como *"th coberto por figure.seed-chart"* | `Element.checkVisibility({checkOpacity, checkVisibilityCSS, contentVisibilityAuto})` + `closest('details:not([open]))` |
| **35º** | **`elementFromPoint` é HIT-TEST DE PONTEIRO, não teste de pintura** | o `kbd` do "Ctrl K", que é `pointer-events:none` desenhado **por cima**, saía **12×** como coberto pelo botão | texto sob `pointer-events: none` → `[n/a]` nomeado |
| **36º** | **a técnica de leitor de tela É uma caixa de 1px com `overflow:hidden`** | SH1-c acusou **304** cortes, sendo 172 `span.vh` + 20 `.visually-hidden` + 12 `.sr` + 12 `label.vlibras` + 8 `caption.vh` | `recorteDeLeitor()`: lado ≤2px, `clip: rect(0 0 0 0)` ou `clip-path` que zera |
| **37º** | **conteúdo ROLADO PARA FORA de `overflow:auto` tem retângulo dentro da janela e está recortado** | a célula `#1208` do `banco-dados` em y=778, `checkVisibility` **true**, e o `elementFromPoint` devolvia a paginação. **A captura mostrou:** `.seed-tablewrap` é `overflow-y:auto` com `clientHeight` 420 e `scrollHeight` 465 — a linha está **fora da vista, não coberta** | o retângulo do texto tem de estar dentro da caixa de **todos** os ancestrais que recortam |
| **38º** | **o eixo que ESCONDE tem de ser o eixo que TRANSBORDA** | o `#quadro` do `tela-quadro` é `overflow: auto/hidden` com excesso **em X**, onde o valor é `auto` — **há rolagem, o usuário alcança tudo** — e reprovava por causa do `hidden` em **Y** | medir o excesso **por eixo**, só onde aquele eixo esconde |
| **39º** | **`clientWidth` e `clientHeight` de elemento INLINE não-substituído valem ZERO** | a SH1-d acusou **168 vazamentos** na primeira execução, e o censo mostrou **124 `code` + 24 `kbd` + 16 `span.kbd` + 4 `span`** — todos inline com fundo e borda (os chips de código e as teclas de atalho desta casa). Com caixa de 0×0, **qualquer** texto "vaza" | para o inline, a caixa certa é o próprio `getBoundingClientRect()` descontadas as bordas |
| **40º** | **`opacity: 0` NÃO tira o elemento do hit-test** | `visibility:hidden` e `display:none` saem do `elementFromPoint`; **`opacity:0` não** — o elemento continua ganhando o hit-test e simplesmente não pinta. E a função `pinta()` olhava cor de fundo, imagem e borda, **não opacidade**. Caso medido: o `div#toast` do `banco-icones.html` (`position:fixed; bottom:16px; opacity:0`, visível só 1,8s depois de um clique que a medição nunca dá) produziu **2 FAIL de SH1-b** — mas **só a 390×844**; a 390×900, a altura canônica desta casa, a mesma caixa invisível cai 56px abaixo e o defeito não aparece | opacidade **acumulada pela cadeia de ancestrais** (`opacity` compõe: pai a 0 zera filho a 1); se a efetiva é 0, sai `[achado]` — *"atrapalha o ponteiro, não a leitura"*, exatamente o tratamento que a guarda já dava ao fundo transparente |
| — | **enfeite ≠ texto** | o `section.hero` do `seed-design-system` acusava 100px cortados em X, e o que transborda ali é o **grafismo** | SH1-c exige que um **nó de texto** passe do recorte; se nenhum passa, sai `[achado] enfeite transbordando` |

> **O 40º ensina uma coisa que os outros seis não ensinavam, e ela é sobre COMO se mede duas vezes.**
> Ele só apareceu porque a segunda medição do acervo a 390px foi rodada com **altura 844** em vez das
> **900** que todos os instrumentos desta casa usam. Rodar o mesmo comando duas vezes não é medir duas
> vezes — é obter o mesmo número duas vezes. **Medir duas vezes é variar uma dimensão e ver se a
> conclusão sobrevive.** A divergência bruta foi **8 FAIL a 390×844 contra 6 FAIL a 390×900**, e a
> explicação da diferença era defeito de instrumento, não diferença de artefato. Depois do conserto as
> duas alturas **concordam em 198 PASS · 6 FAIL**, e a 844 sai com **2 `[achado]` a mais** — o toast
> invisível, agora classificado pelo que ele é.

### 72.5 DECLARAÇÃO COM SAÍDA VERIFICADA — o mecanismo que distingue defeito de desenho

Duas situações do acervo são **legítimas** e, para uma guarda que só olha pixel, **idênticas a defeito**:

1. **o painel de chat do `tela-chat` SOBREPÕE a tabela de propósito.** O contrato CH3 do §55 diz, com
   estas palavras: *"o painel é PEÇA e SOBREPÕE: ele não empurra o miolo, a tela de trabalho não pode
   refluir toda vez que alguém pergunta alguma coisa"* — e há um botão que o recolhe.
2. **a régua do `tela-gantt` é RECORTADA** por `overflow:hidden` e acompanha, por sincronização, a área
   de plotagem, que o usuário rola.

A saída **não** foi afrouxar a régua nem escrever uma lista de exceções à mão — *lista escrita à mão
mede a lista, não o acervo.* Foi exigir que o artefato **declare**, e que a declaração **nomeie a saída**:

| atributo | contrato | o que a guarda VERIFICA |
|---|---|---|
| `data-sobrepoe="declarado:<id>"` | *"eu cubro isto de propósito, e `#<id>` recolhe"* | que `#<id>` **exista**, esteja **visível**, seja **focável** e não esteja **desabilitado** |
| `data-recorte="sincronizado:<id>"` | *"eu recorto, e `#<id>` rola para revelar"* | que `#<id>` **exista**, esteja **visível**, **role de fato** no eixo que esconde e seja **alcançável por teclado** (`tabindex ≥ 0`) |

> **Declaração sem saída verificável NÃO isenta: reprova, com esse motivo.** É o oposto de aceitar um
> comentário no código — comentário não tem como ser medido, e um `id` tem. É o mesmo princípio que a
> CT1 usa com `justify-content: space-*` e a SH1-a usa com margem negativa: *guarda não discute decisão
> declarada — mas a decisão tem de estar declarada em algo que a máquina leia.*

### 72.6 O que a execução tocou, e o placar

| # | arquivo | mudança | por quê |
|---|---|---|---|
| 1 | `tela-chat-template.html` | `<aside class="chat peca" data-sobrepoe="declarado:b-recolher">` | a sobreposição é o contrato CH3, e o `#b-recolher` é a saída |
| 2 | `tela-gantt-template.html` | `<div class="gt-plot" data-recorte="sincronizado:gtArea">` | a régua acompanha `#gtArea`, que é `overflow:auto` e `tabindex="0"` |
| 3 | `tela-chat-template.html` · `tela-gantt-template.html` | `.conteudo` passa de `overflow-x: hidden` a `overflow-x: auto` | a 390px o miolo tinha 366px de caixa e 416px de conteúdo, e o rótulo "Valor" ficava **22px fora, inalcançável**. **A SC 1.4.10 ISENTA tabela de dados da exigência de caber em uma coluna — logo a solução que a norma PRESCREVE é deixar rolar.** *Alternativa descartada: encolher a tabela abaixo da largura mínima das colunas — medido, isso produz corte DENTRO das células, a mesma perda com outro nome* |
| 4 | `seed-design-system.html` | `.ig-post` e `.ig-story` passam de `overflow: hidden` a `auto` | sob o espaçamento da SC 1.4.12, a cópia do story passava **57px** do recorte e a do post **26px**. *`auto` porque estes cartões ILUSTRAM peça de 1080×1080 e 1080×1920: a proporção é a informação, e crescer a caixa mentiria sobre o formato da plataforma. No espaçamento padrão nada muda — não há excesso, logo não há barra* |
| 5 | `seed-design-system.html` | `.feature-tile` recebe `overflow-wrap: anywhere` | a 390px a caixa tinha 106px e o conteúdo 121px, e "Subestações MT" saía **15px** fora. *Alternativa descartada: `overflow: auto`, que poria barra de rolagem dentro de um cartão de 106px — pior de ler que a palavra quebrada* |

| 6 | `tela-gantt-template.html` | `.gt-nav` ganha `flex-wrap: wrap` e os botões `flex: 0 0 auto` | **SH1-d**: os rótulos "Anterior" e "Próximo" vazavam **6px** da borda a 390px e **até 12px** a 1440px sob o espaçamento da norma |

**PLACAR a 1440px, com as QUATRO cláusulas: `204 PASS · 0 FAIL · 0 [n/a]`** — 51 arquivos × 2 temas × 2
espaçamentos · **96.000 pares de irmãos comparados** · **9.826 nós de texto testados por hit-test** ·
**9.456 caixas com fronteira visível** · **0 colisões · 0 textos cobertos · 0 textos cortados · 0 textos
vazando** · **40 `[achado]`**, todos nomeados.
**A 390px: `198 PASS · 6 FAIL`** — os 6 são a pendência **SH-P8**, e **SH1-d sai ZERO nas duas larguras**.
Este placar de 390px foi confirmado em **duas alturas de janela**, 900 e 844, e as duas dão o **mesmo
número** depois do conserto do 40º defeito: `198 · 6`. Os 6 são **2 de SH1-b** no `banco-navegacao`
(só no espaçamento `padrao`: o botão de rolagem da faixa de abas cobre a aba "Equipamentos") e **4 de
SH1-c** no `tela-tabela` (nos dois espaçamentos: `main.conteudo` tem caixa de 366px e conteúdo de 810px,
e o rótulo "Responsável" passa 6px do recorte no espaçamento padrão e 35px sob o da norma).

### 72.7 Pendências

| # | Pendência | Por que fica |
|---|---|---|
| **SH-P8** | **A 390px sobram 6 FAIL**, em 2 arquivos e 2 classes de causa, medidos e confirmados nas alturas 900 e 844: (a) **`banco-navegacao`, 2 FAIL de SH1-b só no espaçamento `padrao`** — o `button.seed-tabscroll` da faixa de abas **cobre** o rótulo "Equipamentos" do `button.seed-tab#tab-equip` no ponto (353,382); (b) **`tela-tabela`, 4 FAIL de SH1-c nos dois espaçamentos** — `main.conteudo` tem caixa de 366×786 e conteúdo de 810×786, e o nó de texto "Responsável" passa **6px** do recorte no padrão e **35px** sob o espaçamento da SC 1.4.12 | É trabalho de **layout móvel**, não de instrumento — e é o tipo de conserto que muda composição aprovada em gate. ⚠ **No `tela-tabela` o conserto óbvio já foi tentado e REVERTIDO:** pôr `overflow-x:auto` no `main.conteudo` viola a cláusula **RF-02** da `suite-container` (*"região focável e nomeada"*) e a **BT7-teclado** da `suite-reflow` (*"região que rola e NÃO se alcança sem mouse"*). O conserto certo é o mesmo que o `tela-chat` recebeu — envelope `role="region" tabindex="0"` com nome em volta da tabela — e ali exige composição, porque a tabela do `tela-tabela` não é um bloco isolado |
| **SH-P4** | sobreposição entre elementos que **não são irmãos** só é vista pela SH1-b, e só onde há **texto** no ponto testado | geometria entre não-irmãos exige comparar árvore inteira contra árvore inteira |
| **SH-P5** | a SH1-b testa **um ponto** por nó de texto — o centro | texto coberto pela metade, com o centro livre, passa |
| **SH-P6** | a largura de **320px** da SC 1.4.10 não entra ainda | é a largura em que a norma exige que nada se perca |
| **SH-P7** | a guarda verifica que a saída declarada **existe e é operável**, não que ela **revele aquele conteúdo** | provar isso exige clicar na saída e re-medir — é o próximo degrau de rigor |




---

---

---

---

---

*Estrutura da Fase 3 (7 blocos, reescopada em 2026-07-31 — ver `seed-ds-roadmap.md`): 1 — Botões ✅ v0.5 (+full-width v0.7) · 2 — Formulários ✅ 13/13 (v0.14) · 3 — Feedback e status ✅ 11/11 (v0.24) · 4 — Superfícies ✅ 7/7 (v0.32: §26–§32) · 5 — Navegação (+ ⌘K) · 6 — Dados (+ pendências: densidade ERP→mobile; barra composta EUI; selecionar-tudo indeterminate §10) · 7 — Iconografia ✅ 1/1 (§45 `estável` v0.47 — set canônico de 28 glifos). Pattern F6: §33 Central de Notificações ✅ `estável` (v0.34). Bloco 5 — Navegação ✅ FECHADO 6/6 (v0.41). Bloco 6 — Dados ✅ FECHADO 5/5 (v0.45): §40 tabela · §41 seleção · §42 toolbar · §43 lista de dados · §44 guia de integração. Componente futuro fora dos blocos: editor rich-text (F6). **Fase 6 (patterns de produto e marketing), aberta em 2026-08-11: bloco 6A — §46 Shell de aplicação ✅ `estável` (FECHADO) → bloco 6B — §47 Página institucional ✅ `estável` (FECHADO 2026-08-12, MK1–MK22 em dois lotes, preview e gate únicos) → bloco 6C — §48 Painel: grade, lentes, frescor, edição direta e mapa ✅ **`estável` (FECHADO 2026-08-13 — FASE 6 a 3/3)**, com **PN1–PN54**; oito lentes, camada visual PN21a–h, edição direta PN22–PN41 sob o SC 2.5.7, contratos do gate PN46–PN50, mapa em dois modos PN51–PN54; **PN42–PN45 reservados ao 6D**; dez supersedes formais em §48.8 e sete pendências em §48.9; preview **v0.8** com **cinco camadas verdes: 139 · 37 · 142 · 6 · 78** + gate visual do Rafael; **INVERSÃO DO HERO em 2026-08-13: a âncora passa de `azul-800` a `turquesa-400` #11B0A0, a ação volta à família da marca e o S4 é RETIRADO — o conflito com o §3.5 de `marca-seed.md` deixa de existir. Segue proposta apenas a tinta da família (S6): pendência PN-P1**).** O preview e o gate visual do 6C são únicos, no fim. Patterns F6 já `estável`: §33 Central de Notificações. **Fase 7 (cobertura, aberta em 2026-08-15 a partir do `mapa-cobertura-ds.md`): F7.1 fundação ✅ (`seed-composicao.md` v2.0: sete tipos de bloco, CP25–CP31, 23 gabaritos, renomeação bancada×tela) → F7.2 TELAS DE DADO — §50 agrupamento de linhas · §51 personalização de colunas · §52 árvore · §53 filtro composto, mais as extensões DT9–DT12, SL6–SL8, TD6 e LD6, todas em `rascunho` até o gate visual; gabaritos `tela-tabela.html` (A3) e `tela-lista.html` (A2). F7.3 REGISTRO — **§49 Detalhe de registro** (PD1–PD18, gabarito A8 `tela-detalhe.html`, fecha a PN-P3) e **§55 Chat** (CH1–CH14, arquétipo A23) — primeira spec do projeto escrita a partir de LEITURA VISUAL do produto de referência (§13 do `estudo-clickup-completo.md`, dispositivos C41–C49) em vez de extração de valores. As sete fases e o inventário de cobertura vivem no `mapa-cobertura-ds.md`. F7.5 COBERTURA DE CONTROLES: §59 valor de domínio · §60 escolha segmentada e múltipla (`estável`) · §61 data, intervalo e hora · §62 seletor de pessoa (bancada `banco-pessoa.html`, 44 · 0 · 1) · §63 prioridade e escalas ordinais · **§64 bancada e tema — BT1–BT6, transversais**.**

---

## 73. HV-P5 FECHADA — a tinta de marca não sobrevive a NENHUMA superfície de interação, e isso é um número (2026-08-20, nona parte) · nasce `--seed-text-brand-strong`

> **Escrito em 2026-08-20, nona parte. Autossuficiente: presume um leitor que nunca viu a conversa.**
> Esta seção fecha a **HV-P5**, que era o item 1 da fila aprovada e a única pendência do projeto
> classificada como **norma violada hoje**. Registro completo em `validacao/MANIFESTO.md` **§100**.

### 73.1 O que a pendência dizia, e onde ela errava

O §71.8 e a abertura diziam: *"90 controles violam o piso de contraste no sobrevoo ou no pressionado,
concentrados em 7 classes de botão"*. **Reproduzido, o 90 bateu e a distribuição estava errada.** Não eram
7 consertos espalhados: eram **5 arquivos**, com **64 dos 90 num só** — o `seed-design-system.html`, o
showcase v1.0 (*legado vivo*), que tem **paleta local** e não consome os gêmeos.

> **PENDÊNCIA ESCRITA É HIPÓTESE — e isso vale também para o CENSO POR CLASSE.** A lista de 7 classes era
> verdadeira como censo e enganosa como plano: ela agrupava pelo que a régua **imprime** (o seletor), não
> pelo que **causa** o defeito (a paleta do arquivo). *Censo por nome de classe é censo do rótulo.*

### 73.2 O número que tira a decisão do campo da preferência

`--seed-text-brand` e `--seed-text-link` valem `#098475` no claro. Eles medem **4,60** sobre a página
branca — **0,10 acima do piso 4,5** — e caem abaixo do piso assim que a superfície deixa de ser branco
puro:

| superfície | valor | mede com `#098475` |
|---|---|---|
| `surface-page` · `surface-raised` | `#FFFFFF` | **4,60** ✅ |
| `surface-subtle` · `action-neutral-hover` | `#F2F6F9` | **4,23** ❌ |
| `action-neutral-active` · `surface-sunken` | `#E3EBF0` | **3,81** ❌ |
| `row-selected-bg` | `#E8FBF7` | **4,28** ❌ |

> **O NÚMERO QUE FECHA:** para `#098475` alcançar 4,5, o fundo precisa de **luminância ≥ 0,978** — o cinza
> neutro mais escuro que serve é **`#FDFDFD`**, dois passos do branco puro. **Não existe superfície de
> interação possível para aquela tinta.** Logo escurecer a **tinta** no estado não é escolha estética: é a
> única saída que não desliga o estado.
>
> ⚠ **E há uma consequência que reordena a HV-P4:** os dois tokens que nasceram na v1.18
> (`action-neutral-hover` e `action-neutral-active`) são **exatamente** as superfícies que derrubam a
> tinta de marca. **Aplicar a gramática HV1-d ao acervo sem o token novo teria CRIADO casos de HV1-c em
> massa.** *A HV-P5 não era só anterior à HV-P4 por ser norma: era pré-requisito técnico dela — e isso
> não estava escrito em lugar nenhum.*

### 73.3 O RITO de três rodadas

| rodada | fonte | o que ela estabelece |
|---|---|---|
| **R1 canon** | **Carbon (IBM)** | tem **exatamente este par**: `$link-primary` (Blue 60 `#0f62fe`) e **`$link-primary-hover`** (Blue 70 `#0043ce`), *"Hover color for $link-primary"*; e **`$link-secondary`** (Blue 70), *"Secondary link color for **lower contrast backgrounds**"* |
| **R1 canon** | Carbon, issue **#7647** | nomeia o **mesmo caso fundador**, verbatim: *"The current link color for selected rows and rows with hover doesn't have enough visual contrast"* → resposta: token **mais escuro**, `#0043CE` claro / `#A6C8FF` escuro |
| **R2 mercado** | **GOV.UK Elements** #546 | defeito idêntico — hover `#2b8cc4` sobre branco = **3,72** — resolvido **escurecendo a paleta** em 2019 |
| **R2 mercado** | **eBay Playbook** | define o véu de estado (4% por estado) e **não diz nada sobre a legibilidade do conteúdo sobre o véu** — o outro lado da conclusão do §71.2 |
| **R3 normas** | **WebAIM**, verbatim | *"the 3:1 contrast rule does not (or should not) apply to the hover, focus, or active states... **the 4.5:1 (or 7:1) contrast requirement to the background does apply to all link states**"* |

### 73.4 O contrato HV1-e e o token que nasceu

| # | Cláusula | Régua | Natureza |
|---|---|---|---|
| **HV1-e** | **A tinta cujo piso foi verificado num ÚNICO fundo não serve a estado.** Toda tinta consumida por controle tem de medir ≥4,5 (texto) contra **todas** as superfícies de interação que o design system declara — `surface-subtle`, `action-neutral-hover`, `action-neutral-active`, `surface-sunken`, `row-selected-bg` —, não só contra a página | tabela tinta × superfície, medida no gêmeo | **norma**, derivada da SC 1.4.3 |

| token (gêmeos **v1.19**) | claro | escuro | mede no claro |
|---|---|---|---|
| `--seed-text-brand-strong` | `#006C62` | `#66D1C2` | **6,32** página · **5,82** `surface-subtle` · **5,24** `action-neutral-active` · **5,89** `row-selected-bg` |

**NENHUM HEX NOVO:** no claro é o valor de `surface-brand-deep` e de `action-primary-hover`; no escuro é o
mesmo de `text-brand` (`#66D1C2`, que já media **8,31 a 10,16** sobre todas as superfícies escuras).
**No tema escuro nada muda de aparência em nenhum pixel.**

> **ALTERNATIVA DESCARTADA, com número:** reusar `action-primary-hover` como tinta **PASSA** (5,24 a 12,85
> nos dois temas) e foi recusada por **acoplamento de propósito** — é token de **fundo** de botão, e uma
> mudança futura no sobrevoo do botão moveria a tinta do link em silêncio. *Mesmo critério com que a v1.18
> recusou `action-ghost-hover` para o sobrevoo neutro.*

### 73.5 A gramática de consumo — onde este token entra, e onde NÃO entra

| situação | tinta | por quê |
|---|---|---|
| link/rótulo de marca **em repouso**, sobre a página | `text-link` / `text-brand` | 4,60, passa — **nada muda** |
| o mesmo link quando a **linha muda de fundo** (`tr:hover`, `tr.selecionada`) | **`text-brand-strong`** | 4,23 e 4,28 → 5,82 e 5,89 |
| botão **fantasma** de marca no seu próprio sobrevoo | **`text-brand-strong`** | 4,23 → 5,82 |
| tinta de marca sobre **superfície elevada branca** | `text-brand` | `surface-raised` é `#FFFFFF` no claro: 4,60, passa |
| tema **escuro**, qualquer superfície | `text-brand` **ou** `text-brand-strong` — mesmo valor | 8,31 a 10,16, já passava |

**O QUE ESTE TOKEN NÃO RESOLVE, e é fronteira declarada:** ele é **tinta**. Onde o defeito é a
**superfície** carregar texto — a marca chapada `#11B0A0` com texto branco em cima, 2,71 — a saída é a que
o **§5-d** já fixou no gate de 2026-08-18: **descer a superfície** para `surface-brand-strong` `#098475`
(4,60), **não** escurecer a tinta. *Ali a opção de tinta escura (`#00352F`, 4,99) foi a opção D e ficou
VETADA pelo Rafael.*

### 73.6 O achado que a HV1 NÃO pode ver — o estado de SELEÇÃO

Ao consertar `.linha-abrir`, medi o par que a guarda **não produz**: `text-link` sobre `row-selected-bg`
(`#E8FBF7`) = **4,28**, abaixo do piso. **A HV1 produz `:hover` e `:active`; ela nunca produz SELEÇÃO.**
É a outra metade do caso fundador do Carbon (*"selected rows **and** rows with hover"*), e entrou no mesmo
conserto.

> **Fica como pendência de dimensão inteira (SE-P1):** **nenhum instrumento desta casa produz o estado de
> SELEÇÃO para medir contraste.** A CT1 cobre repouso/hover/ativo/foco; a HV1 cobre hover/ativo. *Seleção
> é estado de DADO, não de ponteiro — e por isso escapou dos dois eixos.*

### 73.7 Os consertos, com número, e o que ficou intocado

| arquivo | conserto | antes → depois |
|---|---|---|
| `tela-detalhe.html` (gerado) | `tbody tr:hover .linha-abrir` e `tr.selecionada .linha-abrir` → `text-brand-strong`, **no molde e no gerador** | **4,23 → 5,82** · selecionada **4,28 → 5,89** · 20 casos → **0** |
| `tela-gantt.html` · `tela-quadro.html` (gerados) | mesmo bloco, **preventivo** | medido: as duas **declaram** a regra `.linha-abrir` e têm **ZERO instâncias** dela — veredito *sem instância* da rodada zero |
| `banco-navegacao.html` | sobrevoo **próprio** de `[aria-current="page"]`, com fundo e tinta declarados JUNTOS | **1,02 → 9,35** (escuro) |
| `banco-tokens.html` | `.btn-ghost:hover` ganha tinta | **4,23 → 5,82** |
| `seed-design-system.html` | migração de **três papéis** de turquesa + `--cinza-forte` + quatro pares | **862 → 46** folhas · HV1-c **64 → 0** |
| `tela-site.html` | **NENHUM — o artefato estava correto** | falso positivo do 41º defeito de instrumento |

**Todos os consertos tocam SÓ o estado de interação.** Prova: as capturas de **repouso** antes e depois
saem **byte-idênticas** (dois grupos de md5 repetido na folha `render-audit/gate-v140/fechamento.html`, e a
repetição é a prova, não erro).

### 73.8 Pendências

| # | Pendência | Por que fica |
|---|---|---|
| **HV-P6** | a HV1-c **não** aplica o degrau de **texto grande** da SC 1.4.3 (≥24px, ou ≥18,66px com peso ≥700 → piso 3,00) | medido: **0** vereditos mudariam hoje — o maior alvo que reprova mede **14px**. Ficou de fora de propósito, para a prova de inércia do 41º defeito ser legível |
| **SE-P1** | **nenhum instrumento produz o estado de SELEÇÃO** para medir contraste | é um eixo novo, não uma correção: seleção é estado de dado |
| **CC-P9** | as **46** folhas restantes do `seed-design-system.html` | 24 da família amarelo/dourado (**única pergunta de gate**), 8 dos mostruários de cor do próprio documento, 6 dos números decorativos, 6 do defeito de régua da CC-P10, 2 defeitos reais dentro de mockup em SVG |
| **CC-P10** | o `contraste-composicao.mjs` resolve o fundo de elemento com **gradiente** subindo até o ancestral branco, em vez de **recusar** a medição como a HV1 faz | medido no pixel: **6** folhas, **4 falsos positivos** e **2 defeitos reais** |
| **ES-P1** | **nenhuma guarda mede COLISÃO DE ESPECIFICIDADE** — todas medem o resultado | ela já produziu **três** defeitos em três partes: o avatar da barra (§71.7), a página corrente da paginação e o título da coluna de marca |
| **TK-P1** | o bloco de tokens injetado declara de qual versão do gêmeo nasceu, e ninguém mede a defasagem | medido: o acervo declara **seis** versões — v1.5 (1) · v1.7 (2) · v1.8 (1) · v1.9 (15) · v1.12 (1) · v1.17 (23) · v1.19 (3) |

---

## 74. BT1 FECHA POR INSTRUMENTO — a barra de provas travada no topo em TODO o acervo (2026-08-20, DÉCIMA parte) · nasce `validacao/guarda-barra-fixa.mjs`, **alvo de PASTA**

> **Para quem chega sem contexto.** **BT1** é o contrato, escrito em **§64.1** desde 2026-08-16, que
> diz: *a barra de controles de todo artefato produzido — bancada OU tela — é fixa no topo*
> (`position:sticky; top:0`, com faixa de fundo própria e sombra de separação). **Barra de provas** é
> essa barra: os botões que existem só para AVALIAR o artefato (alternar tema claro/escuro, prova em
> escala de cinza, seletor de largura de página), não para usá-lo. **Bancada** (`banco-*.html`) é o
> catálogo dos estados de um componente; **tela** (`tela-*.html`) é um caso de uso montado.
> **Alvo de pasta** é a propriedade de um instrumento que descobre seus próprios alvos varrendo o
> diretório (`readdirSync(dir).filter(f => f.endsWith('.html'))`) em vez de percorrer uma lista
> escrita à mão — com isso a cobertura cresce sozinha quando nasce um arquivo novo.
>
> Esta seção **não** cria contrato novo. Ela registra que o contrato de 2026-08-16 **nunca havia sido
> medido no acervo inteiro**, e o instrumento que passou a medir.

### 74.1 Por que a seção existe: a BT-P2 foi declarada fechada sobre uma LISTA

O contrato BT1 nasceu de um gate visual em 2026-08-16, verbatim do Rafael:

> *"em uma avaliação de página .html deixe sempre o topo com escala de cinza, claro ou escuro, tamanho
> de página, sempre travado no topo pra quando rolar a página ele ficar fixo no topo"*

e foi estendido a telas no dia seguinte, também verbatim:

> *"todo material que você produzir tem que ter a barra fixa, todos você já tem essa opção, só não é
> fixo"*

A pendência que aplicava isso — a **BT-P2** — foi **declarada FECHADA em 2026-08-17** com a frase
*"aplicada e MEDIDA em nove telas mais `banco-dominio`"*, e com uma única exclusão declarada
(`tela-site.html`). **Dez artefatos. O acervo tem trinta e quatro** (cinquenta e um HTMLs contando a
família de e-mail e os mostruários).

Em 2026-08-20 o Rafael abriu o `banco-navegacao.html`, viu os três botões de prova soltos no fluxo do
documento — rolando para fora da janela junto com o conteúdo — e escreveu:

> *"lembra que eu pedi para todos os arquivos .html que tiverem seletor de claro/escuro ou outro tipo
> de edicao, que era pra fixar isso na barra superior. vc fez em varios arquivos, mas o
> banco-navegacao.html nao está dessa forma. **acredito ter outros**."*

> ### A LIÇÃO DE MÉTODO — e esta casa já pagou por ela mais de uma vez
> **PENDÊNCIA DE CONTRATO TRANSVERSAL NÃO FECHA POR LISTA — FECHA POR INSTRUMENTO DE ALVO DE PASTA.**
> O contrato diz *"todo artefato produzido"*; a verificação cobriu dez de trinta e quatro. E
> *"acredito ter outros"* é a forma polida de dizer que **ninguém contou**. A mesma família de defeito
> apareceu no **42º defeito** (§73): a varredura de contraste escolhia alvo por **prefixo de nome de
> arquivo** e deixava 17 dos 51 HTMLs de fora — escondendo 862 folhas de texto reprovadas.
>
> *Alternativa descartada:* consertar só o `banco-navegacao.html` (o arquivo que ele apontou) e
> responder "feito". Descartada porque o próprio pedido dele contém a razão — *"acredito ter outros"*
> —, e porque um conserto sem instrumento não impede o defeito de voltar no próximo arquivo novo.

### 74.2 As quatro cláusulas do instrumento

O `validacao/guarda-barra-fixa.mjs` mede BT1 em quatro cláusulas. **Três das quatro são
COMPORTAMENTAIS** — a página é rolada de verdade num Chromium de verdade e o retângulo é medido
depois — porque `position:sticky` declarado na folha de estilo **não é prova de que a barra gruda**.

| # | Cláusula | Como é medida | Por que ela existe |
|---|---|---|---|
| **BT1-a** | **A barra é travada** | `getComputedStyle(barra).position` ∈ {`sticky`, `fixed`} | **Peneira apenas.** Serve para nomear a causa no relatório; o veredito de verdade é o da BT1-b |
| **BT1-b** | **Ela continua no topo depois de ROLAR** | rola a página até o fim; exige topo do retângulo **dentro da janela** e a **menos de 4px de zero** | Pega o defeito real mais comum: `sticky` declarado e **morto** porque um ANCESTRAL tem `overflow:hidden`, `overflow:auto`, `transform`, `filter` ou `contain` — `sticky` só funciona no contexto de rolagem do ancestral, e **isso não se vê na folha de estilo**. A guarda diagnostica o ancestral culpado pelo nome |
| **BT1-c** | **Ela não é coberta** | depois de rolar, `elementFromPoint` no centro da barra devolve a barra ou um descendente dela; **e** se o clique no controle é INTERCEPTADO, reprova aqui | Nasceu de defeito medido em 2026-08-17: na `tela-chat` a barra e o painel de chat empatavam em `z-index:60` e, a 320px, o chat cobria a barra inteira. **Barra coberta é instrumento inoperante, não alvo ausente** — por isso é FAIL e não `[n/a]` |
| **BT1-d** | **Ela não cobre conteúdo** | com a página **NO TOPO**, o irmão seguinte tem de começar depois do fim da barra | O padrão canônico exige `sticky` e **não** `fixed`, para a barra continuar ocupando espaço no fluxo. Medido: com `fixed`, o primeiro elemento depois dela fica DEBAIXO dela |

### 74.3 Como os controles de prova são achados — peneira DECLARADA + prova COMPORTAMENTAL

⚠ **A peneira inicial é por TEXTO ACESSÍVEL do controle, e ela é uma lista escrita à mão** — que é
exatamente o defeito que esta guarda nasceu para consertar. Usá-la aqui é **dívida declarada**: o
vocabulário sai **IMPRESSO no placar** junto com a contagem do que descartou.

Vocabulário medido em vigor: `tema · escuro · claro · dark · light · cinza · grayscale · escala de
cinza · largura · faixa · px · prova · provas · alternar · grade · baseline · forçar · forcar`.
Medido a 1440×900: **93 candidatos** pela peneira, **608 controles descartados**.

O que impede a dívida de virar mentira é a segunda etapa: **nenhum candidato vira controle de prova
sem PROVA COMPORTAMENTAL.** A guarda **clica** o candidato e exige que o clique mude a assinatura do
`<html>` — um atributo (`data-theme`, `class`, `style`), o `filter` computado, ou a largura do
contêiner de conteúdo. Dos 93 candidatos, **33 se provaram por comportamento**.

> **Candidato que passa a peneira e NÃO muda nada sai `[n/a]` NOMEADO, nunca FAIL** — pode ser um
> botão de conteúdo cujo rótulo apenas *parece* de instrumento. E o clique é **desfeito** (segundo
> clique quando há `aria-pressed`, recarga da página quando não há), para o passe seguinte medir a
> mesma página.

### 74.4 O censo: **22 PASS · 11 FAIL · 18 [n/a]** → **33 PASS · 0 FAIL · 18 [n/a]**

Placar de abertura a 1440×900, com a decomposição por cláusula: **9 BT1-a · 10 BT1-b · 0 BT1-c ·
2 BT1-d**. Os onze reprovados, com o número medido:

| artefato | contêiner da barra | `position` antes | topo medido depois de rolar |
|---|---|---|---|
| `banco-cn.html` | `main.controls` | `static` | **+80px** (a barra desceu com o conteúdo) |
| `banco-composicao.html` | `div.ctr` | `static` | **−1689px** (saiu da janela) |
| `banco-dados.html` | `div.controls` | `static` | **−21,1px** |
| `banco-dataviz-di.html` | `div.bar` | `static` | **−4639,7px** |
| `banco-dataviz-tokens.html` | `div.toolbar` | `static` | **−640,6px** |
| `banco-feedback.html` | `div.toolbar` | **`fixed`** | **+12px** — travada, mas **BT1-b** (12px > 4px de tolerância) **e BT1-d** (cobria o conteúdo) |
| `banco-formfield.html` | `header.top` | `static` | **−7503px** |
| `banco-icones.html` | `div.bar` | `static` | **−221,2px** |
| `banco-navegacao.html` | `div.controls` | `static` | **−1414,1px** — o arquivo que o Rafael apontou a olho nu |
| `banco-superficies.html` | `div.toolbar` | **`fixed`** | **+12px** — mesmo par BT1-b + BT1-d |
| `banco-tokens.html` | `header` | `static` | **−1811px** |

**Depois do conserto: 33 PASS · 0 FAIL · 18 [n/a]**, medido em **DUAS condições** — 1440×900 **e**
320×900. *Medir duas vezes significa VARIAR UMA CONDIÇÃO, não repetir o mesmo comando:* a 320px o
número de candidatos cai para 92 e o de descartados para 585, e o veredito não muda.

Os **18 `[n/a]`** são nomeados, não mudos, e o censo agrega o motivo: **18× artefato SEM controle de
prova** — a família de e-mail (`ea-*`, `en-*`, `et-*` e `seed-email-*`), que é HTML de e-mail e **não
tem nem pode ter** instrumento de avaliação embutido; mais `banco-componentes`, `reconstrucao-dm`,
`seed-dataviz-di-prova-impressa` e `seed-design-system`. Há ainda **2×** (1× a 320px) o motivo
*página NÃO ROLA nesta janela* — `tela-shell` e `tela-referencia` cabem em 900px de altura, e onde não
há rolagem a BT1-b **não pode ser medida**; o veredito de PASS ali vem das outras três cláusulas.

### 74.5 O conserto canônico — e a PRIMEIRA tentativa que o print do Rafael revogou

O bloco aplicado aos onze, idêntico em todos:

```css
#seed-barra-provas { position: sticky; top: 0; z-index: 9000;
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin: 0 0 16px; padding: 10px 12px;
  background: var(--seed-surface-subtle); border-bottom: 2px solid var(--seed-border-default);
  box-shadow: 0 1px 0 rgba(0,0,0,.04), 0 6px 16px -12px rgba(0,0,0,.5); }
```

Três decisões de forma, com o porquê:

1. **O contêiner recebeu `id="seed-barra-provas"`, não uma classe nova.** *Alternativa descartada:*
   trocar a classe existente (`.controls`, `.toolbar`, `.bar`, `.ctr`) por uma única classe canônica.
   Descartada porque **quebraria toda suíte que consulta o seletor antigo** — e o `id` é específico o
   bastante para a regra não vazar para outros elementos com a mesma classe.
2. **`sticky`, não `fixed`.** É a BT1-d: `fixed` sai do fluxo e cobre o primeiro elemento seguinte.
   Os dois arquivos que já usavam `fixed` (`banco-feedback`, `banco-superficies`) reprovavam por isso.
3. **`flex-wrap: wrap`.** A 320px os três botões não cabem em uma linha; sem quebra, o último vaza da
   caixa — que é exatamente a cláusula **SH1-d** de §72.

> ⚠ **A primeira tentativa foi REVOGADA pelo print.** Travar a barra onde ela estava deixou-a
> **ABAIXO do título** do documento: ao rolar, o que ficava grudado no topo era um retângulo de botões
> com o `<h1>` já fora da tela — visualmente pior que antes. **Conserto real: o nó inteiro foi MOVIDO
> para ser o primeiro filho de `<body>`.** É a regra **4.1.10** do método em ação pela segunda sessão
> consecutiva: *medição de propriedade pode passar enquanto o olho reprova; o juiz da aparência é o
> PIXEL, e no caso da composição é o gate visual.*

**Seis dos onze também ganharam declaração de token**: eles consumiam `--seed-surface-subtle` e
`--seed-border-default` — que a folha nova usa — **sem declará-los em nenhum bloco de tema**.

### 74.6 Duas suítes reprovaram meu conserto, e as duas estavam CERTAS

Depois do conserto, `suite-dados` deu **38 · 1** e `suite-navegacao` deu **92 · 1** — as duas nas
provas **NV-01/NV-02**, que exigem que todo token consumido exista declarado. Eram **tokens
fantasma**: os seis arquivos citados acima. Conserto **na causa** (declarar os dois tokens em cada
bloco de tema), não no sintoma: **39 · 0** e **93 · 0**.

> É **exatamente** o mesmo defeito que a guarda **BP1** achou em seis bancadas em 2026-08-17. Fica
> registrado porque **defeito que repete em família é defeito de método, não de arquivo**: a casa
> ainda não tem guarda que impeça um bloco de estilo novo de consumir token não declarado **no ato da
> escrita** — só suíte que o pega depois.

### 74.7 A pasta de prova `render-audit/prova-bt1/` — onde REPROVAR é o resultado desejado

Sete fixtures, uma por cláusula e uma por caminho de `[n/a]`. **Referência: `1 PASS · 4 FAIL ·
2 [n/a]`.**

| fixture | o que ela é | veredito esperado |
|---|---|---|
| `bt-ok.html` | `sticky` correto | **PASS** |
| `bt-fluxo.html` | barra solta no fluxo | **FAIL** BT1-a + BT1-b |
| `bt-sticky-morto.html` | `sticky` matado por ancestral `overflow:hidden` | **FAIL** BT1-b (e a guarda **nomeia o ancestral**) |
| `bt-fixed-cobre.html` | `fixed` | **FAIL** BT1-d |
| `bt-coberto.html` | `sticky` correto, coberto por empate de `z-index:60` | **FAIL** BT1-c |
| `bt-sem-controle.html` | nenhum controle de prova | **`[n/a]`** *artefato sem controle de prova* |
| `bt-rotulo-falso.html` | rótulos que **parecem** instrumento e não mudam nada | **`[n/a]`** *candidato não muda o `<html>`* |

**A pasta de prova pegou dois defeitos do próprio instrumento, antes de o acervo ser medido:**

1. **Falso positivo na BT1-d.** A versão 1 media "o irmão seguinte está debaixo da barra **depois de
   rolar**" — que é **exatamente o que um `sticky` legítimo faz**. O `bt-ok.html` reprovou. Conserto:
   **medir com a página NO TOPO**.
2. **`[n/a]` com o motivo ERRADO.** O `bt-coberto.html` saía como *"rótulo parece de instrumento e não
   é"*; a verdade era *"o controle está coberto"*. Conserto: passou a **reprovar BT1-c nomeando o
   elemento que intercepta**. Fica como regra: **`[n/a]` com motivo errado é PIOR que `[n/a]` mudo**,
   porque o motivo errado encerra a investigação.

### 74.8 Regra nova de FORMA DE ENTREGA: demonstração viva é EXTRAÍDA, nunca redigitada

Pedido do Rafael, verbatim:

> *"ao invés de me pedir para abrir o arquivo, banco-navegacao.html para verificar, abaixo do print,
> traxa a pagina viva para eu poder passar o mouse e testar, para nao ter que ir até o arquivo pra
> fazer o teste. **basta repetir o codigo daquela parte correto?** passe a fazer assim para todos os
> exemplos que precisa de eu passar o mouse ou clicar pra testar."*

*"Basta repetir o código daquela parte"* é a pergunta certa, e a resposta é **quase**. Trecho
**redigitado** na folha de conferência torna a folha uma **SEGUNDA FONTE**: ela pode mostrar um botão
que se comporta de um jeito enquanto o artefato se comporta de outro, e ninguém notaria — a classe de
defeito que este projeto chama de **dado sintético plausível**, o mais perigoso porque não chama
atenção e por isso não é conferido.

> **REGRA: DEMONSTRAÇÃO VIVA NUNCA É REDIGITADA — É EXTRAÍDA.** Nasce
> `validacao/extrai-vivo.mjs`, que abre o artefato real num Chromium, colhe o bloco de tokens, as
> regras que **alcançam** o trecho e o markup, e emite um HTML autossuficiente para ir dentro de um
> `<iframe srcdoc sandbox="allow-scripts">` da folha. *Se o artefato mudar e a folha for regerada, a
> demonstração muda com ele. Se as duas divergirem, é porque alguém editou a folha em vez de
> regerá-la.*

⚠ **FRONTEIRA DECLARADA: o iframe NÃO é o artefato.** Ele não tem o layout inteiro em volta, então
**não serve** para julgar composição, aperto de largura nem colisão — para isso o veredito é do
artefato e das guardas. Ele serve para o que foi pedido: **passar o mouse e clicar**.

**44º defeito de instrumento: a versão 1 do `extrai-vivo.mjs` lia CSS com REGEX.** Medido na hora de
provar a folha: **em TRÊS dos CINCO quadros o bloco de tokens não entrou** — `--seed-text-primary`
resolvia para vazio dentro do iframe e o fundo saía transparente. Causa: **chave dentro de comentário
CSS desalinha um parser de texto**, e esta casa acabou de escrever, no mesmo dia, comentários que
contêm `{ position: fixed }`. **A régua certa já existia na casa**: a `guarda-literal-de-cor.mjs`
percorre o **CSSOM** (`document.styleSheets` → `cssRules`) desde que nasceu — no CSSOM o comentário
não existe e o seletor já vem normalizado. Reescrito por CSSOM: **5 de 5 resolvem.** *Terceira vez na
mesma sessão em que a régua certa já estava escrita em outro arquivo.*

⚠ Duas armadilhas registradas dentro da ferramenta: **`CSSStyleRule` também tem `cssRules` no Chromium
moderno** (31º defeito) — por isso a recursão testa `style` **antes** de `cssRules`; e **a folha de
estilo da demonstração tem de vir DEPOIS do CSS extraído**, senão a regra `body` do artefato a
sobrescreve (medido: o fundo saía transparente).

**Fidelidade dos quadros, medida pela guarda HV1 oficial** (não por sonda própria — minha primeira
sonda de sobrevoo dentro do iframe relatou "0 de 19", e era **falsa**: `boundingBox()` dentro de um
iframe de origem opaca não dá coordenada usável): `vivo-paginacao-claro` **6/0/0**,
`vivo-paginacao-escuro` **6/0/0**, `vivo-linha-abrir` **10/0/0**, `vivo-btn-ghost` **4/1/0** — e esse
único "1" **reproduz um defeito real remanescente** de HV1-a no artefato, o que é a prova de que o
quadro é fiel.

### 74.9 Pendências abertas por esta seção

| # | Pendência | Por que fica aberta, com número |
|---|---|---|
| **BT-P3** | **O contrato BT2 — seletor de LARGURA DE PÁGINA em toda bancada — não foi medido por instrumento.** A `guarda-barra-fixa.mjs` declara não julgar a COMPOSIÇÃO da barra | BT1 e BT2 nasceram no mesmo gate (§64.1) e só um dos dois ganhou régua. Dos **33** artefatos com barra provada por comportamento, quantos têm seletor de largura é hoje **desconhecido** — e "desconhecido" é o estado que a BT-P2 tinha quando foi declarada fechada |
| **IN-P2** | **Nenhum censo diz quais `.mjs` desta casa leem CSS por TEXTO em vez de CSSOM.** O 44º defeito acabou de custar 3 de 5 quadros | A `guarda-literal-de-cor.mjs` e o `extrai-vivo.mjs` v2 usam CSSOM; os outros **não foram verificados**. Enquanto o censo não existe, o defeito pode estar dormindo em qualquer instrumento que leia folha de estilo |

---

## 75. TP1 — A TIPOGRAFIA E OS RECURSOS DO ARTEFATO REALMENTE CARREGAM · regra transversal (2026-08-20, décima parte, segunda rodada) · `validacao/guarda-tipografia-viva.mjs` · e as duas decisões de cor que estão ESPERANDO GATE

> **Para quem chega sem contexto.** Esta seção nasceu de uma pergunta do Rafael que era sobre outra
> coisa. Ele viu, num anexo, *"uma imagem que não carregou"* e perguntou se ela mudava a avaliação de
> contraste. **Não mudava** — e a resposta está em §75.1, com número. Mas a investigação achou uma
> **dimensão de cobertura inteira que não existia**: ninguém nesta casa jamais mediu se as **famílias
> tipográficas** e os **recursos externos** que um artefato declara **realmente chegam**.
>
> *Pergunta do decisor sobre um detalhe é o modo mais barato de descobrir uma dimensão de cobertura
> inteira que não existia.*
>
> **Vocabulário.** *Pilha de `font-family`* = a lista de alternativas de uma declaração
> (`'Montserrat', sans-serif` é UMA pilha com uma família nomeada e uma genérica). *Genérica* =
> `serif`, `sans-serif`, `cursive`, `monospace`, `system-ui` e afins — o navegador sempre resolve.
> *`@font-face` embutido* = a fonte viaja dentro do arquivo (ou vem de um `woff2` declarado), e por isso
> é **portátil**. *Fonte de sistema* = a família existe na máquina de quem olha, e por isso **não** é
> portátil.

### 75.1 A resposta à pergunta: a imagem quebrada NÃO muda o veredito

Medido variando **uma** condição — o mesmo cartão renderizado (A) com as imagens quebradas e (B) com as
imagens substituídas por um arquivo local do mesmo tamanho declarado:

| o que foi medido | A · imagem quebrada | B · imagem carregada | diferença |
|---|---|---|---|
| cor da tinta do texto amarelo | `rgb(250,214,29)` | `rgb(250,214,29)` | **zero** |
| tamanho / peso | 18px / 300 | 18px / 300 | **zero** |
| retângulo do texto **dentro do cartão** | 221,1px do topo · 248,9×55,8 | 221,1px do topo · 248,9×55,8 | **zero** |
| cor do fundo **no pixel** | `#098475` | `#098475` | **zero** |

O documento fica **1.099px mais alto** com as 45 imagens presentes, e a caixa do logotipo muda **0,8px**
de altura — mas nenhum dos dois move o texto dentro do cartão. **Logo os 3,22 do amarelo estão certos,
com ou sem a imagem.**

> ⚠ **O que a pergunta não continha, e é maior.** O `seed-design-system.html` depende de **47 recursos
> de rede**: 45 imagens em `lh3.googleusercontent.com` (Google Drive) e 1 folha do Google Fonts que traz
> **duas** famílias. Medido: **0 de 45** imagens e **0 de 2** famílias carregam, todas com
> `ERR_TUNNEL_CONNECTION_FAILED`. **Consequência: todo recorte daquele artefato que esta casa já mostrou
> ao decisor saiu com a tipografia de RESERVA.** O número de contraste nunca mudou por isso — cor,
> tamanho e peso são declarados em CSS e não vêm da fonte —, mas a **aparência** que ele julgou não era a
> real. *E aparência é o domínio dele, não do instrumento.*

### 75.2 ⚠⚠ A RÉGUA ÓBVIA MENTE — e é por isso que a guarda existe

`document.fonts.check("18px 'Indie Flower'")` devolveu **`true`** com a folha de fonte **falhando**. Ela
responde sobre a família **resolvida**, e a resolvida era a de reserva.

> **Guarda de fonte escrita com `check()` mede sempre verde.** Fica catalogado como armadilha de
> instrumento, ao lado do 31º defeito (`CSSStyleRule` também tem `cssRules`) e do 44º (parser de CSS por
> regex).

As duas réguas honestas — e a guarda usa **as duas**, porque medir duas vezes é **variar uma condição**:

| régua | o que ela prova | medido no `seed-design-system.html` |
|---|---|---|
| `[...document.fonts].length` | **portabilidade** — o `FontFaceSet` só ganha entrada com `@font-face` de verdade carregado | **0** antes do reparo · **7** depois |
| **largura em canvas** do mesmo texto com a família e com uma família inexistente | se a família entrou de algum jeito (embutida **ou** de sistema) | **348 = 348** — idênticas, logo não carregou |

*O cruzamento das duas é o que distingue `@font-face` de fonte de sistema, e essa distinção é o valor
principal da guarda.*

### 75.3 Os contratos

| # | Contrato | Veredito | Por quê |
|---|---|---|---|
| **TP1-a** | **toda pilha de `font-family` resolve alguma coisa declarada** — é FAIL só quando nenhuma família nomeada resolve **e** a pilha não termina numa genérica | **FAIL** | aí o navegador escolhe **sem nenhuma instrução** do artefato, e a tipografia deixou de ser uma decisão |
| **TP1-b** | **nenhum recurso externo falha** | **FAIL** | não é sobre fonte: é sobre o artefato ser **autossuficiente**. Um artefato canônico que perde a marca quando o Drive muda uma permissão não é confiável |
| **TP1-c** | **imagem declarada tem pixel** (`complete && naturalWidth>0`) | **FAIL** | o texto de `alt` que aparece no lugar dela é **tinta que nenhuma régua desta casa mede** — `<img>` não tem nó de texto próprio, logo a ausência não entra em nenhum placar de contraste |
| `[achado]` **PORTABILIDADE** | a pilha resolve, mas por **fonte de sistema**, sem `@font-face` | contado, **nunca FAIL** | não é violação de contrato — é **dependência da máquina de quem olha**, e tem de aparecer no placar |
| `[achado]` **PRIMEIRA ESCOLHA** | a primeira família da pilha não resolve, mas uma alternativa resolve | contado, **nunca FAIL** | a pilha está funcionando, mas o artefato **não** está recebendo a fonte que pediu primeiro |

### 75.4 O placar, e a leitura correta dele

**Acervo (51 arquivos, 1440×900): 30 PASS · 21 FAIL · 0 `[n/a]`** — `0 TP1-a · 21 TP1-b · 14 TP1-c`.
Achados contados: **85 de portabilidade · 56 de primeira escolha**.

**Nenhum artefato perdeu o controle da tipografia** (`0 TP1-a`). As 21 reprovações são de **dependência
externa**, e a atribuição por família muda o que elas significam:

| host que falha | quantos artefatos | quais | leitura |
|---|---|---|---|
| `assets.seed.eng.br` | **13** | a família de e-mail (`ea-*`, `en-*`, `et-*`, `seed-email-*`) | ⚠ **ISENÇÃO DECLARADA: e-mail não pode embutir ativo.** Imagem remota é a norma do meio, e o cliente de e-mail bloqueia até o leitor liberar. Aqui o FAIL é **esperado**, e o host é da própria SEED |
| `fonts.googleapis.com` | **7** | `banco-cn`, `banco-componentes`, `banco-dados`, `banco-feedback`, `banco-navegacao`, `banco-superficies`, `banco-tokens` | **este é o achado novo e real.** Sete bancadas carregam a fonte da rede. Numa máquina sem Montserrat instalada e sem rede, elas caem para a genérica |
| `lh3.googleusercontent.com` + `drive.google.com` + `fonts.googleapis.com` | **1** | `seed-design-system.html` | o caso fundador: **46 recursos** num só arquivo |

**Pasta de prova `render-audit/prova-tp1/` — referência `3 PASS · 3 FAIL · 0 [n/a]`**, com
`1 TP1-a · 2 TP1-b · 1 TP1-c` e `3 achados de portabilidade · 1 de primeira escolha`. Seis fixtures, uma
por cláusula e uma por caminho de achado. **Nela, FAIL é o resultado desejado.**

> ⚠⚠ **CONTAMINAÇÃO DO AMBIENTE, DECLARADA.** Para consertar a fidelidade dos recortes desta rodada, o
> contêiner recebeu `apt install fonts-montserrat`. **Montserrat passou a resolver como fonte de
> sistema**, e é por isso que os 85 achados de portabilidade existem: artefatos que declaram Montserrat
> **sem embuti-la** aparecem como "resolvem" **aqui** e não resolveriam numa máquina limpa. A guarda
> **imprime esse aviso em toda execução**. *Contaminação de ambiente que não é declarada é dado sintético
> plausível com outro nome.*

### 75.5 ⚠⚠⚠ ESTA GUARDA ESTEVE ERRADA CINCO VEZES, E O NÚMERO DE PASSES É O ACHADO

Fica registrado inteiro, porque a repetição é a lição:

| passe | o que ela media | quanto reprovou | por que estava errada |
|---|---|---|---|
| **v1** | exigia que **toda família nomeada** resolvesse | **48 de 51** | `font-family` é **pilha de alternativas por construção**. `SFMono-Regular` e `Menlo` são fontes de **macOS** e não existem em Linux — isso não é defeito, é a pilha funcionando |
| **v2** | passou a medir por **pilha**, mas ainda reprovava pilha sem família nomeada resolvida | **46 de 51** | pilha que **termina em genérica** tem fallback **declarado e intencional**: `'JetBrains Mono', ui-monospace, monospace` diz *"alguma monoespaçada serve"* |
| **v3** | corrigiu a genérica, mas lia as pilhas do **CSSOM declarado** | **22 de 51**, com **3 TP1-a falsos** | o CSSOM devolve `var(--seed-font-sans)` **sem resolver**, e a guarda tratava isso como "pilha que não resolve nada". **Três bancadas reprovavam por defeito meu** |
| **v4** | passou a ler o **`computed style`** de cada elemento | **6 de 6** na pasta de prova | o `computed` de elemento que nenhuma regra do autor alcança devolve a **fonte padrão da UA** (`"Times New Roman"`), que não existe em Linux — **e o padrão do navegador não é declaração do artefato** |
| **v5** | descobre o padrão da UA num **iframe limpo** e o exclui por igualdade exata | **3 de 6** na prova (a referência) e **21 de 51** no acervo | correta — e a **prova de inércia** confirmou: a matriz da pasta de prova ficou idêntica linha por linha |

> **A LIÇÃO, e ela vale para toda guarda nova desta casa: quando uma régua nova reprova EM BLOCO, a
> primeira hipótese não é "o acervo está ruim" — é "a régua está medindo outra coisa".** Cinco vezes
> seguidas o erro foi o mesmo em forma diferente: medir algo **adjacente** ao que o contrato nomeia.
> *E as cinco vezes foram pegas pela pasta de prova ou pela atribuição por família — nunca pelo placar
> verde.*

### 75.6 As duas decisões de COR que esta seção NÃO fecha — elas esperam gate

Registradas aqui porque a medição está feita e não deve ser refeita. **Nada disto foi aplicado a
nenhum artefato.**

#### (i) O amarelo no cartão de post social — o pedido dele não era entregue por nenhuma opção

Pedido verbatim: *"opção B é melhor mesmo, mas o texto amarelo do tamanho do C"* — isto é, **manter o
amarelo, em 18px**. Duas descobertas mudaram a pergunta:

**A onda decorativa passa por baixo da SEGUNDA linha do texto amarelo.** Ela é
`fill="#098475" opacity=".7"`. No cartão de hoje é **invisível**, porque o cartão também é `#098475` —
mesma cor sobre mesma cor. Assim que o cartão escurece, a onda passa a **clarear** aquele pedaço.
Medido no pixel, linha por linha:

| opção | fundo do cartão | linha 1 | linha 2 (sobre a onda) | piso | o texto amarelo |
|---|---|---|---|---|---|
| HOJE | `#098475` | 3,22 | 3,22 | 4,5 | reprova |
| (A) texto vira branco | `#098475` | 4,60 | 4,60 | 4,5 | passa — perde o amarelo |
| (B) amarelo a 24px | `#098475` | 3,22 | 3,22 | **3,0** | passa — o texto cresce |
| (C) fundo `#006C62` | `#006C62` | 4,43 | **3,52** | 4,5 | reprova |
| (E) fundo `#005048` | `#005048` | 6,56 | **3,92** | 4,5 | reprova só na linha 2 |
| (E++) só a onda escurece | `#098475` | **3,22** | 6,94 | 4,5 | reprova só na linha 1 |
| **(E+) `#005048` + onda `#00352F` a .7** | `#005048` | **6,56** | **8,54** | 4,5 | **passa nas duas** |

**A linha `#PROVEMOS EFICIÊNCIA` já reprovava, e a régua não mostrava.** Ela é branca com
`opacity:.9`. A régua lê a cor **declarada** (`#fff`) e informa **4,60**; o pixel real é `#e6f2f1` e
mede **4,01**. **Ou seja: (A) e (B) deixavam uma segunda reprovação no cartão**, que a folha anterior
dava como limpo. Escurecer o cartão conserta essa linha de graça: **5,44** em `#006C62` e **7,90** em
`#005048`.

> **RECOMENDAÇÃO ESCRITA — (E+):** cartão `#005048` (`turquesa-800`) e onda `#00352F`
> (`turquesa-900`) a 70%. **O amarelo `#FAD61D` não muda de valor** (é o acento oficial de marca), **o
> tamanho fica 18px**, **zero hex novo**, e **tudo no cartão passa com margem**: 6,56 · 8,54 · 7,90 ·
> 9,37. E a onda fica **mais visível** — escurecer em vez de clarear é ganho de desenho.
> *Alternativas descartadas, com número: (A) perde o amarelo e deixa o kicker em 4,01; (B) cresce o
> texto e deixa o kicker em 4,01; (C) para em 4,43/3,52; (E) para em 3,92 na segunda linha; (E++) para
> em 3,22 na primeira.*

#### (ii) O dourado — a recomendação anterior CONTRARIAVA uma decisão já aprovada

Ele escreveu: *"escurecer o dourado está mudando a cor que realmente representa a empresa, ou mantem ou
vc acha uma solução melhor"*.

> ### ERRO REGISTRADO
> A recomendação **(D)** da folha v14.1 — escurecer `#F9B11C` para `#986900` — **contraria o
> `seed-tokens.md` v1.5, de 2026-08-11, aprovado por gate visual dele**, que diz verbatim:
> *"Regra do dourado: permitido em área grande com rótulo direto; **vetado como fill solitário sem
> rótulo (1.85 é insolúvel sem matar a cor da marca)**."*
> E a decisão de **2026-07-30**: *"amarelo `#FAD61D` = **acento oficial de marca**; dourado `#F9B11C` =
> **cor de trabalho** (warning, dataviz cat-2, detalhe fotográfico). Nunca os dois na mesma peça."*
>
> **Ele reconheceu de olho o que já estava escrito, e eu não conferi antes de recomendar.**
> ⭐ **Régua que sai daqui: antes de propor mudança de VALOR de cor de marca, leia a linha do canon
> sobre aquela cor — ela pode já ter decidido, com número.** *Alternativa descartada é alternativa que
> alguém já mediu; propor de novo o que foi vetado é desfazer trabalho aprovado.*

**A saída preserva o hex exato: o dourado não muda de valor, muda de PAPEL.** Medido, com
`#F9B11C` intocado:

| o dourado como TINTA sobre… | razão | piso 4,5 | piso 3,0 (texto grande) |
|---|---|---|---|
| branco | 1,85 | ✗ | ✗ |
| `#FFF8E6` (o creme do próprio documento) | 1,75 | ✗ | ✗ |
| verde de marca `#098475` | 2,48 | ✗ | ✗ |
| `turquesa-700 #006C62` | 3,41 | ✗ | ✓ |
| `turquesa-800 #005048` | 5,06 | ✓ | ✓ |
| `turquesa-900 #00352F` | 7,31 | ✓ | ✓ |
| **`cinza-900 #242E34`** | **7,48** | ✓ | ✓ |
| **como ÁREA, com rótulo `#242E34` em cima** | **7,48** | ✓ | ✓ |

**Tamanho não salva o dourado:** mesmo a 44px, com o piso caindo para 3,0, ele mede 1,85 sobre branco.
*Foi por isso que a folha anterior propôs escurecer — e por isso a proposta estava errada: a saída não é
a cor, é a superfície.*

Os cinco lugares onde o dourado pinta letra no `seed-design-system.html`, e a saída de cada um:

| lugar | o que é | hoje | saída |
|---|---|---|---|
| nome do **EEny** (38px, texto à mão) | peça de **MARCA** | 1,85 | **(F)** inverter a superfície do cartão → **7,48** · dourado intocado |
| herói **"renova mundos."** (44px) | peça de **MARCA** | 1,85 | **(F)** ou **(G)** → **7,48** |
| `.type-sample-h2` (28px) | demonstração tipográfica | 1,85 | **(G)** o dourado vira área com rótulo escuro → **7,48** |
| badge e alerta de **warning** | **UI de TRABALHO**, não peça de marca | 1,75 | **(W)** os tokens que **já existem** → **9,05** |
| `.subtitle` | **CSS morto** — zero instância | — | nada a fazer *(apagar exige confirmação dele)* |

> **O caso do warning é diferente, e o canon já resolveu.** Ali o dourado não representa a marca —
> **sinaliza severidade**. Os tokens existem e foram aprovados em 2026-08-09/11:
> `feedback-warning-text` `#5B3E00` sobre `feedback-warning-bg` `#FFF4E4` mede **9,05**, enquanto
> `feedback-warning-border` e `-solid` **continuam `#F9B11C`**. **A cor de marca fica intacta na borda e
> no sólido; só a LETRA usa o degrau escuro da mesma rampa.** *Não é escurecer a cor da empresa — é usar
> o token de severidade que a casa já tinha, o mesmo critério que decidiu o `gauge-range-warning`.*

### 75.7 Pendências que esta seção abre

| # | Pendência | Por que fica, com número |
|---|---|---|
| **RD-P1** | o `seed-design-system.html` depende de **46 recursos de rede**; sete bancadas dependem do Google Fonts | medido: 0 de 45 imagens e 0 de 2 famílias carregam sem rede. O conserto — embutir os ativos e as fontes — mexe em 45 pontos de um arquivo e em 7 bancadas, e merece rodada própria. ⚠ **A família de e-mail está ISENTA e a isenção é declarada:** e-mail não pode embutir ativo |
| **CC-P11** | a `contraste-composicao.mjs` resolve **um** fundo por **elemento** e não vê texto que atravessa **duas** superfícies | medido: ela deu **6,56** onde o pixel dá **3,92**. Protótipo do conserto em `validacao/mede-fundo-por-linha.mjs`, que mede por **linha** com `Range.getClientRects()` — a mesma técnica que a SH1 já usa para vazamento. Irmã da **CC-P10** (gradiente) |
| **CC-P12** | a régua lê a cor **declarada** e ignora `opacity` no elemento ou em ancestral | medido: **4,60 declarado × 4,01 real** no kicker deste cartão. *Já era lição de método desde a nona parte; agora tem caso fundador e sigla* |
| **TP-P1** | a `guarda-tipografia-viva.mjs` não distingue recurso externo **legítimo** (e-mail) de **indevido** (bancada) — a atribuição por família foi feita **à mão** nesta rodada | é a mesma família da peneira declarada da BT1: lista escrita à mão, que funciona e tem de sair impressa. O conserto é uma isenção por padrão de nome (`^(ea|en|et|seed-email)`), **declarada no placar** |

---

## 76. GRAFISMO DA MARCA — a SERRA EM ARCOS (rodapé, **PADRÃO**) e a MONTANHA (topo, **opção**) · dois regimes de regra independentes · **`estável`** (2026-08-20/21, décima primeira parte) · geradores `gen-zero-formatos.py` · `gen-z3-largos.py` · `gen-variantes-montanha.py`

> **QUEM LÊ ISTO SEM TER VISTO A CONVERSA.** O "grafismo" da SEED engenharia é o
> elemento gráfico de linha que assina as peças de marca (post, cartão, banner,
> capa, pasta, story) e as superfícies de produto. O canon o define em
> `marca-seed.md` §7 — *"Grafismo v2 — linguagem de linhas"* — e essa definição é
> aqui citada de **SEGUNDA MÃO**: a pasta `Design System v2` guarda apenas uma
> CÓPIA daquele arquivo, e a leitura do canônico é a pendência **MR-P3**.
> **Toda regra numérica de canon citada nesta seção é de segunda mão até a MR-P3
> fechar.** O que NÃO é de segunda mão: os onze vereditos do decisor (verbatim
> abaixo) e todos os números medidos, que vieram de instrumento nesta casa.
> **Registro completo da rodada:** `validacao/MANIFESTO.md` **§103**.
> **Dimensões de cobertura que nasceram dela:** `mapa-cobertura-ds.md` **§22.15**.

### 76.1 O PAPEL — e por que este componente é diferente de todos os outros deste arquivo

Todos os outros componentes deste arquivo são de **INTERAÇÃO**: têm estados,
respondem a ponteiro e teclado, e por isso se validam por comportamento. **O
grafismo não interage com nada.** Ele é decorativo no sentido normativo estrito:
não carrega informação, não recebe foco, é `aria-hidden="true"` e
`focusable="false"`, e **não responde a piso de contraste de norma** (nem SC
1.4.3, que é de texto, nem SC 1.4.11, que é de componente de interface). *Isso
não o torna livre: ele responde a um contrato de MARCA, que é mais restritivo em
área e mais frouxo em contraste.*

Do canon (segunda mão, `marca-seed.md` §7): traço **1,5–2px**,
`stroke-linecap: round`, sempre **horizontal** (nunca rotacionar), **nunca voltar
à massa preenchida**, **área total de grafismo ≤10% da peça**, e a frase que
governa tudo — ***"grafismo é o elemento mais silencioso da peça"***.

**A ORIGEM DO DESENHO**, dita pelo decisor em 2026-08-20 e que **não estava
escrita em nenhum documento** (é o núcleo da emenda **MR-P4**):

> *"as ondas sao representacao de montanhas, pois remete ao verde, a natureza,
> logo as ondas devem se cruzar para formar um efeito de uma imagem na qual tem
> montanhas na frente e atras."*

A curva **não é uma onda decorativa: é uma cordilheira em planos que se cruzam.**
Perder essa leitura é perder o significado, não só a estética. *Origem não
registrada morre na primeira pessoa que sai do projeto — e foi por não estar
escrita que se desenharam duas linhas paralelas, que é **ritmo**, não serra.*

### 76.2 ⚠ DOIS MODELOS, DOIS REGIMES DE REGRA — leia isto antes de tocar em qualquer um dos dois

Este é o ponto onde um leitor futuro se engana se a seção não avisar. **Existem
DOIS modelos de grafismo aprovados, e as regras de um NÃO valem para o outro.**
A fronteira é veredito, não inferência:

> **Rafael Sant'Ana, 2026-08-21, verbatim:** *"o uso das montanhas/ondas no topo
> nao é o padrao, o padrao em em baixo, mas aprovei esse modelo de montanha para
> usar no topo caso o criador queira coloca-las no topo."*
>
> **E, sobre a tentativa de estender a regra do rodapé ao topo:** *"para as
> montanhas do topo, eu aprovei dessa forma porque o desenho ficou muito bom,
> entao nao tem problema nao seguir a regra das ondas do rodape. **considere que
> o topo segue uma regra e o rodapé outra regra**."*

| | modelo **MONTANHA** | modelo **SERRA EM ARCOS** |
|---|---|---|
| **onde** | **TOPO** da peça | **RODAPÉ** da peça |
| **status** | **OPÇÃO** — à disposição de quem monta a peça, quando a composição pedir, e sem precisar justificar | **PADRÃO** |
| **planos** | **3** (fundo · meio · frente) | **4** (fundo · longe · perto · frente) |
| **traçado** | crista **bezier** de curvatura distribuída | **arcos** que nascem e morrem abaixo da moldura |
| **encontro das linhas** | oclusão **COM FOLGA** (traço de máscara 7px) | **TOQUE EXATO** (recorte na própria crista) |
| **faixa** | 64px em peça de 420 = **15,24%** | **13,8%** cartão · **11,8%** banner · **15,7%** capa |
| **gerador** | `validacao/gen-variantes-montanha.py` | `gen-zero-formatos.py` · `gen-z3-largos.py` |
| **folha de gate** | `render-audit/gate-montanha/fechamento.html` | `render-audit/gate-z3/fechamento{,-largos}.html` |

**REGRA DE ESCOPO, e ela vale para além deste componente:** **regra derivada de um
modelo fica ESCOPADA a ele até haver veredito que a estenda.** O **toque exato**
parecia lei da casa e **é regra do RODAPÉ**. *Generalizar por conta própria teria
mudado em silêncio uma peça que o decisor já havia aprovado — que é a forma mais
cara de "conserto" que existe neste projeto.*

**O CONFLITO DE ALTURA, e por que os dois modelos o resolvem por lados opostos —
a emenda de canon tem de escrever OS DOIS, senão parecem contradição:** a serra
precisa de **~15%** da altura da peça para **LER** como serra (abaixo disso vira
linha), e o canon dá **~8%** ao *"detalhe de rodapé"*. A montanha escapou **indo
para o TOPO**, onde aquele teto não se aplica; a serra em arcos escapou
**mudando o teto** — o decisor aprovou faixas de 11,8% a 15,7% no rodapé.

### 76.3 O MODELO SERRA EM ARCOS — anatomia, e as três regras de construção

**Anatomia (4 planos, de trás para a frente):**
- **FUNDO** — única linha **contínua** atravessando a peça inteira; a mais alta e
  a mais clara. Sem "ombros" (a 2,2px eles viravam ruído — veredito).
- **LONGE** e **PERTO** — morros **PARCIAIS** em duas profundidades: arcos que
  **nascem e morrem abaixo da moldura** ou atrás de outro morro. Cume **fora de
  centro**, flancos **assimétricos**, ápice **ARREDONDADO** — nunca ponta:
  *"o nosso nao é pontudo e sim uma ondulaçao"*.
- **FRENTE** — baixa e suave; atravessa a peça **mergulhando abaixo do limite
  inferior** entre um cume e outro. Não pode ser alta: *"nao pode ser tao alta,
  ela tampa os cumes do fundo"*.

**Três regras de construção, todas nascidas de veredito:**

**① TOQUE EXATO.** *"as linhas devem se tocar, se uma linha acaba antes de tocar a
outra nao da sensacao de profundidade"*. A oclusão é feita por recorte na
**PRÓPRIA crista** dentro de `<mask>`, folga **zero**: a ponta redonda da linha de
trás **ENCOSTA** no traço da frente. **É construção, não retoque.** *O que existia
antes era uma máscara deslocada 2,2× o traço, e a linha de trás terminava
flutuando ~11px acima — literalmente "acabar antes de tocar".*
⚠ **A oclusão é sempre por `<mask>`, NUNCA por forma preenchida:** assim funciona
sobre qualquer fundo e não viola *"linha, nunca massa"*. *E o corte de oclusão é o
idioma do desenho técnico — linha interrompida para indicar o que passa por cima —
o que conversa com o princípio "precisão de engenharia como estética" em vez de
brigar com ele.*

**② REGRA DO FRAGMENTO.** *"pode fazer sentido ver um pedacinho de morro sobre o
outro [na foto], mas na nossa representacao nao faz sentido pois em linhas nao
fica bonito e profissional, clean"*. Trecho visível de linha com comprimento
**< 80px** (calibrado na cena de 1080; escala com a janela) **OU** folga máxima
**< 9px** sobre a linha que o oclui é **SUPRIMIDO na máscara**. **Ou o morro
aparece de verdade, ou não aparece.** *A regra roda POR FORMATO: o que vira
fragmento numa proporção não vira em outra.*

**③ CRITÉRIO DO MOVIMENTO.** *"nao pode perder o sentido de movimento apesar de
serem montanhas"*. **O movimento é parte do SIGNIFICADO** — a onda originou o
desenho, a montanha é a leitura. **Composição que congele a serra viola o
aprovado**, mesmo com cada linha correta.

### 76.4 A LEI DA ESCALA — forma em UNIDADES DE FAIXA, e a âncora em peça larga

**O defeito que a criou** (veredito, 2026-08-21): *"no cartão de visita e no
banner 3:1 1500×500, ficou muito achatado em baixo, esmagado."*

**A causa, medida** (e é de causa, não de sintoma): as larguras dos morros eram
frações da **LARGURA DA PEÇA** e as alturas frações da **FAIXA**. No post
(1080×85, razão **w/F = 12,7**) o morro massivo sai **432×62 px** — a proporção
aprovada. No banner (1500×39, **w/F = 38,5**) o MESMO morro sai **450×28 px** —
razão 16:1, esmagado.

> **LEI: a forma do morro é constante em UNIDADES DE F (altura da faixa); o que
> muda com a largura da peça é a COMPOSIÇÃO, nunca a forma.** Peça larga **não**
> ganha morro esticado. **Razão natural da cena aprovada: w/F = 12,7.**

**Consequência de composição — a ÂNCORA.** Peça larga-baixa recebe a cena
**ancorada** numa janela de proporção natural (largura da janela = 12,7 × F), no
**lado oposto ao texto**; a linha da FRENTE continua atravessando a peça inteira,
mergulhada abaixo do limite fora da janela. *Foi assim que L4 e L5 foram
resolvidos: a janela começa no CENTRO (largura = w/2) e a faixa sai por
CONSEQUÊNCIA — banner 750/12,7 = 59px, capa 792/12,7 = 62px.*

**Regras de adaptação por formato**, declaradas em `gen-zero-formatos.py`:
faixa = `floor(h × 0,0795)` · densidade do fundo `n = max(3, round(7 × w/1080))` ·
peça com largura **< 700px** recebe **SUBCONJUNTO** da cena (massivo + apoio +
frente de 1 cume) · traço escala com a FAIXA (`3,5 × F/86`), com **grampo de
1,5 a 4,5px** · a regra do fragmento roda por formato.

### 76.5 AS ESCADAS DE COR — e nenhum hex fora da rampa canônica

Rampa turquesa canônica (`seed-tokens.md` §3): `#E8FBF7` 50 · `#CDF3EC` 100 ·
`#9CE4D8` 200 · `#66D1C2` 300 · **`#11B0A0` 400 (âncora)** · `#00A192` 500 ·
**`#098475` 600 (âncora, o verde institucional)** · `#006C62` 700 · `#005048` 800
· `#00352F` 900.

**Princípio: perspectiva atmosférica.** O que está longe é mais claro e mais fino;
a frente é sempre a mais firme. No tema escuro o plano de trás quase se dissolve
no fundo — é a "névoa".

| modelo · tema | fundo da peça | frente | perto | longe | plano de fundo |
|---|---|---|---|---|---|
| **arcos · claro (C1)** | `#FFFFFF` | `#098475` | `#11B0A0` | `#66D1C2` | `#9CE4D8` |
| **arcos · escuro (E1-névoa)** | `#005048` | `#66D1C2` | `#11B0A0` | `#098475` | `#006C62` |
| **montanha · claro** | `#FFFFFF` | `#098475` | `#66D1C2` | — | `#9CE4D8` |
| **montanha · escuro** | `#005048` | `#66D1C2` | `#11B0A0` | — | `#006C62` |

**Veredito da escada escura** (2026-08-21): *"pergunta 2, E1 nevoa fica melhor"*.
**A alternativa descartada, com número:** a escada "presença" (que começava em
`#9CE4D8`). Ele viu as duas na MESMA peça, com a cor como única variável:
diferença medida de **7.413 px de 630.000 (1,18%)** no verso do cartão e
**5.048 px (0,80%)** na capa, **delta máximo de canal 85 de 255** — medição
repetida com uma condição variada, e o veredito sobrevive.

> ### ⚠ SUPERSEDE FORMAL — o `#0A6B60` está MORTO, e a regra que saiu disso
> Até 2026-08-21 o plano de trás das peças escuras usava **`#0A6B60`**. **Essa cor
> nunca foi token da SEED:** varredura de alvo de pasta em `seed-tokens.md` e
> `seed-tokens.json` → **0 ocorrências**. Ela entrou "por precedente" e o arquivo
> do precedente **não existia**. Veredito: *"eu nao autorizei e vc nao deveria ter
> criado sem autorizacao algo canonico e ja decidio."*
> **Substituída por `#006C62` (turquesa-700).** Medição que garante que a
> aparência aprovada não se perde: **ΔE76 = 1,03** (limiar de percepção ~2,3) ·
> contraste entre as duas **1,011** · contra `#005048`, **1,466** e **1,482** · no
> pixel da peça inteira a troca move **0,31%** dos pixels com delta máximo de
> **10 em 255**.
> **REGRA §0-b DA CASA:** valor de marca que não existe no canônico **não se
> cria** — nem por precedente, nem provisoriamente, nem porque a diferença é
> invisível. **`ls` o arquivo do precedente antes de invocá-lo.**
> ⚠ **Onde o `#0A6B60` PERMANECE, e é de propósito:** no estudo 3
> (`gen-estudo-seed.py` e `render-audit/estudo-seed/estudo.html`). Aquele arquivo
> é o **REGISTRO do que o decisor viu quando escolheu**; trocar o valor lá seria
> falsificar a pergunta para arrumar a resposta. Recebeu supersede no docstring, e
> o HTML gerado saiu **byte-idêntico** (md5 `b88e6d32485b9312439659c2681c0e60`).

**DECISÃO REGISTRADA COM O PORQUÊ: o AMARELO não entra nas linhas do grafismo.** O
canon dá ao amarelo o papel de **acento único** (o "risco") e manda o grafismo ser
o elemento mais silencioso da peça — amarelo na serra criaria **dois acentos
competindo**. *Alternativa descartada, não esquecida.*

### 76.6 AS PEÇAS APROVADAS — o inventário com número

**Serra em arcos · 7 formatos** (`gen-zero-formatos.py`):

| # | formato | faixa | % altura | traço |
|---|---|---|---|---|
| F1 | post 1:1 · 1080×1080 · claro | 85px | 7,9% | 3,46px |
| F2 | cartão de visita · 1050×600 · claro | 47px | 7,8% | 1,91px |
| F3 | cartão · brand-deep | 47px | 7,8% | 1,91px |
| F4 | banner 3:1 · 1500×500 · claro | 39px | 7,8% | 1,59px |
| F5 | capa LinkedIn 4:1 · 1584×396 · brand-deep | 31px | 7,8% | 1,50px (piso) |
| F6 | pasta / A4 · 874×1240 · claro | 98px | 7,9% | 3,99px |
| F7 | story 9:16 · 1080×1920 · brand-deep | 152px | 7,9% | 4,50px (teto) |

**Serra em arcos · 5 peças largas** (`gen-z3-largos.py`), todas com w/F = 12,7 —
**L2, L3, L4 e L5 APROVADAS** (*"L2 e L3 estão ok"* · *"pergunta 1… está
aprovado"*); L1 descartada, era só a variação de comparação:

| # | peça | janela | faixa | % |
|---|---|---|---|---|
| L1 | cartão dentro do teto de ~8% | 597px à direita | 47px | 7,8% |
| **L2** | cartão, frente, rodapé natural | largura total | 83px | **13,8%** |
| **L3** | cartão, verso brand-deep | largura total | 83px | **13,8%** |
| **L4** | banner 3:1, janela do CENTRO à direita | 750px | 59px | **11,8%** |
| **L5** | capa 4:1, janela do CENTRO à direita | 792px | 62px | **15,7%** |

**Montanha · 5 colocações** (`gen-variantes-montanha.py`) — **P3 e P4
APROVADAS**. ⚠ Placares medidos pela `guarda-grafismo.mjs`, que foi **perdida**:
são **históricos e hoje irreproduzíveis** (**GR-P3**).

| # | colocação | faixa | % | área | glifo velado | GR1 | lê como serra? |
|---|---|---|---|---|---|---|---|
| P1 | rodapé 32px | 32px | 7,62% | 1,84% | 0 | PASS | **não** — vira linha |
| P2 | rodapé 64px | 64px | 15,24% | 2,17% | 0 | FAIL GR1-c | sim |
| **P3** | **TOPO 64px · branco** | 64px | 15,24% | 2,17% | 0 | **PASS** | sim |
| **P4** | **TOPO 64px · brand-deep** | 64px | 15,24% | 2,04% | 0 | **PASS** | sim |
| P5 | rodapé 64px · brand-deep | 64px | 15,24% | 2,04% | 0 | FAIL GR1-c | sim |

Contraste nas quatro colocações: **8 PASS · 0 FAIL**. **Zero pixel de núcleo de
letra velado em TODAS** as doze peças dos dois modelos — o grafismo não toca o
texto em nenhuma.

**A geometria da montanha, congelada e com procedência** (extraída literalmente do
artefato aprovado; viewBox 420×64 com `preserveAspectRatio="none"`, que é como a
faixa escala):

| plano | traçado | traço |
|---|---|---|
| fundo | `M-4,60.0 C 70,58.0 96,4.0 118,4.0 C 148,4.0 220,52.0 424,48.0` | 1,5px |
| meio | `M-4,62.0 C 90,60.0 150,24.0 186,22.0 C 220,22.0 300,54.0 424,52.0` | 1,75px |
| frente | `M-4,64.0 C 160,62.0 226,58.0 268,14.0 C 284,2.0 310,14.0 424,60.0` | 2px |

**NÃO EDITAR estes três traçados sem veredito dele: são a peça aprovada.**

### 76.7 O CONTRATO GR1 — escrito, e HOJE SEM INSTRUMENTO

| # | cláusula | limite | medido nas peças |
|---|---|---|---|
| **GR1-a** | área total de grafismo na peça | **≤ 10%** | arcos 0,67–1,12% · montanha 1,84–2,17% |
| **GR1-b** | o grafismo não vela o núcleo do glifo do símbolo | **0 px** | 0 em todas as 12 |
| **GR1-c** | faixa de **rodapé** ≤ teto do canon | **~8%** da altura | 7,8–7,9% nos 7 formatos |

> ⚠⚠ **A GUARDA NÃO EXISTE NA PASTA.** A `validacao/guarda-grafismo.mjs` **nunca
> foi propagada** (ver MANIFESTO §103.9): não está na pasta nem no índice do Drive.
> **Todo placar "GR1 7 PASS" que apareça em folha antiga é HISTÓRICO e
> IRREPRODUZÍVEL**, e nesta casa irreproduzível vale como **NÃO VERIFICADO**. As
> três cláusulas acima são a reconstrução do contrato a partir das citações e dos
> placares impressos — é o que torna a **GR-P3** executável. **Pasta de prova
> primeiro** (`render-audit/prova-gr1/`, onde FAIL é o resultado desejado), com
> uma prova por cláusula, e só então placar novo.

> ⚠ **A GR1-c REPROVA QUATRO PEÇAS APROVADAS, DE PROPÓSITO.** L2, L3, L4 e L5
> excedem o teto de ~8%. São **FAILs sancionados pelo decisor**, que ficam
> vermelhos até a emenda de canon existir. **Guarda cujo limite se afrouxa para
> caber no desenho não é guarda** — o limite muda quando o CANON muda, nunca antes.

### 76.8 COMO CONSUMIR — a árvore de decisão

1. **A peça leva grafismo?** Se ela já tem foto ou dado forte perto da borda,
   considere não levar: *grafismo é o elemento mais silencioso da peça.*
2. **Rodapé ou topo?** **Rodapé é o PADRÃO.** Topo é **opção** de quem monta,
   quando a composição pedir — e usa o **outro** modelo (montanha), com o **outro**
   regime de regra.
3. **Tema.** Claro → escada C1. Brand-deep → escada **E1-névoa**. *Nenhum hex fora
   da rampa canônica turquesa, nos dois.*
4. **Escala.** A forma é constante em unidades de **F**; **w/F = 12,7** é a razão
   natural. Peça larga-baixa → cena **ANCORADA** em janela natural no lado oposto
   ao texto, com a linha da frente atravessando a peça inteira.
5. **Nunca** rotacionar · **nunca** massa preenchida · **nunca** amarelo nas
   linhas · **nunca** um morro em fragmento (regra do §76.3-②).
6. **Não recrie o traçado à mão.** Os geradores são a fonte: `gen-zero-formatos.py`
   (7 formatos), `gen-z3-largos.py` (peças largas), `gen-variantes-montanha.py`
   (topo). *Traçado redigitado é dado sintético plausível — a classe de defeito
   mais perigosa desta casa, porque não chama atenção.*

### 76.9 FRONTEIRAS E PENDÊNCIAS

- **MR-P3** — o `marca-seed.md` desta pasta é **CÓPIA**. Toda regra de canon
  citada aqui é de **segunda mão**. ✅ O acesso à pasta-mãe existe desde
  2026-08-21; falta ler.
- **MR-P4** — a emenda de canon, a escrever **COM o decisor**, com quatro itens:
  a origem "onda = montanha"; o critério do movimento; a distinção `serra-cena` ≠
  `detalhe de rodapé de 1–2 linhas` com os tetos por formato (13,8% · 11,8% ·
  15,7%); e **os dois regimes de regra** com a hierarquia de uso. **Só depois disso
  a GR1-c é atualizada.**
- **MR-P6** — **traço em MM para impressão** (cartão 89×51mm, pasta A4).
  **Todo número desta seção é px de TELA.**
- **GR-P3** — reescrever a `guarda-grafismo.mjs` (§76.7).
- **Dívida técnica declarada** — a gramática (`cordilheira`/`filetes`/`morro`/
  `frente_ondulada`/`compoe`) está **COPIADA** em três geradores. A troca de cor
  desta rodada precisou ser feita em **dois arquivos**. *Consolidar em lib quando o
  modelo estabilizar; até então, mudança num é mudança nos outros.*
- **Este componente não tem bancada nem suíte**, e a fronteira é declarada: ele
  não interage, então não há comportamento a asserir. **O que ele tem é gate visual
  humano + o contrato GR1 + a régua de contraste.** *Quando a GR-P3 fechar, a
  bancada faz sentido; hoje seria uma galeria de estados impressos, que é DESENHO
  (§67.4).*


## 77. GR1 GANHA INSTRUMENTO — o contrato do grafismo volta a ser verificável, e não por lista: por alvo de pasta com pasta de prova (2026-08-21, décima segunda parte) · `validacao/guarda-grafismo.mjs`

> **Contexto para quem lê isto sem ter visto o resto.** O **grafismo** da SEED v2
> é a "linguagem de linhas" do §7 do `marca-seed.md`: curvas finas de 1,5–2px em
> turquesa, que substituíram as ondas chapadas de 2018. Ele existe em dois modelos
> — a **SERRA EM ARCOS** no rodapé (**PADRÃO**) e a **MONTANHA** no topo
> (**opção** de quem monta a peça) — com **regimes de regra independentes**, por
> veredito do decisor. A anatomia, os planos, a gramática e as 12 peças estão no
> **§76**; esta seção trata do **INSTRUMENTO** que verifica o contrato.

### 77.1 O estado que o §76 declarava, e que esta seção fecha

O §76 registrou o contrato **GR1 sem instrumento**: as três cláusulas escritas, o
código perdido na propagação, e a frase de que "todo placar GR1 de folha antiga é
histórico e irreproduzível". **Contrato escrito não é contrato verificado** — a
mesma distinção que obrigou o supersede formal da tabela BT1–BT7 no §64.1.
Agora o contrato tem régua.

### 77.2 As três cláusulas, como se medem e o que cada uma devolve

| cláusula | como se mede (o juiz é o pixel) | teto | devolve |
|---|---|---|---|
| **GR1-a** | render sem grafismo × render com grafismo; a área é o conjunto de pixels que **muda** | ≤ **10%** da peça | `0,47%–1,79%` nas 18 peças |
| **GR1-b** | máscara do símbolo (render sem ele × com ele), **erodida 1px** para excluir antisserra, intersectada com a máscara do grafismo | **0 px** | **0 px em todas as 18** |
| **GR1-c** | **regime** pela âncora geométrica (que borda a caixa encosta, tol. 2px); **altura** pela extensão vertical da tinta; a caixa **declarada** sai ao lado | ≤ **~8%** da altura | 7 formatos `5,89–6,67%` pintada / `7,80–7,92%` declarada |

**Alvo de PASTA, sem filtro de nome** (`readdirSync(...).filter(endsWith('.html'))`),
exclusões por `PULA=` **impressas e contadas** — o 42º defeito nasceu de um filtro
por prefixo de nome, que é lista escrita à mão com outra sintaxe.

**Classificação estrutural, não por nome de classe:** um `<svg>` da peça é
**GRAFISMO** quando todas as suas formas pintáveis (fora de
`defs`/`mask`/`clipPath`/`symbol`) têm `fill:none` e têm `stroke`; `<svg>` com
forma preenchida é **IDENTIDADE**, e é o alvo da GR1-b. **O censo com o motivo de
cada elemento sai impresso por peça.** *Consequência canon-fiel e declarada: o
`<svg class="risco">` amarelo de 56px é grafismo — é a linha "Marcador de seção"
da tabela do §7 ("linha curva curta (40–80px) … turquesa-400 ou amarelo"). Ele
pinta 164 px (0,026% do cartão) e entra na área; sai `[n/a]` na GR1-c.*

### 77.3 A gramática de consumo — o que um criador de peça precisa cumprir

1. **Rodapé é o padrão; topo é opção.** Veredito verbatim: *"o uso das
   montanhas/ondas no topo nao é o padrao, o padrao em em baixo, mas aprovei esse
   modelo de montanha para usar no topo caso o criador queira coloca-las no
   topo."*
2. ⚠ **SUPERSEDIDO PELO §78 (mesma data).** O texto original dizia: *"Faixa de
   rodapé, canon vigente (v5.3): ≤ ~8% da altura. Peça que precisa de serra-cena
   (11,8%–15,7%) hoje reprova de propósito e espera a emenda MR-P4"*. **A emenda
   MR-P4 foi aprovada no mesmo dia** e o canon vigente é a **v5.5**: faixa de
   rodapé responde ao regime (**detalhe ≤~8%** · **serra-cena com teto por
   formato**, §78.2) e faixa de topo responde à **largura** (§79). *A frase de
   cima ficou verdadeira por poucas horas — e é por isso que ela está riscada em
   vez de apagada.*
3. **Faixa de topo não responde ao teto de rodapé** e sai `[n/a]` nomeado —
   veredito **MT-P1**, verbatim: *"considere que o topo segue uma regra e o rodapé
   outra regra"*.
4. **Área total de grafismo ≤ 10%**, contando **todo** elemento de linguagem de
   linhas da peça, inclusive o marcador de seção.
5. **O grafismo não toca o núcleo do glifo do símbolo. 0 px, sem tolerância.**
6. **Cor só em stop da rampa canônica turquesa** (regra §0-b, nascida da retirada
   do `#0A6B60`). A guarda **não** mede cor — quem mede é a
   `guarda-literal-de-cor.mjs` (NV2) e a régua de contraste.

### 77.4 Fronteira declarada — o que este instrumento NÃO julga

- **Não julga se o desenho lê como serra.** Legibilidade de **forma** não é
  medível por guarda; o juiz continua sendo o **gate visual humano**. *Foi por
  isso que a peça de rodapé de 32px (P1) passou em todas as cláusulas e ainda
  assim o registro diz "não lê como serra".*
- **Não mede milímetros.** Todo número é px de **tela** — **MR-P6** aberta para o
  cartão 89×51mm e a pasta A4.
- **Não mede contraste** do grafismo contra o fundo, nem a supressão de fragmento
  (a "regra do fragmento" do §76.4, que é construção do gerador).
- **Não tem bancada nem suíte**, e a fronteira segue a do §76: o componente não
  interage, então não há comportamento a asserir.

### 77.5 O 47º defeito de instrumento, porque ele muda como se escreve guarda nesta casa

**Esconder um elemento e mostrá-lo de novo não restaura o pixel** — 112 px do
glifo do símbolo voltam diferentes depois do ciclo `visibility:hidden` → `''`,
enquanto quatro capturas sem toque nenhum são byte-idênticas. O sinal foi um
número **impossível**: sobreposição de **−77 px** entre dois planos.
**Conserto:** um estado de render por **carga nova**, e a carga só esconde.
**Régua que sai disso, e vale para toda guarda:** quem mexe na página para medir
**imprime prova de estabilidade** (estado de controle renderizado duas vezes, em
cargas independentes, exigindo 0 px) e devolve `[n/a]` quando instável.
Reproduza: `node validacao/prova-47o-defeito.mjs`.

### 77.6 Onde está a prova, e como se roda

```
DIR=render-audit/prova-gr1            node validacao/guarda-grafismo.mjs   # 10·3·8
DIR=render-audit/gate-z3/formatos     node validacao/guarda-grafismo.mjs   # 21·0·0
DIR=render-audit/gate-z3/largos       node validacao/guarda-grafismo.mjs   # 11·4·0
DIR=render-audit/gate-montanha        node validacao/guarda-grafismo.mjs   # 14·2·2
DIR=render-audit/prova-gr1 ESCALA=2   node validacao/guarda-grafismo.mjs   # 10·3·8 (condição variada)
```

Folha de conferência: `render-audit/gate-gr1/fechamento.html`
(gerada por `validacao/monta-folha-gr1.py`, que lê os `placar-*.json` escritos
pela própria guarda — **nenhum número redigitado**).
`LEIA-ME.md` autossuficiente em `render-audit/prova-gr1/`.

### 77.7 Pendências que esta seção deixa

- **GR-P5 (nova)** — estender a GR1-c com o regime **SERRA-CENA** por formato,
  **depois** de MR-P4 aprovada, com a fixture escrita antes e **uma dimensão por
  vez**.
- **GR-P6 (nova)** — a divergência de ÁREA contra o instrumento perdido
  (`0,49–1,79%` × `0,67–2,17%` históricos) não é resolvível: fica registrada. *Se
  alguém encontrar o arquivo antigo num backup, a primeira coisa a fazer é
  comparar as duas definições de área.*
- **GR-P7 (nova)** — a prova de estabilidade existe em **1 de 56** instrumentos.
  A varredura de quais outros mexem na página e não a imprimem é uma varredura de
  uma linha, e é irmã da varredura de `scroll-behavior` (43º defeito) e da
  **IN-P2**.
- **MR-P6** — grafismo em milímetros (segue aberta, do §76).


## 78. GRAFISMO — OS DOIS REGIMES entram no contrato GR1 · `DETALHE DE LINHA` × `SERRA-CENA` · **`estável`** (2026-08-21, décima terceira parte) · canon `marca-seed.md` **v5.4** §7.2/§7.4

> **Contexto para quem lê isto sem o resto.** O §76 especifica o grafismo como
> componente (a serra em arcos no rodapé, a montanha no topo, as 12 peças). O §77
> registra o instrumento que o verifica (`validacao/guarda-grafismo.mjs`, contrato
> GR1). Esta seção registra a **mudança de CANON** que o decisor aprovou em
> 2026-08-21 e o que ela muda na gramática de consumo.

### 78.1 O que mudou no canon, e qual era o conflito

O teto de **~8% da altura** da §7 foi escrito para **detalhe de rodapé de 1–2
linhas**. A serra-cena aprovada precisa de **11,8% a 15,7%** — e o motivo está no
canon com número: **a serra precisa de ~15% da altura para LER como serra**;
abaixo disso ela não fica discreta, **desaparece** (em 32px o traço do plano de
trás cai a 1,50px e a cena vira uma linha).

**A v5.4 resolve reconhecendo DOIS REGIMES** (§7.2), e o canon aceita **as duas
saídas** do conflito, para que ninguém leia uma como contradição da outra:
**(a) ir para o TOPO** — foi a saída do modelo MONTANHA; **(b) mudar o teto NO
RODAPÉ**, formato a formato — foi a saída do modelo SERRA EM ARCOS.

| regime | definição (§7.2) | teto |
|---|---|---|
| **DETALHE DE LINHA** | 1–2 linhas discretas na base, **sem** cruzamento nem oclusão | **≤ ~8%** da altura |
| **SERRA-CENA** | composição de **3 ou 4 planos** com cruzamento, oclusão e hierarquia de peso (§7.1) | **POR FORMATO** (§7.4) |

### 78.2 A tabela de tetos que passa a valer (§7.4) — com o veredito de cada linha

| formato | banda | teto | veredito dele |
|---|---|---|---|
| cartão de visita, frente e verso (razão 1,75) | 83px de 600 | **13,83%** | *"L2 e L3 estão ok"* |
| banner 3:1 | 59px de 500 | **11,80%** | *"está aprovado"* |
| capa 4:1 | 62px de 396 | **15,66%** | *"está aprovado"* — é o **mínimo** que satisfaz "a partir do centro" + proporção natural |
| post 1:1 · pasta A4 · story 9:16 | `floor(h × 0,0795)` | **7,95%** | cabe no regime de detalhe (§7.4, linha 1) |
| montanha no **TOPO** (peça 1:1) | 64px de 420 | **15,24%** | aprovada em 2026-08-20 (P3 branco · P4 brand-deep) |

⚠ **Formato que não está nesta tabela NÃO TEM TETO.** A guarda devolve `[n/a]`
nomeado e a lacuna vai ao decisor. *Teto de canon não se interpola nem se herda
por analogia — regra §0-b, a mesma que retirou o `#0A6B60` do sistema.*
⚠ **Todos estes números são px de TELA.** Milímetros para impressão: **MR-P6**.

### 78.3 A gramática de consumo, atualizada — o que muda para quem monta peça

1. **Rodapé é o PADRÃO; topo é OPÇÃO** (não mudou, §76).
2. **Escolha do regime é de DESENHO, e ela tem consequência de teto:** se a peça
   pede uma cena de 3–4 planos com cruzamento e oclusão, ela está em serra-cena e
   responde ao teto do **formato**. Se pede 1–2 linhas discretas, está em detalhe e
   responde a **~8%**. *Não existe caminho de "cena de 4 planos com teto de
   detalhe" nem de "1–2 linhas com teto de serra-cena".*
3. **Formato novo (razão de peça que o canon não tabela) é PERGUNTA, não conta de
   três.** A guarda o marca `[n/a]`; o teto nasce com veredito dele.
4. **Lei da escala inalterada** (§7.4): a forma do morro é constante em unidades
   de F; o que muda com a largura é a **composição**. Razão natural `w/F = 12,7`.
   Peça larga-baixa recebe a cena **ancorada** numa janela de proporção natural.
5. **Cor: só stops da rampa canônica turquesa** (§7.5). Regra §0-b.
6. **O amarelo NÃO entra nas linhas do grafismo** (§7.5) — criaria dois acentos
   competindo. *Alternativa descartada, não esquecida.* ⚠ Isto **não** contradiz o
   `<svg class="risco">` amarelo de 56px: aquele é o **"Marcador de seção"** da
   tabela §7.6, um uso permitido do regime de **detalhe**, não uma linha da
   serra-cena.

### 78.4 O que o instrumento passou a fazer (detalhe em §77, atualizado aqui)

- **Regime medido, não presumido:** contagem de **formas de traço** do elemento
  (1–2 → detalhe; ≥3 → serra-cena), impressa em toda peça. ⚠ *Forma de traço não é
  exatamente plano: a serra em arcos tem **5 formas em 4 planos**. Para a fronteira
  que o canon nomeia ("1–2 linhas" × "3 ou 4 planos"), contar formas é a régua
  honesta.*
- **Teto casado por âncora + razão w/h** (tolerância 0,01), com a citação do canon
  na linha do placar.
- **Veredito pela faixa DECLARADA**, porque os tetos do canon são escritos como
  **banda** ("83px de 600"). A **pintada** continua impressa e ganhou função: tinta
  abaixo de **70%** da banda vira o achado **"declarou e não aparece"**.
- **Topo julgado só na razão 1:1**, a única que o canon mediu.

### 78.5 O placar que sai disso — e por que ele é fechamento POR CANON

| corrida | antes da v5.4 | depois |
|---|---|---|
| 5 peças largas (L1…L5) | `11 PASS · 4 FAIL · 0 [n/a]` | **`15 · 0 · 0`** |
| 6 peças da montanha | `14 · 2 · 2` | **`16 · 2 · 0`** |
| 7 formatos da serra | `21 · 0 · 0` | **`21 · 0 · 0`** (inércia) |
| pasta de prova | `10 · 3 · 8` (7 fixtures) | **`20 · 6 · 10`** (12 fixtures) — ⚠ **histórico**: horas depois virou `24 · 7 · 11` em 14 fixtures, com o teto de largura do topo (**§79**) |

**Nenhum limite da guarda foi mexido.** O teto mudou **no canon**, aprovado por
ele, e a guarda passou a ler o canon. *A diferença entre isso e afrouxar uma
guarda é a única coisa que separa um design system de uma coleção de exceções.*

⚠ **P2 e P5 seguem FAIL, e é correto:** montanha de 15,24% no **RODAPÉ** de peça
1:1, cujo teto no rodapé é 7,95%. O aprovado foi o **TOPO** (P3/P4, agora medidas
e PASS com 15,24% exatos). *Regra de escopo: veredito dado sobre um modelo fica
escopado a ele até ele estender.*

### 78.6 Pendências

- ✅ **GR-P5 fechada** (era: estender a GR1-c com o regime serra-cena).
- **GR-P8 (nova)** — teto do **topo** em razão diferente de 1:1: o canon não mediu,
  a guarda devolve `[n/a]`. Só nasce com veredito dele.
- **GR-P6** — a divergência de ÁREA contra o instrumento perdido (registrada).
- **GR-P7** — prova de estabilidade de captura em 1 de 56 instrumentos.
- **GR-P9 (nova)** — **integridade das saídas do instrumento** (48º defeito):
  conferir exit code e mtime do derivado em toda guarda que escreve arquivo.
- **MR-P6** — grafismo em milímetros para impressão.


## 79. GRAFISMO NO TOPO — o teto é de LARGURA, não de altura · **`estável`** (2026-08-21, décima quarta parte) · canon `marca-seed.md` **v5.5 §7.4-b**

> **Para quem lê isto sem o resto:** o **§76** especifica o grafismo como
> componente; o **§77**, o instrumento que o verifica (GR1); o **§78**, os dois
> regimes (`DETALHE DE LINHA` × `SERRA-CENA`). Esta seção corrige **o eixo de
> medição do regime serra-cena quando ele está no TOPO**.

### 79.1 O veredito e a regra

> **Rafael Sant'Ana, 2026-08-21, verbatim:** *"acho que deixar 1:1 nao seria o
> ideial, o ponto é medir a aplicação na largura se atende ao desenho selecionado
> para o topo, a altura nao importa."*

**Regra:** `F ≤ w / 6,5625`, onde `F` é a altura da faixa e `w` a largura da peça.
**6,5625** é a razão da **peça aprovada** (w = 420, F = 64). **A altura da peça é
irrelevante** e a razão de aspecto **não entra**.

**O que isto supersede:** a leitura do §78 de que o topo respondia a **15,24% da
altura**, válida só na razão 1:1. Aquele número continua correto como *descrição*
da peça aprovada (64 de 420 = 15,24%), mas **não é o teto**.

**Escopo:** isto é do **TOPO**. O **RODAPÉ** segue com os tetos **por formato** do
§78.2. *Regra derivada de um modelo fica escopada a ele até haver veredito que a
estenda — a mesma regra que impediu o "toque exato" do rodapé de virar lei geral.*

### 79.2 O que muda para quem monta peça

1. **Peça larga com montanha no topo não precisa mais de veredito novo.** Ela
   responde à mesma proporção: numa peça de 1500 de largura, a faixa pode ir até
   **228px** — mesmo que isso seja 24% ou 45% da altura, que **não importa**.
2. **Peça estreita aperta a faixa:** numa peça de 420 de largura, o máximo é
   **64px** — exatamente a peça aprovada.
3. **O outro lado do eixo continua sendo julgamento humano:** faixa fina demais
   faz a cena desaparecer (em 32px o traço de trás cai a 1,50px), e isso é
   legibilidade de **forma** — gate visual, não guarda (§7.2 do canon).
4. **A % da altura continua impressa** em toda medição, como **diagnóstico**.

### 79.3 O que o instrumento faz

`validacao/guarda-grafismo.mjs`, na GR1-c: quando a âncora é **topo** e o regime é
**serra-cena**, o veredito é `F ≤ w / 6,5625`; a linha do placar imprime
`w/F` medido, o máximo em px para aquela largura, a largura da peça, **e** a % da
altura marcada como diagnóstico. **Duas fixtures novas** provam os dois lados:
`gr1c-topo-largura-conforme` (1500×500, faixa 120px = 24,00% da altura → **PASS**,
`w/F = 12,50`) e `gr1c-topo-largura-excede` (mesma peça, 300px → **FAIL**,
`w/F = 5,00`).

**Placar da pasta de prova: `24 PASS · 7 FAIL · 11 [n/a]`** em 14 fixtures
(idêntico na escala 2×). **Inércia: 6 de 7 fixtures antigas idênticas; a única que
mudou** — `gr1c-faixa-topo-na`, de `[n/a]` para **PASS** — **mudou porque ele
decidiu**, não por efeito colateral.

### 79.4 A régua de método que sai daqui

> **Quando um teto parece precisar de "vale só neste formato", a primeira pergunta
> não é "qual formato?" — é "o EIXO da medição está certo?".** Escopo apertado
> demais parece prudência e é, na prática, uma guarda que se **recusa a medir**:
> o `[n/a]` do topo era tecnicamente honesto e ainda assim reduzia a cobertura a
> **um único formato de peça**.

### 79.5 Pendências

- ✅ **GR-P8 fechada** (era: teto do topo em razão diferente de 1:1).
- Seguem abertas: **GR-P6** (divergência de área contra o instrumento perdido) ·
  **GR-P7** (estabilidade de captura, 1 de 56) · **GR-P9** (integridade das saídas
  do instrumento, 1 de 56) · **MR-P6** (grafismo em milímetros para impressão).


## 80. O TRAÇO DO GRAFISMO EM MILÍMETROS — a peça de tela e a peça de papel não têm a mesma régua · **`estável`** (2026-08-22, décima sexta parte) · canon `marca-seed.md` **v5.6 §7.4-c** · instrumento `validacao/mede-traco-mm.py`

> **Para quem lê isto sem o resto:** o **§76** especifica o grafismo como
> componente; o **§77**, o instrumento que o verifica (contrato GR1); o **§78**, os
> dois regimes de altura; o **§79**, o eixo de largura do topo. Esta seção trata da
> **unidade física do traço**.
>
> ⭐ **ESTADO: `estável` desde 2026-08-22.** Esta seção nasceu em **`rascunho`**
> horas antes, porque piso em milímetro é **valor de marca** e não existe até o
> decisor aprovar (regra **§0-b** do projeto). **Ele aprovou**, verbatim:
> *"saída B — piso de 0,25 mm por ESCALA, e só para peça IMPRESSA"*, e a regra está
> hoje no canônico `marca-seed.md` **§7.4-c (v5.6)**. *A seção fica com a medição do
> estado ANTES preservada no §80.3, porque é ela que explica por que o piso existe.*

### 80.0 A REGRA, para quem só precisa dela

**Em peça impressa, nenhum traço do grafismo fica abaixo de `0,25 mm` (≈ 0,71 pt).**
Quando o mais fino ficaria abaixo, **multiplique TODOS os planos pelo mesmo fator**
até o mais fino tocar o piso — **nunca eleve só o mais fino** (§80.4 regra 3).

- **peça impressa** = peça com **alvo físico declarado** (o cartão de visita, a pasta
  A4). Post, story, banner e capa **não entram** — *peça de tela não tem milímetro.*
- **vale para a SERRA.** O **marcador de seção** (risco amarelo, 2 px = 0,170 mm no
  cartão) **fica intocado até veredito próprio** — não é linha de serra.
- **quem executa:** `validacao/gen-z3-largos.py` (`PISO_MM_CANON = 0.25`,
  `PPMM_CARTAO = 1050/89`). ⚠ O `ppmm` é passado **só** nas chamadas de cartão — é
  assim que o escopo vive no código e não só na prosa.
- **quem confere:** `validacao/mede-traco-mm.py`, lendo o artefato renderizado.

**O estado aplicado, medido no cartão a 299,7 DPI:**

| | antes | depois |
|---|---|---|
| 4 planos | `2,12 / 2,41 / 2,90 / 3,38 px` | `2,95 / 3,35 / 4,03 / 4,70 px` |
| em mm | `0,180 / 0,204 / 0,246 / 0,286` | **`0,250 / 0,284 / 0,342 / 0,398`** |
| folga sobre o piso digital reverso (0,176 mm) | 2% | **42%** |
| pesos distintos | 4 | **4** |
| custo de aparência | — | a cena engrossa **~39%** (visto por ele com lupa 3× antes do veredito) |

### 80.1 O problema, em uma frase

**A lei do traço é em pixel** (`3,5 × F/86`, grampeada em `[1,5 · 4,5] px`), e o
grampo também. **Logo o traço físico de uma peça impressa não é propriedade do
desenho: é função da resolução em que a peça for exportada.** Dobrar o DPI de
exportação **divide o traço em mm pela metade** sem ninguém mexer em nada.

### 80.2 Os pisos de impressão (mercado, pesquisado — não é canon SEED)

| processo | POSITIVA | REVERSA (clara sobre escuro) |
|---|---|---|
| offset litográfico | 0,053 mm | 0,088 mm |
| digital (Indigo/Xeikon) | 0,088 mm | **0,176 mm** |
| serigrafia | 0,353 mm | 0,706 mm |

**Linha reversa exige o dobro da positiva** — a tinta espalha e fecha a linha clara.
⚠ **O verso do cartão é linha reversa por definição:** serra clara sobre `#005048`.

### 80.3 O estado medido das peças físicas — ⚠ ESTA TABELA É O **ANTES**

*Preservada porque é o diagnóstico que motivou o piso. Para o estado de hoje, §80.0.*

| peça | DPI real | regime | traço mais fino | veredito |
|---|---|---|---|---|
| cartão frente **L2** (aprovado) | 299,7 | positiva | 0,180 mm | passa offset e digital; reprova serigrafia |
| cartão verso **L3** (aprovado) | 299,7 | **reversa** | 0,180 mm | **passa o digital por 0,004 mm — 2% de margem** |
| cartão verso **F3** (7 formatos) | 299,7 | **reversa** | 0,102 mm | **REPROVA o digital nos 4 planos** |
| pasta A4 **F6** | **105,7** | positiva | 0,603 mm | passa em tudo — **mas por causa do DPI baixo do mock** |

**Peça de TELA (post · story · banner · capa) sai `[n/a]` NOMEADO.** *Peça de tela
não tem milímetro, e inventar um alvo físico seria pior que não ter.*

### 80.4 A gramática de consumo, enquanto o piso não existe

1. **Peça impressa exige a resolução declarada junto.** "Traço de 2,12 px" não diz
   nada sobre papel sem o par (tamanho físico, DPI).
2. **Verso escuro é linha reversa** e responde ao piso dobrado. Se a peça vai a
   digital, é ali que ela quebra primeiro.
3. **Não grampeie o traço por baixo para salvar impressão.** Grampo achata planos
   no mesmo peso e **destrói a hierarquia de peso e tom**, que é uma das três
   condições que fazem a linha ler como plano (§7.1 do canon). Medido: 4 pesos
   distintos caem para 2. **Se for preciso engrossar, engrosse a cena inteira por
   ESCALA.**
4. **Serigrafia hoje não é atendida por nenhuma peça de cartão** — nem a aprovada.
   Só a pasta A4 passa, e por causa do DPI do mock.

### 80.5 Fronteira declarada

- Este instrumento **não julga aparência** e **não cria piso**: mede e compara com
  piso de mercado.
- Ele **não sabe** qual processo a gráfica vai usar — quem sabe é quem compra. Por
  isso o placar sai **por processo**, não como um veredito único.
- **Não há guarda de alvo de pasta para isto ainda.** É medição sob demanda; virar
  guarda só faz sentido depois de existir piso de canon para comparar.

### 80.6 Pendências

- ✅ **MR-P6 FECHADA** em 2026-08-22 — o piso existe, está no canon (**§7.4-c**) e
  está aplicado. Folhas: `render-audit/gate-mr6/gate.html` (as três saídas que ele
  leu) e `render-audit/gate-mr6/fechamento.html` (antes × depois do que foi feito).
- **MR-P8** — pasta A4 a **105,7 DPI**; precisa ser regerada a **300 DPI
  (2480×3508 px)**. ⚠ **Virou consequência da regra:** peça impressa exportada em
  resolução de mock recebe o piso na resolução errada.
- **MR-P9 (nova)** — o cartão está em **89×51 mm** (medida americana, 3,5×2 in); o
  **padrão brasileiro é 90×50 mm**. Decisão de peça física é dele.

## 81. A MEDIDA FÍSICA DO CARTÃO — `90×50 mm`, padrão brasileiro · **`estável`** (2026-08-22, décima sétima parte) · canon `marca-seed.md` **v5.7 §7.4-d**

> **Para quem lê isto sem o resto:** o **§76** especifica o grafismo como componente;
> o **§77**, o instrumento que o verifica (contrato GR1); o **§78**, os dois regimes
> de altura; o **§79**, o eixo de largura do topo; o **§80**, o piso do traço em
> milímetros. Esta seção trata da **medida física da peça** — o retângulo de papel.

### 81.1 A REGRA

**O cartão de visita da SEED engenharia é `90 × 50 mm`** (9×5 cm). Peça de
referência: `render-audit/gate-z3/largos/L2-cartao-rodape-natural.html` (frente) e
`L3-cartao-verso.html` (verso), em **1062×590 px = 11,8 px/mm = 299,7 DPI**.
**Supersede os `89×51 mm`.**

| | |
|---|---|
| **medida** | **90 × 50 mm** |
| **px de referência** | **1062 × 590** (299,7 DPI) |
| **razão w/h** | 1,800 |
| **faixa da serra** | 84 px de 590 = **14,2%** (teto da §7.4) |
| **traço mais fino** | **0,250 mm** (piso da §7.4-c, intacto) |

### 81.2 Por que a medida mudou — e não foi preferência

**89×51 mm é `3,5 × 2 polegadas`:** o padrão de EUA e Canadá, e o default de
software de layout americano. **90×50 mm é o padrão dominante nas gráficas
brasileiras** — o que a gráfica corta sem pedir arquivo especial e o que
porta-cartão, carteira e álbum vendidos no país acomodam.

⚠ **A varredura do histórico não achou nenhum veredito escolhendo 89×51.** Ela
entrou porque estava no gerador. *Classe de defeito com nome: **valor herdado de
ferramenta que passa por decisão de marca**. Irmã do hex inventado da §7.5 do canon
— com a diferença de que este foi importado, não criado, e por isso nada parecia
errado.*

### 81.3 O que a troca custou — medido, três formatos, uma condição variada

⚠ Os três formatos foram gerados pelo **mesmo gerador**, no **mesmo px/mm**. Variar
resolução junto mediria duas coisas ao mesmo tempo.

| | A · 89×51 | **B · 90×50 (vigente)** | C · 90×48 |
|---|---|---|---|
| traço mais fino | 0,250 mm | **0,250 mm** | 0,250 mm |
| piso digital REVERSO (0,176 mm) | PASS | **PASS** | PASS |
| área do grafismo (teto 10%) | 1,51% | **1,54%** | 1,58% |
| faixa declarada | 13,83% | **14,24%** | 14,84% |
| GR1 nas 5 peças largas | 15·0·0 | **15·0·0** | — |
| acervo dos 7 formatos | 21·0·0 | **21·0·0** | — |

**A faixa subir de 13,83% para 14,24% NÃO é o desenho mudando.** A serra tem quase o
mesmo tamanho físico (7,0 → 7,1 mm); a peça é que ficou **1 mm mais baixa**. *Mudou
o denominador.*

**O único custo real: uma linha nova no teto da §7.4** — a tabela é indexada pela
razão da peça (tolerância 0,01), e a razão foi de 1,745 para 1,800.

### 81.4 A gramática de consumo

1. **Peça física se declara em MILÍMETRO e em PX, sempre no par.** "1062 px" não diz
   nada sobre papel sem o `90 mm` ao lado — é a mesma lição do §80.
2. **A cena da serra não se ajusta à mão quando o formato muda.** A lei é
   proporcional (`F = w / 12,7`): regere a peça e confira o placar. *Retoque manual
   em cena gerada é como um valor divergente nasce.*
3. **Formato novo exige teto novo no canon**, e o teto é veredito dele. A guarda
   devolve `[n/a]` com o motivo escrito até o teto existir — **não interpola**.
4. ⚠ **Compra online: confirme o formato no pedido.** O da Printi é **90×48 mm**.
   Dois milímetros viram corte. A variante está medida em
   `render-audit/gate-mr9/br-90x48/` e **não é canon** — é restrição de fornecedor.

### 81.5 Fronteira declarada

- **A família dos 7 formatos (`gate-z3/formatos`) NÃO foi convertida.** Ela segue na
  razão 1,745 e mede pela linha **herdada** do teto. Foi superseditada como cartão
  impresso pelas peças L2/L3 (§108.6) — *converter o acervo por completude seria
  trabalho sem consumidor.*
- **A pasta A4 não foi tocada** e segue a 105,7 DPI (**MR-P8**).
- **Sangria não existe em peça nenhuma deste projeto** (**MR-P10**) — e o verso do
  cartão é fundo cheio, logo ela vai ser exigida.

### 81.6 Pendências

- ✅ **MR-P9 FECHADA** em 2026-08-22.
- **MR-P10 (nova)** — **SANGRIA e margem de segurança.** Nenhum valor medido nem
  autorizado; não se resolve por analogia (§0-b).
- **MR-P8** — pasta A4 a 300 DPI (2480×3508 px).

## 82. CONTRATO MM1 — o piso de traço em milímetros, por ALVO DE PASTA · **`estável`** (2026-08-22, décima oitava parte) · instrumento `validacao/guarda-piso-mm.py` · canon `marca-seed.md` **§7.4-c (v5.8)**

> **Para quem lê isto sem o resto:** o **§76** especifica o grafismo (a serra de 3–4
> planos de linha que assina as peças); o **§77** o contrato **GR1**, que mede área,
> véu do glifo e faixa **pelo PIXEL**; o **§80** o **piso de traço em milímetros**
> que o decisor aprovou; o **§81** a medida física do cartão. Esta seção é o
> **instrumento** que faz o piso do §80 valer sem depender de alguém lembrar.

### 82.1 O que ele julga

| cláusula | exige | de onde vem o número |
|---|---|---|
| **MM1-a** | traço mais fino **≥ 0,25 mm** | **canon §7.4-c** — veredito *"saída B"* |
| **MM1-b** | a razão do **alvo físico declarado** casa com a razão em **px** (tol. 1%) | coerência interna do artefato — tolerância de **instrumento** |
| **MM1-c** | a cena tem **≥ 3 pesos DISTINTOS** | **canon §7.1 ③** + **§7.3** (serra-cena = 3–4 planos) |
| **MM1-d** | arquivo de **PRODUÇÃO** declara sangria **≥ 3 mm** | **canon §7.4-e** (v5.10) |
| **MM1-e** | conteúdo a **≥ 3 mm** da linha de corte | **canon §7.4-e** (v5.10) |

**Saída:** placar `PASS / FAIL / [n/a]` por cláusula, `COBERTURA: n de n`, JSON
opcional (`JSON=…`) e **exit code** (0 = sem FAIL). ⚠ *Confira o exit code: o 48º
defeito desta casa foi um placar de texto completo com o JSON derivado morrendo em
silêncio.*

### 82.2 A decisão de arquitetura — e ela é o coração da seção

**Quem é peça impressa é decidido pelo que o ARTEFATO DECLARA:**

```html
<meta name="alvo-fisico" content="90x50mm">
```

Escrito pelos geradores nas peças físicas. A guarda lê isso do arquivo. **Nenhuma
lista de nomes, nenhum filtro por prefixo.**

⚠ **Por que isso é regra e não estilo:** filtro por nome de arquivo tem a
**aparência** de alvo de pasta e é a origem documentada de dois defeitos deste
projeto — o **42º** (filtro por prefixo deixou **17 de 51** arquivos nunca medidos) e
o fechamento da **BT-P2** (declarado sobre **dez** artefatos num acervo de **trinta e
quatro**). **Peça sem a declaração sai `[n/a]` NOMEADO, nunca PASS:** peça de tela
não tem milímetro, e inventar alvo físico para ela seria pior que não ter.

### 82.3 Por que três cláusulas e não uma — as fixtures provam

| fixture | o que ela é | resultado |
|---|---|---|
| `mm-b1-FALHA-alvo-incoerente` | alvo físico **mentiroso** (210×297mm numa peça 1062×590px) | **passa o piso com 0,583 mm** e falha MM1-b. *O milímetro sai errado e nada parece errado* |
| `mm-c1-FALHA-hierarquia-grampeada` | o **GRAMPO** — a saída que o decisor recusou | **passa** MM1-a e falha MM1-c: 4 planos, 2 pesos. *MM1-a sozinha salvaria o número e perderia o desenho* |
| `mm-a3-BORDA-exatamente-no-piso` | 0,2500 mm **cravado** | **PASS** — o piso é **inclusivo** |

Pasta de prova: `render-audit/prova-mm/` (gerador `validacao/gen-prova-mm.py`),
**7 fixtures, uma cláusula por fixture**, com o veredito peça-por-peça impresso
**antes de o instrumento existir**. Placar de referência: **13 PASS · 3 FAIL ·
5 [n/a]**. ⚠ *Este placar é CONTRATO: se ele mudar sem uma fixture ter mudado, a
guarda regrediu.*

### 82.4 O que a estreia achou — inclusive um defeito real do gerador

A primeira execução **reprovou em bloco** as 3 peças de cartão aprovadas e a pasta
A4. ⭐ *Régua da casa, aplicada pela sexta vez: quando uma régua nova reprova em
bloco, a primeira hipótese não é "o acervo está ruim", é "a régua está medindo outra
coisa".*

1. **Defeito da RÉGUA:** MM1-c exigia "pesos distintos == número de FORMAS". **A
   serra real tem 5 formas para 4 planos** — a linha da frente entra e sai da peça em
   segmentos, e dois segmentos compartilham o peso do mesmo plano. Corrigido para
   **≥ 3 pesos distintos** (canon §7.3).
2. **Defeito do GERADOR, e é real:** na pasta A4 (11,80952 px/mm) o piso vale
   2,95238 px e `round(...,2)` devolvia **2,95 px = 0,24980 mm** — **abaixo do
   canon por 0,0002 mm**. Corrigido: o **menor** peso arredonda para **CIMA**.
   *Piso violado por arredondamento é piso que não existe.* Virou esclarecimento
   normativo no canon **v5.8**.

⚠ **Quem achou o defeito 2 foi a guarda, na primeira vez que rodou** — o
`mede-traco-mm.py` rodado à mão imprimia "0,250 mm" arredondado e ninguém veria.
*É o argumento inteiro a favor de instrumento sobre conferência à mão.*

### 82.5 Estado do acervo

| pasta | MM1 | leitura |
|---|---|---|
| `gate-z3/largos` | **9 PASS · 0 FAIL · 6 [n/a]** | 3 cartões medidos; banner e capa são tela |
| `gate-z3/formatos` | **3 PASS · 0 FAIL · 18 [n/a]** | só a pasta A4 é impressa nesta família |
| `gate-montanha` | **0 PASS · 0 FAIL · 18 [n/a]** | tudo tela — e isso é o certo |
| `gate-mr9/br-90x50` | **0 PASS · 0 FAIL · 15 [n/a]** | ⚠ gerado antes de a meta existir (**MM-P1**) |

### 82.6 Fronteira declarada

- **Não renderiza.** Lê o `stroke-width` **declarado**. ⚠ *CSS que sobrescreva o
  `stroke-width` passa invisível; nenhum gerador desta casa faz isso hoje, e se
  passar a fazer, a cláusula tem de virar medição de pixel.*
- **Não exige DPI mínimo** — não existe piso de resolução no canon (§0-b). O DPI é
  **diagnóstico**.
- **Não julga aparência** e **não sabe qual processo a gráfica usa** (isso é o
  diagnóstico do `mede-traco-mm.py`, contra pisos de mercado).
- **Não há folha de conferência**, de propósito: o produto é placar e exit code.
  *Folha existe quando há aparência a julgar.*

### 82.7 Pendências

- **MM-P1 (nova)** — varredura de quais artefatos do acervo são fisicamente
  impressos e **não declaram** alvo físico. Hoje eles saem `[n/a]`, o que é honesto
  mas não é cobertura.
- **MR-P10** — **sangria**, com decisão dele pendente.

### 82.8 As duas cláusulas da SANGRIA — MM1-d e MM1-e (novas na v5.10)

**A regra que elas fazem valer** é o canon **§7.4-e**: em peça impressa, **3 mm de
sangria** e **3 mm de margem de segurança**, com a composição definida para a **área de
corte** e a sangria como **crescimento para fora** — os dois vereditos dele,
`"acredito que 3+3"` e `"deixar o desenho dentro da area real, e a parte da sangria vc
cresce o desenho"`.

**O que muda no consumo, e é a parte que o time precisa saber:**

| tipo de arquivo | tamanho | quem mede | declara no artefato |
|---|---|---|---|
| **GATE** | área de corte | **GR1** (área · véu · faixa) e o olho do decisor | `papel=gate` |
| **PRODUÇÃO** | corte + 3 mm | **MM1-d** e **MM1-e** | `papel=producao` · `corte-px` · `sangria` |

⚠ **Nunca coloque um arquivo de produção na pasta de gate.** A GR1 mediria a tela com
sangria (razão 1134/662 = 1,713), que não casa com nenhum teto da §7.4, e devolveria
`[n/a]`. *Defeito que não é defeito, produzido por medir a coisa certa no lugar errado.*

**Como se gera o arquivo de produção:**

```
PRODUCAO=1 DEST_LARGOS=producao/cartao-90x50 python3 validacao/gen-z3-largos.py
```

⚠ **Peça de TELA é PULADA** nesse modo, e com aviso impresso: *peça de tela não tem
corte nem sangria, e escrevê-la ali criaria um arquivo que parece de gráfica e não é.*

**Três coisas medidas que valem como regra de consumo:**
1. **A sangria em px arredonda para CIMA** (3 mm × 11,8 = 35,4 → **36 px = 3,05 mm**).
   *Sangria curta é sangria que não existe* — mesma regra do piso de traço (§7.4-c).
2. **O layout aprovado cabe em 3 mm sem mexer:** conteúdo a **4,24 mm** do corte,
   folga de 1,24 mm. Contra 5 mm faltariam 0,76 mm e o conteúdo teria de ir de `50px`
   para `59px` — **mudaria a composição aprovada**.
3. **Sangria e margem protegem coisas diferentes.** A fixture `e1` prova: sangria
   correta **e** conteúdo a 2,03 mm do corte → passa MM1-d e **falha MM1-e**.
   *Sangria evita fio branco; margem evita nome decepado.*

⚠ **Defeito de instrumento registrado:** a primeira versão da MM1-e reportava a
**própria linha de corte** como "o conteúdo mais perto do corte" — a 3,05 mm — e
**passava por acidente**, escondendo a distância real do texto. *Régua que mede a si
mesma sempre passa.* Corrigido: `.corte` e `.card` são moldura, não conteúdo.

### 82.9 Fronteira nova: peça com DOBRA não está coberta

**`MR-P12`.** Peça com **aba, vinco ou furo** tem margem própria — a fonte pesquisada
dá, como exemplo, **15 mm** no lado da espiral em encadernação wire-o. A **pasta A4 com
aba** é o primeiro caso do acervo e **não existe como arquivo de produção**: o que
existe é uma face plana a 300 DPI, marcada `papel=gate`. *Medir dobra é frente nova, e
depende de ele decidir se a pasta vira arquivo de produção.*

---

## 83. TELA A19 — AUTENTICAÇÃO (entrar · recuperar · criar conta) · **`estável`** (2026-08-23) · **fecha a CD-P5** · tela `05-html-de-referencia/telas/tela-autenticacao.html` v0.2 · instrumento `06-validacao/suites/suite-autenticacao.mjs` (**21 PASS · 0 FAIL · 0 [n/a]**)

> **O que é:** a primeira tela da F7.6 (ordem aprovada no consolidado de
> 2026-08-22: A19 → A20 → A17 → A18 → A14 → A13). Promovida por gate visual do
> Rafael em 2026-08-23 com UMA correção, verbatim: *"a tela de login deve ter o
> icone da seed no lugar do botao turquesa. fazendo essa correção, está
> aprovado"* — aplicada antes da promoção (ver AU-13). A tela **não inventa
> componente**: compõe o §66 (credenciais CD1–CD16), o §2/§3.5 (form-field e
> norma de senha), o §1 (botão), o §1.13/1.13-b (live region) e o gabarito A19
> da F7.1 (`seed-composicao.md` §3: um cartão centrado, coluna única, 420px
> máx, par sombra+anel; <600px vira largura total com calha mínima).
> **Leitura de referência:** `09-pesquisa/leitura-f76-bordas.md` (2026-08-22)
> — ClickUp público (login/forgot/signup, MEDIDO) + shadcn login-01 (MEDIDO,
> a stack do ERP). Onde a norma decide, a spec mede contra a norma (precedente
> do §66.0); a leitura serviu à COMPOSIÇÃO.

### 83.1 Contratos (a suíte mede todos por comportamento, nunca por regex)

- **AU-01** — `<form>` REAL por vista (4: entrar · recuperar · código · criar)
  e **nenhum botão de submit nasce desabilitado** (§2.6; o ClickUp nasce
  desabilitado a 1,4:1 de contraste e reprova nesta régua).
- **AU-02** — e-mail: `type=email` + `autocomplete=username` +
  `inputmode=email` + `autocapitalize=none` + `spellcheck=false` (CD2/§2.6) —
  nos TRÊS formulários que pedem e-mail.
- **AU-03** — senha de entrar = `current-password`; senha de criar =
  `new-password`; **nenhuma senha tem `maxlength`** (CD4). Código de
  verificação = `one-time-code` + `inputmode=numeric` + `pattern`/`maxlength`
  legítimos (CD11 — código tem forma fechada, o oposto da senha).
- **AU-04** — revelar senha é `button[type=button]` com
  `aria-pressed`/`aria-controls` (CD6), a visibilidade é ANUNCIADA na live
  region (CD7), o SUBMIT devolve a `type=password` (CD8) e a senha NASCE
  oculta (CD9).
- **AU-05** — todo input tem `<label for>` VISÍVEL (§2) — placeholder nunca é
  rótulo (defeito do vizinho, lido e rejeitado).
- **AU-06** — erro de CREDENCIAL: o container `role=alert` **existe no DOM
  vazio e oculto ANTES do conteúdo** (1.13-b) e a mensagem fala do PAR
  ("E-mail ou senha incorretos"), **nunca de qual metade errou** (CD13). Erro
  de FORMATO diz e aponta o campo (blur, B4; `aria-invalid` sempre com valor
  explícito, §2.9).
- **AU-07** — código em **UM campo**, nunca N caixinhas (CD10).
- **AU-08** — o prazo do código é DECLARADO em texto (mono para o número) e o
  reenvio existe como ação terciária (CD12).
- **AU-09** — criar conta **sem "confirme a senha"** (CD5/SC 3.3.7); força da
  senha em texto + forma, **nunca bloqueia o envio** (CD14).
- **AU-10** — `lang=pt-BR`, um `<main>`, um `h1` por vista (o ClickUp tem
  ZERO h1/main na tela de login), uma vista visível por vez, e **a troca de
  vista move o foco ao h1** da vista nova.
- **AU-11** — live region única `role=status aria-atomic`, visualmente oculta
  e separada do nó visível (§1.13).
- **AU-12** — **o símbolo da marca é o ASSET OFICIAL, byte-idêntico**: os
  paths de `03-assets/simbolos/simbolo-principal.svg` (tema claro) e
  `simbolo-mono-negativo.svg` (tema escuro — tabela fundo×variante do
  `marca-seed.md` §5.1) foram INJETADOS POR SCRIPT na tela, nunca
  redigitados; a suíte confere o path âncora contra o arquivo do asset.
- **AU-13** *(veredito do gate)* — a identidade acima do cartão é o símbolo
  oficial + "SEED engenharia" em texto. A sigla geométrica genérica das telas
  internas NÃO serve à porta do produto.
- **AU-14** — Caps Lock ligado no campo de senha gera AVISO visível
  (`warning`, nunca `danger`) e anunciado, que morre no blur (CD15).

### 83.2 Decisões de composição (com a leitura que as sustenta)

Cartão, não canvas aberto (gabarito F7.1; shadcn confirma; o CU diverge) ·
"Esqueceu a senha?" na LINHA do rótulo da senha, à direita (shadcn) · SSO
("Continuar com o Google") DEPOIS do formulário e do divisor "ou" — o método
primário da casa é o e-mail corporativo · botão de recuperação nomeia o
EFEITO ("Enviar link de recuperação") e o de criação nomeia o MÉTODO ("Criar
conta com e-mail") — dispositivos lidos no CU e adotados · título da
recuperação é PERGUNTA ("Esqueceu a senha?") · confirmação de envio NEUTRA
("Se o e-mail existir na conta…") — não revela existência de conta · termos
como texto de continuação (redação final = produto/jurídico, LGPD) · **v0.2
sem faixa de marca**: a âncora da tela é o botão primário turquesa (PN21-05:
UMA âncora por página); o CP19 (faixa) segue PERMITIDO pelo gabarito como
variante, se um gate futuro a pedir.

### 83.3 Fronteiras (produto/Supabase — nada disto é do DS)

Sessão, redirecionamento, rate-limit, política de senha (§3.5/NIST),
"lembrar-me", MFA/2FA de produto, seleção de workspace multi-empresa (SI17-e),
redação legal dos termos e os E-MAILS do fluxo (EM15: o template canônico em
HTML puro é exatamente o que o Supabase Auth aceita — quando a decisão vier, é
colar). O deep-link `#vista=…&tema=…&estado=…` da tela é INSTRUMENTO de
avaliação (headless/suíte), não spec — em produto, vista é rota.

### 83.4 O que a produção pegou (instrumento e método)

1. **Container query não estiliza o próprio container** — a primeira versão
   punha `container-type` no `.palco` e mirava `.palco` dentro do
   `@container`: nunca disparava. Pego por MEDIÇÃO em navegador real a 390px
   (padding seguia 24px); o container subiu para o `body`. Medido depois:
   calha 12px, cartão 366px, `scrollWidth` = viewport.
2. **O screenshot do Chrome CLI a 390px inventou um overflow** (link
   "vazando" da tela) que o navegador real desmente — REINCIDÊNCIA da
   armadilha catalogada no marco v1.9: render de prova é navegador
   real/puppeteer, nunca o CLI `--screenshot`.
3. **Ambiente de suíte nesta máquina:** o `G:` (Drive File Stream) rejeita
   junction e `node_modules` dentro do stream é inviável; o jsdom vive em
   `C:\Users\seed\AppData\Local\Temp\claude\jsdom-env` e a suíte o resolve
   por caminho (env `JSDOM_DIR`). ⚠ As suítes antigas (`import 'jsdom'`
   puro) seguem sem rodar aqui — pendência de ambiente **AU-P1**.
4. Defeito pego no pré-olhar: link "Esqueceu a senha?" herdava o AZUL default
   do UA no tema escuro (seletor de cor não cobria `.linha-rotulo a`) —
   consertado e reconferido.

### 83.5 Pendências que a seção deixa

- **AU-P1** — ambiente: as ~60 suítes antigas não rodam nesta máquina (jsdom
  fora do repo; ver 83.4-3). Decidir o padrão da casa: env `JSDOM_DIR` em
  todas, ou ambiente node dedicado fora do stream.
- **AU-P2** — a variante COM faixa de marca (CP19) não foi desenhada; gatilho:
  gate futuro pedir a versão de marketing da porta.
- **AU-P3** — o fluxo multi-empresa pós-login (seleção de workspace, SI17-e)
  é tela de PRODUTO; se o ERP pedir, nasce como extensão desta seção com
  leitura própria.

---

## 84. TELA A20 — ONBOARDING / PRIMEIRO USO · **`estável`** (2026-08-23, gate dele: *"a imagem da onboarding está ok. aprovo"*) · tela `05-html-de-referencia/telas/tela-onboarding.html` v0.1 · instrumento `06-validacao/suites/suite-onboarding.mjs` (**14 PASS · 0 FAIL · 1 [n/a] declarado**)

> **O que é:** a segunda tela da F7.6. O gabarito da F7.1 define o arquétipo
> como COMPOSIÇÃO, não peça nova: *"herda o canvas do arquétipo que ainda não
> tem dado; o vazio de primeiro-uso (§25/Z1) ocupando o slot da peça ausente;
> ilustração colapsa antes do texto (<600px)"*. O hospedeiro é o PAINEL (§48)
> e o consumidor real declarado é o primeiro acesso ao ERP. A tela põe a
> taxonomia Z1 — já `estável` desde o Bloco 3 — **viva em composição**, nos
> três estados que a leitura provou não serem intercambiáveis (CU §6.1:
> *"um vazio de onboarding sem ilustração parece uma tela quebrada"*; VdIA
> §3.13: o vazio ensina).

### 84.1 Contratos (medidos por comportamento na suíte)

- **ON-01** — a tela abre em PRIMEIRO USO, um estado visível por vez; o
  primeiro-uso é educativo + CTA primário de criação (Z1).
- **ON-02** — **Z5 vivo**: EEny presente em primeiro-uso e esvaziado,
  **AUSENTE em sem-permissão** (ícone neutro no lugar); toda ilustração é
  `aria-hidden`.
- **ON-03** — **o vazio ENSINA**: o principal diz o critério de preenchimento
  ("tudo o que a equipe cadastrar aparece neste painel…" — A7 do VdIA /
  PN21-16); os slots menores usam vazio TEXTUAL seco (tratamento B da
  leitura) dizendo O QUE os preenche, sem CTA — uma âncora por página.
- **ON-04** — os três estados têm título, texto e ação PRÓPRIOS (Z1);
  sem-permissão esconde os slots e diz o CAMINHO de desbloqueio (quem/onde).
- **ON-05** — a ilustração colapsa ANTES do texto <600px (§25.4-7) —
  [n/a] no jsdom (container query é render), **provado em navegador real a
  390px**: `display:none`, app empilha, zero overflow.
- **ON-06** — vazio é estático: NENHUMA live region própria (Z6); só o
  anúncio global `.vh`.
- **ON-07** — hero herda a saudação ciente da hora do painel (PN21c).
- **ON-08** — **os EEny são os ASSETS OFICIAIS byte-idênticos**
  (`03-assets/mascote-eeny/` pose-01 no primeiro-uso, pose-04 no esvaziado,
  injetados por script; a suíte confere o raster contra o arquivo).
  Esclarecimento de termo, do gate: *"byte-idêntico" é fidelidade
  EMBED↔ASSET, nunca igualdade entre poses — as 8 poses têm 8 MD5 distintos,
  medidos em 2026-08-23 a pedido dele.
- **ON-09** — o h1 do hero NÃO carrega número: não existe KPI antes do
  primeiro dado (PN21-06b por vacuidade).

### 84.2 Fatos de produção que viram regra de consumo

1. **Escolher pose de mascote exige OLHAR o pose**: o pose-05 (o mais leve)
   é o EEny DE COSTAS e parecia decapitado na tela; e o desenho ocupa só
   ~35%×49% do canvas 1773². Régua: **medir o bounding box por pixel antes
   de embutir** e, quando o canvas tem margem gorda, usar **crop de
   APRESENTAÇÃO no viewBox do embed** — o desenho fica intocado.
2. Peso: a tela carrega 242KB (dois rasters do mascote) — aceitável em
   gabarito interno; peça PÚBLICA com EEny usa asset otimizado (MK12).
3. A 1ª versão da régua ON-03a reprovou a tela CORRETA: o texto quebra linha
   no fonte e o regex exigia espaço literal — *whitespace ≠ espaço*; régua
   corrigida com o defeito anotado nela.

### 84.3 Fronteiras (do consolidado aprovado)

Tour guiado/coach marks — FORA (nenhuma fonte tem leitura; se o produto
pedir, nasce com leitura própria) · persistência do "já vi"/dispensa =
produto (CN4) · wizard multi-página = §38/produto · o deep-link
`#estado=…&tema=…` é instrumento de avaliação, não spec.

---

## 85. TELA A17 — BUSCA / RESULTADOS · **`estável`** (2026-08-23, gate dele: *"fora isso está aprovado"*, com a correção HV1 aplicada antes da promoção) · tela `05-html-de-referencia/telas/tela-busca.html` v0.2 · instrumento `06-validacao/suites/suite-busca.mjs` (**15 PASS · 0 FAIL · 1 [n/a] declarado e provado em navegador real**)

> **O que é:** a terceira tela da F7.6 — a TELA DE RESULTADOS que faltava do
> arquétipo (o §39/⌘K cobre o gatilho; o §4 cobre o campo; a superfície de
> resultados nunca existiu). Gabarito F7.1: canvas `surface-subtle`, a lista
> de resultados INTEIRA é peça (herda o arquétipo lista), os filtros são
> RÉGUA, <600px filtros viram gaveta e o realce do termo permanece.

### 85.1 Contratos (suíte por comportamento + CSSOM; nunca regex)

- **BU-01** — a query é ECOADA no título (aspas semânticas `<q>`) e a
  contagem de resultados é SEMPRE visível junto dele (D4-b do §4).
- **BU-02** — o realce do termo é **sublinhado + peso 800** — nunca só cor
  (gabarito F7.1; confirmado na leitura do produto de referência); o fundo
  suave `brand-subtle` é reforço, não portador. Medido no pixel: `underline`
  + `800` no navegador real.
- **BU-03** — resultado = UM LINK com ícone de tipo + título realçado +
  contexto ("Ordem de serviço · em Obras › …") + idade relativa em mono;
  lista semântica `<ol>`.
- **BU-04/05** — filtros por DIMENSÃO com contagem de ativos (TD1); chips
  aplicados em linha própria e o × do chip **remove o filtro E desmarca a
  origem, refaz a contagem e volta à página 1** (TD2/TD3/PG4).
- **BU-06** — zero-resultado com a microcopy D9 do contexto ERP (query +
  escopo ecoados) e as DUAS saídas: [Limpar busca] [Buscar em todo o sistema]
  (§25 Z1).
- **BU-07** — o espelho FALADO é nó separado (`role=status aria-atomic`,
  `.vh`) com debounce ~300ms — o visível muda na hora (D4-b); o debounce é
  MEDIDO por espera real na suíte.
- **BU-08** — filtro é input RESTRITO: zero texto livre por dimensão (TD5).
- **BU-09/10** — <600px os filtros colapsam em GAVETA sob disclosure
  (`aria-expanded` espelhado); o colapso é render — provado em navegador
  real (régua `display:none`, gatilho visível, clique abre, zero overflow).
- **BU-11** — paginação `<nav>` nomeada com `aria-current` (§37).
- **BU-12** — **HV1 (§71) na tela**: hover e pressionado DECLARADOS para
  paginação, filtros, botões e resultados — ver 85.2.

### 85.2 O gate pegou a HV1 de novo — e o conserto alcançou as TRÊS telas

No gate desta tela o Rafael perguntou, sobre o botão da página 2: *"pelo que
definimos era pra ter um efeito sobre o botao?"* — **tinha**: é a HV1-a
(§71), nascida de gate DELE em 2026-08-19, e as três telas da F7.6 nasceram
violando (paginação/filtros sem hover; NENHUM controle com pressionado
distinto). Conserto na mesma janela, retroativo às três:
**hover** de controle neutro = fundo desce um degrau (`surface-sunken`);
hover de controle de fundo de marca = **véu de 8%**; **pressionado = véu de
12%** (monotônico press > hover — o padrão eBay/M3 citado no §71.2); véu por
`box-shadow inset` (funciona sobre qualquer fundo, nos dois temas); hover só
sob `@media (hover: hover)` (§71.3 — toque não tem sobrevoo) e o `:active`
FORA da media (toque também pressiona). HV1-c: fundos novos são tokens já
medidos; véus escurecem fundo sob tinta clara (contraste sobe) e mantêm
≥4,5 sob a tinta escura do primário no escuro.
⚠ **Defeito de instrumento no caminho (49º da série):** o check CSSOM das
suítes reprovou as três telas JÁ CORRETAS — em CSSOM moderno, style rule
também tem `.cssRules` (CSS Nesting), vazia mas truthy, e o flatMap ingênuo
a substituía por LISTA VAZIA, engolindo as regras `:active` de topo. *Régua
nova que reprova em bloco mede outra coisa* — reincidência da lição do §75.
**O juiz do PIXEL do hover segue sendo a `guarda-resposta-ponteiro`**,
pendente de ambiente nesta máquina (AU-P1); a suíte confere a DECLARAÇÃO.

### 85.3 Fronteiras (produto)

Relevância/ranking, índice de busca, fuzzy, conectores externos (as abas de
fonte do produto de referência), "Pergunte à IA" (slot do chat, F6) e a
**virtualização de lista longa** — regra AUSENTE no acervo, registrada como
pendência **BU-P1** (a tela de resultados é o caso canônico de lista longa).
Dados da demo 100% fictícios, sem nome de pessoa (LGPD).

---

## 86. TELA A18 — CONFIGURAÇÕES / PREFERÊNCIAS · **`estável`** (2026-08-23, gate dele: *"aprovo"*) · tela `05-html-de-referencia/telas/tela-configuracoes.html` v0.1 · instrumento `06-validacao/suites/suite-configuracoes.mjs` (**18 PASS · 0 FAIL · 1 [n/a] provado em navegador real**)

> **O que é:** a quarta tela da F7.6 — era um dos DOIS arquétipos sem NENHUMA
> leitura de referência, fechada pela volta dirigida de 2026-08-22
> (`leitura-f76-bordas.md` §3). Gabarito F7.1: navegação lateral de seções
> (peça) + cartão por grupo (peça); <600px a lateral vira ABAS — nunca some.

### 86.1 Contratos

- **CF-01/02** — settings é ROTA própria: nav lateral nomeada, grupos com
  micro-rótulo, item ativo em pastilha (`aria-current=page`), e **"Sair"
  isolado no rodapé da navegação** (destrutiva isolada — leitura CU +
  convergência com o popover de conta do VdIA §3.16).
- **CF-03** — duas colunas por grupo (nome+descrição × campos); rótulo
  visível em todo campo (§2).
- **CF-04 — DOIS REGIMES DE GRAVAÇÃO** *(a decisão que o consolidado deixou
  em aberto, tomada aqui)*: **formulário = salvar EXPLÍCITO** — editar cria
  a barra de dirty (Salvar · Descartar), salvar CONFIRMA no próprio lugar
  ("Salvo.", lido no CU) e a barra se recolhe; descartar REVERTE; o salvar
  NUNCA nasce desabilitado (§2.6). **Switch de preferência = aplicação
  IMEDIATA** + anúncio na live region ("Aplicado") — o §8 já dizia
  "configuração é Switch", e switch com botão de salvar cria o estado
  enganoso ligado-mas-não-salvo.
- **CF-06** — grupo com CHIP DE ESTADO que ESPELHA os controles (2FA
  "Habilitada" ↔ "Desativada", medido pela suíte nos dois sentidos).
- **CF-07** — preferência de detalhe grande por PRESET (select restrito
  Padrão/Personalizado); a engrenagem de detalhe só existe no Personalizado.
- **CF-08** — recurso indisponível declara o estado + correção inline.
- **CF-09** — <600px: a lateral vira ABAS com AS MESMAS seções (comparação
  1:1 na suíte); provado em navegador real (lateral `none`, abas `flex`,
  grupo em 1 coluna, zero overflow a 390px).
- **CF-10** — HV1 (§71) desde o nascimento — a primeira tela da F7.6 que já
  nasce conforme (hover degrau/véu 8% · pressionado véu 12%).
- **CF-11** — check de LGPD NA SUÍTE: a persona da demo é fictícia e a régua
  reprova se dado real entrar.

### 86.2 Fronteiras (produto)

Persistência (PC6: declarada POR GABARITO) · guard de rota ao sair com dirty ·
o conjunto real de seções · 2FA de verdade (Supabase).

### 86.3 Registro de instrumento

A régua CF-09a saiu confusa na 1ª versão (`a && b || c` — precedência
traiçoeira que passava pelo fallback) e foi REESCRITA como comparação 1:1 das
seções antes da promoção. *Régua que passa por acaso é régua que mente.*

---

## 87. TELA A14 — FEED / ATIVIDADE · **`estável`** (2026-08-23, gate dele: *"aprovado"*) · tela `05-html-de-referencia/telas/tela-atividade.html` v0.1 · instrumento `06-validacao/suites/suite-atividade.mjs` (**16 PASS · 0 FAIL · 0 [n/a]**)

> **O que é:** a quinta tela da F7.6, no escopo aprovado no consolidado (P3):
> ATIVIDADE/HISTÓRICO GLOBAL — consumidor real: o histórico de projeto e
> manutenção do ERP (a lacuna "timeline/diário" do censo do VdIA). NÃO é a
> central de notificações (§33 segue dona do inbox; CN7: "vista/seen" não
> existe no DS).

### 87.1 Contratos

- **AT-01/02** — a INVERSÃO (CP2-b/C33) aplicada com a condição de admissão
  MEDIDA: agrupador de período em `surface-sunken` SEM sombra ("o delta de
  superfície agrupa"), objetos internos como peças claras; o agrupador não é
  acionável, cada objeto é.
- **AT-03** — agrupamento por TEMPO RELATIVO e, dentro, por OBJETO (C82/C83).
- **AT-04** — os DOIS PESOS do §56: evento de sistema = LINHA (sem avatar,
  sem ações — "registra, não conversa", CM2); comentário = BLOCO com avatar
  (CM1: bloco, NÃO cartão — zero borda/sombra/raio, medido por CSSOM).
- **AT-05** — a alteração é FRASE com antigo → novo INLINE (C84), nunca
  tabela de diff.
- **AT-06** — CM7: a sequência colapsa atrás de disclosure com a CONTAGEM no
  rótulo e `aria-expanded` espelhado.
- **AT-07** — CM11: "só comentários" esconde eventos; o contador conta
  COMENTÁRIOS e não muda ao filtrar (medido nos dois sentidos).
- **AT-08** — CM6: @menção é link inline em cor de link — nunca chip.
- **AT-09** — PN20: todo horário em JetBrains Mono, e permanece no mobile.
- **AT-10 — C27 ganha a primeira SPEC de tela**: a timeline vertical (fio de
  1px `border-subtle` + marcador de 9px por entrada; o marcador do comentário
  é distinto, em tinta de marca) — fecha a lacuna "existe no painel como
  `.tl`, sem spec".
- **AT-11/12** — HV1 desde o nascimento · check de LGPD (pessoas fictícias).

### 87.2 Divergência declarada e fronteiras

O produto de referência abre documento/detalhe como overlay; ESTA tela é
ROTA — atividade global é destino de auditoria, não espiada. Fronteiras
(produto): tempo real/push (CN4) · retenção · thread (CM-P1, segue sem
leitura) · anexo (CM-P2) · virtualização (classe BU-P1).

---

## 88. TELA A13 — DOCUMENTO / LEITURA · **`estável`** (2026-08-23, gate dele: *"aprovo"*) · **FECHA A F7.6 — 6 TELAS DE 6** · tela `05-html-de-referencia/telas/tela-documento.html` v0.1 · instrumento `06-validacao/suites/suite-documento.mjs` (**15 PASS · 0 FAIL · 0 [n/a]**)

> **O que é:** a última das seis telas da F7.6, no escopo aprovado (P4): a
> tela de LEITURA de documento — laudo/relatório renderizado — com o EDITOR
> declarado fora (ET-P3; gatilho: o ERP pedir edição). O consumidor real é a
> família mais SEED (laudo, memorial, O&M), e é aqui que os gráficos
> impressos da F5-DI ganham a tela que os hospeda.

### 88.1 Contratos

- **DC-01/02/03** — C25 vivo: o canvas É a tela (surface-page chapado, zero
  peça dentro do documento) · coluna de leitura trava em **660px** ·
  registro canônico da marca §4.1 (corpo 16px Light, entrelinha 1.65) — e a
  régua de recusa do §3 vale: "aproveitar a tela" é recusado.
- **DC-04** — sumário: navegação interna nomeada com âncoras que apontam
  para seções REAIS (a suíte confere os alvos).
- **DC-05** — C104: as ações do documento são LINKS com ícone, em linha,
  sem caixa (Baixar PDF · Imprimir · Histórico).
- **DC-06/07** — figura com rótulo acessível + figcaption + FALLBACK textual
  em tabela (DG1); o gráfico consome os tokens de dado por `var()` — zero
  hex assado no SVG (medido).
- **DC-08** — C106: callout tingido COM rótulo textual (1.4.1).
- **DC-09** — tabelas semânticas com caption; números em mono à direita.
- **DC-10 — O PAPEL**: `@media print` no padrão do DI com a **TINTA
  VIGENTE** (#0B3330/#2F5A54) — a errata do `banco-dataviz-di` (2026-08-22)
  virou LEI: a suíte reprova hex aposentado no bloco de print, e o PDF real
  foi MEDIDO nos bytes (zero aposentada). Fallback ABERTO no papel, blocos
  sem quebra, chrome de tela (controles/sumário/ações) não imprime,
  margem 18×16mm.
- **DC-11** — um h1 real, hierarquia h2/h3, um main, lang=pt-BR.
- **DC-12** — o rodapé técnico (responsável · CREA · ART) é PLACEHOLDER de
  template — o conteúdo real é da peça (Lei 5.194/66, plano de produção); a
  suíte reprova se dado real entrar.
- **DC-13** — HV1 desde o nascimento.

### 88.2 Divergência declarada e fronteiras

O produto de referência abre documento como OVERLAY com × (C101); aqui é
**ROTA** — laudo de engenharia é destino (auditoria, impressão, citação),
não espiada de colaboração. Fronteiras (produto/fases): o editor (ET-P3) ·
comentário ancorado em trecho (sem leitura) · versionamento/diff · a geração
do PDF (o print CSS é o contrato de forma) · export O&M da F8.

### 88.3 O fechamento da fase

**A F7.6 FECHA 6/6 em 2026-08-23**, produzida em DOIS DIAS de gates
encadeados sobre o consolidado aprovado em 2026-08-22 (P1–P5): A19
autenticação (§83) · A20 onboarding (§84) · A17 busca/resultados (§85) ·
A18 configurações (§86) · A14 feed/atividade (§87) · A13 documento (§88).
Seis telas, seis suítes (**101 PASS · 0 FAIL** somados no fechamento), uma
volta de leitura dirigida, o retroativo HV1, e três pendências novas que
ficam (AU-P1 ambiente das suítes antigas · AU-P2 variante com faixa ·
BU-P1 virtualização). Da F7 restam **F7.6 ✅ → F7.7 (instrumentação e
rigor)**, o último degrau antes de a fase fechar inteira.

---

## 89. MICROCÓPIA DE INTERFACE · **`estável`** (2026-08-24, F7.7-P4, cinco decisões dele no gate) · MC1–MC18

**O que esta seção é, e o que ela não é.** O `marca-seed.md` §10 governa a **voz
editorial** — display, eyebrow, hierarquia, respiro. Esta seção governa o
**rótulo de controle**: o texto que a pessoa lê para decidir o que clicar, o que
o erro diz quando algo falha, e o que a tela vazia explica. São camadas
diferentes: a §10 escreve para quem *lê*; a §89 escreve para quem *age*. Onde as
duas se tocam (tom, exclamação, emoji), **a §10 é a fonte e esta seção apenas
propaga** — regra mais antiga vence.

**Fontes lidas na rodada de 2026-08-24** (rodada de pesquisa própria, com
proveniência declarada arquivo por arquivo): Shopify Polaris (repositório, porque
o site redireciona e as content guidelines saíram dele) · Atlassian Design System
· IBM Carbon · Material Design 3 · Microsoft (Windows UX Guide, Fluent writing
style e o **Portuguese (Brazil) Localization Style Guide**, 56 páginas) · NN/g
(error messages, confirmation dialogs, empty states) · GOV.UK Design System ·
USWDS · Apple HIG · **GNOME Brasil, guia de estilo de tradução** · Mozilla L10n ·
Mailchimp. Mais o `estudo-clickup-completo.md` §9, que já tinha o produto de
referência MEDIDO em pt-BR.

> **Por que a pesquisa foi necessária, e o que ela mudou:** o §9 do estudo mediu
> um produto real e extraiu padrões que *pareciam* consenso. A rodada mostrou que
> três deles são **divergência declarada entre guias maduros**, não consenso — e
> que dois têm apoio público em fonte **pt-BR** que ninguém tinha consultado (o
> guia de localização da Microsoft e o guia do GNOME Brasil). Copiar o produto
> medido teria importado escolha alheia como se fosse lei. *Medir o vizinho diz o
> que ele faz; não diz o que nós devemos fazer.*

### 89.1 As cinco decisões do gate

| # | Decisão | Veredito dele | Alternativa descartada, e o número que a derruba |
|---|---|---|---|
| **MC1** | **Rótulo de ação = VERBO + SUBSTANTIVO**, sem artigo, sem pontuação. "Criar proposta", "Adicionar unidade consumidora". O substantivo cai **somente** dentro de diálogo ou painel cujo título já o nomeia | **(a)** | *Verbo nu* ("Criar", "Editar"), que é o **Polaris** — e ele proíbe a forma (a) por redundância. Cai porque três botões "Criar" em três telas soam idênticos para quem navega por teclado ou leitor de tela, e o rótulo perde sentido fora de contexto. Apoio da forma adotada: Microsoft (objeto direto quando o objeto não é evidente), Atlassian ("comece com o verbo e especifique o que está sendo agido") e a prática pt-BR de menu do GNOME Brasil |
| **MC2** | **Capitalização = SENTENCE CASE** em todo rótulo, menu, aba e cabeçalho de ação: "Adicionar unidade consumidora", "Configurações da conta" | **(a)** | *Seguir a fonte inglesa*, que é a regra do **guia pt-BR da Microsoft** ("Add to Contacts" → "Adicionar a Contatos"), desenhada para simplificar localização em escala. Cai por dois custos: a maiúscula no meio da frase soa germânica em português, e cria dúvida caso a caso sobre o que é "nome de produto" — cada rótulo novo viraria discussão. Convergem em sentence case: Polaris, Atlassian, Carbon, Material 3, GOV.UK |
| **MC3** | **Voz do erro = IMPESSOAL.** "Não foi possível gerar a proposta". "Nós" **somente** quando a falha é comprovadamente da SEED | **(a)** | *Primeira pessoa do plural* ("Não conseguimos gerar…"), que é o **Atlassian** — e ele **proíbe** a forma impessoal, com o argumento de que ela soa distante. Cai por um motivo local que nenhuma fonte poderia trazer: **o produto emite laudo, memorial e documento com ART**. Assumir autoria de erro por padrão, em texto que pode ser citado por cliente ou órgão, é risco que a microcópia não deve criar. A exceção do "nós" é a regra do Polaris. Bônus: "Não foi possível" é a fórmula que o guia pt-BR da Microsoft prescreve para "could not" — alinhamento de localização de graça |
| **MC4** | **Botão de confirmação destrutiva = VERBO ESPECÍFICO** que resume o resultado: "Excluir proposta" / "Manter". Nunca OK, nunca Sim/Não | **(a)** | *"Excluir mesmo assim"* como regra condicional (Microsoft: atrito deliberado quando a confirmação existe para AVISAR de algo) — que **era a minha recomendação** e ele preferiu (a). O custo da escolha fica escrito, porque é real e foi enunciado no gate: o rótulo específico é rápido demais — a pessoa acabou de pedir "excluir", então clica no verbo sem ler o aviso. **Consequência que a MC5 absorve:** se o rótulo não freia, o **texto** tem de frear, então a MC5 passa a exigir a consequência declarada no corpo do diálogo, com o dado identificador. Convergem em (a): NN/g, Material 3, Apple HIG |
| **MC5** | **O corpo do diálogo destrutivo declara a CONSEQUÊNCIA, não faz pergunta retórica** — com o dado identificador (nome, número, contagem) e a palavra **"permanente"** quando não há desfazer | consequência de (a) | *"Tem certeza que deseja continuar?"* — o "Don't" literal do Polaris, e o alvo direto do NN/g: sem detalhe, a única reação sensata a "tem certeza?" é clicar sim sem pensar, o que anula a proteção |

### 89.2 O que a evidência ou o nosso canon já fechavam (sem gate)

| # | Regra | De onde vem |
|---|---|---|
| **MC6** | **Zero emoji na UI de produto. Zero exclamação.** | **Já era lei nossa**: a marca §10 diz "sem emojis em material de marca" e que título é afirmação, não grito (o contraexemplo literal lá é *"NOVIDADES!!!"*). Convergem: Polaris ("no máximo uma por página, só para coisas realmente empolgantes"), Carbon, Atlassian e Material 3 — que proíbe exclamação **especificamente** em estado vazio e tarefa comum. Nossa regra é mais antiga que todas |
| **MC7** | **Humor proibido em erro, bloqueio e primeiro uso.** Leveza só em conclusão e sucesso, e mais contida quanto mais frequente a mensagem | NN/g ("evite humor: ele fica velho quando a pessoa vê o erro muitas vezes") · Carbon (não faça piada em estado de erro) · GOV.UK (bane "oops") · a fronteira mais precisa é do Atlassian: *delight são pequenos floreios, não piada* |
| **MC8** | **"Por favor" e "desculpe" ficam fora.** | GOV.UK e Atlassian, com racional: "por favor" faz um passo obrigatório parecer opcional; "desculpe" faz o erro parecer maior do que é. Exceção do Carbon, adotada: permitido quando a pessoa está sendo **incomodada** ("A indexação pode levar alguns minutos. Aguarde.") |
| **MC9** | **Estrutura do erro: o que aconteceu → (por quê) → o que fazer agora.** O "por quê" é condicional — entra quando ajuda a agir ou quando não há solução a oferecer | Microsoft (a estrutura canônica de três partes) · Polaris (o heading declara o efeito sobre a pessoa; o body diz como corrigir) · Atlassian (título de 3–4 palavras, sem instrução no título) · NN/g · GOV.UK |
| **MC10** | **Erro nunca culpa.** Proibidas as palavras `inválido`, `ilegal`, `incorreto`, `você esqueceu`, `proibido` | Convergência total: NN/g, GOV.UK (que bane até `valid`/`invalid` por não acrescentarem nada), Polaris, USWDS, Microsoft Fluent |
| **MC11** | **Instrução para campo vazio, descrição para limite excedido** — e consistência entre os dois. "Informe o nome do responsável" (vazio) × "O nome deve ter no máximo 35 caracteres" (limite) | GOV.UK, que é a única fonte que separa as duas formas com exemplo — e a régua é copiável |
| **MC12** | **Código de erro por revelação progressiva:** "Mostrar detalhes" → `Código do erro: X`, sempre acompanhado de texto de problema e solução. Um código único por causa | Microsoft. É a única das três posições que serve a um produto de engenharia **com atendimento técnico**: NN/g manda esconder (e o suporte perde rastreabilidade) e GOV.UK manda banir. *O suporte precisa do código; a pessoa não* |
| **MC13** | **Confirmação existe só para o incomum e irreversível.** O resto é **desfazer com feedback visível** — e desfazer sem a pessoa perceber que errou não serve de nada | Apple ("evite alertas para ações comuns e desfazíveis, mesmo destrutivas") · Microsoft (o exemplo canônico: excluir arquivo não confirma porque existe Lixeira) · NN/g (a fábula do lobo: confirmando demais, ninguém lê) · Material 3. A ressalva do feedback é da Microsoft |
| **MC14** | **Atrito extra — digitar uma palavra para confirmar — reservado ao topo da escala:** exclusão de dado de cliente, de UC ou de documento com ART emitida | NN/g é a única fonte que documenta o padrão, e ela mesma manda reservá-lo: se virar rotina, também vira automatismo |
| **MC15** | **Estado vazio nomeia o CRITÉRIO que faria a lista encher**, em vez de dizer que está vazia. "As propostas enviadas nos últimos 90 dias aparecem aqui" | O padrão medido no produto de referência (§9.3 do estudo) tem validação pública: NN/g cita o mesmo padrão como exemplo bom, e o Carbon manda o título ser afirmação positiva. Título orientado à ação (Polaris) e **uma** CTA primária |
| **MC16** | **Taxonomia de estado vazio = a nossa, do §25 Z1** (seis tipos), com o mapeamento declarado para os três do Carbon: *sem dado* ← primeiro uso · *ação da pessoa* ← zero resultado de busca/filtro · *gestão de erro* ← sem permissão, falha de sistema, configuração necessária. Em **sem permissão**, dizer o processo para solicitar acesso; em **configuração necessária**, dar o primeiro passo | Carbon é a fonte mais completa (tem tabela de o-que-dizer por tipo de erro), e os nossos seis tipos já eram mais granulares que a referência — o mapeamento evita que alguém "adote o Carbon" e perca granularidade |
| **MC17** | **PROIBIDO string que concorde gramaticalmente com valor injetado.** Forma neutra por padrão ("Itens marcados com estrela aparecem aqui"); seletor de plural quando o número é informação. Toda variável leva nota de localização dizendo o que ela vale em tempo de execução | É o **defeito medido** no produto de referência (§9.4): *"Marque um **Painéis** com estrela para que apareça aqui"* — o template injeta o nome do hub no plural e funciona por acidente onde singular e rótulo coincidem. Racional da Mozilla L10n: adjetivo cujo substantivo não está na string precisa de declinação por número e gênero; e ID de string descreve o **papel na interface**, para desencorajar reuso entre contextos |
| **MC18** | **Estado vazio nunca é mostrado antes de o dado chegar.** Enquanto carrega, o lugar é do skeleton (§19) — nunca de "nenhum registro" | O segundo defeito medido (§9.4: primeiro paint em inglês, rótulos trocando quando o bundle carrega) tem fonte pública: NN/g trata "vazio exibido enquanto ainda carrega" como mensagem de status imprecisa, e diz que no melhor caso a pessoa perde confiança, no pior nunca vê o conteúdo |

### 89.3 Registro verbal para pt-BR — a parte que só fonte brasileira resolvia

O inglês diz "start with an imperative verb" e pronto. Em português o mesmo
rótulo tem **três registros possíveis** e eles significam coisas diferentes: *Abrir
arquivo* (comando), *Abre um arquivo* (descrição do que o comando faz), *Abra um
arquivo* (ordem à pessoa). O **GNOME Brasil** é a única fonte pública que separa
os três por tipo de elemento, e o **guia pt-BR da Microsoft** confirma o registro
de menu. A tabela adotada:

| Elemento | Registro | Exemplo nosso |
|---|---|---|
| Botão de comando, item de menu, aba, rótulo de checkbox | **INFINITIVO** | `Criar proposta` · `Exportar relatório` · `Configurações da conta` |
| Tooltip, barra de status, descrição longa, texto de ajuda contextual | **PRESENTE, 3ª pessoa** | `Exporta as linhas selecionadas em CSV` |
| Instrução dentro de formulário, campo vazio, próximo passo | **IMPERATIVO** | `Informe o nome do responsável` · `Publique a tabela e gere novamente` |
| Descrição de tipo/recurso em seletor (o padrão do §9.2 do estudo) | **IMPERATIVO**, por decisão de MC1 (mesma voz do rótulo que o acompanha) | `Planeje dependências e prazo` |
| Mensagem de erro | **IMPESSOAL** (MC3), com "Não é possível" para impossibilidade e **"Não foi possível"** para tentativa que falhou | `Não foi possível gerar a proposta` |

> **Distinção de tempo que o guia pt-BR da Microsoft prescreve e nós adotamos:**
> *"Não é possível"* = a operação é impossível naquele estado. *"Não foi
> possível"* = a tentativa ocorreu e falhou. São mensagens diferentes e a pessoa
> age diferente em cada uma. Também adotado dele: `Falha ao` + infinitivo para
> "failed to", e **erro sempre em sentence case**, mesmo quando a fonte inglesa
> está em title case.

### 89.4 Pontuação e forma

| Regra | Onde |
|---|---|
| **Sem ponto final** em botão, item de menu, rótulo de campo, checkbox, chip, aba | Convergência Microsoft/GNOME: rótulo curto não leva ponto |
| **Com ponto final** em tooltip, mensagem de erro, texto de diálogo, descrição longa | idem |
| **Sem artigo** em rótulo de ação: `Criar proposta`, nunca `Criar uma proposta` | Atlassian, verbatim: "evite artigos em botões, rótulos e cabeçalhos de ação" |
| **Reticências (`…`)** só quando a ação abre outra etapa que exige entrada — `Exportar…` abre diálogo; `Exportar` executa | convenção de plataforma (Microsoft/GNOME), adotada porque distingue ação imediata de ação em duas etapas |
| **Números com o dado identificador** em confirmação: `Excluir 3 propostas?`, nunca `Excluir os itens selecionados?` | NN/g (MC5) |

### 89.5 Fronteiras declaradas

- **Conteúdo de marketing e display** é da §10 da marca, não desta seção.
- **Terminologia de domínio** (unidade consumidora × UC, ART, TUSD/TE, HFP/HR) **não** é decidida aqui: é vocabulário técnico regulado, e um glossário próprio é trabalho da F9 (publicação), onde ele serve site, ERP e peça ao mesmo tempo. **Pendência nomeada `MC-P1`.**
- **Texto de e-mail transacional** segue o contrato da F4 (`seed-email.md`); esta seção governa a UI. Onde os dois se cruzarem, o mais específico vence.
- **Tradução para outros idiomas** está fora do escopo do DS por decisão do roadmap (PT-BR only até a operação pedir) — mas a **MC17 já é a preparação**: string que não concorda com valor injetado é string que sobrevive a qualquer idioma futuro.

### 89.6 O que fica pendente de instrumento

A microcópia é a primeira seção deste canônico **sem guarda automatizada**, e
isso está escrito para não passar por esquecimento. O que é mecanicamente
verificável e vira guarda quando a F7.7 tiver fôlego (**pendência `MC-P2`**):
rótulo de ação sem artigo · ponto final em botão · presença de `inválido`/`ilegal`
/`incorreto` em mensagem de erro · exclamação e emoji em qualquer string de UI ·
`OK`/`Sim`/`Não` em botão de confirmação · template com placeholder seguido de
substantivo (o padrão da MC17). O que **não** é verificável por máquina e fica no
gate humano: se o "por quê" do erro ajuda a agir, se o critério do estado vazio é
o critério certo, e se o registro verbal casa com o elemento.

---

## 90. OFFLINE E FALHA DE REDE · **`estável`** (2026-08-24, F7.7-P5, duas decisões dele no gate) · OF1–OF9

**O que esta seção é.** O contrato do que a interface mostra quando a rede falha
— e do que ela **não** faz. Ela fecha a primeira das três linhas que o
`mapa-cobertura-ds.md` §6.1 marcava **❌ AUSENTE** desde a F7.1. Evidência
completa em `09-pesquisa/leitura-f77-estados-ausentes.md`.

**Medido antes de escrever:** **0 de 40** artefatos do acervo têm qualquer estado
de rede. A única menção de conexão que existe é uma linha de erro genérico do
§25/Z1 no `banco-feedback.html` (*"Verifique a conexão e tente de novo…"*), que é
estado de **erro**, não estado de **rede**. E o canal de anúncio já existe:
**9 das 16 telas** têm `aria-live="polite"`. Faltava o que dizer nele.

### 90.1 OF1 — offline NUNCA se declara por flag

`navigator.onLine` devolvendo `true` **não garante** acesso à internet: a
heurística do sistema considera "online" quem está numa LAN sem saída, e no
Windows o status depende de alcançar um servidor da Microsoft, o que firewall e
VPN bloqueiam com a internet funcionando. A recomendação da MDN é normativa aqui:
*não desabilite recursos com base no status online; apenas ofereça dicas.*

**A regra:**

- **`false` é confiável** (se o navegador diz que está offline, está) e pode
  ADIANTAR a dica;
- **`true` não é evidência de nada** e nunca serve para reabilitar sozinho;
- **o estado de falha se conclui de uma REQUISIÇÃO QUE FALHOU**, e se for para
  insistir, se confirma por batida (*heartbeat*).

⚠ **Proibido:** desabilitar controle porque a flag disse offline. Isso trava a
interface de quem usa VPN — e a nossa gente de campo usa VPN.

### 90.2 OF2 — são DOIS estados, não um (decisão dele: (a))

| Estado | Quando | O que a tela diz, na forma da §89 |
|---|---|---|
| **Sem conexão** (do lado de quem usa) | o dispositivo não alcança a rede | *"Você está sem conexão."* + o que ainda é possível |
| **Sistema indisponível** (do nosso lado) | a rede está boa e o servidor não responde ou devolve erro | *"Não foi possível alcançar o sistema."* + o que fazer |

**Por que dois e não um.** É o que a diretriz de UX offline manda (*deixe claro
se o problema é do nosso lado ou do lado de quem usa*) e é a **continuação
direta da MC3**, que ele decidiu na §89: voz impessoal, com "nós" reservado para
quando a falha é da SEED. Um estado único culparia a rede do cliente por uma
queda nossa — e o produto emite documento com ART, onde essa distinção importa.

**Custo declarado, e ele é do produto:** exige que a camada de dados informe qual
falha ocorreu. Sem essa informação, a interface cai no estado **Sistema
indisponível**, nunca no "sem conexão" — errar para o lado de assumir a falha é
coerente com a MC3.

Distinção verbal obrigatória (§89.3): **"não é possível"** (regra permanente) ×
**"não foi possível"** (esta tentativa falhou). Falha de rede é sempre a segunda.

### 90.3 OF3 — o estado crítico é PERSISTENTE; a recuperação é TRANSITÓRIA

- Enquanto a falha dura, o aviso **fica**: faixa na borda superior da região
  afetada, ou do `app` quando a falha é global. Não é toast, não some sozinho.
- Quando a conexão volta, o aviso **sai** e a volta é anunciada por **toast**
  (§17), que some. Voltar ao normal não merece ocupação permanente.

### 90.4 OF4 — o aviso diz o estado E o que ainda é possível

Nunca só o estado. A faixa tem, sempre: **o que aconteceu** · **o que continua
possível** · **uma ação** (tentar de novo, quando faz sentido). Faixa que só
anuncia a desgraça transfere o problema sem transferir a saída — é a mesma
doença que a MC5 corrige no diálogo destrutivo.

### 90.5 OF5 — nunca bloquear

**Proibido:** modal de carregamento que impede navegação, e requisição que
bloqueia conteúdo já carregado. A pessoa continua lendo, navegando e preenchendo.
O que falha é o **envio**, não a interface.

### 90.6 OF6 — o item NÃO ENVIADO tem marca própria

O que a pessoa fez e não saiu ainda **aparece como tal**, no próprio item, com
**forma + texto** (nunca só cor — OF8): marca de pendência e o rótulo
*"Não enviado"*. Três estados possíveis por item: **enviado** (sem marca) ·
**não enviado** (marca de pendência) · **falhou** (marca de erro + ação de tentar
de novo).

⭐ **Esta é a peça que a decisão (c) dele manda especificar AGORA**, mesmo sem
fila: a marcação é idêntica com ou sem fila, então especificá-la hoje é trabalho
que não se repete amanhã.

### 90.7 OF7 — a mudança de estado é ANUNCIADA

Entrada e saída do estado de falha vão à região `aria-live="polite"` que a tela
já tem (§1.13-b). **Nunca `assertive`**: perda de rede não interrompe o que a
pessoa está fazendo — a faixa persistente já carrega a informação para quem vê, e
o anúncio educado basta para quem ouve.

### 90.8 OF8 — nunca só cor

Estado de rede se marca por **linguagem + forma + cor**, os três. Regra herdada
da SC 1.4.1 e já praticada no §25/Z1.

### 90.9 OF9 — fronteira do design system

O DS fecha o **contrato visível**. **Não** são desta seção: service worker,
sincronização em segundo plano, IndexedDB, resolução de ordem de envio e política
de repetição. Isso é produto.

**Pendência `OF-P1` — a FILA de ações offline.** Decisão dele: (c) — o aviso e a
marcação fecham agora; a fila entra depois, **com a obrigação escrita ANTES de
alguém implementá-la**:

> Qualquer fila que sobreviva ao fechamento da aba grava, **no dispositivo**, o
> que a operação carrega — número de unidade consumidora, documento, endereço,
> valor de fatura. Isso é tratamento de dado pessoal **fora do servidor** e puxa
> LGPD: **base legal declarada · limite de retenção · cifragem em repouso ·
> limpeza no encerramento de sessão**. A fila não nasce sem essas quatro.

E a razão de ela existir um dia é local: **a SEED trabalha onde não há sinal** —
subestação, sala de painéis, canteiro e usina em zona rural de MG, ES e BA são
exatamente os lugares onde a coleta acontece. Offline aqui não é borda exótica, é
condição de trabalho.

### 90.10 OF10 — os estados de SINCRONIZAÇÃO (emenda de 2026-08-24, tarde)

**Por que esta cláusula nasceu.** A §90 foi escrita com a fila como *hipótese*
(OF-P1). No mesmo dia ele informou que ela é **decisão tomada**, verbatim:

> *"vamos criar um app para poder comunicar com o ERP para os funcionários
> poderem fazer cadastro off-line de certas coisas para poder depois sincronizar
> quando tiver internet (…) por exemplo, OS, a equipe de campo sincroniza o app e
> tem a OS que precisa trabalhar, pode preencher as infos offline sem problema e
> sincronizar quando tiver internet, o mesmo ocorre com sistema de ponto."*

Isso **não contradiz** a decisão (c) do gate — ela guardou exatamente a peça que
os dois mundos compartilham. Mas revela **três estados que a §90 não tinha**,
porque ela foi escrita para o caso "a rede caiu no meio do uso", e o app é o caso
"a rede não estava lá desde o começo, e por projeto".

**OF10-a — o item tem QUATRO estados, não dois.**

| Estado | O que significa | Marca |
|---|---|---|
| **enviado** | está no servidor | sem marca |
| **não enviado** | existe só no dispositivo, esperando rede | marca de pendência + rótulo |
| **sincronizando** | está subindo agora | marca de progresso, no item |
| **falhou** | tentou e não subiu | marca de erro + ação de tentar de novo |

O estado **sincronizando** é do ITEM, não só da tela: quem tem 40 apontamentos
subindo precisa ver quais já foram.

**OF10-b — a sincronização é um EVENTO COM RESULTADO, e o resultado fica.**
Terminada a sincronização, a interface diz **quantos subiram, quantos falharam e
o que fazer com os que falharam**. Esse resultado **não some sozinho**: permanece
até a pessoa reconhecê-lo. Sincronização que termina em silêncio é a forma mais
cara de perder trabalho, porque ninguém fica sabendo.

**OF10-c — antes de sair do local, dá para saber o que não subiu.** O resumo do
pendente é alcançável **sem procurar** — a equipe de campo decide se pode ir
embora com base nele. É o mesmo princípio da OF4 (dizer o que ainda é possível),
aplicado ao momento em que a decisão é irreversível: sair do canteiro.

**OF10-d — nunca "sincronizado" otimista.** Item só sai de *não enviado* quando o
servidor **confirmou**. Marcar como enviado ao despachar é mentir sobre o dado.

### 90.11 OF-P1 deixa de ser hipótese — e o ponto tem exigência PRÓPRIA

A pendência **OF-P1** se mantém aberta como trabalho de produto, mas muda de
natureza: **a fila vai existir**, então as quatro obrigações da §90.9 (base legal
declarada · limite de retenção · cifragem em repouso · limpeza no encerramento de
sessão) deixam de ser cautela e passam a ser **requisito de projeto**.

⚠ **E o registro de PONTO tem regra própria, acima da LGPD.** A **Portaria MTP
671/2021** criou o **REP-P** (registrador eletrônico de ponto por programa) e
**admite expressamente a marcação offline com sincronização posterior** — logo o
plano dele é suportado pela norma. Mas o REP-P vem com exigências que **são de
desenho, não só de backend**:

- **criptografia de no mínimo 128 bits na transmissão E no armazenamento** — o
  que transforma "cifragem em repouso" de recomendação em obrigação;
- **comprovante de registro ao trabalhador a cada marcação**, impresso ou em PDF,
  com assinatura no padrão **PAdES** — isto é uma PEÇA, e ela não existe no
  inventário;
- **o registro não se altera** — o que significa que a marcação de ponto **não
  entra no regime de conflito da §91**: ela não é editável, é apenas
  transmitida.

**Pendência nova `PT-P1`:** o comprovante de registro de ponto do trabalhador é
peça a especificar (família Interna do inventário), e a conformidade ao REP-P é
**[não conferido]** por esta sessão — precisa de quem responde por folha e
jurídico na SEED. O design system desenha a peça; ele não atesta conformidade
trabalhista.

---

## 91. CONFLITO DE EDIÇÃO CONCORRENTE · **`estável`** (2026-08-24, F7.7-P5, duas decisões dele no gate) · CE1–CE8

**O que esta seção é.** O contrato de quando duas pessoas editam o mesmo objeto.
Fecha a segunda linha **❌ AUSENTE** do §6.1.

**Medido antes de escrever:** **0 de 40** artefatos têm qualquer estado de
conflito, e hoje **uma única tela** tem gravação explícita — a
`tela-configuracoes.html` (*"Salvar alterações"*, regime CF do §86). É a única
exposta à atualização perdida hoje; o ERP inteiro estará amanhã.

### 91.1 CE1 — detectar é OBRIGATÓRIO, e é do servidor

O problema tem nome antigo — *lost update* — e solução padronizada na **RFC
9110**: lê-se o recurso com `ETag`, grava-se com `If-Match: <etag>`, e o servidor
devolve **412 Precondition Failed** em vez de sobrescrever.

**Aviso na interface não impede perda nenhuma: informa.** O que impede é a
pré-condição no servidor. Logo:

- **CE1-a** — toda gravação de objeto compartilhado envia a pré-condição de
  versão. Gravação sem pré-condição é defeito, não escolha de implementação.
- **CE1-b** — o que esta seção especifica é o que a pessoa vê **quando o 412
  chega**.

### 91.2 CE2 — o regime padrão é OTIMISTA (decisão dele: (b))

Ninguém trava. A pessoa edita, e o conflito aparece **na gravação**.

**Alternativa descartada e por quê:** o regime **pessimista** (o objeto trava
enquanto alguém edita), que é o do SAP Fiori e é referência madura de ERP, foi
recusado como padrão porque **quem perde conexão em campo deixa o objeto travado
até a trava expirar**, e trava indevida bloqueia trabalho legítimo — na operação
da SEED isso não é hipótese.

**Custo declarado do otimista, e a literatura o registra:** dá para digitar vinte
minutos e descobrir o conflito no fim. A CE3 existe para reduzir isso.

### 91.3 CE3 — presença AVISA, nunca bloqueia

Quando outra pessoa está com o mesmo objeto aberto em edição, a tela mostra
**aviso de presença** — quem é e desde quando —, em superfície discreta e
**sem desabilitar nada**. É informação para a pessoa decidir, não permissão.

⚠ O aviso nomeia um colega, e isso é uso interno legítimo. **Nunca** exibir dado
pessoal além do necessário para identificar quem edita.

### 91.4 CE4 — a EXCEÇÃO: trava exclusiva para objeto que gera documento com ART

**Objeto cujo conteúdo sai em documento assinado com Anotação de
Responsabilidade Técnica — memorial, laudo, projeto, proposta técnica — trava em
regime EXCLUSIVO.**

**A razão é nossa e não vem de nenhuma fonte externa:** quem assina responde pelo
que está lá, e **conteúdo mesclado em silêncio num documento com ART é risco que
a interface não pode criar**. É a mesma família da MC3, que decidiu a voz do erro
pelo mesmo motivo.

Regras da trava:

- **CE4-a** — quem chega depois vê **leitura, com o motivo dito**: quem está
  editando e desde quando. Ler nunca é bloqueado.
- **CE4-b** — a trava **expira** por inatividade, e o rascunho do dono sobrevive
  à expiração. Trava sem expiração é objeto perdido quando alguém fecha o
  notebook no canteiro.
- **CE4-c** — expirada a trava com rascunho pendente, o objeto mostra
  **"Alterações não salvas de <quem>"** — estado distinto de "livre" e de
  "travado".
- **CE4-d** — quem é dono da trava e volta antes de outra pessoa assumir
  **retoma** o próprio rascunho.
- **CE4-e** — a classe do objeto (gera documento com ART, ou não) é **declarada
  pelo objeto**, nunca inferida do nome da tela. Mesma regra do MM1: quem é peça
  impressa é decidido pelo que o artefato declara.

### 91.5 CE5 — o diálogo de conflito tem TRÊS saídas (decisão dele: (a))

Quando o 412 chega, o diálogo (§30) oferece, nesta ordem:

1. **Ver o que mudou** — a comparação, antes de qualquer decisão. É a saída
   padrão e a de menor risco.
2. **Manter o meu e sobrescrever** — destrutiva. Entra sob a **MC4** (verbo
   específico no rótulo, nunca "OK") e a **MC5** (o corpo declara a
   consequência, com o dado identificador: o que será descartado e de quem).
3. **Descartar o meu e recarregar** — também destrutiva, e também sob MC4/MC5.

**Alternativa descartada:** as duas saídas ("recarregar e perder" ou "salvar como
cópia"). Recusada porque "recarregar e perder" faz a pessoa refazer o trabalho, e
essa é exatamente a queixa que a literatura registra como o custo do padrão
otimista — oferecê-la como única saída é escolher pagar o custo inteiro.

### 91.6 CE6 — o que NUNCA se faz

- **Mesclar em silêncio.** Nunca, em nenhum regime.
- **Perder sem oferecer saída.** Nenhum caminho do diálogo termina com trabalho
  perdido sem a pessoa ter escolhido isso, com a consequência dita.
- **Bloquear a leitura.** Trava é de escrita.

### 91.7 CE7 — o conflito é ANUNCIADO como alerta

Diferente do offline: aqui a interrupção é apropriada. O diálogo de conflito
chega por `role="alertdialog"` com foco movido, porque a pessoa está prestes a
gravar e precisa saber **antes** de repetir a ação.

### 91.8 CE8 — fronteira do design system

**Não** são desta seção: mesclagem automática, edição simultânea de verdade
(CRDT, cursores de várias pessoas), histórico de versões navegável e a política
de expiração em minutos. O DS fecha os **estados**: presença, trava, alterações
não salvas de terceiro, e o diálogo de conflito.

### 91.9 CE4-f — a fronteira COLETA × EMISSÃO (emenda de 2026-08-24, tarde)

**O problema que a emenda resolve, e ele é uma colisão real entre duas cláusulas
aprovadas no mesmo gate.** A CE4 manda travar em regime exclusivo o objeto que
gera documento com ART. A §90 admite trabalho offline. **Não se adquire trava sem
rede.** Se as duas valessem sobre o mesmo objeto, a equipe de campo ficaria
impedida de preencher a OS no canteiro — que é exatamente o caso de uso que o
app existe para servir.

A saída não é afrouxar nenhuma das duas: é **reconhecer que são objetos
diferentes em momentos diferentes**.

| Momento | O que é | Regime |
|---|---|---|
| **COLETA** — a OS preenchida em campo, a medição, a foto, o apontamento, a marcação de ponto | dado bruto, de autoria de quem coletou, ainda não é documento | **offline-first, SEM trava** — §90 governa |
| **EMISSÃO** — o laudo, o memorial, o projeto, a proposta técnica que o engenheiro assina | documento que sai com ART e responsabiliza quem assina | **online, com trava exclusiva** — CE4 governa |

**CE4-f — a trava exclusiva é do DOCUMENTO, nunca da coleta que o alimenta.** O
que a CE4 protege é o ato de compor e assinar; nada nela impede que o dado de
campo chegue depois, por sincronização.

⭐ **A razão de isso não afrouxar a CE4:** a preocupação que a fundou era conteúdo
**mesclado em silêncio** dentro de documento assinado. Dado de campo que chega por
sincronização **não se mescla em silêncio** — ele entra como material novo, com
autoria e horário, e quem assina decide se incorpora. A trava segue protegendo
exatamente o que precisava.

### 91.10 CE9 — conflito detectado na SINCRONIZAÇÃO

O conflito da CE5 é **interativo**: a pessoa aperta gravar e a resposta vem na
hora. O conflito de sincronização é outro animal, e as três diferenças mudam o
desenho:

1. **Chega em lote**, com muitos itens de uma vez.
2. **Chega tarde** — horas depois da edição, às vezes no dia seguinte.
3. **Chega sem ação em curso** — a pessoa pode estar dirigindo, ou nem estar com
   o app aberto.

Logo:

- **CE9-a — nunca resolver sozinho.** Nem "o servidor ganha", nem "o dispositivo
  ganha". Resolução automática de conflito é a mesma família da mesclagem em
  silêncio que a CE6 proíbe.
- **CE9-b — nunca descartar.** O item conflitado **fica retido** num estado
  próprio (*"precisa da sua atenção"*), com o conteúdo do dispositivo
  preservado. Trabalho de campo não se perde porque o servidor mudou.
- **CE9-c — a notificação é ASSÍNCRONA, não é diálogo.** Diferente da CE7: aqui
  **não** se usa `alertdialog` nem se move o foco, porque não há ação em curso
  para interromper. É aviso persistente na lista de pendências, do mesmo tipo do
  resultado de sincronização da OF10-b.
- **CE9-d — a resolução usa as MESMAS três saídas da CE5** (ver o que mudou ·
  manter o meu e sobrescrever · descartar o meu), no momento em que a pessoa
  abrir o item. Mesma gramática, momento diferente — quem aprendeu uma sabe a
  outra.
- **CE9-e — o lote não vira um diálogo por item.** Muitos conflitos se resolvem
  na lista, com o item aberto um a um. Trinta diálogos em sequência é a forma de
  fazer a pessoa clicar em "sobrescrever" sem ler.

**Fronteira:** a marcação de **ponto** não entra aqui. Pela Portaria 671/2021 o
registro não se altera, logo ele não tem conflito de edição — tem apenas
transmissão bem ou malsucedida, que é a OF10.

---

## 92. LISTA LONGA · **`estável`** (2026-08-24, F7.7-P5, uma decisão dele no gate) · LL1–LL6 · **fecha a BU-P1**

**O que esta seção é.** A regra de quando uma lista deixa de poder ser desenhada
do jeito simples. Fecha a terceira linha **❌ AUSENTE** do §6.1 e a pendência
**BU-P1**, aberta no §85.3 desde a F7.6.

### 92.1 Por que a regra nasceu de COBAIA, e não do acervo

A lista mais longa que existe aqui tem **38 itens** (`tela-quadro.html`). Nenhum
artefato nosso exercita o problema — o produto de referência medido no
`estudo-clickup-completo.md` §6.7 tem **~470 itens** num grupo. Logo a regra não
podia ser medida no acervo: foi medida em cobaia com carga estrutural igual à da
linha real do `tela-tabela.html` (9 campos, caixa de seleção, SVG de 9 formas,
chip ≈ 25 nós por linha), pelo instrumento
`06-validacao/ferramentas/mede-lista-longa.mjs`, mediana de 3 cargas frescas em
Chrome real, dado 100% sintético.

⭐ **Regra de método que fica: regra sem caso no acervo não se mede no acervo —
mede-se em cobaia, com a cobaia declarada.**

### 92.2 LL1 — a lista de TRABALHO é paginada (decisão dele: (a))

**O padrão da lista de trabalho é paginação**, não rolagem contínua. E o motivo
não é desempenho: é **achabilidade**.

A NN/g delimita o encaixe da rolagem infinita: itens **homogêneos, sem tarefa e
sem objetivo** — entretenimento, notícia, rede social. E nomeia o custo que
atinge trabalho: some o ponto de referência, e voltar a um item já visto fica
difícil (o "pogo sticking" devolve a pessoa ao topo). **Uma lista de ordem de
serviço é o oposto do caso de encaixe:** é heterogênea, com tarefa e com
objetivo.

A Deque enumera oito grupos prejudicados pela rolagem infinita — teclado (o foco
pula da última linha visível para o próximo nó na ordem do DOM), reconhecimento
de voz, comutador, tremor, baixa visão com ampliação, carga cognitiva, alcançar o
rodapé, e toque em tela pequena — e recomenda **"carregar mais" ou paginação**.

**Exceção declarada:** o **feed** (§87) é o único arquétipo homogêneo-sem-tarefa
da casa, e ali "carregar mais" com botão focável no fim é admitido — nunca
carregamento automático sem controle.

### 92.3 LL2 — o limiar medido é 2.000 linhas, e vale DENTRO da página

Orçamento de referência: **INP ≤ 200 ms** é a faixa "bom" das Core Web Vitals;
tarefa acima de **50 ms** já é tarefa longa.

| N | layout forçado (tabela) | layout (blocos) | clique→quadro (tabela) | clique→quadro (blocos) |
|---|---|---|---|---|
| 500 | 116,3 ms | 58,7 ms | 60,0 ms | 73,0 ms |
| 1.000 | 219,5 ms | 118,6 ms | 85,2 ms | 105,7 ms |
| **2.000** | **395,4 ms** | **222,8 ms** | **162,8 ms** | **166,9 ms** |
| 5.000 | 983,1 ms | 379,5 ms | 343,8 ms | 355,2 ms |
| 10.000 | 1.632,3 ms | 660,8 ms | 578,0 ms | 444,8 ms |

Em **2.000 linhas** um único layout forçado já custa **222–395 ms** — acima do
orçamento inteiro — e o clique consome **81–83%** dele. Em 5.000 a interação
passa de 340 ms, faixa "ruim".

**LL2-a** — abaixo de 2.000 linhas renderizadas, a lista é plana e simples.
Virtualizar aí é custo sem retorno.
**LL2-b** — acima de 2.000, vale a LL3.
**LL2-c** — 2.000 é **piso desta forma de linha**, não constante universal:
linha mais pesada baixa o limiar, e quem desenhar linha mais pesada **mede**.

### 92.4 LL3 — acima do limiar, contenção nativa — e o MATERIAL tem de ser bloco

Ganho da contenção (`content-visibility:auto` + `contain-intrinsic-size`) sobre o
plano, no custo de um layout forçado, medido:

| N | tabela `<tr>` | blocos `div` com papel ARIA |
|---|---|---|
| 500 | 1,04× | **5,87×** |
| 1.000 | 0,95× | **10,31×** |
| 2.000 | 0,93× | **15,37×** |
| 5.000 | 0,96× | **25,47×** |
| 10.000 | 0,91× | **17,44×** |

Mesmo CSS, mesma carga por linha, mesmos 9 campos, mesmo SVG. **O que muda é só a
caixa:** contenção de tamanho **não se aplica a caixa de tabela interna**
(`table-row`, `table-cell`), então `content-visibility` no `<tr>` é declaração
sem efeito.

**LL3-a** — lista que pode passar do limiar nasce em **blocos com papel ARIA**
(`grid`/`row`/`gridcell`), não em `<table>`.
**LL3-b** — a contenção mantém **todos os nós no DOM e na árvore de
acessibilidade**: focáveis, selecionáveis e **achaveis pelo Ctrl+F do
navegador**. É a razão de ela vir antes da virtualização.
**LL3-c** — `<table>` segue permitida abaixo do limiar, e as tabelas do acervo
hoje (≤ 15 linhas) ficam intactas.

### 92.5 LL4 — virtualização por JS só por MEDIÇÃO, e com compensação obrigatória

O que a janela por JS compra além da contenção é pouco — **1,11× · 1,22× · 1,24×
· 1,67× · 2,53× · 7,02×** — e cobra caro: **achabilidade = NÃO** em todo N ≥ 500
medido. O texto do item do meio **não está no DOM** e o Ctrl+F do navegador não
acha.

**LL4-a** — virtualizar exige **medição que mostre a contenção insuficiente**
naquele caso. Não se virtualiza por regra nem por hábito.
**LL4-b** — virtualizando, são obrigatórios: `aria-setsize` e `aria-posinset` em
cada item, com **`aria-setsize="-1"`** quando o total é desconhecido ou muda
(padrão da APG) · **substituto de busca dentro da lista**, porque o Ctrl+F deixou
de funcionar · **foco preservado** ao reciclar nó · **âncora de rolagem**, para a
volta não jogar a pessoa ao topo.
**LL4-c** — o contraexemplo está medido e é o vizinho: virtualizou ~470 itens e,
na mesma tela, o §8.4 do estudo mediu **0 `role="grid"`, 0 `role="row"`, 0
`role="gridcell"`, 0 `h1`/`h2`/`h3`, nenhum salto de conteúdo, 3 regiões
`aria-live` todas vazias, 249 focáveis, 90 alvos abaixo de 24×24**. *Virtualizar
sem pagar as compensações foi o que produziu uma lista impenetrável.*

### 92.6 LL5 — esta regra NÃO TEM conferência por declaração

⚠ `getComputedStyle(linha).contentVisibility` devolve **`auto` nas duas
estruturas** — em `<tr>`, onde não faz nada, e em bloco, onde faz. O estilo é
aceito, resolvido e reportado; só não tem efeito.

**Consequência normativa: nenhuma guarda pode aprovar esta regra lendo
declaração.** Uma guarda que perguntasse *"a lista longa declara contenção?"*
aprovaria uma tabela que não contém nada. **A única régua honesta é o tempo
medido.**

É a terceira ocorrência da mesma classe nesta casa — depois do
`document.fonts.check()` devolvendo `true` com a fonte falhando (§75) e do
resolvedor de fundo cego a gradiente. ⭐ **Regra que fica: quando o valor
computado e o efeito medido discordam, o computado não é evidência de nada.**

**Pendência `LL-P1`** — se um dia esta regra ganhar instrumento, ele mede TEMPO,
com orçamento impresso no placar, e é caro (classe do `contraste-composicao`, ver
CC-P1). Até lá, é gate humano com medição declarada.

### 92.7 LL6 — fronteiras

**Não** são desta seção: índice de busca, relevância e ranking (§85.3, produto) ·
estratégia de paginação no servidor (cursor × deslocamento) · agrupamento e
recolhimento de grupo, que são contratos do §40/DT8.

**Fronteiras da medição, declaradas antes de medir:** jank de rolagem
**[não conferido]** — tempo de quadro em navegador headless não representa o de
janela real · o FCP apareceu em 24 das 36 células, ausente exatamente nas páginas
mais leves, e serve de corroboração (blocos contidos pintam 1,4×–2,0× mais cedo),
nunca de número da regra.
