fix: lápis de editar em /categorias e /contas ganham aria-label (closes #127) - #140
fix: lápis de editar em /categorias e /contas ganham aria-label (closes #127)#140Guiroos wants to merge 2 commits into
Conversation
#127) Os três diálogos de edição (CategoryDialog, GroupDialog, AccountDialog) usam um <Button size="icon" variant="ghost"> cujo único filho é um <Pencil> do lucide-react, sem aria-label/title/sr-only. lucide-react está pinado em 1.8.0 e marca o ícone como aria-hidden quando não recebe filho nem prop de a11y, então o role=button ficava sem nenhuma fonte de nome (WCAG 4.1.2). Rotula o <Button>, não o ícone, seguindo o padrão já usado pelos 6 outros diálogos de edição do repo (WithdrawalEditButton, ContributionEditButton, InstallmentGroupEditDialog, IncomeEditDialog, TransactionEditDialog, FixedExpenseEditDialog). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpoqDa8kyqy7ewGn28LM2a
…arquivo Nit de revisão em #139: o regex anterior (<Button\b[^]*?onClick=...[^]*?<Trash2...) podia atravessar um <Button> anterior no mesmo arquivo, caso ele existisse antes do gatilho — a proteção dependia da ordem do fonte (o gatilho ser o primeiro <Button>), não do regex em si. Sem falso-positivo hoje, mas frágil. Troca por findClosestButtonWithIcon(source, iconTag), com lookahead negativa que impede o match de atravessar outro "<Button" antes de alcançar o ícone-alvo — mesmo helper que o PR #140 (issue #127) introduz ao criar este mesmo arquivo. Validado que a lookahead rejeita o cenário do reviewer (Button rotulado antes do gatilho, gatilho ainda sem nome) e que o comportamento correto (rejeita pré-fix, aceita pós-fix) se mantém. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014vttNSZKJfEaaPukZWKhvB
Guiroos
left a comment
There was a problem hiding this comment.
Revisão do head 66b0062. A correção está certa e fecha a #127 por inteiro (exigência 8): os 3 sites — CategoryDialog.tsx:161, GroupDialog.tsx:93, AccountDialog.tsx:169 — são o ramo else do ternário mode === 'create' ? … : … em cada arquivo, e os três estão rotulados no <Button>, não no <Pencil>. O {...props} de components/ui/button.tsx:80 repassa o atributo ao <button> real (o Comp é 'button' quando asChild é falso, que é o caso dos três), então o nome chega mesmo à árvore.
Conferi por conta própria as duas afirmações do corpo que sustentam decisões, em vez de aceitá-las: grep por <Pencil devolve 17 ocorrências no repo e nenhuma quarta em /categorias ou /contas, então o recorte da fatia está fechado; e os três mode="edit" são renderizados de fato — categorias/page.tsx:70 (grupo), categorias/page.tsx:99 (categoria) e contas/page.tsx:70 (conta) —, nenhum é caminho morto (exigência 7).
Rodei os gates nesta sessão, sobre este head: npm ci, npm run lint, npm run format:check, npm run typecheck e npm test verdes (35 arquivos, 533 testes).
Um achado, nit — sobre o alcance do gate, não sobre o fix. A fatia de findClosestButtonWithIcon se estende até </Button> e engloba os atributos do <Pencil>, então a correção errada que a própria #127 nomeia (aria-label no ícone) só é barrada enquanto o prettier mantiver a tag do ícone numa linha só. Mutei o fonte e rodei: na forma quebrada em linhas, os três casos ficam verdes com o gatilho sem nome. A correção é remover a cauda do regex — que é exatamente o que o #145 já faz para o mesmo problema. Reprodução, o diff de uma linha e a bateria de mutação com a versão corrigida no comentário inline.
Sendo justo com a força do gate: as duas correções erradas mais prováveis são barradas, e isso também mutei em vez de deduzir. Fix revertido nos três arquivos → vermelho. aria-label no botão de criar do mesmo arquivo (+ Categoria, + Novo grupo, + Nova conta, que abrem antes do de editar e também usam onClick={() => setOpen(true)}) → vermelho: a lookahead negativa (?:(?!<Button\b)[\s\S])*? segura, e vale registrar que ela é a correção do buraco de ancoragem que apontei no #139, onde o [^]*? atravessa um botão rotulado anterior. Se o #139 mergear primeiro, é esta versão que deve sobreviver ao conflito.
Generated by Claude Code
Review em #140: a asserção de aria-label ancorava até "</Button>", então os atributos do próprio ícone entravam no trecho testado. Um aria-label colocado por engano no <Pencil> (em vez do <Button>) passava a asserção de linha isolada sempre que o prettier quebrasse os atributos do ícone em múltiplas linhas — exatamente o erro que a issue #127 existe para barrar. Truncar a captura em "<Pencil\b" tira os atributos do ícone do trecho e fecha a lacuna, sem enfraquecer nenhuma das rejeições existentes (revert, aria-label no botão de criar). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpoqDa8kyqy7ewGn28LM2a
O que mudou
Os três diálogos de edição de cadastro (
CategoryDialog,GroupDialog,AccountDialog) usam um<Button size="icon" variant="ghost">cujo único filho é um<Pencil>dolucide-react, semaria-label,aria-labelledby,titleousr-only.lucide-reactestá pinado em1.8.0(package.json) e oIcondessa versão marca o ícone comoaria-hidden="true"quando não recebe filho nem prop de a11y — então a computação do nome dorole=buttonficava sem nenhuma fonte (WCAG 4.1.2 Name, Role, Value, nível A).<Button size="icon" variant="ghost" className="h-7 w-7 text-text-tertiary hover:text-text-primary" onClick={() => setOpen(true)} + aria-label="Editar categoria" > <Pencil className="h-3.5 w-3.5" /> </Button>Uma linha por arquivo, aditiva, sem mudança visual:
CategoryDialog.tsx→aria-label="Editar categoria"GroupDialog.tsx→aria-label="Editar grupo"AccountDialog.tsx→aria-label="Editar conta"Cada botão de criar no mesmo arquivo (
+ Categoria,+ Novo grupo,+ Nova conta) já tem nome via texto visível e não foi tocado.Por que dessa forma
Segui a proposta da issue à risca:
<Pencil aria-label="...">também desligaria oaria-hidden(viahasA11yPropdo lucide), mas poria o nome no ícone em vez do controle clicável — o alvo de clique e o nó nomeado ficariam em elementos diferentes, divergindo dos outros 20 controles do repo que rotulam o<Button>."Editar"seco./categoriasrenderiza duas listas na mesma página (grupos e categorias); dois"Editar"idênticos por linha não resolvem a ambiguidade que a issue existe para resolver. Segui a mesma convenção dos 6 diálogos de edição que já rotulam (WithdrawalEditButton,ContributionEditButton,InstallmentGroupEditDialog,IncomeEditDialog,TransactionEditDialog,FixedExpenseEditDialog).Como testei
npm ci && npm run lint && npm run format:check && npm run typecheck && npm test && npm run build— todos verdes (35 arquivos de teste, 533 testes).ds-reviewerrodado sobre os três arquivos ao final (não após cada edição individual, por causa dos 3 disparos consecutivos do hookPostToolUse:Edit): aprovado sem violações —aria-labelé atributo ARIA padrão, ortogonal às regras do DS.__tests__/unit/a11y-nomes-acessiveis.test.ts(não existia ainda nomain— a issue já previa essa possibilidade, já que a [a11y] DeleteButton do DS não tem nome acessível — 6 pontos de render anunciam o botão destrutivo como "botão" #126/fix: DeleteButton do DS ganha aria-label no gatilho de exclusão (closes #126) #139, que o criaria, ainda não tinha mergeado; criei com o mesmo cabeçalho/padrão derow-actions.test.tsea11y-estado-selecao.test.ts, já que o projeto não tem@testing-library/react).<Button>mais próximo do ícone<Pencil>(via lookahead negativa que impede o match de atravessar outro<Button>), não no arquivo inteiro — validei manualmente com um script Node que o regex (a) rejeita o código anterior ao fix nos três arquivos, (b) passa com a correção, (c) não seria enganado pelo botão de criar (+ Categoriaetc.), que abre antes do de editar no mesmo arquivo e também usaonClick={() => setOpen(true)}.npm run test:integrationnão foi rodado — exige credenciais do Neon e só roda em push paramain; esta mudança não toca camada de banco.Risco e o que NÃO foi coberto
@testing-library/react); cobertura fica no nível de gate por texto-fonte, mesmo padrão de achados de a11y anteriores./categoriase/contas. As gêmeas de/metas,/configuracao-mese/investimentos([a11y] Lápis de editar sem nome acessível em /metas, /configuracao-mes e /investimentos #128), oDeleteButtondo DS ([a11y] DeleteButton do DS não tem nome acessível — 6 pontos de render anunciam o botão destrutivo como "botão" #126, já com PR fix: DeleteButton do DS ganha aria-label no gatilho de exclusão (closes #126) #139 aberto) e oSplitSection([a11y] Dois "X" idênticos e sem nome na divisão de despesa do /registro — um fecha tudo, o outro remove uma pessoa #129) são issues separadas, ainda não implementadas.__tests__/unit/a11y-nomes-acessiveis.test.tsdo zero (nenhuma das duas tinha mergeado ainda quando cada uma rodou). Quem mergear primeiro define o arquivo-base; a segunda PR precisará resolver o conflito somando osdescribeblocks, não descartando um lado.Arquivos tocados
components/categorias/CategoryDialog.tsxcomponents/categorias/GroupDialog.tsxcomponents/contas/AccountDialog.tsx__tests__/unit/a11y-nomes-acessiveis.test.ts(novo)Closes #127
🤖 Generated with Claude Code
https://claude.ai/code/session_01UpoqDa8kyqy7ewGn28LM2a
Generated by Claude Code