Pular para conteúdo

0014 — No celular, a Ajuda esconde metade do índice atrás de um gesto que a tela não anuncia

Status CORRIGIDO
Branch fix/0014-ajuda-mobile-pilulas-scroll-horizontal
Aberto em 2026-08-24
Severidade MÉDIA
Apps afetados web · e2e
Revisão do diagnóstico aprovado na rodada 1
Revisão da correção aprovado na rodada 2

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

Nota de classificação, para quem revisa. O comportamento descrito abaixo é o que a spec 0045, §7.2 especificou, e o código o implementa fielmente. O que este registro sustenta é que a decisão de §7.2 derrota a razão que a própria §7.2 dá para existir — e que isso é conferível na tela, não é questão de gosto. O usuário abriu como /fix e pediu a substituição do componente; a seção 4 explica por que a causa é a decisão de desenho, e não um erro de implementação dela.

E esta branch carrega duas coisas, de propósito. Além da correção acima, o usuário pediu em 2026-08-24 que a Ajuda parasse de rolar junto com o menu da gaveta, ficando fixa como a linha de Novidades — navegação, que não decorre da causa raiz da seção 4. Está isolada na seção 6.3, com critérios próprios (CA13 e CA14), para o portão da correção poder julgá-la separada do conserto. O usuário decidiu que ela entra aqui, e não numa spec própria.

A seção 6.2 registra um caminho que foi construído e depois retirado a pedido dele: um ícone de Ajuda na barra do topo do celular. Ele não está no diff; o texto ficou porque explica como a 6.3 foi encontrada.

1. Sintoma

Na tela Ajuda (/ajuda), em celular (375px), o índice de assuntos é uma faixa horizontal de pílulas no topo, e abaixo dela a lista de títulos só do assunto marcado. Com os dois assuntos publicados hoje, o resultado na tela é:

  • a segunda pílula ("Gestão de imobiliárias") nasce cortada na borda direita — o texto termina no meio do glifo, cortado pelo overflow do contêiner. Não é reticência: a pílula não tem textOverflow: "ellipsis", então nada na tela sinaliza que há mais à direita;
  • a única linha de conteúdo visível é um tutorial (Abrir uma análise de crédito, 7:09), com ~79% da tela vazia abaixo dela;
  • o outro assunto e o tutorial dele existem apenas atrás de um arraste lateral, num elemento cuja barra de rolagem está deliberadamente escondida.

Não há erro de console, nem falha de rede: a tela renderiza como o código manda. As medições que sustentam cada afirmação acima estão na seção 2, tiradas do dev-remote.

2. Reprodução

  1. Logar como imob@e2e.test (senha S21d2qu1) no dev-remote.
  2. Abrir https://dev.garantora.com.br/app/ajuda com viewport de 375 × 812.
  3. Observar a faixa de pílulas no topo e a lista abaixo.

Medido no navegador (Playwright, page.evaluate sobre a tela carregada):

viewport            375 × 812
[data-testid="ajuda-submenu-assuntos"]
    top 64px, altura 52px, scrollWidth 433, clientWidth 375  → rola para o lado (58px ocultos)
    2 pílulas
[data-testid="ajuda-lista"]
    top 124px, altura 44px, 1 linha de tutorial
documentElement.scrollWidth == clientWidth (375)  → a **página** não rola na horizontal

