Agregado3
Módulos opcionais passam a poder declarar o que a anonimização de LGPD apaga neles
Um módulo opcional passa a ter como fazer a exclusão de dados de LGPD alcançar as tabelas dele, sem que a instalação precise lembrar de nada: o módulo declara uma vez, na própria migration, quais colunas guardam texto livre sobre a pessoa e como a linha se liga a ele, e a anonimização passa a redigir essas colunas junto com as do resto do sistema. Hoje nenhum módulo declara seção (honorários não guarda texto livre sobre a pessoa), então nada muda na sua instalação até um módulo declarar. Quem não instalou o módulo não muda de comportamento — a seção declarada para uma tabela que não existe é pulada na hora, sem erro, e a exclusão do contato segue funcionando igualzinho. Antes, o caminho seguro seria alguém reescrever a cascata inteira de anonimização a cada módulo novo; quem esquecesse entregaria sucesso com a pessoa ainda legível, que é exatamente o que a LGPD não permite.
A declaração errada (coluna que não existe) agora falha alto, dizendo qual módulo e qual tabela, em vez de redigir pela metade e devolver sucesso — porque entregar um pedido de exclusão como cumprido com dado legível é pior do que ele falhar e ser repetido. O registro que guarda essas seções é fechado: só quem aplica o schema escreve nele — nem a chave de serviço do app —, e o gatilho não roda em papel de cliente.
Também entra a prova de que as funções de um módulo existem mesmo sem as tabelas dele — que é como toda instalação vive, já que as tabelas só nascem quando alguém instala o módulo: a cadeia inteira é recriada com a validação de corpo ligada e as tabelas ausentes, e tem de compilar. Não exige ação de quem já está rodando: o update.sh cria a tabela do registro (vazia) e o gatilho, sem nenhuma mudança de tela.
Contribuição de @webtecnica (#1901).
A tela de atualização diz onde está o detalhe da disputa de banco
Quando a atualização pelo botão da tela encontra o banco em uso por outro processo e precisa de mais de uma passada, o resumo dessa disputa já aparecia no fim da atualização. Faltava dizer onde procurar o detalhe.
Agora, logo abaixo do resumo, a tela aponta o arquivo .update.log, na pasta do projeto no servidor, onde fica o que cada passada não aplicou. A linha aparece tanto quando a atualização termina bem quanto quando ela volta para a versão anterior. Se a rodada não teve disputa, ou se ela não foi medida, a linha não aparece. Nada muda para quem atualiza pela linha de comando. Não exige ação.
Contribuição de @webtecnica (#1898, issue #1040).
Relatório por etiqueta — volume, espera e desfecho de cada assunto no período
Quem opera agora pode perguntar à API qual assunto ocupou a operação em um período e quanto tempo o cliente esperou. GET /api/v1/reports/tags devolve, para cada etiqueta em uso, quantos atendimentos começaram no período, quantos ainda estão abertos e quantos foram encerrados, a espera média pela nossa resposta e a fatia de cada etiqueta sobre o total. A lista de etiquetas vem das que existem de fato nas conversas: uma etiqueta sem atendimento no período aparece com zero em vez de sumir, e um período sem dado nenhum diz isso na resposta em vez de devolver uma tabela de zeros. O pedido aceita de, ate (datas válidas, até 90 dias) e tz, porque a janela é contada no fuso de quem lê. Quando a janela tem mais conversas do que a leitura alcança, a resposta avisa que está cortada. É só leitura, sem migration e ainda sem tela: a tela vem depois. Contribuição de @webtecnica (#1888).
Cambiado1
A exclusão de uma conexão passa a ter prova de que o aviso dela sai junto
O fecho do aviso quando a conexão é removida já existia na rota, mas a prova na rota só cobria o ramo que ARQUIVA. O ramo que EXCLUI de vez — canal sem histórico, o .delete() — seguia sem teste nenhum com aviso aberto, que é exatamente o caso descrito na issue: o operador apaga a conexão e o crítico continuava na Central. Agora há um caso de rota para esse ramo, afirmando que nenhum aviso daquela conexão sobra, que o aviso de outra conexão segue aberto e que a auditoria diz avisos_fechados: resolvido. Nada muda no comportamento de quem opera: só a cobertura.
Contribuição de @webtecnica (#1903, issue #1023).
Corregido2
A busca de contatos entende como a gente digita — e para de devolver a lista inteira
Quem procura um contato passa a achar mesmo digitando do jeito que se digita na pressa: "Paulo Lima" com espaço duplo, "Paulo Jr" com as palavras separadas e "Silva, Maria" com ou sem vírgula encontram o cadastro, onde antes davam zero para gente que existe. Digitar uma letra só deixa de devolver a lista inteira dos contatos — lista inteira sob busca não é resposta, é ruído que parece resposta.
A busca de contatos passou a seguir a mesma régua da caixa de entrada: todo separador (espaço, vírgula, ponto e vírgula) vira o curinga da busca e o termo só vai ao banco depois de ter os dois caracteres mínimos. A régua é uma só e continua morando em lib/inbox/termo-de-busca.ts, então a caixa de entrada e os contatos andam juntos a partir de agora — e se a regra mudar, muda para os dois ao mesmo tempo. A busca por telefone, CPF e e-mail continua exatamente como estava, e a caixa (maiúscula/minúscula) continua sendo do banco.
A busca continua sem diferenciar acento: "Joao" ainda não encontra "João". Isso é outra fatia, porque exige coluna nova no banco e preenchimento dos cadastros existentes. Não muda esquema nenhum. Não exige ação.
Contribuição de @webtecnica (#1892, issue #1835).
Dar merge na main deixa de ser acusado como autoria de quem fez o merge
Mergear a main na sua branch voltou a passar sem válvula nenhuma — e sem afrouxar nada. Quem atualizava a branch com o que a main publicou (um invariante reescrito, uma migration nova) era tratado como quem escreveu aquilo: a catraca de invariantes e a guarda de plan/features.json liam o índice do merge inteiro e não tinham como separar o que veio da main do que a branch introduziu. A causa era do git, não da regra: ele chama o pre-merge-commit ANTES de escrever o MERGE_HEAD, então as referências de procedência estavam vazias no instante exato em que a pergunta é feita — e a guarda, no lado seguro, falhava fechado sobre o merge todo. Era o lado seguro errado: ela não estava defendendo a regra, estava punindo o merge.
A regra continua idêntica. O que mudou é como o alcance é calculado: o git entrega o outro lado, naquele instante, em GITHEAD_<sha>=<ref>, e é por ali que a guarda agora enxerga de onde veio o que o commit tem. Com o outro lado em mãos, as condições de procedência valem igual no caminho limpo e no caminho conflituoso — o que a main já tinha continua não sendo autoria de quem mergeia, o invariante da main modificado pela branch continua barrado, e a migration com sequência já usada continua barrada. Nenhuma barreira foi removida, nenhum escape foi retirado: o que some é só a acusação sobre o merge.
Como esse sinal é uma variável de ambiente, quem roda o commit pode forjá-lo, e o mesmo valia para um MERGE_HEAD escrito à mão. Por isso a guarda de plan/features.json ganhou as duas condições que a catraca de invariantes já tinha: o conteúdo que entra tem de ser o que a main tem agora, e a branch não pode ter tocado o arquivo. Antes, um commit comum com o sinal apontando para uma versão antiga da main conseguia apagar features do plano.
Não exige ação de ninguém.
Contribuição de @webtecnica (#1900).