Relatório de engenharia · Dudata Hub
O PR "edit Social Listening details from Entregas" nasceu com CI verde e ficou 5 dias sem review. Três waves de review agressivo acharam 2 problemas altos reais que o CI escondia, derrubaram 2 falsos positivos por prova executada, e o código foi mergeado e deployado em produção. Este relatório fecha o balanço e propõe o que vira regra.
O contexto em 30 segundos
A Dudata entrega relatórios de Social Listening (análise de menções e conversas em redes sociais) para os clientes. O Hub é o sistema interno que organiza essas entregas. Toda entrega tem três dados de cabeçalho: tÃtulo, briefing e prazo, a data combinada com o cliente.
Até este PR, esses campos ficavam travados depois da criação. Um prazo digitado errado, ou renegociado com o cliente, não tinha como ser corrigido pelo produto, e a data errada seguia na tela para todo mundo que organiza o trabalho por ela. O PR #739 cria essa edição no Backoffice, só para entregas de Social Listening ainda vivas (as encerradas ficam somente leitura), e grava cada edição na trilha de auditoria.
O código veio do Cursor em 5 commits (2026-08-20): 15 arquivos, +1019/−2. Entregava a tela de Entregas no Backoffice, a mutation deliveries.updateDetails e o predicado canEditDeliveryDetails em shared, com testes. A estrutura estava certa: autorização server-side pelos helpers de authz.ts, transição validada por assertTransition, sem máquina de estados paralela, copy e idioma conforme a convenção. Os ADRs 0008, 0010, 0011 e 0020 foram respeitados. Isso não é pouco: o guardrail de arquitetura funcionou.
Os problemas eram todos de segunda ordem. Nenhum aparecia no CI, e é exatamente por isso que importam.
| Severidade | Problema |
|---|---|
| Alto | Trilha de auditoria incompleta. A mutation não gravava activityLogs. Toda mutation vizinha grava. |
| Alto | Duplo submit possÃvel. void onSubmit(values) no handleSubmit quebrava o isSubmitting do dialog. Bug real de UX que nenhum teste pegou. |
| Médio | Duplicação em três frentes: helpers de data copiados pela 3ª vez, hook de mutation paralelo ao canônico useDeliveryActions, fixtures de teste copiadas em 4 arquivos (~60 linhas cada). |
| Médio | Testes com buracos: sem caso de entrega inexistente, sem dueIso com whitespace, tÃtulos em português (contra a convenção), asserções frouxas (length > 0) e uma tautologia. |
| Médio | Branch 20 commits atrás da main, com 1 conflito real acumulado (a main tinha refatorado os handlers para o padrão *Impl). |
Wave 1: 5 reviewers paralelos, um por dimensão (backend, web, testes, arquitetura, integração). Wave 2: 3 reviewers sobre o estado corrigido e mergeado com a main. Wave 3: veredito de aprovação. Os fixes rodaram em pipeline, sem barreira, com a regra de 1 dono por arquivo por vez. Escopo novo foi anexado a agente em voo por mensagem, sem matar e recriar.
Achados por wave e severidade
A contagem cai a zero em três rodadas. É o critério de parada.
Wave 2 teve ainda apontamentos cosméticos, fora da contagem. Passe o cursor nos segmentos para o detalhe.
dueIso ?? undefined impede limpar o prazo"Era um dos 2 bloqueantes da wave 1. Um teste convex-test executado na hora provou o contrário: o Convex 1.42 remove o campo quando recebe undefined. O achado caiu. Zero mudança de código.
O app estava certo. O fill da ferramenta não dispara o onChange sintético do React em <textarea> (em <input> funciona). Disparar new Event('input') à mão persistiu o valor. Zero mudança de código.
5 commits, CI verde desde o primeiro push. A branch ficou parada 5 dias e acumulou 20 commits de distância da main.
5 reviewers paralelos, 19 achados, 1 bloqueante derrubado por prova. Correções: fixtures compartilhadas (9aa132a), logActivity e trim de dueIso (421068b), refactor do web para os hooks canônicos (39bcb65).
Conflito único em deliveries.ts, resolvido no padrão *Impl (c74e7a5). A wave 2 achou o bug do duplo submit, corrigido em 8f57b41; testes endurecidos em 9b9a525.
O card do quadro da fila passou a mostrar o prazo em vez da data de abertura (8ff3577), com review dedicada aprovada sem findings. A mudança de seed quebrou um teste consumidor no CI; corrigido em fc3cf25.
Wave 3: zero gaps. E2E em navegador real no ambiente local: 7 de 7 eixos aprovados, incluindo limpar prazo, timeline de auditoria com 3 registros e o anti duplo-submit.
Merge na main como 572f6fa com aprovação explÃcita do dono. Release build 32883952669, promoção via deploy.yml no run 32884452936. Produção ok.
| Item | Detalhe |
|---|---|
| Tool MCP | Expor updateDetails como tool MCP e atualizar docs/api/mcp-deliveries.md. Hoje a UI edita e o agente não. |
| Mobile | O card da fila no mobile tem o mesmo viés corrigido no web: mostra data de abertura em vez do prazo (fila-vm.ts:114). |
| DueDatePicker | O padrão Calendar+Popover está copiado em 3 features. Extrair componente compartilhado. |
| Escape no dialog | Escape fecha o dialog junto com o popover de data. Comportamento Radix padrão, UX menor. |
Cada proposta abaixo sai de um fato deste PR, não de princÃpio abstrato. O custo do experimento foi real: 3 waves mais o diagnóstico de 2 falsos positivos. O retorno também: 2 problemas altos reais que iam para produção com CI verde, e um deploy que saiu no mesmo dia.
Wave 1 multi-dimensão com reviewers paralelos, fixes em pipeline com 1 dono por arquivo, wave final como critério de parada (zero gaps). Não é para todo PR: é para PR que toca mutation de domÃnio ou fluxo crÃtico.
Evidência: 19 → 4 → 0 achados; os 2 altos reais eram invisÃveis ao CI.
Reviewer que afirma comportamento de runtime entrega o teste ou o comando que prova. Sem prova, o achado não bloqueia. A regra vale nos dois sentidos: também protege código certo de correção errada.
Evidência: o falso bloqueante do dueIso e o falso positivo do E2E, ambos derrubados em minutos de prova.
Merge da main cedo e sempre. O conflito do padrão *Impl existiu porque a branch ficou 20 commits para trás; com sync no dia seguinte ele não existiria.
Evidência: 5 dias parada, 1 conflito real, resolução manual no dia do merge.
Mudança de seed é mudança de contrato para todo teste que lê o seed. Rodar localmente os testes consumidores do seed vira passo obrigatório do checklist de push.
Evidência: a única quebra de CI de todo o processo veio daà (corrigida em fc3cf25).
TÃtulo de teste em inglês, asserção frouxa, fixture duplicada: tudo isso virou achado de review humano quando devia ser check automático. Cada convenção que der para codificar sai do custo da wave e entra no CI.
Evidência: parte dos 6 médios da wave 1 era convenção pura, detectável por script.
Teste unitário não pegou o duplo submit; o navegador pegou. E2E do fluxo crÃtico entra antes do merge, com ferramenta que dispara onChange sintético de verdade em <textarea>, senão o próprio teste gera falso positivo.
Evidência: o bug do isSubmitting e o falso positivo do fill, no mesmo dia.