Mantendo o chat ativo quando o celular entra em suspensão
O agente do Mythex pode trabalhar por minutos em um único pedido, e você pode muito bem estar acompanhando pelo celular, que bloqueia, troca de app e muda de rede enquanto trabalha. Dois bugs faziam isso dar errado. Em um, o chat tratava todo sinal de “a página está visível de novo” como prova de que a conexão tinha morrido, e ficava em loop até travar em “Conectando…”. No outro, um celular que dormiu durante a primeira resposta de um chat novo voltava com apenas os primeiros pedaços dela, até um recarregamento manual. Os dois estão corrigidos.
Como o chat acompanha um turno em execução
Enquanto o agente trabalha, o chat mantém aberto um stream dos nossos servidores: cada passo (um pensamento, uma chamada de ferramenta, um trecho da resposta) chega como um evento. Se o stream cair, o chat reconecta e o servidor reenvia os eventos recentes de um buffer que guarda os últimos 120 segundos. Quando o turno termina, o chat também pede ao servidor o histórico salvo e reconcilia o que mostrava com o que foi armazenado. Essa última etapa pega qualquer coisa que o stream tenha perdido.
Para distinguir uma conexão viva de uma morta quando nada está acontecendo, o servidor envia um pequeno “ping” a cada 15 segundos.
Bug 1: acordar matava um stream saudável
Os navegadores avisam a página quando ela volta a ficar visível: depois de trocar de aba, depois que a tela desbloqueia. Usávamos isso como sinal para verificar a conexão e, na prática, para abortar o stream e reconectar, presumindo que ele tivesse morrido enquanto a página estava oculta.
Em celulares e navegadores embutidos, esses eventos disparam com muito mais frequência do que os streams morrem: a cada troca de aba, a cada desbloqueio e, em alguns navegadores embutidos, a cada poucos segundos. Cada um abortava um stream perfeitamente saudável. O chat reconectava, recebia o reenvio, era abortado de novo e acabava parado em “Conectando…”. Depois, dizia que o turno tinha terminado enquanto o agente ainda trabalhava.
A correção: julgar o stream pelo fato de ele estar transmitindo
O chat agora registra quando o stream de cada projeto recebeu qualquer coisa pela última vez, tanto eventos quanto os pings de 15 segundos. Um sinal de “visível de novo” só aborta um stream que ficou em silêncio por 25 segundos: mais do que o intervalo do ping somado a uma pequena demora da rede, então um stream vivo nunca se enquadra. Testes verificam que um ping conta como sinal de vida e que um stream que acabou de pingar não é tratado como morto.
A lição que tiramos: a visibilidade de uma página diz algo sobre a página, não sobre a conexão de rede. Pergunte à conexão.
Bug 2: metade de uma resposta depois que o celular acordou
Esse conseguimos reproduzir sempre no simulador do iOS:
- Inicie um chat novo e envie um pedido que demore.
- Deixe o celular dormir durante a criação.
- Volte depois que o turno terminou e o buffer de reenvio de dois minutos já expirou.
O chat mostrava os primeiros blocos da resposta, marcados como concluídos, e continuava assim até você atualizar a página.
Por quê
Um chat novo não tem uma thread (o histórico salvo da conversa) até a primeira mensagem criá-la. Isso acontece dentro da função de envio, depois que o envio já tinha capturado a etapa de reconciliação, que ainda achava que não havia thread. Assim, ao fim do primeiro turno de um chat novo, a reconciliação não via thread e simplesmente retornava sem pedir nada.
Normalmente ninguém percebia, porque o stream já tinha entregue o turno inteiro. Mas um celular que dormiu durante o turno e voltou depois que o buffer de reenvio de 120 segundos expirou não conseguia recuperar os eventos perdidos pelo stream, e a reconciliação que deveria preencher a lacuna nunca tinha sido executada.
A correção
- O envio informa à reconciliação a qual thread o turno pertence, então o primeiro turno de um chat novo é reconciliado como qualquer outro. Se você tiver aberto outro chat quando a resposta chegar, o resultado é descartado em vez de aparecer no lugar errado.
- Uma reconciliação que falhou é tentada de novo. Um celular que acorda com uma conexão ruim pode descobrir que o turno terminou e, em seguida, não conseguir buscá-lo. Isso agora fica registrado, e o chat pergunta de novo quando a aba volta a ser exibida ou a rede retorna.
- O “Trabalhou por” é honesto. Cada resposta mostra quanto tempo o agente trabalhou. Se a aba nunca recebeu a mensagem de fim de turno do servidor, ela preenchia isso com o próprio relógio, que contava desde o Enviar até o celular acordar, incluindo o tempo ausente. Esse valor agora é marcado como estimativa e substituído pela duração salva assim que o histórico chega.
O que tiramos disso
- Teste no dispositivo que as pessoas realmente usam, do jeito que elas usam. Os dois bugs viviam na distância entre “um stream que funciona” e “um celular no bolso”.
- Um fallback que nunca roda não é um fallback. A etapa de reconciliação existia justamente para pegar esse caso, e silenciosamente o pulava.
- Tente de novo também a recuperação. O momento em que um celular acorda costuma ser quando a conexão está pior.
- Não mostre um palpite como fato. Uma duração medida pelo relógio errado é pior do que nenhuma; rotule-a ou substitua-a.
Esse trabalho também organizou algumas coisas que você verá enquanto um turno roda: as etapas de shell são rotuladas pelo comando que faz o trabalho, e não por cd; as capturas de tela no chat mantêm o canto superior esquerdo da página e cabem na coluna; e um projeto novo recebe seu nome gerado sem precisar atualizar a página.
Se você está criando um app para celulares, nossos guias sobre como criar um app web compatível com celulares e apps nativos x web progressivos cobrem o básico. Mais sobre o próprio chat está na documentação do chat.