Skip to content

fix: lápis de editar em /categorias e /contas ganham aria-label (closes #127) - #140

Draft
Guiroos wants to merge 2 commits into
mainfrom
claude/issue-127-lapis-editar-cadastro
Draft

fix: lápis de editar em /categorias e /contas ganham aria-label (closes #127)#140
Guiroos wants to merge 2 commits into
mainfrom
claude/issue-127-lapis-editar-cadastro

Conversation

@Guiroos

@Guiroos Guiroos commented Sep 2, 2026

Copy link
Copy Markdown
Owner

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> do lucide-react, sem aria-label, aria-labelledby, title ou sr-only. lucide-react está pinado em 1.8.0 (package.json) e o Icon dessa versão marca o ícone como aria-hidden="true" quando não recebe filho nem prop de a11y — então a computação do nome do role=button ficava 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.tsxaria-label="Editar categoria"
  • GroupDialog.tsxaria-label="Editar grupo"
  • AccountDialog.tsxaria-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:

  • Rotular o botão, não o ícone. <Pencil aria-label="..."> também desligaria o aria-hidden (via hasA11yProp do 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>.
  • Rótulo específico por tela, não "Editar" seco. /categorias renderiza 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-reviewer rodado sobre os três arquivos ao final (não após cada edição individual, por causa dos 3 disparos consecutivos do hook PostToolUse:Edit): aprovado sem violações — aria-label é atributo ARIA padrão, ortogonal às regras do DS.
  • Novo arquivo __tests__/unit/a11y-nomes-acessiveis.test.ts (não existia ainda no main — 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 de row-actions.test.ts e a11y-estado-selecao.test.ts, já que o projeto não tem @testing-library/react).
    • A asserção ancora no <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 (+ Categoria etc.), que abre antes do de editar no mesmo arquivo e também usa onClick={() => setOpen(true)}.
  • npm run test:integration não foi rodado — exige credenciais do Neon e só roda em push para main; esta mudança não toca camada de banco.

Risco e o que NÃO foi coberto

Arquivos tocados

  • components/categorias/CategoryDialog.tsx
  • components/categorias/GroupDialog.tsx
  • components/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

#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
Guiroos pushed a commit that referenced this pull request Sep 2, 2026
…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 Guiroos left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread __tests__/unit/a11y-nomes-acessiveis.test.ts Outdated
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[a11y] Lápis de editar sem nome acessível em /categorias e /contas — três diálogos de cadastro

2 participants