Pular para conteúdo

0002 — Zoom automático no mobile ao focar qualquer campo

Status CORRIGIDO
Branch fix/0002-zoom-automatico-no-mobile
Aberto em 2026-08-03
Severidade ALTA
Apps afetados api (sem impacto) · web · e2e

Status: RASCUNHO → DIAGNOSTICADO → EM CORREÇÃO → CORRIGIDO

1. Sintoma

No celular, tocar em qualquer campo faz a tela ampliar sozinha. A página fica maior que a viewport, o teclado cobre metade do que sobrou, e é preciso apertar os dedos para voltar ao tamanho normal — a cada campo. Preencher um formulário de cadastro, que tem dez campos, significa dez ampliações e dez correções manuais. O relato do usuário é direto: "quebra a utilização".

Não há mensagem de erro; nada aparece no console. É comportamento do navegador, não do app — o que explica por que nenhum teste pegou.

2. Reprodução

  1. Abrir o app num iPhone (ou no Safari com iOS emulado), autenticado.
  2. Ir a Inquilinos → Novo inquilino.
  3. Tocar no campo "Nome".

A tela amplia no instante do foco. Repete em qualquer campo de qualquer tela: login, busca das listas, formulário de imóvel, análise de crédito.

  • Ambiente: dev-remote (e local — não depende de dados).
  • Superfície: mobile, especificamente iOS/iPadOS. O Android Chrome não amplia ao focar, então quem testa em Android não vê o defeito. Não é questão de breakpoint: o gatilho é o tamanho da fonte do campo, que é o mesmo em qualquer largura.
  • PWA: não agrava nem alivia. O comportamento é do motor de renderização, e vale igual na aba do Safari e na tela instalada.
  • Dados/pré-condições: nenhuma. Vale para qualquer papel e tenant, inclusive na tela de login, antes de autenticar.

3. Esperado × Atual

  • Esperado: tocar num campo posiciona o cursor e abre o teclado. A escala da página não muda. O usuário continua podendo ampliar por conta própria, se quiser.
  • Atual: tocar num campo amplia a página automaticamente, e ela permanece ampliada até o usuário desfazer com o gesto.

4. Causa raiz

O Safari do iOS amplia a página quando o campo que recebe foco tem font-size menor que 16px. É comportamento deliberado do navegador — a suposição é que texto pequeno demais para ler precisa de aproximação — e não há como desligá-lo a não ser satisfazendo o limite, ou proibindo o zoom por completo.

Os campos do app estão todos abaixo do limite. Medido com iPhone 13 emulado:

Tela Campos Tamanho medido
Login 2 14px
Novo inquilino 10 14px (8 simples + 2 multilinha)
Busca das listas 1 14px

A origem é única: MuiOutlinedInput.input define fontSize: 14 no tema do @garantora/design-system (dist/theme/components.js:37). Todo campo do app nasce dali — TextField, Autocomplete, Select, busca das listas.

Há um segundo tamanho na medição, 13,3px, que não é gatilho: é o MuiSelect-nativeInput, o input oculto que o MUI mantém para submissão de formulário. Ele nunca recebe foco, então não provoca ampliação. Registrado aqui para não virar caça ao fantasma depois.

Não é regressão. O valor 14px existe desde que o app adotou o tema da marca; o defeito sempre esteve lá e só agora foi relatado. O viewport do index.html está correto (width=device-width, initial-scale=1, sem maximum-scale) — ele não bloqueia o zoom do usuário, o que é o comportamento acessível desejado.

5. Correção

Decidido: os campos vão a 16px em todas as telas, desktop e mobile — uma medida só, em vez de um valor por breakpoint —, e a mudança é feita no @garantora/design-system, não no apps/web.

apps/api

Sem impacto.

apps/web

A triagem previa que o trabalho aqui seria "só subir o pino". Ao implementar, isso se mostrou insuficiente — o pino sozinho não conserta (ver Registro):

  • contexts/color-mode — o merge de components era spread raso, e o app sobrescreve MuiInputBase e MuiNativeSelect, exatamente as duas chaves que a 2.9.2 usa como rede de segurança. Passou a deepmerge (@mui/utils, forma que o theme/README.md do pacote recomenda), para as duas coisas coexistirem.
  • components/shared/DocumentsPanel — dois campos de observação encolhidos por sx local (13px e 12px). sx de página vence o tema, então a correção do pacote não os alcançava. Os encolhimentos saíram.
  • package.json — pino em #v2.9.2 e @mui/utils@^6.4.9, casando a major do @mui/material 6.5.0 (o pnpm add sem versão trouxe a 9.x, muito à frente).

