Classname/token pin removal, form-action ladder guard, kill TooltipProvider JSX nesting pin.
7.8 KiB
Remover pins de className / tokens Tailwind literais
Status: registrado Atualizado em: 2026-08-02 Issue: #240 (OPS13) Priority: P2 Model: composer-2.5 Impeccable: A — N/A (sem superfície UI; só testes de convenção / pin unitário) Appetite: ~0,5–1 dia eng; editar specs unitárias; sem migration; sem mudança de produto obrigatória Responsável: —
Dados → decisão → apresentação
Dados: N/A — higiene de guardrails de teste; sem métrica de produto.
Contexto
Review de guardrails (2026-08-02): vários unit specs congelam strings completas de className, sequências Tailwind e tokens hex de CSS. Exemplos vivos:
tests/unit/campaignComponents.unit.spec.ts—className="h-svh min-h-0 overflow-hidden print:h-auto print:overflow-visible"; stickysticky left-0 z-20…min-w-56…;--sidebar: #fafaf9/ foreground hextests/unit/campaignHomeActionButton.unit.spec.tsx—w-[5.5rem],gap-0,px-4tests/unit/campaignHomeLayout.unit.spec.tsx—-mx-4,w-[calc(100%+2rem)],grid-rows-[0fr],opacity-0tests/unit/campaignWizardShell.unit.spec.tsx—pt-2vspt-3,pb-6tests/unit/campaignQuickActionsDrawer.unit.spec.tsx—-mx-4,w-[calc(100%+2rem)]tests/unit/homeSearchResultsLayout.unit.spec.tsx—flex-col/md:grid-cols-2/lg:grid-cols-3
O intent original (shell fixa + scroll no content pane; coluna Município sticky; touch target; contraste da sidebar) é legítimo. O mecanismo (literal de utilitários) empurra agentes a: (a) não mexer no layout com bug real; (b) satisfazer a string e colocar o comportamento em outro arquivo; (c) “passar” o gate sem preservar a invariante.
Pedido: remover esses pins de classname/token e, onde a invariante ainda importa, substituir por pin comportamental (data-slot, role/ARIA, presença de região de scroll, não-presença de title= onde deve ser texto acessível, etc.).
Objetivos
- Nenhum unit spec sob
tests/unit/exige igualdade/substring de uma lista completa de utilitários Tailwind (ex. string multilasse colada do JSX) nem hex de token CSS como prova de layout. - Invariantes que ainda valem ficam pinadas por contrato estável:
data-slot,data-*de domínio, roles/ARIA, uso de constantes exportadas (HOME_SEARCH_*_CLASScomo símbolo, sem reassertar o conteúdo da constante), ou comportamento renderizado (texto acessível, overflow scrollport presente). - Specs que só existiam para congelar spacing/sizing cosmético (
pt-2vspt-3, largura exata do botão) somem ou viram assert do contrato exportado, sem re-listar utilities. - Atualizar
docs/GUARDRAILS.md/ nota curta se alguma linha citar esses pins; entrada no ledger se P4-H-adjunto não se aplicar. - Guardrails: sem migration; sem mudança de UI obrigatória neste PR (só testes/docs). Se um pin comportamental exigir
data-slotnovo no shell, é permitido e mínimo. - Tracer bullet: apagar o
toContain('className="h-svh…')emcampaignComponents→ substituir por assert emdata-slot="campaign-content-scroll"+ overflow no scrollport (já parcialmente presente) →pnpm gate:fastverde.
Decisões travadas
- Remover literais de className/token; não “afrouxar” para regex parcial da mesma string. Opções: A) delete puro | B) delete + pin comportamental onde havia invariante de produto | C) manter e documentar judgment-only. Recomendação: B. A sozinho perde cobertura real (shell scroll / sticky / a11y). C perpetua o incentivo a shortcut. Rejeitado: C; “só comentar o expect”.
- Constantes exportadas (
HOME_SEARCH_*_CLASS) podem continuar sendo referenciadas por identidade (toBe(CONST)), semtoContain('rounded-md')adjacente que recongele o conteúdo. Se o spec hoje comparaclassName === CONSTe ainda lista utilities, manter só a igualdade ao símbolo. Rejeitado: expandir pins para o valor string da constante. - Hex de
--sidebar*: provar contraste/tema via token name (text-sidebar-foreground) ou snapshot de contrato de tema se existir; não o valor#fafaf9. Rejeitado: pin de palette hex em unit (design tokens mudam sem bug). - Sticky Município: pinar
data-slot/ sticky via atributo estável ou teste de presença da célula sticky sem a sequênciaz-20…min-w-56…. Rejeitado: regex da class completa. - Kind:
chore(OPS13). Engenharia de guardrail, não feature de/campanha. - i18n: ids de teste/data-slot em inglês; copy pt-BR intocada.
Questões em aberto
- Incluir também asserts
min-h-11/min-w-11(touch target)? Opções: A) remover junto (são classname) | B) manter como proxy de touch target até haver pin computed-style/e2e. Recomendação: A neste item — o produto já tem regra emcampanha-edit-where-you-see; o literal Tailwind é o mesmo cheiro. Se regressão de touch aparecer, e2e/comportamental depois. (assumido) campaignCompositionCleanupw-fullno submit? Opções: A) no escopo | B) fora (é um assert pontual de form). Recomendação: A se forclassNameliteral; fora se for só layout smoke sem lista de utilities. (assumido: incluir se for toContain de utility)
Abordagem proposta
flowchart LR
audit["Listar expects className/token"] --> classify{"Invariante de produto?"}
classify -->|não| delete["Apagar expect"]
classify -->|sim| replace["Pin data-slot / ARIA / símbolo"]
delete --> gate["gate:fast"]
replace --> gate
Componentes:
- Auditoria mecânica:
rg "className|toMatch\\(/sticky|--sidebar: #" tests/unit— checklist no PR; cada hit classificado delete vs replace. tests/unit/campaignComponents.unit.spec.ts: remover strings de layout/sidebar hex/sticky utilities; conservardata-slot="campaign-content-scroll",data-scope, badges semânticos, a11y de prioridade/classe territorial (sr-onlyvstitle— esse pin é de conteúdo acessível, não de classname de layout; manter).- Home / wizard / drawer / search layout specs: cortar expects de spacing/bleed/grid utilities; onde B115/B116 etc. dependem de contrato, apontar para constante exportada ou
data-slotjá existente. docs/GUARDRAILS.md: se alguma linha referenciar pin de classname, atualizar; senão só o PR das Issues.- Migration: nenhuma.
Dependências
- Nenhuma dura. Independente de OPS14/OPS15.
- Soft: issues de UI mobile (B115/B116) podem conflitar em specs de home — rebase trivial; não bloqueia registro.
Não escopo
- Reescrever o shell visual / redesign.
- Matar pins de comportamento (
data-pending,aria-busy,data-slotsemântico, texto acessível vstitle). - OPS14 (form-action por política) e OPS15 (TooltipProvider nesting).
- ESLint banindo Tailwind arbitrary values.
Rabbit holes
- "Já que mexo no spec, refatoro o layout". Explode appetite. Mitigação: só testes (+
data-slotmínimo se faltar âncora). - Substituir classname por snapshot HTML enorme. Mesmo cheiro. Mitigação: asserts pontuais de contrato.
- Computed style em jsdom. Frágil e barulhento. Mitigação: não neste item; data-slot/ARIA bastam.
Adiado com gatilho
- Pin e2e de scroll/sticky real. Revisitar se regressão de shell voltar após remoção dos literais.
- Lint
no-restricted-syntaxcontratoContain('min-h-em tests. Só se agentes reintroduzirem o padrão ≥2×.
Referências
- GitHub Issue #240 (OPS13)
- Review de guardrails (chat 2026-08-02) — item 1
tests/unit/campaignComponents.unit.spec.ts(shell + sticky + sidebar)tests/unit/campaignHomeLayout.unit.spec.tsx,campaignHomeActionButton.unit.spec.tsx,campaignWizardShell.unit.spec.tsx,homeSearch*.unit.spec.tsx,campaignQuickActionsDrawer.unit.spec.tsxdocs/GUARDRAILS.md— escada de determinismo- AGENTS.md / engineering-standards — gates; não congelar incidental syntax