Ou seja: o conteúdo termina em 168px de uma tela de 812px; os 644px restantes (79%) estão vazios, enquanto 58px de índice estão fora da vista.

  • Ambiente: dev-remote (https://dev.garantora.com.br/app), bundle reconstruído hoje (commit 6d3e165, imagem garantora-devremote-web).
  • Superfície: mobile, e o defeito é do breakpoint. Abaixo de sm (600px, useIsMobile) o conteúdo é a lista de títulos e o desperdício de altura é total; entre 600 e 1199px a mesma faixa aparece, mas ali ela cabe (medido em 900px: scrollWidth == clientWidth == 668) e o conteúdo são cartões, então o sintoma característico — índice cortado — não se manifesta. A partir de 1200px não há faixa nenhuma: é a barra lateral, e a tela está correta.
  • PWA: não agrava o defeito em si; agrava só a verificação da correção — a tela instalada continua servindo o bundle anterior até o service worker atualizar.
  • Dados/pré-condições: basta haver mais de um assunto no catálogo (apps/web/src/tutoriais/*.json — hoje imobiliarias e gestao-imobiliarias). Com um assunto só a faixa não rola e o corte não aparece; o desperdício de altura, sim.

3. Esperado × Atual

  • Esperado: em 375px, quem abre a Ajuda vê o índice inteiro — todos os assuntos do catálogo ao mesmo tempo, nenhum cortado na borda — e alcança os vídeos de qualquer um deles sem precisar descobrir um gesto que a tela não anuncia. A altura, que é o recurso escasso, é gasta mostrando o que existe. (Que forma isso toma — lista vertical que abre os vídeos dentro — é a correção pedida pelo usuário, e está na seção 6.)
  • Atual: vê um assunto marcado e o começo do nome do segundo, cortado na borda; os vídeos dos demais assuntos só existem depois de um arraste lateral não anunciado; e 79% da tela fica em branco.

4. Causa raiz

Onde

  • apps/web/src/pages/ajuda/NavegadorDeAssuntos.tsx:67-117 — a variante submenu: um Box com display: flex, flexWrap: "nowrap" e overflowX: "auto" (NavegadorDeAssuntos.tsx:72, :74 e :75), com a barra de rolagem explicitamente escondida em NavegadorDeAssuntos.tsx:79-80 ("&::-webkit-scrollbar": { display: "none" } e scrollbarWidth: "none"). Cada pílula leva flexShrink: 0 e whiteSpace: "nowrap" (NavegadorDeAssuntos.tsx:96 e :109).
  • apps/web/src/pages/ajuda/index.tsx:119 — a escolha da variante: variante={comBarra ? "barra" : "submenu"}, com comBarra definido em apps/web/src/pages/ajuda/index.tsx:40 como useMediaQuery(theme.breakpoints.up("lg")). Logo, todo viewport abaixo de 1200px recebe a faixa, inclusive os 375px.
  • apps/web/src/pages/ajuda/index.tsx:198 — a faixa é renderizada acima da grade de conteúdo ({!comBarra && navegador}), e o conteúdo de apps/web/src/pages/ajuda/index.tsx:129-169 lista os tutoriais de corrente apenas (corrente?.tutoriais.map, index.tsx:130).

Não é regressão de implementação: é a decisão de desenho que entrou inteira no commit 22b5020 ("feat(spec 0045): a Ajuda vira um navegador por assunto, e não mais uma grade"), especificada em spec/0045-grupos-e-navegacao-na-ajuda/spec.md §7.2 — "Uma faixa horizontal com os assuntos, rolável para o lado quando não couberem… O que o app já tem de mais próximo é a linha rolável de pílulas do MobileFilterBar". O código faz exatamente isso. O defeito está na decisão, não no cumprimento dela.

O mecanismo

A causa é uma analogia errada: §7.2 tomou emprestada a mecânica do MobileFilterBar — faixa rolável de pílulas com item marcado — para uma coisa que não é um filtro, é o índice. As duas mecânicas parecem iguais na tela e diferem no que custa esconder uma opção:

  1. Num filtro, o que a faixa esconde é uma opção; a lista filtrada continua na tela. Quem não arrasta ainda vê o conteúdo — só vê tudo, em vez de um recorte. Esconder opção atrás de um gesto custa pouco, e é por isso que o MobileFilterBar pode esconder a barra de rolagem sem prejuízo.
  2. Num índice, o que a faixa esconde é o único caminho até o conteúdo. Em index.tsx:130 a lista abaixo mostra corrente?.tutoriais — os tutoriais do assunto marcado, e só deles. Então cada assunto fora da vista leva junto todos os vídeos dele. O que na origem era "uma opção a menos" vira aqui "uma parte do catálogo que não existe".

Daí sai, elo a elo, o que se vê em 375px:

  • flexWrap: "nowrap" + flexShrink: 0 + whiteSpace: "nowrap" fazem as pílulas manterem a largura do título inteiro numa linha só → o conteúdo mede 433px numa caixa de 375px;
  • overflowX: "auto" contém esse excesso dentro do bloco — o que é a razão de a página não rolar na horizontal (medido: documentElement.scrollWidth == 375), e é também o que faz o excesso ficar invisível;
  • a barra de rolagem escondida (:79-80) tira da faixa o indício que o navegador daria de graça. O peso desse elo depende da superfície: em 375px emulado, que é onde a medição desta seção foi feita, a barra existiria; num aparelho de verdade ela é overlay e já estaria ausente em repouso de qualquer forma. A causa não depende deste elo — ela é o nowrap contendo o excesso num bloco que rola, com a lista abaixo presa a corrente;
  • sobra como único indício o corte da pílula na borda, e ele é acidente de largura, não sinal de desenho: basta a fronteira entre duas pílulas cair perto da borda para a faixa parecer completa estando truncada — o que hoje não acontece por sorte dos dois títulos, e passa a acontecer assim que o catálogo crescer;
  • ao mesmo tempo, a faixa cobra 52px de altura para exibir um assunto por vez, enquanto o conteúdo de um assunto (uma linha de 44px, hoje) deixa 79% da tela vazia. A altura, que §7.2 declara ser o recurso escasso em 375px, está sendo gasta no eixo errado: o índice ocupa o eixo horizontal — onde há 375px e faltam — e recusa o vertical, onde sobram 644px.

E é isto que fecha o mecanismo: a §7.2 justifica a lista de títulos (em vez de cartões) dizendo que "uma lista de títulos mostra o assunto inteiro de uma vez, que é o que a pessoa veio ver". A faixa de pílulas aplica o raciocínio oposto ao nível de cima — mostra um assunto de cada vez —, de modo que a tela cumpre a letra da §7.2 e derrota a razão que a §7.2 dá para si mesma. Não há largura de tela em que a faixa deixe de fazer isso: com dois assuntos ela corta; com um catálogo maior, esconde mais.

Por que aqui e não em toda parte: o MobileFilterBar continua correto onde está, porque lá a premissa "o conteúdo permanece visível" é verdadeira. O defeito aparece só na Ajuda, que é o único lugar onde a faixa rolável é a navegação, e não um refinamento dela.

Por que agora: a faixa nasceu com a spec 0045 (commit 22b5020, mesclado nesta semana), que foi também quando a Ajuda passou a ter mais de um assunto. Antes havia grade única sem índice, e portanto nada a esconder.

O alcance

  • A mesma variante submenu serve toda a faixa < lg (index.tsx:40 e :119), não só o celular. Entre 600 e 1199px o índice também mostra um assunto por vez, e também gastará altura para isso; hoje ele cabe na largura (medido em 900px: scrollWidth == clientWidth == 668), então o sintoma do corte ainda não aparece ali — mas aparecerá assim que o catálogo crescer. Quem corrigir precisa decidir explicitamente o que fazer nessa faixa, em vez de corrigir só os 375px e deixar a mesma causa viva 300px acima.
  • A causa não está no MobileFilterBar. Procurei os outros usos da mecânica: grep -rn "MobileFilterBar" apps/web/src — ver seção 7. Nos usos de filtro a premissa se sustenta e não há o que corrigir; o que se copiou para a Ajuda foi a aparência, não a situação.
  • A página do tutorial não é afetada. Em 375px ela não renderiza a faixa (spec 0045 §7.2, e o e2e ajuda.spec.ts:430 prova com toHaveCount(0)), então o defeito vive só na tela de lista.
  • O useTrazerAVista (NavegadorDeAssuntos.tsx:47-53) existe justamente para compensar o defeito: ele chama scrollIntoView({ inline: "center" }) para trazer a pílula marcada à vista. É paliativo da mesma causa — resolve "a marca está fora da tela", não "metade do índice está fora da tela". Se a faixa sair no celular, esse remendo sai com ela ali.

5. Revisão do diagnóstico

Rodada Critical Important Minor Veredicto
1 0 0 3 APROVADO

Resultado: aprovado na rodada 1.

A rodada conferiu no código as 14 citações da seção 4 — as duas dos componentes da Ajuda, as do ajuda.spec.ts, o commit 22b5020 e as três da §7.2 da spec 0045 — e todas batem. Confirmou também, sem rodar navegador, dois números da seção 2: os 52px da faixa (theme.spacing é 4 em apps/web/src/design-system/theme/index.ts:58, então pt: 1 + pb: 1 sobre minHeight: 44) e os 668px de 900px (DRAWER_WIDTH = 232 em apps/web/src/components/custom-sider/index.tsx:25).

Os três Minor foram corrigidos na mesma rodada, e não sobraram pendências:

  1. O corte não é reticência. A pílula não tem textOverflow: "ellipsis"; o texto é cortado pelo overflow do contêiner, no meio do glifo. A seção 1 dizia Gestão de imob…, o que sugeria um sinal de desenho onde não há nenhum — e o mecanismo se apoia justamente em não haver.
  2. O exemplo do corte provava o contrário do que afirmava. Com dois assuntos de nomes curtos nada ficaria escondido, então não havia truncamento a "parecer completo". O argumento vale para catálogo maior, e o texto passou a dizer isso.
  3. O elo da barra de rolagem escondida era mais forte que a superfície sustenta. Em aparelho real a barra é overlay e já estaria ausente em repouso; o elo pesa no 375px emulado, que é onde a medição foi feita. O mecanismo passou a declarar que não depende desse elo.

Pendências aceitas: nenhuma.

Aviso registrado para o portão da correção: o alcance nomeia a faixa de 600 a 1199px, e a seção 6 só prevê mudar ajuda.spec.ts:401. O bloco de 900px (ajuda.spec.ts:378) também afirma ajuda-submenu-assuntos visível — e continua verdadeiro, porque a decisão da seção 10 deixa o tablet como está. Se essa decisão mudar, aquele teste muda junto.

6. Correção

apps/api

Sem impacto — a Ajuda lê catálogo do bundle (import.meta.glob), não a API.

apps/web — 6.1 A correção da causa raiz

  • Onde: apps/web/src/pages/ajuda/NavegadorDeAssuntos.tsx e apps/web/src/pages/ajuda/index.tsx.
  • O quê: no celular, trocar a faixa horizontal por uma lista vertical de assuntos que abre os vídeos dentro dela — a mesma mecânica de abre-e-fecha que a variante barra já usa no desktop (NavegadorDeAssuntos.tsx:137-221), em largura cheia e sem a moldura sticky. Tocar num assunto fechado abre os vídeos dele e navega para /ajuda/:assunto (preservando o endereço e o "Ver o assunto" da §7.2); tocar no aberto recolhe, sem navegar — exatamente o par aoEscolherAssunto / aoAlternarExpansao de hoje.
  • Com isso a lista de títulos separada (index.tsx:129-169) deixa de existir como bloco próprio no celular: ela passa para dentro do assunto aberto. As linhas continuam com título truncado, ícone de play e duração, como a §7.2 pede.
  • Faixa de 600 a 1199px: não muda — decisão do usuário registrada na seção 10.
  • data-testid: manter ajuda-assunto-<id> e ajuda-linha-<slug> (o e2e já os usa); o wrapper ajuda-submenu-assuntos deixa de existir no celular e o e2e correspondente passa a valer para o navegador vertical.
  • Responsividade & PWA: a correção é do celular; não pode mexer na barra lateral do desktop, que está correta. Verificar nos dois viewports, contra o dev-remote reconstruído.

apps/web — 6.2 O atalho na barra do topo — construído e retirado

Não está no diff, e a história fica aqui porque explica a 6.3.

O usuário pediu "fixar a Ajuda no menu igual às versões, para ficar sempre visível". Lido como "o único lugar onde novidades aparece e Ajuda não é a barra do topo", isso virou um ícone de interrogação na barra do topo do celular, que foi implementado, testado e chegou a rodar no dev-remote por duas rodadas de portão.

Era a leitura errada, e quem apontou foi o usuário vendo a tela: "a Ajuda no dev-remote ainda está no final do menu no mobile". O que ele queria dizer com "fixar igual às versões" era literal — a linha de Novidades não rola, a da Ajuda rolava. O lugar nunca foi o problema; a rolagem era. Isso é a 6.3.

Com a 6.3 no ar, o ícone virou o segundo caminho permanente para o mesmo destino, e o usuário pediu para tirá-lo e devolver a barra do topo ao que era. Foi retirado por inteiro — o IconButton, os testes que só existiam por causa dele (o do ícone, o do pior caso da barra, o de não vazar para o /admin) e os critérios CA9 a CA12. A barra do topo do celular voltou a ser título + ações da tela, byte a byte: git diff main sobre mobile-shell/index.tsx mostra só a mudança da gaveta.

O que sobra de aprendizado, e é o motivo de isto não ter sido apagado: a pergunta que ofereceu as três opções foi feita cedo demais, sobre um pedido de uma frase, e as três descreviam lugares — nenhuma perguntava o que está errado hoje. A resposta certa estava a um grep de distância, na estrutura da gaveta: três regiões, e as duas linhas em regiões diferentes.

apps/web — 6.3 A Ajuda sai da rolagem da gaveta (pedido à parte)

Também não sai da causa raiz da seção 4, e foi o pedido original do usuário — só ficou claro depois de a 6.2 estar no ar, quando ele apontou que "a Ajuda no dev-remote ainda está no final do menu no mobile".

  • O que existia. A gaveta tem três regiões (RN1 da spec 0011): cabeçalho fixo, miolo que rola, rodapé fixo. O item da Ajuda era renderizado dentro do miolo (apps/web/src/components/mobile-shell/index.tsx, o bloco {itemRodape && …} que ficava logo depois do groups.map), enquanto a linha de Novidades é flex: "0 0 auto" fora dele, logo acima do rodapé de conta. Daí a assimetria que o usuário viu: as duas coisas parecem morar no mesmo lugar, mas só uma fica.
  • O quê. O bloco da Ajuda saiu do miolo e virou faixa própria, flex: "0 0 auto", entre o miolo e a linha de Novidades — o mesmo tratamento, pelo mesmo motivo.
  • A spec 0042 não é contrariada aqui. Ela escolheu "o fim da gaveta" como lugar, e o lugar continua sendo esse; o que muda é ele deixar de ser um fim que se afasta quando a navegação cresce.
  • data-testid: inalterado (mobile-nav-ajuda) — é o mesmo item, noutra região.

apps/e2e

  • Teste de regressão: em apps/e2e/tests/e2e/ajuda.spec.ts, bloco Ajuda — mobile. O cenário que hoje passa e precisa mudar de sentido é ajuda.spec.ts:401, que exige ajuda-submenu-assuntos visível em 375px. O teste novo prova o que hoje falha: em 375px, todos os assuntos do catálogo estão visíveis e alcançáveis sem rolagem lateral do índice, e os vídeos do assunto aberto aparecem sob ele.
  • Da faixa fixa da 6.3: aberta a gaveta em 375px, o item da Ajuda não é descendente de mobile-drawer-nav — a região que rola —, e continua na tela com o miolo rolado até o fim. A asserção é estrutural de propósito: uma que dependesse de o menu de hoje ser longo o bastante para empurrá-la para fora da tela passaria a mentir no dia em que um grupo saísse.

7. Risco / impacto colateral

  • Não toca dinheiro, permissão nem isolamento. A Ajuda é tela de leitura sobre arquivo do bundle; nenhum papel vê mais nem menos por causa desta correção (RN7 da spec 0042 continua: todo autenticado vê tudo).
  • Contradiz a §7.2 da spec 0045 por decisão do usuário, e a divergência fica registrada aqui em vez de reabrir a spec — seção 10.
  • O MobileFilterBar não muda. Confirmar com grep -rn "MobileFilterBar" apps/web/src que a Ajuda não o importa (ela não importa: usa Box próprio), de modo que a correção não alcança as telas de filtro.
  • Risco de quebrar o desktop: a variante barra e a variante nova compartilham o mesmo componente. A correção precisa deixar a barra intacta — o e2e de desktop (ajuda-barra-assuntos) é a rede.
  • Da faixa fixa da 6.3: ela mexe na gaveta de toda tela do celular, e o que muda de verdade é o orçamento de altura: a região que rola perde ~48px para uma faixa fixa a mais. Em telas baixas isso significa menos navegação visível de uma vez — é o preço de a Ajuda não se afastar, e é o mesmo que a linha de Novidades já cobra desde a spec 0043. O mobile-shell.spec.ts (as três regiões da RN1) e o layout-mobile.spec.ts são a rede. O ícone da 6.2, que teria tocado a barra do topo de toda tela, não está mais no diff — o maior alcance desta branch saiu com ele.

8. Critérios de aceite

  • [x] CA1. Seguindo os passos da seção 2, em 375px o índice da Ajuda é uma lista vertical: os dois assuntos do catálogo aparecem na tela ao mesmo tempo, nenhum cortado na borda.
  • [x] CA2. O índice do celular não rola na horizontal: medido no navegador, o elemento do navegador tem scrollWidth == clientWidth.
  • [x] CA3. Tocar num assunto fechado abre a lista de vídeos dele dentro da própria linha e leva o endereço para /ajuda/:assunto; tocar no assunto aberto recolhe sem mudar o endereço.
  • [x] CA4. Tocar num vídeo abre o tutorial, e o "Ver o assunto" volta para o assunto de onde a pessoa saiu (comportamento da §7.2, preservado).
  • [x] CA5. A página em 375px continua sem rolagem horizontal (documentElement.scrollWidth == window.innerWidth).
  • [x] CA6. Alvo de toque de 44px mantido no cabeçalho do assunto e nas linhas de vídeo.
  • [x] CA7. O desktop (≥1200px) não muda: a barra lateral à direita continua como está, e o e2e de desktop da Ajuda segue verde.
  • [x] CA8. O spec e2e ajuda.spec.ts foi atualizado e o cenário novo falha sobre o código de hoje.

Da faixa fixa da seção 6.3:

  • [x] CA13. Aberta a gaveta em 375px, o item da Ajuda não é descendente de mobile-drawer-nav — a região que rola.
  • [x] CA14. Com o miolo da gaveta rolado até o fim, a Ajuda continua dentro da tela.

Do atalho da seção 6.2: retirados. CA9 a CA12 cobriam o ícone da barra do topo, que foi construído e depois removido a pedido do usuário — ver a seção 6.2. Não há critério em aberto por causa disso: o que eles mediam deixou de existir, e a barra do topo voltou a ser o que era.

9. Plano

  • [x] Escrever o teste que reproduz o bug e vê-lo falhar (apps/e2e/tests/e2e/ajuda.spec.ts) — antes de qualquer correção
  • [x] Corrigir a causa raiz no apps/web (não há camada de API envolvida) — seção 6.1
  • [x] Depois, e em commit separado: o atalho da seção 6.2, para o revisor e um eventual git revert poderem separar as duas coisas
  • [x] Red-green verificado: o cenário novo passa → reverter a correção → rodar → falha → restaurar → passa. Como é correção visual, a prova complementar é o script de screenshot em 375px e em desktop, com as medições de scrollWidth
  • [x] make preflight, make test e make lint sem apontamento novo nos arquivos tocados
  • [ ] Validar no browser de verdade em 375px e desktop, contra o dev-remote reconstruído (/devremote-rebuild), reabrindo a tela para furar o cache do service worker

10. Decisões em aberto

Nenhuma. As duas que travavam a correção foram decididas pelo usuário em 2026-08-24:

  • A faixa de 600 a 1199px não acompanha. A troca vale abaixo de 600px (useIsMobile, a fonte única do "modo mobile" desde a RN1 da spec 0004). Entre 600 e 1199px ficam as pílulas e os cartões, como hoje: ali a faixa cabe (medido em 900px: scrollWidth == clientWidth == 668), o corte não acontece, e trocar o índice sem trocar os cartões duplicaria os títulos na mesma tela — mais mudança do que o pedido. A causa continua viva nessa faixa, e voltará a morder quando o catálogo crescer a ponto de a faixa não caber em 900px; está registrado aqui de propósito, para não parecer resolvido.
  • A §7.2 da spec 0045 fica como está, e a divergência é registrada neste fix. A spec é o registro do que foi decidido então; este registro é o de por que a decisão mudou.

11. Revisão da correção

Rodada Critical Important Minor Veredicto
1 0 1 5 REPROVADO
2 0 0 4 APROVADO

Resultado: aprovado na rodada 2.

O que a rodada 1 apontou, e o que se fez

O Important — o colateral de maior alcance da branch não tinha medição nenhuma. O ícone da 6.2 entra na barra do topo de toda tela do tenant abaixo de 600px e come 48px fixos do espaço em que o título disputa flex: 1; a própria seção 7 nomeia mobile-shell.spec.ts e layout-mobile.spec.ts como a rede, e a seção 11 relatava só o ajuda.spec.ts. O revisor achou ainda uma terceira rede que ninguém tinha citado — apps/e2e/tests/e2e/page-header.spec.ts:170, que exige título com mais de 40px em 375px. Foram rodadas as três, contra o dev-remote reconstruído: ver o resultado abaixo.

As correções de rodada:

  1. CA12 reescrito com piso mensurável. A redação anterior — "sem truncar o título mais do que já truncava" — não pode ser verdade de nenhum desenho que acrescente controle permanente a uma barra de largura fixa, e um CA sem piso se fecha por afirmação. Passou a ser o piso do fix 0005 (>40px), agora medido dentro do próprio teste do ícone.
  2. Uma asserção que a correção esvaziou. ajuda.spec.ts exigia ajuda-submenu-assuntos ausente na página do tutorial em 375px; como o fix tirou esse elemento de todo o viewport abaixo de 600px, a asserção passou a ser verdadeira de graça — passaria mesmo se a página do tutorial renderizasse o navegador novo e comesse a altura do vídeo. Ganhou ajuda-lista-assuntos ao lado.
  3. O useTrazerAVista saiu da variante do celular, como a seção 4 previa. Ele compensava um índice que não cabia na tela; aqui cabe, e scrollIntoView dentro do <main> rolável moveria a página logo depois do toque sem ter o que corrigir.
  4. CA6 deixou de ser minHeight lido no código e passou a ser medido: 44px no cabeçalho do assunto e na linha do vídeo.

Dois achados recusados, com o motivo — não por discordância de estilo, mas porque a leitura que os sustenta não bate com o código:

  • "o ícone rouba o canto que a ação principal ocupava de propósito". O comentário citado do PageHeader diz que a ação principal é "a última coisa antes dos controles do usuário" — ou seja, há controle de usuário à direita dela por desenho. E é exatamente a ordem da barra do desktop (apps/web/src/components/app-layout/index.tsx:88-93): título, ações da tela, depois os controles do aplicativo (novidades, tema, avatar). Pôr o ícone antes do actionsRef é que divergiria das duas coisas. O raciocínio ficou escrito no código, que era o que faltava.
  • "data-testid do namespace ajuda-* dentro da casca". É o precedente do IconeNovidades, que usa versao-icone na barra do desktop pelo mesmo motivo: o testid nomeia o destino, não a casca que o hospeda. E a derivação a partir do path é a mesma convenção do item da gaveta logo abaixo (mobile-nav-${path}).

Aceito sem corrigir, e é o que o revisor apontou de mais fino: no teste do índice, a asserção scrollWidth === clientWidth não é a que mata o desenho antigo — sobre um flex-direction: column; width: 100% ela é quase tautológica. Quem mata é a existência do ajuda-lista-assuntos e a borda do segundo assunto dentro da tela. A asserção fica porque guarda outra coisa daqui para a frente (um filho estourando a caixa), mas o comentário do teste que a chamava de "a asserção que morre se a faixa voltar" estava exagerando, e isto aqui é o registro disso.

O teste sobreviveria à reversão? Sim, e a prova não é um git revert mental: os quatro testes foram rodados contra o build sem a correção e vistos vermelhos, e depois contra o build com ela. Como a imagem do web embute o código, cada execução mediu um bundle diferente de verdade.

Vermelho — dev-remote no bundle index-C0Tr_OVA.js (a main, commit 6d3e165, sem uma linha desta branch), npx playwright test tests/e2e/ajuda.spec.ts --workers=1:

✘ o índice mostra todos os assuntos de uma vez, sem rolagem lateral
      Error: element(s) not found — getByTestId('ajuda-lista-assuntos')
✘ tocar num assunto abre os vídeos dele dentro da linha, e leva o endereço
      Error: element(s) not found — ajuda-assunto-bloco-imobiliarias >> ajuda-linha-…
✘ tocar no assunto aberto recolhe, e recolher não navega
✘ o ícone da Ajuda fica na barra do topo e não passa pela gaveta
      Error: element(s) not found — getByTestId('ajuda-icone')

E um deles nasceu falso. Na primeira escrita, "tocar num assunto abre os vídeos dele dentro da linha" passou sobre o código velho: com a faixa de pílulas, tocar num assunto também fazia aparecer a linha do vídeo — num bloco separado, abaixo. Procurar a linha em qualquer lugar da página passava nos dois desenhos. A asserção foi reescrita para exigir que o vídeo seja descendente de ajuda-assunto-bloco-<id>, e só então ficou vermelha. Está registrado porque é exatamente o teste que R1 existe para pegar, e ele quase entrou verde.

Verde — depois de /devremote-rebuild web (bundle index-10iydR45.js), a suíte inteira da Ajuda: 38 de 38, incluindo os 4 acima e os que protegem o desktop (ajuda-barra-assuntos) e a largura intermediária (ajuda-submenu-assuntos em 900px).

Verde de novo, depois das correções da rodada 1 — segundo rebuild, e agora com as três suítes que medem o colateral do ícone na barra do topo:

ajuda.spec.ts + layout-mobile.spec.ts + mobile-shell.spec.ts + page-header.spec.ts
58 passed (2.0m)

Entre elas, as que o revisor pediu por nome: layout-mobile.spec.ts:66 (a barra do topo mede 64px e o ícone vem em contêiner de 40px), mobile-shell.spec.ts:115 (só a aba "Mais" abre a gaveta — a RN8 da spec 0011, que o atalho não atropela) e page-header.spec.ts:170 (todo detalhe mostra o título e respeita o teto de ações, com o piso de 40px em 375px).

E a execução final, depois das correções da rodada 2 — terceiro rebuild, com o agencies.spec.ts junto porque o teste novo do CA12 caminha pela lista de imobiliárias:

ajuda + agencies + layout-mobile + mobile-shell + page-header
63 passed, 1 skipped, 0 failed (2.2m)

O skipped é o agencies.spec.ts:93 ("deve cadastrar todas as imobiliárias da seed"), um test.skip que já estava no repositório e nada tem a ver com esta branch.

E a execução final de verdade, depois de o ícone da barra do topo ser retirado a pedido do usuário e de a Ajuda sair da rolagem da gaveta — quinto rebuild:

ajuda + mobile-shell + layout-mobile + page-header
56 passed, 0 failed (2.0m)

Numa execução intermediária, um vermelho que não é desta branch. O ajuda.spec.ts:160 ("versão desconhecida … é ignorada em silêncio") falhou uma vez. A evidência barata: o teste não aparece em git diff main -- tests/e2e/ajuda.spec.ts; o diff não toca a página do tutorial nem a escolha de gravação por ?v=; ele passou nas três execuções isoladas seguintes e nas execuções seguintes da suíte. É o estouro de tempo intermitente que o apps/e2e/CLAUDE.md já descreve para o dev-remote — não regressão daqui. Não é a mesma coisa que "já estava quebrado", e não se afirma isso.

A suíte e2e completa não foi executada — só estas quatro.

A guarda do /admin não é prova, e é de propósito. O teste "o ícone da Ajuda não aparece no /admin" passa nos dois lados — antes e depois. Ele não é evidência da correção: é o que impede alguém de mover o botão para dentro da casca e levar a Ajuda do tenant para o painel do SaaS. Os testes que morrem sem a correção são os quatro acima.

As demais suítes: pytest da API 698 verdes, 0 vermelhos (nenhum .py foi tocado — é confirmação de que nada quebrou, não prova da correção). tsc do web limpo. make preflight limpo. ruff não foi executado porque o diff não tem Python.

A medição da tela, que é o que um fix visual deve mostrar (Playwright contra o dev-remote reconstruído, os mesmos números da seção 2):

antes depois
índice em 375px faixa: scrollWidth 433 × clientWidth 375 lista: scrollWidth 359 × clientWidth 359
2º assunto cortado na borda borda direita em 367px de 375 — inteiro
altura usada pelo conteúdo 168px de 812 206px de 812, com os dois assuntos na tela
ícone de Ajuda na barra do topo ausente presente
900px pílulas + cartões igual (scrollWidth == clientWidth == 668)
desktop 1440px barra à direita igual, e sem ícone na barra do topo

A rodada 2, e o que se fez com os quatro Minor

Três foram corrigidos; o quarto é instrução para a hora de commitar.

  1. O navegador vertical podia vazar para a faixa de 600–1199px sem ninguém notar. O bloco de 900px afirmava ajuda-barra-assuntos ausente, mas não o ajuda-lista-assuntos — a mesma lacuna que a rodada 1 fechou na página do tutorial, e justo na faixa em que a seção 10 registra que a causa continua viva de propósito. Ganhou a asserção gêmea.
  2. O CA12 estava medido no caso fácil. A medição rodava em /credit-analyses, uma lista, que tem no máximo uma ação no topo; quem espreme a barra é o detalhe, com três. Ganhou teste próprio em 375px. E ele nasceu instável: pegava a primeira imobiliária da lista, e medindo o dev-remote descobriu-se que das quatro primeiras uma tem três ações e três não têm nenhuma — então .first() era cara-ou-coroa, e o teste passou isolado e falhou na suíte quando o agencies.spec.ts mudou quem vem primeiro. Foi reescrito para procurar uma linha com 2+ ações entre as oito primeiras e reclamar se não achar nenhuma, em vez de aprovar por engano. Medido com três ações: título de 121px contra o piso de 40.
  3. O ícone era o único dos três caminhos para a Ajuda sem estado ativo. O rodapé da sider e a linha da gaveta recebem isNavItemActive; o atalho, não — em /ajuda ele ficava apagado, convidando a navegar para onde a pessoa já está. Passou a acender (primary.main, ícone em fill pela RN3 da spec 0011, aria-current="page"), com asserção no teste.
  4. Commit separado para a 6.2 — o corte continua limpo por arquivo, e está anotado na seção 9. Fica para a hora de commitar, que é pedido do usuário.

E um achado da rodada 1 que se aceitou sem corrigir, porque o revisor tinha razão sobre o texto e não sobre o teste: no teste do índice, scrollWidth === clientWidth não é a asserção que mata o desenho antigo — sobre um flex-direction: column; width: 100% ela é quase tautológica. Quem mata é a existência do ajuda-lista-assuntos e a borda do segundo assunto dentro da tela. A asserção fica porque guarda outra coisa daqui para a frente (um filho estourando a caixa); o que saiu foi a pretensão do comentário.

Defeitos aceitos: nenhum em aberto.

12. Registro

O que era. Em 375px, o índice da Ajuda era uma faixa horizontal de pílulas (NavegadorDeAssuntos.tsx, variante submenu) e, abaixo dela, um bloco separado com os títulos do assunto marcado (index.tsx). A faixa media 433px de conteúdo numa caixa de 375px.

O que virou. Abaixo de 600px a faixa saiu e entrou a variante lista: a mesma sanfona do desktop, em largura cheia e sem moldura, com os vídeos dentro do assunto aberto. O bloco de conteúdo separado deixou de existir no celular — índice e conteúdo passaram a ser a mesma coisa, que é o que faz o catálogo inteiro caber na tela. Entre 600 e 1199px e no desktop, nada mudou.

E entrou junto, a pedido do usuário: a Ajuda saiu da região que rola na gaveta do celular e virou faixa fixa, entre o miolo e a linha de Novidades — o mesmo tratamento que a de versões já tinha. Está isolada na seção 6.3 com CAs próprios, e o plano prevê commit separado.

E saiu, também a pedido dele: o ícone de Ajuda na barra do topo do celular, construído antes de a 6.3 ser encontrada. A barra do topo voltou ao que era; a seção 6.2 guarda a história.

O que divergiu do plano

  • O pedido "fixar a Ajuda no menu igual às versões" foi entendido errado, e custou três rodadas. Virou um ícone na barra do topo; o que ele dizia, literalmente, era que a linha de Novidades não rola e a da Ajuda rolava. A diferença estava na estrutura da gaveta — três regiões, e as duas linhas em regiões diferentes —, e teria aparecido lendo o mobile-shell antes de oferecer opções. O ícone foi construído, revisado em duas rodadas de portão e depois retirado inteiro. A lição não é "perguntar mais": é que as três opções oferecidas descreviam lugares, e nenhuma perguntava o que está errado hoje.
  • O desktop nunca ganhou nada, e isso também foi descoberto no caminho. Lá a Ajuda já estava em toda tela — ancorada no rodapé da sider, fora da rolagem, exatamente o que a 6.3 passou a fazer no celular. A assimetria era só do celular.
  • Cinco rebuilds do dev-remote, não um. Cada rodada que mexeu em código pediu outro, porque a imagem do web embute o bundle e testar sem reconstruir mediria a versão anterior. Dois deles foram do ícone que acabou saindo.
  • Um teste nasceu falso e outro nasceu instável, e os dois estão contados na seção 11. O falso passava sobre o código velho (procurava a linha do vídeo em qualquer lugar da página, e no desenho antigo ela também aparecia — num bloco separado). O instável dependia de qual imobiliária vinha primeiro na lista. Os dois foram reescritos, não afrouxados.

O que fica em aberto, e é de propósito

A causa raiz continua viva entre 600 e 1199px: ali a faixa também mostra um assunto por vez, e voltará a cortar quando o catálogo crescer a ponto de não caber em 900px. É decisão do usuário registrada na seção 10, não esquecimento — e o teste daquele viewport foi mantido de propósito, agora com a asserção que impede a sanfona de vazar para lá.