@garantora/design-system — entregue na 2.9.2

O pacote resolveu, e de forma mais completa que o pedido: MuiOutlinedInput.input de 14 para 16, mais MuiInputBase.input e MuiNativeSelect.select como rede de segurança, o token --field-font-size e um piso em tokens/base.css (input,select,textarea{font-size:16px}). Rótulos, dicas e erros ficaram na escala de sempre — como o pedido pedia.

apps/e2e

  • Teste de regressão: varredura que reprova se algum campo focável ficar abaixo de 16px, em viewport mobile. Diferente dos outros testes desta suíte, ele não exercita fluxo — afirma uma propriedade do CSS, que é onde o bug vive. Sem ele, um valor reintroduzido em qualquer componente volta a ampliar a tela sem ninguém notar.

6. Risco / impacto colateral

  • Densidade das telas de formulário no desktop. 14 → 16px é ~14% de aumento no texto de todo campo. O OutlinedInput tem altura fixa de 36px no tema; 16px caibem, mas o respiro vertical diminui. Precisa de olhada nas telas mais densas (análise de crédito, parâmetros) depois do bump.
  • Alinhamento com rótulo e helper text. InputLabel fica em 13px; a diferença com o valor digitado passa de 1px para 3px. Não quebra, mas muda a hierarquia visual.
  • Nada de dinheiro. Não toca pricing, billing nem sinistro — é tipografia de controle de formulário.
  • Sem risco de dado. Nenhuma migration, nenhum contrato de API.

7. Critérios de aceite

  • [x] CA1. Seguindo os passos da seção 2 num iOS (real ou emulado), tocar num campo não altera a escala da página.
  • [x] CA2. Todo campo focável (input, textarea) mede ≥ 16px em viewport mobile, em login, busca de lista e formulários de inquilino, imóvel e análise.
  • [x] CA3. O usuário continua podendo ampliar por gesto — o viewport segue sem maximum-scale nem user-scalable=no.
  • [x] CA4. No desktop, os formulários não ganham rolagem nem quebra de alinhamento com o campo 2px maior.
  • [x] CA5. O teste de regressão reprova se algum campo voltar a ficar abaixo de 16px.

8. Plano

  • [x] Reproduzir o bug e medir os tamanhos reais (iPhone 13 emulado)
  • [x] Localizar a causa raiz no tema do pacote
  • [x] @garantora/design-system — corrigido na 2.9.2 pelo mantenedor
  • [x] apps/web — pino em #v2.9.2
  • [x] apps/web — deepmerge no tema (sem ele o pino perdia a rede de segurança)
  • [x] apps/web — encolhimentos locais do DocumentsPanel removidos
  • [x] apps/e2e — teste de regressão do tamanho mínimo de campo
  • [x] Validado no browser com iPhone 13 emulado
  • [x] tsc limpo, ESLint no mesmo baseline de 53. make test não se aplica — nada de API

9. Decisões em aberto

Nenhuma pendente de decisão — as duas foram resolvidas na triagem:

  • Como corrigir: 16px em todas as telas (não só no mobile, e sem bloquear o zoom no viewport, que seria regressão de acessibilidade — WCAG 1.4.4).
  • Onde corrigir: no @garantora/design-system, com o app aguardando a publicação.

Dependência externa que bloqueia o fechamento: o pacote precisa publicar a mudança. Enquanto isso, este fix fica em DIAGNOSTICADO e o mobile segue com o defeito — o que é consequência assumida da decisão, não descuido.

10. Registro

2026-08-03 — corrigido, e o pino sozinho não bastava.

A 2.9.2 saiu com a correção completa. Mas a triagem havia previsto que o trabalho no app seria "só subir o pino", e isso estava errado por dois motivos que apareceram ao medir.

