Pular para conteúdo

0017 — Salvar em Parâmetros (mobile) trava os botões da tab bar do rodapé

Status CORRIGIDO
Branch spec/0050-analise-credito-boavista (achado do usuário durante a spec 0050; corrigido na mesma branch por já estar aberta — não relacionado ao bureau de crédito)
Aberto em 2026-09-05
Severidade MÉDIA
Apps afetados web
Revisão do diagnóstico não realizada — corrigido em fluxo de conversação, sem o portão formal do /fix
Revisão da correção não realizada — mesmo motivo

Este registro documenta uma correção que não passou pelo laço de revisão do /fix (subagente de contexto limpo, rubrica, rodadas). Foi diagnosticada e corrigida dentro de uma sessão em curso da spec 0050, a pedido direto do usuário, com evidência reproduzida ao vivo contra o dev-remote e red-green manual — mas sem o portão. Registrado aqui para manter a convenção de numeração e o histórico, não como prova de que passou pela régua normal.

1. Sintoma

Relato do usuário: "Em parametrizações no mobile quando clico em salvar configurações trava os botões do rodape menu." — depois de salvar em Parâmetros (/settings) no mobile, os botões da tab bar (Painel · Análise de Crédito · Garantias · Mais) param de responder ao toque por alguns segundos, sem nenhum indício visual do motivo.

2. Reprodução

  1. Autenticado como ADMIN_TENANT, viewport ≤ 599px (casca mobile).
  2. Abrir a gaveta (aba "Mais") → Parâmetros.
  3. Tocar em "Salvar configurações".
  4. Imediatamente depois, tentar tocar em qualquer aba da tab bar (ex.: "Garantias") — o toque não navega, pelos ~3 segundos em que o aviso "Configurações salvas com sucesso" está na tela.

