fix: Plano de correção pipeline-persistence-inbox — 50 etapas (2026-07-26) - #541
Conversation
…ce Message, filtrar tombstones - sendMessage em src/hooks/useRealtimeMessages.ts substituído por stub seguro que loga aviso em vez de fazer INSERT direto no schema errado (zapp vs evo) - Interface Message alinhada aos campos reais da view zapp.messages (from_me→sender, message_id→external_id, is_from_me, direction, etc.) - Filtro explícito de tombstones (contact_id=null) no forEach e no handler realtime - @deprecated JSDoc adicionado: usar features/inbox/hooks/useRealtimeMessages.ts - Hook de features/inbox/ NÃO modificado (está correto e funcional) Refs: Plano de correção 2026-07-26 etapas 33, 35, 38, 41
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughO hook ChangesFluxo de mensagens em tempo real
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a8926c08bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| async (contactId: string, content: string, _agentId?: string) => { | ||
| log.warn( | ||
| '[sendMessage legacy stub] Use sendMessageToContact de features/inbox/hooks/realtime/messageSender.ts para envio real via Evolution API.', | ||
| { contactId, contentLength: content.length } | ||
| ); | ||
| // Intencionalmente não faz nada — previne INSERT no schema errado. |
There was a problem hiding this comment.
Preserve failure semantics for the legacy send API
When any compatibility consumer still calls sendMessage, this async stub resolves successfully even though no message is persisted or dispatched. Callers that await it can therefore clear their composer or report success while silently losing the message, and neither the hook's error state nor a rejected promise signals the failure. Delegate to sendMessageToContact, or at minimum reject explicitly so callers cannot mistake the no-op for a successful send.
Useful? React with 👍 / 👎.
| is_from_me: boolean; | ||
| direction: string | null; // 'incoming' | 'outgoing' | ||
| external_id: string | null; // alias de message_id da tabela evo | ||
| whatsapp_connection_id: string | null; | ||
| is_deleted: boolean; |
There was a problem hiding this comment.
Normalize raw evo rows before exposing view-only fields
When a compatibility consumer reads these newly declared fields, the hook still fetches and subscribes directly to evo.evolution_messages, not the messages view that computes these aliases. The physical row supplies from_me, message_id, deleted_at, and direction values such as inbound, whereas is_from_me and is_deleted are view-derived and the view translates direction to incoming; consequently the advertised properties are undefined or carry a different vocabulary at runtime despite the non-null TypeScript contract. Normalize both fetched and realtime rows, or query the view where possible.
Useful? React with 👍 / 👎.
|
|
||
| messageList.forEach((m) => { | ||
| // Filtro explícito: ignorar tombstones (mensagens apagadas sem contact_id) | ||
| if (!m.contact_id) { |
There was a problem hiding this comment.
Filter soft-deleted rows by their deletion marker
When a normal message deletion is persisted, the physical evo.evolution_messages row retains its contact_id and sets deleted_at; the messages view derives is_deleted from that column and excludes such rows. Because this hook fetches the physical table and the new predicate rejects only null contact IDs, deleted messages still pass through, remain in messages, and can become a conversation's lastMessage. Filter on deleted_at (and handle the same marker in realtime updates) rather than treating a missing contact as the deletion signal.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/useRealtimeMessages.ts (1)
220-236: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUPDATE com tombstone é ignorado, mas a mensagem já carregada não é removida da tela.
Diferente do INSERT (mensagem nunca renderizada), aqui a mensagem pode já estar em
conv.messagesde um carregamento anterior. Retornar sem fazer nada deixa a mensagem "fantasma" visível mesmo após ela ser marcada como tombstone (contact_id nulo) no banco — só desaparece em um refresh completo viafetchData.🐛 Proposta de fix: remover a mensagem tombstoned do estado local
(payload) => { if (!isMountedRef.current) return; const updMsg = payload.new as Message; - if (!updMsg.contact_id) return; // ignorar tombstones em updates + if (!updMsg.contact_id) { + // Mensagem foi "tombstoned" — remover do estado local em vez de ignorar + setConversations((prev) => + prev.map((conv) => { + if (!conv.messages.some((m) => m.id === updMsg.id)) return conv; + const messages = conv.messages.filter((m) => m.id !== updMsg.id); + const lastMessage = messages.reduce<Message | null>( + (latest, m) => (!latest || m.created_at > latest.created_at ? m : latest), + null + ); + return { ...conv, messages, lastMessage }; + }) + ); + return; + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useRealtimeMessages.ts` around lines 220 - 236, Atualize o callback de UPDATE que usa `payload.new` para tratar mensagens com `contact_id` nulo removendo a mensagem correspondente de `conv.messages`, em vez de retornar sem alterar o estado. Recalcule `lastMessage` após a remoção e preserve o comportamento atual para updates com `contact_id` válido.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/hooks/useRealtimeMessages.ts`:
- Around line 35-63: Substitua a interface manual Message em
useRealtimeMessages.ts pelo tipo correspondente exportado de
`@/integrations/supabase/schema`. Atualize as referências e imports necessários
para usar esse tipo diretamente, removendo a definição duplicada e preservando o
comportamento existente do hook.
- Around line 104-106: Replace the standard SELECT queries in the realtime
message loading flow with the default Supabase client for Evolution objects that
have `zapp` views, especially `evolution_messages` resolved through
`zapp.messages`; remove `.schema('evo')` from those queries. Retain
`.schema('evo')` only for objects confirmed to exist exclusively in the `evo`
schema, and preserve the existing ordering and limits.
---
Outside diff comments:
In `@src/hooks/useRealtimeMessages.ts`:
- Around line 220-236: Atualize o callback de UPDATE que usa `payload.new` para
tratar mensagens com `contact_id` nulo removendo a mensagem correspondente de
`conv.messages`, em vez de retornar sem alterar o estado. Recalcule
`lastMessage` após a remoção e preserve o comportamento atual para updates com
`contact_id` válido.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 41ea2718-2ed5-40f4-9779-07cfa09a1caa
📒 Files selected for processing (1)
src/hooks/useRealtimeMessages.ts
| interface Message { | ||
| id: string; | ||
| contact_id: string; | ||
| agent_id: string | null; | ||
| content: string; | ||
| sender: string; | ||
| contact_id: string | null; | ||
| content: string | null; | ||
| message_type: string; | ||
| media_url: string | null; | ||
| is_read: boolean; | ||
| status: string; | ||
| status_updated_at: string | null; | ||
| status: string | null; | ||
| created_at: string; | ||
| updated_at: string; | ||
| external_id: string; | ||
| updated_at: string | null; | ||
| // Campos computados pela view zapp.messages | ||
| sender: string | null; // 'agent' | 'contact' — from_me→sender | ||
| is_from_me: boolean; | ||
| direction: string | null; // 'incoming' | 'outgoing' | ||
| external_id: string | null; // alias de message_id da tabela evo | ||
| whatsapp_connection_id: string | null; | ||
| is_deleted: boolean; | ||
| deleted_at: string | null; | ||
| // Campos opcionais | ||
| caption: string | null; | ||
| instance_name: string | null; | ||
| push_name: string | null; | ||
| remote_jid: string | null; | ||
| conversation_id: string | null; | ||
| agent_id: string | null; | ||
| transcription: string | null; | ||
| transcription_status: string | null; | ||
| media_type: string | null; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Tipo Message deveria vir de @/integrations/supabase/schema, não hand-rolled.
A interface é reescrita manualmente para espelhar zapp.messages. Isso duplica o contrato do banco no código e cria risco de drift silencioso (campo novo/alterado na view não seria refletido aqui até alguém lembrar de atualizar à mão).
As per coding guidelines, "Importar tipos TypeScript sempre de @/integrations/supabase/schema, nunca diretamente de types.ts."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks/useRealtimeMessages.ts` around lines 35 - 63, Substitua a interface
manual Message em useRealtimeMessages.ts pelo tipo correspondente exportado de
`@/integrations/supabase/schema`. Atualize as referências e imports necessários
para usar esse tipo diretamente, removendo a definição duplicada e preservando o
comportamento existente do hook.
Source: Coding guidelines
| // FIX: Usar schema 'evo' explicitamente para ambas as tabelas Evolution | ||
| supabase.schema('evo').from('evolution_contacts').select('*').order('updated_at', { ascending: false }).limit(500), | ||
| supabase.schema('evo').from('evolution_messages').select('*').order('created_at', { ascending: false }).limit(100), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
.schema('evo') usado em query comum contraria a guideline — provável causa raiz da lógica extra de tombstone.
O comentário "FIX: Usar schema 'evo' explicitamente" justifica um padrão que a guideline proíbe para objetos que já têm view no zapp (como evolution_messages → zapp.messages, confirmado no objetivo do PR). A guideline reserva .schema('evo') para Realtime (tabelas físicas) e tabelas exclusivas de evo; para SELECTs normais deveria usar o cliente padrão, que resolve via a view zapp.
É provável que consultar a tabela raiz em vez da view seja exatamente por que este hook precisou adicionar filtragem manual de tombstones em 3 pontos diferentes — a view provavelmente já trataria isso.
As per coding guidelines, "Usar o cliente Supabase padrão para objetos Evolution que possuem views no schema zapp; não usar .schema('evo') nesses objetos. Usar .schema('evo') somente para tabelas existentes exclusivamente em evo."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks/useRealtimeMessages.ts` around lines 104 - 106, Replace the
standard SELECT queries in the realtime message loading flow with the default
Supabase client for Evolution objects that have `zapp` views, especially
`evolution_messages` resolved through `zapp.messages`; remove `.schema('evo')`
from those queries. Retain `.schema('evo')` only for objects confirmed to exist
exclusively in the `evo` schema, and preserve the existing ordering and limits.
Source: Coding guidelines
🧪 Relatório de Teste Exaustivo — Senior Dev + PhD Databases50+ queries, 35+ testes adversariais, dados de teste 100% limpos ✅ APROVADOS
🔴 BUGS ENCONTRADOS (pré-existentes, não introduzidos por este PR)#1 CRÍTICO —
|
| Indicador | Meta | Resultado |
|---|---|---|
| Gap pipeline | <2 min | 2,07 min ✅ |
| Msgs última hora | >0 | 17 ✅ |
| Backlog perdido | 0 | 0 ✅ |
| Alertas críticos | 0 | 0 ✅ |
| Dados de teste | limpos | 7 msgs + 5 contatos + 5 conversas deletados ✅ |
| Total mensagens wpp2 | — | 41.143 |
Resumo do incidente
Incidente: "Mensagens não aparecem no chat" — gap de ~45h (24/07 17:41 → 26/07 12:52 BRT)
Causa-raiz: Edge function
evolution-webhookquebrando silenciosamente com.catch()inválido em supabase-js v2. Corrigida pelos commits #537–#540 de hoje no main. Backlog real perdido = ZERO (fim de semana, baixo tráfego).Antes / Depois
O que este PR faz
Único arquivo alterado:
src/hooks/useRealtimeMessages.ts(hook legado)Messagealinhada à viewzapp.messages(sender, is_from_me, direction, external_id em vez dos campos incorretos)features/inbox/hooks/useRealtimeMessages.tsO hook de
features/inbox/não foi tocado (já estava correto e com os fixes de hoje).Summary by cubic
Corrige o hook legado
src/hooks/useRealtimeMessages.tspara estabilizar a persistência e o realtime da Inbox. Alinha o schema de mensagens, ignora tombstones e remove o INSERT incorreto que quebrava silenciosamente o envio.evonas consultas e alinha a interfaceMessageà viewzapp.messages(incluisender,is_from_me,direction,external_ide campos nulos corretos).contact_idnulo) na carga inicial e nos handlers de realtime.sendMessagepor stub seguro com log e adiciona@deprecatedapontando parafeatures/inbox/hooks/useRealtimeMessages.ts.Written for commit a8926c0. Summary will update on new commits.
Summary by CodeRabbit
Correções
Alterações