1. O merge de tema era raso, e comia a rede de segurança. buildTheme fazia {...base.components, ...appOverrides(mode)}. Spread substitui a chave inteira: como o app sobrescreve MuiInputBase (cor do placeholder) e MuiNativeSelect (ícone do caret), o fontSize: 16 que a 2.9.2 pôs nessas duas chaves era apagado. O comentário no próprio arquivo já avisava do risco — "usar a mesma chave num merge raso o apagaria" — escrito quando o pacote ainda não usava MuiInputBase. Agora usa.

Medindo, a perda era parcial, não total: o login continuou a 16px mesmo com o spread, porque aqueles campos passam por MuiOutlinedInput, que o app não sobrescreve. Ou seja, o bug principal ficaria corrigido e a rede de segurança silenciosamente ausente — o pior tipo de defeito, o que não aparece hoje. Trocado por deepmerge.

2. Dois campos encolhidos por sx local. No DocumentsPanel, as observações de upload e de edição estavam em 13px e 12px via sx de página, que vence o tema. A correção do pacote não os alcançava. Encontrados varrendo MuiInputBase-input e MuiInputBase-root com fontSize, e removidos. A varredura ampliada mostrou que eram os únicos campos editáveis encolhidos; o único outro fontSize em campo é o OTP do verify-email, a 24px, acima do piso.

Um terceiro encolhimento ficou de propósito: o Select de tipo do mesmo painel, a 13px. Select do MUI não é campo digitável — o elemento que recebe foco é um input oculto que o iOS ignora —, então não dispara ampliação.

Medição depois da correção (iPhone 13 emulado): login 2 campos a 16px; /renters e /properties 1 cada a 16px; /renters/create 9 a 16px; /settings 9 a 16px. Nenhum campo focável abaixo de 16px em nenhuma tela varrida.

Teste de regressão: apps/e2e/tests/e2e/mobile-zoom.spec.ts, 4 casos, 4 passando no chromium. Ele não exercita fluxo — afirma a propriedade do CSS, porque é onde o bug vive e porque nenhum navegador de teste reproduz a ampliação do WebKit móvel. Um dos casos guarda o outro lado da moeda: o viewport não pode ganhar user-scalable=no nem maximum-scale=1, senão corrigir o zoom automático custaria o zoom intencional (WCAG 1.4.4).

Lacuna declarada: os dois campos do DocumentsPanel não têm caso automatizado. A linha de envio do painel só é montada sob condições que não reproduzi de fora — configurar tipo de documento na área ADDITIONAL não bastou. Preferi declarar a lacuna a deixar teste vermelho ou skip silencioso. A correção neles está verificada por leitura e pela medição do tema; falta o caso que a defende de regressão.

Sobre o CA4 (densidade no desktop): os campos ficaram 2px maiores, dentro dos 36px de altura do OutlinedInput. Sem rolagem nova nem quebra de alinhamento nas telas medidas.

Suíte completa executada. Primeira rodada: 253 passaram, 3 falharam — e nenhuma das três era o fix.

  • Duas no credit-analysis-documents (chromium): resíduo meu. Depurando o teste de regressão, tentei montar a linha de envio do painel com um PUT /settings/credit-analysis-documents marcando "Comprovante de Renda" como ADDITIONAL/OPTIONAL — no tenant compartilhado do e2e, e sem desfazer. O checklist passou a mostrar duas entradas do mesmo tipo ("Falta" e "(opcional) Falta") e o getByTestId do spec bateu em dois elementos. Limpei a área que sujei; o spec voltou a 12/12. Os specs da casa mutam configuração dentro de try/finally — o meu era código de depuração solto, que é pior.
  • Uma no renters-properties-list (webkit): corrida no teste da spec 0013. O CA2 lia a contagem depois de esperar só o chip do filtro, que vem do estado do React no instante do clique; a contagem só muda quando a resposta da API chega. No navegador mais lento, lia o número antigo — 891 < 891. Trocado por expect.poll. Esse teste já estava na main: passou no merge da spec 0013 por sorte de tempo. Corrigido aqui em vez de deixar instável.

Segunda rodada, depois das duas correções: 256 passaram, 6 skipped, 0 falhas (12,3 min, chromium + firefox + webkit).

O que a suíte não prova: ela roda em chromium, firefox e no webkit desktop, e o defeito é do WebKit móvel. Nenhum deles reproduz a ampliação. A suíte confirma a propriedade (nenhum campo abaixo de 16px) e a ausência de regressão nos fluxos; a prova final é abrir num iPhone de verdade.