Reproduzido contra o dev-remote (https://dev.garantora.com.br/app), com um script Playwright ad hoc (não versionado) que fez login como gestor@e2e.test e mediu, via document.elementFromPoint, qual elemento realmente recebe o toque na região da tab bar no instante em que o aviso está visível.

3. Esperado × Atual

  • Esperado: a tab bar do rodapé continua respondendo ao toque a qualquer momento, inclusive com um aviso de sucesso na tela.
  • Atual: por ~3s depois de qualquer salvamento bem-sucedido em Parâmetros, o toque na tab bar é interceptado pelo próprio Snackbar de confirmação.

4. Causa raiz

  • Onde: apps/web/src/pages/settings/index.tsx — o <Snackbar open={success} .../> (fim do arquivo) não tinha anchorOrigin nem sx de posição, usando o padrão do MUI ({ vertical: "bottom", horizontal: "left" }, que no breakpoint xs estica para quase toda a largura, a 8px do fundo da viewport).
  • O mecanismo: o Snackbar é renderizado por portal, direto em document.body — fora da árvore de layout da casca (components/mobile-shell/index.tsx) —, com position: fixed, z-index: 1400 (padrão MUI) e pointer-events: auto. A tab bar da casca (data-testid="mobile-tabbar", TABBAR_H = 62px) ocupa exatamente a mesma faixa vertical do fundo da viewport onde o Snackbar aparece por padrão — medido no dev-remote em 375×812: tab bar em top: 750, bottom: 812; Snackbar em top: 754.5, bottom: 804, quase 100% sobreposto. Como o Snackbar tem z-index muito mais alto que a tab bar (que não define nenhum) e pointer-events: auto, ele fica acima na pilha de empilhamento e intercepta o toque — confirmado com document.elementFromPoint em cinco pontos ao longo da largura da tab bar: todos resolviam para elementos do Snackbar (MuiSnackbarContent-message/-root), nenhum para a tab bar. Nada quebra visualmente porque o texto do aviso, sobre o fundo escuro da tab bar, é pouco contrastante — daí parecer que os botões simplesmente pararam de responder, sem indicar por quê.
  • O alcance: o mesmo padrão (<Snackbar> sem deslocamento vertical seguro para mobile) existia em mais dois lugares — apps/web/src/pages/my-garantidora/index.tsx e apps/web/src/pages/settings/credit-analysis-documents/index.tsx. Os dois já tinham anchorOrigin={{ horizontal: "center" }}, o que evita a sobreposição horizontal (cobrir a largura toda) mas não a vertical — o Snackbar continua na mesma faixa de 62px do fundo, agora só centralizado, cobrindo a aba que calhar de estar no meio. Verificado ao vivo (mesma técnica) em my-garantidora: antes da correção, o Snackbar ficava em top: 754.5, dentro da faixa da tab bar; depois, em top: 692, bottom: 742, acima dela. credit-analysis-documents recebeu a mesma correção por analogia (mesmo código, mesma causa) — não verificado ao vivo por não ter sido priorizado no tempo disponível.

5. Revisão do diagnóstico

Não realizada — ver nota no cabeçalho.

6. Correção

apps/web

  • components/mobile-shell/index.tsx — TABBAR_H (já existia, só uso interno) passou a export const, com docstring explicando o novo consumidor.
  • pages/settings/index.tsx, pages/my-garantidora/index.tsx, pages/settings/credit-analysis-documents/index.tsx — cada <Snackbar> ganhou sx={{ bottom: { xs: \${TABBAR_H + 8}px` } }}, somando a altura da tab bar ao deslocamento só até o breakpointxs(abaixo desm`) — acima disso o app usa a sider desktop e não há tab bar para desviar, então o comportamento padrão do MUI (24px do fundo) continua valendo sem alteração.
  • Por que não uma trava de pointer-events ou z-index menor no Snackbar, em vez de movê-lo: deixaria o toque passar, mas o aviso continuaria visualmente encostado (e parcialmente ilegível) sobre a tab bar — o problema visual sobreviveria, só o funcional sumiria. Mover o Snackbar acima da tab bar resolve os dois.

apps/e2e

  • Spec novo: tests/e2e/settings-mobile-snackbar.spec.ts, dois casos — (1) no instante em que o aviso aparece, document.elementFromPoint sobre o centro da aba "Garantias" resolve para a própria aba, não para o Snackbar (teste determinístico: não usa .click(), que tem retentativa e mascararia o defeito depois que o Snackbar some sozinho); (2) o retângulo do Snackbar não invade verticalmente o da tab bar. Cobre só pages/settings/index.tsx — os outros dois pontos não ganharam spec próprio.

7. Risco / impacto colateral

  • Nenhum — é reposicionamento puro de um elemento sem estado nem dado; não toca contrato de API, permissão nem cálculo de negócio.
  • credit-analysis-documents/index.tsx não foi verificado ao vivo (só por analogia de código) — risco baixo, mesmo padrão exato dos outros dois, mas fica registrado como lacuna.

8. Critérios de aceite

  • [x] CA1. Depois de salvar em Parâmetros no mobile, a tab bar do rodapé continua clicável enquanto o aviso de sucesso está na tela. (settings-mobile-snackbar.spec.ts, caso 1)
  • [x] CA2. O retângulo do Snackbar não se sobrepõe ao da tab bar. (mesma spec, caso 2)
  • [x] CA3. O mesmo defeito, achado por inspeção de código nos outros dois usos de <Snackbar> do app, recebeu a mesma correção. (my-garantidora verificado ao vivo; credit-analysis-documents por analogia — ver seção 7)

9. Plano

  • [x] Reproduzir o defeito contra o dev-remote com evidência de geometria/hit-test, não só relato
  • [x] Escrever o teste e2e e vê-lo falhar contra o build com o defeito
  • [x] Corrigir a causa raiz nos três arquivos afetados
  • [x] Red-green verificado: revertido pages/settings/index.tsx → rebuild → os dois casos do spec novo falham (um com null no lugar de mobile-tab-garantias, outro com 804 > 750) → restaurado → rebuild → os dois voltam a passar
  • [x] tsc --noEmit limpo depois da correção
  • [x] make test-web (suíte inteira) — 214/214 verdes, sem regressão

10. Decisões em aberto

Nenhuma — a correção não exigiu nenhuma decisão de negócio, só técnica.

11. Revisão da correção

Não realizada — ver nota no cabeçalho.

O teste sobreviveria à reversão? Sim, confirmado por red-green de verdade (seção 9), contra o dev-remote real, não em ambiente isolado.

12. Registro

O que era: o Snackbar de confirmação de "Salvar configurações" (e dois outros, my-garantidora e credit-analysis-documents) usava posicionamento padrão do MUI, que no mobile cai exatamente na faixa de 62px que a tab bar da casca ocupa no fundo da tela. Por ter z-index mais alto e renderizar via portal fora da árvore de layout do app, o Snackbar intercepta todo toque destinado à tab bar enquanto está visível (~3-4s), sem indicar visualmente o motivo — parece que os botões "travaram".

O que virou: TABBAR_H (constante já existente em mobile-shell/index.tsx) exportada; os três Snackbar do app ganharam sx={{ bottom: { xs: \${TABBAR_H + 8}px` } }}`, subindo acima da tab bar só no breakpoint mobile.

Como foi achado e confirmado: a pedido do usuário, reproduzido com um script Playwright ad hoc contra o dev-remote (login como gestor@e2e.test, usuário de e2e já seedado — app.db.seed_e2e, idempotente), medindo com document.elementFromPoint/getBoundingClientRect que o Snackbar de fato ocupa a mesma região da tab bar e intercepta o toque ali. Depois da correção, a mesma medição confirmou o Snackbar acima da tab bar, sem sobreposição, em dois dos três pontos afetados (settings, my-garantidora); o terceiro (credit-analysis-documents) recebeu a mesma correção por analogia, sem verificação ao vivo separada.

Não passou pelo portão do /fix: diagnosticado e corrigido dentro do fluxo de conversação da spec 0050 (branch já aberta), a pedido direto do usuário — sem o laço de revisão de subagente de contexto limpo que o /fix implement normalmente exige. Marcado explicitamente no cabeçalho para não passar como se tivesse sido revisado.

Efeito colateral do diagnóstico: a checagem por .click() do Playwright no caso 1 do spec novo teve que ser trocada por um hit-test direto (elementFromPoint) porque o .click() tem retentativa automática — como o Snackbar some sozinho em ~3s, um clique comum eventualmente funciona mesmo com o defeito presente (confirmado: a primeira versão do teste passou tanto antes quanto depois da correção, o que não prova nada).